diff --git a/docs/changelog.md b/docs/changelog.md index 4d0ef44..e50a031 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,12 +16,16 @@ All notable changes to this project will be documented below. #### annotate.napari_label_classes -* v0.1dev: viewer = **annotate.napari_label_classes**(*img, classes, size=10, shape='square', importdata=False, show=True*) +* v0.1dev: viewer = **annotate.napari_label_classes**(*img, classes, size=10, importdata=False, show=True*) #### annotate.napari_open * v0.1dev: viewer = **annotate.napari_open**(*img, mode = 'native', show=True*) +#### annotate.napari_points_mask + +* v0.1dev: mask_dict = **annotate.napari_points_mask**(*img, viewer*) + #### annotate.napari_read_coor * v0.1dev: data = **annotate.napari_read_coor**(*coor, dataformat='yx'*) diff --git a/docs/img/documentation_images/napari_points_mask/background.png b/docs/img/documentation_images/napari_points_mask/background.png new file mode 100644 index 0000000..1b95a72 Binary files /dev/null and b/docs/img/documentation_images/napari_points_mask/background.png differ diff --git a/docs/img/documentation_images/napari_points_mask/chlorosis.png b/docs/img/documentation_images/napari_points_mask/chlorosis.png new file mode 100644 index 0000000..e3d010a Binary files /dev/null and b/docs/img/documentation_images/napari_points_mask/chlorosis.png differ diff --git a/docs/img/documentation_images/napari_points_mask/healthy.png b/docs/img/documentation_images/napari_points_mask/healthy.png new file mode 100644 index 0000000..7da9064 Binary files /dev/null and b/docs/img/documentation_images/napari_points_mask/healthy.png differ diff --git a/docs/img/documentation_images/napari_points_mask/rust.png b/docs/img/documentation_images/napari_points_mask/rust.png new file mode 100644 index 0000000..a411242 Binary files /dev/null and b/docs/img/documentation_images/napari_points_mask/rust.png differ diff --git a/docs/img/documentation_images/napari_points_mask/viewer_labeled.png b/docs/img/documentation_images/napari_points_mask/viewer_labeled.png new file mode 100644 index 0000000..e36e5a9 Binary files /dev/null and b/docs/img/documentation_images/napari_points_mask/viewer_labeled.png differ diff --git a/docs/napari_join_labels.md b/docs/napari_join_labels.md index b8cd44e..bd55342 100644 --- a/docs/napari_join_labels.md +++ b/docs/napari_join_labels.md @@ -2,17 +2,18 @@ This function joins classes with the same label. This function would be run after classes are labeled with napari_label_classes. -**plantcv.annotate.napari_join_labels*(*img, viewer*) +**plantcv.annotate.napari_join_labels**(*img, viewer*) **returns** relabeled mask, dictionary of masks for each class - **Parameters:** - img - image data (compatible with gray, RGB, and hyperspectral data. If data is hyperspecral it should be the array e.g. hyperspectral.array_data) - - viewer - viewer with labeled classes. If no points are selected for a class, + - viewer - viewer with labeled classes(likely created with [`napari_label_classes`](napari_label_classes.md)). + If no points are selected for a class, data without labels will default to this class when napari_join_labels is run. If all classes have points labeled, any clusters not labeled will default to the last class in the list if napari_join_labels is - run. + run. - **Context:** - This function would be run after labeling classes in Napari is complete. diff --git a/docs/napari_label_classes.md b/docs/napari_label_classes.md index 6fab410..a5d99a6 100644 --- a/docs/napari_label_classes.md +++ b/docs/napari_label_classes.md @@ -3,7 +3,8 @@ This function opens an image in Napari and then defines a set of Points layers with the user-defined labels called `classes`. A random `shape` of the annotation symbol is assigned to each of the `classes`. Image can be annotated as long as viewer is open. -**plantcv.annotate.napari_label_classes*(*img, classes, size=10, shape='square', importdata=False, show=True*) + +**plantcv.annotate.napari_label_classes**(*img, classes, size=10, importdata=False, show=True*) **returns** napari viewer object @@ -11,8 +12,6 @@ Image can be annotated as long as viewer is open. - img - image data (compatible with gray, RGB, and hyperspectral data. If data is hyperspecral it should be the array e.g. hyperspectral.array_data) - classes - list of classes to label. This option is not necessary if data is data is imported. - size - integer pixel size of label (also adjustable from the interactive Napari viewer) - - shape - shape of the annotation symbol. Can be 'o', 'arrow', 'clobber', 'cross', 'diamond', 'disc', 'hbar', 'ring', 'square' (default), 'star', 'tailed_arrow', - 'triangle_down', 'triangle_up', 'vbar', or 'x' (also adjustable from the interactive Napari viewer) - importdata - dictionary of data, data saved from napari_save_coor or data imported from napari_read_coor - show - if `show=True`, viewer is launched. `False` setting is useful for test purposes. diff --git a/docs/napari_points_mask.md b/docs/napari_points_mask.md new file mode 100644 index 0000000..d124f64 --- /dev/null +++ b/docs/napari_points_mask.md @@ -0,0 +1,60 @@ +## Make Mask of Napari Points + +This function is to generate a mask from Napari point information. +This application of this function could be to get information about image +at particular points (e.g. color or intensity information) + +**plantcv.annotate.napari_points_mask**(*img, viewer*) + +**returns** dictionary of masks (one for each class where the class label is the key to access) + +- **Parameters:** + - img - image data (compatible with gray, RGB, and hyperspectral data. If data is hyperspecral it should be the array e.g. hyperspectral.array_data) + - viewer = Napari Viewer with point classes labeled (likely created with [`napari_label_classes`](napari_label_classes.md)). The size of the points in the mask will be determined from the viewer parameters. + +- **Context:** + - This function can be used to generate a mask from Napari points in order to get information about point data. + +- **Example use:** + - An application of this function might be collection of color data for the [Naive Bayes module](https://plantcv.readthedocs.io/en/latest/tutorials/machine_learning_tutorial/). + + +```python +import plantcv.plantcv as pcv +import plantcv.annotate as pcvan +import napari + +# Create an instance of the Points class +img, path, name = pcv.readimage("./wheat.png") + +# Should open interactive napari viewer +viewer = pcvan.napari_label_classes(img=img, classes=['background','healthy', 'rust', 'chlorosis'], size=4) + +maskdict = pcvan.napari_points_mask(img, viewer) + +pcv.plot_image(maskdict['background']) +pcv.plot_image(maskdict['healthy']) +pcv.plot_image(maskdict['rust']) +pcv.plot_image(maskdict['chlorosis']) + +``` + +![Screenshot](img/documentation_images/napari_points_mask/viewer_labeled.png) + +***Background Mask*** + +![Screenshot](img/documentation_images/napari_points_mask/background.png) + +***Healthy Mask*** + +![Screenshot](img/documentation_images/napari_points_mask/healthy.png) + +***Rust Mask*** + +![Screenshot](img/documentation_images/napari_points_mask/rust.png) + +***Chlorosis Mask*** + +![Screenshot](img/documentation_images/napari_points_mask/chlorosis.png) + +**Source Code:** [Here](https://github.com/danforthcenter/plantcv-annotate/blob/main/plantcv/annotate/napari_points_mask.py) diff --git a/mkdocs.yml b/mkdocs.yml index fb27379..2be77e3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ nav: - Napari Join: napari_join_labels.md - Napari Label: napari_label_classes.md - Napari Open: napari_open.md + - Napari Points Mask: napari_points_mask.md - Napari Read Coor: napari_read_coor.md - Napari Save Coor: napari_save_coor.md - Points: Points.md diff --git a/plantcv/annotate/__init__.py b/plantcv/annotate/__init__.py index 1a4548d..204af28 100644 --- a/plantcv/annotate/__init__.py +++ b/plantcv/annotate/__init__.py @@ -7,6 +7,7 @@ from plantcv.annotate.napari_join_labels import napari_join_labels from plantcv.annotate.napari_save_coor import napari_save_coor from plantcv.annotate.napari_read_coor import napari_read_coor +from plantcv.annotate.napari_points_mask import napari_points_mask # Auto versioning __version__ = version("plantcv-annotate") @@ -19,5 +20,6 @@ "napari_label_classes", "napari_join_labels", "napari_save_coor", - "napari_read_coor" + "napari_read_coor", + "napari_points_mask" ] diff --git a/plantcv/annotate/napari_label_classes.py b/plantcv/annotate/napari_label_classes.py index 42b65c7..78e7f13 100755 --- a/plantcv/annotate/napari_label_classes.py +++ b/plantcv/annotate/napari_label_classes.py @@ -6,7 +6,7 @@ from plantcv.annotate import napari_classes -def napari_label_classes(img, classes=False, size=10, shape='square', +def napari_label_classes(img, classes=False, size=10, importdata=False, show=True): """ open img in napari and label classes @@ -22,7 +22,6 @@ def napari_label_classes(img, classes=False, size=10, shape='square', will default to the last class in the list when napari_join_labels is run. size = size of marker in pixels - shape = either 'square' or 'circle' importdata = dictionary of values in Napari format (y,x). Output of napari_read_coor show = if show is True the viewer is launched. This opetion is useful for @@ -35,7 +34,6 @@ def napari_label_classes(img, classes=False, size=10, shape='square', :param img: numpy.ndarray :param classes: list :param size: int - :param shape: str :param importdata: dict :param show: str @@ -83,7 +81,7 @@ def napari_label_classes(img, classes=False, size=10, shape='square', if classes is not False: for x in classes: if x not in keys: - viewer.add_points(np.array([]), name=x, symbol=shape, + viewer.add_points(np.array([]), name=x, symbol='square', edge_color=random.choice(color), face_color=random.choice(color), size=size) keys = napari_classes(viewer) @@ -95,7 +93,8 @@ def napari_label_classes(img, classes=False, size=10, shape='square', if key in keys: viewer.layers[key].add(importdata[key]) else: - viewer.add_points(importdata[key], name=key, symbol=shape, + viewer.add_points(importdata[key], name=key, + symbol='square', edge_color=random.choice(color), face_color=random.choice(color), size=size) diff --git a/plantcv/annotate/napari_open.py b/plantcv/annotate/napari_open.py index a229e50..9aff3e8 100755 --- a/plantcv/annotate/napari_open.py +++ b/plantcv/annotate/napari_open.py @@ -2,8 +2,8 @@ import cv2 import numpy as np -from skimage.color import label2rgb import napari +from skimage.color import label2rgb def napari_open(img, mode='native', show=True): diff --git a/plantcv/annotate/napari_points_mask.py b/plantcv/annotate/napari_points_mask.py new file mode 100755 index 0000000..d73eaa5 --- /dev/null +++ b/plantcv/annotate/napari_points_mask.py @@ -0,0 +1,55 @@ +# Make Masks of Labelled Napari Points + +import numpy as np +import os +import cv2 +from plantcv.annotate import napari_classes +from plantcv.plantcv import params +from plantcv.plantcv._debug import _debug + + +def napari_points_mask(img, viewer): + """ + draw points mask based on Napari viewer annotations + + Inputs: + img = img (grayimg, rgbimg, or hyperspectral image array data + e.g. hyperspectraldata.array_data). This is used to find the x,y size + of the image + viewer = Napari Viewer with classes labeled. The size of the masked points + will be from the viewer parameters + + Returns: + mask_dict = dictionary of masks; mask for each labelled class + + :param img: numpy.ndarray + :param viewer: Napari Viewer object + :param shape: str + :return mask_dict: dict of numpy.ndarray + + """ + # get shape of image + size = np.shape(img) + keys = napari_classes(viewer) + maskdict = {} + + for key in keys: + maskname = str(key) + mask = np.zeros((size[0], size[1])) + data = list(viewer.layers[key].data) + shapesize = viewer.layers[key]._current_size + shapesizehalf = int(shapesize/2) + + for y, x in data: + startpoint = (int(x-shapesizehalf), int(y-shapesizehalf)) + endpoint = (int(x+shapesizehalf-1), int(y+shapesizehalf-1)) + mask = cv2.rectangle(mask, startpoint, endpoint, (255), -1) + + maskdict[maskname] = mask + + _debug(visual=mask, filename=os.path.join(params.debug_outdir, + str(params.device) + + str(maskname) + + '_labeled_mask.png')) + + return maskdict diff --git a/tests/test_napari_classes.py b/tests/test_napari_classes.py index e71826d..8d37b94 100644 --- a/tests/test_napari_classes.py +++ b/tests/test_napari_classes.py @@ -9,9 +9,9 @@ def test_napari_classes(make_napari_viewer): img = np.zeros((100, 100)) coor = [(25, 25), (50, 50)] viewer.add_image(img) - viewer.add_points(np.array(coor), symbol="o", name="total", + viewer.add_points(np.array(coor), symbol="square", name="total", face_color="red", size=30) - viewer.add_points(np.array(coor), symbol="o", name="test", + viewer.add_points(np.array(coor), symbol="square", name="test", face_color="red", size=30) keys = napari_classes(viewer) diff --git a/tests/test_napari_join_labels.py b/tests/test_napari_join_labels.py index f3ae04b..61160bc 100644 --- a/tests/test_napari_join_labels.py +++ b/tests/test_napari_join_labels.py @@ -1,8 +1,8 @@ import numpy as np -from plantcv.annotate.napari_open import napari_open -from plantcv.annotate.napari_label_classes import napari_label_classes +from plantcv.annotate import napari_open +from plantcv.annotate import napari_label_classes from plantcv.plantcv import readimage -from plantcv.annotate.napari_join_labels import napari_join_labels +from plantcv.annotate import napari_join_labels from plantcv.plantcv import params @@ -23,10 +23,10 @@ def test_napari_join_allclass(test_data): img, _, _ = readimage(test_data.kmeans_seed_gray_img) viewer = napari_open(img, show=False) background = [(54, 143), (77, 246)] - viewer.add_points(np.array(background), symbol="o", name='background', + viewer.add_points(np.array(background), symbol="square", name='background', face_color="red", size=1) wing = [(275, 54)] - viewer.add_points(np.array(wing), symbol="o", name='wing', + viewer.add_points(np.array(wing), symbol="square", name='wing', face_color="red", size=1) seed = [(280, 218)] viewer.add_points(np.array(seed), symbol="o", name='seed', @@ -44,13 +44,13 @@ def test_napari_join_warn(test_data): img, _, _ = readimage(test_data.kmeans_seed_gray_img) viewer = napari_open(img, show=False) background = [(54, 143), (77, 246)] - viewer.add_points(np.array(background), symbol="o", name='background', + viewer.add_points(np.array(background), symbol="square", name='background', face_color="red", size=1) wing = [(275, 54)] - viewer.add_points(np.array(wing), symbol="o", name='wing', + viewer.add_points(np.array(wing), symbol="square", name='wing', face_color="red", size=1) seed = [(275, 54)] - viewer.add_points(np.array(seed), symbol="o", name='seed', + viewer.add_points(np.array(seed), symbol="square", name='seed', face_color="red", size=1) labeled, _ = napari_join_labels(img, viewer) @@ -68,13 +68,13 @@ def test_napari_join_print(test_data, tmpdir): img, _, _ = readimage(test_data.kmeans_seed_gray_img) viewer = napari_open(img, show=False) background = [(54, 143), (77, 246)] - viewer.add_points(np.array(background), symbol="o", name='background', + viewer.add_points(np.array(background), symbol="square", name='background', face_color="red", size=1) wing = [(280, 218)] - viewer.add_points(np.array(wing), symbol="o", name='wing', + viewer.add_points(np.array(wing), symbol="square", name='wing', face_color="red", size=1) seed = [(275, 54)] - viewer.add_points(np.array(seed), symbol="o", name='seed', + viewer.add_points(np.array(seed), symbol="square", name='seed', face_color="red", size=1) labeled, _ = napari_join_labels(img, viewer) diff --git a/tests/test_napari_label_classes.py b/tests/test_napari_label_classes.py index 4661b1b..d6eac8c 100644 --- a/tests/test_napari_label_classes.py +++ b/tests/test_napari_label_classes.py @@ -11,7 +11,7 @@ def test_napari_label_classes_gray(test_data): viewer = napari_label_classes(img, ['total'], size=5, importdata=data, show=False) coor = [(50, 25)] - viewer.add_points(np.array(coor), symbol="o", name='coor', + viewer.add_points(np.array(coor), symbol="square", name='coor', face_color="red", size=5) assert len(viewer.layers['total'].data) == 1 diff --git a/tests/test_napari_open.py b/tests/test_napari_open.py index c6d1376..33c72a4 100644 --- a/tests/test_napari_open.py +++ b/tests/test_napari_open.py @@ -22,7 +22,7 @@ def test_napari_open_gray(test_data): img, _, _ = readimage(test_data.kmeans_seed_gray_img) viewer = napari_open(img, show=False) coor = [(25, 25), (50, 50)] - viewer.add_points(np.array(coor), symbol="o", name="total", + viewer.add_points(np.array(coor), symbol="square", name="total", face_color="red", size=1) assert len(viewer.layers['total'].data) == 2 @@ -49,7 +49,7 @@ def test_napari_open_envi(test_data): img = img.array_data viewer = napari_open(img, show=False) coor = [(25, 25), (50, 50)] - viewer.add_points(np.array(coor), symbol="o", name="total", + viewer.add_points(np.array(coor), symbol="square", name="total", face_color="red", size=1) assert len(viewer.layers['total'].data) == 2 diff --git a/tests/test_napari_points_mask.py b/tests/test_napari_points_mask.py new file mode 100644 index 0000000..10a2834 --- /dev/null +++ b/tests/test_napari_points_mask.py @@ -0,0 +1,19 @@ +from plantcv.annotate import napari_points_mask +from plantcv.plantcv import readimage +from plantcv.annotate import napari_label_classes +import numpy as np + + +def test_napari_points_mask(test_data): + """Test for PlantCV.Annotate""" + # Read in test data + img, _, _ = readimage(test_data.small_rgb_img) + data = {'total': [(25, 25)], 'background': [(50, 50)]} + viewer = napari_label_classes(img, ['total'], size=50, importdata=data, + show=False) + maskdict = napari_points_mask(img, viewer) + + summask = int((np.sum(maskdict['total']))/255) + + assert summask == 2500 + viewer.close() diff --git a/tests/test_napari_save_coor.py b/tests/test_napari_save_coor.py index fafc359..069a409 100644 --- a/tests/test_napari_save_coor.py +++ b/tests/test_napari_save_coor.py @@ -12,7 +12,7 @@ def test_napari_save_coor(test_data, tmpdir): img, _, _ = readimage(test_data.kmeans_seed_gray_img) viewer = napari_label_classes(img, ['seed'], show=False) coor = [(25, 25)] - viewer.add_points(np.array(coor), symbol="o", name='background', + viewer.add_points(np.array(coor), symbol="square", name='background', face_color="red", size=1) filename = os.path.join(cache_dir, 'tempfile.txt')