Spaces:
Running
Running
File size: 7,133 Bytes
7ef2f88 13a9d7d 7ef2f88 294d14f 13a9d7d 7ef2f88 13a9d7d 7ef2f88 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 7ef2f88 13a9d7d 7ef2f88 13a9d7d 7ef2f88 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 7ef2f88 13a9d7d 7ef2f88 13a9d7d 294d14f 13a9d7d 294d14f 7ef2f88 13a9d7d 294d14f 7ef2f88 13a9d7d 294d14f 13a9d7d 294d14f 7ef2f88 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 13a9d7d 7ef2f88 13a9d7d 7ef2f88 13a9d7d 294d14f 13a9d7d 294d14f 7ef2f88 294d14f 13a9d7d 294d14f 13a9d7d 294d14f 7ef2f88 13a9d7d 294d14f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
import streamlit as st
from typing import Optional, Tuple, Dict, Any, List, Union
import pydicom
import pydicom.valuerep
import numpy as np
import logging
logger = logging.getLogger(__name__)
# --- DICOM Metadata Display ---
def display_dicom_metadata(metadata: Optional[Dict[str, Any]]) -> None:
"""
Displays formatted DICOM metadata in a Streamlit expander, arranged in two columns.
Args:
metadata: A dictionary containing DICOM tags (keys) and their values.
Handles basic formatting for lists, UIDs, bytes, and sensitive types.
If None or empty, displays a placeholder message.
"""
with st.expander("View DICOM Metadata", expanded=False):
if not metadata:
st.caption("No metadata extracted or available.")
return
cols = st.columns(2)
col_idx = 0
logger.debug(f"Displaying {len(metadata)} metadata items.")
for key, value in metadata.items():
display_value = "N/A" # Default display value
try:
if value is None:
display_value = "N/A"
elif isinstance(value, list):
display_value = ", ".join(map(str, value))
elif isinstance(value, pydicom.uid.UID):
display_value = value.name
elif isinstance(value, bytes):
display_value = f"[Binary Data ({len(value)} bytes)]"
elif isinstance(value, pydicom.valuerep.PersonName):
# Mask sensitive information or display a placeholder.
display_value = "[Person Name]"
else:
display_value = str(value).strip()
# Truncate very long strings to improve readability.
if len(display_value) > 150:
display_value = display_value[:147] + "..."
except Exception as e:
logger.warning(f"Error formatting metadata key '{key}': {e}", exc_info=True)
display_value = "[Error formatting value]"
# Alternate between the two columns.
cols[col_idx % 2].markdown(f"**{key}:** {display_value}")
col_idx += 1
# --- DICOM Window/Level Sliders ---
def dicom_wl_sliders(
ds: Optional[pydicom.Dataset],
metadata: Dict[str, Any]
) -> Tuple[Optional[float], Optional[float]]:
"""
Creates Streamlit sliders for adjusting DICOM Window Center (Level) and Width.
Derives slider ranges and default values from the dataset's pixel data and metadata.
Provides a "Reset W/L" button that reruns the app to restore default values.
Args:
ds: The pydicom Dataset object (must contain PixelData).
metadata: Dictionary containing extracted metadata, used for default window/level values.
Returns:
A tuple (window_center, window_width) as floats.
Returns (None, None) if sliders cannot be created.
"""
st.subheader("DICOM Window/Level Adjustment")
if ds is None or 'PixelData' not in ds:
st.caption("Cannot create W/L sliders: DICOM data or PixelData missing.")
logger.warning("dicom_wl_sliders called with missing Dataset or PixelData.")
return None, None
# --- Determine Pixel Range ---
pixel_min: float = 0.0
pixel_max: float = 4095.0 # Default fallback range
try:
pixel_array = ds.pixel_array
if 'RescaleSlope' in ds and 'RescaleIntercept' in ds:
slope = float(ds.RescaleSlope)
intercept = float(ds.RescaleIntercept)
logger.debug(f"Applying Rescale Slope ({slope}) / Intercept ({intercept}) for range calculation.")
rescaled_array = pixel_array.astype(np.float64) * slope + intercept
pixel_min = float(rescaled_array.min())
pixel_max = float(rescaled_array.max())
else:
pixel_min = float(pixel_array.min())
pixel_max = float(pixel_array.max())
logger.info(f"Determined pixel value range: Min={pixel_min}, Max={pixel_max}")
# Avoid zero-width range.
if pixel_max == pixel_min:
logger.warning("Pixel data range is zero (constant image). Adjusting range.")
pixel_max += 1.0
except Exception as e:
st.caption(f"Could not determine pixel range (Error: {e}). Using default range.")
logger.error(f"Error determining pixel range for sliders: {e}", exc_info=True)
# --- Get and Validate Default Window/Level from Metadata ---
def safe_float_convert(value: Any) -> Optional[float]:
"""Safely converts a value (or first element of a list) to float."""
if isinstance(value, (list, pydicom.multival.MultiValue)):
val_to_convert = value[0] if len(value) > 0 else None
else:
val_to_convert = value
try:
return float(val_to_convert) if val_to_convert is not None else None
except (ValueError, TypeError):
return None
default_wc_raw = metadata.get("WindowCenter", None)
default_ww_raw = metadata.get("WindowWidth", None)
default_wc: Optional[float] = safe_float_convert(default_wc_raw)
default_ww: Optional[float] = safe_float_convert(default_ww_raw)
calculated_center = (pixel_max + pixel_min) / 2.0
calculated_width = max(1.0, (pixel_max - pixel_min) * 0.8)
if default_wc is None:
default_wc = calculated_center
logger.debug(f"Using calculated default Window Center: {default_wc:.2f}")
if default_ww is None or default_ww <= 0:
default_ww = calculated_width
logger.debug(f"Using calculated default Window Width: {default_ww:.2f}")
logger.info(f"Slider defaults - WC: {default_wc:.2f}, WW: {default_ww:.2f}")
# --- Calculate Slider Bounds ---
data_range = pixel_max - pixel_min
slider_min_level = pixel_min - data_range * 0.5 # Extend 50% below minimum
slider_max_level = pixel_max + data_range * 0.5 # Extend 50% above maximum
slider_max_width = min(max(1.0, data_range * 2.0), 65536.0) # Cap maximum width
clamped_default_wc = max(slider_min_level, min(slider_max_level, default_wc))
clamped_default_ww = max(1.0, min(slider_max_width, default_ww))
# --- Create Sliders ---
wc = st.slider(
"Window Center (Level)",
min_value=slider_min_level,
max_value=slider_max_level,
value=clamped_default_wc,
step=max(0.1, data_range / 1000.0),
key="dicom_wc_slider",
help=f"Adjust brightness center. Range: [{pixel_min:.1f} - {pixel_max:.1f}]"
)
ww = st.slider(
"Window Width",
min_value=1.0,
max_value=slider_max_width,
value=clamped_default_ww,
step=max(0.1, data_range / 1000.0),
key="dicom_ww_slider",
help=f"Adjust contrast range. Data range: {data_range:.1f}"
)
# --- Reset Button ---
if st.button("Reset W/L", key="reset_wl_button"):
logger.info("Reset W/L button clicked. Rerunning to apply default values.")
st.rerun()
return float(wc), float(ww)
|