"""Download the public PHerc0139 winding meshes (9.362 um tifxyz) and Vesuvius Challenge ink previews (ds8 JPGs). python tools/fetch.py (from the Space's root; stdlib only; writes raw/, about 110 MB) It lists the open data bucket itself, keeps the wNNN winding segments that have a 9.362 um mesh, and writes raw/inv_PHerc0139.json plus raw/wNNN/{x,y,z.tif, meta.json, ink2um_ds8.jpg, ink1um_ds8.jpg, source.json}. UNROLL_RAW overrides the output folder. tools/build.py reads the X-ray layer straight from the bucket. """ import json import os import re import time import urllib.parse import urllib.request from concurrent.futures import ThreadPoolExecutor B = "https://vesuvius-challenge-open-data.s3.amazonaws.com" ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) RAW = os.environ.get("UNROLL_RAW", f"{ROOT}/raw") SCROLL = os.environ.get("UNROLL_SCROLL", "PHerc0139") MESH_RES = os.environ.get("UNROLL_MESH_RES", "9.362um") # which tifxyz to take (the coarse scan keeps int16 coordinates in range) MESH_VOL = os.environ.get("UNROLL_MESH_VOL", "") # optional volume id, when a segment has two meshes at one resolution INK_RES = os.environ.get("UNROLL_INK_RES", "") # which ink preview to call ink2um_ds8.jpg ("" = the non-1.129 um one, newest model) NO_INK = os.environ.get("UNROLL_NO_INK") == "1" # X-ray only: fetch no ink at all def get(url, tries=5): for i in range(tries): try: with urllib.request.urlopen(url, timeout=120) as r: data = r.read() n = r.headers.get("Content-Length") if n is None or len(data) == int(n): return data except OSError: if i == tries - 1: raise time.sleep(1 + i) raise OSError(f"short reads from {url}") def listing(prefix): """(sub-prefixes, [(key, size)]) under prefix, following continuation tokens.""" dirs, keys, token = [], [], None while True: q = {"list-type": "2", "prefix": prefix, "delimiter": "/"} if token: q["continuation-token"] = token t = get(f"{B}/?{urllib.parse.urlencode(q)}").decode() dirs += [p for p in re.findall(r"(.*?)", t) if p != prefix] keys += [(k, int(s)) for k, s in re.findall(r"(.*?).*?(\d+)", t)] m = re.search(r"(.*?)", t) if not m: return dirs, keys token = m.group(1) def segment(seg_prefix): d = {"seg": seg_prefix.rstrip("/").split("/")[-1]} sub, _ = listing(seg_prefix) for x in sub: name = x.rstrip("/").split("/")[-1] if name == "mesh": d["mesh"] = [p.rstrip("/").split("/")[-1] for p in listing(x)[0]] elif name == "ink-detection": d["ink"] = [(k.split("/")[-1], s) for k, s in listing(x)[1]] d["ink_ds"] = [k.split("/")[-1] for k, _ in listing(x + "downsampled/")[1]] elif name == "surface-volumes": d["sv"] = [p.rstrip("/").split("/")[-1] for p in listing(x)[0]] return d def main(): os.makedirs(RAW, exist_ok=True) segs, _ = listing(f"{SCROLL}/segments/") with ThreadPoolExecutor(8) as ex: inv = list(ex.map(segment, segs)) json.dump(inv, open(f"{RAW}/inv_{SCROLL}.json", "w"), indent=1) jobs = [] named = [re.search(r"-(w\d{3})_", s["seg"]) for s in inv] nums = [m.group(1) for m in named if m] use_w = len(nums) >= 0.8 * len(inv) and len(set(nums)) == len(nums) for s, m in zip(inv, named): if use_w and not m: continue meshes = [x for x in s.get("mesh", []) if x.endswith(f"-{MESH_RES}.tifxyz") and MESH_VOL in x] if not meshes: continue inks = sorted(s.get("ink_ds", [])) main_ink = [f for f in inks if (INK_RES in f if INK_RES else "1.129um" not in f)] if not NO_INK and not main_ink: print("skip", s["seg"], "(no ink preview)") continue # wNNN when the segment names carry unique winding numbers; otherwise the segment timestamp (build renumbers by radius) w = m.group(1) if use_w else "s" + s["seg"].split("-")[0] mesh = meshes[0] d = f"{RAW}/{w}" os.makedirs(d, exist_ok=True) base = f"{B}/{SCROLL}/segments/{s['seg']}" for f in ("x.tif", "y.tif", "z.tif", "meta.json"): jobs.append((f"{base}/mesh/{mesh}/{f}", f"{d}/{f}")) if not NO_INK: jobs.append((f"{base}/ink-detection/downsampled/{main_ink[-1]}", f"{d}/ink2um_ds8.jpg")) if True: for f in inks: if "1.129um" in f: jobs.append((f"{base}/ink-detection/downsampled/{f}", f"{d}/ink1um_ds8.jpg")) json.dump({"seg": s["seg"], "w": w, "mesh": mesh, "sv": s.get("sv"), "ink": s.get("ink"), "ink_ds": None if NO_INK else main_ink[-1]}, open(f"{d}/source.json", "w"), indent=1) def fetch(job): url, path = job if os.path.exists(path) and os.path.getsize(path) > 0: return 0 data = get(url) open(path, "wb").write(data) return len(data) with ThreadPoolExecutor(6) as ex: n = sum(ex.map(fetch, jobs)) print(len(jobs), "files,", n // 1_000_000, "MB fetched into", RAW) if __name__ == "__main__": main()