Commit ·
1fa8091
1
Parent(s): ead1826
add gif via LFS and update README/app
Browse files- .gitattributes +1 -0
- README.md +26 -26
- app.py +1 -1
- screenshots/demo_overview.gif +3 -0
- utils.py +1 -1
.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 |
+
screenshots/*.gif filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -10,17 +10,23 @@ pinned: false
|
|
| 10 |
|
| 11 |
# Image Quantization and CT Windowing Explorer
|
| 12 |
|
| 13 |
-
An interactive Streamlit app for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
## Project Summary
|
| 16 |
|
| 17 |
-
This app
|
| 18 |
|
| 19 |
-
1. Quantization reduces the number of
|
| 20 |
-
2. Windowing
|
| 21 |
|
| 22 |
-
|
| 23 |
-
In the histogram view, the overlaid window bounds act as visual reference markers, while the plotted distributions are intended to highlight quantization effects on stored intensities.
|
| 24 |
|
| 25 |
## Local Run
|
| 26 |
|
|
@@ -57,30 +63,28 @@ sample_images/builtin_samples/
|
|
| 57 |
docs/
|
| 58 |
requirements.txt
|
| 59 |
Makefile
|
|
|
|
| 60 |
```
|
| 61 |
|
| 62 |
## Features
|
| 63 |
|
| 64 |
-
- Built-in
|
| 65 |
-
-
|
| 66 |
-
-
|
| 67 |
-
-
|
| 68 |
-
-
|
| 69 |
-
-
|
| 70 |
-
-
|
| 71 |
-
-
|
| 72 |
-
-
|
| 73 |
-
-
|
| 74 |
-
- Intensity histogram with window overlay; the overlay marks the chosen window, while the distribution view focuses on quantization effects
|
| 75 |
-
- Squared-error map for the currently selected comparison
|
| 76 |
-
- Metrics: MSE, PSNR, gray levels used, and entropy
|
| 77 |
-
- In-app explanatory notes for every interactive control
|
| 78 |
|
| 79 |
## Known Limitations
|
| 80 |
|
| 81 |
- The app is educational and not intended for clinical interpretation.
|
| 82 |
- Uploaded PNG/JPG images may already be restricted to 8-bit precision.
|
| 83 |
- The current version works on one 2D slice at a time.
|
|
|
|
| 84 |
- The CT-RATE built-in sample is rotated 90 degrees clockwise in the app to match the expected viewing orientation.
|
| 85 |
- Public deployment should still respect the original dataset licenses and redistribution terms for bundled sample content.
|
| 86 |
|
|
@@ -88,8 +92,8 @@ Makefile
|
|
| 88 |
|
| 89 |
- **Input source** switches between prepared built-in examples and your own uploaded data.
|
| 90 |
- **Built-in sample** lets you compare different CT-oriented datasets quickly.
|
| 91 |
-
- **Slice index** appears for volumetric inputs and chooses the active axial slice.
|
| 92 |
-
- **Rotate uploaded image** appears for uploaded DICOM
|
| 93 |
- **Quantization bit depth** controls how many gray levels remain after uniform quantization.
|
| 94 |
- The app initializes the quantization display at 4 bits by default, unless the loaded image has a lower maximum bit depth.
|
| 95 |
- **CT window preset** applies common lower/upper bound pairs such as lung or mediastinal ranges.
|
|
@@ -98,10 +102,6 @@ Makefile
|
|
| 98 |
- **Visualization arrows** can be clicked to choose which pair of panels should be compared.
|
| 99 |
- **Metrics → Compare** is synchronized with the arrow graph and updates all downstream plots.
|
| 100 |
|
| 101 |
-
## Screenshots / GIF
|
| 102 |
-
|
| 103 |
-
Add screenshots here after the UI is finalized.
|
| 104 |
-
|
| 105 |
## Design Notes
|
| 106 |
|
| 107 |
See [docs/design_choices.md](docs/design_choices.md).
|
|
|
|
| 10 |
|
| 11 |
# Image Quantization and CT Windowing Explorer
|
| 12 |
|
| 13 |
+
An interactive Streamlit app for exploring how quantization and CT-style windowing change grayscale medical image display.
|
| 14 |
+
|
| 15 |
+
## Screenshots / GIF
|
| 16 |
+
|
| 17 |
+
### Main app overview
|
| 18 |
+
|
| 19 |
+

|
| 20 |
+
|
| 21 |
|
| 22 |
## Project Summary
|
| 23 |
|
| 24 |
+
This app compares two related but different operations on grayscale medical images:
|
| 25 |
|
| 26 |
+
1. Quantization reduces the number of stored gray levels and can introduce banding or reconstruction error.
|
| 27 |
+
2. Windowing changes only how a chosen intensity range is displayed and clips values outside that range.
|
| 28 |
|
| 29 |
+
The interface lets users adjust bit depth and window bounds, then compare four image states, a histogram, an error map, and summary metrics side by side.
|
|
|
|
| 30 |
|
| 31 |
## Local Run
|
| 32 |
|
|
|
|
| 63 |
docs/
|
| 64 |
requirements.txt
|
| 65 |
Makefile
|
| 66 |
+
Dockerfile
|
| 67 |
```
|
| 68 |
|
| 69 |
## Features
|
| 70 |
|
| 71 |
+
- Built-in CT-oriented examples from CT-RATE, LDCT-and-Projection-data, and RSNA PE
|
| 72 |
+
- Upload support for `PNG`, `JPG`, `JPEG`, and single-slice `DICOM`
|
| 73 |
+
- Automatic handling of grayscale images, including 3-channel grayscale copies
|
| 74 |
+
- Adjustable quantization bit depth and CT-style window bounds
|
| 75 |
+
- Slice selection for volumetric built-in samples
|
| 76 |
+
- Rotation control for uploaded DICOM slices
|
| 77 |
+
- Four synchronized views: original, windowed, quantized, and quantized + windowed
|
| 78 |
+
- Histogram, squared-error map, and summary metrics (`MSE`, `PSNR`, gray levels, entropy)
|
| 79 |
+
- Clickable comparison arrows synchronized with the metrics selector
|
| 80 |
+
- In-app explanatory notes for each main control
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
## Known Limitations
|
| 83 |
|
| 84 |
- The app is educational and not intended for clinical interpretation.
|
| 85 |
- Uploaded PNG/JPG images may already be restricted to 8-bit precision.
|
| 86 |
- The current version works on one 2D slice at a time.
|
| 87 |
+
- Uploaded files are limited to PNG/JPG and single-slice DICOM in the current app; NIfTI upload is not enabled in the active UI.
|
| 88 |
- The CT-RATE built-in sample is rotated 90 degrees clockwise in the app to match the expected viewing orientation.
|
| 89 |
- Public deployment should still respect the original dataset licenses and redistribution terms for bundled sample content.
|
| 90 |
|
|
|
|
| 92 |
|
| 93 |
- **Input source** switches between prepared built-in examples and your own uploaded data.
|
| 94 |
- **Built-in sample** lets you compare different CT-oriented datasets quickly.
|
| 95 |
+
- **Slice index** appears for volumetric built-in inputs and chooses the active axial slice.
|
| 96 |
+
- **Rotate uploaded image** appears for uploaded DICOM data when the slice orientation needs correction.
|
| 97 |
- **Quantization bit depth** controls how many gray levels remain after uniform quantization.
|
| 98 |
- The app initializes the quantization display at 4 bits by default, unless the loaded image has a lower maximum bit depth.
|
| 99 |
- **CT window preset** applies common lower/upper bound pairs such as lung or mediastinal ranges.
|
|
|
|
| 102 |
- **Visualization arrows** can be clicked to choose which pair of panels should be compared.
|
| 103 |
- **Metrics → Compare** is synchronized with the arrow graph and updates all downstream plots.
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
## Design Notes
|
| 106 |
|
| 107 |
See [docs/design_choices.md](docs/design_choices.md).
|
app.py
CHANGED
|
@@ -143,7 +143,7 @@ def maybe_reset_controls(loaded_key: str, default_low: float, default_high: floa
|
|
| 143 |
return
|
| 144 |
|
| 145 |
max_bits = int(st.session_state.get("bit_depth_max", 12))
|
| 146 |
-
bit_depth = min(
|
| 147 |
st.session_state["loaded_key"] = loaded_key
|
| 148 |
st.session_state["bit_depth"] = bit_depth
|
| 149 |
st.session_state["bit_depth_slider"] = bit_depth
|
|
|
|
| 143 |
return
|
| 144 |
|
| 145 |
max_bits = int(st.session_state.get("bit_depth_max", 12))
|
| 146 |
+
bit_depth = min(6, max_bits)
|
| 147 |
st.session_state["loaded_key"] = loaded_key
|
| 148 |
st.session_state["bit_depth"] = bit_depth
|
| 149 |
st.session_state["bit_depth_slider"] = bit_depth
|
screenshots/demo_overview.gif
ADDED
|
Git LFS Details
|
utils.py
CHANGED
|
@@ -432,7 +432,7 @@ def create_histogram_figure(
|
|
| 432 |
ax.set_ylabel("Count")
|
| 433 |
ax.set_title(label, fontsize=10, loc="left")
|
| 434 |
ax.legend(loc="upper right", fontsize=8)
|
| 435 |
-
axes[-1].set_xlabel("Intensity")
|
| 436 |
fig.suptitle("Intensity Histogram Comparison", fontsize=12)
|
| 437 |
fig.tight_layout()
|
| 438 |
return fig
|
|
|
|
| 432 |
ax.set_ylabel("Count")
|
| 433 |
ax.set_title(label, fontsize=10, loc="left")
|
| 434 |
ax.legend(loc="upper right", fontsize=8)
|
| 435 |
+
axes[-1].set_xlabel("Intensity/HU Value")
|
| 436 |
fig.suptitle("Intensity Histogram Comparison", fontsize=12)
|
| 437 |
fig.tight_layout()
|
| 438 |
return fig
|