Skip to content

Commit aaf6f30

Browse files
committed
plot_manual_landmark_transforms: fix empty left panel and slow load
- Fix TIFF channel indexing: lm_stack[1] assumed (C,H,W) axis order but boundary_gfp TIFFs are (H,W,C); now disambiguates via shape[0]<=8 heuristic - Replace full-res sdata load (n=0, 4k-8k px) with pyramid level 2 for speed; landmark coordinates scaled by (display/fullres) factor so overlay stays correct
1 parent 1d0f484 commit aaf6f30

1 file changed

Lines changed: 30 additions & 7 deletions

File tree

src/xenium_analysis_tools/alignment/process_landmarks.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,13 +296,35 @@ def plot_manual_landmark_transforms(landmarks_before,
296296
Display inline. Set ``False`` when called from a worker thread.
297297
"""
298298
# ── Load landmarked TIFF ──────────────────────────────────────────────
299+
# Handle both channel-first (C, H, W) and channel-last (H, W, C) TIFFs.
300+
# A small first dimension (≤8) reliably indicates (C, H, W); a large one
301+
# means the first axis is height, i.e. (H, W) or (H, W, C).
299302
with tifffile.TiffFile(landmarked_image_path) as tif:
300303
lm_stack = tif.asarray()
301-
lm_img = lm_stack if lm_stack.ndim == 2 else lm_stack[1]
302-
303-
# ── Load sdata morphology at full resolution ──────────────────────────
304-
morph = sdata['morphology_focus']
305-
sdata_img = np.asarray(sd.get_pyramid_levels(morph, n=0)[1])
304+
if lm_stack.ndim == 2:
305+
lm_img = lm_stack
306+
elif lm_stack.ndim == 3 and lm_stack.shape[0] <= 8: # (C, H, W)
307+
lm_img = lm_stack[min(1, lm_stack.shape[0] - 1)]
308+
elif lm_stack.ndim == 3: # (H, W, C)
309+
lm_img = lm_stack[:, :, min(1, lm_stack.shape[2] - 1)]
310+
else: # unexpected shape — take first plane
311+
lm_img = lm_stack.reshape(-1, lm_stack.shape[-2], lm_stack.shape[-1])[0]
312+
313+
# ── Load sdata morphology at a downsampled level for speed ────────────
314+
# Full-res (n=0) can be 4k–8k px and is slow to materialise; a lower
315+
# level is sufficient for a diagnostic overlay. Landmarks are in full-res
316+
# pixel space, so we compute scale factors to map them to display space.
317+
morph = sdata['morphology_focus']
318+
n_scales = len(list(morph.keys()))
319+
disp_lvl = min(2, n_scales - 1)
320+
disp_da = sd.get_pyramid_levels(morph, n=disp_lvl)
321+
n_ch = disp_da.shape[0]
322+
sdata_img = np.asarray(disp_da[min(1, n_ch - 1)])
323+
324+
# Scale factors: full-res → display level (dask shape is cheap to read)
325+
full_res_shape = sd.get_pyramid_levels(morph, n=0).shape[-2:] # (H, W), lazy
326+
scale_y = sdata_img.shape[0] / full_res_shape[0]
327+
scale_x = sdata_img.shape[1] / full_res_shape[1]
306328

307329
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
308330

@@ -314,8 +336,10 @@ def plot_manual_landmark_transforms(landmarks_before,
314336
axes[0].set_title('Landmarked image + original landmarks (image pixel space)')
315337

316338
# ── Right: sdata image + transformed landmarks ────────────────────────
339+
# Scale landmark coordinates from full-res pixel space to display level.
317340
axes[1].imshow(sdata_img, cmap='gray')
318-
axes[1].scatter(landmarks_after['xenium_x'], landmarks_after['xenium_y'],
341+
axes[1].scatter(landmarks_after['xenium_x'] * scale_x,
342+
landmarks_after['xenium_y'] * scale_y,
319343
c='red', s=15, zorder=5)
320344
subtitle = 'sdata morphology_focus (scale0) + transformed landmarks'
321345
if landmarks_tf_info is not None:
@@ -338,7 +362,6 @@ def plot_manual_landmark_transforms(landmarks_before,
338362
else:
339363
plt.close(fig)
340364

341-
342365
def find_landmarked_img_transforms(landmarked_image_path,
343366
sdata_path,
344367
landmarks,

0 commit comments

Comments
 (0)