diff --git a/.github/workflows/nextflow-test.yaml b/.github/workflows/nextflow-test.yaml index 46adbea..fcc538b 100644 --- a/.github/workflows/nextflow-test.yaml +++ b/.github/workflows/nextflow-test.yaml @@ -7,6 +7,9 @@ on: - 'main' workflow_dispatch: +env: + DEFAULT_CONTAINER: ${{ vars.CONTAINER || 'ghcr.io/karchinlab/tcrtoolkit:main' }} + jobs: unit-tests: name: Unit Tests @@ -17,6 +20,22 @@ jobs: with: submodules: recursive + - &resolve_test_container + name: Resolve test container + shell: bash + env: + IMAGE_BASE: ghcr.io/${{ github.repository }} + SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + container="$DEFAULT_CONTAINER" + if [[ "$container" == 'ghcr.io/karchinlab/tcrtoolkit:main' ]]; then + candidate="$IMAGE_BASE:${SOURCE_SHA::7}" + if docker manifest inspect "$candidate" >/dev/null 2>&1; then + container="$candidate" + fi + fi + echo "CONTAINER=$container" >> "$GITHUB_ENV" + - name: Setup Java uses: actions/setup-java@v2 with: @@ -62,6 +81,8 @@ jobs: with: submodules: recursive + - *resolve_test_container + - name: Setup Java uses: actions/setup-java@v2 with: diff --git a/Dockerfile b/Dockerfile index 50f3cb3..c8c5a0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mambaorg/micromamba:1.5.8 +FROM mambaorg/micromamba:2 # Ensure we run as root for apt USER root @@ -50,14 +50,22 @@ RUN git init /opt/GIANA && \ chmod +x /opt/GIANA/GIANA4.1.py && \ ln -s /opt/GIANA/GIANA4.1.py /usr/local/bin/GIANA -# Install quarto -RUN mkdir -p /opt/quarto/1.6.42 \ - && curl -o /tmp/quarto.tar.gz -L \ - "https://github.com/quarto-dev/quarto-cli/releases/download/v1.6.42/quarto-1.6.42-linux-amd64.tar.gz" \ - && tar -zxvf /tmp/quarto.tar.gz \ - -C "/opt/quarto/1.6.42" \ - --strip-components=1 \ - && rm /tmp/quarto.tar.gz +# Install quarto (match the container architecture) +RUN set -eux; \ + arch="$(dpkg --print-architecture)"; \ + case "$arch" in \ + amd64) quarto_arch="amd64" ;; \ + arm64) quarto_arch="arm64" ;; \ + *) echo "Unsupported architecture for Quarto: $arch"; exit 1 ;; \ + esac; \ + mkdir -p /opt/quarto/1.9.38; \ + curl -L -o /tmp/quarto.tar.gz \ + "https://github.com/quarto-dev/quarto-cli/releases/download/v1.9.38/quarto-1.9.38-linux-${quarto_arch}.tar.gz"; \ + tar -zxf /tmp/quarto.tar.gz \ + -C "/opt/quarto/1.9.38" \ + --strip-components=1; \ + test -x /opt/quarto/1.9.38/bin/quarto; \ + rm /tmp/quarto.tar.gz # Install VDJmatch and symlink RUN mkdir -p /opt/vdjmatch/1.3.1 \ @@ -68,7 +76,8 @@ RUN mkdir -p /opt/vdjmatch/1.3.1 \ && ln -s /opt/vdjmatch/1.3.1/vdjmatch-1.3.1/vdjmatch-1.3.1.jar /usr/local/bin/vdjmatch.jar # Add to PATH -ENV PATH="/opt/quarto/1.6.42/bin:${PATH}" +ENV PATH="/opt/quarto/1.9.38/bin:${PATH}" # Add LD_LIBRARY_PATH for pandas ENV LD_LIBRARY_PATH=/opt/conda/lib + diff --git a/README.md b/README.md index 7ec7b79..ec9b44f 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ With the update to [Nextflow strict syntax](https://docs.seqera.io/nextflow/stri nextflow run KarchinLab/TCRtoolkit \ -params-file params.yml ``` +> [!IMPORTANT] +> If having an error similar to `qemu: uncaught target signal 11 (Segmentation fault) - core dumped` (observed on Mac OS Tahoe i.e. `26.5.1`), rebuild the Docker container locally, using the provided Dockerfile, and use that in the params, e.g. `--container `. It features arch selectors to pick the right `quarto` installation. ## Input Formats @@ -92,4 +94,4 @@ Certain sub-reports are automatically appended based on input and workflow optio | `--sobject_gex` | — | Path to TSV file containing cell-barcode phenotypes for pseudo-bulk phenotyping | | `--max_memory` | `768.GB` | Maximum memory allocation | | `--max_cpus` | `192` | Maximum CPU allocation | - \ No newline at end of file + diff --git a/conf/base.config b/conf/base.config index 0b8e8b3..6e5785a 100644 --- a/conf/base.config +++ b/conf/base.config @@ -9,7 +9,7 @@ */ process { - container = "ghcr.io/karchinlab/tcrtoolkit:main" + container = "${params.container}" publishDir = [ path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, diff --git a/conf/modules.config b/conf/modules.config index c697775..f664c9b 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -39,4 +39,12 @@ process { label = params.matrix_sparsity == 'sparse' ? 'process_medium' : ['process_high', 'process_high_memory'] } + withName: RENDER_NOTEBOOK { + publishDir = [ + path: { "${params.outdir}/report" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + } \ No newline at end of file diff --git a/env.yml b/env.yml index ffe94ca..2da0e1b 100644 --- a/env.yml +++ b/env.yml @@ -11,9 +11,7 @@ dependencies: - seaborn=0.13.0 - dash>=2.15.0 - matplotlib=3.8.1 - - pip=23.2.1 - - jupyterlab=4.0.8 - - notebook=7.0.6 + - pip=26.1.2 - fsspec=2024.3.1 - s3fs=2024.3.1 - python-igraph=0.11.8 @@ -26,6 +24,8 @@ dependencies: - rpy2=3.6.4 - unzip - openjdk=8 + - parasail-python + - jupyter - upsetplot=0.9.0 # R and R packages @@ -39,8 +39,8 @@ dependencies: # Pip packages - pip: + - papermill - csvkit==1.3.0 - - papermill==2.4.0 - plotly==5.18.0 - abydos==0.5.0 - fastcluster==1.2.6 diff --git a/modules/local/report/render_notebook.nf b/modules/local/report/render_notebook.nf new file mode 100644 index 0000000..a7f8136 --- /dev/null +++ b/modules/local/report/render_notebook.nf @@ -0,0 +1,28 @@ +// Generic process to render a Quarto notebook to HTML +process RENDER_NOTEBOOK { + tag "${notebook.getBaseName()}" + label 'process_single' + + input: + tuple path(notebook), path(files) // path(files) just stages files in root dir + val project_name + val workflow_cmd + + output: + path "${notebook.getBaseName()}.html", emit: report_html + + script: + """ + ## render qmd report to html + quarto render ${notebook} \\ + -P project_name:${project_name} \\ + -P workflow_cmd:'${workflow_cmd}' \\ + -P sample_table:${file(params.samplesheet)} \\ + --to html + """ + + stub: + """ + touch ${notebook.getBaseName()}.html + """ +} diff --git a/nextflow.config b/nextflow.config index d79a53b..ccc4534 100644 --- a/nextflow.config +++ b/nextflow.config @@ -41,7 +41,7 @@ params { timepoint_col = 'timepoint' timepoint_order_col = 'timepoint_order' alias_col = 'alias' - subject_col = 'subject_id' + subject_col = 'patient' // OLGA parameters olga_chunk_length = 100000 // larger chunk size = less parallelization @@ -69,6 +69,12 @@ params { matrix_sparsity = "sparse" distance_metric = "tcrdist" db_path = "${projectDir}/assets/tcrdist3_files/alphabeta_gammadelta_db.tsv" + + //container to use for running the workflow + container = "ghcr.io/karchinlab/tcrtoolkit:main" + + //reports + template_qc = "${projectDir}/notebooks/template_qc.qmd" } includeConfig 'conf/base.config' diff --git a/nextflow_schema.json b/nextflow_schema.json index ac90f76..26d3c20 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -80,6 +80,11 @@ "default": "copy", "enum": ["copy", "move", "link", "symlink"], "description": "Method used by `publishDir` to save outputs." + }, + "container": { + "type": "string", + "description": "Docker/Singularity container image to use for pipeline processes.", + "fa_icon": "fas fa-box" } } }, @@ -111,6 +116,10 @@ "compare_stats_template": { "type": "string", "description": "Path to compare notebook template." + }, + "template_qc": { + "type": "string", + "description": "Path to QC notebook template." } } }, @@ -124,7 +133,27 @@ "samplechart_x_col": { "type": "string", "default": "timepoint" }, "samplechart_color_col": { "type": "string", "default": "origin" }, "vgene_subject_col": { "type": "string", "default": "patient" }, - "vgene_x_cols": { "type": "string", "default": "origin,timepoint" } + "vgene_x_cols": { "type": "string", "default": "origin,timepoint" }, + "subject_col": { + "type": "string", + "default": "patient", + "description": "Samplesheet column identifying the subject/patient." + }, + "timepoint_col": { + "type": "string", + "default": "timepoint", + "description": "Samplesheet column identifying the timepoint." + }, + "timepoint_order_col": { + "type": "string", + "default": "timepoint_order", + "description": "Samplesheet column giving the sort order of timepoints." + }, + "alias_col": { + "type": "string", + "default": "alias", + "description": "Samplesheet column giving a display alias for each sample." + } } }, diff --git a/notebooks/template_qc.qmd b/notebooks/template_qc.qmd index 7928457..5c0d2b3 100644 --- a/notebooks/template_qc.qmd +++ b/notebooks/template_qc.qmd @@ -48,13 +48,12 @@ Thank you for using TCRtoolkit! This report is generated from the data provided. # --------------------------------------------------------- workflow_cmd = '' project_name='' -project_dir='' sample_table='' timepoint_col = 'timepoint' timepoint_order_col = 'timepoint_order' alias_col = 'alias' -subject_col = 'subject_id' +subject_col = 'patient' ``` @@ -66,9 +65,8 @@ subject_col = 'subject_id' # --------------------------------------------------------- # Define files -project_dir=f"{project_dir}/{project_name}" -sample_stats_csv = f"{project_dir}/sample/sample_stats.csv" -concat_file = f"{project_dir}/annotate/concatenated_cdr3_sorted.tsv" +sample_stats_csv = "sample_stats.csv" +concat_file = "concatenated_cdr3_sorted.tsv" ``` @@ -189,8 +187,7 @@ For $q=0$, this simplifies to $R$ (Total unique clonotypes). For $q=1$, the limi # --- 1. CALCULATION FUNCTION --- def calculate_diversity_metrics(df): results = [] - # Check if 'counts' exists, if not try 'clone_count' or similar - count_col = 'counts' if 'counts' in df.columns else df.columns[0] # Fallback + count_col = 'duplicate_count' if 'duplicate_count' in df.columns else df.columns[0] # Fallback grouped = df.groupby('sample') for sample, data in grouped: @@ -1018,14 +1015,14 @@ Where $|A \cap B|$ is the count of unique clonotypes shared by both samples, and # --- 1. CALCULATION FUNCTION (Only Jaccard) --- def calculate_overlaps(df): samples = sorted(df['sample'].unique()) - clones = sorted(df['CDR3b'].unique()) + clones = sorted(df['junction_aa'].unique()) sample_map = {s: i for i, s in enumerate(samples)} clone_map = {c: i for i, c in enumerate(clones)} row_idx = df['sample'].map(sample_map).values - col_idx = df['CDR3b'].map(clone_map).values - values = df['counts'].values + col_idx = df['junction_aa'].map(clone_map).values + values = df['duplicate_count'].values n_samples = len(samples) n_clones = len(clones) @@ -1838,7 +1835,7 @@ In an ideal experiment, **we would sequence until we reach saturation** (the poi #| fig-height: 5 # --- 1. Merge the missing counts data --- -total_counts = concat_df.groupby('sample')['counts'].sum().to_frame(name='total_counts') +total_counts = concat_df.groupby('sample')['duplicate_count'].sum().to_frame(name='total_counts') bias_plot_df = plot_df.merge(total_counts, on='sample', how='inner') # --- 2. Setup Grouping Options --- @@ -1978,8 +1975,8 @@ You **cannot fairly compare the diversity of a sample with 1 million reads to on # --------------------------------------------------------- qc_stats = concat_df.groupby('sample').agg( - sequencing_depth=('counts', 'sum'), - clonotype_richness=('CDR3b', 'nunique') + sequencing_depth=('duplicate_count', 'sum'), + clonotype_richness=('junction_aa', 'nunique') ).reset_index() # We extract just the metadata columns we need from 'df' @@ -2000,7 +1997,7 @@ def get_rarefaction_curve(sample_df, sample, steps=50): """ # Flat array where each element is a clone ID, repeated by its count # Use integer IDs for memory efficiency instead of strings - counts = sample_df['counts'].values + counts = sample_df['duplicate_count'].values n_reads = counts.sum() if n_reads == 0: @@ -2134,9 +2131,9 @@ Metrics that measure **dominance** or **inequality** focus on the most abundant # --- 1. PCA Calculation Function (Unchanged) --- def analyze_v_usage(df): """ - Expects df with columns: ['sample', 'TRBV', 'counts'] + Expects df with columns: ['sample', 'v_call', 'duplicate_count'] """ - matrix = df.groupby(['sample', 'TRBV'])['counts'].sum().unstack(fill_value=0) + matrix = df.groupby(['sample', 'v_call'])['duplicate_count'].sum().unstack(fill_value=0) freq_matrix = matrix.div(matrix.sum(axis=1), axis=0) scaler = StandardScaler() @@ -2255,10 +2252,10 @@ This aggregates everything. We calculate Z-scores for every metric. If a sample # CDR3 Length Distribution & Distances def analyze_cdr3_lengths(df): """ - Expects df with columns: ['sample', 'CDR3b'] + Expects df with columns: ['sample', 'junction_aa'] """ # Calculate lengths - df['cdr3_len'] = df['CDR3b'].apply(len) + df['cdr3_len'] = df['junction_aa'].apply(len) # 1. Basic Stats per sample stats = df.groupby('sample')['cdr3_len'].agg( diff --git a/params.yml b/params.yml index af15fd8..6d7eec4 100644 --- a/params.yml +++ b/params.yml @@ -1,6 +1,7 @@ -samplesheet: "https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/samplesheet.csv" +samplesheet: "tests/test_data/minimal-example/samplesheet.csv" outdir: "out-minimal-dev" -workflow_level: "sample,patient,compare" +#workflow_level: "sample,compare,patient" - uncomment once patient is fixed +workflow_level: "sample,compare" input_format: "adaptive" max_memory: 10.GB max_cpus: 4 \ No newline at end of file diff --git a/subworkflows/local/report.nf b/subworkflows/local/report.nf new file mode 100644 index 0000000..b9ff66e --- /dev/null +++ b/subworkflows/local/report.nf @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT LOCAL MODULES +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +include { RENDER_NOTEBOOK } from '../../modules/local/report/render_notebook' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + RUN MAIN SUBWORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// Single-channel listener: receives tuples of +// (notebook_template, files_to_stage) +// and renders each notebook to HTML. +workflow REPORT { + + take: + ch_reports // channel of tuples: tuple(path notebook_template, path files_to_stage) + main: + RENDER_NOTEBOOK( + ch_reports, + params.project_name, + workflow.commandLine + ) + + emit: + rendered_html = RENDER_NOTEBOOK.out +} diff --git a/subworkflows/local/sample.nf b/subworkflows/local/sample.nf index 52923bf..28f7b67 100644 --- a/subworkflows/local/sample.nf +++ b/subworkflows/local/sample.nf @@ -110,4 +110,6 @@ workflow SAMPLE { VDJDB_VDJMATCH (processed_samples, VDJDB_GET.out.ref_db) + emit: + sample_csv = SAMPLE_CALC.out.sample_csv } \ No newline at end of file diff --git a/tests/fixtures/test.qmd b/tests/fixtures/test.qmd new file mode 100644 index 0000000..1dee0ee --- /dev/null +++ b/tests/fixtures/test.qmd @@ -0,0 +1,9 @@ +--- +title: "test notebook" +format: html +jupyter: python3 +--- + +```{python} +print("Hello world!") +``` diff --git a/tests/main.nf.test b/tests/main.nf.test index 3b7fbe4..92bb2b9 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -5,7 +5,7 @@ nextflow_pipeline { test("Minimal example dataset") { - tag "basic-sd" + tag "minimal-example" when { params { @@ -14,6 +14,7 @@ nextflow_pipeline { input_format = "adaptive" max_memory = "8.GB" max_cpus = 4 + container = System.getenv("CONTAINER") ?: "ghcr.io/karchinlab/tcrtoolkit:main" } } diff --git a/tests/modules/local/report/render_notebook.nf.test b/tests/modules/local/report/render_notebook.nf.test new file mode 100644 index 0000000..eba24f6 --- /dev/null +++ b/tests/modules/local/report/render_notebook.nf.test @@ -0,0 +1,42 @@ +nextflow_process { + + name "Test RENDER_NOTEBOOK" + script "modules/local/report/render_notebook.nf" + process "RENDER_NOTEBOOK" + + test("Should render basic notebook") { + // Note: this fixture uses the Jupyter `python3` engine (see tests/fixtures/test.qmd) + // If Jupyter kernels are not available in the container, this test will fail. + + tag "basic" + tag "notebook" + + when { + params { + samplesheet = "${projectDir}/tests/fixtures/valid_samplesheet.csv" + container = System.getenv("CONTAINER") ?: "ghcr.io/karchinlab/tcrtoolkit:main" + } + + process { + """ + input[0] = [ + file("${projectDir}/tests/fixtures/test.qmd"), + file("${projectDir}/tests/fixtures") + ] + input[1] = "TCRtoolkit" + input[2] = "nextflow run main.nf" + """ + } + } + + then { + assert process.success + with(process.out.report_html) { + def html_file = path(get(0)) + assert html_file.exists() + assert html_file.getFileName().toString().endsWith('.html') + } + } + } + +} diff --git a/tests/modules/local/samplesheet/samplesheet_check.nf.test b/tests/modules/local/samplesheet/samplesheet_check.nf.test index f405c4f..92328c9 100644 --- a/tests/modules/local/samplesheet/samplesheet_check.nf.test +++ b/tests/modules/local/samplesheet/samplesheet_check.nf.test @@ -55,7 +55,7 @@ nextflow_process { def lines = path(samplesheet_utf8.get(0)).readLines() assert lines.size() == 36 // Header + 35 samples - assert lines[0].replaceAll("^\uFEFF", "") == "sample,patient,timepoint,origin,file" + assert lines[0].replaceAll("^\uFEFF", "") == "sample,alias,patient,timepoint,origin,file" } } } diff --git a/tests/test_data/minimal-example/samplesheet.csv b/tests/test_data/minimal-example/samplesheet.csv index 79d1b0f..5f89b10 100644 --- a/tests/test_data/minimal-example/samplesheet.csv +++ b/tests/test_data/minimal-example/samplesheet.csv @@ -1,36 +1,36 @@ -sample,patient,timepoint,origin,file -Patient01_Base,Patient01,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient01_Base.tsv -Patient02_Base,Patient02,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient02_Base.tsv -Patient03_Base,Patient03,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient03_Base.tsv -Patient04_Base,Patient04,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient04_Base.tsv -Patient05_Base,Patient05,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient05_Base.tsv -Patient06_Base,Patient06,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient06_Base.tsv -Patient07_Base,Patient07,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient07_Base.tsv -Patient08_Base,Patient08,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient08_Base.tsv -Patient09_Base,Patient09,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient09_Base.tsv -Patient10_Base,Patient10,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient10_Base.tsv -Patient11_Base,Patient11,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient11_Base.tsv -Patient12_Base,Patient12,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient12_Base.tsv -Patient13_Base,Patient13,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient13_Base.tsv -Patient14_Base,Patient14,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient14_Base.tsv -Patient15_Base,Patient15,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient15_Base.tsv -Patient16_Base,Patient16,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient16_Base.tsv -Patient16_Post,Patient16,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient16_Post.tsv -Patient17_Base,Patient17,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient17_Base.tsv -Patient17_Post,Patient17,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient17_Post.tsv -Patient18_Base,Patient18,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient18_Base.tsv -Patient18_Post,Patient18,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient18_Post.tsv -Patient19_Base,Patient19,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient19_Base.tsv -Patient19_Post,Patient19,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient19_Post.tsv -Patient20_Base,Patient20,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient20_Base.tsv -Patient20_Post,Patient20,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient20_Post.tsv -Patient21_Base,Patient21,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient21_Base.tsv -Patient21_Post,Patient21,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient21_Post.tsv -Patient22_Base,Patient22,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient22_Base.tsv -Patient22_Post,Patient22,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient22_Post.tsv -Patient23_Base,Patient23,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient23_Base.tsv -Patient23_Post,Patient23,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient23_Post.tsv -Patient24_Base,Patient24,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient24_Base.tsv -Patient24_Post,Patient24,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient24_Post.tsv -Patient25_Base,Patient25,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient25_Base.tsv -Patient25_Post,Patient25,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient25_Post.tsv +sample,alias,patient,timepoint,origin,file +Patient01_Base,Patient01_Base,Patient01,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient01_Base.tsv +Patient02_Base,Patient02_Base,Patient02,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient02_Base.tsv +Patient03_Base,Patient03_Base,Patient03,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient03_Base.tsv +Patient04_Base,Patient04_Base,Patient04,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient04_Base.tsv +Patient05_Base,Patient05_Base,Patient05,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient05_Base.tsv +Patient06_Base,Patient06_Base,Patient06,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient06_Base.tsv +Patient07_Base,Patient07_Base,Patient07,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient07_Base.tsv +Patient08_Base,Patient08_Base,Patient08,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient08_Base.tsv +Patient09_Base,Patient09_Base,Patient09,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient09_Base.tsv +Patient10_Base,Patient10_Base,Patient10,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient10_Base.tsv +Patient11_Base,Patient11_Base,Patient11,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient11_Base.tsv +Patient12_Base,Patient12_Base,Patient12,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient12_Base.tsv +Patient13_Base,Patient13_Base,Patient13,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient13_Base.tsv +Patient14_Base,Patient14_Base,Patient14,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient14_Base.tsv +Patient15_Base,Patient15_Base,Patient15,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient15_Base.tsv +Patient16_Base,Patient16_Base,Patient16,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient16_Base.tsv +Patient16_Post,Patient16_Post,Patient16,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient16_Post.tsv +Patient17_Base,Patient17_Base,Patient17,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient17_Base.tsv +Patient17_Post,Patient17_Post,Patient17,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient17_Post.tsv +Patient18_Base,Patient18_Base,Patient18,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient18_Base.tsv +Patient18_Post,Patient18_Post,Patient18,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient18_Post.tsv +Patient19_Base,Patient19_Base,Patient19,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient19_Base.tsv +Patient19_Post,Patient19_Post,Patient19,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient19_Post.tsv +Patient20_Base,Patient20_Base,Patient20,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient20_Base.tsv +Patient20_Post,Patient20_Post,Patient20,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient20_Post.tsv +Patient21_Base,Patient21_Base,Patient21,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient21_Base.tsv +Patient21_Post,Patient21_Post,Patient21,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient21_Post.tsv +Patient22_Base,Patient22_Base,Patient22,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient22_Base.tsv +Patient22_Post,Patient22_Post,Patient22,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient22_Post.tsv +Patient23_Base,Patient23_Base,Patient23,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient23_Base.tsv +Patient23_Post,Patient23_Post,Patient23,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient23_Post.tsv +Patient24_Base,Patient24_Base,Patient24,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient24_Base.tsv +Patient24_Post,Patient24_Post,Patient24,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient24_Post.tsv +Patient25_Base,Patient25_Base,Patient25,Base,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient25_Base.tsv +Patient25_Post,Patient25_Post,Patient25,Post,tumor,https://raw.githubusercontent.com/KarchinLab/TCRtoolkit/refs/heads/main/tests/test_data/minimal-example/PD1_Patient25_Post.tsv diff --git a/workflows/tcrtoolkit.nf b/workflows/tcrtoolkit.nf index 7237e29..f962d0b 100644 --- a/workflows/tcrtoolkit.nf +++ b/workflows/tcrtoolkit.nf @@ -16,6 +16,7 @@ include { PATIENT } from '../subworkflows/local/patient' include { COMPARE } from '../subworkflows/local/compare' include { VALIDATE_PARAMS } from '../subworkflows/local/validate_params' include { ANNOTATE } from '../subworkflows/local/annotate' +include { REPORT } from '../subworkflows/local/report' include { PSEUDOBULK_PHENOTYPE }from '../subworkflows/local/pseudobulk_phenotype' @@ -105,6 +106,29 @@ workflow TCRTOOLKIT { ANNOTATE.out.cdr3_pgen ) } + + // Report - works on channel of tuples [report name, [report files]] + ch_reports = channel.empty() + + // QC report requires sample-level aggregate outputs. + ch_qc_report = SAMPLE.out.sample_csv + .collectFile(name: "sample_stats.csv", keepHeader: true, skip: 1, sort: true) + .combine(ANNOTATE.out.concat_cdr3_sorted) + .map { sample_stats_csv, concat_cdr3_sorted -> + tuple( + file(params.template_qc), + [sample_stats_csv, + concat_cdr3_sorted] + ) + } + ch_reports = ch_reports.mix(ch_qc_report) + + // Another report + // ch_reports = ch_reports.mix( ... ) + + REPORT( ch_reports ) + + } /*