Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

SONIC: Spatial Organization through Nonrandom-pattern Inference and Comparison

Detect spatial patterns in omics data via kernel-based hypothesis tests for spatial variability (Q-tests) and co-expression (R-tests), then compare spatial pattern spectra across samples or conditions.

Key features:

  • Reliable: CAR kernel eliminates false negatives from Moran's I spectral cancellation
  • Scalable: Implicit sparse solvers and FFT acceleration handle millions of spots
  • Universal: Works with Visium, Visium HD, MERFISH, lineage trees, any spatial/graph data
  • Integrated: Native AnnData and SpatialData support
  • Comparative: Alignment-free cross-sample pattern comparison with FFT/NUFFT spectra

Installation

Install the release candidate from PyPI:

pip install sonic-spatial==1.0.0rc1

The distribution name is sonic-spatial; the Python import remains sonic.

For the latest source:

pip install "sonic-spatial @ git+https://github.com/JiayuSuPKU/sonic.git"

For development:

git clone https://github.com/JiayuSuPKU/sonic.git && cd sonic && pip install -e .

Code written for the former quadsv package remains supported by a separate compatibility distribution. pip install quadsv==1.0.0rc1 installs SONIC, and legacy quadsv imports emit a deprecation warning while forwarding to the same implementation.

Usage

Q-test: Single gene spatial variability

import numpy as np
from sonic import MatrixKernel, spatial_q_test

# simulate coordinates and gene expression
coords = np.random.randn(500, 2)
gene_expression = np.random.randn(500)

# build CAR kernel and run Q-test
kernel = MatrixKernel.from_coordinates(coords, method='car', k_neighbors=15, rho=0.9)
Q, pval = spatial_q_test(gene_expression, kernel)
print(f"Q-statistic: {Q:.4f}, p-value: {pval:.4e}")

R-test: Spatial co-expression

from sonic import spatial_r_test

# run R-test with the same kernel
gene1, gene2 = np.random.randn(500), np.random.randn(500)
R, pval = spatial_r_test(gene1, gene2, kernel)
print(f"R-statistic: {R:.4f}, p-value: {pval:.4e}")

FFT-accelerated tests (for regular grids like Visium HD)

import numpy as np
from sonic import FFTKernel, spatial_q_test, spatial_r_test

# For grid data (e.g., 1000x1000 Visium HD)
kernel_fft = FFTKernel(shape=(1000, 1000), method='car', rho=0.9)

# simulate gene expression on grid
gene_grid = np.random.randn(1000, 1000)

# run FFT-based Q-test
Q_fft, pval_fft = spatial_q_test(gene_grid, kernel_fft)
print(f"FFT Q-test: Q={Q_fft:.4f}, p-value={pval_fft:.4e}")

# run FFT-based R-test
gene1_grid = np.random.randn(1000, 1000)
gene2_grid = np.random.randn(1000, 1000)
R_fft, pval_fft = spatial_r_test(gene1_grid, gene2_grid, kernel_fft)
print(f"FFT R-test: R={R_fft:.4f}, p-value={pval_fft:.4e}")

Tutorials

Detect SVG and spatial co-expression using AnnData

import anndata as ad
from sonic import Detector

adata = ad.read_h5ad("spatial_data.h5ad")
detector = Detector(
    adata,
    kernel_method='car',
    backend='matrix',
    rho=0.9,
    k_neighbors=4,
).setup_data(adata, obsm_key='spatial', min_cells_frac=0.05)

# Compute Q-statistics and p-values genome-wide
q_results_df = detector.compute_qstat(source='var', features=None, return_pval=True)

# Returns DataFrame with columns: [Q, P_value, P_adj, Z_score]
significant_genes = q_results_df[q_results_df['P_adj'] < 0.05]
print(f"Found {len(significant_genes)} spatially variable genes")

# Select top 1000 SVGs for pairwise R-test
top_genes = significant_genes.sort_values('Q', ascending=False).head(1000).index.tolist()
r_results_df = detector.compute_rstat(
    source='var',
    features_x=top_genes,
    features_y=None,
    return_pval=True,
)
# Returns DataFrame with columns: [Feature_1, Feature_2, R, P_value, P_adj, Z_score]
significant_pairs = r_results_df[r_results_df['P_adj'] < 0.05]
print(f"Found {len(significant_pairs)} spatially co-expressed gene pairs")

FFT-based Q-test for large grids

import spatialdata as sd
from sonic import Detector

sdata = sd.read_zarr("visium_hd.zarr/")
detector = Detector(sdata, kernel_method='car', rho=0.9, topology='square').setup_data(
    sdata,
    bins='Visium_HD_bin',
    table_name='table',
    col_key='array_col',
    row_key='array_row',
)
results = detector.compute_qstat(
    n_jobs=4, workers=2, return_pval=True
)

Cross-sample spatial pattern comparison

import anndata as ad
import numpy as np
from sonic import Comparator

sample_paths = [
    "control_1.h5ad",
    "control_2.h5ad",
    "control_3.h5ad",
    "case_1.h5ad",
    "case_2.h5ad",
    "case_3.h5ad",
]
samples = [ad.read_h5ad(path) for path in sample_paths]
groups = np.array([0, 0, 0, 1, 1, 1])  # 1-D labels: control vs case

cmp = (
    Comparator(samples)
    .compute_spectra(n_jobs=4)
    .normalize_background()
)

# Pattern-level difference: compares radial power spectra, not total expression.
pattern_hits = cmp.test_diff_freq(groups, statistic='log_l2', normalize_shape=True)
# Companion expression-level difference on the DC component.
expression_hits = cmp.test_diff_expr(groups)

pattern_hits returns per-gene columns [Feature, Statistic, P_value, P_adj]. Use ComparatorGrid or the same Comparator(...) factory with SpatialData samples for regular rasterized grids.

Full tutorials: See docs/guides/quickstart.rst, docs/guides/multisample.rst, and test suite examples.

Kernel Methods

Method Type Spectrum Parameters Use Case
gaussian Distance Positive Definite bandwidth Isotropic, exponential decay
matern Distance Positive Definite bandwidth, nu Tunable smoothness ✓ Recommended
moran ⚠️ Graph Indefinite k_neighbors Autocorrelation (false negatives)
laplacian Graph Semi-Definite k_neighbors High-frequency filter
car Graph Strictly Positive rho, k_neighbors CAR kernel ✓ Recommended

Recommendation: Use car (CAR kernel) for robust, consistent detection across all functional patterns.

Testing & Development

pytest tests/ --cov=sonic              # Run all tests
pytest tests/test_tutorials.py -v       # Run tutorial examples
pip install -e ".[dev,docs]"            # Install dev + docs dependencies

Troubleshooting

Dependencies may be installed in a way that causes conflicts or unexpected behavior. To confirm whether a failure is environment-specific, validate from a clean conda environment:

conda create -n sonic-test -c conda-forge python=3.12 pip -y
conda activate sonic-test
python -m pip install -e ".[dev]"
python -m pytest -q

There are several known issues that may arise due to problematic environment configurations:

Numba cache error during import

This happens before any tests run, often through spatialdata -> xrspatial, where helpers are decorated with numba.njit(cache=True). The typical error is:

RuntimeError: cannot cache function ... no locator available

To fix this, either point Numba at a writable cache directory via

mkdir -p /private/tmp/numba-cache
NUMBA_CACHE_DIR=/private/tmp/numba-cache python -c 'import sonic'

or disable JIT to bypass the import-time cache path via

NUMBA_DISABLE_JIT=1 python -m pytest -q

Segfault during irregular NUFFT tests

On macOS arm64, having multiple OpenMP runtimes in one environment can cause segfaults. For example, when running finufft.nufft2d1 -> Plan.setpts, conda OpenBLAS may load $CONDA_PREFIX/lib/libomp.dylib while the PyPI FINUFFT wheel may load finufft/.dylibs/libomp.dylib.

To fix this, either set the environment variable KMP_DUPLICATE_LIB_OK=True, which risks introducing multithreading instability; or alternatively, use a single OpenMP runtime via

OMP_NUM_THREADS=1 python -m pytest -q

Longer-term, prefer a consistent conda-forge native stack so FINUFFT, OpenBLAS, NumPy/SciPy, and scikit-learn do not bring separate vendored OpenMP runtimes.

Documentation

ReadTheDocs

References

Su, Jiayu, et al. "On the consistent and scalable detection of spatial patterns." arXiv (2026): 2602.02825. link to preprint

License & Support

Release files for sonic-spatial 1.0.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sonic-spatial 1.0.0rc1
File Size Uploaded
sonic_spatial-1.0.0rc1.tar.gz 199.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sonic-spatial 1.0.0rc1
File Interpreter ABI Platform
sonic_spatial-1.0.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 374.3 kB

Release files / sonic_spatial-1.0.0rc1.tar.gz

Download URL sonic_spatial-1.0.0rc1.tar.gz
Size 199.8 kB
Tags Source
SHA-256 checksum
How to use checksums
850f5742b06731fd267562bda0effaab492b8800e68f5cad205a8d7ac1576f8e
BLAKE2b-256 checksum
How to use checksums
9ae2267229f9a2bc0d6ad6796e1e41a066626de5af3bef059d9ff1f20a024d6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.

Transparency log

Release files / sonic_spatial-1.0.0rc1-py3-none-any.whl

Download URL sonic_spatial-1.0.0rc1-py3-none-any.whl
Size 174.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50b6f6880040eec6173768289511f2b9ee83000cdb55933692ae27f9586a8a70
BLAKE2b-256 checksum
How to use checksums
558a22b77606c67ca451d52f0e60612e0aabd708842d8eb6f56813a24f3f55d4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0rc1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page