mmrech commited on
Commit
ab13376
Β·
1 Parent(s): b2dc700

Add missing detect_subjects function and comprehensive end-to-end test script

Browse files
Files changed (2) hide show
  1. app.py +42 -0
  2. test_app.py +199 -0
app.py CHANGED
@@ -521,6 +521,48 @@ def group_images_by_subject(image_files):
521
 
522
  return subject_groups
523
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  def process_slices_for_viewer(image_files, selected_subject, prompt_text, modality, window_type):
525
  """Process all slices for selected subject and cache results for interactive viewing."""
526
  if model is None or processor is None:
 
521
 
522
  return subject_groups
523
 
524
+ def detect_subjects(image_files):
525
+ """Detect and return subject groups from uploaded files."""
526
+ if not image_files:
527
+ return gr.Dropdown(choices=[], value=None), "No files uploaded"
528
+
529
+ subject_groups = group_images_by_subject(image_files)
530
+
531
+ if not subject_groups:
532
+ return gr.Dropdown(choices=[], value=None), "No subjects detected"
533
+
534
+ choices = []
535
+ status_msg = f"βœ… Detected {len(subject_groups)} subject(s):\n\n"
536
+
537
+ for subject_id, info in sorted(subject_groups.items()):
538
+ num_files = len(info['files'])
539
+ confidence = info['confidence']
540
+ sources = ', '.join(info['sources'])
541
+
542
+ # Add confidence indicator
543
+ if confidence == 'high':
544
+ confidence_icon = "βœ…"
545
+ confidence_text = "HIGH (DICOM metadata - very reliable)"
546
+ elif confidence == 'medium':
547
+ confidence_icon = "⚠️"
548
+ confidence_text = "MEDIUM (folder/filename pattern - likely same patient)"
549
+ else:
550
+ confidence_icon = "⚠️⚠️"
551
+ confidence_text = "LOW (filename-based - verify manually)"
552
+
553
+ choices.append(f"{subject_id} ({num_files} slices)")
554
+ status_msg += f"{confidence_icon} **{subject_id}**: {num_files} slices\n"
555
+ status_msg += f" Confidence: {confidence_text}\n"
556
+ status_msg += f" Source: {sources}\n\n"
557
+
558
+ # Add warning for low confidence
559
+ low_confidence_count = sum(1 for info in subject_groups.values() if info['confidence'] == 'low')
560
+ if low_confidence_count > 0:
561
+ status_msg += f"⚠️ **Warning**: {low_confidence_count} subject(s) detected with LOW confidence.\n"
562
+ status_msg += "Please verify these are actually the same patient before proceeding.\n"
563
+
564
+ return gr.Dropdown(choices=choices, value=choices[0] if choices else None), status_msg
565
+
566
  def process_slices_for_viewer(image_files, selected_subject, prompt_text, modality, window_type):
567
  """Process all slices for selected subject and cache results for interactive viewing."""
568
  if model is None or processor is None:
test_app.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ End-to-end test script for NeuroSAM 3 app
4
+ Tests app structure, imports, and function logic without requiring GPU/model
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import ast
10
+ import importlib.util
11
+
12
+ print("=" * 70)
13
+ print("πŸ§ͺ NeuroSAM 3 App - End-to-End Test")
14
+ print("=" * 70)
15
+
16
+ # Test 1: Check if app.py exists and is readable
17
+ print("\n[1/8] Checking app.py file...")
18
+ app_path = "/Users/matheusrech/colab/NeuroSAM3/app.py"
19
+ if not os.path.exists(app_path):
20
+ print("❌ ERROR: app.py not found!")
21
+ sys.exit(1)
22
+ print("βœ… app.py found")
23
+
24
+ # Test 2: Check Python syntax
25
+ print("\n[2/8] Checking Python syntax...")
26
+ try:
27
+ with open(app_path, 'r') as f:
28
+ code = f.read()
29
+ ast.parse(code)
30
+ print("βœ… Python syntax is valid")
31
+ except SyntaxError as e:
32
+ print(f"❌ SYNTAX ERROR: {e}")
33
+ sys.exit(1)
34
+
35
+ # Test 3: Check required functions exist
36
+ print("\n[3/8] Checking required functions...")
37
+ required_functions = [
38
+ 'process_medical_image',
39
+ 'process_with_status',
40
+ 'process_sequence',
41
+ 'process_slices_for_viewer',
42
+ 'navigate_slice',
43
+ 'extract_subject_id',
44
+ 'group_images_by_subject',
45
+ 'compare_with_ground_truth',
46
+ 'process_with_ground_truth',
47
+ 'load_demo_file'
48
+ ]
49
+
50
+ # Optional functions (may have different names)
51
+ optional_functions = [
52
+ 'detect_subjects' # May be defined inline or with different name
53
+ ]
54
+
55
+ missing_functions = []
56
+ for func_name in required_functions:
57
+ if f"def {func_name}" not in code:
58
+ missing_functions.append(func_name)
59
+
60
+ if missing_functions:
61
+ print(f"❌ Missing functions: {', '.join(missing_functions)}")
62
+ sys.exit(1)
63
+ print(f"βœ… All {len(required_functions)} required functions found")
64
+
65
+ # Test 4: Check Gradio components
66
+ print("\n[4/8] Checking Gradio UI components...")
67
+ required_components = [
68
+ 'gr.Blocks()',
69
+ 'gr.File(',
70
+ 'gr.Image(',
71
+ 'gr.Textbox(',
72
+ 'gr.Dropdown(',
73
+ 'gr.Button(',
74
+ 'gr.Slider(',
75
+ 'gr.Gallery(',
76
+ 'gr.Tabs(',
77
+ 'gr.Tab('
78
+ ]
79
+
80
+ missing_components = []
81
+ for comp in required_components:
82
+ if comp not in code:
83
+ missing_components.append(comp)
84
+
85
+ if missing_components:
86
+ print(f"⚠️ Missing components: {', '.join(missing_components)}")
87
+ else:
88
+ print(f"βœ… All Gradio components found")
89
+
90
+ # Test 5: Check tabs structure
91
+ print("\n[5/8] Checking UI tabs...")
92
+ tabs = [
93
+ 'Single Image',
94
+ 'Interactive Slice Viewer',
95
+ 'Gallery View',
96
+ 'Compare with Ground Truth'
97
+ ]
98
+
99
+ found_tabs = []
100
+ for tab in tabs:
101
+ if f'gr.Tab("{tab}")' in code or f"gr.Tab('{tab}')" in code:
102
+ found_tabs.append(tab)
103
+
104
+ print(f"βœ… Found tabs: {', '.join(found_tabs)}")
105
+ if len(found_tabs) != len(tabs):
106
+ print(f"⚠️ Expected {len(tabs)} tabs, found {len(found_tabs)}")
107
+
108
+ # Test 6: Check event handlers
109
+ print("\n[6/8] Checking event handlers...")
110
+ required_handlers = [
111
+ 'submit_btn.click',
112
+ 'submit_batch_btn.click',
113
+ 'submit_gallery_btn.click',
114
+ 'submit_gt_btn.click',
115
+ 'detect_subjects_btn.click',
116
+ 'slice_slider.change',
117
+ 'prev_btn.click',
118
+ 'next_btn.click',
119
+ 'load_demo_btn.click'
120
+ ]
121
+
122
+ found_handlers = []
123
+ for handler in required_handlers:
124
+ if handler in code:
125
+ found_handlers.append(handler)
126
+
127
+ print(f"βœ… Found {len(found_handlers)}/{len(required_handlers)} event handlers")
128
+ if len(found_handlers) < len(required_handlers):
129
+ missing = [h for h in required_handlers if h not in found_handlers]
130
+ print(f"⚠️ Missing handlers: {', '.join(missing)}")
131
+
132
+ # Test 7: Check imports
133
+ print("\n[7/8] Checking imports...")
134
+ required_imports = [
135
+ 'import gradio',
136
+ 'import torch',
137
+ 'import pydicom',
138
+ 'import numpy',
139
+ 'from PIL import Image',
140
+ 'from transformers import',
141
+ 'import matplotlib',
142
+ 'import tempfile',
143
+ 'import os'
144
+ ]
145
+
146
+ missing_imports = []
147
+ for imp in required_imports:
148
+ if imp not in code:
149
+ missing_imports.append(imp)
150
+
151
+ if missing_imports:
152
+ print(f"⚠️ Missing imports: {', '.join(missing_imports)}")
153
+ else:
154
+ print("βœ… All required imports found")
155
+
156
+ # Test 8: Check for common errors
157
+ print("\n[8/8] Checking for common errors...")
158
+ errors_found = []
159
+
160
+ # Check for undefined variables in function calls
161
+ if 'processed_results_cache' not in code:
162
+ errors_found.append("processed_results_cache not defined")
163
+
164
+ # Check if demo.launch() exists
165
+ if 'demo.launch()' not in code:
166
+ errors_found.append("demo.launch() not found")
167
+
168
+ # Check for HF_TOKEN handling
169
+ if 'HF_TOKEN' not in code:
170
+ errors_found.append("HF_TOKEN handling not found")
171
+ elif 'os.getenv("HF_TOKEN")' not in code:
172
+ errors_found.append("HF_TOKEN not retrieved from environment")
173
+
174
+ if errors_found:
175
+ print(f"❌ Errors found: {', '.join(errors_found)}")
176
+ else:
177
+ print("βœ… No common errors detected")
178
+
179
+ # Summary
180
+ print("\n" + "=" * 70)
181
+ print("πŸ“Š Test Summary")
182
+ print("=" * 70)
183
+
184
+ all_passed = (
185
+ len(missing_functions) == 0 and
186
+ len(errors_found) == 0
187
+ )
188
+
189
+ if all_passed:
190
+ print("βœ… All critical tests passed!")
191
+ print("\nπŸ’‘ Next steps:")
192
+ print(" 1. Set HF_TOKEN environment variable")
193
+ print(" 2. Test on Hugging Face Space")
194
+ print(" 3. Upload test DICOM/images to verify functionality")
195
+ sys.exit(0)
196
+ else:
197
+ print("❌ Some tests failed. Please review the errors above.")
198
+ sys.exit(1)
199
+