File size: 12,277 Bytes
7280d57
ca80b7d
4a3e941
ca80b7d
7280d57
 
2d84aa6
7280d57
2d84aa6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7280d57
 
 
 
 
 
 
 
2d84aa6
 
79e9544
7280d57
ca80b7d
 
7280d57
 
 
ca80b7d
7280d57
ca80b7d
 
 
7280d57
 
 
ca80b7d
7280d57
2d84aa6
 
7280d57
 
 
 
 
 
 
79e9544
ca80b7d
 
 
7280d57
ca80b7d
2d84aa6
7280d57
ca80b7d
 
7280d57
ca80b7d
 
 
 
79e9544
7280d57
 
 
5f571dd
2d84aa6
7280d57
2d84aa6
7280d57
1637463
7280d57
f1fe858
ca80b7d
 
 
 
7280d57
2d84aa6
 
 
 
4a3e941
ca80b7d
7280d57
 
a5a89e3
 
2d84aa6
ca80b7d
7280d57
ca80b7d
7280d57
ca80b7d
 
 
7280d57
ca80b7d
2d84aa6
 
ca80b7d
 
2d84aa6
ca80b7d
 
 
 
7280d57
ca80b7d
2d84aa6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca80b7d
 
 
 
 
 
2d84aa6
ca80b7d
 
7280d57
ca80b7d
 
 
 
4a3e941
ca80b7d
 
7280d57
 
2d84aa6
 
a5a89e3
7280d57
 
a5a89e3
 
 
 
 
 
 
 
 
7280d57
 
2d84aa6
702516c
7280d57
 
ca80b7d
7280d57
ca80b7d
2d84aa6
 
7280d57
 
 
 
 
 
 
 
a5a89e3
 
7280d57
a5a89e3
7280d57
 
a5a89e3
 
7280d57
 
 
a5a89e3
 
 
7280d57
 
a5a89e3
7280d57
a5a89e3
ca80b7d
7280d57
 
a5a89e3
7280d57
ca80b7d
7280d57
4a3e941
7280d57
 
5f571dd
ca80b7d
 
 
7280d57
 
 
 
 
 
 
 
 
 
 
2d84aa6
 
ca80b7d
4a3e941
86ef121
a5a89e3
 
ca80b7d
 
7280d57
a5a89e3
7280d57
 
 
a5a89e3
 
 
7280d57
ca80b7d
 
7280d57
a5a89e3
7280d57
 
 
 
ca80b7d
 
7280d57
 
a5a89e3
7280d57
ca80b7d
7280d57
 
a5a89e3
7280d57
f1fe858
ca80b7d
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# app.py
import streamlit as st
import os
import sys
import subprocess
import logging
import copy

# --- Step 0: Setup Logging and Page Config EARLY ---
from config import (
    setup_logging, LOG_LEVEL, APP_TITLE, FAVICON, LAYOUT,
    INITIAL_SIDEBAR_STATE, CUSTOM_CSS, LOGO_PATH, FOOTER_HTML
)

# !!! IMPORTANT: Set page config as the VERY FIRST Streamlit command !!!
st.set_page_config(
    page_title=APP_TITLE,
    layout=LAYOUT,
    page_icon=FAVICON,
    initial_sidebar_state=INITIAL_SIDEBAR_STATE
)

# Now, setup logging
logger = setup_logging() # Get the configured logger

logger.info("--- RadVision AI Application Start ---")
logger.info(f"Streamlit Version: {st.__version__}")
logger.info(f"Logging Level: {LOG_LEVEL}")

# --- Step 1: Handle Core Dependencies and Availability ---
# Try importing essential libraries and set availability flags
# (Error/Warning messages using st.* inside these blocks are now OK,
# because set_page_config has already run)

# Deep Translator (Attempt install if missing)
try:
    from deep_translator import GoogleTranslator
    from translation_models import translate, detect_language, LANGUAGE_CODES, AUTO_DETECT_INDICATOR
    TRANSLATION_AVAILABLE = True
    logger.info("Deep Translator and translation_models imported successfully.")
except ImportError:
    logger.warning("Deep Translator or translation_models not found initially. Attempting install...")
    try:
        subprocess.check_call([sys.executable, "-m", "pip", "install", "deep-translator"])
        from deep_translator import GoogleTranslator
        from translation_models import translate, detect_language, LANGUAGE_CODES, AUTO_DETECT_INDICATOR
        TRANSLATION_AVAILABLE = True
        logger.info("Successfully installed and imported deep-translator and translation_models.")
    except Exception as e:
        logger.error(f"CRITICAL: Failed to install/import deep-translator or translation_models: {e}", exc_info=True)
        # Display warning in the UI now that set_page_config has run
        st.warning("Translation features are disabled: Could not load or install 'deep-translator'. See logs for details.")
        TRANSLATION_AVAILABLE = False
        translate = None
        detect_language = None
        LANGUAGE_CODES = {"English": "en"}
        AUTO_DETECT_INDICATOR = "Auto-Detect"

# Streamlit Drawable Canvas
try:
    import streamlit_drawable_canvas as st_canvas_module
    CANVAS_VERSION = getattr(st_canvas_module, '__version__', 'Unknown')
    DRAWABLE_CANVAS_AVAILABLE = True
    logger.info(f"Streamlit Drawable Canvas Version: {CANVAS_VERSION} available.")
except ImportError:
    st.error("CRITICAL ERROR: `streamlit-drawable-canvas` is not installed. ROI drawing is disabled. Run `pip install streamlit-drawable-canvas`.")
    logger.critical("streamlit-drawable-canvas not found. ROI functionality disabled.")
    DRAWABLE_CANVAS_AVAILABLE = False

# Pillow (PIL) - Essential for image handling
try:
    from PIL import Image, ImageDraw, UnidentifiedImageError
    import PIL
    PIL_VERSION = getattr(PIL, '__version__', 'Unknown')
    PIL_AVAILABLE = True
    logger.info(f"Pillow (PIL) Version: {PIL_VERSION} available.")
    import utils
    utils.Image = Image
except ImportError:
    # This error is critical, stop the app
    logger.critical("Pillow (PIL) not found. Application cannot function correctly.")
    st.error("CRITICAL ERROR: Pillow (PIL) is not installed. Image handling is disabled. Run `pip install Pillow`.")
    st.stop() # Stop execution if PIL is missing

# Pydicom and related libraries
try:
    import pydicom
    import pydicom.errors
    PYDICOM_VERSION = getattr(pydicom, '__version__', 'Unknown')
    PYDICOM_AVAILABLE = True
    logger.info(f"Pydicom Version: {PYDICOM_VERSION} available.")
    try: import pylibjpeg; logger.info("pylibjpeg found.")
    except ImportError: logger.info("pylibjpeg not found.")
    try: import gdcm; logger.info("python-gdcm found.")
    except ImportError: logger.info("python-gdcm not found.")
except ImportError:
    PYDICOM_AVAILABLE = False
    PYDICOM_VERSION = 'Not Installed'
    logger.warning("pydicom not found. DICOM file support will be disabled.")
    # Display warning in sidebar if possible (needs to be called after page_config)
    # Moved this specific UI warning inside the sidebar rendering itself for better timing
    pydicom = None

# Custom Utilities & Backend Modules
try:
    from dicom_utils import parse_dicom, extract_dicom_metadata, dicom_to_image, get_default_wl
    DICOM_UTILS_AVAILABLE = True
    logger.info("dicom_utils imported successfully.")
except ImportError as e:
    logger.error(f"Failed to import dicom_utils: {e}. DICOM specific features disabled.", exc_info=True)
    DICOM_UTILS_AVAILABLE = False
    if PYDICOM_AVAILABLE:
        st.warning("DICOM processing utilities (`dicom_utils.py`) not found. DICOM handling may be limited.")

try:
    # This file needs to exist now, even if it's basic
    from report_utils import generate_pdf_report_bytes
    REPORT_UTILS_AVAILABLE = True
    logger.info("report_utils imported successfully.")
except ImportError as e:
    logger.error(f"Failed to import report_utils: {e}. PDF reporting disabled.", exc_info=True)
    REPORT_UTILS_AVAILABLE = False
    # Display warning in the UI
    st.warning("Report generation utility (`report_utils.py`) not found or failed to import. PDF reporting disabled.")
    # Define a placeholder function to avoid errors in actions.py if it's called
    def generate_pdf_report_bytes(*args, **kwargs):
        logger.warning("generate_pdf_report_bytes called, but report_utils is unavailable. Returning None.")
        return None


try:
    from llm_interactions import run_initial_analysis, run_multimodal_qa, run_disease_analysis, estimate_ai_confidence
    LLM_INTERACTIONS_AVAILABLE = True
    logger.info("llm_interactions imported successfully.")
except ImportError as e:
    st.error(f"Core AI module (`llm_interactions.py`) failed to import: {e}. AI analysis functions are disabled.")
    logger.critical(f"Failed to import llm_interactions: {e}", exc_info=True)
    LLM_INTERACTIONS_AVAILABLE = False
    # Define placeholders if needed, or let actions.py handle the disabled state

try:
    from ui_components import display_dicom_metadata, dicom_wl_sliders
    UI_COMPONENTS_AVAILABLE = True
    logger.info("ui_components imported successfully.")
except ImportError as e:
    logger.warning(f"Failed to import ui_components: {e}. Custom UI elements might use fallbacks.", exc_info=True)
    UI_COMPONENTS_AVAILABLE = False

# HF Fallback Models (Optional)
try:
    from hf_models import query_hf_vqa_inference_api, HF_VQA_MODEL_ID
    HF_MODELS_AVAILABLE = True
    logger.info(f"hf_models imported successfully (Fallback VQA Model: {HF_VQA_MODEL_ID}).")
except ImportError:
    HF_VQA_MODEL_ID = "hf_model_not_found"
    HF_MODELS_AVAILABLE = False
    logger.warning("hf_models not found or failed to import. Fallback VQA disabled.")


# --- Update config with availability flags (optional) ---
# These flags can be used by other modules if needed, but ensure consistency
import config
config.DRAWABLE_CANVAS_AVAILABLE = DRAWABLE_CANVAS_AVAILABLE
config.PIL_AVAILABLE = PIL_AVAILABLE
config.PYDICOM_AVAILABLE = PYDICOM_AVAILABLE
config.DICOM_UTILS_AVAILABLE = DICOM_UTILS_AVAILABLE
config.LLM_INTERACTIONS_AVAILABLE = LLM_INTERACTIONS_AVAILABLE
config.REPORT_UTILS_AVAILABLE = REPORT_UTILS_AVAILABLE # Updated based on import success
config.UI_COMPONENTS_AVAILABLE = UI_COMPONENTS_AVAILABLE
config.HF_MODELS_AVAILABLE = HF_MODELS_AVAILABLE
config.TRANSLATION_AVAILABLE = TRANSLATION_AVAILABLE


# --- Step 2: Configure Streamlit Page ---
# Already done at the top!

# Apply Custom CSS
st.markdown(CUSTOM_CSS, unsafe_allow_html=True)

# --- Step 3: Apply Monkey Patches (Conditional) ---
import streamlit.elements.image as st_image
import utils
utils.apply_image_to_url_monkey_patch(st_image)

# --- Step 4: Initialize Session State ---
from state import initialize_session_state
initialize_session_state()
logger.debug(f"Session state verified/initialized for session ID: {st.session_state.session_id}")

# --- Step 5: Display Logo ---
if os.path.exists(LOGO_PATH):
    # Use columns to potentially control width/alignment better if needed
    logo_col, _ = st.columns([1, 3]) # Adjust ratio as desired
    with logo_col:
        st.image(LOGO_PATH, width=300) # Adjust width as needed
else:
    logger.warning(f"Hero logo not found at: {LOGO_PATH}")
    # Optionally display a text warning if the logo is crucial
    # st.warning("RadVision AI Logo image missing.")

# --- Step 6: Render Sidebar and Get Uploaded File ---
from ui import render_sidebar
# ****** CORRECTED FUNCTION CALL ******
# Remove UI_COMPONENTS_AVAILABLE and DICOM_UTILS_AVAILABLE arguments
# as ui.py now handles these checks internally.
uploaded_file = render_sidebar(
    IS_DICOM=st.session_state.get("is_dicom", False),
    REPORT_UTILS_AVAILABLE=REPORT_UTILS_AVAILABLE # Keep this one
)
# **************************************

# --- Step 7: Handle File Upload Processing ---
from file_handler import process_uploaded_file
# Pass necessary availability flags needed by the handler
process_uploaded_file(uploaded_file, PYDICOM_AVAILABLE, DICOM_UTILS_AVAILABLE, PIL_AVAILABLE)

# --- Step 8: Render Main Page Content ---
st.markdown("---")
st.title(f"⚕️ {APP_TITLE}: AI-Assisted Image Analysis")

with st.expander("User Guide & Disclaimer", expanded=False):
    st.warning("⚠️ **Disclaimer**: This tool is for research/educational purposes only and is **NOT** a substitute for professional medical advice or diagnosis.")
    st.markdown("""
    **Workflow:**
    1.  **Upload Image**: Use the sidebar to upload DICOM, JPG, or PNG files.
    2.  **(Optional) Demo Mode**: Check the box in the sidebar to load a sample image.
    3.  **(DICOM)** Adjust Window/Level sliders in the sidebar if needed for optimal contrast.
    4.  **(Optional) ROI**: Draw a rectangle on the image in the 'Image Viewer' below to focus the AI's analysis. Clear it using the sidebar button if needed.
    5.  **AI Analysis**: Use the buttons in the sidebar:
        *   `Run Initial Analysis`: Get a general overview.
        *   `Ask Question`: Ask specific questions about the image/ROI.
        *   `Analyze Condition`: Focus the AI on signs of a specific condition.
    6.  **Review Results**: Check the tabs ('Initial Analysis', 'Q&A History', 'Condition Focus') for AI output.
    7.  **(Optional) Translation**: Use the 'Translation' tab to translate AI-generated text.
    8.  **(Optional) Confidence**: Use the 'Estimate AI Confidence' button (sidebar) after analysis.
    9.  **(Optional) Report**: Click 'Generate PDF Report Data' (sidebar) and then 'Download PDF Report' to save a summary. **(Note: PDF generation may be disabled if `report_utils.py` is missing or faulty)**
    """) # Added note about disabled reporting

st.markdown("---")

# Main layout columns
col1, col2 = st.columns([2, 3]) # Adjust ratio as needed

with col1:
    from ui import render_image_viewer
    # Pass necessary data to the image viewer
    render_image_viewer(
        display_img=st.session_state.get("display_image"),
        is_dicom=st.session_state.get("is_dicom", False),
        dicom_metadata=st.session_state.get("dicom_metadata", {})
        # No need to pass DRAWABLE_CANVAS_AVAILABLE or UI_COMPONENTS_AVAILABLE
        # as ui.py checks these internally now.
    )

with col2:
    from ui import render_results_tabs
    # Determine the correct translate function to pass based on availability
    translate_func = translate if TRANSLATION_AVAILABLE else None
    render_results_tabs(
        TRANSLATION_SERVICE_AVAILABLE=TRANSLATION_AVAILABLE,
        translate_function=translate_func
        )

# --- Step 9: Handle Button Actions ---
from actions import handle_actions
# Pass necessary availability flags to the action handler
handle_actions(LLM_INTERACTIONS_AVAILABLE, REPORT_UTILS_AVAILABLE)

# --- Step 10: Render Footer ---
from ui import render_footer
# Pass the current session ID to the footer
render_footer(st.session_state.get("session_id"))

logger.info(f"--- Application render complete for session: {st.session_state.session_id} ---")