File size: 4,240 Bytes
b4123b8
dd1d7f5
b4123b8
 
 
 
 
 
 
dd1d7f5
 
b4123b8
 
 
 
 
 
dd1d7f5
b4123b8
dd1d7f5
 
 
 
 
b4123b8
 
 
 
 
 
 
 
dd1d7f5
b4123b8
dd1d7f5
b4123b8
 
dd1d7f5
b4123b8
 
 
dd1d7f5
b4123b8
dd1d7f5
 
 
 
b4123b8
 
dd1d7f5
b4123b8
 
dd1d7f5
 
b4123b8
 
dd1d7f5
 
b4123b8
dd1d7f5
 
 
b4123b8
dd1d7f5
 
b4123b8
 
dd1d7f5
 
 
 
 
 
b4123b8
 
dd1d7f5
b4123b8
4c1c4a7
 
 
b4123b8
 
 
 
 
dd1d7f5
 
b4123b8
 
dd1d7f5
 
b4123b8
dd1d7f5
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""
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