comvis / inference.py
Jennigwen's picture
deploy BE ke huggingface
5c22e5c
Raw
History Blame Contribute Delete
3.5 kB
"""
inference.py β€” ONNX Runtime wrapper for the Random Forest model.
Expected model: final_model_Random_Forest.onnx
Input : float_input [None, 120] float32 (raw features β€” no scaling needed)
Outputs: label [None] int64
probabilities [None, 3] float32
"""
from pathlib import Path
from threading import Lock
import numpy as np
import onnxruntime as rt
MODEL_PATH = Path(__file__).parent / "model" / "final_model_Random_Forest.onnx"
CLASS_NAMES = {
0: "Common / Benign Nevi",
1: "Atypical / Other Benign",
2: "Melanoma (Suspected)",
}
CLASS_RISK = {
0: "healthy",
1: "watch",
2: "danger",
}
CLASS_WHAT = {
0: "A common benign mole. Melanocytic nevi are very common β€” most adults have 10–40. Almost always completely harmless.",
1: "This category includes atypical or other benign lesions such as seborrhoeic keratosis, actinic keratosis, dermatofibroma, or vascular lesions. While many are harmless, some may need treatment.",
2: "Melanoma is the most serious type of skin cancer. It develops from pigment-producing cells. Early detection is critical β€” when caught early, treatment is highly effective.",
}
CLASS_ACTION = {
0: "No action needed. Monitor for changes in shape, colour, size, or bleeding.",
1: "Recommended: book a consultation with a dermatologist for professional evaluation.",
2: "Please see a dermatologist or doctor as soon as possible. Do not delay.",
}
_session = None
_session_lock = Lock()
def load_model() -> rt.InferenceSession:
"""Load ONNX model (cached after first call)."""
global _session
if _session is None:
with _session_lock:
if _session is None:
if not MODEL_PATH.exists():
raise FileNotFoundError(
f"ONNX model not found at {MODEL_PATH}. "
"Please copy final_model_Random_Forest.onnx into backend/models/"
)
opts = rt.SessionOptions()
opts.intra_op_num_threads = 4
_session = rt.InferenceSession(str(MODEL_PATH), sess_options=opts)
return _session
def predict(features: np.ndarray) -> dict:
"""
Run ONNX inference on a (120,) or (1, 120) feature vector.
Returns:
{
"label": int, # 0, 1, or 2
"class_name": str,
"risk": str, # healthy / watch / danger
"probabilities": [p0, p1, p2], # float list, sums to 1
"confidence": float, # max probability
"what": str, # plain-language explanation
"action": str, # recommended next step
}
"""
sess = load_model()
if features.ndim == 1:
features = features.reshape(1, -1)
features = features.astype(np.float32)
label_arr, prob_arr = sess.run(
["label", "probabilities"],
{"float_input": features}
)
label = int(label_arr[0])
probs = prob_arr[0].tolist()
if label not in CLASS_NAMES:
raise ValueError(f"Model returned unexpected label {label}; probabilities={probs}")
return {
"label": label,
"class_name": CLASS_NAMES[label],
"risk": CLASS_RISK[label],
"probabilities": probs,
"confidence": float(max(probs)),
"what": CLASS_WHAT[label],
"action": CLASS_ACTION[label],
}