1000 Genomes Full Pipeline Example

This example runs the complete GRiD pipeline (from raw CRAMs through diploid copy number estimation) using the publicly available 1000 Genomes Project high-coverage WGS dataset. No login or data access agreement is required.

  1#!/bin/bash
  2
  3module load anaconda mosdepth samtools 
  4
  5POP_FILTER=""
  6N_SAMPLES=0   # 0 = all
  7JOBS_OVERRIDE=0
  8 
  9usage() {
 10    grep '^#' "$0" | sed -e 's/^#//' -e '1d'
 11    exit 0
 12}
 13 
 14while [[ $# -gt 0 ]]; do
 15    case $1 in
 16        --pop)  POP_FILTER="$2"; shift 2 ;;
 17        --n)    N_SAMPLES="$2";  shift 2 ;;
 18        --jobs) JOBS_OVERRIDE="$2"; shift 2 ;;
 19        -h|--help) usage ;;
 20        *) echo "Unknown argument: $1"; usage ;;
 21    esac
 22done
 23 
 24# Dependency check
 25MISSING=()
 26for cmd in samtools mosdepth conda awk wget; do
 27    command -v "$cmd" &>/dev/null || MISSING+=("$cmd")
 28done
 29if [[ ${#MISSING[@]} -gt 0 ]]; then
 30    echo "ERROR: missing required tools: ${MISSING[*]}"
 31    echo "Install with: conda install -c bioconda ${MISSING[*]}"
 32    exit 1
 33fi
 34 
 35# Paths and parameters
 36SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 37IBS_SCRIPT="$SCRIPT_DIR/IBS_example.sh"
 38 
 39WORK_DIR="$(pwd)"
 40CRAM_DIR="$WORK_DIR/crams"
 41LOG_DIR="$WORK_DIR/logs"
 42DATA_DIR="$WORK_DIR/data"
 43OUTPUT_DIR="$WORK_DIR/output"
 44MOSDEPTH_WORK="$WORK_DIR/mosdepth_work"
 45 
 46mkdir -p "$CRAM_DIR" "$LOG_DIR" "$DATA_DIR" "$OUTPUT_DIR" "$MOSDEPTH_WORK"
 47 
 48THREADS="${SLURM_CPUS_PER_TASK:-$(nproc)}"
 49if [[ "$JOBS_OVERRIDE" -gt 0 ]]; then
 50    STREAM_JOBS="$JOBS_OVERRIDE"
 51else
 52    STREAM_JOBS=$(( THREADS > 2 ? 2 : THREADS ))
 53fi
 54 
 55BASE_URL="https://ftp.1000genomes.ebi.ac.uk/vol1/ftp"
 56PANEL_URL="${BASE_URL}/release/20130502/integrated_call_samples_v3.20130502.ALL.panel"
 57REF_URL="https://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/000/001/405/GCA_000001405.15_GRCh38/seqs_for_alignment_pipelines.ucsc_ids/GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.gz"
 58REGIONS_FILE_URL="https://raw.githubusercontent.com/caterer-z-t/GRiD/main/files/734_possible_coding_vntr_regions.IBD2R_gt_0.25.uniq.txt"
 59REPEAT_MASK_URL="https://raw.githubusercontent.com/alexliyihao/vntrwrap/main/normalize_mosdepth/external_source/repeat_mask_list.hg38.ucsc_bed"
 60 
 61timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
 62log() { echo "[$(timestamp)] $*" | tee -a "$LOG_DIR/pipeline.log"; }
 63 
 64download_with_retry() {
 65    local url="$1"
 66    local out="$2"
 67    local attempt
 68 
 69    for attempt in 1 2 3 4 5; do
 70        if wget -q --tries=1 --timeout=60 -O "$out" "$url"; then
 71            return 0
 72        fi
 73        rm -f "$out"
 74        sleep $(( attempt * 3 + RANDOM % 3 ))
 75    done
 76 
 77    echo "ERROR: failed to download $url after 5 attempts" >&2
 78    return 1
 79}
 80 
 81# Fetch static inputs
 82if [[ ! -f "$DATA_DIR/regions.txt" ]]; then
 83    log "Downloading VNTR regions file..."
 84    download_with_retry "$REGIONS_FILE_URL" "$DATA_DIR/regions.txt"
 85fi
 86 
 87read -r CHR START END < <(awk '$7=="LPA" {print $1, $2, $3; exit}' "$DATA_DIR/regions.txt") || true
 88if [[ -z "${CHR:-}" || -z "${START:-}" || -z "${END:-}" ]]; then
 89    echo "ERROR: Could not parse LPA coordinates from regions.txt"
 90    exit 1
 91fi
 92REGION="chr${CHR}:${START}-${END}"
 93FOCAL_BP=$(( (START + END) / 2 ))
 94
 95REPEAT_MASK="$DATA_DIR/repeat_mask.bed"
 96
 97if [[ ! -f "$REPEAT_MASK" ]]; then
 98    log "Downloading repeat mask file..."
 99    download_with_retry "$REPEAT_MASK_URL" "$REPEAT_MASK"
100fi
101 
102log "  GRiD — 1000 Genomes Example"
103log "  Locus:   LPA KIV-2  ($REGION, hg38)"
104log "  Threads: $THREADS  |  Parallel streams: $STREAM_JOBS"
105log "  Output:  $OUTPUT_DIR"
106 
107# Activate environment
108if ! conda env list | grep -qE '^\s*grid\s'; then
109    echo "ERROR: conda environment 'grid' not found."
110    echo "Create it first, e.g.: conda env create -f environment.yml"
111    exit 1
112fi
113source "$(conda info --base)/etc/profile.d/conda.sh"
114conda activate grid
115 
116# Phase 1 — Reference genome
117REF_FA="$DATA_DIR/GRCh38_no_alt.fa"
118if [[ ! -f "$REF_FA" ]]; then
119    log "Downloading GRCh38 reference genome..."
120    download_with_retry "$REF_URL" "$DATA_DIR/GRCh38_no_alt.fa.gz"
121    gunzip "$DATA_DIR/GRCh38_no_alt.fa.gz"
122    samtools faidx "$REF_FA"
123    log "Reference genome ready: $REF_FA"
124else
125    log "Reference genome already present — skipping download."
126fi
127 
128# Phase 2 — Sample list
129PANEL="$DATA_DIR/1000G_panel.txt"
130SAMPLES_FILE="$DATA_DIR/streaming_manifest.txt"
131 
132if [[ ! -f "$PANEL" ]]; then
133    log "Downloading 1000G panel file..."
134    download_with_retry "$PANEL_URL" "$PANEL"
135fi
136 
137if [[ -n "$POP_FILTER" ]]; then
138    log "Filtering to superpopulation: $POP_FILTER"
139    awk -v pop="$POP_FILTER" 'NR>1 && $3==pop {print $1, $2}' "$PANEL" > "$SAMPLES_FILE"
140else
141    awk 'NR>1 {print $1, $2}' "$PANEL" > "$SAMPLES_FILE"
142fi
143 
144TOTAL=$(wc -l < "$SAMPLES_FILE")
145if [[ "$N_SAMPLES" -gt 0 && "$N_SAMPLES" -lt "$TOTAL" ]]; then
146    head -n "$N_SAMPLES" "$SAMPLES_FILE" > "${SAMPLES_FILE}.tmp" && mv "${SAMPLES_FILE}.tmp" "$SAMPLES_FILE"
147    log "Using first $N_SAMPLES of $TOTAL available samples."
148else
149    log "Using all $TOTAL samples."
150fi
151 
152# Phase 3 — Stream LPA-region CRAMs (parallel, remote)
153log "Streaming LPA region CRAMs from 1000G EBI..."
154 
155FAILED_LOG="$LOG_DIR/failed_samples.txt"
156: > "$FAILED_LOG"
157 
158stream_cram() {
159    local sample="$1"
160    local pop="$2"
161    local ref="$3"
162    local region="$4"
163    local out_dir="$5"
164 
165    local out="${out_dir}/${sample}.cram"
166 
167    if [[ -f "$out" && -f "${out}.crai" ]]; then
168        return 0
169    fi
170 
171    local dir_url="https://ftp.1000genomes.ebi.ac.uk/vol1/ftp/data_collections/1000_genomes_project/data/${pop}/${sample}/alignment/"
172 
173    local listing=""
174    local attempt
175    for attempt in 1 2 3 4 5; do
176        listing=$(wget -qO- --tries=1 --timeout=30 "$dir_url" 2>/dev/null || true)
177        if [[ -n "$listing" ]]; then
178            break
179        fi
180        sleep $(( attempt * 3 + RANDOM % 3 ))
181    done
182 
183    if [[ -z "$listing" ]]; then
184        echo "ERROR: failed to fetch directory listing for $sample after 5 attempts: $dir_url" >&2
185        echo "$sample" >> "$FAILED_LOG"
186        return 1
187    fi
188 
189    local full_filename
190    full_filename=$(echo "$listing" | grep -oE "${sample}\.alt_bwamem_GRCh38DH\.[0-9]+\.${pop}\.low_coverage\.cram" | head -n 1)
191 
192    if [[ -z "$full_filename" ]]; then
193        echo "ERROR: sample $sample has a listing but no matching low_coverage CRAM at $dir_url" >&2
194        echo "$sample" >> "$FAILED_LOG"
195        return 1
196    fi
197 
198    local url="${dir_url}${full_filename}"
199 
200    if ! (samtools view -T "$ref" -b "$url" "$region" | \
201          samtools sort -@ 2 -o "$out" && \
202          samtools index "$out"); then
203        echo "ERROR: streaming/sort/index failed for $sample — cleaning up partial output" >&2
204        rm -f "$out" "${out}.crai"
205        echo "$sample" >> "$FAILED_LOG"
206        return 1
207    fi
208}
209 
210export -f stream_cram
211xargs -L 1 -P "$STREAM_JOBS" bash -c '
212    ref="$1"
213    region="$2"
214    out_dir="$3"
215    sample="$4"
216    pop="$5"
217    stream_cram "$sample" "$pop" "$ref" "$region" "$out_dir"
218' _ "$REF_FA" "$REGION" "$CRAM_DIR" < "$SAMPLES_FILE" >> "$LOG_DIR/stream_crams.log" 2>&1 || true
219
220find "$WORK_DIR" -maxdepth 1 -name "*.low_coverage.cram.crai" -delete
221
222# Compile GRiD Input manifest file directly from downloaded assets
223GRID_SAMPLES_FILE="$DATA_DIR/grid_samples.txt"
224: > "$GRID_SAMPLES_FILE"
225
226for cram_path in "$CRAM_DIR"/*.cram; do
227    if [[ -f "$cram_path" ]]; then
228        filename=$(basename "$cram_path")
229        sample_id="${filename%.cram}"
230        echo "$sample_id" >> "$GRID_SAMPLES_FILE"
231    fi
232done
233
234N_GRID_SAMPLES=$(wc -l < "$GRID_SAMPLES_FILE" | tr -d ' ')
235if [[ "$N_GRID_SAMPLES" -eq 0 ]]; then
236    echo "ERROR: no files found in $CRAM_DIR — nothing to pass to GRiD."
237    exit 1
238fi
239
240# Phase 4 — Run IBS_example.sh if needed
241IBS_OUTPUT="$DATA_DIR/ibs_neighbors_chr6.tsv.gz"
242HAPLOID_RUN="False"
243 
244if [[ -f "$IBS_OUTPUT" ]]; then
245    log "IBS neighbors file already present — skipping IBS_example.sh."
246    HAPLOID_RUN="True"
247else
248    if [[ ! -f "$IBS_SCRIPT" ]]; then
249        log "WARNING: IBS neighbors file not found and $IBS_SCRIPT is missing."
250        log "         Haploid CN estimation will be skipped for this run."
251    else
252        log "IBS neighbors file not found — running IBS_example.sh to generate it..."
253        if WORK_DIR="$WORK_DIR" bash "$IBS_SCRIPT" --focal-bp "$FOCAL_BP"; then
254            if [[ -f "$IBS_OUTPUT" ]]; then
255                log "IBS neighbors file generated: $IBS_OUTPUT"
256                HAPLOID_RUN="True"
257            else
258                log "WARNING: IBS_example.sh completed but $IBS_OUTPUT still missing."
259                log "         Haploid CN estimation will be skipped for this run."
260            fi
261        else
262            log "WARNING: IBS_example.sh failed. Haploid CN estimation will be skipped for this run."
263            log "         See $LOG_DIR/IBS.log for details."
264        fi
265    fi
266fi
267
268# Phase 5 — Generate config
269CONFIG="$WORK_DIR/config.yaml"
270
271cat > "$CONFIG" << YAML
272# Auto-generated GRiD config — 1000 Genomes LPA KIV-2 example
273samples_file: "$GRID_SAMPLES_FILE"
274directory_loc: "$CRAM_DIR"
275reference_genome: "$REF_FA"
276output_dir: "$OUTPUT_DIR"
277threads: $THREADS
278file_type: "cram"
279chrom: "chr${CHR}"
280start_bp: $START
281end_bp: $END
282output_file_type: "tsv"
283
284index:
285  run: True
286  output_file_prefix: "index_file_results"
287
288count_reads:
289  run: True
290  output_file_prefix: "read_counts"
291  min_mapq: 1
292  flags:
293    - 83    # proper pair, read reverse strand
294    - 147   # proper pair, mate reverse strand
295    - 81    # read reverse strand
296    - 145   # mate reverse strand
297
298mosdepth:
299  run: True
300  output_file_prefix: "mosdepth_results"
301  bin_size: 1000
302  mode: "fast"
303  region_name: "LPA"
304  work_dir: "$MOSDEPTH_WORK"
305  remove_intermediate: True
306
307  normalize:
308    run: True
309    min_depth: 1
310    max_depth: 30
311    top_frac: 0.1
312    output_file_prefix: "mosdepth_normalized"
313    repeat_mask_file: "$REPEAT_MASK"
314
315  neighbors:
316    run: True
317    output_file_prefix: "neighbors"
318    num_neighbors: 5
319    zmax: 2.0
320    sigma2_max: 1000
321
322compute_diploid_genotypes:
323  run: True
324  output_file_prefix: "diploid_genotypes"
325
326compute_haploid_genotypes:
327  run: $HAPLOID_RUN
328  output_file_prefix: "haploid_genotypes"
329  method: "ibs"
330  ibs_output: "$DATA_DIR/ibs_neighbors_chr6.tsv.gz"
331  min_neighbors: 1
332  max_neighbors: 10
333  n_iters: 100
334YAML
335
336log "Config written to: $CONFIG"
337
338# Phase 5 — Run GRiD
339log "Running GRiD pipeline..."
340grid wgs $CONFIG

Usage

Run all samples from a single superpopulation:

bash examples/1000G_example.sh --pop EUR

Limit to 50 samples for a quick test:

bash examples/1000G_example.sh --pop EUR --n 50

Submit to SLURM:

sbatch --cpus-per-task=16 --mem=32G examples/1000G_example.sh --pop EUR --n 100

Override the working directory:

WORK_DIR=/scratch/my_run bash examples/1000G_example.sh --pop AFR

Next Step — Haplotype Inference

The pipeline above estimates diploid copy number. To decompose into haplotype-specific copy numbers, you need an IBS neighbors file first. See IBS/IBD Neighbor Computation Example for instructions, then re-run the pipeline with compute_haploid_genotypes.run: True in the generated config.