Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/closing.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Filters out dark noise from an image.

- **Parameters:**
- gray_img - Grayscale or binary image data
- kernel - Optional neighborhood, expressed as an array of 1's and 0's. See the [kernel making](get_kernel.md) function. If None,
use cross-shaped structuring element.
- kernel - Optional neighborhood, expressed as an int, tuple, or array of 1's and 0's. See the [kernel making](get_kernel.md) function.
If None, use cross-shaped structuring element.
- roi - Optional rectangular ROI as returned by [`pcv.roi.rectangle`](roi_rectangle.md) within which to apply this function. (default = None, which uses the entire image)
- **Context:**
- Used to reduce image noise, specifically small dark spots (i.e. "pepper").
Expand Down
4 changes: 3 additions & 1 deletion docs/dilate.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ conditions set in kernel are true.

- **Parameters:**
- gray_img - Grayscale (usually binary) image data.
- ksize - An odd integer that is used to build a ksize x ksize matrix using np.ones. Must be greater than 1 to have an effect.
- ksize - Kernel specified as an int, tuple, or numpy.ndarray.
An integer is used to build a ksize x ksize matrix using np.ones and
must be greater than 1 to have an effect.
- i - An integer for number of iterations, i.e. the number of consecutive filtering passes.
- roi - Optional rectangular ROI as returned by [`pcv.roi.rectangle`](roi_rectangle.md) within which to apply this function. (default = None, which uses the entire image)
- **Context:**
Expand Down
3 changes: 2 additions & 1 deletion docs/erode.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ conditions set in kernel are true, otherwise removes pixel.

- **Parameters:**
- gray_img - Grayscale (usually binary) image data
- ksize - Kernel size, an odd integer that is used to build a ksize x ksize matrix using np.ones. Must be greater than 1 to have an effect
- ksize - Kernel specified as an int, tuple, or numpy.ndarray. An integer is used to build a ksize x ksize matrix using np.ones.
An int must be greater than 1 to have an effect
- i - An integer for number of iterations, i.e. the number of consecutive filtering passes
- roi - Optional rectangular ROI as returned by [`pcv.roi.rectangle`](roi_rectangle.md) within which to apply this function. (default = None, which uses the entire image)

Expand Down
2 changes: 1 addition & 1 deletion docs/gaussian_threshold.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In the Gaussian adaptive threshold, the local average is a weighted average of t

- **Parameters:**
- gray_img - Grayscale image data.
- ksize - Size of the block of pixels used to compute the local average.
- ksize - Kernel specified as a binary numpy.ndarray for arbitrary shapes, shape tuple for a rectangular kernel, or integer for a square kernel. Here any input will be simplified to an integer specifying a square kernel.
- offset - Value substracted from the local average to compute the local threshold.
A negative offset sets the local threshold above the local average.
- object_type - "light" or "dark" (default: "light").
Expand Down
2 changes: 1 addition & 1 deletion docs/texture_threshold.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ texture calculation for thresholding.

- **Parameters:**
- gray_img - Grayscale image data
- ksize - Kernel size for texture measure calculation
- ksize - Kernel specified as a binary numpy.ndarray for arbitrary shapes, shape tuple for a rectangular kernel, or integer for a square kernel. Here any input will be simplified to an integer specifying a square kernel.
- threshold - Threshold value (0-255)
- offset - Distance offsets (default offset=3)
- texture_method - Feature of a grey level co-occurrence matrix, either
Expand Down
41 changes: 24 additions & 17 deletions plantcv/plantcv/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from skimage import morphology
from plantcv.plantcv import fatal_error, warn
from plantcv.plantcv._globals import params
from plantcv.plantcv.get_kernel import _format_kernel
import pandas as pd


Expand All @@ -16,8 +17,10 @@ def _closing(gray_img, kernel=None, roi=None):
----------
gray_img = numpy.ndarray
input image (grayscale or binary)
kernel = numpy.ndarray
optional neighborhood, expressed as an array of 1s and 0s. If None, use cross-shaped structuring element.
kernel = int, numpy.ndarray, or tuple
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
If None, uses cross-shaped structuring element.
roi : Objects class
Optional rectangular ROI to erode within

Expand All @@ -34,19 +37,20 @@ def _closing(gray_img, kernel=None, roi=None):
# Make sure the image is binary/grayscale
if len(np.shape(gray_img)) != 2:
fatal_error("Input image must be grayscale or binary")
k = _format_kernel(kernel, to=np.ndarray)

if len(np.unique(gray_img)) <= 2:
bool_img = gray_img.astype(bool)
sub_img = _rect_filter(bool_img, roi=roi, function=morphology.binary_closing,
**{"footprint": kernel})
**{"footprint": k})
filtered_img = sub_img.astype(np.uint8) * 255
replaced_img = _rect_replace(bool_img.astype(np.uint8) * 255, filtered_img, roi)
# Otherwise use method appropriate for grayscale images
else:
filtered_img = _rect_filter(gray_img,
roi=roi,
function=morphology.closing,
**{"footprint": kernel})
**{"footprint": k})
replaced_img = _rect_replace(gray_img, filtered_img, roi)

return replaced_img
Expand Down Expand Up @@ -96,8 +100,11 @@ def _erode(gray_img, ksize, i, roi=None):
----------
gray_img : numpy.ndarray
Grayscale (usually binary) image data
ksize : int
Kernel size (int). A ksize x ksize kernel will be built. Must be greater than 1 to have an effect.
ksize : int, numpy.ndarray, or tuple
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
If ksize is an int then a k x k kernel will be built and ksize must
be greater than 1 to have an effect.
i : int
interations, i.e. number of consecutive filtering passes
roi : Objects class
Expand All @@ -113,13 +120,11 @@ def _erode(gray_img, ksize, i, roi=None):
ValueError
If ksize is less than or equal to 1.
"""
if ksize <= 1:
if isinstance(ksize, int) and ksize <= 1:
raise ValueError('ksize needs to be greater than 1 for the function to have an effect')

kernel1 = int(ksize)
kernel2 = np.ones((kernel1, kernel1), np.uint8)
k = _format_kernel(ksize, to=np.ndarray)
sub_er_img = _rect_filter(img=gray_img, roi=roi, function=cv2.erode,
**{"kernel": kernel2, "iterations": i})
**{"kernel": k, "iterations": i})
er_img = _rect_replace(gray_img, sub_er_img, roi)

return er_img
Expand All @@ -132,8 +137,11 @@ def _dilate(gray_img, ksize, i, roi=None):
----------
gray_img : numpy.ndarray
Grayscale image data to be dilated
ksize : int
Kernel size (int). A k x k kernel will be built. Must be greater than 1 to have an effect.
ksize : : int, numpy.ndarray, or tuple
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
If ksize is an int then a k x k kernel will be built and ksize must
be greater than 1 to have an effect.
i : int
Number of iterations (i.e. how many times to apply the dilation).
roi : Objects class
Expand All @@ -149,13 +157,12 @@ def _dilate(gray_img, ksize, i, roi=None):
ValueError
If ksize is less than or equal to 1.
"""
if ksize <= 1:
if isinstance(ksize, int) and ksize <= 1:
raise ValueError('ksize needs to be greater than 1 for the function to have an effect')

kernel1 = int(ksize)
kernel2 = np.ones((kernel1, kernel1), np.uint8)
k = _format_kernel(ksize, to=np.ndarray)
sub_dil_img = _rect_filter(img=gray_img, roi=roi, function=cv2.dilate,
**{"kernel": kernel2, "iterations": i})
**{"kernel": k, "iterations": i})
dil_img = _rect_replace(gray_img, sub_dil_img, roi)

return dil_img
Expand Down
38 changes: 38 additions & 0 deletions plantcv/plantcv/get_kernel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Create a kernel structuring element

import cv2
import numpy as np
from plantcv.plantcv import fatal_error


Expand Down Expand Up @@ -30,3 +31,40 @@ def get_kernel(size, shape):
fatal_error("Shape " + str(shape) + " is not rectangle, ellipse or cross!")

return kernel


def _format_kernel(k, to=int):
"""turn a kernel/ksize into an array/tuple kernel
Parameters
----------
k : int, tuple, or numpy.ndarray
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
to : tuple, accepted classes to convert k to including any of
int, tuple, or np.ndarray. This should be set internally
and depends on how the kernel argument is going to be used.

Returns
-------
kernel specified as 'most complex' class from `to`
"""
if k is None:
return k
if isinstance(k, to):
return k
if not isinstance(to, tuple):
to = [to]

# if not, pick the next most informative specification from `to`
convert_to = [cls for cls in [np.ndarray, tuple, int] if cls in to][0]

conversions = {
(int, np.ndarray): lambda v: get_kernel((v, v), "rectangle"),
(tuple, np.ndarray): lambda v: get_kernel(v, "rectangle"),
(int, tuple): lambda v: (v, v),
(np.ndarray, tuple): np.shape,
(tuple, int): lambda v: v[0],
(np.ndarray, int): lambda v: np.shape(v)[0],
}

return conversions[(type(k), convert_to)](k)
128 changes: 69 additions & 59 deletions plantcv/plantcv/threshold/threshold_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from matplotlib import pyplot as plt
from plantcv.plantcv import fatal_error, warn, params
from plantcv.plantcv._debug import _debug
from plantcv.plantcv.get_kernel import _format_kernel
from plantcv.plantcv._helpers import _rgb2lab, _rgb2hsv, _rgb2gray, _rgb2cmyk
from skimage.feature import graycomatrix, graycoprops
from scipy.ndimage import generic_filter
Expand Down Expand Up @@ -58,25 +59,27 @@ def gaussian(gray_img, ksize, offset, object_type="light"):
In the Gaussian adaptive threshold, the local average is a weighed average of the pixel values
in the block, where the weights are a 2D Gaussian centered in the middle.

Inputs:
gray_img = Grayscale image data
ksize = Size of the block of pixels used to compute the local average
offset = Value substracted from the local average to compute the local threshold.
A negative offset sets the local threshold above the local average.
object_type = "light" or "dark" (default: "light")
- "light" (for objects brighter than the background) sets the pixels above
the local threshold to 255 and the pixels below to 0.
- "dark" (for objects darker than the background) sets the pixels below the
local threshold to 255 and the pixels above to 0.
Parameters:
-------
gray_img = numpy.ndarray,
Grayscale image data
ksize = int, numpy.ndarray, or tuple
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
offset = float,
Value substracted from the local average to compute the local threshold.
A negative offset sets the local threshold above the local average.
object_type = str,
"light" or "dark" (default: "light")
- "light" (for objects brighter than the background) sets the pixels above
the local threshold to 255 and the pixels below to 0.
- "dark" (for objects darker than the background) sets the pixels below the
local threshold to 255 and the pixels above to 0.

Returns:
bin_img = Thresholded, binary image

:param gray_img: numpy.ndarray
:param ksize: int
:param offset: float
:param object_type: str
:return bin_img: numpy.ndarray
--------
bin_img = numpy.ndarray,
Thresholded binary image
"""
# Set the threshold method
threshold_method = ""
Expand All @@ -88,8 +91,8 @@ def gaussian(gray_img, ksize, offset, object_type="light"):
fatal_error('Object type ' + str(object_type) + ' is not "light" or "dark"!')

params.device += 1

bin_img = _call_adaptive_threshold(gray_img, ksize, offset, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
k = _format_kernel(ksize, to=int)
bin_img = _call_adaptive_threshold(gray_img, k, offset, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
threshold_method, "_gaussian_threshold_")

return bin_img
Expand All @@ -105,25 +108,27 @@ def mean(gray_img, ksize, offset, object_type="light"):

In the mean adaptive threshold, the local average is the average of the pixel values in the block.

Inputs:
gray_img = Grayscale image data
ksize = Size of the block of pixels used to compute the local average
offset = Value substracted from the local average to compute the local threshold.
A negative offset sets the local threshold above the local average.
object_type = "light" or "dark" (default: "light")
- "light" (for objects brighter than the background) sets the pixels above
the local threshold to 255 and the pixels below to 0.
- "dark" (for objects darker than the background) sets the pixels below the
local threshold to 255 and the pixels above to 0.
Parameters:
-------
gray_img = numpy.ndarray,
Grayscale image data
ksize = int, numpy.ndarray, or tuple
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
offset = float,
Value substracted from the local average to compute the local threshold.
A negative offset sets the local threshold above the local average.
object_type = str,
"light" or "dark" (default: "light")
- "light" (for objects brighter than the background) sets the pixels above
the local threshold to 255 and the pixels below to 0.
- "dark" (for objects darker than the background) sets the pixels below the
local threshold to 255 and the pixels above to 0.

Returns:
bin_img = Thresholded, binary image

:param gray_img: numpy.ndarray
:param ksize: int
:param offset: float
:param object_type: str
:return bin_img: numpy.ndarray
--------
bin_img = numpy.ndarray,
Thresholded binary image
"""
# Set the threshold method
threshold_method = ""
Expand All @@ -135,8 +140,8 @@ def mean(gray_img, ksize, offset, object_type="light"):
fatal_error('Object type ' + str(object_type) + ' is not "light" or "dark"!')

params.device += 1

bin_img = _call_adaptive_threshold(gray_img, ksize, offset, cv2.ADAPTIVE_THRESH_MEAN_C,
k = _format_kernel(ksize, to=int)
bin_img = _call_adaptive_threshold(gray_img, k, offset, cv2.ADAPTIVE_THRESH_MEAN_C,
threshold_method, "_mean_threshold_")

return bin_img
Expand Down Expand Up @@ -287,29 +292,34 @@ def texture(gray_img, ksize, threshold, offset=3, texture_method='dissimilarity'
This function is quite slow.

Inputs:
gray_img = Grayscale image data
ksize = Kernel size for texture measure calculation
threshold = Threshold value (0-255)
offset = Distance offsets
texture_method = Feature of a grey level co-occurrence matrix, either
'contrast', 'dissimilarity', 'homogeneity', 'ASM', 'energy',
or 'correlation'.For equations of different features see
scikit-image.
borders = How the array borders are handled, either 'reflect',
'constant', 'nearest', 'mirror', or 'wrap'
gray_img = numpy.ndarray,
Grayscale image data
ksize = int, numpy.ndarray, or tuple
Kernel specified as a binary numpy.ndarray for arbitrary shapes,
shape tuple for a rectangular kernel, or integer for a square kernel.
threshold = int,
Threshold value (0-255)
offset = float,
Distance offsets
texture_method = str,
Feature of a grey level co-occurrence matrix, either
'contrast', 'dissimilarity', 'homogeneity', 'ASM', 'energy',
or 'correlation'.For equations of different features see
scikit-image.
borders = str,
How the array borders are handled, either 'reflect',
'constant', 'nearest', 'mirror', or 'wrap'

Returns:
bin_img = Thresholded, binary image

:param gray_img: numpy.ndarray
:param ksize: int
:param threshold: int
:param offset: int
:param texture_method: str
:param borders: str
:return bin_img: numpy.ndarray
--------
bin_img = numpy.ndarray,
Thresholded binary image
"""
# format kernel
k = _format_kernel(ksize, to=int)

# Function that calculates the texture of a kernel

def calc_texture(inputs):
"""Kernel calculate texture function.

Expand All @@ -323,7 +333,7 @@ def calc_texture(inputs):
float
Texture value
"""
inputs = np.reshape(inputs, (ksize, ksize))
inputs = np.reshape(inputs, (k, k))
inputs = inputs.astype(np.uint8)
# Greycomatrix takes image, distance offset, angles (in radians), symmetric, and normed
# http://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.graycomatrix
Expand All @@ -335,7 +345,7 @@ def calc_texture(inputs):
output = np.zeros(gray_img.shape, dtype=gray_img.dtype)

# Apply the texture function over the whole image
generic_filter(gray_img, calc_texture, size=ksize, output=output, mode=borders)
generic_filter(gray_img, calc_texture, size=k, output=output, mode=borders)

# Threshold so higher texture measurements stand out
bin_img = binary(gray_img=output, threshold=threshold, object_type='light')
Expand Down
Loading