Fahimeh Orvati Nia
update
4c1c4a7
"""
Minimal texture feature extraction.
"""
import numpy as np
import torch
import torch.nn.functional as F
from skimage.feature import local_binary_pattern, hog
from skimage import exposure
from scipy import ndimage
from typing import Dict, Tuple, Optional
import logging
logger = logging.getLogger(__name__)
class TextureExtractor:
"""Minimal texture extraction (LBP, HOG, Lacunarity only)."""
def __init__(self, lbp_points: int = 8, lbp_radius: int = 1,
hog_orientations: int = 9, hog_pixels_per_cell: Tuple[int, int] = (8, 8),
hog_cells_per_block: Tuple[int, int] = (2, 2), lacunarity_window: int = 15,
ehd_threshold: float = 0.3, angle_resolution: int = 45):
"""Initialize with defaults."""
self.lbp_points = lbp_points
self.lbp_radius = lbp_radius
self.hog_orientations = hog_orientations
self.hog_pixels_per_cell = hog_pixels_per_cell
self.hog_cells_per_block = hog_cells_per_block
self.lacunarity_window = lacunarity_window
def extract_lbp(self, gray_image: np.ndarray) -> np.ndarray:
"""Extract Local Binary Pattern."""
try:
lbp = local_binary_pattern(gray_image, self.lbp_points, self.lbp_radius, method='uniform')
return self._convert_to_uint8(lbp)
except Exception as e:
logger.error(f"LBP failed: {e}")
return np.zeros_like(gray_image, dtype=np.uint8)
def extract_hog(self, gray_image: np.ndarray) -> np.ndarray:
"""Extract HOG features."""
try:
_, vis = hog(gray_image, orientations=self.hog_orientations,
pixels_per_cell=self.hog_pixels_per_cell,
cells_per_block=self.hog_cells_per_block,
visualize=True, feature_vector=True)
return exposure.rescale_intensity(vis, out_range=(0, 255)).astype(np.uint8)
except Exception as e:
logger.error(f"HOG failed: {e}")
return np.zeros_like(gray_image, dtype=np.uint8)
def compute_local_lacunarity(self, gray_image: np.ndarray) -> np.ndarray:
"""Compute lacunarity."""
try:
arr = gray_image.astype(np.float32)
m1 = ndimage.uniform_filter(arr, size=self.lacunarity_window)
m2 = ndimage.uniform_filter(arr * arr, size=self.lacunarity_window)
var = m2 - m1 * m1
lac = var / (m1 * m1 + 1e-6) + 1
lac[m1 <= 1e-6] = 0
return self._convert_to_uint8(lac)
except Exception as e:
logger.error(f"Lacunarity failed: {e}")
return np.zeros_like(gray_image, dtype=np.uint8)
def extract_all_texture_features(self, gray_image: np.ndarray) -> Dict[str, np.ndarray]:
"""Extract LBP, HOG, and Lacunarity."""
return {
'lbp': self.extract_lbp(gray_image),
'hog': self.extract_hog(gray_image),
'lac2': self.compute_local_lacunarity(gray_image)
}
def _convert_to_uint8(self, arr: np.ndarray) -> np.ndarray:
"""Convert to uint8."""
arr = np.nan_to_num(arr, nan=0.0, posinf=0.0, neginf=0.0)
ptp = np.ptp(arr)
if ptp > 0:
normalized = (arr - arr.min()) / (ptp + 1e-6) * 255
else:
normalized = np.zeros_like(arr)
return np.clip(normalized, 0, 255).astype(np.uint8)
def compute_texture_statistics(self, features: Dict[str, np.ndarray],
mask: Optional[np.ndarray] = None) -> Dict[str, Dict[str, float]]:
"""Compute basic statistics."""
stats = {}
for feature_name, feature_data in features.items():
if mask is not None and mask.shape == feature_data.shape:
masked_data = np.where(mask > 0, feature_data, np.nan)
else:
masked_data = feature_data
valid_data = masked_data[~np.isnan(masked_data)]
if len(valid_data) > 0:
stats[feature_name] = {
'mean': float(np.mean(valid_data)),
'std': float(np.std(valid_data)),
}
return stats