Alignment API¶
Everything you need to bring two spatial modalities into one coordinate system,
importable from smint.alignment. The functions are grouped by the task they
serve, in the order you normally use them:
| Task | Use it when | Start with |
|---|---|---|
| Pre-registration | The datasets differ in scale, rotation or mirroring | build_pretransform |
| Centroid registration and refinement | Both modalities come from the same section (post-staining) | register_centroid_files |
| ST–SM registration | The modalities come from sequential sections | register_sm_to_st |
| Coordinate columns | You want to know which columns SMINT will read | detect_coordinate_columns |
To run a registration out of process, locally or on SLURM, see the Jobs API. For the files these functions read and write, see Data formats.
from smint.alignment import register_centroid_files
table, result = register_centroid_files(
"nuclei_centroids.csv", "xenium_cells.csv",
source_cols=("x_centroid", "y_centroid"),
output_path="nuclei_registered.csv",
)
print(result["tre_initial"][0], "->", result["tre_validation"][0])
print(table["registration_status"].value_counts())
Coordinate conventions
Everything here is plain (x, y), except inside the STalign wrappers:
STalign works in row–column (y, x) order, and
load_landmarks swaps the pair on load.
transform_points returns (x, y) in the
frame you choose with orientation.
Pre-registration¶
A coarse scale, rotation and flip, held as one 3×3 affine so it can be inspected,
saved with save_transform and replayed. Run it
before landmark picking or LDDMM when the two datasets start in different frames.
Operations are applied flip → rotate → scale about the source centroid, then the
source centroid is moved onto the reference centroid.
import pandas as pd
from smint.alignment import build_pretransform, apply_pretransform, describe_pretransform
sm_xy = pd.read_csv("sm_matrix.csv", usecols=["x", "y"]).to_numpy(float)
xenium_xy = pd.read_csv("xenium_cells.csv", usecols=["x_centroid", "y_centroid"]).to_numpy(float)
matrix = build_pretransform(sm_xy, xenium_xy, rotation=90)
sm_pre = apply_pretransform(sm_xy, matrix)
print(describe_pretransform(matrix)) # scale_x, scale_y, rotation_deg, reflects, ...
smint.alignment.build_pretransform
¶
build_pretransform(source_xy, reference_xy=None, scale=None, scale_mode='extent', preserve_aspect=True, rotation=0.0, flip_x=False, flip_y=False, align_centroids=True, center=None)
Build the coarse pre-registration affine for a source dataset.
Applies flip, then rotation, then scale, all about center (the source
centroid by default), and finally an optional translation putting the
source centroid on the reference centroid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
array - like
|
|
required |
reference_xy
|
array - like
|
|
None
|
scale
|
tuple of float
|
Explicit |
None
|
scale_mode
|
(extent, max, none)
|
Fitting mode when |
'extent'
|
preserve_aspect
|
bool
|
Isotropic scaling when fitting. |
True
|
rotation
|
float
|
Counter-clockwise degrees. |
0.0
|
flip_x
|
bool
|
Mirror horizontally / vertically. |
False
|
flip_y
|
bool
|
Mirror horizontally / vertically. |
False
|
align_centroids
|
bool
|
Translate so the source centroid lands on the reference centroid. |
True
|
center
|
sequence of float
|
Centre for flip/rotate/scale; defaults to the source centroid. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
3x3 homogeneous affine, applicable with
|
smint.alignment.apply_pretransform
¶
Apply a 3x3 affine to (N, 2) (x, y) coordinates.
Thin wrapper over apply_affine
so pre-registration reads naturally on its own.
smint.alignment.fit_scale_to_reference
¶
Scale factors bringing a source dataset onto a reference's coordinate system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
array - like
|
|
required |
reference_xy
|
array - like
|
|
required |
mode
|
(extent, max, none)
|
|
'extent'
|
preserve_aspect
|
bool
|
If True (default), use a single isotropic factor -- the smaller of the two -- so the tissue is not distorted. Anisotropic scaling can force bounding boxes to agree while making the shapes match worse, which then misleads the landmark step. |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
sx |
float
|
Scale factor along x. |
sy |
float
|
Scale factor along y (equal to |
Notes
Bounding boxes are sensitive to outliers: a handful of stray points inflates the span and shrinks the fitted factor. Check the result on a plot, and drop obvious debris before fitting.
smint.alignment.overlap_score
¶
Fraction of occupied reference bins that the source also occupies.
A cheap, correspondence-free way to tell whether a coarse pre-registration is getting better or worse -- useful for driving an interactive rotate/flip control, where a full registration per adjustment would be far too slow. Ranges 0 to 1; higher is better.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
array - like
|
|
required |
reference_xy
|
array - like
|
|
required |
pixel_size
|
float
|
Bin size. Too small and nothing overlaps; too large and everything does. Roughly the tissue feature scale works well. |
50.0
|
smint.alignment.describe_pretransform
¶
Decompose an affine into human-readable scale, rotation and shear.
Useful for sanity-checking a transform before committing to it -- a negative determinant means the transform includes a reflection, which is easy to introduce accidentally by combining a flip with a rotation.
Returns:
| Type | Description |
|---|---|
dict
|
|
smint.alignment.save_transform
¶
Save an affine transform as JSON, with its decomposition alongside.
A coarse transform that cannot be reproduced is of little use when revisiting a registration months later, so the matrix is stored together with the human-readable parameters it corresponds to and any extra metadata worth recording (source files, rotation/flip settings).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
ndarray
|
3x3 homogeneous affine. |
required |
path
|
str
|
Destination |
required |
**metadata
|
Additional JSON-serialisable fields to record. |
{}
|
Returns:
| Type | Description |
|---|---|
Path
|
The path written. |
smint.alignment.load_transform
¶
Load a 3x3 affine saved by save_transform.
Returns:
| Type | Description |
|---|---|
ndarray
|
The 3x3 matrix. Read the file directly if you also want the metadata. |
Matrix building blocks¶
smint.alignment.compose
¶
Compose 3x3 affine matrices, applied left to right.
compose(A, B) returns a matrix equivalent to applying A then B.
Note this is B @ A in matrix terms -- the argument order follows the
order operations happen, which is the one that matches how people describe
them.
smint.alignment.rotation_matrix
¶
3x3 affine rotating counter-clockwise by degrees about center.
Counter-clockwise in standard xy orientation. Note that images displayed with an inverted y axis will appear to turn the other way.
smint.alignment.flip_matrix
¶
3x3 affine mirroring about center.
flip_x mirrors left-right (negates x), flip_y mirrors top-bottom.
A flip is a scale by -1, so it is handled by the same machinery.
smint.alignment.scale_matrix
¶
3x3 affine scaling about center; sy defaults to sx.
Centroid registration and refinement¶
For a second modality acquired on the same section, where nuclei and cells
correspond one-to-one. register_centroids
fits the transform (multiround displacement voting by default) and reports
target registration error on held-out points;
refine_by_assignment then snaps
confidently matched nuclei exactly onto their cells.
register_centroid_files does both
from two CSV files and is what smint-alignment centroid runs.
flowchart LR
A[source centroids] --> B[register_centroids<br/>multiround voting]
T[target centroids] --> B
B -->|transform| C[x/y_transformed_smooth]
C --> D[refine_by_assignment<br/>snap, fill, assign]
T --> D
D --> E[x/y_transformed<br/>registration_status]
smint.alignment.register_centroid_files
¶
register_centroid_files(source_file, target_file, source_cols=None, target_cols=None, output_path=None, x_col='x_transformed', y_col='y_transformed', refine=True, refine_kwargs=None, **kwargs)
Register centroids from two CSV files and write the transformed source.
Every source row is transformed, not just the matched ones -- matching selects the pairs used to fit the transform, which then applies everywhere.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_file
|
str
|
CSVs holding the two centroid sets. |
required |
target_file
|
str
|
CSVs holding the two centroid sets. |
required |
source_cols
|
sequence of str
|
|
None
|
target_cols
|
sequence of str
|
|
None
|
output_path
|
str
|
Where to write the transformed source table. |
None
|
x_col
|
str
|
Output column names. |
'x_transformed'
|
y_col
|
str
|
Output column names. |
'x_transformed'
|
refine
|
bool
|
After registering, snap confidently matched points onto their targets
with |
True
|
refine_kwargs
|
dict
|
Forwarded to |
None
|
**kwargs
|
Forwarded to |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
result_df |
DataFrame
|
Source table with transformed coordinates appended. |
result |
dict
|
The |
smint.alignment.register_centroids
¶
register_centroids(source_xy, target_xy, method='multiround', max_distance=100.0, mutual=True, validation_fraction=0.25, residual_threshold=10.0, max_trials=1000, tps_regularization=None, n_control=1000, random_state=0, schedule=None, min_improvement=0.01)
Register two centroid sets acquired from the same physical section.
multiround (the default) re-matches and refits over several rounds;
the single-round methods match once by nearest neighbour, hold out a
fraction of the pairs, and fit the rest. All report TRE on held-out data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
ndarray
|
|
required |
target_xy
|
ndarray
|
|
required |
method
|
(multiround, affine, ransac, tps, ransac + tps)
|
Registration model: |
'multiround'
|
max_distance
|
float
|
Correspondence cutoff, in coordinate units. |
100.0
|
mutual
|
bool
|
Require reciprocal nearest neighbours. |
True
|
validation_fraction
|
float
|
Fraction held out from fitting: matched pairs for the single-round
methods, source points for |
0.25
|
residual_threshold
|
optional
|
RANSAC settings ( |
10.0
|
max_trials
|
optional
|
RANSAC settings ( |
10.0
|
tps_regularization
|
optional
|
TPS settings. Regularisation defaults to 1000 for |
None
|
n_control
|
optional
|
TPS settings. Regularisation defaults to 1000 for |
None
|
random_state
|
int
|
Seed for the split, RANSAC and control-point sampling. |
0
|
schedule
|
sequence of (max_distance, block_size, bin_size)
|
|
None
|
min_improvement
|
float
|
|
0.01
|
Returns:
| Type | Description |
|---|---|
dict
|
|
smint.alignment.refine_by_assignment
¶
refine_by_assignment(source_xy, target_xy, transform=None, snap_radius=10.0, neighbour_radius=100.0, consistency_tol=None, min_neighbours=5, k_fill=8, validation_fraction=0.1, random_state=0, passes=4, assign_remaining=True, assign_radius=None)
Snap registered source points exactly onto their partner targets.
A port of the lab's alignment_refinement.r. A smooth transform leaves
each nucleus a few microns from its cell -- partly because a nucleus
really is off-centre in its cell. This step instead assigns nuclei to
cells and moves each confidently assigned nucleus onto its partner:
- Pair transformed source points one-to-one with targets (mutual nearest
neighbours within
snap_radius). - Keep a pair only if its offset agrees, within
consistency_tol, with the median offset of the other pairs withinneighbour_radius-- a pairing with the wrong cell disagrees with its neighbourhood. - Snap kept pairs: the source point moves exactly onto its target.
- Every other point moves by the median offset of its
k_fillnearest snapped neighbours (withinneighbour_radius), or not at all. - Repeat: the moved, still-unsnapped points are paired again with the
still-unmatched targets, and kept if they agree with the snapped field
around them -- up to
passestimes, until nothing new snaps. - Assign what is left: the filled points and the still-unmatched targets
are paired by a minimum-total-distance one-to-one assignment within
assign_radius, and each assigned point moves onto its target (status'assigned'). These pairs skipped the consistency test, so they are less certain than'snapped'ones; filter on the status if that matters.
Snapped points coincide with their targets by construction, so their
residual says nothing about correctness. The check is validation: a
fraction of the kept pairs is left out of the offset field, their
positions are predicted from their neighbours alone (step 4), and the
distance from each prediction to its partner is reported.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
ndarray
|
|
required |
target_xy
|
ndarray
|
|
required |
transform
|
callable
|
Registration to apply first (e.g. |
None
|
snap_radius
|
float
|
Maximum distance for a pair. |
10.0
|
neighbour_radius
|
float
|
Neighbourhood for the consistency test and for filling in. |
100.0
|
consistency_tol
|
float
|
Allowed deviation from the neighbourhood's median offset. Default 0.4 x the target's median nearest-neighbour spacing. |
None
|
min_neighbours
|
int
|
Pairs with fewer neighbouring pairs than this cannot be checked and are not snapped. |
5
|
passes
|
int
|
Pairing passes (step 5); 1 disables re-pairing. |
4
|
assign_remaining
|
bool
|
Run step 6. |
True
|
assign_radius
|
float
|
Maximum distance for step 6. Default 0.75 x the target's median nearest-neighbour spacing. |
None
|
k_fill
|
int
|
Snapped neighbours whose median offset moves an unsnapped point. |
8
|
validation_fraction
|
float
|
Fraction of kept pairs used for the held-out check. |
0.1
|
Returns:
| Type | Description |
|---|---|
dict
|
|
smint.alignment.alignment_quality
¶
Correspondence-free alignment summary of moved_xy against target_xy.
Returns median_nn (median distance to the nearest target) and
mutual_within_5 / mutual_within_10: the fraction of points whose
nearest target has them as its nearest point too, within 5 / 10 units.
smint.alignment.vote_block_displacements
¶
vote_block_displacements(source_xy, target_xy, max_distance, block_size, bin_size=2.0, k=None, min_points=25)
Estimate the local source -> target displacement in square blocks.
Every source point votes for the displacements to its k nearest
targets within max_distance. Wrong candidates point in random
directions, but the true offset is shared by every point in the block, so
it is the peak of the (lightly smoothed) 2D vote histogram. This works even
when the misalignment exceeds the cell spacing, where nearest-neighbour
matching cannot -- provided the tissue is not a regular lattice, whose
periodicity makes every whole-spacing shift look equally good.
k defaults to the expected number of targets within max_distance
(from the target density, capped at 128), so the true partner is among
the candidates anywhere in that radius.
Returns:
| Name | Type | Description |
|---|---|---|
centres |
ndarray
|
|
displacements |
ndarray
|
|
Correspondence and error¶
smint.alignment.match_centroids
¶
Establish nearest-neighbour correspondences between two centroid sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
ndarray
|
|
required |
target_xy
|
ndarray
|
|
required |
max_distance
|
float
|
Maximum distance for a pair to be considered corresponding, in the coordinate units (microns for Xenium). |
100.0
|
mutual
|
bool
|
If True (default), keep only pairs that are each other's nearest neighbour. Plain one-directional matching lets many source points collapse onto a single popular target, which biases the fit and inflates the apparent match count. |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
source_idx |
ndarray
|
Source index of each matched pair. |
target_idx |
ndarray
|
Target index of each matched pair. |
distances |
ndarray
|
Distance for each matched pair. |
smint.alignment.split_pairs
¶
Split matched pairs into fit and held-out validation index sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_pairs
|
int
|
Number of matched pairs. |
required |
validation_fraction
|
float
|
Fraction held out, in |
0.25
|
random_state
|
int
|
Seed for the permutation. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
fit_idx |
ndarray
|
Indices of the pairs to fit. |
val_idx |
ndarray
|
Indices of the held-out pairs. |
smint.alignment.target_registration_error
¶
Target Registration Error between paired point sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
ndarray
|
|
required |
target_xy
|
ndarray
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
mean |
float
|
Mean per-pair distance. |
std |
float
|
Standard deviation of the per-pair distance. |
per_pair |
ndarray
|
The per-pair distances. |
Notes
On the points used for fitting, TRE is circular: correspondences come from
nearest neighbours, so a flexible enough transform drives it towards zero
whether or not the pairs are right. Prefer the held-out value reported by
register_centroids.
Transform models¶
smint.alignment.estimate_affine
¶
Least-squares affine transform mapping source onto target.
Returns:
| Type | Description |
|---|---|
ndarray
|
3x3 homogeneous matrix; identity if there are too few pairs. |
smint.alignment.estimate_affine_ransac
¶
estimate_affine_ransac(source_xy, target_xy, residual_threshold=10.0, max_trials=1000, min_samples=3, random_state=0)
RANSAC-robust affine transform, rejecting outlier correspondences.
Nearest-neighbour matching inevitably produces some wrong pairs -- an unmatched cell picks up whichever neighbour is closest. Least squares lets those wrong pairs drag the fit; RANSAC finds the transform supported by the largest consistent subset and reports the rest as outliers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
ndarray
|
|
required |
target_xy
|
ndarray
|
|
required |
residual_threshold
|
float
|
Max residual (in coordinate units) for a pair to count as an inlier. |
10.0
|
max_trials
|
int
|
RANSAC iterations. |
1000
|
min_samples
|
int
|
Points per RANSAC sample; 3 is the minimum for an affine. |
3
|
random_state
|
int
|
Seed, for reproducibility. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
matrix |
ndarray
|
3x3 homogeneous affine. |
inliers |
ndarray
|
Boolean mask of inlier pairs. |
smint.alignment.apply_affine
¶
Apply a 3x3 homogeneous affine matrix to (N, 2) points.
smint.alignment.fit_tps
¶
Fit a thin-plate spline warping source control points onto targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_xy
|
ndarray
|
|
required |
target_xy
|
ndarray
|
|
required |
regularization
|
float
|
Ridge term added to the TPS system. |
1.0
|
n_control
|
int
|
Number of control points to subsample. TPS solves an
|
1000
|
random_state
|
int
|
Seed for control-point subsampling. |
0
|
Returns:
| Type | Description |
|---|---|
dict or None
|
Model with keys |
smint.alignment.apply_tps
¶
Warp points with a fitted TPS model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points_xy
|
ndarray
|
|
required |
model
|
dict or None
|
Output of |
required |
chunk_size
|
int
|
Points processed per block. The kernel is |
50000
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Warped |
ST–SM registration (sequential sections)¶
For spatial metabolomics (SM) and spatial transcriptomics (ST) from different sections. STalign's LDDMM does the warping, initialised from landmarks you pick by hand, so the workflow has two phases around a manual step:
flowchart LR
A[ST CSV + SM matrix] --> B[prepare_landmark_inputs<br/>rasterise, write .npz]
B --> C[pick landmarks<br/>napari plugin or point annotator]
C --> D[_st_points.npy<br/>_sm_points.npy]
D --> E[register_sm_to_st<br/>landmark-initialised LDDMM]
A --> E
E --> F[sm_transformed.csv<br/>x/y_transformed]
These functions import STalign, which needs numpy < 2 and torch. Run them in a
worker environment (stalign_available tells
you whether the current one qualifies), or submit them with the Jobs API.
smint.alignment.prepare_landmark_inputs
¶
prepare_landmark_inputs(st_file, sm_file, output_prefix, dx=30.0, st_x_col=None, st_y_col=None, sm_kwargs=None)
Phase 1: rasterize an ST/SM pair and write .npz files for annotation.
After this returns, run point_annotator.py on each .npz and pick the
same anatomical landmarks, in the same order, in both. The annotator writes
<prefix>_st_points.npy / <prefix>_sm_points.npy next to them, which
register_sm_to_st then consumes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
st_file
|
str
|
ST annotation CSV. |
required |
sm_file
|
str
|
SM matrix CSV. |
required |
output_prefix
|
str
|
Path prefix. Writes |
required |
dx
|
float
|
Rasterization pixel size, in microns. Must match between the pair. |
30.0
|
st_x_col
|
str
|
ST coordinate columns. |
None
|
st_y_col
|
str
|
ST coordinate columns. |
None
|
sm_kwargs
|
dict
|
Forwarded to |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Paths written and the rasterized arrays, keyed |
smint.alignment.register_sm_to_st
¶
register_sm_to_st(st_file, sm_file, st_points_file, sm_points_file, output_path=None, dx=30.0, niter=1000, epV=200.0, device=None, orientation='dataset', st_x_col=None, st_y_col=None, sm_kwargs=None, lddmm_params=None)
Phase 2: register an SM section onto an ST section using manual landmarks.
Rasterizes both modalities, runs landmark-initialised LDDMM with SM as source and ST as target, transforms every SM pixel, and returns the SM table with transformed coordinates appended.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
st_file
|
str
|
Input tables for the same physical section. |
required |
sm_file
|
str
|
Input tables for the same physical section. |
required |
st_points_file
|
str
|
|
required |
sm_points_file
|
str
|
|
required |
output_path
|
str
|
CSV destination for the transformed SM table. |
None
|
dx
|
float
|
Rasterization pixel size; must match what was annotated. |
30.0
|
niter
|
optional
|
LDDMM optimisation settings. |
1000
|
epV
|
optional
|
LDDMM optimisation settings. |
1000
|
device
|
str
|
Torch device; defaults to CUDA when available. |
None
|
orientation
|
(dataset, stalign)
|
Output coordinate frame. |
'dataset'
|
st_x_col
|
str
|
Which ST columns to register. Auto-detected when omitted; pass them explicitly when the table carries several coordinate pairs, since the wrong choice misregisters silently rather than raising. |
None
|
st_y_col
|
str
|
Which ST columns to register. Auto-detected when omitted; pass them explicitly when the table carries several coordinate pairs, since the wrong choice misregisters silently rather than raising. |
None
|
sm_kwargs
|
dict
|
Forwarded to |
None
|
lddmm_params
|
dict
|
Extra parameters forwarded to |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
SM data with |
smint.alignment.stalign_available
¶
Return True if STalign can actually be imported and used.
Reading inputs¶
smint.alignment.read_sm_matrix
¶
read_sm_matrix(mtx_file, scale_xy=10.0, rotate_left_90=False, keep_positive=True, origin=None, usecols=None, x_col=None, y_col=None, verbose=True)
Read a spatial metabolomics matrix file and normalise it for registration.
Applies, in order: delimiter sniffing, x/y name standardisation,
X-prefixing of numeric m/z headers, coordinate scaling, optional 90 deg
CCW rotation, and an optional shift to keep coordinates non-negative.
The SM grid is stored in pixel units; scale_xy converts it to the same
micron scale as the ST centroids, so registration starts near the right
magnitude. For the MALDI data SMINT was developed on this is 10.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mtx_file
|
str
|
Path to the |
required |
scale_xy
|
float
|
Multiplier applied to |
10.0
|
rotate_left_90
|
bool
|
If True, apply |
False
|
keep_positive
|
bool
|
If True, translate so coordinates are >= |
True
|
origin
|
tuple of float
|
Target lower bound |
None
|
usecols
|
sequence of str
|
Restrict to these columns. Passing |
None
|
x_col
|
str
|
Which columns hold the coordinates. By default the columns literally
named |
None
|
y_col
|
str
|
Which columns hold the coordinates. By default the columns literally
named |
None
|
verbose
|
bool
|
Log progress. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Full frame with |
Notes
This returns the whole DataFrame rather than a (coords, data, mz)
tuple; use sm_coordinates to pull the coordinate array out.
smint.alignment.read_st_annotations
¶
Read an ST annotation table (e.g. sample2_xenium_z2.csv).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
st_file
|
str
|
Path to the ST CSV. |
required |
x_col
|
str
|
Coordinate column names. Auto-detected when omitted; see
|
None
|
y_col
|
str
|
Coordinate column names. Auto-detected when omitted; see
|
None
|
verbose
|
bool
|
Log progress. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The frame, with the coordinate columns guaranteed present and numeric. |
smint.alignment.resolve_st_columns
¶
Decide which ST columns hold the coordinates to register.
Explicit names win. When omitted, columns are auto-detected in the priority
order in smint.alignment.columns, and the choice is logged.
Be deliberate here. ST tables often carry several plausible coordinate
pairs from successive processing stages -- x_centroid alongside
x_new and x_new_add, say -- and picking the wrong one produces a
silently misregistered result rather than an error. Pass the names
explicitly whenever more than one pair exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
st_file
|
str
|
Path to the ST CSV. |
required |
x_col
|
str
|
Explicit column names; auto-detected when omitted. |
None
|
y_col
|
str
|
Explicit column names; auto-detected when omitted. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
x_col |
str
|
Column holding x. |
y_col |
str
|
Column holding y. |
smint.alignment.list_columns
¶
Column names of a CSV, without reading its contents.
Lets a caller offer a coordinate-column picker before committing to reading a 450 MB matrix.
smint.alignment.sm_coordinates
¶
Return the (N, 2) [x, y] array from an SM frame.
smint.alignment.st_coordinates
¶
Return the (N, 2) [x, y] array from an ST frame.
Columns are auto-detected when omitted.
smint.alignment.find_sm_matrix_files
¶
Locate SM matrix files for the given layers.
Looks under base_path/layer_{n}/matrix/*information_matrix_tissue_only_full_with_xy.csv.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_path
|
str
|
Root of the |
required |
layers
|
sequence of int
|
Layer numbers to search. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
|
Rasterising and landmarks¶
smint.alignment.rasterize_coordinates
¶
Rasterize point coordinates into a density image via STalign.rasterize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array - like
|
Point coordinates. |
required |
y
|
array - like
|
Point coordinates. |
required |
dx
|
float
|
Pixel size, in the same units as |
30.0
|
draw
|
int
|
Passed to STalign; |
0
|
**kwargs
|
Forwarded to |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
X |
ndarray
|
Pixel locations along x. |
Y |
ndarray
|
Pixel locations along y. |
I |
ndarray
|
Rasterized image, channels first. |
fig |
matplotlib figure or None
|
The drawn figure when |
Notes
STalign.rasterize returns a 3-tuple when draw is falsy and a
4-tuple when it is truthy. This wrapper always returns 4 values so callers
can unpack unconditionally.
smint.alignment.save_rasterized
¶
Save a rasterized image as .npz for the manual point annotator.
point_annotator.py expects arrays named x, y and I, and
writes its landmarks alongside as <stem>_points.npy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str
|
Destination path; |
required |
X
|
ndarray
|
Outputs of |
required |
Y
|
ndarray
|
Outputs of |
required |
I
|
ndarray
|
Outputs of |
required |
Returns:
| Type | Description |
|---|---|
str
|
The path written. |
smint.alignment.load_landmarks
¶
Load landmarks written by point_annotator.py into row-col order.
The annotator stores a {label: [(x, y)]} dict. STalign's
L_T_from_points and LDDMM both expect points in row-col
(y, x) order, so the pair is swapped on load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points_file
|
str
|
Path to a |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the landmark file is missing -- usually meaning the manual annotation step has not been run for this section yet. |
smint.alignment.check_landmark_pair
¶
Validate a source/target landmark pair before running LDDMM.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the counts differ or there are fewer than 3 points (an affine fit needs 3), which otherwise surfaces as an opaque failure inside LDDMM. |
smint.alignment.affine_from_landmarks
¶
Compute an affine matrix from landmark correspondences.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points_source
|
ndarray
|
|
required |
points_target
|
ndarray
|
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
3x3 affine matrix. |
Notes
run_lddmm_alignment derives its own initialisation from
points_source/points_target internally, so this is not needed
for the standard workflow. It is kept for inspecting or seeding a
landmark-only (affine) registration.
LDDMM and transforming points¶
smint.alignment.run_lddmm_alignment
¶
run_lddmm_alignment(source_grid, source_image, target_grid, target_image, points_source=None, points_target=None, niter=1000, epV=200.0, device=None, init_from_landmarks=True, **params)
Run STalign LDDMM to register a source image onto a target image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_grid
|
sequence
|
Grids as |
required |
target_grid
|
sequence
|
Grids as |
required |
source_image
|
ndarray
|
Rasterized images from |
required |
target_image
|
ndarray
|
Rasterized images from |
required |
points_source
|
ndarray
|
Landmark correspondences in row-col order. STalign's LDDMM uses them
only as a matching term and otherwise starts from the identity (it
also skips its own pre-alignment when landmarks are given), so on its
own a large offset barely moves. With |
None
|
points_target
|
ndarray
|
Landmark correspondences in row-col order. STalign's LDDMM uses them
only as a matching term and otherwise starts from the identity (it
also skips its own pre-alignment when landmarks are given), so on its
own a large offset barely moves. With |
None
|
niter
|
int
|
Optimisation iterations. |
1000
|
epV
|
float
|
Velocity-field regularisation weight. |
200.0
|
device
|
str
|
Torch device; defaults to CUDA when available, else CPU. |
None
|
init_from_landmarks
|
bool
|
Initialise the affine ( |
True
|
**params
|
Forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
dict
|
STalign LDDMM output, including |
smint.alignment.transform_points
¶
Apply an LDDMM result to source point coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lddmm_output
|
dict
|
Output of |
required |
x
|
array - like
|
Source coordinates, as plain x and y vectors. |
required |
y
|
array - like
|
Source coordinates, as plain x and y vectors. |
required |
orientation
|
(dataset, stalign)
|
Which frame to return. STalign returns points in row-column order;
|
'dataset'
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
smint.alignment.save_transformed_data
¶
save_transformed_data(source_data, transformed_coords, output_path=None, x_col=TRANSFORMED_X_COL, y_col=TRANSFORMED_Y_COL, orientation=None)
Attach transformed coordinates to the source table and optionally save it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_data
|
DataFrame
|
The SM frame the coordinates were derived from. |
required |
transformed_coords
|
ndarray
|
|
required |
output_path
|
str
|
CSV destination. Parent directories are created. |
None
|
x_col
|
str
|
Output column names. |
TRANSFORMED_X_COL
|
y_col
|
str
|
Output column names. |
TRANSFORMED_X_COL
|
orientation
|
str
|
Recorded in |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Copy of |
Coordinate columns¶
SMINT tables name their coordinates differently depending on where they came from. These helpers resolve them in one place, trying the candidates below in order (case-insensitive). See Data formats for where auto-detection applies and where you must name columns yourself.
smint.alignment.columns.X_CANDIDATES
module-attribute
¶
smint.alignment.columns.Y_CANDIDATES
module-attribute
¶
smint.alignment.columns.detect_coordinate_columns
¶
Guess the (x, y) column names in a table.
Candidates are tried in priority order, so a table carrying both
x_final and x resolves to the more specific one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
columns
|
sequence of str
|
The table's column names. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
x_col |
str or None
|
The x column, or None if nothing matched. |
y_col |
str or None
|
The y column, or None if nothing matched. |
smint.alignment.columns.require_coordinate_columns
¶
Detect coordinate columns, raising a useful error when they are missing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
columns
|
sequence of str
|
The table's column names. |
required |
source
|
str
|
Name of the table, used in the error message. |
'table'
|
Returns:
| Name | Type | Description |
|---|---|---|
x_col |
str
|
The x column. |
y_col |
str
|
The y column. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Naming the source and listing what was actually available, since the usual cause is pointing at the wrong file. |
Next steps¶
- Jobs API: run any of these registrations as a local or SLURM job.
- Choosing a registration method: same-section or sequential?
- Command line: the same functions from
smint-alignment.