File size: 7,632 Bytes
e8f37df
1ee6bb0
 
 
 
 
883845f
13830e0
883845f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8f37df
 
1ee6bb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b1e0a1
 
 
 
 
 
 
 
 
 
 
 
 
1ee6bb0
 
 
 
 
 
 
 
 
7b1e0a1
1ee6bb0
 
 
 
09b2e24
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import gradio as gr
import numpy as np
import joblib
from PIL import Image
from skimage.transform import resize
from huggingface_hub import hf_hub_download
from skimage.feature import hog, local_binary_pattern
from skimage.color import rgb2gray, rgb2hsv
import numpy as np


# 0. Inject feature extraction functions

def extract_hog_features_from_list(X_images_rgb):
    """
    Takes a list of pre-resized RGB images and returns HOG features.
    
    INPUTS:
        - X_images_rgb: list of images in memory
    
    OUTPUTS:
        - np.array(X_hog_features): Numpy array of HOG features
    """
    
    X_hog_features = []
    
    for img_rgb in X_images_rgb:
        # 1. Convert RGB to Gray using skimage
        img_gray = rgb2gray(img_rgb)

        # 2. Extract HOG
        hog_features = hog(
            img_gray,
            orientations=9,
            pixels_per_cell=(8, 8),
            cells_per_block=(2, 2),
            visualize=False
        )

        X_hog_features.append(hog_features)
        
    return np.array(X_hog_features)


def extract_lbp_features_from_list(X_images_rgb):
    """
    Standardized LBP extraction using pre-resized RGB images.
    
    INPUTS
        - X_images_rgb: list of images in memory
        
    OUTPUTS:
        - np.array(X_lbp_features): numpy array of LBP features for each image
    """
    
    X_lbp_features = []
    
    for img_rgb in X_images_rgb:
        # 1. Convert RGB to Gray
        img_gray = rgb2gray(img_rgb)

        # 2. Extract LBP
        lbp = local_binary_pattern(img_gray.astype(dtype="int32"), P=8, R=1, method='uniform')
        
        # 3. Create Histogram (10 bins for P=8 uniform)
        lbp_hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, 11), density=True)

        X_lbp_features.append(lbp_hist)
        
    return np.array(X_lbp_features)


def extract_hsv_features_from_list(X_images_rgb, hue_lower_bound=0.2, hue_upper_bound=0.45, sat_lower=0.25, bins=10):
    """
    Extracts masked HSV histograms from a list of RGB images.
    Uses color-based thresholding to isolate plant matter from the background.
    
    INPUTS:
        - X_images_rgb: List of RGB images in memory
        - hue_lower_bound: Lower threshold for green hue (default 0.2)
        - hue_upper_bound: Upper threshold for green hue (default 0.45)
        - sat_lower: Minimum saturation to filter out gray/background (default 0.25)
        - bins: Number of histogram bins per channel
        
    OUTPUTS:
        - np.array(X_hsv_features): Numpy array of concatenated H-S-V histograms
    """
    
    X_hsv_features = []
    
    for img_rgb in X_images_rgb:
        
        # 1. Extract channels
        hsv_img = rgb2hsv(img_rgb)
        hue_chan = hsv_img[:, :, 0]
        sat_chan = hsv_img[:, :, 1]
        val_chan = hsv_img[:, :, 2]

        # 2. Build mask
        hsv_mask = (hue_chan > hue_lower_bound) & (hue_chan < hue_upper_bound) & (sat_chan > sat_lower)
        
        # 3. Apply mask
        plant_hue = hue_chan[hsv_mask]
        plant_sat = sat_chan[hsv_mask]
        plant_val = val_chan[hsv_mask]

        # 4. Create Histogram for each channel
        if plant_hue.size > 0:
            hue_hist, _ = np.histogram(plant_hue, bins=bins, range=(0, 1), density=True)
            sat_hist, _ = np.histogram(plant_sat, bins=bins, range=(0, 1), density=True)
            val_hist, _ = np.histogram(plant_val, bins=bins, range=(0, 1), density=True)
        else:
#             print("Zeroed image")
            hue_hist = np.zeros(bins)
            sat_hist = np.zeros(bins)
            val_hist = np.zeros(bins)

        # 5. Combine channels into one vector
        hsv_vector = np.concatenate([hue_hist, sat_hist, val_hist]) # Flat
        
        # 6. Append to feature list
        X_hsv_features.append(hsv_vector)
        
    return np.array(X_hsv_features)


def feature_fusion(super_matrix:np.ndarray=None, feature_list:list =[]):
    """
    Fuses multiple feature sets into a single matrix via horizontal stacking.
    Used to combine HOG, LBP, and HSV features into a unified feature vector.
    
    INPUTS:
        - super_matrix: Existing numpy matrix of features (None for first fusion)
        - feature_list: List of feature arrays to be appended to the super_matrix
        
    OUTPUTS:
        - super_matrix: Numpy matrix with features stacked horizontally per image
    """
    
    new_features = [np.array(f) for f in feature_list]
    
    if super_matrix is None:
        super_matrix = np.hstack([ np.array(f) for f in feature_list ])
        
    else:
        super_matrix = np.hstack([super_matrix] + new_features)
        
    return super_matrix


# 1. Load artifacts
REPO_ID = "ipetrousov/weedcrop_svm_classifier"

model_file = hf_hub_download(repo_id=REPO_ID, filename="svc_opt.joblib")
scaler_file = hf_hub_download(repo_id=REPO_ID, filename="scaler.joblib")
pca_file = hf_hub_download(repo_id=REPO_ID, filename="pca.joblib")

model = joblib.load(model_file)
scaler = joblib.load(scaler_file)
pca = joblib.load(pca_file)

# 2. Optimized threshold
OPT_THRESHOLD = 0.023699424100647747

def classify_plant(image_input):
    if image_input is None:
        return "No image provided", "0.00%"

    # Convert to NumPy array and resize to target dimension (256, 256)
    img_np = np.array(image_input)
    img_resized = resize(img_np, (256, 256), anti_aliasing=True)
    img_list = [img_resized]

    # Feature extraction
    img_hog = extract_hog_features_from_list(img_list)
    img_lbp = extract_lbp_features_from_list(img_list)
    img_hsv = extract_hsv_features_from_list(img_list)

    # Dimensionality reduction on HOG features
    img_hog_scaled = scaler.transform(img_hog)
    img_hog_reduced = pca.transform(img_hog_scaled)

    # Feature fusion
    X_combined = feature_fusion(
        feature_list=[img_lbp, img_hog_reduced, img_hsv]
    )

    # Probabilistic inference
    probs = model.predict_proba(X_combined)
    crop_prob = probs[0, 0]

    # Apply threshold logic
    if crop_prob >= OPT_THRESHOLD:
        prediction = "CROP"
        confidence = crop_prob
    else:
        prediction = "WEED"
        confidence = probs[0, 1]

    confidence_str = f"{confidence:.4%}"
    detailed_metrics = (
        f"Decision: {prediction}\n"
        f"Confidence: {confidence_str}\n"
        f"Raw Crop Probability: {crop_prob:.6f}\n"
        f"Operating Threshold: {OPT_THRESHOLD:.6f}"
    )

    return prediction, detailed_metrics

description_html = """
Drop a plant image to evaluate it using an optimized SVM classifier with multi-modal feature fusion (HOG + LBP + HSV).

<div style="display: flex; gap: 10px; margin-top: 15px;">
    <a href="https://github.com/gpetrousov/ml_assignment_demokritos" target="_blank">
        <img src="https://img.shields.io/badge/GitHub-View_Repository-181717?style=for-the-badge&logo=github" alt="GitHub Repository" />
    </a>
    <a href="https://github.com/gpetrousov/ml_assignment_demokritos/blob/master/notebooks/01_report.ipynb" target="_blank">
        <img src="https://img.shields.io/badge/Presentation-View_Notebook-0052CC?style=for-the-badge&logo=googleslides" alt="Presentation Notebook" />
    </a>
</div>
"""

# 3. Gradio Interface Construction
demo = gr.Interface(
    fn=classify_plant,
    inputs=gr.Image(type="pil", label="Upload Seedling Image"),
    outputs=[
        gr.Textbox(label="Predicted Class"),
        gr.Textbox(label="Evaluation Breakdown")
    ],
    title="Autofarm: Surgical Weed & Crop Classifier",
    description=description_html,
    examples=[]
)

if __name__ == "__main__":
    demo.launch(ssr_mode=False)