Skip to content
Merged

Fixes #200

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 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
d7afb84
added fastreseg
dariarom94 Jul 23, 2026
f87a1d9
segger bug new fix
dariarom94 Jul 23, 2026
123e112
fastreseg to workflow
dariarom94 Jul 23, 2026
7549589
add fastreseg test
dariarom94 Jul 23, 2026
9fa9604
Merge branch 'main' into fixes
dariarom94 Jul 23, 2026
ff04467
optimized fastreseg build
dariarom94 Jul 23, 2026
7ffc514
Merge branch 'main' into fixes
dariarom94 Jul 23, 2026
a7404d8
add s3 paths
dariarom94 Jul 23, 2026
19e5f83
troubleshoot comseg/segger
dariarom94 Jul 24, 2026
aaca151
segger update
dariarom94 Jul 25, 2026
c9bdb91
data loader bug
dariarom94 Jul 25, 2026
1df9834
Merge branch 'main' into fixes
dariarom94 Jul 25, 2026
4467d32
rctd adjustment (raw counts)
dariarom94 Jul 26, 2026
58912e4
fix segger and comseg
dariarom94 Jul 26, 2026
0a5aa99
optimize cosmx
dariarom94 Jul 26, 2026
298e666
Merge branch 'main' into fixes
dariarom94 Jul 26, 2026
c921937
parameter test for cellpose4
dariarom94 Jul 26, 2026
57c2d79
add atera
dariarom94 Jul 26, 2026
cf67e09
add a test in pciseq and dynamic memory for bruker
dariarom94 Jul 27, 2026
9f71692
add test to vizgen data
dariarom94 Jul 28, 2026
d795f33
Merge branch 'main' into fixes
dariarom94 Jul 28, 2026
fefaadc
param sweep
dariarom94 Jul 28, 2026
4dc08d3
add params to segmentation
dariarom94 Jul 28, 2026
e9505f1
adjust segger mem
dariarom94 Jul 29, 2026
3a155be
update fastreseg to tacco
dariarom94 Jul 29, 2026
acdd6c7
Add annotation + expression-correction parameter sweeps (rctd, ssam, …
dariarom94 Jul 30, 2026
fa462b8
Add moscot + split parameter sweeps (annotation, expression correction)
dariarom94 Jul 30, 2026
fd37aee
singler: read par['celltype_key'] instead of hardcoding "cell_type"
dariarom94 Jul 30, 2026
3449bd0
fastreseg
dariarom94 Jul 30, 2026
331d219
Merge branch 'main' into fixes
dariarom94 Jul 30, 2026
5961d0a
adjust labels
dariarom94 Jul 30, 2026
857e16a
bruker nsclc
dariarom94 Jul 31, 2026
54f023e
adjust bruker nsclc loader
dariarom94 Jul 31, 2026
6e8b9ea
setup
dariarom94 Jul 31, 2026
4eec493
Merge branch 'main' into fixes
dariarom94 Jul 31, 2026
44ad7af
method correction
dariarom94 Jul 31, 2026
b351148
Merge branch 'main' into fixes
dariarom94 Jul 31, 2026
2b8177c
pin anndata
dariarom94 Aug 1, 2026
6c16707
mirror nsclc
dariarom94 Aug 1, 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
14 changes: 7 additions & 7 deletions scripts/create_resources/spatial/mirror_bruker_to_s3.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# SCRATCH_DIR=/path/to/big/scratch ./mirror_bruker_to_s3.sh

set -euo pipefail

SCRATCH_DIR="/Volumes/SeagateHHD"
# --- Config -----------------------------------------------------------------

SRC_BASE="https://smi-public.objects.liquidweb.services"
Expand All @@ -33,11 +33,11 @@ SCRATCH_DIR="${SCRATCH_DIR:-$PWD/bruker_mirror_scratch}"
# - SOURCE is either a full https URL, or a name relative to SRC_BASE (the mouse/liver host).
# The URL-encoded names are what the liquidweb server serves.
# - LOCAL_NAME is the (decoded) name to store under on S3.
FILES=(
"HalfBrain.zip|HalfBrain.zip"
"Half%20%20Brain%20simple%20%20files%20.zip|Half Brain simple files.zip"
"NormalLiverFiles.zip|NormalLiverFiles.zip"
)
#FILES=(
# "HalfBrain.zip|HalfBrain.zip"
# "Half%20%20Brain%20simple%20%20files%20.zip|Half Brain simple files.zip"
# "NormalLiverFiles.zip|NormalLiverFiles.zip"
#)

# NSCLC lung-cancer samples: each ships a flat-files+cell-labels archive and a
# raw-morphology-images archive. The bruker_cosmx_nsclc loader streams both from S3.
Expand Down Expand Up @@ -132,7 +132,7 @@ for entry in "${FILES[@]}"; do

# Upload to S3
echo " Uploading to s3://$bucket/$key ..."
aws s3 cp "$local_path" "s3://$bucket/$key"
aws s3 cp "$local_path" "s3://$bucket/$key" --profile op

# Free scratch space before the next (much larger) file
echo " Removing local copy to free space"
Expand Down
2 changes: 1 addition & 1 deletion src/base/setup_spatialdata_partial.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
setup:
- type: python
pypi: ["spatialdata>=0.7.3", "anndata>=0.12.0", "zarr>=3.0.0"]
pypi: ["spatialdata>=0.7.3", "anndata>=0.12.0,<0.13", "zarr>=3.0.0"]
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ engines:
- /src/base/setup_txsim_partial.yaml
setup:
- type: python
pypi: ["anndata>=0.12.0", scvi-tools]
pypi: ["anndata>=0.12.0,<0.13", scvi-tools]
- type: native

runners:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ engines:
- /src/base/setup_txsim_partial.yaml
setup:
- type: python
pypi: ["anndata>=0.12.0"]
pypi: ["anndata>=0.12.0,<0.13"]
- type: native

runners:
Expand Down
2 changes: 1 addition & 1 deletion src/methods_qc_filter/basic_qc_filter/config.vsh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ engines:
- /src/base/setup_txsim_partial.yaml
setup:
- type: python
pypi: ["anndata>=0.12.0"]
pypi: ["anndata>=0.12.0,<0.13"]
- type: native

runners:
Expand Down
14 changes: 14 additions & 0 deletions src/methods_transcript_assignment/fastreseg/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ def parse_arguments():
label_image = sdata_segm["segmentation"]["scale0"].image.to_numpy()
else:
label_image = sdata_segm["segmentation"].to_numpy()
# Guard against an empty segmentation. If the label image has no cells (all zeros)
# every transcript is assigned to background (cell_id 0), generate_adata drops them
# all, and the downstream tacco annotation then divides by a zero count norm and
# fails with a cryptic "divide by zero" instead of a useful message. Observed for the
# vizgen lung-cancer merscope dataset, whose polygon->label rasterization produced an
# all-zero image (for custom_segmentation this means the dataset's `cell_labels` is
# empty; re-process the dataset). Mirrors the guard in methods_transcript_assignment/pciseq.
if label_image.max() == 0:
raise ValueError(
f"Segmentation '{input_segmentation_path}' contains no cells: the label image "
f"(shape={label_image.shape}, dtype={label_image.dtype}) is entirely zero. FastReseg "
f"cannot assign transcripts to cells "
f"(for custom_segmentation this means the dataset's `cell_labels` is empty)."
)
# Clamp coords to the label-image bounds: transcripts at the crop boundary can
# round a few pixels past the raster edge, or the transcript field of view can
# extend beyond the segmented raster (real data), so label_image[y, x] would
Expand Down
93 changes: 72 additions & 21 deletions src/methods_transcript_assignment/segger/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@
segmentation_coord_systems = sd.transformations.get_transformation(sdata_segm["segmentation"], get_all=True).keys()
assert par['coordinate_system'] in segmentation_coord_systems, f"Coordinate system '{par['coordinate_system']}' not found in input data."

# Guard against an empty (all-background) segmentation. sd.to_polygons() on an all-zero
# label image returns an EMPTY GeoDataFrame, which then crashes deep inside spatialdata's
# ShapesModel.parse (`data["geometry"].iloc[0]` -> IndexError: "single positional indexer
# is out-of-bounds"). This happens for datasets whose cell_labels are all-zero from the
# stale pre-0.8.0 spatialdata rasterize bug -- e.g. 2022_vizgen_human_lung_cancer_merfish_
# combined via custom_segmentation (which just copies cell_labels). The real fix is to
# re-process that dataset under spatialdata>=0.8.0; fail fast here with an actionable
# message rather than the cryptic pandas IndexError. Mirrors the guard in pciseq/script.py.
_seg_elem = sdata_segm["segmentation"]
_seg_arr = _seg_elem["scale0"].image if isinstance(_seg_elem, xr.DataTree) else _seg_elem
if int(_seg_arr.data.max()) == 0:
raise ValueError(
"Segmentation label image is empty (all background, no cells) -- segger cannot "
"assign transcripts to zero cells. This typically means the dataset's cell_labels "
"are all-zero from the stale pre-0.8.0 spatialdata rasterize bug (e.g. "
"2022_vizgen_human_lung_cancer_merfish_combined via custom_segmentation). "
"Re-process that dataset under spatialdata>=0.8.0 to fix it at the source."
)

##########################################
# boundaries.parquet (from segmentation) #
##########################################
Expand Down Expand Up @@ -141,15 +160,35 @@
label_image = sdata_segm["segmentation"]["scale0"].image.to_numpy()
else:
label_image = sdata_segm["segmentation"].to_numpy()
# Clip to the label image bounds (transcripts can sit just outside after transform).
n_oob = int(np.count_nonzero((y_coords < 0) | (y_coords >= label_image.shape[0]) | (x_coords < 0) | (x_coords >= label_image.shape[1])))
y_coords = np.clip(y_coords, 0, label_image.shape[0] - 1)
x_coords = np.clip(x_coords, 0, label_image.shape[1] - 1)
print(f"Clamped {n_oob}/{len(x_coords)} transcripts outside the {label_image.shape[0]}x{label_image.shape[1]} label image to its edge", flush=True)
prior_cell_id = label_image[y_coords, x_coords].astype(np.int64) # 0 == background

# Canonical transcripts frame (native coordinates, clean RangeIndex). Its row
# order matches prior_cell_id and the row_index segger reports back below.
# Transcripts can land outside the label image after the transform (e.g. the combine step
# crops the image/labels to ~20000x20000 but transcripts span a larger field -> here ~38%
# of them fall outside). Those OOB transcripts must NOT be fed to segger: segger tiles the
# transcript field into a heterogeneous graph with 'tx' (transcript) and 'bd' (boundary/
# cell) node types, and a tile covering a transcripts-only region (no boundaries) produces a
# training mini-batch with ZERO 'bd' nodes. segger's positional encoder then runs
# `torch.zeros((batch.max()+1, 2))` on an empty batch -> `RuntimeError: max(): Expected
# reduction dim to be specified for input.numel() == 0`, which kills training MID-RUN (only
# when the DataLoader's shuffle happens to draw an all-OOB mini-batch, so it can survive
# several epochs first). A transcript outside the segmentation can't belong to any segmented
# cell anyway, so we EXCLUDE it from the segger input and leave it unassigned (cell_id 0) in
# the final output. Datasets whose segmentation covers the whole transcript field are
# unaffected (in_bounds all-True, n_oob == 0).
in_bounds = (
(y_coords >= 0) & (y_coords < label_image.shape[0])
& (x_coords >= 0) & (x_coords < label_image.shape[1])
)
n_oob = int((~in_bounds).sum())
y_look = np.clip(y_coords, 0, label_image.shape[0] - 1)
x_look = np.clip(x_coords, 0, label_image.shape[1] - 1)
prior_cell_id = label_image[y_look, x_look].astype(np.int64) # 0 == background
prior_cell_id[~in_bounds] = 0 # OOB -> background (excluded from segger regardless)
print(f"{n_oob}/{len(in_bounds)} transcripts fall outside the "
f"{label_image.shape[0]}x{label_image.shape[1]} label image; excluding them from the "
f"segger input (they stay unassigned in the output).", flush=True)

# Canonical transcripts frame (native coordinates, clean RangeIndex). Its row order matches
# prior_cell_id / in_bounds; the IN-BOUNDS subset's order matches the parquet we write and
# hence segger's reported row_index (mapped back through seg_orig_idx below).
tx_pd = transcripts_reset.compute()
n_tx = len(tx_pd)

Expand All @@ -158,19 +197,30 @@
else:
transcript_id = np.arange(n_tx, dtype=np.uint64)

# Full-frame positions of the transcripts actually handed to segger. segger's output
# row_index indexes into the (in-bounds) parquet row order; seg_orig_idx maps it back.
seg_orig_idx = np.nonzero(in_bounds)[0]
if seg_orig_idx.size == 0:
raise ValueError(
"No transcripts fall within the segmentation label image, so segger has nothing to "
"assign. This usually means the transcripts and segmentation are in mismatched "
"coordinate frames (check the dataset's crop / transforms)."
)

n_in = int(in_bounds.sum())
tx_out = pd.DataFrame({
"transcript_id": transcript_id,
"x_location": tx_pd["x"].to_numpy().astype(np.float32),
"y_location": tx_pd["y"].to_numpy().astype(np.float32),
"feature_name": tx_pd["feature_name"].astype(str).to_numpy(),
"transcript_id": transcript_id[in_bounds],
"x_location": tx_pd["x"].to_numpy().astype(np.float32)[in_bounds],
"y_location": tx_pd["y"].to_numpy().astype(np.float32)[in_bounds],
"feature_name": tx_pd["feature_name"].astype(str).to_numpy()[in_bounds],
# segger's Xenium loader keys transcripts to boundaries by cell_id string;
# background (0) becomes the UNASSIGNED sentinel.
"cell_id": np.where(prior_cell_id > 0, prior_cell_id.astype(str), "UNASSIGNED"),
"qv": np.full(n_tx, 40.0, dtype=np.float32),
"overlaps_nucleus": (prior_cell_id > 0).astype(np.int8),
"cell_id": np.where(prior_cell_id[in_bounds] > 0, prior_cell_id[in_bounds].astype(str), "UNASSIGNED"),
"qv": np.full(n_in, 40.0, dtype=np.float32),
"overlaps_nucleus": (prior_cell_id[in_bounds] > 0).astype(np.int8),
})
if "z" in tx_pd.columns:
tx_out.insert(3, "z_location", tx_pd["z"].to_numpy().astype(np.float32))
tx_out.insert(3, "z_location", tx_pd["z"].to_numpy().astype(np.float32)[in_bounds])
tx_out.to_parquet(XENIUM_DIR / "transcripts.parquet", index=False)
del transcripts, y_coords, x_coords, label_image

Expand Down Expand Up @@ -291,14 +341,15 @@ def run_segger(node_dim):
seg = seg[keep_mask]
print(f"segger kept {len(seg)} assignments of {n_tx} transcripts", flush=True)

# segger reports row_index into the transcripts.parquet we wrote (i.e. tx_pd
# row order). Factorize the (string) segger_cell_id into contiguous positive
# integers; unassigned transcripts stay 0.
# segger reports row_index into the transcripts.parquet we wrote, which is the IN-BOUNDS
# subset (OOB transcripts were excluded above). Map that back to the full-frame position via
# seg_orig_idx, then factorize the (string) segger_cell_id into contiguous positive integers;
# unassigned / out-of-bounds transcripts stay 0.
cell_id_per_tx = np.zeros(n_tx, dtype=np.int64)
if len(seg):
row_idx = seg["row_index"].to_numpy().astype(np.int64)
codes, _ = pd.factorize(seg["segger_cell_id"].astype(str), sort=True)
cell_id_per_tx[row_idx] = codes.astype(np.int64) + 1
cell_id_per_tx[seg_orig_idx[row_idx]] = codes.astype(np.int64) + 1

#############################################
# Build transcript-assignment output object #
Expand Down
Loading