Mahrufa commited on
Commit
6f38849
·
0 Parent(s):

Initial BioMedVisionLab research workbench

Browse files
Files changed (6) hide show
  1. .gitignore +18 -0
  2. .streamlit/config.toml +5 -0
  3. README.md +399 -0
  4. app.py +0 -0
  5. build_index.py +291 -0
  6. requirements.txt +10 -0
.gitignore ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ data/
2
+ venv/
3
+ __pycache__/
4
+ .streamlit/secrets.toml
5
+
6
+ data/
7
+ embeddings/
8
+ *.pyc
9
+ .DS_Store
10
+
11
+ *.pt
12
+ *.pth
13
+ *.onnx
14
+ *.h5
15
+ *.ckpt
16
+ *.npy
17
+ *.npz
18
+ *.csv
.streamlit/config.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [server]
2
+ maxUploadSize = 500
3
+
4
+ [browser]
5
+ gatherUsageStats = false
README.md ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # BioMedVisionLab
2
+
3
+ **Biomedical Imaging Visualization Toolkit for Retrieval and Super-Resolution**
4
+
5
+ BioMedVisionLab is a polished, professor-facing Streamlit workbench for evaluating biomedical image-like data. It supports visual inspection, embedding comparison, retrieval benchmarking, super-resolution baseline evaluation, future model-output comparison, contact-map visualization, and CSV experiment export.
6
+
7
+ 1. Chest X-ray similarity retrieval visualization
8
+ 2. Biomedical image super-resolution demo
9
+ 3. Grant Alignment Lab for chemical imaging and Hi-C contact-map research directions
10
+
11
+ BioMedVisionLab is upload-first for deployed demonstrations. The app can use a built-in/local CXR embedding index when one is available, but it also works without a local Kaggle dataset by letting users upload biomedical images or contact-map matrices directly in the browser.
12
+
13
+ The project is intended for education, research prototyping, professor-facing demonstrations, and portfolio presentation. It is not intended for medical diagnosis, triage, treatment, or clinical decision-making.
14
+
15
+ ## Overview
16
+
17
+ BioMedVisionLab combines representation learning, medical image retrieval, CXR-specific encoder support, batch retrieval quality measurement, super-resolution baseline evaluation, model-output comparison, Hi-C-like matrix visualization, and report export in one interactive dashboard.
18
+
19
+ - `build_index.py` scans a local chest X-ray dataset, extracts embeddings, and saves searchable encoder-specific indexes.
20
+ - `app.py` launches a Streamlit toolkit with tabs for retrieval, super-resolution visualization, and grant-alignment demos.
21
+
22
+ ## Motivation
23
+
24
+ Biomedical imaging research often needs visual, explainable tools for understanding what models learn and how image quality affects downstream interpretation. This project focuses on showing model behavior and image-processing concepts without making diagnostic claims.
25
+
26
+ ## Module 1: CXR Retrieval
27
+
28
+ The CXR retrieval module visualizes similarity search over a local chest X-ray dataset.
29
+
30
+ Features:
31
+
32
+ - Loads saved embeddings from `embeddings/`
33
+ - Shows a clear **Local CXR index available** or **Upload-only mode** status
34
+ - Supports dataset image queries and uploaded image queries
35
+ - If no saved embeddings are available, supports upload-first retrieval:
36
+ - upload a query image
37
+ - upload a small reference gallery of JPG/PNG/JPEG files
38
+ - build temporary in-session embeddings for the uploaded gallery
39
+ - retrieve visually similar uploaded reference images
40
+ - Uploaded gallery retrieval is temporary and is not saved
41
+ - Uses pretrained torchvision ResNet18 as a lightweight generic ImageNet feature-extraction baseline
42
+ - Adds pretrained torchvision ResNet50 as a deeper generic ImageNet feature-extraction baseline
43
+ - Optionally supports TorchXRayVision DenseNet121 as a CXR-specific embedding encoder when `torchxrayvision` is installed
44
+ - Uses 512-dimensional L2-normalized embeddings for ResNet18, 2048-dimensional L2-normalized embeddings for ResNet50, and DenseNet feature embeddings for TorchXRayVision
45
+ - Stores encoder-specific indexes in `embeddings/resnet18_imagenet/`, `embeddings/resnet50_imagenet/`, and `embeddings/torchxrayvision_densenet121/`
46
+ - Stores metadata for encoder name, embedding dimension, dataset size, build time, and device
47
+ - Lets the app sidebar switch between the generic ImageNet baseline and the CXR-specific encoder when their indexes are available
48
+ - Retrieves top-k visually similar images with cosine similarity
49
+ - Avoids returning the same image as the top result for dataset queries
50
+ - Shows query image, query filename, query label, image resolution, embedding dimension, and query source
51
+ - Uses colored clinical-style labels for `NORMAL`, `PNEUMONIA`, agreement state, and retrieval confidence
52
+ - Includes a compact model card with encoder, embedding dimension, similarity metric, dataset, and clinical status
53
+ - Adds a clinical-style retrieval triage panel for query source, majority retrieved pattern, agreement, similarity range, and review status
54
+ - Reports retrieval quality metrics for dataset-selected queries:
55
+ - Top-k label agreement count
56
+ - Precision@K
57
+ - Mean cosine similarity
58
+ - Shows Top-k sensitivity for Top-3, Top-5, and Top-10 label agreement when the query label is known
59
+ - Shows a retrieval confidence badge using a simple heuristic based on top similarity and, when available, Precision@K
60
+ - Makes clear that retrieval confidence is not clinical or diagnostic confidence
61
+ - Renames retrieved examples as a **Similar Case Board**
62
+ - Shows rank, filename, label badge, cosine similarity, query-label agreement, and a progress bar on each result card
63
+ - Includes a compact result table with rank, filename, label, cosine similarity, and label agreement
64
+ - Keeps compact charts for rank-based similarity scores and retrieved-label distribution
65
+ - Adds a query-vs-retrieved comparison view with query image, selected retrieved image, and a simple absolute difference map
66
+ - Adds an **Intensity Heatmap Preview** for the query image
67
+ - Exports the current retrieval experiment as CSV
68
+ - Adds batch retrieval evaluation for 20, 50, or 100 sampled queries
69
+ - Reports Mean Precision@K, label-specific Precision@K, Top-1 match rate, same-label counts, and retrieved-label distributions
70
+ - Exports a batch retrieval benchmark CSV with one row per retrieved result for comparing encoder experiments
71
+ - Adds an Encoder Comparison Benchmark when both indexes exist, using the same sampled queries to compare Mean Precision@K, class Precision@K, and Top-1 match rate
72
+ - Includes the Research Extension Roadmap in the CXR Retrieval tab only, so retrieval users can explore future encoder, explainability, text, microscopy, diffusion, and genomics directions without repeating the roadmap across every tab
73
+
74
+ The intensity heatmap is a simple grayscale intensity visualization. It is not Grad-CAM and does not provide model-based lesion localization.
75
+ The difference map is a visual comparison aid, not a clinical finding map.
76
+
77
+ ## Module 2: Super-Resolution Demo
78
+
79
+ The super-resolution module demonstrates the basic image degradation and reconstruction problem.
80
+
81
+ Features:
82
+
83
+ - Select an image from the indexed CXR dataset or upload a biomedical image
84
+ - Works in upload-only deployments when no local CXR index exists
85
+ - Simulate low resolution by downsampling by `2x` or `4x`
86
+ - Reconstruct the image back to original size with bicubic interpolation
87
+ - Display original, simulated low-resolution, and bicubic enhanced images side by side
88
+ - Calculate MSE, PSNR, and SSIM when `scikit-image` is available
89
+ - Compare zoomed center crops of the original, low-resolution, and bicubic enhanced images
90
+ - Use a crop-size slider for quick local inspection
91
+ - Includes a baseline model card for degradation, reconstruction, metrics, and clinical status
92
+ - Adds an artifact risk card explaining that bicubic interpolation can smooth fine structures and cannot recover true missing anatomical detail
93
+ - Provides a large preview selector for original, simulated low-resolution, and bicubic enhanced images
94
+ - Supports uploading a model-enhanced image for comparison against the bicubic baseline
95
+ - Shows bicubic vs original and uploaded-output vs original metrics in a comparison table
96
+ - Adds batch super-resolution evaluation for 20, 50, or 100 indexed images
97
+ - Exports batch super-resolution benchmark CSV files for method comparison
98
+ - Exports the current super-resolution experiment as CSV
99
+ - Explains the relevance of throughput/resolution tradeoffs, microscopy/pathology, and future diffusion-based enhancement
100
+ - Adds a **Super-Resolution Exploration Studio** with prompts for uploaded learned outputs, crop-level inspection, metric deltas, and artifact checks
101
+
102
+ This module is a lightweight educational prototype. Bicubic super-resolution is an educational baseline, not a trained AI model or a clinical enhancement method.
103
+
104
+ ## Module 3: Grant Alignment Lab
105
+
106
+ The Grant Alignment Lab frames BioMedVisionLab as a reusable visual evaluation workbench for biomedical imaging and multimodal biology research.
107
+
108
+ Features:
109
+
110
+ - Chemical Imaging Super-Resolution alignment card:
111
+ - low-resolution biomedical/chemical imaging challenge
112
+ - current support for degradation simulation, bicubic baseline, PSNR, SSIM, MSE, crop inspection, and artifact warnings
113
+ - future diffusion-based super-resolution, microscopy/pathology datasets, and SRS/Raman-style image support
114
+ - Hi-C Contact Map Visualization alignment card:
115
+ - sparse, noisy, high-dimensional image-like contact matrices
116
+ - current support for matrix visualization and resolution-enhancement concepts
117
+ - future real Hi-C/Micro-C upload, contact-map retrieval, and RNA-seq/ATAC-seq metadata integration
118
+ - Synthetic Hi-C contact map mini-demo:
119
+ - generates synthetic contact matrices with diagonal structure and loop-like bright spots
120
+ - simulates low-resolution contact maps and bicubic upscaling
121
+ - displays original, low-resolution, upscaled, and absolute difference maps
122
+ - reports Matrix MSE, Matrix PSNR, and optional Matrix SSIM
123
+ - exports the synthetic Hi-C demo report as CSV
124
+ - Upload support for `.npy` or `.csv` square contact matrices:
125
+ - validates square matrices
126
+ - safely normalizes values
127
+ - displays uploaded contact map, simulated low-resolution map, bicubic-upscaled map, and difference map
128
+ - exports Hi-C matrix evaluation metrics as CSV
129
+ - Adds a **Multimodal Exploration Hub** for matrix uploads, low/high-resolution matrix comparison, diagonal profiles, and future metadata links
130
+
131
+ The deployed app can therefore demonstrate radiology image retrieval, biomedical super-resolution baselines, and contact-map matrix visualization without requiring users to download the repository or install the original local dataset.
132
+
133
+ Synthetic contact maps are used only to demonstrate the visualization interface. They are not biological results.
134
+ Uploaded contact maps are visualized as matrix images. This prototype does not yet perform biological feature calling.
135
+
136
+ ## Biomedical Imaging Relevance
137
+
138
+ BioMedVisionLab connects to common biomedical imaging research workflows:
139
+
140
+ - Chest X-ray dataset exploration
141
+ - Visual search and content-based medical image retrieval
142
+ - Representation learning with pretrained vision models
143
+ - Retrieval confidence and uncertainty-style summaries for visual search behavior
144
+ - Clinical-style retrieval triage and Top-k sensitivity checks for research review
145
+ - Comparing generic ImageNet embeddings with CXR-specific embeddings
146
+ - Batch measurement of retrieval quality across repeated query samples
147
+ - Query-vs-retrieved visual comparison with a non-clinical difference map
148
+ - Explainable AI previews and future Grad-CAM visual grounding
149
+ - Image degradation and reconstruction experiments
150
+ - Super-resolution metrics for biomedical imaging baselines
151
+ - Artifact-risk framing for educational super-resolution baselines
152
+ - Comparing future learned or diffusion-based super-resolution outputs against bicubic interpolation
153
+ - Chemical imaging super-resolution grant prototypes
154
+ - Chromatin contact-map visualization and retrieval concepts
155
+ - Multimodal biology workbench patterns for image-like matrices and metadata
156
+ - Educational demonstrations for biomedical AI portfolios
157
+ - Conceptual links to microscopy and pathology super-resolution
158
+
159
+ ## Connection to Core Concepts
160
+
161
+ **Representation learning:** ResNet18, ResNet50, or TorchXRayVision converts images into numerical embeddings that summarize visual patterns.
162
+
163
+ **Image retrieval:** Cosine similarity compares embeddings and ranks images by visual resemblance.
164
+
165
+ **Explainable AI:** The dashboard includes a simple intensity heatmap preview as a non-diagnostic visualization placeholder. Future work can add Grad-CAM or other model-based visual grounding.
166
+
167
+ **Super-resolution:** The demo degrades an image and reconstructs it, illustrating why high-resolution structure matters in biomedical imaging.
168
+
169
+ ## Dataset Instructions
170
+
171
+ Place a small public chest X-ray dataset inside:
172
+
173
+ ```text
174
+ data/chest_xray/
175
+ train/
176
+ NORMAL/
177
+ PNEUMONIA/
178
+ test/
179
+ NORMAL/
180
+ PNEUMONIA/
181
+ val/
182
+ NORMAL/
183
+ PNEUMONIA/
184
+ ```
185
+
186
+ The app also works if only `train/` and `test/` exist.
187
+
188
+ Supported image formats:
189
+
190
+ - `.jpg`
191
+ - `.jpeg`
192
+ - `.png`
193
+
194
+ ## Setup Commands
195
+
196
+ Create and activate a virtual environment:
197
+
198
+ ```bash
199
+ python -m venv venv
200
+ ```
201
+
202
+ On Windows:
203
+
204
+ ```bash
205
+ venv\Scripts\activate
206
+ ```
207
+
208
+ On macOS or Linux:
209
+
210
+ ```bash
211
+ source venv/bin/activate
212
+ ```
213
+
214
+ Install dependencies:
215
+
216
+ ```bash
217
+ pip install -r requirements.txt
218
+ ```
219
+
220
+ Build the CXR retrieval index with the default generic ResNet18 ImageNet encoder:
221
+
222
+ ```bash
223
+ python build_index.py --encoder resnet18_imagenet
224
+ ```
225
+
226
+ Build an optional TorchXRayVision DenseNet121 CXR-specific index:
227
+
228
+ ```bash
229
+ python build_index.py --encoder torchxrayvision_densenet121
230
+ ```
231
+
232
+ Indexes are stored separately:
233
+
234
+ ```text
235
+ embeddings/
236
+ resnet18_imagenet/
237
+ resnet50_imagenet/
238
+ torchxrayvision_densenet121/
239
+ ```
240
+
241
+ Build the deeper generic ResNet50 ImageNet encoder:
242
+
243
+ ```bash
244
+ python build_index.py --encoder resnet50_imagenet
245
+ ```
246
+
247
+ ResNet18 is a lightweight generic natural-image baseline pretrained on ImageNet. ResNet50 is a deeper generic ImageNet baseline with a 2048-dimensional penultimate feature vector. TorchXRayVision DenseNet121 is pretrained for chest X-ray tasks and is included to support research comparisons between generic and CXR-specific visual representations.
248
+
249
+ Run the dashboard:
250
+
251
+ ```bash
252
+ python -m streamlit run app.py
253
+ ```
254
+
255
+ If `streamlit` is available on your PATH, this also works:
256
+
257
+ ```bash
258
+ streamlit run app.py
259
+ ```
260
+
261
+ ## Deployment on Hugging Face Spaces
262
+
263
+ BioMedVisionLab is prepared for Hugging Face Spaces as a Streamlit app. The main entry file is:
264
+
265
+ ```text
266
+ app.py
267
+ ```
268
+
269
+ The Streamlit configuration is stored in:
270
+
271
+ ```text
272
+ .streamlit/config.toml
273
+ ```
274
+
275
+ with a `500 MB` upload limit and browser usage-stat collection disabled.
276
+
277
+ ### Run Locally
278
+
279
+ Install dependencies and start the app:
280
+
281
+ ```bash
282
+ pip install -r requirements.txt
283
+ python -m streamlit run app.py
284
+ ```
285
+
286
+ Local CXR indexes are optional. If no embeddings are present, the app opens in upload-only mode for query/gallery retrieval, image super-resolution, and contact-map visualization.
287
+
288
+ ### Deploy to Hugging Face Spaces
289
+
290
+ 1. Create a new Hugging Face Space.
291
+ 2. Select **Streamlit** as the Space SDK.
292
+ 3. Upload or push the repository files, including:
293
+ - `app.py`
294
+ - `requirements.txt`
295
+ - `.streamlit/config.toml`
296
+ - `README.md`
297
+ 4. Spaces will install dependencies from `requirements.txt` and run the Streamlit app from `app.py`.
298
+ 5. Optional local embedding folders can be added under `embeddings/`, but deployed demos can also run without them through upload-first workflows.
299
+
300
+ ### Deployment Limitations
301
+
302
+ - Uploaded files are temporary and are not saved as a persistent dataset.
303
+ - Large datasets should be uploaded as smaller representative samples.
304
+ - The app is not for diagnosis, triage, treatment, or clinical decision-making.
305
+ - Heavy diffusion-model training or large-scale model inference is not supported in this demo.
306
+ - If TorchXRayVision is unavailable, uploaded-image embedding falls back to ResNet18 and the app shows a warning.
307
+ - If `cooler` is unavailable, `.cool` and `.mcool` upload is disabled with a helpful message; `.csv` and `.npy` contact matrices still work.
308
+
309
+ ## Method Details
310
+
311
+ For CXR retrieval, each image is:
312
+
313
+ 1. Converted to RGB
314
+ 2. Resized to `224x224`
315
+ 3. Normalized with ImageNet mean and standard deviation
316
+ 4. Passed through pretrained ResNet18 with the classifier removed
317
+ 5. Converted into a 512-dimensional embedding
318
+ 6. L2-normalized for cosine similarity search
319
+ 7. Compared with indexed embeddings using cosine similarity
320
+ 8. Summarized with rank-based similarity, label agreement, Precision@K, and mean similarity
321
+ 9. Reviewed with model-card metadata, retrieval triage, Top-k sensitivity, and optional visual comparison
322
+ 10. Exported as a CSV experiment report
323
+
324
+ Batch retrieval evaluation samples labeled dataset queries, retrieves Top-k neighbors while excluding the query image, and exports one row per retrieved result. This makes CSV exports useful for comparing generic and CXR-specific encoders.
325
+
326
+ Retrieval confidence is a heuristic:
327
+
328
+ - High confidence for dataset queries if top similarity is at least `0.90` and Precision@K is at least `0.80`
329
+ - Medium confidence if top similarity is at least `0.80`
330
+ - Low confidence otherwise
331
+ - Uploaded images use top similarity only because query-label agreement is unavailable
332
+
333
+ For super-resolution, each image is:
334
+
335
+ 1. Converted safely to RGB
336
+ 2. Downsampled by `2x` or `4x`
337
+ 3. Reconstructed to original size using bicubic interpolation
338
+ 4. Compared against the original image using MSE, PSNR, and optional SSIM
339
+ 5. Displayed with center-crop comparisons for local detail inspection
340
+ 6. Reviewed with artifact-risk and baseline-method cards
341
+ 7. Exported as a CSV experiment report
342
+
343
+ ChemDiffuse-style comparison allows an externally generated enhanced image to be uploaded and compared against the original image and bicubic baseline using PSNR, SSIM, and MSE.
344
+
345
+ For the synthetic Hi-C mini-demo:
346
+
347
+ 1. A synthetic square contact matrix is generated with diagonal structure and loop-like bright spots.
348
+ 2. Noise is added using the dashboard slider.
349
+ 3. The matrix is downsampled by `2x` or `4x`.
350
+ 4. The low-resolution matrix is bicubic-upscaled to the original matrix size.
351
+ 5. Matrix MSE, Matrix PSNR, and optional Matrix SSIM are reported.
352
+ 6. The demo can be exported as a CSV report.
353
+
354
+ ## Limitations
355
+
356
+ - ResNet18 and ResNet50 are pretrained on natural images, not chest X-rays.
357
+ - TorchXRayVision support requires optional dependency installation and a separately built index.
358
+ - Retrieval results show visual similarity, not clinical equivalence.
359
+ - Retrieval confidence is not clinical confidence.
360
+ - Retrieval triage is a research dashboard summary, not clinical triage.
361
+ - Query-vs-retrieved difference maps are visual comparison aids, not finding maps.
362
+ - Folder labels are used only for visualization.
363
+ - The intensity heatmap is not Grad-CAM and does not identify pathology.
364
+ - Bicubic interpolation is a simple baseline, not a learned super-resolution model.
365
+ - Bicubic interpolation may smooth fine detail and cannot recover true missing anatomical structure.
366
+ - Synthetic Hi-C contact maps are interface demonstrations only and are not biological results.
367
+ - Uploaded Hi-C/contact matrices are visualized as matrix images and are not biologically interpreted.
368
+ - PSNR is a basic image-quality metric and does not measure clinical usefulness.
369
+ - SSIM is an image-similarity metric and does not measure clinical usefulness.
370
+ - The project should not be used for diagnosis, triage, treatment, or clinical decision-making.
371
+
372
+ ## CXR Retrieval Research Extension Roadmap
373
+
374
+ The Research Extension Roadmap is shown only in the CXR Retrieval tab. The Super-Resolution and Grant Alignment tabs have their own exploration sections instead of repeating the same roadmap.
375
+
376
+ - CXR-specific encoders such as TorchXRayVision or medical foundation models
377
+ - Grad-CAM visual grounding
378
+ - Report-aware retrieval using radiology text
379
+ - Microscopy/pathology super-resolution datasets
380
+ - Diffusion-based biomedical image enhancement
381
+ - Learned super-resolution models such as SRCNN, EDSR, or SwinIR variants
382
+ - Metadata-aware retrieval filters
383
+ - Hi-C contact map visualization as a future biomedical data visualization extension
384
+ - Real Hi-C/Micro-C upload, contact-map retrieval, and omics metadata integration
385
+ - Chemical imaging super-resolution with SRS/Raman-style data
386
+ - Grant-ready experiment report templates
387
+
388
+ ## Disclaimer
389
+
390
+ BioMedVisionLab is a research visualization prototype and is not intended for medical diagnosis or clinical decision-making.
391
+
392
+ - Retrieval confidence is not diagnostic or clinical confidence.
393
+ - The intensity heatmap preview is not Grad-CAM.
394
+ - Bicubic super-resolution is an educational baseline, not a trained AI model.
395
+ - Synthetic Hi-C contact maps are not biological results.
396
+
397
+ ## Suggested LinkedIn Project Description
398
+
399
+ Built **BioMedVisionLab**, a reusable biomedical visual evaluation workbench in Python and Streamlit. The app includes chest X-ray similarity retrieval using pretrained ResNet18, ResNet50, and optional TorchXRayVision embeddings with cosine similarity, retrieval triage, Top-k sensitivity, query-vs-result comparison, experiment report export, an educational super-resolution demo with MSE, PSNR, SSIM, artifact-risk framing, and a Grant Alignment Lab with a synthetic Hi-C contact-map mini-demo. The project highlights medical image retrieval, chemical imaging super-resolution, explainable AI, and chromatin contact-map visualization concepts for research and portfolio demonstration, not clinical diagnosis or biological interpretation.
app.py ADDED
The diff for this file is too large to render. See raw diff
 
build_index.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ from datetime import datetime, timezone
4
+ from pathlib import Path
5
+
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ from PIL import Image, UnidentifiedImageError
10
+ from torchvision import models, transforms
11
+
12
+ try:
13
+ import torchxrayvision as xrv
14
+ except ImportError:
15
+ xrv = None
16
+
17
+
18
+ DATA_DIR = Path("data/chest_xray")
19
+ EMBEDDINGS_ROOT = Path("embeddings")
20
+ ENCODER_TYPE = "resnet18_imagenet"
21
+ VALID_EXTENSIONS = {".jpg", ".jpeg", ".png"}
22
+ VALID_LABELS = {"NORMAL", "PNEUMONIA"}
23
+
24
+ IMAGENET_MEAN = [0.485, 0.456, 0.406]
25
+ IMAGENET_STD = [0.229, 0.224, 0.225]
26
+
27
+
28
+ class EncoderBundle:
29
+ def __init__(self, key: str, name: str, model: nn.Module, transform: transforms.Compose, output_dim: int):
30
+ self.key = key
31
+ self.name = name
32
+ self.model = model
33
+ self.transform = transform
34
+ self.output_dim = output_dim
35
+
36
+
37
+ ENCODER_CONFIGS = {
38
+ "resnet18_imagenet": {
39
+ "name": "ResNet18 ImageNet baseline",
40
+ "output_dim": 512,
41
+ },
42
+ "resnet50_imagenet": {
43
+ "name": "ResNet50 ImageNet baseline",
44
+ "output_dim": 2048,
45
+ },
46
+ "torchxrayvision_densenet121": {
47
+ "name": "TorchXRayVision DenseNet121 CXR encoder",
48
+ "output_dim": 1024,
49
+ },
50
+ }
51
+
52
+
53
+ def canonical_encoder_name(encoder_name: str) -> str:
54
+ aliases = {
55
+ "resnet18": "resnet18_imagenet",
56
+ "resnet50": "resnet50_imagenet",
57
+ "torchxrayvision": "torchxrayvision_densenet121",
58
+ }
59
+ return aliases.get(encoder_name, encoder_name)
60
+
61
+
62
+ def find_image_files(data_dir: Path) -> list[Path]:
63
+ """Find supported image files that live under NORMAL or PNEUMONIA folders."""
64
+ image_files = []
65
+
66
+ for path in data_dir.rglob("*"):
67
+ if not path.is_file() or path.suffix.lower() not in VALID_EXTENSIONS:
68
+ continue
69
+
70
+ if infer_label(path) in VALID_LABELS:
71
+ image_files.append(path)
72
+
73
+ return sorted(image_files)
74
+
75
+
76
+ def infer_label(image_path: Path) -> str:
77
+ """Infer a chest X-ray label from any parent folder named NORMAL or PNEUMONIA."""
78
+ for parent in image_path.parents:
79
+ label = parent.name.upper()
80
+ if label in VALID_LABELS:
81
+ return label
82
+ return "UNKNOWN"
83
+
84
+
85
+ def get_resnet_transform() -> transforms.Compose:
86
+ return transforms.Compose(
87
+ [
88
+ transforms.Resize((224, 224)),
89
+ transforms.Grayscale(num_output_channels=3),
90
+ transforms.ToTensor(),
91
+ transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
92
+ ]
93
+ )
94
+
95
+
96
+ def get_xrv_transform() -> transforms.Compose:
97
+ return transforms.Compose(
98
+ [
99
+ transforms.Resize((224, 224)),
100
+ transforms.Grayscale(num_output_channels=1),
101
+ transforms.ToTensor(),
102
+ transforms.Lambda(lambda tensor: (tensor * 2048.0) - 1024.0),
103
+ ]
104
+ )
105
+
106
+
107
+ def load_encoder(encoder_name: str, device: torch.device | None = None) -> EncoderBundle:
108
+ encoder_name = canonical_encoder_name(encoder_name)
109
+ device = device or torch.device("cuda" if torch.cuda.is_available() else "cpu")
110
+ if encoder_name == "torchxrayvision_densenet121":
111
+ if xrv is None:
112
+ raise ImportError(
113
+ "torchxrayvision is not installed. Install requirements or use --encoder resnet18_imagenet."
114
+ )
115
+ model = xrv.models.DenseNet(weights="densenet121-res224-all")
116
+ model.op_threshs = None
117
+ model.to(device)
118
+ model.eval()
119
+ return EncoderBundle(
120
+ key="torchxrayvision_densenet121",
121
+ name="TorchXRayVision DenseNet121 CXR encoder",
122
+ model=model,
123
+ transform=get_xrv_transform(),
124
+ output_dim=1024,
125
+ )
126
+
127
+ if encoder_name == "resnet50_imagenet":
128
+ weights = models.ResNet50_Weights.DEFAULT
129
+ model = models.resnet50(weights=weights)
130
+ output_dim = 2048
131
+ encoder_label = "ResNet50 ImageNet baseline"
132
+ else:
133
+ weights = models.ResNet18_Weights.DEFAULT
134
+ model = models.resnet18(weights=weights)
135
+ output_dim = 512
136
+ encoder_label = "ResNet18 ImageNet baseline"
137
+ model.fc = nn.Identity()
138
+ model.to(device)
139
+ model.eval()
140
+ return EncoderBundle(
141
+ key=encoder_name,
142
+ name=encoder_label,
143
+ model=model,
144
+ transform=get_resnet_transform(),
145
+ output_dim=output_dim,
146
+ )
147
+
148
+
149
+ def preprocess_image_for_encoder(image: Image.Image, encoder_name: str) -> torch.Tensor:
150
+ encoder_name = canonical_encoder_name(encoder_name)
151
+ transform = get_xrv_transform() if encoder_name == "torchxrayvision_densenet121" else get_resnet_transform()
152
+ return transform(image).unsqueeze(0)
153
+
154
+
155
+ def load_rgb_image(image_path: Path) -> Image.Image:
156
+ """Open a chest X-ray and convert grayscale images to 3-channel RGB."""
157
+ return Image.open(image_path).convert("RGB")
158
+
159
+
160
+ def extract_embedding(
161
+ image: Image.Image,
162
+ encoder_name: str,
163
+ encoder: EncoderBundle | None = None,
164
+ device: torch.device | None = None,
165
+ ) -> np.ndarray:
166
+ """Extract one L2-normalized embedding."""
167
+ encoder_name = canonical_encoder_name(encoder_name)
168
+ device = device or torch.device("cuda" if torch.cuda.is_available() else "cpu")
169
+ encoder = encoder or load_encoder(encoder_name, device)
170
+ image_tensor = preprocess_image_for_encoder(image, encoder_name).to(device)
171
+
172
+ with torch.no_grad():
173
+ if encoder_name == "torchxrayvision_densenet121":
174
+ features = encoder.model.features(image_tensor)
175
+ features = torch.nn.functional.relu(features, inplace=False)
176
+ features = torch.nn.functional.adaptive_avg_pool2d(features, (1, 1))
177
+ embedding = torch.flatten(features, 1).cpu().numpy()[0]
178
+ else:
179
+ embedding = encoder.model(image_tensor).cpu().numpy()[0]
180
+
181
+ norm = np.linalg.norm(embedding)
182
+ if norm > 0:
183
+ embedding = embedding / norm
184
+
185
+ return embedding.astype(np.float32)
186
+
187
+
188
+ def extract_embedding_from_path(
189
+ image_path: Path,
190
+ encoder: EncoderBundle,
191
+ device: torch.device,
192
+ ) -> np.ndarray | None:
193
+ try:
194
+ image = load_rgb_image(image_path)
195
+ except (OSError, UnidentifiedImageError) as error:
196
+ print(f"Skipping unreadable image: {image_path} ({error})")
197
+ return None
198
+ return extract_embedding(image, encoder.key, encoder, device)
199
+
200
+
201
+ def encoder_feature_note(encoder_key: str) -> str:
202
+ if encoder_key == "torchxrayvision_densenet121":
203
+ return "TorchXRayVision DenseNet121 intermediate convolutional features with global average pooling."
204
+ if encoder_key == "resnet50_imagenet":
205
+ return "ResNet50 penultimate 2048-D feature vector."
206
+ return "ResNet18 penultimate 512-D feature vector."
207
+
208
+
209
+ def build_index(encoder_type: str) -> None:
210
+ encoder_type = canonical_encoder_name(encoder_type)
211
+ if not DATA_DIR.exists():
212
+ raise FileNotFoundError(
213
+ "Dataset folder was not found. Please place images under data/chest_xray/ "
214
+ "with folders such as train/NORMAL and train/PNEUMONIA."
215
+ )
216
+
217
+ image_files = find_image_files(DATA_DIR)
218
+ if not image_files:
219
+ raise ValueError(
220
+ "No JPG, JPEG, or PNG images were found under data/chest_xray inside "
221
+ "NORMAL or PNEUMONIA folders."
222
+ )
223
+
224
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
225
+ print(f"Using device: {device}")
226
+
227
+ encoder = load_encoder(encoder_type, device)
228
+ print(f"Using encoder: {encoder.name}")
229
+
230
+ indexed_paths = []
231
+ labels = []
232
+ embeddings = []
233
+
234
+ for image_path in image_files:
235
+ embedding = extract_embedding_from_path(image_path, encoder, device)
236
+ if embedding is None:
237
+ continue
238
+
239
+ indexed_paths.append(str(image_path))
240
+ labels.append(infer_label(image_path))
241
+ embeddings.append(embedding)
242
+
243
+ if not embeddings:
244
+ raise ValueError("Images were found, but none could be read successfully.")
245
+
246
+ output_dir = EMBEDDINGS_ROOT / encoder.key
247
+ output_dir.mkdir(parents=True, exist_ok=True)
248
+ np.save(output_dir / "image_paths.npy", np.array(indexed_paths, dtype=object))
249
+ np.save(output_dir / "labels.npy", np.array(labels, dtype=object))
250
+ np.save(output_dir / "embeddings.npy", np.vstack(embeddings).astype(np.float32))
251
+
252
+ metadata = {
253
+ "encoder_key": encoder.key,
254
+ "encoder_name": encoder.name,
255
+ "embedding_dimension": int(np.vstack(embeddings).shape[1]),
256
+ "dataset_size": len(indexed_paths),
257
+ "build_time": datetime.now(timezone.utc).isoformat(),
258
+ "device": str(device),
259
+ "feature_note": encoder_feature_note(encoder.key),
260
+ }
261
+ (output_dir / "metadata.json").write_text(json.dumps(metadata, indent=2), encoding="utf-8")
262
+
263
+ print(f"Indexed {len(indexed_paths)} images.")
264
+ print(f"Saved embeddings to: {output_dir.resolve()}")
265
+
266
+
267
+ def parse_args() -> argparse.Namespace:
268
+ parser = argparse.ArgumentParser(description="Build BioMedVisionLab CXR embedding index.")
269
+ parser.add_argument(
270
+ "--encoder",
271
+ choices=[
272
+ "resnet18_imagenet",
273
+ "resnet50_imagenet",
274
+ "torchxrayvision_densenet121",
275
+ "resnet18",
276
+ "resnet50",
277
+ "torchxrayvision",
278
+ ],
279
+ default=ENCODER_TYPE,
280
+ help="Encoder used to build embeddings.",
281
+ )
282
+ return parser.parse_args()
283
+
284
+
285
+ if __name__ == "__main__":
286
+ args = parse_args()
287
+ try:
288
+ build_index(args.encoder)
289
+ except (FileNotFoundError, ValueError, ImportError) as error:
290
+ print(f"Error: {error}")
291
+ raise SystemExit(1)
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ torch
3
+ torchvision
4
+ Pillow
5
+ numpy
6
+ pandas
7
+ scikit-image
8
+ altair
9
+ torchxrayvision
10
+ cooler