Spaces:
Running on Zero
Running on Zero
File size: 5,783 Bytes
5d4afe2 d686612 5d4afe2 d686612 4bb4db8 d686612 4bb4db8 012754b 4bb4db8 012754b d686612 012754b 5d4afe2 d686612 5d4afe2 d686612 4bb4db8 d686612 4bb4db8 d686612 5d4afe2 d686612 012754b d686612 5d4afe2 d686612 5d4afe2 d686612 5d4afe2 d686612 | 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 | import os
import torch
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from data_loader import GTEX_TISSUE_PROFILES, MEDDRA_ADR_CLASSES, ORGAN_NAMES
from model import EpiADRNet
from utils import highlight_toxic_subgraph, smiles_to_graph
app = FastAPI(
title="EpiADR-Net REST Microservice API",
description="Enterprise API for Tissue-Conditioned Zero-Shot Adverse Drug Reaction (ADR) Disaggregation — v5 Foundation Edition (100M+ Parameters)",
version="5.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
# Enable CORS for frontend integration
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Global Model Initialization — EpiADR-Net v5 (~116.5M Parameters)
MODEL = EpiADRNet(
in_features=24, hidden_dim=1536, tissue_dim=1024,
num_classes=10, num_gat_layers=12, num_heads=16, dropout=0.1,
)
MODEL_WEIGHTS_PATH = "model.pt"
if os.path.exists(MODEL_WEIGHTS_PATH):
try:
MODEL.load_state_dict(torch.load(MODEL_WEIGHTS_PATH, map_location=torch.device('cpu')))
MODEL.eval()
except Exception:
pass
# Pydantic Schemas
class PredictionRequest(BaseModel):
smiles: str = Field(..., json_schema_extra={"example": "CC(=O)NC1=CC=C(O)C=C1"}, description="Valid molecular SMILES string")
organ: str = Field(..., json_schema_extra={"example": "Liver"}, description="Target human tissue profile (Liver, Heart, Brain, Kidney, Lung, Pancreas, Spleen, Intestine, Skin, Bone_Marrow)")
mc_samples: int | None = Field(20, ge=5, le=50, description="Monte Carlo Dropout sample passes")
class ExplainRequest(BaseModel):
smiles: str = Field(..., json_schema_extra={"example": "O=C1C2=C(O)C=CC=C2C(=O)C3=C1C(O)=C4C(=C3O)C(O)(C(=O)CO)CC(O)C4"})
top_k: int | None = Field(3, ge=1, le=10)
class CompareRequest(BaseModel):
smiles: str = Field(..., json_schema_extra={"example": "CC(=O)NC1=CC=C(O)C=C1"})
organ_a: str = Field(..., json_schema_extra={"example": "Liver"})
organ_b: str = Field(..., json_schema_extra={"example": "Heart"})
@app.get("/", tags=["System Health"])
def root():
return {
"status": "online",
"service": "EpiADR-Net REST Microservice",
"version": "5.0.0",
"architecture": "12-Layer Graph Transformer + SwiGLU FFN + DMPNN | 1024-dim GTEx | 116.5M params",
"documentation": "/docs"
}
@app.get("/health", tags=["System Health"])
def health_check():
return {
"status": "healthy",
"model_loaded": True,
"device": "cpu",
"organs": ORGAN_NAMES,
"num_adr_classes": len(MEDDRA_ADR_CLASSES)
}
@app.get("/organs", tags=["Reference Metadata"])
def get_supported_organs():
return {"supported_organs": ORGAN_NAMES, "vector_dimension": 1024}
@app.get("/adr-classes", tags=["Reference Metadata"])
def get_adr_classes():
return {"meddra_terms": MEDDRA_ADR_CLASSES}
@app.post("/predict", tags=["ADR Inference"])
def predict_adr(req: PredictionRequest):
if req.organ not in GTEX_TISSUE_PROFILES:
raise HTTPException(status_code=400, detail=f"Unsupported organ '{req.organ}'. Choose from {ORGAN_NAMES}")
try:
node_feats, edge_index, _atom_symbols = smiles_to_graph(req.smiles)
batch = torch.zeros(node_feats.size(0), dtype=torch.long)
tissue_vec = GTEX_TISSUE_PROFILES[req.organ].unsqueeze(0)
mc_res = MODEL.predict_mc_dropout(node_feats, edge_index, batch, tissue_vec, num_samples=req.mc_samples or 30)
mu = mc_res["mean_probabilities"][0].cpu().numpy()
sigma = mc_res["uncertainty_sigma"][0].cpu().numpy()
attn = mc_res["attention_weights"]
predictions = []
for i, term in enumerate(MEDDRA_ADR_CLASSES):
predictions.append({
"meddra_term": term,
"predicted_probability": float(round(mu[i], 4)),
"epistemic_uncertainty": float(round(sigma[i], 4)),
"is_significant": bool(mu[i] > 0.45)
})
xai_breakdown = highlight_toxic_subgraph(req.smiles, attn, top_k=3)
return {
"smiles": req.smiles,
"conditioned_organ": req.organ,
"predictions": predictions,
"xai_explanation": xai_breakdown
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Inference failed: {e!s}")
@app.post("/explain", tags=["Explainable AI"])
def explain_subgraph(req: ExplainRequest):
try:
node_feats, edge_index, _atom_symbols = smiles_to_graph(req.smiles)
batch = torch.zeros(node_feats.size(0), dtype=torch.long)
tissue_vec = GTEX_TISSUE_PROFILES["Liver"].unsqueeze(0)
_, attn = MODEL(node_feats, edge_index, batch, tissue_vec, return_attention=True)
explanation = highlight_toxic_subgraph(req.smiles, attn, top_k=req.top_k)
return explanation
except Exception as e:
raise HTTPException(status_code=500, detail=f"XAI extraction failed: {e!s}")
@app.post("/compare-organs", tags=["Comparative Analysis"])
def compare_organs(req: CompareRequest):
if req.organ_a not in GTEX_TISSUE_PROFILES or req.organ_b not in GTEX_TISSUE_PROFILES:
raise HTTPException(status_code=400, detail="Invalid organ selected")
res_a = predict_adr(PredictionRequest(smiles=req.smiles, organ=req.organ_a))
res_b = predict_adr(PredictionRequest(smiles=req.smiles, organ=req.organ_b))
return {
"smiles": req.smiles,
"organ_a": req.organ_a,
"organ_b": req.organ_b,
"predictions_organ_a": res_a["predictions"],
"predictions_organ_b": res_b["predictions"]
}
|