/************************************************************************* * * * This file is part of the 20n/act project. * * 20n/act enables DNA prediction for synthetic biology/bioengineering. * * Copyright (C) 2017 20n Labs, Inc. * * * * Please direct all queries to act@20n.com. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * *************************************************************************/ package com.act.biointerpretation.networkanalysis.GraphViz; import com.act.biointerpretation.networkanalysis.ImmutableNetwork; import com.act.biointerpretation.networkanalysis.NetworkEdge; import com.act.biointerpretation.networkanalysis.NetworkNode; import com.act.biointerpretation.networkanalysis.PrecursorReport; import com.act.jobs.FileChecker; import com.act.jobs.JavaRunnable; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Class for turning a PrecursorReport into a DotGraph that can be visualized. * Implements JavaRunnable for workflow incorporation. */ public class PrecursorReportVisualizer { private static final Logger LOGGER = LogManager.getFormatterLogger(PrecursorReportVisualizer.class); private static final Double FULL_CONFIDENCE = 1.0; private static final DotColor LCMS_HIT_COLOR = DotColor.RED; private static final DotColor LCMS_MISS_COLOR = DotColor.DEFAULT_BLACK; private static final DotEdge.EdgeStyle REACTION_EDGE_STYLE = DotEdge.EdgeStyle.DEFAULT_SOLID; private static final DotEdge.EdgeStyle PREDICTION_EDGE_STYLE = DotEdge.EdgeStyle.DOTTED; // A map from organisms of interest to their respective colors. // Any edge with any organism name which contains one of these strings will be colored. // An edge which matches multiple keys of this map will be given one edge per distinct color specified by its // org matches. private final Map<String, DotColor> orgToColor; public void addOrgOfInterest(String org, DotColor color) { orgToColor.put(org, color); } public PrecursorReportVisualizer() { this.orgToColor = new HashMap<>(); } public Runner getRunner(File inputNetwork, File outputFile) { return new Runner(inputNetwork, outputFile); } /** * Builds DOT graph representation of the precursor report. The graph is printed out with the target metabolite * on the bottom, all its direct precursors one level up, all their precursors two levels up, etc. Only edges * between adjacent levels are drawn, resulting in a reverse BFS tree representation of the precursors. * @param report The PrecursorReport. * @return The DotGraph. */ public DotGraph buildDotGraph(PrecursorReport report) { ImmutableNetwork network = report.getNetwork(); DotGraph graph = new DotGraph(); for (NetworkNode node : network.getNodes()) { if (report.isPrecursor(node) && report.getLcmsConfidence(node) != null && report.getLcmsConfidence(node).equals(FULL_CONFIDENCE)) { graph.addNode(new DotNode(node.getUID().toString(), LCMS_HIT_COLOR)); } else { graph.addNode(new DotNode(node.getUID().toString(), LCMS_MISS_COLOR)); } } // Add edges to the graph. One or more DotEdges are added for each (substrate, product) pair where the substrate // is one level farther back in the tree than the product. for (NetworkEdge edge : network.getEdges()) { for (Integer substrate : edge.getSubstrates()) { for (Integer product : edge.getProducts()) { if (report.edgeInBfsTree(network.getNodeByUID(substrate), network.getNodeByUID(product))) { addEdgesToGraph(edge, substrate.toString(), product.toString(), graph); } } } } return graph; } /** * Helper method to build DotEdges given a NetworkEdge, and a particular (substrate, product) pair. * Each edge is formatted as a solid line if it has a DB reaction associated, or a dotted line otherwise. * The color of the edge is determined by whether it matches an organism of interest. * If the NetworkEdge matches multiple orgs of interest, one edge is drawn for each one it matches, * in the appropriate color. */ private void addEdgesToGraph(NetworkEdge edge, String substrateId, String productId, DotGraph graph) { DotEdge.EdgeStyle style = edge.getReactionIds().isEmpty() ? PREDICTION_EDGE_STYLE : REACTION_EDGE_STYLE; Set<DotColor> colors = new HashSet<>(); for (String orgOfInterest : orgToColor.keySet()) { if (matchesOrg(edge, orgOfInterest)) { colors.add(orgToColor.get(orgOfInterest)); } } if (colors.isEmpty()) { colors.add(DotColor.DEFAULT_BLACK); } colors.stream().map(c -> new DotEdge(substrateId, productId, c, style)).forEach(graph::addEdge); } /** * Helper method to test whether a given edge matches the orgOfInterest string. * Returns true if any organism entry in the edge contains orgOfInterest as a substring. * For example, we can put in "homo sapiens" if we only want to match to the specific genus and species, * but we can also put in only "homo" and this method will return true on any organism which has "homo" * in its name. */ private boolean matchesOrg(NetworkEdge edge, String orgOfInterest) { return edge.getOrgs().stream().filter(s -> s.contains(orgOfInterest)).count() > 0; } /** * Workflow-compatible component for graph visualization. */ public class Runner implements JavaRunnable { private final File inputFile; private final File outputFile; public Runner(File inputFile, File outputFile) { this.inputFile = inputFile; this.outputFile = outputFile; } /** * Loads in a precursorReport from file, builds a DotGraph from it, and writes it to file. * The graph can be visualized with an online GraphViz viewer like http://www.webgraphviz.com/. * @throws IOException */ @Override public void run() throws IOException { FileChecker.verifyInputFile(inputFile); FileChecker.verifyAndCreateOutputFile(outputFile); PrecursorReport report = PrecursorReport.readFromJsonFile(inputFile); LOGGER.info("Handled input files. Building dot graph."); DotGraph graph = buildDotGraph(report); LOGGER.info("Build graph. Writing out graph."); graph.writeGraphToFile(outputFile); LOGGER.info("Graph written to %s. Visualization complete!", outputFile.getAbsolutePath()); } } }