savana.rs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. use crate::{
  2. annotation::{Annotation, Annotations, Caller, CallerCat},
  3. collection::{vcf::Vcf, HasOutputs, Initialize, Version},
  4. commands::bcftools::{bcftools_keep_pass, BcftoolsConfig},
  5. config::Config,
  6. helpers::force_or_not,
  7. io::vcf::read_vcf,
  8. runners::{run_wait, CommandRun, Run},
  9. variant::{variant::Variants, variant_collection::VariantCollection},
  10. };
  11. use anyhow::Context;
  12. use rayon::prelude::*;
  13. use log::info;
  14. use std::{fs, path::Path};
  15. #[derive(Debug)]
  16. pub struct Savana {
  17. pub id: String,
  18. pub passed_vcf: String,
  19. pub config: Config,
  20. pub log_dir: String,
  21. }
  22. impl Initialize for Savana {
  23. fn initialize(id: &str, config: Config) -> anyhow::Result<Self> {
  24. let log_dir = format!("{}/{}/log/savana", config.result_dir, id);
  25. if !Path::new(&log_dir).exists() {
  26. fs::create_dir_all(&log_dir)
  27. .context(format!("Failed to create {log_dir} directory"))?;
  28. }
  29. // Check for haplotagged bam
  30. let passed_vcf = config.savana_passed_vcf(id);
  31. Ok(Self {
  32. id: id.to_string(),
  33. passed_vcf,
  34. config,
  35. log_dir,
  36. })
  37. }
  38. }
  39. impl Run for Savana {
  40. fn run(&mut self) -> anyhow::Result<()> {
  41. force_or_not(
  42. &self.config.savana_output_vcf(&self.id),
  43. self.config.savana_force,
  44. )?;
  45. info!("Running Savana v{}", Savana::version(&self.config)?);
  46. let id = &self.id;
  47. let savana_args = [
  48. // "run",
  49. "--tumour",
  50. &self.config.tumoral_haplotagged_bam(id),
  51. "--normal",
  52. &self.config.normal_haplotagged_bam(id),
  53. "--outdir",
  54. &self.config.savana_output_dir(id),
  55. "--ref",
  56. &self.config.reference,
  57. "--phased_vcf",
  58. &self.config.germline_phased_vcf(id),
  59. "--no_blacklist",
  60. "--threads",
  61. &self.config.savana_threads.to_string(),
  62. ];
  63. let args = [
  64. "-c",
  65. &format!(
  66. "source {} && conda activate savana && {} {}",
  67. self.config.conda_sh,
  68. self.config.savana_bin,
  69. savana_args.join(" ")
  70. ),
  71. ];
  72. let mut cmd_run = CommandRun::new("bash", &args);
  73. let report = run_wait(&mut cmd_run).context(format!(
  74. "Error while running `severus.py {}`",
  75. args.join(" ")
  76. ))?;
  77. let log_file = format!("{}/savana_", self.log_dir);
  78. report
  79. .save_to_file(&log_file)
  80. .context(format!("Error while writing logs into {log_file}"))?;
  81. // Keep PASS
  82. let output_vcf = &self.config.savana_output_vcf(id);
  83. if !Path::new(&self.passed_vcf).exists() && Path::new(output_vcf).exists() {
  84. let report =
  85. bcftools_keep_pass(output_vcf, &self.passed_vcf, BcftoolsConfig::default())
  86. .context(format!(
  87. "Error while running bcftools keep PASS for {output_vcf}"
  88. ))?;
  89. let log_file = format!("{}/bcftools_pass", self.log_dir);
  90. report
  91. .save_to_file(&log_file)
  92. .context(format!("Error while writing logs into {log_file}"))?;
  93. }
  94. Ok(())
  95. }
  96. }
  97. impl HasOutputs for Savana {
  98. fn has_output(&self, id: &str) -> (bool, bool) {
  99. let output = &self.config.savana_passed_vcf(id);
  100. let passed = &self.config.savana_passed_vcf(id);
  101. (Path::new(output).exists(), Path::new(passed).exists())
  102. }
  103. }
  104. impl Version for Savana {
  105. fn version(config: &Config) -> anyhow::Result<String> {
  106. let savana_args = ["--version"];
  107. let args = [
  108. "-c",
  109. &format!(
  110. "source {} && conda activate savana && {} {}",
  111. config.conda_sh,
  112. config.savana_bin,
  113. savana_args.join(" ")
  114. ),
  115. ];
  116. let mut cmd_run = CommandRun::new("bash", &args);
  117. let report = run_wait(&mut cmd_run)
  118. .context(format!("Error while running `savana {}`", args.join(" ")))?;
  119. let log = report.log;
  120. let start = log
  121. .find("Version ")
  122. .context("Failed to find 'Version ' in the log")?;
  123. let start_index = start + "Version ".len();
  124. let end = log[start_index..]
  125. .find('\n')
  126. .context("Failed to find newline after 'Version '")?;
  127. Ok(log[start_index..start_index + end]
  128. .to_string()
  129. .trim()
  130. .to_string())
  131. }
  132. }
  133. impl CallerCat for Savana {
  134. fn caller_cat(&self) -> (Caller, Annotation) {
  135. (Caller::Savana, Annotation::Somatic)
  136. }
  137. }
  138. impl Variants for Savana {
  139. fn variants(&self, annotations: &Annotations) -> anyhow::Result<VariantCollection> {
  140. let (caller, category) = self.caller_cat();
  141. let add = vec![Annotation::Callers(caller.clone()), category.clone()];
  142. info!(
  143. "Loading variants from Savana {} with annotations: {:?}",
  144. self.id, add
  145. );
  146. let variants = read_vcf(&self.passed_vcf)?;
  147. variants.par_iter().for_each(|v| {
  148. annotations.insert_update(v.hash(), &add);
  149. });
  150. Ok(VariantCollection {
  151. variants,
  152. vcf: Vcf::new(self.passed_vcf.clone().into())?,
  153. caller,
  154. category,
  155. })
  156. }
  157. }