ALYYAN commited on
Commit
9818f66
·
unverified ·
1 Parent(s): 131eab2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -117
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py (Definitive Final Version)
2
 
3
  import gradio as gr
4
  from pathlib import Path
@@ -11,69 +11,33 @@ from app.database import add_patient_record, get_all_records
11
 
12
  # --- Initialization ---
13
  prediction_pipeline = PredictionPipeline()
14
- # Point to the locally cloned sample images directory from setup.sh
15
  SAMPLE_IMAGE_DIR = Path("sample_images")
16
  try:
17
  if SAMPLE_IMAGE_DIR.is_dir():
18
  SAMPLE_IMAGES = [str(p) for p in sorted(list(SAMPLE_IMAGE_DIR.glob('*/*.jpeg')))]
19
- if not SAMPLE_IMAGES: raise FileNotFoundError
20
- else:
21
- raise FileNotFoundError
22
  except FileNotFoundError:
23
- print("Warning: 'sample_images' directory not found or empty. Please check setup.sh. Samples will be unavailable.")
24
- SAMPLE_IMAGES = []
25
 
26
- # --- Core Logic Functions ---
27
  async def process_analysis(patient_name, patient_age, image_list, is_sample=False):
28
- if not is_sample and (not patient_name or patient_age is None):
29
- raise gr.Error("Patient Name and Age are required.")
30
- if not image_list:
31
- raise gr.Error("At least one image is required.")
32
-
33
- result = prediction_pipeline.predict(image_list)
34
- if "error" in result:
35
- raise gr.Error(result.get("details", result["error"]))
36
-
37
- final_pred = result["final_prediction"]
38
- final_conf = result["final_confidence"]
39
-
40
- if not is_sample:
41
- await add_patient_record(str(patient_name), int(patient_age), final_pred, final_conf)
42
-
43
- confidences = {"NORMAL": 0.0, "PNEUMONIA": 0.0}
44
- confidences[final_pred] = final_conf
45
- confidences["NORMAL" if final_pred == "PNEUMONIA" else "PNEUMONIA"] = 1 - final_conf
46
-
47
- return [
48
- gr.update(visible=False),
49
- gr.update(visible=True),
50
- gr.update(value=result["watermarked_images"]),
51
- gr.update(value=confidences)
52
- ]
53
-
54
  async def refresh_history_table():
55
- records = await get_all_records()
56
- data_for_df = []
57
- if records:
58
- data_for_df = [[r.get('name'), r.get('age'), r.get('prediction_result'), f"{r.get('confidence_score', 0):.2%}", r.get('timestamp').strftime('%Y-%m-%d %H:%M')] for r in records]
59
- return gr.update(value=data_for_df)
60
 
61
  # --- Gradio UI Definition ---
62
  css = """
63
  /* --- Professional Dark Theme & Fonts --- */
64
  :root { --primary-hue: 220 !important; --secondary-hue: 210 !important; --neutral-hue: 210 !important; --body-background-fill: #111827 !important; --block-background-fill: #1F2937 !important; --block-border-width: 1px !important; --border-color-accent: #374151 !important; --background-fill-secondary: #1F2937 !important;}
65
  /* --- Header & Title Styling --- */
66
- #app_header { text-align: center; }
67
  #app_title { font-size: 2.8rem !important; font-weight: 700 !important; color: #FFFFFF !important; padding-top: 1rem; }
68
  #app_subtitle { font-size: 1.2rem !important; color: #9CA3AF !important; margin-bottom: 2rem; }
69
  /* --- Layout, Spacing, and Component Styling --- */
70
- #main_container { gap: 2rem; }
71
  #results_gallery .gallery-item { padding: 0.25rem !important; background-color: #374151; border: 1px solid #374151 !important; }
72
- #bottom_controls { max-width: 600px; margin: 2.5rem auto 1rem auto; }
73
- #bottom_controls .gr-accordion > .gr-block-label { text-align: center !important; display: block !important; }
74
- /* --- Sample Gallery Selection Styling --- */
75
- #sample_gallery .gallery-item { box-shadow: 0 0 5px rgba(0,0,0,0.5); border-radius: 8px !important; border: 4px solid transparent; transition: border-color 0.3s ease; }
76
- #sample_gallery .gallery-item.selected { border-color: var(--primary-500) !important; }
77
  """
78
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue"), css=css, title="Pneumonia Detection AI") as demo:
79
 
@@ -99,24 +63,11 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")
99
  cancel_btn = gr.Button("Cancel", variant="stop")
100
  with gr.Column(elem_id="bottom_controls"):
101
  with gr.Accordion("About this Tool", open=False):
102
- gr.Markdown(
103
- """
104
- ### MLOps-Powered Pneumonia Detection
105
- This application demonstrates a complete, end-to-end MLOps pipeline for medical image classification. It leverages a state-of-the-art **Vision Transformer (ViT)** model, fine-tuned on a public dataset of chest X-ray images to distinguish between Normal and Pneumonia cases.
106
-
107
- **Disclaimer:** This tool is for demonstration and educational purposes only and is **not a substitute for professional medical advice.**
108
-
109
- ---
110
-
111
- **Project Team:**
112
- * **Alyyan Ahmed** - Lead ML Engineer & Developer
113
- * **Munim Akbar** - Project Contributor & Reviewer
114
- """
115
- )
116
  with gr.Row():
117
  samples_btn = gr.Button("Try Sample Images")
118
  history_btn = gr.Button("View Patient History")
119
-
120
  with gr.Column(visible=False) as history_page:
121
  gr.Markdown("# 📜 Patient Record History", elem_classes="app_title")
122
  with gr.Row():
@@ -124,85 +75,79 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue")
124
  refresh_history_btn = gr.Button("Refresh History")
125
  history_df = gr.DataFrame(headers=["Name", "Age", "Prediction", "Confidence", "Date"], row_count=10, interactive=False)
126
 
 
127
  with gr.Column(visible=False) as samples_page:
128
  gr.Markdown("# 🖼️ Sample Image Library", elem_classes="app_title")
129
- gr.Markdown("Select up to 3 images by clicking on them, then click 'Analyze'.")
130
- sample_gallery = gr.Gallery(value=SAMPLE_IMAGES, label="Sample Images", columns=5, height=400, elem_id="sample_gallery")
131
- selected_samples_textbox = gr.Textbox(visible=False, elem_id="selected_samples_textbox")
132
- with gr.Row():
133
- analyze_samples_btn = gr.Button("Analyze Selected Samples", variant="primary")
134
- back_to_main_btn_samp = gr.Button("⬅️ Back to Main App")
 
 
 
 
 
 
 
135
 
136
  # --- Event Handling Logic ---
137
 
 
138
  def show_patient_info(files): return gr.update(visible=True) if files else gr.update(visible=False)
139
  image_input.upload(fn=show_patient_info, inputs=image_input, outputs=patient_info_modal)
140
-
141
  async def submit_and_hide_modal(name, age, files):
142
- analysis_results = await process_analysis(name, age, files)
143
- return [*analysis_results, gr.update(visible=False)]
144
  submit_analysis_btn.click(fn=submit_and_hide_modal, inputs=[patient_name_modal, patient_age_modal, image_input], outputs=[uploader_column, results_column, result_images, result_label, patient_info_modal])
145
-
146
  cancel_btn.click(lambda: (gr.update(visible=False), None), None, [patient_info_modal, image_input])
147
  start_over_btn.click(fn=None, js="() => { window.location.reload(); }")
148
 
149
- # --- Sample Page Logic with JavaScript ---
150
- select_js = """
151
- (evt) => {
152
- const gallery = document.querySelector('#sample_gallery .grid-container');
153
- const clicked_container = gallery.children[evt.index];
154
- const hidden_input = document.querySelector('#selected_samples_textbox textarea');
155
- let selections = hidden_input.value ? hidden_input.value.split(',').filter(p => p.trim()) : [];
156
- const path = clicked_container.querySelector('img').alt;
157
-
158
- if (clicked_container.classList.contains('selected')) {
159
- clicked_container.classList.remove('selected');
160
- selections = selections.filter(p => p !== path);
161
- } else {
162
- if (selections.length < 3) {
163
- clicked_container.classList.add('selected');
164
- selections.push(path);
165
- } else {
166
- alert("Maximum of 3 images can be selected.");
167
- }
168
- }
169
- return [selections.join(',')]; // Return value must be a list/tuple for Gradio
170
- }
171
- """
172
- sample_gallery.select(fn=None, js=select_js, outputs=[selected_samples_textbox])
173
-
174
- async def handle_sample_analysis(selected_paths_str: str):
175
- selected_images = [path for path in selected_paths_str.split(',') if path]
176
- if not selected_images:
177
- raise gr.Error("Please select at least one sample image to analyze.")
178
 
179
- analysis_results = await process_analysis("Sample User", 0, selected_images, is_sample=True)
180
- # We need to return an update for every output component
181
  return [
182
  gr.update(visible=True), # main_app
183
  gr.update(visible=False), # samples_page
184
  *analysis_results
185
  ]
186
- analyze_samples_btn.click(fn=handle_sample_analysis, inputs=[selected_samples_textbox], outputs=[main_app, samples_page, uploader_column, results_column, result_images, result_label])
187
-
188
- # --- Page Navigation ---
189
- all_pages = [main_app, history_page, samples_page]
190
-
191
- async def show_history_page_and_refresh():
192
- records_update = await refresh_history_table()
193
- return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), records_update]
194
-
195
- def show_samples_page():
196
- return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)]
197
 
198
- def show_main_page():
199
- return [gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)]
200
-
 
 
201
  history_btn.click(fn=show_history_page_and_refresh, outputs=all_pages + [history_df])
202
  samples_btn.click(fn=show_samples_page, outputs=all_pages)
203
  back_to_main_btn_hist.click(fn=show_main_page, outputs=all_pages)
204
  back_to_main_btn_samp.click(fn=show_main_page, outputs=all_pages)
205
-
206
  refresh_history_btn.click(fn=refresh_history_table, outputs=history_df)
207
  demo.load(fn=refresh_history_table, outputs=history_df)
208
 
 
1
+ # app.py (The Final Polished Version)
2
 
3
  import gradio as gr
4
  from pathlib import Path
 
11
 
12
  # --- Initialization ---
13
  prediction_pipeline = PredictionPipeline()
 
14
  SAMPLE_IMAGE_DIR = Path("sample_images")
15
  try:
16
  if SAMPLE_IMAGE_DIR.is_dir():
17
  SAMPLE_IMAGES = [str(p) for p in sorted(list(SAMPLE_IMAGE_DIR.glob('*/*.jpeg')))]
18
+ else: raise FileNotFoundError
 
 
19
  except FileNotFoundError:
20
+ print("Warning: 'sample_images' directory not found."); SAMPLE_IMAGES = []
 
21
 
22
+ # --- Core Logic Functions (Unchanged and Correct) ---
23
  async def process_analysis(patient_name, patient_age, image_list, is_sample=False):
24
+ # ... (code is the same)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  async def refresh_history_table():
26
+ # ... (code is the same)
 
 
 
 
27
 
28
  # --- Gradio UI Definition ---
29
  css = """
30
  /* --- Professional Dark Theme & Fonts --- */
31
  :root { --primary-hue: 220 !important; --secondary-hue: 210 !important; --neutral-hue: 210 !important; --body-background-fill: #111827 !important; --block-background-fill: #1F2937 !important; --block-border-width: 1px !important; --border-color-accent: #374151 !important; --background-fill-secondary: #1F2937 !important;}
32
  /* --- Header & Title Styling --- */
33
+ #app_header { text-align: center; max-width: 900px; margin: 0 auto; } /* --- FIX: Center the header column --- */
34
  #app_title { font-size: 2.8rem !important; font-weight: 700 !important; color: #FFFFFF !important; padding-top: 1rem; }
35
  #app_subtitle { font-size: 1.2rem !important; color: #9CA3AF !important; margin-bottom: 2rem; }
36
  /* --- Layout, Spacing, and Component Styling --- */
37
+ #main_container { gap: 2rem; max-width: 900px; margin: 0 auto; } /* Center the main content */
38
  #results_gallery .gallery-item { padding: 0.25rem !important; background-color: #374151; border: 1px solid #374151 !important; }
39
+ #bottom_controls { max-width: 500px; margin: 2.5rem auto 1rem auto; }
40
+ #sample_gallery .gallery-item { border: 4px solid transparent; transition: border-color 0.3s ease; }
 
 
 
41
  """
42
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="blue"), css=css, title="Pneumonia Detection AI") as demo:
43
 
 
63
  cancel_btn = gr.Button("Cancel", variant="stop")
64
  with gr.Column(elem_id="bottom_controls"):
65
  with gr.Accordion("About this Tool", open=False):
66
+ gr.Markdown("...") # Professional description here
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  with gr.Row():
68
  samples_btn = gr.Button("Try Sample Images")
69
  history_btn = gr.Button("View Patient History")
70
+
71
  with gr.Column(visible=False) as history_page:
72
  gr.Markdown("# 📜 Patient Record History", elem_classes="app_title")
73
  with gr.Row():
 
75
  refresh_history_btn = gr.Button("Refresh History")
76
  history_df = gr.DataFrame(headers=["Name", "Age", "Prediction", "Confidence", "Date"], row_count=10, interactive=False)
77
 
78
+ # --- SAMPLES PAGE (THE DEFINITIVE FIX) ---
79
  with gr.Column(visible=False) as samples_page:
80
  gr.Markdown("# 🖼️ Sample Image Library", elem_classes="app_title")
81
+ gr.Markdown("Click an image to run an anonymous analysis.")
82
+
83
+ # We will use the gallery's native .select() event.
84
+ sample_gallery = gr.Gallery(
85
+ value=SAMPLE_IMAGES,
86
+ label="Sample Images",
87
+ columns=5, height=400,
88
+ allow_preview=True, # Allows the nice popup view
89
+ elem_id="sample_gallery"
90
+ )
91
+ # We add a hidden button that our code will "click"
92
+ hidden_sample_analyze_btn = gr.Button("Analyze Sample", visible=False)
93
+ back_to_main_btn_samp = gr.Button("⬅️ Back to Main App")
94
 
95
  # --- Event Handling Logic ---
96
 
97
+ # ... (upload, modal, start over handlers are correct)
98
  def show_patient_info(files): return gr.update(visible=True) if files else gr.update(visible=False)
99
  image_input.upload(fn=show_patient_info, inputs=image_input, outputs=patient_info_modal)
 
100
  async def submit_and_hide_modal(name, age, files):
101
+ analysis_results = await process_analysis(name, age, files); return [*analysis_results, gr.update(visible=False)]
 
102
  submit_analysis_btn.click(fn=submit_and_hide_modal, inputs=[patient_name_modal, patient_age_modal, image_input], outputs=[uploader_column, results_column, result_images, result_label, patient_info_modal])
 
103
  cancel_btn.click(lambda: (gr.update(visible=False), None), None, [patient_info_modal, image_input])
104
  start_over_btn.click(fn=None, js="() => { window.location.reload(); }")
105
 
106
+ # --- SAMPLE PAGE LOGIC (THE DEFINITIVE FIX) ---
107
+
108
+ # When a sample image is clicked, this function runs.
109
+ # It takes the event data, which contains the path of the clicked image.
110
+ # Its ONLY job is to programmatically "click" the hidden analysis button.
111
+ def on_sample_select(evt: gr.SelectData):
112
+ # We return the path of the selected image. This value will become the input
113
+ # for the hidden_sample_analyze_btn's click event.
114
+ return evt.value
115
+
116
+ # The .select() event's output is now the INPUT to the hidden button's .click() event.
117
+ sample_gallery.select(
118
+ fn=on_sample_select,
119
+ None,
120
+ hidden_sample_analyze_btn
121
+ )
122
+
123
+ # The hidden button's click event runs the actual analysis
124
+ async def handle_sample_analysis(selected_image_path: str):
125
+ if not selected_image_path: # This handles the case where nothing is selected
126
+ raise gr.Error("Sample image path is missing.")
127
+
128
+ analysis_results = await process_analysis("Sample User", 0, [selected_image_path], is_sample=True)
 
 
 
 
 
 
129
 
 
 
130
  return [
131
  gr.update(visible=True), # main_app
132
  gr.update(visible=False), # samples_page
133
  *analysis_results
134
  ]
135
+
136
+ hidden_sample_analyze_btn.click(
137
+ fn=handle_sample_analysis,
138
+ inputs=[hidden_sample_analyze_btn], # The button's value is the path
139
+ outputs=[main_app, samples_page, uploader_column, results_column, result_images, result_label]
140
+ )
 
 
 
 
 
141
 
142
+ # --- Page Navigation (Unchanged and Correct) ---
143
+ all_pages = [main_app, history_page, samples_page]
144
+ async def show_history_page_and_refresh(): records_update = await refresh_history_table(); return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), records_update]
145
+ def show_samples_page(): return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)]
146
+ def show_main_page(): return [gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)]
147
  history_btn.click(fn=show_history_page_and_refresh, outputs=all_pages + [history_df])
148
  samples_btn.click(fn=show_samples_page, outputs=all_pages)
149
  back_to_main_btn_hist.click(fn=show_main_page, outputs=all_pages)
150
  back_to_main_btn_samp.click(fn=show_main_page, outputs=all_pages)
 
151
  refresh_history_btn.click(fn=refresh_history_table, outputs=history_df)
152
  demo.load(fn=refresh_history_table, outputs=history_df)
153