Skip to content

fix: tile-invariant featurization (positions, truncation, multiscale, granularity) + lower output memory - #1295

Draft
timtreis wants to merge 3 commits into
mainfrom
fix/featurization-tiling-correctness
Draft

timtreis wants to merge 3 commits into
mainfrom
fix/featurization-tiling-correctness

Conversation

@timtreis

Copy link
Copy Markdown
Member

Tiled calculate_image_features (and calculate_tiling_qc) could silently return values that depend on tile_size:

  • Positions were tile-local. Center_X/Y, BoundingBox*, Location_*, centroid-* are now in the labels' pixel grid.
  • Asymmetric cells were truncated. Tiles now crop to the union of their owned cells' bounding boxes + 1 px, so no cell is cut. This also stops one huge object from inflating every tile's read.
  • Multiscale labels dropped small cells. Centroids came from the coarsest scale only; the scan now uses the requested scale.
  • Granularity drifted with tiling. Tiles that compute it are aligned to its 16 px sampling grid and padded by 256 px. On real Xenium nuclei, Spearman vs an untiled run for bins 1-5 goes from 0.77-0.90 to 0.94-0.997.

Output assembly no longer copies the full table three times: ~3x lower peak memory and ~27x faster on 1e8 values, with identical output.

# same values for any tile_size (except the coarsest granularity bins, which depend on context beyond any tile)
a = sq.experimental.im.calculate_image_features(sdata, image_key="img", labels_key="cells", tile_size=100_000, inplace=False)
b = sq.experimental.im.calculate_image_features(sdata, image_key="img", labels_key="cells", tile_size=1024, inplace=False)

- Positional features (Center_*, BoundingBox*, Location_*, centroid-*)
  were relative to each tile's crop; report them in the labels' pixel
  grid, including the alignment crop offset.
- The auto margin assumed a cell reaches at most half its bbox from its
  centroid, truncating asymmetric cells at tile edges. Crop each tile to
  the union of its owned cells' bounding boxes plus 1 px instead.
- Multiscale labels computed centroids on the coarsest scale only, so
  cells that vanish there were never featurized or QC'd. Scan the
  requested scale.
Cast each tile to float32 and fill one preallocated, label-sorted array
instead of concat -> sort_index -> nunique -> cast. Constant columns are
found from per-tile min/max (same semantics) and never copied. ~3x lower
peak memory and ~27x faster on 1e8 values; output is bit-identical.
Granularity samples each crop on a 1/4 then 1/16 grid anchored at the
crop origin and removes background with an opening reaching ~320 px, so
tiled values drifted from an untiled run. When granularity is requested,
snap tile crops to its 16 px grid and pad them by 256 px. On real Xenium
DAPI nuclei, tiled-vs-untiled Spearman for bins 1-5 rises from
0.77-0.90 to 0.94-0.997; higher bins remain context-dependent
(documented).
@codecov

codecov Bot commented Sep 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.77465% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.51%. Comparing base (8286274) to head (ba04760).

Files with missing lines Patch % Lines
...uidpy/experimental/im/_calculate_image_features.py 98.07% 1 Missing ⚠️
src/squidpy/experimental/im/_tiling.py 93.75% 0 Missing and 1 partial ⚠️
src/squidpy/experimental/tl/_tiling_qc.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1295      +/-   ##
==========================================
+ Coverage   78.44%   78.51%   +0.07%     
==========================================
  Files          63       63              
  Lines        9532     9537       +5     
  Branches     1594     1593       -1     
==========================================
+ Hits         7477     7488      +11     
+ Misses       1489     1486       -3     
+ Partials      566      563       -3     
Files with missing lines Coverage Δ
...uidpy/experimental/im/_calculate_image_features.py 89.93% <98.07%> (+0.50%) ⬆️
src/squidpy/experimental/im/_tiling.py 90.64% <93.75%> (+1.57%) ⬆️
src/squidpy/experimental/tl/_tiling_qc.py 70.25% <66.66%> (-0.32%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant