|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Download GDSC2 drug-response + CCLE/DepMap RNA-seq for the sarcoma subset. |
| 3 | +
|
| 4 | +Two open data sources, paired on COSMIC ID: |
| 5 | +
|
| 6 | +1. GDSC2 (Sanger Institute, release 8.5, dated 27Oct23). Fitted dose-response |
| 7 | + (AUC, IC50), compound metadata, cell-line metadata. Hosted on the public |
| 8 | + Sanger CDN; no auth required. |
| 9 | +
|
| 10 | +2. DepMap (Broad Institute, latest public release, ~quarterly). RNA-seq raw |
| 11 | + read counts (per protein-coding gene, RSEM-style; fed through DESeq2 |
| 12 | + median-of-ratios in the loader to match Soragni's normalization scheme) |
| 13 | + plus model metadata mapping DepMap ACH-XXXXX IDs to COSMIC IDs. DepMap |
| 14 | + distributes through figshare collections that change per release (26Q1, |
| 15 | + 25Q4, ...). To swap to a newer release: |
| 16 | +
|
| 17 | + Go to https://depmap.org/portal/download/all/, pick the latest release, |
| 18 | + right-click the file and copy the figshare ndownloader link, then update |
| 19 | + the corresponding entry in DEPMAP_FILES below. |
| 20 | +
|
| 21 | +Outputs land in data/raw/gdsc2_sarcoma/ with sha256 of each file recorded in |
| 22 | +manifest.json. The manifest schema is shared with download_soragni.py via |
| 23 | +scripts/download/_utils.py. |
| 24 | +
|
| 25 | +Usage: |
| 26 | + python scripts/download/download_gdsc2_sarcoma.py |
| 27 | + python scripts/download/download_gdsc2_sarcoma.py --verify-only |
| 28 | +""" |
| 29 | + |
| 30 | +from __future__ import annotations |
| 31 | + |
| 32 | +import argparse |
| 33 | +import sys |
| 34 | +import urllib.request |
| 35 | +from pathlib import Path |
| 36 | +from urllib.error import HTTPError, URLError |
| 37 | + |
| 38 | +from _utils import ( |
| 39 | + MANIFEST_NAME, |
| 40 | + load_manifest, |
| 41 | + sha256_file, |
| 42 | + skip_or_fail_on_hash, |
| 43 | + verify_manifest, |
| 44 | + write_manifest, |
| 45 | +) |
| 46 | + |
| 47 | +REPO_ROOT = Path(__file__).resolve().parents[2] |
| 48 | +OUTPUT_DIR = REPO_ROOT / "data" / "raw" / "gdsc2_sarcoma" |
| 49 | +MANIFEST_PATH = OUTPUT_DIR / MANIFEST_NAME |
| 50 | + |
| 51 | +# GDSC2 release 8.5 (27Oct23) -- verified live on the Sanger CDN. |
| 52 | +# Bump these when Sanger publishes a new release. |
| 53 | +GDSC2_RELEASE = "8.5" |
| 54 | +GDSC2_BASE = f"https://cog.sanger.ac.uk/cancerrxgene/GDSC_release{GDSC2_RELEASE}" |
| 55 | +GDSC2_FILES: list[tuple[str, str]] = [ |
| 56 | + ( |
| 57 | + "GDSC2_fitted_dose_response_27Oct23.xlsx", |
| 58 | + f"{GDSC2_BASE}/GDSC2_fitted_dose_response_27Oct23.xlsx", |
| 59 | + ), |
| 60 | + ("screened_compounds_rel_8.5.csv", f"{GDSC2_BASE}/screened_compounds_rel_8.5.csv"), |
| 61 | + ("Cell_Lines_Details.xlsx", f"{GDSC2_BASE}/Cell_Lines_Details.xlsx"), |
| 62 | +] |
| 63 | + |
| 64 | +# DepMap files -- release 26Q1 (canonical-id snapshot public-26q1-5bbf). |
| 65 | +# URLs hit depmap.org which 302s to a signed Google Cloud Storage URL that's |
| 66 | +# regenerated per request, so the depmap.org URL itself is stable across runs. |
| 67 | +# |
| 68 | +# RawReadCount is RSEM expected counts per protein-coding gene (linear, |
| 69 | +# integer-ish); the loader runs DESeq2 median-of-ratios on this to match |
| 70 | +# Soragni's pre-computed normalized counts. The log2(TPM+1) variant at the |
| 71 | +# same canonical-id (.27) is available if a length-normalized view is ever |
| 72 | +# needed for a sensitivity row; not pulled by default. |
| 73 | +DEPMAP_RELEASE = "26Q1" |
| 74 | +DEPMAP_BASE = "https://depmap.org/portal/download/api/download" |
| 75 | +DEPMAP_FILES: list[tuple[str, str]] = [ |
| 76 | + ( |
| 77 | + "OmicsExpressionRawReadCountHumanProteinCodingGenes.csv", |
| 78 | + f"{DEPMAP_BASE}?file_name=downloads-by-canonical-id%2Fpublic-26q1-5bbf.27%2FOmicsExpressionRawReadCountHumanProteinCodingGenes.csv&dl_name=OmicsExpressionRawReadCountHumanProteinCodingGenes.csv&bucket=depmap-external-downloads", |
| 79 | + ), |
| 80 | + ( |
| 81 | + "Model.csv", |
| 82 | + f"{DEPMAP_BASE}?file_name=downloads-by-canonical-id%2Fpublic-26q1-5bbf.37%2FModel.csv&dl_name=Model.csv&bucket=depmap-external-downloads", |
| 83 | + ), |
| 84 | +] |
| 85 | + |
| 86 | + |
| 87 | +def default_manifest() -> dict: |
| 88 | + return { |
| 89 | + "dataset": "gdsc2_sarcoma", |
| 90 | + "release": {"gdsc": GDSC2_RELEASE, "depmap": DEPMAP_RELEASE}, |
| 91 | + "files": {}, |
| 92 | + } |
| 93 | + |
| 94 | + |
| 95 | +def download(url: str, dest: Path) -> None: |
| 96 | + dest.parent.mkdir(parents=True, exist_ok=True) |
| 97 | + tmp = dest.with_suffix(dest.suffix + ".part") |
| 98 | + req = urllib.request.Request(url, headers={"User-Agent": "fm-pdo-evaluator/0.1"}) |
| 99 | + with urllib.request.urlopen(req) as response, tmp.open("wb") as fh: |
| 100 | + while True: |
| 101 | + chunk = response.read(1 << 20) |
| 102 | + if not chunk: |
| 103 | + break |
| 104 | + fh.write(chunk) |
| 105 | + tmp.replace(dest) |
| 106 | + |
| 107 | + |
| 108 | +def fetch_with_sha(name: str, url: str, manifest: dict) -> None: |
| 109 | + dest = OUTPUT_DIR / name |
| 110 | + if skip_or_fail_on_hash(name, dest, manifest["files"]): |
| 111 | + print(f"[skip] {name} present with matching sha256") |
| 112 | + return |
| 113 | + print(f"[get ] {name} <- {url}") |
| 114 | + try: |
| 115 | + download(url, dest) |
| 116 | + except (HTTPError, URLError) as e: |
| 117 | + sys.exit(f"[fail] {name} download error: {e}") |
| 118 | + digest = sha256_file(dest) |
| 119 | + manifest["files"][name] = { |
| 120 | + "sha256": digest, |
| 121 | + "bytes": dest.stat().st_size, |
| 122 | + "source_uri": url, |
| 123 | + } |
| 124 | + print(f" sha256 {digest}") |
| 125 | + |
| 126 | + |
| 127 | +def fetch_gdsc2(manifest: dict) -> None: |
| 128 | + for name, url in GDSC2_FILES: |
| 129 | + fetch_with_sha(f"gdsc2/{name}", url, manifest) |
| 130 | + |
| 131 | + |
| 132 | +def fetch_depmap(manifest: dict) -> None: |
| 133 | + for name, url in DEPMAP_FILES: |
| 134 | + fetch_with_sha(f"depmap/{name}", url, manifest) |
| 135 | + |
| 136 | + |
| 137 | +def main() -> None: |
| 138 | + parser = argparse.ArgumentParser( |
| 139 | + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter |
| 140 | + ) |
| 141 | + parser.add_argument( |
| 142 | + "--verify-only", action="store_true", help="re-verify existing files; no download" |
| 143 | + ) |
| 144 | + args = parser.parse_args() |
| 145 | + |
| 146 | + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) |
| 147 | + manifest = load_manifest(MANIFEST_PATH, default_manifest()) |
| 148 | + |
| 149 | + if args.verify_only: |
| 150 | + errors = verify_manifest(OUTPUT_DIR, MANIFEST_PATH) |
| 151 | + sys.exit(1 if errors else 0) |
| 152 | + |
| 153 | + fetch_gdsc2(manifest) |
| 154 | + fetch_depmap(manifest) |
| 155 | + expected = {f"gdsc2/{n}" for n, _ in GDSC2_FILES} | {f"depmap/{n}" for n, _ in DEPMAP_FILES} |
| 156 | + manifest["files"] = {k: v for k, v in manifest["files"].items() if k in expected} |
| 157 | + write_manifest(MANIFEST_PATH, manifest) |
| 158 | + print(f"[done] manifest written to {MANIFEST_PATH.relative_to(REPO_ROOT)}") |
| 159 | + |
| 160 | + |
| 161 | +if __name__ == "__main__": |
| 162 | + main() |
0 commit comments