Metadata-Version: 2.4
Name: prform
Version: 0.2.1
Summary: Predict programmed ribosomal frameshift (PRF) sites in RNA sequences.
Author-email: Khoa Hoang <khoang99@stanford.edu>
License: MIT
Project-URL: Homepage, https://github.com/khoa-yelo/PRForm
Project-URL: Issues, https://github.com/khoa-yelo/PRForm/issues
Keywords: bioinformatics,rna,ribosomal-frameshift,deep-learning
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.0
Requires-Dist: numpy>=1.22
Requires-Dist: scipy>=1.9
Requires-Dist: scikit-learn>=1.1
Requires-Dist: joblib>=1.2
Requires-Dist: h5py>=3.7
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# PRForm

Predict programmed ribosomal frameshift (PRF) sites in Viral genomes.

`prform` is a deep-learning tool that scores every nucleotide in viral genome
sequence for the probability that a -1 or +1 frameshift event occurs there.
Predictions come from a 1D residual network with a 10 kb receptive
field.

The model was trained on NCBI ~50,000 deduplicated viral genomes (as of Feb 2026). Trained model checkpoint and calibrator are bundled with the package and ready to run.

## Install

From PyPI:

```bash
pip install prform
```

From bioconda (**work in progress** — recipe submitted, not yet on the
channel; use pip until it lands):

```bash
conda install -c conda-forge -c bioconda prform
```

From source (development):

```bash
git clone https://github.com/khoa-yelo/PRForm.git
cd PRForm/prform_pkg
pip install -e .[dev]
```

## Usage

```bash
prform predict input.fasta --outdir results/
```

(equivalent: `python -m prform predict ...` — useful when `~/.local/bin`
isn't on your `PATH`.)

Writes two files into `results/`:

- `predictions.csv` — one row per FASTA record: the top-k predicted peak
  positions, each with its calibrated -1 and +1 probabilities and the
  predicted frameshift type (-1 PRF, +1 PRF, or no frameshift detected).
  Peaks are ranked by `minus1_PRF_prob + plus1_PRF_prob`.
- `predictions.h5` — full per-nucleotide calibrated probability tracks.

`predictions.h5` layout:

```
records/
    accession_id     (R,) str
    length           (R,) int
    top{k}_pos             (R,) int      # k = 1..top_k, 1-based; -1 if no peak
    top{k}_minus1_PRF_prob (R,) float32  # the track values at that position
    top{k}_plus1_PRF_prob  (R,) float32
    top{k}_type            (R,) int8     # -1 PRF, +1 PRF, or 0 = none
per_nucleotide/
    {accession_id}/                # one group per FASTA record
        no_PRF_prob      (L,) float32
        minus1_PRF_prob  (L,) float32
        plus1_PRF_prob   (L,) float32
```

With `--both-strands` each orientation gets its own block — `fwd_`/`rev_`
column and dataset prefixes, and `fwd`/`rev` per-nucleotide subgroups — see
[Scoring both strands](#scoring-both-strands).

Read a record's per-nucleotide tracks in Python:

```python
import h5py
with h5py.File("results/predictions.h5") as f:
    for acc in f["records/accession_id"].asstr()[:]:
        g = f[f"per_nucleotide/{acc}"]
        minus1 = g["minus1_PRF_prob"][:]
        plus1  = g["plus1_PRF_prob"][:]
        prf_track = minus1 + plus1
```

## Scoring both strands

PRForm is a **directional** detector: it only sees a frameshift in the
orientation it is given. A PRF encoded on the minus strand of your input is
invisible on a forward-only pass. Pass `--both-strands` to score each record in
both orientations:

```bash
prform predict genomes.fasta --outdir results/ --both-strands
```

**Both orientations are reported.** The peaks and tracks are duplicated into a
forward block and a reverse block:

```
predictions.csv
    accession_id, length,
    fwd_top{k}_pos, fwd_top{k}_minus1_PRF_prob, fwd_top{k}_plus1_PRF_prob, fwd_top{k}_type,
    rev_top{k}_pos, rev_top{k}_minus1_PRF_prob, rev_top{k}_plus1_PRF_prob, rev_top{k}_type

predictions.h5
    records/                       # same fwd_/rev_ prefixes as the CSV
        accession_id, length,
        fwd_top{k}_{pos,minus1_PRF_prob,plus1_PRF_prob,type}
        rev_top{k}_{pos,minus1_PRF_prob,plus1_PRF_prob,type}
    per_nucleotide/
        {accession_id}/
            fwd/  no_PRF_prob, minus1_PRF_prob, plus1_PRF_prob   (L,) each
            rev/  no_PRF_prob, minus1_PRF_prob, plus1_PRF_prob   (L,) each
```

The reverse block is mirrored back into **forward-genome coordinates**, so both
blocks index the same nucleotide: `fwd_top1_pos`, `rev_top1_pos` and index `i`
of either track all refer to position `i+1` of the sequence as you supplied it.
Class channels are *not* swapped — a -1 PRF read on the reverse complement
stays in `minus1_PRF_prob`, because the shift direction is defined relative to
the strand being read.

## Test the install

`prform` ships with small FASTA fixtures (one folder per labeled
frameshift class) so you can verify the install end-to-end without any
external data. Copy them into your working directory:

```bash
prform download-examples ./prform_examples
```

Layout after download:

```
prform_examples/
    plus1_PRF/    influenza_pax.fasta
    minus1_PRF/   coronavirus_khosta2.fasta, phage_tyson.fasta
    no_PRF/       monkeypox, ledantevirus, nanovirus, ourmiavirus, orbivirus
```

Run `predict` on each class:

```bash
# +1 PRF: peak should land near the labeled site
prform predict prform_examples/plus1_PRF/influenza_pax.fasta \
    --outdir prform_test/plus1

# -1 PRF: peak should land near the labeled site
prform predict prform_examples/minus1_PRF/coronavirus_khosta2.fasta \
    --outdir prform_test/minus1

# no PRF: no frameshift should be called
prform predict prform_examples/no_PRF/nanovirus.fasta \
    --outdir prform_test/no_prf

cat prform_test/plus1/predictions.csv
cat prform_test/minus1/predictions.csv
cat prform_test/no_prf/predictions.csv
```

Expected behavior:

| fixture | labeled site | what to look for |
| --- | --- | --- |
| `plus1_PRF/influenza_pax.fasta`        | +1 PRF @ pos 582   | `top1_pos` within a few nt of 582,  `top1_type == 1` |
| `minus1_PRF/coronavirus_khosta2.fasta` | -1 PRF @ pos 13248 | `top1_pos` near 13248,              `top1_type == -1` |
| `minus1_PRF/phage_tyson.fasta`         | -1 PRF @ pos 10279 | `top1_pos` near 10279,              `top1_type == -1` |
| `no_PRF/*.fasta`                       | none               | `top1_type == 0` (no frameshift called) |

Each labeled-positive FASTA header carries the labeled site for easy comparison.

## Python API

```python
from prform import PRFormPredictor

predictor = PRFormPredictor()                      # loads bundled weights
results = predictor.predict_fasta("input.fasta")   # list of dicts
# or
results = predictor.predict_sequences([("rec1", "ACGT" * 1000)])
# score both orientations, keep the better strand per record
results = predictor.predict_fasta("input.fasta", both_strands=True)
```

Each result entry is:

| key | type | meaning |
| --- | --- | --- |
| `accession_id` | str | FASTA record id |
| `length` | int | sequence length in nt |
| `strand` | str | `+`, or `-` when the reverse complement scored higher |
| `no_PRF_prob` | (L,) float32 | calibrated per-nucleotide class |
| `minus1_PRF_prob` | (L,) float32 | probabilities; the three sum |
| `plus1_PRF_prob` | (L,) float32 | to 1 at each position |
| `top` | list | per peak: `{pos, minus1_PRF_prob, plus1_PRF_prob, type}`, `pos` 1-based |
| `fwd`, `rev` | dict or None | each orientation's own tracks + `top` |

Track names match `predictions.h5`, and everything is in forward-genome
coordinates. `fwd`/`rev` are populated only with `both_strands=True`.

## Hardware

CPU works. GPU is faster — `prform` automatically uses CUDA when available.
For long sequences (>1 Mb total input) a GPU is recommended.

## License

MIT
