DotMatch and scverse for Perturb-seq and Feature Barcodes
This tutorial covers the handoff from DotMatch feature assignments to
AnnData/scverse objects. Use dotmatch feature matrix when another workflow
has already produced one row per observation with an explicit cell identifier
and an extracted feature sequence.
1. Build a cell-by-feature matrix
The input table must be headered TSV (or CSV) and include a cell identifier and the feature sequence window. A minimal table looks like this:
observation_id cell_barcode feature_seq
read_001 AAACCTGAGAAACCAT ACGTACGTACGTACGTACG
read_002 AAACCTGAGAAACCAT ACGTACGTACGTACGTACG
read_003 AAACCTGAGCTAACAA TGCATGCATGCATGCATGC
Run the matrix command with the input column names explicitly:
dotmatch feature matrix \
--observations feature_observations.tsv \
--targets feature_library.tsv \
--id-column observation_id \
--cell-column cell_barcode \
--sequence-column feature_seq \
--metric hamming \
--k 1 \
--ambiguity-policy radius \
--out-dir feature_matrix/
feature_matrix/matrix.mtx is a sparse cells × features Matrix Market
matrix. Its row order is recorded in barcodes.tsv; its column order and
target sequences are recorded in features.tsv. cell_feature_counts.tsv is
the same unique-assignment result in long TSV form. assignments.tsv,
cell_qc.tsv, and summary.json retain the full outcome and run settings.
Only unique assignments add counts. ambiguous, none, and invalid
observations remain in the diagnostic artifacts instead of being forced into a
feature.
This command does not extract reads from FASTQ, pair read sides, correct cell barcodes, deduplicate UMIs, or call cells. Perform those steps in the upstream workflow before writing the observation table, and retain their provenance next to the DotMatch output directory.
2. Load the matrix into AnnData
from pathlib import Path
import anndata as ad
import pandas as pd
from scipy.io import mmread
run = Path("feature_matrix")
cells = pd.read_csv(run / "barcodes.tsv", sep="\t")
features = pd.read_csv(run / "features.tsv", sep="\t")
feature_adata = ad.AnnData(
X=mmread(run / "matrix.mtx").tocsr(),
obs=cells.set_index("cell_barcode"),
var=features.set_index("target_id"),
)
feature_adata.uns["dotmatch"] = {
"summary": str(run / "summary.json"),
"assignments": str(run / "assignments.tsv"),
"cell_qc": str(run / "cell_qc.tsv"),
"ambiguity_policy": "radius",
"ambiguous_observations_counted": False,
}
3. Attach assignment rows when needed
For a smaller assignment table, assignments_to_anndata can build the same
kind of count matrix. Keep the status column and request unique-only counts:
import pandas as pd
import dotmatch
assignments = pd.read_csv("feature_matrix/assignments.tsv", sep="\t")
feature_adata = dotmatch.assignments_to_anndata(
assignments,
cell_col="cell_barcode",
feature_col="target_id",
status_col="status",
count_unique_only=True,
)
4. Use scanpy-style helpers for notebook-scale work
import dotmatch.tl as dm_tl
library = [
{"id": "guide_A", "sequence": "ACGTACGTACGTACGTACG"},
{"id": "guide_B", "sequence": "TGCATGCATGCATGCATGC"},
]
dm_tl.assign_features(
adata,
seq_col="guide_sequence",
library=library,
k=1,
metric="hamming",
)
feature_adata = dm_tl.feature_counts(
adata,
seq_col="guide_sequence",
cell_col="cell_barcode",
library=library,
k=1,
metric="hamming",
)
Use the command-line matrix writer for reproducible table-to-matrix runs. Use
the dotmatch.tl helpers for notebook-scale inspection where the observations
are already in AnnData.
5. Review per-cell assignment QC
For Perturb-seq and feature-barcode analysis, review cell_qc.tsv alongside
standard scRNA-seq QC. A high ambiguous or unmatched rate can indicate an
incorrect feature window, target library, orientation, or correction radius.
The feature matrix alone does not establish cell identity or UMI-collapsed
molecule counts.