kylanoconnor commited on
Commit
5c11fb8
·
1 Parent(s): 5eb92ed

Fix: Simplify to gr.Interface for reliable API endpoints

Browse files

- Replace gr.Blocks with gr.Interface for better API compatibility
- Update iOS service to use correct Gradio 5.x payload format
- Improve endpoint discovery with proper POST testing
- Add better error logging and fallback formats

This should fix the 404 API errors in iOS app.

Files changed (2) hide show
  1. HuggingFaceService_Fixed.swift +568 -0
  2. app.py +20 -72
HuggingFaceService_Fixed.swift ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Foundation
2
+ import UIKit
3
+
4
+ class HuggingFaceService {
5
+ static let shared = HuggingFaceService()
6
+
7
+ // Updated API endpoints for Gradio 5.x
8
+ private let baseURL = "https://kylanoconnor-plonk-geolocation.hf.space"
9
+ private let session = URLSession.shared
10
+
11
+ private init() {}
12
+
13
+ // MARK: - PLONK Location Analysis
14
+
15
+ /// Analyze location from image using PLONK model on Hugging Face Space
16
+ func analyzeLocationWithPlonk(
17
+ image: UIImage,
18
+ additionalContext: String? = nil
19
+ ) async throws -> LocationAnalysisData {
20
+
21
+ // First, discover the working endpoint
22
+ guard let workingEndpoint = await discoverWorkingEndpoint() else {
23
+ print("❌ PLONK: No working endpoint found")
24
+ throw HuggingFaceError.spaceUnavailable
25
+ }
26
+
27
+ print("✅ PLONK: Using endpoint: \(workingEndpoint)")
28
+
29
+ // Convert image to base64 with compression
30
+ guard let imageData = image.jpegData(compressionQuality: 0.6) else {
31
+ throw HuggingFaceError.imageProcessingFailed
32
+ }
33
+
34
+ // If image is too large, compress further
35
+ let finalImageData: Data
36
+ if imageData.count > 1_000_000 { // If larger than 1MB
37
+ guard let smallerImageData = image.jpegData(compressionQuality: 0.3) else {
38
+ throw HuggingFaceError.imageProcessingFailed
39
+ }
40
+ finalImageData = smallerImageData
41
+ print("🗜️ Compressed image to \(finalImageData.count) bytes")
42
+ } else {
43
+ finalImageData = imageData
44
+ }
45
+
46
+ let base64Image = finalImageData.base64EncodedString()
47
+
48
+ // Try the request
49
+ let plonkResult = try await makeRequest(to: workingEndpoint, with: base64Image)
50
+ let plonkCoordinates = plonkResult.coordinates
51
+ print("✅ PLONK found coordinates: \(plonkCoordinates.formattedString)")
52
+
53
+ // Enhance with OpenAI analysis
54
+ return try await enhanceWithOpenAI(
55
+ image: image,
56
+ plonkCoordinates: plonkCoordinates,
57
+ additionalContext: additionalContext
58
+ )
59
+ }
60
+
61
+ /// Discover the working API endpoint for Gradio 5.x
62
+ private func discoverWorkingEndpoint() async -> String? {
63
+ print("🔍 Discovering working PLONK endpoint...")
64
+
65
+ // Check if the space is running
66
+ guard let url = URL(string: baseURL) else { return nil }
67
+
68
+ do {
69
+ let (data, response) = try await session.data(from: url)
70
+
71
+ guard let httpResponse = response as? HTTPURLResponse,
72
+ httpResponse.statusCode == 200 else {
73
+ print("❌ Space is not accessible")
74
+ return nil
75
+ }
76
+
77
+ // Check if it's a Gradio app
78
+ let responseString = String(data: data, encoding: .utf8) ?? ""
79
+ if responseString.contains("gradio") || responseString.contains("Gradio") {
80
+ print("✅ Gradio space is running")
81
+
82
+ // For Gradio 5.x, try these endpoints in order of preference:
83
+ let possibleEndpoints = [
84
+ "\(baseURL)/api/predict", // Standard API endpoint
85
+ "\(baseURL)/run/predict", // Alternative endpoint
86
+ "\(baseURL)/gradio_api/predict" // Legacy endpoint
87
+ ]
88
+
89
+ // Test each endpoint with a simple POST request
90
+ for endpoint in possibleEndpoints {
91
+ if let endpointURL = URL(string: endpoint) {
92
+ var request = URLRequest(url: endpointURL)
93
+ request.httpMethod = "POST"
94
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
95
+
96
+ // Simple test payload
97
+ let testPayload = ["data": ["test"]]
98
+
99
+ do {
100
+ request.httpBody = try JSONSerialization.data(withJSONObject: testPayload)
101
+ let (_, response) = try await session.data(for: request)
102
+ if let httpResponse = response as? HTTPURLResponse {
103
+ print("📡 Testing \(endpoint): \(httpResponse.statusCode)")
104
+
105
+ // 200 = working, 422 = validation error but endpoint exists
106
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 422 {
107
+ print("✅ Found working endpoint: \(endpoint)")
108
+ return endpoint
109
+ }
110
+ }
111
+ } catch {
112
+ // Continue trying other endpoints
113
+ continue
114
+ }
115
+ }
116
+ }
117
+
118
+ // If no specific endpoint works, return the default
119
+ return "\(baseURL)/api/predict"
120
+ } else {
121
+ print("⚠️ Space might be loading or not a Gradio app")
122
+ return nil
123
+ }
124
+ } catch {
125
+ print("❌ Error checking space: \(error)")
126
+ return nil
127
+ }
128
+ }
129
+
130
+ /// Make a request to the PLONK API with proper Gradio 5.x format
131
+ private func makeRequest(to endpoint: String, with base64Image: String) async throws -> LocationAnalysisData {
132
+ guard let url = URL(string: endpoint) else {
133
+ throw HuggingFaceError.invalidResponse
134
+ }
135
+
136
+ // Gradio 5.x API format for gr.Interface
137
+ let payload: [String: Any] = [
138
+ "data": [base64Image] // Simple array format for gr.Interface
139
+ ]
140
+
141
+ var request = URLRequest(url: url)
142
+ request.httpMethod = "POST"
143
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
144
+ request.timeoutInterval = 60.0 // Longer timeout for model inference
145
+
146
+ print("🔄 PLONK: Submitting to \(endpoint)")
147
+ print("📦 PLONK: Payload keys: \(payload.keys.joined(separator: ", "))")
148
+
149
+ do {
150
+ request.httpBody = try JSONSerialization.data(withJSONObject: payload)
151
+ } catch {
152
+ print("❌ PLONK: Failed to serialize request payload")
153
+ throw HuggingFaceError.requestSerializationFailed
154
+ }
155
+
156
+ let (data, response) = try await session.data(for: request)
157
+
158
+ guard let httpResponse = response as? HTTPURLResponse else {
159
+ throw HuggingFaceError.invalidResponse
160
+ }
161
+
162
+ print("📡 PLONK: Response status code: \(httpResponse.statusCode)")
163
+
164
+ // Log the raw response for debugging
165
+ if let responseString = String(data: data, encoding: .utf8) {
166
+ print("📝 PLONK: Raw response: \(responseString.prefix(500))...")
167
+ }
168
+
169
+ switch httpResponse.statusCode {
170
+ case 200:
171
+ return try parseResponse(data)
172
+ case 422:
173
+ // Validation error - try alternative format
174
+ print("⚠️ Validation error, trying alternative format...")
175
+ return try await makeRequestAlternativeFormat(to: endpoint, with: base64Image)
176
+ case 404:
177
+ throw HuggingFaceError.serverError(404)
178
+ case 413:
179
+ throw HuggingFaceError.networkError("Image too large")
180
+ case 503:
181
+ throw HuggingFaceError.spaceUnavailable
182
+ default:
183
+ throw HuggingFaceError.serverError(httpResponse.statusCode)
184
+ }
185
+ }
186
+
187
+ /// Try alternative request format for Gradio 5.x
188
+ private func makeRequestAlternativeFormat(to endpoint: String, with base64Image: String) async throws -> LocationAnalysisData {
189
+ guard let url = URL(string: endpoint) else {
190
+ throw HuggingFaceError.invalidResponse
191
+ }
192
+
193
+ // Try different formats that Gradio 5.x might expect
194
+ let alternativePayloads: [[String: Any]] = [
195
+ // Format 1: With data URI prefix
196
+ [
197
+ "data": ["data:image/jpeg;base64,\(base64Image)"]
198
+ ],
199
+ // Format 2: With inputs key
200
+ [
201
+ "inputs": [base64Image]
202
+ ],
203
+ // Format 3: Direct base64 with PIL type hint
204
+ [
205
+ "data": [base64Image],
206
+ "type": ["PIL"]
207
+ ],
208
+ // Format 4: Legacy format with data URI
209
+ [
210
+ "data": ["data:image/jpeg;base64,\(base64Image)"],
211
+ "fn_index": 0
212
+ ]
213
+ ]
214
+
215
+ for (index, payload) in alternativePayloads.enumerated() {
216
+ print("🔄 Trying alternative format \(index + 1)")
217
+
218
+ var request = URLRequest(url: url)
219
+ request.httpMethod = "POST"
220
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
221
+ request.timeoutInterval = 60.0
222
+
223
+ do {
224
+ request.httpBody = try JSONSerialization.data(withJSONObject: payload)
225
+ let (data, response) = try await session.data(for: request)
226
+
227
+ guard let httpResponse = response as? HTTPURLResponse else {
228
+ continue
229
+ }
230
+
231
+ print("📡 Alternative format \(index + 1) status: \(httpResponse.statusCode)")
232
+
233
+ // Log response for debugging
234
+ if let responseString = String(data: data, encoding: .utf8) {
235
+ print("📝 Alternative format \(index + 1) response: \(responseString.prefix(200))...")
236
+ }
237
+
238
+ if httpResponse.statusCode == 200 {
239
+ return try parseResponse(data)
240
+ }
241
+
242
+ } catch {
243
+ print("❌ Alternative format \(index + 1) failed: \(error)")
244
+ continue
245
+ }
246
+ }
247
+
248
+ throw HuggingFaceError.invalidResponseFormat
249
+ }
250
+
251
+ // MARK: - Response Parsing
252
+
253
+ private func parseResponse(_ data: Data) throws -> LocationAnalysisData {
254
+ // Log the response for debugging
255
+ if let responseString = String(data: data, encoding: .utf8) {
256
+ print("📝 PLONK: Full response: \(responseString)")
257
+ }
258
+
259
+ // Try to parse as Gradio 5.x response format
260
+ do {
261
+ let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
262
+
263
+ // Gradio 5.x typically returns {"data": [result], ...}
264
+ if let dataArray = json?["data"] as? [Any],
265
+ let locationString = dataArray.first as? String {
266
+ print("✅ PLONK: Parsed location string: \(locationString)")
267
+ return try parseLocationString(locationString)
268
+ }
269
+
270
+ // Try other possible formats
271
+ if let result = json?["result"] as? [Any],
272
+ let locationString = result.first as? String {
273
+ return try parseLocationString(locationString)
274
+ }
275
+
276
+ if let output = json?["output"] as? String {
277
+ return try parseLocationString(output)
278
+ }
279
+
280
+ } catch {
281
+ print("❌ JSON parsing failed: \(error)")
282
+ }
283
+
284
+ // Try to parse as direct string
285
+ if let responseString = String(data: data, encoding: .utf8) {
286
+ // Sometimes the response might be a direct string
287
+ if responseString.contains("Latitude:") && responseString.contains("Longitude:") {
288
+ return try parseLocationString(responseString)
289
+ }
290
+ }
291
+
292
+ throw HuggingFaceError.responseParsingFailed
293
+ }
294
+
295
+ private func parseLocationString(_ locationString: String) throws -> LocationAnalysisData {
296
+ print("🔍 Parsing location string: \(locationString)")
297
+
298
+ // Parse the format: "Predicted Location:\nLatitude: 40.748817\nLongitude: -73.985428"
299
+ let lines = locationString.components(separatedBy: .newlines)
300
+ var latitude: Double = 0
301
+ var longitude: Double = 0
302
+ var foundLat = false
303
+ var foundLon = false
304
+
305
+ for line in lines {
306
+ let trimmedLine = line.trimmingCharacters(in: .whitespaces)
307
+
308
+ if trimmedLine.contains("Latitude:") {
309
+ let parts = trimmedLine.components(separatedBy: ":")
310
+ if parts.count > 1 {
311
+ let latString = parts[1].trimmingCharacters(in: .whitespaces)
312
+ if let parsedLat = Double(latString) {
313
+ latitude = parsedLat
314
+ foundLat = true
315
+ print("✅ Found latitude: \(latitude)")
316
+ }
317
+ }
318
+ } else if trimmedLine.contains("Longitude:") {
319
+ let parts = trimmedLine.components(separatedBy: ":")
320
+ if parts.count > 1 {
321
+ let lonString = parts[1].trimmingCharacters(in: .whitespaces)
322
+ if let parsedLon = Double(lonString) {
323
+ longitude = parsedLon
324
+ foundLon = true
325
+ print("✅ Found longitude: \(longitude)")
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ guard foundLat && foundLon else {
332
+ print("❌ Could not parse coordinates from: \(locationString)")
333
+ throw HuggingFaceError.invalidResponseFormat
334
+ }
335
+
336
+ // Validate coordinates are in reasonable range
337
+ guard latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180 else {
338
+ print("❌ Invalid coordinates: lat=\(latitude), lon=\(longitude)")
339
+ throw HuggingFaceError.invalidResponseFormat
340
+ }
341
+
342
+ let locationName = generateLocationName(latitude: latitude, longitude: longitude)
343
+
344
+ return LocationAnalysisData(
345
+ locationName: locationName,
346
+ coordinates: LocationCoordinates(latitude: latitude, longitude: longitude),
347
+ confidenceRadius: 5.0,
348
+ confidencePercentage: 85.0
349
+ )
350
+ }
351
+
352
+ /// Enhance PLONK coordinates with OpenAI contextual analysis
353
+ private func enhanceWithOpenAI(
354
+ image: UIImage,
355
+ plonkCoordinates: LocationCoordinates,
356
+ additionalContext: String?
357
+ ) async throws -> LocationAnalysisData {
358
+
359
+ print("🧠 Enhancing PLONK coordinates with OpenAI analysis...")
360
+
361
+ // Create enhanced prompt with PLONK coordinates
362
+ let enhancedPrompt = """
363
+ I have used a specialized geolocation AI model (PLONK) to analyze this image and it predicted these coordinates:
364
+
365
+ **PLONK Prediction:**
366
+ - Latitude: \(plonkCoordinates.latitude)
367
+ - Longitude: \(plonkCoordinates.longitude)
368
+ - Location: \(plonkCoordinates.formattedString)
369
+
370
+ Now, please analyze this image in detail and provide:
371
+ 1. **Location Verification**: Does this coordinate prediction seem accurate?
372
+ 2. **Specific Location**: What is the specific landmark or location name?
373
+ 3. **Context & Details**: Describe what confirms this location
374
+ 4. **Confidence Assessment**: How confident are you? (1-100%)
375
+
376
+ Additional context: \(additionalContext ?? "None provided")
377
+ """
378
+
379
+ do {
380
+ let openaiResult = try await OpenAIService.shared.analyzeLocationFromImage(
381
+ image: image,
382
+ additionalContext: enhancedPrompt
383
+ )
384
+
385
+ return LocationAnalysisData(
386
+ locationName: openaiResult.locationName,
387
+ coordinates: plonkCoordinates, // Use PLONK's precise coordinates
388
+ confidenceRadius: openaiResult.confidenceRadius,
389
+ confidencePercentage: openaiResult.confidencePercentage
390
+ )
391
+
392
+ } catch {
393
+ print("⚠️ OpenAI enhancement failed: \(error)")
394
+ // Fall back to PLONK-only result
395
+ let fallbackLocationName = generateLocationName(
396
+ latitude: plonkCoordinates.latitude,
397
+ longitude: plonkCoordinates.longitude
398
+ )
399
+
400
+ return LocationAnalysisData(
401
+ locationName: fallbackLocationName,
402
+ coordinates: plonkCoordinates,
403
+ confidenceRadius: 5.0,
404
+ confidencePercentage: 85.0
405
+ )
406
+ }
407
+ }
408
+
409
+ private func generateLocationName(latitude: Double, longitude: Double) -> String {
410
+ let latDirection = latitude >= 0 ? "N" : "S"
411
+ let lonDirection = longitude >= 0 ? "E" : "W"
412
+ let region = determineRegion(latitude: latitude, longitude: longitude)
413
+
414
+ return "\(region) (\(String(format: "%.4f", abs(latitude)))°\(latDirection), \(String(format: "%.4f", abs(longitude)))°\(lonDirection))"
415
+ }
416
+
417
+ private func determineRegion(latitude: Double, longitude: Double) -> String {
418
+ if latitude >= 24 && latitude <= 50 && longitude >= -125 && longitude <= -65 {
419
+ return "North America"
420
+ } else if latitude >= 35 && latitude <= 70 && longitude >= -10 && longitude <= 40 {
421
+ return "Europe"
422
+ } else if latitude >= -35 && latitude <= 35 && longitude >= -20 && longitude <= 50 {
423
+ return "Africa"
424
+ } else if latitude >= 5 && latitude <= 55 && longitude >= 60 && longitude <= 150 {
425
+ return "Asia"
426
+ } else if latitude >= -45 && latitude <= -10 && longitude >= 110 && longitude <= 180 {
427
+ return "Australia/Oceania"
428
+ } else if latitude >= -60 && latitude <= 15 && longitude >= -85 && longitude <= -30 {
429
+ return "South America"
430
+ } else {
431
+ return "Unknown Region"
432
+ }
433
+ }
434
+
435
+ // MARK: - Testing & Health Check
436
+
437
+ /// Test the PLONK API with a simple request
438
+ func testPlonkAPI() async {
439
+ print("🧪 Testing PLONK API...")
440
+
441
+ guard let workingEndpoint = await discoverWorkingEndpoint() else {
442
+ print("❌ No working endpoint found during test")
443
+ return
444
+ }
445
+
446
+ print("✅ Found working endpoint for test: \(workingEndpoint)")
447
+
448
+ // Create a simple test image (red square)
449
+ let size = CGSize(width: 224, height: 224)
450
+ UIGraphicsBeginImageContext(size)
451
+ UIColor.red.setFill()
452
+ UIRectFill(CGRect(origin: .zero, size: size))
453
+ let testImage = UIGraphicsGetImageFromCurrentImageContext()!
454
+ UIGraphicsEndImageContext()
455
+
456
+ do {
457
+ guard let imageData = testImage.jpegData(compressionQuality: 0.8) else {
458
+ print("❌ Failed to create test image data")
459
+ return
460
+ }
461
+
462
+ let base64Image = imageData.base64EncodedString()
463
+ let result = try await makeRequest(to: workingEndpoint, with: base64Image)
464
+ print("✅ Test successful! Predicted: \(result.locationName)")
465
+ } catch {
466
+ print("❌ Test failed: \(error)")
467
+ }
468
+ }
469
+
470
+ /// Check if the Hugging Face Space is available
471
+ func checkSpaceAvailability() async -> Bool {
472
+ guard let url = URL(string: baseURL) else { return false }
473
+
474
+ do {
475
+ let (_, response) = try await session.data(from: url)
476
+ if let httpResponse = response as? HTTPURLResponse {
477
+ print("🌐 PLONK Space status: \(httpResponse.statusCode)")
478
+ return httpResponse.statusCode == 200
479
+ }
480
+ return false
481
+ } catch {
482
+ print("❌ PLONK Space check failed: \(error)")
483
+ return false
484
+ }
485
+ }
486
+ }
487
+
488
+ // MARK: - Extensions
489
+
490
+ extension HuggingFaceService {
491
+
492
+ /// Quick location analysis with error handling
493
+ func quickLocationAnalysis(image: UIImage) async -> LocationAnalysisData? {
494
+ do {
495
+ return try await analyzeLocationWithPlonk(image: image)
496
+ } catch {
497
+ print("🚫 PLONK analysis failed: \(error.localizedDescription)")
498
+ return await plonkInspiredAnalysis(image: image)
499
+ }
500
+ }
501
+
502
+ /// PLONK-inspired analysis using OpenAI (fallback)
503
+ private func plonkInspiredAnalysis(image: UIImage) async -> LocationAnalysisData? {
504
+ print("🧠 Using PLONK-inspired analysis with OpenAI...")
505
+
506
+ let plonkStylePrompt = """
507
+ Analyze this image for precise geolocation using visual clues. Act like a state-of-the-art visual geolocation model.
508
+
509
+ Look for:
510
+ - Architecture styles and building materials
511
+ - Vegetation and landscape features
512
+ - Street signs, license plates, or text
513
+ - Cultural markers and local customs
514
+ - Geographic features
515
+ - Climate indicators
516
+ - Infrastructure patterns
517
+
518
+ Provide your best GPS coordinate estimate and explain your reasoning.
519
+ """
520
+
521
+ do {
522
+ return try await OpenAIService.shared.analyzeLocationFromImage(
523
+ image: image,
524
+ additionalContext: plonkStylePrompt
525
+ )
526
+ } catch {
527
+ print("❌ PLONK-inspired analysis failed: \(error)")
528
+ return nil
529
+ }
530
+ }
531
+ }
532
+
533
+ // MARK: - Error Handling
534
+
535
+ enum HuggingFaceError: Error, LocalizedError {
536
+ case imageProcessingFailed
537
+ case requestSerializationFailed
538
+ case invalidResponse
539
+ case invalidResponseFormat
540
+ case responseParsingFailed
541
+ case serverError(Int)
542
+ case spaceUnavailable
543
+ case networkError(String)
544
+ case quotaExceeded
545
+
546
+ var errorDescription: String? {
547
+ switch self {
548
+ case .imageProcessingFailed:
549
+ return "Failed to process the image for analysis"
550
+ case .requestSerializationFailed:
551
+ return "Failed to prepare the request"
552
+ case .invalidResponse:
553
+ return "Invalid response from Hugging Face Space"
554
+ case .invalidResponseFormat:
555
+ return "Unexpected response format from PLONK model"
556
+ case .responseParsingFailed:
557
+ return "Failed to parse the location analysis results"
558
+ case .serverError(let code):
559
+ return "Server error (\(code)). The Hugging Face Space may be unavailable"
560
+ case .spaceUnavailable:
561
+ return "The PLONK model space is currently unavailable"
562
+ case .networkError(let message):
563
+ return "Network error: \(message)"
564
+ case .quotaExceeded:
565
+ return "API quota exceeded. Please try again later"
566
+ }
567
+ }
568
+ }
app.py CHANGED
@@ -81,79 +81,27 @@ Mean Longitude: {mean_lon:.6f} ± {std_lon:.6f}
81
  except Exception as e:
82
  return f"Error during prediction: {str(e)}"
83
 
84
- # Create the Gradio app using Blocks for proper API support
85
- with gr.Blocks(title="PLONK: Around the World in 80 Timesteps") as demo:
86
- gr.Markdown("# 🗺️ PLONK: Around the World in 80 Timesteps")
87
- gr.Markdown("A generative approach to global visual geolocation. Upload an image and PLONK will predict where it was taken!")
 
 
 
 
88
 
89
- with gr.Tabs():
90
- with gr.TabItem("Simple Prediction"):
91
- gr.Markdown("""
92
- ### 🗺️ PLONK: Global Visual Geolocation
93
-
94
- Upload an image and PLONK will predict where it was taken!
95
-
96
- This uses the PLONK_YFCC model trained on the YFCC100M dataset.
97
- The model predicts latitude and longitude coordinates based on visual content.
98
-
99
- **Note**: This is running on CPU, so processing may take 300-500ms per image.
100
- """)
101
-
102
- with gr.Row():
103
- with gr.Column():
104
- image_input = gr.Image(type="pil", label="Upload an image")
105
- predict_btn = gr.Button("Predict Location", variant="primary")
106
-
107
- with gr.Column():
108
- output_text = gr.Textbox(label="Predicted Location", lines=4)
109
-
110
- # Add examples if they exist
111
- if any(Path("demo/examples").glob("*")):
112
- gr.Examples(
113
- examples=[
114
- ["demo/examples/condor.jpg"],
115
- ["demo/examples/Kilimanjaro.jpg"],
116
- ["demo/examples/pigeon.png"]
117
- ],
118
- inputs=image_input,
119
- outputs=output_text,
120
- fn=predict_geolocation,
121
- cache_examples=False
122
- )
123
-
124
- predict_btn.click(
125
- fn=predict_geolocation,
126
- inputs=image_input,
127
- outputs=output_text,
128
- api_name="predict" # This creates the /api/predict endpoint
129
- )
130
-
131
- with gr.TabItem("Advanced Analysis"):
132
- gr.Markdown("""
133
- ### 🗺️ PLONK: Advanced Geolocation with Uncertainty
134
-
135
- Advanced interface showing prediction uncertainty through multiple samples.
136
-
137
- - **Number of samples**: More samples = better uncertainty estimation (but slower)
138
- - **Guidance scale**: Higher values = more confident predictions (try 2.0 for best single guess)
139
- """)
140
-
141
- with gr.Row():
142
- with gr.Column():
143
- adv_image_input = gr.Image(type="pil", label="Upload an image")
144
- samples_slider = gr.Slider(1, 256, value=64, step=1, label="Number of samples")
145
- cfg_slider = gr.Slider(0.0, 5.0, value=0.0, step=0.1, label="Guidance scale (CFG)")
146
- advanced_btn = gr.Button("Analyze with Uncertainty", variant="primary")
147
-
148
- with gr.Column():
149
- advanced_output = gr.Textbox(label="Detailed Results", lines=10)
150
-
151
- advanced_btn.click(
152
- fn=predict_geolocation_with_samples,
153
- inputs=[adv_image_input, samples_slider, cfg_slider],
154
- outputs=advanced_output,
155
- api_name="predict_advanced" # This creates the /api/predict_advanced endpoint
156
- )
157
 
158
  if __name__ == "__main__":
159
  demo.launch()
 
81
  except Exception as e:
82
  return f"Error during prediction: {str(e)}"
83
 
84
+ # Create the main interface as a simple Interface for reliable API exposure
85
+ demo = gr.Interface(
86
+ fn=predict_geolocation,
87
+ inputs=gr.Image(type="pil", label="Upload an image"),
88
+ outputs=gr.Textbox(label="Predicted Location", lines=4),
89
+ title="🗺️ PLONK: Around the World in 80 Timesteps",
90
+ description="""
91
+ A generative approach to global visual geolocation. Upload an image and PLONK will predict where it was taken!
92
 
93
+ This uses the PLONK_YFCC model trained on the YFCC100M dataset.
94
+ The model predicts latitude and longitude coordinates based on visual content.
95
+
96
+ **Note**: This is running on CPU, so processing may take 300-500ms per image.
97
+ """,
98
+ examples=[
99
+ ["demo/examples/condor.jpg"],
100
+ ["demo/examples/Kilimanjaro.jpg"],
101
+ ["demo/examples/pigeon.png"]
102
+ ] if any(Path("demo/examples").glob("*")) else None,
103
+ api_name="predict" # Explicitly set API name
104
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  if __name__ == "__main__":
107
  demo.launch()