<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://geocarvalho.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://geocarvalho.github.io/" rel="alternate" type="text/html" /><updated>2026-06-24T01:12:46+00:00</updated><id>https://geocarvalho.github.io/feed.xml</id><title type="html">George Carvalho</title><subtitle>Healthcare Data Scientist and Bioinformatician portfolio and blog. Projects, experience, and thoughts on technology.</subtitle><entry><title type="html">Simple Text-to-Speech: Converting Text Files to Audio with Python</title><link href="https://geocarvalho.github.io/tools/2026/01/21/simple-txt-to-speech-convert-text-to-audio.html" rel="alternate" type="text/html" title="Simple Text-to-Speech: Converting Text Files to Audio with Python" /><published>2026-01-21T00:00:00+00:00</published><updated>2026-01-21T00:00:00+00:00</updated><id>https://geocarvalho.github.io/tools/2026/01/21/simple-txt-to-speech-convert-text-to-audio</id><content type="html" xml:base="https://geocarvalho.github.io/tools/2026/01/21/simple-txt-to-speech-convert-text-to-audio.html"><![CDATA[<p>I often find myself wanting to listen to articles, notes, or documentation while commuting or exercising. Copy-pasting text into online TTS services felt clunky, so I built <a href="https://github.com/geocarvalho/simple-txt-to-speech">simple-txt-to-speech</a> — a minimal command-line tool that converts any text file to MP3 using Microsoft Edge’s neural TTS voices.</p>

<h2 id="why-edge-tts">Why Edge TTS?</h2>

<p>Most TTS APIs are either paid (Google Cloud, AWS Polly) or produce robotic-sounding audio. Microsoft Edge TTS offers high-quality neural voices for free, with support for dozens of languages including Brazilian Portuguese. The <a href="https://pypi.org/project/edge-tts/">edge-tts</a> Python package wraps this service cleanly, making it easy to integrate.</p>

<h2 id="how-it-works">How It Works</h2>

<p>The tool reads a <code class="language-plaintext highlighter-rouge">.txt</code> file, sends the text to Edge TTS, and saves the resulting audio as <code class="language-plaintext highlighter-rouge">.mp3</code>. Under the hood it is straightforward:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Text File (.txt)
    │
    ├── Read &amp; validate UTF-8 content
    │
    ├── Edge TTS API
    │     ├── Neural voice synthesis
    │     └── Streaming audio chunks
    │
    └── Output MP3 file
</code></pre></div></div>

<h2 id="quick-start">Quick Start</h2>

<p>Install and run in three steps:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/geocarvalho/simple-txt-to-speech.git
<span class="nb">cd </span>simple-txt-to-speech
pip <span class="nb">install </span>edge-tts
</code></pre></div></div>

<p>Basic conversion with default settings:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python bin/reader.py <span class="nt">-i</span> my_article.txt <span class="nt">-o</span> my_article.mp3
</code></pre></div></div>

<h2 id="key-features">Key Features</h2>

<p><strong>Customizable speech rate</strong> — speed up or slow down the output by a percentage. I usually set <code class="language-plaintext highlighter-rouge">+15%</code> for articles I already know well and <code class="language-plaintext highlighter-rouge">-10%</code> for dense technical content:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python bin/reader.py <span class="nt">-i</span> notes.txt <span class="nt">-o</span> notes.mp3 <span class="nt">-r</span> 15
</code></pre></div></div>

<p><strong>Multi-language voice selection</strong> — list available voices filtered by locale. This is great for language learning or creating content in Portuguese:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python bin/reader.py <span class="nt">--list-voices</span> <span class="nt">--locale</span> pt-BR
</code></pre></div></div>

<p>Some voice options I use regularly:</p>

<table>
  <thead>
    <tr>
      <th>Voice</th>
      <th>Language</th>
      <th>Gender</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">en-US-GuyNeural</code></td>
      <td>American English</td>
      <td>Male</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">en-US-AriaNeural</code></td>
      <td>American English</td>
      <td>Female</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pt-BR-FranciscaNeural</code></td>
      <td>Brazilian Portuguese</td>
      <td>Female</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">en-GB-SoniaNeural</code></td>
      <td>British English</td>
      <td>Female</td>
    </tr>
  </tbody>
</table>

<p><strong>Progress tracking</strong> — a real-time progress bar shows conversion status, useful for longer texts:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[████████████████████░░░░░░░░░░░░░░░░░░] 60% (1.2 MB)
✓ Success! Audio saved to: result/result.mp3
</code></pre></div></div>

<h2 id="use-cases">Use Cases</h2>

<ul>
  <li><strong>Commute listening</strong> — convert blog posts or papers to audio for the drive</li>
  <li><strong>Accessibility</strong> — make text content available as audio for visually impaired users</li>
  <li><strong>Language learning</strong> — generate native-sounding audio from vocabulary lists or texts</li>
  <li><strong>Proofreading</strong> — hearing your own writing read back helps catch awkward phrasing</li>
</ul>

<h2 id="whats-next">What’s Next</h2>

<p>Some ideas for future improvements:</p>

<ul>
  <li>Support for PDF and Markdown input formats</li>
  <li>Chapter splitting for long texts</li>
  <li>Batch processing of multiple files</li>
  <li>A simple web interface</li>
</ul>

<p>The tool is intentionally minimal — a single Python script with one dependency. Check out the <a href="https://github.com/geocarvalho/simple-txt-to-speech">repository</a> for the full source and feel free to contribute.</p>]]></content><author><name></name></author><category term="tools" /><category term="python" /><category term="text-to-speech" /><category term="tts" /><category term="cli" /><category term="edge-tts" /><category term="accessibility" /><summary type="html"><![CDATA[I often find myself wanting to listen to articles, notes, or documentation while commuting or exercising. Copy-pasting text into online TTS services felt clunky, so I built simple-txt-to-speech — a minimal command-line tool that converts any text file to MP3 using Microsoft Edge’s neural TTS voices.]]></summary></entry><entry><title type="html">Building a Single-Cell RNA-seq Pipeline on AWS HealthOmics</title><link href="https://geocarvalho.github.io/bioinformatics/2025/12/15/single-cell-rnaseq-pipeline-aws-healthomics.html" rel="alternate" type="text/html" title="Building a Single-Cell RNA-seq Pipeline on AWS HealthOmics" /><published>2025-12-15T00:00:00+00:00</published><updated>2025-12-15T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2025/12/15/single-cell-rnaseq-pipeline-aws-healthomics</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2025/12/15/single-cell-rnaseq-pipeline-aws-healthomics.html"><![CDATA[<p>After building the <a href="/bioinformatics/2025/08/28/building-rnaseq-pipeline-rare-diseases-aws-healthomics.html">bulk RNA-seq</a> and <a href="/bioinformatics/2025/10/29/building-wgs-pipeline-rare-diseases-aws-healthomics.html">WGS</a> pipelines at the <a href="https://uclanelsonlab.github.io/">UCLA Nelson Lab</a>, the next step was single-cell. The <a href="https://github.com/uclanelsonlab/nl-snrna-seq_wf">nl-snrna-seq_wf</a> pipeline processes 10x Genomics single-cell and single-nucleus RNA-seq data on <a href="https://aws.amazon.com/healthomics/">AWS HealthOmics</a>, following the same Nextflow + Docker + ECR pattern as the other pipelines.</p>

<p>Single-cell RNA-seq is a different beast from bulk RNA-seq. Instead of measuring the average expression across millions of cells, it captures the transcriptome of individual cells. This is powerful for rare disease research because it reveals cell-type-specific expression patterns, identifies rare cell populations, and can expose disease mechanisms that are diluted away in bulk data.</p>

<h2 id="pipeline-overview">Pipeline Overview</h2>

<p>The pipeline is intentionally lean compared to the bulk RNA-seq and WGS pipelines. Cell Ranger, the 10x Genomics analysis suite, handles the heavy lifting — alignment, cell calling, UMI deduplication, and gene-barcode matrix generation. The pipeline wraps it with QC and storage optimization:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FASTQ R1/R2
    │
    ├── FastP (QC assessment)
    │
    ├── Cell Ranger Count
    │     ├── Alignment to transcriptome
    │     ├── Cell calling &amp; UMI counting
    │     ├── Gene-barcode matrices
    │     ├── Loupe Browser file
    │     └── BAM (optional)
    │           └── Samtools BAM → CRAM
    │
    └── MultiQC (aggregated report)
</code></pre></div></div>

<h2 id="why-keep-it-simple">Why Keep It Simple?</h2>

<p>The bulk RNA-seq pipeline has 15+ tools. This one has four. That’s by design.</p>

<p>Cell Ranger is an opinionated, end-to-end tool from 10x Genomics. It handles alignment (using STAR internally), barcode error correction, UMI counting, cell/empty droplet classification, and secondary analysis (clustering, t-SNE, UMAP) all in one step. Trying to replicate or replace parts of this with individual tools (like STARsolo + custom barcode handling) adds complexity without clear benefits for our use case.</p>

<p>The downstream single-cell analysis — normalization, clustering, differential expression, cell type annotation — happens interactively in R (Seurat) or Python (Scanpy) by the researchers, not in the pipeline. The pipeline’s job is to go from raw FASTQ to clean count matrices reliably and reproducibly.</p>

<h2 id="the-tools">The Tools</h2>

<h3 id="fastp--quality-assessment">FastP — Quality Assessment</h3>

<p><a href="https://github.com/OpenGene/fastp">FastP</a> runs in <strong>QC-only mode</strong> — no trimming is applied. This is important: Cell Ranger expects untrimmed reads with the full 10x barcode and UMI structure intact. Trimming could damage the barcode sequence in Read 1 or the UMI, breaking cell assignment. FastP is here purely to assess read quality and generate reports that feed into MultiQC.</p>

<h3 id="cell-ranger-count--the-core">Cell Ranger Count — The Core</h3>

<p><a href="https://support.10xgenomics.com/">Cell Ranger</a> <code class="language-plaintext highlighter-rouge">count</code> is the primary analysis step. Given paired FASTQ files and a transcriptome reference, it:</p>

<ol>
  <li><strong>Extracts barcodes and UMIs</strong> from Read 1 (the structure depends on the chemistry version).</li>
  <li><strong>Aligns</strong> Read 2 to the transcriptome using an embedded STAR aligner.</li>
  <li><strong>Corrects barcode errors</strong> using a whitelist of valid barcodes for the specific chemistry.</li>
  <li><strong>Deduplicates UMIs</strong> to remove PCR duplicates at the molecular level.</li>
  <li><strong>Calls cells</strong> — distinguishes real cells from empty droplets using an algorithm that looks at the total UMI count distribution.</li>
  <li><strong>Generates count matrices</strong> — both raw (all barcodes) and filtered (cells only), in HDF5 format.</li>
  <li><strong>Runs secondary analysis</strong> — PCA, clustering, t-SNE/UMAP, and differential expression.</li>
  <li><strong>Creates a Loupe file</strong> for interactive visualization in the 10x Loupe Browser.</li>
</ol>

<p>The pipeline supports several key parameters:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">chemistry</code></strong>: Auto-detected by default, but can be forced to a specific 10x chemistry version (SC3Pv3, SC5P-R2, etc.) when auto-detection fails or for non-standard libraries.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">expect_cells</code></strong>: The expected number of recovered cells (default 5000). This guides the cell calling algorithm — setting it too low might filter out real cells, too high might include empty droplets.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">create_bam</code></strong>: When <code class="language-plaintext highlighter-rouge">true</code>, Cell Ranger produces a BAM file with cell barcode and UMI tags, which the pipeline then converts to CRAM. When <code class="language-plaintext highlighter-rouge">false</code> (default), alignment files are skipped entirely, saving significant compute time and storage.</li>
</ul>

<p>Cell Ranger is the most resource-intensive step, requiring 32 CPUs and 128 GB of RAM.</p>

<h3 id="samtools--bam-to-cram-conversion">Samtools — BAM to CRAM Conversion</h3>

<p>When <code class="language-plaintext highlighter-rouge">create_bam</code> is enabled, <a href="http://www.htslib.org/">Samtools</a> converts the Cell Ranger BAM to CRAM format using the reference genome extracted from the transcriptome directory. This provides 40-60% file size reduction. The original BAM is <strong>not</strong> published to S3 — only the CRAM makes it to the output, saving storage costs.</p>

<p>This is optional because many downstream single-cell analyses only need the count matrices, not the alignments. But when you need alignments — for example, to run <a href="http://velocyto.org/">Velocyto</a> for RNA velocity analysis, or to inspect specific loci in IGV — having the CRAM available avoids re-running Cell Ranger.</p>

<h3 id="multiqc--aggregated-reporting">MultiQC — Aggregated Reporting</h3>

<p><a href="https://multiqc.info/">MultiQC</a> collects FastP and Cell Ranger metrics into a single HTML report. The Cell Ranger web summary is already comprehensive, but MultiQC provides a standardized view that’s consistent with our bulk RNA-seq and WGS pipeline reports, making it easier to assess quality across different assay types.</p>

<h2 id="aws-healthomics-deployment">AWS HealthOmics Deployment</h2>

<p>Same pattern as the other pipelines — zip the repository, import as a private workflow, and launch runs with <code class="language-plaintext highlighter-rouge">run_parameters.json</code> pointing to S3 paths:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /path/to/nl-snrna-seq_wf/
<span class="nb">rm </span>nl-snrna-seq_wf.zip<span class="p">;</span> zip <span class="nt">-r</span> nl-snrna-seq_wf.zip <span class="k">*</span>
</code></pre></div></div>

<p>Docker images live in ECR, and outputs go to <code class="language-plaintext highlighter-rouge">/mnt/workflow/pubdir</code> → S3. The transcriptome reference (e.g., <code class="language-plaintext highlighter-rouge">refdata-gex-GRCh38-2024-A</code>) also lives in S3 and is mounted into the Cell Ranger process.</p>

<h2 id="output-structure">Output Structure</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>output/
├── QC/
│   ├── &lt;sample&gt;_fastp.html
│   ├── &lt;sample&gt;_fastp.json
│   └── multiqc_report.html
└── CELLRANGER/
    ├── web_summary.html
    ├── metrics_summary.csv
    ├── filtered_feature_bc_matrix.h5
    ├── raw_feature_bc_matrix.h5
    ├── molecule_info.h5
    ├── cloupe.cloupe
    ├── &lt;sample&gt;.cram (if create_bam: true)
    ├── &lt;sample&gt;.cram.crai (if create_bam: true)
    └── analysis/
</code></pre></div></div>

<p>The key outputs for downstream analysis:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">filtered_feature_bc_matrix.h5</code></strong>: The filtered count matrix — this is what goes into Seurat or Scanpy.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">raw_feature_bc_matrix.h5</code></strong>: The raw matrix with all barcodes, useful for custom cell calling with tools like <a href="https://github.com/broadinstitute/CellBender">CellBender</a> or <a href="https://bioconductor.org/packages/DropletUtils/">EmptyDrops</a>.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">molecule_info.h5</code></strong>: Per-molecule information needed for sample aggregation (<code class="language-plaintext highlighter-rouge">cellranger aggr</code>) across multiple samples.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">cloupe.cloupe</code></strong>: For quick interactive exploration in the 10x Loupe Browser before diving into code-based analysis.</li>
</ul>

<h2 id="how-it-fits-with-the-other-pipelines">How It Fits With the Other Pipelines</h2>

<p>The three pipelines at the Nelson Lab form a complementary toolkit for rare disease genomics:</p>

<table>
  <thead>
    <tr>
      <th>Pipeline</th>
      <th>Assay</th>
      <th>What It Reveals</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://github.com/uclanelsonlab/nl-wgs_wf">nl-wgs_wf</a></td>
      <td>Whole Genome Sequencing</td>
      <td>All variant types genome-wide: SNVs, SVs, CNVs, repeat expansions</td>
    </tr>
    <tr>
      <td><a href="https://github.com/uclanelsonlab/nl-rna-seq_wf">nl-rna-seq_wf</a></td>
      <td>Bulk RNA-seq</td>
      <td>Aberrant expression, aberrant splicing, intron retention across tissue</td>
    </tr>
    <tr>
      <td><a href="https://github.com/uclanelsonlab/nl-snrna-seq_wf">nl-snrna-seq_wf</a></td>
      <td>Single-cell/nucleus RNA-seq</td>
      <td>Cell-type-specific expression, rare cell populations, cellular heterogeneity</td>
    </tr>
  </tbody>
</table>

<p>A pathogenic variant found in WGS might show reduced expression in bulk RNA-seq, and single-cell data can reveal whether that effect is cell-type-specific — for example, only affecting motor neurons but not glial cells. This multi-omics approach is increasingly important for solving the hardest undiagnosed cases.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<p><strong>Don’t fight Cell Ranger.</strong> For 10x data, Cell Ranger is the path of least resistance. The alternative (STARsolo + custom scripts) can work but requires careful handling of barcode whitelists, chemistry detection, and cell calling — all of which Cell Ranger handles robustly.</p>

<p><strong>Skip the BAM by default.</strong> Most single-cell workflows only need count matrices. Making BAM generation optional (<code class="language-plaintext highlighter-rouge">create_bam: false</code>) cuts runtime and storage significantly. Only enable it when you actually need alignments.</p>

<p><strong>Don’t trim 10x reads.</strong> This is a common mistake. The barcode + UMI structure in Read 1 must be intact for Cell Ranger. FastP runs in assessment-only mode for this reason.</p>

<p><strong>Keep the pipeline focused.</strong> The temptation is to add Seurat/Scanpy analysis into the pipeline. Resist it — single-cell analysis is inherently interactive and exploratory. The pipeline should produce clean, reproducible count matrices and let researchers drive the analysis.</p>

<hr />

<p>The pipeline is open source at <a href="https://github.com/uclanelsonlab/nl-snrna-seq_wf">github.com/uclanelsonlab/nl-snrna-seq_wf</a>. Contributions and feedback are welcome.</p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="single-cell" /><category term="rnaseq" /><category term="nextflow" /><category term="aws" /><category term="pipeline" /><category term="10x-genomics" /><summary type="html"><![CDATA[After building the bulk RNA-seq and WGS pipelines at the UCLA Nelson Lab, the next step was single-cell. The nl-snrna-seq_wf pipeline processes 10x Genomics single-cell and single-nucleus RNA-seq data on AWS HealthOmics, following the same Nextflow + Docker + ECR pattern as the other pipelines.]]></summary></entry><entry><title type="html">Building a WGS Pipeline for Rare Diseases on AWS HealthOmics</title><link href="https://geocarvalho.github.io/bioinformatics/2025/10/29/building-wgs-pipeline-rare-diseases-aws-healthomics.html" rel="alternate" type="text/html" title="Building a WGS Pipeline for Rare Diseases on AWS HealthOmics" /><published>2025-10-29T00:00:00+00:00</published><updated>2025-10-29T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2025/10/29/building-wgs-pipeline-rare-diseases-aws-healthomics</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2025/10/29/building-wgs-pipeline-rare-diseases-aws-healthomics.html"><![CDATA[<p>Alongside the <a href="/bioinformatics/2025/08/28/building-rnaseq-pipeline-rare-diseases-aws-healthomics.html">RNA-seq pipeline</a> I built at the <a href="https://uclanelsonlab.github.io/">UCLA Nelson Lab</a>, I also developed a whole genome sequencing (WGS) pipeline for germline short-read data: <a href="https://github.com/uclanelsonlab/nl-wgs_wf">nl-wgs_wf</a>. Like its RNA-seq counterpart, it’s written in Nextflow (DSL2), runs on <a href="https://aws.amazon.com/healthomics/">AWS HealthOmics</a>, and is designed for rare disease diagnostics. This post covers the architecture, tool choices, and design decisions behind it.</p>

<h2 id="pipeline-architecture">Pipeline Architecture</h2>

<p>One key design goal was flexibility in input formats. Clinical labs send data in different states — sometimes raw FASTQ, sometimes already-aligned BAM or CRAM files. Instead of forcing a single entry point, the pipeline uses <strong>subworkflows</strong> that converge into a shared analysis path:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Inputs
  ├── FASTQ R1/R2 → Fastp → BWA-MEM2 → Sort
  ├── BAM ──────────────────────────── → Sort
  └── CRAM ─────── → CRAM-to-BAM ──── → Sort
                                          │
                              ┌────────────┘
                              ▼
                     Picard MarkDuplicates
                     Samtools Index
                              │
              ┌───────────────┼───────────────────┐
              ▼               ▼                   ▼
         QC &amp; Metrics    Variant Calling    SV &amp; Repeat Analysis
              │               │                   │
              ▼               ▼                   ▼
        ┌─────────┐    ┌───────────┐    ┌─────────────────┐
        │ Picard   │    │DeepVariant│    │ Manta (SVs)     │
        │ Qualimap │    │    ↓      │    │ CNVpytor (CNVs) │
        │ Mosdepth │    │ AutoMap   │    │ ExpansionHunter │
        │ MultiQC  │    │ (ROH)    │    │ EH Denovo       │
        └─────────┘    │    ↓      │    └─────────────────┘
                        │ BCFtools  │
                        │    ↓      │
                        │ HapCUT2   │
                        │ (phasing) │
                        └───────────┘
                              │
                              ▼
                        BAM → CRAM
</code></pre></div></div>

<p>This subworkflow architecture means you can re-analyze existing alignments without re-running the expensive alignment step, and CRAM files received from external labs can enter the pipeline directly.</p>

<h2 id="running-on-aws-healthomics">Running on AWS HealthOmics</h2>

<p>The pipeline is structured to run as a private workflow on AWS HealthOmics. Deployment is straightforward:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /path/to/nl-wgs_wf/
<span class="nb">rm </span>nl-wgs_wf.zip<span class="p">;</span> zip <span class="nt">-r</span> nl-wgs_wf.zip <span class="k">*</span>
</code></pre></div></div>

<p>You import the zip file into HealthOmics along with <code class="language-plaintext highlighter-rouge">parameters.json</code> for the parameter definitions, and use <code class="language-plaintext highlighter-rouge">run_parameters.json</code> to specify S3 paths when launching runs. All Docker images live in AWS ECR, and outputs go to <code class="language-plaintext highlighter-rouge">/mnt/workflow/pubdir</code> which syncs back to S3.</p>

<p>The same benefits from the RNA-seq pipeline apply here: managed compute, HIPAA compliance, no infrastructure to maintain, and native Nextflow support.</p>

<h2 id="the-tools-and-why-theyre-there">The Tools and Why They’re There</h2>

<h3 id="fastp--quality-control">Fastp — Quality Control</h3>

<p><a href="https://github.com/OpenGene/fastp">Fastp</a> handles adapter trimming and quality filtering on the raw FASTQ input. Same reasons as in the RNA-seq pipeline: it’s fast, single-pass, and produces JSON output that feeds directly into MultiQC.</p>

<h3 id="bwa-mem2--alignment">BWA-MEM2 — Alignment</h3>

<p><a href="https://github.com/bwa-mem2/bwa-mem2">BWA-MEM2</a> is the SIMD-accelerated successor to BWA-MEM. For WGS data with billions of reads, the speed improvement over the original BWA-MEM is significant — roughly 2-3x faster on modern hardware. It produces identical alignments to BWA-MEM but takes better advantage of CPU vector instructions. Read group information is added during alignment so samples are properly identified throughout the pipeline.</p>

<h3 id="picard--duplicate-marking-and-metrics">Picard — Duplicate Marking and Metrics</h3>

<p>We use <a href="https://broadinstitute.github.io/picard/">Picard</a> for multiple purposes:</p>

<ul>
  <li><strong>MarkDuplicates</strong>: Flags PCR duplicates. For WGS (unlike our RNA-seq pipeline where we used Sambamba), we chose Picard here because it integrates better with the GATK/DeepVariant ecosystem and we already need Picard for metrics collection.</li>
  <li><strong>CollectMultipleMetrics</strong>: Alignment summary, insert size distribution, quality score distribution, and more.</li>
  <li><strong>CollectWgsMetrics</strong>: WGS-specific metrics including coverage uniformity, mean coverage, and the percentage of bases reaching various depth thresholds.</li>
</ul>

<p>These metrics are critical for assessing whether a genome has sufficient quality and coverage for clinical interpretation.</p>

<h3 id="qualimap--bam-quality-control">Qualimap — BAM Quality Control</h3>

<p><a href="http://qualimap.conesalab.org/">Qualimap</a> provides additional BAM-level QC including coverage across chromosomes, GC content distribution, and mapping quality statistics. It complements Picard metrics with different visualizations and is particularly useful for spotting coverage anomalies.</p>

<h3 id="deepvariant--snvindel-calling">DeepVariant — SNV/Indel Calling</h3>

<p><a href="https://github.com/google/deepvariant">DeepVariant</a> is our primary variant caller for SNVs and small indels. Unlike traditional callers like GATK HaplotypeCaller, DeepVariant uses a deep learning model trained on truth sets to evaluate evidence for variants. It consistently performs well in <a href="https://precision.fda.gov/challenges">precisionFDA Truth Challenges</a> and produces both VCF and gVCF outputs.</p>

<p>For WGS we use the standard WGS model (unlike the RNA-seq pipeline where we needed a custom model). DeepVariant is the most resource-intensive step at 192 GB RAM and 48 CPUs, but produces high-quality variant calls.</p>

<h3 id="automap--runs-of-homozygosity">AutoMap — Runs of Homozygosity</h3>

<p><a href="https://github.com/mquinodo/AutoMap">AutoMap</a> detects runs of homozygosity (ROH) from the DeepVariant VCF output. ROH regions are important in rare disease because:</p>

<ul>
  <li>They can indicate <strong>consanguinity</strong>, which increases the likelihood of autosomal recessive conditions.</li>
  <li>Homozygous pathogenic variants in ROH regions are strong candidates for disease causation.</li>
  <li>Large ROH blocks may point to <strong>uniparental disomy</strong> (UPD).</li>
</ul>

<p>AutoMap outputs both TSV files with ROH coordinates and PDF visualizations across the genome.</p>

<h3 id="hapcut2--haplotype-phasing">HapCUT2 — Haplotype Phasing</h3>

<p><a href="https://github.com/vibansal/HapCUT2">HapCUT2</a> phases heterozygous variants into haplotype blocks. The phasing workflow is:</p>

<ol>
  <li><strong>BCFtools</strong> filters the DeepVariant VCF to retain only diploid genotypes (0/0, 0/1, 1/1).</li>
  <li><strong>HapCUT2 extractHAIRS</strong> extracts haplotype-informative reads from the BAM.</li>
  <li><strong>HapCUT2</strong> assembles these into phased haplotype blocks.</li>
</ol>

<p>Phasing is valuable for rare disease because it tells you which variants are on the same chromosome (cis) versus opposite chromosomes (trans). For compound heterozygous variants in a recessive gene, you need to confirm the two variants are on different alleles — phasing provides this evidence directly from the sequencing data.</p>

<h3 id="manta--structural-variant-detection">Manta — Structural Variant Detection</h3>

<p><a href="https://github.com/Illumina/manta">Manta</a> detects structural variants (SVs) including large deletions, duplications, inversions, and insertions. Manta is fast, well-validated, and specifically designed for germline analysis. It uses split-read and paired-end evidence to call SVs with high sensitivity.</p>

<p>SVs are particularly important in rare diseases because they can disrupt genes in ways not captured by SNV callers — a large deletion removing an entire exon, an inversion disrupting a gene, or a translocation creating a fusion.</p>

<h3 id="cnvpytor--copy-number-variant-analysis">CNVpytor — Copy Number Variant Analysis</h3>

<p><a href="https://github.com/abyzovlab/CNVpytor">CNVpytor</a> detects copy number variants (CNVs) from read depth signals. It analyzes coverage across the genome at multiple bin sizes to identify regions with significantly more or fewer reads than expected. CNVpytor also produces Manhattan plots for visual inspection.</p>

<p>CNVpytor complements Manta: Manta excels at breakpoint-resolved SVs from paired-end/split-read evidence, while CNVpytor catches larger CNVs that may not have clear breakpoint signatures but show clear read depth changes.</p>

<h3 id="expansionhunter-and-expansionhunterdenovo--repeat-expansions">ExpansionHunter and ExpansionHunterDenovo — Repeat Expansions</h3>

<p>Repeat expansion disorders are an important class of rare diseases (Huntington’s disease, Fragile X, various ataxias, ALS). We use two complementary tools:</p>

<ul>
  <li><a href="https://github.com/Illumina/ExpansionHunter">ExpansionHunter</a> genotypes known repeat expansion loci from a curated variant catalog. It can detect expansions beyond the read length by analyzing spanning reads, flanking reads, and in-repeat reads. This is the targeted approach — you look at known disease loci.</li>
  <li><a href="https://github.com/Illumina/ExpansionHunterDenovo">ExpansionHunterDenovo</a> takes the untargeted approach: it profiles the genome for any locus showing evidence of repeat expansion, even if it’s not in a known catalog. This is powerful for discovering novel repeat expansions in undiagnosed patients.</li>
</ul>

<h3 id="mosdepth--coverage-analysis">Mosdepth — Coverage Analysis</h3>

<p><a href="https://github.com/brentp/mosdepth">Mosdepth</a> runs on the CRAM output to calculate coverage over specific BED regions, particularly <strong>mitochondrial</strong> regions. Mitochondrial coverage in WGS data reflects the mitochondrial copy number, which can be clinically relevant, and adequate coverage is needed for mitochondrial variant calling.</p>

<h3 id="multiqc--aggregated-reporting">MultiQC — Aggregated Reporting</h3>

<p><a href="https://multiqc.info/">MultiQC</a> aggregates reports from Fastp, Picard (multiple metrics + WGS metrics), Qualimap, DeepVariant, and Mosdepth into a single HTML report for quick sample-level QC assessment.</p>

<h2 id="rna-seq-vs-wgs-complementary-pipelines">RNA-seq vs WGS: Complementary Pipelines</h2>

<p>These two pipelines were designed to work together for rare disease diagnostics:</p>

<table>
  <thead>
    <tr>
      <th>Aspect</th>
      <th>RNA-seq Pipeline</th>
      <th>WGS Pipeline</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Primary input</strong></td>
      <td>FASTQ only</td>
      <td>FASTQ, BAM, or CRAM</td>
    </tr>
    <tr>
      <td><strong>Aligner</strong></td>
      <td>STAR (splice-aware)</td>
      <td>BWA-MEM2 (linear)</td>
    </tr>
    <tr>
      <td><strong>Variant calling</strong></td>
      <td>DeepVariant (custom RNA model, CDS-restricted)</td>
      <td>DeepVariant (standard WGS model, genome-wide)</td>
    </tr>
    <tr>
      <td><strong>Unique analyses</strong></td>
      <td>Intron retention, splice junctions, transcript quantification, rRNA/globin contamination</td>
      <td>Structural variants, CNVs, repeat expansions, haplotype phasing, runs of homozygosity</td>
    </tr>
    <tr>
      <td><strong>Key strength</strong></td>
      <td>Detects expression and splicing anomalies</td>
      <td>Detects all variant types genome-wide</td>
    </tr>
  </tbody>
</table>

<p>A variant found in WGS gains evidence when RNA-seq shows it’s expressed and doesn’t cause nonsense-mediated decay. Conversely, an aberrant splicing event found in RNA-seq points to where to look in the WGS data for the causal variant.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<p><strong>Support multiple input formats from day one.</strong> Clinical collaborators send data in whatever format they have. The subworkflow architecture that accepts FASTQ, BAM, and CRAM saved us from constant format-conversion requests.</p>

<p><strong>Combine targeted and untargeted repeat analysis.</strong> ExpansionHunter catches known loci reliably, but ExpansionHunterDenovo has found novel expansions that would have been missed otherwise.</p>

<p><strong>Phasing is underused in clinical genomics.</strong> Adding HapCUT2 was a late addition but immediately proved its value for resolving compound heterozygotes without requiring parental samples.</p>

<p><strong>Complement SV callers.</strong> No single SV caller catches everything. Manta + CNVpytor gives better coverage across the SV size spectrum than either alone.</p>

<p><strong>Runs of homozygosity are a quick win.</strong> AutoMap runs fast and immediately flags consanguinity and candidate regions for recessive disease — information that’s useful even before looking at individual variants.</p>

<hr />

<p>The pipeline is open source at <a href="https://github.com/uclanelsonlab/nl-wgs_wf">github.com/uclanelsonlab/nl-wgs_wf</a>. Contributions and feedback are welcome.</p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="wgs" /><category term="nextflow" /><category term="aws" /><category term="rare-disease" /><category term="pipeline" /><summary type="html"><![CDATA[Alongside the RNA-seq pipeline I built at the UCLA Nelson Lab, I also developed a whole genome sequencing (WGS) pipeline for germline short-read data: nl-wgs_wf. Like its RNA-seq counterpart, it’s written in Nextflow (DSL2), runs on AWS HealthOmics, and is designed for rare disease diagnostics. This post covers the architecture, tool choices, and design decisions behind it.]]></summary></entry><entry><title type="html">Building an RNA-seq Pipeline for Rare Diseases on AWS HealthOmics</title><link href="https://geocarvalho.github.io/bioinformatics/2025/08/28/building-rnaseq-pipeline-rare-diseases-aws-healthomics.html" rel="alternate" type="text/html" title="Building an RNA-seq Pipeline for Rare Diseases on AWS HealthOmics" /><published>2025-08-28T00:00:00+00:00</published><updated>2025-08-28T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2025/08/28/building-rnaseq-pipeline-rare-diseases-aws-healthomics</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2025/08/28/building-rnaseq-pipeline-rare-diseases-aws-healthomics.html"><![CDATA[<p>Over the past couple of years at the <a href="https://uclanelsonlab.github.io/">UCLA Nelson Lab</a>, I built and maintained a comprehensive RNA-seq analysis pipeline designed specifically for rare disease diagnostics. The pipeline, <a href="https://github.com/uclanelsonlab/nl-rna-seq_wf">nl-rna-seq_wf</a>, is written in Nextflow (DSL2) and runs on <a href="https://aws.amazon.com/healthomics/">AWS HealthOmics</a> — Amazon’s managed service for genomics workflows. In this post I want to walk through the architecture, explain why each tool was chosen, and share some lessons learned from running production genomics in the cloud.</p>

<h2 id="why-aws-healthomics">Why AWS HealthOmics?</h2>

<p>AWS HealthOmics is a purpose-built service for storing, querying, and analyzing genomic data at scale. We chose it for several reasons:</p>

<ul>
  <li><strong>Managed infrastructure</strong>: No need to provision or manage EC2 instances, auto-scaling groups, or job schedulers. HealthOmics handles compute orchestration for you.</li>
  <li><strong>Native Nextflow support</strong>: HealthOmics supports Nextflow workflows natively as a “private workflow”, so we could keep the same DSL2 codebase we developed locally.</li>
  <li><strong>ECR integration</strong>: All Docker images are stored in AWS Elastic Container Registry (ECR), which HealthOmics pulls from directly, avoiding Docker Hub rate limits and ensuring reproducibility.</li>
  <li><strong>S3 storage</strong>: Input FASTQ files, reference genomes, and STAR indices live in S3 buckets. Outputs are written to <code class="language-plaintext highlighter-rouge">/mnt/workflow/pubdir</code>, which HealthOmics automatically syncs back to S3.</li>
  <li><strong>HIPAA compliance</strong>: Since we process clinical patient data from programs like the <a href="https://undiagnosed.hms.harvard.edu/">Undiagnosed Diseases Network (UDN)</a>, HealthOmics provides a compliant environment out of the box.</li>
</ul>

<p>The main trade-off is that HealthOmics has its own constraints — fixed compute tiers, specific Nextflow version requirements, and some limitations on how processes can communicate. But for a production clinical pipeline, the reliability and compliance benefits far outweigh these.</p>

<h2 id="pipeline-overview">Pipeline Overview</h2>

<p>The pipeline takes paired-end RNA-seq FASTQ files and produces a comprehensive set of outputs for rare disease analysis:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FASTQ R1/R2
    │
    ├── Fastp (QC + trimming)
    │     ├── BWA → rRNA contamination check
    │     └── BWA → Globin RNA contamination check
    │
    ├── STAR (splice-aware alignment)
    │     ├── Samtools Index
    │     └── Sambamba MarkDup
    │           ├── FeatureCounts (gene counts)
    │           ├── RNA-SeQC (QC metrics)
    │           ├── Qualimap (QC metrics)
    │           ├── IRFinder (intron retention)
    │           ├── Mosdepth (coverage analysis)
    │           │     └── Bedtools (CDS filtering)
    │           │           └── DeepVariant (variant calling)
    │           ├── BAM2SJ (splice junctions)
    │           └── Samtools CRAM (compression)
    │                 └── Mosdepth BED (XBP1 + MT coverage)
    │
    ├── Kallisto (transcript quantification)
    │
    └── MultiQC (aggregated report)
</code></pre></div></div>

<h2 id="the-tools-and-why-theyre-there">The Tools and Why They’re There</h2>

<h3 id="fastp--quality-control-and-trimming">Fastp — Quality Control and Trimming</h3>

<p><a href="https://github.com/OpenGene/fastp">Fastp</a> is the first step: adapter trimming, quality filtering, and length filtering. We chose fastp over alternatives like Trimmomatic or Cutadapt because it’s significantly faster (written in C++), produces clean HTML/JSON reports, and handles paired-end reads natively in a single pass. The JSON output feeds directly into MultiQC for aggregated reporting.</p>

<h3 id="bwa--contamination-detection-rrna-and-globin-rna">BWA — Contamination Detection (rRNA and Globin RNA)</h3>

<p>Before alignment, we check for two common sources of contamination in blood-derived RNA samples:</p>

<ul>
  <li><strong>rRNA contamination</strong>: Ribosomal RNA should be depleted during library prep, but incomplete depletion wastes sequencing capacity. We align trimmed reads against an rRNA reference using <a href="https://github.com/lh3/bwa">BWA-MEM</a> and compute flagstats to get the percentage of reads mapping to rRNA.</li>
  <li><strong>Globin RNA contamination</strong>: Blood samples are dominated by hemoglobin transcripts. If globin depletion wasn’t performed (or failed), a large fraction of reads will be globin. Same approach: align against a globin reference and check flagstats.</li>
</ul>

<p>High contamination rates in either category are a red flag that the sample may need to be re-prepped or that results should be interpreted with caution.</p>

<h3 id="star--splice-aware-alignment">STAR — Splice-Aware Alignment</h3>

<p><a href="https://github.com/alexdobin/STAR">STAR</a> is the gold standard for RNA-seq alignment. It’s splice-aware, meaning it can align reads that span exon-exon junctions — critical for RNA data. STAR also outputs gene-level read counts directly (<code class="language-plaintext highlighter-rouge">ReadsPerGene.out.tab</code>) and detects novel splice junctions.</p>

<p>One interesting aspect of our pipeline is that we maintain <strong>six different STAR indices</strong> for read lengths of 69, 75, 100, 120, 150, and 151 bp. This is because STAR’s <code class="language-plaintext highlighter-rouge">sjdbOverhang</code> parameter should ideally be <code class="language-plaintext highlighter-rouge">read_length - 1</code>, and our lab processes samples from multiple sequencing platforms and protocols. The pipeline selects the appropriate index based on the detected read length, ensuring optimal alignment regardless of the input data source.</p>

<p>STAR is by far the most resource-intensive step, requiring 192 GB of RAM and 48 CPUs — driven by the size of the human genome index in memory.</p>

<h3 id="sambamba--duplicate-marking">Sambamba — Duplicate Marking</h3>

<p><a href="https://github.com/biod/sambamba">Sambamba</a> marks PCR duplicates in the aligned BAM. We chose Sambamba over Picard’s MarkDuplicates because it’s multi-threaded and significantly faster on large files, which matters when you’re paying per minute on cloud compute. The marked BAM is the starting point for all downstream analyses.</p>

<h3 id="subread-featurecounts--gene-level-quantification">Subread FeatureCounts — Gene-Level Quantification</h3>

<p><a href="https://subread.sourceforge.net/">FeatureCounts</a> from the Subread package assigns aligned reads to genomic features (genes) based on GENCODE annotations. It produces a count matrix that feeds into downstream differential expression tools like <a href="https://bioconductor.org/packages/DESeq2/">DESeq2</a> or aberrant expression detection with <a href="https://bioconductor.org/packages/OUTRIDER/">OUTRIDER</a>. FeatureCounts is fast, handles multi-mapping reads well, and works at the exon level which gives us flexibility.</p>

<h3 id="kallisto--transcript-level-quantification">Kallisto — Transcript-Level Quantification</h3>

<p><a href="https://pachterlab.github.io/kallisto/">Kallisto</a> provides transcript-level abundance estimates using pseudoalignment — it doesn’t perform traditional alignment at all, instead using k-mer matching against a transcriptome index. This makes it extremely fast and complementary to STAR+FeatureCounts. We use Kallisto’s transcript-level quantification for analyses that need isoform resolution, while FeatureCounts handles gene-level counting.</p>

<h3 id="rna-seqc--quality-assessment">RNA-SeQC — Quality Assessment</h3>

<p><a href="https://github.com/getzlab/rnaseqc">RNA-SeQC</a> from the Getz Lab at the Broad Institute computes a comprehensive set of quality metrics: mapping rates, rRNA rates, exonic/intronic/intergenic ratios, 3’/5’ coverage bias, GC bias, and gene-level TPM values. These metrics are essential for identifying problematic samples before they contaminate downstream analyses, especially when building cohort-level models for rare disease detection.</p>

<h3 id="qualimap--additional-qc">Qualimap — Additional QC</h3>

<p><a href="http://qualimap.conesalab.org/">Qualimap</a> provides additional RNA-seq-specific quality metrics and visualizations, complementing RNA-SeQC. Having multiple QC tools gives us a more complete picture of sample quality.</p>

<h3 id="irfinder--intron-retention-detection">IRFinder — Intron Retention Detection</h3>

<p><a href="https://github.com/williamritchie/IRFinder">IRFinder</a> detects intron retention events, where introns that should be spliced out are instead retained in the mature mRNA. Intron retention is increasingly recognized as a mechanism in rare diseases — it can lead to premature stop codons, nonsense-mediated decay, or altered protein function. This is particularly relevant for our work since aberrant splicing is one of the key things RNA-seq can reveal that exome/genome sequencing alone cannot.</p>

<h3 id="mosdepth--coverage-analysis">Mosdepth — Coverage Analysis</h3>

<p><a href="https://github.com/brentp/mosdepth">Mosdepth</a> calculates sequencing depth and coverage quickly from BAM/CRAM files. We run it in two modes:</p>

<ol>
  <li><strong>Genome-wide per-base coverage</strong>: This output feeds into Bedtools to identify coding regions with adequate coverage for variant calling.</li>
  <li><strong>Targeted BED regions</strong>: We specifically check coverage over the <strong>XBP1</strong> gene and <strong>mitochondrial</strong> regions. XBP1 is relevant because its unconventional splicing is a biomarker for certain conditions, and mitochondrial coverage is important for mitochondrial disease diagnostics — a significant subset of rare diseases.</li>
</ol>

<h3 id="bedtools--cds-region-filtering">Bedtools — CDS Region Filtering</h3>

<p><a href="https://bedtools.readthedocs.io/">Bedtools</a> takes the per-base coverage from Mosdepth and intersects it with GENCODE CDS (coding sequence) annotations, filtering for regions that meet a minimum coverage threshold. The resulting BED file defines the genomic regions where we have enough data to confidently call variants. This ensures DeepVariant only runs on well-covered coding regions, improving both accuracy and efficiency.</p>

<h3 id="deepvariant--variant-calling">DeepVariant — Variant Calling</h3>

<p><a href="https://github.com/google/deepvariant">DeepVariant</a> is Google’s deep learning-based variant caller. We use a <strong>custom model trained on RNA-seq data</strong> rather than the default WGS/WES models — this is important because RNA-seq has fundamentally different characteristics (splice junctions, allele-specific expression, variable coverage across exons). The pipeline accepts custom model files (<code class="language-plaintext highlighter-rouge">model_data</code>, <code class="language-plaintext highlighter-rouge">model_index</code>, <code class="language-plaintext highlighter-rouge">model_meta</code>, <code class="language-plaintext highlighter-rouge">model_info</code>) as parameters.</p>

<p>Variant calling from RNA-seq complements WGS/WES by providing expression-aware variant detection. A variant found in both DNA and RNA data, especially with biallelic expression, gives higher confidence for pathogenicity assessment.</p>

<h3 id="bam2sj--splice-junction-analysis">BAM2SJ — Splice Junction Analysis</h3>

<p>BAM2SJ reconstructs splice junctions from the aligned BAM, producing a table of junction coordinates, read support, and strand information. This output is used downstream for aberrant splicing detection with tools like <a href="https://bioconductor.org/packages/FRASER/">FRASER</a>, which identifies samples with statistically unusual splicing patterns compared to a control cohort — a powerful approach for finding disease-causing splice variants.</p>

<h3 id="samtools--cram-compression">Samtools — CRAM Compression</h3>

<p>After all analyses are complete, we convert the final BAM to <a href="https://www.ga4gh.org/cram/">CRAM</a> format using samtools. CRAM files are typically 40-60% smaller than BAM files, which significantly reduces S3 storage costs when you’re processing hundreds of samples.</p>

<h3 id="multiqc--aggregated-reporting">MultiQC — Aggregated Reporting</h3>

<p><a href="https://multiqc.info/">MultiQC</a> collects outputs from Fastp, STAR, FeatureCounts, RNA-SeQC, Qualimap, Kallisto, and the contamination checks into a single interactive HTML report. This is invaluable for quickly assessing sample quality and identifying issues across a batch of samples.</p>

<h2 id="the-rare-disease-context">The Rare Disease Context</h2>

<p>This pipeline was built to support the <a href="https://undiagnosed.hms.harvard.edu/">Undiagnosed Diseases Network (UDN)</a> and similar rare disease programs. RNA-seq adds a critical layer of evidence beyond DNA sequencing:</p>

<ul>
  <li><strong>Aberrant expression</strong>: Genes with significantly reduced expression may harbor regulatory or deep intronic variants not visible on exome.</li>
  <li><strong>Aberrant splicing</strong>: Novel or increased usage of cryptic splice sites can point to pathogenic variants affecting splicing machinery.</li>
  <li><strong>Intron retention</strong>: Retained introns can indicate splicing defects caused by variants in splice regions.</li>
  <li><strong>Allele-specific expression</strong>: Monoallelic expression of a heterozygous variant suggests the other allele may be silenced or degraded.</li>
</ul>

<p>The combination of STAR alignment, IRFinder, BAM2SJ, and the downstream OUTRIDER/FRASER analyses provides a comprehensive view of these RNA-level effects.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<p><strong>Pre-build multiple STAR indices.</strong> Different sequencing platforms produce different read lengths. Having indices ready for common lengths avoids pipeline failures and re-processing.</p>

<p><strong>Always check contamination first.</strong> A 30% rRNA contamination rate means you effectively sequenced 30% less of your transcriptome. Catching this early saves time on interpretation.</p>

<p><strong>Coverage-guided variant calling matters.</strong> Running DeepVariant on the entire genome wastes compute and produces low-confidence calls in poorly covered regions. The Mosdepth → Bedtools → DeepVariant chain focuses resources where the data supports confident calling.</p>

<p><strong>CRAM saves real money.</strong> At hundreds of samples per year, the storage savings from CRAM over BAM are significant on S3.</p>

<p><strong>AWS HealthOmics simplifies compliance but adds constraints.</strong> You trade flexibility for managed infrastructure and compliance. Worth it for clinical workflows, but be prepared to work within the platform’s limitations.</p>

<hr />

<p>The pipeline is open source and available at <a href="https://github.com/uclanelsonlab/nl-rna-seq_wf">github.com/uclanelsonlab/nl-rna-seq_wf</a>. Contributions and feedback are welcome.</p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="rnaseq" /><category term="nextflow" /><category term="aws" /><category term="rare-disease" /><category term="pipeline" /><summary type="html"><![CDATA[Over the past couple of years at the UCLA Nelson Lab, I built and maintained a comprehensive RNA-seq analysis pipeline designed specifically for rare disease diagnostics. The pipeline, nl-rna-seq_wf, is written in Nextflow (DSL2) and runs on AWS HealthOmics — Amazon’s managed service for genomics workflows. In this post I want to walk through the architecture, explain why each tool was chosen, and share some lessons learned from running production genomics in the cloud.]]></summary></entry><entry><title type="html">From CRAM to FASTQ: A Practical Guide for Bioinformaticians</title><link href="https://geocarvalho.github.io/bioinformatics/2025/07/02/from-cram-to-fastq-practical-guide.html" rel="alternate" type="text/html" title="From CRAM to FASTQ: A Practical Guide for Bioinformaticians" /><published>2025-07-02T00:00:00+00:00</published><updated>2025-07-02T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2025/07/02/from-cram-to-fastq-practical-guide</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2025/07/02/from-cram-to-fastq-practical-guide.html"><![CDATA[<p>In the world of genomics, data often comes in CRAM format to save disk space. But what if you need the original FASTQ files for downstream processing like re-alignment or variant calling with a different tool?</p>

<p>In this post, I’ll walk you through the steps to convert a CRAM file into high-quality, paired-end FASTQ files using Dockerized bioinformatics tools like samtools, fastp, and bbmap. You’ll also learn how to validate references, check file integrity, and repair read pairs.</p>

<h2 id="prerequisites">Prerequisites</h2>

<ul>
  <li>At least moderate familiarity with the command line.</li>
  <li>Docker is installed and running on your machine.</li>
  <li>Access to the corresponding FASTA reference file (critical for decompression).</li>
  <li>A CRAM file aligned to hg38 (no-alt or UCSC version depending on the lab standard).</li>
</ul>

<h2 id="step-1-identify-the-reference-genome">Step 1: Identify the Reference Genome</h2>

<p>Before working with a CRAM, you need to know exactly which reference genome was used for alignment. This is critical — using the wrong reference will cause decompression to fail or produce incorrect results.</p>

<h3 id="option-a-using-ref-solver-recommended">Option A: Using ref-solver (recommended)</h3>

<p><a href="https://github.com/fulcrumgenomics/ref-solver">ref-solver</a> is a tool by Fulcrum Genomics that identifies the exact human reference genome used to align a BAM/SAM/CRAM file. It matches the sequence dictionary against a catalog of 15+ known human reference genomes using MD5 checksums, detecting naming conventions (<code class="language-plaintext highlighter-rouge">chr1</code> vs <code class="language-plaintext highlighter-rouge">1</code>), contig sets (with/without ALT contigs), and mitochondrial sequence differences.</p>

<p>Install via cargo or conda:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo <span class="nb">install </span>ref-solver
<span class="c"># or</span>
conda <span class="nb">install</span> <span class="nt">-c</span> bioconda ref-solver
</code></pre></div></div>

<p>Then identify the reference:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ref-solver identify sample.sorted.noalt.hg38.cram
</code></pre></div></div>

<p>Example output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#1 hg38 (UCSC) (EXACT)
   ID: hg38_ucsc
   Assembly: GRCh38
   Source: UCSC
   Match Type: Exact
   Score: 100.0%
</code></pre></div></div>

<p>You can also pipe from samtools or get JSON output for scripting:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>samtools view <span class="nt">-H</span> sample.sorted.noalt.hg38.cram | ref-solver identify -

ref-solver identify sample.sorted.noalt.hg38.cram <span class="nt">--format</span> json
</code></pre></div></div>

<p>If you have a candidate reference FASTA and want to compare directly:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ref-solver score sample.sorted.noalt.hg38.cram reference.fa.fai
</code></pre></div></div>

<p>This is especially useful when you receive CRAM files from external sources (collaborators, sequencing vendors, public repositories) where the reference might be labeled generically as “GRCh38” but could be any of the many variations (UCSC, NCBI, Broad, DRAGEN, etc.). You can also explore the web UI at <a href="https://whatsmygenome.acgt.bio/">whatsmygenome.acgt.bio</a>.</p>

<h3 id="option-b-using-samtools-samples">Option B: Using samtools samples</h3>

<p>A simpler check using samtools to verify if a specific reference file matches your CRAM:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-v</span> <span class="nv">$PWD</span>:<span class="nv">$PWD</span> quay.io/biocontainers/samtools:1.22--h96c455f_0 samtools samples <span class="se">\</span>
  <span class="nt">-h</span> <span class="nt">-f</span> <span class="nv">$PWD</span>/hg38.fa <span class="nv">$PWD</span>/sample.sorted.noalt.hg38.cram
</code></pre></div></div>

<p>If you see a <code class="language-plaintext highlighter-rouge">.</code> in the last column of the output, your reference does not match.</p>

<h2 id="step-2-validate-your-cram">Step 2: Validate Your CRAM</h2>

<p>Ensure the file isn’t truncated or corrupted:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-v</span> <span class="nv">$PWD</span>:<span class="nv">$PWD</span> quay.io/biocontainers/samtools:1.22--h96c455f_0 samtools quickcheck <span class="se">\</span>
  <span class="nv">$PWD</span>/sample.sorted.noalt.hg38.cram <span class="se">\</span>
  <span class="o">&amp;&amp;</span> <span class="nb">echo </span>OK
</code></pre></div></div>

<h2 id="step-3-convert-to-fastq">Step 3: Convert to FASTQ</h2>

<p>Now convert the name-sorted BAM to paired-end FASTQ files:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-v</span> <span class="nv">$PWD</span>:<span class="nv">$PWD</span> quay.io/biocontainers/samtools:1.22--h96c455f_0 samtools fastq <span class="se">\</span>
  -@ 36 <span class="se">\</span>
  <span class="nt">--reference</span> <span class="nv">$PWD</span>/ucsc/hg38.fa <span class="se">\</span>
  <span class="nt">-1</span> <span class="nv">$PWD</span>/sample.R1.fastq.gz <span class="se">\</span>
  <span class="nt">-2</span> <span class="nv">$PWD</span>/sample.R2.fastq.gz <span class="se">\</span>
  <span class="nv">$PWD</span>/sample.sorted.noalt.hg38.cram
</code></pre></div></div>

<h2 id="step-4-repair-paired-end-reads">Step 4: Repair Paired-end Reads</h2>

<p>Trimming or filtering can sometimes desynchronize paired-end reads. Fix that with bbmap’s <code class="language-plaintext highlighter-rouge">repair.sh</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-v</span> <span class="nv">$PWD</span>:<span class="nv">$PWD</span> quay.io/biocontainers/bbmap:39.26--he5f24ec_0 repair.sh <span class="se">\</span>
    <span class="nv">in1</span><span class="o">=</span><span class="nv">$PWD</span>/sample.R1.fastq.gz <span class="se">\</span>
    <span class="nv">in2</span><span class="o">=</span><span class="nv">$PWD</span>/sample.R2.fastq.gz <span class="se">\</span>
    <span class="nv">out1</span><span class="o">=</span><span class="nv">$PWD</span>/sample_R1.fixed.fastq.gz <span class="se">\</span>
    <span class="nv">out2</span><span class="o">=</span><span class="nv">$PWD</span>/sample_R2.fixed.fastq.gz <span class="se">\</span>
    <span class="nv">outs</span><span class="o">=</span><span class="nv">$PWD</span>/sample_singletons.fastq.gz repair
</code></pre></div></div>

<h2 id="step-5-check-fastq-with-fastp">Step 5: Check FASTQ with Fastp</h2>

<p>Use fastp to trim adapters, filter low-quality reads, and generate QC reports.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-v</span> <span class="nv">$PWD</span>:<span class="nv">$PWD</span> quay.io/biocontainers/fastp:1.0.0--heae3180_0 fastp <span class="se">\</span>
  <span class="nt">-w</span> 36 <span class="se">\</span>
  <span class="nt">-i</span> <span class="nv">$PWD</span>/sample_R1.fixed.fastq.gz <span class="se">\</span>
  <span class="nt">-I</span> <span class="nv">$PWD</span>/sample_R2.fixed.fastq.gz <span class="se">\</span>
  <span class="nt">-o</span> <span class="nv">$PWD</span>/results/sample_R1.fastp.fastq.gz <span class="se">\</span>
  <span class="nt">-O</span> <span class="nv">$PWD</span>/results/sample_R2.fastp.fastq.gz <span class="se">\</span>
  <span class="nt">-j</span> <span class="nv">$PWD</span>/results/sample.fastp.json <span class="se">\</span>
  <span class="nt">-h</span> <span class="nv">$PWD</span>/results/sample.fastp.html <span class="se">\</span>
  <span class="nt">--detect_adapter_for_pe</span>
</code></pre></div></div>

<h2 id="final-output">Final Output</h2>

<p>After these steps, you will have:</p>

<ul>
  <li>Quality control reports (<code class="language-plaintext highlighter-rouge">.json</code>, <code class="language-plaintext highlighter-rouge">.html</code>) from fastp.</li>
  <li>Singleton reads (optional): <code class="language-plaintext highlighter-rouge">sample_singletons.fastq.gz</code></li>
  <li>Cleaned and validated FASTQ files: <code class="language-plaintext highlighter-rouge">sample_R1.fixed.fastq.gz</code> and <code class="language-plaintext highlighter-rouge">sample_R2.fixed.fastq.gz</code></li>
</ul>

<h2 id="troubleshooting-tips">Troubleshooting Tips</h2>

<ul>
  <li>Repairing reads is important if your pipeline expects synchronized pairs (e.g., for STAR, BWA, etc).</li>
  <li>If Fastp removes too many reads, inspect the quality thresholds or adapter settings.</li>
  <li>If <code class="language-plaintext highlighter-rouge">samtools fastq</code> fails, double-check that the BAM is name-sorted.</li>
  <li>For example, in one case, a company used <code class="language-plaintext highlighter-rouge">hg38_no_alt.fasta</code> that was not available, and I could not find it anywhere on the internet. It worked just fine with the UCSC <code class="language-plaintext highlighter-rouge">hg38.fasta</code>. In situations like this, <code class="language-plaintext highlighter-rouge">ref-solver identify</code> can help you narrow down which reference genome variant was actually used.</li>
  <li>Try to use the exact reference genome used during alignment, but in case you have a derived reference, one possible solution is to use the base reference used to create the derived reference. Use <code class="language-plaintext highlighter-rouge">ref-solver score</code> to compare your candidate reference against the CRAM header and confirm compatibility.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>CRAM files are efficient for storage, but getting back to raw reads for reanalysis requires care. With this step-by-step pipeline, you can reliably convert and clean CRAMs into FASTQ using reproducible Docker-based commands.</p>

<h2 id="extra-nextflow-simple-pipeline">Extra: Nextflow Simple Pipeline</h2>

<p>Here’s a Nextflow pipeline that automates the entire workflow:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">check_cram_reference</code></li>
  <li><code class="language-plaintext highlighter-rouge">validate_cram</code></li>
  <li><code class="language-plaintext highlighter-rouge">sort_cram_to_bam</code></li>
  <li><code class="language-plaintext highlighter-rouge">bam_to_fastq</code></li>
  <li><code class="language-plaintext highlighter-rouge">fastp_cleanup</code></li>
  <li><code class="language-plaintext highlighter-rouge">repair_reads</code></li>
</ol>

<h3 id="directory-structure-suggested">Directory Structure (suggested)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>project/
├── main.nf
├── nextflow.config
├── data/
│   ├── input.cram
│   └── reference.fa
</code></pre></div></div>

<h3 id="mainnf">main.nf</h3>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">nextflow</span><span class="o">.</span><span class="na">enable</span><span class="o">.</span><span class="na">dsl</span><span class="o">=</span><span class="mi">2</span>

<span class="n">params</span><span class="o">.</span><span class="na">cram</span>        <span class="o">=</span> <span class="s2">"data/input.cram"</span>
<span class="n">params</span><span class="o">.</span><span class="na">reference</span>   <span class="o">=</span> <span class="s2">"data/reference.fa"</span>
<span class="n">params</span><span class="o">.</span><span class="na">sample_id</span>   <span class="o">=</span> <span class="s2">"sample"</span>
<span class="n">params</span><span class="o">.</span><span class="na">outdir</span>      <span class="o">=</span> <span class="s2">"results"</span>

<span class="n">workflow</span> <span class="o">{</span>
    <span class="n">Channel</span>
        <span class="o">.</span><span class="na">fromPath</span><span class="o">(</span><span class="n">params</span><span class="o">.</span><span class="na">cram</span><span class="o">)</span>
        <span class="o">.</span><span class="na">set</span> <span class="o">{</span> <span class="n">cram_ch</span> <span class="o">}</span>

    <span class="n">check_cram_reference</span><span class="o">(</span><span class="n">cram_ch</span><span class="o">,</span> <span class="n">file</span><span class="o">(</span><span class="n">params</span><span class="o">.</span><span class="na">reference</span><span class="o">))</span>
        <span class="o">|</span> <span class="n">validate_cram</span>
        <span class="o">|</span> <span class="n">sort_cram_to_bam</span>
        <span class="o">|</span> <span class="n">bam_to_fastq</span>
        <span class="o">|</span> <span class="n">fastp_cleanup</span>
        <span class="o">|</span> <span class="n">repair_reads</span>
<span class="o">}</span>

<span class="n">process</span> <span class="n">check_cram_reference</span> <span class="o">{</span>
    <span class="n">tag</span> <span class="s2">"$sample_id"</span>

    <span class="nl">input:</span>
    <span class="n">path</span> <span class="n">cram</span>
    <span class="n">path</span> <span class="n">reference</span>

    <span class="nl">output:</span>
    <span class="n">path</span> <span class="s2">"cram.ok"</span><span class="o">,</span> <span class="nl">emit:</span> <span class="n">ok</span>

    <span class="nl">script:</span>
    <span class="n">sample_id</span> <span class="o">=</span> <span class="n">cram</span><span class="o">.</span><span class="na">getBaseName</span><span class="o">().</span><span class="na">tokenize</span><span class="o">(</span><span class="s1">'.'</span><span class="o">)[</span><span class="mi">0</span><span class="o">]</span>
    <span class="s2">"""
    mkdir -p refcheck
    docker run -v \$PWD:\$PWD quay.io/biocontainers/samtools:1.22--h96c455f_0 \\
        samtools samples -h -f $reference $cram &gt; refcheck/${sample_id}_ref_check.txt
    touch cram.ok
    """</span>
<span class="o">}</span>

<span class="n">process</span> <span class="n">validate_cram</span> <span class="o">{</span>
    <span class="n">tag</span> <span class="s2">"$sample_id"</span>

    <span class="nl">input:</span>
    <span class="n">path</span> <span class="n">cram_ok</span>

    <span class="nl">output:</span>
    <span class="n">path</span> <span class="s2">"${params.cram}"</span><span class="o">,</span> <span class="nl">emit:</span> <span class="n">cram</span>

    <span class="nl">script:</span>
    <span class="s2">"""
    docker run -v \$PWD:\$PWD quay.io/biocontainers/samtools:1.22--h96c455f_0 \\
        samtools quickcheck ${params.cram} &amp;&amp; echo OK
    cp ${params.cram} .
    """</span>
<span class="o">}</span>

<span class="n">process</span> <span class="n">sort_cram_to_bam</span> <span class="o">{</span>
    <span class="n">tag</span> <span class="s2">"$sample_id"</span>

    <span class="nl">input:</span>
    <span class="n">path</span> <span class="n">cram</span>
    <span class="n">path</span> <span class="n">reference</span> <span class="n">from</span> <span class="nf">file</span><span class="o">(</span><span class="n">params</span><span class="o">.</span><span class="na">reference</span><span class="o">)</span>

    <span class="nl">output:</span>
    <span class="n">path</span> <span class="s2">"sorted.bam"</span><span class="o">,</span> <span class="nl">emit:</span> <span class="n">bam</span>

    <span class="nl">script:</span>
    <span class="s2">"""
    docker run -v \$PWD:\$PWD quay.io/biocontainers/samtools:1.22--h96c455f_0 \\
        samtools sort -@ 4 -O bam -n --reference $reference \\
        -o sorted.bam $cram
    """</span>
<span class="o">}</span>

<span class="n">process</span> <span class="n">bam_to_fastq</span> <span class="o">{</span>
    <span class="n">tag</span> <span class="s2">"$sample_id"</span>

    <span class="nl">input:</span>
    <span class="n">path</span> <span class="n">bam</span>
    <span class="n">path</span> <span class="n">reference</span> <span class="n">from</span> <span class="nf">file</span><span class="o">(</span><span class="n">params</span><span class="o">.</span><span class="na">reference</span><span class="o">)</span>

    <span class="nl">output:</span>
    <span class="n">tuple</span> <span class="nf">path</span><span class="o">(</span><span class="s2">"R1.fastq.gz"</span><span class="o">),</span> <span class="n">path</span><span class="o">(</span><span class="s2">"R2.fastq.gz"</span><span class="o">)</span>

    <span class="nl">script:</span>
    <span class="s2">"""
    docker run -v \$PWD:\$PWD quay.io/biocontainers/samtools:1.22--h96c455f_0 \\
        samtools fastq -@ 4 --reference $reference \\
        -1 R1.fastq.gz -2 R2.fastq.gz $bam
    """</span>
<span class="o">}</span>

<span class="n">process</span> <span class="n">fastp_cleanup</span> <span class="o">{</span>
    <span class="n">tag</span> <span class="s2">"$sample_id"</span>

    <span class="nl">input:</span>
    <span class="n">tuple</span> <span class="nf">path</span><span class="o">(</span><span class="n">r1</span><span class="o">),</span> <span class="n">path</span><span class="o">(</span><span class="n">r2</span><span class="o">)</span>

    <span class="nl">output:</span>
    <span class="n">tuple</span> <span class="nf">path</span><span class="o">(</span><span class="s2">"clean_R1.fastq.gz"</span><span class="o">),</span> <span class="n">path</span><span class="o">(</span><span class="s2">"clean_R2.fastq.gz"</span><span class="o">)</span>

    <span class="nl">script:</span>
    <span class="s2">"""
    mkdir -p ${params.outdir}/fastp
    docker run -v \$PWD:\$PWD quay.io/biocontainers/fastp:1.0.0--heae3180_0 \\
        fastp -w 4 -i $r1 -I $r2 \\
        -o clean_R1.fastq.gz -O clean_R2.fastq.gz \\
        -j ${params.outdir}/fastp/fastp.json \\
        -h ${params.outdir}/fastp/fastp.html \\
        --detect_adapter_for_pe
    """</span>
<span class="o">}</span>

<span class="n">process</span> <span class="n">repair_reads</span> <span class="o">{</span>
    <span class="n">tag</span> <span class="s2">"$sample_id"</span>

    <span class="nl">input:</span>
    <span class="n">tuple</span> <span class="nf">path</span><span class="o">(</span><span class="n">r1</span><span class="o">),</span> <span class="n">path</span><span class="o">(</span><span class="n">r2</span><span class="o">)</span>

    <span class="nl">output:</span>
    <span class="n">tuple</span> <span class="nf">path</span><span class="o">(</span><span class="s2">"fixed_R1.fastq.gz"</span><span class="o">),</span> <span class="n">path</span><span class="o">(</span><span class="s2">"fixed_R2.fastq.gz"</span><span class="o">),</span> <span class="n">path</span><span class="o">(</span><span class="s2">"singletons.fastq.gz"</span><span class="o">)</span>

    <span class="nl">script:</span>
    <span class="s2">"""
    docker run -v \$PWD:\$PWD quay.io/biocontainers/bbmap:39.26--he5f24ec_0 \\
        repair.sh \\
        in1=$r1 in2=$r2 \\
        out1=fixed_R1.fastq.gz \\
        out2=fixed_R2.fastq.gz \\
        outs=singletons.fastq.gz \\
        repair
    """</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="nextflowconfig">nextflow.config</h3>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">params</span> <span class="o">{</span>
  <span class="n">cram</span>      <span class="o">=</span> <span class="s2">"data/input.cram"</span>
  <span class="n">reference</span> <span class="o">=</span> <span class="s2">"data/reference.fa"</span>
  <span class="n">sample_id</span> <span class="o">=</span> <span class="s2">"sample"</span>
  <span class="n">outdir</span>    <span class="o">=</span> <span class="s2">"results"</span>
<span class="o">}</span>

<span class="n">process</span><span class="o">.</span><span class="na">container</span> <span class="o">=</span> <span class="s1">''</span>
<span class="n">process</span><span class="o">.</span><span class="na">executor</span> <span class="o">=</span> <span class="s1">'local'</span>
<span class="n">process</span><span class="o">.</span><span class="na">errorStrategy</span> <span class="o">=</span> <span class="s1">'terminate'</span>
</code></pre></div></div>

<h3 id="run-the-pipeline">Run the Pipeline</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nextflow run main.nf <span class="nt">--cram</span> data/input.cram <span class="nt">--reference</span> data/reference.fa <span class="nt">--sample_id</span> SAMPLE01
</code></pre></div></div>

<hr />

<p><em>Published originally on <a href="https://medium.com/@geocarvalho/from-cram-to-fastq-a-practical-guide-for-bioinformaticians-d24c82fde5eb">Medium</a>.</em></p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="cram" /><category term="fastq" /><category term="nextflow" /><category term="workflow" /><summary type="html"><![CDATA[In the world of genomics, data often comes in CRAM format to save disk space. But what if you need the original FASTQ files for downstream processing like re-alignment or variant calling with a different tool?]]></summary></entry><entry><title type="html">Aneuploidy Analysis from Whole Genome Sequencing Data</title><link href="https://geocarvalho.github.io/bioinformatics/2023/11/03/aneuploidy-analysis-from-whole-genome-sequencing.html" rel="alternate" type="text/html" title="Aneuploidy Analysis from Whole Genome Sequencing Data" /><published>2023-11-03T00:00:00+00:00</published><updated>2023-11-03T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2023/11/03/aneuploidy-analysis-from-whole-genome-sequencing</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2023/11/03/aneuploidy-analysis-from-whole-genome-sequencing.html"><![CDATA[<p>Aneuploidy — the presence of an abnormal number of chromosomes — is a well-known driver of cancer and developmental disorders. While karyotyping and chromosomal microarray have been the traditional tools for detecting aneuploidy, whole genome sequencing (WGS) data can also be leveraged for this purpose. In rare disease diagnostics, ruling out or confirming aneuploidy from existing WGS data avoids additional testing and speeds up the diagnostic process.</p>

<p>This analysis was performed as part of a case study published in <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC11290577/">Haematologica</a>, where we investigated fraternal twins with <em>HJV</em> mutations causing hemochromatosis but with highly discordant iron overload severity. One of the twins carried a <em>BUB1B</em> variant associated with mosaic variegated aneuploidy syndrome, which motivated a genome-wide aneuploidy assessment from WGS data to rule out chromosomal abnormalities as a contributing factor.</p>

<p>In this post, I walk through the practical approach used for assessing aneuploidy from germline WGS data using two complementary tools: <strong>AStra</strong> and <strong>QDNAseq</strong>.</p>

<h2 id="why-assess-aneuploidy-from-wgs">Why Assess Aneuploidy from WGS?</h2>

<p>In rare disease programs like the <a href="https://undiagnosed.hms.harvard.edu/">Undiagnosed Diseases Network (UDN)</a>, patients typically receive WGS as part of their evaluation. When a case involves phenotypes that could be explained by chromosomal abnormalities — such as developmental delay, growth restriction, or congenital anomalies — checking for aneuploidy directly from the existing sequencing data is a practical first step before pursuing more targeted assays.</p>

<h2 id="tools">Tools</h2>

<h3 id="astra">AStra</h3>

<p><a href="https://github.com/AISKhalil/AStra">AStra</a> generates genome-wide aneuploidy profiles and aneuploidy spectra from WGS data. It corrects for GC content and mappability biases, segments the genome, and calls gains and losses relative to a diploid reference. The aneuploidy spectrum — a frequency distribution of read depths — provides a clear visual summary of the ploidy state.</p>

<p><strong>Reference:</strong> Khalil AIS, Chattopadhyay A, Sanyal A. Analysis of Aneuploidy Spectrum From Whole-Genome Sequencing Provides Rapid Assessment of Clonal Variation Within Established Cancer Cell Lines. <em>Cancer Inform</em>. 2021;20:11769351211049236. <a href="https://pubmed.ncbi.nlm.nih.gov/34671179/">PMID: 34671179</a></p>

<p>The figure below from Khalil et al. shows an example of AStra output on MCF7 cancer cell line strains. On the left, the aneuploidy profile shows copy number states per genomic segment. On the right, the aneuploidy spectrum shows the read depth frequency distribution, where the red line denotes the RD median and the dotted black lines denote CN states.</p>

<p><img src="/assets/images/posts/aneuploidy/figure1.png" alt="AStra example: genome-wide aneuploidy profiles and spectra for MCF7 strains" />
<em>Figure 1: From Khalil, A. I. S., Chattopadhyay, A., &amp; Sanyal, A. (2021). Genome-wide aneuploidy profiles (left) and aneuploidy spectra (right) for MCF7 strain C, G, H, and P.</em></p>

<h3 id="qdnaseq">QDNAseq</h3>

<p><a href="https://github.com/ccagc/QDNAseq">QDNAseq</a> is a Bioconductor R package for DNA copy number analysis from shallow or standard-depth WGS. It uses fixed-size bins across the genome, applies corrections for mappability and GC content, and produces segmented copy number profiles with gain/loss calls.</p>

<p><strong>Reference:</strong> Sie D, et al. QDNAseq: A bioinformatics pipeline for DNA copy number analysis from shallow whole genome sequencing with noise levels near the probabilistic lower limit imposed by read counting. <em>Clinical Cancer Research</em>. 2016;22(1_Supplement):52-52.</p>

<h2 id="approach">Approach</h2>

<p>The analysis follows a trio design (proband + mother + father), which provides built-in controls — unaffected parents are expected to be diploid, and any deviation in the proband relative to both parents is more likely to be real.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WGS BAM/CRAM (Proband + Parents)
    │
    ├── AStra
    │     ├── GC &amp; mappability correction
    │     ├── Segmentation &amp; CN calling
    │     ├── Aneuploidy profile (per chromosome)
    │     └── Aneuploidy spectrum (genome histogram)
    │
    └── QDNAseq
          ├── Bin-level read counting
          ├── GC &amp; mappability correction
          ├── Segmentation
          └── CN profile with gain/loss calls
</code></pre></div></div>

<p>Running both tools independently serves as mutual validation. If both agree on the ploidy state, confidence in the result is high.</p>

<h2 id="interpreting-the-results">Interpreting the Results</h2>

<h3 id="aneuploidy-profile">Aneuploidy Profile</h3>

<p>The aneuploidy profile shows copy number states across all chromosomes. For a normal diploid sample, the profile should be flat at CN=2 across all autosomes, with expected differences on sex chromosomes.</p>

<p>Key things to look for:</p>
<ul>
  <li><strong>Whole-chromosome gains or losses</strong> (trisomy, monosomy)</li>
  <li><strong>Segmental aneuploidies</strong> (partial gains/losses of chromosome arms)</li>
  <li><strong>Consistency between proband and parents</strong> — deviations present in the proband but absent in both parents suggest a de novo event</li>
</ul>

<p>Below is an example from the QDNAseq documentation (low-grade glioma sample LGG150, chromosomes 7–10) showing how CN profiles look after correcting for GC content, mappability, segmenting, and calling gains and losses. This helps illustrate how to interpret the actual trio results in Figure 5.</p>

<p><img src="/assets/images/posts/aneuploidy/figure2.png" alt="QDNAseq documentation example" />
<em>Figure 2: Example from QDNAseq documentation — CN profile after correcting for GC content, mappability, segmenting, and calling gains and losses.</em></p>

<h3 id="aneuploidy-spectrum">Aneuploidy Spectrum</h3>

<p>The aneuploidy spectrum is a histogram of read depth values across the genome. In a diploid sample, the distribution should have a single sharp peak centered at CN=2. Additional peaks or broad shoulders would indicate the presence of cells with different copy number states.</p>

<p>For all three samples, the aneuploidy spectrum shows no significant deviation — each has a clean, single-peak distribution.</p>

<p><img src="/assets/images/posts/aneuploidy/figure3.png" alt="AStra aneuploidy spectrum for trio" />
<em>Figure 3: AStra aneuploidy spectrum histogram for all samples. No significant change in the aneuploidy profile across the trio.</em></p>

<h3 id="qdnaseq-read-count-profile">QDNAseq Read Count Profile</h3>

<p>QDNAseq provides a complementary view with bin-level resolution. The segmented profile should align with AStra’s results. Gains appear as segments above the baseline, and losses appear below.</p>

<p><img src="/assets/images/posts/aneuploidy/figure4.png" alt="QDNAseq read count profile for trio" />
<em>Figure 4: QDNAseq histogram for aneuploidy spectrum for all samples. The read depth (RD) median (red line) and CN states (black line) refer to CN=2 (diploid).</em></p>

<h3 id="qdnaseq-cn-profile">QDNAseq CN Profile</h3>

<p>The full QDNAseq CN profile across the genome for all three samples confirms the diploid state, with no gains or losses called in any sample.</p>

<p><img src="/assets/images/posts/aneuploidy/figure5.png" alt="QDNAseq CN profile for trio" />
<em>Figure 5: QDNAseq CN profile after calling gains and losses across the genome for all samples.</em></p>

<h2 id="example-diploid-confirmation-in-a-trio">Example: Diploid Confirmation in a Trio</h2>

<p>In a recent case, a proband and both parents were analyzed:</p>

<table>
  <thead>
    <tr>
      <th>Sample</th>
      <th>Ploidy State</th>
      <th>CN=2 Coverage</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Proband</td>
      <td>Diploid</td>
      <td>93.24%</td>
    </tr>
    <tr>
      <td>Mother</td>
      <td>Diploid</td>
      <td>93.24%</td>
    </tr>
    <tr>
      <td>Father</td>
      <td>Diploid</td>
      <td>87.91%</td>
    </tr>
  </tbody>
</table>

<p>Both AStra and QDNAseq confirmed diploid states across all three samples with no significant deviations. The aneuploidy spectra showed clean, single-peak distributions centered at CN=2. The slightly lower CN=2 coverage in the father is within normal variation and likely reflects noise in regions with low mappability.</p>

<p>This result effectively rules out aneuploidy as the cause of the proband’s phenotype, redirecting the diagnostic effort toward sequence-level variants (SNVs, indels, structural variants) instead.</p>

<h2 id="when-this-analysis-is-most-useful">When This Analysis Is Most Useful</h2>

<ul>
  <li><strong>Phenotypes suggestive of chromosomal abnormalities</strong> — developmental delay, intellectual disability, multiple congenital anomalies, growth abnormalities</li>
  <li><strong>Cases without prior karyotype or CMA</strong> — extracting aneuploidy information from existing WGS avoids additional testing</li>
  <li><strong>Mosaic aneuploidy screening</strong> — AStra’s spectrum can reveal low-level mosaicism as secondary peaks in the read depth distribution</li>
  <li><strong>Quality control</strong> — unexpected aneuploidies can indicate sample swaps or contamination</li>
</ul>

<h2 id="practical-tips">Practical Tips</h2>

<ol>
  <li><strong>Always analyze the trio together.</strong> Parents provide a baseline — if something looks off in the proband, check if the same pattern appears in a parent before calling it significant.</li>
  <li><strong>Use both tools.</strong> AStra excels at the spectrum-level overview while QDNAseq provides finer bin-level resolution. Agreement between both increases confidence.</li>
  <li><strong>Check sex chromosomes separately.</strong> The expected CN differs by sex (XX vs XY), so sex chromosome aneuploidies like Turner (45,X) or Klinefelter (47,XXY) require sex-aware interpretation.</li>
  <li><strong>Consider sequencing depth.</strong> Shallow WGS (~0.1-1x) works for large-scale aneuploidies but may miss smaller segmental events. Standard clinical WGS (~30-40x) provides sufficient resolution for both.</li>
</ol>

<h2 id="references">References</h2>

<ol>
  <li>Khalil AIS, Chattopadhyay A, Sanyal A. Analysis of Aneuploidy Spectrum From Whole-Genome Sequencing Provides Rapid Assessment of Clonal Variation Within Established Cancer Cell Lines. <em>Cancer Inform</em>. 2021;20:11769351211049236. <a href="https://pubmed.ncbi.nlm.nih.gov/34671179/">PMID: 34671179</a></li>
  <li>Sie D, et al. QDNAseq: A bioinformatics pipeline for DNA copy number analysis from shallow whole genome sequencing. <em>Clinical Cancer Research</em>. 2016;22(1_Supplement):52-52.</li>
</ol>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="aneuploidy" /><category term="wgs" /><category term="copy-number" /><category term="rare-disease" /><category term="genomics" /><summary type="html"><![CDATA[Aneuploidy — the presence of an abnormal number of chromosomes — is a well-known driver of cancer and developmental disorders. While karyotyping and chromosomal microarray have been the traditional tools for detecting aneuploidy, whole genome sequencing (WGS) data can also be leveraged for this purpose. In rare disease diagnostics, ruling out or confirming aneuploidy from existing WGS data avoids additional testing and speeds up the diagnostic process.]]></summary></entry><entry><title type="html">Interpretando genomas para doenças raras — anotações (parte 2)</title><link href="https://geocarvalho.github.io/bioinformatics/2023/04/08/interpretando-genomas-doencas-raras-parte-2.html" rel="alternate" type="text/html" title="Interpretando genomas para doenças raras — anotações (parte 2)" /><published>2023-04-08T00:00:00+00:00</published><updated>2023-04-08T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2023/04/08/interpretando-genomas-doencas-raras-parte-2</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2023/04/08/interpretando-genomas-doencas-raras-parte-2.html"><![CDATA[<p>Anotações da segunda aula do curso “Interpreting Genomes for Rare Disease” ministrada por Anne O’Donnell-Luria, MD, PhD e adição de outros materiais, mais detalhes nas referências.</p>

<p>Nesse artigo vamos falar sobre o que é a anotação de dados advindos de sequenciamento, o problema dos transcritos que são muito importantes biologicamente, mas em termos de interpretação tornam as coisas mais desafiadoras. Sobre os bancos de dados de referência genômica, como o <a href="https://gnomad.broadinstitute.org/about">gnomAD</a> e como os utilizamos. Também falaremos um pouco sobre previsões in silico. No final, vamos discutir outros bancos de dados que podem ser úteis na análise de pacientes com doenças raras.</p>

<p>Quando fazemos o sequenciamento de painéis de genes, exoma ou genoma o resultado final mais “bruto” dos pipelines de bioinformática normalmente é um arquivo de chamada de variantes, que é apenas uma lista de todos os locais onde a sequência difere do sequência de referência, que é a sequência genômica humana de referência que foi determinada como padrão. Existem muitos tipos diferentes de variantes que podemos encontrar. Essas variantes vão cair em diferentes transcritos e terão frequências diferentes na população em geral e podemos prever que terão diferentes consequências com base em previsões de algoritmos chamados <a href="https://www.merriam-webster.com/dictionary/in%20silico">in silico</a>.</p>

<p><img src="/assets/images/posts/genomas-doencas-raras-parte-2/01-tipos-variantes.png" alt="Tipos de variantes" />
<em>Tipos de variantes, onde em rosa temos o mRNA e em roxo a proteína resultante (Elise Valkanas, 2019).</em></p>

<p>Só para lhe dar um exemplo do que eu quero dizer com tipos de variantes, a tabela acima se restringe a variantes do tipo substituição, onde apenas um nucleotídeo é substituído por outro nucleotídeo. Essa é o tipo de variante mais comum, sendo aproximadamente 80% das variantes reportadas nos bancos de variantes públicos. Voltando a imagem, a sequência de referência é listada no topo. Dentro das substituições temos a variante missense é algo que muda o DNA, o RNA e o aminoácido. A variante sinônima também deve mudar o DNA e o RNA, mas não muda a proteína. Em seguida, a variante frameshift insere uma base no DNA e RNA, todos os aminoácidos seguintes mudem, e uma variante nonsense muda um aminoácido ou uma base que causa um códon de parada (stop codon) e trunca a proteína.</p>

<p>Falando um pouco mais sobre tipos de variantes ainda temos deleção, inserção, duplicação, deleção-inserção, inversão e variantes estruturais.</p>

<ul>
  <li>As inserções (AAC-GTT &gt; AACAGTT) são variantes onde um ou mais nucleotídeos são inseridos na sequência. Quando a sequência inserida é uma repetição (tandem copy) da sequência original de DNA, chamamos de duplicação. Mesma ideia, quando uma duplicação afeta mais de 50 pb nós nos referimos como CNVs.</li>
  <li>As deleções (AACGTT &gt; AAC-TT) são variantes onde um ou mais nucleotídeos são deletados da sequência, sendo o segundo tipo de variante mais comum. Quando uma deleção afeta mais de 50 pares de bases (pb), nós referimos a ela como variante no número de cópias (Copy Number Variants, CNVs).</li>
</ul>

<p>Ambas duplicação e deleções ocorrem frequentemente em pequenas regiões repetitivas do DNA.</p>

<ul>
  <li>As variantes estruturais acabam sendo um termo comumente utilizado para grandes mudanças no cromossomo como translocações e transposições. É importante ressaltar aqui que esse tipo de variante é extremamente difícil de ser detectada utilizando sequenciamento de sequências curtas (short reads, tipo Illumina ou MGI), mesmo que existam algoritmos para isso o tamanho das sequências por si já são um fator limitante para detecção de grandes variantes estruturais e até mesmo as pequenas. Se a variante estrutural for muito grande é possível visualizar por meio de tecnologias de mapeamento óptico ou microscopia (cariótipo).</li>
  <li>As inversões (AACGTT &gt; ACGTTT) ocorrem quando um pedaço da sequência se inverte, a nova sequência é exatamente o reverso-complemento da sequência deletada. Elas possuem um tamanho mínimo de dois nucleotídeos, sendo os casos de um uma simples substituição não é mesmo?!</li>
  <li>As deleção-inserção (AACGTT &gt; AATATT) é a combinação de deleções seguidas de inserções na mesma localização do DNA, parecido com a substituição, um ou mais nucleotídeos são substituídos por um ou mais nucleotídeos.</li>
</ul>

<p>Se é do seu interesse ver mais estudos sobre variantes estruturais eu tenho um repositório no GitHub com o que acabei achando de material sobre essas variantes e como analisá-las usando sequenciamento genético, só clicar <a href="https://github.com/geocarvalho/sv-cnv-studies">aqui</a>.</p>

<h2 id="nomenclatura-hgvs">Nomenclatura HGVS</h2>

<p>Existem dois tipos de nomenclaturas sendo a primeira a que vemos nos arquivos VCF, mas a mais comum clinicamente é a nomenclatura da Human Genome Variation Society (<a href="https://www.hgvs.org/">HGVS</a>). Ela utiliza a letra “c.” para explicar a mudança no DNA e a letra “p.” para a mudança no aminoácido. A nomenclatura HGVS e a VCF não se encaixam perfeitamente. A HGVS faz anotações em relação ao transcrito, então é muito importante saber qual transcrito você está se referindo. Já a VCF faz anotações baseadas nas coordenadas genômicas no genoma de referência utilizado e sempre se refere à fita positiva ou superior (fita 5’) do DNA.</p>

<ul>
  <li>As variantes em sítios de splicing são escritas como a posição “c.” mais próxima e se é negativo ou positivo (c.345–2A&gt;T).</li>
  <li>As variantes frameshift são escritas indicando quantas bases estão após a mudança até encontrar um códon de parada (p.Glu54fs ou p.Glu54Glyfs ou Glu54GlyfsX5).</li>
  <li>As variantes nonsense ou stop loss são representadas de três maneiras diferentes, mas todas indicam a introdução de um códon de parada (p.Arg24* ou p.Arg24X ou p.Arg24Ter).</li>
  <li>Para uma variante INDEL descrevemos as bases que são deletadas com a notação “c.” e a mudança real para a proteína. É importante mencionar que INDELs em frame são quando você deleta três bases, ou seja, um aminoácido, mas mantém os aminoácidos seguintes mantendo o quadro de leitura da proteína (c.1115_1117delCCT, p.Ser372del).</li>
  <li>Para variantes missense ou não-sinônimas, se apresenta a posição do códon, como por exemplo a troca de uma isoleucina para valina (p.Ile123Val).</li>
  <li>Para variantes sinônimas ou silenciosas usamos principalmente a notação “p.”, por exemplo, p.= ou p.Ile123= ou p.Ile123Ile para uma isoleucina que se mantém uma isoleucina mesmo com a troca do aminoácido.</li>
</ul>

<p><img src="/assets/images/posts/genomas-doencas-raras-parte-2/02-hgvs-vs-vcf.png" alt="Comparação da anotação HGVS vs VCF" />
<em>Comparação da anotação para a variante Phe508del no gene CFTR (Anne O’Donnell-Luria, 2019).</em></p>

<ul>
  <li>HGVS: NM_000492.3(CFTR) c.1521_1523delCTT ou chr7 g.117199646_117199648del</li>
  <li>VCF: chr7 117199644 ATCT A</li>
</ul>

<p>Novamente, a HGVS se refere ao transcrito e alinha tudo à direita (linha azul na imagem acima), enquanto a notação no VCF se alinha tudo à esquerda (linha vermelha na imagem acima), o que pode causar algumas discrepâncias entre os dois sistemas. Como exemplo, podemos usar a variante Phe508del no gene CFTR que é a variante mais comum para fibrose cística. Na anotação presente no VCF, ancoramos à esquerda na letra A, que não é deletada, e depois deletamos as letras CTT (linha vermelha na imagem acima). Já na nomenclatura HGVS, ancoramos à direita, e como sobram as duas letras T, não podemos dizer quais letras foram deletadas (linha azul na imagem acima). Por isso, na notação HGVS, é relatado que as letras CTT foram deletadas. Isso é importante porque se você estiver procurando em um banco de dados que usa HGVS e o banco de dados estiver usando a notação VCF, pode parecer que você está procurando variantes diferentes. Portanto, é crucial que seus dados estejam anotados de uma maneira que se alinhe com outros dados, para obter a anotação correta em cada variante. Assim, é comum termos ambas as anotações quando criamos bancos ou planilhas para análise.</p>

<h2 id="genomas-de-referência">Genomas de referência</h2>

<p>Os genomas de referência são outra complicação, onde temos uma sequência de referência do genoma humano consensual e pesquisadores trabalham na melhoria e preenchimento dessa sequência. Em sua maior parte, os genes não mudam, mas o espaço entre eles está sendo preenchido com detalhes adicionais. Além disso, haplótipos — regiões que variam bastante entre diferentes populações — estão sendo preenchidos, o que torna os genomas cada vez mais complexos. Recentemente foi publicado mais uma versão do genoma humano chamado telomere-to-telomere (T2T), resolvendo várias regiões complexas do genoma que ficaram indisponíveis por anos, aumentando o número de promessas de descobertas e de uso clinicamente [4–6].</p>

<p>Apenas para mostrar um exemplo, esta é a mesma variante no gene CFTR que falamos anteriormente:</p>

<ul>
  <li>hg37: chr7 117199644 ATCT A</li>
  <li>hg38: chr7 117559590 ATCT A</li>
</ul>

<p>HG37 é um genoma de referência que foi usado por muito tempo e ainda existem bancos de dados que são baseados apenas nele, enquanto que o HG38 é a referência atualmente recomendada clinicamente. No exemplo acima você verá que as alterações de base são as mesmas, mas as posições são diferentes. Elas estão, na verdade, separadas por 350 kilobases (KB). Se você olhar para uma variante no genoma de referência errado ela pode parecer estar em um gene ou localização totalmente diferente do que você esperava, por isso é muito importante saber em qual construção do genoma você está trabalhando.</p>

<p>Se você tiver o arquivo BAM ou CRAM é possível identificar o genoma que foi utilizado para o alinhamento olhando o cabeçalho do arquivo por meio de ferramentas de bioinformática como o <a href="http://www.htslib.org/doc/samtools-view.html">SAMtools</a>. Mas se você quer visualizar variantes, basta abrir o arquivo na ferramenta <a href="https://software.broadinstitute.org/software/igv/">IGV</a> que se for o genoma errado você verá um alinhamento colorido que indica variantes na posição em comparação com o genoma:</p>

<p><img src="/assets/images/posts/genomas-doencas-raras-parte-2/03-igv-genoma-errado.png" alt="BAM alinhado no IGV com genoma errado" />
<em>Exemplo de BAM alinhado no hg38 aberto na ferramenta IGV usando o genoma de referência hg19 (George Carvalho, 2023).</em></p>

<h2 id="métricas-de-qualidade-para-variantes">Métricas de qualidade para variantes</h2>

<p><img src="/assets/images/posts/genomas-doencas-raras-parte-2/04-metricas-qualidade.png" alt="Métricas de qualidade de variantes" />
<em>Exemplo de variante com algumas métricas de qualidade comumente usadas (Anne O’Donnell-Luria, 2019).</em></p>

<p>Existem vários tipos de métricas de qualidade que são usadas para identificar variantes presentes em um arquivo VCF. No exemplo acima, temos um ID para a amostra (GCGS_FAM8_23), o nucleotídeo referência A (que não aparece em negrito) e o G que representa a variante alternativa. O conjunto A/G indica que o paciente é heterozigoto para a variante G na posição onde se espera um A. Se fosse homozigoto para a variante, seria G/G (ambos em negrito), e se fosse homozigoto para a referência, seria A/A.</p>

<ul>
  <li><strong>VQSR</strong> (Variant Quality Score Recalibration): Não está presente nesse exemplo, mas é uma métrica bem sofisticada baseada em modelos Gaussianos por mistura para avaliar se uma variante é mais parecida com uma variante real ou com um artefato. Quando olhamos para os dados, é possível encontrar uma lista de pontuações de VQSR para cada variante [7].</li>
  <li><strong>AB</strong> (Allele Balance ou equilíbrio alélico): é outra característica importante e representa o número de leituras com a variante alternativa em relação ao número total de leituras cobrindo a posição onde se encontra. Neste exemplo, 31% das leituras apresentam a variante alternativa, o que está dentro do esperado para algo heterozigoto de linha germinativa — cerca de 50%. No entanto, vale lembrar que sempre há uma distribuição de leituras e, quanto menor a porcentagem, maior a chance de que essa variante seja somática neste paciente.</li>
  <li><strong>DP</strong> (Depth ou profundidade): É o número de leituras que cobrem uma determinada posição onde a variante se encontra, aqui com 54 leituras nessa posição. Para WGS consideramos DP=10 e WES consideramos DP=100 como valores mínimos para se reportar uma variante.</li>
  <li><strong>GQ</strong> (Genotype Quality ou qualidade do genótipo): Essa métrica varia de 0 a 99, sendo que 99 representa a qualidade máxima. Geralmente, olhamos para as variantes com GQ&gt;20. Porém, é importante ressaltar que é possível ter variantes reais com pontuações mais baixas e artefatos com pontuações mais altas.</li>
</ul>

<h2 id="versões-dos-transcritos-de-um-gene">Versões dos transcritos de um gene</h2>

<p><img src="/assets/images/posts/genomas-doencas-raras-parte-2/05-transcritos-gnomad.png" alt="Versões de transcritos no gnomAD" />
<em>Versões de transcritos para o gene CFTR no site gnomAD (George, 2023).</em></p>

<p>Os transcritos são importantes na interpretação das variantes graças a sua localização no gene podendo ter diferentes impactos. É importante mencionar que existem dois bancos de dados principais para transcritos:</p>

<ul>
  <li><a href="https://useast.ensembl.org/index.html">Ensembl</a>, que é o EMBL-EBI ou GENCODE e mais voltado para anotações europeias.</li>
  <li><a href="https://www.ncbi.nlm.nih.gov/refseq/">RefSeq</a>, que é baseado no NCBI americano.</li>
</ul>

<p>O RefSeq tende a ter menos opções de transcritos, enquanto Ensembl tende a ter mais. Geralmente, o transcrito canônico é o que todos nós falamos e nos referimos como o principal transcrito, mas isso geralmente é apenas porque é o maior e inclui todos os éxons. Isso não significa necessariamente que ele seja a transcrição clinicamente mais relevante. É importante lembrar que existem grupos trabalhando na definição dos transcritos clinicamente mais relevantes, como o projeto <a href="https://www.ncbi.nlm.nih.gov/refseq/MANE/">MANE</a>. Ele é um projeto colaborativo que tem como objetivo convergir na anotação de genes e transcritos humanos e definir um conjunto representativo de transcritos e proteínas correspondentes para genes codificantes de proteínas em todo o genoma humano [8]. Cada transcrito MANE representa uma correspondência exata nas regiões exônicas entre um transcrito no RefSeq e sua contraparte na anotação Ensembl/GENCODE, de modo que os dois identificadores possam ser usados simultaneamente. Além disso, um transcrito MANE corresponde perfeitamente ao genoma de referência GRCh38 e é escolhido com base em critérios biologicamente relevantes, como níveis de expressão do transcrito e a conservação das regiões codificadoras.</p>

<h2 id="ferramentas-de-predição-de-impacto">Ferramentas de predição de impacto</h2>

<p><img src="/assets/images/posts/genomas-doencas-raras-parte-2/06-ferramentas-predicao.png" alt="Ferramentas de predição de impacto" />
<em>Exemplo de ferramentas de predição de impacto de variantes (Anne O’Donnell-Luria, 2019).</em></p>

<p>No geral, muitas dessas ferramentas de predição de impacto (análise <a href="https://www.news-medical.net/life-sciences/What-is-in-Silico.aspx">in silico</a>) usam os mesmos dados de treinamento e métricas semelhantes para os modelos, o que significa que há muita circularidade em usar vários preditores in silico. Por outro lado, é melhor ter mais dados do que menos. Por isso, tendemos a olhar para muitas ferramentas, mas não os consideramos de forma aditiva. Apenas pensamos um pouco sobre o consenso e alguns deles confiamos um pouco mais do que outros. No entanto, para um efeito específico de uma variante, é realmente difícil saber qual é o mais importante. Não vou entrar em detalhes sobre nenhum deles, mas direi que alguns são apenas para variantes missense e outros para todos os tipos de variantes e isso é útil apenas para ter uma pontuação para cada variante. Além disso, existe a ferramenta SpliceAI que foi desenvolvida pela Illumina para predição de junções de splicing, permitindo a predição do impacto de variantes não codificantes como as sinônimas e intrônicas que podem alterar o mecanismo de splicing [9].</p>

<h2 id="conservação">Conservação</h2>

<p><em>(Seção a ser expandida em futuras atualizações.)</em></p>

<h2 id="referências">Referências</h2>

<ol>
  <li><a href="https://www.cell.com/cell/pdf/S0092-8674(18)31629-5.pdf">Jaganathan, Kishore, et al. “Predicting splicing from primary sequence with deep learning.” Cell 176.3 (2019): 535–548</a></li>
  <li><a href="https://www.nature.com/articles/s41586-022-04558-8">Morales, Joannella, et al. “A joint NCBI and EMBL-EBI transcript set for clinical genomics and research.” Nature 604.7905 (2022): 310–315</a></li>
  <li><a href="https://gatk.broadinstitute.org/hc/en-us/articles/360035531612-Variant-Quality-Score-Recalibration-VQSR-">GATK technical documentation — Variant Quality Score Recalibration (VQSR)</a></li>
  <li><a href="https://www.science.org/doi/full/10.1126/science.abl3533">Aganezov, Sergey, et al. “A complete reference genome improves analysis of human genetic variation.” Science 376.6588 (2022): eabl3533</a></li>
  <li><a href="https://qmro.qmul.ac.uk/xmlui/bitstream/handle/123456789/78958/Rowe%20Implications%20of%20the%20first%20complete%20human%20genome%20assembly%202022%20Published.pdf?sequence=2">Alkan, Can, et al. “Implications of the first complete human genome assembly.” Genome Research (2022)</a></li>
  <li><a href="https://www.science.org/doi/full/10.1126/science.abj6987">Miga, Karen H., and Beth A. Sullivan. “Expanding studies of chromosome structure and function in the era of T2T genomics.” Human Molecular Genetics 30.R2 (2021): R198-R205</a></li>
  <li><a href="https://www.elsevier.com/books/clinical-dna-variant-interpretation/patrinos/978-0-12-820519-8">Patrinos, George P. Clinical DNA Variant Interpretation: Theory and Practice. Academic Press, 2021</a></li>
  <li><a href="https://youtu.be/MmAYOgdouJc">Interpreting Genomes for Rare Disease: Variant and Gene Interpretation — Anne O’Donnell-Luria, MD, PhD, 2019</a></li>
  <li><a href="https://cmg.broadinstitute.org/course-offering">Interpreting genomes for rare disease: variant and gene interpretation, 2019</a></li>
</ol>

<hr />

<p><em>Publicado originalmente no <a href="https://geocarvalho.medium.com/interpretando-genomas-para-doen%C3%A7as-raras-anota%C3%A7%C5%8Des-parte-2-b7e85cc724b4">Medium</a>.</em></p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="genomics" /><category term="rare-disease" /><category term="sequencing" /><category term="ngs" /><summary type="html"><![CDATA[Anotações da segunda aula do curso “Interpreting Genomes for Rare Disease” ministrada por Anne O’Donnell-Luria, MD, PhD e adição de outros materiais, mais detalhes nas referências.]]></summary></entry><entry><title type="html">Interpretando genomas para doenças raras — anotações (parte 1)</title><link href="https://geocarvalho.github.io/bioinformatics/2023/04/02/interpretando-genomas-doencas-raras-parte-1.html" rel="alternate" type="text/html" title="Interpretando genomas para doenças raras — anotações (parte 1)" /><published>2023-04-02T00:00:00+00:00</published><updated>2023-04-02T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2023/04/02/interpretando-genomas-doencas-raras-parte-1</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2023/04/02/interpretando-genomas-doencas-raras-parte-1.html"><![CDATA[<p>Anotações da primeira aula do curso “Interpreting Genomes for Rare Disease” ministrada por Daniel MacArthur, PhD e adição de outros materiais, mais detalhes nas referências.</p>

<p>No campo do diagnóstico de doenças raras, as tecnologias de sequenciamento desempenharam um papel vital na identificação de mutações genéticas e genes causadores de doenças. No entanto, antes de mergulharmos nos componentes práticos do artigo, é crucial ter uma compreensão básica do processo de seleção de casos para sequenciamento. Este artigo abordará o processo de seleção, tanto para famílias que são mais propensas a fornecer um diagnóstico genético quanto para estudos de pesquisa interessados em identificar novos genes de doenças. Também discutiremos brevemente o processo de sequenciamento, as três diferentes tecnologias de sequenciamento com seus pontos positivos e negativos. O objetivo final é fornecer uma compreensão aprofundada das tecnologias de sequenciamento e seu papel no diagnóstico de doenças raras.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/01-espectro-arquitetura-genetica.png" alt="Doenças que caem no espectro de arquitetura genética" />
<em>Doenças que caem no espectro de arquitetura genética (Daniel MacArthur, 2019).</em></p>

<p>Doenças raras são apenas uma ponta do espectro de doenças humanas e existem doenças que se encaixam em todo esse espectro de arquitetura genética. Por um lado, temos as doenças em que tradicionalmente trabalhamos no meu grupo: doenças monogênicas muito graves, que podem ser relativamente leves, mas são monogênicas no sentido de que segregam dentro de famílias específicas de maneira dominante, recessiva ou pelo cromossomo X. Elas são normalmente causadas por um número muito pequeno de variantes por família, ou seja, uma ou duas variantes, tipicamente em um único gene, que contribuem para aquela doença e normalmente seguem um padrão mendeliano. Por exemplo, no caso da família dominante mencionada, você tem um único pai afetado que transmitiu a variante da doença para 50% de seus filhos, mas existem algumas ressalvas a serem abordadas futuramente.</p>

<p>Por outro lado, no outro extremo do espectro de doenças, temos as doenças comuns e complexas, que são as que causam a maior mortalidade e morbidade em toda a população humana, como diabetes tipo 2, doenças cardíacas ou câncer. Essas doenças geralmente, mas nem sempre, são massivamente poligênicas, o que significa que em qualquer caso individual existe uma ampla gama de fatores de risco genéticos normalmente pequenos, bem como fatores de risco ambientais que contribuem para o risco daquela pessoa desenvolver a doença. Claro, em alguns casos extremos dessas condições, também existem formas monogênicas, e, em geral, as variantes causais para essas doenças tendem a ser substancialmente mais comuns, mas têm efeitos muito menores do que o que vemos para doenças raras.</p>

<p>É importante ressaltar que este artigo se concentrará principalmente nas doenças raras e monogênicas. Vamos focar em entender as tecnologias de sequenciamento que são usadas para identificar variantes causais para essas doenças e as melhores práticas para interpretar essas variantes.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/02-frequencia-alelica-efeito.png" alt="Frequência alélica vs tamanho do efeito" />
<em>Manolio et al. (2009), Nature. 461:747–753.</em></p>

<p>As variantes genéticas que contribuem para o desenvolvimento de doenças seguem um espectro que pode ser representado em dois eixos. No eixo X, temos a frequência alélica da variante, onde as variantes extremamente raras estão localizadas no final esquerdo do espectro e as variantes mais comuns no final direito. Já no eixo Y, temos o tamanho do efeito, que representa a probabilidade de uma pessoa que carrega essa variante desenvolver a doença. As variantes que possuem grande probabilidade de causar a doença estão localizadas no topo do espectro e convergem para o conceito de penetrância. Já as variantes com menor efeito estão localizadas na parte inferior do espectro, que é o que normalmente se pensa em estudos de associação genômica em larga escala (GWAS).</p>

<p>No entanto, é importante ressaltar que no canto superior direito do espectro, onde estaríamos esperando encontrar variantes com grande efeito, quase não existem variantes comuns. Isso se deve ao fato de que, se houvesse uma variante que aumentasse significativamente o risco de doença, a seleção natural provavelmente a eliminaria da população. Por outro lado, no canto inferior esquerdo do espectro, existem variantes extremamente raras e com efeitos muito pequenos que são impossíveis de detectar com as amostras e tecnologias atuais. Para detectar essas variantes, precisaríamos ter milhões de amostras.</p>

<p>As variantes que podemos detectar e estudar estão localizadas no meio do espectro, onde estão as variantes raras com efeitos enormes associadas às doenças mendelianas no canto superior esquerdo e as variantes comuns com efeitos pequenos que são encontradas em estudos GWAS no canto inferior direito. À medida que aplicamos abordagens de sequenciamento de próxima geração em distúrbios complexos mais comuns, estamos começando a descobrir uma variedade de variantes no meio do espectro.</p>

<h2 id="estratégia-geral">Estratégia geral</h2>

<p>Ao se deparar com casos de doenças raras, nossa estratégia geral é começar com uma família afetada pela doença. Geralmente, essa família já passou por extensos exames prévios, como painéis de genes ou sequenciamento de genes individuais, em busca de causas conhecidas da doença. No entanto, mesmo após anos de tentativas de diagnóstico sem sucesso, nossa primeira abordagem para a análise genética dessas famílias é sequenciar os exomas de todos os membros da família.</p>

<p>Esse método de sequenciamento de exoma (WES) é quase sempre a nossa primeira linha de abordagem para a descoberta genética. Isso ocorre porque, em muitos casos, cerca de 25% a 40%, dependendo da doença em questão, somos capazes de encontrar um diagnóstico usando essa técnica. Quando não conseguimos encontrar um diagnóstico, continuamos a analisar os dados do exoma periodicamente, geralmente a cada 6 a 12 meses, para verificar se alguma nova descoberta genética altera o diagnóstico para aquela família em particular.</p>

<p>Em casos em que não conseguimos encontrar um diagnóstico após vários anos de análise do exoma ou para casos em que há uma forte suspeita de que a condição seja de origem genética, mas o exoma é completamente silencioso, usamos o sequenciamento completo do genoma (WGS) e sempre que possível também realizamos o sequenciamento de RNA dos tecidos afetados.</p>

<h2 id="estratégia-para-seleção-das-famílias-para-provável-diagnóstico-ou-pesquisa-de-gene">Estratégia para seleção das famílias para provável diagnóstico ou pesquisa de gene</h2>

<p>No contexto do diagnóstico, vemos que o WES é realmente nosso teste de frente. Obviamente, painéis e várias outras formas de pré-seleção foram críticos em muitos contextos de diagnóstico, mas ir direto para o exoma ou, possivelmente, para um array, achamos que é agora uma abordagem realmente eficiente para ir o mais rápido possível em direção a um diagnóstico genético. Somos um pouco agnósticos em relação à tecnologia exata que usamos, mas em geral, se estivermos em uma situação em que o dinheiro é limitante — e vamos encarar, isso é típico, embora nem sempre — geralmente somos a favor do WES porque é a abordagem mais eficaz em termos de custo para encontrar o máximo de diagnósticos para um determinado gasto. Mas se estamos limitados pelas amostras — especialmente se temos uma família em que o caso específico é extremamente urgente ou clinicamente urgente em alguns casos, ou se temos uma doença específica em que temos um número muito pequeno de famílias que podemos obter — sabemos que queremos sequenciá-las e analisá-las o mais minuciosamente possível, então às vezes vamos direto para o WGS. Isso ocorre normalmente em uma situação em que as amostras são mais limitantes do que o dinheiro.</p>

<p>No contexto da descoberta de genes, nosso objetivo aqui é realmente obter o maior número possível de famílias afetadas por uma doença específica. Tentamos nos concentrar o máximo possível e aumentar nosso tamanho de amostras sem diluir muito a homogeneidade clínica dos casos. No contexto da descoberta de genes, investimos muito tempo em perguntar a colaboradores e famílias sobre pré-seleção, tentando obter famílias onde muito trabalho foi feito para descartar outras causas de doenças. Vamos nos concentrar em estruturas familiares solucionáveis. Isso é principalmente sobre evitar pequenas famílias dominantes e se concentrar em casos em que achamos que há uma chance razoável de realmente poder obter um diagnóstico com outra estrutura familiar que vemos. Quase sempre favoreceremos WES, pelo menos como uma abordagem de primeira linha na descoberta de genes, porque, novamente, a probabilidade de descobrir um novo gene por dinheiro gasto é substancialmente maior no contexto do WES.</p>

<h3 id="tipos-de-famílias-que-focamos-os-estudos">Tipos de famílias que focamos os estudos</h3>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/03-tipos-familias.png" alt="Tipos de famílias estudadas" />
<em>Quais tipos de famílias são estudadas (Daniel MacArthur, 2019).</em></p>

<p>Estudo genético de famílias é uma das ferramentas mais poderosas para entender as causas de doenças hereditárias. Existem diferentes tipos de famílias que podem ser estudadas, cada uma com suas próprias vantagens e desafios. Um dos tipos de famílias mais interessantes é aquela com pais não afetados e vários filhos afetados. Isso sugere uma doença recessiva, o que muitas vezes pode ser resolvido com apenas uma única família. Identificar o gene candidato responsável pela doença pode ser relativamente fácil nesses casos. Outra explicação possível é mosaicismo e transmissão. No entanto, isso também pode ser resolvido com uma mutação de novo recorrente presente em ambos os filhos.</p>

<p>No entanto, o tipo de família mais comum é um trio com pais não afetados e um filho afetado. Nesses casos, é menos provável que possamos identificar o gene causal com apenas uma única família. Mas se a doença for causada por uma mutação de novo, temos uma boa chance de identificar o gene causal. Além disso, temos uma chance razoável de identificar causas recessivas.</p>

<p>Para famílias dominantes com doenças de início tardio ou compatíveis com a reprodução, geralmente precisamos de famílias substancialmente maiores para ter alguma chance real de descobrir novos genes da doença. Em geral, tendemos a nos concentrar em famílias dominantes muito maiores sempre que pudermos ter acesso a elas. Nessas situações, tentaríamos sequenciar os dois indivíduos afetados mais distantes geneticamente com WES e também tentaríamos realizar microarray para análise de linkage na família inteira.</p>

<p>O último tipo de design de estudo é a coleta de amostras de pacientes com condições de início tardio, pois muitas vezes os pais não estão disponíveis ou são muito difíceis de se obter acesso. Para doenças como distrofia muscular de Lynn Girdle e algumas doenças de retina, estamos construindo uma coleção de amostras de muitos indivíduos afetados pela mesma condição clínica. Em seguida, comparamos centenas de casos com milhares de controles que foram sequenciados na mesma plataforma e procuramos genes que tenham um excesso de mutações patogênicas raras nos casos em relação aos controles.</p>

<p>Em resumo, a escolha do tipo de família ou design de estudo depende da doença que está sendo estudada e da disponibilidade de famílias maiores e amostras de pacientes. Cada tipo de família tem suas próprias vantagens e desafios, mas todos eles podem ser usados para entender melhor as causas de doenças genéticas.</p>

<h3 id="outras-considerações-ao-escolher-famílias-para-estudo">Outras considerações ao escolher famílias para estudo</h3>

<p>Seleção de famílias para estudos de sequenciamento é uma questão complexa. Normalmente a preferência é por famílias com pais não afetados e múltiplos filhos afetados, sugerindo uma desordem recessiva. Nesses casos, geralmente é possível identificar o gene candidato que causa a doença. O segundo tipo de família mais comum é o trio, com pais não afetados e um filho afetado. Embora seja menos provável identificar a causa exata com uma única família, há uma boa chance de identificar mutações de novo. Para famílias dominantes com doenças de início tardio ou compatíveis com a reprodução, são necessárias famílias maiores para descobrir novos genes. Famílias pequenas com apenas uma transmissão tendem a ter pouca sorte em descobrir novos genes. Para doenças de início tardio, está se tornando mais comum coletar grandes grupos de pacientes para análises.</p>

<p>Além disso, a regulamentação é crítica. As famílias precisam ter dado consentimento apropriado para pesquisa e compartilhamento de dados. A disponibilidade de DNA de outros membros da família também é importante em alguns casos. A apresentação clínica incomum pode ser um critério para selecionar uma família. Para WGS é preferível ter acesso a amostras de tecido relevantes para doença para análises de RNA, que podem fornecer informações importantes sobre o impacto funcional das variantes.</p>

<p>Uma grande consideração em nosso contexto de pesquisa, porque a rede é extremamente rigorosa em garantir que as amostras que passam por aqui tenham o consentimento apropriado tanto para sequenciamento quanto para compartilhamento de dados, é a questão regulatória. Portanto, temos as famílias sido adequadamente consentidas para pesquisa e, importantemente, outras consentidas para depósito de seus dados em bancos de dados de acesso controlado como <a href="https://www.ncbi.nlm.nih.gov/gap/">dbGaP</a>, para que outros pesquisadores possam realmente acessar seus dados.</p>

<p>Olhamos muito para a disponibilidade de DNA de outros membros da família e, para alguns projetos de família, isso é absolutamente crítico. Há um critério um pouco vago e incerto aqui, que é a singularidade da apresentação clínica e, tipicamente, descobrimos que, nos casos em que há uma apresentação fortemente sindrômica ou outras características incomuns desse caso em particular, temos muito mais chances de encontrar um novo gene de doença do que se parecer um caso padrão, por exemplo de distrofias musculares.</p>

<p>E o último ponto, para o qual voltarei mais tarde, é que preferimos muito, especialmente para WGS, amostras onde é possível obter acesso a amostras de tecido relevantes para a doença que possamos usar para RNA-seq e a razão para isso é que descobrimos que o RNA-seq nos dá ideias sobre o impacto funcional direto de variantes que afetam a expressão em splicing, o que muitas vezes pode ser muito difícil de prever apenas a partir da sequência de DNA.</p>

<blockquote>
  <p><strong>Lição 1:</strong> Pequenas análises baseadas em famílias geralmente são insuficientes para a descoberta de doenças geneticamente complexas. Isso ocorre porque, assim que você sai da causa monogênica muito estrita da doença, sua descoberta cai precipitadamente. Precisamos olhar para centenas ou possivelmente milhares de amostras para ter o poder de rejeitar essas causas mais complexas geneticamente da doença.</p>
</blockquote>

<blockquote>
  <p><strong>Lição 2:</strong> Temos que ser muito cautelosos em relação a relatórios de triagem prévia. A precisão das triagens prévias para testes de painel, por exemplo, depende muito de quem fez a triagem, ou seja, qual laboratório estava envolvido na realização e execução desse teste de painel específico, qual tecnologia foi usada e quando foi feito.</p>
</blockquote>

<h2 id="como-funciona-o-sequenciamento">Como funciona o sequenciamento</h2>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/04-sequenciamento-corrida.png" alt="Sequenciamento por corrida" />
<em>Sequenciar bilhões de sequências por corrida de sequenciamento (Daniel MacArthur, 2019).</em></p>

<p>Para entender o processo de sequenciamento de terceira geração (trabalhando aqui particularmente com short-read da Illumina), é importante saber que ele basicamente envolve a coleta de uma amostra de DNA de uma pessoa, a quebra dessa amostra em pequenos fragmentos ou leituras individuais de DNA, a leitura de cada um desses segmentos e, em seguida, a união desses segmentos para formar uma imagem da sequência subjacente do genoma ou exoma dessa pessoa. Na prática, se falando de short-read sequencing a maioria das leituras que obtemos pode ter de 100 a 150 pares de bases de comprimento, tanto para WES ou WGS. A partir dessas leituras, podemos formar um conjunto de dados que é representado por um mar de leituras.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/05-genoma-referencia.png" alt="Comparação com genoma de referência" />
<em>Comparar as sequências com o genoma de referência (Daniel MacArthur, 2019).</em></p>

<p>Outro passo importante desse processo é o uso de uma sequência de referência. Essa sequência é definida como um padrão de referência usado universalmente. Atualmente, existem duas sequências de referência amplamente utilizadas na comunidade: a build 37 (<a href="https://www.biostars.org/p/123767/">hg19 ou GRCh37</a>) e a build 38 (<a href="https://gatk.broadinstitute.org/hc/en-us/articles/360035890951-Human-genome-reference-builds-GRCh38-or-hg38-b37-hg19">GRCh38 ou hg38</a>). Muitos estão fazendo a transição da build 37 para a build 38, que já está sendo utilizada em bancos de dados de referência como o <a href="https://gnomad.broadinstitute.org/">gnomAD</a>.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/06-ngs-multiplas-vezes.png" alt="NGS múltiplas leituras" />
<em>NGS nos permite sequenciar uma mesma posição múltiplas vezes (Daniel MacArthur, 2019).</em></p>

<p>Para iniciar esse processo, é necessário utilizar uma sequência de referência padrão que representa uma média da sequência da população humana. A seguir, são utilizados algoritmos de alinhamento, como o <a href="https://github.com/lh3/bwa">BWA</a>, para alinhar todas as leituras individuais de DNA contra essa sequência de referência. Embora esse processo de alinhamento não seja simples e possa apresentar erros, quando realizado adequadamente, é possível identificar variantes de sequência comuns ou raras diretamente a partir dos dados de leitura. O número de leituras de DNA em diferentes partes da sequência de referência pode variar, e em algumas áreas é possível identificar uma base que difere pelo menos em algumas leituras daquela do indivíduo de referência. Isso é chamado de variantes ou polimorfismos e a medida de confiança de que essa variante é real é dada pela relação entre o número de bases de referência e as bases não referenciadas, chamada frequência alélica.</p>

<p>Mapear dados de leituras curtas em relação à sequência de referência é um grande desafio. Normalmente, as leituras possuem cerca de 150 pares de bases, o que pode resultar em ambiguidade na hora de alinhar as leituras com a sequência de referência, especialmente em regiões altamente repetitivas. Além disso, a cobertura do genoma pode variar bastante em diferentes partes do genoma, o que afeta a confiança na chamada de variante de um determinado local. A qualidade da posição tende a ser substancialmente pior para inserções e deleções (INDELs) em comparação com variantes de nucleotídeo único (SNPs). A sensibilidade para INDELs ainda está longe da perfeição, mesmo com dados de sequenciamento de boa qualidade, e a taxa de erro para INDELs é certamente muito maior do que para SNPs. Para piorar, a detecção de variantes estruturais (SVs), como deleções, duplicações (CNVs) e outros rearranjos de DNA em larga escala, é muito difícil.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/07-tipos-arquivos.png" alt="Tipos de arquivos" />
<em>Tipos de arquivos — FASTQ, BAM/CRAM, VCF (Daniel MacArthur, 2019).</em></p>

<p>Em relação à interpretação de dados de sequenciamento, existem três tipos de arquivos importantes a serem considerados: <a href="https://en.wikipedia.org/wiki/FASTQ_format">FASTQ</a>, <a href="https://en.wikipedia.org/wiki/Binary_Alignment_Map">BAM</a>/<a href="https://en.wikipedia.org/wiki/CRAM_(file_format)">CRAM</a> e <a href="https://en.wikipedia.org/wiki/Variant_Call_Format">VCF</a>. O arquivo FASTQ contém os dados brutos de leitura que são gerados pela máquina de sequenciamento. Uma vez que essas leituras são alinhadas à sequência de referência, obtemos um arquivo BAM ou CRAM, que consiste em uma lista de leituras ordenadas pela sua posição em relação ao genoma de referência. O último tipo de arquivo é o VCF, que contém todas as variantes identificadas no arquivo BAM ou CRAM.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/08-comparacao-bancos-dados.png" alt="Comparação com bancos de dados" />
<em>Fazer um genoma ter sentido requer a comparação com muitos outros (Daniel MacArthur, 2019).</em></p>

<p>Para compreender as variações que surgem de cada um desses arquivos VCF, é necessário compará-los com bancos de dados populacionais como o gnomAD. Esses conjuntos de dados consistem em indivíduos de referência que foram agregados onde é possível verificar para qualquer variante encontrada no paciente, a frequência com que ela foi observada em uma coleção de dezenas de milhares de indivíduos — 140.000 indivíduos no caso do gnomAD — e o quão comum ela é em diferentes populações.</p>

<h2 id="tecnologias-disponíveis">Tecnologias disponíveis</h2>

<h3 id="1-microarrays">1. Microarrays</h3>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/09-microarrays.png" alt="Genotipagem por Microarrays" />
<em>Genotipagem por Microarrays, região de interesse em vermelho (Daniel MacArthur, 2019).</em></p>

<p>Para explorar as variações presentes no genoma de um indivíduo, existem várias tecnologias disponíveis. Uma dessas tecnologias é a genotipagem por meio de microarray. Esta tecnologia permite a investigação de posições específicas no genoma, geralmente posições que contêm variações comuns na população em geral. Os microarrays são extremamente baratos, com centenas de milhares de marcadores podendo ser testados por apenas ~$20. Podendo ser extremamente útil para análises de linkage em famílias. No entanto, os microarrays têm uma limitação importante: eles não detectam a maioria das variações raras, pois suas posições não incluem a maioria das variantes que são geralmente causadoras de doenças raras.</p>

<h3 id="2-wes">2. WES</h3>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/10-wes.png" alt="Sequenciamento de Exoma" />
<em>Sequenciamento de Exoma, região de interesse em vermelho (Daniel MacArthur, 2019).</em></p>

<p>Uma tecnologia muito mais útil para o nosso contexto é WES que é uma tecnologia focada na parte codificante do genoma humano que conhecidamente dá origem a proteínas, representando cerca de 2% do total do genoma, mas contém praticamente todas as partes que realmente entendemos a função biológica. A grande vantagem do WES é seu custo, o preço para sequenciar o exoma pode chegar a menos de $200. Isso permite que você obtenha sequenciamento para todos os tipos de variantes, incluindo variantes muito raras presentes nas regiões codificantes de proteínas do genoma. O lado negativo do WES é que ele só fornece o que você está procurando, ou seja, apenas as regiões codificantes do genoma, sendo todo o resto descartado.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/11-target-sequencing.png" alt="Target sequencing" />
<em>Sequenciamento com alvo (target sequencing), região de interesse em vermelho (Daniel MacArthur, 2019).</em></p>

<p>A ideia básica do WES é usar beads ou alguma outra sonda que contenha sequências individuais que se hibridizam apenas com as partes de sequência que estamos interessados em sequenciar, neste caso, as regiões codificantes de proteínas.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/12-cobertura-exoma.png" alt="Cobertura do exoma" />
<em>A cobertura do exoma é alta mas possui alta variabilidade (Daniel MacArthur, 2019).</em></p>

<p>Um dos desafios na análise de dados de sequenciamento de exoma é a grande diferença na cobertura de um exon para outro. Podemos ver que a cobertura média dessas regiões é de 50X a 60X, mas há uma enorme variação. Alguns exons têm cobertura de até 150X, enquanto outros têm cobertura muito baixa ou nenhuma cobertura em uma amostra específica.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/13-cobertura-genoma.png" alt="Cobertura do genoma" />
<em>A cobertura do genoma é bem uniforme quando comparado ao exoma (Daniel MacArthur, 2019).</em></p>

<p>Para comparar, é assim que a cobertura de sequenciamento se parece nos dados de sequenciamento do genoma. A cobertura, em média, é menor do que nas sequências do exoma, mas a uniformidade é maravilhosa. Há uma faixa muito estreita de cobertura, onde quase todos os éxons no genoma tendem a ser cobertos dentro de uma precisão bastante apertada.</p>

<h4 id="por-que-wes-como-primeira-alternativa">Por que WES como primeira alternativa?</h4>

<p>Então, vale a pena perguntar por que fazemos sequenciamento de exoma. A resposta óbvia é que é muito barato e podemos fazer um exoma familiar de trio por menos do que o custo de um único WGS hoje em dia. A cobertura mais alta dos segmentos principais significa que temos, talvez, 60X ou às vezes uma cobertura ainda maior em exons individuais. Mas a grande vantagem do sequenciamento de exoma é que ele nos fornece cobertura custo-efetiva das partes do genoma que podemos realmente interpretar. E quase todas as mutações conhecidas de doenças raras são encontradas nessas regiões codificadoras de proteínas. Achamos que é provável que mais de 85% de todas as variantes causais verdadeiras para condições mendelianas caiam dentro do exoma.</p>

<h3 id="3-wgs">3. WGS</h3>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/14-wgs.png" alt="Sequenciamento de genoma completo" />
<em>Sequenciamento de genoma completo, região de interesse em vermelho (Daniel MacArthur, 2019).</em></p>

<p>O sequenciamento de todo o genoma de uma pessoa é substancialmente mais caro do que o sequenciamento do exoma. Ele cobre quase todas as 3 bilhões de bases do genoma humano, com exceção das partes altamente repetitivas que são desafiadoras de serem mapeadas. No entanto, o sequenciamento do genoma inteiro tem muitas vantagens, como a possibilidade de identificar facilmente variações no número de cópias (CNVs) e variações estruturais (SVs), além da capacidade de descobrir variantes não codificantes.</p>

<h4 id="exemplo-de-casos-resolvidos-por-wgs">Exemplo de casos resolvidos por WGS</h4>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/15-dmd-pacientes.png" alt="Pacientes com DMD diagnosticados por WGS" />
<em>Pacientes com distrofia muscular de Duchenne que foram diagnosticados graças à análise de WGS (Daniel MacArthur, 2019).</em></p>

<p>Vou dar um exemplo de um caso que só poderia ser resolvido usando o sequenciamento completo do genoma. Esse caso foi descoberto em um estudo sobre distrofia muscular de Duchenne, que é a forma mais comum de distrofia muscular. Ela afeta principalmente meninos, exceto em casos raros. A doença é causada por mutações no gene DMD, encontrado no cromossomo X.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/16-dmd-inversao-exon.png" alt="Inversão de éxon no gene DMD" />
<em>Caso sem diagnóstico com inversão de éxon do gene DMD (Daniel MacArthur, 2019).</em></p>

<p>Na imagem acima, temos parte do gene DMD, que é um gene enorme no cenário genômico. Podemos observar duas coisas. Primeiro, há uma seção de leituras que está completamente ausente aqui. Então, neste menino, parece haver uma deleção, mas ela está no íntron do gene. No entanto, há também esse interessante grupo de leituras que estão se alinhando na orientação oposta ao que esperaríamos. Isso é característico de uma inversão.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/17-dmd-inversao-resolvida.png" alt="Resolução da inversão no DMD" />
<em>Caso de inversão do éxon no gene DMD (Daniel MacArthur, 2019).</em></p>

<p>Uma vez que resolvida a posição das leituras individuais, fica claro o que aconteceu: pequenas deleções de um lado e uma maior em verde. Além disso, o éxon encontrado nessa região foi invertido, resultando na sua exclusão da transcrição. Isso resultou em uma mudança no quadro de leitura, levando à perda completa do RNA e da proteína no paciente em questão. Apenas com o WGS é possível detectar rearranjos como este.</p>

<h4 id="wgs-não-resolve-tudo">WGS não resolve tudo</h4>

<p>Com o tempo descobrimos que mesmo sequenciando todo o genoma, nem sempre conseguimos solucionar todos os casos que sequenciamos. O WGS completo fornece apenas um aumento de 5% a 10% no poder de diagnóstico para casos que já tiveram WES. Para a grande maioria dos casos que foram negativos para o exoma, o sequenciamento do genoma não fornecerá a resposta.</p>

<h3 id="4-rna-seq">4. RNA-seq</h3>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/18-rna-seq.png" alt="Sequenciamento de RNA" />
<em>Sequenciamento de RNA, região de interesse em vermelho (Daniel MacArthur, 2019).</em></p>

<p>A tecnologia de sequenciamento de RNA, embora ainda não amplamente utilizada em diagnósticos, é muito poderosa e esperamos que seja cada vez mais adotada. O sequenciamento de RNA é diferente das outras tecnologias mencionadas, pois se concentra no RNA mensageiro em vez dos componentes de DNA do genoma. Ele sequencia todas as regiões expressas do genoma e fornece informações diretas sobre o impacto de variantes genéticas na expressão gênica e splicing. Sua capacidade de detectar interrupções no splicing tem sido poderosa para identificar variantes de splicing não anotadas.</p>

<h4 id="dois-casos-não-diagnosticados-de-miopatia-relacionada-ao-colágeno">Dois casos não diagnosticados de miopatia relacionada ao colágeno</h4>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/19-col6-paciente.png" alt="Paciente com miopatia do colágeno VI" />
<em>Paciente com miopatia relacionada ao colágeno VI (Daniel MacArthur, 2019).</em></p>

<p>A miopatia do colágeno é uma doença rara em que os distúrbios nos genes do colágeno seis (COL6), geralmente dominantes, mas às vezes recessivos, resultam em uma variedade de fenótipos musculares relativamente graves. O médico da paciente na imagem tinha certeza do diagnóstico de miopatia do colágeno 6, mas não conseguiram identificar uma variante causal, apesar de terem feito o sequenciamento de todas as três cadeias do colágeno 6, WES e WGS.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/20-sashimi-col6a1.png" alt="Gráfico de sashimi COL6A1" />
<em>Gráfico de sashimi onde RNA-seq identifica um ganho de splicing intrônico no gene COL6A1 (Daniel MacArthur, 2019).</em></p>

<p>Foram obtidas amostras de RNA muscular desses dois pacientes e sequenciado o RNA. Na última fileira do gráfico de sashimi, vemos o RNA-seq de um controle em uma região do genoma que contém três éxons do gene COL6A1. O que chamou a atenção em ambos os casos diagnosticados com colagenopatia 6 foi que ambos tinham um novo pseudo éxon que aparecia no meio de um intron e que não foi visto em nenhum outro indivíduo normal para quem o sequenciamento de RNA muscular foi feito — cerca de 180 pessoas.</p>

<p><img src="/assets/images/posts/sequenciamento-doencas-raras/21-variante-gc-gt.png" alt="Variante GC&gt;GT" />
<em>A variante GC&gt;GT gera um sítio doador de splicing (Daniel MacArthur, 2019).</em></p>

<p>Ao analisarmos a sequência do genoma nessa região específica, encontramos em ambos os pacientes uma mutação de GC para GT, resultando em uma variante doador de splice GT no meio de um íntron. Essa mutação GT resultou na criação de um novo local de splice e, portanto, na criação de um novo exon no meio da sequência intrônica. Curiosamente, essa inclusão de exon parece ser específica para o músculo e essa variante se tornou a mutação mais comum nos casos não diagnosticados de miopatia do colágeno 6.</p>

<h3 id="quando-considerar-rna-seq">Quando considerar RNA-seq?</h3>

<p>O sequenciamento de RNA é mais útil em casos onde há um gene candidato forte, mas a causa genética ainda não está clara. Especialmente em casos de famílias recessivas, onde há apenas uma mutação em um gene recessivo que se encaixa perfeitamente no fenótipo, mas não há uma segunda variante codificadora que explique o caso daquela família em particular.</p>

<p>Um segundo fator chave é a disponibilidade de tecido. É realmente importante sequenciar o tecido certo. Outra coisa a considerar é que muitas das mutações causais descobertas com RNA-seq afetarão o splicing, muitas vezes fora das regiões de splicing canônicas. Portanto, é útil ter dados de WGS em paralelo com o RNA-seq.</p>

<h2 id="alguns-pontos-a-serem-discutidos">Alguns pontos a serem discutidos</h2>

<h3 id="será-que-o-wgs-vai-substituir-wes-como-teste-de-entrada">Será que o WGS vai substituir WES como teste de entrada?</h3>

<p>WES é mais eficiente em termos de custo do que o WGS, vale a pena pensar sobre o momento em que WGS se tornará a tecnologia mais eficiente em termos de custo. Acreditamos que isso provavelmente acontecerá em algum momento nos próximos anos, embora vale mencionar que à medida que o preço do WGS diminui, o preço do WES continua a acompanhar.</p>

<h3 id="quais-classes-de-genes-relacionados-a-doenças-podem-ser-descobertas-apenas-por-wgs">Quais classes de genes relacionados a doenças podem ser descobertas apenas por WGS?</h3>

<p>Exemplos disso poderiam ser casos em que todas as mutações causais para aquela doença específica são em regiões do genoma que não codificam proteínas por algum motivo, ou podem estar em alguma região que é inacessível por WES.</p>

<h3 id="como-lidar-com-a-descoberta-de-genes-em-casos-de-doenças-em-que-a-causa-genética-subjacente-não-é-monogênica">Como lidar com a descoberta de genes em casos de doenças em que a causa genética subjacente não é monogênica?</h3>

<p>Especialmente em condições genéticas complexas, onde é provável que haja vários genes que contribuam para esse fenótipo. Nesse caso, a única resposta é o tamanho da amostra. Só com grupos de mais de 1000 indivíduos é que poderíamos começar a obter o poder necessário.</p>

<h3 id="quais-tipos-de-tecidos-são-uma-boa-opção-para-rna-seq">Quais tipos de tecidos são uma boa opção para RNA-seq?</h3>

<p>Um caso a se considerar é a utilização de RNA-seq de alta profundidade do sangue. Além disso, sempre que possível, obter fibroblastos seria muito útil. Eles parecem ser um tipo de tecido mais útil em geral do que o sangue. Acredita-se que isso seja especialmente verdadeiro para condições neurológicas, em que fibroblastos parecem ser um melhor substituto para o cérebro do que o sangue.</p>

<h2 id="referências">Referências</h2>

<ol>
  <li>Manolio, Teri A., et al. “Finding the missing heritability of complex diseases.” Nature 461.7265 (2009): 747–753.</li>
  <li><a href="https://youtu.be/57McshGrllQ">Interpreting Genomes for Rare Disease: Intro to Next Generation Sequencing — Daniel MacArthur, PhD, 2019</a></li>
  <li><a href="https://cmg.broadinstitute.org/course-offering">Interpreting genomes for rare disease: variant and gene interpretation, 2019</a></li>
</ol>

<hr />

<p><em>Publicado originalmente no <a href="https://geocarvalho.medium.com/introdu%C3%A7%C3%A3o-ao-sequenciamento-de-terceira-gera%C3%A7%C3%A3o-para-doen%C3%A7as-raras-44fd8df18989">Medium</a>.</em></p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="bioinformatics" /><category term="genomics" /><category term="rare-disease" /><category term="sequencing" /><category term="ngs" /><summary type="html"><![CDATA[Anotações da primeira aula do curso “Interpreting Genomes for Rare Disease” ministrada por Daniel MacArthur, PhD e adição de outros materiais, mais detalhes nas referências.]]></summary></entry><entry><title type="html">Introdução ao WDL — Parte 1</title><link href="https://geocarvalho.github.io/bioinformatics/2021/02/28/introducao-ao-wdl-parte-1.html" rel="alternate" type="text/html" title="Introdução ao WDL — Parte 1" /><published>2021-02-28T00:00:00+00:00</published><updated>2021-02-28T00:00:00+00:00</updated><id>https://geocarvalho.github.io/bioinformatics/2021/02/28/introducao-ao-wdl-parte-1</id><content type="html" xml:base="https://geocarvalho.github.io/bioinformatics/2021/02/28/introducao-ao-wdl-parte-1.html"><![CDATA[<p>O WDL (Workflow Description Language) é uma linguagem de script open-source que permite você especificar workflows de processos de uma forma fácil e com uma sintaxe simples (~palavras deles~).</p>

<p>Foi originalmente desenvolvida para a área de Genômica pelo instituto BROAD, mas pode ser usado em diversas outras áreas que trabalhem com dados.</p>

<p>WDL se lê “Widdle”.</p>

<p>Não vou entrar em comparações entre linguagens de workflow, que são várias. Para quem tem interesse, existe essa <a href="https://github.com/common-workflow-language/common-workflow-language/wiki/Existing-Workflow-systems">lista</a> feita pelo pessoal do CWL (Common Workflow Language). Particularmente, eu tenho mais interesse por <a href="https://github.com/nextflow-io/nextflow">Nextflow</a> (pretendo escrever tutoriais usando isso posteriormente). Mas comecei usando <a href="https://github.com/common-workflow-language/common-workflow-language">CWL</a>, depois Nextflow, dei uma olhada em <a href="https://github.com/snakemake/snakemake">Snakemake</a> e hoje tenho estudado WDL.</p>

<p>Isso depende muito do grupo que você está trabalhando e parecido com linguagens de programação você deve entender onde melhor aplicadas elas seriam. WDL por exemplo tem uma relação muito forte com o pessoal da ferramenta <a href="https://gatk.broadinstitute.org/hc/en-us/articles/360035889771-Pipelining-GATK-with-WDL-and-Cromwell">GATK</a> e o <a href="https://support.terra.bio/hc/en-us/articles/360037117492-Getting-Started-with-WDL">Terra</a> (baseado no Google Cloud), assim já existem diversos scripts que você pode reutilizar, por exemplo no <a href="https://github.com/biowdl">BioWDL</a> você já encontra alguns.</p>

<h2 id="aprendendo-wdl">Aprendendo WDL</h2>

<p>Iremos trabalhar os tutoriais do “<a href="https://github.com/openwdl/learn-wdl">learn-wdl</a>”, para isso faça download do repositório e abra a pasta com seu editor de código.</p>

<p>Para o Visual Studio Code recomendo baixar a extensão “WDL DevTools”.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/openwdl/learn-wdl.git
</code></pre></div></div>

<h3 id="ambiente-do-wdl">Ambiente do WDL</h3>

<p>Usei como referência esse material do learn-wdl. Eu estou trabalhando em cima do Ubuntu Xenial (16.04). Primeiramente você precisa ter os seguintes pré-requisitos:</p>

<p><strong>Java:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java <span class="nt">-version</span>
<span class="c"># openjdk version "1.8.0_275"</span>
<span class="c"># OpenJDK Runtime Environment (build 1.8.0_275-8u275-b01-0ubuntu1~16.04-b01)</span>
<span class="c"># OpenJDK 64-Bit Server VM (build 25.275-b01, mixed mode)</span>
</code></pre></div></div>

<p>Para instalar você pode usar os seguintes comandos:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>openjre-8-headless
<span class="c"># ou</span>
<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install </span>default-jdk
</code></pre></div></div>

<p><strong>Docker:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker <span class="nt">--version</span>
<span class="c"># Docker version 17.06.1-ce, build 874a737</span>
</code></pre></div></div>

<p>Para instalar o Docker:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sS</span> https://get.docker.com/ | sh
<span class="nb">sudo </span>usermod <span class="nt">-aG</span> docker <span class="nv">$USER</span>
</code></pre></div></div>

<p>Caso não funcione pra você procure na <a href="https://docs.docker.com/engine/install/ubuntu/">documentação</a> do Docker.</p>

<p><strong>Cromwell e Womtool:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> cromwell-56.jar <span class="nt">--version</span>
<span class="c"># cromwell 56</span>

<span class="nb">sudo </span>java <span class="nt">-jar</span> womtool-56.jar <span class="nt">--version</span>
<span class="c"># womtool 56</span>
</code></pre></div></div>

<p>Para instalar:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>cromwell<span class="p">;</span> <span class="nb">cd </span>cromwell
curl <span class="nt">-L</span> <span class="nt">-o</span> cromwell-56.jar https://github.com/broadinstitute/cromwell/releases/download/56/cromwell-56.jar
curl <span class="nt">-L</span> <span class="nt">-o</span> womtool-56.jar https://github.com/broadinstitute/cromwell/releases/download/56/womtool-56.jar
</code></pre></div></div>

<p>É bom copiar o caminho desses <code class="language-plaintext highlighter-rouge">.jar</code> para usarmos depois pra rodar os scripts.</p>

<p>Também é bom dar uma olhada no <a href="https://github.com/broadinstitute/cromwell/releases">github</a> para obter a release mais recente das duas ferramentas.</p>

<h2 id="1-como-fazer-um-hello-world">1. Como fazer um “hello world”</h2>

<p>Volte a pasta do learn-wdl. No caso eu fui para:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ../learn-wdl/1_script_examples/1_hello_worlds/1_hello
</code></pre></div></div>

<p>Vamos analisar o código <code class="language-plaintext highlighter-rouge">hello.wdl</code> que temos as três principais partes do WDL:</p>

<pre><code class="language-wdl">version 1.0

workflow HelloWorld {
  call WriteGreeting
}

task WriteGreeting {
  command {
     echo "Hello World"
  }
  output {
     File output_greeting = stdout()
  }
}
</code></pre>

<ul>
  <li>Linhas 7–15 nós temos a task que é um comando simples em bash para printar um “Hello” que vai ser um output com nome <code class="language-plaintext highlighter-rouge">output_greeting</code>.</li>
  <li>Linhas 3–5 temos a definição do workflow que simplesmente chama uma task definida.</li>
</ul>

<h2 id="2-executando-o-script">2. Executando o script</h2>

<ul>
  <li>Cromwell é um serviço em Java que organiza a execução (job scheduler) e pode ser configurado para rodar em diferentes serviços (AWS, GCP, etc).</li>
  <li>WDL é o script que descreve o workflow.</li>
</ul>

<p>Para começar vamos usar o Cromwell no modo Run que é normalmente utilizado para rodar protótipos, rodando workflows individuais. Podemos usar em uma máquina local (como fizemos anteriormente) ou em uma máquina virtual na nuvem.</p>

<p>Para trabalhar em produção normalmente se trabalha com o modo Server. Usando uma máquina na nuvem que vai ser utilizada como o organizador de execuções (Java scheduler), assim poderemos rodar múltiplos workflows.</p>

<h3 id="então-como-rodamos-o-script">Então como rodamos o script?</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run hello.wdl
</code></pre></div></div>

<p>Verás que o output é bem verboso, que no meio em algum momento ele printou nosso “Hello World” com sucesso!</p>

<h2 id="3-sobre-o-modo-server">3. Sobre o modo Server</h2>

<p>Quando vamos rodar nossas análises, nós normalmente utilizamos o modo Server. Você pode utilizar do jeito mais comum que é na “nuvem pública” ou no seu próprio HPC (High Performance Computing):</p>

<ul>
  <li><strong>Terra.bio</strong> — Cromwell já disponível no ambiente da GCP.</li>
  <li><strong>Azure</strong> — conjuntos de VMs com Azure batch.</li>
  <li><strong>AWS</strong> — conjuntos de VMs com AWS batch.</li>
  <li><strong>GCP</strong> — conjunto de VMs (máquinas virtuais) com life sciences APIs.</li>
</ul>

<p>A ideia é você usar grupos de VMs que são ativadas dinamicamente. Os criadores indicam usar o Terra.bio por ter sido criado pela BROAD. Para mais informações veja o tutorial do learn-wdl no YouTube [1].</p>

<h2 id="4-lidando-com-erros">4. Lidando com erros</h2>

<p>Vamos analisar agora com o script <code class="language-plaintext highlighter-rouge">x_hello-error.wdl</code> que possui um erro.</p>

<ul>
  <li>Para o modo Run que estamos usando aqui a ferramenta <code class="language-plaintext highlighter-rouge">womtool.jar</code> vai ajudar a detectar possíveis erros no nosso script.</li>
  <li>Primeiramente tenha certeza de estar usando a versão mais atual do Cromwell. Assim você terá certeza que as especificações de linguagem e features são suportadas.</li>
</ul>

<p>Agora vamos tentar rodar esse script e ver o que ele retorna.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ../2_hello_docker
<span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run x_hello-error.wdl
<span class="c"># ...</span>
<span class="c"># Workflow f7b32c3a-6c99-4fdc-b161-4781fde97a2d transitioned to state Failed</span>
</code></pre></div></div>

<p>Verás que ao final do processo uma mensagem indicando um Failed. Mas se você for para o meio do processo verá:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to process workflow definition 'HelloWorld' (reason 1 of 1):
Cannot generate outputs for 'call WriteGreeting'.
No such callable exists in [WriteGreetings]
</code></pre></div></div>

<p>Com essa informação já podemos resolver o problema do script, mas vamos ver o que o <code class="language-plaintext highlighter-rouge">womtool</code> nos mostrará.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/womtool-56.jar validate x_hello-error.wdl
<span class="c"># Failed to process workflow definition 'HelloWorld' (reason 1 of 1):</span>
<span class="c"># Cannot generate outputs for 'call WriteGreeting'.</span>
<span class="c"># No such callable exists in [WriteGreetings]</span>
</code></pre></div></div>

<p>Ele nos mostra o mesmo resultado, porém não precisamos ficar procurando no meio dos processos do comando do Cromwell, facilitando nossa vida. Existem outros comandos que podem ser vistos na <a href="https://cromwell.readthedocs.io/en/stable/WOMtool/">documentação</a> ou usando o <code class="language-plaintext highlighter-rouge">--help</code>.</p>

<p>Abrindo o arquivo para resolver o bug:</p>

<pre><code class="language-wdl">version 1.0

workflow HelloWorld {
  call WriteGreeting
}

task WriteGreetings {
  command {
    echo "Hello World"
  }
  output {
    File output_greeting = stdout()
  }
  runtime {
    docker: "ubuntu:latest"
  }
}
</code></pre>

<p>Vemos a adição de um “s” na <code class="language-plaintext highlighter-rouge">task WriteGreetings</code> que deveria ser <code class="language-plaintext highlighter-rouge">WriteGreeting</code>. Resolvendo isso e rodando novamente obtemos sucesso no processo.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/womtool-56.jar validate x_hello-error.wdl
<span class="c"># Success!</span>
</code></pre></div></div>

<p>Para o modo Server no Terra.bio ao tentar fazer upload de um script no FireCloud ele vai mostrar o mesmo output do <code class="language-plaintext highlighter-rouge">womtool</code>.</p>

<p>Dependendo do ambiente na nuvem é preciso ter no script a descrição de <code class="language-plaintext highlighter-rouge">runtime</code> com os atributos <code class="language-plaintext highlighter-rouge">docker</code> que na primeira versão não tínhamos.</p>

<h2 id="5-variáveis-no-script">5. Variáveis no script</h2>

<p>O script exemplo agora é o <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/3_hello_input_task/input-task.wdl</code></p>

<pre><code class="language-wdl">version 1.0

workflow HelloInput {
  call WriteGreeting
}

task WriteGreeting {
  input {
    String name
  }
  command {
    echo 'hello ${name}!'
  }
  output {
    File response = stdout()
  }
  runtime {
    docker: 'ubuntu:latest'
  }
}
</code></pre>

<p>Primeiramente precisamos validar o script usando o <code class="language-plaintext highlighter-rouge">womtool-56.jar</code> como visto anteriormente:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/womtool-56.jar validate input-task.wdl
<span class="c"># Success!</span>
</code></pre></div></div>

<p>Feito isso, vamos tentar rodar ele usando o Cromwell:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run input-task.wdl
<span class="c"># ...</span>
<span class="c"># Workflow b13559c0-223d-4b08-9548-0a3a2ed1f111 transitioned to state Failed</span>
</code></pre></div></div>

<p>Como vemos que falhou e subindo mais um pouco acharemos o motivo:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Required workflow input 'HelloInput.WriteGreeting.name' not specified
</code></pre></div></div>

<p>Então claramente ele quebrou porque não recebeu a variável esperada, nesse caso o <code class="language-plaintext highlighter-rouge">womtool</code> pode nos ajudar. Nele existe um modo <code class="language-plaintext highlighter-rouge">inputs</code> que indica se o script possui variáveis a serem dadas de entrada.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/womtool-56.jar inputs input-task.wdl
<span class="c"># {</span>
<span class="c">#   "HelloInput.WriteGreeting.name": "String"</span>
<span class="c"># }</span>
</code></pre></div></div>

<p>Esse comando nos retorna o que precisamos dar de entrada, no caso deveria ser um <code class="language-plaintext highlighter-rouge">inputs.json</code> no formato indicado pelo resultado do comando. Para facilitar nossa vida, podemos jogar isso para um arquivo e manipular <code class="language-plaintext highlighter-rouge">"String"</code> com o que queremos e então rodar o script novamente passando esse arquivo dessa vez.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/womtool-56.jar inputs input-task.wdl <span class="o">&gt;</span> inputs.json
vim inputs.json

<span class="nb">cat </span>inputs.json
<span class="c"># {</span>
<span class="c">#   "HelloInput.WriteGreeting.name": "Input"</span>
<span class="c"># }</span>

<span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run input-task.wdl <span class="nt">--inputs</span> inputs.json
<span class="c"># ...</span>
<span class="c"># echo 'hello Input!'</span>
</code></pre></div></div>

<p>Pensando no modo Server existe esse <a href="https://support.terra.bio/hc/en-us/articles/360037485511-Add-Variables">artigo</a> no Terra super bem exemplificado de como usar variáveis lá. Além disso, esse <a href="https://support.terra.bio/hc/en-us/articles/360037484851-Variable-Types-in-WDL">artigo</a> fala sobre os tipos de variáveis disponíveis que é bem útil na hora de você implementar seu pipeline mais na frente.</p>

<p>Tente fazer o mesmo com o script em <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/4_hello_input_workflow/input-workflow.wdl</code>.</p>

<h2 id="6-arquivos-e-variáveis-de-ambiente">6. Arquivos e variáveis de ambiente</h2>

<p>Agora trabalharemos em cima do script <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/5_hello_file/hello-file.wdl</code>.</p>

<pre><code class="language-wdl">version 1.0

workflow HelloFile {
    input {
        File file_input
        Int mem_gb
    }
    call InputFile {
        input:
            file_input=file_input,
            mem_gb=mem_gb
    }
}

task InputFile {
    input {
        File file_input
        Int mem_gb
    }
    command {
        bash echo 'The file is ${file_input}!' ${mem_gb}
    }
    output {
        File result = stdout()
    }
    runtime {
        docker: "ubuntu:latest"
        memory: mem_gb + "GB"
        continueOnReturnCode: 126
    }
}
</code></pre>

<p>Aqui vamos usar o conceito anterior de passar um <code class="language-plaintext highlighter-rouge">.json</code> como entrada, no caso o arquivo que está na mesma pasta.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run hello-file.wdl <span class="nt">--inputs</span> hello-file.json
<span class="c"># ...</span>
<span class="c"># [warn] Local: Key/s [memory] is/are not supported by backend.</span>
<span class="c"># Unsupported attributes will not be part of job executions.</span>
</code></pre></div></div>

<p>Apesar de rodar com sucesso, temos essa observação de <code class="language-plaintext highlighter-rouge">warn</code> a ser analisada em meio a todos os processos printados na tela. Isso se chama memory assignment, só para mostrar que os parâmetros dependem do ambiente de execução.</p>

<p>Só pra lembrar que não precisa estar na pasta dos scripts para rodá-los, você pode passar o caminho+script que ele será rodado normalmente.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run /caminho/para/hello-file.wdl <span class="se">\</span>
    <span class="nt">--inputs</span> /caminho/para/hello-file.json
</code></pre></div></div>

<p>É comum usarmos arquivos que estejam salvos em buckets na nuvem, então é possível por esses arquivos no seu <code class="language-plaintext highlighter-rouge">.json</code> de entrada (com a devida permissão de acesso ao arquivo no bucket) para o script em WDL. Por exemplo:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"HelloFile.file_input"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://storage.googleapis.com/wdl-quickstart/wdl-data/input.txt"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"HelloFile.mem_gb"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Isso é interessante uma vez que vamos rodar pipelines em VMs que estarão disponíveis apenas para gerar os resultados do pipeline, então arquivos que devem persistir para uso no pipeline podem ser incorporados assim.</p>

<h2 id="7-scripts-mais-legíveis">7. Scripts mais legíveis</h2>

<p>Abrindo o script em <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/6_alias_task/hello-again.wdl</code> vemos:</p>

<pre><code class="language-wdl">version 1.0

workflow HelloWorldWithDocker {
    call WriteGreeting as wg1
    call WriteGreeting as wg2
}

task WriteGreeting {
    command {
        echo "Hello Docker"
    }
    output {
        File output_greeting = stdout()
    }
    runtime {
        docker: "ubuntu:latest"
    }
}
</code></pre>

<p>Aqui é apenas para observar a possibilidade de criar alias com as <code class="language-plaintext highlighter-rouge">task</code> que usamos, no exemplo está repetida a <code class="language-plaintext highlighter-rouge">call</code> mas com alias diferentes que podemos ver no output quando rodamos o script.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run /caminho/para/hello-again.wdl
<span class="c"># ...</span>
<span class="c"># {</span>
<span class="c">#   "outputs": {</span>
<span class="c">#     "HelloWorldWithDocker.wg1.output_greeting": ".../call-wg1/execution/stdout",</span>
<span class="c">#     "HelloWorldWithDocker.wg2.output_greeting": ".../call-wg2/execution/stdout"</span>
<span class="c">#   }</span>
<span class="c"># }</span>
</code></pre></div></div>

<p>Existe outro exemplo em <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/6_alias_task/hello-task.wdl</code> caso queira refazer o processo. Mas para esse script nós vamos rodar um script que o importará para fazer a chamada das <code class="language-plaintext highlighter-rouge">task</code> contidas, isso se chama subworkflow. O script maior é o <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/6_alias_task/hello-workflow.wdl</code></p>

<pre><code class="language-wdl">version 1.0

import "hello-task.wdl" as HelloTask

workflow HelloWorldWithDocker {
    call HelloTask.WriteGreeting
    call HelloTask.WriteGreeting as wg
}
</code></pre>

<p>Rodamos esse script que tem duas <code class="language-plaintext highlighter-rouge">call</code> para a mesma <code class="language-plaintext highlighter-rouge">task</code> só para exemplificar que sem o <code class="language-plaintext highlighter-rouge">alias</code> essa chamada daria erro por estar repetida. Além de introduzir o conceito de <code class="language-plaintext highlighter-rouge">import</code> e poder reutilizar scripts menores em processos maiores.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>java <span class="nt">-jar</span> /caminho/para/cromwell-56.jar run /caminho/para/hello-workflow.wdl
<span class="c"># ...</span>
<span class="c"># {</span>
<span class="c">#   "outputs": {</span>
<span class="c">#     "HelloWorldWithDocker.WriteGreeting.output_greeting": ".../call-WriteGreeting/execution/stdout",</span>
<span class="c">#     "HelloWorldWithDocker.wg.output_greeting": ".../call-wg/execution/stdout"</span>
<span class="c">#   }</span>
<span class="c"># }</span>
</code></pre></div></div>

<p>Use os scripts em <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/7_subworkflow/hello-workflow.wdl</code> para treinar tudo que vimos sobre rodar workflow e inputs.</p>

<p>Aqui eu termino a primeira parte da introdução, de acordo com o curso do learn-wdl olhamos todos os arquivos da pasta <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/1_hello_worlds/</code>. Na parte dois que estou escrevendo olharemos a pasta <code class="language-plaintext highlighter-rouge">~/learn-wdl/1_script_examples/2_language_patterns/</code>, pelo menos essa é a ideia. Esse tutorial pode mudar a medida que eu for estudando, então se ligue nas referências.</p>

<h2 id="referências">Referências</h2>

<ol>
  <li><a href="https://support.terra.bio/hc/en-us/articles/360037493871-What-do-I-need-to-set-up-to-write-and-execute-WDL-workflows-">What do I need to set up to write and execute WDL workflows?</a></li>
  <li><a href="https://github.com/openwdl/learn-wdl/blob/master/5_reference_material/2_WDL-dev-env.md#wdl-dev-env">How to set up GCE VM for WDL</a></li>
  <li><a href="https://www.youtube.com/watch?v=RtcW2Zdn_28&amp;list=PL4Q4HssKcxYv5syJKUKRrD8Fbd-_CnxTM">Learn WDL Course playlist</a></li>
</ol>

<hr />

<p><em>Publicado originalmente no <a href="https://geocarvalho.medium.com/introdu%C3%A7%C3%A3o-ao-wdl-parte-1-ba01cf179db2">Medium</a>.</em></p>]]></content><author><name></name></author><category term="bioinformatics" /><category term="wdl" /><category term="bioinformatics" /><category term="workflow" /><category term="cloud" /><summary type="html"><![CDATA[O WDL (Workflow Description Language) é uma linguagem de script open-source que permite você especificar workflows de processos de uma forma fácil e com uma sintaxe simples (~palavras deles~).]]></summary></entry><entry><title type="html">Uma introdução ao AutoML com PyCaret</title><link href="https://geocarvalho.github.io/data-science/2021/02/15/introducao-ao-automl-com-pycaret.html" rel="alternate" type="text/html" title="Uma introdução ao AutoML com PyCaret" /><published>2021-02-15T00:00:00+00:00</published><updated>2021-02-15T00:00:00+00:00</updated><id>https://geocarvalho.github.io/data-science/2021/02/15/introducao-ao-automl-com-pycaret</id><content type="html" xml:base="https://geocarvalho.github.io/data-science/2021/02/15/introducao-ao-automl-com-pycaret.html"><![CDATA[<p>AutoML é a parte de aprendizado de máquina automatizado, nela processos e tarefas que não requerem tomada de decisão são automatizados facilitando a criação de modelos e seu deploy em larga escala com eficiência e aumentando a produtividade do desenvolvedor.</p>

<p>Nesse tutorial vamos ver o <a href="https://github.com/pycaret/pycaret">PyCaret</a> criado por <a href="https://www.linkedin.com/in/profile-moez/">Moez Ali</a>. PyCaret é uma biblioteca aberta com pouco uso de código em Python. Ela permite que você prepare seus dados e compare modelos selecionando o melhor de acordo com uma métrica de interesse em poucos minutos. Ela pode ser usada para problemas de <a href="https://pycaret.readthedocs.io/en/latest/api/classification.html">classificação</a>, <a href="https://pycaret.readthedocs.io/en/latest/api/regression.html">regressão</a>, <a href="https://pycaret.readthedocs.io/en/latest/api/clustering.html">clusterização</a>, <a href="https://pycaret.readthedocs.io/en/latest/api/anomaly.html">detecção de anomalias</a>, <a href="https://pycaret.readthedocs.io/en/latest/api/nlp.html">processamento de linguagem natural</a>, <a href="https://pycaret.readthedocs.io/en/latest/api/arules.html">mineração de regras de associação</a>, etc.</p>

<p>No site deles existem alguns <a href="https://pycaret.readthedocs.io/en/latest/tutorials.html">tutoriais</a> bem interessantes, vale a pena dar uma olhada pra ver se é possível usar em algum projeto do seu interesse. Nas referências desse post vou deixar alguns materiais que levei em conta ao escrever esse. Aqui nós vamos usar dados bem simples do UCI “<a href="https://archive.ics.uci.edu/ml/datasets/Heart+failure+clinical+records">Heart failure clinical records Data Set</a>” que já estão prontos (fora da realidade) para serem processados para um problema de classificação.</p>

<p>Eu recomendo você usar o Google Colab para testar essa ferramenta, o meu colab que usei para criar esse post pode ser visto <a href="https://github.com/geocarvalho/tutoriais/blob/main/ml/pycaret_first_impression.ipynb">aqui</a>. Recomendo você criar do zero um novo e ir rodando aos poucos e à medida que tiver dúvidas ir jogando no google e pesquisando na documentação do PyCaret.</p>

<p>Na versão 2.x está disponível o uso de GPU para treinar os modelos, mas pra que usar o PyCaret?</p>

<ul>
  <li>O pessoal usa os modelos criados pelo PyCaret nos dashboards do PowerBI.</li>
  <li>Testar modelos para projetos que você ache interessante usar aprendizagem de máquina (protótipos).</li>
</ul>

<h2 id="instalando-o-pycaret">Instalando o PyCaret</h2>

<p>Como estamos usando o Colab, basta usar o comando pip para instalar a ferramenta:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">!</span><span class="n">pip</span> <span class="n">install</span> <span class="n">pycaret</span>
</code></pre></div></div>

<h2 id="abrindo-os-dados-de-exemplo">Abrindo os dados de exemplo</h2>

<p>Como eu disse antes vamos trabalhar com dados já bonitinhos para a criação de modelos e isso tá longe da realidade ok?! Esse é só um caso para a gente ver algumas utilidades do PyCaret. Os dados eu encontrei dentro de outro github e estou reaproveitando o <a href="https://github.com/lorenzodenisi/Heart-Failure-Clinical-Records/blob/master/heart_failure_clinical_records_dataset.csv">link</a> para abrirmos aqui. Primeiro importaremos as bibliotecas necessárias e logo depois usaremos a biblioteca <a href="https://github.com/pandas-dev/pandas">Pandas</a> (sim, subentendo que você já conheça) para abrir os dados no formato de Dataframe.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span>
<span class="kn">import</span> <span class="nn">pycaret.classification</span> <span class="k">as</span> <span class="n">pc</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>

<span class="n">data_url</span> <span class="o">=</span> <span class="s">"https://raw.githubusercontent.com/lorenzodenisi/Heart-Failure-Clinical-Records/master/heart_failure_clinical_records_dataset.csv"</span>
<span class="n">data_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">data_url</span><span class="p">)</span>
<span class="n">data_df</span><span class="p">.</span><span class="n">head</span><span class="p">()</span>
</code></pre></div></div>

<p>Com os dados disponíveis, vemos se está tudo ok vendo o tamanho do Dataframe (299 amostras e 13 colunas) e se existe algum dado faltante (não deveria).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data_df</span><span class="p">.</span><span class="n">shape</span>
<span class="n">data_df</span><span class="p">.</span><span class="n">isnull</span><span class="p">().</span><span class="nb">sum</span><span class="p">()</span>
</code></pre></div></div>

<p>Agora é bom dar uma olhada nas colunas para saber qual delas vamos usar como classe de interesse.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data_df</span><span class="p">.</span><span class="n">columns</span>
</code></pre></div></div>

<p>No nosso caso vamos usar a coluna “DEATH_EVENT” como classe alvo, sempre é bom ver se estamos trabalhando com dados desbalanceados (apesar que não vamos levar isso em consideração agora).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data_df</span><span class="p">[</span><span class="s">"DEATH_EVENT"</span><span class="p">].</span><span class="n">value_counts</span><span class="p">().</span><span class="n">plot</span><span class="p">(</span><span class="n">kind</span><span class="o">=</span><span class="s">"bar"</span><span class="p">)</span>
</code></pre></div></div>

<p>Bora dividir os dados em treino e teste. Esse teste a gente só vai usar no final pra rodar a predição do modelo.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">train</span><span class="p">,</span> <span class="n">test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span><span class="n">data_df</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="usando-pycaret-finalmente">Usando PyCaret, finalmente</h2>

<p>Como visto antes importamos o <code class="language-plaintext highlighter-rouge">pycaret.classification</code> por estarmos trabalhando com um problema de classificação, para outros problemas como regressão por exemplo seria uma outra importação, para mais informações sobre isso dá uma olhada na <a href="https://pycaret.org/setup/">documentação</a>. Para ver os métodos e atributos disponíveis:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">dir</span><span class="p">(</span><span class="n">pc</span><span class="p">)</span>
</code></pre></div></div>

<p>Por estarmos usando o Google Colab é preciso fazer uma ativação do ambiente:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pycaret.utils</span> <span class="kn">import</span> <span class="n">enable_colab</span>
<span class="n">enable_colab</span><span class="p">()</span>
</code></pre></div></div>

<p>Agora podemos iniciar a brincadeira, para isso passamos os dados e a coluna alvo para o método <code class="language-plaintext highlighter-rouge">setup</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">clf</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">setup</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">train</span><span class="p">,</span> <span class="n">target</span><span class="o">=</span><span class="s">"DEATH_EVENT"</span><span class="p">)</span>
</code></pre></div></div>

<p>Aqui você precisa dar enter se os tipos das colunas foram inferidos corretamente, senão é possível modificar após escrever <code class="language-plaintext highlighter-rouge">quit</code> a partir de parâmetro no <code class="language-plaintext highlighter-rouge">setup</code>, mais informações <a href="https://pycaret.org/data-types/">aqui</a>. Além disso é possível aqui ignorar alguma coluna que você não queira usar como feature para os modelos, basta:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pc</span><span class="p">.</span><span class="n">setup</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">train</span><span class="p">,</span> <span class="n">target</span><span class="o">=</span><span class="s">"DEATH_EVENT"</span><span class="p">,</span> <span class="n">ignore_features</span><span class="o">=</span><span class="p">[</span><span class="s">"ages"</span><span class="p">,</span> <span class="s">"diabetes"</span><span class="p">])</span>
</code></pre></div></div>

<p>Não é o nosso caso ignorar colunas, então vamos seguir com o comando anterior.</p>

<h2 id="comparando-modelos">Comparando modelos</h2>

<p>Esse é o primeiro passo recomendado em qualquer experimento supervisionado. É bem simples e muito útil, ele mostra um relatório com cada modelo e as métricas obtidas. Com o parâmetro <code class="language-plaintext highlighter-rouge">sort</code> você pode especificar a métrica utilizada para ordenar os modelos, por definição é <code class="language-plaintext highlighter-rouge">accuracy</code> para classificação (<code class="language-plaintext highlighter-rouge">R2</code> para regressão) mas vou utilizar <code class="language-plaintext highlighter-rouge">F1</code> aqui.</p>

<p>Essa função treina todos os modelos disponíveis usando os parâmetros predefinidos e avalia seu desempenho usando validação cruzada, <code class="language-plaintext highlighter-rouge">fold=10</code>. Ela retorna o melhor modelo de acordo com a métrica estabelecida no <code class="language-plaintext highlighter-rouge">sort</code>. Alguns algoritmos são excluídos da análise pelo seu tempo de processamento, para evitar sua exclusão basta adicionar <code class="language-plaintext highlighter-rouge">turbo=False</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">model</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">compare_models</span><span class="p">(</span><span class="n">sort</span><span class="o">=</span><span class="s">"F1"</span><span class="p">,</span> <span class="n">turbo</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div>

<p>É possível adicionar uma lista com os modelos que você gostaria de excluir no processamento com o parâmetro <code class="language-plaintext highlighter-rouge">exclude</code>, mas não é o nosso caso. Mais informações na <a href="https://pycaret.org/compare-models/">documentação</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pc</span><span class="p">.</span><span class="n">compare_models</span><span class="p">(</span><span class="n">exclude</span><span class="o">=</span><span class="p">[</span><span class="s">"svm"</span><span class="p">])</span>
</code></pre></div></div>

<p>Os nomes dos modelos estão na primeira coluna, na versão anterior era necessária usar a biblioteca <code class="language-plaintext highlighter-rouge">neatutils</code> para saber como ficava as abreviações.</p>

<h2 id="criando-um-modelo-a-partir-do-vencedor">Criando um modelo, a partir do vencedor</h2>

<p>Aqui iremos de fato trabalhar o modelo vencedor da comparação anterior. Para isso usamos o <code class="language-plaintext highlighter-rouge">create_model</code> passando a abreviação do modelo vencedor ou a variável contendo a comparação que retornou o melhor modelo de acordo com a métrica escolhida, no nosso caso o F1-score.</p>

<p>Como visto anteriormente o modelo vencedor para os nossos dados foi o Random Forest. No caso de classificação essa função recebe apenas o modelo e retorna uma tabela com os k-fold da validação cruzada junto com o modelo treinado. O número de folds pode ser definido pelo parâmetro <code class="language-plaintext highlighter-rouge">fold</code>, mas por pré-definição teremos <code class="language-plaintext highlighter-rouge">fold=10</code>. A quantidade de valores decimais também pode ser ajustada pelo <code class="language-plaintext highlighter-rouge">round</code>, sendo quatro decimais por pré-definição. Aqui ainda é possível usar um parâmetro de <code class="language-plaintext highlighter-rouge">ensemble</code>, mais detalhes na <a href="https://pycaret.org/create-model/">documentação</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">best_model</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">create_model</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="tunando-os-hiperparâmetros">Tunando os hiperparâmetros</h2>

<p>Com a função <code class="language-plaintext highlighter-rouge">tune_model</code>, os hiperparâmetros são tunados usando o Random grid search com grids predefinidos que podem ser customizados. Para aprendizado supervisionado, essa retorna uma tabela com k-fold validação cruzada com as métricas de avaliação junto com o modelo treinado.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tuned_model</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">tune_model</span><span class="p">(</span><span class="n">best_model</span><span class="p">)</span>
</code></pre></div></div>

<p>O número de <code class="language-plaintext highlighter-rouge">fold</code> pode ser alterado, mas por pré-definição é de 10. A quantidade de decimais também pode ser alterada pelo <code class="language-plaintext highlighter-rouge">round</code>, sendo de 4 por pré-definição. O número de iterações aleatórias feitas pelo Random grid search pode ser mudado pelo parâmetro <code class="language-plaintext highlighter-rouge">n_iter</code>, sendo de 10 por pré-definição. Aumentando esse valor o tempo de treinamento também aumenta. Além disso, é possível adicionar a métrica que deve ser otimizada pelo parâmetro <code class="language-plaintext highlighter-rouge">optimize</code>, para classificação é <code class="language-plaintext highlighter-rouge">accuracy</code> e regressão <code class="language-plaintext highlighter-rouge">R2</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tuned_model_opt</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">tune_model</span><span class="p">(</span><span class="n">best_model</span><span class="p">,</span> <span class="n">optimize</span><span class="o">=</span><span class="s">"F1"</span><span class="p">,</span> <span class="n">n_iter</span><span class="o">=</span><span class="mi">25</span><span class="p">)</span>
</code></pre></div></div>

<p>Se comparado, vemos que sem passar os parâmetros já tínhamos o “melhor” modelo. Seguiremos com ele então.</p>

<h2 id="avaliando-o-modelo">Avaliando o modelo</h2>

<p>Para avaliar o modelo você pode usar o <code class="language-plaintext highlighter-rouge">evaluate_model</code> que mostrará todas as métricas e gráficos associados ao modelo em uma interface com abas para seleção ou você pode usar o <code class="language-plaintext highlighter-rouge">plot_model</code> e ir selecionando cada métrica ou gráfico individualmente.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pc</span><span class="p">.</span><span class="n">evaluate_model</span><span class="p">(</span><span class="n">tuned_model</span><span class="p">)</span>
</code></pre></div></div>

<p>Por pré-definição o <code class="language-plaintext highlighter-rouge">plot_model</code> mostra o gráfico de AUC, mas você pode especificar o plot do seu interesse usando o parâmetro <code class="language-plaintext highlighter-rouge">plot</code>. Mais detalhes de possibilidades na <a href="https://pycaret.org/plot-model/">documentação</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pc</span><span class="p">.</span><span class="n">plot_model</span><span class="p">(</span><span class="n">tuned_model</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pc</span><span class="p">.</span><span class="n">plot_model</span><span class="p">(</span><span class="n">tuned_model</span><span class="p">,</span> <span class="n">plot</span><span class="o">=</span><span class="s">"error"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="otimizando-os-limites-fn-e-fp">Otimizando os limites (FN e FP)</h2>

<p>Para problemas de classificação o custo de resultados falso-positivos (FP ou erro tipo 1) é normalmente diferente dos falso-negativos (FN ou erro tipo 2). Assim, se você está trabalhando em um projeto em que os erros tipo 1 e tipo 2 possuem impacto diferentes, é possível otimizar sua classificação para um valor limite (threshold) de probabilidade melhorando a função de perda (loss function) apenas definindo o custo de FP e FN separadamente.</p>

<p>Para isso usamos a função <code class="language-plaintext highlighter-rouge">optimize_threshold</code> que pega o modelo treinado e a função de perda representada pelos FP, FN, TP (verdadeiro-positivos) e TN (verdadeiro-negativos). Essa função retorna um gráfico interativo onde a função de perda no eixo-y é representada por uma função de probabilidade de diferentes valores limites no eixo-x. Uma linha vertical representa o melhor valor da probabilidade limite para seu classificador. Mais detalhes na <a href="https://pycaret.org/optimize-threshold/">documentação</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">opt_prob</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">optimize_threshold</span><span class="p">(</span><span class="n">tuned_model</span><span class="p">,</span> <span class="n">true_negative</span><span class="o">=</span><span class="mi">1500</span><span class="p">,</span> <span class="n">false_negative</span><span class="o">=</span><span class="mi">5000</span><span class="p">)</span>
</code></pre></div></div>

<p>Não sei o motivo, mas para mim não apareceu nenhuma imagem. Testa aí e me diz se foi.</p>

<h2 id="interpretando-o-modelo">Interpretando o modelo</h2>

<p>Aqui ele chama de interpretar o modelo, pra mim nada mais é que quais features o modelo deu mais importância. Talvez precise instalar a biblioteca <code class="language-plaintext highlighter-rouge">shap</code> que é uma abordagem de teoria dos jogos, mais info <a href="https://github.com/slundberg/shap">aqui</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">!</span><span class="n">pip</span> <span class="n">install</span> <span class="n">shap</span>
<span class="n">pc</span><span class="p">.</span><span class="n">interpret_model</span><span class="p">(</span><span class="n">tuned_model</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="finalizando-o-modelo">Finalizando o modelo</h2>

<p>Esse é o último passo de um experimento supervisionado já que quando iniciamos o projeto usando o <code class="language-plaintext highlighter-rouge">setup</code> parte dos dados não é utilizado no treinamento. Com ele o modelo é treinado uma última vez com os dados completos, mais detalhes na <a href="https://pycaret.org/finalize-model/">documentação</a>. Essa função recebe o modelo treinado e retorna o modelo treinado nos dados completos.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">final_model</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">finalize_model</span><span class="p">(</span><span class="n">tuned_model</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="salvando-e-abrindo-o-modelo">Salvando e abrindo o modelo</h2>

<p>É bem simples de salvar seu modelo treinado, basta usar a função <code class="language-plaintext highlighter-rouge">save_model</code> passando o modelo treinado que será salvo todo o pipeline no formato pickle para usar posteriormente.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pc</span><span class="p">.</span><span class="n">save_model</span><span class="p">(</span><span class="n">final_model</span><span class="p">,</span> <span class="s">"rf_saved_model_02152021"</span><span class="p">)</span>
</code></pre></div></div>

<p>Já para abrir o modelo é mais simples ainda, basta usar a função <code class="language-plaintext highlighter-rouge">load_model</code> com o nome do modelo que você usou.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">loaded_rf</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">load_model</span><span class="p">(</span><span class="s">"rf_saved_model_02152021"</span><span class="p">)</span>
</code></pre></div></div>

<p>Como saída ele mostra o pipeline completo e os valores dos parâmetros do modelo.</p>

<h2 id="usando-o-modelo-para-predição">Usando o modelo para predição</h2>

<p>Vamos fazer uma predição usando os dados de teste que separamos no começo da análise. Também usarei o modelo que foi salvo e reaberto.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prediction</span> <span class="o">=</span> <span class="n">pc</span><span class="p">.</span><span class="n">predict_model</span><span class="p">(</span><span class="n">loaded_rf</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">test</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">prediction</span><span class="p">.</span><span class="n">head</span><span class="p">())</span>
</code></pre></div></div>

<p>Em Label vemos a predição feita pelo modelo final e na coluna vemos o DEATH_EVENT com a realidade. Um modelo simples que erra em alguns casos, mas acerta a maioria.</p>

<p>É possível melhorar esse modelo? Sim, mas por não ser o foco desse post recomendo olhar as referências e ver o que pessoal fez que pode melhorar os resultados desse projetinho.</p>

<p>Confesso que achei que ele seria útil nos dados que tou usando no meu projeto de mestrado. Porém, depois de 3 dias rodando ele estourou memória tentando rodar os meus dados /risos</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MemoryError: Unable to allocate 1.04 TiB for an array with shape
(377799, 377799) and data type float64
</code></pre></div></div>

<p>Então voltarei a forma clássica de criar modelos para esses pequenos dados que tenho trabalhado. Deixo aqui o material que utilizei pra brincar um pouco com a biblioteca com o intuito de ser útil para alguém.</p>

<h2 id="referências">Referências</h2>

<ul>
  <li><a href="https://towardsdatascience.com/creating-the-whole-machine-learning-pipeline-with-pycaret-db39a3006840">Creating the Whole Machine Learning Pipeline with PyCaret</a></li>
  <li><a href="https://towardsdatascience.com/machine-learning-made-easy-by-pycaret-5be22394b1ac">Machine Learning Made Easy by PyCaret</a></li>
  <li><a href="https://blog.jcharistech.com/2020/07/03/machine-learning-with-pycaret-in-python/">Machine Learning with PyCaret in Python</a></li>
  <li><a href="https://machinelearningmastery.com/pycaret-for-machine-learning/">A Gentle Introduction to PyCaret for Machine Learning</a></li>
  <li><a href="https://medium.com/ensina-ai/pycaret-a-biblioteca-de-aprendizagem-de-m%C3%A1quinas-para-quem-tem-prazo-1c5b09667763">PyCaret 2.x — A biblioteca de aprendizagem de máquinas para quem tem prazo</a></li>
  <li><a href="https://youtu.be/jlW5kRBwcb0">Talks # 7: Moez Ali: Machine learning with PyCaret</a></li>
  <li><a href="https://youtu.be/cnxOGWtwdv8">Machine Learning with PyCaret and Python</a></li>
</ul>

<hr />

<p><em>Publicado originalmente no <a href="https://medium.com/@geocarvalho/uma-introdu%C3%A7%C3%A3o-ao-automl-com-pycaret-82a8d1eec83">Medium</a>.</em></p>]]></content><author><name></name></author><category term="data-science" /><category term="pycaret" /><category term="machine-learning" /><category term="automl" /><category term="data-science" /><category term="python" /><summary type="html"><![CDATA[AutoML é a parte de aprendizado de máquina automatizado, nela processos e tarefas que não requerem tomada de decisão são automatizados facilitando a criação de modelos e seu deploy em larga escala com eficiência e aumentando a produtividade do desenvolvedor.]]></summary></entry></feed>