Skip to content
Merged
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/logical_xor.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ from plantcv import plantcv as pcv
pcv.params.debug = "plot"

# Combine two images that have had different thresholds applied to them.
# For logical 'and' operation object pixel must be in both images
# to be included in 'and' image.
# For logical 'xor' operation object pixel must be in either images
# but not both to be included in 'xor' image.
xor_image = pcv.logical_xor(s_threshold, b_threshold)

```
Expand Down
36 changes: 33 additions & 3 deletions plantcv/plantcv/_helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import cv2
import numpy as np
from plantcv.plantcv.dilate import dilate
from plantcv.plantcv.logical_and import logical_and
from plantcv.plantcv.image_subtract import image_subtract
from plantcv.plantcv import fatal_error, warn
from plantcv.plantcv import params
Expand Down Expand Up @@ -47,7 +46,7 @@ def _find_segment_ends(skel_img, leaf_objects, plotting_img, size):
segment_plot = np.zeros(skel_img.shape[:2], np.uint8)
cv2.drawContours(segment_plot, obj, -1, 255, 1, lineType=8)
segment_plot = dilate(segment_plot, 3, 1)
overlap_img = logical_and(segment_plot, tips)
overlap_img = _logical_operation(segment_plot, tips, 'and')
x, y = segment_end_obj[j].ravel()[:2]
coord = (int(x), int(y))
coords.append(coord)
Expand Down Expand Up @@ -339,7 +338,7 @@ def _roi_filter(img, roi, obj, hierarchy, roi_type="partial"):
for c, _ in enumerate(object_contour):
filtering_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
cv2.fillPoly(filtering_mask, [np.vstack(object_contour[c])], (255))
overlap_img = logical_and(filtering_mask, roi_mask)
overlap_img = _logical_operation(filtering_mask, roi_mask, 'and')
# Delete contours that do not overlap at all with the ROI
if np.sum(overlap_img) == 0:
cv2.drawContours(mask, object_contour, c, (0), -1, lineType=8, hierarchy=obj_hierarchy)
Expand Down Expand Up @@ -581,3 +580,34 @@ def _rgb2gray(rgb_img):
gray = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)

return gray


def _logical_operation(bin_img1, bin_img2, operation):
"""Perform a logical operation on two binary images.

Parameters
----------
bin_img1 : numpy.ndarray
First binary image
bin_img2 : numpy.ndarray
Second binary image
operation : str
Logical operation to perform ('and', 'or', or 'xor')

Returns
-------
numpy.ndarray
Resulting binary image after the logical operation
"""
# Dictionary of operations
operations = {
'and': cv2.bitwise_and,
'or': cv2.bitwise_or,
'xor': cv2.bitwise_xor
}
# Check if the operation is valid
if operation.lower() not in operations:
fatal_error(f"Operation '{operation}' is not supported. Use 'and', 'or', or 'xor'.")
# Perform the logical operation
mask = operations[operation.lower()](bin_img1, bin_img2)
return mask
3 changes: 2 additions & 1 deletion plantcv/plantcv/kmeans_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from plantcv.plantcv._debug import _debug
from plantcv.plantcv import params
from plantcv.learn.train_kmeans import patch_extract
from plantcv.plantcv._helpers import _logical_operation


def predict_kmeans(img, model_path="./kmeansout.fit", patch_size=10):
Expand Down Expand Up @@ -83,7 +84,7 @@ def mask_kmeans(labeled_img, k, cat_list=None):
if idx == 0:
mask_light = np.where(labeled_img == i, 255, 0).astype("uint8")
else:
mask_light = pcv.logical_or(mask_light, np.where(labeled_img == i, 255, 0).astype("uint8"))
mask_light = _logical_operation(mask_light, np.where(labeled_img == i, 255, 0).astype("uint8"), "or")
params.debug = debug
_debug(visual=mask_light, filename=os.path.join(params.debug_outdir, "_kmeans_combined_mask.png"))
return mask_light
34 changes: 16 additions & 18 deletions plantcv/plantcv/logical_and.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
# Join images (AND)

import cv2
import os
from plantcv.plantcv._debug import _debug
from plantcv.plantcv import params
from plantcv.plantcv._helpers import _logical_operation


def logical_and(bin_img1, bin_img2):
"""Join two images using the bitwise AND operator.

Parameters
----------
bin_img1 : numpy.ndarray
First binary image.
bin_img2 : numpy.ndarray
Second binary image.

Returns
-------
numpy.ndarray
Joined binary image.
"""
Join two images using the bitwise AND operator.
merged = _logical_operation(bin_img1, bin_img2, 'and')

Inputs:
bin_img1 = Binary image data to be compared to bin_img2
bin_img2 = Binary image data to be compared to bin_img1

Returns:
merged = joined binary image

:param bin_img1: numpy.ndarray
:param bin_img2: numpy.ndarray
:return merged: numpy.ndarray
"""
merged = cv2.bitwise_and(bin_img1, bin_img2)

_debug(visual=merged,
filename=os.path.join(params.debug_outdir, str(params.device) + '_and_joined.png'),
cmap='gray')
_debug(visual=merged, filename=os.path.join(params.debug_outdir, f'{params.device}_and_joined.png'), cmap='gray')

return merged
34 changes: 16 additions & 18 deletions plantcv/plantcv/logical_or.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
# Join images (OR)

import cv2
import os
from plantcv.plantcv._debug import _debug
from plantcv.plantcv import params
from plantcv.plantcv._helpers import _logical_operation


def logical_or(bin_img1, bin_img2):
"""Join two images using the bitwise OR operator.

Parameters
----------
bin_img1 : numpy.ndarray
First binary image.
bin_img2 : numpy.ndarray
Second binary image.

Returns
-------
numpy.ndarray
Joined binary image.
"""
Join two images using the bitwise OR operator.
merged = _logical_operation(bin_img1, bin_img2, 'or')

Inputs:
bin_img1 = Binary image data to be compared to bin_img2
bin_img2 = Binary image data to be compared to bin_img1

Returns:
merged = joined binary image

:param bin_img1: numpy.ndarray
:param bin_img2: numpy.ndarray
:return merged: numpy.ndarray
"""
merged = cv2.bitwise_or(bin_img1, bin_img2)

_debug(visual=merged,
filename=os.path.join(params.debug_outdir, str(params.device) + '_or_joined.png'),
cmap='gray')
_debug(visual=merged, filename=os.path.join(params.debug_outdir, f'{params.device}_or_joined.png'), cmap='gray')

return merged
34 changes: 16 additions & 18 deletions plantcv/plantcv/logical_xor.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
# Join images (XOR)

import cv2
import os
from plantcv.plantcv._debug import _debug
from plantcv.plantcv import params
from plantcv.plantcv._helpers import _logical_operation


def logical_xor(bin_img1, bin_img2):
"""Join two images using the bitwise XOR operator.

Parameters
----------
bin_img1 : numpy.ndarray
First binary image.
bin_img2 : numpy.ndarray
Second binary image.

Returns
-------
numpy.ndarray
Joined binary image.
"""
Join two images using the bitwise XOR operator.
merged = _logical_operation(bin_img1, bin_img2, 'xor')

Inputs:
bin_img1 = Binary image data to be compared to bin_img2
bin_img2 = Binary image data to be compared to bin_img1

Returns:
merged = joined binary image

:param bin_img1: numpy.ndarray
:param bin_img2: numpy.ndarray
:return merged: numpy.ndarray
"""
merged = cv2.bitwise_xor(bin_img1, bin_img2)

_debug(visual=merged,
filename=os.path.join(params.debug_outdir, str(params.device) + '_xor_joined.png'),
cmap='gray')
_debug(visual=merged, filename=os.path.join(params.debug_outdir, f'{params.device}_xor_joined.png'), cmap='gray')

return merged
5 changes: 2 additions & 3 deletions plantcv/plantcv/morphology/segment_insertion_angle.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
from plantcv.plantcv import dilate
from plantcv.plantcv import closing
from plantcv.plantcv import outputs
from plantcv.plantcv import logical_and
from plantcv.plantcv import fatal_error
from plantcv.plantcv import color_palette
from plantcv.plantcv.morphology.segment_tangent_angle import _slope_to_intesect_angle
from plantcv.plantcv._debug import _debug
from plantcv.plantcv._helpers import _cv2_findcontours, _find_tips, _iterative_prune
from plantcv.plantcv._helpers import _cv2_findcontours, _find_tips, _iterative_prune, _logical_operation


def segment_insertion_angle(skel_img, segmented_img, leaf_objects, stem_objects, size, label=None):
Expand Down Expand Up @@ -88,7 +87,7 @@ def segment_insertion_angle(skel_img, segmented_img, leaf_objects, stem_objects,
segment_plot = np.zeros(segmented_img.shape[:2], np.uint8)
cv2.drawContours(segment_plot, obj, -1, 255, 1, lineType=8)
segment_plot = dilate(segment_plot, 3, 1)
overlap_img = logical_and(segment_plot, tips)
overlap_img = _logical_operation(segment_plot, tips, "and")

# If none of the tips are within a segment_end then it's an insertion segment
if np.sum(overlap_img) == 0:
Expand Down
5 changes: 2 additions & 3 deletions plantcv/plantcv/morphology/segment_sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import numpy as np
from plantcv.plantcv import dilate
from plantcv.plantcv import params
from plantcv.plantcv import logical_and
from plantcv.plantcv._helpers import _find_tips
from plantcv.plantcv._helpers import _find_tips, _logical_operation
from plantcv.plantcv._debug import _debug


Expand Down Expand Up @@ -50,7 +49,7 @@ def segment_sort(skel_img, objects, mask=None, first_stem=True):
for i, cnt in enumerate(objects):
segment_plot = np.zeros(skel_img.shape[:2], np.uint8)
cv2.drawContours(segment_plot, objects, i, 255, 1, lineType=8)
overlap_img = logical_and(segment_plot, tips_img)
overlap_img = _logical_operation(segment_plot, tips_img, "and")

# The first contour is the base, and while it contains a tip, it isn't a leaf
if i == 0 and first_stem:
Expand Down
4 changes: 2 additions & 2 deletions plantcv/plantcv/roi/quick_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from plantcv.plantcv import params
from plantcv.plantcv.roi import roi2mask
from plantcv.plantcv._debug import _debug
from plantcv.plantcv.logical_and import logical_and
from plantcv.plantcv._helpers import _logical_operation


def quick_filter(mask, roi):
Expand Down Expand Up @@ -107,7 +107,7 @@ def _quick_cutto(mask, roi):
# Pixel intensity of (i+1) such that the first object has value
cv2.drawContours(labeled_mask, roi.contours[i], -1, (i+1), -1)
cv2.drawContours(bin_mask, roi.contours[i], -1, (255), -1)
cropped_mask = logical_and(mask_copy, bin_mask)
cropped_mask = _logical_operation(mask_copy, bin_mask, "and")
# Make a labeled mask from the cropped objects
label_mask_where = np.where(cropped_mask == 255, labeled_mask, 0)

Expand Down
10 changes: 10 additions & 0 deletions tests/plantcv/test_helper_logical_operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import cv2
import pytest
from plantcv.plantcv._helpers import _logical_operation


def test_logical_operation_bad_type(test_data):
"""Test for PlantCV."""
mask = cv2.imread(test_data.small_bin_img, -1)
with pytest.raises(RuntimeError):
_ = _logical_operation(bin_img1=mask, bin_img2=mask, operation="invalid")