samwaugh commited on
Commit
586c545
·
1 Parent(s): cfa56f8

major fix try

Browse files
backend/runner/app.py CHANGED
@@ -79,7 +79,6 @@ executor = ThreadPoolExecutor(max_workers=4)
79
 
80
  # Use the Space data volume, not the repo folder
81
  from .config import (
82
- DATA_ROOT,
83
  ARTIFACTS_DIR,
84
  OUTPUTS_DIR,
85
  JSON_INFO_DIR,
@@ -202,32 +201,32 @@ def upload_file(run_id: str):
202
 
203
  # Ensure artifacts directory exists
204
  try:
205
- os.makedirs(ARTIFACTS_DIR, exist_ok=True)
206
- print(f"📤 Artifacts directory: {ARTIFACTS_DIR} (exists: {os.path.exists(ARTIFACTS_DIR)})")
207
  except Exception as e:
208
  print(f"❌ Failed to create artifacts directory: {e}")
209
  return jsonify({"error": f"directory-creation-failed: {str(e)}"}), 500
210
 
211
  # Save the file as artifacts/<runId>.jpg
212
- file_path = os.path.join(ARTIFACTS_DIR, f"{run_id}.jpg")
213
  print(f"📤 Saving file to {file_path}")
214
 
215
  try:
216
- file.save(file_path)
217
  except Exception as e:
218
  print(f"❌ Failed to save file: {e}")
219
  return jsonify({"error": f"file-save-failed: {str(e)}"}), 500
220
 
221
  # Check file exists otherwise 500
222
- if not os.path.exists(file_path):
223
  print(f"❌ File not saved for run {run_id}")
224
  return jsonify({"error": "file-not-saved"}), 500
225
 
226
- file_size = os.path.getsize(file_path)
227
  print(f"✅ File saved successfully for run {run_id}, size: {file_size} bytes")
228
 
229
  # Respond with 204 No Content (success, no response body)
230
- return "{}", 204
231
 
232
  except Exception as e:
233
  print(f"❌ Unexpected error in upload_file: {e}")
@@ -339,8 +338,8 @@ def get_output_file(filename: str):
339
  filename = filename + ".json"
340
 
341
  # Check if file exists
342
- file_path = os.path.join(OUTPUTS_DIR, filename)
343
- if not os.path.exists(file_path):
344
  return jsonify({"error": "file-not-found"}), 404
345
 
346
  return send_from_directory(OUTPUTS_DIR, filename)
@@ -449,27 +448,34 @@ def cell_similarity():
449
  ]
450
  })
451
 
452
- run_id = request.args["runId"]
453
- row = int(request.args["row"])
454
- col = int(request.args["col"])
455
- k = int(request.args.get("k", 25))
456
-
457
- # Get the run info to retrieve filtering parameters
458
- run_info = RUNS.get(run_id, {})
459
- topics = run_info.get("topics", [])
460
- creators = run_info.get("creators", [])
461
- model = run_info.get("model", "paintingclip")
462
-
463
- img_path = ARTIFACTS_DIR / f"{run_id}.jpg"
464
- results = inference.run_inference(
465
- str(img_path),
466
- cell=(row, col),
467
- top_k=k,
468
- filter_topics=topics,
469
- filter_creators=creators,
470
- model_type=model,
471
- )
472
- return jsonify(results)
 
 
 
 
 
 
 
473
 
474
 
475
  # --------------------------------------------------------------------------- #
 
79
 
80
  # Use the Space data volume, not the repo folder
81
  from .config import (
 
82
  ARTIFACTS_DIR,
83
  OUTPUTS_DIR,
84
  JSON_INFO_DIR,
 
201
 
202
  # Ensure artifacts directory exists
203
  try:
204
+ ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
205
+ print(f"📤 Artifacts directory: {ARTIFACTS_DIR} (exists: {ARTIFACTS_DIR.exists()})")
206
  except Exception as e:
207
  print(f"❌ Failed to create artifacts directory: {e}")
208
  return jsonify({"error": f"directory-creation-failed: {str(e)}"}), 500
209
 
210
  # Save the file as artifacts/<runId>.jpg
211
+ file_path = ARTIFACTS_DIR / f"{run_id}.jpg"
212
  print(f"📤 Saving file to {file_path}")
213
 
214
  try:
215
+ file.save(str(file_path))
216
  except Exception as e:
217
  print(f"❌ Failed to save file: {e}")
218
  return jsonify({"error": f"file-save-failed: {str(e)}"}), 500
219
 
220
  # Check file exists otherwise 500
221
+ if not file_path.exists():
222
  print(f"❌ File not saved for run {run_id}")
223
  return jsonify({"error": "file-not-saved"}), 500
224
 
225
+ file_size = file_path.stat().st_size
226
  print(f"✅ File saved successfully for run {run_id}, size: {file_size} bytes")
227
 
228
  # Respond with 204 No Content (success, no response body)
229
+ return "", 204
230
 
231
  except Exception as e:
232
  print(f"❌ Unexpected error in upload_file: {e}")
 
338
  filename = filename + ".json"
339
 
340
  # Check if file exists
341
+ file_path = OUTPUTS_DIR / filename
342
+ if not file_path.exists():
343
  return jsonify({"error": "file-not-found"}), 404
344
 
345
  return send_from_directory(OUTPUTS_DIR, filename)
 
448
  ]
449
  })
450
 
451
+ try:
452
+ run_id = request.args["runId"]
453
+ row = int(request.args["row"])
454
+ col = int(request.args["col"])
455
+ k = int(request.args.get("k", 25))
456
+
457
+ # Get the run info to retrieve filtering parameters
458
+ run_info = RUNS.get(run_id, {})
459
+ topics = run_info.get("topics", [])
460
+ creators = run_info.get("creators", [])
461
+ model = run_info.get("model", "paintingclip")
462
+
463
+ img_path = ARTIFACTS_DIR / f"{run_id}.jpg"
464
+ if not img_path.exists():
465
+ return jsonify({"error": "image-not-found"}), 404
466
+
467
+ results = inference.run_inference(
468
+ str(img_path),
469
+ cell=(row, col),
470
+ top_k=k,
471
+ filter_topics=topics,
472
+ filter_creators=creators,
473
+ model_type=model,
474
+ )
475
+ return jsonify(results)
476
+ except Exception as e:
477
+ print(f"❌ Error in cell_similarity: {e}")
478
+ return jsonify({"error": str(e)}), 500
479
 
480
 
481
  # --------------------------------------------------------------------------- #
backend/runner/config.py CHANGED
@@ -6,30 +6,25 @@ All runner modules should import from this module instead of defining their own
6
  import os
7
  from pathlib import Path
8
 
9
- # Get the directory where this config.py file is located
10
- CONFIG_DIR = Path(__file__).parent.parent.parent # Go up from backend/runner/config.py to project root
11
-
12
- # Data root from environment variable (set by Hugging Face Space)
13
- # Fall back to project data directory if /app/data doesn't exist or isn't writable
14
- DATA_ROOT_ENV = os.getenv("DATA_ROOT", "/app/data")
15
- DATA_ROOT = Path(DATA_ROOT_ENV)
16
-
17
- # Check if the default directory is writable, fall back to project data directory if not
18
- if not DATA_ROOT.exists() or not os.access(DATA_ROOT, os.W_OK):
19
- print(f"⚠️ DATA_ROOT {DATA_ROOT} not writable, falling back to project data directory")
20
- DATA_ROOT = CONFIG_DIR / "data"
21
  else:
22
- print(f"✅ Using DATA_ROOT: {DATA_ROOT}")
23
 
24
- DATA_ROOT = DATA_ROOT.resolve()
25
 
26
- # Core data directories
27
- EMBEDDINGS_DIR = DATA_ROOT / "embeddings"
28
- JSON_INFO_DIR = DATA_ROOT / "json_info"
29
- MODELS_DIR = DATA_ROOT / "models"
30
- OUTPUTS_DIR = DATA_ROOT / "outputs"
31
- ARTIFACTS_DIR = DATA_ROOT / "artifacts"
32
- MARKER_DIR = DATA_ROOT / "marker_output"
33
 
34
  # Model-specific embedding directories
35
  CLIP_EMBEDDINGS_DIR = EMBEDDINGS_DIR / "CLIP_Embeddings"
@@ -38,12 +33,9 @@ PAINTINGCLIP_EMBEDDINGS_DIR = EMBEDDINGS_DIR / "PaintingClip_Embeddings"
38
  # Model directories
39
  PAINTINGCLIP_MODEL_DIR = MODELS_DIR / "PaintingClip"
40
 
41
- # Metadata files
42
- SENTENCES_JSON = JSON_INFO_DIR / "sentences.json"
43
- WORKS_JSON = JSON_INFO_DIR / "works.json"
44
- TOPICS_JSON = JSON_INFO_DIR / "topics.json"
45
- CREATORS_JSON = JSON_INFO_DIR / "creators.json"
46
- TOPIC_NAMES_JSON = JSON_INFO_DIR / "topic_names.json"
47
 
48
  # Ensure writable directories exist
49
  for dir_path in [OUTPUTS_DIR, ARTIFACTS_DIR]:
@@ -52,3 +44,10 @@ for dir_path in [OUTPUTS_DIR, ARTIFACTS_DIR]:
52
  print(f"✅ Ensured directory exists: {dir_path}")
53
  except Exception as e:
54
  print(f"⚠️ Could not create directory {dir_path}: {e}")
 
 
 
 
 
 
 
 
6
  import os
7
  from pathlib import Path
8
 
9
+ # READ root (repo data - read-only)
10
+ PROJECT_ROOT = Path(__file__).resolve().parents[2]
11
+ DATA_READ_ROOT = PROJECT_ROOT / "data"
12
+
13
+ # WRITE root (Space volume or tmp - writable)
14
+ WRITE_ROOT = Path(os.getenv("WRITE_ROOT", os.getenv("DATA_ROOT", "/data")))
15
+ if not WRITE_ROOT.exists() or not os.access(WRITE_ROOT, os.W_OK):
16
+ print(f"⚠️ WRITE_ROOT {WRITE_ROOT} not writable, falling back to /tmp")
17
+ WRITE_ROOT = Path("/tmp")
 
 
 
18
  else:
19
+ print(f"✅ Using WRITE_ROOT: {WRITE_ROOT}")
20
 
21
+ print(f"✅ Using READ_ROOT: {DATA_READ_ROOT}")
22
 
23
+ # Read-only directories (from repo)
24
+ EMBEDDINGS_DIR = DATA_READ_ROOT / "embeddings"
25
+ JSON_INFO_DIR = DATA_READ_ROOT / "json_info"
26
+ MODELS_DIR = DATA_READ_ROOT / "models"
27
+ MARKER_DIR = DATA_READ_ROOT / "marker_output"
 
 
28
 
29
  # Model-specific embedding directories
30
  CLIP_EMBEDDINGS_DIR = EMBEDDINGS_DIR / "CLIP_Embeddings"
 
33
  # Model directories
34
  PAINTINGCLIP_MODEL_DIR = MODELS_DIR / "PaintingClip"
35
 
36
+ # Writable directories (outside repo)
37
+ OUTPUTS_DIR = WRITE_ROOT / "outputs"
38
+ ARTIFACTS_DIR = WRITE_ROOT / "artifacts"
 
 
 
39
 
40
  # Ensure writable directories exist
41
  for dir_path in [OUTPUTS_DIR, ARTIFACTS_DIR]:
 
44
  print(f"✅ Ensured directory exists: {dir_path}")
45
  except Exception as e:
46
  print(f"⚠️ Could not create directory {dir_path}: {e}")
47
+
48
+ # Metadata files
49
+ SENTENCES_JSON = JSON_INFO_DIR / "sentences.json"
50
+ WORKS_JSON = JSON_INFO_DIR / "works.json"
51
+ TOPICS_JSON = JSON_INFO_DIR / "topics.json"
52
+ CREATORS_JSON = JSON_INFO_DIR / "creators.json"
53
+ TOPIC_NAMES_JSON = JSON_INFO_DIR / "topic_names.json"
frontend/js/artefact-context.js CHANGED
@@ -353,7 +353,18 @@ $(document).ready(function () {
353
  $('#exampleContainer').addClass('d-none');
354
  $('#workingOverlay').removeClass('d-none');
355
  $('#imageTools').removeClass('d-none');
356
- fetchPresign();
 
 
 
 
 
 
 
 
 
 
 
357
  };
358
  reader.readAsDataURL(file);
359
  }
@@ -429,20 +440,26 @@ $(document).ready(function () {
429
  * and registering a run. Triggers polling for run status.
430
  */
431
  function fetchPresign() {
432
- $('#debugStatus').text('Requesting ID...');
433
- logWorkingMessage('Requesting Session ID...', 'text-white');
434
-
435
- // Save the current image to history immediately when backend processing starts
436
- saveCurrentImageToHistory();
437
-
438
- fetch(`${API_BASE_URL}/presign`, {
439
- method: 'POST',
440
- headers: {
441
- 'Content-Type': 'application/json'
442
- },
443
- body: JSON.stringify({ fileName: 'selected.jpg' })
444
- })
445
- .then(res => res.json())
 
 
 
 
 
 
446
  .then(data => {
447
  runId = data.runId;
448
  imageKey = data.imageKey;
@@ -548,7 +565,14 @@ function fetchPresign() {
548
  console.error('Presign error:', err);
549
  $('#debugStatus').text('Error fetching ID');
550
  logWorkingMessage('Error fetching ID', 'text-danger');
 
 
 
551
  });
 
 
 
 
552
  }
553
 
554
  /**
@@ -557,11 +581,21 @@ function fetchPresign() {
557
  * @param {string} runId - The run/session ID to poll.
558
  */
559
  function pollRunStatus(runId) {
 
 
 
 
 
560
  logWorkingMessage('Polling run status...', 'text-white');
561
 
562
  const intervalId = setInterval(() => {
563
  fetch(`${API_BASE_URL}/runs/${runId}`)
564
- .then(res => res.json())
 
 
 
 
 
565
  .then(data => {
566
  $('#debugStatus').text(`Status: ${data.status}`);
567
  logWorkingMessage(`Status: ${data.status}`, 'text-white');
@@ -611,8 +645,10 @@ function pollRunStatus(runId) {
611
  })
612
  .catch(err => {
613
  console.error('Polling error:', err);
614
- logWorkingMessage('Error polling status', 'text-danger');
615
  clearInterval(intervalId);
 
 
616
  });
617
  }, 1000);
618
  }
@@ -638,58 +674,71 @@ function escapeHTML(str) {
638
  * @param {Array|Object} data - Array of sentence objects or {sentences:[…]}
639
  */
640
  function display_sentences(data) {
641
- console.log('display_sentences called with:', data); // Debug logging
642
-
643
- // normalise payload
644
- if (!Array.isArray(data)) {
645
- data = (data && Array.isArray(data.sentences)) ? data.sentences : [];
646
- }
647
- console.log('Normalized data:', data); // Debug logging
648
-
649
- if (!data.length) { // nothing to show ⇒ just hide overlay
650
- $('#workingOverlay').addClass('d-none');
651
- return;
652
- }
653
-
654
- // Show the sentences panel
655
- $('.col-md-3').removeClass('d-none');
656
- $('#sentenceList').empty();
657
-
658
- /* ---------- sentence list construction ---------- */
659
- data.forEach((item, index) => {
660
- console.log(`Processing item ${index}:`, item); // Debug logging
661
 
662
- // Validate required fields
663
- if (!item.english_original || typeof item.english_original !== 'string') {
664
- console.warn(`Item ${index} has invalid english_original:`, item.english_original);
665
- return; // Skip this item
 
666
  }
667
 
668
- const li = $(`
669
- <li class="list-group-item sentence-item mb-1"
670
- data-work="${item.work || 'unknown'}"
671
- data-sentence="${escapeHTML(item.english_original)}">
672
- <div class="d-flex align-items-center">
673
- <span class="flex-grow-1">${escapeHTML(item.english_original)}</span>
674
- <button class="btn btn-sm btn-outline-dark ms-2 heatmap-btn"
675
- title="View heatmap"
676
- data-sentence="${escapeHTML(item.english_original)}">
677
- <i class="bi bi-thermometer-half"></i>
678
- </button>
679
- </div>
680
- </li>
681
- `);
682
- li.find('span').on('click', function () {
683
- lookupDOI(li.data('work'), li.data('sentence'));
684
- });
685
- li.find('.heatmap-btn').on('click', function(e) {
686
- e.stopPropagation();
687
- requestHeatmap($(this).data('sentence'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
  });
689
- $('#sentenceList').append(li);
690
- });
691
- showBottomCards();
692
- adjustMainWidth();
 
 
 
693
  }
694
 
695
  // helper runs whenever the right-hand column is shown/hidden
 
353
  $('#exampleContainer').addClass('d-none');
354
  $('#workingOverlay').removeClass('d-none');
355
  $('#imageTools').removeClass('d-none');
356
+
357
+ // Fix: Wait for image to load before proceeding
358
+ $('#uploadedImage').on('load', function() {
359
+ fetchPresign();
360
+ });
361
+
362
+ // Fallback if load event doesn't fire
363
+ setTimeout(() => {
364
+ if ($('#uploadedImage').attr('src')) {
365
+ fetchPresign();
366
+ }
367
+ }, 1000);
368
  };
369
  reader.readAsDataURL(file);
370
  }
 
440
  * and registering a run. Triggers polling for run status.
441
  */
442
  function fetchPresign() {
443
+ try {
444
+ $('#debugStatus').text('Requesting ID...');
445
+ logWorkingMessage('Requesting Session ID...', 'text-white');
446
+
447
+ // Fix: Call saveCurrentImageToHistory here
448
+ saveCurrentImageToHistory();
449
+
450
+ fetch(`${API_BASE_URL}/presign`, {
451
+ method: 'POST',
452
+ headers: {
453
+ 'Content-Type': 'application/json'
454
+ },
455
+ body: JSON.stringify({ fileName: 'selected.jpg' })
456
+ })
457
+ .then(res => {
458
+ if (!res.ok) {
459
+ throw new Error(`Presign failed: ${res.status}`);
460
+ }
461
+ return res.json();
462
+ })
463
  .then(data => {
464
  runId = data.runId;
465
  imageKey = data.imageKey;
 
565
  console.error('Presign error:', err);
566
  $('#debugStatus').text('Error fetching ID');
567
  logWorkingMessage('Error fetching ID', 'text-danger');
568
+ // Fix: Show error to user and allow retry
569
+ $('#workingOverlay').addClass('d-none');
570
+ $('#uploadTrigger').removeClass('d-none');
571
  });
572
+ } catch (err) {
573
+ console.error('Error in fetchPresign:', err);
574
+ logWorkingMessage('Error in fetchPresign: ' + err.message, 'text-danger');
575
+ }
576
  }
577
 
578
  /**
 
581
  * @param {string} runId - The run/session ID to poll.
582
  */
583
  function pollRunStatus(runId) {
584
+ if (!runId) {
585
+ console.error('pollRunStatus called with invalid runId:', runId);
586
+ return;
587
+ }
588
+
589
  logWorkingMessage('Polling run status...', 'text-white');
590
 
591
  const intervalId = setInterval(() => {
592
  fetch(`${API_BASE_URL}/runs/${runId}`)
593
+ .then(res => {
594
+ if (!res.ok) {
595
+ throw new Error(`Status check failed: ${res.status}`);
596
+ }
597
+ return res.json();
598
+ })
599
  .then(data => {
600
  $('#debugStatus').text(`Status: ${data.status}`);
601
  logWorkingMessage(`Status: ${data.status}`, 'text-white');
 
645
  })
646
  .catch(err => {
647
  console.error('Polling error:', err);
648
+ logWorkingMessage('Error polling status: ' + err.message, 'text-danger');
649
  clearInterval(intervalId);
650
+ // Fix: Show error to user
651
+ $('#workingOverlay').addClass('d-none');
652
  });
653
  }, 1000);
654
  }
 
674
  * @param {Array|Object} data - Array of sentence objects or {sentences:[…]}
675
  */
676
  function display_sentences(data) {
677
+ try {
678
+ console.log('display_sentences called with:', data); // Debug logging
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
 
680
+ // Validate input
681
+ if (!data) {
682
+ console.warn('display_sentences called with null/undefined data');
683
+ $('#workingOverlay').addClass('d-none');
684
+ return;
685
  }
686
 
687
+ // normalise payload
688
+ if (!Array.isArray(data)) {
689
+ data = (data && Array.isArray(data.sentences)) ? data.sentences : [];
690
+ }
691
+ console.log('Normalized data:', data); // Debug logging
692
+
693
+ if (!data.length) { // nothing to show ⇒ just hide overlay
694
+ $('#workingOverlay').addClass('d-none');
695
+ return;
696
+ }
697
+
698
+ // Show the sentences panel
699
+ $('.col-md-3').removeClass('d-none');
700
+ $('#sentenceList').empty();
701
+
702
+ /* ---------- sentence list construction ---------- */
703
+ data.forEach((item, index) => {
704
+ console.log(`Processing item ${index}:`, item); // Debug logging
705
+
706
+ // Validate required fields
707
+ if (!item.english_original || typeof item.english_original !== 'string') {
708
+ console.warn(`Item ${index} has invalid english_original:`, item.english_original);
709
+ return; // Skip this item
710
+ }
711
+
712
+ const li = $(`
713
+ <li class="list-group-item sentence-item mb-1"
714
+ data-work="${item.work || 'unknown'}"
715
+ data-sentence="${escapeHTML(item.english_original)}">
716
+ <div class="d-flex align-items-center">
717
+ <span class="flex-grow-1">${escapeHTML(item.english_original)}</span>
718
+ <button class="btn btn-sm btn-outline-dark ms-2 heatmap-btn"
719
+ title="View heatmap"
720
+ data-sentence="${escapeHTML(item.english_original)}">
721
+ <i class="bi bi-thermometer-half"></i>
722
+ </button>
723
+ </div>
724
+ </li>
725
+ `);
726
+ li.find('span').on('click', function () {
727
+ lookupDOI(li.data('work'), li.data('sentence'));
728
+ });
729
+ li.find('.heatmap-btn').on('click', function(e) {
730
+ e.stopPropagation();
731
+ requestHeatmap($(this).data('sentence'));
732
+ });
733
+ $('#sentenceList').append(li);
734
  });
735
+ showBottomCards();
736
+ adjustMainWidth();
737
+ } catch (err) {
738
+ console.error('Error in display_sentences:', err);
739
+ logWorkingMessage('Error displaying sentences: ' + err.message, 'text-danger');
740
+ $('#workingOverlay').addClass('d-none');
741
+ }
742
  }
743
 
744
  // helper runs whenever the right-hand column is shown/hidden