SamMec commited on
Commit
8633a62
·
1 Parent(s): 6e90971

demoday_api first commit

Browse files
Files changed (5) hide show
  1. .dockerignore +7 -0
  2. .gitignore +4 -0
  3. Dockerfile +39 -0
  4. radio_check_app.py +116 -0
  5. requirements.txt +16 -0
.dockerignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .git/
6
+ secrets.env
7
+ .env
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ secrets.env
2
+ .env
3
+ __pycache__/
4
+ .hf_cache/
Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM continuumio/miniconda3
2
+
3
+ # Update packages and install nano and curl
4
+ RUN apt-get update -y
5
+ RUN apt-get install nano curl -y
6
+
7
+ # Force Conda to downgrade Python to a stable ML baseline (Python 3.10)
8
+ RUN conda install -y python=3.10
9
+
10
+ # THIS IS SPECIFIC TO HUGGINFACE
11
+ # We create a new user named "user" with ID of 1000
12
+ RUN useradd -m -u 1000 user
13
+ # We switch from "root" (default user when creating an image) to "user"
14
+ USER user
15
+ # We set two environmnet variables
16
+ # so that we can give ownership to all files in there afterwards
17
+ # we also add /home/user/.local/bin in the $PATH environment variable
18
+ # PATH environment variable sets paths to look for installed binaries
19
+ # We update it so that Linux knows where to look for binaries if we were to install them with "user".
20
+ ENV HOME=/home/user \
21
+ PATH=/home/user/.local/bin:$PATH \
22
+ PORT=7860
23
+
24
+ # We set working directory to $HOME/app (<=> /home/user/app)
25
+ WORKDIR $HOME/app
26
+
27
+ # Copy requirements first to leverage Docker layer caching
28
+ COPY --chown=user requirements.txt $HOME/app/requirements.txt
29
+
30
+ # Install dependencies and clear cache in the same layer to save space
31
+ RUN pip install -r requirements.txt
32
+
33
+ # Copy the rest of the application files
34
+ COPY --chown=user . $HOME/app
35
+
36
+ EXPOSE 7860
37
+
38
+ # Run FastAPI
39
+ CMD ["sh", "-c", "fastapi run radio_check_app.py --port ${PORT}"]
radio_check_app.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
6
+ from PIL import Image
7
+ import mlflow.pytorch
8
+ from transformers import AutoTokenizer, AutoModel
9
+ from health_multimodal.image.model.pretrained import get_biovil_t_image_encoder
10
+ from health_multimodal.image.data.transforms import create_chest_xray_transform_for_inference
11
+
12
+ app = FastAPI(title="BioVil Cross-Attention+MLP Inference API")
13
+
14
+ # Global instances for your 3 models and required processors
15
+ device = None
16
+ tokenizer = None
17
+ text_model = None
18
+ image_model = None
19
+ image_transform = None
20
+ cross_att_classifier = None
21
+
22
+ # STARTUP COMPONENT
23
+ @app.on_event("startup")
24
+ def load_all_models_and_assets():
25
+ global device, tokenizer, text_model, image_model, image_transform, cross_att_classifier
26
+ try:
27
+ # Setup device configuration
28
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
29
+ print(f"Using device: {device}")
30
+
31
+ # Load the comprehensive BioViL-T repo for Text
32
+ model_id = "microsoft/BiomedVLP-BioViL-T"
33
+
34
+ # Specialized CXR-BERT tokenizer and model
35
+ tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
36
+ text_model = AutoModel.from_pretrained(model_id, trust_remote_code=True).to(device)
37
+ text_model.eval()
38
+
39
+ # Instantiate the BioViL-T Image Engine
40
+ image_model = get_biovil_t_image_encoder().to(device)
41
+ image_transform = create_chest_xray_transform_for_inference(resize=512, center_crop_size=448)
42
+ image_model.eval()
43
+
44
+ # Connect to Hugging Face MLflow instance and pull the Cross-Attention Classifier
45
+ mlflow.set_tracking_uri(os.environ.get("APP_URI"))
46
+ model_uri = "models:/biovil_cross_attention_mlp/latest"
47
+
48
+ cross_att_classifier = mlflow.pytorch.load_model(model_uri, map_location=torch.device('cpu'))
49
+ cross_att_classifier.to(device).eval()
50
+
51
+ print("All 3 models and processors loaded into memory successfully!")
52
+ except Exception as e:
53
+ print(f"❌ Startup Error: {str(e)}")
54
+ raise e
55
+
56
+ # PREPROCESSING PIPELINES
57
+ def get_text_embeddings(report_text):
58
+ inputs = tokenizer(
59
+ report_text,
60
+ padding="max_length",
61
+ truncation=True,
62
+ max_length=512,
63
+ return_tensors="pt"
64
+ ).to(device)
65
+
66
+ with torch.no_grad():
67
+ outputs = text_model(
68
+ input_ids=inputs.input_ids,
69
+ attention_mask=inputs.attention_mask,
70
+ return_dict=True
71
+ )
72
+ return outputs.last_hidden_state
73
+
74
+ def get_image_embeddings_from_pil(pil_image):
75
+ # Adjusted to accept the PIL image object directly from memory
76
+ raw_image = pil_image.convert("L")
77
+ processed_tensor = image_transform(raw_image).unsqueeze(0).to(device)
78
+
79
+ with torch.no_grad():
80
+ image_outputs = image_model(processed_tensor)
81
+ return image_outputs.projected_patch_embeddings
82
+
83
+ # THE PREDICT ENDPOINT
84
+ @app.post("/predict")
85
+ async def predict(
86
+ text_input: str = Form(...),
87
+ image_file: UploadFile = File(...)
88
+ ):
89
+ # Guard against queries hitting the server before models are fully loaded
90
+ if None in (cross_att_classifier, text_model, image_model):
91
+ raise HTTPException(status_code=503, detail="Models are initializing. Try again shortly.")
92
+
93
+ try:
94
+ # Read incoming file stream directly into memory as a PIL Image
95
+ image_bytes = await image_file.read()
96
+ pil_image = Image.open(io.BytesIO(image_bytes))
97
+
98
+ # Use custom preprocessing pipelines
99
+ sequence_outputs = get_text_embeddings(text_input)
100
+ patch_img_emb = get_image_embeddings_from_pil(pil_image)
101
+
102
+ # Run inputs through registered Cross-Attention Classifier
103
+ with torch.no_grad():
104
+ outputs = cross_att_classifier(patch_img_emb, sequence_outputs[:, :256, :]).squeeze(1)
105
+ probability = torch.sigmoid(outputs).item()
106
+ prediction = int(probability >= 0.5)
107
+
108
+ # Return JSON response back to Streamlit
109
+ return {
110
+ "status": "success",
111
+ "prediction": prediction,
112
+ "probability": round(probability, 4)
113
+ }
114
+
115
+ except Exception as e:
116
+ raise HTTPException(status_code=500, detail=f"Inference error: {str(e)}")
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Web & API Core ---
2
+ fastapi[standard]
3
+
4
+ # --- Deep Learning & NLP ---
5
+ transformers
6
+ accelerate
7
+
8
+ # --- Computer Vision & Imaging ---
9
+ Pillow
10
+ hi-ml-multimodal
11
+
12
+ # --- MLOps & Remote Model Registry (S3/MLflow) ---
13
+ mlflow
14
+ boto3
15
+ fsspec
16
+ s3fs