Oncoct_v1 / api.py
Hibbaan's picture
Upload 11 files
b976ba2 verified
Raw History Blame Contribute Delete
2.21 kB
from __future__ import annotations
import hashlib, os, time, uuid
from pathlib import Path
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import torch
from model import OncoCT1B, OncoCTConfig
app = FastAPI(title="OncoCT-1B Research Inference", version="0.1.0")
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
MODEL = OncoCT1B(OncoCTConfig()).to(DEVICE).eval()
CHECKPOINT = os.getenv("ONCOCT_CHECKPOINT")
if CHECKPOINT and Path(CHECKPOINT).exists():
state = torch.load(CHECKPOINT, map_location=DEVICE, weights_only=False)
MODEL.load_state_dict(state.get("model", state), strict=False)
class InferenceRequest(BaseModel):
volume_tokens_path: str = Field(..., description="Path to a preprocessed research tensor; do not expose raw filesystem paths in production")
study_time: float = 0.0
request_id: str | None = None
@app.get("/health")
def health(): return {"status": "ok", "device": str(DEVICE), "model_loaded": CHECKPOINT is not None}
@app.post("/infer")
def infer(req: InferenceRequest):
rid = req.request_id or str(uuid.uuid4())
started = time.time()
p = Path(req.volume_tokens_path)
if not p.exists(): raise HTTPException(404, "tensor input not found")
item = torch.load(p, map_location="cpu", weights_only=True)
tokens = item["volume_tokens"].float().unsqueeze(0).to(DEVICE)
t = torch.tensor([[req.study_time]], device=DEVICE)
with torch.inference_mode(): out = MODEL(tokens, t)
abstain = float(torch.sigmoid(out["abstain_logit"])[0].cpu())
response_prob = torch.softmax(out["response_logits"], -1)[0].cpu().tolist()
audit_hash = hashlib.sha256(f"{rid}|{p}|{time.time_ns()}".encode()).hexdigest()
return {"request_id": rid, "status": "abstain" if abstain >= 0.5 else "review_required", "abstention_score": abstain, "response_probabilities": response_prob, "evidence": {"volume_log1p": float(out["volume_log1p"][0].cpu()), "long_axis_mm": float(out["long_axis_mm"][0].cpu())}, "latency_ms": (time.time()-started)*1000, "audit_hash": audit_hash, "clinical_notice": "Research output only. Requires qualified radiologist/oncologist review; does not diagnose or recommend treatment."}