ddecosmo commited on
Commit
cbf2680
·
verified ·
1 Parent(s): fbf4b5e

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -314
app.py DELETED
@@ -1,314 +0,0 @@
1
- """
2
- Lanternfly Field Capture Space (V7 - Global Class Selector Fix)
3
-
4
- This version uses a highly desperate, non-standard JavaScript selector (targeting
5
- the global .gradio-textbox class) to bypass unstable component IDs in the
6
- failing Colab environment.
7
- """
8
-
9
- import gradio as gr
10
- import os
11
- import json
12
- import uuid
13
- from datetime import datetime
14
- from PIL import Image
15
- from huggingface_hub import HfApi, hf_hub_download, create_repo, file_exists, upload_file
16
- import io
17
- import time
18
- import requests
19
-
20
- # Configuration
21
- # NOTE: In Colab, you would need to set HF_TOKEN as an environment variable
22
- # or pass it explicitly to enable saving.
23
- HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HF_TOKEN_SPACE")
24
- DATASET_REPO = os.getenv("DATASET_REPO", "rlogh/lanternfly-data")
25
-
26
- # Initialize HF API only if credentials are available
27
- api = None
28
- if HF_TOKEN and DATASET_REPO:
29
- try:
30
- api = HfApi(token=HF_TOKEN)
31
- create_repo(DATASET_REPO, repo_type="dataset", exist_ok=True, token=HF_TOKEN)
32
- print("✅ Hugging Face credentials found - dataset saving enabled")
33
- except Exception as e:
34
- print(f"⚠️ Error initializing HF API: {e}")
35
- api = None
36
- else:
37
- print("⚠️ Running in test mode - no HF credentials (dataset saving disabled)")
38
-
39
- # Constants for file paths
40
- METADATA_PATH = "metadata/entries.jsonl"
41
- IMAGES_DIR = "images"
42
-
43
- # --- Utility Functions ---
44
-
45
- def get_current_time():
46
- """Get current timestamp in ISO format"""
47
- return datetime.now().isoformat()
48
-
49
- def _append_jsonl_in_repo(new_row: dict) -> None:
50
- """Appends a JSON line to metadata/entries.jsonl in the dataset repo."""
51
- if not api: return # Skip if API is not initialized
52
-
53
- buf = io.BytesIO()
54
- existing_lines = []
55
-
56
- # Try downloading existing file (retries for resilience)
57
- for i in range(3):
58
- try:
59
- if file_exists(DATASET_REPO, METADATA_PATH, repo_type="dataset", token=HF_TOKEN):
60
- local_path = hf_hub_download(
61
- repo_id=DATASET_REPO, filename=METADATA_PATH,
62
- repo_type="dataset", token=HF_TOKEN
63
- )
64
- with open(local_path, "r", encoding="utf-8") as f:
65
- existing_lines = f.read().splitlines()
66
- break
67
- except Exception as e:
68
- if i == 2: raise e
69
- time.sleep(1 * (i + 1)) # Exponential backoff
70
-
71
- existing_lines.append(json.dumps(new_row, ensure_ascii=False))
72
- data = "\n".join(existing_lines).encode("utf-8")
73
- buf.write(data); buf.seek(0)
74
-
75
- upload_file(
76
- path_or_fileobj=buf,
77
- path_in_repo=METADATA_PATH,
78
- repo_id=DATASET_REPO,
79
- repo_type="dataset",
80
- token=HF_TOKEN,
81
- commit_message=f"Append 1 entry at {datetime.now().isoformat()}Z",
82
- )
83
-
84
- def _save_image_to_repo(pil_img: Image.Image, dest_rel_path: str) -> None:
85
- """Uploads a PIL image into the dataset repo."""
86
- if not api: return # Skip if API is not initialized
87
-
88
- img_bytes = io.BytesIO()
89
- pil_img.save(img_bytes, format="JPEG", quality=90)
90
- img_bytes.seek(0)
91
-
92
- upload_file(
93
- path_or_fileobj=img_bytes,
94
- path_in_repo=dest_rel_path,
95
- repo_id=DATASET_REPO,
96
- repo_type="dataset",
97
- token=HF_TOKEN,
98
- commit_message=f"Upload image {dest_rel_path}",
99
- )
100
-
101
- def handle_gps_location(json_str):
102
- """Handle GPS location data from JavaScript."""
103
- try:
104
- data = json.loads(json_str)
105
-
106
- if 'error' in data:
107
- error_map = {
108
- 1: "Permission Denied (Check browser settings)",
109
- 2: "Position Unavailable (Poor signal/network)",
110
- 3: "Timeout Expired (Fix took too long)",
111
- 0: "Geolocation not supported",
112
- 'N/A': "Unknown Geolocation Error"
113
- }
114
- error_code = data.get('code', 'N/A')
115
- error_msg = error_map.get(error_code, data.get('error', 'Unknown Error'))
116
-
117
- gr.Warning(f"GPS Error: Code {error_code} ({error_msg})")
118
-
119
- status_msg = f"❌ **GPS Error (Code {error_code})**: {error_msg}"
120
- return status_msg, "N/A", "N/A", "N/A", get_current_time()
121
-
122
- lat = str(data.get('latitude', ''))
123
- lon = str(data.get('longitude', ''))
124
- accuracy = str(data.get('accuracy', ''))
125
- timestamp = str(data.get('timestamp', ''))
126
-
127
- try:
128
- acc_display = f"{float(accuracy):.1f}"
129
- except ValueError:
130
- acc_display = "N/A"
131
-
132
- status_msg = f"✅ **GPS Captured**: {lat[:8]}, {lon[:8]} (accuracy: {acc_display}m)"
133
- return status_msg, lat, lon, accuracy, timestamp
134
-
135
- except Exception as e:
136
- status_msg = f"❌ **Error**: Failed to process GPS JSON: {str(e)}"
137
- gr.Error(status_msg)
138
- return status_msg, "Error", "Error", "Error", "Error"
139
-
140
-
141
- def get_gps_js():
142
- """JavaScript for robust GPS capture, using a global selector."""
143
- return """
144
- () => {
145
- // --- V7 FIX: Target the HIDDEN textbox using its internal class and index ---
146
- // This is a last resort if elem_id selectors fail due to nested/shadow DOM issues.
147
- const allTextareas = document.querySelectorAll('.gradio-textbox textarea');
148
- let textarea = null;
149
-
150
- // The hidden component is usually the first or last *visible* textbox element in the DOM
151
- // that is marked hidden by Gradio's internal styling. We will rely on its elem_id
152
- // to be included on the outer container, and select the first element that's actually hidden.
153
-
154
- // Find the specific hidden textarea within the component container (#hidden_gps_input)
155
- const container = document.querySelector('#hidden_gps_input');
156
- if (container) {
157
- textarea = container.querySelector('textarea');
158
- }
159
-
160
- if (!textarea) {
161
- console.error("DEBUG: Fatal: Hidden GPS textbox cannot be found by ID/Class.");
162
- return;
163
- }
164
-
165
- if (!navigator.geolocation) {
166
- console.error("DEBUG: Geolocation not supported by browser.");
167
- textarea.value = JSON.stringify({error: "Geolocation not supported", code: 0});
168
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
169
- return;
170
- }
171
-
172
- console.log("DEBUG: Auto-starting Geolocation request with 60s timeout.");
173
-
174
- navigator.geolocation.getCurrentPosition(
175
- function(position) {
176
- console.log("DEBUG: Geolocation SUCCESS.", position.coords);
177
- const data = {
178
- latitude: position.coords.latitude,
179
- longitude: position.coords.longitude,
180
- accuracy: position.coords.accuracy,
181
- timestamp: new Date(position.timestamp).toISOString()
182
- };
183
-
184
- textarea.value = JSON.stringify(data);
185
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
186
- },
187
- function(err) {
188
- console.error(`DEBUG: Geolocation FAILURE. Code: ${err.code}, Message: ${err.message}`);
189
- textarea.value = JSON.stringify({ error: err.message, code: err.code });
190
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
191
- },
192
- // Options: disableHighAccuracy for speed, maximumAge for caching, 60s timeout
193
- { enableHighAccuracy: false, timeout: 60000, maximumAge: 5000 }
194
- );
195
- }
196
- """
197
-
198
- def save_to_dataset(image, lat, lon, accuracy_m, device_ts):
199
- """Save image and metadata to Hugging Face dataset"""
200
- try:
201
- # 1. Validation
202
- if image is None:
203
- return "❌ **Error**: No image captured.", ""
204
- if lat == "N/A" or lon == "N/A":
205
- return "❌ **Error**: GPS coordinates missing. Get GPS data first.", ""
206
-
207
- # 2. Test Mode Check
208
- if not api:
209
- server_ts = datetime.now().isoformat()
210
- img_id = str(uuid.uuid4())
211
- timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
212
- row = {
213
- "id": img_id, "image": f"test_{timestamp_str}_{img_id[:8]}.jpg",
214
- "latitude": float(lat) if lat else None, "longitude": float(lon) if lon else None,
215
- "accuracy_m": accuracy_m, "device_timestamp": device_ts,
216
- "server_timestamp_utc": server_ts, "notes": ""
217
- }
218
- status = f"🔍 **Test Mode**: Data validated successfully! Sample {img_id[:8]}"
219
- preview = json.dumps(row, indent=2)
220
- return status, preview
221
-
222
- # 3. Save Image & Metadata
223
- sample_id = str(uuid.uuid4())
224
- timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
225
- image_rel_path = f"{IMAGES_DIR}/lanternfly_{timestamp_str}_{sample_id[:8]}.jpg"
226
-
227
- _save_image_to_repo(image, image_rel_path)
228
-
229
- server_ts_utc = datetime.now().isoformat() + "Z"
230
-
231
- row = {
232
- "id": sample_id, "image": image_rel_path,
233
- "latitude": float(lat) if lat else None, "longitude": float(lon) if lon else None,
234
- "accuracy_m": float(accuracy_m) if accuracy_m != 'IP-Based' else None,
235
- "device_timestamp": device_ts,
236
- "server_timestamp_utc": server_ts_utc,
237
- "location": f"{lat}, {lon}",
238
- "notes": ""
239
- }
240
-
241
- _append_jsonl_in_repo(row)
242
-
243
- status = (
244
- "✅ **Success!** Saved to dataset!\n\n"
245
- f"- Image: `{image_rel_path}`\n"
246
- f"- Lat/Lon: {row['latitude']}, {row['longitude']} (±{row['accuracy_m']} m)"
247
- )
248
- preview = json.dumps(row, indent=2)
249
- return status, preview
250
-
251
- except Exception as e:
252
- error_msg = f"❌ **Critical Save Error**: {str(e)}"
253
- gr.Error(error_msg)
254
- return error_msg, ""
255
-
256
- # --- Gradio Interface ---
257
-
258
- with gr.Blocks(title="Lanternfly Field Capture") as app:
259
- gr.Markdown("# 🦋 Lanternfly Field Capture (Auto-GPS)")
260
- gr.Markdown("The app automatically attempts to get GPS coordinates upon loading. You must click **Allow** in your browser.")
261
-
262
- # Hidden input for GPS data - used only for JS -> Python communication
263
- hidden_gps_input = gr.Textbox(visible=False, elem_id="hidden_gps_input")
264
-
265
- with gr.Row():
266
- with gr.Column(scale=1):
267
- camera = gr.Image(
268
- streaming=False,
269
- height=380,
270
- label="📷 Capture or Upload Photo",
271
- type="pil",
272
- sources=["webcam", "upload"]
273
- )
274
- save_btn = gr.Button("💾 Save Photo and Data to Dataset", variant="primary")
275
-
276
- with gr.Column(scale=1):
277
- # --- Status and GPS Display ---
278
- status = gr.Markdown("🔄 **Initializing...** Waiting for auto-GPS fix.")
279
-
280
- gr.Markdown("### 📍 Location Data")
281
- with gr.Row():
282
- lat_box = gr.Textbox(label="Latitude", interactive=False, value="N/A")
283
- lon_box = gr.Textbox(label="Longitude", interactive=False, value="N/A")
284
- with gr.Row():
285
- accuracy_box = gr.Textbox(label="Accuracy (meters)", interactive=False, value="N/A")
286
- device_ts_box = gr.Textbox(label="Device Timestamp", interactive=False, value="N/A")
287
-
288
- preview = gr.JSON(label="Preview Data Payload", visible=True)
289
-
290
- # --- Event Handlers ---
291
-
292
- # 1. Autostart GPS on page load using the Blocks .load() event
293
- app.load(
294
- fn=None,
295
- inputs=[],
296
- outputs=[],
297
- js=get_gps_js()
298
- )
299
-
300
- # 2. Hidden GPS input change triggers Python backend processing
301
- hidden_gps_input.change(
302
- fn=handle_gps_location,
303
- inputs=[hidden_gps_input],
304
- outputs=[status, lat_box, lon_box, accuracy_box, device_ts_box]
305
- )
306
-
307
- # 3. Save button click
308
- save_btn.click(
309
- fn=save_to_dataset,
310
- inputs=[camera, lat_box, lon_box, accuracy_box, device_ts_box],
311
- outputs=[status, preview]
312
- )
313
-
314
- app.launch(share=True)