Changes

2.0.0 (2026-10-14)

This version keeps every function and argument of version 1.0, though some defaults changed (listed under New functions and options). Alignments run much faster, several bugs are fixed, and LDDMM and LDDMM_3D_to_slice choose more of their settings from the data (the new defaults below), so results differ from earlier ones. legacy_defaults=True turns the new defaults off.

Faster

  • LDDMM and LDDMM_3D_to_slice have a new optional C++ engine, compiled by pip when a C++17 compiler is available. Without one, they use a new pure torch implementation. Both compute the same iterations up to rounding. Time of the tutorials’ alignments (LDDMM or LDDMM_3D_to_slice) with their own data, parameters and numbers of iterations, on a Mac / on Linux (end to end adds rasterizing the cells and transforming them):

    tutorial

    iterations

    version 1.0

    C++ engine

    torch engine

    C++ engine, legacy_defaults=True

    MERFISH to MERFISH

    10,000

    9.2 min / 12.1 min

    10 s / 45 s

    1.5 min / 1.7 min

    7.9 s / 42 s

    MERFISH, simulated deformation

    1,000

    2.3 min / 3.8 min

    4.2 s / 14 s

    19 s / 31 s

    2.1 s / 11 s

    Xenium to Xenium

    300

    10 s / 15 s

    0.18 s / 0.65 s

    1.8 s / 2.3 s

    0.17 s / 0.64 s

    Xenium to H&E image

    2,000

    1.3 min / 1.9 min

    1.5 s / 6.5 s

    14 s / 24 s

    1.4 s / 6.3 s

    Heart sections

    1,000

    2.9 min / 3.8 min

    7.2 s / 31 s

    29 s / 1.1 min

    3.6 s / 26 s

    MERFISH to Visium, with landmarks

    200

    4.2 min / 12.5 min

    4.4 s / 30 s

    35 s / 2.0 min

    4.3 s / 29 s

    MERFISH to Allen atlas (3D)

    2,000

    21.6 min / 48.4 min

    34 s / 3.0 min

    1.8 min / 8.1 min

    28 s / 2.8 min

    STARmap to Allen atlas (3D)

    800

    16.3 min / 32.8 min

    30 s / 2.8 min

    2.0 min / 7.6 min

    24 s / 2.7 min

    Visium to Visium, affine only

    1,000

    16 s / 30 s

    0.92 s / 1.1 s

    1.3 s / 1.9 s

    0.06 s / 0.03 s

    Xenium to STARmap

    4,000

    2.4 min / 3.3 min

    6.2 s / 14 s

    25 s / 31 s

    2.7 s / 9.3 s

    all 10 tutorials

    60.6 min / 119.3 min

    1.6 min / 8.2 min

    7.5 min / 22.0 min

    1.2 min / 7.5 min

    all 10, end to end

    61.1 min / 120.2 min

    1.7 min / 8.2 min

    7.5 min / 22.1 min

    1.3 min / 7.6 min

    Measured on an Apple M1 Ultra (macOS, 16 threads) and an AMD Ryzen 5 5560U (Linux, 8 threads, nice -n 19), one alignment at a time, on the CPU in float64 without figures, with replicas of the tutorials (this version: best of 2 runs); version 1.0 ran with 2 threads or with 16 (Linux: 8), whichever was faster, for 300 iterations (100 for MERFISH to Visium and the 3D tutorials, all of them for Xenium to Xenium and Visium to Visium), extrapolated to the tutorial’s number from its steady time per iteration, which includes redrawing its figures every 10 iterations as it always does (three full-length runs were within 2.5% of the extrapolation). In total, the C++ engine runs the ten fits 37 times faster than version 1.0 on the Mac and 15 times faster on Linux (17–58 and 7–27 times per tutorial), and the torch engine 8 and 5 times faster. The tutorials pass their own numbers of iterations, so these times do not include the shorter default of 2000 iterations in 2D (below). The other new defaults add up to 6 s per fit on the Mac and 12 s on Linux, mostly for the affine pre-fit; without them (legacy_defaults=True) the C++ engine is 49 and 16 times faster in total. On the Linux machine the C++ engine gains less: with one thread it is 1.6–2.1 times slower than on the Mac, and 8 threads make it 3.4–4.2 times faster than one thread, where 16 threads on the Mac make it 11–13 times faster (MERFISH to MERFISH and MERFISH to Allen atlas, legacy_defaults=True).

  • LDDMM and LDDMM_3D_to_slice draw their figures once, after the last iteration, instead of every 10 iterations (draw=True restores the old behavior).

  • rasterize is about 60 times faster on the Mac and 45 times on Linux (85,958 cells: 1.8 s to 31 ms and 3.3 s to 75 ms; 2 million cells: 45 s to 0.72 s and 78 s to 1.6 s), and rasterize_with_signal as much with one signal column and about 20 times with 16 columns. Both give the same images up to rounding (1e-12 relative). Peak memory is bounded.

  • The transform functions split large point sets into chunks and use several threads. Transforming all cells of a tutorial takes at most 0.07 s on the Mac and 0.3 s on Linux; analyze3Dalign takes 0.07 s instead of 1.9 s in the STARmap tutorial (Linux: 0.27 s instead of 5.4 s). In 2D the exact inverse (below) makes source-to-target transforms slower than the previous approximation, though still fast: 0.025 s instead of 0.005 s for the cells of the MERFISH tutorial (Mac).

More accurate default alignments

LDDMM and LDDMM_3D_to_slice now start with a coarse affine pre-alignment, and choose the regularization weight sigmaR and (in 2D) the step sizes from the images. The accuracy comparisons in this section are with legacy_defaults=True (below): this version, with the corrections under Changes that affect results, but with the previous defaults, which are those of version 1.0 except sigmaR=2e6 in 3D. On the held-out problems of the evaluation suite (synthetic sections with a known deformation, never used for tuning, all run on the same machine type and each paired with its fit under legacy_defaults=True), the geometric mean error of the alignment fell from 27.5 to 11.9 µm in 2D (ratio 0.43, ±2 SE 0.27–0.70; better on 21 of 27 problems, worse on 2; median ratio 0.87) and from 123.9 to 25.8 µm in 3D (ratio 0.21, 0.08–0.51; better on 4 of 4). On 15 further held-out problems with other combinations of deformation size, realism and pixel size, the ratios were 0.37 in 2D (0.18–0.76; 10 better, 2 worse of 13) and 0.28 in 3D (2 problems; not significant). On the suite’s main set, whose problems were also used to choose the new settings, they were 0.36 in 2D (0.21–0.63) and 0.13 in 3D (0.08–0.21). The 2D fits took 0.43–0.52 times as long as with legacy_defaults=True (about half), mostly because of the new default of 2000 iterations. The large gains come from alignments that failed with the old defaults: rotated sections, slices tilted by a few degrees, and the synthetic Xenium sections, on which the old step sizes were unstable. Version 2.0 also matches landmarks under the fitted map, and adds align_cells for two sets of cells and a mixture model for targets with missing tissue (an option of LDDMM, on by default in align_cells).

  • Affine pre-fit (prefit='auto'). LDDMM’s gradient descent does not correct a large initial misalignment: it failed on test sections rotated by 20–30° and never corrected atlas planes tilted by a few degrees. Now, before LDDMM, many starts are fitted as affine maps on coarse (about 80 pixels across) images, and the best one is refined and handed to LDDMM. In 2D the starts are the initial affine rotated in 12 steps of 30° about the target’s tissue centroid; in 3D the initial plane tilted by -6, 0 and 6° about both in-plane axes and shifted by -300, 0 and 300 µm along its normal. Ties (energies within 1%) go to the start closest to the initial affine, so nearly symmetric tissue keeps its orientation. In 2D the pre-fit is skipped when landmarks are given, and in both from a nonzero initial velocity v (a warm start); prefit=True or False forces it. A target without tissue (a constant image) skips it with a warning. It takes about 1–4 s per 2D tutorial and 5 s per 3D tutorial on the Mac (4 or 16 threads), and 1–4 s and 8.5–10 s on Linux (8 threads), is deterministic, and is reported in the result’s 'prefit' entry.

  • sigmaR from a displacement scale (sigmaR=None, new sigma_disp=94.03). A fixed sigmaR means a different deformation prior at every pixel size, intensity scale and smoothness a: on the evaluation suite the best sigmaR moved with the pixel size. The default sigmaR is now sigma_disp · sqrt(C π) · a^(d/2) · sqrt(nt) · (dx / 30 µm) · sigmaM / s_J (C = 12 in 2D, 64 in 3D; dx the target pixel size in source units; s_J the typical tissue intensity of the target), so the prior allows displacements of about sigma_disp µm whatever the images. At the defaults, a 30 µm target with s_J = sigmaM gets about the previous 2D default 5e5. It assumes coordinates in µm. A given sigmaR is used as is. In 3D it replaces the previous default sigmaR=1e8, which regularizes much less with the corrected 3D regularizer (below): the 3D tutorials get about 1.1e7 (MERFISH) and 1.85e6 (STARmap).

  • 2D step sizes from the data (epL, epT, epV = None). The previous defaults are scaled by sigmaM² · 1.1 / G, where G estimates the curvature of the image term from the target’s variance and the source’s gradients after the pre-fit. Fixed steps were unstable on some images and needlessly small on others. G sums over the target’s pixels, but the largest stable step of the velocity field does not grow with the tissue area, so on small images these steps alone were far too large (a 1.2 mm image fitted to itself diverged, its cells moving by over 100 µm), and they did not account for landmarks, a small sigma_disp or, for the affine steps, coordinates far from the origin. Each default step is therefore lowered where needed, from the curvature of the energy at the starting affine (the image term under the same assumptions as G, the landmarks and the regularizer): epV to at most half the step at which gradient descent diverges, 1 / (λ + DV / sigmaR²) with λ the largest eigenvalue of the kernel-smoothed curvature in the velocity field, and epL and epT by a common factor to at most a quarter of theirs. λ is found by subspace iteration, whose time grows with the number of points of the velocity grid: for a 17 × 21 mm target with 25 µm pixels it took 1.9 s at the default a=500 and 15 s at a=200 (2 threads). On 351 test fits (crops and rescales of the MERFISH, Xenium and heart sections to 1–16 mm with 15–60 µm pixels, fitted to themselves or to a rotated, deformed copy, with and without landmarks, at sigma_disp from 10 to 300, and with landmarks 20–50 mm from the origin), 142 were unstable with the steps scaled by G alone and none are with the caps; sections fitted to themselves stay within 0.04 µm of the identity (within 0.74 µm over 99% of the tissue when the pre-fit, accurate to a few µm, starts them). On the evaluation suite only the steps of the sparse heart sections are lowered (epV by 0.50–0.68; the uncapped ones were at 73–100% of the divergence limit), plus the affine steps of the heart and Xenium-to-H&E tutorials: the serial heart sections moved by less than 0.1 µm, the error of two partial ones fell from 105.8 to 84.1 µm and from 82.8 to 67.5 µm (the first started at 42.5 µm, so it fails with or without the cap), and the tutorials’ results are unchanged except Xenium-to-H&E (landmark error 111.7 to 108.4 µm, within its sensitivity to rounding). align_cells without the mixture model (mixture=None), with its 500 iterations per level, converges more slowly on the serial heart sections (up to 9% more error; the same error with 1500 iterations per level) and improves on the partial ones. Given values are used as is.

  • 3D step sizes. LDDMM_3D_to_slice keeps its steps (epL=1e-6, epT=1e1), but its epV default is now None: 1e3 as before, lowered where the regularizer alone would make gradient descent diverge (to epV · DV / sigmaR² ≤ 1, DV the volume of a velocity grid cell). That happens only for a very small sigma_disp with sigmaM well below the target’s intensity. A given value is used as is.

  • 2000 iterations by default in 2D (niter=None, previously 5000). With the new steps, stopping at 2000 instead of 5000 iterations changed the error by 0.4% (±2 SE −0.2 to +1.0%) and halved the time of the 2D fits.

  • Landmarks are matched under the fitted map (LDDMM with pointsI and pointsJ). The landmark term moved pointsI by forward Euler steps, which invert the fitted target-to-source map only approximately, so the fit matched landmark positions that differ from those transform_points_source_to_target gives after the fit. Each time step is now solved exactly (fixed-point iteration and a Newton step; the gradient follows from the implicit function theorem), in both engines. With the other new defaults, on the MERFISH-to-Visium tutorial problem the error of its 12 landmarks fell from 75.4 to 70.1 µm, and when 4 of them were held out of the fit, their error fell from 272.8 to 268.1 µm. The Xenium-to-Xenium tutorial is unchanged (28.5 µm), and on Xenium-to-H&E the change (114.3 to 111.7 µm) is within that problem’s sensitivity to rounding. A RuntimeWarning reports steps that did not converge, and steps whose Newton matrix is singular, which take the forward Euler step instead.

  • Caveat: targets with missing tissue. Where the target lacks tissue that the source has (partial sections), the new defaults did not help: of 8 such test problems, 3 improved on legacy_defaults=True (by 14–78%) and 5 got worse (by 9–29%). Check such alignments, and compare with legacy_defaults=True or prefit=False, or try mixture='em' (below), which align_cells uses by default.

  • legacy_defaults=True (keyword-only) restores the previous defaults: no pre-fit (unless prefit=True), forward Euler steps in the landmark term, niter=5000, epL=2e-8, epT=2e-1, epV=2e3 and sigmaR=5e5 in 2D, and sigmaR=2e6 and epV=1e3 without the cap in 3D. The results are then bit for bit those without these changes (the corrections below still apply).

  • The result has two new entries: 'prefit' (whether the pre-fit ran, the affine 'A' the fit started from, which is the initial affine when the pre-fit did not run, and if it ran the energy of every start) and 'settings' (the niter, sigmaR, epL, epT and epV the fit used). Passing them back, with A=out['prefit']['A'] in place of L, T or A and prefit=False (and legacy_defaults=True if the fit used it), repeats the fit exactly, for example to align several sections with the same settings.

  • New function align_cells(xI, yI, xJ, yJ) aligns two sets of cells in one call. It rasterizes both at 15 µm with a Gaussian kernel of 30 µm standard deviation and runs LDDMM coarse to fine on mean-pooled copies of these images (500 iterations at 60 µm pixels, then 500 at 30 µm), each level starting from the previous level’s affine and velocity field, with LDDMM’s defaults at each level except for the mixture model (mixture='em', below), which align_cells uses by default and mixture=None turns off. It returns LDDMM’s result, so the transform functions work on it as usual, plus the two rasters. On the 27 held-out synthetic cell-to-cell problems of our evaluation suite (known deformations of real MERFISH, Xenium and heart cell sets), it had less than half the error of LDDMM with the new defaults on the suite’s own rasters (15, 30 or 60 µm pixels with a kernel tied to the pixel size): geometric mean 4.6 instead of 11.9 µm (ratio 0.39, ±2 SE 0.29–0.51; better on 24 problems, worse on 3), in less than half the fitting time, and 0.17 times the error of LDDMM with legacy_defaults=True (27.5 µm; 0.10–0.27). On the suite’s main set and on the 13 further held-out problems, its ratios to LDDMM were 0.38 (0.30–0.50) and 0.34 (0.21–0.55). It was less accurate than LDDMM only on problems whose rasters in the suite already had 15 µm pixels: on 9 of these 11 problems, by 5–14% (10–14% on the 3 held-out ones; 3.3 instead of 3.0 µm, for example). On the other 2, whose rasters had a kernel twice as wide, it had 0.42–0.44 times the error of LDDMM. Images from other sources (H&E, Visium, an atlas) still go to LDDMM directly.

  • align_cells uses the mixture model by default, because it fixes most cases of partial tissue: over the 64 distinct synthetic cell-to-cell problems of the three sets, it cut the geometric mean error of the 8 partial sections from 15.2 to 4.3 µm (ratio 0.29, ±2 SE 0.18–0.45; all 8 better). Without it, a sparse heart section missing 20% of its cells barely improved on the initial affine (49.9 instead of 50.9 µm; 4.5 µm with it). Where the target has all the tissue but uneven cell density, it does harm: the error of the 6 such sections rose from 7.9 to 12.8 µm (ratio 1.62, 1.31–2.00; up to 1.99 times), though it stays well below the 46.7 µm of LDDMM, and 5 of the 8 serial heart sections got 10–57% worse (ratio 1.14, 1.02–1.26). The other 42 problems changed by less than 2%. Over all 64 the ratio was 0.91 (0.80–1.03; 0.91 on the main set and 0.88 and 1.00 on the two held-out sets, none significant). Pass mixture=None where the target section is complete.

  • New option LDDMM(mixture='em') (keyword-only, 2D, off by default): a mixture model that estimates the noise of the matching term from the residual and gives a low matching weight to target pixels that look like background where the deformed source has tissue, as where the target section is incomplete. From iteration 50, every 5 iterations, each target pixel is weighted between the matching class and a background class whose mean is the median of the target’s border (or muB); a dict changes these choices (see the docstring). On the main set of the evaluation suite, with the other new defaults, it cut the error of the 3 partial-section problems to 0.36 times that without it (all 3 better) and left the 21 serial sections unchanged on average (ratio 1.01), but one of the 2 problems with uneven cell density got 10% worse (the other 23% better). Overall the ratio was 0.89 (±2 SE 0.78–1.02, not significant), so it is not LDDMM’s default: try it where the target lacks tissue. align_cells uses it by default (above). Both engines implement it natively, at no measurable cost per iteration. LDDMM_3D_to_slice does not implement it (any mixture but None raises NotImplementedError).

New functions and options

  • align_cells and LDDMM(mixture=...) (above).

  • import STalign also gives the functions of STalign.STalign (STalign.LDDMM and so on), so the tutorials that import STalign.py from the repository with sys.path.append also work when STalign is installed. from STalign import STalign works as before.

  • LDDMM and LDDMM_3D_to_slice take the keyword-only arguments prefit, sigma_disp and legacy_defaults (above), backend ('auto', 'cpp' or 'torch'), num_threads, draw and affine_step_control (default False in LDDMM and True in LDDMM_3D_to_slice, see below). The new backend_info() reports which engine is used and, if the C++ engine is unavailable, why.

  • The number of threads is at most num_threads, else the environment variable STALIGN_NUM_THREADS, else torch.get_num_threads(). Results do not depend on it.

  • transform_points_source_to_target, build_transform(direction='f'), build_transform3D(direction='f') and transform_image_target_to_source take exact_inverse (default True, see below).

  • rasterize and rasterize_with_signal take a keyword-only blur_um: the kernel standard deviation in the units of the cell coordinates, instead of blur in pixels (blur = blur_um/(2*dx)), so the kernel keeps its physical width at any pixel size. Giving both blur and blur_um is an error. Calls without blur_um give the same images as before.

  • Changed defaults (each explained above): None for niter (was 5000), sigmaR (5e5), epL (2e-8), epT (2e-1) and epV (2e3) in LDDMM, for sigmaR (1e8) and epV (1e3) in LDDMM_3D_to_slice, and for blur (1.0) in rasterize and rasterize_with_signal, where it means 1.0 unless blur_um is given.

Changes that affect results

These corrections apply whatever the defaults. Their effects below were measured with the previous defaults (those of legacy_defaults=True).

  • Source-to-target transforms use the exact inverse of the fitted mapping. LDDMM fits a target-to-source map; mapping cells the other way previously used a forward Euler approximation that is not its inverse. On the tutorial fits, cells move by 1–13 µm on average and up to ~100 µm. The ground-truth error of the simulation tutorial drops from 9.68 to 6.68 µm (6.38 µm with the new defaults). exact_inverse=False gives the previous approximation.

  • transform_points_target_to_source integrates the velocity in the correct time order (t = nt-1 down to 0, as LDDMM and build_transform do). Changes are small: at most 0.26 raster pixels on the tutorials.

  • The 3D regularizer is corrected. LDDMM_3D_to_slice transformed the velocity over only two of its three axes, so it applied no smoothness left-to-right and a position-dependent penalty up to 83,521 times stronger at the midline. It now computes the documented 0.5/sigmaR² Σ_t ‖L v_t‖². At the old default sigmaR=1e8 the corrected term regularizes much less than the old one did (in the 3D tutorials the rms velocity became up to twice as large). sigmaR=2e6, the value legacy_defaults=True uses, gives deformations of about the previous size (rms velocity 16.7 and 17.4 µm in the MERFISH and STARmap tutorials, against 15.7 and 14.7 µm before); the default is now chosen from the data (above). sigmaR=1e8 gives the old value.

  • LDDMM_3D_to_slice controls the step of each affine parameter (affine_step_control=True, the new default). With the default epT, the translation found by plain gradient descent never settled: it jumped back and forth by 45–165 µm per iteration until the last one, so the result depended on rounding. Now the step of each entry of L and T is multiplied by its own factor between 0.001 and 1, which halves when that parameter’s gradient changes sign from one iteration to the next and grows by 5% per iteration while it does not, so steps are never larger than epL and epT. The affine parameters converge (the translation moves by 0.001–0.3 µm per iteration at the end), and changing the initial translation in its last digit moves the cells by about 1e-12 µm instead of 30–84 µm. With sigmaR=2e6, the 3D tutorial alignments match the atlas about as well as version 1.0 did:

    matching energy

    cells in brain

    Dice (tissue vs brain)

    correlation with the atlas

    MERFISH, version 1.0

    29,095

    97.1%

    0.885

    0.394

    MERFISH, this version, legacy_defaults=True

    30,169

    96.9%

    0.882

    0.400

    STARmap, version 1.0

    92,564

    97.5%

    0.804

    0.516

    STARmap, this version, legacy_defaults=True

    77,782

    97.7%

    0.805

    0.522

    (Matching energy: EM over the last 20 iterations, lower is better. Correlation: the target with the deformed atlas Nissl image over the tissue.) With the new defaults, 97.3% of the cells are in the brain in the MERFISH tutorial, with Dice 0.885 and correlation 0.632, and 98.8%, 0.806 and 0.666 in the STARmap tutorial. affine_step_control=False gives the plain gradient steps. The control is off by default in LDDMM: on the 2D tutorials the plain steps either already converge (MERFISH: the cells move by 0.03 µm with the control) or the control made the landmark error worse (the noisy H&E target of the Xenium tutorial: 34.2 instead of 30.3 pixels of the H&E image).

  • analyze3Dalign returns correct atlas coordinates. It indexed a 10-unit grid with indices computed from dx, so coordinates were wrong whenever dx was not 10 (by 4.3 mm on average in the STARmap tutorial, dx=50) and biased by up to 19 µm otherwise. Region labels were mostly right and change for under 3% of cells at region boundaries.

  • LDDMM and LDDMM_3D_to_slice return the affine A, velocity v and (3D) sample points Xs of the same, last evaluated iteration. v previously came from one update later than A. The difference is one velocity step.

  • muA and muB are matched to the number of target channels. A 3-entry mean against a 1-channel target (as in the 3D tutorials) previously counted the squared distance three times; it now warns and uses the single value. Integer, float, list, numpy and torch values are accepted.

  • rasterize_with_signal keeps the sign of a signed signal (it returned the absolute value). normalize maps a constant array to t_min instead of NaN, so rasterize with a constant gene gives a zero image instead of NaN.

Long 3D fits with plain affine steps are very sensitive to rounding: a change in the last digit of the input moved version 1.0’s result by 30–84 µm on average in the 3D tutorials. Small differences from earlier versions are therefore expected even where nothing was fixed. With the affine step control, the new default in 3D, the same change moves the result by about 1e-12 µm, and the C++ and torch engines give the same 3D tutorial alignments to within 1e-11 µm. 2D alignments of noisy images can still be sensitive: the same change moves the cells of the Xenium-to-H&E tutorial by 8 µm on average.

Bug fixes

  • rasterizePCA works (it raised NameError: name 'tools' is not defined) and tolerates genes without counts.

  • build_transform3D(direction='f') works (it always raised an error) and accepts v=None.

  • v_to_phii and v_to_phii_3D accept the velocity layout that LDDMM returns.

  • calculate_tre and the transform functions accept numpy arrays, torch tensors and lists of integers or floats.

  • point_annotator.py and curve_annotator.py work with output file names (they raised NameError).

  • saveRasters writes into the folder path (it wrote next to it without a trailing slash).

  • download_aba_ontology and download_aba_image_labels raise on HTTP errors instead of saving the error page.

  • plot_brain_regions gives every region its own color with a readable legend; both plot helpers return (fig, ax).

  • LDDMM_3D_to_slice warns that it ignores pointsI and pointsJ (it has no landmark term).

  • Inputs are checked before fitting, with error messages that name the problem. A drawing error warns instead of stopping the fit.

Tutorials

  • All 17 tutorial notebooks were re-run with this version, and the aligned cell positions they write into docs/*_data were regenerated. On the Mac with the C++ engine, their alignment cells take 0.5–36 s; the exception is the curve-annotator tutorial, whose 10,000 iterations on the full H&E image take 3.8 min. (The comparison with version 1.0 is under Faster.)

  • The MERFISH-to-MERFISH, Xenium-to-Xenium, Xenium-to-STARmap and heart tutorials keep their step-by-step walkthrough and end with a section on align_cells. The section aligns the same cells in one call and compares the result with the step-by-step one: the median distance between the two is 48, 14, 98 and 69 µm. The sections explain when to turn off the mixture model (mixture=None). The heart tutorial, whose complete serial sections are such a case, turns it off.

  • The 3D tutorials start the fit from the atlas slice the user picks. Before, the starting plane was off by xI[0][slice]*(1/scale_z - 1): 253 µm in the MERFISH tutorial and 47 µm in the STARmap tutorial. They also pass a one-entry muA and muB for their one-channel target. The STARmap tutorial reads its data by a relative path and draws inline figures.

  • The Xenium-to-Xenium tutorial reads the matching weights WM on the target grid instead of the source grid. This has no visible effect there, but it matters whenever the two grids differ.

  • Tutorials that no longer ran were fixed:

    • The simulation tutorial uses the current API and scikit-learn’s root_mean_squared_error. Its ground-truth error is now 6.38 µm instead of 9.68 µm.

    • The MERFISH-to-Xenium tutorial had wrong data paths and a commented-out import.

    • The curve-annotator tutorial resamples each pair of curves to the same number of points.

    • The L/T tutorial no longer unpacks LDDMM’s result as a tuple.

    • The Xenium-to-Xenium, Xenium-to-H&E, Xenium-to-STARmap and heart tutorials save their results as tables: the target’s own columns plus aligned_x and aligned_y (their save cells called to_csv on numpy arrays and raised an error). The Xenium-to-H&E tutorial also saved an undefined variable, and the heart tutorial’s result file, CN73_E2_STalign_to_CN73_E1.csv.gz (the cells of CN73_E2 in the coordinates of CN73_E1), is new, as its save cell never ran before.

  • The point-annotator tutorial writes the filtered cells into its ..._filtered.csv.gz file (it wrote all cells). The tutorials convert between torch tensors and numpy arrays explicitly and pass no unused color limits to scatter, so they run without warnings, and the L/T tutorial no longer has a leftover cell that reloaded STalign.

  • The uncompressed Xenium_Breast_Cancer_Rep1_STalign_to_HE.csv and Xenium_Breast_Cancer_Rep2_STalign_to_Rep1.csv in docs/xenium_data and starmap_STalign_to_xenium.csv in docs/starmap_data were written by earlier versions of the tutorials, which now write .csv.gz files. They are left as they were, so they do not match the results of this version.

Packaging and testing

  • The version is 2.0.0. It is defined once, as STalign.__version__, which setup.py and the documentation read.

  • requirements.txt lists only the runtime dependencies, unpinned: matplotlib, numpy, pandas, pynrrd, requests and torch. pandas, pynrrd and requests are needed only by the Allen atlas helpers. The tutorials also use plotly, scipy, scikit-learn and scikit-image.

  • pyproject.toml declares the build requirements. pybind11 is needed only to compile the C++ engine; if compilation fails, installation still succeeds and the torch engine is used. Building the C++ engine in a clone (python setup.py build_ext --inplace) also needs setuptools, which Python 3.12 and later no longer include in new virtual environments.

  • Wheels include the license of pocketfft (BSD-3-Clause), which the C++ engine compiles in, next to STalign’s own.

  • A pytest suite in tests/ checks both engines against a plain-autograd reference implementation of the iteration (tests/reference.py), and the rasterization, the transforms and the function signatures against version 1.0, whose STalign.py it takes from the environment variable STALIGN_ORIGINAL.

  • A GitHub Actions workflow (.github/workflows/tests.yaml) builds STalign with its C++ engine and runs the test suite on Linux and macOS (Apple silicon) with Python 3.10, 3.12 and 3.14, with version 1.0’s STalign.py taken from git for the comparisons. It also installs STalign without a compiler and checks that the torch engine is used. The documentation workflow now uses actions/setup-python@v7 and peaceiris/actions-gh-pages@v4 (the previous versions needed Node 16) and actions/checkout@v7, and installs docs/requirements.txt with the CPU build of torch. The documentation renders the numpy-style docstrings (with napoleon) and includes this file.

  • docs/requirements.txt can be installed with pip (it listed the standard-library modules os, collections and copy, and nrrd instead of pynrrd) and adds ipython, which highlights the notebooks’ code. The Pipfile adds scikit-image and scikit-learn, which the tutorials use, and pybind11 and setuptools, which building the C++ engine in place needs. pipenv-requirements.txt is regenerated from it.