package com.compomics.util.experiment.massspectrometry.indexes; import com.compomics.util.experiment.massspectrometry.Peak; import com.compomics.util.experiment.personalization.UrParameter; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import org.apache.commons.math.util.FastMath; /** * This map stores the fragment ions indexed by mass. * * @author Marc Vaudel */ public class SpectrumIndex implements UrParameter { /** * The precursor mass tolerance. */ double precursorTolerance; /** * Boolean indicating whether the precursor mass tolerance is in ppm. */ boolean ppm; /** * Map of the precursors by bin and m/z. */ private HashMap<Integer, HashMap<Double, Peak>> peaksMap; /** * An m/z anchor to determine the bins in ppm */ private static final double mzAnchor = 1000; /** * The log of the m/z anchor. */ private static final double mzAnchorLog = FastMath.log(mzAnchor); /** * The scaling factor used for the bins in ppm. */ private double scalingFactor; /** * The highest bin in index. */ private Integer binMax; /** * The lowest bin in index. */ private Integer binMin; /** * The total intensity above the intensity threshold. */ private Double totalIntensity; /** * Constructor for an empty index. */ public SpectrumIndex() { } /** * Builds a new index. * * @param peaks map of the peaks indexed by m/z * @param intenstiyLimit a lower limit for the intensity of the peaks to * index * @param tolerance the tolerance to use * @param ppm boolean indicating whether the tolerance is in ppm */ public SpectrumIndex(HashMap<Double, Peak> peaks, Double intenstiyLimit, double tolerance, boolean ppm) { this.peaksMap = new HashMap<Integer, HashMap<Double, Peak>>(); this.precursorTolerance = tolerance; this.ppm = ppm; if (ppm) { scalingFactor = FastMath.log((1000000 - tolerance) / (1000000 + tolerance)); } totalIntensity = 0.0; for (Peak peak : peaks.values()) { if (intenstiyLimit == null || peak.intensity >= intenstiyLimit) { totalIntensity += peak.intensity; Integer bin = getBin(peak.mz); if (binMax == null || bin > binMax) { binMax = bin; } if (binMin == null || bin < binMin) { binMin = bin; } HashMap<Double, Peak> peaksInBin = peaksMap.get(bin); if (peaksInBin == null) { peaksInBin = new HashMap<Double, Peak>(4); peaksMap.put(bin, peaksInBin); } peaksInBin.put(peak.mz, peak); } } } /** * Returns the bin corresponding to the given m/z. * * @param mz the m/z * * @return the bin */ public Integer getBin(double mz) { if (ppm) { return getBinPpm(mz); } else { return getBinAbsolute(mz); } } /** * Returns the bin corresponding to the given m/z with absolute tolerance in * Da. * * @param mz the m/z * * @return the bin */ private Integer getBinAbsolute(double mz) { Integer bin = (int) (mz / precursorTolerance); return bin; } /** * Returns the bin corresponding to the given m/z with relative tolerance in * ppm. * * @param mz the m/z * * @return the bin */ private Integer getBinPpm(double mz) { int bin = (int) ((FastMath.log(mz) - mzAnchorLog) / scalingFactor); return bin; } /** * Returns the peaks matching the given m/z. TODO: check only one/two bins * when possible. * * @param mz a m/z to query * * @return the peaks matching the given m/z */ public ArrayList<Peak> getMatchingPeaks(double mz) { int bin0; if (ppm) { bin0 = getBinPpm(mz); } else { bin0 = getBinAbsolute(mz); } ArrayList<Peak> result = new ArrayList<Peak>(0); HashMap<Double, Peak> binContent = peaksMap.get(bin0 - 1); if (binContent != null) { for (Double peakMz : binContent.keySet()) { double error; if (ppm) { error = 1000000 * (peakMz - mz) / mz; } else { error = peakMz - mz; } if (Math.abs(error) <= precursorTolerance) { result.add(binContent.get(peakMz)); } } } binContent = peaksMap.get(bin0); if (binContent != null) { for (Double peakMz : binContent.keySet()) { double error; if (ppm) { error = 1000000 * (peakMz - mz) / mz; } else { error = peakMz - mz; } if (Math.abs(error) <= precursorTolerance) { result.add(binContent.get(peakMz)); } } } binContent = peaksMap.get(bin0 + 1); if (binContent != null) { for (Double peakMz : binContent.keySet()) { double error; if (ppm) { error = 1000000 * (peakMz - mz) / mz; } else { error = peakMz - mz; } if (Math.abs(error) <= precursorTolerance) { result.add(binContent.get(peakMz)); } } } return result; } /** * Returns the bins in the map as a list. The list is created every time me method is called. * * @return the bins in the map */ public ArrayList<Integer> getBins() { return new ArrayList<Integer>(peaksMap.keySet()); } /** * Returns the bins in the map as collection of keys from the map. * * @return the bins in the map */ public Set<Integer> getRawBins() { return peaksMap.keySet(); } /** * Returns the peaks at the given bin indexed by m/z. Null if none found. * * @param bin the bin number * * @return the peaks at the given bin */ public HashMap<Double, Peak> getPeaksInBin(Integer bin) { return peaksMap.get(bin); } /** * Returns the mass associated with the given bin, the middle of the bin. * * @param bin the bin number * * @return the mass associated with the given bin */ public Double getMass(int bin) { if (ppm) { return FastMath.exp((scalingFactor * bin) + mzAnchorLog); } else { return precursorTolerance * (0.5 + bin); } } /** * Returns the highest bin. * * @return binMax the highest bin */ public Integer getBinMax() { return binMax; } /** * Returns the lowest bin. * * @return binMin the lowest bin */ public Integer getBinMin() { return binMin; } /** * Returns the total intensity of the peaks above the intensity threshold. * * @return the total intensity of the peaks above the intensity threshold */ public Double getTotalIntensity() { return totalIntensity; } @Override public String getParameterKey() { return "SpectrumIndex"; } }