Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
5c08081
init
selmanozleyen Apr 2, 2026
5847d84
doc clarifications
selmanozleyen Apr 2, 2026
179ba06
don't expose the builders
selmanozleyen Apr 2, 2026
e187aba
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 2, 2026
cdb5699
deduplicate
selmanozleyen Apr 2, 2026
847a46b
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 2, 2026
69cda1a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 2, 2026
fddc027
arrange builders
selmanozleyen Apr 2, 2026
a30988a
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 2, 2026
f194b10
better behaviour
selmanozleyen Apr 2, 2026
ae1eba1
move classes
selmanozleyen Apr 2, 2026
a86572b
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen Apr 2, 2026
039b852
cleanup
selmanozleyen Apr 2, 2026
4699993
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 2, 2026
b5dcf9f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 2, 2026
4c11adf
add docs
selmanozleyen Apr 2, 2026
2b937e9
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 2, 2026
ea1ce93
remove leftover
selmanozleyen Apr 2, 2026
ebb7fd5
resolution in reolve func
selmanozleyen Apr 2, 2026
69a9394
reduce dup code
selmanozleyen Apr 2, 2026
77755ed
deprecate invalid n_neighs
selmanozleyen Apr 2, 2026
d8a8145
expose new functions and deprecate the flat one
selmanozleyen Apr 2, 2026
2e75b3f
move branches to classes
selmanozleyen Apr 2, 2026
8c1c2ef
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 2, 2026
268d30c
remove radius abstraction
selmanozleyen Apr 2, 2026
3f41243
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 2, 2026
aa9734a
add extensibility page
selmanozleyen Apr 2, 2026
67bad43
unprivate methods and add extensibility page
selmanozleyen Apr 2, 2026
65fa245
give a better example
selmanozleyen Apr 2, 2026
85bfdeb
apply signature suggestion
selmanozleyen Apr 9, 2026
79f584f
reorganize abstractions
selmanozleyen Apr 9, 2026
43cdbb6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2026
e632f74
remove unnecessary abstraction
selmanozleyen Apr 9, 2026
1f9da0e
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 9, 2026
c026860
put the warning in resolve graph builder code
selmanozleyen Apr 9, 2026
10cc2f0
remove n_neighs from RadiusBuilder
selmanozleyen Apr 9, 2026
6477f18
definition of Adj, Dst and rename to adj, dst
selmanozleyen Apr 9, 2026
516b3e0
Generalize the base class for extensibility
selmanozleyen Apr 10, 2026
141fdb6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 10, 2026
1fe316b
update docs
selmanozleyen Apr 10, 2026
bc6746a
Merge branch 'feat/spatial_neighbours' of https://github.com/selmanoz…
selmanozleyen Apr 10, 2026
bb1ec50
clarify delauney vs grid
selmanozleyen Apr 10, 2026
84cbdc4
dtype rec
selmanozleyen Apr 10, 2026
6d942ec
be a bit more verbose on funcitons
selmanozleyen Apr 10, 2026
ae9a4e6
spatial_neighbours_from_builder
selmanozleyen Apr 10, 2026
cd3f6fd
update docs to refer to each other
selmanozleyen Apr 10, 2026
95012dc
refer to the classes properly
selmanozleyen Apr 10, 2026
5a02b59
mark as TODO's
selmanozleyen Apr 13, 2026
3af98cc
rename sugg
selmanozleyen Apr 13, 2026
e8a19d7
add combine into API
selmanozleyen Apr 13, 2026
ac682a3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 13, 2026
0f3329c
update docs
selmanozleyen Apr 13, 2026
00ec2b0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 13, 2026
042f749
[pre-commit.ci] pre-commit autoupdate (#1149)
pre-commit-ci[bot] Apr 10, 2026
1a87841
Functions to QC histopathology images (#1036)
timtreis Apr 10, 2026
1cb6fde
remove coord_type abstraction
selmanozleyen Apr 13, 2026
8afcd03
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 13, 2026
3c4dffb
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen Apr 13, 2026
21f02c3
make builder positional consistently
selmanozleyen Apr 13, 2026
744c757
make more abstractions
selmanozleyen Apr 13, 2026
dbc7f0d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 13, 2026
d7e5e5f
graphmatrixT exposure
selmanozleyen Apr 13, 2026
13768e2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 13, 2026
ea3f3ca
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen Apr 20, 2026
21f248e
add radius: float | int | tuple t
selmanozleyen May 12, 2026
3bbc535
4 or 6
selmanozleyen May 12, 2026
a0426e6
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 12, 2026
f3eab97
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 15, 2026
8436656
Merge branch 'main' into feat/spatial_neighbours
timtreis May 19, 2026
07d59bf
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 19, 2026
3e9fc41
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 21, 2026
77627dc
mark snn as ilustrative
selmanozleyen May 21, 2026
0afbe3c
add uns params
selmanozleyen May 21, 2026
1711a62
deprecation warnings alignment
selmanozleyen May 21, 2026
4632b74
set diag isnt defaulted to none
selmanozleyen May 21, 2026
f4805c1
fix
selmanozleyen May 21, 2026
f2b0fa0
radius is also positional
selmanozleyen May 21, 2026
0ed1604
n_neighs on grid mode
selmanozleyen May 21, 2026
c85ba43
make radius kw only
selmanozleyen May 26, 2026
d9264a8
put a working example
selmanozleyen May 27, 2026
9c89dc2
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 27, 2026
52b4e79
fix api.md
selmanozleyen May 27, 2026
60e888f
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 28, 2026
bfbb3e8
be more compatible with main
selmanozleyen May 28, 2026
7e4ddec
Merge branch 'main' into feat/spatial_neighbours
selmanozleyen May 28, 2026
9dd330e
align more to main
selmanozleyen May 28, 2026
cf38fe3
Merge branch 'feat/spatial_neighbours'
selmanozleyen May 28, 2026
6c61ddc
save state
selmanozleyen Jun 1, 2026
e2266de
save state
selmanozleyen Jun 1, 2026
b2d25cb
refactor
selmanozleyen Jun 1, 2026
14d36b0
undo lock file
selmanozleyen Jun 1, 2026
86c9d76
Merge remote-tracking branch 'origin/main' into feat/experimental-fit…
selmanozleyen Jun 1, 2026
8e4bb93
undo changes with main
selmanozleyen Jun 1, 2026
9a489f2
resuse resolve_labels_array
selmanozleyen Jun 1, 2026
1955bed
Merge branch 'main' into feat/experimental-fit-core
selmanozleyen Jun 1, 2026
068f3e7
Merge branch 'main' into feat/experimental-fit-core
selmanozleyen Jun 1, 2026
759e09e
Update api.md
selmanozleyen Jun 1, 2026
ad200ea
Update api.md
selmanozleyen Jun 1, 2026
0e9b8c1
Merge branch 'main' into feat/experimental-fit-core
selmanozleyen Jun 8, 2026
c9eb7e4
rename file and refactor
selmanozleyen Jun 8, 2026
77f3e7d
move files
selmanozleyen Jun 8, 2026
57f9d21
fix imports
selmanozleyen Jun 8, 2026
5d69264
fixes
selmanozleyen Jun 8, 2026
6587efe
remove bloat code
selmanozleyen Jun 8, 2026
e45e247
typr hints
selmanozleyen Jun 8, 2026
beb1476
add tests
selmanozleyen Jun 8, 2026
5eeb95d
reduce bloat
selmanozleyen Jun 8, 2026
3c2ed02
reduce bloat
selmanozleyen Jun 8, 2026
1ea9996
remove useless file
selmanozleyen Jun 8, 2026
ee741ab
fix
selmanozleyen Jun 8, 2026
ba91e4f
undo rasterize changes
selmanozleyen Jun 8, 2026
1d65f4a
doc landmark
selmanozleyen Jun 8, 2026
a3da05a
remove bloat
selmanozleyen Jun 8, 2026
373ca02
docs
selmanozleyen Jun 8, 2026
aaf48c7
doc debloatr
selmanozleyen Jun 8, 2026
4387a61
expose result classes
selmanozleyen Jun 8, 2026
85df8fc
refactoring, testing, typing and bug fix on copy mode
selmanozleyen Jun 11, 2026
599186f
landmark is positional only now
selmanozleyen Jun 11, 2026
8cfc770
expose api
selmanozleyen Jun 11, 2026
96bc445
Merge branch 'main' into feat/experimental-fit-core
selmanozleyen Jun 11, 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
3 changes: 3 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ See the {doc}`extensibility guide </extensibility>` for how to implement a custo

experimental.tl.calculate_tiling_qc
experimental.tl.TilingQCParams
experimental.tl.align
experimental.tl.align_by_landmarks
experimental.tl.AlignResult
experimental.pl.tiling_qc
experimental.im.fit_stain_reference
experimental.im.apply_stain_normalization
Expand Down
4 changes: 4 additions & 0 deletions hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ extra-dependencies = ["diff-cover"]
matrix = [
{ deps = ["stable"], python = ["3.11", "3.12", "3.13"] },
{ deps = ["pre"], python = ["3.13"] },
{ deps = ["stable"], python = ["3.13"], extras = ["jax"] },
]
overrides.matrix.deps.env-vars = [
{ key = "UV_PRERELEASE", value = "allow", if = ["pre"] },
]
overrides.matrix.extras.features = [
{ value = "jax", if = ["jax"] },
]
# default commands (only `cov-report` is overridden)
scripts.run = "pytest{env:HATCH_TEST_ARGS:} -p no:cov {args}"
scripts.run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} -p no:cov {args}"
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ dependencies = [
"xarray>=2024.10",
"zarr>=3",
]
optional-dependencies.jax = [
"jax",
]
optional-dependencies.leiden = [
"leidenalg",
"spatialleiden>=0.4",
Expand Down
8 changes: 8 additions & 0 deletions src/squidpy/experimental/_methods/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""In-memory model-fitting core for experimental methods."""

from __future__ import annotations

from squidpy.experimental._methods._protocols import AlignLandmarksFn, AlignResult, AlignSamplesFn
from squidpy.experimental._methods._registry import Registry

__all__ = ["Registry", "AlignResult", "AlignSamplesFn", "AlignLandmarksFn"]
14 changes: 14 additions & 0 deletions src/squidpy/experimental/_methods/_families.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Estimator family registries."""

from __future__ import annotations

from squidpy.experimental._methods._protocols import AlignLandmarksFn, AlignSamplesFn
from squidpy.experimental._methods._registry import Registry

#: Sample-to-sample alignment estimators -- ref/query point clouds in, transform out.
#: Consumed by ``squidpy.experimental.tl.align``.
ALIGN_SAMPLES: Registry[AlignSamplesFn] = Registry("align_samples")

#: Closed-form landmark alignment estimators -- paired landmarks in, affine out.
#: Consumed by ``squidpy.experimental.tl.align_by_landmarks``.
ALIGN_LANDMARKS: Registry[AlignLandmarksFn] = Registry("align_landmarks")
59 changes: 59 additions & 0 deletions src/squidpy/experimental/_methods/_protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Structural contracts shared across the alignment estimator families.

These :class:`~typing.Protocol` types are what the public API and the registries
are typed against, so the orchestration layer never names a concrete estimator
result (e.g. ``StalignResult``). A new estimator only has to satisfy
:class:`AlignResult` -- a ``transform`` that maps points into the reference
frame -- to plug into :func:`squidpy.experimental.tl.align`.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable

import numpy.typing as npt

from squidpy._utils import NDArrayA

if TYPE_CHECKING:
from squidpy.experimental._methods.align_landmarks._landmark import AffineFitResult

__all__ = ["AlignResult", "AlignSamplesFn", "AlignLandmarksFn"]


@runtime_checkable
class AlignResult(Protocol):
"""A fitted alignment that maps ``(N, 2)`` ``(x, y)`` points into the reference frame.

This is the only thing the public ``align*`` functions require of an
estimator's result, so ``output_mode="object"`` is agnostic to the method
that produced it.
"""

def transform(self, points: npt.ArrayLike, /) -> NDArrayA:
"""Map an ``(N, 2)`` ``(x, y)`` array into the reference frame."""
...


class AlignSamplesFn(Protocol):
"""Calling convention for ``align_samples`` estimators.

Two point clouds in (passed by keyword as ``ref`` / ``query`` so the
direction can never be silently swapped), one :class:`AlignResult` out.
Solver-specific options arrive through ``**kwargs``.
"""

def __call__(self, ref: npt.ArrayLike, query: npt.ArrayLike, **kwargs: Any) -> AlignResult: ...


class AlignLandmarksFn(Protocol):
"""Calling convention for ``align_landmarks`` estimators: paired landmarks in, affine out."""

def __call__(
self,
landmarks_ref: npt.ArrayLike,
landmarks_query: npt.ArrayLike,
*,
source_cs: str | None = ...,
target_cs: str | None = ...,
) -> AffineFitResult: ...
73 changes: 73 additions & 0 deletions src/squidpy/experimental/_methods/_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""A flat registry mapping method names to fitting functions."""

from __future__ import annotations

import functools
import importlib.util
from collections.abc import Callable
from typing import Any, Generic, TypeVar

#: The calling convention a family's registry advertises (returned by :meth:`Registry.get`).
F = TypeVar("F", bound=Callable[..., Any])
#: The concrete function being registered. Kept separate from ``F`` so an estimator may
#: declare specific keyword parameters (e.g. ``config=``) without having to structurally
#: match the family's open-ended ``**kwargs`` calling convention.
RegisteredT = TypeVar("RegisteredT", bound=Callable[..., Any])


class Registry(Generic[F]):
"""A flat ``name -> function`` registry for one *family* of methods.

One :class:`Registry` is created per family (e.g. ``align``, ``impute``),
so keys are plain method names -- there is no ``(method, mode)`` compound
key, because the family already pins the rest.

The type parameter ``F`` is the family's calling convention (a callable
:class:`~typing.Protocol`); :meth:`get` returns it, so dispatch sites are
typed against the family contract rather than ``Callable[..., Any]``.
"""

def __init__(self, name: str) -> None:
self.name = name
self._registry: dict[str, F] = {}

def register(self, key: str, *, requires: tuple[str, ...] = ()) -> Callable[[RegisteredT], RegisteredT]:
"""Return a decorator registering a method/function under ``key``."""

def decorator(func: RegisteredT) -> RegisteredT:
if key in self._registry:
raise ValueError(f"Method {key!r} is already registered in the {self.name!r} registry.")

if requires:

@functools.wraps(func)
def wrapped(*args: Any, **kwargs: Any) -> Any:
missing = [pkg for pkg in requires if importlib.util.find_spec(pkg) is None]
if missing:
verb = "is" if len(missing) == 1 else "are"
names = ", ".join(repr(p) for p in missing)
extras = ",".join(missing)
raise ImportError(
f"Method {key!r} requires {names}, which {verb} not installed. "
f'Install with `pip install "squidpy[{extras}]"`.'
)
return func(*args, **kwargs)

self._registry[key] = wrapped # type: ignore[assignment]
return wrapped # type: ignore[return-value]
else:
self._registry[key] = func # type: ignore[assignment]
return func

return decorator

def get(self, key: str) -> F:
"""Return the function registered under ``key``."""
try:
return self._registry[key]
except KeyError:
raise ValueError(f"Unknown {self.name} method {key!r}. Available: {sorted(self._registry)}.") from None

def keys(self) -> tuple[str, ...]:
"""Return the registered method names."""
return tuple(self._registry)
17 changes: 17 additions & 0 deletions src/squidpy/experimental/_methods/align_landmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""``align_landmarks`` family: closed-form alignment from paired landmarks."""

from __future__ import annotations

from squidpy.experimental._methods._families import ALIGN_LANDMARKS
from squidpy.experimental._methods.align_landmarks._landmark import (
AffineFitResult,
fit_affine,
fit_similarity,
)

__all__ = [
"ALIGN_LANDMARKS",
"AffineFitResult",
"fit_affine",
"fit_similarity",
]
157 changes: 157 additions & 0 deletions src/squidpy/experimental/_methods/align_landmarks/_landmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""Closed-form landmark alignment estimators."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any

import numpy as np
import numpy.typing as npt

from squidpy._utils import NDArrayA
from squidpy.experimental._methods._families import ALIGN_LANDMARKS


@dataclass
class AffineFitResult:
"""A fitted ``(3, 3)`` homogeneous affine mapping query onto ref, in ``(x, y)``."""

matrix: np.ndarray
source_cs: str | None = None
target_cs: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)

def __post_init__(self) -> None:
if self.matrix.shape != (3, 3):
raise ValueError(f"Expected a (3, 3) homogeneous matrix, found shape {self.matrix.shape}.")

def transform(self, x: npt.ArrayLike) -> NDArrayA:
"""Apply the affine to an ``(N, 2)`` ``(x, y)`` coordinate array."""
coords = np.asarray(x, dtype=float)
if coords.ndim != 2 or coords.shape[1] != 2:
raise ValueError(f"Expected an (N, 2) coordinate array, found shape {coords.shape}.")
return coords @ self.matrix[:2, :2].T + self.matrix[:2, 2]


def _fit_landmark_relation(
landmarks_ref: np.ndarray,
landmarks_query: np.ndarray,
*,
method: str,
solve_fn: Callable[[np.ndarray, np.ndarray], np.ndarray],
source_cs: str | None = None,
target_cs: str | None = None,
) -> AffineFitResult:
ref = _validate_landmarks(landmarks_ref, name="landmarks_ref")
query = _validate_landmarks(landmarks_query, name="landmarks_query")
if ref.shape != query.shape:
raise ValueError(
f"`landmarks_ref` and `landmarks_query` must have the same shape; got {ref.shape} and {query.shape}."
)
if ref.shape[0] < 3:
raise ValueError(f"`{method}` needs at least 3 landmark pairs, got {ref.shape[0]}.")

matrix = solve_fn(ref, query)
return AffineFitResult(
matrix=matrix,
source_cs=source_cs,
target_cs=target_cs,
metadata={"method": method},
)


@ALIGN_LANDMARKS.register("similarity")
def fit_similarity(
landmarks_ref: np.ndarray,
landmarks_query: np.ndarray,
*,
source_cs: str | None = None,
target_cs: str | None = None,
) -> AffineFitResult:
"""4-DOF similarity fit (rotation + uniform scale + translation), via spatialdata.

Parameters
----------
landmarks_ref, landmarks_query
Pre-paired ``(N, 2)`` ``(x, y)`` landmark arrays (``N >= 3``).
source_cs, target_cs
Optional coordinate-system labels stamped onto the result for
traceability; they do not affect the fit.
"""
return _fit_landmark_relation(
landmarks_ref,
landmarks_query,
method="similarity",
solve_fn=_fit_similarity,
source_cs=source_cs,
target_cs=target_cs,
)


@ALIGN_LANDMARKS.register("affine")
def fit_affine(
landmarks_ref: np.ndarray,
landmarks_query: np.ndarray,
*,
source_cs: str | None = None,
target_cs: str | None = None,
) -> AffineFitResult:
"""6-DOF affine fit (rotation + non-uniform scale + shear + translation), via skimage.

Parameters
----------
landmarks_ref, landmarks_query
Pre-paired ``(N, 2)`` ``(x, y)`` landmark arrays (``N >= 3``).
source_cs, target_cs
Optional coordinate-system labels stamped onto the result for
traceability; they do not affect the fit.
"""
return _fit_landmark_relation(
landmarks_ref,
landmarks_query,
method="affine",
solve_fn=_fit_affine,
source_cs=source_cs,
target_cs=target_cs,
)


def _validate_landmarks(points: np.ndarray, *, name: str) -> np.ndarray:
arr = np.asarray(points, dtype=float)
if arr.ndim != 2 or arr.shape[1] != 2:
raise ValueError(f"`{name}` must be a sequence of (x, y) pairs, got shape {arr.shape}.")
if not np.all(np.isfinite(arr)):
raise ValueError(f"`{name}` must contain only finite values.")
return arr


def _fit_similarity(ref_xy: np.ndarray, query_xy: np.ndarray) -> np.ndarray:
"""4-DOF similarity fit, delegated to spatialdata."""
from spatialdata.models import PointsModel
from spatialdata.transformations import get_transformation_between_landmarks

refs_pts = PointsModel.parse(ref_xy)
moving_pts = PointsModel.parse(query_xy)
sd_transform = get_transformation_between_landmarks(refs_pts, moving_pts)
return _extract_affine_matrix(sd_transform)


def _fit_affine(ref_xy: np.ndarray, query_xy: np.ndarray) -> np.ndarray:
"""Full 6-DOF affine fit, delegated to skimage's least-squares estimator."""
from skimage.transform import estimate_transform

model_obj = estimate_transform("affine", src=query_xy, dst=ref_xy)
return np.asarray(model_obj.params)


def _extract_affine_matrix(sd_transform: object) -> np.ndarray:
"""Pull a ``(3, 3)`` homogeneous matrix out of a spatialdata transformation."""
from spatialdata.transformations import Affine as SDAffine
from spatialdata.transformations import Sequence as SDSequence

if isinstance(sd_transform, SDAffine):
return np.asarray(sd_transform.matrix)
if isinstance(sd_transform, SDSequence):
return np.asarray(sd_transform.to_affine_matrix(input_axes=("x", "y"), output_axes=("x", "y")))
raise TypeError(f"Unexpected transformation type from spatialdata: {type(sd_transform).__name__}.")
14 changes: 14 additions & 0 deletions src/squidpy/experimental/_methods/align_samples/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""``align_samples`` family: align two samples' point clouds (STalign).

Importing this package registers the family's estimators into
:data:`~squidpy.experimental._methods._families.ALIGN_SAMPLES`. It stays cheap --
JAX is pulled in lazily, only when an estimator's ``fit`` runs.
"""

from __future__ import annotations

from squidpy.experimental._methods._families import ALIGN_SAMPLES
from squidpy.experimental._methods.align_samples._stalign import fit_stalign
from squidpy.experimental._methods.align_samples._stalign_impl._tools import StalignResult

__all__ = ["ALIGN_SAMPLES", "fit_stalign", "StalignResult"]
Loading
Loading