Spaces:
Runtime error
Runtime error
| """Upload local ONNX weights to a HuggingFace Model repository. | |
| Why this exists separately from the Space: | |
| - HuggingFace Spaces free tier caps total repo size at 1 GB. Our 5 ONNX | |
| models total ~440 MB which is close to the cap and crowds out everything | |
| else. | |
| - HuggingFace Model repos have a much larger free quota and are the | |
| canonical place to publish trained weights. | |
| - The dashboard's _ensure_onnx_models_downloaded() (see dashboard.py) | |
| pulls these files at first boot and caches them locally, so the Space | |
| image stays small while still serving the same models. | |
| Usage: | |
| # Once: create the Model repo on HF (web UI is fine): | |
| # https://huggingface.co/new -> Type: Model, Name: Tri-Netra-AI-models | |
| # Then run: | |
| python scripts/upload_models_to_hf.py --repo anannyavyas1/Tri-Netra-AI-Models | |
| # The HF_TOKEN env var must be set to a token with WRITE scope. | |
| Re-running is safe: it only uploads files whose local sha256 differs from | |
| what's already on the Hub. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import sys | |
| import time | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parent.parent | |
| sys.path.insert(0, str(ROOT)) | |
| # (local path relative to repo root, target path inside the Model repo) | |
| ARTIFACT_MAP = [ | |
| # v8 ConvNeXt-Tiny + 384px + Tversky (current production champion). | |
| # dynamo-exported ONNX is split into graph (.onnx) + external weights | |
| # (.onnx.data). Both MUST be uploaded; ORT auto-loads the sibling. | |
| ('model/best_micro.onnx', | |
| 'attention_unet_v8/best_micro.onnx'), | |
| ('model/best_micro.onnx.data', | |
| 'attention_unet_v8/best_micro.onnx.data'), | |
| # v5 (kept for fallback / A-B testing) | |
| ('segmentation_artifacts/attention_unet_v5/best_model.onnx', | |
| 'attention_unet_v5/best_model.onnx'), | |
| ('segmentation_artifacts/attention_unet_v3/best_model.onnx', | |
| 'attention_unet_v3/best_model.onnx'), | |
| ('segmentation_artifacts/attention_unet_t1c/best_model.onnx', | |
| 'attention_unet_t1c/best_model.onnx'), | |
| ('real_eval_current/cnn/best_weights.onnx', | |
| 'cnn/best_weights.onnx'), | |
| ('real_eval_current/transfer/best_weights.onnx', | |
| 'transfer/best_weights.onnx'), | |
| ('real_eval_current/vit/best_weights.onnx', | |
| 'vit/best_weights.onnx'), | |
| ] | |
| # Conformal-counterfactual calibration artifacts. Each is a small JSON | |
| # (~1 KB) holding one intervention's calibrated quantile q. Loaded by | |
| # src/research/dashboard_integration.py at request time to produce | |
| # voxelwise prediction sets with (1-alpha) post-intervention coverage. | |
| CONFORMAL_DIR = ROOT / 'conformal_artifacts' | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--repo', required=True, | |
| help='HF Model repo id, e.g. anannyavyas1/Tri-Netra-AI-Models') | |
| ap.add_argument('--token', default=None, | |
| help='HF write token. Defaults to HF_TOKEN env var.') | |
| ap.add_argument('--create-if-missing', action='store_true', | |
| help='Auto-create the Model repo if it doesn\'t exist.') | |
| ap.add_argument('--commit-message', default='Upload Tri-Netra AI ONNX models') | |
| args = ap.parse_args() | |
| try: | |
| from huggingface_hub import HfApi | |
| except ImportError: | |
| print('ERROR: huggingface_hub not installed. Run: pip install huggingface_hub') | |
| sys.exit(2) | |
| token = args.token or os.environ.get('HF_TOKEN') | |
| if not token: | |
| print('ERROR: no token. Pass --token or set HF_TOKEN env var (write scope).') | |
| sys.exit(2) | |
| api = HfApi(token=token) | |
| if args.create_if_missing: | |
| try: | |
| api.create_repo(repo_id=args.repo, repo_type='model', exist_ok=True) | |
| print(f'[ok] repo exists or created: {args.repo}') | |
| except Exception as exc: | |
| print(f'[warn] create_repo: {exc}') | |
| # Upload each file individually so a failure on one doesn't abort the rest. | |
| # The Hub deduplicates by sha; re-running with no changes is fast. | |
| uploaded = 0 | |
| skipped_missing = 0 | |
| failed: list[tuple[str, str]] = [] | |
| total_bytes = 0 | |
| t_start = time.perf_counter() | |
| upload_targets = list(ARTIFACT_MAP) | |
| # Auto-discover conformal calibration JSONs so we don't have to edit | |
| # ARTIFACT_MAP every time a new intervention is calibrated. | |
| if CONFORMAL_DIR.exists(): | |
| for p in sorted(CONFORMAL_DIR.glob('*.json')): | |
| upload_targets.append( | |
| (str(p.relative_to(ROOT)).replace('\\', '/'), | |
| f'conformal_artifacts/{p.name}') | |
| ) | |
| for local_rel, repo_rel in upload_targets: | |
| local = ROOT / local_rel | |
| if not local.exists(): | |
| print(f'[skip] {local_rel} not found locally') | |
| skipped_missing += 1 | |
| continue | |
| size_mb = local.stat().st_size / 1e6 | |
| total_bytes += local.stat().st_size | |
| print(f'[upload] {local_rel} -> {args.repo}:{repo_rel} ({size_mb:.1f} MB) ...', flush=True) | |
| try: | |
| t0 = time.perf_counter() | |
| api.upload_file( | |
| path_or_fileobj=str(local), | |
| path_in_repo=repo_rel, | |
| repo_id=args.repo, | |
| repo_type='model', | |
| commit_message=f'{args.commit_message}: {repo_rel}', | |
| ) | |
| print(f' done in {time.perf_counter()-t0:.1f}s') | |
| uploaded += 1 | |
| except Exception as exc: | |
| print(f' FAILED: {type(exc).__name__}: {exc}') | |
| failed.append((local_rel, str(exc))) | |
| elapsed = time.perf_counter() - t_start | |
| print('\n=== upload summary ===') | |
| print(f' repo: {args.repo}') | |
| print(f' uploaded: {uploaded}') | |
| print(f' skipped: {skipped_missing} (file not found locally)') | |
| print(f' failed: {len(failed)}') | |
| print(f' bytes: {total_bytes/1e6:.1f} MB') | |
| print(f' elapsed: {elapsed:.1f}s') | |
| if failed: | |
| print('\nfailures:') | |
| for f, e in failed: | |
| print(f' - {f}: {e}') | |
| sys.exit(1) | |
| if __name__ == '__main__': | |
| main() | |