Skip to content

Commit 77dcf9e

Browse files
committed
WIP: use zarrnii for full-res snaps rather than downsampled
1 parent 1cd0826 commit 77dcf9e

2 files changed

Lines changed: 65 additions & 54 deletions

File tree

spimquant/workflow/rules/qc.smk

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,24 +157,17 @@ voxel dimensions. Provides detail-level visualisation of segmentation
157157
quality within individual brain regions.
158158
"""
159159
input:
160-
spim=bids(
161-
root=root,
162-
datatype="micr",
163-
stain="{stain}",
164-
level=config["registration_level"],
165-
suffix="SPIM.nii.gz",
166-
**inputs["spim"].wildcards,
167-
),
168-
fieldfrac=bids(
160+
spim=inputs["spim"].path,
161+
mask=bids(
169162
root=root,
170163
datatype="seg",
171164
stain="{stain}",
172-
level=config["registration_level"],
165+
level=config['segmentation_level'],
173166
desc="{desc}",
174-
suffix="fieldfrac.nii.gz",
167+
suffix="mask.ozx",
175168
**inputs["spim"].wildcards,
176169
),
177-
dseg=bids(
170+
dseg_nii=bids(
178171
root=root,
179172
datatype="parc",
180173
seg="{seg}",
@@ -200,13 +193,14 @@ quality within individual brain regions.
200193
suffix="roimontage.png",
201194
**inputs["spim"].wildcards,
202195
),
203-
threads: 1
196+
threads: 32
204197
resources:
205-
mem_mb=8000,
198+
mem_mb=32000,
206199
runtime=15,
207200
params:
208201
max_rois=25,
209202
n_cols=5,
203+
level=config['segmentation_level'],
210204
script:
211205
"../scripts/qc_segmentation_roi_zoom.py"
212206

spimquant/workflow/scripts/qc_segmentation_roi_zoom.py

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""ROI-cropped segmentation montage QC.
22
33
For each brain region in the atlas parcellation (resampled to subject space),
4-
crops the SPIM image and the field-fraction mask to the region's bounding box
5-
and shows the central axial slice with the field-fraction overlay. This
6-
provides a detail-level view of segmentation quality within individual brain
7-
regions, complementing the whole-brain overview in ``qc_segmentation_overview``.
4+
crops the SPIM image and the segmentation mask to a fixed 2D bounding box
5+
at the region centroid. This provides a detail-level view of segmentation
6+
quality within individual brain regions, complementing the whole-brain
7+
overview in ``qc_segmentation_overview``.
88
9-
Voxel dimensions from the NIfTI header are used to preserve the correct
9+
Voxel dimensions from the OME-Zarr image are used to preserve the correct
1010
physical aspect ratio in each panel.
1111
1212
This is a Snakemake script that expects the ``snakemake`` object to be
@@ -18,6 +18,7 @@
1818

1919
matplotlib.use("agg")
2020
import matplotlib.pyplot as plt
21+
from zarrnii import ZarrNii, ZarrNiiAtlas
2122
import nibabel as nib
2223
import numpy as np
2324
import pandas as pd
@@ -74,28 +75,36 @@ def main():
7475
max_rois = snakemake.params.max_rois
7576
n_cols = snakemake.params.n_cols
7677

77-
spim_nib = nib.load(snakemake.input.spim)
78-
spim_data = spim_nib.get_fdata()
79-
ff_data = nib.load(snakemake.input.fieldfrac).get_fdata()
80-
dseg_data = nib.load(snakemake.input.dseg).get_fdata().astype(int)
78+
spim_img = ZarrNii.from_ome_zarr(snakemake.input.spim,level=snakemake.params.level, downsample_near_isotropic=True,channel_labels=[snakemake.wildcards.stain])
79+
mask_img = ZarrNii.from_ome_zarr(snakemake.input.mask,level=0)
80+
81+
atlas = ZarrNiiAtlas.from_files(snakemake.input.dseg_nii,snakemake.input.label_tsv)
82+
83+
dseg_data = atlas.dseg.data.compute()
84+
85+
#spim_data = spim_nib.get_fdata()
86+
#ff_data = nib.load(snakemake.input.fieldfrac).get_fdata()
87+
#dseg_data = nib.load(snakemake.input.dseg).get_fdata().astype(int)
8188

8289
# Voxel dimensions (mm) for physical aspect-ratio correction
83-
zooms = spim_nib.header.get_zooms()
84-
dx, dy = float(zooms[0]), float(zooms[1])
85-
# Axial (Z) slice: after rot90, rows=y, cols=x → aspect = dy/dx
86-
aspect_axial = dy / dx
90+
#zooms = spim_nib.header.get_zooms()
91+
#dx, dy = float(zooms[0]), float(zooms[1])
92+
## Axial (Z) slice: after rot90, rows=y, cols=x → aspect = dy/dx
93+
#aspect_axial = dy / dx
94+
95+
8796

8897
# Bring all volumes to the SPIM grid
89-
ff_data = _match_shape(ff_data, spim_data.shape, order=1)
90-
dseg_data = _match_shape(dseg_data, spim_data.shape, order=0).astype(int)
98+
#ff_data = _match_shape(ff_data, spim_data.shape, order=1)
99+
#dseg_data = _match_shape(dseg_data, spim_data.shape, order=0).astype(int)
91100

92101
# Global normalisations
93-
spim_norm = _percentile_norm(spim_data)
102+
#spim_norm = _percentile_norm(spim_data)
94103
# Field fraction values are 0–100; normalise to 0–1 for display
95-
ff_norm = np.clip(ff_data / 100.0, 0.0, 1.0)
104+
#ff_norm = np.clip(ff_data / 100.0, 0.0, 1.0)
96105

97106
# Load atlas label table
98-
label_df = pd.read_csv(snakemake.input.label_tsv, sep="\t")
107+
label_df = atlas.labels_df
99108

100109
# Keep non-background labels that are present in this subject's dseg
101110
present_ids = set(np.unique(dseg_data)) - {0}
@@ -151,35 +160,43 @@ def main():
151160
label_id = int(row["index"])
152161
label_name = str(row.get("name", label_id))
153162

154-
roi_mask = dseg_data == label_id
155-
bbox = _get_bounding_box(roi_mask, pad=5)
156-
if bbox is None:
157-
ax.text(
158-
0.5,
159-
0.5,
160-
f"{label_name}\n(empty)",
161-
ha="center",
162-
va="center",
163-
transform=ax.transAxes,
164-
fontsize=7,
165-
color="gray",
166-
)
167-
ax.axis("off")
168-
continue
163+
164+
#get cropped images for this label
165+
bbox_min, bbox_max = atlas.get_region_bounding_box(region_ids=label_id)
166+
center_coord = bbox_max - bbox_min
167+
spim_crop = spim_img.crop_centered(center_coord, patch_size=(256,256,1))[0]
168+
mask_crop = mask_img.crop_centered(center_coord, patch_size=(256,256,1))[0]
169+
170+
# roi_mask = dseg_data == label_id
171+
#
172+
# bbox = _get_bounding_box(roi_mask, pad=5)
173+
# if bbox is None:
174+
# ax.text(
175+
# 0.5,
176+
# 0.5,
177+
# f"{label_name}\n(empty)",
178+
# ha="center",
179+
# va="center",
180+
# transform=ax.transAxes,
181+
# fontsize=7,
182+
# color="gray",
183+
# )
184+
# ax.axis("off")
185+
# continue
169186

170187
# Crop to ROI bounding box
171-
spim_crop = spim_norm[bbox]
172-
ff_crop = ff_norm[bbox]
188+
# spim_crop = spim_norm[bbox]
189+
# ff_crop = ff_norm[bbox]
173190

174191
# Choose the Z-slice within the crop with the most field-fraction signal
175-
z_best = _select_best_z_slice(ff_crop)
192+
# z_best = _select_best_z_slice(ff_crop)
176193

177-
spim_sl = np.rot90(spim_crop[:, :, z_best])
178-
ff_sl = np.rot90(ff_crop[:, :, z_best])
194+
spim_sl = np.rot90(spim_crop[:, :, 0])
195+
mask_sl = np.rot90(mask_crop[:, :, 0])
179196

180197
ax.imshow(spim_sl, cmap="gray", vmin=0, vmax=1, aspect=aspect_axial)
181-
ff_masked = np.ma.masked_where(ff_sl < 0.01, ff_sl)
182-
ax.imshow(ff_masked, cmap="hot", alpha=0.6, vmin=0, vmax=1, aspect=aspect_axial)
198+
mask_masked = np.ma.masked_where(mask_sl < 0.01, mask_sl)
199+
ax.imshow(mask_masked, cmap="hot", alpha=0.6, vmin=0, vmax=1, aspect=aspect_axial)
183200
ax.set_title(label_name, fontsize=7, pad=2)
184201
ax.set_xticks([])
185202
ax.set_yticks([])

0 commit comments

Comments
 (0)