seqseeker
Fast k-mer–based detection of any target sequence in paired single-cell FASTQ files
seqseeker finds reads containing a user-specified nucleotide sequence (GFP,
mCherry, Cre, any transgene) directly in raw gzipped FASTQ files from 10x
Chromium experiments, attributes each hit to a cell barcode + UMI, and writes
output tables ready for incorporation with downstream analysis e.g. Scanpy/Seurat.
No custom genome build. No Cell Ranger re-run. Per-sample runtimes of 5–15 minutes vs. hours for alignment.
Table of contents
- Installation
- Quick start
- CLI reference
- Python API
- R interface
- Bash wrapper
- Built-in sequences
- Output files
- Loading into Seurat
- Why not just build a custom genome?
- How it works
- Tuning guide
- Citation
Installation
Requirements: Python ≥ 3.10, gcc or cc (for compiling the C core), zlib.
pip install seqseeker
On first use seqseeker automatically compiles its C backend. You can also
pre-compile manually:
seqseeker compile
macOS
# Install Xcode Command Line Tools if you don't have gcc:
xcode-select --install
pip install seqseeker
Linux
sudo apt-get install build-essential zlib1g-dev # Debian/Ubuntu
# or
sudo yum install gcc zlib-devel # RHEL/CentOS
pip install seqseeker
Docker
No compiler or Python environment needed — the C binary is pre-compiled inside the image. Mount your FASTQ directory and run:
docker pull ghcr.io/ethanfenton/seqseeker:latest
docker run --rm \
-v /path/to/your/fastqs:/data \
ghcr.io/ethanfenton/seqseeker:latest \
search \
--sequence-name egfp \
--input /data/novogene/01.RawData \
--output /data/results/gfp
# All sub-commands and flags work identically to the CLI
docker run --rm ghcr.io/ethanfenton/seqseeker:latest --help
The -v /host/path:/data flag makes your local files visible inside the
container at /data/. All --input and --output paths must be under
/data/. Versioned images are available as
ghcr.io/ethanfenton/seqseeker:0.1.2, etc.
R package
# Requires the Python package to be installed first (above).
# Install from GitHub:
if (!requireNamespace("remotes")) install.packages("remotes")
remotes::install_github("ethanfenton/seqseeker", subdir = "R/seqseeker")
Quick start
Python / CLI
# Search for eGFP using a built-in sequence
seqseeker search \
--sequence-name egfp \
--input /data/novogene/01.RawData \
--output results/gfp
# Any gene — pass a FASTA file
seqseeker search \
--sequence-file my_transgene.fasta \
--input /data/fastq/ \
--output results/transgene
# Raw sequence string
seqseeker search \
--sequence ATGGTGAGCAAGGGCGAGGAG... \
--input /data/fastq/ \
--output results/custom
# Only the 3' end (faster, specific)
seqseeker search \
--sequence-name egfp \
--region 3prime --region-len 200 \
--input /data/fastq/ \
--output results/gfp_3p
# Parallel processing
seqseeker search \
--sequence-name egfp \
--input /data/fastq/ \
--workers 0 # 0 = auto-detect CPUs
Python API
import seqseeker
results = seqseeker.search(
input_dirs=["/data/novogene/01.RawData",
"/data/novogene2/01.RawData"],
sequence_name="egfp",
output_dir="results/gfp",
workers=8,
)
print(f"Total hits: {results['n_hits']:,}")
print(f"Unique barcodes:{results['n_unique_barcodes']:,}")
R
library(seqseeker)
results <- seqseeker(
input = c("/data/novogene/01.RawData",
"/data/novogene2/01.RawData"),
sequence_name = "egfp",
output = "results/gfp",
workers = 8L
)
# Add to Seurat
seurat_obj <- seqseeker_add_to_seurat(seurat_obj, results)
FeaturePlot(seurat_obj, features = "seq_hits")
CLI reference
seqseeker <command> [options]
Commands:
search Search FASTQ files for a target sequence
compile Pre-compile the C search binary
list-sequences Show built-in sequences
seqseeker search options:
Input/output:
--input/-i DIR [DIR ...] FASTQ directories (required)
--output/-o DIR Output directory [default: seqseeker_output]
Target sequence (exactly one required):
--sequence/-q ACGT... Raw nucleotide string
--sequence-file/-f FILE FASTA file
--sequence-name/-n NAME Built-in name (egfp, mcherry, venus, tdtomato)
Region selection:
--region/-R full|5prime|3prime Part of target to index [default: full]
--region-len/-L N Length for 5'/3' slice [default: 200]
k-mer tuning:
--kmer-size/-k K k-mer length [default: 20, range: 8–31]
--min-kmers/-m N Min hits to call read positive [default: 1]
10x read layout:
--barcode-len/-b N Barcode length in R1 [default: 16]
--umi-len/-u N UMI length in R1 [default: 12]
Parallelism:
--workers/-w N Parallel processes [default: 1, 0=auto]
Filtering:
--sample-filter SAMPLE ... Process only these sample IDs
--report-interval N Progress every N reads [default: 1000000]
Advanced:
--binary PATH Path to pre-compiled kmer_search binary
Python API
seqseeker.search(
input_dirs, # str | list[str | Path]
output_dir = "seqseeker_output",
*,
sequence = None, # raw nucleotide string
sequence_file = None, # FASTA file path
sequence_name = None, # built-in name
region = "full", # "full" | "5prime" | "3prime"
region_len = 200,
kmer_size = 20,
barcode_len = 16,
umi_len = 12,
min_kmers = 1,
workers = 1, # 0 = auto
sample_filter = None,
report_interval = 1_000_000,
binary_path = None,
) -> dict
Returns a dict with keys:
output_dir, raw_csv, per_cell_csv, positions_csv,
n_hits, n_unique_barcodes.
R interface
# Search
results <- seqseeker(input, output, sequence_name, ...)
# Load existing results
results <- seqseeker_load_results("results/gfp")
# Add to Seurat
seurat_obj <- seqseeker_add_to_seurat(seurat_obj, results,
barcode_suffix = "-1",
meta_col = "gfp_reads")
# List built-ins
seqseeker_list_sequences()
Bash wrapper
The bash/seqseeker script is a thin wrapper that delegates to the Python
package. After pip install seqseeker the seqseeker entry point is already
on your PATH. The bash script is useful in cluster environments where you want
an explicit shell command without activating a Python environment:
# Set a custom Python path
SEQSEEKER_PYTHON=/path/to/env/bin/python3 seqseeker search ...
Built-in sequences
Name Length Description
--------------------------------------------------------------
egfp 720 bp Enhanced GFP (canonical, non-codon-optimised)
venus 720 bp Venus YFP (T203Y/S65G/V68L/S72A mutations)
mcherry 711 bp mCherry red fluorescent protein
tdtomato 1386 bp tdTomato (tandem dimer Tomato)
Run seqseeker list-sequences to see the current list.
For a codon-optimised variant of eGFP (common in AAV constructs), use
--sequence-file pointing to your construct FASTA rather than the built-in.
Output files
All outputs land in --output (default: seqseeker_output/):
| File | Description |
|---|---|
hits_raw.csv |
One row per positive read; sample, lane, barcode, UMI, hit count, position, strand, read name, R2 sequence |
hits_per_cell.csv |
One row per unique barcode; n_reads, n_umi, samples seen |
kmer_positions.csv |
Histogram of first k-mer match position along the target (useful for diagnosing partial integrations) |
_per_pair/ |
Intermediate per-FASTQ-pair CSVs (safe to delete after aggregation) |
Loading into Seurat
# Method 1: via seqseeker R package
library(seqseeker)
results <- seqseeker_load_results("results/gfp")
seurat_obj <- seqseeker_add_to_seurat(seurat_obj, results)
# Method 2: base R
hits <- read.csv("results/gfp/hits_per_cell.csv")
hits$bc <- paste0(hits$barcode, "-1")
seurat_obj$gfp_reads <- hits$n_reads[match(colnames(seurat_obj), hits$bc)]
seurat_obj$gfp_reads[is.na(seurat_obj$gfp_reads)] <- 0
Why not just build a custom genome?
See docs/why_kmer_search.md for the full argument. In short:
- ~100× faster — no STAR index build, no full alignment pass.
- Higher sensitivity — searches all raw reads, not just those that survive MAPQ filtering; catches partial integrations and junction reads.
- No false negatives from multi-mapping — transgene k-mers that share similarity with endogenous loci are still detected if any portion is unique.
- No genome required — works from the sequence string alone.
- Direct barcode attribution — output is immediately Seurat-ready.
How it works
- The target sequence (and its reverse complement) is encoded into a compact open-addressing hash table of 2-bit k-mers at startup (~1 ms).
- R2 reads are scanned with a rolling window; any k-mer hit increments a
counter. Once
--min-kmersthreshold is met the read is emitted. - The cell barcode and UMI are extracted from the corresponding R1 read
(first
barcode_len+ nextumi_lenbases). - Results are aggregated per barcode across all FASTQ pairs.
The C core processes ~50 M reads/min/core on a modern server; total time for a 200 M read 10x library is typically <5 min on 4 cores.
Tuning guide
| Goal | Recommended settings |
|---|---|
| Initial survey (maximum sensitivity) | k=20, min=1, region=full |
| Reduce noise after confirming signal | k=25, min=3 |
| Fast specific detection of 3' end | region=3prime, region-len=200 |
| Partial / truncated integration | k=15, min=1, region=full |
| Codon-optimised variant | --sequence-file construct.fasta |
Inspect kmer_positions.csv to see where hits cluster along the target — a
peak at position 600/720 (3' end of eGFP) is expected for normal expression;
a flat distribution suggests unspecific noise.
Citation
If you use seqseeker in published work, please cite:
Fenton E (2026). seqseeker: fast k-mer detection of target sequences in single-cell FASTQ files. GitHub: https://github.com/ethanfenton/seqseeker
License
MIT © 2026 Ethan Fenton
Metadata
Release files for seqseeker 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| seqseeker-0.1.2.tar.gz | 26.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| seqseeker-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.2 kB
Release files / seqseeker-0.1.2.tar.gz
| Download URL | seqseeker-0.1.2.tar.gz |
|---|---|
| Size | 26.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
922a2043ecbac57b1ae807cf2363a0e2344779d28b5d0d095152a01b0206d076
|
|
BLAKE2b-256 checksum How to use checksums |
f20d7ddedcdb45ce002070f9aa85d21ab59a013f56baa523a46c4bcd73aeaef7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jun 7, 2026.
Transparency logRelease files / seqseeker-0.1.2-py3-none-any.whl
| Download URL | seqseeker-0.1.2-py3-none-any.whl |
|---|---|
| Size | 22.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96fba98c5a22c316342111667d575fc16a50a4f6fe7c8e1acf95b7e8b6808cfd
|
|
BLAKE2b-256 checksum How to use checksums |
a6210ee944fe924506e90826fd3a944f7fbc5891ec17e346088b716a1cf0e5ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jun 7, 2026.
Transparency log