nail-anemia-detector / inference.py
JetX-GT's picture
Upload inference.py with huggingface_hub
0e3db21 verified
Raw
History Blame Contribute Delete
3.94 kB
#!/usr/bin/env python3
"""
Inference script for Nail Anemia Detector.
Usage:
python inference.py --image path/to/nail.jpg
python inference.py --image nail.jpg --threshold 0.255 # For 100% recall mode
"""
import argparse
import joblib
import numpy as np
from PIL import Image
from scipy.ndimage import uniform_filter
from pathlib import Path
def extract_features(img):
"""Extract handcrafted color features from nail image."""
img = img.convert('RGB').resize((224, 224))
arr = np.array(img).astype(float)
r, g, b = arr[:,:,0], arr[:,:,1], arr[:,:,2]
brightness = 0.299*r + 0.587*g + 0.114*b
features = []
# Brightness features
features.extend([brightness.mean(), brightness.std()])
features.extend([np.percentile(brightness, p) for p in [10, 25, 50, 75, 90]])
# Redness features
redness = r / (r + g + b + 1e-10)
features.extend([redness.mean(), redness.std()])
# Pallor features
white_ratio = (brightness > 180).sum() / brightness.size
pink_ratio = ((r > 150) & (g < 150) & (b < 150)).sum() / brightness.size
features.extend([white_ratio, pink_ratio])
# Channel statistics
for ch in [r, g, b]:
features.extend([ch.mean(), ch.std()])
# Color ratios
features.extend([
(r.mean() + 1) / (g.mean() + 1),
(r.mean() + 1) / (b.mean() + 1),
(r.mean() - b.mean()) / 255,
])
# Hemoglobin proxy
hb = r / (g + b + 1)
features.extend([hb.mean(), hb.std()])
# Spatial features
h, w = arr.shape[:2]
top = brightness[:h//3, :].mean()
bottom = brightness[2*h//3:, :].mean()
features.append(top - bottom)
center = brightness[h//4:3*h//4, w//4:3*w//4].mean()
features.append(center - brightness.mean())
# Gradient features
gx = np.abs(np.diff(brightness, axis=1, prepend=brightness[:, :1]))
gy = np.abs(np.diff(brightness, axis=0, prepend=brightness[:1, :]))
gradient = np.sqrt(gx**2 + gy**2)
features.extend([gradient.mean(), gradient.std()])
# Local variance
local_mean = uniform_filter(brightness, size=7)
local_var = uniform_filter((brightness - local_mean)**2, size=7)
local_var = np.maximum(local_var, 0)
features.extend([np.sqrt(local_var).mean(), np.sqrt(local_var).std()])
return np.array(features, dtype=np.float32)
def main():
parser = argparse.ArgumentParser(description='Nail Anemia Detection Inference')
parser.add_argument('--image', '-i', required=True, help='Path to nail image')
parser.add_argument('--threshold', '-t', type=float, default=0.10,
help='Prediction threshold (default: 0.10 for balanced mode, use 0.255 for 100%% recall)')
parser.add_argument('--model', '-m', default='mlp_model.joblib', help='Path to model file')
parser.add_argument('--scaler', '-s', default='feature_scaler.joblib', help='Path to scaler file')
args = parser.parse_args()
# Load model and scaler
model = joblib.load(args.model)
scaler = joblib.load(args.scaler)
# Load and process image
img = Image.open(args.image)
features = extract_features(img)
features_scaled = scaler.transform(features.reshape(1, -1))
# Predict
probability = model.predict_proba(features_scaled)[0, 1]
prediction = "anemia" if probability >= args.threshold else "healthy"
print(f"\n{'='*50}")
print("NAIL ANEMIA DETECTION RESULT")
print(f"{'='*50}")
print(f"Image: {args.image}")
print(f"Probability: {probability:.4f}")
print(f"Threshold: {args.threshold}")
print(f"Prediction: {prediction.upper()}")
print(f"{'='*50}")
if prediction == "anemia":
print("\n⚠️ POSITIVE - Please confirm with blood test (hemoglobin/hematocrit)")
else:
print("\n✅ NEGATIVE - Low probability of anemia")
if __name__ == '__main__':
main()