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.