Files
ffpe_damage/README.md
T

200 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ffpe_damage
Методологически корректная оценка дезаминирования в FFPE-образцах (формалин-фиксированная парафинизированная ткань) по BAM.
Инструмент **не** изобретает универсальный «FFPE damage score». Он выдаёт *измеряемые* показатели:
- все 12 типов mismatch;
- нормализованную частоту `C>T` и `G>A` на каждой позиции read;
- отдельно **R1/R2**;
- отдельно **forward/reverse** alignment;
- расстояния от **5- и 3-конца** read;
- профиль частот (не просто количество mismatch);
- BED-треки для IGV;
- CSV для сравнения образцов между собой.
Классификация `low / moderate / high` назначается **только** при сравнении с валидированными контрольными образцами, приготовленными тем же протоколом (`ffpe_compare.py`). Абсолютных порогов типа «>5% = плохо» нет — у них нет валидированного основания.
## Что считаем
Для каждой позиции *i* в read:
```
C>T_i = #(C→T на позиции i) / #(C на позиции i)
G>A_i = #(G→A на позиции i) / #(G на позиции i)
```
Знаменатель — **все** высококачественные наблюдения соответствующего reference-основания на этой позиции, **включая совпадения** (matches). Это принципиально: только так частота сопоставима между позициями и образцами.
Фильтрация: убираются unmapped/secondary/supplementary, `MAPQ < --mapq`, основание с `BQ < --baseq`, дупликаты (кроме `--include-duplicates`). Read-позиция отсчитывается как от 5′-конца (`position_5prime`), так и от 3′-конца (`position_3prime`).
## Установка
```bash
cd ~/Projects/ffpe_damage
python3 -m venv --system-site-packages .venv
.venv/bin/pip install pysam==0.24.0 openpyxl # плюс pandas, matplotlib, scipy, requests
```
Требования на входные данные:
- BAM отсортирован и проиндексирован (`.bai` — создаётся автоматически если отсутствует);
- референсная FASTA проиндексирована (`.fai` — создаётся автоматически).
### Референс hg38 (UCSC, `chr`-префикс)
```bash
wget https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz
gunzip hg38.fa.gz && samtools faidx hg38.fa
# md5 hg38.fa.gz: 1c9dcaddfa41027f17cd8f7a82c7293b
```
Для офлайн-аннотации дополнительно:
```bash
mkdir -p references
wget -O references/gencode.v44.annotation.gtf.gz \
https://ftp.ebi.ac.uk/pub/databases/gencode/Gencode_human/release_44/gencode.v44.annotation.gtf.gz
# 48M, 252k транскриптов — используется для развёртки всех транскриптов без сети
```
## 1. Анализ образца
```bash
.venv/bin/python ffpe_damage_v2.py \
--bam sample.bam \
--reference hg38.fa \
--outdir FFPE_sample
```
Строгий вариант + многопоток (auto = `ядра-1`, cap 8):
```bash
.venv/bin/python ffpe_damage_v2.py \
--bam sample.bam --reference hg38.fa --outdir FFPE_sample \
--mapq 30 --baseq 20 --min-depth 30 --min-alt-count 5 --threads 7
```
Фильтрация повреждённых прямо по BAM с опциональным VCF:
```bash
# удалить повреждённые из VCF:
.venv/bin/python ffpe_damage_v2.py --bam sample.bam --reference hg38.fa --outdir FFPE_sample --vcf input.vcf
# -> FFPE_sample/input.clean.vcf + input.ffpe_flagged.vcf
# оставить но пометить FILTER=FFPE:
.venv/bin/python ffpe_damage_v2.py ... --vcf input.vcf --keep-damaged
```
Настройка детекции повреждённых: `--filter-end 10` (расстояние от конца read) и `--filter-strand-p 0.05`.
### Выходные файлы
| Файл | Содержимое |
|---|---|
| `sample_summary.csv` | число прочитанных/использованных ридов, usable bases, глобальная частота C>T/G>A, пороги фильтрации |
| `substitution_summary.csv` | все 12 типов замен: count, знаменатель, частота |
| `substitution_all12.png` | бар-чарт частот всех 12 типов (C>T и G>A выделены) |
| `normalized_damage_by_read_position.csv` | полный профиль: R1/R2 × позиция × reference-base × замена |
| `normalized_damage_by_read_position_3prime.csv` | то же, по расстоянию от 3′-конца |
| `CtoT_GtoA_normalized_profile.csv` | ключевая таблица «позиция в read vs C>T% / G>A%» |
| `CtoT_GtoA_normalized_profile_3prime.csv` | то же по 3′-концу |
| `strand_damage_profile.csv` | C>T/G>A по цепи выравнивания (`+`/`-`) |
| `strand_damage_profile_3prime.csv` | то же по 3′-концу |
| `end_enrichment.csv` | C>T/G>A % в первых/последних 1,3,5,10,20 основаниях, по R1/R2 |
| `candidate_CtoT.bed`, `candidate_GtoA.bed` | позиции для IGV; имя = `ref>alt;depth;alt;VAF` |
| `candidate_CtoT_clean.bed`, `candidate_GtoA_clean.bed` | те же BED без FFPE-подозрительных |
| `candidate_variants.csv` | контекст каждой позиции: VAF, средняя позиция в read, strand bias, BQ, раскладка R1/R2, `is_ffpe_suspect` |
| `clean_variants.csv` / `ffpe_suspect_variants.csv` | разделение на чистые и подозрительные |
| `CtoT_R1.png``GtoA_R2_3prime.png` | графики частоты по позиции в read (5′ и 3) |
### Как читать результат
- **Классическая FFPE-сигнатура**: пик `C>T` у 5-конца R1 и `G>A` у 3′-конца R2 — две цепи одного и того же дезаминирования цитозина. Проверяйте профили R1/R2 и по strand.
- Если C>T/G>A повышены равномерно по всей длине read (без пика у концов) — это не классическое end-associated повреждение, а другой артефакт/ошибки.
- **Отличить настоящий вариант от дезаминирования** (`candidate_variants.csv`): настоящий вариант имеет ожидаемый VAF, распределён по read (средняя позиция ~ середина), встречается на обеих цепях (strand bias не значим) и не привязан к концам. Дезаминирование концентрируется у концов read и на одной цепи. Критерий по умолчанию: `C>T/G>A` + `mean_pos ≤10` от любого конца → `is_ffpe_suspect`.
## 2. Классификация относительно контролей
```bash
.venv/bin/python ffpe_compare.py \
--controls sampleA_out sampleB_out sampleC_out \
--query query_sample_out \
--out compare_out \
--window 5 --k 2
```
`--controls` принимает несколько папок с выходами шага 1 (в них лежит `end_enrichment.csv`) либо один файл-манифест с путями по одному в строке.
Метрики (окно `W` от конца read):
- `C_to_T_R1_5prime_W` — C>T в первых W основаниях R1 (forward);
- `G_to_A_R2_3prime_W` — G>A в последних W основаниях R2 (reverse);
- `combined` — среднее двух.
Классификация против среднего M и SD S контролей, `z = (query M)/S`:
```
high : z > k
moderate : -k ≤ z ≤ k
low : z < -k
```
Выход: `classification.csv`, `comparison_table.csv`, `control_vs_query.png`.
> Зачем сравнивать только с контролями? FFPE-повреждение зависит от протокола (UDG-обработка, end repair, длина фрагментов, панель). Одно и то же значение частоты может быть нормой для одного протокола и тяжёлым повреждением для другого. Поэтому сравнивать стоит только образцы, приготовленные одинаково, а классификация всегда относительная.
## 3. Аннотация чистых вариантов → Excel
Из `clean_variants.csv` (0-based `pos`, `VAF = alt_count/depth`) — таблица 8 колонок:
`Ген | HGVS (c.6713C>T p.(Pro2238Leu)) | Тип варианта и эффект | VAF | PAF | ACMG значимость | AMP уровень | Уровень онкогенности`
```bash
# Быстро офлайн, 1 лучший транскрипт на вариант (по умолчанию, MANE > appris > canonical):
.venv/bin/python annotate_clean.py \
--clean FFPE_sample/clean_variants.csv \
--out FFPE_sample/clean.annotated.xlsx \
--offline --min-vaf 0.10 --min-depth 20
# Все транскрипты (1 вариант → N строк, ~9.5 среднее):
.venv/bin/python annotate_clean.py ... --offline --min-vaf 0.10 --all-transcripts
# Онлайн pan-cancer (OncoKB токен ~/.config/oncokb/token, Ensembl REST для HGVS):
.venv/bin/python annotate_clean.py --clean FFPE_sample/clean_variants.csv --out FFPE_sample/clean.annotated.xlsx --min-vaf 0.10
# токен: echo "a6061cfc-..." > ~/.config/oncokb/token && chmod 600 ~/.config/oncokb/token
# другой tumor_type: --tumor-type "LUAD"
```
Детали:
- **Ген/HGVS/Эффект** — офлайн: `GENCODE v44` GTF (252k транскриптов, `chr`-префикс, `MANE_Select` > `appris_principal_1` > `Ensembl_canonical` > `basic` > `protein_coding`), `HGVS = ENST...:c.25234445C>T p.(?) (chr1:g.25234445C>T)`, `Тип = transcript_type` или `missense_variant`. Онлайн: `Ensembl REST /vep/homo_sapiens/hgvs` с тем же приоритетом.
- **VAF** — из `clean_variants.csv` как `65.02%`, `PAF` — пусто (по запросу).
- **ACMG** — `ClinVar` + `InterVar` placeholder (`Uncertain significance` без локальной БД, требует ручной курации для `Pathogenic`).
- **AMP/Онкогенность** — `OncoKB` (`pan-cancer` / `All Solid Tumors` по умолчанию, `LEVEL_1→Tier I` и т.д.) + `CIViC` fallback; в `--offline` пропускается (пусто). Большие списки (>500) пропускают OncoKB с предупреждением — фильтруйте по `VAF` перед онлайн.
- **Excel** — `openpyxl`, цвет по `ACMG`/`AMP`/`Onco`, автофильтр, заморозка, ширина колонок. При `VAF>10%` из 3501 → ~318 строк (1:1), с `--all-transcripts` → ~3018.
## 4. Синтетические данные для проверки
```bash
.venv/bin/python make_test_data.py --outdir test_data/damaged --damage-rate 0.5
.venv/bin/python make_test_data.py --outdir test_data/clean --damage-rate 0.0
.venv/bin/python ffpe_damage_v2.py \
--bam test_data/damaged/sample.sorted.bam \
--reference test_data/damaged/synthetic_reference.fa \
--outdir test_data/damaged/out
```
Встроенный ground truth:
- damaged: C>T у 5-конца forward-ридов (R1), G>A у 3-конца reverse-ридов (R2), плюс один «настоящий» гетерозиготный SNP без read-position/strand bias — его инструмент **не** должен классифицировать как дезаминирование;
- clean: только фоновая ошибка секвенирования.
Ожидаемые результаты на damaged:
- R1, 5-профиль: C>T ~48% на позициях 1–5, ~0 дальше;
- R2, 3-профиль: G>A ~50% в последних 5 основаниях, ~0 дальше;
- SNP на `chr1:2504`: VAF ~50%, середина read, обе цепи, без strand bias.
## Оговорки
- Это QC/исследовательский инструмент, а не валидированный клинический assay.
- C>T/G>A в обычном alignment нельзя автоматически интерпретировать как химическое дезаминирование при поиске соматических вариантов: нужны read-position/strand/base-quality, а в идеале — matched normal и сопоставление с variant calls.
- Сравнивать нужно только образцы одного протокола подготовки библиотеки.