/*************************************************************************
* *
* 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);
}
}