Skip to content
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

Skip border functionality #46

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
41 changes: 41 additions & 0 deletions glcm_cupy/glcm/glcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def glcm(
Features.CORRELATION,
Features.DISSIMILARITY),
normalized_features: bool = True,
skip_border: bool = False,
verbose: bool = True
) -> ndarray:
"""
Expand All @@ -71,6 +72,7 @@ def glcm(
max_threads: Maximum threads for CUDA
features: Select features to be included
normalized_features: Whether to normalize features to [0, 1]
skip_border: Wheter to skip border of interfacing windows. When skipping border, result is the same as a GLCM calculated on each 7x7 window, without neighbourhood information. Default is False.
verbose: Whether to enable TQDM logging

Returns:
Expand All @@ -86,6 +88,7 @@ def glcm(
normalized_features=normalized_features,
step_size=step_size,
directions=directions,
skip_border=skip_border,
verbose=verbose
).run(im)

Expand All @@ -99,6 +102,7 @@ class GLCM(GLCMBase):
Direction.SOUTH,
Direction.SOUTH_WEST
)
skip_border: bool = False

def __post_init__(self):
super().__post_init__()
Expand Down Expand Up @@ -203,6 +207,9 @@ def make_windows(self, im_chn: cp.ndarray) -> List[
for direction in self.directions:
i, j = self.pair_windows(ij, direction=direction)

if self.skip_border:
i, j = self._remove_border((i, j), direction)

i = i.reshape((-1, *i.shape[-2:])) \
.reshape((i.shape[0] * i.shape[1], -1))
j = j.reshape((-1, *j.shape[-2:])) \
Expand All @@ -211,6 +218,40 @@ def make_windows(self, im_chn: cp.ndarray) -> List[

return ijs

def _remove_border(self, ij: Tuple[cp.ndarray, cp.ndarray], direction: Direction) -> Tuple[cp.ndarray, cp.ndarray]:
if direction == Direction.EAST:
sl = (
slice(None, None),
slice(None, None),
slice(None, None),
slice(None, -self.step_size),
)
elif direction == Direction.SOUTH_EAST:
sl = (
slice(None, None),
slice(None, None),
slice(None, -self.step_size),
slice(None, -self.step_size),
)
elif direction == Direction.SOUTH:
sl = (
slice(None, None),
slice(None, None),
slice(None, -self.step_size),
slice(None, None),
)
elif direction == Direction.SOUTH_WEST:
sl = (
slice(None, None),
slice(None, None),
slice(None, -self.step_size),
slice(self.step_size, None),
)
else:
raise ValueError("Invalid Direction")

return ij[0][sl], ij[1][sl]

def pair_windows(self, ij: ndarray,
direction: Direction) -> Tuple[ndarray, ndarray]:
""" Pairs the ij windows in specified direction
Expand Down
69 changes: 56 additions & 13 deletions glcm_cupy/glcm/glcm_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
from tqdm import tqdm
from typing import Tuple, Dict

from glcm_cupy.conf import NO_OF_FEATURES, ndarray
from glcm_cupy.glcm_py_base import GLCMPyBase
Expand All @@ -12,22 +13,26 @@

def glcm_py_im(ar: ndarray, bin_from: int, bin_to: int,
radius: int = 2,
step: int = 1):
step: int = 1,
skip_border = False):
return GLCMPy(bin_from=bin_from,
bin_to=bin_to,
radius=radius,
step=step).glcm_im(ar)
step=step,
skip_border=skip_border).glcm_im(ar)


def glcm_py_chn(ar: cp.ndarray,
bin_from: int,
bin_to: int,
radius: int = 2,
step: int = 1):
step: int = 1,
skip_border = False):
return GLCMPy(bin_from=bin_from,
bin_to=bin_to,
radius=radius,
step=step).glcm_chn(ar)
step=step,
skip_border=skip_border).glcm_chn(ar)


def glcm_py_ij(i: cp.ndarray,
Expand All @@ -40,28 +45,36 @@ def glcm_py_ij(i: cp.ndarray,
@dataclass
class GLCMPy(GLCMPyBase):
step: int = 1
skip_border: bool = False

def glcm_chn(self, ar: cp.ndarray):

ar = (ar / self.bin_from * self.bin_to).astype(cp.uint8)
ar_w = sliding_window_view(ar, (self.diameter, self.diameter))

def windows(ar: ndarray):
return ar.reshape((-1, self.diameter, self.diameter))

def flat(ar: ndarray):
ar = ar.reshape((-1, self.diameter, self.diameter))
return ar.reshape((ar.shape[0], -1))

ar_w_i = flat(ar_w[self.step:-self.step, self.step:-self.step])
ar_w_j_sw = flat(ar_w[self.step * 2:, :-self.step * 2])
ar_w_j_s = flat(ar_w[self.step * 2:, self.step:-self.step])
ar_w_j_se = flat(ar_w[self.step * 2:, self.step * 2:])
ar_w_j_e = flat(ar_w[self.step:-self.step, self.step * 2:])
ar_w_i = windows(ar_w[self.step:-self.step, self.step:-self.step])
ar_w_j_sw = windows(ar_w[self.step * 2:, :-self.step * 2])
ar_w_j_s = windows(ar_w[self.step * 2:, self.step:-self.step])
ar_w_j_se = windows(ar_w[self.step * 2:, self.step * 2:])
ar_w_j_e = windows(ar_w[self.step:-self.step, self.step * 2:])

feature_ar = np.zeros((ar_w_i.shape[0], 4, NO_OF_FEATURES))

for j_e, ar_w_j in enumerate(
(ar_w_j_sw, ar_w_j_s, ar_w_j_se, ar_w_j_e)):
for e, (i, j) in tqdm(enumerate(zip(ar_w_i, ar_w_j)),
total=ar_w_i.shape[0]):
ar_i, ar_j = ar_w_i, ar_w_j
if self.skip_border:
ar_i, ar_j = self._remove_border((ar_i, ar_j), j_e)
ar_i = flat(ar_i)
ar_j = flat(ar_j)
for e, (i, j) in tqdm(enumerate(zip(ar_i, ar_j)),
total=ar_i.shape[0]):

feature_ar[e, j_e] = self.glcm_ij(i, j)

h, w = ar_w.shape[:2]
Expand All @@ -72,6 +85,36 @@ def flat(ar: ndarray):

return normalize_features(feature_ar, self.bin_to)

def _remove_border(self, ij: Tuple[cp.ndarray, cp.ndarray], direction: int) -> Tuple[cp.ndarray, cp.ndarray]:
if direction == 0: # Direction.SOUTH_WEST
sl = (
slice(None, None),
slice(None, -self.step),
slice(self.step, None),
)
elif direction == 1: # Direction.SOUTH
sl = (
slice(None, None),
slice(None, -self.step),
slice(None, None),
)
elif direction == 2: # Direction.SOUTH_EAST
sl = (
slice(None, None),
slice(None, -self.step),
slice(None, -self.step),
)
elif direction == 3: # Direction.EAST
sl = (
slice(None, None),
slice(None, None),
slice(None, -self.step),
)
else:
raise ValueError("Invalid Direction")

return ij[0][sl], ij[1][sl]

def glcm_im(self, ar: ndarray):
was_cupy = False
if isinstance(ar, cp.ndarray):
Expand Down
2 changes: 1 addition & 1 deletion glcm_cupy/glcm_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def glcm_ij(self,
self.ar_features[:] = 0

no_of_windows = i.shape[0]
no_of_values = self._diameter ** 2
no_of_values = i.shape[1]

if i.dtype != cp.uint8 or j.dtype != cp.uint8:
raise ValueError(
Expand Down
12 changes: 8 additions & 4 deletions tests/unit_tests/test_glcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ def test_glcm_normalize(ar):
"radius",
[1, 2, 4]
)
def test_glcm(size, bin_from, bin_to, radius):
@pytest.mark.parametrize(
"skip_border",
[False, True]
)
def test_glcm(size, bin_from, bin_to, radius, skip_border):
ar = np.random.randint(0, bin_from, [size, size, 1])
g = GLCM(radius=radius, bin_from=bin_from, bin_to=bin_to).run(ar)
g_fn = glcm(ar, radius=radius, bin_from=bin_from, bin_to=bin_to)
expected = glcm_py_im(ar, radius=radius, bin_from=bin_from, bin_to=bin_to)
g = GLCM(radius=radius, bin_from=bin_from, bin_to=bin_to, skip_border=skip_border).run(ar)
g_fn = glcm(ar, radius=radius, bin_from=bin_from, bin_to=bin_to, skip_border=skip_border)
expected = glcm_py_im(ar, radius=radius, bin_from=bin_from, bin_to=bin_to, skip_border=skip_border)
assert g == pytest.approx(expected, abs=0.001)
assert g_fn == pytest.approx(expected, abs=0.001)

Expand Down