Skip to content
Merged

Fixes #185

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f80b261
Register cellposev4 in benchmark run scripts
dariarom94 Jul 19, 2026
1a2fa09
fix anndata version mismatch with txsim
dariarom94 Jul 19, 2026
82add80
add segger to workflow (test)
dariarom94 Jul 19, 2026
53e1728
duplicates when FOV stiching cleaned up
dariarom94 Jul 19, 2026
1186b7a
chunks issue atera
dariarom94 Jul 20, 2026
18644d7
segger update image
dariarom94 Jul 20, 2026
ecb302d
claude fix for segger
dariarom94 Jul 20, 2026
7d66898
Merge branch 'main' into fixes
dariarom94 Jul 20, 2026
d400ebe
atera version fix
dariarom94 Jul 20, 2026
64d7b4e
wf for the custom rnaseq scripts
dariarom94 Jul 20, 2026
3edfbf1
adjust the loader image name
dariarom94 Jul 20, 2026
cbd2f12
adjust the memory
dariarom94 Jul 20, 2026
184260e
troubleshootig edges
dariarom94 Jul 20, 2026
9fa9a33
Merge branch 'main' into fixes
dariarom94 Jul 20, 2026
36631c4
segger update
dariarom94 Jul 21, 2026
0626127
cell type label correction
dariarom94 Jul 21, 2026
3186435
fix boundaries
dariarom94 Jul 21, 2026
d8a7d93
Merge branch 'main' into fixes
dariarom94 Jul 21, 2026
3505718
OOM fixes
dariarom94 Jul 21, 2026
d6e110a
fix code
dariarom94 Jul 21, 2026
4660f26
RCTD
dariarom94 Jul 21, 2026
5abd651
segger to RAPIDS
dariarom94 Jul 21, 2026
fe2e90a
Merge branch 'main' into fixes
dariarom94 Jul 21, 2026
0b23474
fix rctd
dariarom94 Jul 22, 2026
196ff1f
segger debug (torchvision)
dariarom94 Jul 22, 2026
4be7bd4
Merge branch 'main' into fixes
dariarom94 Jul 22, 2026
b8d3d7b
save the xenium version
dariarom94 Jul 22, 2026
202ac49
add atera to datasets
dariarom94 Jul 22, 2026
14be8d0
Add gene efficiency correction as a separate pipeline stage (#183)
dariarom94 Jul 22, 2026
0cf0243
moscot to pca and segger troubleshooting
dariarom94 Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions scripts/create_resources/combine/process_datasets_xenium_nebius.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion scripts/run_benchmark/run_full_local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ celltype_annotation_methods:
# - rctd
expression_correction_methods:
- no_correction
# - gene_efficiency_correction
# - resolvi_correction
# - split
gene_efficiency_correction_methods:
- no_correction
# - gene_efficiency_correction
method_parameters_yaml: /tmp/method_params.yaml
HERE

Expand Down
4 changes: 3 additions & 1 deletion scripts/run_benchmark/run_full_seqeracloud.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ celltype_annotation_methods:
- rctd
expression_correction_methods:
- no_correction
- gene_efficiency_correction
- resolvi_correction
- split
gene_efficiency_correction_methods:
- no_correction
- gene_efficiency_correction
method_parameters_yaml: /tmp/method_params.yaml
HERE

Expand Down
4 changes: 3 additions & 1 deletion scripts/run_benchmark/run_test_local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ celltype_annotation_methods:
# - rctd
expression_correction_methods:
- no_correction
# - gene_efficiency_correction
# - resolvi_correction
# - split
gene_efficiency_correction_methods:
- no_correction
# - gene_efficiency_correction
method_parameters_yaml: /tmp/method_params.yaml
HERE

Expand Down
3 changes: 3 additions & 0 deletions scripts/run_benchmark/run_test_segger_nebius.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ celltype_annotation_methods:
- tacco
expression_correction_methods:
- no_correction
gene_efficiency_correction_methods:
- no_correction
# - gene_efficiency_correction
HERE

# Write the parameters to file (input_states version, NOTE: enable `-entry_name auto` for this)
Expand Down
4 changes: 3 additions & 1 deletion scripts/run_benchmark/run_test_seqeracloud.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ celltype_annotation_methods:
- rctd
expression_correction_methods:
- no_correction
- gene_efficiency_correction
- resolvi_correction
- split
gene_efficiency_correction_methods:
- no_correction
- gene_efficiency_correction
#method_parameters_yaml: /tmp/method_params.yaml
HERE

Expand Down
31 changes: 31 additions & 0 deletions src/api/comp_method_gene_efficiency_correction.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace: methods_gene_efficiency_correction
info:
type: method
subtype: method_gene_efficiency_correction
type_info:
label: Gene efficiency correction
summary: Correcting per-gene detection efficiency in spatial data
description: >-
A gene efficiency correction method corrects for per-gene detection efficiency in the
(expression-)corrected spatial counts. It runs as a separate stage after expression
correction, consuming and producing the corrected-counts format.
arguments:
- name: --input
required: true
direction: input
__merge__: /src/api/file_spatial_corrected_counts.yaml
- name: --input_scrnaseq_reference
required: false
direction: input
__merge__: /src/api/file_scrnaseq_reference.yaml
- name: --output
required: true
direction: output
__merge__: /src/api/file_spatial_corrected_counts.yaml
test_resources:
- path: /resources_test/task_ist_preprocessing/mouse_brain_combined
dest: resources_test/task_ist_preprocessing/mouse_brain_combined
- type: python_script
path: /common/component_tests/run_and_check_output.py
- type: python_script
path: /common/component_tests/check_config.py
13 changes: 13 additions & 0 deletions src/datasets/loaders/tenx_atera/script.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## code author: Florian Heyl
import shutil
import os
import json
import zipfile
import tempfile
from pathlib import Path
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions src/datasets/loaders/tenx_xenium/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from spatialdata_io import xenium
import shutil
import os
import json
import zipfile
import tempfile

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/methods_cell_type_annotation/moscot/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@
adata_sp.X = adata_sp.layers["normalized"]
adata_sp.obsm["spatial"] = adata_sp.obs[["centroid_x", "centroid_y"]].to_numpy()

sc.pp.pca(adata_sc, n_comps=50) # X is the normalized layer set above

# Define mapping problem
mp = MappingProblem(adata_sc=adata_sc, adata_sp=adata_sp)

mp = mp.prepare(
sc_attr={"attr": "layers", "key": "normalized"},
sc_attr={"attr": "obsm", "key": "X_pca"}, # <-- 50-dim, not raw genes
xy_callback="local-pca",
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__merge__: /src/api/comp_method_expression_correction.yaml
__merge__: /src/api/comp_method_gene_efficiency_correction.yaml

name: gene_efficiency_correction
label: "Gene Efficiency Correction"
Expand Down Expand Up @@ -27,7 +27,7 @@ resources:
engines:
- type: docker
image: openproblems/base_python:1
__merge__:
__merge__:
- /src/base/setup_txsim_partial.yaml
setup:
- type: python
Expand All @@ -38,4 +38,4 @@ runners:
- type: executable
- type: nextflow
directives:
label: [ midtime, lowcpu, midmem ]
label: [ midtime, lowcpu, midmem ]
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,33 @@
# Note: this section is auto-generated by viash at runtime. To edit it, make changes
# in config.vsh.yaml and then run `viash config inject config.vsh.yaml`.
par = {
'input_spatial_with_cell_types': 'resources_test/task_ist_preprocessing/mouse_brain_combined/spatial_with_celltypes.h5ad',
'input': 'resources_test/task_ist_preprocessing/mouse_brain_combined/spatial_corrected.h5ad',
'input_scrnaseq_reference': 'resources_test/task_ist_preprocessing/mouse_brain_combined/scrnaseq_reference.h5ad',
'celltype_key': 'cell_type',
'output': 'spatial_corrected.h5ad',
'output': 'spatial_gene_efficiency_corrected.h5ad',
}
meta = {
'name': 'gene_efficiency_correction',
}
## VIASH END

# Optional parameter check: For this specific correction method the par['input_sc'] is required
assert par['input_scrnaseq_reference'] is not None, 'Single cell input is required for this expr correction method.'
# Optional parameter check: this correction method requires the scRNA-seq reference
assert par['input_scrnaseq_reference'] is not None, 'Single cell input is required for this gene efficiency correction method.'

# Read input
print('Reading input files', flush=True)
adata_sp = ad.read_h5ad(par['input_spatial_with_cell_types'])
adata_sp = ad.read_h5ad(par['input'])
adata_sc = ad.read_h5ad(par['input_scrnaseq_reference'])
adata_sp.layers["normalized_uncorrected"] = adata_sp.layers["normalized"]
# Preserve the pre-correction normalized layer. An upstream expression-correction stage
# already sets it, so only initialise it here if it is missing (keeps it meaning "before
# any correction").
if "normalized_uncorrected" not in adata_sp.layers:
adata_sp.layers["normalized_uncorrected"] = adata_sp.layers["normalized"]
adata_sp_reduced = adata_sp[:,adata_sc.var_names].copy()
obs_sp = adata_sp.obs.copy()

# Apply gene efficiency correction
print('Annotating cell types', flush=True)
print('Applying gene efficiency correction', flush=True)
adata_sp_reduced = tx.preprocessing.gene_efficiency_correction(
adata_sp_reduced, adata_sc, layer_key='normalized', ct_key=par['celltype_key']
)
Expand All @@ -36,7 +40,7 @@
print('Concatenating and reordering', flush=True)
gene_order = adata_sp.var_names.tolist()
gene_mask = ~adata_sp.var_names.isin(adata_sp_reduced.var_names)
adata_sp = ad.concat([adata_sp[:,gene_mask], adata_sp_reduced], axis=1, join="outer")
adata_sp = ad.concat([adata_sp[:,gene_mask], adata_sp_reduced], axis=1, join="outer")
adata_sp = adata_sp[:,gene_order]
adata_sp.obs = obs_sp

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
__merge__: /src/api/comp_method_gene_efficiency_correction.yaml

name: no_correction
label: "No Correction"
summary: "Dummy method that does not apply gene efficiency correction"
description: >-
Dummy method that does not apply gene efficiency correction; passes the (expression-)corrected
input through unchanged.
links:
documentation: "https://github.com/openproblems-bio/task_ist_preprocessing"
repository: "https://github.com/openproblems-bio/task_ist_preprocessing"
references:
doi: "10.1101/2023.02.13.528102"

resources:
- type: python_script
path: script.py

engines:
- type: docker
image: openproblems/base_python:1
setup:
- type: python
pypi: [anndata]
- type: native

runners:
- type: executable
- type: nextflow
directives:
label: [ midtime, lowcpu, lowmem ]
19 changes: 19 additions & 0 deletions src/methods_gene_efficiency_correction/no_correction/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import anndata as ad

## VIASH START
par = {
'input': 'resources_test/task_ist_preprocessing/mouse_brain_combined/spatial_corrected.h5ad',
'output': 'spatial_gene_efficiency_corrected.h5ad',
}
## VIASH END

# Read input
print('Reading input files', flush=True)
adata_sp = ad.read_h5ad(par['input'])
# Preserve the pre-correction normalized layer only if an upstream stage hasn't set it.
if "normalized_uncorrected" not in adata_sp.layers:
adata_sp.layers["normalized_uncorrected"] = adata_sp.layers["normalized"]

# Write output
print('Writing output', flush=True)
adata_sp.write(par['output'])
13 changes: 8 additions & 5 deletions src/methods_transcript_assignment/segger/config.vsh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 27 additions & 2 deletions src/methods_transcript_assignment/segger/script.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import json
import shutil
import subprocess
from pathlib import Path
Expand Down Expand Up @@ -78,11 +79,15 @@
trans = sd.transformations.Sequence([trans_segm_to_global, trans_global_to_transcripts])
boundaries = sd.transform(boundaries, trans, par['coordinate_system'])

# Xenium boundary schema: one row per polygon vertex (cell_id int64, vertex_x/y float64).
# Xenium boundary schema: one row per polygon vertex (vertex_x/y float64). cell_id is cast
# to STRING (via int64, so labels render as clean "1"/"2") to match the transcripts.parquet
# cell_id written below: segger joins transcript cell_id -> boundary cell_id to map each cell
# to its polygon, and a str-vs-int mismatch makes every row miss -> all-nan entity_index ->
# "Index has duplicate keys: [nan]" in setup_anndata. Real Xenium v2+ uses string ids in both.
boundaries.index.name = "cell_id"
boundaries_df = boundaries['geometry'].get_coordinates().rename(columns={'x': 'vertex_x', 'y': 'vertex_y'})
boundaries_df = boundaries_df.reset_index()
boundaries_df['cell_id'] = boundaries_df['cell_id'].astype(np.int64)
boundaries_df['cell_id'] = boundaries_df['cell_id'].astype(np.int64).astype(str)
boundaries_df['vertex_x'] = boundaries_df['vertex_x'].astype(np.float64)
boundaries_df['vertex_y'] = boundaries_df['vertex_y'].astype(np.float64)
boundaries_df = boundaries_df[['cell_id', 'vertex_x', 'vertex_y']]
Expand Down Expand Up @@ -154,6 +159,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 #
################
Expand Down
Loading
Loading