/************************************************************************* * * * 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.lcms.db.analysis; import com.act.lcms.XZ; import com.act.lcms.db.model.StandardWell; import com.act.lcms.plotter.WriteAndPlotMS1Results; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; public class ChemicalToMapOfMetlinIonsToIntensityTimeValues { private static final String FMT = "pdf"; private static final Logger LOGGER = LogManager.getFormatterLogger(ChemicalToMapOfMetlinIonsToIntensityTimeValues.class); private Map<String, Map<String, List<XZ>>> peakData; protected ChemicalToMapOfMetlinIonsToIntensityTimeValues() { this.peakData = new HashMap<>(); } public Set<String> getIonList() { return this.peakData.keySet(); } public Map<String, List<XZ>> getMetlinIonsOfChemical(String chemical) { return this.peakData.get(chemical); } public void addIonIntensityTimeValueToChemical(String chemical, String ion, List<XZ> intensityAndTimeValues) { Map<String, List<XZ>> val = this.peakData.get(chemical); if (val == null) { val = new HashMap<>(); } val.put(ion, intensityAndTimeValues); this.peakData.put(chemical, val); } private static Double findPeakMaxIntensity(List<XZ> intensityTimeValues) { Double maxIntensity = 0.0d; for (XZ val : intensityTimeValues) { maxIntensity = Math.max(maxIntensity, val.getIntensity()); } return maxIntensity; } /** * This function plots the positive ion and negative control ions for a given metlin ion mass per plot. * @param searchMz - The mz value which is used for finding spectra. * @param plottingDirectory - The directory where the plots will live. * @param positiveChemical - The positive chemical is used to make sure it is placed at the top of the spectra plot. * @return This function returns a map of ion to absolute paths where the plot lives. * @throws IOException */ public Map<String, String> plotPositiveAndNegativeControlsForEachMetlinIon( Pair<String, Double> searchMz, String plottingDirectory, String positiveChemical, List<StandardWell> standardWells) throws IOException { Map<String, String> ionToPlottingFilePath = new HashMap<>(); Map<String, Double> individualMaxIntensities = new HashMap<>(); WriteAndPlotMS1Results plottingUtil = new WriteAndPlotMS1Results(); //rearrange the order of plotting ArrayList<String> orderedPlotChemicalTitles = new ArrayList<>(this.peakData.keySet().size()); for (String chemical : peakData.keySet()) { if (chemical.equals(positiveChemical)) { orderedPlotChemicalTitles.add(0, chemical); } else { orderedPlotChemicalTitles.add(chemical); } } // This variable is used as a part of the file path dir to uniquely identify the pos/neg wells for the chemical. StringBuilder indexedPath = new StringBuilder(); for (StandardWell well : standardWells) { indexedPath.append(Integer.toString(well.getId()) + "-"); } for (String ion : this.peakData.get(searchMz.getLeft()).keySet()) { LinkedHashMap<String, List<XZ>> ms1s = new LinkedHashMap<>(); Map<String, Double> metlinMasses = new HashMap<>(); Double maxIntensity = 0.0d; for (String chemical : orderedPlotChemicalTitles) { List<XZ> ionValues = this.peakData.get(chemical).get(ion); ms1s.put(chemical, ionValues); Double localMaxIntensity = findPeakMaxIntensity(ionValues); maxIntensity = Math.max(maxIntensity, localMaxIntensity); individualMaxIntensities.put(chemical, localMaxIntensity); metlinMasses.put(chemical, searchMz.getValue()); } String relativePath = searchMz.getLeft() + "_" + indexedPath.toString() + "_" + ion; File absolutePathFileWithoutExtension = new File(plottingDirectory, relativePath); String absolutePathWithoutExtension = absolutePathFileWithoutExtension.getAbsolutePath(); plottingUtil.plotSpectra( ms1s, maxIntensity, individualMaxIntensities, metlinMasses, absolutePathWithoutExtension, this.FMT, false, false); ionToPlottingFilePath.put(ion, relativePath + "." + this.FMT); } return ionToPlottingFilePath; } /** * This function plots a combination of positive and negative control intensity-time values. * @param searchMzs A list of mass charge values * @param plottingPath The wells used for the analysis. This variable is mainly used for * @param peakDataPos The postive intensity-time value * @param peakDataNegs The negative controls intensity-time values * @param plottingDirectory The directory where the plots are going to be placed in * @return * @throws IOException */ public static Map<String, String> plotPositiveAndNegativeControlsForEachMZ( Set<Pair<String, Double>> searchMzs, String plottingPath, ChemicalToMapOfMetlinIonsToIntensityTimeValues peakDataPos, List<ChemicalToMapOfMetlinIonsToIntensityTimeValues> peakDataNegs, String plottingDirectory) throws IOException { Map<String, String> result = new HashMap<>(); Map<String, Double> individualMaxIntensities = new HashMap<>(); WriteAndPlotMS1Results plottingUtil = new WriteAndPlotMS1Results(); for (Pair<String, Double> mz : searchMzs) { LinkedHashMap<String, List<XZ>> ms1s = new LinkedHashMap<>(); Map<String, Double> metlinMasses = new HashMap<>(); Double maxIntensity = 0.0d; String chemicalAndIonName = mz.getLeft(); Double massChargeValue = mz.getRight(); // Get positive ion results String positiveChemicalName = AnalysisHelper.constructChemicalAndScanTypeName(chemicalAndIonName, ScanData.KIND.POS_SAMPLE); List<XZ> ionValuesPos = peakDataPos.peakData.get(positiveChemicalName).get(chemicalAndIonName); ms1s.put(positiveChemicalName, ionValuesPos); Double localMaxIntensityPos = findPeakMaxIntensity(ionValuesPos); maxIntensity = Math.max(maxIntensity, localMaxIntensityPos); individualMaxIntensities.put(positiveChemicalName, localMaxIntensityPos); metlinMasses.put(positiveChemicalName, massChargeValue); // Get negative control results Integer negNameCounter = 0; for (ChemicalToMapOfMetlinIonsToIntensityTimeValues peakDataNeg : peakDataNegs) { String negativeChemicalName = AnalysisHelper.constructChemicalAndScanTypeName(chemicalAndIonName, ScanData.KIND.NEG_CONTROL); String negativeChemicalNameId = negativeChemicalName + "_" + negNameCounter.toString(); List<XZ> ionValuesNeg = peakDataNeg.peakData.get(negativeChemicalName).get(chemicalAndIonName); ms1s.put(negativeChemicalNameId, ionValuesNeg); Double localMaxIntensityNeg = findPeakMaxIntensity(ionValuesNeg); maxIntensity = Math.max(maxIntensity, localMaxIntensityNeg); individualMaxIntensities.put(negativeChemicalNameId, localMaxIntensityNeg); metlinMasses.put(negativeChemicalNameId, massChargeValue); negNameCounter++; } String relativePath = massChargeValue.toString() + "_" + plottingPath + "_" + chemicalAndIonName; File absolutePathFileWithoutExtension = new File(plottingDirectory, relativePath); String absolutePathWithoutExtension = absolutePathFileWithoutExtension.getAbsolutePath(); String absolutePathWithExtension = absolutePathWithoutExtension + "." + FMT; // Check if the plotting file already exists. If it does, we should not overwrite it. Instead, we just change // the path name by appending a counter till the collision no longer exists. // TODO: Implement an elegant solution to this problem. File duplicateFile = new File(absolutePathWithExtension); Integer fileDuplicateCounter = 0; while (duplicateFile.exists() && !duplicateFile.isDirectory()) { LOGGER.warn("Duplicate file exists for %s, writing to another file", duplicateFile.getAbsolutePath()); fileDuplicateCounter++; relativePath = relativePath + "_" + fileDuplicateCounter.toString(); absolutePathFileWithoutExtension = new File(plottingDirectory, relativePath); absolutePathWithoutExtension = absolutePathFileWithoutExtension.getAbsolutePath(); absolutePathWithExtension = absolutePathWithoutExtension + "." + FMT; duplicateFile = new File(absolutePathWithExtension); } LOGGER.info("Wrote plot to %s", absolutePathWithoutExtension); plottingUtil.plotSpectra( ms1s, maxIntensity, individualMaxIntensities, metlinMasses, absolutePathWithoutExtension, FMT, false, false); result.put(mz.getLeft(), relativePath + "." + FMT); } return result; } }