45 lines
1.6 KiB
Python
45 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
from skimage.feature import local_binary_pattern
|
|
|
|
|
|
def _downsample(gray_image: np.ndarray, binary_mask: np.ndarray | None = None, max_side: int = 96) -> tuple[np.ndarray, np.ndarray | None]:
|
|
working = gray_image
|
|
mask = binary_mask
|
|
max_dim = max(working.shape[:2])
|
|
if max_dim <= max_side:
|
|
return working, mask
|
|
step = int(np.ceil(max_dim / max_side))
|
|
working = working[::step, ::step]
|
|
if mask is not None:
|
|
mask = mask[::step, ::step]
|
|
return working, mask
|
|
|
|
|
|
def texture_lbp_features(gray_image: np.ndarray, binary_mask: np.ndarray | None = None) -> dict[str, float]:
|
|
gray_image, binary_mask = _downsample(gray_image, binary_mask)
|
|
working = np.clip(gray_image, 0, 255).astype(np.uint8, copy=False)
|
|
lbp_r1 = local_binary_pattern(working, P=8, R=1, method="uniform")
|
|
lbp_r2 = local_binary_pattern(working, P=16, R=2, method="uniform")
|
|
|
|
if binary_mask is None:
|
|
values_r1 = lbp_r1.ravel()
|
|
values_r2 = lbp_r2.ravel()
|
|
else:
|
|
values_r1 = lbp_r1[binary_mask]
|
|
values_r2 = lbp_r2[binary_mask]
|
|
if values_r1.size == 0:
|
|
values_r1 = lbp_r1.ravel()
|
|
values_r2 = lbp_r2.ravel()
|
|
|
|
hist_r1, _ = np.histogram(values_r1, bins=np.arange(0, 8 + 3), density=True)
|
|
hist_r2, _ = np.histogram(values_r2, bins=np.arange(0, 16 + 3), density=True)
|
|
|
|
features: dict[str, float] = {}
|
|
for idx, value in enumerate(hist_r1):
|
|
features[f"texture_lbp_r1_bin_{idx:02d}"] = float(value)
|
|
for idx, value in enumerate(hist_r2):
|
|
features[f"texture_lbp_r2_bin_{idx:02d}"] = float(value)
|
|
return features
|