Download code/mr/build_cache.py from odurugol/vlm3d-beyond-normals: direct link, hf CLI and curl.
- Browser
- Download file 4.43 kB
-
https://huggingface.co/odurugol/vlm3d-beyond-normals/resolve/main/code/mr/build_cache.py
- Command line
-
hf download hf://odurugol/vlm3d-beyond-normals/code/mr/build_cache.py
-
curl -L -o build_cache.py https://huggingface.co/odurugol/vlm3d-beyond-normals/resolve/main/code/mr/build_cache.py
4.43 kB
| """Stream one MR-RATE atlas batch tar -> one uint8 memmap shard + index CSV. | |
| One shard per batch rather than one file per study keeps the file count small. | |
| Written to --out: | |
| <batch>.u8 raw uint8, shape (n_studies, 4, *geometry output shape), C order | |
| <batch>.csv row,study_uid,has_t1w,has_t2w,has_flair,has_swi,n_series | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import io | |
| import os | |
| import sys | |
| import tarfile | |
| import time | |
| from multiprocessing import Pool | |
| from threading import Semaphore | |
| import numpy as np | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from preprocess import MODALITIES, process_zip # noqa: E402 | |
| GEOMETRY = "d15" | |
| def _init(geometry): | |
| global GEOMETRY | |
| GEOMETRY = geometry | |
| def _work(job): | |
| uid, raw = job | |
| try: | |
| per, n = process_zip(io.BytesIO(raw), (GEOMETRY,)) | |
| vol, presence = per[GEOMETRY] | |
| return uid, vol, presence, n, "" | |
| except Exception as exc: # a bad study must not kill the shard | |
| return uid, None, None, 0, f"{type(exc).__name__}: {exc}" | |
| def members(tar_path: str, wanted: set[str] | None): | |
| """Yield (uid, zip_bytes) streaming forward through the tar exactly once.""" | |
| with tarfile.open(tar_path, "r|") as tf: | |
| for m in tf: | |
| if not m.isfile() or not m.name.endswith(".zip"): | |
| continue | |
| uid = os.path.basename(m.name)[: -len(".zip")] | |
| if uid.endswith("_atlas"): | |
| uid = uid[: -len("_atlas")] | |
| if wanted is not None and uid not in wanted: | |
| continue | |
| if m.size == 0: | |
| continue | |
| data = tf.extractfile(m).read() | |
| yield uid, data | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--tar", required=True) | |
| ap.add_argument("--batch", required=True) | |
| ap.add_argument("--out", required=True) | |
| ap.add_argument("--geometry", default="d15", choices=("d15", "d2")) | |
| ap.add_argument("--workers", type=int, default=16) | |
| ap.add_argument("--uid-list", default=None, help="optional file of study_uids to keep") | |
| ap.add_argument("--limit", type=int, default=0) | |
| args = ap.parse_args() | |
| wanted = None | |
| if args.uid_list: | |
| with open(args.uid_list) as f: | |
| wanted = {ln.strip() for ln in f if ln.strip()} | |
| os.makedirs(args.out, exist_ok=True) | |
| bin_path = os.path.join(args.out, f"{args.batch}.u8") | |
| csv_path = os.path.join(args.out, f"{args.batch}.csv") | |
| tmp_bin = bin_path + ".part" | |
| tmp_csv = csv_path + ".part" | |
| t0 = time.time() | |
| written = 0 | |
| failed = 0 | |
| src = members(args.tar, wanted) | |
| if args.limit: | |
| src = (x for i, x in enumerate(src) if i < args.limit) | |
| # Pool.imap's feeder queues without bound and each item is a ~150 MB zip, | |
| # so gate it on completed results to keep the tar out of RAM. | |
| inflight = Semaphore(args.workers * 3) | |
| def throttled(it): | |
| for item in it: | |
| inflight.acquire() | |
| yield item | |
| src = throttled(src) | |
| with open(tmp_bin, "wb", buffering=1024 * 1024) as fbin, open(tmp_csv, "w", newline="") as fcsv: | |
| w = csv.writer(fcsv) | |
| w.writerow(["row", "study_uid", *[f"has_{m}" for m in MODALITIES], "n_series"]) | |
| with Pool(args.workers, maxtasksperchild=64, initializer=_init, initargs=(args.geometry,)) as pool: | |
| for uid, vol, presence, n, err in pool.imap(_work, src, chunksize=1): | |
| inflight.release() | |
| if vol is None or n == 0: | |
| failed += 1 | |
| if failed <= 20: | |
| print(f"[skip] {uid} {err or 'no usable series'}", flush=True) | |
| continue | |
| fbin.write(vol.tobytes()) | |
| w.writerow([written, uid, *presence.tolist(), n]) | |
| written += 1 | |
| if written % 200 == 0: | |
| el = time.time() - t0 | |
| print(f"[{args.batch}] {written} studies {el/60:.1f} min " | |
| f"({written/max(el,1e-9):.2f}/s, {failed} skipped)", flush=True) | |
| os.replace(tmp_bin, bin_path) | |
| os.replace(tmp_csv, csv_path) | |
| el = time.time() - t0 | |
| print(f"[{args.batch}] DONE {written} studies, {failed} skipped, " | |
| f"{el/60:.1f} min, {os.path.getsize(bin_path)/2**30:.1f} GiB", flush=True) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |