STARCall integrates image stitching, alignment, and read calling to enable scalable analysis of in situ sequencing data.
The 1 match
- [1] § Methods › Stitching and alignment of images across cycles ↔ ashlar/utils.py, lines 100–146 · score 0.64 · shift theorem, fourier, iFFT, matrix, zero, match
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 267 lines · 10 KB · MIT · 1 match
- import functools
- import itertools
- import warnings
- import skimage
- import skimage.restoration.uft
- import scipy.ndimage
- import numpy as np
- # Pre-calculate the Laplacian operator kernel. We'll always be using 2D images.
- _laplace_kernel = skimage.restoration.uft.laplacian(2, (3, 3))[1]
- def whiten(img, sigma):
- img = skimage.img_as_float32(img)
- if sigma == 0:
- output = scipy.ndimage.convolve(img, _laplace_kernel)
- else:
- output = scipy.ndimage.gaussian_laplace(img, sigma)
- return output
- @functools.lru_cache
- def get_window(shape):
- # Build a 2D Hann window by taking the outer product of two 1-D windows.
- wy = np.hanning(shape[0]).astype(np.float32)
- wx = np.hanning(shape[1]).astype(np.float32)
- window = np.outer(wy, wx)
- return window
- def window(img):
- assert img.ndim == 2
- return img * get_window(img.shape)
- def register(img1, img2, sigma, upsample=10):
- img1w = window(whiten(img1, sigma))
- img2w = window(whiten(img2, sigma))
- shift = skimage.registration.phase_cross_correlation(
- img1w,
- img2w,
- upsample_factor=upsample,
- normalization=None
- )[0]
- # At this point we may have a shift in the wrong quadrant since the FFT
- # assumes the signal is periodic. We test all four possibilities and return
- # the shift that gives the highest direct correlation (sum of products).
- shape = np.array(img1.shape)
- shift_pos = (shift + shape) % shape
- shift_neg = shift_pos - shape
- shifts = list(itertools.product(*zip(shift_pos, shift_neg)))
- correlations = [
- np.abs(np.sum(img1w * scipy.ndimage.shift(img2w, s, order=0)))
- for s in shifts
- ]
- idx = np.argmax(correlations)
- shift = shifts[idx]
- correlation = correlations[idx]
- total_amplitude = np.linalg.norm(img1w) * np.linalg.norm(img2w)
- if correlation > 0 and total_amplitude > 0:
- error = -np.log(correlation / total_amplitude)
- else:
- error = np.inf
- return shift, error
- def nccw(img1, img2, sigma):
- img1w = whiten(img1, sigma)
- img2w = whiten(img2, sigma)
- correlation = np.abs(np.sum(img1w * img2w))
- total_amplitude = np.linalg.norm(img1w) * np.linalg.norm(img2w)
- if correlation > 0 and total_amplitude > 0:
- diff = correlation - total_amplitude
- if diff <= 0:
- error = -np.log(correlation / total_amplitude)
- elif diff < 1e-5:
- # This situation can occur due to numerical precision issues when
- # img1 and img2 are very nearly or exactly identical. If the
- # difference is small enough, let it slide.
- error = 0
- else:
- raise RuntimeError(
- f"correlation > total_amplitude (diff={diff})"
- )
- else:
- error = np.inf
- return error
- def crop(img, offset, shape):
- # Note that this only crops to the nearest whole-pixel offset.
- start = offset.round().astype(int)
- end = start + shape
- img = img[start[0]:end[0], start[1]:end[1]]
- return img
- # TODO:
- # - Deal with ringing from high-frequency elements. The wrapped edges of the
- # image are especially bad, where the wrapping introduces sharp
- # discontinuities. The edge artifacts could be dealt with in several ways
- # (extend the trailing image edge via mirroring, throw away some of the
- # trailing edge of the shifted result) but edges in the "true" image content
- # would require proper pre-filtering. What filter to use, and how to apply it
- # quickly?
- # - Can we use real FFT for a ~50% overall speedup? Fourier-space matrices will
- # all be half-size in the last dimension, so FFT is around 50% faster and our
- # fshift calculations will be too.
- # - Trailing edge pixels should be zeroed to match the behavior of
- # scipy.ndimage.shift, which we rely on in our maximum-intensity projection.
- def fourier_shift(img, shift):
- # Ensure properly aligned complex64 data (fft requires complex to avoid
- # reallocation and copying).
- img = skimage.util.img_as_float32(img)
- img = pyfftw.byte_align(img, dtype=np.complex64)
- # Compute per-axis frequency values according to the Fourier shift theorem.
- # (Read "w" here as "omega".) We pre-multiply as many scalar values as
- # possible on these vectors to avoid operations on the full w matrix below.
- v = np.fft.fftfreq(img.shape[0])
- wy = (2 * np.pi * v * shift[0]).astype(np.float32).reshape(-1, 1)
- u = np.fft.fftfreq(img.shape[1])
- wx = (2 * np.pi * u * shift[1]).astype(np.float32)
- # Add column and row vector to get full expanded matrix of frequencies.
- w = wy + wx
- # We perform an explicit application of Euler's formula with careful
- # management of output arrays to avoid extra memory allocations and copies,
- # squeezing out some speed over the obvious np.exp(-1j*w).
- fshift = np.empty_like(img, dtype=np.complex64)
- np.cos(w, out=fshift.real)
- np.sin(w, out=fshift.imag)
- np.negative(fshift.imag, out=fshift.imag)
- # Perform the FFT, multiply in-place by the shift matrix, then IFFT.
- freq = pyfftw.builders.fft2(img, planner_effort='FFTW_ESTIMATE',
- avoid_copy=True, auto_align_input=True,
- auto_contiguous=True)()
- freq *= fshift
- img_s = pyfftw.builders.ifft2(freq, planner_effort='FFTW_ESTIMATE',
- avoid_copy=True, auto_align_input=True,
- auto_contiguous=True)()
- # Any non-zero imaginary component of the resulting array is due to
- # numerical error, so we can just return the real part.
- # FIXME need to zero out row(s) and column(s) we shifted away from,
- # since at this point we have a cyclic rotation rather than a shift.
- return img_s.real
- def paste(target, img, pos, func=None, subpixel_shift=True):
- """Composite img into target.
- Interpolation of images can be disabled with subpixel_shift=False, otherwise
- images with non integer positions will be shifted with scipy.ndimage.shift
- """
- pos = np.array(pos)
- # Bail out if destination region is out of bounds.
- if np.any(pos >= target.shape[:2]) or np.any(pos + img.shape[:2] < 0):
- return
- if not subpixel_shift:
- pos = np.round(pos)
- pos_f, pos_i = np.modf(pos)
- yi, xi = pos_i.astype('i8')
- # Clip img to the edges of the mosaic.
- if yi < 0:
- img = img[-yi:]
- yi = 0
- if xi < 0:
- img = img[:, -xi:]
- xi = 0
- target_slice = target[yi:yi+img.shape[0], xi:xi+img.shape[1]]
- img = crop_like(img, target_slice)
- # Skip expensive sub-pixel shift if fractional position is zero.
- if subpixel_shift and pos_f.any():
- if img.ndim == 2:
- img = scipy.ndimage.shift(img, pos_f)
- else:
- for c in range(img.shape[2]):
- img[...,c] = scipy.ndimage.shift(img[...,c], pos_f)
- # For any axis where there is a non-zero subpixel shift, crop out the
- # last row or column of pixels on the "losing" side. These pixels will
- # be darker than normal and will introduce artifacts in most blending
- # modes.
- y1 = None if pos_f[0] <= 0 else 1
- y2 = None if pos_f[0] >= 0 else -1
- x1 = None if pos_f[1] <= 0 else 1
- x2 = None if pos_f[1] >= 0 else -1
- img = img[y1:y2, x1:x2]
- target_slice = target_slice[y1:y2, x1:x2]
- # Exit if image area is zero after subpixel shift.
- if not np.all(img.shape):
- return
- if np.issubdtype(img.dtype, np.floating):
- np.clip(img, 0, 1, img)
- img = dtype_convert(img, target.dtype)
- if func is None:
- target_slice[:] = img
- elif isinstance(func, np.ufunc):
- func(target_slice, img, out=target_slice)
- else:
- target_slice[:] = func(target_slice, img)
- def pastefunc_blend(target, img):
- """Linear blend based on distance to unfilled space in target."""
- # This should catch actual holes but not the actual unfilled space.
- # FIXME Should generate mask from tile boundaries instead.
- hole_threshold = np.mean(target.shape)
- mask = skimage.morphology.remove_small_holes(target != 0, hole_threshold)
- dist = scipy.ndimage.distance_transform_cdt(mask)
- dmax = dist.max()
- if dmax == 0:
- alpha = 0
- else:
- alpha = dist / dmax
- # Keep target pixel values where img has value 0 (with a 1-pixel
- # dilation to clean up the edge). This is a temporary hack to support
- # image corrections that leave regions of zero pixels around the edge of
- # img, such as barrel correction and rotation.
- # FIXME Should compute the geometry of the source image mask more
- # deliberately and precisely.
- alpha[skimage.morphology.binary_dilation(img == 0)] = 1
- return target * alpha + img * (1 - alpha)
- def crop_like(img, target):
- if (img.shape[0] > target.shape[0]):
- img = img[:target.shape[0], :]
- if (img.shape[1] > target.shape[1]):
- img = img[:, :target.shape[1]]
- return img
- def dtype_convert(img, dtype):
- """Convert an image to the requested data-type.
- This is just a wrapper around skimage.util.dtype.convert that silences its
- FutureWarning, as Ashlar pins skimage to a version before that planned
- deprecation.
- """
- with warnings.catch_warnings():
- warnings.filterwarnings("ignore", r".*scikit-image 1\.0", FutureWarning)
- return skimage.util.dtype.convert(img, dtype)
- def imsave(fname, arr, **kwargs):
- """Save an image to file.
- This is a wrapper around skimage.io.imsave to force check_contrast=False
- since the contrast check kills us on the huge images we create.
- """
- if "check_contrast" in kwargs:
- warnings.warn("ignoring check_contrast argument -- forcing to False")
- kwargs["check_contrast"] = False
- # We use scikit-image's vendored copy of tifffile directly rather than allow
- # scikit-image to optimistically use a separately-installed copy of tifffile
- # due to bugs and API inconsistencies in the latest pypi-hosted version:
- # * Use of "centimeter" for resolution units instead of "cm"
- # * A bug in writing single-tile planes -- issue #3 on GitHub
- # FIXME Once scikit-image un-vendors tifffile (#4235) AND tifffile fixes #3
- # we can remove this block and use `skimage.io.imsave` directly again. Or we
- # might just want to switch to tifffile.imsave.
- del kwargs["check_contrast"]
- import skimage.external.tifffile
- skimage.external.tifffile.imsave(fname, arr, **kwargs)
utils.py at commit fc1d70b, under MIT · at the source
Overview
- Department of Genome Sciences, University of Washington, Seattle, Washington, United States of America
- Medical Scientist Training Program, University of Washington, Seattle, Washington, United States of America
- Department of Bioengineering, University of Washington, Seattle, Washington, United States of America
- Brotman Baty Institute for Precision Medicine, Seattle, Washington, United States of America
Abstract
Fluorescent in situ sequencing involves imaging-based sequencing by synthesis in intact cells or tissues to reveal target nucleotide sequences inside each cell. Often, the target sequences are barcodes that indicate a perturbation (e.g., CRISPR guide or genetic variant) delivered to the cell. However, processing in situ sequencing data presents a considerable challenge, requiring stitching and aligning tens of thousands of images with millions of cells, detecting small amplicon colonies across sequencing cycles, and calling reads. To address these challenges, we introduce STARCall: STitching, Alignment and Read Calling for in situ sequencing, a software package that analyzes raw in situ sequencing images to produce a genotype-to-phenotype mapping for each cell. STARCall improves upon previous solutions by combining stitching and alignment of images into a single step that minimizes both inter-cycle and intra-cycle alignment error. STARCall also improves detection and extraction of sequencing reads, incorporating filters and normalization to combat background fluorophore signal. We compare STARCall to other methods using a diverse set of images that include commonly encountered imaging problems such as variable intensity across channels and cycles and high levels of background. Specifically, this comprises ~250,000 images from a pooled screen of ~3,500 barcoded LMNA variants expressed in U2OS cells and ~1,200 barcoded PTEN variants in induced pluripotent stem cells (iPSC) and iPSC-derived neurons. Overall, STARCall aligned more than 50% of tiles with <1 pixel residual misalignment on all nine image sets, outperforming alternative packages by 14–35%. STARCall also yielded an 8–40% increase in genotyped cells due to improved filtering and normalization methods that address background fluorescence. STARCall can call tools like CellPose to segment cells and CellProfiler to compute cell features from the phenotyping images. STARcall is open-source and freely available, providing a robust solution for the analysis of in situ sequencing data.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.
njbradley/ashlar
fc1d70be2d9b5cc1243cefb1727b2df0c34fd981, 3 October 2025Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
18 files
- ashlar/
__init__.py , Python, 3 lines - ashlar/
_version.py , Python, 683 lines - ashlar/
filepattern.py , Python, 123 lines - ashlar/
fileseries.py , Python, 222 lines - ashlar/
reg.py , Python, 1,585 lines - ashlar/
scripts/ , Python, 1 line__init__.py - ashlar/
scripts/ , Python, 489 linesashlar.py - ashlar/
scripts/ , Python, 63 linesmake_alignment_movie.py - ashlar/
scripts/ , Python, 102 linespreview_slide.py - ashlar/
thumbnail.py , Python, 85 lines - ashlar/
transform.py , Python, 95 lines - ashlar/
utils.py , Python, 267 lines, 1 match - ashlar/
viewer.py , Python, 131 lines - ashlar/
zen.py , Python, 87 lines - setup.py, Python, 131 lines
- versioneer.py, Python, 2,277 lines
- LICENSE, License, 21 lines
- README.md, Text, 114 lines
fowlerlab.github.io/starcall-docs
Availability: 1 check, the latest on 30 September 2026: the link answers (HTTP 200)
- 30 September 2026: the link answers (HTTP 200)
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 16 scripts, each with its path and the digest of its content;
- 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
- zenodo:19027452, at Zenodo; found in “Data Availability”
Data Availability
STARCall is freely available under the MIT licence. STARCall can be found at https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 30 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 4 authors, 10 MeSH terms, 3 funders, 20 references.
Cite
This paper
Bradley, N. J., Pendyala, S., Partington, K., & Fowler, D. M. (2026). STARCall integrates image stitching, alignment, and read calling to enable scalable analysis of in situ sequencing data. PLoS computational biology, 22(4), e1013689. https://
BibTeX
@article{bradley2026star
author = {Bradley, Nicholas J and Pendyala, Sriram and Partington, Katie and Fowler, Douglas M},
title = {{STARCall integrates image stitching, alignment, and read calling to enable scalable analysis of in situ sequencing data}},
journal = {PLoS computational biology},
year = {2026},
month = apr,
volume = {22},
number = {4},
pages = {e1013689},
publisher = {PLOS},
issn = {1553-734X},
doi = {10.1371/
url = {https://
pmid = {42044152},
pmcid = {PMC13160441}
}
RIS
TY - JOUR
AU - Bradley, Nicholas J
AU - Pendyala, Sriram
AU - Partington, Katie
AU - Fowler, Douglas M
TI - STARCall integrates image stitching, alignment, and read calling to enable scalable analysis of in situ sequencing data
T2 - PLoS computational biology
J2 - PLoS Comput Biol
PY - 2026
DA - 2026/
VL - 22
IS - 4
SP - e1013689
SN - 1553-734X
PB - PLOS
DO - 10.1371/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1371/
"type": "article-journal",
"title": "STARCall integrates image stitching, alignment, and read calling to enable scalable analysis of in situ sequencing data",
"container-title": "PLoS computational biology",
"author": [
{
"family": "Bradley",
"given": "Nicholas J"
},
{
"family": "Pendyala",
"given": "Sriram"
},
{
"family": "Partington",
"given": "Katie"
},
{
"family": "Fowler",
"given": "Douglas M"
}
],
"container-title-short":
"volume": "22",
"issue": "4",
"page": "e1013689",
"DOI": "10.1371/
"PMID": "42044152",
"PMCID": "PMC13160441",
"ISSN": "1553-734X",
"publisher": "PLOS",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
27
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1371/journal.pcbi.1014571 [code]
- SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.Journal: PLoS computational biologyIn common: napari, tifffile, NetworkX, 6 other tools, 1 reference
- [2] doi:10.7554/elife.100880 [code]
- An applicable and efficient retrograde monosynaptic circuit mapping tool for larval zebrafish.Journal: eLifeIn common: tifffile, NetworkX, scikit-image, 5 other tools, 1 reference
- [3] doi:10.1038/s42003-026-10063-9 [code]
- Conserved Kir channel mechanisms governing intrinsic excitability in human and rodent parvalbumin neurons.Journal: Communications biologyIn common: napari, tifffile, scikit-image, 4 other tools, 1 reference
- [4] doi: [code]
- Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement controlJournal: eLifeIn common: napari, tifffile, scikit-image, 5 other tools
- [5] doi:10.1016/j.celrep.2026.117420 [code]
- Neural population dynamics of direct electrical stimulation of neocortex.Journal: Cell reportsIn common: napari, NetworkX, scikit-image, 5 other tools
- [6] doi:10.1016/j.xcrm.2026.102766 [code]
- A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.Journal: Cell reports. MedicineIn common: tifffile, NetworkX, scikit-image, 5 other tools, genetics / omics
- [7] doi:10.1093/neuonc/noag128 [code]
- Spatially-resolved single-cell imaging of melanoma brain metastases identifies localized immune patterns predictive of immune checkpoint blockade response.Journal: Neuro-oncologyIn common: napari, tifffile, NetworkX, 3 other tools, genetics / omics
- [8] doi:10.1038/s41467-026-75352-7 [code]
- Mechanosensory encoding of surface mechanics optimizes locomotion.Journal: Nature communicationsIn common: napari, tifffile, scikit-image, 4 other tools
- [9] doi:10.1038/s41467-026-76569-2 [code]
- Self-organization of vascularized muscle from bovine embryonic stem cells.Journal: Nature communicationsIn common: tifffile, NetworkX, scikit-image, 5 other tools
- [10] doi:10.1016/j.isci.2026.116206 [code]
- Gut distension evokes rapid neural dynamics in vagal and hindbrain populations of larval zebrafish.Journal: iScienceIn common: tifffile, NetworkX, scikit-image, 5 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 16 scripts, and 1 match between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:3aa9e1743cc58b32…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
