-
Notifications
You must be signed in to change notification settings - Fork 9
qc command refactor #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,242 @@ | ||||||||||||||||||||||||||||||||||||||||
| #!/bin/bash | ||||||||||||||||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||||||||||||||||
| # STARsolo CLI - QC aggregation | ||||||||||||||||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||||||||||||||||
| # Aggregate QC statistics for one or more STARsolo output directories. | ||||||||||||||||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # ---------- Helpers ---------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| _qc_guess_species() { | ||||||||||||||||||||||||||||||||||||||||
| local logfile="$1" | ||||||||||||||||||||||||||||||||||||||||
| local genomedir | ||||||||||||||||||||||||||||||||||||||||
| genomedir=$(grep "^genomeDir" "$logfile" | tail -n1) | ||||||||||||||||||||||||||||||||||||||||
| if echo "$genomedir" | grep -q "/human/"; then | ||||||||||||||||||||||||||||||||||||||||
| echo "Human" | ||||||||||||||||||||||||||||||||||||||||
| elif echo "$genomedir" | grep -q "/mouse/"; then | ||||||||||||||||||||||||||||||||||||||||
| echo "Mouse" | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| echo "Other" | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| _qc_guess_wl() { | ||||||||||||||||||||||||||||||||||||||||
| local logfile="$1" | ||||||||||||||||||||||||||||||||||||||||
| local wlfile | ||||||||||||||||||||||||||||||||||||||||
| wlfile=$(grep "^soloCBwhitelist" "$logfile" | tail -n1 | awk '{print $NF}') | ||||||||||||||||||||||||||||||||||||||||
| wlfile=$(basename "$wlfile") | ||||||||||||||||||||||||||||||||||||||||
| case "$wlfile" in | ||||||||||||||||||||||||||||||||||||||||
| 3M-february-2018.txt) echo "v3" ;; | ||||||||||||||||||||||||||||||||||||||||
| 3M-3pgex-may-2023.txt) echo "v4-3p" ;; | ||||||||||||||||||||||||||||||||||||||||
| 3M-5pgex-jan-2023.txt) echo "v4-5p" ;; | ||||||||||||||||||||||||||||||||||||||||
| 737K-august-2016.txt) echo "v2" ;; | ||||||||||||||||||||||||||||||||||||||||
| 737K-april-2014_rc.txt) echo "v1" ;; | ||||||||||||||||||||||||||||||||||||||||
| 737K-arc-v1.txt) echo "arc" ;; | ||||||||||||||||||||||||||||||||||||||||
| *) echo "Undef" ;; | ||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # ---------- Main QC workflow ------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| run_qc() { | ||||||||||||||||||||||||||||||||||||||||
| local FORCE_SPECIES="" CHECK_CONTAMINATION=1 | ||||||||||||||||||||||||||||||||||||||||
| local -a DIRS=() | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+44
|
||||||||||||||||||||||||||||||||||||||||
| # --- Argument parsing --- | ||||||||||||||||||||||||||||||||||||||||
| while (( $# > 0 )); do | ||||||||||||||||||||||||||||||||||||||||
| case "$1" in | ||||||||||||||||||||||||||||||||||||||||
| -h|--help) | ||||||||||||||||||||||||||||||||||||||||
| show_qc_help; exit 0 ;; | ||||||||||||||||||||||||||||||||||||||||
| -s|--species|--specie) | ||||||||||||||||||||||||||||||||||||||||
| FORCE_SPECIES="$2"; shift 2 ;; | ||||||||||||||||||||||||||||||||||||||||
| --no-contamination-check) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+52
|
||||||||||||||||||||||||||||||||||||||||
| CHECK_CONTAMINATION=0; shift ;; | ||||||||||||||||||||||||||||||||||||||||
| -*) | ||||||||||||||||||||||||||||||||||||||||
| die "Unknown option: $1. Run 'starsolo qc --help'." ;; | ||||||||||||||||||||||||||||||||||||||||
| *) | ||||||||||||||||||||||||||||||||||||||||
| DIRS+=("$1"); shift ;; | ||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (( ${#DIRS[@]} == 0 )); then | ||||||||||||||||||||||||||||||||||||||||
| show_qc_help | ||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Strip trailing slashes | ||||||||||||||||||||||||||||||||||||||||
| local -a CLEAN_DIRS=() | ||||||||||||||||||||||||||||||||||||||||
| local d | ||||||||||||||||||||||||||||||||||||||||
| for d in "${DIRS[@]}"; do | ||||||||||||||||||||||||||||||||||||||||
| CLEAN_DIRS+=("${d%/}") | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # --- Check completion status --- | ||||||||||||||||||||||||||||||||||||||||
| log_info "Checking that all STARsolo jobs went to completion …" | ||||||||||||||||||||||||||||||||||||||||
| local i | ||||||||||||||||||||||||||||||||||||||||
| for i in "${CLEAN_DIRS[@]}"; do | ||||||||||||||||||||||||||||||||||||||||
| if [[ -d "$i/output" && -s "$i/Log.final.out" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| if [[ -d "$i/_STARtmp" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| log_warn "Sample $i did not run to completion: _STARtmp is still present!" | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # --- Cross-contamination check --- | ||||||||||||||||||||||||||||||||||||||||
| if (( CHECK_CONTAMINATION )); then | ||||||||||||||||||||||||||||||||||||||||
| log_info "Checking potential sample cross-contamination …" | ||||||||||||||||||||||||||||||||||||||||
| local TMPDIR_SORT | ||||||||||||||||||||||||||||||||||||||||
| TMPDIR_SORT=$(mktemp -d) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+89
|
||||||||||||||||||||||||||||||||||||||||
| local j local_i local_j N1 N2 MIN COMM PCT | ||||||||||||||||||||||||||||||||||||||||
| for i in "${CLEAN_DIRS[@]}"; do | ||||||||||||||||||||||||||||||||||||||||
| if [[ -s "$i/output/Gene/filtered/barcodes.tsv.gz" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| for j in "${CLEAN_DIRS[@]}"; do | ||||||||||||||||||||||||||||||||||||||||
| if [[ -s "$j/output/Gene/filtered/barcodes.tsv.gz" && "$i" != "$j" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| local_i="$TMPDIR_SORT/$(basename "$i").barcodes.tsv" | ||||||||||||||||||||||||||||||||||||||||
| local_j="$TMPDIR_SORT/$(basename "$j").barcodes.tsv" | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+91
to
+96
|
||||||||||||||||||||||||||||||||||||||||
| for i in "${CLEAN_DIRS[@]}"; do | |
| if [[ -s "$i/output/Gene/filtered/barcodes.tsv.gz" ]]; then | |
| for j in "${CLEAN_DIRS[@]}"; do | |
| if [[ -s "$j/output/Gene/filtered/barcodes.tsv.gz" && "$i" != "$j" ]]; then | |
| local_i="$TMPDIR_SORT/$(basename "$i").barcodes.tsv" | |
| local_j="$TMPDIR_SORT/$(basename "$j").barcodes.tsv" | |
| declare -A SORTED_BARCODE_FILES=() | |
| for i in "${CLEAN_DIRS[@]}"; do | |
| if [[ -s "$i/output/Gene/filtered/barcodes.tsv.gz" ]]; then | |
| for j in "${CLEAN_DIRS[@]}"; do | |
| if [[ -s "$j/output/Gene/filtered/barcodes.tsv.gz" && "$i" != "$j" ]]; then | |
| if [[ -z "${SORTED_BARCODE_FILES["$i"]}" ]]; then | |
| SORTED_BARCODE_FILES["$i"]=$(mktemp "$TMPDIR_SORT/barcodes.XXXXXX.tsv") | |
| fi | |
| if [[ -z "${SORTED_BARCODE_FILES["$j"]}" ]]; then | |
| SORTED_BARCODE_FILES["$j"]=$(mktemp "$TMPDIR_SORT/barcodes.XXXXXX.tsv") | |
| fi | |
| local_i="${SORTED_BARCODE_FILES["$i"]}" | |
| local_j="${SORTED_BARCODE_FILES["$j"]}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use unique temp-file keys for contamination inputs
Naming the sorted barcode cache files with basename conflates different directories that share a sample folder name (for example batch1/sampleA and batch2/sampleA). In that case both samples write/read the same temp path, so comm can compare a file to itself and report near-100% overlap (plus concurrent writes to one file), producing false cross-contamination warnings for unrelated samples.
Useful? React with 👍 / 👎.
Copilot
AI
Apr 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PCT divides by MIN, but MIN can be 0 if either barcode list is empty (wc -l == 0). That will cause an awk division-by-zero error under set -e and abort QC. Guard against MIN==0 (e.g., treat overlap as 0% and continue).
| PCT=$(echo "$COMM" | awk -v v="$MIN" '{printf "%d\n",100*$1/v+0.5}') | |
| if (( MIN == 0 )); then | |
| PCT=0 | |
| else | |
| PCT=$(echo "$COMM" | awk -v v="$MIN" '{printf "%d\n",100*$1/v+0.5}') | |
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle absent RE-DEFINED strand markers safely
starsolo enables set -euo pipefail, so this pipeline returns non-zero when Log.out does not contain RE-DEFINED; the command then exits before the fallback ST="Undef" logic can run. That turns a previously tolerated condition into a hard failure and causes starsolo qc to abort on valid logs that omit this marker.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation says the
Samplecolumn is the "Directory name", butrun_qccurrently prints the directory argument verbatim (which may include paths likeresults/GSM123). Either update the docs to say it's the provided path, or change QC output to printbasename(<dir>)(and ensure uniqueness if doing so).