/************************************************************************* * * * 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; import com.act.lcms.MS1; import com.act.lcms.v2.Ion; import com.act.lcms.v2.IonCalculator; import com.act.lcms.v2.Metabolite; import com.act.lcms.v2.PeakSpectrum; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Group all the data relating to a single precursor report. The report contains a target metabolite, * an ImmutableNetwork containing its precursors, and a Map associating each node to its level in the backwards * BF search tree. * TODO: add LCMS data to the report */ public class PrecursorReport { private static transient final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @JsonProperty("target") Metabolite target; @JsonProperty("network") ImmutableNetwork network; @JsonProperty("level_map") Map<NetworkNode, Integer> levelMap; // Maps nodes to confidence that they're in the lcms trace, from 0.0 to 1.0. @JsonProperty("lcms_hit_map") Map<NetworkNode, Double> lcmsMap; public PrecursorReport(Metabolite target, ImmutableNetwork network) { this(target, network, new HashMap<>(), new HashMap<>()); } public PrecursorReport( Metabolite target, ImmutableNetwork network, Map<NetworkNode, Integer> levelMap) { this(target, network, levelMap, new HashMap<>()); } public PrecursorReport( Metabolite target, ImmutableNetwork network, Map<NetworkNode, Integer> levelMap, Map<NetworkNode, Double> lcmsMap) { this.target = target; this.network = network; this.levelMap = levelMap; this.lcmsMap = lcmsMap; } /** * Uses a PeakSpectrum to decide on an LCMS confidence value for each node of the network. For now, we say that * a node has a confidence of 1.0 if any of its ions match up with a peak in the spectrum with confidence 1.0. If none * of its ions match up with any peaks with confidence 1.0, we give a confidence of 0.0. We can, of course, make this * calculation more sophisticated as needed. * @param peakSpectrum The peaks to match the nodes against. * @param ionCalculator The ion calculator to use. * @param ions The ions to use. */ public void addLcmsData(PeakSpectrum peakSpectrum, IonCalculator ionCalculator, Set<String> ions) { for (NetworkNode node : network.getNodes()) { lcmsMap.put(node, 0.0); for (Ion ion : ionCalculator.getSelectedIons(node.getMetabolite(), ions, MS1.IonMode.POS)) { if (!peakSpectrum.getPeaksByMZ(ion.getMzValue(), 1.0).isEmpty()) { lcmsMap.put(node, 1.0); break; } } } } public Metabolite getTarget() { return target; } @JsonProperty("network") public ImmutableNetwork getNetwork() { return network; } /** * Check if a given node is a precursor of the target node. This is not always the case, as some nodes in the network * may be products of precursors of the target, but not themselves precursors. */ public boolean isPrecursor(NetworkNode node) { return levelMap.containsKey(node); } /** * @return 0 for target node, 1 for its direct precursors, 2 for second-level precursors, etc. Returns null if the * node is not in the level map, i.e. if it's not a precursor, and isPrecursor(node) == false. */ public Integer getLevel(NetworkNode node) { return levelMap.get(node); } /** * Returns a value between 0 and 1, where 0 indicates the node definitely is not an lcms hit, and 1 indicates * that it definitely is. */ public Double getLcmsConfidence(NetworkNode node) { return lcmsMap.get(node); } /** * Returns true if the substrate is exactly one level deeper in the precursor tree than the product. */ public boolean edgeInBfsTree(NetworkNode substrate, NetworkNode product) { return isPrecursor(substrate) && isPrecursor(product) && getLevel(substrate).equals(1 + getLevel(product)); } /** * Custom deserializer for Jackson, to transform the serialized String->Integer map back into a * NetworkNode->Integer map. */ @JsonCreator private static PrecursorReport getFromJson( @JsonProperty("target") Metabolite target, @JsonProperty("network") ImmutableNetwork network, @JsonProperty("level_map") Map<Integer, Integer> levelMap, @JsonProperty("lcms_hit_map") Map<Integer, Double> lcmsMap) { PrecursorReport report = new PrecursorReport(target, network); levelMap.entrySet().forEach(e -> report.levelMap.put(network.getNodeByUID(e.getKey()), e.getValue())); lcmsMap.entrySet().forEach(e -> report.lcmsMap.put(network.getNodeByUID(e.getKey()), e.getValue())); ; return report; } /** * Custom serializer for jackson, since it chokes on keys which are not strings. Transforms lcmsMap to a * UID->confidence map instead of a NetworkNode->confidence map. */ @JsonProperty("lcms_hit_map") private Map<Integer, Double> getSerializableLcmsMap() { return lcmsMap.entrySet().stream().collect(Collectors.toMap( e -> e.getKey().getUID(), e -> e.getValue())); } /** * Custom serializer for jackson, since it chokes on keys which are not Strings. Transforms levelMap to a * UID->Integer map instead of a NetworkNode->Integer map. */ @JsonProperty("level_map") private Map<Integer, Integer> getSerializableLevelMap() { return levelMap.entrySet().stream().collect(Collectors.toMap( e -> e.getKey().getUID(), e -> e.getValue())); } public void writeToJsonFile(File outputFile) throws IOException { try (BufferedWriter predictionWriter = new BufferedWriter(new FileWriter(outputFile))) { OBJECT_MAPPER.writeValue(predictionWriter, this); } } public static PrecursorReport readFromJsonFile(File inputFile) throws IOException { return OBJECT_MAPPER.readValue(inputFile, PrecursorReport.class); } }