-
Notifications
You must be signed in to change notification settings - Fork 283
adding sub_mask function to plantcv.plantcv #1864
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
base: v5.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| ## Sub Mask | ||
|
|
||
| Sample circular regions from a mask. | ||
|
|
||
| **plantcv.plantcv.sub_mask**(*img, mask, num_masks=1, radius=5*) | ||
|
|
||
| **returns** A `numpy.ndarray` labelled mask. | ||
|
|
||
| - **Parameters:** | ||
| - img - Grayscale or RGB image. | ||
| - mask - Binary mask of the image. | ||
| - num_masks - A number of circular regions to make masks of, defaults to 1. These spots cannot overlap, so specifying too many may trigger a warning that fewer masks could be placed than were specified. | ||
| - radius - Radius of the circular region(s) to select. These spots cannot overlap, so specifying too large of a radius may trigger a warning that fewer masks could be placed than were specified. | ||
|
|
||
|
|
||
| - **Context:** | ||
| - Used to downsample an image, particularly for spectral analysis. | ||
|
|
||
|
|
||
| - **Example use:** | ||
| - Below | ||
|
|
||
|
|
||
|
|
||
| ```python | ||
|
|
||
| from plantcv import plantcv as pcv | ||
|
|
||
| spot_masks = pcv.sub_mask(img, mask, num_masks=2, radius=5) | ||
|
|
||
| ``` | ||
|
|
||
| **Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/main/plantcv/plantcv/submask.py) | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,106 @@ | ||||||||||
| from plantcv.plantcv.roi.roi_methods import circle | ||||||||||
| from plantcv.plantcv.roi.roi2mask import roi2mask | ||||||||||
| from plantcv.plantcv.warn import warn | ||||||||||
| import numpy as np | ||||||||||
| import random | ||||||||||
| import cv2 | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def sub_mask(img, mask, num_masks=1, radius=5): | ||||||||||
| """ | ||||||||||
| Make circular sub-masks inside the mask of an object | ||||||||||
|
|
||||||||||
| Parameters | ||||||||||
| ---------- | ||||||||||
| img = numpy.ndarray, input image | ||||||||||
| mask = numpy.ndarray, binary mask of object | ||||||||||
| num_masks = int, number of circular sub-masks to make | ||||||||||
| radius = int, radius of circular mask to make. Defaults to 5. | ||||||||||
|
|
||||||||||
| Returns | ||||||||||
| ------- | ||||||||||
| labeled_mask = numpy.ndarray, labelled mask of circular masks each within complete mask. | ||||||||||
| """ | ||||||||||
|
Comment on lines
+10
to
+23
|
||||||||||
| # Create an empty mask | ||||||||||
| labeled_mask = np.zeros_like(mask) | ||||||||||
|
Comment on lines
+24
to
+25
|
||||||||||
| # Create an empty mask | |
| labeled_mask = np.zeros_like(mask) | |
| # Create an empty labeled mask with sufficient integer range for labels | |
| labeled_mask = np.zeros(mask.shape[:2], dtype=np.int32) |
Copilot
AI
Mar 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop condition recalculates np.unique(labeled_mask) on every iteration, which scans the full array repeatedly and can be expensive on large masks. Since you already track sample_num, you can drive the loop with while sample_num < num_masks: (or maintain a counter) to avoid repeated full-array uniques.
Copilot
AI
Mar 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_make_random_circle can raise at runtime in two common cases: (1) if mask has no non-zero pixels then len(coords) == 0 and random.randint(0, -1) will error; (2) if a non-zero pixel is near the image border, roi_methods.circle will raise (fatal_error) because the ROI extends outside the image. Consider filtering candidate centers to those that are at least radius pixels from the image edges and returning early (or warning) when no valid centers exist.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import cv2 | ||
| import numpy as np | ||
| from plantcv.plantcv.submask import sub_mask | ||
|
|
||
| def test_sub_mask_success(test_data): | ||
| """Test for PlantCV.""" | ||
| img = cv2.imread(test_data.small_rgb_img, -1) | ||
| mask = cv2.imread(test_data.small_bin_img, -1) | ||
| spots = sub_mask(img, mask, 2, 2) | ||
| assert len(np.unique(spots)) == 3 | ||
|
Comment on lines
+5
to
+10
|
||
|
|
||
|
|
||
| def test_sub_mask_too_large(test_data): | ||
| """Test for PlantCV.""" | ||
| img = cv2.imread(test_data.small_rgb_img, -1) | ||
| mask = cv2.imread(test_data.small_bin_img, -1) | ||
| spots = sub_mask(img, mask, 2, 20) | ||
| assert len(np.unique(spots)) == 1 | ||
|
|
||
|
|
||
| def test_sub_mask_too_many(test_data): | ||
| """Test for PlantCV.""" | ||
| img = cv2.imread(test_data.small_rgb_img, -1) | ||
| mask = cv2.imread(test_data.small_bin_img, -1) | ||
| spots = sub_mask(img, mask, 5, 3) | ||
| assert len(np.unique(spots)) == 2 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This page doesn’t follow the established docs pattern used elsewhere (e.g.,
docs/auto_crop.md): it usesplantcv.plantcv.sub_maskinstead ofplantcv.sub_mask, and several list items are indented with tabs which can render as code blocks in Markdown. Consider switching to the standard**plantcv.sub_mask**(...)heading and replacing tabs with spaces for consistent rendering.