Metadata-Version: 2.4
Name: prismalign
Version: 0.2.1
Summary: N-color (2-color / 3-color / 3-nt) nucleotide-conversion alignment engine with pluggable backends
Author-email: Chang Ye <yech1990@gmail.com>
License-Expression: GPL-3.0-only
Keywords: bioinformatics,nucleotide conversion,bisulfite,SLAM-seq,m6A,alignment,epigenetics
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: bwamem>=0.0.56
Requires-Dist: pysam>=0.22
Provides-Extra: mappy
Requires-Dist: mappy>=2.24; extra == "mappy"
Provides-Extra: minibwa
Requires-Dist: minibwa>=0.1.7; extra == "minibwa"
Dynamic: license-file

# Prismalign

**N-color nucleotide-conversion alignment engine** with pluggable backends.

Prismalign maps sequencing reads from any nucleotide-conversion chemistry
(bisulfite-seq `C→T`, SLAM-seq `T→C`, m6A / A-to-I `A→G`, MK/KM dual-base,
or a custom 3rd channel) using a **HISAT-3N-style** strategy:

1. build a *converted* reference index (`scheme.ref_from → ref_to`)
2. transform each read per color channel and align it to the converted index
   via a pluggable backend (**bwamem** by default; a built-in **native C
   k-mer backend**; **WFA2-lib**; **minimap2/mappy** optional)
3. **re-score every hit against the *original* reference** so that real
   conversions are rewarded (not counted as mismatches), emitting a
   color-correct `MD` plus per-channel `Y`/`Z` counts in BAM tags.

All per-read kernels are **C** (bwamem / WFA2-lib / the built-in k-mer
aligner); the Python layer is a thin, friendly wrapper.

## Install

```bash
pip install -e .              # bwamem + built-in C k-mer backends
pip install -e "./[mappy]"    # + minimap2 backend
```

## Usage — Python (clean wrapper)

```python
import prismalign as ps

# one-shot mapping -> BAM (builds indexes, maps, cleans up)
ps.map_reads("reads.fq", "ref.fa", "out.bam",
             scheme="MK", backend="bwamem", threads=4)

# object API / reuse
with ps.NColorMapper(scheme=ps.BS, backend="python") as mapper:
    mapper.map_file("reads.fq", ref_files=["ref.fa"], output_files=["bs.bam"])
```

## Usage — CLI

```bash
# classic two-color (MK: A->G + C->T) on bwamem
prismalign map -s MK --backend bwamem -r ref.fa -o out.bam reads.fq

# bisulfite-seq (3-nt single channel C->T)
prismalign map -s BS -r genome.fa -o bs.bam --index-dir idx reads.fq

# parallel (2 copies of the reads, byte-identical output to -t 1)
prismalign map -s MK -r ref.fa -o out.bam -t 4 reads.fq

# list built-in schemes
prismalign schemes
```

## Schemes

| name  | reference index | channels | use case |
|-------|-----------------|----------|----------|
| `MK`  | `AC→GT`         | 2        | dual-base conversion A→G + C→T (classic two-color) |
| `KM`  | `GT→AC`         | 2        | reverse of MK |
| `BS`  | `C→T`           | 1        | bisulfite-seq (3-nt) |
| `SLAM`| `T→C`           | 1        | SLAM-seq |
| `A2G` | `A→G`           | 1        | m6A / A-to-I editing |
| `THREE`| `AC→GT`        | 3        | three-color demo (add your 3rd base pair in `schemes.py`) |

## Python API

```python
from prismalign import NColorMapper, BS

mapper = NColorMapper(scheme=BS, backend="bwamem", index_dir="idx")
mapper.map_file(r1_file="reads.fq", ref_files=["genome.fa"],
                output_files=["out.bam"])
```

## Backends

Prismalign's engine only needs `align() -> [RawHit]` from a backend (re-scoring
against the original reference is engine-side), so adding one is easy:

| backend | engine | notes |
|---------|--------|-------|
| `bwamem` | BWA-MEM via the `bwamem` package | default, fast C backend |
| `python` | **native C k-mer kernel** (`python/pyalign.c`) | fast built-in reference aligner (~150x the old pure-Python one); no extra deps |
| `wfa2` | **WFA2-lib** (vendored v2.3.6, MIT) compiled in-process | exact gapped (indel-aware) wavefront alignment; no CLI wrapper |
| `mappy` | minimap2 via `mappy` | official minimap2 Python binding |
| `minibwa` | **lh3/minibwa** (bwa-mem successor) via **PyO3 pip binding `minibwa`** (fg-labs) | ~2-3x faster than bwa-mem; `pip install minibwa` |
| `sam` | generic SAM-emitting mapper (subprocess) | wrap `bwa`, `bwa-mem2`, `bowtie2`, `hisat2`, … via a command template |
| `strobealign` | **ksahlin/strobealign** (Rust, ultra-fast short reads) | `.sti` index, SAM out; subprocess |

> **Direct vs CLI backends.** `bwamem`, `minibwa`, `mappy`, `wfa2` and `python`
> are direct/in-process (native bindings / compiled C). The *only* CLI
> (subprocess) backends are `sam` (generic) and `strobealign` (ultra-fast short
> reads — no Rust→Python binding). `wfa2` reuses `PythonBackend`'s k-mer seeding
> to anchor a diagonal and runs WFA2's exact gap-affine alignment for true
> I/D CIGARs — the same "one core algorithm" as wfmash/gem3, minus the CLI
> layer. On exact / simple-mismatch reads every backend's output is identical;
> on gapped reads WFA2 may pick a *different-but-equally-valid* split of the
> M-runs around an indel than BWA (same position and I/D set), so byte-identity
> applies to the mapping, not to the exact CIGAR representation.
>
> Full inventory — including **where each Python wrapper lives** — is in
> [`docs/backends.md`](docs/backends.md).

## Speed & IO

* **Parallel mapping**: `-t/--threads N` maps reads in an ordered fork+COW
  process pool (any backend); batches are drained in read order so the BAM is
  **byte-identical** to `threads=1`. `--batch-size` tunes reads per worker.
* **Reduced repeated IO**: references are copy+converted **once** even when
  reused across layers (cache keyed by path+scheme); per-hit reference fetch
  is cached in memory for small contigs (RNA/transcript references), so only
  one indexed read per contig.

## Limitations (v0.0.1)

* paired-end needs the `bwamem` backend (`python`/`mappy` backends are SE-only)
