TheBug95 commited on
Commit
a99c555
·
1 Parent(s): 6638b2b

upload labeling tool

Browse files
.gitignore ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =====================
2
+ # macOS
3
+ # =====================
4
+ .DS_Store
5
+ .AppleDouble
6
+ .LSOverride
7
+ Icon?
8
+ ._*
9
+ .Spotlight-V100
10
+ .Trashes
11
+
12
+ # =====================
13
+ # Python
14
+ # =====================
15
+ __pycache__/
16
+ *.py[cod]
17
+ *$py.class
18
+ .env
19
+ .venv
20
+ venv/
21
+ ENV/
22
+ env/
23
+ pip-wheel-metadata/
24
+ *.egg-info/
25
+ .eggs/
26
+ build/
27
+ dist/
28
+
29
+ # =====================
30
+ # PyTorch
31
+ # =====================
32
+ *.pt
33
+ *.pth
34
+ *.ckpt
35
+ checkpoints/
36
+ runs/
37
+ lightning_logs/
38
+
39
+ # =====================
40
+ # Jupyter Notebooks
41
+ # =====================
42
+ .ipynb_checkpoints/
43
+ *.nbconvert.ipynb
44
+
45
+ # =====================
46
+ # Streamlit
47
+ # =====================
48
+ .streamlit/secrets.toml
49
+ .streamlit/.cache
50
+ .streamlit/cache
51
+ .streamlit/config.toml
52
+
53
+ # =====================
54
+ # Logs & Temp Files
55
+ # =====================
56
+ *.log
57
+ *.tmp
58
+ *.swp
59
+
60
+ # =====================
61
+ # Archives (skip zip files)
62
+ # =====================
63
+ *.zip
64
+ *.tar
65
+ *.tar.gz
66
+ *.rar
67
+
68
+ # =====================
69
+ # IDEs
70
+ # =====================
71
+ .vscode/
72
+ .idea/
README.md ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How to Run MedGemma
2
+
3
+ This guide explains how to set up the environment and run both the Streamlit interface and the Jupyter Notebook for the MedGemma project.
4
+
5
+ ## 1. Prerequisites & Setup
6
+
7
+ ### A. Download the Dataset
8
+
9
+ The application requires the fundus image dataset to function.
10
+
11
+ 1. **Download:** [**Click here to download full-fundus.zip**](https://upm365-my.sharepoint.com/:u:/g/personal/angelmario_garcia_upm_es/IQCP3cLo1x3tRK_TFCrt2HR0AfSAca5rzHrwaRa4Cm-EfL4?e=UcrIgy)
12
+ 2. **Extract:** Unzip the file into the root directory of your project.
13
+ * **Verify:** Ensure you see a folder named `full-fundus/` in your project folder.
14
+
15
+ ### B. Install Dependencies
16
+
17
+ Make sure you have Python installed, then run:
18
+
19
+ ```bash
20
+ pip install streamlit notebook torch transformers pillow whisper
21
+ ````
22
+
23
+ -----
24
+
25
+ ## 2. Running the Web Interface (Streamlit)
26
+
27
+ Use this method for a user-friendly dashboard to analyze images.
28
+
29
+ 1. Open your terminal (Command Prompt or Terminal).
30
+ 2. Navigate to your project folder:
31
+
32
+ ```bash
33
+ cd /path/to/your/project
34
+ ```
35
+
36
+ 3. Run the Streamlit application:
37
+
38
+ ```bash
39
+ streamlit run interface/main.py
40
+ ```
41
+
42
+ 4. A new tab will open in your browser automatically at `http://localhost:8501`.
43
+
44
+ -----
45
+
46
+ ## 3\. Running the Notebook (Jupyter)
47
+
48
+ Use this method to see the code logic, fine-tune the model, or debug.
49
+
50
+ 1. Open your terminal and navigate to the project folder.
51
+ 2. Launch Jupyter:
52
+
53
+ ```bash
54
+ jupyter notebook medgemma.ipynb
55
+ ```
56
+
57
+ 3. The Jupyter interface will open in your browser.
58
+ 4. Click on `medgemma.ipynb`.
59
+ 5. Run the cells sequentially (Shift + Enter) to execute the model.
interface/database.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import datetime
3
+ import sqlite3
4
+ import math
5
+
6
+ # Try importing firebase_admin
7
+ try:
8
+ import firebase_admin
9
+ from firebase_admin import credentials, firestore
10
+ FIREBASE_AVAILABLE = True
11
+ except ImportError:
12
+ FIREBASE_AVAILABLE = False
13
+
14
+ DB_TYPE = "SQLITE"
15
+ db_ref = None
16
+
17
+ def init_db():
18
+ """Initializes the database connection (Firebase or SQLite)."""
19
+ global DB_TYPE, db_ref
20
+
21
+ # Try Firebase first
22
+ if FIREBASE_AVAILABLE and os.path.exists("serviceAccountKey.json"):
23
+ try:
24
+ if not firebase_admin._apps:
25
+ cred = credentials.Certificate("serviceAccountKey.json")
26
+ firebase_admin.initialize_app(cred)
27
+ db_ref = firestore.client()
28
+ DB_TYPE = "FIREBASE"
29
+ return "FIREBASE"
30
+ except Exception as e:
31
+ print(f"Firebase init failed: {e}")
32
+
33
+ # Fallback to SQLite
34
+ try:
35
+ conn = sqlite3.connect('local_diagnoses.db', check_same_thread=False)
36
+ c = conn.cursor()
37
+ c.execute('''CREATE TABLE IF NOT EXISTS diagnoses
38
+ (id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ image_id TEXT,
40
+ diagnosis_text TEXT,
41
+ timestamp DATETIME)''')
42
+ c.execute('''CREATE INDEX IF NOT EXISTS idx_image_id ON diagnoses (image_id)''')
43
+ conn.commit()
44
+ conn.close()
45
+ DB_TYPE = "SQLITE"
46
+ return "SQLITE"
47
+ except Exception as e:
48
+ raise Exception(f"Database initialization failed: {e}")
49
+
50
+ def save_diagnosis(image_id, text, doctor_name="LocalUser"):
51
+ """Saves the diagnosis to the active database."""
52
+ timestamp = datetime.datetime.now()
53
+
54
+ if DB_TYPE == "FIREBASE":
55
+ db_ref.collection("ophthalmo_diagnoses").add({
56
+ "imageId": image_id,
57
+ "diagnosisText": text,
58
+ "createdAt": timestamp,
59
+ "doctor": doctor_name
60
+ })
61
+ else:
62
+ conn = sqlite3.connect('local_diagnoses.db', check_same_thread=False)
63
+ c = conn.cursor()
64
+ c.execute("INSERT INTO diagnoses (image_id, diagnosis_text, timestamp) VALUES (?, ?, ?)",
65
+ (image_id, text, timestamp))
66
+ conn.commit()
67
+ conn.close()
68
+
69
+ def get_latest_diagnosis(image_id):
70
+ """Retrieves the most recent diagnosis for a specific image ID."""
71
+ if DB_TYPE == "FIREBASE":
72
+ docs = db_ref.collection("ophthalmo_diagnoses")\
73
+ .where("imageId", "==", image_id)\
74
+ .order_by("createdAt", direction=firestore.Query.DESCENDING)\
75
+ .limit(1)\
76
+ .stream()
77
+ for doc in docs:
78
+ return doc.to_dict().get("diagnosisText", "")
79
+ else:
80
+ conn = sqlite3.connect('local_diagnoses.db', check_same_thread=False)
81
+ c = conn.cursor()
82
+ c.execute("SELECT diagnosis_text FROM diagnoses WHERE image_id = ? ORDER BY id DESC LIMIT 1", (image_id,))
83
+ row = c.fetchone()
84
+ conn.close()
85
+ if row:
86
+ return row[0]
87
+ return ""
88
+
89
+ def get_history_paginated(search_query="", page=1, per_page=10):
90
+ """
91
+ Retrieves history with search filtering and pagination.
92
+ Returns: (list_of_items, total_count)
93
+ """
94
+ offset = (page - 1) * per_page
95
+ history = []
96
+ total_count = 0
97
+
98
+ if DB_TYPE == "FIREBASE":
99
+ ref = db_ref.collection("ophthalmo_diagnoses")
100
+ if search_query:
101
+ # Prefix search hack for Firestore
102
+ query = ref.where("imageId", ">=", search_query)\
103
+ .where("imageId", "<=", search_query + '\uf8ff')
104
+ else:
105
+ query = ref.order_by("createdAt", direction=firestore.Query.DESCENDING)
106
+
107
+ all_docs = list(query.stream())
108
+ total_count = len(all_docs)
109
+
110
+ # In-memory pagination for Firebase
111
+ start = offset
112
+ end = offset + per_page
113
+ for doc in all_docs[start:end]:
114
+ history.append(doc.to_dict())
115
+
116
+ else:
117
+ # SQLite Implementation
118
+ conn = sqlite3.connect('local_diagnoses.db', check_same_thread=False)
119
+ c = conn.cursor()
120
+
121
+ # 1. Count
122
+ if search_query:
123
+ c.execute("SELECT COUNT(*) FROM diagnoses WHERE image_id LIKE ?", (f"%{search_query}%",))
124
+ else:
125
+ c.execute("SELECT COUNT(*) FROM diagnoses")
126
+ total_count = c.fetchone()[0]
127
+
128
+ # 2. Fetch
129
+ query_sql = "SELECT image_id, diagnosis_text, timestamp FROM diagnoses"
130
+ params = []
131
+
132
+ if search_query:
133
+ query_sql += " WHERE image_id LIKE ?"
134
+ params.append(f"%{search_query}%")
135
+
136
+ query_sql += " ORDER BY id DESC LIMIT ? OFFSET ?"
137
+ params.extend([per_page, offset])
138
+
139
+ c.execute(query_sql, params)
140
+ rows = c.fetchall()
141
+ for row in rows:
142
+ history.append({
143
+ "imageId": row[0],
144
+ "diagnosisText": row[1],
145
+ "createdAt": row[2]
146
+ })
147
+ conn.close()
148
+
149
+ return history, total_count
150
+
151
+ def get_last_active_image_id():
152
+ """Retrieves the image_id of the most recently saved diagnosis."""
153
+ if DB_TYPE == "FIREBASE":
154
+ docs = db_ref.collection("ophthalmo_diagnoses")\
155
+ .order_by("createdAt", direction=firestore.Query.DESCENDING)\
156
+ .limit(1)\
157
+ .stream()
158
+ for doc in docs:
159
+ return doc.to_dict().get("imageId")
160
+ else:
161
+ conn = sqlite3.connect('local_diagnoses.db', check_same_thread=False)
162
+ c = conn.cursor()
163
+ # Fetch the most recent entry based on timestamp (or ID if timestamp is unreliable)
164
+ c.execute("SELECT image_id FROM diagnoses ORDER BY timestamp DESC LIMIT 1")
165
+ row = c.fetchone()
166
+ conn.close()
167
+ if row:
168
+ return row[0]
169
+ return None
interface/dataset_fl.csv ADDED
The diff for this file is too large to render. See raw diff
 
interface/main.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ # CRITICAL FIX: MUST BE THE FIRST LINE
3
+ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
4
+
5
+ import streamlit as st
6
+ import tempfile
7
+ import math
8
+ import database as db
9
+ import utils
10
+
11
+ # CONFIGURATION
12
+ st.set_page_config(page_title="OphthalmoCapture", layout="wide", page_icon="👁️")
13
+
14
+ # Change these paths to match your actual folders
15
+ CSV_FILE_PATH = "interface/dataset_fl.csv" # Your CSV file
16
+ IMAGE_FOLDER = "full-fundus" # Folder containing your images
17
+
18
+ # INITIALIZATION
19
+ utils.setup_env()
20
+
21
+ try:
22
+ active_db_type = db.init_db()
23
+ except Exception as e:
24
+ st.error(f"Critical Database Error: {e}")
25
+ st.stop()
26
+
27
+ # LOAD REAL DATASET
28
+ # This replaces the mock data. It runs once per session.
29
+ if 'dataset' not in st.session_state:
30
+ st.session_state.dataset = utils.load_dataset(CSV_FILE_PATH, IMAGE_FOLDER)
31
+
32
+ # Helper to access the dataset safely
33
+ DATASET = st.session_state.dataset
34
+
35
+ if not DATASET:
36
+ st.error("Please ensure 'dataset.csv' exists and 'images' folder is populated.")
37
+ st.stop() # Stop execution if no data
38
+
39
+ # SIDEBAR: SETTINGS & HISTORY
40
+ with st.sidebar:
41
+ st.title("⚙️ Settings")
42
+
43
+ # Model Selector
44
+ model_options = ["tiny", "tiny.en", "base", "base.en", "small", "small.en", "medium", "medium.en", "large", "turbo"]
45
+ selected_model = st.selectbox("Whisper Model Size", model_options, index=1)
46
+
47
+ st.divider()
48
+
49
+ # History Section
50
+ st.header(f"🗄️ History ({active_db_type})")
51
+
52
+ search_input = st.text_input("🔍 Search ID", value=st.session_state.get('history_search', ""))
53
+ if search_input != st.session_state.get('history_search', ""):
54
+ st.session_state.history_search = search_input
55
+ st.session_state.history_page = 1
56
+ st.rerun()
57
+
58
+ if 'history_page' not in st.session_state:
59
+ st.session_state.history_page = 1
60
+
61
+ ITEMS_PER_PAGE = 5
62
+ try:
63
+ history_data, total_items = db.get_history_paginated(
64
+ st.session_state.get('history_search', ""),
65
+ st.session_state.history_page,
66
+ ITEMS_PER_PAGE
67
+ )
68
+ except Exception as e:
69
+ st.error(f"Error fetching history: {e}")
70
+ history_data, total_items = [], 0
71
+
72
+ if not history_data:
73
+ st.info("No diagnoses found.")
74
+ else:
75
+ for item in history_data:
76
+ ts = str(item.get('createdAt'))[:16]
77
+ img_id = item.get('imageId', 'N/A')
78
+ text = item.get('diagnosisText', '')
79
+ preview = (text[:50] + '..') if len(text) > 50 else text
80
+
81
+ with st.expander(f"{img_id} ({ts})"):
82
+ st.caption(ts)
83
+ st.write(f"_{preview}_")
84
+
85
+ if st.button("Load Report", key=f"load_{item.get('createdAt')}_{img_id}"):
86
+ # 1. Update the text
87
+ st.session_state.current_transcription = text
88
+
89
+ # 2. Find and update the image index
90
+ found_index = -1
91
+ for idx, data_item in enumerate(DATASET):
92
+ if str(data_item['id']) == str(img_id):
93
+ found_index = idx
94
+ break
95
+
96
+ if found_index != -1:
97
+ st.session_state.img_index = found_index
98
+ else:
99
+ st.warning(f"Image ID {img_id} not found in current dataset.")
100
+
101
+ st.rerun()
102
+
103
+ total_pages = math.ceil(total_items / ITEMS_PER_PAGE)
104
+ if total_pages > 1:
105
+ st.divider()
106
+ c1, c2, c3 = st.columns([1, 2, 1])
107
+ with c1:
108
+ if st.session_state.history_page > 1:
109
+ if st.button("◀️"):
110
+ st.session_state.history_page -= 1
111
+ st.rerun()
112
+ with c2:
113
+ st.markdown(f"<div style='text-align: center; padding-top: 5px;'>{st.session_state.history_page} / {total_pages}</div>", unsafe_allow_html=True)
114
+ with c3:
115
+ if st.session_state.history_page < total_pages:
116
+ if st.button("▶️"):
117
+ st.session_state.history_page += 1
118
+ st.rerun()
119
+
120
+ # LOAD MODEL
121
+ with st.spinner(f"Loading Whisper '{selected_model}' model..."):
122
+ model = utils.load_whisper_model(selected_model)
123
+
124
+ # SESSION STATE MANAGEMENT
125
+ if 'img_index' not in st.session_state:
126
+ # Default to 0
127
+ start_index = 0
128
+
129
+ # Try to find the last worked-on image from the DB
130
+ try:
131
+ last_id = db.get_last_active_image_id()
132
+ if last_id:
133
+ # Find the index of this ID in the current DATASET
134
+ for i, item in enumerate(DATASET):
135
+ if str(item["id"]) == str(last_id):
136
+ start_index = i
137
+ break
138
+ except Exception as e:
139
+ print(f"Could not restore session: {e}")
140
+
141
+ st.session_state.img_index = start_index
142
+
143
+ def load_current_image_data():
144
+ """Updates session state with DB data for the new image."""
145
+ current_img_id = DATASET[st.session_state.img_index]["id"]
146
+ try:
147
+ existing_text = db.get_latest_diagnosis(current_img_id)
148
+ st.session_state.current_transcription = existing_text if existing_text else ""
149
+ except Exception as e:
150
+ st.error(f"Failed to load diagnosis: {e}")
151
+ st.session_state.current_transcription = ""
152
+ st.session_state.last_processed_audio = None
153
+
154
+ if 'current_transcription' not in st.session_state:
155
+ load_current_image_data()
156
+ if 'last_processed_audio' not in st.session_state:
157
+ st.session_state.last_processed_audio = None
158
+
159
+ # MAIN CONTENT
160
+ st.title("👁️ OphthalmoCapture")
161
+ st.caption(f"Medical Dictation System • Model: {selected_model}")
162
+
163
+ col_img, col_diag = st.columns([1.5, 1])
164
+ current_img = DATASET[st.session_state.img_index]
165
+
166
+ with col_img:
167
+ st.image(current_img["url"], width="stretch")
168
+
169
+ # Navigation
170
+ c1, c2, c3 = st.columns([1, 2, 1])
171
+ with c1:
172
+ if st.button("⬅️ Previous"):
173
+ st.session_state.img_index = (st.session_state.img_index - 1) % len(DATASET)
174
+ load_current_image_data()
175
+ st.rerun()
176
+ with c2:
177
+ st.markdown(f"<div style='text-align: center'><b>{current_img['label']}</b><br>(ID: {current_img['id']})</div>", unsafe_allow_html=True)
178
+ with c3:
179
+ if st.button("Next ➡️"):
180
+ st.session_state.img_index = (st.session_state.img_index + 1) % len(DATASET)
181
+ load_current_image_data()
182
+ st.rerun()
183
+
184
+ with col_diag:
185
+ st.subheader("Dictation & Report")
186
+
187
+ audio_wav = st.audio_input("Record Voice", key=f"audio_{current_img['id']}")
188
+
189
+ if audio_wav is not None:
190
+ if st.session_state.last_processed_audio != audio_wav:
191
+ with st.spinner("Analyzing audio..."):
192
+ try:
193
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
194
+ tmp_file.write(audio_wav.read())
195
+ tmp_path = tmp_file.name
196
+
197
+ result = model.transcribe(tmp_path, language="es")
198
+ new_text = result["text"].strip()
199
+
200
+ if st.session_state.current_transcription:
201
+ st.session_state.current_transcription += " " + new_text
202
+ else:
203
+ st.session_state.current_transcription = new_text
204
+
205
+ st.session_state.last_processed_audio = audio_wav
206
+ os.remove(tmp_path)
207
+ except Exception as e:
208
+ st.error(f"Transcription Error: {e}")
209
+
210
+ diagnosis_text = st.text_area(
211
+ "Findings:",
212
+ value=st.session_state.current_transcription,
213
+ height=300
214
+ )
215
+
216
+ if diagnosis_text != st.session_state.current_transcription:
217
+ st.session_state.current_transcription = diagnosis_text
218
+
219
+ if st.button("💾 Save to Record", type="primary"):
220
+ if diagnosis_text.strip():
221
+ try:
222
+ db.save_diagnosis(current_img['id'], diagnosis_text)
223
+ st.success("Successfully saved to database.")
224
+ except Exception as e:
225
+ st.error(f"Save failed: {e}")
226
+ else:
227
+ st.warning("Cannot save empty diagnosis.")
interface/utils.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import whisper
3
+ import os
4
+ import pandas as pd
5
+
6
+
7
+ @st.cache_resource
8
+ def load_whisper_model(model_size):
9
+ """Loads the Whisper model (Cached)."""
10
+ print(f"Loading Whisper model: {model_size}...")
11
+ return whisper.load_model(model_size)
12
+
13
+ def setup_env():
14
+ """Sets up environment variables."""
15
+ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
16
+
17
+ def load_dataset(csv_path, image_folder):
18
+ """
19
+ Reads a CSV and checks for image existence.
20
+ Expected CSV columns: 'filename' (required), 'label' (optional).
21
+ """
22
+ images_list = []
23
+
24
+ # 1. Check if CSV exists
25
+ if not os.path.exists(csv_path):
26
+ st.error(f"⚠️ CSV file not found: {csv_path}")
27
+ return []
28
+
29
+ try:
30
+ df = pd.read_csv(csv_path)
31
+ except Exception as e:
32
+ st.error(f"Error reading CSV: {e}")
33
+ return []
34
+
35
+ # 2. Iterate through CSV
36
+ # We look for a 'filename' column. If not found, use the first column.
37
+ filename_col = 'filename'
38
+ if 'filename' not in df.columns:
39
+ filename_col = df.columns[0]
40
+ st.warning(f"Column 'filename' not found. Using '{filename_col}' as filename.")
41
+
42
+ for index, row in df.iterrows():
43
+ base_name = str(row[filename_col]).strip()
44
+
45
+ # Construct full path
46
+ full_path = os.path.join(image_folder, base_name)
47
+
48
+ # Handle extensions if filename doesn't have them (optional check)
49
+ if not os.path.exists(full_path):
50
+ # Try adding common extensions if file not found
51
+ for ext in ['.jpg', '.png', '.jpeg', '.tif']:
52
+ if os.path.exists(full_path + ext):
53
+ full_path = full_path + ext
54
+ break
55
+
56
+ # Only add if file actually exists
57
+ if os.path.exists(full_path):
58
+ images_list.append({
59
+ "id": base_name,
60
+ "label": row.get('label', base_name), # Use 'label' column or fallback to name
61
+ "url": full_path # Streamlit accepts local paths here
62
+ })
63
+
64
+ if not images_list:
65
+ st.warning(f"No valid images found in '{image_folder}' matching the CSV.")
66
+
67
+ return images_list
local_diagnoses.db ADDED
Binary file (16.4 kB). View file
 
medgemma.ipynb ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "d8392208-f730-4554-b7ae-6fd7a67bb3ed",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import os\n",
11
+ "import csv\n",
12
+ "import re\n",
13
+ "import glob\n",
14
+ "from PIL import Image\n",
15
+ "import torch\n",
16
+ "from tqdm import tqdm\n",
17
+ "from transformers import pipeline\n",
18
+ "\n",
19
+ "# CONFIGURATION\n",
20
+ "MODEL_ID = \"google/medgemma-4b-it\"\n",
21
+ "\n",
22
+ "OUTPUT_FILE = \"./results_marisse.csv\"\n",
23
+ "IMAGE_PATH = \"./full-fundus/*.*\"\n",
24
+ "\n",
25
+ "# INITIALIZE PIPELINE\n",
26
+ "pipe = pipeline(\n",
27
+ " \"image-text-to-text\",\n",
28
+ " model=MODEL_ID,\n",
29
+ " torch_dtype=torch.bfloat16,\n",
30
+ " # device_map=\"auto\",\n",
31
+ " device=1,\n",
32
+ ")\n",
33
+ "\n",
34
+ "# PROMPT\n",
35
+ "base_prompt_text = (\n",
36
+ " \"You are an expert ophthalmologist. Evaluate this fundus image for signs of glaucoma \"\n",
37
+ " \"(optic disc cupping, RNFL loss, peripapillary atrophy). \"\n",
38
+ " \"Write your Key Findings. Then provide your Conclusion. Do not include a Disclaimer.\"\n",
39
+ ")\n",
40
+ "\n",
41
+ "# LOAD IMAGES\n",
42
+ "image_files = sorted(glob.glob(IMAGE_PATH))\n",
43
+ "print(f\"Found {len(image_files)} images.\")\n",
44
+ "\n",
45
+ "# Ensure directory exists\n",
46
+ "os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)\n",
47
+ "\n",
48
+ "# PROCESSING LOOP\n",
49
+ "with open(OUTPUT_FILE, mode='w', newline='', encoding='utf-8') as csvfile:\n",
50
+ " writer = csv.writer(csvfile)\n",
51
+ " writer.writerow([\"Image File\", \"Full Reasoning\"])\n",
52
+ "\n",
53
+ " for image_file in tqdm(image_files):\n",
54
+ " try:\n",
55
+ " image = Image.open(image_file).convert(\"RGB\") # Ensure consistent color channels\n",
56
+ "\n",
57
+ " messages = [\n",
58
+ " {\n",
59
+ " \"role\": \"user\",\n",
60
+ " \"content\": [\n",
61
+ " {\"type\": \"text\", \"text\": base_prompt_text},\n",
62
+ " {\"type\": \"image\", \"image\": image}\n",
63
+ " ]\n",
64
+ " }\n",
65
+ " ]\n",
66
+ "\n",
67
+ " output = pipe(\n",
68
+ " messages,\n",
69
+ " max_new_tokens=2048,\n",
70
+ " do_sample=False\n",
71
+ " )\n",
72
+ "\n",
73
+ " generated_text = output[0][\"generated_text\"]\n",
74
+ " if isinstance(generated_text, list):\n",
75
+ " raw_response = generated_text[-1][\"content\"].strip()\n",
76
+ " else:\n",
77
+ " raw_response = generated_text.strip()\n",
78
+ "\n",
79
+ " writer.writerow([os.path.basename(image_file), raw_response])\n",
80
+ "\n",
81
+ " except Exception as e:\n",
82
+ " print(f\"Error processing {image_file}: {e}\")\n",
83
+ " writer.writerow([os.path.basename(image_file), \"ERROR\", str(e)])\n",
84
+ "\n",
85
+ "print(\"Processing complete.\")"
86
+ ]
87
+ }
88
+ ],
89
+ "metadata": {
90
+ "kernelspec": {
91
+ "display_name": "medgemma",
92
+ "language": "python",
93
+ "name": "medgemma"
94
+ },
95
+ "language_info": {
96
+ "codemirror_mode": {
97
+ "name": "ipython",
98
+ "version": 3
99
+ },
100
+ "file_extension": ".py",
101
+ "mimetype": "text/x-python",
102
+ "name": "python",
103
+ "nbconvert_exporter": "python",
104
+ "pygments_lexer": "ipython3",
105
+ "version": "3.10.18"
106
+ }
107
+ },
108
+ "nbformat": 4,
109
+ "nbformat_minor": 5
110
+ }
results_marisse.csv ADDED
The diff for this file is too large to render. See raw diff