DotMatch to scverse for Perturb-seq and Feature Barcodes

This tutorial shows the intended handoff from DotMatch assignment artifacts to AnnData/scverse objects. Use the CLI for FASTQ-scale assignment, then load the small, stable TSV outputs into Python.

1. Count guide or feature-barcode reads

dotmatch count \
  --targets guides.tsv \
  --reads guide_capture_R2.fastq.gz \
  --sample-label guide_capture \
  --target-start 63 \
  --target-length 19 \
  --k 1 \
  --metric hamming \
  --ambiguity-policy radius \
  --ambiguous discard \
  --out guide_counts.tsv \
  --summary guide_summary.json \
  --sample-qc guide_sample_qc.tsv \
  --assignments guide_assignments.tsv

For TotalSeq/CITE-seq-style feature barcodes, use the feature-barcode table as --targets and set --target-start / --target-length to the antibody or feature barcode window.

2. Load counts into AnnData

import dotmatch

guide_adata = dotmatch.counts_tsv_to_anndata("guide_counts.tsv")
guide_adata.uns["dotmatch_summary_json"] = "guide_summary.json"
guide_adata.uns["dotmatch_sample_qc_tsv"] = "guide_sample_qc.tsv"

The count matrix contains uniquely assigned targets only. Ambiguous reads are reported in summary.json, sample_qc.tsv, and assignments.tsv; they are not silently assigned to a guide or feature.

3. Attach per-read assignments when cell barcodes are available

If your assignment table includes a cell barcode column, convert it to an AnnData observation-level table:

import dotmatch

assign_adata = dotmatch.assignments_to_anndata(
    "guide_assignments.tsv",
    cell_col="cell_barcode",
    target_col="target_id",
)

For custom pipelines, join DotMatch assignments to cell barcodes before this step. Keep assignment_status so downstream filtering can distinguish unique, ambiguous, unmatched, and invalid reads.

4. Use scanpy-style helpers

import scanpy as sc
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",
    library=library,
    k=1,
    metric="hamming",
)

Use this path for notebook-scale inspection and prototypes. For production FASTQ processing, prefer dotmatch count so the exact command, assignment engine, ambiguity policy, and QC summaries are written as reproducible files.