Skip to main content
Glama
README.md
# BioTrax

**The first unified genomic track, peak, and sequence retrieval tool.**

One Python package and MCP server to discover, describe, and download data
from ENCODE, ChIP-Atlas, ReMap, GEO, and SRA/ENA — with unified metadata,
resolved DOI/PMID provenance, and ready-to-run pipeline outputs
(nf-core/rnaseq samplesheets, RBP-RELI/TF-RELI library indexes).

No existing tool unifies peak/track retrieval from all five sources behind
one interface. BioTrax fills that gap and goes further: it is also a better
`sra-prefetch`, pulling FASTQs directly from ENA with md5 verification and
no SRA-toolkit dependency.

---

## What problem it solves

| Before BioTrax | With BioTrax |
|---|---|
| 5 different APIs and 5 scripts | `biotrax search-tracks --target QKI --assay eCLIP` |
| `sra-prefetch` + `fasterq-dump` setup required | Direct ENA FASTQ download, no SRA toolkit |
| Manual nf-core samplesheet creation | Auto-generated from any SRA/GEO accession |
| No DOI/PMID linkage on downloaded files | Every leaf folder has `metadata.json` + `README.md` + resolved DOI |
| RELI peak library built by hand | `make_reli_index` exporter from any peak pull |
| "What did I download and from where?" | Append-only master index TSV across all downloads |

---

## Sources

| Source | Data | Capabilities |
|---|---|---|
| **ENCODE** | All assays: eCLIP, ChIP-seq, ATAC-seq, DNase-seq, RNA-seq, Hi-C, ... | search, list_files, doi_resolve, design_note |
| **ChIP-Atlas** | Assembled TF/histone ChIP-seq + ATAC-seq peak BEDs (merged from all public experiments) | search, list_files |
| **ReMap** | Curated TF ChIP-seq peaks — primary TF-RELI library source (human hg38) | search, list_files |
| **GEO** | Supplementary track/peak files (BED, BigWig, narrowPeak) with rich series provenance | search, list_files, doi_resolve, design_note |
| **SRA/ENA** | Sequencing reads — any strategy (RNA-seq, ChIP-seq, ATAC-seq, ...) | search, list_runs, doi_resolve, design_note |

---

## Install

```bash
# From source (recommended while pre-release):
git clone https://github.com/SpaceSorcerer/BioTrax.git
pip install -e BioTrax

# With uvx (runs without permanent install):
uvx --from /path/to/BioTrax biotrax list-sources

# Once published to PyPI:
pip install biotrax
```

Python >= 3.10 required. Dependencies: `httpx>=0.27`, `fastmcp>=0.4`.

---

## Download root (configurable)

By default, downloads land in **`~/BioTrax`** (your home directory).

Set the environment variable `BIOTRAX_DOWNLOAD_ROOT` to use any path:

```bash
# Linux / macOS
export BIOTRAX_DOWNLOAD_ROOT=/data/biotrax

# Windows PowerShell
$env:BIOTRAX_DOWNLOAD_ROOT = "D:\BioTraxData"

# In an MCP config (e.g. Claude Code) — set under env for the server process
```

Precedence: explicit `out_root=` argument > `BIOTRAX_DOWNLOAD_ROOT` env var > `~/BioTrax`.

> **Example local setup (author's machine):** `BIOTRAX_DOWNLOAD_ROOT=F:\BioTrax` is set
> in the MCP server environment, pointing downloads to a dedicated research data drive.

---

## MCP server (Claude / AI agents)

### Add to Claude Code

```bash
# Point to the Python interpreter and server module in your clone:
claude mcp add biotrax -- /path/to/BioTrax/.venv/bin/python /path/to/BioTrax/biotrax/server.py
```

To set the download root in the MCP config, add it to the server's environment:

```json
{
  "mcpServers": {
    "biotrax": {
      "command": "/path/to/.venv/bin/python",
      "args": ["/path/to/BioTrax/biotrax/server.py"],
      "env": {
        "BIOTRAX_DOWNLOAD_ROOT": "/your/download/path"
      }
    }
  }
}
```

### Run manually (stdio transport)

```bash
python -m biotrax.server
# or:
python /path/to/BioTrax/biotrax/server.py
```

### Available MCP tools

| Tool | What it does |
|---|---|
| `search_tracks` | Search ENCODE, ChIP-Atlas, ReMap, GEO for peak/track datasets |
| `list_track_files` | List downloadable files for one dataset (with direct URLs) |
| `get_dataset` | Fetch full metadata for one dataset by accession |
| `download_tracks` | Download files to disk; writes manifest + metadata + README |
| `list_sources` | Introspect all sources, capabilities, and filter keys |
| `search_runs` | Search SRA/ENA for sequencing experiments |
| `download_fastq` | Resolve accessions, download FASTQs, emit nf-core samplesheet |
| `make_rnaseq_samplesheet` | Build nf-core/rnaseq samplesheet from SeqRun dicts |
| `make_reli_index` | Build RBP-RELI / TF-RELI library index from downloaded BED files |

---

## CLI usage

```bash
# Search ENCODE for QKI eCLIP datasets (GRCh38, human default)
biotrax search-tracks --target QKI --assay eCLIP --sources ENCODE

# List peak files for an ENCODE experiment
biotrax list-files --source ENCODE --id ENCSR366YOG --output-type peaks

# Get full metadata (DOI, design note, file count)
biotrax get-dataset --source GEO --id GSE78509

# Search ChIP-Atlas for CTCF hg38 assembled peaks
biotrax search-tracks --target CTCF --sources ChIP-Atlas --genome GRCh38

# Search ReMap for all CTCF ChIP-seq datasets
biotrax search-tracks --target CTCF --sources ReMap

# Download FASTQs from an SRA study + write nf-core samplesheet
biotrax download-fastq --accessions SRP123456 --strandedness reverse

# Download FASTQs from a GEO series
biotrax download-fastq --accessions GSE151511

# Resolve a single SRR run
biotrax search-runs --raw-query tax_eq(9606) --limit 5

# List all sources and their filter keys
biotrax list-sources

# Override the download root for a single command
biotrax download-tracks --items '[...]' --out-root /tmp/test_download
```

---

## Python library usage

```python
from biotrax.sources.encode import ENCODEAdapter

adapter = ENCODEAdapter()

# Search for QKI eCLIP datasets
datasets = adapter.search(target="QKI", assay="eCLIP", genome="GRCh38")
# Returns: [Dataset(dataset_id='ENCSR366YOG', target='QKI', biosample='K562', ...),
#           Dataset(dataset_id='ENCSR570WLM', target='QKI', biosample='HepG2', ...)]

# Get full metadata including DOI
ds = adapter.get_dataset("ENCSR366YOG")
# ds.publication_doi -> "10.1038/s41586-020-2077-3"
# ds.pmid            -> "32728246"
# ds.design_note     -> "eCLIP, QKI, K562 experiment from ENCODE. ..."

# List peak files with direct download URLs
files = adapter.list_files(ds, output_type="peaks")
# files[0].url -> "https://www.encodeproject.org/files/ENCFF786UOW/@@download/..."
# files[0].file_format -> "bed"
# files[0].md5 -> "..."
```

```python
from biotrax.sources.sra_ena import SRAENAAdapter
from biotrax.exporters.nfcore_rnaseq import write_nfcore_samplesheet

adapter = SRAENAAdapter()

# Resolve a GSE to runs with ENA FASTQ URLs (no SRA toolkit needed)
runs = adapter.list_runs("GSE151511")
# runs[0].run_accession -> "SRR12345678"
# runs[0].fastq_urls    -> ["https://ftp.sra.ebi.ac.uk/...R1.fastq.gz", ...]
# runs[0].fastq_md5     -> ["abc123...", ...]
# runs[0].library_layout -> "PAIRED"

# Write nf-core/rnaseq samplesheet
write_nfcore_samplesheet(runs, "samplesheet.csv", strandedness="reverse")
```

```python
from biotrax.sources.geo import GEOAdapter

adapter = GEOAdapter()

# Fetch a GEO series with provenance
datasets = adapter.search(filters={"gse": "GSE78509"})
ds = datasets[0]
# ds.pmid             -> "27068461"
# ds.publication_doi  -> "10.1016/j.celrep.2016.03.052"
# ds.design_note      -> "This SuperSeries is composed of..."

# List supplementary track files (BED, BigWig, narrowPeak)
files = adapter.list_files(ds)
# -> 37 TrackFile objects with HTTPS download URLs
```

```python
from biotrax.sources.remap import RemapAdapter

adapter = RemapAdapter()
datasets = adapter.search(target="CTCF", organism="Homo sapiens")
# datasets[0].n_files -> 629  (1 bulk BED + 628 per-experiment BEDs)
# datasets[0].publication_doi -> "10.1093/nar/gkab996"

files = adapter.list_files(datasets[0])
# files[0].url -> "http://remap.univ-amu.fr/storage/remap2022/hg38/MACS2/TF/CTCF/..."
```

```python
from biotrax.exporters.reli_index import write_reli_index, write_reli_index_annotated
from pathlib import Path

# Build RBP-RELI / TF-RELI library index from downloaded peak BEDs
write_reli_index(track_files, local_paths, "CLIPseq.my_run.index")
# Output (tab-delimited, no header, two columns):
#   QKI_K562_ENCODE_ENCFF786UOW    /path/to/ENCFF786UOW.bed.gz
#   CTCF_K562_ENCODE_ENCFF001XYZ   /path/to/ENCFF001XYZ.bed.gz
```

---

## Download layout

Files land in a self-describing, deterministic hierarchy under `$BIOTRAX_DOWNLOAD_ROOT`
(or `~/BioTrax` by default):

```
$BIOTRAX_DOWNLOAD_ROOT/
  ENCODE/GRCh38/QKI__eCLIP__K562__ENCSR366YOG/
      ENCFF786UOW.bed.gz
      manifest.tsv          <- file-level: filename, url, md5, size_bytes, format
      metadata.json         <- full Dataset: DOI, PMID, design_note, all fields
      README.md             <- human-readable per-dataset summary

  ChIP-Atlas/GRCh38/TFs-and-others__CTCF__All-cell-types__05__hg38/
      Oth.ALL.05.CTCF.AllCell.bed
      manifest.tsv
      metadata.json
      README.md

  SRA-ENA/SRP123456/SRR12345678/
      SRR12345678_1.fastq.gz
      SRR12345678_2.fastq.gz
      manifest.tsv
      metadata.json
      README.md

  GEO/GSE78509__IGF2BP1-H9ES-eCLIP/
      GSM2071742_IGF2BP1_H9ES_Rep1_eCLIP.InputNormalizedPeaks.bed.gz
      manifest.tsv
      metadata.json
      README.md

  _index/
      biotrax_downloads_index.tsv   <- append-only master log of all downloads
```

**Slug rule** (deterministic, documented): spaces/slashes replaced with `-`;
consecutive `-` collapsed; leading/trailing `-` stripped; truncated at 80 chars.
Logical compound names use `__` (double underscore) as separator so single
underscores in gene names (e.g. `QKI_5`) survive unchanged.

---

## Unified schema

```python
Dataset(
    source,           # "ENCODE" | "ChIP-Atlas" | "ReMap" | "GEO" | "SRA-ENA"
    dataset_id,       # primary accession (ENCSR000AKW, GSE12345, remap2022_CTCF_hg38)
    target,           # protein/antigen (e.g. "QKI", "CTCF") — None for GEO series
    assay,            # assay type (e.g. "eCLIP", "ChIP-seq", "RNA-seq")
    biosample,        # cell line / tissue (e.g. "K562", "HepG2")
    organism,         # defaults "Homo sapiens"
    genome,           # normalized assembly (e.g. "GRCh38") — default
    n_files,          # count of downloadable files (0 = unknown)
    publication_doi,  # resolved DOI (None if not found — never fabricated)
    pmid,             # PubMed ID
    design_note,      # <=2-sentence human-readable experiment setup
    source_url,       # canonical portal URL
    retrieved,        # ISO date of when BioTrax fetched this record
    extra,            # source-specific dict (never fabricated)
    ignored_filters,  # filter keys this source could not apply (transparency)
)

TrackFile(
    source, dataset_id, file_id,
    target, assay, biosample, genome,
    output_type,      # "peaks" | "signal" | "IDR thresholded peaks" | ...
    file_format,      # "bed" | "narrowPeak" | "bigBed" | "bigWig" | ...
    url,              # direct download URL (HTTPS)
    size,             # bytes (when available)
    md5,              # expected md5 (when available)
    parent,           # back-reference to owning Dataset
)

SeqRun(
    run_accession,    # e.g. SRR12345678
    study,            # SRP / PRJNA accession
    sample,           # SRS / SAMN accession
    library_strategy, # "RNA-Seq" | "ChIP-Seq" | "ATAC-seq" | ...
    library_layout,   # "PAIRED" | "SINGLE"
    instrument,       # e.g. "Illumina NovaSeq 6000"
    read_count,       # total reads
    organism,         # defaults "Homo sapiens"
    fastq_urls,       # ENA HTTPS FASTQ URLs [R1, R2] or [SE]
    fastq_md5,        # parallel md5 list
    parent,           # back-reference to owning Dataset
)
```

---

## Pipeline exporters

```python
from biotrax.exporters.nfcore_rnaseq import write_nfcore_samplesheet
from biotrax.exporters.reli_index import write_reli_index, write_reli_index_annotated
from biotrax.exporters.worksheet import write_worksheet
from biotrax.exporters.descriptor import write_dataset_readme, write_set_readme

# nf-core/rnaseq samplesheet from SRA runs
# Columns: sample, fastq_1, fastq_2, strandedness
write_nfcore_samplesheet(runs, "samplesheet.csv", strandedness="reverse")

# Minimal RELI binary-compatible index (2-column, tab-delimited, no header)
write_reli_index(track_files, local_paths, "CLIPseq.my_run.index")

# Annotated RELI index with provenance columns
write_reli_index_annotated(track_files, local_paths, "CLIPseq.my_run.index.annotated.tsv")

# Generic tidy sample worksheet (TSV or CSV)
write_worksheet(datasets, track_files=files, out_path="results.tsv")
```

---

## Flexible filtering

Every source exposes a `filters` dict for source-specific constraints.
Unrecognized keys are **reported in `Dataset.ignored_filters`** — never
silently dropped.

```python
# ENCODE-specific filters
datasets = adapter.search(
    target="QKI",
    filters={
        "assay_title": "eCLIP",          # exact ENCODE assay_title
        "lab": "Gene Yeo, UCSD",         # lab filter
        "status": "released",            # experiment status
        "date_released": "2020-01-01",   # released on or after
    }
)

# GEO-specific filters
datasets = geo_adapter.search(
    filters={
        "gse": "GSE78509",               # exact accession lookup
        "supplementary_file_type": "BW", # require BigWig supplementary
        "date_from": "2018/01/01",
        "min_samples": 4,
    }
)

# ChIP-Atlas-specific filters
datasets = chipatlas_adapter.search(
    target="CTCF",
    filters={
        "antigen_class": "TFs and others",
        "cell_type_class": "Blood",
        "threshold": "10",               # MACS2 q-value threshold (05/10/20)
    }
)

# SRA/ENA-specific filters
datasets = sra_adapter.search(
    filters={
        "library_strategy": "RNA-Seq",
        "library_layout": "PAIRED",
        "min_read_count": 10000000,
    }
)
```

Use `biotrax list-sources` (CLI) or the `list_sources()` MCP tool to see all
filter keys per source.

---

## Contributing: add a source adapter

BioTrax is architected so each source is an isolated, independently testable
module. Adding a new source takes four steps:

**1. Create `biotrax/sources/myadapter.py`**

```python
from biotrax.sources.base import SourceAdapter
from biotrax.core import Dataset, TrackFile

class MyAdapter(SourceAdapter):
    name = "MySource"
    description = "One-line description of the source."
    supported_filters = ["filter_key_1", "filter_key_2"]
    capabilities = {"search", "list_files", "download"}

    def search(self, target=None, assay=None, biosample=None,
               genome="GRCh38", organism="Homo sapiens",
               filters=None, raw_query=None, limit=50):
        filters = filters or {}
        ignored = self._extract_ignored(filters, self.supported_filters)
        # ... call your API, build Dataset objects ...
        return datasets   # list[Dataset]

    def list_files(self, dataset_or_id, *, output_type=None,
                   file_format=None, genome="GRCh38"):
        acc = self._dataset_id(dataset_or_id)
        # ... fetch file list, build TrackFile objects ...
        return track_files  # list[TrackFile]
```

**2. Set `name`, `description`, `supported_filters`, `capabilities`**

The `supported_filters` list tells `list_sources()` (and AI agents) exactly
what filter keys this source understands. Any key in `filters` not in this
list is automatically collected into `ignored_filters` via
`self._extract_ignored()` — the transparency protocol.

**3. Register in `biotrax/server.py`**

```python
from biotrax.sources.myadapter import MyAdapter

_PEAK_ADAPTERS["MySource"] = MyAdapter   # or _ALL_ADAPTERS for seq sources
```

**4. Add live-API tests in `tests/test_myadapter.py`**

Use `pytest -k myadapter` to run only your tests. Follow the pattern in
`tests/test_base_adapter.py` for the ABC contract and
`tests/test_exporters.py` for exporter tests.

Anti-hallucination rule: verify against the live API before merging. If the
API can't deliver something cleanly, document the caveat in the adapter
docstring — never fabricate results.

---

## Defaults

- **Genome:** GRCh38 (alias "hg38" accepted everywhere)
- **Organism:** Homo sapiens
- **Download root:** `~/BioTrax` (override via `BIOTRAX_DOWNLOAD_ROOT` env var or `out_root=` argument)
- **Output type default:** peaks (for peak sources)
- **Strandedness default:** "auto" (for nf-core samplesheets)

---

## License

MIT — see LICENSE.

---

## Citation

If BioTrax is useful to your work, please also cite the databases it queries:

- **ENCODE:** ENCODE Project Consortium (2012) *Nature* 489:57-74
- **ChIP-Atlas:** Oki et al. (2018) *NAR* [doi:10.1093/nar/gky488]; Oki et al. (2024) *NAR* [doi:10.1093/nar/gkae358]
- **ReMap:** Hammal et al. (2022) *NAR* [doi:10.1093/nar/gkab996]
- **GEO:** Barrett et al. (2013) *NAR* [doi:10.1093/nar/gks1193]
- **ENA/SRA:** Leinonen et al. (2011) *NAR* [doi:10.1093/nar/gkq967]