From 833d0f37b580767b34687898a6b3a9969fd42b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Fri, 31 May 2024 13:38:34 +0200 Subject: [PATCH 01/21] analysis: update alignment QC analysis in order to launch a single job that executes sequentially samtools, plot-bamstats and fastqc, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java modified: opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java modified: opencga-core/src/main/java/org/opencb/opencga/core/tools/OpenCgaToolExecutor.java modified: opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java --- .../qc/AlignmentFastQcMetricsAnalysis.java | 9 +- .../alignment/qc/AlignmentQcAnalysis.java | 378 +++++++++--------- .../opencga/analysis/tools/OpenCgaTool.java | 23 +- .../DockerWrapperAnalysisExecutor.java | 5 +- .../SamtoolsWrapperAnalysisExecutor.java | 2 +- .../alignment/AlignmentAnalysisTest.java | 92 ++++- .../core/tools/OpenCgaToolExecutor.java | 11 +- .../tools/result/ExecutionResultManager.java | 2 +- 8 files changed, 319 insertions(+), 203 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java index 1dd71a315af..a556927fae5 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java @@ -96,7 +96,7 @@ protected void run() throws ToolException { }); } - public static FastQcMetrics parseResults(Path outDir) throws ToolException { + public static FastQcMetrics parseResults(Path outDir, String confJobDir) throws ToolException { Path fastQcPath = null; Path imgPath = null; for (java.io.File file : outDir.toFile().listFiles()) { @@ -116,8 +116,11 @@ public static FastQcMetrics parseResults(Path outDir) throws ToolException { // Replace absolute paths to relative paths List relativePaths = new ArrayList<>(); for (String path : fastQcMetrics.getFiles()) { - int index = path.indexOf("JOBS/"); - relativePaths.add(index == -1 ? new java.io.File(path).getName() : path.substring(index)); + // Sanity check + if (!path.startsWith(confJobDir)) { + throw new ToolException("The FastQC file " + path + " is not in the configuration job folder "+ confJobDir); + } + relativePaths.add(path.substring(confJobDir.length() + 1)); } fastQcMetrics.setFiles(relativePaths); } else { diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index 6b541da0037..941c999bdbe 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -16,34 +16,40 @@ package org.opencb.opencga.analysis.alignment.qc; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.opencb.biodata.formats.alignment.samtools.SamtoolsFlagstats; import org.opencb.biodata.formats.alignment.samtools.SamtoolsStats; import org.opencb.biodata.formats.sequence.fastqc.FastQcMetrics; -import org.opencb.commons.datastore.core.Event; import org.opencb.commons.datastore.core.ObjectMap; -import org.opencb.commons.datastore.core.Query; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.opencga.analysis.AnalysisUtils; import org.opencb.opencga.analysis.tools.OpenCgaToolScopeStudy; +import org.opencb.opencga.analysis.wrappers.executors.DockerWrapperAnalysisExecutor; +import org.opencb.opencga.analysis.wrappers.fastqc.FastqcWrapperAnalysisExecutor; +import org.opencb.opencga.analysis.wrappers.samtools.SamtoolsWrapperAnalysis; +import org.opencb.opencga.analysis.wrappers.samtools.SamtoolsWrapperAnalysisExecutor; import org.opencb.opencga.catalog.exceptions.CatalogException; -import org.opencb.opencga.core.api.ParamConstants; import org.opencb.opencga.core.exceptions.ToolException; -import org.opencb.opencga.core.models.alignment.*; +import org.opencb.opencga.core.models.alignment.AlignmentQcParams; import org.opencb.opencga.core.models.common.Enums; import org.opencb.opencga.core.models.file.File; import org.opencb.opencga.core.models.file.FileQualityControl; import org.opencb.opencga.core.models.file.FileUpdateParams; -import org.opencb.opencga.core.models.job.Job; -import org.opencb.opencga.core.response.OpenCGAResult; import org.opencb.opencga.core.tools.annotations.Tool; import org.opencb.opencga.core.tools.annotations.ToolParams; +import org.opencb.opencga.core.tools.result.ExecutionResultManager; +import java.io.IOException; +import java.nio.charset.Charset; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import static org.apache.commons.io.FileUtils.readLines; import static org.opencb.opencga.core.api.ParamConstants.ALIGNMENT_QC_DESCRIPTION; +import static org.opencb.opencga.core.tools.OpenCgaToolExecutor.EXECUTOR_ID; @Tool(id = AlignmentQcAnalysis.ID, resource = Enums.Resource.ALIGNMENT) public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { @@ -51,15 +57,16 @@ public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { public static final String ID = "alignment-qc"; public static final String DESCRIPTION = ALIGNMENT_QC_DESCRIPTION; + public static final String SAMTOOLS_STATS_STEP = "samtools-stats"; + public static final String SAMTOOLS_FLAGSTATS_STEP = "samtools-flagstats"; + private static final String PLOT_BAMSTATS_STEP = "plot-bamstats"; + public static final String FASTQC_METRICS_STEP = "fastqc-metrics"; + @ToolParams protected final AlignmentQcParams analysisParams = new AlignmentQcParams(); private File catalogBamFile; - private AlignmentFileQualityControl alignmentQc = null; - - private boolean runStats = true; - private boolean runFlagStats = true; - private boolean runFastqc = true; + private FileQualityControl fileQc = null; @Override protected void check() throws Exception { @@ -71,211 +78,206 @@ protected void check() throws Exception { try { catalogBamFile = AnalysisUtils.getCatalogFile(analysisParams.getBamFile(), study, catalogManager.getFileManager(), token); - if (catalogBamFile.getQualityControl() != null) { - alignmentQc = catalogBamFile.getQualityControl().getAlignment(); - } - - // Prepare flags - String skip = null; - if (StringUtils.isNotEmpty(analysisParams.getSkip())) { - skip = analysisParams.getSkip().toLowerCase().replace(" ", ""); - } - if (StringUtils.isNotEmpty(skip)) { - Set skipValues = new HashSet<>(Arrays.asList(skip.split(","))); - if (skipValues.contains(AlignmentQcParams.STATS_SKIP_VALUE) - || - (!analysisParams.isOverwrite() && alignmentQc != null && alignmentQc.getSamtoolsStats() != null)) { - runStats = false; - } - if (skipValues.contains(AlignmentQcParams.FLAGSTATS_SKIP_VALUE) - || - (!analysisParams.isOverwrite() && alignmentQc != null && alignmentQc.getSamtoolsFlagStats() != null)) { - runFlagStats = false; - } - if (skipValues.contains(AlignmentQcParams.FASTQC_METRICS_SKIP_VALUE) - || - (!analysisParams.isOverwrite() && alignmentQc != null && alignmentQc.getFastQcMetrics() != null)) { - runFastqc = false; - } - } } catch (CatalogException e) { throw new ToolException("Error accessing to the BAM file '" + analysisParams.getBamFile() + "'", e); } } + @Override + protected List getSteps() { + return Arrays.asList(SAMTOOLS_STATS_STEP, SAMTOOLS_FLAGSTATS_STEP, PLOT_BAMSTATS_STEP, FASTQC_METRICS_STEP); + } + @Override protected void run() throws ToolException { + // Get alignment QC metrics to update + if (catalogBamFile.getQualityControl() != null) { + fileQc = catalogBamFile.getQualityControl(); + } + if (fileQc == null) { + fileQc = new FileQualityControl(); + } - step(() -> { - Map params; - String statsJobId = null; - String flagStatsJobId = null; - String fastQcMetricsJobId = null; + step(SAMTOOLS_FLAGSTATS_STEP, this::runSamtoolsFlagStats); + step(SAMTOOLS_STATS_STEP, this::runSamtoolsStats); + step(PLOT_BAMSTATS_STEP, this::runPlotBamStats); + step(FASTQC_METRICS_STEP, this::runFastQcMetrics); - try { - if (runFlagStats) { - // Flag stats - params = new AlignmentFlagStatsParams(analysisParams.getBamFile(), null) - .toParams(new ObjectMap(ParamConstants.STUDY_PARAM, study)); - - OpenCGAResult flagStatsJobResult = catalogManager.getJobManager() - .submit(study, AlignmentFlagStatsAnalysis.ID, Enums.Priority.MEDIUM, params, null, "Job generated by " - + getId() + " - " + getJobId(), Collections.emptyList(), Collections.emptyList(), token); - flagStatsJobId = flagStatsJobResult.first().getId(); - addEvent(Event.Type.INFO, "Submit job " + flagStatsJobId + " to compute stats (" + AlignmentFlagStatsAnalysis.ID - + ")"); - } - } catch (CatalogException e) { - addWarning("Error launching job for Alignment Flag Stats Analysis: " + e.getMessage()); - } - - try { - if (runStats) { - // Stats - params = new AlignmentStatsParams(analysisParams.getBamFile(), null) - .toParams(new ObjectMap(ParamConstants.STUDY_PARAM, study)); - - OpenCGAResult statsJobResult = catalogManager.getJobManager() - .submit(study, AlignmentStatsAnalysis.ID, Enums.Priority.MEDIUM, params, null, "Job generated by " - + getId() + " - " + getJobId(), Collections.emptyList(), Collections.emptyList(), token); - statsJobId = statsJobResult.first().getId(); - addEvent(Event.Type.INFO, "Submit job " + statsJobId + " to compute stats (" + AlignmentStatsAnalysis.ID + ")"); - } - } catch (CatalogException e) { - addWarning("Error launching job for Alignment Stats Analysis: " + e.getMessage()); - } + // Finally, update file quality control + try { + FileUpdateParams fileUpdateParams = new FileUpdateParams().setQualityControl(fileQc); + catalogManager.getFileManager().update(study, catalogBamFile.getId(), fileUpdateParams, QueryOptions.empty(), token); + } catch (CatalogException e) { + throw new ToolException(e); + } - try { - if (runFastqc) { - // FastQC metrics - params = new AlignmentFastQcMetricsParams(analysisParams.getBamFile(), null) - .toParams(new ObjectMap(ParamConstants.STUDY_PARAM, study)); - - OpenCGAResult fastQcMetricsJobResult = catalogManager.getJobManager() - .submit(study, AlignmentFastQcMetricsAnalysis.ID, Enums.Priority.MEDIUM, params, null, - "Job generated by " + getId() + " - " + getJobId(), Collections.emptyList(), Collections.emptyList(), - token); - fastQcMetricsJobId = fastQcMetricsJobResult.first().getId(); - addEvent(Event.Type.INFO, "Submit job " + fastQcMetricsJobId + " to compute FastQC metrics (" - + AlignmentFastQcMetricsAnalysis.ID + ")"); - } - } catch (CatalogException e) { - addWarning("Error launching job for Alignment FastQC Metrics Analysis: " + e.getMessage()); - } + // Unset the executor info since it is executed by different executors, it will be indicated in the + // tool step attributes + getErm().setExecutorInfo(null); + } - // Wait for those jobs before saving QC - SamtoolsFlagstats samtoolsFlagstats = null; - SamtoolsStats samtoolsStats = null; - FastQcMetrics fastQcMetrics = null; - - if (flagStatsJobId != null) { - try { - if (waitFor(flagStatsJobId)) { - Job job = getJob(flagStatsJobId); - Path resultPath = AlignmentFlagStatsAnalysis.getResultPath(job.getOutDir().getUri().getPath(), - catalogBamFile.getName()); - samtoolsFlagstats = AlignmentFlagStatsAnalysis.parseResults(resultPath); - } - } catch (Exception e) { - addWarning("Error waiting for job '" + flagStatsJobId + "' (Alignment Flag Stats Analysis): " + e.getMessage()); - } - } + private void runSamtoolsFlagStats() throws ToolException { + Path outPath = getOutDir().resolve(SAMTOOLS_FLAGSTATS_STEP); + try { + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating SAMtools flagstat output folder: " + outPath, e); + } - if (statsJobId != null) { - try { - if (waitFor(statsJobId)) { - Job job = getJob(statsJobId); - Path resultPath = AlignmentStatsAnalysis.getResultPath(job.getOutDir().getUri().getPath(), - catalogBamFile.getName()); - samtoolsStats = AlignmentStatsAnalysis.parseResults(resultPath, Paths.get(job.getOutDir().getUri().getPath())); - } - } catch (Exception e) { - addWarning("Error waiting for job '" + statsJobId + "' (Alignment Stats Analysis): " + e.getMessage()); - } - } + SamtoolsWrapperAnalysisExecutor executor = getToolExecutor(SamtoolsWrapperAnalysisExecutor.class, + SamtoolsWrapperAnalysisExecutor.ID) + .setCommand("flagstat") + .setInputFile(catalogBamFile.getUri().getPath()); - if (fastQcMetricsJobId != null) { - try { - if (waitFor(fastQcMetricsJobId)) { - Job job = getJob(fastQcMetricsJobId); - fastQcMetrics = AlignmentFastQcMetricsAnalysis.parseResults(Paths.get(job.getOutDir().getUri().getPath())); - } - } catch (Exception e) { - addWarning("Error waiting for job '" + fastQcMetricsJobId + "' (Alignment FastQC Metrics Analysis): " + e.getMessage()); - } - } + ExecutionResultManager erm = new ExecutionResultManager(SamtoolsWrapperAnalysisExecutor.ID, outPath); + ObjectMap params = new ObjectMap(); + executor.setUp(erm, params, outPath); - // Update quality control for the catalog file - catalogBamFile = AnalysisUtils.getCatalogFile(analysisParams.getBamFile(), study, catalogManager.getFileManager(), token); - FileQualityControl qc = catalogBamFile.getQualityControl(); - // Sanity check - if (qc == null) { - qc = new FileQualityControl(); - } else if (qc.getAlignment() == null) { - qc.setAlignment(new AlignmentFileQualityControl()); - } + // Execute + executor.execute(); + getErm().addStepAttribute("CLI", executor.getCommandLine()); - boolean saveQc = false; - if (samtoolsFlagstats != null) { - qc.getAlignment().setSamtoolsFlagStats(samtoolsFlagstats); - saveQc = true; - } - if (samtoolsStats != null) { - qc.getAlignment().setSamtoolsStats(samtoolsStats); - saveQc = true; - } - if (fastQcMetrics != null) { - qc.getAlignment().setFastQcMetrics(fastQcMetrics); - saveQc = true; + // Check results and update QC file + Path flagStatsFile = AlignmentFlagStatsAnalysis.getResultPath(outPath.toAbsolutePath().toString(), catalogBamFile.getName()); + java.io.File stdoutFile = outPath.resolve(DockerWrapperAnalysisExecutor.STDOUT_FILENAME).toFile(); + List lines ; + try { + lines = readLines(stdoutFile, Charset.defaultCharset()); + } catch (IOException e) { + throw new ToolException("Error reading running samtools-flagstat results.", e); + } + if (lines.size() > 0 && lines.get(0).contains("QC-passed")) { + try { + FileUtils.copyFile(stdoutFile, flagStatsFile.toFile()); + } catch (IOException e) { + throw new ToolException("Error copying samtools-flagstat results.", e); } + } else { + throw new ToolException("Something wrong happened running samtools-flagstat."); + } - if (saveQc) { - catalogManager.getFileManager().update(getStudy(), catalogBamFile.getId(), new FileUpdateParams().setQualityControl(qc), - QueryOptions.empty(), getToken()); - } - }); + // Check results and update QC file + SamtoolsFlagstats samtoolsFlagstats = AlignmentFlagStatsAnalysis.parseResults(flagStatsFile); + fileQc.getAlignment().setSamtoolsFlagStats(samtoolsFlagstats); } - private boolean waitFor(String jobId) throws ToolException { - Query query = new Query("id", jobId); - OpenCGAResult result = null; + private void runSamtoolsStats() throws ToolException { + Path outPath = getOutDir().resolve(SAMTOOLS_STATS_STEP); try { - result = catalogManager.getJobManager().search(study, query, QueryOptions.empty(), token); - } catch (CatalogException e) { - new ToolException("Error waiting for job '" + jobId + "': " + e.getMessage()); + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating SAMtools stats output folder: " + outPath, e); } - Job job = result.first(); - String status = job.getInternal().getStatus().getId(); - while (status.equals(Enums.ExecutionStatus.PENDING) || status.equals(Enums.ExecutionStatus.RUNNING) - || status.equals(Enums.ExecutionStatus.QUEUED) || status.equals(Enums.ExecutionStatus.READY) - || status.equals(Enums.ExecutionStatus.REGISTERING)) { - // Sleep for 1 minute + SamtoolsWrapperAnalysisExecutor executor = getToolExecutor(SamtoolsWrapperAnalysisExecutor.class, + SamtoolsWrapperAnalysisExecutor.ID) + .setCommand("stats") + .setInputFile(catalogBamFile.getUri().getPath()); + + ExecutionResultManager erm = new ExecutionResultManager(SamtoolsWrapperAnalysisExecutor.ID, outPath); + ObjectMap params = new ObjectMap(); + params.put(EXECUTOR_ID, SamtoolsWrapperAnalysisExecutor.ID); + // Filter flag: + // - not primary alignment (0x100) + // - read fails platform/vendor quality checks (0x200) + // - supplementary alignment (0x800) + params.put("F", "0xB00"); + executor.setUp(erm, params, outPath); + + // Execute + executor.execute(); + getErm().addStepAttribute("CLI", executor.getCommandLine()); + + // Check results + Path statsFile = AlignmentStatsAnalysis.getResultPath(outPath.toAbsolutePath().toString(), catalogBamFile.getName()); + java.io.File stdoutFile = outPath.resolve(DockerWrapperAnalysisExecutor.STDOUT_FILENAME).toFile(); + List lines ; + try { + lines = readLines(stdoutFile, Charset.defaultCharset()); + } catch (IOException e) { + throw new ToolException("Error reading running samtools-stats results.", e); + } + if (lines.size() > 0 && lines.get(0).startsWith("# This file was produced by samtools stats")) { try { - Thread.sleep(60000); - result = catalogManager.getJobManager().search(study, query, QueryOptions.empty(), token); - job = result.first(); - } catch (CatalogException | InterruptedException e) { - new ToolException("Error waiting for job '" + jobId + "': " + e.getMessage()); + FileUtils.copyFile(stdoutFile, statsFile.toFile()); + } catch (IOException e) { + throw new ToolException("Error copying samtools-stats results.", e); } - status = job.getInternal().getStatus().getId(); + } else { + throw new ToolException("Something wrong happened running samtools-stats."); } - return status.equals(Enums.ExecutionStatus.DONE) ? true : false; + // Check results and update QC file + SamtoolsStats samtoolsStats; + try { + samtoolsStats = SamtoolsWrapperAnalysis.parseSamtoolsStats(statsFile.toFile()); + } catch (IOException e) { + throw new ToolException("Error parsing samtools-stats results."); + } + fileQc.getAlignment().setSamtoolsStats(samtoolsStats); } - private Job getJob(String jobId) { - Job job = null; + private void runPlotBamStats() throws ToolException { + Path outPath = getOutDir().resolve(PLOT_BAMSTATS_STEP); try { - Query query = new Query("id", jobId); - OpenCGAResult result = catalogManager.getJobManager().search(study, query, QueryOptions.empty(), token); - job = result.first(); - } catch (CatalogException e) { - new ToolException("Error getting job '" + jobId + "' from catalog: " + e.getMessage()); + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating plot-bamstats output folder: " + outPath, e); } - if (job == null) { - new ToolException("Error getting job '" + jobId + "' from catalog."); + + Path statsFile = AlignmentStatsAnalysis.getResultPath(getOutDir().resolve(SAMTOOLS_STATS_STEP).toString(), + catalogBamFile.getName()); + SamtoolsWrapperAnalysisExecutor executor = getToolExecutor(SamtoolsWrapperAnalysisExecutor.class, + SamtoolsWrapperAnalysisExecutor.ID) + .setCommand("plot-bamstats") + .setInputFile(statsFile.toString()); + + ExecutionResultManager erm = new ExecutionResultManager(SamtoolsWrapperAnalysisExecutor.ID, outPath); + ObjectMap params = new ObjectMap(); + params.put(EXECUTOR_ID, SamtoolsWrapperAnalysisExecutor.ID); + executor.setUp(erm, params, outPath); + + // Execute + executor.execute(); + getErm().addStepAttribute("CLI", executor.getCommandLine()); + + // Check results and update QC file + List images = new ArrayList<>(); + for (java.io.File file : outPath.toFile().listFiles()) { + if (file.getName().endsWith("png")) { + // Sanity check + if (!file.getAbsolutePath().startsWith(configuration.getJobDir())) { + throw new ToolException("plot-bamstats image is not in the configuration job folder "+ configuration.getJobDir()); + } + images.add(file.getAbsolutePath().substring(configuration.getJobDir().length() + 1)); + } } - return job; + fileQc.getAlignment().getSamtoolsStats().setFiles(images); + } + + private void runFastQcMetrics() throws ToolException { + Path outPath = getOutDir().resolve(FASTQC_METRICS_STEP); + try { + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating FastQC output folder: " + outPath, e); + } + + FastqcWrapperAnalysisExecutor executor = getToolExecutor(FastqcWrapperAnalysisExecutor.class, FastqcWrapperAnalysisExecutor.ID) + .setInputFile(catalogBamFile.getUri().getPath()); + + ExecutionResultManager erm = new ExecutionResultManager(FastqcWrapperAnalysisExecutor.ID, outPath); + ObjectMap params = new ObjectMap(); + params.put(EXECUTOR_ID, FastqcWrapperAnalysisExecutor.ID); + params.put("extract", "true"); + executor.setUp(erm, params, outPath); + + // Execute + executor.execute(); + getErm().addStepAttribute("CLI", executor.getCommandLine()); + + // Check results and update QC file + FastQcMetrics fastQcMetrics = AlignmentFastQcMetricsAnalysis.parseResults(outPath, configuration.getJobDir()); + fileQc.getAlignment().setFastQcMetrics(fastQcMetrics); } } diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java index 7769315137b..344e34d61cc 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java @@ -16,6 +16,7 @@ package org.opencb.opencga.analysis.tools; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; @@ -109,7 +110,7 @@ public final OpenCgaTool setUp(String opencgaHome, CatalogManager catalogManager if (params != null) { this.params.putAll(params); } - this.executorParams = new ObjectMap(); + this.executorParams = getExecutorParams(params); this.outDir = outDir; //this.params.put("outDir", outDir.toAbsolutePath().toString()); @@ -124,7 +125,7 @@ public final OpenCgaTool setUp(String opencgaHome, ObjectMap params, Path outDir if (params != null) { this.params.putAll(params); } - this.executorParams = new ObjectMap(); + this.executorParams = getExecutorParams(params); this.outDir = outDir; //this.params.put("outDir", outDir.toAbsolutePath().toString()); @@ -345,6 +346,15 @@ protected final Enums.Resource getToolResource() { return this.getClass().getAnnotation(Tool.class).resource(); } + public ExecutionResultManager getErm() { + return erm; + } + + public OpenCgaTool setErm(ExecutionResultManager erm) { + this.erm = erm; + return this; + } + /** * Method called internally to obtain the list of steps. * @@ -585,6 +595,15 @@ private void loadStorageConfiguration() throws IOException { this.storageConfiguration = ConfigurationUtils.loadStorageConfiguration(opencgaHome); } + private ObjectMap getExecutorParams(ObjectMap params) { + ObjectMap executorParams = new ObjectMap(); + if (MapUtils.isNotEmpty(params)) { + if (params.containsKey(EXECUTOR_ID)) { + executorParams.put(EXECUTOR_ID, params.getString(EXECUTOR_ID)); + } + } + return executorParams; + } // TODO can this method be removed? // protected final Analyst getAnalyst(String token) throws ToolException { // try { diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java index ae2b5072473..3765ab247b8 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java @@ -7,7 +7,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.opencb.commons.datastore.core.ObjectMap; import org.opencb.commons.exec.Command; -import org.opencb.opencga.analysis.wrappers.deeptools.DeeptoolsWrapperAnalysis; import org.opencb.opencga.core.common.GitRepositoryState; import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.tools.OpenCgaToolExecutor; @@ -18,9 +17,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.*; public abstract class DockerWrapperAnalysisExecutor extends OpenCgaToolExecutor { @@ -158,6 +154,7 @@ protected void appendOtherParams(Set skipParams, StringBuilder sb) { protected void runCommandLine(String cmdline) throws ToolException { checkDockerDaemonAlive(); try { + setCommandLine(cmdline); new Command(cmdline) .setOutputOutputStream( new DataOutputStream(new FileOutputStream(getOutDir().resolve(STDOUT_FILENAME).toFile()))) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java index 67a7743df87..7981fcf9eb4 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java @@ -315,7 +315,7 @@ private void runFlagstat() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: " + sb); runCommandLine(sb.toString()); } diff --git a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java index bdc744fc49c..1360558bc0c 100644 --- a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java +++ b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java @@ -16,6 +16,7 @@ package org.opencb.opencga.analysis.alignment; +import htsjdk.samtools.util.BufferedLineReader; import org.junit.AfterClass; import org.junit.Before; import org.junit.ClassRule; @@ -26,8 +27,10 @@ import org.opencb.biodata.models.clinical.Phenotype; import org.opencb.commons.datastore.core.ObjectMap; import org.opencb.commons.datastore.core.QueryOptions; +import org.opencb.commons.utils.FileUtils; import org.opencb.opencga.TestParamConstants; import org.opencb.opencga.analysis.alignment.qc.AlignmentGeneCoverageStatsAnalysis; +import org.opencb.opencga.analysis.alignment.qc.AlignmentQcAnalysis; import org.opencb.opencga.analysis.tools.ToolRunner; import org.opencb.opencga.analysis.variant.OpenCGATestExternalResource; import org.opencb.opencga.analysis.variant.manager.VariantStorageManager; @@ -37,6 +40,7 @@ import org.opencb.opencga.core.config.storage.StorageConfiguration; import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.models.alignment.AlignmentGeneCoverageStatsParams; +import org.opencb.opencga.core.models.alignment.AlignmentQcParams; import org.opencb.opencga.core.models.file.File; import org.opencb.opencga.core.models.file.FileLinkParams; import org.opencb.opencga.core.models.organizations.OrganizationCreateParams; @@ -48,13 +52,19 @@ import org.opencb.opencga.storage.hadoop.variant.HadoopVariantStorageEngine; import org.opencb.opencga.storage.hadoop.variant.HadoopVariantStorageTest; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.spi.FileSystemProvider; import java.util.Arrays; import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.opencb.opencga.core.tools.OpenCgaToolExecutor.EXECUTOR_ID; @RunWith(Parameterized.class) @Category(MediumTests.class) @@ -270,7 +280,7 @@ public void geneCoverageStatsTest() throws IOException, ToolException, CatalogEx //String bamFilename = getClass().getResource("/biofiles/NA19600.chrom20.small.bam").getFile(); File bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, null, null), false, token).first(); - assertEquals(0, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().size()); + assertEquals(0, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().size()); AlignmentGeneCoverageStatsParams params = new AlignmentGeneCoverageStatsParams(); params.setBamFile(bamFile.getId()); @@ -285,4 +295,80 @@ public void geneCoverageStatsTest() throws IOException, ToolException, CatalogEx assertEquals(geneName, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().get(0).getGeneName()); assertEquals(10, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().get(0).getStats().size()); } + + @Test + public void testAlignmentQc() throws IOException, ToolException, CatalogException { + Path outdir = Paths.get(opencga.createTmpOutdir("_alignment_qc")); + + // setup BAM files + String bamFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam").toString(); + //String baiFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam.bai").toString(); + //String bamFilename = getClass().getResource("/biofiles/NA19600.chrom20.small.bam").getFile(); + File bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, + null, null), false, token).first(); + + System.out.println("bamFile.getQualityControl().getAlignment() = " + bamFile.getQualityControl().getAlignment()); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(bamFile.getId()); + + toolRunner.execute(AlignmentQcAnalysis.class, params, new ObjectMap(), outdir, + "test-alignment-qc-job-id", token); + + bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, + null, null), false, token).first(); + + System.out.println("bamFile.getQualityControl().getAlignment() = " + bamFile.getQualityControl().getAlignment()); + System.out.println("outdir = " + outdir); + } + + + @Test + public void testBlobFuse2() throws IOException { + displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/tata.txt")); + displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/toto.txt")); + displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/titi.txt")); + displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/jobdir")); + displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/jobdir/helloworld.txt")); + displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/jobdir/kk.txt")); + } + + public void displayAttributes(Path path) throws IOException { + System.out.println(); + System.out.println(path); + + if (Files.exists(path)) { + System.out.println("\t- Exists"); + } +// try (BufferedReader reader = new BufferedLineReader(path).newBufferedReader(path)) { +// System.out.println("\t- Exists by using FileUtils.newBufferedReader"); +// } +// try (BufferedWriter writer = FileUtils.newBufferedWriter(path)) { +// System.out.println("\t- Exists by using FileUtils.newBufferedWriter"); +// } + if (Files.isDirectory(path)) { + System.out.println("\t- Directory"); + } + if (Files.isRegularFile(path)) { + System.out.println("\t- Regular file"); + } + if (Files.isReadable(path)) { + System.out.println("\t- Readable"); + } + if (Files.isWritable(path)) { + System.out.println("\t- Writable"); + } + if (Files.isSymbolicLink(path)) { + System.out.println("\t- Symbolic link"); + } +// +// +// FileSystemProvider provider = FileSystems.getDefault().provider()getFileSystem(new URI(path.toAbsolutePath().toString())).provider(); +// BasicFileAttributes basicFileAttributes = provider.readAttributes(path, BasicFileAttributes.class); +// System.out.println("basicFileAttributes = " + basicFileAttributes); +// System.out.println("basicFileAttributes.isRegularFile() = " + basicFileAttributes.isRegularFile()); +// System.out.println("basicFileAttributes.isDirectory() = " + basicFileAttributes.isDirectory()); +// System.out.println("basicFileAttributes.isSymbolicLink() = " + basicFileAttributes.isSymbolicLink()); +// System.out.println("basicFileAttributes.creationTime() = " + basicFileAttributes.creationTime()); + } } \ No newline at end of file diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/tools/OpenCgaToolExecutor.java b/opencga-core/src/main/java/org/opencb/opencga/core/tools/OpenCgaToolExecutor.java index 15cf7a51215..dbc936b9e7a 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/tools/OpenCgaToolExecutor.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/tools/OpenCgaToolExecutor.java @@ -31,6 +31,7 @@ public abstract class OpenCgaToolExecutor { private ObjectMap executorParams; private Path outDir; private ExecutionResultManager arm; + private String commandLine; protected OpenCgaToolExecutor() { } @@ -81,6 +82,15 @@ protected final String getToken() { return getExecutorParams().getString("token"); } + public String getCommandLine() { + return commandLine; + } + + protected OpenCgaToolExecutor setCommandLine(String commandLine) { + this.commandLine = commandLine; + return this; + } + protected final void addWarning(String warning) throws ToolException { arm.addWarning(warning); } @@ -88,5 +98,4 @@ protected final void addWarning(String warning) throws ToolException { protected final void addAttribute(String key, Object value) throws ToolException { arm.addStepAttribute(key, value); } - } diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java b/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java index b1e716bd41f..426a39e9920 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java @@ -312,7 +312,7 @@ public interface ExecutionResultFunction { R apply(ExecutionResult execution) throws ToolException; } - private synchronized R updateResult(ExecutionResultFunction update) throws ToolException { + public synchronized R updateResult(ExecutionResultFunction update) throws ToolException { ExecutionResult execution = read(); R apply = update.apply(execution); write(execution); From b0aea7b64dde9d6fe6b20ac22d8e954223abf217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Fri, 14 Jun 2024 19:43:53 +0200 Subject: [PATCH 02/21] app: fix circos.R and move to app/analysis/genome-plot, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: new file: opencga-app/app/analysis/genome-plot/circos.R --- opencga-app/app/analysis/genome-plot/circos.R | 632 ++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 opencga-app/app/analysis/genome-plot/circos.R diff --git a/opencga-app/app/analysis/genome-plot/circos.R b/opencga-app/app/analysis/genome-plot/circos.R new file mode 100644 index 00000000000..f9ecba14b02 --- /dev/null +++ b/opencga-app/app/analysis/genome-plot/circos.R @@ -0,0 +1,632 @@ +#!/usr/bin/env Rscript + +library(RCircos); +library(scales) +library(optparse) + + +my.RCircos.Tile.Plot <- function (tile.data, track.num, side, tile.colors=NA) +{ + RCircos.Pos <- RCircos.Get.Plot.Positions() + RCircos.Par <- RCircos.Get.Plot.Parameters() + tile.data <- RCircos.Get.Plot.Data.nosort(tile.data, "plot") + the.layer <- 1 + the.chr <- tile.data[1, 1] + start <- tile.data[1, 2] + end <- tile.data[1, 3] + tile.layers <- rep(1, nrow(tile.data)) + if (nrow(tile.data)>1) { + for (a.row in 2:nrow(tile.data)) { + if (tile.data[a.row, 2] >= end) { + the.layer <- 1 + start <- tile.data[a.row, 2] + end <- tile.data[a.row, 3] + } + else if (tile.data[a.row, 1] != the.chr) { + the.layer <- 1 + the.chr <- tile.data[a.row, 1] + start <- tile.data[a.row, 2] + end <- tile.data[a.row, 3] + } + else { + the.layer <- the.layer + 1 + if (tile.data[a.row, 3] > end) { + end <- tile.data[a.row, 3] + } + } + tile.layers[a.row] <- 1 + } + } + locations <- RCircos.Track.Positions.my(side, track.num) + out.pos <- locations[1] + in.pos <- locations[2] + layer.height <- RCircos.Par$track.height/RCircos.Par$max.layers + num.layers <- max(tile.layers) + if (num.layers > RCircos.Par$max.layers) { + if (side == "in") { + in.pos <- out.pos - layer.height * num.layers + } + else { + out.pos <- in.pos + layer.height * num.layers + } + cat(paste("Tiles plot will use more than one track.", + "Please select correct area for next track.\n")) + } + if (num.layers < RCircos.Par$max.layers) { + layer.height <- RCircos.Par$track.height/num.layers + } + if (length(tile.colors)==1) { + tile.colors <- RCircos.Get.Plot.Colors(tile.data, RCircos.Par$tile.color)} + RCircos.Track.Outline.my(out.pos, in.pos, num.layers) + the.loc <- ncol(tile.data) + for (a.row in 1:nrow(tile.data)) { + tile.len <- tile.data[a.row, 3] - tile.data[a.row, 2] + tile.range <- round(tile.len/RCircos.Par$base.per.unit/2, + digits = 0) + start <- tile.data[a.row, the.loc] - tile.range + end <- tile.data[a.row, the.loc] + tile.range + layer.bot <- in.pos + layer.top <- out.pos + + #Catch positions that fall outside a band (eg when using exome ideogram) + if (is.na(start) || (is.na(end))) { + next; + } + polygon.x <- c(RCircos.Pos[start:end, 1] * layer.top, + RCircos.Pos[end:start, 1] * layer.bot) + polygon.y <- c(RCircos.Pos[start:end, 2] * layer.top, + RCircos.Pos[end:start, 2] * layer.bot) + polygon(polygon.x, polygon.y, col = tile.colors[a.row], lwd=RCircos.Par$line.width, border=tile.colors[a.row]) + } +} + + +RCircos.Chromosome.Ideogram.Plot.my <- function (chrTextColor = 'grey', gridLineColor = 'grey', textSize = 0.6) +{ + RCircos.Cyto <- RCircos.Get.Plot.Ideogram() + RCircos.Pos <- RCircos.Get.Plot.Positions() + RCircos.Par <- RCircos.Get.Plot.Parameters() + right.side <- nrow(RCircos.Pos)/2 + if (is.null(RCircos.Par$chr.ideog.pos)){ + RCircos.Par$chr.ideog.pos <- 1.95 #3.1 #1.95 + } + outer.location <- RCircos.Par$chr.ideog.pos + RCircos.Par$chrom.width + inner.location <- RCircos.Par$chr.ideog.pos + chroms <- unique(RCircos.Cyto$Chromosome) + for (a.chr in 1:length(chroms)) { + the.chr <- RCircos.Cyto[RCircos.Cyto$Chromosome == chroms[a.chr], + ] + ##new RCircos version + start <- the.chr$StartPoint[1] + end <- the.chr$EndPoint[nrow(the.chr)] + mid <- round((end - start + 1)/2, digits = 0) + start + # chr.color <- 'grey' + chr.color <- chrTextColor + pos.x <- c(RCircos.Pos[start:end, 1] * outer.location, + RCircos.Pos[end:start, 1] * inner.location) + pos.y <- c(RCircos.Pos[start:end, 2] * outer.location, + RCircos.Pos[end:start, 2] * inner.location) + # polygon(pos.x, pos.y, border='grey', lwd=0.5) + polygon(pos.x, pos.y, border=gridLineColor, lwd=0.5) + chr.name <- sub(pattern = "chr", replacement = "", chroms[a.chr]) + text(RCircos.Pos[mid, 1] * RCircos.Par$chr.name.pos, + RCircos.Pos[mid, 2] * RCircos.Par$chr.name.pos, label = chr.name, + # srt = RCircos.Pos$degree[mid], col='grey', cex=0.6) + srt = RCircos.Pos$degree[mid], col=gridLineColor, cex=textSize) + lines(RCircos.Pos[start:end, ] * RCircos.Par$highlight.pos, + col = chr.color, lwd = 0.5) + } + for (a.band in 1:nrow(RCircos.Cyto)) { + a.color <- RCircos.Cyto$BandColor[a.band] + if (a.color == "white") { + next + } + ##new RCircos version + start <- RCircos.Cyto$StartPoint[a.band] + end <- RCircos.Cyto$EndPoint[a.band] + pos.x <- c(RCircos.Pos[start:end, 1] * outer.location, + RCircos.Pos[end:start, 1] * inner.location) + pos.y <- c(RCircos.Pos[start:end, 2] * outer.location, + RCircos.Pos[end:start, 2] * inner.location) + polygon(pos.x, pos.y, col = alpha(a.color,0.25), border = NA) + } +} + + +RCircos.Get.Plot.Data.nosort <- function (genomic.data, plot.type, validate=TRUE) +{ + + data.points <- rep(0, nrow(genomic.data)) + for (a.row in 1:nrow(genomic.data)) { + chromosome <- as.character(genomic.data[a.row, 1]) + location <- round((genomic.data[a.row, 2] + genomic.data[a.row, + 3])/2, digits = 0) + data.points[a.row] <- RCircos.Data.Point(chromosome, location) + } + genomic.data["Location"] <- data.points + return(genomic.data) +} + + +RCircos.Heatmap.Plot.my <- function (heatmap.data, data.col, track.num, side, plotTrack=TRUE, heatmap.ranges=NA, heatmap.color=NA) +{ + RCircos.Cyto <- RCircos.Get.Plot.Ideogram() + RCircos.Pos <- RCircos.Get.Plot.Positions() + RCircos.Par <- RCircos.Get.Plot.Parameters() + + min.with <- 1000000 + heatmap.data$width <- heatmap.data$chromEnd - heatmap.data$chromStart + heatmap.data <- heatmap.data[order(-heatmap.data$width),] # make sure the narrowest plots are drawn as last + narrow.cn <- heatmap.data$width < min.with + flank <- (min.with - heatmap.data$width[narrow.cn])/2 + heatmap.data$chromEnd[narrow.cn] <- heatmap.data$chromEnd[narrow.cn ] + flank + heatmap.data$chromStart[narrow.cn ] <- heatmap.data$chromStart[narrow.cn ] - flank + heatmap.data$chromStart[heatmap.data$chromStart<0] <- 0 + + heatmap.data <- RCircos.Get.Plot.Data.nosort(heatmap.data, "plot") + heatmap.data1 <- RCircos.Get.Plot.Data.nosort(data.frame(Chromosome=heatmap.data$Chromosome, chromStart=heatmap.data$chromStart, chromEnd=heatmap.data$chromStart), "plot") + heatmap.data2 <- RCircos.Get.Plot.Data.nosort(data.frame(Chromosome=heatmap.data$Chromosome, chromStart=heatmap.data$chromEnd, chromEnd=heatmap.data$chromEnd), "plot") + + + if ((length(heatmap.ranges)==1) && (is.na(heatmap.ranges))) { + ColorLevel <- RCircos.Par$heatmap.ranges + } else { + ColorLevel <- heatmap.ranges + } + + if ((length(heatmap.color)==1) && (is.na(heatmap.color))) { + ColorRamp <- RCircos.Get.Heatmap.ColorScales(RCircos.Par$heatmap.color) + } + + columns <- 5:(ncol(heatmap.data) - 1) + min.value <- min(as.matrix(heatmap.data[, columns])) + max.value <- max(as.matrix(heatmap.data[, columns])) + + heatmap.locations1 <- as.numeric(heatmap.data1[, ncol(heatmap.data2)]) + heatmap.locations2 <- as.numeric(heatmap.data2[, ncol(heatmap.data2)]) + + start <- heatmap.locations1 # - RCircos.Par$heatmap.width/2 + end <- heatmap.locations2 # + RCircos.Par$heatmap.width/2 + data.chroms <- as.character(heatmap.data[, 1]) + chromosomes <- unique(data.chroms) + cyto.chroms <- as.character(RCircos.Cyto$Chromosome) + + for (a.chr in 1:length(chromosomes)) { + cyto.rows <- which(cyto.chroms == chromosomes[a.chr]) + locations <- as.numeric(RCircos.Cyto$EndPoint[cyto.rows]) # chromosome locations + chr.start <- min(locations) - RCircos.Cyto$StartPoint[cyto.rows[1]] # chromosome start + chr.end <- max(locations) # chromosome end + data.rows <- which(data.chroms == chromosomes[a.chr]) # points on this chromosome + start[data.rows[start[data.rows] < chr.start]] <- chr.start # chromosome starts for each point + end[data.rows[end[data.rows] > chr.end]] <- chr.end # chromosome end for each point + } + + locations <- RCircos.Track.Positions.my(side, track.num) # positions + out.pos <- locations[1] + in.pos <- locations[2] + chroms <- unique(RCircos.Cyto$Chromosome) + for (a.chr in 1:length(chroms)) { + the.chr <- RCircos.Cyto[RCircos.Cyto$Chromosome == chroms[a.chr], + ] + the.start <- the.chr$StartPoint[1] + the.end <- the.chr$EndPoint[nrow(the.chr)] + polygon.x <- c(RCircos.Pos[the.start:the.end, 1] * out.pos, + RCircos.Pos[the.end:the.start, 1] * in.pos) + polygon.y <- c(RCircos.Pos[the.start:the.end, 2] * out.pos, + RCircos.Pos[the.end:the.start, 2] * in.pos) + polygon(polygon.x, polygon.y, col = "white", border = RCircos.Par$grid.line.color, lwd=0.3) + } + + + heatmap.value <- as.numeric(heatmap.data[, data.col]) + for (a.point in 1:length(heatmap.value)) { + + the.level <- which(ColorLevel <= heatmap.value[a.point]) + cell.color <- heatmap.color[max(the.level)] # establish the color + + the.start <- start[a.point] + the.end <- end[a.point] + #if (is.na(the.start) | is.na(the.end)) { + # browser() + #} + + #Catch positions that fall outside a band (eg when using exome ideogram) + if (is.na(the.start) || (is.na(the.end))) { + next; + } + polygon.x <- c(RCircos.Pos[the.start:the.end, 1] * out.pos, RCircos.Pos[the.end:the.start, 1] * in.pos) + polygon.y <- c(RCircos.Pos[the.start:the.end, 2] * out.pos, RCircos.Pos[the.end:the.start, 2] * in.pos) + polygon(polygon.x, polygon.y, col = cell.color, border = NA) + } + +} + + +RCircos.Link.Plot.my <- function (link.data, track.num, by.chromosome = FALSE, link.colors=NA) +{ + + if (length(link.colors)==1) { + link.colors <- rep('BurlyWood', nrow(link.data)) + } + + + RCircos.Pos <- RCircos.Get.Plot.Positions() + RCircos.Par <- RCircos.Get.Plot.Parameters() + locations <- RCircos.Track.Positions.my('in', track.num) + start <- locations[['out.loc']] + base.positions <- RCircos.Pos * start + data.points <- matrix(rep(0, nrow(link.data) * 2), ncol = 2) + for (a.link in 1:nrow(link.data)) { + data.points[a.link, 1] <- RCircos.Data.Point(link.data[a.link, + 1], link.data[a.link, 2]) + data.points[a.link, 2] <- RCircos.Data.Point(link.data[a.link, + 4], link.data[a.link, 5]) + if (data.points[a.link, 1] == 0 || data.points[a.link, + 2] == 0) { + print("Error in chromosome locations ...") + break + } + } + for (a.link in 1:nrow(data.points)) { + point.one <- data.points[a.link, 1] + point.two <- data.points[a.link, 2] + if (point.one > point.two) { + point.one <- data.points[a.link, 2] + point.two <- data.points[a.link, 1] + } + P0 <- as.numeric(base.positions[point.one, ]) + P2 <- as.numeric(base.positions[point.two, ]) + links <- RCircos.Link.Line(P0, P2) + lines(links$pos.x, links$pos.y, type = "l", col = link.colors[a.link], lwd=RCircos.Par$link.line.width) + } +} + + +RCircos.Scatter.Plot.color <- function (scatter.data, data.col, track.num, side, scatter.colors, draw.bg =TRUE, no.sort=FALSE) +{ + + RCircos.Pos <- RCircos.Get.Plot.Positions() + pch <- RCircos.Get.Plot.Parameters()$point.type + cex <- RCircos.Get.Plot.Parameters()$point.size + scatter.data <- RCircos.Get.Plot.Data.nosort(scatter.data, "plot") + + locations <- RCircos.Track.Positions.my(side, track.num, track.heights = 4) + out.pos <- locations[1] + in.pos <- locations[2] + point.bottom <- in.pos + data.ceiling <- max(scatter.data[, data.col]) + + sub.height <- out.pos - point.bottom + + RCircos.Track.Outline.my(out.pos, in.pos) + + scatter.data[scatter.data[data.col]>data.ceiling, data.col] <- data.ceiling + scatter.data[scatter.data[data.col]<(-data.ceiling), data.col] <- -data.ceiling + scatter.data$height <- point.bottom + scatter.data[, data.col]/data.ceiling * sub.height + scatter.data$x_coord <- RCircos.Pos[scatter.data$Location, 1] * scatter.data$height + scatter.data$y_coord <- RCircos.Pos[scatter.data$Location, 2] * scatter.data$height + + points(scatter.data$x_coord, + scatter.data$y_coord, + col = scatter.colors, + pch = pch, + cex = cex) +} + + +RCircos.Track.Outline.my <- function (out.pos, in.pos, num.layers = 1) +{ + RCircos.Cyto <- RCircos.Get.Plot.Ideogram() + RCircos.Pos <- RCircos.Get.Plot.Positions() + RCircos.Par <- RCircos.Get.Plot.Parameters() + subtrack.height <- (out.pos - in.pos)/num.layers + chroms <- unique(RCircos.Cyto$Chromosome) + for (a.chr in 1:length(chroms)) { + the.chr <- RCircos.Cyto[RCircos.Cyto$Chromosome == chroms[a.chr], ] + start <- the.chr$StartPoint[1] + end <- the.chr$EndPoint[nrow(the.chr)] + polygon.x <- c(RCircos.Pos[start:end, 1] * out.pos, RCircos.Pos[end:start, + 1] * in.pos) + polygon.y <- c(RCircos.Pos[start:end, 2] * out.pos, RCircos.Pos[end:start, + 2] * in.pos) + polygon(polygon.x, polygon.y, col = NULL, lwd=0.3, border=RCircos.Par$grid.line.color) + + for (a.line in 1:(num.layers - 1)) { + height <- out.pos - a.line * subtrack.height + lines(RCircos.Pos[start:end, 1] * height, RCircos.Pos[start:end, 2] * height, col = RCircos.Par$grid.line.color, lwd=0.3) + } + } +} + + +RCircos.Track.Positions.my <- function (side, track.num, track.heights = 1) +{ + RCircos.Par <- RCircos.Get.Plot.Parameters() + one.track <- RCircos.Par$track.height + RCircos.Par$track.padding + side <- tolower(side) + if (side == "in") { + out.pos <- RCircos.Par$track.in.start - (track.num - + 1) * one.track + in.pos <- out.pos - RCircos.Par$track.height - + one.track * ( track.heights - 1) + } else if (side == "out") { + in.pos <- RCircos.Par$track.out.start + (track.num - + 1) * one.track + out.pos <- in.pos + RCircos.Par$track.height + } else { + stop("Incorrect track location. It must be \"in\" or \"out\".") + } + return(c(out.loc = out.pos, in.loc = in.pos)) +} + + +set.plot.circosParams <- function(){ + + # circos parameters + circosParams.my <- list() + + #use these two circosParams to adjust circle size + circosParams.my$plot.radius <- 2.15 + circosParams.my$genomeplot.margin <- 0.25 + + circosParams.my$track.background <- 'white' + circosParams.my$highlight.width <- 0.2 + circosParams.my$point.size <- 0.3 + circosParams.my$point.type <- 16 + circosParams.my$radius.len <- 3 + circosParams.my$chr.ideog.pos <- 3.2 + circosParams.my$highlight.pos <- 2.09 #3.35 + circosParams.my$chr.name.pos <- 2.14 #3.45 + circosParams.my$track.in.start <- 3.05 + circosParams.my$track.out.start <- 3.2 + + circosParams.my$tracks.inside <- 10 + circosParams.my$tracks.outside <- 1 + + circosParams.my$line.width <- 1 + circosParams.my$link.line.width <- 0.5 + + circosParams.my$text.size <- 0.6 + + circosParams.my$text.color <- 'black' + + circosParams.my$track.padding <- c(0.07, 0.0, 0.07, 0.0,0.07, 0) + + circosParams.my$grid.line.color <- 'lightgrey' + circosParams.my$chr.text.color <- 'grey' + + circosParams.my$track.heights <- c(0.85, 0.07, 0.07, 0.1, 0.1, 0.1) + circosParams.my$track.height <- 0.1 + circosParams.my$sub.tracks <- 1 + circosParams.my$heatmap.cols <- c(alpha('lightcoral', 1), + alpha('lightcoral', 0.5), + alpha('lightgrey',0.10), + alpha('olivedrab2', 0.3), + alpha('olivedrab2', 0.5), + alpha('olivedrab2',.7), + alpha('olivedrab2', 0.75), + alpha('olivedrab3', 0.9), + alpha('olivedrab4', 0.9)) + circosParams.my$heatmap.ranges <- c(0,1,3,4,8,16, 32,64,1000) + + #Set copynumber (and indel) colour scheme + circosParams.my$heatmap.color.gain <- c( alpha('lightgrey',0.10), alpha('olivedrab2', 0.3), alpha('olivedrab2', 0.5), alpha('olivedrab2',.7), alpha('olivedrab2', 0.75), alpha('olivedrab3', 0.9), alpha('olivedrab4', 0.9)) + circosParams.my$heatmap.ranges.gain <- c(0,2,4,8,16, 32,64,1000) + + circosParams.my$heatmap.ranges.loh <- c(0,1,1000) + circosParams.my$heatmap.color.loh <- c(alpha('lightcoral', 1), alpha('lightgrey',0.10)) + + circosParams.my$heatmap.key.gain.col <- alpha('olivedrab2', 0.3) + circosParams.my$heatmap.key.loh.col <- alpha('lightcoral', 1) + circosParams.my$heatmap.key.gain.title <- 'gain' + circosParams.my$heatmap.key.loh.title <- 'LOH' + + #tumour majorCN + circosParams.my$heatmap.data.col.gain <- 8 + #tumour minorCN + circosParams.my$heatmap.data.col.loh <- 7 + + #Indel colours + circosParams.my$indel.mhomology <- 'firebrick4' + circosParams.my$indel.repeatmediated <- 'firebrick1' + circosParams.my$indel.other <- 'firebrick3' + circosParams.my$indel.insertion <- 'darkgreen' + circosParams.my$indel.complex <- 'grey' + + return(circosParams.my) + +} + + +genomePlot <- function(snvs.file, indels.file, cnvs.file, rearrs.file, + sampleID, genome.v="hg19", ..., plot_title = NULL, + no_snvs = FALSE, no_copynumber = FALSE, no_rearrangements = FALSE, no_indels = FALSE, out_format = "png", out_path = ".") { + + genome.ideogram = switch(genome.v, + "hg19" = "UCSC.HG19.Human.CytoBandIdeogram", + "hg38" = "UCSC.HG38.Human.CytoBandIdeogram") + data(list=genome.ideogram, package = "RCircos"); + species.cyto <- get(genome.ideogram); + + circosParams.my <- set.plot.circosParams() + + # rearrangement links colors + inv.col <- alpha('dodgerblue2', 1) + del.col <- alpha('coral2', 1) + dupl.col <- alpha('darkgreen', 1) + transloc.colour <- alpha('gray35', 1) + + #Set up height, width and resolution parameters + cPanelWidth = 0 + graph.height = 4100 + graph.wd_ht_ratio = 1 #width/height ratio + graph.width = graph.height * graph.wd_ht_ratio + graph.wd_res_ratio = (4100/550) + graph.res = graph.width/graph.wd_res_ratio + + graph.height.inches = graph.height/graph.res + graph.width.inches = graph.width/graph.res + + # substitutions + if (!no_snvs) { + subs <- read.table(file = snvs.file, sep = '\t', header = TRUE) + # subs <- read.table(file = '/home/dapregi/tmp/snvs.tsv', sep = '\t', header = TRUE) + subs$color[(subs$ref=='C' & subs$alt=='A') | (subs$ref=='G' & subs$alt=='T')] <- 'royalblue' + subs$color[(subs$ref=='C' & subs$alt=='G') | (subs$ref=='G' & subs$alt=='C')] <- 'black' + subs$color[(subs$ref=='C' & subs$alt=='T') | (subs$ref=='G' & subs$alt=='A')] <- 'red' + subs$color[(subs$ref=='T' & subs$alt=='A') | (subs$ref=='A' & subs$alt=='T')] <- 'grey' + subs$color[(subs$ref=='T' & subs$alt=='C') | (subs$ref=='A' & subs$alt=='G')] <- 'green2' + subs$color[(subs$ref=='T' & subs$alt=='G') | (subs$ref=='A' & subs$alt=='C')] <- 'hotpink' + } + + # indels + indels <- read.table(file = indels.file, sep = '\t', header = TRUE) + # indels <- read.table(file = '/home/dapregi/tmp/indels.tsv', sep = '\t', header = TRUE) + dels.formatted <- data.frame() + ins.formatted <- data.frame() + if (!no_indels && !is.null(indels)) { + ins <- indels[which(indels$type=='I'),] + dels <- indels[which(indels$type=='D' | indels$type=='DI'),] + dels$color[dels$classification=='Microhomology-mediated'] <- circosParams.my$indel.mhomology + dels$color[dels$classification=='Repeat-mediated'] <- circosParams.my$indel.repeatmediated + dels$color[dels$classification=='None'] <- circosParams.my$indel.other + } + + # copy number + cv.data <- data.frame() + #Skip if no copynumber was requested + if (!no_copynumber) { + cv.data <- read.table(file = cnvs.file, sep = '\t', header = TRUE) + # cv.data <- read.table(file = '/home/dapregi/tmp/cnvs.tsv', sep = '\t', header = TRUE) + if(is.null(cv.data) || nrow(cv.data)==0){ + no_copynumber <- TRUE + } + } + + # rearrangements + rearrs <- data.frame() + if (!no_rearrangements) { + rearrs <- read.table(file = rearrs.file, sep = '\t', header = TRUE) + if(is.null(rearrs) || nrow(rearrs)==0){ + no_rearrangements <- TRUE + } + } + + ################################################################################ + + fn = file.path(out_path, paste(sampleID, ".genomePlot.", out_format, sep=''), fsep = .Platform$file.sep) + + if (out_format == 'png') { + png(file=fn, height=graph.height, width=(graph.width*(1/(1-cPanelWidth))), res=graph.res) + } else if (out_format == 'svg') { + svg(fn, height=graph.height.inches, width=graph.width.inches) + } else { + stop("Invalid file type. Only png and svg are supported"); + } + + RCircos.Set.Core.Components(cyto.info=species.cyto, chr.exclude=NULL, tracks.inside=circosParams.my$tracks.inside, + tracks.outside=circosParams.my$tracks.outside); + + # set plot colours and parameters + circosParams <- RCircos.Get.Plot.Parameters(); + circosParams$point.type <- circosParams.my$point.type + circosParams$point.size <- circosParams.my$point.size + RCircos.Reset.Plot.Parameters(circosParams) + + par(mar=c(0.001, 0.001, 0.001, 0.001)) + par(mai=c(circosParams.my$genomeplot.margin, circosParams.my$genomeplot.margin, circosParams.my$genomeplot.margin, circosParams.my$genomeplot.margin)) + plot.new() + plot.window(c(-circosParams.my$plot.radius,circosParams.my$plot.radius), c(-circosParams.my$plot.radius, circosParams.my$plot.radius)) + RCircos.Chromosome.Ideogram.Plot.my(circosParams.my$chr.text.color, circosParams.my$grid.line.color, circosParams.my$text.size); + + title(main = sampleID) + + if (!is.null(plot_title)) { + title(paste(plot_title, sep=''), line=-1); + } + + # substitutions + if (exists("subs") && (nrow(subs)>0)) { + RCircos.Scatter.Plot.color(scatter.data=subs, data.col=6, track.num=1, side="in", scatter.colors = subs$color); + } + + # Insertions + circosParams <- RCircos.Get.Plot.Parameters(); + circosParams$line.color <- 'white' + circosParams$highlight.width <- 0.2 + circosParams$max.layers <- 5 + circosParams$tile.color <- 'darkgreen' + RCircos.Reset.Plot.Parameters(circosParams) + if (exists("ins") && nrow(ins)>0) { + my.RCircos.Tile.Plot(tile.data=ins, track.num=5, side="in"); + } + + # Deletions + circosParams <- RCircos.Get.Plot.Parameters(); + circosParams$tile.color <- 'firebrick4' + RCircos.Reset.Plot.Parameters(circosParams) + if (exists("dels") && nrow(dels)>0) { + my.RCircos.Tile.Plot(tile.data=dels, track.num=6, side="in", tile.colors=dels$color); + } + + + # Copy number + if (exists('cv.data') && (nrow(cv.data)>0)) { + heatmap.ranges.major <-circosParams.my$heatmap.ranges.gain + heatmap.color.major <-circosParams.my$heatmap.color.gain + RCircos.Heatmap.Plot.my(heatmap.data=cv.data, data.col=5, track.num=7, side="in", heatmap.ranges=heatmap.ranges.major , heatmap.color=heatmap.color.major ); # major copy number + + heatmap.ranges.minor <-circosParams.my$heatmap.ranges.loh + heatmap.color.minor <-circosParams.my$heatmap.color.loh + RCircos.Heatmap.Plot.my(heatmap.data=cv.data, data.col=6, track.num=8, side="in", heatmap.ranges=heatmap.ranges.minor , heatmap.color=heatmap.color.minor ); # minor copy number + + } + + # Rearrangement + # Chromosome chromStart chromEnd Chromosome.1 chromStart.1 chromEnd.1 type + link.colors <- vector() + if (exists("rearrs")) { + link.data <- rearrs + link.colors[link.data$type=='INVERSION'] <- inv.col + link.colors[link.data$type=='DELETION'] <- del.col + link.colors[link.data$type=='TANDEM_DUPLICATION' | link.data$type=='DUPLICATION'] <- dupl.col + link.colors[link.data$type=='TRANSLOCATION'] <- transloc.colour + if (nrow( rearrs)>0) { + RCircos.Link.Plot.my(link.data = rearrs, track.num=9, by.chromosome=TRUE, link.colors); + } + } + + invisible(dev.off()) + +} + +#################################################################################### + +# Getting command arguments +option_list <- list( + make_option(c("--genome_version"), type="character", default="hg19", + help="Genome version", metavar="character"), + make_option(c("--plot_title"), type="character", default="", + help="Plot title", metavar="character"), + make_option(c("--no_snvs"), action="store_true", default=FALSE, + help="No SNVs"), + make_option(c("--no_copynumber"), action="store_true", default=FALSE, + help="No CNV"), + make_option(c("--no_rearrangements"), action="store_true", default=FALSE, + help="No rearrangements"), + make_option(c("--no_indels"), action="store_true", default=FALSE, + help="No indels"), + make_option(c("--out_format"), type="character", default="png", + help="Output format", metavar="character"), + make_option(c("--out_path"), type="character", default=".", + help="Output file path", metavar="character") +) +parser <- OptionParser(usage = "%prog [options] snvs_file indels_file cnvs_file rearrs_file sampleId", option_list=option_list) +arguments <- parse_args(parser, positional_arguments = 5) +opt <- arguments$options +args <- arguments$args + + +genomePlot(args[1], args[2], args[3], args[4], args[5], genome.v=opt$genome_version, plot_title = opt$plot_title, + no_snvs = opt$no_snvs, no_copynumber = opt$no_copynumber, no_rearrangements = opt$no_rearrangements, no_indels = opt$no_indels, + out_format = opt$out_format, out_path = opt$out_path) From 333e6dcc13a1b9442c6c748f2056d0f555d06888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Tue, 18 Jun 2024 10:40:09 +0200 Subject: [PATCH 03/21] analysis: add FastQC docker cli in the result.json file, and remove unused parameters, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysisExecutor.java --- .../fastqc/FastqcWrapperAnalysis.java | 9 ++++++--- .../fastqc/FastqcWrapperAnalysisExecutor.java | 20 +++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysis.java index b829bc0d781..37962887253 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysis.java @@ -32,16 +32,19 @@ @Tool(id = FastqcWrapperAnalysis.ID, resource = Enums.Resource.ALIGNMENT, description = FastqcWrapperAnalysis.DESCRIPTION) public class FastqcWrapperAnalysis extends OpenCgaToolScopeStudy { - public final static String ID = "fastqc"; - public final static String DESCRIPTION = "A high throughput sequence QC analysis tool"; + public static final String ID = "fastqc"; + public static final String DESCRIPTION = "A high throughput sequence QC analysis tool"; - public final static Set FILE_PARAM_NAMES = new HashSet<>(Arrays.asList("l", "limits", "a", "adapters", "c", "contaminants")); + protected static final Set FILE_PARAM_NAMES = new HashSet<>(Arrays.asList("l", "limits", "a", "adapters", "c", "contaminants")); + + public static final String FASTQC_DOCKER_CLI_KEY = "FASTQC_DOCKER_CLI"; @ToolParams protected final FastqcWrapperParams analysisParams = new FastqcWrapperParams(); private String inputFilePath = null; + @Override protected void check() throws Exception { super.check(); diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysisExecutor.java index e13d9468d13..7d5b10ba2cd 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/fastqc/FastqcWrapperAnalysisExecutor.java @@ -3,27 +3,31 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.opencb.opencga.analysis.wrappers.executors.DockerWrapperAnalysisExecutor; +import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.tools.annotations.ToolExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import static org.opencb.opencga.analysis.wrappers.fastqc.FastqcWrapperAnalysis.FASTQC_DOCKER_CLI_KEY; + @ToolExecutor(id = FastqcWrapperAnalysisExecutor.ID, tool = FastqcWrapperAnalysis.ID, source = ToolExecutor.Source.STORAGE, framework = ToolExecutor.Framework.LOCAL) public class FastqcWrapperAnalysisExecutor extends DockerWrapperAnalysisExecutor { - public final static String ID = FastqcWrapperAnalysis.ID + "-local"; + public static final String ID = FastqcWrapperAnalysis.ID + "-local"; - private String study; private String inputFile; private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override protected void run() throws Exception { + addStepParams(); + StringBuilder sb = initCommandLine(); // Append mounts @@ -46,17 +50,13 @@ protected void run() throws Exception { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(FASTQC_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } - public String getStudy() { - return study; - } - - public FastqcWrapperAnalysisExecutor setStudy(String study) { - this.study = study; - return this; + private void addStepParams() throws ToolException { + addAttribute("INPUT_FILE", inputFile); } public String getInputFile() { From 177f1a2ead3ee2a19860e51068e1d25fdb4c4630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Tue, 18 Jun 2024 10:43:38 +0200 Subject: [PATCH 04/21] analysis: add Samtools docker CLI in the result.json file, and remove unused parameters, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java --- .../samtools/SamtoolsWrapperAnalysis.java | 2 + .../SamtoolsWrapperAnalysisExecutor.java | 43 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysis.java index 25dabac64b9..0f1e4efa373 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysis.java @@ -48,6 +48,8 @@ public class SamtoolsWrapperAnalysis extends OpenCgaToolScopeStudy { public final static String DESCRIPTION = "Samtools is a program for interacting with high-throughput sequencing data in SAM, BAM" + " and CRAM formats. " + SAMTOOLS_COMMAND_DESCRIPTION; + public final static String SAMTOOlS_DOCKER_CLI_KEY = "SAMTOOLS_DOCKER_CLI"; + @ToolParams protected final SamtoolsWrapperParams analysisParams = new SamtoolsWrapperParams(); diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java index 7981fcf9eb4..fbc984fea19 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/samtools/SamtoolsWrapperAnalysisExecutor.java @@ -4,7 +4,6 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.opencb.opencga.analysis.wrappers.executors.DockerWrapperAnalysisExecutor; -import org.opencb.opencga.core.common.GitRepositoryState; import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.tools.annotations.ToolExecutor; import org.slf4j.Logger; @@ -12,6 +11,8 @@ import java.util.*; +import static org.opencb.opencga.analysis.wrappers.samtools.SamtoolsWrapperAnalysis.SAMTOOlS_DOCKER_CLI_KEY; + @ToolExecutor(id = SamtoolsWrapperAnalysisExecutor.ID, tool = SamtoolsWrapperAnalysis.ID, source = ToolExecutor.Source.STORAGE, @@ -20,7 +21,6 @@ public class SamtoolsWrapperAnalysisExecutor extends DockerWrapperAnalysisExecut public final static String ID = SamtoolsWrapperAnalysis.ID + "-local"; - private String study; private String command; private String inputFile; @@ -28,6 +28,8 @@ public class SamtoolsWrapperAnalysisExecutor extends DockerWrapperAnalysisExecut @Override public void run() throws ToolException { + addStepParams(); + switch (command) { case "depth": runDepth(); @@ -82,7 +84,8 @@ private void runDepth() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -120,7 +123,8 @@ private void runDict() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -155,7 +159,8 @@ private void runView() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -179,7 +184,8 @@ private void runIndex() throws ToolException { appendOtherParams(null, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -215,7 +221,8 @@ private void runSort() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -238,7 +245,8 @@ private void runFaidx() throws ToolException { appendOtherParams(null, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -261,7 +269,8 @@ private void runStats() throws ToolException { appendOtherParams(null, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -290,7 +299,8 @@ private void runPlotBamStats() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb.toString()); + logger.info("Docker command line: " + sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } @@ -315,17 +325,14 @@ private void runFlagstat() throws ToolException { appendOtherParams(skipParams, sb); // Execute command and redirect stdout and stderr to the files - logger.info("Docker command line: " + sb); + logger.info("Docker command line: {}", sb); + addAttribute(SAMTOOlS_DOCKER_CLI_KEY, sb); runCommandLine(sb.toString()); } - public String getStudy() { - return study; - } - - public SamtoolsWrapperAnalysisExecutor setStudy(String study) { - this.study = study; - return this; + private void addStepParams() throws ToolException { + addAttribute("COMMAND", command); + addAttribute("INPUT_FILE", inputFile); } public String getCommand() { From 512c17d8b4b82d7325dee299e68354bde1622f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Tue, 18 Jun 2024 10:50:11 +0200 Subject: [PATCH 05/21] analysis: use ToolRunner to execute each alignment QC step (i.e.: samtools stats/flagstats, fastqc), #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java --- .../alignment/qc/AlignmentQcAnalysis.java | 219 ++++++++++++------ 1 file changed, 144 insertions(+), 75 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index 941c999bdbe..d374cf6bc5c 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -16,40 +16,42 @@ package org.opencb.opencga.analysis.alignment.qc; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.opencb.biodata.formats.alignment.samtools.SamtoolsFlagstats; import org.opencb.biodata.formats.alignment.samtools.SamtoolsStats; import org.opencb.biodata.formats.sequence.fastqc.FastQcMetrics; -import org.opencb.commons.datastore.core.ObjectMap; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.opencga.analysis.AnalysisUtils; import org.opencb.opencga.analysis.tools.OpenCgaToolScopeStudy; +import org.opencb.opencga.analysis.tools.ToolRunner; import org.opencb.opencga.analysis.wrappers.executors.DockerWrapperAnalysisExecutor; -import org.opencb.opencga.analysis.wrappers.fastqc.FastqcWrapperAnalysisExecutor; +import org.opencb.opencga.analysis.wrappers.fastqc.FastqcWrapperAnalysis; import org.opencb.opencga.analysis.wrappers.samtools.SamtoolsWrapperAnalysis; -import org.opencb.opencga.analysis.wrappers.samtools.SamtoolsWrapperAnalysisExecutor; import org.opencb.opencga.catalog.exceptions.CatalogException; import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.models.alignment.AlignmentQcParams; +import org.opencb.opencga.core.models.alignment.FastqcWrapperParams; +import org.opencb.opencga.core.models.alignment.SamtoolsWrapperParams; import org.opencb.opencga.core.models.common.Enums; import org.opencb.opencga.core.models.file.File; +import org.opencb.opencga.core.models.file.FileLinkParams; import org.opencb.opencga.core.models.file.FileQualityControl; import org.opencb.opencga.core.models.file.FileUpdateParams; import org.opencb.opencga.core.tools.annotations.Tool; import org.opencb.opencga.core.tools.annotations.ToolParams; -import org.opencb.opencga.core.tools.result.ExecutionResultManager; +import org.opencb.opencga.core.tools.result.ExecutionResult; +import org.opencb.opencga.core.tools.result.Status; +import org.opencb.opencga.storage.core.StorageEngineFactory; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import static org.apache.commons.io.FileUtils.readLines; import static org.opencb.opencga.core.api.ParamConstants.ALIGNMENT_QC_DESCRIPTION; -import static org.opencb.opencga.core.tools.OpenCgaToolExecutor.EXECUTOR_ID; @Tool(id = AlignmentQcAnalysis.ID, resource = Enums.Resource.ALIGNMENT) public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { @@ -63,9 +65,16 @@ public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { public static final String FASTQC_METRICS_STEP = "fastqc-metrics"; @ToolParams - protected final AlignmentQcParams analysisParams = new AlignmentQcParams(); + protected final AlignmentQcParams alignmentQcParams = new AlignmentQcParams(); + + private ToolRunner toolRunner; + + private boolean runSamtoolsStatsStep = true; + private boolean runSamptoolsFlagstatsStep = true; + private boolean runFastqcMetricsStep = true; private File catalogBamFile; + private File catalogStatsFile; private FileQualityControl fileQc = null; @Override @@ -77,19 +86,60 @@ protected void check() throws Exception { } try { - catalogBamFile = AnalysisUtils.getCatalogFile(analysisParams.getBamFile(), study, catalogManager.getFileManager(), token); + catalogBamFile = AnalysisUtils.getCatalogFile(alignmentQcParams.getBamFile(), study, catalogManager.getFileManager(), token); } catch (CatalogException e) { - throw new ToolException("Error accessing to the BAM file '" + analysisParams.getBamFile() + "'", e); + throw new ToolException("Error accessing to the BAM file '" + alignmentQcParams.getBamFile() + "'", e); + } + + // Prepare skip flags + String skip = null; + if (StringUtils.isNotEmpty(alignmentQcParams.getSkip())) { + skip = alignmentQcParams.getSkip().toLowerCase().replace(" ", ""); + } + if (StringUtils.isNotEmpty(skip)) { + Set skipValues = new HashSet<>(Arrays.asList(skip.split(","))); + if (skipValues.contains(AlignmentQcParams.STATS_SKIP_VALUE)) { + runSamtoolsStatsStep = false; + String msg = "Skipping Samtools stats (and plot) by user"; + addWarning(msg); + logger.warn(msg); + } + if (skipValues.contains(AlignmentQcParams.FLAGSTATS_SKIP_VALUE)) { + runSamptoolsFlagstatsStep = false; + String msg = "Skipping Samtools flagstats by user"; + addWarning(msg); + logger.warn(msg); + } + if (skipValues.contains(AlignmentQcParams.FASTQC_METRICS_SKIP_VALUE)) { + runFastqcMetricsStep = false; + String msg = "Skipping FastQC metrics by user"; + addWarning(msg); + logger.warn(msg); + } } } @Override protected List getSteps() { - return Arrays.asList(SAMTOOLS_STATS_STEP, SAMTOOLS_FLAGSTATS_STEP, PLOT_BAMSTATS_STEP, FASTQC_METRICS_STEP); + List steps = new ArrayList<>(); + if (runSamtoolsStatsStep) { + steps.add(SAMTOOLS_STATS_STEP); + steps.add(PLOT_BAMSTATS_STEP); + } + if (runSamptoolsFlagstatsStep) { + steps.add(SAMTOOLS_FLAGSTATS_STEP); + } + if (runFastqcMetricsStep) { + steps.add(FASTQC_METRICS_STEP); + } + return steps; } @Override protected void run() throws ToolException { + // Create the tool runner + toolRunner = new ToolRunner(opencgaHome, catalogManager, StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); + // Get alignment QC metrics to update if (catalogBamFile.getQualityControl() != null) { fileQc = catalogBamFile.getQualityControl(); @@ -98,10 +148,16 @@ protected void run() throws ToolException { fileQc = new FileQualityControl(); } - step(SAMTOOLS_FLAGSTATS_STEP, this::runSamtoolsFlagStats); - step(SAMTOOLS_STATS_STEP, this::runSamtoolsStats); - step(PLOT_BAMSTATS_STEP, this::runPlotBamStats); - step(FASTQC_METRICS_STEP, this::runFastQcMetrics); + if (runSamtoolsStatsStep) { + step(SAMTOOLS_STATS_STEP, this::runSamtoolsStats); + step(PLOT_BAMSTATS_STEP, this::runPlotBamstats); + } + if (runSamptoolsFlagstatsStep) { + step(SAMTOOLS_FLAGSTATS_STEP, this::runSamtoolsFlagstats); + } + if (runFastqcMetricsStep) { + step(FASTQC_METRICS_STEP, this::runFastqcMetrics); + } // Finally, update file quality control try { @@ -116,7 +172,7 @@ protected void run() throws ToolException { getErm().setExecutorInfo(null); } - private void runSamtoolsFlagStats() throws ToolException { + private void runSamtoolsFlagstats() throws ToolException { Path outPath = getOutDir().resolve(SAMTOOLS_FLAGSTATS_STEP); try { FileUtils.forceMkdir(outPath.toFile()); @@ -124,18 +180,20 @@ private void runSamtoolsFlagStats() throws ToolException { throw new ToolException("Error creating SAMtools flagstat output folder: " + outPath, e); } - SamtoolsWrapperAnalysisExecutor executor = getToolExecutor(SamtoolsWrapperAnalysisExecutor.class, - SamtoolsWrapperAnalysisExecutor.ID) - .setCommand("flagstat") - .setInputFile(catalogBamFile.getUri().getPath()); + // Prepare parameters + SamtoolsWrapperParams samtoolsWrapperParams = new SamtoolsWrapperParams("flagstat", catalogBamFile.getId(), null, + new HashMap<>()); - ExecutionResultManager erm = new ExecutionResultManager(SamtoolsWrapperAnalysisExecutor.ID, outPath); - ObjectMap params = new ObjectMap(); - executor.setUp(erm, params, outPath); + // Execute the Samtools flag stats analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(SamtoolsWrapperAnalysis.class, study, samtoolsWrapperParams, outPath, + null, token); + addStepAttributes(executionResult); - // Execute - executor.execute(); - getErm().addStepAttribute("CLI", executor.getCommandLine()); + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the Samtools flagstat analysis. Execution status = " + + executionResult.getStatus().getName()); + } // Check results and update QC file Path flagStatsFile = AlignmentFlagStatsAnalysis.getResultPath(outPath.toAbsolutePath().toString(), catalogBamFile.getName()); @@ -144,16 +202,16 @@ private void runSamtoolsFlagStats() throws ToolException { try { lines = readLines(stdoutFile, Charset.defaultCharset()); } catch (IOException e) { - throw new ToolException("Error reading running samtools-flagstat results.", e); + throw new ToolException("Error reading running Samtools flagstat results.", e); } - if (lines.size() > 0 && lines.get(0).contains("QC-passed")) { + if (CollectionUtils.isNotEmpty(lines) && lines.get(0).contains("QC-passed")) { try { FileUtils.copyFile(stdoutFile, flagStatsFile.toFile()); } catch (IOException e) { - throw new ToolException("Error copying samtools-flagstat results.", e); + throw new ToolException("Error copying Samtools flagstat results.", e); } } else { - throw new ToolException("Something wrong happened running samtools-flagstat."); + throw new ToolException("Something wrong happened running Samtools flagstat analysis."); } // Check results and update QC file @@ -169,24 +227,25 @@ private void runSamtoolsStats() throws ToolException { throw new ToolException("Error creating SAMtools stats output folder: " + outPath, e); } - SamtoolsWrapperAnalysisExecutor executor = getToolExecutor(SamtoolsWrapperAnalysisExecutor.class, - SamtoolsWrapperAnalysisExecutor.ID) - .setCommand("stats") - .setInputFile(catalogBamFile.getUri().getPath()); - - ExecutionResultManager erm = new ExecutionResultManager(SamtoolsWrapperAnalysisExecutor.ID, outPath); - ObjectMap params = new ObjectMap(); - params.put(EXECUTOR_ID, SamtoolsWrapperAnalysisExecutor.ID); + // Prepare parameters + Map statsParams = new HashMap<>(); // Filter flag: // - not primary alignment (0x100) // - read fails platform/vendor quality checks (0x200) // - supplementary alignment (0x800) - params.put("F", "0xB00"); - executor.setUp(erm, params, outPath); - - // Execute - executor.execute(); - getErm().addStepAttribute("CLI", executor.getCommandLine()); + statsParams.put("F", "0xB00"); + SamtoolsWrapperParams samtoolsWrapperParams = new SamtoolsWrapperParams("stats", catalogBamFile.getId(), null, statsParams); + + // Execute the Samtools stats analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(SamtoolsWrapperAnalysis.class, study, samtoolsWrapperParams, outPath, + null, token); + addStepAttributes(executionResult); + + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the Samtools stats analysis. Execution status = " + + executionResult.getStatus().getName()); + } // Check results Path statsFile = AlignmentStatsAnalysis.getResultPath(outPath.toAbsolutePath().toString(), catalogBamFile.getName()); @@ -197,14 +256,14 @@ private void runSamtoolsStats() throws ToolException { } catch (IOException e) { throw new ToolException("Error reading running samtools-stats results.", e); } - if (lines.size() > 0 && lines.get(0).startsWith("# This file was produced by samtools stats")) { + if (CollectionUtils.isNotEmpty(lines) && lines.get(0).startsWith("# This file was produced by samtools stats")) { try { FileUtils.copyFile(stdoutFile, statsFile.toFile()); } catch (IOException e) { - throw new ToolException("Error copying samtools-stats results.", e); + throw new ToolException("Error copying Samtools stats results.", e); } } else { - throw new ToolException("Something wrong happened running samtools-stats."); + throw new ToolException("Something wrong happened running Samtools stats analysis."); } // Check results and update QC file @@ -212,12 +271,21 @@ private void runSamtoolsStats() throws ToolException { try { samtoolsStats = SamtoolsWrapperAnalysis.parseSamtoolsStats(statsFile.toFile()); } catch (IOException e) { - throw new ToolException("Error parsing samtools-stats results."); + throw new ToolException("Error parsing Samtools stats results."); } + + // Link the stats file to the OpenCGA catalog to be used by the plot-batmstats later + try { + catalogStatsFile = catalogManager.getFileManager().link(study, new FileLinkParams(statsFile.toUri().toString(), "", "", "", + null, null, null, null, null), false, token).first(); + } catch (CatalogException e) { + throw new ToolException("Error linking the Samtools stats results to OpenCGA catalog"); + } + fileQc.getAlignment().setSamtoolsStats(samtoolsStats); } - private void runPlotBamStats() throws ToolException { + private void runPlotBamstats() throws ToolException { Path outPath = getOutDir().resolve(PLOT_BAMSTATS_STEP); try { FileUtils.forceMkdir(outPath.toFile()); @@ -225,23 +293,22 @@ private void runPlotBamStats() throws ToolException { throw new ToolException("Error creating plot-bamstats output folder: " + outPath, e); } - Path statsFile = AlignmentStatsAnalysis.getResultPath(getOutDir().resolve(SAMTOOLS_STATS_STEP).toString(), - catalogBamFile.getName()); - SamtoolsWrapperAnalysisExecutor executor = getToolExecutor(SamtoolsWrapperAnalysisExecutor.class, - SamtoolsWrapperAnalysisExecutor.ID) - .setCommand("plot-bamstats") - .setInputFile(statsFile.toString()); + // Prepare parameters + SamtoolsWrapperParams samtoolsWrapperParams = new SamtoolsWrapperParams("plot-bamstats", catalogStatsFile.getId(), null, + new HashMap<>()); - ExecutionResultManager erm = new ExecutionResultManager(SamtoolsWrapperAnalysisExecutor.ID, outPath); - ObjectMap params = new ObjectMap(); - params.put(EXECUTOR_ID, SamtoolsWrapperAnalysisExecutor.ID); - executor.setUp(erm, params, outPath); + // Execute the plot-bamstats analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(SamtoolsWrapperAnalysis.class, study, samtoolsWrapperParams, outPath, + null, token); + addStepAttributes(executionResult); - // Execute - executor.execute(); - getErm().addStepAttribute("CLI", executor.getCommandLine()); + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the plot-bamstats analysis. Execution status = " + + executionResult.getStatus().getName()); + } - // Check results and update QC file + // Add images from plot-bamstats to the QC alignment List images = new ArrayList<>(); for (java.io.File file : outPath.toFile().listFiles()) { if (file.getName().endsWith("png")) { @@ -255,7 +322,7 @@ private void runPlotBamStats() throws ToolException { fileQc.getAlignment().getSamtoolsStats().setFiles(images); } - private void runFastQcMetrics() throws ToolException { + private void runFastqcMetrics() throws ToolException { Path outPath = getOutDir().resolve(FASTQC_METRICS_STEP); try { FileUtils.forceMkdir(outPath.toFile()); @@ -263,18 +330,20 @@ private void runFastQcMetrics() throws ToolException { throw new ToolException("Error creating FastQC output folder: " + outPath, e); } - FastqcWrapperAnalysisExecutor executor = getToolExecutor(FastqcWrapperAnalysisExecutor.class, FastqcWrapperAnalysisExecutor.ID) - .setInputFile(catalogBamFile.getUri().getPath()); + // Prepare parameters + Map fastQcParams = new HashMap<>(); + fastQcParams.put("extract", "true"); + FastqcWrapperParams fastqcWrapperParams = new FastqcWrapperParams(catalogBamFile.getId(), null, fastQcParams); - ExecutionResultManager erm = new ExecutionResultManager(FastqcWrapperAnalysisExecutor.ID, outPath); - ObjectMap params = new ObjectMap(); - params.put(EXECUTOR_ID, FastqcWrapperAnalysisExecutor.ID); - params.put("extract", "true"); - executor.setUp(erm, params, outPath); + // Execute the FastQC analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(FastqcWrapperAnalysis.class, study, fastqcWrapperParams, outPath, null, token); + addStepAttributes(executionResult); - // Execute - executor.execute(); - getErm().addStepAttribute("CLI", executor.getCommandLine()); + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the FastQC analysis. Execution status = " + + executionResult.getStatus().getName()); + } // Check results and update QC file FastQcMetrics fastQcMetrics = AlignmentFastQcMetricsAnalysis.parseResults(outPath, configuration.getJobDir()); From 65a1bdd076bf0012139e162d9979518860860335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Tue, 18 Jun 2024 13:25:06 +0200 Subject: [PATCH 06/21] analysis: support the flag overwrite, and forward excetions, and minor sonnar issues, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java --- .../alignment/qc/AlignmentQcAnalysis.java | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index d374cf6bc5c..546fca85fe7 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -61,7 +61,7 @@ public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { public static final String SAMTOOLS_STATS_STEP = "samtools-stats"; public static final String SAMTOOLS_FLAGSTATS_STEP = "samtools-flagstats"; - private static final String PLOT_BAMSTATS_STEP = "plot-bamstats"; + public static final String PLOT_BAMSTATS_STEP = "plot-bamstats"; public static final String FASTQC_METRICS_STEP = "fastqc-metrics"; @ToolParams @@ -87,11 +87,12 @@ protected void check() throws Exception { try { catalogBamFile = AnalysisUtils.getCatalogFile(alignmentQcParams.getBamFile(), study, catalogManager.getFileManager(), token); + fileQc = catalogBamFile.getQualityControl(); } catch (CatalogException e) { throw new ToolException("Error accessing to the BAM file '" + alignmentQcParams.getBamFile() + "'", e); } - // Prepare skip flags + // Prepare flags from skip and overwrite String skip = null; if (StringUtils.isNotEmpty(alignmentQcParams.getSkip())) { skip = alignmentQcParams.getSkip().toLowerCase().replace(" ", ""); @@ -117,6 +118,26 @@ protected void check() throws Exception { logger.warn(msg); } } + if (!alignmentQcParams.isOverwrite() && fileQc != null && fileQc.getAlignment() != null) { + if (runSamtoolsStatsStep && fileQc.getAlignment().getSamtoolsStats() != null) { + runSamtoolsStatsStep = false; + String msg = "Skipping Samtools stats (and plots) because they already exist and the overwrite flag is not set"; + addWarning(msg); + logger.warn(msg); + } + if (runSamptoolsFlagstatsStep && fileQc.getAlignment().getSamtoolsFlagStats() != null) { + runSamptoolsFlagstatsStep = false; + String msg = "Skipping Samtools flag stats because they already exist and the overwrite flag is not set"; + addWarning(msg); + logger.warn(msg); + } + if (runFastqcMetricsStep && fileQc.getAlignment().getFastQcMetrics() != null) { + runFastqcMetricsStep = false; + String msg = "Skipping FastQC metrics because they already exist and the overwrite flag is not set"; + addWarning(msg); + logger.warn(msg); + } + } } @Override @@ -164,7 +185,7 @@ protected void run() throws ToolException { FileUpdateParams fileUpdateParams = new FileUpdateParams().setQualityControl(fileQc); catalogManager.getFileManager().update(study, catalogBamFile.getId(), fileUpdateParams, QueryOptions.empty(), token); } catch (CatalogException e) { - throw new ToolException(e); + throw new ToolException("Error updating alignment quality control", e); } // Unset the executor info since it is executed by different executors, it will be indicated in the @@ -202,16 +223,16 @@ private void runSamtoolsFlagstats() throws ToolException { try { lines = readLines(stdoutFile, Charset.defaultCharset()); } catch (IOException e) { - throw new ToolException("Error reading running Samtools flagstat results.", e); + throw new ToolException("Error reading running Samtools flagstat results", e); } if (CollectionUtils.isNotEmpty(lines) && lines.get(0).contains("QC-passed")) { try { FileUtils.copyFile(stdoutFile, flagStatsFile.toFile()); } catch (IOException e) { - throw new ToolException("Error copying Samtools flagstat results.", e); + throw new ToolException("Error copying Samtools flagstat results", e); } } else { - throw new ToolException("Something wrong happened running Samtools flagstat analysis."); + throw new ToolException("Something wrong happened running Samtools flagstat analysis"); } // Check results and update QC file @@ -254,16 +275,16 @@ private void runSamtoolsStats() throws ToolException { try { lines = readLines(stdoutFile, Charset.defaultCharset()); } catch (IOException e) { - throw new ToolException("Error reading running samtools-stats results.", e); + throw new ToolException("Error reading running samtools-stats results", e); } if (CollectionUtils.isNotEmpty(lines) && lines.get(0).startsWith("# This file was produced by samtools stats")) { try { FileUtils.copyFile(stdoutFile, statsFile.toFile()); } catch (IOException e) { - throw new ToolException("Error copying Samtools stats results.", e); + throw new ToolException("Error copying Samtools stats results", e); } } else { - throw new ToolException("Something wrong happened running Samtools stats analysis."); + throw new ToolException("Something wrong happened running Samtools stats analysis"); } // Check results and update QC file @@ -271,15 +292,22 @@ private void runSamtoolsStats() throws ToolException { try { samtoolsStats = SamtoolsWrapperAnalysis.parseSamtoolsStats(statsFile.toFile()); } catch (IOException e) { - throw new ToolException("Error parsing Samtools stats results."); + throw new ToolException("Error parsing Samtools stats results", e); } // Link the stats file to the OpenCGA catalog to be used by the plot-batmstats later try { - catalogStatsFile = catalogManager.getFileManager().link(study, new FileLinkParams(statsFile.toUri().toString(), "", "", "", - null, null, null, null, null), false, token).first(); + String path; + if (outPath.startsWith(configuration.getJobDir())) { + path = outPath.toString().substring(configuration.getJobDir().length() + 1); + } else { + path = outPath.toString(); + logger.warn("Using path {} to link {} to OpenCGA catalog", outPath, catalogStatsFile.getName()); + } + catalogStatsFile = catalogManager.getFileManager().link(study, new FileLinkParams(statsFile.toUri().toString(), path, "", "", + null, null, null, null, null), true, token).first(); } catch (CatalogException e) { - throw new ToolException("Error linking the Samtools stats results to OpenCGA catalog"); + throw new ToolException("Error linking the Samtools stats results to OpenCGA catalog", e); } fileQc.getAlignment().setSamtoolsStats(samtoolsStats); From 55c81ffd18281566c046bcd347df4c862a28c669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Tue, 18 Jun 2024 13:26:39 +0200 Subject: [PATCH 07/21] test: add JUnit tests for alignment QC, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java --- .../alignment/AlignmentAnalysisTest.java | 367 +++++++++++------- 1 file changed, 216 insertions(+), 151 deletions(-) diff --git a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java index 1360558bc0c..4f59dbef01a 100644 --- a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java +++ b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java @@ -17,6 +17,9 @@ package org.opencb.opencga.analysis.alignment; import htsjdk.samtools.util.BufferedLineReader; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.hdfs.server.diskbalancer.command.ExecuteCommand; +import org.apache.tools.ant.taskdefs.Exec; import org.junit.AfterClass; import org.junit.Before; import org.junit.ClassRule; @@ -24,13 +27,18 @@ import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.opencb.biodata.formats.alignment.samtools.SamtoolsFlagstats; +import org.opencb.biodata.formats.alignment.samtools.SamtoolsStats; +import org.opencb.biodata.formats.sequence.fastqc.FastQcMetrics; import org.opencb.biodata.models.clinical.Phenotype; import org.opencb.commons.datastore.core.ObjectMap; +import org.opencb.commons.datastore.core.Query; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.commons.utils.FileUtils; import org.opencb.opencga.TestParamConstants; import org.opencb.opencga.analysis.alignment.qc.AlignmentGeneCoverageStatsAnalysis; import org.opencb.opencga.analysis.alignment.qc.AlignmentQcAnalysis; +import org.opencb.opencga.analysis.sample.qc.SampleQcAnalysis; import org.opencb.opencga.analysis.tools.ToolRunner; import org.opencb.opencga.analysis.variant.OpenCGATestExternalResource; import org.opencb.opencga.analysis.variant.manager.VariantStorageManager; @@ -39,14 +47,19 @@ import org.opencb.opencga.core.api.ParamConstants; import org.opencb.opencga.core.config.storage.StorageConfiguration; import org.opencb.opencga.core.exceptions.ToolException; +import org.opencb.opencga.core.models.alignment.AlignmentFileQualityControl; import org.opencb.opencga.core.models.alignment.AlignmentGeneCoverageStatsParams; import org.opencb.opencga.core.models.alignment.AlignmentQcParams; import org.opencb.opencga.core.models.file.File; import org.opencb.opencga.core.models.file.FileLinkParams; +import org.opencb.opencga.core.models.file.FileQualityControl; +import org.opencb.opencga.core.models.file.FileUpdateParams; import org.opencb.opencga.core.models.organizations.OrganizationCreateParams; import org.opencb.opencga.core.models.organizations.OrganizationUpdateParams; import org.opencb.opencga.core.models.user.User; import org.opencb.opencga.core.testclassification.duration.MediumTests; +import org.opencb.opencga.core.tools.result.ExecutionResult; +import org.opencb.opencga.core.tools.result.ToolStep; import org.opencb.opencga.storage.core.StorageEngineFactory; import org.opencb.opencga.storage.core.variant.VariantStorageEngine; import org.opencb.opencga.storage.hadoop.variant.HadoopVariantStorageEngine; @@ -62,8 +75,10 @@ import java.nio.file.spi.FileSystemProvider; import java.util.Arrays; import java.util.Map; +import java.util.stream.Collectors; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +import static org.opencb.opencga.core.models.alignment.AlignmentQcParams.*; import static org.opencb.opencga.core.tools.OpenCgaToolExecutor.EXECUTOR_ID; @RunWith(Parameterized.class) @@ -89,6 +104,8 @@ public class AlignmentAnalysisTest { private static String cancer_sample = "AR2.10039966-01T"; private static String germline_sample = "AR2.10039966-01G"; + private String bamFilename = "HG00096.chrom20.small.bam"; + private String baiFilename = "HG00096.chrom20.small.bam.bai"; @Parameterized.Parameters(name = "{0}") public static Object[][] parameters() { @@ -148,75 +165,10 @@ public void setUp() throws Throwable { setUpCatalogManager(); -// file = opencga.createFile(STUDY, "variant-test-file.vcf.gz", token); -// variantStorageManager.index(STUDY, file.getId(), opencga.createTmpOutdir("_index"), new ObjectMap(VariantStorageOptions.ANNOTATE.key(), true), token); - -// for (int i = 0; i < file.getSampleIds().size(); i++) { -// String id = file.getSampleIds().get(i); -// if (id.equals(son)) { -// SampleUpdateParams updateParams = new SampleUpdateParams().setSomatic(true); -// catalogManager.getSampleManager().update(STUDY, id, updateParams, null, token); -// } -// if (i % 2 == 0) { -// SampleUpdateParams updateParams = new SampleUpdateParams().setPhenotypes(Collections.singletonList(PHENOTYPE)); -// catalogManager.getSampleManager().update(STUDY, id, updateParams, null, token); -// } -// } - -// catalogManager.getCohortManager().create(STUDY, new CohortCreateParams().setId("c1") -// .setSamples(file.getSampleIds().subList(0, 2).stream().map(s -> new SampleReferenceParam().setId(s)).collect(Collectors.toList())), -// null, null, null, token); -// catalogManager.getCohortManager().create(STUDY, new CohortCreateParams().setId("c2") -// .setSamples(file.getSampleIds().subList(2, 4).stream().map(s -> new SampleReferenceParam().setId(s)).collect(Collectors.toList())), -// null, null, null, token); - -// Phenotype phenotype = new Phenotype("phenotype", "phenotype", ""); -// Disorder disorder = new Disorder("disorder", "disorder", "", "", Collections.singletonList(phenotype), Collections.emptyMap()); -// List individuals = new ArrayList<>(4); -// -// // Father -// individuals.add(catalogManager.getIndividualManager() -// .create(STUDY, new Individual(father, father, new Individual(), new Individual(), new Location(), SexOntologyTermAnnotation.initMale(), null, null, null, null, "", -// Collections.emptyList(), false, 0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), IndividualInternal.init(), Collections.emptyMap()), Collections.singletonList(father), new QueryOptions(ParamConstants.INCLUDE_RESULT_PARAM, true), token).first()); -// // Mother -// individuals.add(catalogManager.getIndividualManager() -// .create(STUDY, new Individual(mother, mother, new Individual(), new Individual(), new Location(), SexOntologyTermAnnotation.initFemale(), null, null, null, null, "", -// Collections.emptyList(), false, 0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), IndividualInternal.init(), Collections.emptyMap()), Collections.singletonList(mother), new QueryOptions(ParamConstants.INCLUDE_RESULT_PARAM, true), token).first()); -// // Son -// individuals.add(catalogManager.getIndividualManager() -// .create(STUDY, new Individual(son, son, new Individual(), new Individual(), new Location(), SexOntologyTermAnnotation.initMale(), null, null, null, null, "", -// Collections.emptyList(), false, 0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), IndividualInternal.init(), Collections.emptyMap()).setFather(individuals.get(0)).setMother(individuals.get(1)).setDisorders(Collections.singletonList(disorder)), Collections.singletonList(son), new QueryOptions(ParamConstants.INCLUDE_RESULT_PARAM, true), token).first()); -// // Daughter -// individuals.add(catalogManager.getIndividualManager() -// .create(STUDY, new Individual(daughter, daughter, new Individual(), new Individual(), new Location(), SexOntologyTermAnnotation.initFemale(), null, null, null, null, "", -// Collections.emptyList(), false, 0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), IndividualInternal.init(), Collections.emptyMap()).setFather(individuals.get(0)).setMother(individuals.get(1)), Collections.singletonList(daughter), new QueryOptions(ParamConstants.INCLUDE_RESULT_PARAM, true), token).first()); -// catalogManager.getFamilyManager().create( -// STUDY, -// new Family("f1", "f1", Collections.singletonList(phenotype), Collections.singletonList(disorder), null, null, 3, null, null), -// individuals.stream().map(Individual::getId).collect(Collectors.toList()), new QueryOptions(), -// token); -// -// // Cancer (SV) -// ObjectMap config = new ObjectMap(); -//// config.put(VariantStorageOptions.ANNOTATE.key(), true); -// config.put(VariantStorageOptions.LOAD_SPLIT_DATA.key(), VariantStorageEngine.SplitData.MULTI); -// -// file = opencga.createFile(CANCER_STUDY, "AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz", token); -// variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); -// file = opencga.createFile(CANCER_STUDY, "AR2.10039966-01T.copynumber.caveman.vcf.gz", token); -// variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); -// file = opencga.createFile(CANCER_STUDY, "AR2.10039966-01T_vs_AR2.10039966-01G.annot.pindel.vcf.gz", token); -// variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); -// -// SampleUpdateParams updateParams = new SampleUpdateParams().setSomatic(true); -// catalogManager.getSampleManager().update(CANCER_STUDY, cancer_sample, updateParams, null, token); - opencga.getStorageConfiguration().getVariant().setDefaultEngine(storageEngine); VariantStorageEngine engine = opencga.getStorageEngineFactory().getVariantStorageEngine(storageEngine, DB_NAME); -// if (storageEngine.equals(HadoopVariantStorageEngine.STORAGE_ENGINE_ID)) { -// VariantHbaseTestUtils.printVariants(((VariantHadoopDBAdaptor) engine.getDBAdaptor()), Paths.get(opencga.createTmpOutdir("_hbase_print_variants")).toUri()); -// } } + // Reset engines opencga.getStorageEngineFactory().close(); catalogManager = opencga.getCatalogManager(); @@ -233,7 +185,7 @@ public static void afterClass() { opencga.after(); } - public void setUpCatalogManager() throws CatalogException { + public void setUpCatalogManager() throws CatalogException, IOException { catalogManager.getOrganizationManager().create(new OrganizationCreateParams().setId("test"), null, opencga.getAdminToken()); catalogManager.getUserManager().create(new User().setId(USER).setName("User Name").setEmail("mail@ebi.ac.uk").setOrganization("test"), PASSWORD, opencga.getAdminToken()); @@ -245,28 +197,12 @@ public void setUpCatalogManager() throws CatalogException { null, "GRCh38", new QueryOptions(ParamConstants.INCLUDE_RESULT_PARAM, true), token).first().getId(); catalogManager.getStudyManager().create(projectId, STUDY, null, "Phase 1", "Done", null, null, null, null, null, token); - // Create 10 samples not indexed -// for (int i = 0; i < 10; i++) { -// Sample sample = new Sample().setId("SAMPLE_" + i); -// if (i % 2 == 0) { -// sample.setPhenotypes(Collections.singletonList(PHENOTYPE)); -// } -// catalogManager.getSampleManager().create(STUDY, sample, null, token); -// } -// -// // Cancer -// List samples = new ArrayList<>(); -// catalogManager.getStudyManager().create(projectId, CANCER_STUDY, null, "Phase 1", "Done", null, null, null, null, null, token); -// Sample sample = new Sample().setId(cancer_sample).setSomatic(true); -// samples.add(sample); -//// catalogManager.getSampleManager().create(CANCER_STUDY, sample, null, token); -// sample = new Sample().setId(germline_sample); -// samples.add(sample); -//// catalogManager.getSampleManager().create(CANCER_STUDY, sample, null, token); -// Individual individual = catalogManager.getIndividualManager() -// .create(CANCER_STUDY, new Individual("AR2.10039966-01", "AR2.10039966-01", new Individual(), new Individual(), new Location(), SexOntologyTermAnnotation.initMale(), null, null, null, null, "", -// samples, false, 0, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), IndividualInternal.init(), Collections.emptyMap()), Collections.emptyList(), new QueryOptions(ParamConstants.INCLUDE_RESULT_PARAM, true), token).first(); -// assertEquals(2, individual.getSamples().size()); + + // BAM and BAI files + catalogManager.getFileManager().link(STUDY, new FileLinkParams(opencga.getResourceUri("biofiles/" + bamFilename).toString(), + "", "", "", null, null, null, null, null), false, token).first(); + catalogManager.getFileManager().link(STUDY, new FileLinkParams(opencga.getResourceUri("biofiles/" + baiFilename).toString(), + "", "", "", null, null, null, null, null), false, token).first(); } @@ -275,11 +211,12 @@ public void geneCoverageStatsTest() throws IOException, ToolException, CatalogEx Path outdir = Paths.get(opencga.createTmpOutdir("_genecoveragestats")); // setup BAM files - String bamFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam").toString(); - String baiFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam.bai").toString(); +// String bamFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam").toString(); +// String baiFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam.bai").toString(); //String bamFilename = getClass().getResource("/biofiles/NA19600.chrom20.small.bam").getFile(); - File bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, - null, null), false, token).first(); +// File bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, +// null, null), false, token).first(); + File bamFile = getCatalogFile(bamFilename); assertEquals(0, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().size()); AlignmentGeneCoverageStatsParams params = new AlignmentGeneCoverageStatsParams(); @@ -289,8 +226,7 @@ public void geneCoverageStatsTest() throws IOException, ToolException, CatalogEx toolRunner.execute(AlignmentGeneCoverageStatsAnalysis.class, params, new ObjectMap(), outdir, null, token); - bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, - null, null), false, token).first(); + bamFile = getCatalogFile(bamFilename); assertEquals(1, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().size()); assertEquals(geneName, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().get(0).getGeneName()); assertEquals(10, bamFile.getQualityControl().getCoverage().getGeneCoverageStats().get(0).getStats().size()); @@ -298,77 +234,206 @@ public void geneCoverageStatsTest() throws IOException, ToolException, CatalogEx @Test public void testAlignmentQc() throws IOException, ToolException, CatalogException { - Path outdir = Paths.get(opencga.createTmpOutdir("_alignment_qc")); + Path outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc")); - // setup BAM files - String bamFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam").toString(); - //String baiFilename = opencga.getResourceUri("biofiles/HG00096.chrom20.small.bam.bai").toString(); - //String bamFilename = getClass().getResource("/biofiles/NA19600.chrom20.small.bam").getFile(); - File bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, - null, null), false, token).first(); + File bamFile = getCatalogFile(bamFilename); + resetAlignemntQc(bamFile); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(bamFile.getId()); + + ExecutionResult executionResult = toolRunner.execute(AlignmentQcAnalysis.class, params, new ObjectMap(), outDir, null, token); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.SAMTOOLS_FLAGSTATS_STEP)); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.SAMTOOLS_STATS_STEP)); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.PLOT_BAMSTATS_STEP)); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.FASTQC_METRICS_STEP)); + + // Check + bamFile = catalogManager.getFileManager().get(STUDY, bamFile.getId(), QueryOptions.empty(), token).first(); + checkSamtoolsFlagstats(bamFile.getQualityControl().getAlignment().getSamtoolsFlagStats()); + checkSamtoolsStats(bamFile.getQualityControl().getAlignment().getSamtoolsStats()); + checkFastQcMetrics(bamFile.getQualityControl().getAlignment().getFastQcMetrics()); + System.out.println("outdir = " + outDir); + } + @Test + public void testAlignmentQcSamtoolsFlagstat() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_samtools_flagstat")); + + File bamFile = getCatalogFile(bamFilename); + resetAlignemntQc(bamFile); System.out.println("bamFile.getQualityControl().getAlignment() = " + bamFile.getQualityControl().getAlignment()); AlignmentQcParams params = new AlignmentQcParams(); params.setBamFile(bamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(STATS_SKIP_VALUE, FASTQC_METRICS_SKIP_VALUE), ",")); + + ExecutionResult executionResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.SAMTOOLS_FLAGSTATS_STEP)); - toolRunner.execute(AlignmentQcAnalysis.class, params, new ObjectMap(), outdir, - "test-alignment-qc-job-id", token); + // Check + bamFile = catalogManager.getFileManager().get(STUDY, bamFile.getId(), QueryOptions.empty(), token).first(); + checkSamtoolsFlagstats(bamFile.getQualityControl().getAlignment().getSamtoolsFlagStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getFastQcMetrics()); + System.out.println("outdir = " + outDir); + } - bamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(bamFilename, "", "", "", null, null, null, - null, null), false, token).first(); + @Test + public void testAlignmentQcSamtoolsStatsPlots() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_samtools_stats_plots")); + File bamFile = getCatalogFile(bamFilename); + resetAlignemntQc(bamFile); System.out.println("bamFile.getQualityControl().getAlignment() = " + bamFile.getQualityControl().getAlignment()); - System.out.println("outdir = " + outdir); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(bamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(FLAGSTATS_SKIP_VALUE, FASTQC_METRICS_SKIP_VALUE), ",")); + + ExecutionResult executionResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.SAMTOOLS_STATS_STEP)); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.PLOT_BAMSTATS_STEP)); + + // Check + bamFile = catalogManager.getFileManager().get(STUDY, bamFile.getId(), QueryOptions.empty(), token).first(); + checkSamtoolsStats(bamFile.getQualityControl().getAlignment().getSamtoolsStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsFlagStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getFastQcMetrics()); + System.out.println("outdir = " + outDir); } + @Test + public void testAlignmentQcFastqc() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_fastqc")); + + File bamFile = getCatalogFile(bamFilename); + resetAlignemntQc(bamFile); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(bamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(STATS_SKIP_VALUE, FLAGSTATS_SKIP_VALUE), ",")); + + ExecutionResult executionResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertTrue(executionResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.FASTQC_METRICS_STEP)); + + // Check + bamFile = catalogManager.getFileManager().get(STUDY, bamFile.getId(), QueryOptions.empty(), token).first(); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsFlagStats()); + checkFastQcMetrics(bamFile.getQualityControl().getAlignment().getFastQcMetrics()); + System.out.println("outdir = " + outDir); + } @Test - public void testBlobFuse2() throws IOException { - displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/tata.txt")); - displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/toto.txt")); - displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/titi.txt")); - displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/jobdir")); - displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/jobdir/helloworld.txt")); - displayAttributes(Paths.get("/home/jtarraga/data/blobfuse2-test1/jobdir/kk.txt")); + public void testAlignmentQcFastqcAndOverwrite() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_fastqc_and_overwrite")); + + File bamFile = getCatalogFile(bamFilename); + resetAlignemntQc(bamFile); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(bamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(STATS_SKIP_VALUE, FLAGSTATS_SKIP_VALUE), ",")); + + ExecutionResult executeResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertTrue(executeResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.FASTQC_METRICS_STEP)); + + outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_fastqc_overwrite_and_overwrite_2")); + params.setOverwrite(true); + executeResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertTrue(executeResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.FASTQC_METRICS_STEP)); + + // Check + bamFile = catalogManager.getFileManager().get(STUDY, bamFile.getId(), QueryOptions.empty(), token).first(); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsFlagStats()); + checkFastQcMetrics(bamFile.getQualityControl().getAlignment().getFastQcMetrics()); + System.out.println("outdir = " + outDir); } - public void displayAttributes(Path path) throws IOException { - System.out.println(); - System.out.println(path); + @Test + public void testAlignmentQcFastqcAndDoNotOverwrite() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_fastqc_and_do_not_overwrite")); - if (Files.exists(path)) { - System.out.println("\t- Exists"); - } -// try (BufferedReader reader = new BufferedLineReader(path).newBufferedReader(path)) { -// System.out.println("\t- Exists by using FileUtils.newBufferedReader"); -// } -// try (BufferedWriter writer = FileUtils.newBufferedWriter(path)) { -// System.out.println("\t- Exists by using FileUtils.newBufferedWriter"); -// } - if (Files.isDirectory(path)) { - System.out.println("\t- Directory"); - } - if (Files.isRegularFile(path)) { - System.out.println("\t- Regular file"); - } - if (Files.isReadable(path)) { - System.out.println("\t- Readable"); - } - if (Files.isWritable(path)) { - System.out.println("\t- Writable"); - } - if (Files.isSymbolicLink(path)) { - System.out.println("\t- Symbolic link"); - } -// -// -// FileSystemProvider provider = FileSystems.getDefault().provider()getFileSystem(new URI(path.toAbsolutePath().toString())).provider(); -// BasicFileAttributes basicFileAttributes = provider.readAttributes(path, BasicFileAttributes.class); -// System.out.println("basicFileAttributes = " + basicFileAttributes); -// System.out.println("basicFileAttributes.isRegularFile() = " + basicFileAttributes.isRegularFile()); -// System.out.println("basicFileAttributes.isDirectory() = " + basicFileAttributes.isDirectory()); -// System.out.println("basicFileAttributes.isSymbolicLink() = " + basicFileAttributes.isSymbolicLink()); -// System.out.println("basicFileAttributes.creationTime() = " + basicFileAttributes.creationTime()); + File bamFile = getCatalogFile(bamFilename); + resetAlignemntQc(bamFile); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(bamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(STATS_SKIP_VALUE, FLAGSTATS_SKIP_VALUE), ",")); + + ExecutionResult executeResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertTrue(executeResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.FASTQC_METRICS_STEP)); + + // Check + bamFile = catalogManager.getFileManager().get(STUDY, bamFile.getId(), QueryOptions.empty(), token).first(); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsStats()); + assertEquals(null, bamFile.getQualityControl().getAlignment().getSamtoolsFlagStats()); + checkFastQcMetrics(bamFile.getQualityControl().getAlignment().getFastQcMetrics()); + + outDir = Paths.get(opencga.createTmpOutdir("_alignment_qc_fastqc_and_do_not_overwrite_2")); + + executeResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + assertFalse(executeResult.getSteps().stream().map(ToolStep::getId).collect(Collectors.toList()).contains(AlignmentQcAnalysis.FASTQC_METRICS_STEP)); + + System.out.println("outdir = " + outDir); + } + + private void checkSamtoolsStats(SamtoolsStats stats) { + System.out.println("stats = " + stats); + assertTrue(stats != null); + assertEquals(108, stats.getSequences()); + assertEquals(55, stats.getLastFragments()); + assertEquals(0, stats.getReadsDuplicated()); + assertEquals(0, stats.getReadsQcFailed()); + assertEquals(10800, stats.getTotalLength()); + assertEquals(10047, stats.getBasesMappedCigar()); + assertEquals(49, stats.getMismatches()); + assertEquals(31.0, stats.getAverageQuality(), 0.001f); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals3.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("coverage.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("insert-size.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("gc-content.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("acgt-cycles.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals2.png")).collect(Collectors.toList()).size()); + } + + private void checkSamtoolsFlagstats(SamtoolsFlagstats flagstats) { + System.out.println("flagstats = " + flagstats); + assertTrue(flagstats != null); + assertEquals(108, flagstats.getTotalReads()); + assertEquals(0, flagstats.getSecondaryAlignments()); + assertEquals(53, flagstats.getRead1()); + assertEquals(55, flagstats.getRead2()); + assertEquals(104, flagstats.getProperlyPaired()); + } + + private void checkFastQcMetrics(FastQcMetrics metrics) { + System.out.println("metrics = " + metrics); + assertTrue(metrics != null); + assertEquals("PASS", metrics.getSummary().getBasicStatistics()); + assertEquals("FAIL", metrics.getSummary().getPerSeqGcContent()); + assertEquals("WARN", metrics.getSummary().getOverrepresentedSeqs()); + assertEquals(7, metrics.getBasicStats().size()); + assertEquals("108", metrics.getBasicStats().get("Total Sequences")); + assertEquals("100", metrics.getBasicStats().get("Sequence length")); + assertEquals("46", metrics.getBasicStats().get("%GC")); + assertEquals(8, metrics.getFiles().size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("per_sequence_quality.png")).collect(Collectors.toList()).size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("duplication_levels.png")).collect(Collectors.toList()).size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("per_base_quality.png")).collect(Collectors.toList()).size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("adapter_content.png")).collect(Collectors.toList()).size()); + } + + private File getCatalogFile(String name) throws CatalogException { + return catalogManager.getFileManager().search(STUDY, new Query("name", name), QueryOptions.empty(), token).first(); + } + + private void resetAlignemntQc(File bamFile) throws CatalogException { + FileUpdateParams updateParams = new FileUpdateParams(); + updateParams.setQualityControl(new FileQualityControl()); + catalogManager.getFileManager().update(STUDY, new Query("id", bamFile.getId()), updateParams, QueryOptions.empty(), token); } } \ No newline at end of file From 0d78abdc17473dbfba1b706b12d863c20c1942d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 08:17:31 +0200 Subject: [PATCH 08/21] analysis: add docker CLI and parameters in the tool step, fix some typos and sonnar issues, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureLocalAnalysisExecutor.java modified: opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/MutationalSignatureAnalysisExecutor.java --- .../MutationalSignatureAnalysis.java | 31 ++++++++++--------- ...ationalSignatureLocalAnalysisExecutor.java | 10 ++++-- .../MutationalSignatureAnalysisExecutor.java | 20 ++++++++++++ 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureAnalysis.java index 56a95cfa92e..81d65b14e60 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureAnalysis.java @@ -64,6 +64,9 @@ public class MutationalSignatureAnalysis extends OpenCgaToolScopeStudy { public final static String MUTATIONAL_SIGNATURE_DATA_MODEL_FILENAME = "mutational_signature.json"; public final static String MUTATIONAL_SIGNATURE_FITTING_DATA_MODEL_FILENAME = "mutational_signature_fitting.json"; + public final static String SV_CLUSTERING_CLI_KEY = "SV_CLUSTERING_CLI"; + public final static String SIGNATURE_FIT_CLI_KEY = "SIGNATURE_FIT_CLI"; + public static final String CLUSTERED = "clustered"; public static final String NON_CLUSTERED = "non-clustered"; public static final String LENGTH_NA = "na"; @@ -185,20 +188,20 @@ protected void check() throws Exception { assembly = getAssembly(study, catalogManager, token); // Log messages - logger.info("Signagture id: {}", signatureParams.getId()); - logger.info("Signagture description: {}", signatureParams.getDescription()); - logger.info("Signagture sample: {}", signatureParams.getSample()); - logger.info("Signagture query: {}", signatureParams.getQuery()); - logger.info("Signagture fit id: {}", signatureParams.getFitId()); - logger.info("Signagture fit method: {}", signatureParams.getFitMethod()); - logger.info("Signagture fit sig. version: {}", signatureParams.getFitSigVersion()); - logger.info("Signagture fit organ: {}", signatureParams.getFitOrgan()); - logger.info("Signagture fit n boot: {}", signatureParams.getFitNBoot()); - logger.info("Signagture fit threshold percentage: {}", signatureParams.getFitThresholdPerc()); - logger.info("Signagture fit threshold p-value: {}", signatureParams.getFitThresholdPval()); - logger.info("Signagture fit max. rare sigs.: {}", signatureParams.getFitMaxRareSigs()); - logger.info("Signagture fit signatures file: {}", signaturesFile); - logger.info("Signagture fit rare signatures file: {}", rareSignaturesFile); + logger.info("Signature id: {}", signatureParams.getId()); + logger.info("Signature description: {}", signatureParams.getDescription()); + logger.info("Signature sample: {}", signatureParams.getSample()); + logger.info("Signature query: {}", signatureParams.getQuery()); + logger.info("Signature fit id: {}", signatureParams.getFitId()); + logger.info("Signature fit method: {}", signatureParams.getFitMethod()); + logger.info("Signature fit sig. version: {}", signatureParams.getFitSigVersion()); + logger.info("Signature fit organ: {}", signatureParams.getFitOrgan()); + logger.info("Signature fit n boot: {}", signatureParams.getFitNBoot()); + logger.info("Signature fit threshold percentage: {}", signatureParams.getFitThresholdPerc()); + logger.info("Signature fit threshold p-value: {}", signatureParams.getFitThresholdPval()); + logger.info("Signature fit max. rare sigs.: {}", signatureParams.getFitMaxRareSigs()); + logger.info("Signature fit signatures file: {}", signaturesFile); + logger.info("Signature fit rare signatures file: {}", rareSignaturesFile); logger.info("Skip: {}", signatureParams.getSkip()); } diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureLocalAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureLocalAnalysisExecutor.java index 042784b844a..db9fccce655 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureLocalAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/mutationalSignature/MutationalSignatureLocalAnalysisExecutor.java @@ -77,6 +77,7 @@ public class MutationalSignatureLocalAnalysisExecutor extends MutationalSignatur @Override public void run() throws ToolException, CatalogException, IOException, StorageEngineException { opencgaHome = Paths.get(getExecutorParams().getString("opencgaHome")); + addStepParams(); // Check genome context file for that sample, and create it if necessary if (StringUtils.isNotEmpty(getSkip()) @@ -456,8 +457,11 @@ private File computeClusteredFile(Query query, QueryOptions queryOptions) throws + " /jobdir/" + inputFile.getName() + " /jobdir/" + outputFile.getName(); - // Execute R script in docker - DockerUtils.run(MutationalSignatureLocalAnalysisExecutor.R_DOCKER_IMAGE, inputBindings, outputBinding, rParams, null); + // Execute R script in docker and save the CLI as attribute + String cmdline = DockerUtils.run(MutationalSignatureLocalAnalysisExecutor.R_DOCKER_IMAGE, inputBindings, outputBinding, rParams, + null); + addAttribute(SV_CLUSTERING_CLI_KEY, cmdline); + logger.info("Docker command line: {}", cmdline); } catch (Exception e) { throw new ToolException(e); } @@ -634,8 +638,10 @@ private void computeSignatureFitting() throws IOException, ToolException, Catalo } } + // Execute docker and save the cli as attribute String cmdline = DockerUtils.run(R_DOCKER_IMAGE, inputBindings, outputBinding, scriptParams.toString(), null); + addAttribute(SIGNATURE_FIT_CLI_KEY, cmdline); logger.info("Docker command line: {}", cmdline); // Check fitting file before parsing and creating the mutational signature fitting data model diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/MutationalSignatureAnalysisExecutor.java b/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/MutationalSignatureAnalysisExecutor.java index be8fdcf1a62..0d86487e77e 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/MutationalSignatureAnalysisExecutor.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/MutationalSignatureAnalysisExecutor.java @@ -57,6 +57,26 @@ public abstract class MutationalSignatureAnalysisExecutor extends OpenCgaToolExe public MutationalSignatureAnalysisExecutor() { } + protected void addStepParams() throws ToolException { + addAttribute("STUDY", study); + addAttribute("SAMPLE", sample); + addAttribute("ASSEMBLY", assembly); + addAttribute("SIGNATURE_ID", queryId); + addAttribute("SIGNATURE_DESCRIPTION", queryDescription); + addAttribute("SIGNATURE_QUERY", query); + addAttribute("FIT_ID", fitId); + addAttribute("FIT_METHOD", fitMethod); + addAttribute("FIT_N_BOOT", nBoot); + addAttribute("FIT_SIG_VERSION", sigVersion); + addAttribute("FIT_ORGAN", organ); + addAttribute("FIT_THRESHOLD_PERC", thresholdPerc); + addAttribute("FIT_THRESHOLD_PVAL", thresholdPval); + addAttribute("FIT_MAX_RARE_SIGS", maxRareSigs); + addAttribute("FIT_SIGNATURES_FILE", signaturesFile); + addAttribute("FIT_RARE_SIGNATURES_FILE", rareSignaturesFile); + addAttribute("SKIP", skip); + } + protected static Map> initCountMap() { Map> map = new LinkedHashMap<>(); for (String firstKey : FIRST_LEVEL_KEYS) { From af024c6ad66a536bdbb5c2f1a05f4717e8ef0d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 08:22:34 +0200 Subject: [PATCH 09/21] analysis: add parameters in the tool step, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsLocalAnalysisExecutor.java modified: opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/SampleVariantStatsAnalysisExecutor.java --- .../variant/stats/SampleVariantStatsAnalysis.java | 8 ++++++++ .../stats/SampleVariantStatsLocalAnalysisExecutor.java | 1 + .../tools/variant/SampleVariantStatsAnalysisExecutor.java | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsAnalysis.java index 3d91b877bdd..02160f20f89 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsAnalysis.java @@ -332,4 +332,12 @@ protected void run() throws ToolException { } } + public SampleVariantStatsAnalysisParams getToolParams() { + return toolParams; + } + + public SampleVariantStatsAnalysis setToolParams(SampleVariantStatsAnalysisParams toolParams) { + this.toolParams = toolParams; + return this; + } } diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsLocalAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsLocalAnalysisExecutor.java index 3009ae19d88..a73edcfbfd6 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsLocalAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/stats/SampleVariantStatsLocalAnalysisExecutor.java @@ -53,6 +53,7 @@ public int getMaxBatchSize() { @Override public void run() throws ToolException { + addStepParams(); VariantStorageManager variantStorageManager = getVariantStorageManager(); diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/SampleVariantStatsAnalysisExecutor.java b/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/SampleVariantStatsAnalysisExecutor.java index 9e06c1e389a..9f18ddc7b7f 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/SampleVariantStatsAnalysisExecutor.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/SampleVariantStatsAnalysisExecutor.java @@ -95,6 +95,12 @@ protected void writeStatsToFile(List stats) throws ToolExcep } } + protected void addStepParams() throws ToolException { + addAttribute("STUDY", study); + addAttribute("SAMPLES", sampleNames); + addAttribute("QUERY", variantQuery); + } + public abstract int getDefaultBatchSize(); public abstract int getMaxBatchSize(); } From edb882b8b63e27148b6419541179fcb47e21d853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 08:40:09 +0200 Subject: [PATCH 10/21] analysis: add docker CLI and parameters in the tool step (in genome plot analysis), fix some typos and sonnar issues, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java modified: opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/GenomePlotAnalysisExecutor.java --- .../variant/genomePlot/GenomePlotAnalysis.java | 15 +++------------ .../GenomePlotLocalAnalysisExecutor.java | 10 +++++++--- .../tools/variant/GenomePlotAnalysisExecutor.java | 6 ++++++ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotAnalysis.java index 04a5241623f..cb7eb113591 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotAnalysis.java @@ -97,7 +97,7 @@ protected void run() throws ToolException { if (imgFile.getName().endsWith(GenomePlotAnalysis.SUFFIX_FILENAME)) { int index = imgFile.getAbsolutePath().indexOf("JOBS/"); String relativeFilePath = (index == -1 ? imgFile.getName() : imgFile.getAbsolutePath().substring(index)); - genomePlot = new GenomePlot("", getGenomePlotParams().getDescription(), plotConfig, relativeFilePath); + genomePlot = new GenomePlot(getGenomePlotParams().getId(), getGenomePlotParams().getDescription(), plotConfig, relativeFilePath); break; } } @@ -116,27 +116,18 @@ protected void run() throws ToolException { }); } - public static GenomePlot parseResults(Path outDir, String description, GenomePlotConfig plotConfig) throws IOException { + public static GenomePlot parseResults(Path outDir, String id, String description, GenomePlotConfig plotConfig) throws IOException { // Get image file for (java.io.File imgFile : outDir.toFile().listFiles()) { if (imgFile.getName().endsWith(GenomePlotAnalysis.SUFFIX_FILENAME)) { int index = imgFile.getAbsolutePath().indexOf("JOBS/"); String relativeFilePath = (index == -1 ? imgFile.getName() : imgFile.getAbsolutePath().substring(index)); - return new GenomePlot("", description, plotConfig, relativeFilePath); + return new GenomePlot(id, description, plotConfig, relativeFilePath); } } return null; } - public String getStudy() { - return study; - } - - public GenomePlotAnalysis setStudy(String study) { - this.study = study; - return this; - } - public GenomePlotAnalysisParams getGenomePlotParams() { return genomePlotParams; } diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java index a86cc797287..25c1ebfc8d3 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java @@ -33,6 +33,7 @@ import org.opencb.opencga.analysis.ResourceUtils; import org.opencb.opencga.analysis.StorageToolExecutor; import org.opencb.opencga.analysis.variant.manager.VariantStorageManager; +import org.opencb.opencga.analysis.variant.mutationalSignature.MutationalSignatureAnalysis; import org.opencb.opencga.catalog.exceptions.CatalogException; import org.opencb.opencga.core.common.GitRepositoryState; import org.opencb.opencga.core.common.JacksonUtils; @@ -48,6 +49,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.*; @@ -79,6 +81,7 @@ public class GenomePlotLocalAnalysisExecutor extends GenomePlotAnalysisExecutor @Override public void run() throws ToolException, IOException, CatalogException { + addStepParams(); plotConfig = JacksonUtils.getDefaultObjectMapper().readerFor(GenomePlotConfig.class).readValue(getConfigFile()); @@ -146,6 +149,7 @@ public void run() throws ToolException, IOException, CatalogException { StopWatch stopWatch = StopWatch.createStarted(); String cmdline = DockerUtils.run(R_DOCKER_IMAGE, inputBindings, outputBinding, scriptParams, null); + addAttribute("CIRCOS_CLI", cmdline); logger.info("Docker command line: " + cmdline); logger.info("Execution time: " + TimeUtils.durationToString(stopWatch)); } else { @@ -441,14 +445,14 @@ private boolean rearrangementQuery(Query query, VariantStorageManager storageMan + mate.getChromosome() + "\t" + mate.getPosition() + "\t" + mate.getPosition() + "\t" + variantType); } else { - pwOut.println(v.toString() + "\tBreakend mate is empty (variant type: " + variantType + pwOut.println(v + "\tBreakend mate is empty (variant type: " + variantType + ")"); } } else { - pwOut.println(v.toString() + "\tBreakend is empty (variant type: " + variantType + ")"); + pwOut.println(v + "\tBreakend is empty (variant type: " + variantType + ")"); } } else { - pwOut.println(v.toString() + "\tSV is empty (variant type: " + variantType + ")"); + pwOut.println(v + "\tSV is empty (variant type: " + variantType + ")"); } } } diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/GenomePlotAnalysisExecutor.java b/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/GenomePlotAnalysisExecutor.java index 2205143c670..30a3c767ab1 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/GenomePlotAnalysisExecutor.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/tools/variant/GenomePlotAnalysisExecutor.java @@ -16,6 +16,7 @@ package org.opencb.opencga.core.tools.variant; +import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.tools.OpenCgaToolExecutor; import java.io.File; @@ -33,6 +34,11 @@ public GenomePlotAnalysisExecutor(String study, File configFile) { this.configFile = configFile; } + protected void addStepParams() throws ToolException { + addAttribute("STUDY", study); + addAttribute("CONFIG_FILE", configFile); + } + @Override public String toString() { final StringBuilder sb = new StringBuilder("GenomePlotAnalysisExecutor{"); From 39c2f6a1766efe0d0453c020cc7ee6ba09f750f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 08:45:15 +0200 Subject: [PATCH 11/21] analysis: add the method addStepAttributes, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java --- .../opencga/analysis/tools/OpenCgaTool.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java index 344e34d61cc..ab6ddb08f4e 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java @@ -16,6 +16,7 @@ package org.opencb.opencga.analysis.tools; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; @@ -44,6 +45,7 @@ import org.opencb.opencga.core.tools.result.ExecutionResult; import org.opencb.opencga.core.tools.result.ExecutionResultManager; import org.opencb.opencga.core.tools.result.ExecutorInfo; +import org.opencb.opencga.core.tools.result.ToolStep; import org.opencb.opencga.storage.core.StorageEngineFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +58,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static org.opencb.opencga.core.tools.OpenCgaToolExecutor.EXECUTOR_ID; @@ -67,7 +70,7 @@ public abstract class OpenCgaTool { protected VariantStorageManager variantStorageManager; private String jobId; - private String opencgaHome; + protected String opencgaHome; protected String token; protected final ObjectMap params; @@ -505,6 +508,20 @@ protected final void addAttribute(String key, Object value) throws ToolException erm.addAttribute(key, value); } + protected final void addStepAttributes(ExecutionResult executionResult) throws ToolException { + if (executionResult != null) { + if (CollectionUtils.isNotEmpty(executionResult.getSteps())) { + for (ToolStep step : executionResult.getSteps()) { + if (MapUtils.isNotEmpty(step.getAttributes())) { + for (Map.Entry entry : step.getAttributes().entrySet()) { + erm.addStepAttribute(entry.getKey(), entry.getValue()); + } + } + } + } + } + } + protected final void moveFile(String study, Path source, Path destiny, String catalogDirectoryPath, String token) throws ToolException { File file; try { @@ -604,6 +621,7 @@ private ObjectMap getExecutorParams(ObjectMap params) { } return executorParams; } + // TODO can this method be removed? // protected final Analyst getAnalyst(String token) throws ToolException { // try { From 7e3ca472f641d5e42cd5157697d3ed9cfab25c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 08:47:33 +0200 Subject: [PATCH 12/21] analysis: use the ToolRunner instead of launching jobs for each sample QC step, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java --- .../analysis/sample/qc/SampleQcAnalysis.java | 381 +++++++++--------- 1 file changed, 189 insertions(+), 192 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java index 54f22745057..48f2aa320f9 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java @@ -17,40 +17,35 @@ package org.opencb.opencga.analysis.sample.qc; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; -import org.opencb.biodata.models.clinical.qc.GenomePlot; -import org.opencb.biodata.models.clinical.qc.GenomePlotConfig; import org.opencb.biodata.models.clinical.qc.SampleQcVariantStats; -import org.opencb.commons.datastore.core.Event; -import org.opencb.commons.datastore.core.ObjectMap; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.opencga.analysis.AnalysisUtils; import org.opencb.opencga.analysis.individual.qc.IndividualQcUtils; import org.opencb.opencga.analysis.tools.OpenCgaToolScopeStudy; +import org.opencb.opencga.analysis.tools.ToolRunner; import org.opencb.opencga.analysis.variant.genomePlot.GenomePlotAnalysis; import org.opencb.opencga.analysis.variant.mutationalSignature.MutationalSignatureAnalysis; import org.opencb.opencga.analysis.variant.stats.SampleVariantStatsAnalysis; import org.opencb.opencga.catalog.exceptions.CatalogException; import org.opencb.opencga.catalog.utils.CatalogFqn; -import org.opencb.opencga.core.api.ParamConstants; -import org.opencb.opencga.core.common.JacksonUtils; import org.opencb.opencga.core.exceptions.ToolException; import org.opencb.opencga.core.models.JwtPayload; import org.opencb.opencga.core.models.common.Enums; import org.opencb.opencga.core.models.file.File; -import org.opencb.opencga.core.models.job.Job; import org.opencb.opencga.core.models.sample.Sample; -import org.opencb.opencga.core.models.sample.SampleQualityControl; -import org.opencb.opencga.core.models.sample.SampleUpdateParams; -import org.opencb.opencga.core.models.sample.SampleVariantQualityControlMetrics; import org.opencb.opencga.core.models.variant.GenomePlotAnalysisParams; import org.opencb.opencga.core.models.variant.MutationalSignatureAnalysisParams; import org.opencb.opencga.core.models.variant.SampleQcAnalysisParams; import org.opencb.opencga.core.models.variant.SampleVariantStatsAnalysisParams; -import org.opencb.opencga.core.response.OpenCGAResult; import org.opencb.opencga.core.tools.annotations.Tool; import org.opencb.opencga.core.tools.annotations.ToolParams; +import org.opencb.opencga.core.tools.result.ExecutionResult; +import org.opencb.opencga.core.tools.result.Status; +import org.opencb.opencga.storage.core.StorageEngineFactory; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @@ -64,10 +59,14 @@ public class SampleQcAnalysis extends OpenCgaToolScopeStudy { public static final String DESCRIPTION = "Run quality control (QC) for a given sample. It includes variant stats, and if the sample " + "is somatic, mutational signature and genome plot are calculated."; + private static final String SAMPLE_VARIANT_STATS_STEP = "sample-variant-stats"; + private static final String MUTATIONAL_SIGNATURE_STEP = "mutational-signature"; + private static final String GENOME_PLOT_STEP = "genome-plot"; + @ToolParams - protected final SampleQcAnalysisParams analysisParams = new SampleQcAnalysisParams(); + protected final SampleQcAnalysisParams sampleQcParams = new SampleQcAnalysisParams(); - private Path genomePlotConfigPath; + private ToolRunner toolRunner; private boolean runVariantStats = true; private boolean runSignatureCatalogue = true; @@ -96,19 +95,19 @@ protected void check() throws Exception { } // Sanity check - if (StringUtils.isEmpty(analysisParams.getSample())) { + if (StringUtils.isEmpty(sampleQcParams.getSample())) { throw new ToolException("Missing sample ID."); } - Sample sample = IndividualQcUtils.getValidSampleById(getStudy(), analysisParams.getSample(), catalogManager, token); + Sample sample = IndividualQcUtils.getValidSampleById(getStudy(), sampleQcParams.getSample(), catalogManager, token); if (sample == null) { - throw new ToolException("Sample '" + analysisParams.getSample() + "' not found."); + throw new ToolException("Sample '" + sampleQcParams.getSample() + "' not found."); } // Prepare flags String skip = null; - if (StringUtils.isNotEmpty(analysisParams.getSkip())) { - skip = analysisParams.getSkip().toLowerCase().replace(" ", ""); + if (StringUtils.isNotEmpty(sampleQcParams.getSkip())) { + skip = sampleQcParams.getSkip().toLowerCase().replace(" ", ""); } if (StringUtils.isNotEmpty(skip)) { Set skipValues = new HashSet<>(Arrays.asList(skip.split(","))); @@ -131,237 +130,235 @@ protected void check() throws Exception { // Check variant stats if (runVariantStats) { final String OPENCGA_ALL = "ALL"; - if (OPENCGA_ALL.equals(analysisParams.getVsId())) { - new ToolException("Invalid parameters: " + OPENCGA_ALL + " is a reserved word, you can not use as a variant stats ID"); + if (OPENCGA_ALL.equals(sampleQcParams.getVsId())) { + throw new ToolException("Invalid parameters: " + OPENCGA_ALL + " is a reserved word, you can not use as a variant stats ID"); } - if (StringUtils.isEmpty(analysisParams.getVsId()) && analysisParams.getVsQuery() != null - && !analysisParams.getVsQuery().toParams().isEmpty()) { - new ToolException("Invalid parameters: if variant stats ID is empty, variant stats query must be empty"); + if (StringUtils.isEmpty(sampleQcParams.getVsId()) && sampleQcParams.getVsQuery() != null + && !sampleQcParams.getVsQuery().toParams().isEmpty()) { + throw new ToolException("Invalid parameters: if variant stats ID is empty, variant stats query must be empty"); } - if (StringUtils.isNotEmpty(analysisParams.getVsId()) - && (analysisParams.getVsQuery() == null || analysisParams.getVsQuery().toParams().isEmpty())) { - new ToolException("Invalid parameters: if you provide a variant stats ID, variant stats query can not be empty"); + if (StringUtils.isNotEmpty(sampleQcParams.getVsId()) + && (sampleQcParams.getVsQuery() == null || sampleQcParams.getVsQuery().toParams().isEmpty())) { + throw new ToolException("Invalid parameters: if you provide a variant stats ID, variant stats query can not be empty"); } - if (StringUtils.isEmpty(analysisParams.getVsId())) { - analysisParams.setVsId(OPENCGA_ALL); + if (StringUtils.isEmpty(sampleQcParams.getVsId())) { + sampleQcParams.setVsId(OPENCGA_ALL); } - if (analysisParams.getVsQuery() == null) { - new ToolException("Invalid parameters: variant stats query is empty"); + if (sampleQcParams.getVsQuery() == null) { + throw new ToolException("Invalid parameters: variant stats query is empty"); } if (sample.getQualityControl() != null && sample.getQualityControl().getVariant() != null) { if (CollectionUtils.isNotEmpty(sample.getQualityControl().getVariant().getVariantStats()) - && OPENCGA_ALL.equals(analysisParams.getVsId())) { + && OPENCGA_ALL.equals(sampleQcParams.getVsId())) { runVariantStats = false; } else { for (SampleQcVariantStats variantStats : sample.getQualityControl().getVariant().getVariantStats()) { - if (variantStats.getId().equals(analysisParams.getVsId())) { - throw new ToolException("Invalid parameters: variant stats ID '" + analysisParams.getVsId() + if (variantStats.getId().equals(sampleQcParams.getVsId())) { + throw new ToolException("Invalid parameters: variant stats ID '" + sampleQcParams.getVsId() + "' is already used"); } } } } + } else { + String msg = "Skipping sample variant stats analysis by user"; + addWarning(msg); + logger.warn(msg); } // Check mutational signature if (runSignatureCatalogue) { - if (StringUtils.isEmpty(analysisParams.getMsQuery())) { - new ToolException("Invalid parameters: mutational signature query is empty"); + if (!sample.isSomatic()) { + String msg = "Skipping mutational signature catalog analysis:" + getSampleIsNotSomaticMsg(sample.getId()); + addWarning(msg); + logger.warn(msg); + runSignatureCatalogue = false; + } else if (StringUtils.isEmpty(sampleQcParams.getMsQuery())) { + throw new ToolException("Invalid parameters: mutational signature query is empty"); } - } - - if (runSignatureCatalogue && !sample.isSomatic()) { - String msg = "Skipping mutational signature catalog analysis: sample '" + sample.getId() + "' is not somatic."; + } else { + String msg = "Skipping mutational signature catalogue analysis by user"; addWarning(msg); logger.warn(msg); - runSignatureCatalogue = false; } - if (runSignatureFitting && !sample.isSomatic()) { - String msg = "Skipping mutational signature fitting analysis: sample '" + sample.getId() + "' is not somatic."; + if (runSignatureFitting) { + if (!sample.isSomatic()) { + String msg = "Skipping mutational signature fitting analysis:" + getSampleIsNotSomaticMsg(sample.getId()); + addWarning(msg); + logger.warn(msg); + runSignatureFitting = false; + } + } else { + String msg = "Skipping mutational signature fitting analysis by user"; addWarning(msg); logger.warn(msg); - runSignatureFitting = false; } // Check genome plot if (runGenomePlot) { - if (StringUtils.isEmpty(analysisParams.getGpConfigFile())) { - new ToolException("Invalid parameters: genome plot configuration file is empty"); - } - if (runGenomePlot && !sample.isSomatic()) { - String msg = "Skipping genome plot: sample '" + sample.getId() + "' is not somatic."; + if (!sample.isSomatic()) { + String msg = "Skipping genome plot: " + getSampleIsNotSomaticMsg(sample.getId()); addWarning(msg); logger.warn(msg); runGenomePlot = false; } else { - File genomePlotConfFile = AnalysisUtils.getCatalogFile(analysisParams.getGpConfigFile(), getStudy(), + if (StringUtils.isEmpty(sampleQcParams.getGpConfigFile())) { + throw new ToolException("Invalid parameters: genome plot configuration file is empty"); + } + + File genomePlotConfFile = AnalysisUtils.getCatalogFile(sampleQcParams.getGpConfigFile(), getStudy(), catalogManager.getFileManager(), getToken()); - genomePlotConfigPath = Paths.get(genomePlotConfFile.getUri().getPath()); + Path genomePlotConfigPath = Paths.get(genomePlotConfFile.getUri().getPath()); if (!genomePlotConfigPath.toFile().exists()) { - new ToolException("Invalid parameters: genome plot configuration file does not exist (" + genomePlotConfigPath + ")"); + throw new ToolException("Invalid parameters: genome plot configuration file does not exist (" + genomePlotConfigPath + + ")"); } } + } else { + String msg = "Skipping genome plot analysis by user"; + addWarning(msg); + logger.warn(msg); } } @Override - protected void run() throws ToolException { - step(() -> { - Map params; - String variantStatsJobId = null; - String signatureJobId = null; - String genomePlotJobId = null; - - try { - if (runVariantStats) { - // Run variant stats - params = new SampleVariantStatsAnalysisParams(Collections.singletonList(analysisParams.getSample()), null, null, true, - false, analysisParams.getVsId(), analysisParams.getVsDescription(), null, - analysisParams.getVsQuery()) - .toParams(new ObjectMap(ParamConstants.STUDY_PARAM, getStudy())); - - OpenCGAResult variantStatsJobResult = catalogManager.getJobManager() - .submit(study, SampleVariantStatsAnalysis.ID, Enums.Priority.MEDIUM, params, null, "Job generated by " - + getId() + " - " + getJobId(), Collections.emptyList(), Collections.emptyList(), token); - variantStatsJobId = variantStatsJobResult.first().getId(); - addEvent(Event.Type.INFO, "Submit job " + variantStatsJobId + " to compute stats (" + SampleVariantStatsAnalysis.ID - + ")"); - } - } catch (CatalogException e) { - addWarning("Error launching job for sample variant stats analysis: " + e.getMessage()); - variantStatsJobId = null; - } - - try { - if (runSignatureCatalogue || runSignatureFitting) { - // Run mutational signature - logger.info("Preparing to submit the mutational signature analysis job"); - - String skip = null; - if (!runSignatureCatalogue) { - skip = MutationalSignatureAnalysisParams.SIGNATURE_CATALOGUE_SKIP_VALUE; - } else if (!runSignatureFitting) { - skip = MutationalSignatureAnalysisParams.SIGNATURE_FITTING_SKIP_VALUE; - } - - params = new MutationalSignatureAnalysisParams() - .setId(analysisParams.getMsId()) - .setDescription(analysisParams.getMsDescription()) - .setSample(analysisParams.getSample()) - .setQuery(analysisParams.getMsQuery()) - .setFitId(analysisParams.getMsFitId()) - .setFitMethod(analysisParams.getMsFitMethod()) - .setFitSigVersion(analysisParams.getMsFitSigVersion()) - .setFitOrgan(analysisParams.getMsFitOrgan()) - .setFitNBoot(analysisParams.getMsFitNBoot()) - .setFitThresholdPerc(analysisParams.getMsFitThresholdPerc()) - .setFitThresholdPval(analysisParams.getMsFitThresholdPval()) - .setFitMaxRareSigs(analysisParams.getMsFitMaxRareSigs()) - .setFitSignaturesFile(analysisParams.getMsFitSignaturesFile()) - .setFitRareSignaturesFile(analysisParams.getMsFitRareSignaturesFile()) - .setSkip(skip) - .toParams(new ObjectMap(ParamConstants.STUDY_PARAM, getStudy())); - - OpenCGAResult signatureJobResult = catalogManager.getJobManager() - .submit(getStudy(), MutationalSignatureAnalysis.ID, Enums.Priority.MEDIUM, params, null, "Job generated by " - + getId() + " - " + getJobId(), Collections.emptyList(), Collections.emptyList(), token); - signatureJobId = signatureJobResult.first().getId(); - logger.info("Submitted job {} to compute the mutational signature analysis {}", signatureJobId, - MutationalSignatureAnalysis.ID); - addEvent(Event.Type.INFO, "Submit job " + signatureJobId + " to compute the mutational signature (" - + MutationalSignatureAnalysis.ID + ")"); - } - } catch (CatalogException e) { - throw new ToolException(e); - } + protected List getSteps() { + List steps = new ArrayList<>(); + if (runVariantStats) { + steps.add(SAMPLE_VARIANT_STATS_STEP); + } + if (runSignatureCatalogue || runSignatureFitting) { + steps.add(MUTATIONAL_SIGNATURE_STEP); + } + if (runGenomePlot) { + steps.add(GENOME_PLOT_STEP); + } + return steps; + } + @Override + protected void run() throws ToolException { + // Create the tool runner + toolRunner = new ToolRunner(opencgaHome, catalogManager, StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); - try { - if (runGenomePlot) { - // Run genome plot - params = new GenomePlotAnalysisParams(analysisParams.getSample(), analysisParams.getGpId(), - analysisParams.getGpDescription(), analysisParams.getGpConfigFile(), null) - .toParams(new ObjectMap(ParamConstants.STUDY_PARAM, getStudy())); + // Sample variant stats + if (runVariantStats) { + step(SAMPLE_VARIANT_STATS_STEP, this::runSampleVariantStats); + } - OpenCGAResult genomePlotJobResult = catalogManager.getJobManager() - .submit(getStudy(), GenomePlotAnalysis.ID, Enums.Priority.MEDIUM, params, null, - "Job generated by " + getId() + " - " + getJobId(), Collections.emptyList(), Collections.emptyList(), - token); - genomePlotJobId = genomePlotJobResult.first().getId(); - addEvent(Event.Type.INFO, "Submit job " + genomePlotJobId + " to compute genome plot (" + GenomePlotAnalysis.ID - + ")"); - } - } catch (CatalogException e) { - addWarning("Error launching job for sample genome plot analysis: " + e.getMessage()); - genomePlotJobId = null; - } + // Mutational signature + if (runSignatureCatalogue || runSignatureFitting) { + step(MUTATIONAL_SIGNATURE_STEP, this::runMutationalSignature); + } + // Genome plot + if (runGenomePlot) { + step(GENOME_PLOT_STEP, this::runGenomePlot); + } + } - // Wait for those jobs before saving QC - GenomePlot genomePlot = null; + private void runSampleVariantStats() throws ToolException { + // Create out folder + Path outPath = getOutDir().resolve(SAMPLE_VARIANT_STATS_STEP); + try { + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating sample variant stats folder: " + outPath, e); + } - if (variantStatsJobId != null) { - try { - logger.info("Waiting for variant stats job: {} ...", variantStatsJobId); - AnalysisUtils.waitFor(variantStatsJobId, getStudy(), catalogManager.getJobManager(), getToken()); - // Sample quality control is updated in the variant stats analysis, nothing more to do here - } catch (Exception e) { - addWarning("Error waiting for job '" + variantStatsJobId + "' (sample variant stats): " + e.getMessage()); - } - } + // Prepare parameters + SampleVariantStatsAnalysisParams sampleVariantStatsParams = new SampleVariantStatsAnalysisParams( + Collections.singletonList(sampleQcParams.getSample()), null, null, true, false, sampleQcParams.getVsId(), + sampleQcParams.getVsDescription(), null, sampleQcParams.getVsQuery()); - if (signatureJobId != null) { - try { - logger.info("Waiting for mutational signature job: {} ...", signatureJobId); - AnalysisUtils.waitFor(signatureJobId, getStudy(), catalogManager.getJobManager(), getToken()); - } catch (Exception e) { - addWarning("Error waiting for job '" + signatureJobId + "' (mutational signature analysis): " + e.getMessage()); - } - } + // Execute the sample variant stats analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(SampleVariantStatsAnalysis.class, study, sampleVariantStatsParams, outPath, + null, token); + addStepAttributes(executionResult); - if (genomePlotJobId != null) { - try { - if (AnalysisUtils.waitFor(genomePlotJobId, getStudy(), catalogManager.getJobManager(), getToken())) { - Job job = AnalysisUtils.getJob(genomePlotJobId, getStudy(), catalogManager.getJobManager(), getToken()); + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the sample variant stats analysis. Execution status = " + + executionResult.getStatus().getName()); + } + } - // Parse configuration file - GenomePlotConfig plotConfig = JacksonUtils.getDefaultObjectMapper().readerFor(GenomePlotConfig.class) - .readValue(genomePlotConfigPath.toFile()); + private void runMutationalSignature() throws ToolException { + // Create the output folder + Path outPath = getOutDir().resolve(MUTATIONAL_SIGNATURE_STEP); + try { + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating mutational signature folder: " + outPath, e); + } - // Parse genome plot results - genomePlot = GenomePlotAnalysis.parseResults(Paths.get(job.getOutDir().getUri().getPath()), - analysisParams.getGpDescription(), plotConfig); - } - } catch (Exception e) { - addWarning("Error waiting for job '" + genomePlotJobId + "' (genome plot analysis): " + e.getMessage()); - } - } + // Prepare parameters + String skip = null; + if (!runSignatureCatalogue) { + skip = MutationalSignatureAnalysisParams.SIGNATURE_CATALOGUE_SKIP_VALUE; + } else if (!runSignatureFitting) { + skip = MutationalSignatureAnalysisParams.SIGNATURE_FITTING_SKIP_VALUE; + } - // Update quality control for the sample - logger.info("Preparing to save quality control for sample {}", analysisParams.getSample()); - Sample sample = IndividualQcUtils.getValidSampleById(getStudy(), analysisParams.getSample(), catalogManager, token); - if (sample == null) { - throw new ToolException("Can not access to the sample " + analysisParams.getSample() + " in order to save quality control"); - } - SampleQualityControl qc = sample.getQualityControl(); + MutationalSignatureAnalysisParams mutationalSignatureParams = new MutationalSignatureAnalysisParams() + .setId(sampleQcParams.getMsId()) + .setDescription(sampleQcParams.getMsDescription()) + .setSample(sampleQcParams.getSample()) + .setQuery(sampleQcParams.getMsQuery()) + .setFitId(sampleQcParams.getMsFitId()) + .setFitMethod(sampleQcParams.getMsFitMethod()) + .setFitSigVersion(sampleQcParams.getMsFitSigVersion()) + .setFitOrgan(sampleQcParams.getMsFitOrgan()) + .setFitNBoot(sampleQcParams.getMsFitNBoot()) + .setFitThresholdPerc(sampleQcParams.getMsFitThresholdPerc()) + .setFitThresholdPval(sampleQcParams.getMsFitThresholdPval()) + .setFitMaxRareSigs(sampleQcParams.getMsFitMaxRareSigs()) + .setFitSignaturesFile(sampleQcParams.getMsFitSignaturesFile()) + .setFitRareSignaturesFile(sampleQcParams.getMsFitRareSignaturesFile()) + .setSkip(skip); + + // Execute the mutational signature analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(MutationalSignatureAnalysis.class, study, mutationalSignatureParams, outPath, + null, token); + addStepAttributes(executionResult); + + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the mutational signature analysis. Execution status = " + + executionResult.getStatus().getName()); + } + } - // Sanity check - if (qc == null) { - qc = new SampleQualityControl(); - } else if (qc.getVariant() == null) { - qc.setVariant(new SampleVariantQualityControlMetrics()); - } + private void runGenomePlot() throws ToolException { + Path outPath = getOutDir().resolve(GENOME_PLOT_STEP); + try { + FileUtils.forceMkdir(outPath.toFile()); + } catch (IOException e) { + throw new ToolException("Error creating genome plot folder: " + outPath, e); + } - if (genomePlot != null) { - qc.getVariant().setGenomePlot(genomePlot); + // Prepare parameters + GenomePlotAnalysisParams genomePlotParams = new GenomePlotAnalysisParams() + .setSample(sampleQcParams.getSample()) + .setId(sampleQcParams.getGpId()) + .setDescription(sampleQcParams.getGpDescription()) + .setConfigFile(sampleQcParams.getGpConfigFile()); + + // Execute the genome plot analysis and add its step attributes if exist + ExecutionResult executionResult = toolRunner.execute(GenomePlotAnalysis.class, study, genomePlotParams, outPath, null, token); + addStepAttributes(executionResult); + + // Check execution status + if (executionResult.getStatus().getName() != Status.Type.DONE) { + throw new ToolException("Something wrong happened running the mutational signature analysis. Execution status = " + + executionResult.getStatus().getName()); + } + } - catalogManager.getSampleManager().update(getStudy(), sample.getId(), new SampleUpdateParams().setQualityControl(qc), - QueryOptions.empty(), getToken()); - logger.info("Quality control saved for sample {}", sample.getId()); - } - }); + public static String getSampleIsNotSomaticMsg(String id) { + return "sample '" + id + "' is not somatic."; } } From f88c7033d7738c0980ded0df4cae96ce631d40a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 09:25:32 +0200 Subject: [PATCH 13/21] analysis: remove unused get/set methods and R scripts, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: deleted: opencga-analysis/src/main/R/genome-plot/circos.R deleted: opencga-analysis/src/main/R/mutational-signature/mutational-signature.r modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java --- .../src/main/R/genome-plot/circos.R | 636 ------------------ .../mutational-signature.r | 170 ----- .../alignment/qc/AlignmentQcAnalysis.java | 4 - .../opencga/analysis/tools/OpenCgaTool.java | 9 - 4 files changed, 819 deletions(-) delete mode 100644 opencga-analysis/src/main/R/genome-plot/circos.R delete mode 100644 opencga-analysis/src/main/R/mutational-signature/mutational-signature.r diff --git a/opencga-analysis/src/main/R/genome-plot/circos.R b/opencga-analysis/src/main/R/genome-plot/circos.R deleted file mode 100644 index c14d8ca921a..00000000000 --- a/opencga-analysis/src/main/R/genome-plot/circos.R +++ /dev/null @@ -1,636 +0,0 @@ -#!/usr/bin/env Rscript - -library(RCircos); -library(scales) -library(optparse) - - -my.RCircos.Tile.Plot <- function (tile.data, track.num, side, tile.colors=NA) -{ - RCircos.Pos <- RCircos.Get.Plot.Positions() - RCircos.Par <- RCircos.Get.Plot.Parameters() - tile.data <- RCircos.Get.Plot.Data.nosort(tile.data, "plot") - the.layer <- 1 - the.chr <- tile.data[1, 1] - start <- tile.data[1, 2] - end <- tile.data[1, 3] - tile.layers <- rep(1, nrow(tile.data)) - if (nrow(tile.data)>1) { - for (a.row in 2:nrow(tile.data)) { - if (tile.data[a.row, 2] >= end) { - the.layer <- 1 - start <- tile.data[a.row, 2] - end <- tile.data[a.row, 3] - } - else if (tile.data[a.row, 1] != the.chr) { - the.layer <- 1 - the.chr <- tile.data[a.row, 1] - start <- tile.data[a.row, 2] - end <- tile.data[a.row, 3] - } - else { - the.layer <- the.layer + 1 - if (tile.data[a.row, 3] > end) { - end <- tile.data[a.row, 3] - } - } - tile.layers[a.row] <- 1 - } - } - locations <- RCircos.Track.Positions.my(side, track.num) - out.pos <- locations[1] - in.pos <- locations[2] - layer.height <- RCircos.Par$track.height/RCircos.Par$max.layers - num.layers <- max(tile.layers) - if (num.layers > RCircos.Par$max.layers) { - if (side == "in") { - in.pos <- out.pos - layer.height * num.layers - } - else { - out.pos <- in.pos + layer.height * num.layers - } - cat(paste("Tiles plot will use more than one track.", - "Please select correct area for next track.\n")) - } - if (num.layers < RCircos.Par$max.layers) { - layer.height <- RCircos.Par$track.height/num.layers - } - if (length(tile.colors)==1) { - tile.colors <- RCircos.Get.Plot.Colors(tile.data, RCircos.Par$tile.color)} - RCircos.Track.Outline.my(out.pos, in.pos, num.layers) - the.loc <- ncol(tile.data) - for (a.row in 1:nrow(tile.data)) { - tile.len <- tile.data[a.row, 3] - tile.data[a.row, 2] - tile.range <- round(tile.len/RCircos.Par$base.per.unit/2, - digits = 0) - start <- tile.data[a.row, the.loc] - tile.range - end <- tile.data[a.row, the.loc] + tile.range - layer.bot <- in.pos - layer.top <- out.pos - - #Catch positions that fall outside a band (eg when using exome ideogram) - if (is.na(start) || (is.na(end))) { - next; - } - polygon.x <- c(RCircos.Pos[start:end, 1] * layer.top, - RCircos.Pos[end:start, 1] * layer.bot) - polygon.y <- c(RCircos.Pos[start:end, 2] * layer.top, - RCircos.Pos[end:start, 2] * layer.bot) - polygon(polygon.x, polygon.y, col = tile.colors[a.row], lwd=RCircos.Par$line.width, border=tile.colors[a.row]) - } -} - - -RCircos.Chromosome.Ideogram.Plot.my <- function (chrTextColor = 'grey', gridLineColor = 'grey', textSize = 0.6) -{ - RCircos.Cyto <- RCircos.Get.Plot.Ideogram() - RCircos.Pos <- RCircos.Get.Plot.Positions() - RCircos.Par <- RCircos.Get.Plot.Parameters() - right.side <- nrow(RCircos.Pos)/2 - if (is.null(RCircos.Par$chr.ideog.pos)){ - RCircos.Par$chr.ideog.pos <- 1.95 #3.1 #1.95 - } - outer.location <- RCircos.Par$chr.ideog.pos + RCircos.Par$chrom.width - inner.location <- RCircos.Par$chr.ideog.pos - chroms <- unique(RCircos.Cyto$Chromosome) - for (a.chr in 1:length(chroms)) { - the.chr <- RCircos.Cyto[RCircos.Cyto$Chromosome == chroms[a.chr], - ] - ##new RCircos version - start <- the.chr$StartPoint[1] - end <- the.chr$EndPoint[nrow(the.chr)] - mid <- round((end - start + 1)/2, digits = 0) + start - # chr.color <- 'grey' - chr.color <- chrTextColor - pos.x <- c(RCircos.Pos[start:end, 1] * outer.location, - RCircos.Pos[end:start, 1] * inner.location) - pos.y <- c(RCircos.Pos[start:end, 2] * outer.location, - RCircos.Pos[end:start, 2] * inner.location) - # polygon(pos.x, pos.y, border='grey', lwd=0.5) - polygon(pos.x, pos.y, border=gridLineColor, lwd=0.5) - chr.name <- sub(pattern = "chr", replacement = "", chroms[a.chr]) - text(RCircos.Pos[mid, 1] * RCircos.Par$chr.name.pos, - RCircos.Pos[mid, 2] * RCircos.Par$chr.name.pos, label = chr.name, - # srt = RCircos.Pos$degree[mid], col='grey', cex=0.6) - srt = RCircos.Pos$degree[mid], col=gridLineColor, cex=textSize) - lines(RCircos.Pos[start:end, ] * RCircos.Par$highlight.pos, - col = chr.color, lwd = 0.5) - } - for (a.band in 1:nrow(RCircos.Cyto)) { - a.color <- RCircos.Cyto$BandColor[a.band] - if (a.color == "white") { - next - } - ##new RCircos version - start <- RCircos.Cyto$StartPoint[a.band] - end <- RCircos.Cyto$EndPoint[a.band] - pos.x <- c(RCircos.Pos[start:end, 1] * outer.location, - RCircos.Pos[end:start, 1] * inner.location) - pos.y <- c(RCircos.Pos[start:end, 2] * outer.location, - RCircos.Pos[end:start, 2] * inner.location) - polygon(pos.x, pos.y, col = alpha(a.color,0.25), border = NA) - } -} - - -RCircos.Get.Plot.Data.nosort <- function (genomic.data, plot.type, validate=TRUE) -{ - - data.points <- rep(0, nrow(genomic.data)) - for (a.row in 1:nrow(genomic.data)) { - chromosome <- as.character(genomic.data[a.row, 1]) - location <- round((genomic.data[a.row, 2] + genomic.data[a.row, - 3])/2, digits = 0) - data.points[a.row] <- RCircos.Data.Point(chromosome, location) - } - genomic.data["Location"] <- data.points - return(genomic.data) -} - - -RCircos.Heatmap.Plot.my <- function (heatmap.data, data.col, track.num, side, plotTrack=TRUE, heatmap.ranges=NA, heatmap.color=NA) -{ - RCircos.Cyto <- RCircos.Get.Plot.Ideogram() - RCircos.Pos <- RCircos.Get.Plot.Positions() - RCircos.Par <- RCircos.Get.Plot.Parameters() - - min.with <- 1000000 - heatmap.data$width <- heatmap.data$chromEnd - heatmap.data$chromStart - heatmap.data <- heatmap.data[order(-heatmap.data$width),] # make sure the narrowest plots are drawn as last - narrow.cn <- heatmap.data$width < min.with - flank <- (min.with - heatmap.data$width[narrow.cn])/2 - heatmap.data$chromEnd[narrow.cn] <- heatmap.data$chromEnd[narrow.cn ] + flank - heatmap.data$chromStart[narrow.cn ] <- heatmap.data$chromStart[narrow.cn ] - flank - heatmap.data$chromStart[heatmap.data$chromStart<0] <- 0 - - heatmap.data <- RCircos.Get.Plot.Data.nosort(heatmap.data, "plot") - heatmap.data1 <- RCircos.Get.Plot.Data.nosort(data.frame(Chromosome=heatmap.data$Chromosome, chromStart=heatmap.data$chromStart, chromEnd=heatmap.data$chromStart), "plot") - heatmap.data2 <- RCircos.Get.Plot.Data.nosort(data.frame(Chromosome=heatmap.data$Chromosome, chromStart=heatmap.data$chromEnd, chromEnd=heatmap.data$chromEnd), "plot") - - - if ((length(heatmap.ranges)==1) && (is.na(heatmap.ranges))) { - ColorLevel <- RCircos.Par$heatmap.ranges - } else { - ColorLevel <- heatmap.ranges - } - - if ((length(heatmap.color)==1) && (is.na(heatmap.color))) { - ColorRamp <- RCircos.Get.Heatmap.ColorScales(RCircos.Par$heatmap.color) - } - - columns <- 5:(ncol(heatmap.data) - 1) - min.value <- min(as.matrix(heatmap.data[, columns])) - max.value <- max(as.matrix(heatmap.data[, columns])) - - heatmap.locations1 <- as.numeric(heatmap.data1[, ncol(heatmap.data2)]) - heatmap.locations2 <- as.numeric(heatmap.data2[, ncol(heatmap.data2)]) - - start <- heatmap.locations1 # - RCircos.Par$heatmap.width/2 - end <- heatmap.locations2 # + RCircos.Par$heatmap.width/2 - data.chroms <- as.character(heatmap.data[, 1]) - chromosomes <- unique(data.chroms) - cyto.chroms <- as.character(RCircos.Cyto$Chromosome) - - for (a.chr in 1:length(chromosomes)) { - cyto.rows <- which(cyto.chroms == chromosomes[a.chr]) - locations <- as.numeric(RCircos.Cyto$EndPoint[cyto.rows]) # chromosome locations - chr.start <- min(locations) - RCircos.Cyto$StartPoint[cyto.rows[1]] # chromosome start - chr.end <- max(locations) # chromosome end - data.rows <- which(data.chroms == chromosomes[a.chr]) # points on this chromosome - start[data.rows[start[data.rows] < chr.start]] <- chr.start # chromosome starts for each point - end[data.rows[end[data.rows] > chr.end]] <- chr.end # chromosome end for each point - } - - locations <- RCircos.Track.Positions.my(side, track.num) # positions - out.pos <- locations[1] - in.pos <- locations[2] - chroms <- unique(RCircos.Cyto$Chromosome) - for (a.chr in 1:length(chroms)) { - the.chr <- RCircos.Cyto[RCircos.Cyto$Chromosome == chroms[a.chr], - ] - the.start <- the.chr$StartPoint[1] - the.end <- the.chr$EndPoint[nrow(the.chr)] - polygon.x <- c(RCircos.Pos[the.start:the.end, 1] * out.pos, - RCircos.Pos[the.end:the.start, 1] * in.pos) - polygon.y <- c(RCircos.Pos[the.start:the.end, 2] * out.pos, - RCircos.Pos[the.end:the.start, 2] * in.pos) - polygon(polygon.x, polygon.y, col = "white", border = RCircos.Par$grid.line.color, lwd=0.3) - } - - - heatmap.value <- as.numeric(heatmap.data[, data.col]) - for (a.point in 1:length(heatmap.value)) { - - the.level <- which(ColorLevel <= heatmap.value[a.point]) - cell.color <- heatmap.color[max(the.level)] # establish the color - - the.start <- start[a.point] - the.end <- end[a.point] - #if (is.na(the.start) | is.na(the.end)) { - # browser() - #} - - #Catch positions that fall outside a band (eg when using exome ideogram) - if (is.na(the.start) || (is.na(the.end))) { - next; - } - polygon.x <- c(RCircos.Pos[the.start:the.end, 1] * out.pos, RCircos.Pos[the.end:the.start, 1] * in.pos) - polygon.y <- c(RCircos.Pos[the.start:the.end, 2] * out.pos, RCircos.Pos[the.end:the.start, 2] * in.pos) - polygon(polygon.x, polygon.y, col = cell.color, border = NA) - } - -} - - -RCircos.Link.Plot.my <- function (link.data, track.num, by.chromosome = FALSE, link.colors=NA) -{ - - if (length(link.colors)==1) { - link.colors <- rep('BurlyWood', nrow(link.data)) - } - - - RCircos.Pos <- RCircos.Get.Plot.Positions() - RCircos.Par <- RCircos.Get.Plot.Parameters() - locations <- RCircos.Track.Positions.my('in', track.num) - start <- locations[['out.loc']] - base.positions <- RCircos.Pos * start - data.points <- matrix(rep(0, nrow(link.data) * 2), ncol = 2) - for (a.link in 1:nrow(link.data)) { - data.points[a.link, 1] <- RCircos.Data.Point(link.data[a.link, - 1], link.data[a.link, 2]) - data.points[a.link, 2] <- RCircos.Data.Point(link.data[a.link, - 4], link.data[a.link, 5]) - if (data.points[a.link, 1] == 0 || data.points[a.link, - 2] == 0) { - print("Error in chromosome locations ...") - break - } - } - for (a.link in 1:nrow(data.points)) { - point.one <- data.points[a.link, 1] - point.two <- data.points[a.link, 2] - if (point.one > point.two) { - point.one <- data.points[a.link, 2] - point.two <- data.points[a.link, 1] - } - P0 <- as.numeric(base.positions[point.one, ]) - P2 <- as.numeric(base.positions[point.two, ]) - links <- RCircos.Link.Line(P0, P2) - lines(links$pos.x, links$pos.y, type = "l", col = link.colors[a.link], lwd=RCircos.Par$link.line.width) - } -} - - -RCircos.Scatter.Plot.color <- function (scatter.data, data.col, track.num, side, scatter.colors, draw.bg =TRUE, no.sort=FALSE) -{ - - RCircos.Pos <- RCircos.Get.Plot.Positions() - pch <- RCircos.Get.Plot.Parameters()$point.type - cex <- RCircos.Get.Plot.Parameters()$point.size - scatter.data <- RCircos.Get.Plot.Data.nosort(scatter.data, "plot") - - locations <- RCircos.Track.Positions.my(side, track.num, track.heights = 4) - out.pos <- locations[1] - in.pos <- locations[2] - point.bottom <- in.pos - data.ceiling <- max(scatter.data[, data.col]) - - sub.height <- out.pos - point.bottom - - RCircos.Track.Outline.my(out.pos, in.pos) - - scatter.data[scatter.data[data.col]>data.ceiling, data.col] <- data.ceiling - scatter.data[scatter.data[data.col]<(-data.ceiling), data.col] <- -data.ceiling - scatter.data$height <- point.bottom + scatter.data[, data.col]/data.ceiling * sub.height - scatter.data$x_coord <- RCircos.Pos[scatter.data$Location, 1] * scatter.data$height - scatter.data$y_coord <- RCircos.Pos[scatter.data$Location, 2] * scatter.data$height - - points(scatter.data$x_coord, - scatter.data$y_coord, - col = scatter.colors, - pch = pch, - cex = cex) -} - - -RCircos.Track.Outline.my <- function (out.pos, in.pos, num.layers = 1) -{ - RCircos.Cyto <- RCircos.Get.Plot.Ideogram() - RCircos.Pos <- RCircos.Get.Plot.Positions() - RCircos.Par <- RCircos.Get.Plot.Parameters() - subtrack.height <- (out.pos - in.pos)/num.layers - chroms <- unique(RCircos.Cyto$Chromosome) - for (a.chr in 1:length(chroms)) { - the.chr <- RCircos.Cyto[RCircos.Cyto$Chromosome == chroms[a.chr], ] - start <- the.chr$StartPoint[1] - end <- the.chr$EndPoint[nrow(the.chr)] - polygon.x <- c(RCircos.Pos[start:end, 1] * out.pos, RCircos.Pos[end:start, - 1] * in.pos) - polygon.y <- c(RCircos.Pos[start:end, 2] * out.pos, RCircos.Pos[end:start, - 2] * in.pos) - polygon(polygon.x, polygon.y, col = NULL, lwd=0.3, border=RCircos.Par$grid.line.color) - - for (a.line in 1:(num.layers - 1)) { - height <- out.pos - a.line * subtrack.height - lines(RCircos.Pos[start:end, 1] * height, RCircos.Pos[start:end, 2] * height, col = RCircos.Par$grid.line.color, lwd=0.3) - } - } -} - - -RCircos.Track.Positions.my <- function (side, track.num, track.heights = 1) -{ - RCircos.Par <- RCircos.Get.Plot.Parameters() - one.track <- RCircos.Par$track.height + RCircos.Par$track.padding - side <- tolower(side) - if (side == "in") { - out.pos <- RCircos.Par$track.in.start - (track.num - - 1) * one.track - in.pos <- out.pos - RCircos.Par$track.height - - one.track * ( track.heights - 1) - } else if (side == "out") { - in.pos <- RCircos.Par$track.out.start + (track.num - - 1) * one.track - out.pos <- in.pos + RCircos.Par$track.height - } else { - stop("Incorrect track location. It must be \"in\" or \"out\".") - } - return(c(out.loc = out.pos, in.loc = in.pos)) -} - - -set.plot.circosParams <- function(){ - - # circos parameters - circosParams.my <- list() - - #use these two circosParams to adjust circle size - circosParams.my$plot.radius <- 2.15 - circosParams.my$genomeplot.margin <- 0.25 - - circosParams.my$track.background <- 'white' - circosParams.my$highlight.width <- 0.2 - circosParams.my$point.size <- 0.3 - circosParams.my$point.type <- 16 - circosParams.my$radius.len <- 3 - circosParams.my$chr.ideog.pos <- 3.2 - circosParams.my$highlight.pos <- 2.09 #3.35 - circosParams.my$chr.name.pos <- 2.14 #3.45 - circosParams.my$track.in.start <- 3.05 - circosParams.my$track.out.start <- 3.2 - - circosParams.my$tracks.inside <- 10 - circosParams.my$tracks.outside <- 1 - - circosParams.my$line.width <- 1 - circosParams.my$link.line.width <- 0.5 - - circosParams.my$text.size <- 0.6 - - circosParams.my$text.color <- 'black' - - circosParams.my$track.padding <- c(0.07, 0.0, 0.07, 0.0,0.07, 0) - - circosParams.my$grid.line.color <- 'lightgrey' - circosParams.my$chr.text.color <- 'grey' - - circosParams.my$track.heights <- c(0.85, 0.07, 0.07, 0.1, 0.1, 0.1) - circosParams.my$track.height <- 0.1 - circosParams.my$sub.tracks <- 1 - circosParams.my$heatmap.cols <- c(alpha('lightcoral', 1), - alpha('lightcoral', 0.5), - alpha('lightgrey',0.10), - alpha('olivedrab2', 0.3), - alpha('olivedrab2', 0.5), - alpha('olivedrab2',.7), - alpha('olivedrab2', 0.75), - alpha('olivedrab3', 0.9), - alpha('olivedrab4', 0.9)) - circosParams.my$heatmap.ranges <- c(0,1,3,4,8,16, 32,64,1000) - - #Set copynumber (and indel) colour scheme - circosParams.my$heatmap.color.gain <- c( alpha('lightgrey',0.10), alpha('olivedrab2', 0.3), alpha('olivedrab2', 0.5), alpha('olivedrab2',.7), alpha('olivedrab2', 0.75), alpha('olivedrab3', 0.9), alpha('olivedrab4', 0.9)) - circosParams.my$heatmap.ranges.gain <- c(0,2,4,8,16, 32,64,1000) - - circosParams.my$heatmap.ranges.loh <- c(0,1,1000) - circosParams.my$heatmap.color.loh <- c(alpha('lightcoral', 1), alpha('lightgrey',0.10)) - - circosParams.my$heatmap.key.gain.col <- alpha('olivedrab2', 0.3) - circosParams.my$heatmap.key.loh.col <- alpha('lightcoral', 1) - circosParams.my$heatmap.key.gain.title <- 'gain' - circosParams.my$heatmap.key.loh.title <- 'LOH' - - #tumour majorCN - circosParams.my$heatmap.data.col.gain <- 8 - #tumour minorCN - circosParams.my$heatmap.data.col.loh <- 7 - - #Indel colours - circosParams.my$indel.mhomology <- 'firebrick4' - circosParams.my$indel.repeatmediated <- 'firebrick1' - circosParams.my$indel.other <- 'firebrick3' - circosParams.my$indel.insertion <- 'darkgreen' - circosParams.my$indel.complex <- 'grey' - - return(circosParams.my) - -} - - -genomePlot <- function(snvs.file, indels.file, cnvs.file, rearrs.file, - sampleID, genome.v="hg19", ..., plot_title = NULL, - no_copynumber = FALSE, no_rearrangements = FALSE, no_indels = FALSE, out_format = "png", out_path = ".") { - - genome.ideogram = switch(genome.v, - "hg19" = "UCSC.HG19.Human.CytoBandIdeogram", - "hg38" = "UCSC.HG38.Human.CytoBandIdeogram") - data(list=genome.ideogram, package = "RCircos"); - species.cyto <- get(genome.ideogram); - - circosParams.my <- set.plot.circosParams() - - # rearrangement links colors - inv.col <- alpha('dodgerblue2', 1) - del.col <- alpha('coral2', 1) - dupl.col <- alpha('darkgreen', 1) - transloc.colour <- alpha('gray35', 1) - - #Set up height, width and resolution parameters - cPanelWidth = 0 - graph.height = 4100 - graph.wd_ht_ratio = 1 #width/height ratio - graph.width = graph.height * graph.wd_ht_ratio - graph.wd_res_ratio = (4100/550) - graph.res = graph.width/graph.wd_res_ratio - - graph.height.inches = graph.height/graph.res - graph.width.inches = graph.width/graph.res - - # substitutions - subs <- read.table(file = snvs.file, sep = '\t', header = TRUE) - # subs <- read.table(file = '/home/dapregi/tmp/snvs.tsv', sep = '\t', header = TRUE) - subs$color[(subs$ref=='C' & subs$alt=='A') | (subs$ref=='G' & subs$alt=='T')] <- 'royalblue' - subs$color[(subs$ref=='C' & subs$alt=='G') | (subs$ref=='G' & subs$alt=='C')] <- 'black' - subs$color[(subs$ref=='C' & subs$alt=='T') | (subs$ref=='G' & subs$alt=='A')] <- 'red' - subs$color[(subs$ref=='T' & subs$alt=='A') | (subs$ref=='A' & subs$alt=='T')] <- 'grey' - subs$color[(subs$ref=='T' & subs$alt=='C') | (subs$ref=='A' & subs$alt=='G')] <- 'green2' - subs$color[(subs$ref=='T' & subs$alt=='G') | (subs$ref=='A' & subs$alt=='C')] <- 'hotpink' - - # indels - indels <- read.table(file = indels.file, sep = '\t', header = TRUE) - # indels <- read.table(file = '/home/dapregi/tmp/indels.tsv', sep = '\t', header = TRUE) - dels.formatted <- data.frame() - ins.formatted <- data.frame() - if (!no_indels && !is.null(indels)) { - ins <- indels[which(indels$type=='I'),] - dels <- indels[which(indels$type=='D' | indels$type=='DI'),] - dels$color[dels$classification=='Microhomology-mediated'] <- circosParams.my$indel.mhomology - dels$color[dels$classification=='Repeat-mediated'] <- circosParams.my$indel.repeatmediated - dels$color[dels$classification=='None'] <- circosParams.my$indel.other - } - - # copy number - cv.data <- data.frame() - #Skip if no copynumber was requested - if (!no_copynumber) { - cv.data <- read.table(file = cnvs.file, sep = '\t', header = TRUE) - # cv.data <- read.table(file = '/home/dapregi/tmp/cnvs.tsv', sep = '\t', header = TRUE) - if(is.null(cv.data) || nrow(cv.data)==0){ - no_copynumber <- TRUE - } - } - - # rearrangements - rearrs <- data.frame() - if (!no_rearrangements) { - rearrs <- read.table(file = rearrs.file, sep = '\t', header = TRUE) - if(is.null(rearrs) || nrow(rearrs)==0){ - no_rearrangements <- TRUE - } - } - - ################################################################################ - - fn = file.path(out_path, paste(sampleID, ".genomePlot.", out_format, sep=''), fsep = .Platform$file.sep) - - if (out_format == 'png') { - png(file=fn, height=graph.height, width=(graph.width*(1/(1-cPanelWidth))), res=graph.res) - } else if (out_format == 'svg') { - svg(fn, height=graph.height.inches, width=graph.width.inches) - } else { - stop("Invalid file type. Only png and svg are supported"); - } - - RCircos.Set.Core.Components(cyto.info=species.cyto, chr.exclude=NULL, tracks.inside=circosParams.my$tracks.inside, - tracks.outside=circosParams.my$tracks.outside); - - # set plot colours and parameters - circosParams <- RCircos.Get.Plot.Parameters(); - circosParams$point.type <- circosParams.my$point.type - circosParams$point.size <- circosParams.my$point.size - RCircos.Reset.Plot.Parameters(circosParams) - - par(mar=c(0.001, 0.001, 0.001, 0.001)) - par(mai=c(circosParams.my$genomeplot.margin, circosParams.my$genomeplot.margin, circosParams.my$genomeplot.margin, circosParams.my$genomeplot.margin)) - plot.new() - plot.window(c(-circosParams.my$plot.radius,circosParams.my$plot.radius), c(-circosParams.my$plot.radius, circosParams.my$plot.radius)) - RCircos.Chromosome.Ideogram.Plot.my(circosParams.my$chr.text.color, circosParams.my$grid.line.color, circosParams.my$text.size); - - title(main = sampleID) - - if (!is.null(plot_title)) { - title(paste(plot_title, sep=''), line=-1); - } - - # substitutions - # start.time <- Sys.time() - # summary(subs) - # subs$distance <- 10^subs$logDistPrev - # subs <- subs[subs$distance<400000,] - # print(nrow(subs)) - if (exists("subs")) { - RCircos.Scatter.Plot.color(scatter.data=subs, data.col=6, track.num=1, side="in", scatter.colors = subs$color); - } - # end.time <- Sys.time() - # time.taken <- end.time - start.time - # print(time.taken) - - # Insertions - circosParams <- RCircos.Get.Plot.Parameters(); - circosParams$line.color <- 'white' - circosParams$highlight.width <- 0.2 - circosParams$max.layers <- 5 - circosParams$tile.color <- 'darkgreen' - RCircos.Reset.Plot.Parameters(circosParams) - if (exists("ins") && nrow(ins)>0) { - my.RCircos.Tile.Plot(tile.data=ins, track.num=5, side="in"); - } - - # Deletions - circosParams <- RCircos.Get.Plot.Parameters(); - circosParams$tile.color <- 'firebrick4' - RCircos.Reset.Plot.Parameters(circosParams) - if (exists("dels") && nrow(dels)>0) { - my.RCircos.Tile.Plot(tile.data=dels, track.num=6, side="in", tile.colors=dels$color); - } - - - # Copy number - if (exists('cv.data') && (nrow(cv.data)>0)) { - heatmap.ranges.major <-circosParams.my$heatmap.ranges.gain - heatmap.color.major <-circosParams.my$heatmap.color.gain - RCircos.Heatmap.Plot.my(heatmap.data=cv.data, data.col=5, track.num=7, side="in", heatmap.ranges=heatmap.ranges.major , heatmap.color=heatmap.color.major ); # major copy number - - heatmap.ranges.minor <-circosParams.my$heatmap.ranges.loh - heatmap.color.minor <-circosParams.my$heatmap.color.loh - RCircos.Heatmap.Plot.my(heatmap.data=cv.data, data.col=6, track.num=8, side="in", heatmap.ranges=heatmap.ranges.minor , heatmap.color=heatmap.color.minor ); # minor copy number - - } - - # Rearrangement - # Chromosome chromStart chromEnd Chromosome.1 chromStart.1 chromEnd.1 type - link.colors <- vector() - if (exists("rearrs")) { - link.data <- rearrs - link.colors[link.data$type=='INVERSION'] <- inv.col - link.colors[link.data$type=='DELETION'] <- del.col - link.colors[link.data$type=='TANDEM_DUPLICATION' | link.data$type=='DUPLICATION'] <- dupl.col - link.colors[link.data$type=='TRANSLOCATION'] <- transloc.colour - if (nrow( rearrs)>0) { - RCircos.Link.Plot.my(link.data = rearrs, track.num=9, by.chromosome=TRUE, link.colors); - } - } - - invisible(dev.off()) - -} - -#################################################################################### - -# Getting command arguments -option_list <- list( - make_option(c("--genome_version"), type="character", default="hg19", - help="Genome version", metavar="character"), - make_option(c("--plot_title"), type="character", default="", - help="Plot title", metavar="character"), - make_option(c("--no_copynumber"), action="store_true", default=FALSE, - help="No CNV"), - make_option(c("--no_rearrangements"), action="store_true", default=FALSE, - help="No rearrangements"), - make_option(c("--no_indels"), action="store_true", default=FALSE, - help="No indels"), - make_option(c("--out_format"), type="character", default="png", - help="Output format", metavar="character"), - make_option(c("--out_path"), type="character", default=".", - help="Output file path", metavar="character") - ) -parser <- OptionParser(usage = "%prog [options] snvs_file indels_file cnvs_file rearrs_file sampleId", option_list=option_list) -arguments <- parse_args(parser, positional_arguments = 5) -opt <- arguments$options -args <- arguments$args - - -genomePlot(args[1], args[2], args[3], args[4], args[5], genome.v=opt$genome_version, plot_title = opt$plot_title, - no_copynumber = opt$no_copynumber, no_rearrangements = opt$no_rearrangements, no_indels = opt$no_indels, - out_format = opt$out_format, out_path = opt$out_path) diff --git a/opencga-analysis/src/main/R/mutational-signature/mutational-signature.r b/opencga-analysis/src/main/R/mutational-signature/mutational-signature.r deleted file mode 100644 index 30517e786f1..00000000000 --- a/opencga-analysis/src/main/R/mutational-signature/mutational-signature.r +++ /dev/null @@ -1,170 +0,0 @@ -#install.packages("nnls") -#install.packages("ggplot2") -#install.packages("jsonlite") - -library(nnls) -library(ggplot2) -suppressMessages(library(jsonlite)) - -# Getting command arguments -args <- commandArgs(trailingOnly = TRUE) - -context <- args[1] -signatures <- args[2] -outdir <- args[3] - -print(paste("Genome context file: ", context)) -print(paste("Mutational signatures file: ", signatures)) -print(paste("Output directory: ", outdir)) - - -# Getting absolute counts for each SNV trinucleotide context -#dataTable <- read.table(args[1], sep = "\t", header = TRUE) -dataTable <- read.table(context, sep = "\t", header = TRUE) -dataTable <- as.vector(dataTable$Count) - -# Normalising frequencies to values between 0 and 1 like the signature table is -dataTable <- dataTable / sum(dataTable) - -# Getting signature probabilities reference table -#signatureTable <- read.table(args[2], sep = "\t", header = TRUE) -signatureTable <- read.table(signatures, sep = "\t", header = TRUE) -header <- scan(args[2], nlines = 1, what = character()) -tags <- header[2:length(header)]; -signatureTable <- as.matrix(signatureTable[1:96,2:length(header)]) - -# Applying non-negative least squares (NNLS) -coefficients <- nnls(signatureTable, dataTable) - -################################# -# Calculating confidence values # -################################# - -# Getting the original RSS value -RSSOriginal <- coefficients$deviance - -# Optimising the RSS score by iteratively removing signatures with coefficients -# less than 0.06 and refitting the model. -# Optimisation is limitted to 10 iterations to avoid large computaional time -# and because model improvement will not be significant after 2-3 iterations -coefficientsCopy <- coefficients # Copy of the nnls object -signatureTableCopy <- signatureTable # Copy of COSMIC signature table -counter <- 0 # Iteration counter -toRemove <- which(coefficientsCopy$x < 0.06 & coefficientsCopy$x > 0 ) - -# Optimisation -while(length(toRemove) > 0 && counter < 10) { - toRemove <- which(coefficientsCopy$x < 0.06 & coefficientsCopy$x > 0 ) - # Removing - for (i in 1:length(toRemove)) { - signatureTableCopy[, toRemove[i]] <- 0 - } - # Recalculating coefficients - coefficientsCopy <- nnls(signatureTableCopy, dataTable) - counter <- counter + 1 - toRemove <- which(coefficientsCopy$x < 0.06 & coefficientsCopy$x > 0 ) -} - -# Explained Sum of Squares -#ESS <- sum((coefficientsCopy$fitted - apply(dataTable, 1, mean)) ^ 2) - -# Residual Sum of Squares -RSS <- sum(coefficientsCopy$residuals^2) - -# Total Sum of Squares -#TSS <- ESS + RSS - -# Value to be reported: RSS -RSS <- round(RSS, digits = 4) - -##################### -# Writing JSON file # -##################### - -# Normalising coefficients to values between 0 and 100 -coefficients <- coefficients$x / sum(coefficients$x) * 100 - -# Creating signature names -#tags <- paste("Signature", 1:30, sep = " ") - -# Writing coefficients and RSS to JSON file -dfCoeff <- as.data.frame(t(data.frame(coefficients))) -colnames(dfCoeff) <- sapply(sub(' ', '', tags), tolower) -rownames(dfCoeff) <- NULL -df <- NULL -df$coefficients <- dfCoeff -df$rss <- RSS -j <- gsub("\\[|\\]", "", toJSON(df)) -write(j, paste0(outdir, "/signature_coefficients.json")) - - -############ -# Plotting # -############ - -# Ordering coefficients by value -orderedIndex <- order(coefficients, decreasing = TRUE) -orderedCoefficients <- coefficients[orderedIndex] -orderedTags <- tags[orderedIndex] - -# Filtering out coefficients below 5 -coeffs2 <- orderedCoefficients[orderedCoefficients > 5] -tags2 <- orderedTags[orderedCoefficients > 5] - -# Setting up boxes y-limits -cumulativeSum <- cumsum(c(0, coeffs2, 100 - sum(coeffs2))) -starts <- cumulativeSum[1:length(cumulativeSum) - 1] -ends <- cumulativeSum[2:length(cumulativeSum)] - -# Setting up boxes colours -colours <- c("darkblue", "darkgreen", "gold2", "darkorange", "darkred", "purple4", "blue1", "chartreuse3", "darkgoldenrod1", "chocolate1", "firebrick1", "darkorchid3") - -# Setting up ticks and ticks labels -tickPos <- 0:5 * 20 -tickNames <- paste0(tickPos, "%") - -# Setting up legend positions -numLegends <- 1:(length(coeffs2) + 1) -legendPos <- ((numLegends - (mean(numLegends))) * 7) + 50 - -# PLOTTING -png(paste0(outdir, "/signature_summary.png"), width = 1028, height = 800, type='cairo') -ggplot() + - # Boxes - geom_rect(mapping=aes(xmin = 0, xmax = 1, ymin = starts, ymax = ends - 0.3), - fill = c(colours[1:length(starts) - 1], "white"), - color = "black", alpha = 0.7) + - # Plot width - xlim(-1, 2) + - # Y-axis line - geom_segment(mapping = aes(x = -0.1, xend = -0.1, y = 0, yend = 100)) + - # Y-axis ticks - geom_segment(mapping = aes(x = -0.1, xend = -0.15, - y = tickPos, yend = tickPos)) + - # Y-axis labels - geom_text(mapping = aes(x = -0.3, y = tickPos), label = tickNames) + - # Y-axis title - geom_text(mapping = aes(x = -0.5, y = 50), label = "Signature contribution", - angle = 90) + - # Legend boxes - geom_rect(mapping = aes(xmin = 1.2, xmax = 1.25, - ymin = (legendPos - 1), ymax = (legendPos + 1)), - color = "black", - fill = c(colours[1:length(legendPos) - 1], "white"), - alpha = 0.7) + - # Legend text - geom_text(mapping = aes(x = 1.28, y = legendPos), label = c(tags2, "Other"), - hjust = 0) + - # Removing every other ggplot element from the plot - theme(axis.text.x = element_blank(), - axis.ticks.y = element_blank(), - axis.ticks.x = element_blank(), - axis.title.x = element_blank(), - axis.title.y = element_blank(), - axis.text.y = element_blank(), - panel.background = element_blank(), - panel.grid.major = element_blank(), - panel.grid.minor = element_blank(), - plot.background = element_blank()) - -garbage <- dev.off() diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index 546fca85fe7..e90ebf16e60 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -187,10 +187,6 @@ protected void run() throws ToolException { } catch (CatalogException e) { throw new ToolException("Error updating alignment quality control", e); } - - // Unset the executor info since it is executed by different executors, it will be indicated in the - // tool step attributes - getErm().setExecutorInfo(null); } private void runSamtoolsFlagstats() throws ToolException { diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java index ab6ddb08f4e..6b3ed00b912 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java @@ -349,15 +349,6 @@ protected final Enums.Resource getToolResource() { return this.getClass().getAnnotation(Tool.class).resource(); } - public ExecutionResultManager getErm() { - return erm; - } - - public OpenCgaTool setErm(ExecutionResultManager erm) { - this.erm = erm; - return this; - } - /** * Method called internally to obtain the list of steps. * From aed7911648a552bfa3060550d2d216de8c5e2903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 09:28:09 +0200 Subject: [PATCH 14/21] analysis: update circos/genome plot to take the R script from the analysis folder, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/circos/CircosLocalAnalysisExecutor.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java --- .../analysis/variant/circos/CircosLocalAnalysisExecutor.java | 5 ++++- .../variant/genomePlot/GenomePlotLocalAnalysisExecutor.java | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/circos/CircosLocalAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/circos/CircosLocalAnalysisExecutor.java index 356d53d1d84..41cdd0135d7 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/circos/CircosLocalAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/circos/CircosLocalAnalysisExecutor.java @@ -30,6 +30,7 @@ import org.opencb.commons.utils.DockerUtils; import org.opencb.opencga.analysis.ResourceUtils; import org.opencb.opencga.analysis.StorageToolExecutor; +import org.opencb.opencga.analysis.variant.genomePlot.GenomePlotAnalysis; import org.opencb.opencga.analysis.variant.manager.VariantStorageManager; import org.opencb.opencga.catalog.exceptions.CatalogException; import org.opencb.opencga.core.common.GitRepositoryState; @@ -48,6 +49,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.*; @@ -133,7 +135,8 @@ public void run() throws ToolException, IOException, CatalogException { if (MapUtils.isEmpty(errors)) { // Execute R script // circos.R ./snvs.tsv ./indels.tsv ./cnvs.tsv ./rearrs.tsv SampleId - String rScriptPath = getExecutorParams().getString("opencgaHome") + "/analysis/R/genome-plot"; + String rScriptPath = Paths.get(getExecutorParams().getString("opencgaHome")).resolve("analysis/" + GenomePlotAnalysis.ID) + .toAbsolutePath().toString(); List> inputBindings = new ArrayList<>(); inputBindings.add(new AbstractMap.SimpleEntry<>(rScriptPath, DOCKER_INPUT_PATH)); AbstractMap.SimpleEntry outputBinding = new AbstractMap.SimpleEntry<>(getOutDir() diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java index 25c1ebfc8d3..64e061c7668 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/variant/genomePlot/GenomePlotLocalAnalysisExecutor.java @@ -122,7 +122,8 @@ public void run() throws ToolException, IOException, CatalogException { if (MapUtils.isEmpty(errors)) { // Execute R script // circos.R ./snvs.tsv ./indels.tsv ./cnvs.tsv ./rearrs.tsv SampleId - String rScriptPath = getExecutorParams().getString("opencgaHome") + "/analysis/R/" + getToolId(); + String rScriptPath = Paths.get(getExecutorParams().getString("opencgaHome")).resolve("analysis/" + GenomePlotAnalysis.ID) + .toAbsolutePath().toString(); List> inputBindings = new ArrayList<>(); inputBindings.add(new AbstractMap.SimpleEntry<>(rScriptPath, DOCKER_INPUT_PATH)); AbstractMap.SimpleEntry outputBinding = new AbstractMap.SimpleEntry<>(getOutDir() From 592c793745002c9ec2f53356b9117c90bef3cfb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 09:45:29 +0200 Subject: [PATCH 15/21] tests: add and update JUnit tests for sample QC, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/OpenCGATestExternalResource.java modified: opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/VariantAnalysisTest.java deleted: opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T.copynumber.caveman.vcf.gz deleted: opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz deleted: opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T_vs_AR2.10039966-01G.annot.pindel.vcf.gz new file: opencga-storage/opencga-storage-core/src/test/resources/cancer-cnvs.vcf.gz new file: opencga-storage/opencga-storage-core/src/test/resources/cancer-indels.vcf.gz new file: opencga-storage/opencga-storage-core/src/test/resources/cancer-rearrs.vcf.gz new file: opencga-storage/opencga-storage-core/src/test/resources/cancer-snvs.vcf.gz new file: opencga-storage/opencga-storage-core/src/test/resources/genome-plot-config.json --- .../variant/OpenCGATestExternalResource.java | 9 +- .../analysis/variant/VariantAnalysisTest.java | 289 ++++++++++++++++-- ...AR2.10039966-01T.copynumber.caveman.vcf.gz | Bin 3137 -> 0 bytes ...01T_vs_AR2.10039966-01G.annot.brass.vcf.gz | Bin 17788 -> 0 bytes ...1T_vs_AR2.10039966-01G.annot.pindel.vcf.gz | Bin 12111 -> 0 bytes .../src/test/resources/cancer-cnvs.vcf.gz | Bin 0 -> 4997 bytes .../src/test/resources/cancer-indels.vcf.gz | Bin 0 -> 22636 bytes .../src/test/resources/cancer-rearrs.vcf.gz | Bin 0 -> 42980 bytes .../src/test/resources/cancer-snvs.vcf.gz | Bin 0 -> 18183 bytes .../test/resources/genome-plot-config.json | 32 ++ 10 files changed, 311 insertions(+), 19 deletions(-) delete mode 100644 opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T.copynumber.caveman.vcf.gz delete mode 100644 opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz delete mode 100644 opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T_vs_AR2.10039966-01G.annot.pindel.vcf.gz create mode 100644 opencga-storage/opencga-storage-core/src/test/resources/cancer-cnvs.vcf.gz create mode 100644 opencga-storage/opencga-storage-core/src/test/resources/cancer-indels.vcf.gz create mode 100644 opencga-storage/opencga-storage-core/src/test/resources/cancer-rearrs.vcf.gz create mode 100644 opencga-storage/opencga-storage-core/src/test/resources/cancer-snvs.vcf.gz create mode 100644 opencga-storage/opencga-storage-core/src/test/resources/genome-plot-config.json diff --git a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/OpenCGATestExternalResource.java b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/OpenCGATestExternalResource.java index 1f05dfc5dd3..07d25325fc5 100644 --- a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/OpenCGATestExternalResource.java +++ b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/OpenCGATestExternalResource.java @@ -252,12 +252,17 @@ public Path isolateOpenCGA() throws IOException { // Files.copy(inputStream, opencgaHome.resolve("examples") // .resolve("1k.chr1.phase3_shapeit2_mvncall_integrated_v5.20130502.genotypes.vcf.gz"), StandardCopyOption.REPLACE_EXISTING); - // Mutational signatue analysis + // Mutational signature analysis files Path analysisPath = Files.createDirectories(opencgaHome.resolve("analysis/mutational-signature")).toAbsolutePath(); inputStream = new FileInputStream("../opencga-app/app/analysis/mutational-signature/sv_clustering.R"); Files.copy(inputStream, analysisPath.resolve("sv_clustering.R"), StandardCopyOption.REPLACE_EXISTING); - // Pedigree graph analysis + // Genome plot analysis files + analysisPath = Files.createDirectories(opencgaHome.resolve("analysis/genome-plot")).toAbsolutePath(); + inputStream = new FileInputStream("../opencga-app/app/analysis/genome-plot/circos.R"); + Files.copy(inputStream, analysisPath.resolve("circos.R"), StandardCopyOption.REPLACE_EXISTING); + + // Pedigree graph analysis files analysisPath = Files.createDirectories(opencgaHome.resolve("analysis/pedigree-graph")).toAbsolutePath(); inputStream = new FileInputStream("../opencga-app/app/analysis/pedigree-graph/ped.R"); Files.copy(inputStream, analysisPath.resolve("ped.R"), StandardCopyOption.REPLACE_EXISTING); diff --git a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/VariantAnalysisTest.java b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/VariantAnalysisTest.java index 59d2ba43d5d..03de8ce62de 100644 --- a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/VariantAnalysisTest.java +++ b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/variant/VariantAnalysisTest.java @@ -18,6 +18,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.mutable.MutableInt; import org.hamcrest.CoreMatchers; import org.junit.*; @@ -35,11 +36,13 @@ import org.opencb.biodata.models.variant.StudyEntry; import org.opencb.biodata.models.variant.avro.VariantType; import org.opencb.biodata.models.variant.metadata.SampleVariantStats; +import org.opencb.commons.datastore.core.Event; import org.opencb.commons.datastore.core.ObjectMap; import org.opencb.commons.datastore.core.Query; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.opencga.TestParamConstants; import org.opencb.opencga.analysis.clinical.ClinicalAnalysisLoadTask; +import org.opencb.opencga.analysis.sample.qc.SampleQcAnalysis; import org.opencb.opencga.analysis.tools.ToolRunner; import org.opencb.opencga.analysis.variant.gwas.GwasAnalysis; import org.opencb.opencga.analysis.variant.hrdetect.HRDetectAnalysis; @@ -130,8 +133,10 @@ public class VariantAnalysisTest { private static String daughter = "NA19600"; public static final String CANCER_STUDY = "cancer"; - private static String cancer_sample = "AR2.10039966-01T"; - private static String germline_sample = "AR2.10039966-01G"; + private static String cancer_sample = "HCC1954"; // "AR2.10039966-01T"; + private static String germline_sample = "HCC1954BL"; //""AR2.10039966-01G"; + + private static String genomePlotConfigFilename = "genome-plot-config.json"; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -234,16 +239,21 @@ public void setUp() throws Throwable { config.put(VariantStorageOptions.LOAD_SPLIT_DATA.key(), VariantStorageEngine.SplitData.MULTI); File file; - file = opencga.createFile(CANCER_STUDY, "AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz", token); + file = opencga.createFile(CANCER_STUDY, "cancer-rearrs.vcf.gz", token); + variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); + file = opencga.createFile(CANCER_STUDY, "cancer-cnvs.vcf.gz", token); variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); - file = opencga.createFile(CANCER_STUDY, "AR2.10039966-01T.copynumber.caveman.vcf.gz", token); + file = opencga.createFile(CANCER_STUDY, "cancer-indels.vcf.gz", token); variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); - file = opencga.createFile(CANCER_STUDY, "AR2.10039966-01T_vs_AR2.10039966-01G.annot.pindel.vcf.gz", token); + file = opencga.createFile(CANCER_STUDY, "cancer-snvs.vcf.gz", token); variantStorageManager.index(CANCER_STUDY, file.getId(), opencga.createTmpOutdir("_index"), config, token); SampleUpdateParams updateParams = new SampleUpdateParams().setSomatic(true); catalogManager.getSampleManager().update(CANCER_STUDY, cancer_sample, updateParams, null, token); + opencga.createFile(CANCER_STUDY, genomePlotConfigFilename, token); + assertEquals(genomePlotConfigFilename, catalogManager.getFileManager().get(CANCER_STUDY, genomePlotConfigFilename, QueryOptions.empty(), token).first().getName()); + opencga.getStorageConfiguration().getVariant().setDefaultEngine(storageEngine); VariantStorageEngine engine = opencga.getStorageEngineFactory().getVariantStorageEngine(storageEngine, DB_NAME); if (storageEngine.equals(HadoopVariantStorageEngine.STORAGE_ENGINE_ID)) { @@ -834,19 +844,8 @@ public void testMutationalSignatureCatalogueSV() throws Exception { VariantQuery query = new VariantQuery() .sample(cancer_sample) .type(VariantType.SV.name()) - //.file("AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz"); - .fileData("AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz:BAS>=0;BKDIST>=-1") .region("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y"); - //https://ws.opencb.org/opencga-test/webservices/rest/v2/analysis/variant/mutationalSignature/query - // ?study=serena@cancer38:test38 - // &fitting=false - // &sample=AR2.10039966-01T - // &fileData=AR2.10039966-01T_vs_AR2.10039966-01G.annot.brass.vcf.gz:BAS>=0;BKDIST>=-1;EXT_PS_SOM>=4;EXT_RC_SOM>=0 - // ®ion=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y - // &type=SV - - params.setQuery(query.toJson()); params.setSkip("fitting"); @@ -1028,7 +1027,7 @@ public void testHRDetect() throws Exception { System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); } if (hrDetect.getScores().containsKey("del.mh.prop")) { - Assert.assertEquals(-1.5702984, hrDetect.getScores().getFloat("del.mh.prop"), 0.00001f); + Assert.assertEquals(-2.6106846, hrDetect.getScores().getFloat("del.mh.prop"), 0.00001f); return; } } @@ -1113,6 +1112,262 @@ public void testClinicalAnalysisLoading() throws IOException, ToolException, Cat Assert.assertEquals(ca2Id, clinicalAnalysis.getId()); } + @Test + public void testSampleQcVS() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_variant_stats")); + + String sampleId = file.getSampleIds().get(0); + Sample sample = catalogManager.getSampleManager().get(STUDY, sampleId, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(sampleId); + params.setVsQuery(new AnnotationVariantQueryParams().setRegion("1,2")); + params.setVsId("regions-1-2"); + params.setVsDescription("Sample variant stats on regions 1,2"); + params.setSkip(SampleQcAnalysisParams.SIGNATURE_SKIP_VALUE + "," + SampleQcAnalysisParams.GENOME_PLOT_SKIP_VALUE); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + sample = catalogManager.getSampleManager().get(STUDY, sampleId, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertTrue(sample.getQualityControl().getVariant().getVariantStats().stream().map(SampleQcVariantStats::getId).collect(Collectors.toList()).contains(params.getVsId())); + assertTrue(sample.getQualityControl().getVariant().getVariantStats().stream().map(SampleQcVariantStats::getDescription).collect(Collectors.toList()).contains(params.getVsDescription())); + } + + @Test + public void testSampleQcVSSignatureGenomePlotNoSomatic() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_variant_stats")); + + String sampleId = file.getSampleIds().get(0); + Sample sample = catalogManager.getSampleManager().get(STUDY, sampleId, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(sampleId); + params.setVsQuery(new AnnotationVariantQueryParams().setRegion("2,1")); + params.setVsId("regions-2-1"); + params.setVsDescription("Sample variant stats on regions 2,1"); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + // Expected three events with the message "is not somatic" (for catalogue, fitting and genome plot) + Assert.assertEquals(3, result.getEvents().stream().map(Event::getMessage).filter(msg -> msg.contains(SampleQcAnalysis.getSampleIsNotSomaticMsg(sampleId))).collect(Collectors.toList()).size()); + + sample = catalogManager.getSampleManager().get(STUDY, sampleId, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertTrue(sample.getQualityControl().getVariant().getVariantStats().stream().map(SampleQcVariantStats::getId).collect(Collectors.toList()).contains(params.getVsId())); + assertTrue(sample.getQualityControl().getVariant().getVariantStats().stream().map(SampleQcVariantStats::getDescription).collect(Collectors.toList()).contains(params.getVsDescription())); + } + + @Test + public void testSampleQcVSError() throws IOException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_variant_stats_error")); + + Sample sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(cancer_sample); + params.setVsQuery(new AnnotationVariantQueryParams().setRegion("1,2")); + params.setVsId("regions-1-2"); + params.setVsDescription("Sample variant stats on regions 1,2"); + params.setSkip(SampleQcAnalysisParams.SIGNATURE_SKIP_VALUE + "," + SampleQcAnalysisParams.GENOME_PLOT_SKIP_VALUE); + + ExecutionResult result = null; + try { + result = toolRunner.execute(SampleQcAnalysis.class, CANCER_STUDY, params, outDir, null, token); + } catch (ToolException e) { + System.out.println("result = " + result); + e.printStackTrace(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + System.out.println("outDir = " + outDir); + return; + } + fail(); + } + + @Test + public void testSampleQcMutationalSignatureCatalogueSNV() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_mutational_signature_catalogue_snv")); + + Sample sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(cancer_sample); + params.setMsId("catalogue-snv-1"); + params.setMsDescription("Catalogue SNV #1"); + VariantQuery query = new VariantQuery() + .sample(cancer_sample) + .type(VariantType.SNV.name()) + .region("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y"); + params.setMsQuery(query.toJson()); + + params.setSkip(StringUtils.join(Arrays.asList( + SampleQcAnalysisParams.VARIANT_STATS_SKIP_VALUE, + SampleQcAnalysisParams.SIGNATURE_FITTING_SKIP_VALUE, + SampleQcAnalysisParams.GENOME_PLOT_SKIP_VALUE), ",")); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, CANCER_STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getId).collect(Collectors.toList()).contains(params.getMsId())); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getDescription).collect(Collectors.toList()).contains(params.getMsDescription())); + } + + @Test + public void testSampleQcMutationalSignatureCatalogueSV() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_mutational_signature_catalogue_sv")); + + Sample sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(cancer_sample); + params.setMsId("catalogue-sv-1"); + params.setMsDescription("Catalogue SV #1"); + VariantQuery query = new VariantQuery() + .sample(cancer_sample) + .type(VariantType.SV.name()) + .region("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y"); + params.setMsQuery(query.toJson()); + + params.setSkip(StringUtils.join(Arrays.asList( + SampleQcAnalysisParams.VARIANT_STATS_SKIP_VALUE, + SampleQcAnalysisParams.SIGNATURE_FITTING_SKIP_VALUE, + SampleQcAnalysisParams.GENOME_PLOT_SKIP_VALUE), ",")); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, CANCER_STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getId).collect(Collectors.toList()).contains(params.getMsId())); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getDescription).collect(Collectors.toList()).contains(params.getMsDescription())); + } + + @Test + public void testSampleQcMutationalSignatureCatalogueFittingSV() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_mutational_signature_catalogue_fitting_sv")); + + Sample sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(cancer_sample); + params.setMsId("catalogue-sv-2"); + params.setMsDescription("Catalogue SV #2"); + VariantQuery query = new VariantQuery() + .sample(cancer_sample) + .type(VariantType.SV.name()) + .region("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y"); + params.setMsQuery(query.toJson()); + params.setMsFitId("fitting-2"); + params.setMsFitMethod("FitMS"); + params.setMsFitSigVersion("RefSigv2"); + params.setMsFitOrgan("Breast"); + params.setMsFitNBoot(200); + params.setMsFitThresholdPerc(5.0f); + params.setMsFitThresholdPval(0.05f); + params.setMsFitMaxRareSigs(1); +// params.setMsFitSignaturesFile(signatureFileId); +// params.setMsFitRareSignaturesFile(signatureFileId); + + params.setSkip(StringUtils.join(Arrays.asList( + SampleQcAnalysisParams.VARIANT_STATS_SKIP_VALUE, + SampleQcAnalysisParams.GENOME_PLOT_SKIP_VALUE), ",")); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, CANCER_STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getId).collect(Collectors.toList()).contains(params.getMsId())); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getDescription).collect(Collectors.toList()).contains(params.getMsDescription())); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().flatMap(s -> s.getFittings().stream().map(SignatureFitting::getId)).collect(Collectors.toList()).contains(params.getMsFitId())); + } + + @Test + public void testSampleQcGenomePlot() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_genome_plot")); + + Sample sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + File genomePlotConfigFile = catalogManager.getFileManager().get(CANCER_STUDY, genomePlotConfigFilename, QueryOptions.empty(), token).first(); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(cancer_sample); + params.setGpId("genome-plot1"); + params.setGpDescription("Genome plot description 1"); + params.setGpConfigFile(genomePlotConfigFile.getId()); + + params.setSkip(StringUtils.join(Arrays.asList( + SampleQcAnalysisParams.VARIANT_STATS_SKIP_VALUE, + SampleQcAnalysisParams.SIGNATURE_SKIP_VALUE), ",")); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, CANCER_STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertEquals(params.getGpId(), sample.getQualityControl().getVariant().getGenomePlot().getId()); + assertEquals(params.getGpDescription(), sample.getQualityControl().getVariant().getGenomePlot().getDescription()); + } + + @Test + public void testSampleQcMutationalSignatureCatalogueFittingSVGenomePlot() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_sample_qc_mutational_signature_catalogue_fitting_sv")); + + Sample sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + + File genomePlotConfigFile = catalogManager.getFileManager().get(CANCER_STUDY, genomePlotConfigFilename, QueryOptions.empty(), token).first(); + + SampleQcAnalysisParams params = new SampleQcAnalysisParams(); + params.setSample(cancer_sample); + + // Mutational signature + params.setMsId("catalogue-sv-3"); + params.setMsDescription("Catalogue SV #3"); + VariantQuery query = new VariantQuery() + .sample(cancer_sample) + .type(VariantType.SV.name()) + .region("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y"); + params.setMsQuery(query.toJson()); + params.setMsFitId("fitting-2"); + params.setMsFitMethod("FitMS"); + params.setMsFitSigVersion("RefSigv2"); + params.setMsFitOrgan("Breast"); + params.setMsFitNBoot(200); + params.setMsFitThresholdPerc(5.0f); + params.setMsFitThresholdPval(0.05f); + params.setMsFitMaxRareSigs(1); + + // Genome plot params + params.setGpId("genome-plot3"); + params.setGpDescription("Genome plot description 3"); + params.setGpConfigFile(genomePlotConfigFile.getId()); + + params.setSkip(SampleQcAnalysisParams.VARIANT_STATS_SKIP_VALUE); + + ExecutionResult result = toolRunner.execute(SampleQcAnalysis.class, CANCER_STUDY, params, outDir, null, token); + System.out.println("outDir = " + outDir); + + sample = catalogManager.getSampleManager().get(CANCER_STUDY, cancer_sample, new QueryOptions(), token).first(); + System.out.println("sample.getQualityControl() = " + sample.getQualityControl()); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getId).collect(Collectors.toList()).contains(params.getMsId())); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().map(Signature::getDescription).collect(Collectors.toList()).contains(params.getMsDescription())); + assertTrue(sample.getQualityControl().getVariant().getSignatures().stream().flatMap(s -> s.getFittings().stream().map(SignatureFitting::getId)).collect(Collectors.toList()).contains(params.getMsFitId())); + assertEquals(params.getGpId(), sample.getQualityControl().getVariant().getGenomePlot().getId()); + assertEquals(params.getGpDescription(), sample.getQualityControl().getVariant().getGenomePlot().getDescription()); + } + @Test public void testCellbaseConfigure() throws Exception { String project = "Project_test_cellbase_configure"; diff --git a/opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T.copynumber.caveman.vcf.gz b/opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T.copynumber.caveman.vcf.gz deleted file mode 100644 index 3cd9a6f34943f460def5e0e1de5936060e4e9ce6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3137 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 Tephc<{@ect@f1=FaG?PJQd{E~ diff --git a/opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T_vs_AR2.10039966-01G.annot.pindel.vcf.gz b/opencga-storage/opencga-storage-core/src/test/resources/AR2.10039966-01T_vs_AR2.10039966-01G.annot.pindel.vcf.gz deleted file mode 100644 index 69eb2b0300281cb2572a5b46c9b51a8e7fdb6f09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12111 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 PD&7AA?m8_?MP~p2*p9+I literal 0 HcmV?d00001 diff --git a/opencga-storage/opencga-storage-core/src/test/resources/cancer-indels.vcf.gz b/opencga-storage/opencga-storage-core/src/test/resources/cancer-indels.vcf.gz new file mode 100644 index 0000000000000000000000000000000000000000..be382289e90192398f57aab3b135b24b6f3ed44d GIT binary patch literal 22636 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 literal 0 HcmV?d00001 diff --git a/opencga-storage/opencga-storage-core/src/test/resources/cancer-rearrs.vcf.gz b/opencga-storage/opencga-storage-core/src/test/resources/cancer-rearrs.vcf.gz new file mode 100644 index 0000000000000000000000000000000000000000..1efdc418c9e647820475d9d599ef074135e0518c GIT binary patch literal 42980 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¨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 literal 0 HcmV?d00001 diff --git a/opencga-storage/opencga-storage-core/src/test/resources/genome-plot-config.json b/opencga-storage/opencga-storage-core/src/test/resources/genome-plot-config.json new file mode 100644 index 00000000000..dbec75c380f --- /dev/null +++ b/opencga-storage/opencga-storage-core/src/test/resources/genome-plot-config.json @@ -0,0 +1,32 @@ +{"density": "MEDIUM", + "query": {"sample": "HCC1954"}, + "title": "HCC1954", + "tracks": [{"id": "snv", + "query": { + "region": "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y", + "sample": "HCC1954", + "study": "test@project:cancer", + "type": "SNV"}, + "type": "SNV"}, + {"id": "indel", + "query": { + "region": "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y", + "sample": "HCC1954", + "study": "test@project:cancer", + "type": "INDEL,INSERTION,DELETION"}, + "type": "INDEL"}, + {"id": "cnv1", + "query": { + "region": "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y", + "sample": "HCC1954", + "study": "test@project:cancer", + "type": "COPY_NUMBER"}, + "type": "COPY-NUMBER"}, + {"id": "rearr1", + "query": { + "region": "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,X,Y", + "sample": "HCC1954", + "study": "test@project:cancer", + "type": "SV"}, + "type": "REARRANGEMENT"}] +} \ No newline at end of file From 9f6913baec78918152e6b90cea12b63cd9d16b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 17:11:28 +0200 Subject: [PATCH 16/21] analysis: add a new tool step to update the alignment QC, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java --- .../alignment/qc/AlignmentQcAnalysis.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index e90ebf16e60..03ea6d725cd 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -63,6 +63,7 @@ public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { public static final String SAMTOOLS_FLAGSTATS_STEP = "samtools-flagstats"; public static final String PLOT_BAMSTATS_STEP = "plot-bamstats"; public static final String FASTQC_METRICS_STEP = "fastqc-metrics"; + public static final String UPDATE_FILE_ALIGNMENT_QC_STEP = "update-file-alignment-qc"; @ToolParams protected final AlignmentQcParams alignmentQcParams = new AlignmentQcParams(); @@ -72,6 +73,7 @@ public class AlignmentQcAnalysis extends OpenCgaToolScopeStudy { private boolean runSamtoolsStatsStep = true; private boolean runSamptoolsFlagstatsStep = true; private boolean runFastqcMetricsStep = true; + private boolean updateQcStep = true; private File catalogBamFile; private File catalogStatsFile; @@ -138,6 +140,8 @@ protected void check() throws Exception { logger.warn(msg); } } + + updateQcStep = (runSamptoolsFlagstatsStep || runSamtoolsStatsStep || runFastqcMetricsStep) ? true : false; } @Override @@ -153,13 +157,17 @@ protected List getSteps() { if (runFastqcMetricsStep) { steps.add(FASTQC_METRICS_STEP); } + if (updateQcStep) { + steps.add(UPDATE_FILE_ALIGNMENT_QC_STEP); + } return steps; } @Override protected void run() throws ToolException { // Create the tool runner - toolRunner = new ToolRunner(opencgaHome, catalogManager, StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); + toolRunner = new ToolRunner(getOpencgaHome().toString(), catalogManager, + StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); // Get alignment QC metrics to update if (catalogBamFile.getQualityControl() != null) { @@ -179,13 +187,8 @@ protected void run() throws ToolException { if (runFastqcMetricsStep) { step(FASTQC_METRICS_STEP, this::runFastqcMetrics); } - - // Finally, update file quality control - try { - FileUpdateParams fileUpdateParams = new FileUpdateParams().setQualityControl(fileQc); - catalogManager.getFileManager().update(study, catalogBamFile.getId(), fileUpdateParams, QueryOptions.empty(), token); - } catch (CatalogException e) { - throw new ToolException("Error updating alignment quality control", e); + if (updateQcStep) { + step(UPDATE_FILE_ALIGNMENT_QC_STEP, this::updateAlignmentQc); } } @@ -373,4 +376,14 @@ private void runFastqcMetrics() throws ToolException { FastQcMetrics fastQcMetrics = AlignmentFastQcMetricsAnalysis.parseResults(outPath, configuration.getJobDir()); fileQc.getAlignment().setFastQcMetrics(fastQcMetrics); } + + private void updateAlignmentQc() throws ToolException { + // Finally, update file quality control + try { + FileUpdateParams fileUpdateParams = new FileUpdateParams().setQualityControl(fileQc); + catalogManager.getFileManager().update(study, catalogBamFile.getId(), fileUpdateParams, QueryOptions.empty(), token); + } catch (CatalogException e) { + throw new ToolException("Error updating alignment quality control", e); + } + } } From 4518a72a6a7ea6ec6a937e20b81c435283cdf311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Wed, 19 Jun 2024 17:13:05 +0200 Subject: [PATCH 17/21] analysis: minor improvements in sample QC analysis, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java modified: opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java --- .../opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java | 3 ++- .../java/org/opencb/opencga/analysis/tools/OpenCgaTool.java | 2 +- .../opencga/core/tools/result/ExecutionResultManager.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java index 48f2aa320f9..bf1ad753d82 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java @@ -242,7 +242,8 @@ protected List getSteps() { @Override protected void run() throws ToolException { // Create the tool runner - toolRunner = new ToolRunner(opencgaHome, catalogManager, StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); + toolRunner = new ToolRunner(getOpencgaHome().toString(), catalogManager, + StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); // Sample variant stats if (runVariantStats) { diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java index 741f29acd20..bea867db7c3 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java @@ -70,7 +70,7 @@ public abstract class OpenCgaTool { protected VariantStorageManager variantStorageManager; private String jobId; - protected String opencgaHome; + private String opencgaHome; protected String token; protected final ObjectMap params; diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java b/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java index 426a39e9920..b1e716bd41f 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/tools/result/ExecutionResultManager.java @@ -312,7 +312,7 @@ public interface ExecutionResultFunction { R apply(ExecutionResult execution) throws ToolException; } - public synchronized R updateResult(ExecutionResultFunction update) throws ToolException { + private synchronized R updateResult(ExecutionResultFunction update) throws ToolException { ExecutionResult execution = read(); R apply = update.apply(execution); write(execution); From 06d46a751e709018670bf8cd6233bfa3bc6e0d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Thu, 20 Jun 2024 09:50:20 +0200 Subject: [PATCH 18/21] analysis: read the stderr file to get error messages when something was wrong running dockers (samtools stats/flagstats and fastqc), #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java modified: opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java --- .../qc/AlignmentFastQcMetricsAnalysis.java | 4 +- .../alignment/qc/AlignmentQcAnalysis.java | 8 +- .../DockerWrapperAnalysisExecutor.java | 30 ++- .../alignment/AlignmentAnalysisTest.java | 196 +++++++++++++----- 4 files changed, 182 insertions(+), 56 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java index a556927fae5..e63f3e91a42 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentFastQcMetricsAnalysis.java @@ -22,6 +22,7 @@ import org.opencb.commons.datastore.core.Query; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.opencga.analysis.tools.OpenCgaToolScopeStudy; +import org.opencb.opencga.analysis.wrappers.executors.DockerWrapperAnalysisExecutor; import org.opencb.opencga.analysis.wrappers.fastqc.FastqcWrapperAnalysisExecutor; import org.opencb.opencga.catalog.db.api.FileDBAdaptor; import org.opencb.opencga.core.exceptions.ToolException; @@ -124,7 +125,8 @@ public static FastQcMetrics parseResults(Path outDir, String confJobDir) throws } fastQcMetrics.setFiles(relativePaths); } else { - throw new ToolException("Something wrong happened: FastQC file " + fastQcPath.getFileName() + " not found!"); + String msg = DockerWrapperAnalysisExecutor.getStdErrMessage("Something wrong happened running FastQC analysis.", outDir); + throw new ToolException(msg); } } catch (IOException e) { new ToolException("Error parsing Alignment FastQC Metrics file: " + e.getMessage()); diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index 03ea6d725cd..99301822675 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -231,7 +231,9 @@ private void runSamtoolsFlagstats() throws ToolException { throw new ToolException("Error copying Samtools flagstat results", e); } } else { - throw new ToolException("Something wrong happened running Samtools flagstat analysis"); + String msg = DockerWrapperAnalysisExecutor.getStdErrMessage("Something wrong happened running Samtools flagstat analysis.", + outPath); + throw new ToolException(msg); } // Check results and update QC file @@ -283,7 +285,9 @@ private void runSamtoolsStats() throws ToolException { throw new ToolException("Error copying Samtools stats results", e); } } else { - throw new ToolException("Something wrong happened running Samtools stats analysis"); + String msg = DockerWrapperAnalysisExecutor.getStdErrMessage("Something wrong happened running Samtools stats analysis.", + outPath); + throw new ToolException(msg); } // Check results and update QC file diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java index 9f5f6cb9756..740b66fbfc1 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/wrappers/executors/DockerWrapperAnalysisExecutor.java @@ -13,11 +13,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; + +import static org.apache.commons.io.FileUtils.readLines; public abstract class DockerWrapperAnalysisExecutor extends OpenCgaToolExecutor { @@ -212,6 +215,25 @@ public static List> getInputFilenames(String inputFile, Set return inputFilenames; } + public static String getStdErrMessage(String title, Path outPath) { + List errMessages = new ArrayList<>(); + errMessages.add(title); + + java.io.File stderrFile = outPath.resolve(DockerWrapperAnalysisExecutor.STDERR_FILENAME).toFile(); + if (Files.exists(stderrFile.toPath())) { + try { + errMessages.addAll(readLines(stderrFile, Charset.defaultCharset())); + } catch (IOException e) { + errMessages.add("It could not read the stderr file '" + stderrFile + "' to retrieve error messages:"); + errMessages.addAll(Arrays.stream(e.getStackTrace()).map(StackTraceElement::toString).collect(Collectors.toList())); + } + } else { + errMessages.add("The stderr file '" + stderrFile + "' does not exist in order to retrieve error messages."); + } + + return StringUtils.join(errMessages, "\n"); + } + protected static boolean skipParameter(String param) { switch (param) { case "opencgaHome": diff --git a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java index add0d866470..1d7416f7872 100644 --- a/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java +++ b/opencga-analysis/src/test/java/org/opencb/opencga/analysis/alignment/AlignmentAnalysisTest.java @@ -365,61 +365,98 @@ public void testAlignmentQcFastqcAndDoNotOverwrite() throws IOException, ToolExc System.out.println("outdir = " + outDir); } - private void checkSamtoolsStats(SamtoolsStats stats) { - System.out.println("stats = " + stats); - assertTrue(stats != null); - assertEquals(108, stats.getSequences()); - assertEquals(55, stats.getLastFragments()); - assertEquals(0, stats.getReadsDuplicated()); - assertEquals(0, stats.getReadsQcFailed()); - assertEquals(10800, stats.getTotalLength()); - assertEquals(10047, stats.getBasesMappedCigar()); - assertEquals(49, stats.getMismatches()); - assertEquals(31.0, stats.getAverageQuality(), 0.001f); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals.png")).collect(Collectors.toList()).size()); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals3.png")).collect(Collectors.toList()).size()); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("coverage.png")).collect(Collectors.toList()).size()); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("insert-size.png")).collect(Collectors.toList()).size()); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("gc-content.png")).collect(Collectors.toList()).size()); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("acgt-cycles.png")).collect(Collectors.toList()).size()); - assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals2.png")).collect(Collectors.toList()).size()); - } + @Test + public void testFailureOnAlignmentQcSamtoolsFlagstat() throws IOException, ToolException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_failure_on_alignment_qc_samtools_flagstat")); - private void checkSamtoolsFlagstats(SamtoolsFlagstats flagstats) { - System.out.println("flagstats = " + flagstats); - assertTrue(flagstats != null); - assertEquals(108, flagstats.getTotalReads()); - assertEquals(0, flagstats.getSecondaryAlignments()); - assertEquals(53, flagstats.getRead1()); - assertEquals(55, flagstats.getRead2()); - assertEquals(104, flagstats.getProperlyPaired()); - } + File bamFile = getCatalogFile(bamFilename); - private void checkFastQcMetrics(FastQcMetrics metrics) { - System.out.println("metrics = " + metrics); - assertTrue(metrics != null); - assertEquals("PASS", metrics.getSummary().getBasicStatistics()); - assertEquals("FAIL", metrics.getSummary().getPerSeqGcContent()); - assertEquals("WARN", metrics.getSummary().getOverrepresentedSeqs()); - assertEquals(7, metrics.getBasicStats().size()); - assertEquals("108", metrics.getBasicStats().get("Total Sequences")); - assertEquals("100", metrics.getBasicStats().get("Sequence length")); - assertEquals("46", metrics.getBasicStats().get("%GC")); - assertEquals(8, metrics.getFiles().size()); - assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("per_sequence_quality.png")).collect(Collectors.toList()).size()); - assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("duplication_levels.png")).collect(Collectors.toList()).size()); - assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("per_base_quality.png")).collect(Collectors.toList()).size()); - assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("adapter_content.png")).collect(Collectors.toList()).size()); + Path tmpDir = Files.createDirectories(outDir.resolve("tmp")); + if (!Files.exists(tmpDir)) { + throw new IOException("It could not create the directory " + tmpDir); + } + Path newBamFilePath = Files.copy(Paths.get(bamFile.getUri()), tmpDir.resolve(bamFile.getName())); + File newBamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(newBamFilePath.toString(), "bam_to_file_on_flagstats", "", "", null, null, null, + null, null), true, token).first(); + + Paths.get(newBamFile.getUri()).toFile().delete(); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(newBamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(STATS_SKIP_VALUE, FASTQC_METRICS_SKIP_VALUE), ",")); + + ExecutionResult executionResult; + try { + System.out.println("outdir = " + outDir); + executionResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + } catch (ToolException e) { + assertTrue(e.getMessage().contains("Cannot open input file")); + return; + } + fail(); } - private File getCatalogFile(String name) throws CatalogException { - return catalogManager.getFileManager().search(STUDY, new Query("name", name), QueryOptions.empty(), token).first(); + @Test + public void testFailureOnAlignmentQcSamtoolsStats() throws IOException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_failure_on_alignment_qc_samtools_stats")); + + File bamFile = getCatalogFile(bamFilename); + + Path tmpDir = Files.createDirectories(outDir.resolve("tmp")); + if (!Files.exists(tmpDir)) { + throw new IOException("It could not create the directory " + tmpDir); + } + Path newBamFilePath = Files.copy(Paths.get(bamFile.getUri()), tmpDir.resolve(bamFile.getName())); + File newBamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(newBamFilePath.toString(), "bam_to_file_on_stats", "", "", null, null, null, + null, null), true, token).first(); + + Paths.get(newBamFile.getUri()).toFile().delete(); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(newBamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(FLAGSTATS_SKIP_VALUE, FASTQC_METRICS_SKIP_VALUE), ",")); + + ExecutionResult executionResult; + try { + System.out.println("outdir = " + outDir); + executionResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + } catch (ToolException e) { + assertTrue(e.getMessage().contains("No such file or directory")); + return; + } + fail(); } - private void resetAlignemntQc(File bamFile) throws CatalogException { - FileUpdateParams updateParams = new FileUpdateParams(); - updateParams.setQualityControl(new FileQualityControl()); - catalogManager.getFileManager().update(STUDY, new Query("id", bamFile.getId()), updateParams, QueryOptions.empty(), token); + @Test + public void testFailureOnAlignmentQcFastQc() throws IOException, CatalogException { + Path outDir = Paths.get(opencga.createTmpOutdir("_failure_on_alignment_qc_fastqc")); + + File bamFile = getCatalogFile(bamFilename); + + Path tmpDir = Files.createDirectories(outDir.resolve("tmp")); + if (!Files.exists(tmpDir)) { + throw new IOException("It could not create the directory " + tmpDir); + } + Path newBamFilePath = Files.copy(Paths.get(bamFile.getUri()), tmpDir.resolve(bamFile.getName())); + File newBamFile = catalogManager.getFileManager().link(STUDY, new FileLinkParams(newBamFilePath.toString(), "bam_to_file_on_fastqc", "", "", null, null, null, + null, null), true, token).first(); + + Paths.get(newBamFile.getUri()).toFile().delete(); + + AlignmentQcParams params = new AlignmentQcParams(); + params.setBamFile(newBamFile.getId()); + params.setSkip(StringUtils.join(Arrays.asList(STATS_SKIP_VALUE, FLAGSTATS_SKIP_VALUE), ",")); + + ExecutionResult executionResult; + try { + System.out.println("outdir = " + outDir); + executionResult = toolRunner.execute(AlignmentQcAnalysis.class, STUDY, params, outDir, null, token); + } catch (ToolException e) { + assertTrue(e.getMessage().contains("which didn't exist")); + System.out.println("e.getMessage() = " + e.getMessage()); + return; + } + fail(); } @Test @@ -567,4 +604,65 @@ public void testReadOnlyCoverageIndex() throws Exception { Runtime.getRuntime().exec("chmod 777 " + readOnlyDir.toAbsolutePath()); } + + //------------------------------------------------------------------------- + // U T I L S + //------------------------------------------------------------------------- + + private void checkSamtoolsStats(SamtoolsStats stats) { + System.out.println("stats = " + stats); + assertTrue(stats != null); + assertEquals(108, stats.getSequences()); + assertEquals(55, stats.getLastFragments()); + assertEquals(0, stats.getReadsDuplicated()); + assertEquals(0, stats.getReadsQcFailed()); + assertEquals(10800, stats.getTotalLength()); + assertEquals(10047, stats.getBasesMappedCigar()); + assertEquals(49, stats.getMismatches()); + assertEquals(31.0, stats.getAverageQuality(), 0.001f); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals3.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("coverage.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("insert-size.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("gc-content.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("acgt-cycles.png")).collect(Collectors.toList()).size()); + assertEquals(1, stats.getFiles().stream().filter(n -> n.endsWith("quals2.png")).collect(Collectors.toList()).size()); + } + + private void checkSamtoolsFlagstats(SamtoolsFlagstats flagstats) { + System.out.println("flagstats = " + flagstats); + assertTrue(flagstats != null); + assertEquals(108, flagstats.getTotalReads()); + assertEquals(0, flagstats.getSecondaryAlignments()); + assertEquals(53, flagstats.getRead1()); + assertEquals(55, flagstats.getRead2()); + assertEquals(104, flagstats.getProperlyPaired()); + } + + private void checkFastQcMetrics(FastQcMetrics metrics) { + System.out.println("metrics = " + metrics); + assertTrue(metrics != null); + assertEquals("PASS", metrics.getSummary().getBasicStatistics()); + assertEquals("FAIL", metrics.getSummary().getPerSeqGcContent()); + assertEquals("WARN", metrics.getSummary().getOverrepresentedSeqs()); + assertEquals(7, metrics.getBasicStats().size()); + assertEquals("108", metrics.getBasicStats().get("Total Sequences")); + assertEquals("100", metrics.getBasicStats().get("Sequence length")); + assertEquals("46", metrics.getBasicStats().get("%GC")); + assertEquals(8, metrics.getFiles().size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("per_sequence_quality.png")).collect(Collectors.toList()).size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("duplication_levels.png")).collect(Collectors.toList()).size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("per_base_quality.png")).collect(Collectors.toList()).size()); + assertEquals(1, metrics.getFiles().stream().filter(n -> n.endsWith("adapter_content.png")).collect(Collectors.toList()).size()); + } + + private File getCatalogFile(String name) throws CatalogException { + return catalogManager.getFileManager().search(STUDY, new Query("name", name), QueryOptions.empty(), token).first(); + } + + private void resetAlignemntQc(File bamFile) throws CatalogException { + FileUpdateParams updateParams = new FileUpdateParams(); + updateParams.setQualityControl(new FileQualityControl()); + catalogManager.getFileManager().update(STUDY, new Query("id", bamFile.getId()), updateParams, QueryOptions.empty(), token); + } } \ No newline at end of file From 37faea003efa4880de11f0d146b80db139afb781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Fri, 21 Jun 2024 11:28:48 +0200 Subject: [PATCH 19/21] analysis: add the result of the "nested" analysis to the current step attributes, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java --- .../alignment/qc/AlignmentQcAnalysis.java | 8 ++++---- .../analysis/sample/qc/SampleQcAnalysis.java | 6 +++--- .../opencga/analysis/tools/OpenCgaTool.java | 16 ++++------------ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index 99301822675..88945731aba 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -207,7 +207,7 @@ private void runSamtoolsFlagstats() throws ToolException { // Execute the Samtools flag stats analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(SamtoolsWrapperAnalysis.class, study, samtoolsWrapperParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { @@ -261,7 +261,7 @@ private void runSamtoolsStats() throws ToolException { // Execute the Samtools stats analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(SamtoolsWrapperAnalysis.class, study, samtoolsWrapperParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { @@ -331,7 +331,7 @@ private void runPlotBamstats() throws ToolException { // Execute the plot-bamstats analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(SamtoolsWrapperAnalysis.class, study, samtoolsWrapperParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { @@ -368,7 +368,7 @@ private void runFastqcMetrics() throws ToolException { // Execute the FastQC analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(FastqcWrapperAnalysis.class, study, fastqcWrapperParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java index bf1ad753d82..72680074227 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java @@ -278,7 +278,7 @@ private void runSampleVariantStats() throws ToolException { // Execute the sample variant stats analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(SampleVariantStatsAnalysis.class, study, sampleVariantStatsParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { @@ -324,7 +324,7 @@ private void runMutationalSignature() throws ToolException { // Execute the mutational signature analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(MutationalSignatureAnalysis.class, study, mutationalSignatureParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { @@ -350,7 +350,7 @@ private void runGenomePlot() throws ToolException { // Execute the genome plot analysis and add its step attributes if exist ExecutionResult executionResult = toolRunner.execute(GenomePlotAnalysis.class, study, genomePlotParams, outPath, null, token); - addStepAttributes(executionResult); + addStepAttribute(STEP_EXECUTION_RESULT_ATTRIBUTE_KEY, executionResult); // Check execution status if (executionResult.getStatus().getName() != Status.Type.DONE) { diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java index bea867db7c3..479d44901c8 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/tools/OpenCgaTool.java @@ -64,6 +64,8 @@ public abstract class OpenCgaTool { + protected final static String STEP_EXECUTION_RESULT_ATTRIBUTE_KEY = "STEP_EXECUTION_RESULT"; + protected CatalogManager catalogManager; protected Configuration configuration; protected StorageConfiguration storageConfiguration; @@ -499,18 +501,8 @@ protected final void addAttribute(String key, Object value) throws ToolException erm.addAttribute(key, value); } - protected final void addStepAttributes(ExecutionResult executionResult) throws ToolException { - if (executionResult != null) { - if (CollectionUtils.isNotEmpty(executionResult.getSteps())) { - for (ToolStep step : executionResult.getSteps()) { - if (MapUtils.isNotEmpty(step.getAttributes())) { - for (Map.Entry entry : step.getAttributes().entrySet()) { - erm.addStepAttribute(entry.getKey(), entry.getValue()); - } - } - } - } - } + protected final void addStepAttribute(String key, Object value) throws ToolException { + erm.addStepAttribute(key, value); } protected final void moveFile(String study, Path source, Path destiny, String catalogDirectoryPath, String token) throws ToolException { From 0531c48739c0c851e47f2c35608c6ced92c6b433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Fri, 21 Jun 2024 11:43:34 +0200 Subject: [PATCH 20/21] analysis: use a simpler ToolRunner constructor, #TASK-6325, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java --- .../opencga/analysis/alignment/qc/AlignmentQcAnalysis.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java index 88945731aba..1e403df8fa7 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/alignment/qc/AlignmentQcAnalysis.java @@ -166,8 +166,7 @@ protected List getSteps() { @Override protected void run() throws ToolException { // Create the tool runner - toolRunner = new ToolRunner(getOpencgaHome().toString(), catalogManager, - StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); + toolRunner = new ToolRunner(getOpencgaHome().toString(), catalogManager, variantStorageManager); // Get alignment QC metrics to update if (catalogBamFile.getQualityControl() != null) { From a53bd049890771c54c36f27127c9834e2983f248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20T=C3=A1rraga=20Gim=C3=A9nez?= Date: Fri, 21 Jun 2024 11:47:18 +0200 Subject: [PATCH 21/21] analysis: use a simpler ToolRunner constructor, #TASK-6326, #TASK-6324 On branch TASK-6324 Changes to be committed: modified: opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java --- .../opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java index 72680074227..1eb42e4a38a 100644 --- a/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java +++ b/opencga-analysis/src/main/java/org/opencb/opencga/analysis/sample/qc/SampleQcAnalysis.java @@ -242,8 +242,7 @@ protected List getSteps() { @Override protected void run() throws ToolException { // Create the tool runner - toolRunner = new ToolRunner(getOpencgaHome().toString(), catalogManager, - StorageEngineFactory.get(variantStorageManager.getStorageConfiguration())); + toolRunner = new ToolRunner(getOpencgaHome().toString(), catalogManager, variantStorageManager); // Sample variant stats if (runVariantStats) {