-
Notifications
You must be signed in to change notification settings - Fork 283
Make analyze.texture #1896
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?
Make analyze.texture #1896
Changes from all commits
21feb29
e8c2859
75e955f
c63323a
49af0fb
866f407
ad56e49
67f5571
7666ad4
9d550d4
6feb635
3aac567
0aedb28
d28d1c7
e6ef475
0c432ca
6c6386b
cbc8776
99f1a68
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,57 @@ | ||
| ## Analyze the Texture Characteristics of Objects | ||
|
|
||
| Texture analysis outputs numeric properties for individual plants, seeds, leaves, etc. | ||
|
|
||
| **plantcv.analyze.texture**(*img, labeled_mask, methods=None, distances=None, angles=None, | ||
| levels=None, symmetric=False, normalize=False, n_labels=1, label=None*) | ||
|
|
||
| **returns** analysis_image | ||
|
|
||
| - **Parameters:** | ||
| - img - Grayscale image data for plotting. If img has multiple channels it will be coerced to grayscale. | ||
| - labeled_mask - Labeled mask of objects (32-bit, output from [`pcv.create_labels`](create_labels.md) or [`pcv.roi.filter`](roi_filter.md)). | ||
| - methods - A list of texture phenotypes to return. If None (the default) then the entire list of possible methods is used (`["contrast", "dissimilarity", "homogeneity", "ASM", "energy", "correlation", "mean", "variance", "std", "entropy"]`) | ||
| - distances - A list of distances between pixels to use, defaults to None which will use `[1]` to only compare adjacent pixels. | ||
| - angles - A list of angles between pixels to compare, defaults to None, which will use `[0]`. | ||
| - symmetric - Logical, Should the order of values pairs be ignored? The default, False, will not always have [i, j] in the gray co-occurence matrix equal to [j, i] and will calculate both values. | ||
| - normalize - Logical, should the matrix be rescaled to sum to 1? Defaults to False. | ||
| - n_labels - Total number expected individual objects (default = 1). | ||
| - label - Optional label parameter, modifies the variable name of observations recorded. Can be a prefix or list (default = pcv.params.sample_label). | ||
|
|
||
| - **Context:** | ||
| - Used to output texture characteristics of individual objects (labeled regions). | ||
| - About the analysis image: The analysis image is a scatterplot of the extracted phenotypes. | ||
|
|
||
| - **Example use:** | ||
| - [Use In Seed Analysis Tutorial](https://plantcv.org/tutorials/seed-analysis-workflow) | ||
|
|
||
| - **Output data stored:** Data including any of ("contrast", "dissimilarity", "homogeneity", "ASM", "energy", "correlation", "mean", "variance", "std", "entropy") automatically are stored to the [`Outputs` class](outputs.md) when this function is | ||
| run. These data can be accessed during a workflow (example below). For more detail about data output see | ||
| [Summary of Output Observations](output_measurements.md#summary-of-output-observations) | ||
|
|
||
| **Original image** | ||
|
|
||
|  | ||
|
|
||
| ```python | ||
|
|
||
| from plantcv import plantcv as pcv | ||
|
|
||
| # Set global debug behavior to None (default), "print" (to file), | ||
| # or "plot" (Jupyter Notebooks or X11) | ||
|
|
||
| pcv.params.debug = "plot" | ||
|
|
||
| # Characterize object texture from 3rd Channel Grayscale image | ||
| _ = pcv.analyze.texture(img=img[:,:,2], labeled_mask=mask) | ||
|
|
||
| # Access data stored out from analyze.texture | ||
| plant_contrast = pcv.outputs.observations['default_1']['contrast']['value'] | ||
|
|
||
| ``` | ||
|
|
||
| **Texture Features Plot** | ||
|
|
||
|  | ||
|
|
||
| **Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/main/plantcv/plantcv/analyze/texture.py) | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,145 @@ | ||||
| """Analyzes the texture of objects and outputs data.""" | ||||
| from skimage.feature import graycomatrix, graycoprops | ||||
| from plantcv.plantcv._globals import params, outputs | ||||
| from plantcv.plantcv._helpers import _iterate_analysis, _rgb2gray | ||||
| from plantcv.plantcv._debug import _debug | ||||
| import altair as alt | ||||
| import pandas as pd | ||||
| import numpy as np | ||||
| import cv2 | ||||
| import os | ||||
|
|
||||
|
|
||||
| def texture(img, labeled_mask, methods=None, | ||||
| distances=None, angles=None, | ||||
| symmetric=False, normalize=False, | ||||
| n_labels=1, label=None): | ||||
| """A function that analyzes the texture of objects and outputs data. | ||||
|
|
||||
| Parameters | ||||
| ---------- | ||||
| img = numpy.ndarray, grayscale image data. If data is not grayscale then | ||||
| it will be coerced to grayscale using pcv._helpers._rgb2gray. | ||||
| labeled_mask = numpy.ndarray, Labeled mask of objects (32-bit). | ||||
| methods = list, List of str specifying phenotypes to return. Options | ||||
| come from skimage.feature.graycoprops and include "contrast", | ||||
| "dissimilarity", "homogeneity", "ASM", "energy", "correlation", | ||||
| "mean", "variance", "std", "entropy". The default, None, will | ||||
| use all methods. | ||||
| distances = list of Int. Pixel pair distances, defaults to 1 which compares | ||||
| adjacent pixels. | ||||
| angles = list of float, angles or direction to check travel for each | ||||
| distance. | ||||
| symmetric = bool, optional | ||||
| If True, the output is symmetric because the order of value pairs | ||||
| is ignored so that (i, j) and (j, i) the same. The default is False. | ||||
| normalize = bool, optional | ||||
| If True then the matrix is rescaled to sum to 1. Default is False. | ||||
| n_labels = Int, Total number expected individual objects (default = 1). | ||||
| label = str, Optional label parameter, modifies the variable name of | ||||
| observations recorded (default = pcv.params.sample_label). | ||||
|
|
||||
| Returns | ||||
| ------- | ||||
| plot = altair.vegalite.v5.api.FacetChart, Diagnostic image showing measurements. | ||||
| """ | ||||
| if distances is None: | ||||
| distances = [1] | ||||
| if angles is None: | ||||
| angles = [0] | ||||
| if len(np.shape(img)) > 2: | ||||
| img = _rgb2gray(img) | ||||
| if label is None: | ||||
| label = params.sample_label | ||||
| if methods is None: | ||||
| methods = ["contrast", "dissimilarity", "homogeneity", | ||||
| "ASM", "energy", "correlation", | ||||
| "mean", "variance", "std", "entropy"] | ||||
| # for the debug image I would like to have a mix of matrices, but for non-uint8 | ||||
|
joshqsumner marked this conversation as resolved.
|
||||
| # that could get really large if there is a multi-object mask. | ||||
| _ = _iterate_analysis(img=img, labeled_mask=labeled_mask, | ||||
| n_labels=n_labels, label=label, | ||||
| function=_analyze_texture, | ||||
| **{'distances': distances, 'angles': angles, | ||||
| 'symmetric': symmetric, 'methods': methods, | ||||
| 'normalize': normalize} | ||||
| ) | ||||
| plot = _make_texture_debug_plot() | ||||
| _debug(visual=plot, cmap="turbo", | ||||
| filename=os.path.join(params.debug_outdir, str(params.device) + "_textures.png")) | ||||
| return plot | ||||
|
|
||||
|
|
||||
| def _analyze_texture(img, mask, label, methods, distances, angles, symmetric, normalize): | ||||
| """Analyze the texture of individual objects. | ||||
|
|
||||
| Parameters | ||||
| ---------- | ||||
| img = numpy.ndarray, grayscale image data | ||||
| mask = Binary image data | ||||
| label = str, Optional label parameter, modifies the variable name of | ||||
| observations recorded (default = pcv.params.sample_label). | ||||
| methods = list, List of str specifying phenotypes to return. Options | ||||
| come from skimage.feature.graycoprops and include "contrast", | ||||
| "dissimilarity", "homogeneity", "ASM", "energy", "correlation", | ||||
| "mean", "variance", "std", "entropy" | ||||
| distances = list of Int. Pixel pair distances, defaults to 1 which compares | ||||
| adjacent pixels. | ||||
| angles = list of float, angles or direction to check travel for each | ||||
| distance. | ||||
| symmetric = bool, optional | ||||
| If True, the output is symmetric because the order of value pairs | ||||
| is ignored so that (i, j) and (j, i) the same. The default is False. | ||||
| normalize = bool, optional | ||||
| If True then the matrix is rescaled to sum to 1. Default is False. | ||||
|
|
||||
| Returns | ||||
| ------- | ||||
| glcm = numpy.ndarray, currently not used. | ||||
| """ | ||||
| params.device += 1 | ||||
|
||||
| params.device += 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import cv2 | ||
| import numpy as np | ||
| from plantcv.plantcv._globals import outputs | ||
|
joshqsumner marked this conversation as resolved.
|
||
| from plantcv.plantcv.analyze.texture import texture | ||
|
|
||
|
|
||
| def test_analyze_texture(test_data): | ||
| """Test for PlantCV.""" | ||
| outputs.clear() | ||
| gray = cv2.imread(test_data.small_gray_img, -1) | ||
| mask = cv2.imread(test_data.small_bin_img, -1) | ||
| _ = texture(gray, mask) | ||
| assert isinstance(outputs.observations["default_1"]["contrast_1_0"]["value"], np.float64) | ||
|
|
||
|
|
||
| def test_analyze_texture_rgb(test_data): | ||
| """Test for PlantCV.""" | ||
| outputs.clear() | ||
| rgb_img = cv2.imread(test_data.small_rgb_img) | ||
| mask = cv2.imread(test_data.small_bin_img, -1) | ||
| _ = texture(rgb_img, mask) | ||
| assert isinstance(outputs.observations["default_1"]["contrast_1_0"]["value"], np.float64) | ||
Uh oh!
There was an error while loading. Please reload this page.