Simplify reference handling through the workflow prepareAnnotation [CW-7232]
This commit is contained in:
parent
3e9d57a9e1
commit
dc0ebf3a52
@ -51,33 +51,17 @@ def _find_patterns(text, patterns):
|
|||||||
class PreparedReference:
|
class PreparedReference:
|
||||||
"""A reference genome prepared for analysis."""
|
"""A reference genome prepared for analysis."""
|
||||||
|
|
||||||
def __init__(self, input_path, work_dir):
|
def __init__(self, input_path):
|
||||||
"""Initialize and prepare reference genome."""
|
"""Initialize and prepare reference genome."""
|
||||||
self.input_path = Path(input_path)
|
self.input_path = Path(input_path)
|
||||||
self.work_dir = Path(work_dir)
|
|
||||||
self.output_path = self.work_dir / "reference.fasta"
|
|
||||||
self._seqnames = None
|
self._seqnames = None
|
||||||
self._prepare()
|
|
||||||
|
|
||||||
def _prepare(self):
|
|
||||||
"""Prepare reference (decompress if gzipped)."""
|
|
||||||
if _is_gzip(self.input_path):
|
|
||||||
with gzip.open(self.input_path, "rb") as src, open(
|
|
||||||
self.output_path, "wb"
|
|
||||||
) as dst:
|
|
||||||
shutil.copyfileobj(src, dst)
|
|
||||||
else:
|
|
||||||
shutil.copyfile(self.input_path, self.output_path)
|
|
||||||
|
|
||||||
if not self.output_path.exists() or self.output_path.stat().st_size < 1:
|
|
||||||
raise ValueError(f"Prepared file is empty: {self.output_path}")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def seqnames(self):
|
def seqnames(self):
|
||||||
"""Extract and cache sorted FASTA sequence names."""
|
"""Extract and cache sorted FASTA sequence names."""
|
||||||
if self._seqnames is None:
|
if self._seqnames is None:
|
||||||
ids = set()
|
ids = set()
|
||||||
with open(self.output_path, encoding="utf-8") as f:
|
with open(self.input_path, encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.startswith(">"):
|
if line.startswith(">"):
|
||||||
ids.add(line[1:].split()[0])
|
ids.add(line[1:].split()[0])
|
||||||
@ -94,7 +78,7 @@ class PreparedReference:
|
|||||||
providers.update(_find_patterns(self.input_path.name, PROVIDER_PATTERNS))
|
providers.update(_find_patterns(self.input_path.name, PROVIDER_PATTERNS))
|
||||||
|
|
||||||
# check headers
|
# check headers
|
||||||
with open(self.output_path, encoding="utf-8") as f:
|
with open(self.input_path, encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.startswith(">"):
|
if line.startswith(">"):
|
||||||
builds.update(_find_patterns(line, BUILD_PATTERNS))
|
builds.update(_find_patterns(line, BUILD_PATTERNS))
|
||||||
@ -475,7 +459,7 @@ def prepare_annotation_reference(annotation, reference, out_dir):
|
|||||||
out_dir.mkdir(parents=True, exist_ok=False)
|
out_dir.mkdir(parents=True, exist_ok=False)
|
||||||
|
|
||||||
# prepare reference genome
|
# prepare reference genome
|
||||||
ref = PreparedReference(reference, out_dir)
|
ref = PreparedReference(reference)
|
||||||
|
|
||||||
# prepare annotation (convert to GTF if needed, then filter to stranded)
|
# prepare annotation (convert to GTF if needed, then filter to stranded)
|
||||||
ann = Annotation(annotation, out_dir)
|
ann = Annotation(annotation, out_dir)
|
||||||
@ -518,7 +502,6 @@ def prepare_annotation_reference(annotation, reference, out_dir):
|
|||||||
},
|
},
|
||||||
"reference": {
|
"reference": {
|
||||||
"input": str(ref.input_path),
|
"input": str(ref.input_path),
|
||||||
"prepared": str(ref.output_path),
|
|
||||||
},
|
},
|
||||||
"seqnames": seqnames,
|
"seqnames": seqnames,
|
||||||
"analysis_seqnames": seqnames,
|
"analysis_seqnames": seqnames,
|
||||||
@ -555,7 +538,7 @@ def main(args):
|
|||||||
logger.info(
|
logger.info(
|
||||||
"Prepared annotation %s and reference %s.",
|
"Prepared annotation %s and reference %s.",
|
||||||
summary["annotation"]["prepared"],
|
summary["annotation"]["prepared"],
|
||||||
summary["reference"]["prepared"],
|
summary["reference"]["input"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -73,11 +73,11 @@ MOUSE_GTF = (
|
|||||||
[
|
[
|
||||||
(SIMPLE_REFERENCE, "reference.fa", SIMPLE_GTF,
|
(SIMPLE_REFERENCE, "reference.fa", SIMPLE_GTF,
|
||||||
"annotation.gtf"),
|
"annotation.gtf"),
|
||||||
(SIMPLE_REFERENCE, "reference.fa.gz", SIMPLE_GTF,
|
(SIMPLE_REFERENCE, "reference.fa", SIMPLE_GTF,
|
||||||
"annotation.gtf.gz"),
|
"annotation.gtf.gz"),
|
||||||
(SIMPLE_REFERENCE, "reference.fa", GFF3_INPUT,
|
(SIMPLE_REFERENCE, "reference.fa", GFF3_INPUT,
|
||||||
"annotation.gff3.gz"),
|
"annotation.gff3.gz"),
|
||||||
(NCBI_REFERENCE, "reference.fna.gz", NCBI_GTF,
|
(NCBI_REFERENCE, "reference.fna", NCBI_GTF,
|
||||||
"annotation.gtf.gz"),
|
"annotation.gtf.gz"),
|
||||||
(SIMPLE_REFERENCE + ">chrExtra\nTTTT\n", "reference.fa",
|
(SIMPLE_REFERENCE + ">chrExtra\nTTTT\n", "reference.fa",
|
||||||
MIXED_STRANDED_GTF, "annotation.gtf"),
|
MIXED_STRANDED_GTF, "annotation.gtf"),
|
||||||
@ -94,9 +94,6 @@ def test_prepare_all_formats_produce_valid_outputs(
|
|||||||
"""All supported input formats produce annotation.gtf and reference.fasta."""
|
"""All supported input formats produce annotation.gtf and reference.fasta."""
|
||||||
# write reference
|
# write reference
|
||||||
ref_path = tmp_path / ref_name
|
ref_path = tmp_path / ref_name
|
||||||
if ref_name.endswith(".gz"):
|
|
||||||
_write_gzip(ref_path, ref_data)
|
|
||||||
else:
|
|
||||||
_write(ref_path, ref_data)
|
_write(ref_path, ref_data)
|
||||||
|
|
||||||
# write annotation
|
# write annotation
|
||||||
@ -113,15 +110,15 @@ def test_prepare_all_formats_produce_valid_outputs(
|
|||||||
|
|
||||||
# output files must exist
|
# output files must exist
|
||||||
assert (out_dir / "annotation.gtf").exists()
|
assert (out_dir / "annotation.gtf").exists()
|
||||||
assert (out_dir / "reference.fasta").exists()
|
assert (ref_path).exists()
|
||||||
assert (out_dir / "annotation_reference_summary.json").exists()
|
assert (out_dir / "annotation_reference_summary.json").exists()
|
||||||
|
|
||||||
# paths returned in summary must match
|
# paths returned in summary must match
|
||||||
assert summary["annotation"]["prepared"] == str(out_dir / "annotation.gtf")
|
assert summary["annotation"]["prepared"] == str(out_dir / "annotation.gtf")
|
||||||
assert summary["reference"]["prepared"] == str(out_dir / "reference.fasta")
|
assert summary["reference"]["input"] == str(ref_path)
|
||||||
|
|
||||||
# outputs must be valid for downstream tools (Bambu, SQANTI)
|
# outputs must be valid for downstream tools (Bambu, SQANTI)
|
||||||
reference = Path(summary["reference"]["prepared"])
|
reference = Path(summary["reference"]["input"])
|
||||||
annotation = Path(summary["annotation"]["prepared"])
|
annotation = Path(summary["annotation"]["prepared"])
|
||||||
assert reference.read_text(encoding="utf-8").startswith(">")
|
assert reference.read_text(encoding="utf-8").startswith(">")
|
||||||
assert summary["analysis_seqnames"]["has_overlap"] is True
|
assert summary["analysis_seqnames"]["has_overlap"] is True
|
||||||
@ -142,7 +139,7 @@ def test_prepare_all_formats_produce_valid_outputs(
|
|||||||
|
|
||||||
def test_ncbi_format_sanitises_gene_ids(tmp_path):
|
def test_ncbi_format_sanitises_gene_ids(tmp_path):
|
||||||
"""NCBI gene_id='transcript_id' should be sanitised to actual transcript_id."""
|
"""NCBI gene_id='transcript_id' should be sanitised to actual transcript_id."""
|
||||||
reference = _write_gzip(tmp_path / "reference.fna.gz", NCBI_REFERENCE)
|
reference = _write(tmp_path / "reference.fna", NCBI_REFERENCE)
|
||||||
annotation = _write_gzip(tmp_path / "annotation.gtf.gz", NCBI_GTF)
|
annotation = _write_gzip(tmp_path / "annotation.gtf.gz", NCBI_GTF)
|
||||||
|
|
||||||
summary = prepare_annotation_reference.prepare_annotation_reference(
|
summary = prepare_annotation_reference.prepare_annotation_reference(
|
||||||
|
|||||||
@ -14,7 +14,7 @@ process bambuDiscover {
|
|||||||
input:
|
input:
|
||||||
tuple val(meta), val(aliases), path(bams, stageAs: "bams/??.bam"), path(bais, stageAs: "bams/??.bam.bai"), path(sample_sheet)
|
tuple val(meta), val(aliases), path(bams, stageAs: "bams/??.bam"), path(bais, stageAs: "bams/??.bam.bai"), path(sample_sheet)
|
||||||
path annotation, stageAs: "annotation/*"
|
path annotation, stageAs: "annotation/*"
|
||||||
path reference, stageAs: "reference/*"
|
tuple path(reference, stageAs: "reference/reference.fa"), path(ref_fai, stageAs: "reference/reference.fai")
|
||||||
output:
|
output:
|
||||||
tuple val(meta), path("discover"), emit: dir
|
tuple val(meta), path("discover"), emit: dir
|
||||||
script:
|
script:
|
||||||
@ -51,7 +51,7 @@ process bambuQuant {
|
|||||||
errorStrategy 'retry'
|
errorStrategy 'retry'
|
||||||
input:
|
input:
|
||||||
tuple val(meta), val(chunk_id), val(annotation_tx_count), path(chunk_rds), path(discovered_annotation)
|
tuple val(meta), val(chunk_id), val(annotation_tx_count), path(chunk_rds), path(discovered_annotation)
|
||||||
path reference, stageAs: "reference/*"
|
tuple path(reference, stageAs: "reference/reference.fa"), path(ref_fai, stageAs: "reference/reference.fai")
|
||||||
output:
|
output:
|
||||||
tuple val(meta), val(chunk_id), path("${chunk_id}"), emit: dir
|
tuple val(meta), val(chunk_id), path("${chunk_id}"), emit: dir
|
||||||
script:
|
script:
|
||||||
|
|||||||
@ -91,7 +91,6 @@ process prepareAnnotationReference {
|
|||||||
output:
|
output:
|
||||||
stdout emit: warnings
|
stdout emit: warnings
|
||||||
path "annotation.gtf", emit: annotation
|
path "annotation.gtf", emit: annotation
|
||||||
path "reference.fasta", emit: reference
|
|
||||||
path "annotation_reference_summary.json", emit: summary
|
path "annotation_reference_summary.json", emit: summary
|
||||||
path "unstranded_annotation.gtf", optional: true, emit: unstranded
|
path "unstranded_annotation.gtf", optional: true, emit: unstranded
|
||||||
script:
|
script:
|
||||||
@ -111,7 +110,7 @@ process buildCohortTranscriptomeFasta {
|
|||||||
memory "4 GB"
|
memory "4 GB"
|
||||||
input:
|
input:
|
||||||
path "transcripts.gtf"
|
path "transcripts.gtf"
|
||||||
path reference
|
tuple path(reference), path(ref_idx)
|
||||||
output:
|
output:
|
||||||
path "cohort.transcriptome.fa", emit: fasta
|
path "cohort.transcriptome.fa", emit: fasta
|
||||||
script:
|
script:
|
||||||
@ -127,7 +126,7 @@ process buildSampleTranscriptomeFasta {
|
|||||||
memory "4 GB"
|
memory "4 GB"
|
||||||
input:
|
input:
|
||||||
tuple val(meta), path("transcripts.gtf")
|
tuple val(meta), path("transcripts.gtf")
|
||||||
path reference
|
tuple path(reference), path(ref_idx)
|
||||||
output:
|
output:
|
||||||
tuple val(meta), path("${meta.alias}.transcriptome.fa"), emit: fasta
|
tuple val(meta), path("${meta.alias}.transcriptome.fa"), emit: fasta
|
||||||
script:
|
script:
|
||||||
@ -144,7 +143,7 @@ process runJointSqanti {
|
|||||||
input:
|
input:
|
||||||
path gtf
|
path gtf
|
||||||
path annotation, stageAs: "annotation/*"
|
path annotation, stageAs: "annotation/*"
|
||||||
path reference, stageAs: "reference/*"
|
tuple path(reference, stageAs: "reference/reference.fa"), path(ref_fai, stageAs: "reference/reference.fai")
|
||||||
output:
|
output:
|
||||||
path "sqanti_cohort", emit: dir
|
path "sqanti_cohort", emit: dir
|
||||||
path "sqanti_cohort/classification_summary.tsv", emit: summary
|
path "sqanti_cohort/classification_summary.tsv", emit: summary
|
||||||
@ -177,7 +176,7 @@ process runPerSampleSqanti {
|
|||||||
input:
|
input:
|
||||||
tuple val(meta), path(gtf)
|
tuple val(meta), path(gtf)
|
||||||
path annotation, stageAs: "annotation/*"
|
path annotation, stageAs: "annotation/*"
|
||||||
path reference, stageAs: "reference/*"
|
tuple path(reference, stageAs: "reference/reference.fa"), path(ref_fai, stageAs: "reference/reference.fai")
|
||||||
output:
|
output:
|
||||||
tuple val(meta), path("${meta.alias}_sqanti"), emit: dir
|
tuple val(meta), path("${meta.alias}_sqanti"), emit: dir
|
||||||
tuple val(meta), path("${meta.alias}_sqanti/classification_summary.tsv"), emit: summary
|
tuple val(meta), path("${meta.alias}_sqanti/classification_summary.tsv"), emit: summary
|
||||||
@ -217,8 +216,7 @@ workflow transcriptome_analysis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
analysis_annotation = prepared_reference_annotation.annotation.first()
|
analysis_annotation = prepared_reference_annotation.annotation.first()
|
||||||
analysis_reference = prepared_reference_annotation.reference.first()
|
analysis_reference = ref_genome.first()
|
||||||
|
|
||||||
joint_meta = [alias: "cohort"]
|
joint_meta = [alias: "cohort"]
|
||||||
|
|
||||||
joint_discover = runJointBambuDiscover(
|
joint_discover = runJointBambuDiscover(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user