te-reference-saf-build
Use when setting up a NEW genome build / species for TE-compatible RNA-seq preprocessing (build-once, shared across datasets). Builds the TE GTF + subfamily SAF + exon-subtracted no-exon SAF from the
Files7
View on GitHubSKILL.mdskills/te-reference-saf-build/SKILL.md
GitHubTE Reference SAF Build (build-once, shared)
Overview
This skill is the canonical, version-pinned home for building the shared TE
reference artifacts that TE-compatible bulk RNA-seq preprocessing consumes: the
TEtranscripts TE GTF, the grouped subfamily SAF, and the exon-subtracted no-exon
SAF. These artifacts are built ~once per genome build and shared across every
dataset — all reference mm39 runs (13036-DM, AdaW_eWAT_WL, 14839-DM) point at the
same files under /data1/shared/ref/mouse/Ensembl/mm39/. The build recipe was
previously homeless: the toolkit owned zero build code, and the recipe lived only as
inline awk/bedtools prose in per-dataset /scratch READMEs.
This skill gives that recipe a single source of truth and an owned, tested executable
(scripts/build_te_saf.sh).
The headline differentiator from star-te-preprocessing: this skill owns the
build-once construction of the reference artifacts; star-te-preprocessing owns
the per-dataset usage contract (what the BAM/count matrix must satisfy) and
consumes what this skill produces. Cadence and audience differ — a reader running
preprocessing on every dataset should not have to load rare-path build instructions.
When to use this skill:
- Setting up a NEW genome build or species for TE preprocessing (e.g. moving from mm39 to a new mouse build, or to human GRCh38).
- The shared
*_GROUPED_all_noExon.safdoes not yet exist for your build, or you must rebuild it (e.g. a new gene-GTF release changed the exon set). - You need the version-pinned, reviewer-transparent recipe + script for these artifacts rather than copy-pasting awk/bedtools out of an old README.
When NOT to use this skill:
- Per-dataset alignment + counting (every run) → use
star-te-preprocessing. - Locus-level / copy-resolved TE quantification → out of scope; use SQuIRE/Telescope.
- Building a combined gene+TE GTF or a Dfam annotation → not this recipe.
Decision Tree
Need a TE reference for RNA-seq?
│
├─ Building artifacts for a NEW genome build / species (build-once, shared)?
│ → THIS skill (TE GTF → grouped SAF → no-exon SAF)
│
├─ The shared *_GROUPED_all_noExon.saf already exists for your build?
│ → Do NOT rebuild. Point star-te-preprocessing at the existing file.
│
├─ Aligning/counting a specific dataset against an existing reference?
│ → star-te-preprocessing (per-dataset recipe + contract)
│
└─ Need locus-level / copy-resolved TE expression?
→ out of scope → SQuIRE / Telescope (locus-level annotations)
Build-once cadence
These are shared, build-once artifacts. Before building, check whether the canonical file already exists for your build:
ls -la /data1/shared/ref/mouse/Ensembl/mm39/GRCm39_rmsk_TE_GROUPED_all_noExon.saf
If it exists, do not rebuild — every dataset reuses the same file (rebuilding
risks a silently different artifact across datasets). Rebuild only when the genome
build changes, the TE GTF source changes, or the gene GTF (and thus the exon set)
changes. Record every build in a provenance record
(references/reference-build-record-template.md).
The three artifacts
For mm39 / GENCODE vM37, the canonical names and home directory are (match these
EXACTLY — star-te-preprocessing/references/te-counting-workflow.md:139-141):
| Artifact | Canonical filename (under /data1/shared/ref/mouse/Ensembl/mm39/) |
Role |
|---|---|---|
| TE GTF | GRCm39_Ensembl_rmsk_TE.gtf.gz |
Downloaded (TEtranscripts), not built. Source of gene_id/family_id/class_id. |
| Grouped SAF (all) | GRCm39_rmsk_TE_GROUPED_all.saf |
Intermediate. One row per locus; GeneID = Subfamily:Family:Class. |
| Grouped no-exon SAF | GRCm39_rmsk_TE_GROUPED_all_noExon.saf |
The counting input. Grouped SAF with exonic loci subtracted. |
The grouped
_all.safis an intermediate; the_noExon.safis the load-bearing shared artifact fed tofeatureCounts. Do NOT use the stale nameGRCm39_rmsk_TE_formatted.saf(it appears in the toolkit driver's usage examples but matches no production run — artifact 08 §A.2; fixing that stale ref is a deferred toolkit task, not this skill's job).
For a new species/build, substitute the build prefix (e.g. GRCh38_...) but keep
the _GROUPED_all / _GROUPED_all_noExon suffixes and the Subfamily:Family:Class
GeneID convention.
Build recipe
Three steps. Steps 2–3 are owned end-to-end by scripts/build_te_saf.sh; step 1 is
a manual download. The verbatim awk/bedtools below is lifted from the canonical
READMEs (13036-DM/README.md, AdaW_eWAT_WL/README.md).
Step 1 — acquire the TE GTF (DOWNLOAD, not build)
The TE annotation is the TEtranscripts pre-generated RepeatMasker GTF — not
Dfam, not built locally, not combined with the gene GTF. It carries gene_id
(repName / subfamily), family_id, and class_id per locus (13036-DM/README.md:264,
AdaW_eWAT_WL/README.md:88-90):
# Source: TEtranscripts (https://www.mghlab.org/software/tetranscripts), pre-generated
# by their team. mm39 file: GRCm39_Ensembl_rmsk_TE.gtf.gz (TEtranscripts Dropbox).
TE_GTF=/data1/shared/ref/mouse/Ensembl/mm39/GRCm39_Ensembl_rmsk_TE.gtf.gz
# Download into place, then RECORD the source URL + date + checksum in the build record.
md5sum "$TE_GTF" # capture for provenance
Acquisition gap (artifact 08 §B.1): the source is documented but there is no scripted fetch and no recorded checksum in any prior run. When you build a new reference, capture the exact download URL, date, and md5 in the build record so the artifact is reproducible.
Step 2 — grouped SAF via awk (GeneID = Subfamily:Family:Class)
One row per TE locus, but the SAF GeneID is the group label — so featureCounts
sums all loci sharing a label into one subfamily meta-feature (~1,243 groups for
mm39). The SAF column format is GeneID Chr Start End Strand (1-based, tab-separated),
exactly what the toolkit's runFeatureCounts_TE_and_genes.sh -F SAF consumes.
Verbatim from 13036-DM/README.md:162-171 (identical at AdaW_eWAT_WL/README.md:100-108):
zcat "$TE_GTF" |
awk 'BEGIN{OFS="\t"; print "GeneID\tChr\tStart\tEnd\tStrand"}
$0 !~ /^#/ {
match($0,/gene_id "([^"]+)"/,g); # subfamily / repName
match($0,/family_id "([^"]+)"/,f); # family
match($0,/class_id "([^"]+)"/,c); # class (LINE/LTR/SINE/DNA/RC/...)
gid = g[1] ":" f[1] ":" c[1]; # TEtranscripts-like label
print gid, $1, $4, $5, $7
}' > "$SAF_ALL"
The 3-field order Subfamily:Family:Class is canon — it is the exact label that
te_utils.R::parse_te_id consumes downstream. Construct it in this order or
annotation breaks. (Requires GNU awk for the 3-arg match().)
Step 3 — no-exon SAF via bedtools subtract
Subtract gene exons from the TE intervals so a read counts as gene OR TE, never both
— the hard precondition for a valid combined gene+TE matrix. Verbatim from
13036-DM/README.md:187-262 (condensed at AdaW_eWAT_WL/README.md:119-144):
# 3a) Exon BED (0-based) from the GENE GTF
awk 'BEGIN{OFS="\t"} $3=="exon"{print $1,$4-1,$5,".",".",$7}' "$GENE_GTF" > exons.bed
# 3b) TE BED from the grouped SAF (0-based), GeneID carried as the BED name
awk 'BEGIN{OFS="\t"} NR>1{print $2,$3-1,$4,$1,".",$5}' "$SAF_ALL" > te_grouped.bed
# 3c) Normalize contig names FIRST (both with-chr or both without). Ensembl mm39 has
# no 'chr' prefix; mismatched naming SILENTLY yields zero overlap (a silent trap).
sed -E 's/^chr//' exons.bed > exons.tmp && mv exons.tmp exons.bed
# 3d) Sort both (LC_ALL=C, -k1,1 -k2,2n) — bedtools is faster/correct on sorted input
LC_ALL=C sort -k1,1 -k2,2n exons.bed -o exons.bed
LC_ALL=C sort -k1,1 -k2,2n te_grouped.bed -o te_grouped.bed
# 3e) Subtract, then VERIFY 0 residual overlap
bedtools subtract -a te_grouped.bed -b exons.bed > te_grouped_noExon.bed
bedtools intersect -a te_grouped_noExon.bed -b exons.bed -u | wc -l # MUST be 0
# 3f) Rebuild SAF (Start back to 1-based)
awk 'BEGIN{OFS="\t"; print "GeneID\tChr\tStart\tEnd\tStrand"}
{print $4,$1,$2+1,$3,$6}' te_grouped_noExon.bed > "$SAF_NOEXON"
Exon-BED provenance gotcha (artifact 08 §A.3, §B.3): the exon BED MUST come from the SAME gene GTF/build as the rest of the pipeline. Both prior mm39 runs borrowed a filtered gene GTF from a sibling project's outdir (
.../13441-DM/results_mm39_TE/genome/...filtered.gtf,13036-DM/README.md:190) — coupling the no-exon SAF's provenance to a fourth project's run. Recommended instead: derive the exon BED freshly from the canonical gene GTF for the build (e.g.gencode.vM37.primary_assembly.annotation.gtf(.gz)).scripts/build_te_saf.shdoes exactly this — it takes the gene GTF as an input and derives the exon BED itself, never hardcoding a sibling-project path.
Group count should stay ~1,243 after subtraction.
Owned executable
scripts/build_te_saf.sh is the SSoT executable owner of steps 2–3:
scripts/build_te_saf.sh \
--te-gtf /data1/shared/ref/mouse/Ensembl/mm39/GRCm39_Ensembl_rmsk_TE.gtf.gz \
--gene-gtf /data1/shared/ref/mouse/Ensembl/mm39/gencode.vM37.primary_assembly.annotation.gtf.gz \
--out-dir /data1/shared/ref/mouse/Ensembl/mm39 \
--prefix GRCm39_rmsk_TE
# -> <out-dir>/<prefix>_GROUPED_all.saf and <out-dir>/<prefix>_GROUPED_all_noExon.saf
It accepts gzipped or plain GTFs, derives the exon BED from --gene-gtf, normalizes
contig names, runs the subtract + 0-overlap verification, and writes the canonical
filenames. --keep-classes '^(LINE|SINE|LTR|RC)$' optionally whitelists classes
(retro-only); the default keeps all classes (the canonical decision).
Science rationale (pointer, not a copy)
Building these artifacts is a science-laden, once-per-build decision exercise — not a
mechanical format conversion. The full theory lives in TE-RNAseq-toolkit v2.0.3 —
docs/METHODOLOGY.md (version-pinned; do not duplicate). The builder must internalize:
- Subfamily grouping is the deliberate quantification unit, not a shortcut
(
METHODOLOGY.md:85-87). The groupedGeneID = Subfamily:Family:Classis what causes featureCounts to pool all loci of a subfamily into one meta-feature (~1,243 for mm39). Locus-unique GeneIDs instead give ~3.7M rows with signal diluted and lower statistical power — that needs a different tool (Telescope/SQuIRE),METHODOLOGY.md:86-87,177. - Exon subtraction is a hard precondition, not optional cleanup
(
METHODOLOGY.md:76-83). Without_noExon, a read in a gene-embedded TE is counted twice, inflating library size and breaking normalization/FDR. Always verify 0 residual overlap after subtracting (step 3e). - Integer Random-One coupling (
METHODOLOGY.md:17-29). The SAF is paired with the STAR Random-One contract (--outSAMmultNmax 1) so each fragment contributes exactly 1 count; this is why featureCounts uses-Mwithout--fraction. The interval SAF itself is format-agnostic, but the builder must know which counting contract it feeds. - Class composition (keep-all vs retro-only) (
METHODOLOGY.md+13036-DM/README.md:287). The canonical no-exon SAF keeps all RepeatMasker classes (LINE/SINE/LTR/DNA/RC/ Simple_repeat/...); a builder may whitelist retrotransposons (^(LINE|SINE|LTR|RC)$) to stop simple/low-complexity repeats dominating counts, but the canonical decision is "keep all, filter downstream." - Stated limitation to disclose (
METHODOLOGY.md:174-179): the subfamily artifact cannot claim which specific insertion is active; Random-One is stochastic per read. Fit for subfamily-activity questions only.
Outputs, naming convention, provenance
For each build, you produce (canonical mm39 names; substitute the build prefix for other species):
GRCm39_Ensembl_rmsk_TE.gtf.gz— the downloaded TEtranscripts TE GTF.GRCm39_rmsk_TE_GROUPED_all.saf— grouped SAF (intermediate).GRCm39_rmsk_TE_GROUPED_all_noExon.saf— exon-subtracted grouped SAF (the shared counting input).
Naming convention: <BUILD>_rmsk_TE_GROUPED_all[_noExon].saf, GeneID = Subfamily:Family:Class, columns GeneID Chr Start End Strand (1-based, TSV). Home
directory: /data1/shared/ref/<species>/<provider>/<build>/.
Provenance note: record every build using
references/reference-build-record-template.md — genome build, TE-GTF source URL +
date + md5, gene GTF used for the exon BED, the exact build_te_saf.sh command,
output paths + md5s, and the class-composition choice. This closes the
acquisition/build provenance gaps that artifact 08 flagged.
Verification Checklist
After building, confirm:
- Grouped SAF labels:
cut -f1 *_GROUPED_all.saf | tail -n +2 | headshowsSubfamily:Family:Class(e.g.L1Md_A:L1:LINE). - Group count sane:
cut -f1 *_GROUPED_all.saf | tail -n +2 | sort -u | wc -l≈ 1,243 for mm39 (a few-k for other mammalian builds). - 0 residual exon overlap:
bedtools intersect -a te_grouped_noExon.bed -b exons.bed -u | wc -lis 0. - Group count preserved: unique groups in
_noExon.saf≈ unchanged after subtraction. - Column format: header is
GeneID\tChr\tStart\tEnd\tStrand; Start is 1-based. - Provenance recorded: a filled
reference-build-record-template.mdexists with source URL, md5s, and the exact build command.
Common Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
bedtools subtract removes nothing (0 overlaps where many expected) |
Contig naming mismatch (chr1 vs 1) — silent zero-overlap trap |
Normalize BOTH BEDs to the same convention first (step 3c); Ensembl mm39 has no chr. |
Downstream parse_te_id fails / unparseable labels |
SAF GeneID not in 3-field Subfamily:Family:Class order |
Build with the verbatim awk in step 2; keep gene_id:family_id:class_id order. |
match($0,/.../,arr) errors |
awk is mawk, not GNU awk (3-arg match is gawk) |
Use gawk; the build script checks for it. |
| Different no-exon SAF across datasets | Rebuilt with a different gene GTF / borrowed sibling-project GTF | Build once per genome build; derive the exon BED from the canonical gene GTF; record provenance. |
| Simple/low-complexity repeats dominate counts | Kept all classes when retro-only was wanted | Optional `--keep-classes '^(LINE |
Handoff to star-te-preprocessing
Once *_GROUPED_all_noExon.saf exists for the build, hand it to
star-te-preprocessing as the TE SAF (-e) for the featureCounts TE pass. That
skill owns the per-dataset usage contract (Random-One STAR args, integer -M no
--fraction, strandedness discipline). Do not rebuild the reference per dataset —
all datasets on the same build reuse this one file.
Resources
- Build script (owned):
scripts/build_te_saf.sh— steps 2–3 as a deterministic, awk/bedtools-only executable. - Build record template:
references/reference-build-record-template.md. - Downstream consumer:
star-te-preprocessing/SKILL.mdandstar-te-preprocessing/references/te-counting-workflow.md. - Methodology / rationale (subfamily vs locus, exon subtraction, Random-One, class composition): TE-RNAseq-toolkit v2.0.3 —
docs/METHODOLOGY.md(version-pinned; pointed to, not copied). - TE GTF source: TEtranscripts — https://www.mghlab.org/software/tetranscripts
- TE-ID parser (label
Subfamily:Family:Class): TE-RNAseq-toolkit —R/te_utils.R::parse_te_id.
When not to use
- Do not use per-dataset — these artifacts are built once per genome build and reused across every dataset. For the per-run alignment+counting recipe use star-te-preprocessing.
- Do not use for locus-level TE references — this builds subfamily-grouped SAFs (one meta-feature per subfamily). For copy-resolved/locus-level annotations use Telescope or SQuIRE.
- Do not use to build a combined gene+TE GTF or a Dfam annotation — the TE source here is the TEtranscripts pre-generated RepeatMasker GTF, kept separate from the gene GTF.
See also
This skill sits upstream of star-te-preprocessing in the handoff graph:
te-reference-saf-build → star-te-preprocessing → annotate-bulk-rnaseq-data → bulk-rnaseq-gsea. For locus-level / copy-resolved TE quantification (out of scope here), see SQuIRE/Telescope (external).
star-te-preprocessing— Next step; downstream consumer of the no-exon SAF for per-dataset STAR alignment + featureCounts