Skip to content

General purpose sq.tl.align() function with STAlign core logic#1162

Open
timtreis wants to merge 7 commits intoscverse:mainfrom
timtreis:align-skeleton
Open

General purpose sq.tl.align() function with STAlign core logic#1162
timtreis wants to merge 7 commits intoscverse:mainfrom
timtreis:align-skeleton

Conversation

@timtreis
Copy link
Copy Markdown
Member

@timtreis timtreis commented Apr 16, 2026

Implements the following functions:

sq.experimental.tl.align_by_landmarks(
    sdata,
    cs_name_ref="section_a",
    cs_name_query="section_b",
    landmarks_ref=((x1, y1), (x2, y2), (x3, y3)),
    landmarks_query=((x1, y1), (x2, y2), (x3, y3)),
    model="similarity",  # or "affine" for 6-DOF
)

This registers a spatialdata Affine transformation on every element in cs_b, mapping it into cs_a. After the call, sdata is updated in-place, all elements in section_b now know how to transform into section_a's coordinate system.

result = sq.experimental.tl.align_obs(
    adata_ref,
    adata_query,
    flavour="stalign", # will later accept moscot
    inplace=False, # otherwise, creates new aligned adata in sdata
    output_mode="obs",
)

output_mode controls what you get back:

  • "obs": a new AnnData with baked-in aligned coordinates
  • "affine": registers a transform on the SpatialData element (only works if the backend produces an affine; LDDMM doesn't)
  • "return": raw AlignResult with the displacement field and full solver state for power users
sq.experimental.tl.align_images(
    ...
)

Currently dead but will be implemente via STalign. Already in here to better align function internals

Introduces a backend-agnostic alignment API (align_obs, align_images,
align_by_landmarks) with STalign and landmark backends, lazy JAX imports,
and e2e tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 31.13855% with 502 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.54%. Comparing base (5b3c95d) to head (1d4c135).

Files with missing lines Patch % Lines
.../experimental/tl/_align/_backends/_stalign_core.py 0.00% 156 Missing ⚠️
...perimental/tl/_align/_backends/_stalign_helpers.py 0.00% 115 Missing ⚠️
...experimental/tl/_align/_backends/_stalign_tools.py 0.00% 92 Missing ⚠️
src/squidpy/experimental/tl/_align/_io.py 55.26% 43 Missing and 8 partials ⚠️
...uidpy/experimental/tl/_align/_backends/_stalign.py 36.66% 18 Missing and 1 partial ⚠️
src/squidpy/experimental/tl/_align/_api.py 69.23% 12 Missing and 4 partials ⚠️
src/squidpy/experimental/tl/_align/_types.py 70.37% 14 Missing and 2 partials ⚠️
...quidpy/experimental/tl/_align/_backends/_moscot.py 0.00% 11 Missing ⚠️
.../squidpy/experimental/tl/_align/_backends/_base.py 0.00% 10 Missing ⚠️
...idpy/experimental/tl/_align/_backends/_landmark.py 76.47% 5 Missing and 3 partials ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1162      +/-   ##
==========================================
- Coverage   73.56%   69.54%   -4.02%     
==========================================
  Files          44       56      +12     
  Lines        6929     7658     +729     
  Branches     1174     1272      +98     
==========================================
+ Hits         5097     5326     +229     
- Misses       1347     1825     +478     
- Partials      485      507      +22     
Files with missing lines Coverage Δ
src/squidpy/experimental/tl/_align/_jax.py 88.88% <88.88%> (ø)
src/squidpy/experimental/tl/_align/_validation.py 86.53% <86.53%> (ø)
...idpy/experimental/tl/_align/_backends/_landmark.py 76.47% <76.47%> (ø)
.../squidpy/experimental/tl/_align/_backends/_base.py 0.00% <0.00%> (ø)
...quidpy/experimental/tl/_align/_backends/_moscot.py 0.00% <0.00%> (ø)
src/squidpy/experimental/tl/_align/_api.py 69.23% <69.23%> (ø)
src/squidpy/experimental/tl/_align/_types.py 70.37% <70.37%> (ø)
...uidpy/experimental/tl/_align/_backends/_stalign.py 36.66% <36.66%> (ø)
src/squidpy/experimental/tl/_align/_io.py 55.26% <55.26%> (ø)
...experimental/tl/_align/_backends/_stalign_tools.py 0.00% <0.00%> (ø)
... and 2 more

... 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.

- Replace private `sdata._gen_elements()` with public `gen_elements()`
- Replace dict-style `sdata[key]` lookup with explicit element-type search
- Add subprocess timeout (30s) to lazy-import hygiene test
- Document shallow X sharing in `materialise_obs` docstring
- Document JAX array retention in stalign metadata comment
- Document camelCase convention in STalignRegistrationConfig docstring
- Broaden landmark type hints to accept Sequence and np.ndarray
- Remove stale TODO comment from _stalign_helpers.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
timtreis and others added 2 commits April 16, 2026 12:52
- Resolve JAX_DTYPE lazily via jax_dtype() to respect runtime x64 config
- Replace 14-line config field unpack with dataclasses.asdict(registration)
- Remove unreachable ValueError in _writeback (already validated upstream)
- Remove task-tracking comment from moscot stub
- Clean up PR-line-number reference in stalign comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a hatch-test.py3.13-stable-jax environment that installs the [jax]
optional extra so the STalign solver and e2e alignment tests run in CI.
Excluded from macOS to avoid doubling runner cost.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@timtreis timtreis changed the title Migrate logic into general skeleton General purpose sq.tl.align() function with STAlign core logic Apr 16, 2026
timtreis and others added 3 commits April 16, 2026 14:13
- align_obs: default output_mode="obs" (was "affine", which crashed
  with the default stalign backend). Auto-generate key_added from
  query name when not provided for SpatialData inputs.
- align_by_landmarks: make cs_name_ref/query and landmarks_ref/query
  keyword-only required args (were Optional with None defaults that
  immediately errored). Remove unused scale_ref/scale_query params.
  Wire get_extent validation for landmark bounds checking against cs
  extent. Fix docstring (y, x) -> (x, y).
- align_images: make img_ref/query_name keyword-only required. Remove
  from public __all__ (no backend implements it yet).
- align_obs docstring: note that inplace only affects SpatialData.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JAX selects the appropriate device based on its install (CPU/GPU) and
runtime context managers. The explicit device arg added unnecessary
complexity with no benefit over JAX's built-in device management.

Removes device from: align_obs, align_images, AlignBackend protocol,
StAlignBackend, MoscotBackend, and require_jax. Simplifies require_jax
to a pure import guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@timtreis timtreis requested a review from selmanozleyen April 19, 2026 22:33
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