Pushpak21 commited on
Commit
45d60ba
·
verified ·
1 Parent(s): ec06d7f

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +12 -12
  3. Model2_exact_serialized.keras +3 -0
  4. README.md +14 -16
  5. requirements.txt +9 -3
  6. streamlit_app.py +151 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Model2_exact_serialized.keras filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -1,20 +1,20 @@
1
- FROM python:3.13.5-slim
 
2
 
 
3
  WORKDIR /app
4
 
5
- RUN apt-get update && apt-get install -y \
6
- build-essential \
7
- curl \
8
- git \
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
- COPY requirements.txt ./
12
- COPY src/ ./src/
 
13
 
14
- RUN pip3 install -r requirements.txt
 
15
 
16
  EXPOSE 8501
17
-
18
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
-
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
1
+ # Dockerfile for Streamlit app
2
+ FROM python:3.10-slim
3
 
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
  WORKDIR /app
6
 
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ build-essential libjpeg-dev libopenjp2-7 \
9
+ libglib2.0-0 libsm6 libxrender1 libxext6 \
 
10
  && rm -rf /var/lib/apt/lists/*
11
 
12
+ COPY requirements.txt /app/requirements.txt
13
+ RUN pip install --upgrade pip
14
+ RUN pip install --no-cache-dir -r /app/requirements.txt
15
 
16
+ COPY streamlit_app.py /app/streamlit_app.py
17
+ COPY Model2_exact_serialized.keras /app/Model2_exact_serialized.keras
18
 
19
  EXPOSE 8501
20
+ CMD ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
 
 
Model2_exact_serialized.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:431586f6f821c9fb89b1dbd5c0a5387a47a2bb2cfe9cbee53b0377d37222c7c1
3
+ size 79068760
README.md CHANGED
@@ -1,19 +1,17 @@
1
- ---
2
- title: Pneumonia Detection
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: Streamlit template space
12
- ---
13
 
14
- # Welcome to Streamlit!
 
15
 
16
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
 
 
 
17
 
18
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
19
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
1
+ # Pneumonia Detection — Streamlit App
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ ## Place model
4
+ Copy your model file into this folder as `Model2_exact_serialized.keras` (or edit `MODEL_FILENAME` inside streamlit_app.py).
5
 
6
+ ## Run locally
7
+ 1. pip install -r requirements.txt
8
+ 2. streamlit run streamlit_app.py
9
+ 3. Open http://localhost:8501
10
 
11
+ ## Docker
12
+ docker build -t pneumonia-streamlit .
13
+ docker run -p 8501:8501 pneumonia-streamlit
14
+
15
+ ## Notes
16
+ - DICOMs may contain PHI. Do not store/share patient-identifying DICOM metadata.
17
+ - If your DICOMs are compressed, the pylibjpeg plugins in requirements help decode them.
requirements.txt CHANGED
@@ -1,3 +1,9 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
 
 
1
+ streamlit>=1.18
2
+ tensorflow>=2.10
3
+ numpy
4
+ Pillow
5
+ pydicom>=2.4
6
+ pylibjpeg>=1.3
7
+ pylibjpeg-libjpeg>=1.4
8
+ pylibjpeg-openjpeg>=1.0
9
+ matplotlib
streamlit_app.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # streamlit_app.py
2
+ import io
3
+ import os
4
+ import numpy as np
5
+ from PIL import Image
6
+ import streamlit as st
7
+ import tensorflow as tf
8
+ from tensorflow.keras.models import load_model
9
+ from tensorflow.keras.applications.densenet import preprocess_input as densenet_preprocess
10
+ import pydicom
11
+ from pydicom.pixel_data_handlers.util import apply_voi_lut
12
+ import matplotlib.cm as cm
13
+
14
+ # -------- CONFIG --------
15
+ MODEL_FILENAME = "Model2_exact_serialized.keras" # model file expected in app folder
16
+ IMG_SIZE = (224, 224)
17
+ THRESHOLD = 0.62
18
+ ENABLE_GRADCAM = True
19
+ # ------------------------
20
+
21
+ st.set_page_config(page_title="Pneumonia Detection (CheXNet)", layout="centered")
22
+
23
+ st.title("Pneumonia detection (CheXNet)")
24
+ st.write("Upload a chest X-ray (DICOM or PNG/JPG). The app predicts probability of pneumonia.")
25
+
26
+ # ------- utilities -------
27
+ def dicom_to_image_array(dicom_bytes):
28
+ ds = pydicom.dcmread(io.BytesIO(dicom_bytes), force=True)
29
+ try:
30
+ arr = ds.pixel_array
31
+ except Exception as e:
32
+ raise RuntimeError(f"Could not decode DICOM pixel data: {e}")
33
+ if arr.ndim == 3:
34
+ arr = arr[0]
35
+ try:
36
+ arr = apply_voi_lut(arr, ds)
37
+ except Exception:
38
+ pass
39
+ arr = arr.astype(np.float32)
40
+ if getattr(ds, "PhotometricInterpretation", "").upper() == "MONOCHROME1":
41
+ arr = np.max(arr) - arr
42
+ mn, mx = arr.min(), arr.max()
43
+ if mx > mn:
44
+ arr = (arr - mn) / (mx - mn)
45
+ else:
46
+ arr = arr - mn
47
+ arr = (arr * 255.0).clip(0,255).astype(np.uint8)
48
+ return arr
49
+
50
+ def to_rgb_uint8_from_upload(uploaded_file):
51
+ """Return RGB uint8 (H,W,3) array resized to IMG_SIZE."""
52
+ if uploaded_file is None:
53
+ raise RuntimeError("No file")
54
+ raw = uploaded_file.read()
55
+ # try DICOM
56
+ try:
57
+ ds = pydicom.dcmread(io.BytesIO(raw), stop_before_pixels=True, force=True)
58
+ if hasattr(ds, "PixelData") or getattr(ds, "Rows", None):
59
+ arr = dicom_to_image_array(raw)
60
+ if arr.ndim == 2:
61
+ arr = np.stack([arr]*3, axis=-1)
62
+ pil = Image.fromarray(arr).convert("RGB").resize(IMG_SIZE)
63
+ return np.array(pil)
64
+ except Exception:
65
+ pass
66
+ # fallback normal image
67
+ try:
68
+ pil = Image.open(io.BytesIO(raw)).convert("L").resize(IMG_SIZE)
69
+ arr = np.stack([np.array(pil)]*3, axis=-1)
70
+ return arr.astype(np.uint8)
71
+ except Exception as e:
72
+ raise RuntimeError("Unsupported file format. Upload a DICOM or PNG/JPG.") from e
73
+
74
+ # -------- model load (cached) --------
75
+ @st.cache_resource
76
+ def load_predict_model(model_path):
77
+ if not os.path.exists(model_path):
78
+ raise FileNotFoundError(f"Model file not found: {model_path}")
79
+ m = load_model(model_path, compile=False)
80
+ return m
81
+
82
+ # Grad-CAM utilities
83
+ def find_last_conv_layer(m):
84
+ for layer in reversed(m.layers):
85
+ out_shape = getattr(layer, "output_shape", None)
86
+ if out_shape and len(out_shape) == 4 and "conv" in layer.name:
87
+ return layer.name
88
+ return m.layers[-3].name
89
+
90
+ def make_gradcam_image(rgb_uint8, model, last_conv_name=None, alpha=0.4, cmap_name="jet"):
91
+ img = rgb_uint8.astype(np.float32)
92
+ if last_conv_name is None:
93
+ last_conv_name = find_last_conv_layer(model)
94
+ grad_model = tf.keras.models.Model([model.inputs], [model.get_layer(last_conv_name).output, model.output])
95
+ x = densenet_preprocess(np.expand_dims(img.astype(np.float32), axis=0))
96
+ with tf.GradientTape() as tape:
97
+ conv_outputs, preds = grad_model(x)
98
+ loss = preds[:, 0]
99
+ grads = tape.gradient(loss, conv_outputs)
100
+ weights = tf.reduce_mean(grads, axis=(1,2))
101
+ cam = tf.reduce_sum(tf.multiply(weights[:, tf.newaxis, tf.newaxis, :], conv_outputs), axis=-1)
102
+ cam = tf.squeeze(cam).numpy()
103
+ cam = np.maximum(cam, 0)
104
+ cam_max = cam.max() if cam.max() != 0 else 1e-8
105
+ cam = cam / cam_max
106
+ cam_img = Image.fromarray(np.uint8(cam * 255)).resize((img.shape[1], img.shape[0]), resample=Image.BILINEAR)
107
+ cam_arr = np.array(cam_img).astype(np.float32)/255.0
108
+ colormap = cm.get_cmap(cmap_name)
109
+ heatmap = colormap(cam_arr)[:, :, :3]
110
+ heat_uint8 = np.uint8(heatmap * 255)
111
+ heat_pil = Image.fromarray(heat_uint8).convert("RGBA").resize((img.shape[1], img.shape[0]))
112
+ base_pil = Image.fromarray(np.uint8(img)).convert("RGBA")
113
+ blended = Image.blend(base_pil, heat_pil, alpha=alpha)
114
+ return blended.convert("RGB")
115
+
116
+ # -------- UI elements --------
117
+ col1, col2 = st.columns([1,1])
118
+ with col1:
119
+ uploaded = st.file_uploader("Upload DICOM or PNG/JPG", type=["dcm","png","jpg","jpeg","tif","tiff"])
120
+ with col2:
121
+ thresh = st.number_input("Decision threshold (probability)", min_value=0.0, max_value=1.0, value=float(THRESHOLD), step=0.01)
122
+
123
+ if uploaded is not None:
124
+ try:
125
+ rgb = to_rgb_uint8_from_upload(uploaded)
126
+ except Exception as e:
127
+ st.error(f"Failed to process file: {e}")
128
+ st.stop()
129
+
130
+ st.image(rgb, caption="Input (resized)", use_column_width=False)
131
+
132
+ # load model (cached)
133
+ model = load_predict_model(MODEL_FILENAME)
134
+
135
+ # predict
136
+ x_pre = densenet_preprocess(np.expand_dims(rgb.astype(np.float32), axis=0))
137
+ prob = float(model.predict(x_pre, verbose=0).ravel()[0])
138
+ pred = int(prob >= thresh)
139
+
140
+ st.markdown(f"**Pneumonia probability:** `{prob:.4f}`")
141
+ st.markdown(f"**Predicted class (binary):** `{pred}` — **{'Pneumonia' if pred==1 else 'Normal'}**")
142
+
143
+ if ENABLE_GRADCAM:
144
+ try:
145
+ cam = make_gradcam_image(rgb, model)
146
+ st.image(cam, caption="Grad-CAM overlay", use_column_width=False)
147
+ except Exception as e:
148
+ st.warning(f"Grad-CAM failed: {e}")
149
+
150
+ else:
151
+ st.info("Upload a DICOM or PNG/JPG image to run inference.")