package com.compomics.util.experiment.massspectrometry;
import com.compomics.util.experiment.identification.matches.IonMatch;
import com.compomics.util.experiment.personalization.ExperimentObject;
import com.compomics.util.math.BasicMathFunctions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
/**
* This class models a spectrum.
*
* @author Marc Vaudel
* @author Harald Barsnes
*/
public abstract class Spectrum extends ExperimentObject {
/**
* The version UID for Serialization/Deserialization compatibility.
*/
static final long serialVersionUID = 7152424141470431489L;
/**
* Spectrum title.
*/
protected String spectrumTitle;
/**
* Spectrum file name.
*/
protected String fileName;
/**
* The MS level.
*/
protected int level;
/**
* mz indexed Peak list.
*/
protected HashMap<Double, Peak> peakList;
/**
* Intensity indexed Peak map.
*/
protected HashMap<Double, ArrayList<Peak>> intensityPeakMap = null;
/**
* Scan number or range.
*/
protected String scanNumber;
/**
* The time point when the spectrum was recorded (scan start time in mzML
* files).
*/
protected double scanStartTime;
/**
* The splitter in the key between spectrumFile and spectrumTitle.
*/
public static final String SPECTRUM_KEY_SPLITTER = "_cus_";
/**
* The peak list as an array directly plottable by JFreeChart.
*/
private double[][] jFreePeakList = null;
/**
* The mz values as array. Null until set by the getter.
*/
private double[] mzValuesAsArray = null;
/**
* The mz values sorted in acceding order as array. Null until set by the
* getter.
*/
private double[] mzValuesOrderedAsArray = null;
/**
* The peak list as an array list formatted as text, e.g. [[303.17334
* 3181.14],[318.14542 37971.93], ... ].
*/
private String peakListAsString = null;
/**
* The intensity values as array. Null until set by the getter.
*/
private double[] intensityValuesAsArray = null;
/**
* The intensity values as array normalized against the most intense peak.
* Null until set by the getter.
*/
private double[] intensityValuesNormaizedAsArray = null; // @TODO: correct typo
/**
* The mz and intensity values as array. Null until set by the getter.
*/
private double[][] mzAndIntensityAsArray = null;
/**
* The total intensity.
*/
private Double totalIntensity;
/**
* The maximal intensity.
*/
private Double maxIntensity;
/**
* The maximal m/z.
*/
private Double maxMz;
/**
* The minimal m/z.
*/
private Double minMz;
/**
* Mutex for the setting of the attributes in cache.
*/
private Semaphore mutex = new Semaphore(1);
/**
* Cache for the intensity limit.
*/
private Double intensityLimit = null;
/**
* Intensity level corresponding to the value in cache.
*/
private double intensityLimitLevel = -1.0;
/**
* Convenience method returning the key for a spectrum.
*
* @param spectrumFile the spectrum file
* @param spectrumTitle the spectrum title
*
* @return the corresponding spectrum key
*/
public static String getSpectrumKey(String spectrumFile, String spectrumTitle) {
return spectrumFile + SPECTRUM_KEY_SPLITTER + spectrumTitle;
}
/**
* Convenience method to retrieve the name of a file from the spectrum key.
*
* @param spectrumKey the spectrum key
* @return the name of the file containing the spectrum
*/
public static String getSpectrumFile(String spectrumKey) {
return spectrumKey.substring(0, spectrumKey.indexOf(SPECTRUM_KEY_SPLITTER));
}
/**
* Convenience method to retrieve the name of a spectrum from the spectrum
* key.
*
* @param spectrumKey the spectrum key
* @return the title of the spectrum
*/
public static String getSpectrumTitle(String spectrumKey) {
return spectrumKey.substring(spectrumKey.indexOf(SPECTRUM_KEY_SPLITTER) + SPECTRUM_KEY_SPLITTER.length());
}
/**
* Set the spectrum title.
*
* @param spectrumTitle the title to set
*/
public void setSpectrumTitle(String spectrumTitle) {
this.spectrumTitle = spectrumTitle;
}
/**
* Returns the key of the spectrum.
*
* @return the key of the spectrum
*/
public String getSpectrumKey() {
StringBuilder stringBuilder = new StringBuilder(fileName.length() + SPECTRUM_KEY_SPLITTER.length() + spectrumTitle.length());
stringBuilder.append(fileName);
stringBuilder.append(SPECTRUM_KEY_SPLITTER);
stringBuilder.append(spectrumTitle);
return stringBuilder.toString();
}
/**
* Returns the spectrum title.
*
* @return spectrum title
*/
public String getSpectrumTitle() {
return spectrumTitle;
}
/**
* Format the peaks so that they can be plotted in JFreeChart.
*
* @return a table containing the peaks
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double[][] getJFreePeakList() throws InterruptedException {
if (jFreePeakList == null) {
mutex.acquire();
if (jFreePeakList == null) {
double[] mz = new double[peakList.size()];
double[] intensity = new double[peakList.size()];
int cpt = 0;
for (Peak currentPeak : peakList.values()) {
mz[cpt] = currentPeak.mz;
intensity[cpt] = currentPeak.intensity;
cpt++;
}
jFreePeakList = new double[6][mz.length];
jFreePeakList[0] = mz;
jFreePeakList[1] = mz;
jFreePeakList[2] = mz;
jFreePeakList[3] = intensity;
jFreePeakList[4] = intensity;
jFreePeakList[5] = intensity;
}
mutex.release();
}
return jFreePeakList;
}
/**
* Returns a peak map where peaks are indexed by their m/z.
*
* @return a peak map
*/
public HashMap<Double, Peak> getPeakMap() {
return peakList;
}
/**
* Adds a peak to the spectrum peak list.
*
* @param aPeak the peak to add
*/
public synchronized void addPeak(Peak aPeak) {
if (peakList == null) {
peakList = new HashMap<Double, Peak>();
}
this.peakList.put(aPeak.mz, aPeak);
resetSavedData();
}
/**
* Set the peaks.
*
* @param peaks the peaks to set
*/
public synchronized void setPeaks(ArrayList<Peak> peaks) {
if (peakList != null) {
this.peakList.clear();
} else {
peakList = new HashMap<Double, Peak>();
}
for (Peak p : peaks) {
double mz = p.mz;
peakList.put(mz, p);
}
resetSavedData();
}
/**
* Getter for the scan number.
*
* @return the spectrum scan number
*/
public String getScanNumber() {
return scanNumber;
}
/**
* Setter for the scan number or range.
*
* @param scanNumber or range
*/
public synchronized void setScanNumber(String scanNumber) {
this.scanNumber = scanNumber;
}
/**
* Returns the file name.
*
* @return the file name
*/
public String getFileName() {
return fileName;
}
/**
* Returns at which level the spectrum was recorded.
*
* @return at which level the spectrum was recorded
*/
public int getLevel() {
return level;
}
/**
* Returns the peak list.
*
* @return the peak list
*/
public Collection<Peak> getPeakList() {
return peakList.values();
}
/**
* Sets the peak list.
*
* @param peakList HashSet of peaks containing the peaks of the spectrum
*/
public synchronized void setPeakList(HashMap<Double, Peak> peakList) {
this.peakList = peakList;
resetSavedData();
}
/**
* Returns the peak list as an array list formatted as text, e.g.
* [[303.17334 3181.14],[318.14542 37971.93], ... ].
*
* @return the peak list as an array list formatted as text
*
* @throws java.lang.InterruptedException thrown if the thread is
* interrupted
*/
public String getPeakListAsString() throws InterruptedException {
if (peakListAsString == null) {
double[] mzValues = getOrderedMzValues();
mutex.acquire();
StringBuilder sb = new StringBuilder();
sb.append("[");
for (double mzValue : mzValues) {
if (sb.length() > 1) {
sb.append(",");
}
Peak currentPeak = peakList.get(mzValue);
sb.append("[");
sb.append(currentPeak.mz);
sb.append(",");
sb.append(currentPeak.intensity);
sb.append("]");
}
sb.append("]");
peakListAsString = sb.toString();
mutex.release();
}
return peakListAsString;
}
/**
* Returns the scan start time.
*
* @return the scan start time
*/
public double getScanStartTime() {
return scanStartTime;
}
/**
* Sets the scan start time.
*
* @param scanStartTime the time point when the spectrum was recorded
*/
public synchronized void setScanStartTime(double scanStartTime) {
this.scanStartTime = scanStartTime;
}
/**
* This method will remove the peak list in order to reduce memory
* consumption of the model.
*/
public synchronized void removePeakList() {
if (peakList != null) {
peakList.clear();
}
}
/**
* Returns the mz values as an array. Note: the array is not necessarily
* ordered.
*
* @return the mz values as an array
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double[] getMzValuesAsArray() throws InterruptedException {
if (mzValuesAsArray == null) {
mutex.acquire();
if (mzValuesAsArray == null) {
mzValuesAsArray = new double[peakList.size()];
int counter = 0;
for (double currentMz : peakList.keySet()) {
mzValuesAsArray[counter++] = currentMz;
}
}
mutex.release();
}
return mzValuesAsArray;
}
/**
* Returns a list of the m/z values sorted in ascending order.
*
* @return a list of the m/z values sorted in ascending order
*
* @throws java.lang.InterruptedException thrown if the thread is
* interrupted
*/
public double[] getOrderedMzValues() throws InterruptedException {
if (mzValuesOrderedAsArray == null) {
getMzValuesAsArray();
if (mzValuesOrderedAsArray == null) {
mutex.acquire();
mzValuesOrderedAsArray = mzValuesAsArray.clone();
Arrays.sort(mzValuesOrderedAsArray);
mutex.release();
}
}
return mzValuesOrderedAsArray;
}
/**
* Setter for the intensityValuesAsArray.
*
* @param intensityValuesAsArray the intensity values array
*/
public synchronized void setIntensityValuesAsArray(double[] intensityValuesAsArray) {
this.intensityValuesAsArray = intensityValuesAsArray;
removePeakList();
}
/**
* Returns the intensity values as an array.
*
* @return the intensity values as an array
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double[] getIntensityValuesAsArray() throws InterruptedException {
if (intensityValuesAsArray == null || (intensityValuesAsArray.length != peakList.size())) {
mutex.acquire();
if (intensityValuesAsArray == null || (intensityValuesAsArray.length != peakList.size())) {
intensityValuesAsArray = new double[peakList.size()];
int counter = 0;
for (Peak currentPeak : peakList.values()) {
intensityValuesAsArray[counter++] = currentPeak.intensity;
}
}
mutex.release();
}
return intensityValuesAsArray;
}
/**
* Returns the intensity values as an array normalized against the largest
* peak.
*
* @return the normalized intensity values as an array
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double[] getIntensityValuesNormalizedAsArray() throws InterruptedException {
if (intensityValuesNormaizedAsArray == null) {
mutex.acquire();
if (intensityValuesNormaizedAsArray == null) {
intensityValuesNormaizedAsArray = new double[peakList.size()];
double highestIntensity = 0.0;
int counter = 0;
for (Peak currentPeak : peakList.values()) {
intensityValuesNormaizedAsArray[counter++] = currentPeak.intensity;
if (currentPeak.intensity > highestIntensity) {
highestIntensity = currentPeak.intensity;
}
}
if (highestIntensity > 0) {
for (int i = 0; i < intensityValuesNormaizedAsArray.length; i++) {
intensityValuesNormaizedAsArray[i] = intensityValuesNormaizedAsArray[i] / highestIntensity * 100;
}
}
}
mutex.release();
}
return intensityValuesNormaizedAsArray;
}
/**
* Returns the m/z and intensity values as an array in increasing order
* sorted on m/z value.
*
* @return the m/z and intensity values as an array
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double[][] getMzAndIntensityAsArray() throws InterruptedException {
if (mzAndIntensityAsArray == null) {
double[] orderedMzValues = getOrderedMzValues();
mutex.acquire();
if (mzAndIntensityAsArray == null) {
mzAndIntensityAsArray = new double[2][peakList.size()];
int counter = 0;
for (double mz : orderedMzValues) {
Peak currentPeak = peakList.get(mz);
mzAndIntensityAsArray[0][counter] = currentPeak.mz;
mzAndIntensityAsArray[1][counter] = currentPeak.intensity;
counter++;
}
}
mutex.release();
}
return mzAndIntensityAsArray;
}
/**
* Returns the total intensity of the spectrum.
*
* @return the total intensity. 0 if no peak.
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double getTotalIntensity() throws InterruptedException {
if (totalIntensity == null) {
mutex.acquire();
if (totalIntensity == null) {
totalIntensity = 0.0;
for (Peak currentPeak : peakList.values()) {
totalIntensity += currentPeak.intensity;
}
}
mutex.release();
}
return totalIntensity;
}
/**
* Returns the max intensity value.
*
* @return the max intensity value. 0 if no peak.
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double getMaxIntensity() throws InterruptedException {
if (maxIntensity == null) {
mutex.acquire();
if (maxIntensity == null) {
maxIntensity = 0.0;
for (Peak currentPeak : peakList.values()) {
if (currentPeak.intensity > maxIntensity) {
maxIntensity = currentPeak.intensity;
}
}
}
mutex.release();
}
return maxIntensity;
}
/**
* Returns the max mz value.
*
* @return the max mz value
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double getMaxMz() throws InterruptedException {
if (maxMz == null) {
mutex.acquire();
if (maxMz == null) {
if (peakList.keySet().isEmpty()) {
maxMz = 0.0;
} else {
maxMz = Collections.max(peakList.keySet());
}
}
mutex.release();
}
return maxMz;
}
/**
* Returns the min mz value.
*
* @return the min mz value
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public double getMinMz() throws InterruptedException {
if (minMz == null) {
mutex.acquire();
if (minMz == null) {
if (peakList.keySet().isEmpty()) {
minMz = 0.0;
} else {
minMz = Collections.min(peakList.keySet());
}
}
mutex.release();
}
return minMz;
}
/**
* Returns an array containing the intensity of all peaks strictly above the
* provided threshold.
*
* @param threshold the lower threshold
*
* @return an array containing the intensity of all peak above the provided
* threshold
*/
public ArrayList<Double> getPeaksAboveIntensityThreshold(double threshold) {
ArrayList<Double> peakIntensities = new ArrayList<Double>();
for (Peak currentPeak : peakList.values()) {
if (currentPeak.intensity > threshold) {
peakIntensities.add(currentPeak.intensity);
}
}
return peakIntensities;
}
/**
* Returns the intensity limit in intensity from a given percentile.
*
* @param intensityFraction the fraction of the intensity to use as limit,
* e.g., 0.75 for the 75% most intense peaks.
*
* @return the intensity limit
*/
public double getIntensityLimit(double intensityFraction) {
if (intensityLimit == null || intensityLimitLevel != intensityFraction) {
intensityLimit = estimateIntneistyLimit(intensityFraction);
intensityLimitLevel = intensityFraction;
}
return intensityLimit;
}
/**
* Estimates the intensity limit in intensity from a given percentile.
*
* @param intensityFraction the fraction of the intensity to use as limit,
* e.g., 0.75 for the 75% most intense peaks.
*
* @return the intensity limit
*/
private double estimateIntneistyLimit(double intensityFraction) {
ArrayList<Double> intensities = new ArrayList<Double>(peakList.size());
for (Peak peak : peakList.values()) {
double mz = peak.mz;
// Skip the low mass region of the spectrum @TODO: skip precursor as well
if (mz > 200) {
intensities.add(peak.intensity);
}
}
if (intensities.isEmpty()) {
return 0;
}
return BasicMathFunctions.percentile(intensities, intensityFraction);
}
/**
* Returns a recalibrated peak list.
*
* @param mzCorrections the m/z corrections to apply
*
* @return the recalibrated list of peaks indexed by m/z
*/
public HashMap<Double, Peak> getRecalibratedPeakList(HashMap<Double, Double> mzCorrections) {
HashMap<Double, Peak> result = new HashMap<Double, Peak>(peakList.size());
ArrayList<Double> keys = new ArrayList<Double>(mzCorrections.keySet());
Collections.sort(keys);
for (Peak peak : peakList.values()) {
double fragmentMz = peak.mz;
double key1 = keys.get(0);
double correction = 0.0;
if (fragmentMz <= key1) {
correction = mzCorrections.get(key1);
} else {
key1 = keys.get(keys.size() - 1);
if (fragmentMz >= key1) {
correction = mzCorrections.get(key1);
} else {
for (int i = 0; i < keys.size() - 1; i++) {
key1 = keys.get(i);
if (key1 == fragmentMz) {
correction = mzCorrections.get(key1);
break;
}
double key2 = keys.get(i + 1);
if (key1 < fragmentMz && fragmentMz < key2) {
double y1 = mzCorrections.get(key1);
double y2 = mzCorrections.get(key2);
correction = y1 + ((fragmentMz - key1) * (y2 - y1) / (key2 - key1));
break;
}
}
}
}
result.put(peak.mz - correction, new Peak(peak.mz - correction, peak.intensity));
}
return result;
}
/**
* Returns the peak list of this spectrum without matched peaks.
*
* @param matches the ion matches
*
* @return a peak list which does not contain the peak matched
*/
public HashMap<Double, Peak> getDesignaledPeakList(ArrayList<IonMatch> matches) {
HashMap<Double, Peak> result = new HashMap<Double, Peak>(peakList);
for (IonMatch ionMatch : matches) {
result.remove(ionMatch.peak.mz);
}
return result;
}
/**
* Returns the part of the spectrum contained between mzMin (inclusive) and
* mzMax (exclusive) as a peak list
*
* @param mzMin the minimum m/z value
* @param mzMax the maximum m/z value
*
* @return the part of the spectrum contained between mzMin (inclusive) and
* mzMax (exclusive)
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public HashMap<Double, Peak> getSubSpectrum(double mzMin, double mzMax) throws InterruptedException {
HashMap<Double, Peak> result = new HashMap<Double, Peak>();
for (double mz : getOrderedMzValues()) {
if (mz >= mzMin && mz < mzMax) {
result.put(mz, peakList.get(mz));
} else if (mz >= mzMax) {
break;
}
}
return result;
}
/**
* Returns the peak list in a map where peaks are indexed by their
* intensity.
*
* @return the peak list in a map where peaks are indexed by their intensity
*
* @throws java.lang.InterruptedException exception thrown if the thread is
* interrupted
*/
public HashMap<Double, ArrayList<Peak>> getIntensityMap() throws InterruptedException {
if (intensityPeakMap == null) {
mutex.acquire();
if (intensityPeakMap == null) {
intensityPeakMap = new HashMap<Double, ArrayList<Peak>>(peakList.size());
for (Peak peak : peakList.values()) {
double intensity = peak.intensity;
ArrayList<Peak> peaksAtIntensity = intensityPeakMap.get(intensity);
if (peaksAtIntensity == null) {
peaksAtIntensity = new ArrayList<Peak>();
intensityPeakMap.put(intensity, peaksAtIntensity);
}
peaksAtIntensity.add(peak);
}
}
mutex.release();
}
return intensityPeakMap;
}
/**
* Returns the number of peaks in the spectrum.
*
* @return the number of peaks in the spectrum
*/
public int getNPeaks() {
if (peakList == null) {
return 0;
}
return peakList.size();
}
/**
* Returns a boolean indicating whether the spectrum is empty.
*
* @return a boolean indicating whether the spectrum is empty
*/
public boolean isEmpty() {
return getNPeaks() == 0;
}
/**
* Resets all the saved values to null. Used after altering the peak data.
*/
private void resetSavedData() {
jFreePeakList = null;
peakListAsString = null;
mzValuesAsArray = null;
mzValuesOrderedAsArray = null;
intensityValuesAsArray = null;
intensityValuesNormaizedAsArray = null;
mzAndIntensityAsArray = null;
totalIntensity = null;
maxIntensity = null;
maxMz = null;
minMz = null;
intensityPeakMap = null;
intensityLimit = null;
}
}