rallou commited on
Commit
6fc4fc6
·
1 Parent(s): f6830df

Refine code

Browse files
Files changed (5) hide show
  1. .gitignore +2 -0
  2. app.py +0 -2
  3. export.py +3 -30
  4. models.py +2 -2
  5. pipeline.py +13 -3
.gitignore CHANGED
@@ -1 +1,3 @@
1
  overview_docs/
 
 
 
1
  overview_docs/
2
+ __pycache__/
3
+ *.pyc
app.py CHANGED
@@ -185,8 +185,6 @@ def _report_to_html(text: str) -> str:
185
  return f'<pre id="report-pre">{escaped}</pre>'
186
 
187
 
188
- # Accent: sky blue (#38BDF8 / #7DD3FC) on the existing dark navy/black backgrounds.
189
- # Much higher contrast than dark purple; still clinical and calm.
190
  CUSTOM_CSS = """
191
  /* ── Base font scale ─────────────────────────────────────── */
192
  html { font-size: 22px !important; }
 
185
  return f'<pre id="report-pre">{escaped}</pre>'
186
 
187
 
 
 
188
  CUSTOM_CSS = """
189
  /* ── Base font scale ─────────────────────────────────────── */
190
  html { font-size: 22px !important; }
export.py CHANGED
@@ -8,11 +8,11 @@ from PIL import Image as PILImage
8
  PAGE_W = 210
9
  PAGE_H = 297
10
  MARGIN = 15
11
- USABLE_W = PAGE_W - 2 * MARGIN # 180 mm
12
 
13
  N_COLS = 3
14
  COL_GAP = 3
15
- IMG_W = (USABLE_W - (N_COLS - 1) * COL_GAP) / N_COLS # ~58 mm
16
  IMG_H = IMG_W
17
  CAP_H = 5
18
 
@@ -93,12 +93,6 @@ def _section(pdf: FPDF, title: str, gap_before: float = 7):
93
 
94
 
95
  def _image_grid(pdf: FPDF, items: list):
96
- """
97
- 3-column grid of (caption, PIL Image).
98
- Uses set_xy + image(w, h) [no explicit y] per image.
99
- Caption placed with pdf.text() — zero cursor impact.
100
- row_y is a local variable, never derived from get_y() after image placement.
101
- """
102
  row_y = None
103
 
104
  for i, (caption, img) in enumerate(items):
@@ -112,12 +106,9 @@ def _image_grid(pdf: FPDF, items: list):
112
 
113
  x = MARGIN + col * (IMG_W + COL_GAP)
114
 
115
- # Place image at (x, row_y) without specifying y explicitly
116
  pdf.set_xy(x, row_y)
117
  pdf.image(_png(img), w=IMG_W, h=IMG_H)
118
- # fpdf2 advances cursor to (x + IMG_W, row_y + IMG_H) — we ignore it
119
 
120
- # Caption: pdf.text() places text at absolute coords, does NOT move cursor
121
  pdf.set_font("Helvetica", style='', size=FS_SMALL)
122
  pdf.set_text_color(*MID_GRAY)
123
  cap = _safe(caption)
@@ -125,24 +116,17 @@ def _image_grid(pdf: FPDF, items: list):
125
  pdf.text(x + (IMG_W - tw) / 2, row_y + IMG_H + 4, cap)
126
  pdf.set_text_color(*BLACK)
127
 
128
- # After last column (or last item), advance cursor past the row
129
  if col == N_COLS - 1 or i == len(items) - 1:
130
  pdf.set_xy(MARGIN, row_y + IMG_H + CAP_H + 4)
131
 
132
 
133
  def _table(pdf: FPDF, metrics: dict):
134
- """
135
- 2-column table. Uses new_y='TOP' for first cell (y stays),
136
- new_y='NEXT' for last cell (y advances one row).
137
- No TMARGIN — that resets y to page top and breaks layout.
138
- """
139
  lw = USABLE_W * 0.68
140
  vw = USABLE_W * 0.32
141
  rh = 7
142
 
143
  pdf.set_draw_color(*BLACK)
144
 
145
- # Header row
146
  pdf.set_font("Helvetica", style='B', size=FS_SMALL)
147
  pdf.set_text_color(*BLACK)
148
  pdf.cell(lw, rh, "BIOMARKER / SCORE", border=1,
@@ -150,7 +134,6 @@ def _table(pdf: FPDF, metrics: dict):
150
  pdf.cell(vw, rh, "VALUE", border=1,
151
  new_x='LMARGIN', new_y='NEXT')
152
 
153
- # Data rows
154
  for k, v in metrics.items():
155
  pdf.set_font("Helvetica", style='', size=FS_BODY)
156
  pdf.set_text_color(*BLACK)
@@ -167,7 +150,6 @@ def generate_pdf_report(data: dict) -> str:
167
  pdf.set_auto_page_break(auto=True, margin=MARGIN)
168
  pdf.add_page()
169
 
170
- # ── TITLE ────────────────────────────────────────────────────
171
  pdf.set_font("Helvetica", style='B', size=FS_TITLE)
172
  pdf.set_text_color(*DARK_GRAY)
173
  pdf.cell(0, 9, "GlauNET - Glaucoma Screening Report",
@@ -187,7 +169,6 @@ def generate_pdf_report(data: dict) -> str:
187
  _hline(pdf)
188
  pdf.set_text_color(*BLACK)
189
 
190
- # ── DIAGNOSIS ────────────────────────────────────────────────
191
  _section(pdf, "Automated Diagnosis", gap_before=5)
192
  decision = data["decision"]
193
  is_positive = "SUSPECTED" in decision
@@ -195,17 +176,15 @@ def generate_pdf_report(data: dict) -> str:
195
  pdf.set_text_color(*BLACK)
196
  pdf.cell(0, 9, _safe(decision), align='C', new_x='LMARGIN', new_y='NEXT')
197
 
198
- # ── INPUT FUNDUS IMAGE ───────────────────────────────────────
199
  _section(pdf, "Input Fundus Image")
200
 
201
  if pdf.get_y() + INPUT_W + 8 > PAGE_H - MARGIN:
202
  pdf.add_page()
203
 
204
  x_img = MARGIN + (USABLE_W - INPUT_W) / 2
205
- img_top = pdf.get_y() # save y BEFORE image
206
  pdf.set_xy(x_img, img_top)
207
  pdf.image(_png(data["input_image"]), w=INPUT_W, h=INPUT_W)
208
- # fpdf2 cursor is now at img_top + INPUT_W — we IGNORE it and use img_top
209
 
210
  pdf.set_font("Helvetica", style='I', size=FS_SMALL)
211
  pdf.set_text_color(*MID_GRAY)
@@ -214,24 +193,19 @@ def generate_pdf_report(data: dict) -> str:
214
  img_top + INPUT_W + 4, cap)
215
  pdf.set_text_color(*BLACK)
216
 
217
- # Advance cursor manually: img_top + image height + caption gap
218
  pdf.set_xy(MARGIN, img_top + INPUT_W + 8)
219
 
220
- # ── SEGMENTATION PIPELINE (first 5 images) ───────────────────
221
  all_images = list(data["images"].items())
222
  _section(pdf, "Segmentation Pipeline")
223
  _image_grid(pdf, all_images[:5])
224
 
225
- # ── GRADCAM ANALYSIS (last 4 images) ─────────────────────────
226
  _section(pdf, "GradCAM Analysis")
227
  _image_grid(pdf, all_images[5:])
228
 
229
- # ── CLINICAL BIOMARKERS ──────────────────────────────────────
230
  _section(pdf, "Clinical Biomarkers")
231
  _table(pdf, data["metrics"])
232
  pdf.ln(3)
233
 
234
- # ── MEDGEMMA CLINICAL REPORT ─────────────────────────────────
235
  _section(pdf, "MedGemma Clinical Report")
236
  pdf.set_font("Courier", style='', size=FS_MONO)
237
  pdf.set_text_color(*DARK_GRAY)
@@ -243,7 +217,6 @@ def generate_pdf_report(data: dict) -> str:
243
  pdf.cell(0, 5, "(No report generated)", new_x='LMARGIN', new_y='NEXT')
244
  pdf.set_text_color(*BLACK)
245
 
246
-
247
  tmp = tempfile.NamedTemporaryFile(
248
  suffix=".pdf", prefix="glaunet_report_", delete=False
249
  )
 
8
  PAGE_W = 210
9
  PAGE_H = 297
10
  MARGIN = 15
11
+ USABLE_W = PAGE_W - 2 * MARGIN
12
 
13
  N_COLS = 3
14
  COL_GAP = 3
15
+ IMG_W = (USABLE_W - (N_COLS - 1) * COL_GAP) / N_COLS
16
  IMG_H = IMG_W
17
  CAP_H = 5
18
 
 
93
 
94
 
95
  def _image_grid(pdf: FPDF, items: list):
 
 
 
 
 
 
96
  row_y = None
97
 
98
  for i, (caption, img) in enumerate(items):
 
106
 
107
  x = MARGIN + col * (IMG_W + COL_GAP)
108
 
 
109
  pdf.set_xy(x, row_y)
110
  pdf.image(_png(img), w=IMG_W, h=IMG_H)
 
111
 
 
112
  pdf.set_font("Helvetica", style='', size=FS_SMALL)
113
  pdf.set_text_color(*MID_GRAY)
114
  cap = _safe(caption)
 
116
  pdf.text(x + (IMG_W - tw) / 2, row_y + IMG_H + 4, cap)
117
  pdf.set_text_color(*BLACK)
118
 
 
119
  if col == N_COLS - 1 or i == len(items) - 1:
120
  pdf.set_xy(MARGIN, row_y + IMG_H + CAP_H + 4)
121
 
122
 
123
  def _table(pdf: FPDF, metrics: dict):
 
 
 
 
 
124
  lw = USABLE_W * 0.68
125
  vw = USABLE_W * 0.32
126
  rh = 7
127
 
128
  pdf.set_draw_color(*BLACK)
129
 
 
130
  pdf.set_font("Helvetica", style='B', size=FS_SMALL)
131
  pdf.set_text_color(*BLACK)
132
  pdf.cell(lw, rh, "BIOMARKER / SCORE", border=1,
 
134
  pdf.cell(vw, rh, "VALUE", border=1,
135
  new_x='LMARGIN', new_y='NEXT')
136
 
 
137
  for k, v in metrics.items():
138
  pdf.set_font("Helvetica", style='', size=FS_BODY)
139
  pdf.set_text_color(*BLACK)
 
150
  pdf.set_auto_page_break(auto=True, margin=MARGIN)
151
  pdf.add_page()
152
 
 
153
  pdf.set_font("Helvetica", style='B', size=FS_TITLE)
154
  pdf.set_text_color(*DARK_GRAY)
155
  pdf.cell(0, 9, "GlauNET - Glaucoma Screening Report",
 
169
  _hline(pdf)
170
  pdf.set_text_color(*BLACK)
171
 
 
172
  _section(pdf, "Automated Diagnosis", gap_before=5)
173
  decision = data["decision"]
174
  is_positive = "SUSPECTED" in decision
 
176
  pdf.set_text_color(*BLACK)
177
  pdf.cell(0, 9, _safe(decision), align='C', new_x='LMARGIN', new_y='NEXT')
178
 
 
179
  _section(pdf, "Input Fundus Image")
180
 
181
  if pdf.get_y() + INPUT_W + 8 > PAGE_H - MARGIN:
182
  pdf.add_page()
183
 
184
  x_img = MARGIN + (USABLE_W - INPUT_W) / 2
185
+ img_top = pdf.get_y()
186
  pdf.set_xy(x_img, img_top)
187
  pdf.image(_png(data["input_image"]), w=INPUT_W, h=INPUT_W)
 
188
 
189
  pdf.set_font("Helvetica", style='I', size=FS_SMALL)
190
  pdf.set_text_color(*MID_GRAY)
 
193
  img_top + INPUT_W + 4, cap)
194
  pdf.set_text_color(*BLACK)
195
 
 
196
  pdf.set_xy(MARGIN, img_top + INPUT_W + 8)
197
 
 
198
  all_images = list(data["images"].items())
199
  _section(pdf, "Segmentation Pipeline")
200
  _image_grid(pdf, all_images[:5])
201
 
 
202
  _section(pdf, "GradCAM Analysis")
203
  _image_grid(pdf, all_images[5:])
204
 
 
205
  _section(pdf, "Clinical Biomarkers")
206
  _table(pdf, data["metrics"])
207
  pdf.ln(3)
208
 
 
209
  _section(pdf, "MedGemma Clinical Report")
210
  pdf.set_font("Courier", style='', size=FS_MONO)
211
  pdf.set_text_color(*DARK_GRAY)
 
217
  pdf.cell(0, 5, "(No report generated)", new_x='LMARGIN', new_y='NEXT')
218
  pdf.set_text_color(*BLACK)
219
 
 
220
  tmp = tempfile.NamedTemporaryFile(
221
  suffix=".pdf", prefix="glaunet_report_", delete=False
222
  )
models.py CHANGED
@@ -52,7 +52,7 @@ def conv_block(in_ch, out_ch):
52
  )
53
 
54
 
55
- class UNetEfficientNetB5(nn.Module):
56
  def __init__(self, num_classes=3, pretrained=False):
57
  super().__init__()
58
  self.encoder = timm.create_model(
@@ -132,7 +132,7 @@ def load_yolo(path: str):
132
 
133
 
134
  def load_unet(path: str):
135
- model = UNetEfficientNetB5(num_classes=3, pretrained=False).to(DEVICE)
136
  state_dict = torch.load(path, map_location=DEVICE)
137
  model.load_state_dict(state_dict, strict=True)
138
  model.eval()
 
52
  )
53
 
54
 
55
+ class UNetEfficientNetB4(nn.Module):
56
  def __init__(self, num_classes=3, pretrained=False):
57
  super().__init__()
58
  self.encoder = timm.create_model(
 
132
 
133
 
134
  def load_unet(path: str):
135
+ model = UNetEfficientNetB4(num_classes=3, pretrained=False).to(DEVICE)
136
  state_dict = torch.load(path, map_location=DEVICE)
137
  model.load_state_dict(state_dict, strict=True)
138
  model.eval()
pipeline.py CHANGED
@@ -1,4 +1,5 @@
1
  import cv2
 
2
  import numpy as np
3
  import torch
4
  import torchvision.transforms.functional as TF
@@ -178,9 +179,18 @@ def predict_glaucoma(image_path: str, yolo_model, unet_model, eff_model,
178
  si_compliant, si_ratios = compute_si_ratio(od_mask, oc_mask)
179
  nrr_area = compute_nrr_area(od_mask, oc_mask)
180
 
181
- p_holistic = run_efficientnet(image, eff_model, device)
182
-
183
- if seg_failed:
 
 
 
 
 
 
 
 
 
184
  p_fused = p_holistic
185
  else:
186
  p_fused = fusion_model.predict_proba([[vCDR, p_holistic]])[0, 1]
 
1
  import cv2
2
+ import math
3
  import numpy as np
4
  import torch
5
  import torchvision.transforms.functional as TF
 
179
  si_compliant, si_ratios = compute_si_ratio(od_mask, oc_mask)
180
  nrr_area = compute_nrr_area(od_mask, oc_mask)
181
 
182
+ try:
183
+ p_holistic = run_efficientnet(image, eff_model, device)
184
+ eff_failed = math.isnan(p_holistic) or math.isinf(p_holistic)
185
+ except Exception:
186
+ p_holistic = None
187
+ eff_failed = True
188
+
189
+ if seg_failed and eff_failed:
190
+ raise ValueError("Segmentarea și clasificarea holistică au eșuat. Analiza nu poate fi efectuată.")
191
+ elif eff_failed:
192
+ raise ValueError("Clasificarea holistică a eșuat. Analiza nu poate fi efectuată.")
193
+ elif seg_failed:
194
  p_fused = p_holistic
195
  else:
196
  p_fused = fusion_model.predict_proba([[vCDR, p_holistic]])[0, 1]