starfriend commited on
Commit
3d1c448
·
verified ·
1 Parent(s): 62f3771

Upload paper_figures.py

Browse files
Files changed (1) hide show
  1. metadata_search/paper_figures.py +63 -53
metadata_search/paper_figures.py CHANGED
@@ -8,6 +8,7 @@ import os
8
  import unicodedata
9
  from pathlib import Path, PurePosixPath, PureWindowsPath
10
  from urllib.parse import quote
 
11
 
12
  from metadata_search.config import PAPER_FIGURES_DIR
13
 
@@ -16,7 +17,8 @@ logger = logging.getLogger(__name__)
16
  STEM_PREFIX_MIN = 40
17
 
18
  _index: dict[str, str] | None = None
19
- _manifests: dict[str, dict] | None = None
 
20
 
21
 
22
  HF_DATASET_REPO = os.getenv(
@@ -82,15 +84,33 @@ def _batch_folders(path: Path) -> list[str]:
82
  if not isinstance(batch.get("papers"), list):
83
  raise ValueError(f"Missing papers list in media batch manifest: {path}")
84
  folders = []
 
85
  for record in batch["papers"]:
86
  folder = _media_folder(str(record.get("output_directory") or ""))
87
  if folder is None:
88
  raise ValueError("Invalid media output_directory in batch manifest")
89
- if folder not in folders:
90
  folders.append(folder)
 
91
  return folders
92
 
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  def resolve_figure_asset(folder: str, filename: str) -> Path | None:
95
  """Resolve one figure asset locally, then fetch only that asset if needed."""
96
  safe_folder = _media_folder(folder)
@@ -149,7 +169,12 @@ def _register_stem(stem_index: dict[str, str], key: str, folder: str) -> None:
149
  stem_index[stem] = folder
150
 
151
 
152
- def _load_index() -> tuple[dict[str, str], dict[str, dict]]:
 
 
 
 
 
153
  global _index, _manifests
154
  if _index is not None and _manifests is not None:
155
  return _index, _manifests
@@ -157,6 +182,25 @@ def _load_index() -> tuple[dict[str, str], dict[str, dict]]:
157
  stem_index: dict[str, str] = {}
158
  manifests: dict[str, dict] = {}
159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  # 1) Existing local behavior.
161
  root = figures_dir()
162
  if root is not None:
@@ -182,55 +226,6 @@ def _load_index() -> tuple[dict[str, str], dict[str, dict]]:
182
  if source_pdf:
183
  _register_stem(stem_index, PureWindowsPath(source_pdf).name, folder)
184
 
185
- # 2) Cloud fallback. List repository paths once, then download only
186
- # manifest.json files. Image assets remain on-demand.
187
- if not manifests and HF_FIGURES_ON_DEMAND:
188
- try:
189
- suffix = "/manifest.json"
190
- prefix = f"{HF_FIGURES_PREFIX}/"
191
- batch_path = _hf_download(f"{prefix}batch_manifest.json")
192
- if batch_path is not None:
193
- manifest_files = [f"{prefix}{folder}{suffix}"
194
- for folder in _batch_folders(batch_path)]
195
- else:
196
- # Support data without an overall manifest; only study manifests.
197
- repo_files = _hf_api().list_repo_files(
198
- repo_id=HF_DATASET_REPO, repo_type="dataset",
199
- )
200
- manifest_files = [path for path in repo_files
201
- if path.startswith(prefix) and path.endswith(suffix)]
202
-
203
- for repo_file in manifest_files:
204
- relative = repo_file[len(prefix):]
205
- folder = relative[:-len(suffix)]
206
- # Keep the year component when resolving study manifests.
207
- if _media_folder(folder) is None:
208
- continue
209
-
210
- manifest_path = _hf_download(repo_file)
211
- if manifest_path is None:
212
- continue
213
- try:
214
- manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
215
- except (OSError, json.JSONDecodeError) as exc:
216
- logger.warning("Skipping cloud figure manifest %s: %s", repo_file, exc)
217
- continue
218
-
219
- manifests[folder] = manifest
220
- _register_stem(stem_index, PurePosixPath(folder).name, folder)
221
-
222
- source_pdf = (manifest.get("source_pdf") or "").strip()
223
- if source_pdf:
224
- _register_stem(stem_index, PureWindowsPath(source_pdf).name, folder)
225
-
226
- logger.info(
227
- "Indexed figures for %s papers from HF Dataset %s",
228
- len(manifests),
229
- HF_DATASET_REPO,
230
- )
231
- except Exception as exc:
232
- logger.warning("Unable to build HF figure manifest index: %s", exc)
233
-
234
  _index = stem_index
235
  _manifests = manifests
236
  return _index, _manifests
@@ -299,7 +294,22 @@ def figure_image_url(folder: str, filename: str) -> str:
299
 
300
  def figures_payload_for_folder(folder: str) -> dict | None:
301
  _, manifests = _load_index()
302
- manifest = manifests.get(folder)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  if not manifest:
304
  return None
305
 
 
8
  import unicodedata
9
  from pathlib import Path, PurePosixPath, PureWindowsPath
10
  from urllib.parse import quote
11
+ from threading import RLock
12
 
13
  from metadata_search.config import PAPER_FIGURES_DIR
14
 
 
17
  STEM_PREFIX_MIN = 40
18
 
19
  _index: dict[str, str] | None = None
20
+ _manifests: dict[str, dict | None] | None = None
21
+ _index_lock = RLock()
22
 
23
 
24
  HF_DATASET_REPO = os.getenv(
 
84
  if not isinstance(batch.get("papers"), list):
85
  raise ValueError(f"Missing papers list in media batch manifest: {path}")
86
  folders = []
87
+ seen = set()
88
  for record in batch["papers"]:
89
  folder = _media_folder(str(record.get("output_directory") or ""))
90
  if folder is None:
91
  raise ValueError("Invalid media output_directory in batch manifest")
92
+ if folder not in seen:
93
  folders.append(folder)
94
+ seen.add(folder)
95
  return folders
96
 
97
 
98
+
99
+ def _batch_index(path: Path) -> tuple[dict[str, str], dict[str, dict | None]]:
100
+ """Index folder and PDF aliases without reading individual study manifests."""
101
+ folders = dict.fromkeys(_batch_folders(path))
102
+ stem_index: dict[str, str] = {}
103
+ for folder in folders:
104
+ _register_stem(stem_index, PurePosixPath(folder).name, folder)
105
+ batch = json.loads(path.read_text(encoding="utf-8-sig"))
106
+ for record in batch["papers"]:
107
+ folder = _media_folder(str(record.get("output_directory") or ""))
108
+ source_pdf = str(record.get("source_pdf") or "").strip()
109
+ if source_pdf:
110
+ _register_stem(stem_index, PureWindowsPath(source_pdf).name, folder)
111
+ return stem_index, folders
112
+
113
+
114
  def resolve_figure_asset(folder: str, filename: str) -> Path | None:
115
  """Resolve one figure asset locally, then fetch only that asset if needed."""
116
  safe_folder = _media_folder(folder)
 
169
  stem_index[stem] = folder
170
 
171
 
172
+ def _load_index() -> tuple[dict[str, str], dict[str, dict | None]]:
173
+ with _index_lock:
174
+ return _load_index_locked()
175
+
176
+
177
+ def _load_index_locked() -> tuple[dict[str, str], dict[str, dict | None]]:
178
  global _index, _manifests
179
  if _index is not None and _manifests is not None:
180
  return _index, _manifests
 
182
  stem_index: dict[str, str] = {}
183
  manifests: dict[str, dict] = {}
184
 
185
+ # The overall manifest is sufficient for health counts and paper lookup.
186
+ # None marks an indexed study whose detailed manifest is not loaded yet.
187
+ root = figures_dir()
188
+ batch_path = root / "batch_manifest.json" if root is not None else None
189
+ if batch_path is None or not batch_path.is_file():
190
+ batch_path = _hf_download(f"{HF_FIGURES_PREFIX}/batch_manifest.json")
191
+ if batch_path is not None:
192
+ try:
193
+ _index, _manifests = _batch_index(batch_path)
194
+ logger.info("Indexed media for %s papers from overall manifest", len(_manifests))
195
+ return _index, _manifests
196
+ except (OSError, ValueError, TypeError, AttributeError) as exc:
197
+ logger.warning("Unable to read overall media manifest %s: %s", batch_path, exc)
198
+ # Do not permanently cache a transient failure.
199
+ return {}, {}
200
+ if root is None:
201
+ logger.warning("Overall media manifest unavailable; will retry on next request")
202
+ return {}, {}
203
+
204
  # 1) Existing local behavior.
205
  root = figures_dir()
206
  if root is not None:
 
226
  if source_pdf:
227
  _register_stem(stem_index, PureWindowsPath(source_pdf).name, folder)
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  _index = stem_index
230
  _manifests = manifests
231
  return _index, _manifests
 
294
 
295
  def figures_payload_for_folder(folder: str) -> dict | None:
296
  _, manifests = _load_index()
297
+ if folder not in manifests:
298
+ return None
299
+ with _index_lock:
300
+ manifest = manifests.get(folder)
301
+ if manifest is None:
302
+ manifest_path = resolve_figure_asset(folder, "manifest.json")
303
+ if manifest_path is None:
304
+ return None
305
+ try:
306
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
307
+ if not isinstance(manifest, dict):
308
+ raise ValueError("Study manifest must be a JSON object")
309
+ except (OSError, ValueError) as exc:
310
+ logger.warning("Unable to read study manifest %s: %s", manifest_path, exc)
311
+ return None
312
+ manifests[folder] = manifest
313
  if not manifest:
314
  return None
315