diff --git a/scripts/create_resources/combine/process_datasets_xenium_nebius.sh b/scripts/create_resources/combine/process_datasets_xenium_nebius.sh index 43dad7f5e..a630053f1 100644 --- a/scripts/create_resources/combine/process_datasets_xenium_nebius.sh +++ b/scripts/create_resources/combine/process_datasets_xenium_nebius.sh @@ -129,6 +129,17 @@ param_list: dataset_description: "Xenium V1 FFPE Human Breast IDC + 2021 Wu scRNAseq" dataset_organism: "homo_sapiens" + - id: "2026_10x_human_breast_cancer_atera_combined" + input_sp: "$input_dir/10x_atera/2026_10x_human_breast_cancer_atera/dataset.zarr" + input_sc: "$input_dir/wu_human_breast_cancer_sc/2021Wu_human_breast_cancer_sc/dataset.h5ad" + dataset_id: "2026_10x_human_breast_cancer_atera_combined" + dataset_name: "Human breast cancer combined 2026 10x Atera WTA 2021 Wu scRNAseq" + dataset_url: "https://www.10xgenomics.com/datasets/atera-wta-ffpe-human-breast-cancer" + dataset_reference: "https://doi.org/10.1038/s41588-021-00911-1" + dataset_summary: "Atera WTA FFPE Human Breast Cancer (DCIS Grade 3) + 2021 Wu scRNAseq" + dataset_description: "Atera WTA FFPE Human Breast Cancer (DCIS Grade 3) + 2021 Wu scRNAseq" + dataset_organism: "homo_sapiens" + output_sc: "\$id/output_sc.h5ad" output_sp: "\$id/output_sp.zarr" output_state: "\$id/state.yaml" diff --git a/src/datasets/loaders/tenx_atera/script.py b/src/datasets/loaders/tenx_atera/script.py index 6a2009ddf..9c7e5f32b 100644 --- a/src/datasets/loaders/tenx_atera/script.py +++ b/src/datasets/loaders/tenx_atera/script.py @@ -1,6 +1,7 @@ ## code author: Florian Heyl import shutil import os +import json import zipfile import tempfile from pathlib import Path @@ -140,6 +141,18 @@ def extract_zip(input_zip: Path, output_dir: Path, strip_root: bool = False): for key, value in new_uns.items(): sdata.tables["table"].uns[key] = value +# Preserve the Xenium analysis software version (from the raw experiment.xenium) so +# downstream components can recover it instead of hardcoding — e.g. segger reads it to +# select its v1 vs v2+ Xenium loader. Best-effort: skip if absent/unreadable. +try: + with open(input_extracted / "experiment.xenium") as f: + xenium_sw_version = json.load(f).get("analysis_sw_version") + if xenium_sw_version: + sdata.tables["table"].uns["xenium_analysis_sw_version"] = xenium_sw_version + print(f"Xenium analysis_sw_version: {xenium_sw_version}", flush=True) +except (OSError, ValueError) as e: + print(f"(no experiment.xenium analysis_sw_version: {e})", flush=True) + # Force a regular chunk grid so the written store is readable by spatialdata's # ome-zarr reader (see rechunk_uniform). Do NOT enable array.rectilinear_chunks: # that only permits writing the unreadable rectilinear grids we are avoiding. diff --git a/src/datasets/loaders/tenx_xenium/script.py b/src/datasets/loaders/tenx_xenium/script.py index b1a2d4b7e..3a1bdeebd 100644 --- a/src/datasets/loaders/tenx_xenium/script.py +++ b/src/datasets/loaders/tenx_xenium/script.py @@ -3,6 +3,7 @@ from spatialdata_io import xenium import shutil import os +import json import zipfile import tempfile @@ -97,6 +98,18 @@ for key, value in new_uns.items(): sdata.tables["table"].uns[key] = value + # Preserve the Xenium analysis software version (from the raw experiment.xenium) + # so downstream components can recover it instead of hardcoding — e.g. segger reads + # it to select its v1 vs v2+ Xenium loader. Best-effort: skip if absent/unreadable. + try: + with open(os.path.join(par_input, "experiment.xenium")) as f: + xenium_sw_version = json.load(f).get("analysis_sw_version") + if xenium_sw_version: + sdata.tables["table"].uns["xenium_analysis_sw_version"] = xenium_sw_version + print(f"Xenium analysis_sw_version: {xenium_sw_version}", flush=True) + except (OSError, ValueError) as e: + print(f"(no experiment.xenium analysis_sw_version: {e})", flush=True) + print(f"Output: {sdata}", flush=True) print(f"Writing to '{par['output']}'", flush=True) diff --git a/src/methods_transcript_assignment/segger/config.vsh.yaml b/src/methods_transcript_assignment/segger/config.vsh.yaml index fdd8872c1..377150533 100644 --- a/src/methods_transcript_assignment/segger/config.vsh.yaml +++ b/src/methods_transcript_assignment/segger/config.vsh.yaml @@ -65,13 +65,16 @@ engines: - type: python github: - openproblems-bio/core#subdirectory=packages/python/openproblems - # torch for the GNN. Standard PyPI cu124 wheel (numpy-2 compatible) coexists - # with the RAPIDS cuda-12 libs. Unlike the NGC custom torch, torch_scatter has - # a PREBUILT wheel for this torch (the pyg index) — no CUDA compile needed. + # torch (+ torchvision, which segger's data_module imports) for the GNN. + # Standard PyPI cu124 wheels (numpy-2 compatible) coexist with the RAPIDS + # cuda-12 libs. torchvision must match torch (2.6.0 <-> 0.21.0). Unlike the NGC + # custom torch, torch_scatter has a PREBUILT wheel for this torch (pyg index) — + # no CUDA compile needed. typer + torch_geometric + lightning are pure Python + # (segger imports all three but declares none of them). - type: docker run: - - pip install --no-cache-dir torch==2.6.0 --index-url https://download.pytorch.org/whl/cu124 - - pip install --no-cache-dir torch_geometric lightning + - pip install --no-cache-dir torch==2.6.0 torchvision==0.21.0 --index-url https://download.pytorch.org/whl/cu124 + - pip install --no-cache-dir torch_geometric lightning typer - pip install --no-cache-dir torch_scatter -f https://data.pyg.org/whl/torch-2.6.0+cu124.html # I/O stack. spatialdata pinned <0.8 (resolves to 0.7.1): 0.7.2+ require # dask>=2026.x, but RAPIDS 25.04 hard-pins dask==2025.2.0. 0.7.1 accepts dask diff --git a/src/methods_transcript_assignment/segger/script.py b/src/methods_transcript_assignment/segger/script.py index 373aa7deb..7c97e66c3 100644 --- a/src/methods_transcript_assignment/segger/script.py +++ b/src/methods_transcript_assignment/segger/script.py @@ -1,4 +1,5 @@ import os +import json import shutil import subprocess from pathlib import Path @@ -154,6 +155,26 @@ tx_out.to_parquet(XENIUM_DIR / "transcripts.parquet", index=False) del transcripts, y_coords, x_coords, label_image +# segger 0.1.0 auto-detects the platform from marker files: its Xenium loader needs an +# `experiment.xenium` JSON and reads only `analysis_sw_version` from it, which selects its +# v1 ("-1") vs v2+ ("UNASSIGNED") loader. We always write the "UNASSIGNED" sentinel above, +# so we need the v2+ loader (version >= 2). Reuse the real version the Xenium dataset +# loader stashed in the metadata table's uns when it is v2+; otherwise (v1 source, missing, +# or a non-Xenium dataset wrapped here as Xenium-layout) fall back to a v2+ default. +DEFAULT_SW_VERSION = "xenium-3.0.0" +sw_version = DEFAULT_SW_VERSION +try: + src_version = sdata.tables["metadata"].uns.get("xenium_analysis_sw_version") + if src_version and int(str(src_version).split("-")[-1].split(".")[0]) >= 2: + sw_version = str(src_version) + elif src_version: + print(f"Source Xenium version '{src_version}' is <2, but we write the " + f"UNASSIGNED sentinel; using {sw_version} instead.", flush=True) +except (KeyError, AttributeError, ValueError, IndexError): + pass +print(f"Writing experiment.xenium with analysis_sw_version={sw_version}", flush=True) +(XENIUM_DIR / "experiment.xenium").write_text(json.dumps({"analysis_sw_version": sw_version})) + ################ # Run segger # ################