/*
* Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fhcrc.cpl.viewer.quant.gui;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.LookupPaintScale;
import org.apache.log4j.Logger;
import org.fhcrc.cpl.toolbox.proteomics.MSRun;
import org.fhcrc.cpl.toolbox.proteomics.feature.Spectrum;
import org.fhcrc.cpl.toolbox.proteomics.feature.Feature;
import org.fhcrc.cpl.toolbox.datastructure.FloatRange;
import org.fhcrc.cpl.toolbox.statistics.BasicStatistics;
import org.fhcrc.cpl.toolbox.gui.chart.*;
import org.fhcrc.cpl.viewer.quant.QuantEvent;
import javax.imageio.ImageIO;
import java.util.*;
import java.util.List;
import java.io.File;
import java.io.IOException;
import java.awt.image.BufferedImage;
import java.awt.*;
/**
* Constructs several charts with information about a quantitative event
*
* TODO: no reason to extend PanelWithHeatMap here -- that's legacy.
*/
public class PanelWithSpectrumChart extends PanelWithHeatMap
{
static Logger _log = Logger.getLogger(PanelWithSpectrumChart.class);
//Default mass to assume separates each peak. Properly this should be the mass of a proton,
//but for historical reasons it's 1.0Da in Q3, do it's 1.0Da here
public static final float DEFAULT_PEAK_SEPARATION_MASS = 1.f;
//Default peak mass tolerance, same as in Q3
public static final float DEFAULT_PEAK_TOLERANCE_PPM = 25f;
//number of peaks used by Q3
public static final int MAX_Q3_PEAKS = 5;
protected MSRun run;
protected float ratioOnePeak = QuantEvent.MISSING_RATIO;
//boundaries
protected int minScan = 0;
protected int maxScan = 0;
protected float minMz = 0;
protected float maxMz = 0;
//resolution. Bumping this up makes things take longer, and also makes things look a bit funny
protected int resolution = DEFAULT_RESOLUTION;
public static final int DEFAULT_RESOLUTION = 100;
//info about the quant event
protected int lightFirstScanLine = 0;
protected int lightLastScanLine = 0;
protected int heavyFirstScanLine = 0;
protected int heavyLastScanLine = 0;
protected float lightMz = 0;
protected float heavyMz = 0;
protected int charge = 0;
protected double upperZBoundNearPeak = 0;
protected int numSafePeaks = -1;
protected float[] peakMzs = null;
protected float peakSeparationMass = DEFAULT_PEAK_SEPARATION_MASS;
protected float peakTolerancePPM = DEFAULT_PEAK_TOLERANCE_PPM;
protected boolean shouldGenerateLineCharts = false;
protected Map<Integer, PanelWithLineChart> scanLineChartMap = null;
protected boolean shouldGenerate3DChart = false;
protected PanelWithRPerspectivePlot contourPlot = null;
protected PanelWithLineChart intensitySumChart;
protected PanelWithLineChart intensitySumPeaksChart;
//3D parameter defaults
public static final int DEFAULT_CONTOUR_PLOT_WIDTH = 1000;
public static final int DEFAULT_CONTOUR_PLOT_HEIGHT = 1000;
public static final int DEFAULT_CONTOUR_PLOT_ROTATION = 80;
public static final int DEFAULT_CONTOUR_PLOT_TILT = 20;
public static final boolean DEFAULT_CONTOUR_PLOT_SHOW_AXES = true;
//3D plot parameters
protected int contourPlotWidth = DEFAULT_CONTOUR_PLOT_WIDTH;
protected int contourPlotHeight = DEFAULT_CONTOUR_PLOT_HEIGHT;
protected int contourPlotRotationAngle = DEFAULT_CONTOUR_PLOT_ROTATION;
protected int contourPlotTiltAngle = DEFAULT_CONTOUR_PLOT_TILT;
protected boolean contourPlotShowAxes = DEFAULT_CONTOUR_PLOT_SHOW_AXES;
//this is a bit of a hack -- we store and return the scan level of the event scan.
//As long as we're looking at scans in this run
protected int idEventScan = -1;
protected float idEventMz = -1;
protected boolean specifiedScanFoundMS1 = false;
//information about other events included with this quant event because they're similar
protected List<Integer> otherEventScans;
protected List<Float> otherEventMZs;
public PanelWithSpectrumChart()
{
super();
}
protected void init()
{
super.init();
setPalette(PanelWithHeatMap.PALETTE_POSITIVE_WHITE_BLUE_NEGATIVE_BLACK_RED);
setAxisLabels("scan", "m/z");
scanLineChartMap = new HashMap<Integer, PanelWithLineChart>();
}
public PanelWithSpectrumChart(MSRun run, int minScan, int maxScan, float minMz, float maxMz,
int lightFirstScanLine, int lightLastScanLine,
int heavyFirstScanLine, int heavyLastScanLine,
float lightMz, float heavyMz, int charge)
{
init();
this.run = run;
this.minScan = minScan;
this.maxScan = maxScan;
this.minMz = minMz;
this.maxMz = maxMz;
this.lightFirstScanLine = lightFirstScanLine;
this.lightLastScanLine = lightLastScanLine;
this.heavyFirstScanLine = heavyFirstScanLine;
this.heavyLastScanLine = heavyLastScanLine;
this.lightMz = lightMz;
this.heavyMz = heavyMz;
this.charge = charge;
}
/**
* Generate all the charts
*/
public void generateCharts()
{
int minScanIndex = Math.abs(run.getIndexForScanNum(minScan));
int maxScanIndex = Math.abs(run.getIndexForScanNum(maxScan));
int numScans = maxScanIndex - minScanIndex + 1;
int numMzBins = (int) ((float) resolution * (maxMz - minMz)) + 1;
double[] scanValues = new double[numScans];
double[] mzValues = new double[numMzBins];
for (int i=0; i<numScans; i++)
scanValues[i] = run.getScanNumForIndex(minScanIndex + i);
for (int i=0; i<numMzBins; i++)
mzValues[i] = minMz + (i / (float) resolution);
//Resampling adds some slop on each end
float minMzForRange = (int) minMz;
if (minMzForRange > minMz) minMzForRange--;
float maxMzForRange = (int) maxMz;
if (maxMzForRange < maxMz) maxMzForRange++;
FloatRange mzWindowForResample = new FloatRange(minMzForRange, maxMzForRange);
double[][] intensityValues = new double[numScans][numMzBins];
_log.debug("Loading spectrum in range....");
double[] sumIntensitiesInQuantRange = new double[numMzBins];
//carry just the intensities used in quantitation
double[] sumPeakIntensitiesInQuantRange = new double[numMzBins];
if (numSafePeaks < 0)
numSafePeaks = Math.min(Math.round((heavyMz - lightMz) * charge), MAX_Q3_PEAKS);
if (heavyMz < lightMz)
numSafePeaks = 4;
if (peakMzs != null)
numSafePeaks = Math.min(numSafePeaks, peakMzs.length);
List<Float> peakMzsToQuantitate = new ArrayList<Float>();
List<Float> nonMonoisotopicPeakMzList = new ArrayList<Float>();
for (int i=0; i<numSafePeaks; i++)
{
float lightPeakMz = lightMz + (peakSeparationMass / charge) * i;
float heavyPeakMz = heavyMz + (peakSeparationMass / charge) * i;
if (peakMzs != null)
{
lightPeakMz = peakMzs[i];
heavyPeakMz = heavyMz + (lightPeakMz - lightMz);
}
peakMzsToQuantitate.add(lightPeakMz);
peakMzsToQuantitate.add(heavyPeakMz);
nonMonoisotopicPeakMzList.add(lightPeakMz);
if (heavyPeakMz > lightMz)
nonMonoisotopicPeakMzList.add(heavyPeakMz);
}
int heavyMonoisotopicPeakIndex = numSafePeaks;
float[] nonMonoisotopicPeakMzs = new float[nonMonoisotopicPeakMzList.size()];
for (int i=0; i<nonMonoisotopicPeakMzs.length; i++)
{
nonMonoisotopicPeakMzs[i] = nonMonoisotopicPeakMzList.get(i);
}
Collections.sort(peakMzsToQuantitate);
//dhmay adding 20090723
//For calculating single-peak ratio based on theoretical highest peak
int maxIdealPeakIndex = Spectrum.calcMaxIdealPeakIndex(lightMz - Spectrum.HYDROGEN_ION_MASS) * charge;
float lightMaxIdealPeakIntensitySum = 0f;
float heavyMaxIdealPeakIntensitySum = 0f;
for (int scanArrayIndex = 0; scanArrayIndex < numScans; scanArrayIndex++)
{
int scanIndex = minScanIndex + scanArrayIndex;
MSRun.MSScan scan = run.getScan(scanIndex);
if (scan.getNum() == idEventScan)
specifiedScanFoundMS1 = true;
float[] signalFromResample = Spectrum.Resample(scan.getSpectrum(), mzWindowForResample, resolution);
int firstIndexToKeep = -1;
for (int i=0; i<signalFromResample.length; i++)
{
float mzValueThisIndex = minMzForRange + (i / (float) resolution);
if (mzValueThisIndex >= minMz && firstIndexToKeep == -1)
{
firstIndexToKeep = i;
break;
}
}
//this is horrible. arraycopy would be better, but need to convert float to double
double[] signal = new double[numMzBins];
for (int i=0; i<numMzBins; i++)
{
signal[i] = signalFromResample[firstIndexToKeep + i];
}
if (shouldGenerateLineCharts)
{
double[] signalPeaks = new double[numMzBins];
int currentPeakIndex = -1;
float minMzCurrentPeak = -1;
float maxMzCurrentPeak = -1;
for (int i=0; i<numMzBins; i++)
{
double mz = mzValues[i];
if (mz > maxMzCurrentPeak && currentPeakIndex < peakMzsToQuantitate.size()-1)
{
currentPeakIndex++;
float currentPeakMz = peakMzsToQuantitate.get(currentPeakIndex);
float currentPeakMass = (currentPeakMz - Spectrum.HYDROGEN_ION_MASS) * charge;
float deltaMz = (peakTolerancePPM * currentPeakMass / 1000000f) / charge;
minMzCurrentPeak = currentPeakMz - deltaMz;
maxMzCurrentPeak = currentPeakMz + deltaMz;
}
if (mz >= minMzCurrentPeak && mz <= maxMzCurrentPeak)
{
signalPeaks[i] += signal[i];
}
}
upperZBoundNearPeak = Math.max(upperZBoundNearPeak, BasicStatistics.max(signalPeaks));
PanelWithLineChart lineChart =
new PanelWithPeakChart(mzValues, signalPeaks, "Scan " + (int) scanValues[scanArrayIndex],
PanelWithLineChart.defaultShape, Color.BLUE);
lineChart.addData(mzValues, signal, "all",
PanelWithLineChart.defaultShape, Color.GRAY);
lineChart.setAxisLabels("m/z", "intensity");
scanLineChartMap.put((int) scanValues[scanArrayIndex], lineChart);
}
intensityValues[scanArrayIndex] = signal;
upperZBound = Math.max(upperZBound, BasicStatistics.max(signal));
if (scan.getNum() >= lightFirstScanLine && scan.getNum() >= heavyFirstScanLine &&
scan.getNum() <= lightLastScanLine && scan.getNum() <= heavyLastScanLine)
{
int currentPeakIndex = -1;
float minMzCurrentPeak = -1;
float maxMzCurrentPeak = -1;
for (int i=0; i<sumIntensitiesInQuantRange.length; i++)
{
sumIntensitiesInQuantRange[i] += signal[i];
float mz = (float) mzValues[i];
if (mz > maxMzCurrentPeak && currentPeakIndex < peakMzsToQuantitate.size()-1)
{
currentPeakIndex++;
float currentPeakMz = peakMzsToQuantitate.get(currentPeakIndex);
float currentPeakMass = (currentPeakMz - Spectrum.HYDROGEN_ION_MASS) * charge;
float deltaMz = (peakTolerancePPM * currentPeakMass / 1000000f) / charge;
minMzCurrentPeak = currentPeakMz - deltaMz;
maxMzCurrentPeak = currentPeakMz + deltaMz;
}
if (mz >= minMzCurrentPeak && mz <= maxMzCurrentPeak)
{
sumPeakIntensitiesInQuantRange[i] += signal[i];
//dhmay adding 20090723 for single-peak quantitation
if (currentPeakIndex == maxIdealPeakIndex)
lightMaxIdealPeakIntensitySum += signal[i];
else if (currentPeakIndex == heavyMonoisotopicPeakIndex + maxIdealPeakIndex)
heavyMaxIdealPeakIntensitySum += signal[i];
}
}
}
}
ratioOnePeak = lightMaxIdealPeakIntensitySum / Math.max(heavyMaxIdealPeakIntensitySum, 0.001f);
double maxIntensityOnSumChart = BasicStatistics.max(sumIntensitiesInQuantRange);
double maxPeakIntensity = BasicStatistics.max(sumPeakIntensitiesInQuantRange);
if (maxIntensityOnSumChart < 1)
maxIntensityOnSumChart = maxPeakIntensity;
float[] monoisotopicMzs = new float[] { lightMz, heavyMz };
double intensityForSumChartHeight = maxIntensityOnSumChart * 1.5;
float[] monoisotopicIntensitiesSumChart =
new float[] { (float) intensityForSumChartHeight, (float) intensityForSumChartHeight};
intensitySumChart = new PanelWithPeakChart(mzValues, sumPeakIntensitiesInQuantRange,"Peak Intensities");
intensitySumChart.addData(mzValues, sumIntensitiesInQuantRange,"Intensity Sum",
PanelWithLineChart.defaultShape, Color.LIGHT_GRAY);
if (heavyMz > 0)
intensitySumChart.addDataFloat(monoisotopicMzs, monoisotopicIntensitiesSumChart,
"Monoisotopic light and heavy", PanelWithLineChart.defaultShape, Color.GREEN);
else
intensitySumChart.addDataFloat(new float[] {lightMz}, new float[] { (float) intensityForSumChartHeight },
"Monoisotopic light and heavy", PanelWithLineChart.defaultShape, Color.GREEN);
if (nonMonoisotopicPeakMzs.length > 0)
{
//for (float mz : nonMonoisotopicPeakMzs) System.err.println("***" + mz);
float[] nonmonoisotopicIntensitiesSumChart =
new float[nonMonoisotopicPeakMzs.length];
Arrays.fill(nonmonoisotopicIntensitiesSumChart, (float) intensityForSumChartHeight);
//System.err.println("ADDING LINES, peakMzs null ? " + (peakMzs == null));
if (peakMzs != null)
{
intensitySumChart.addDataFloat(nonMonoisotopicPeakMzs, nonmonoisotopicIntensitiesSumChart,
"Nonmonoisotopic light and heavy (exact)", PanelWithLineChart.defaultShape, Color.RED);
}
else
{
intensitySumChart.addDataFloat(nonMonoisotopicPeakMzs, nonmonoisotopicIntensitiesSumChart,
"Nonmonoisotopic light and heavy (approx)", PanelWithLineChart.defaultShape, Color.ORANGE);
}
}
intensitySumChart.getChart().removeLegend();
intensitySumChart.setAxisLabels("m/z","Intensity (Sum)");
if (maxPeakIntensity > 1)
((XYPlot) intensitySumChart.getPlot()).getRangeAxis().setRange(0, maxPeakIntensity * 1.25);
intensitySumChart.setSize(getWidth(), getHeight());
if (shouldGenerateLineCharts)
{
float rangeMax = (float) upperZBoundNearPeak * 1.25f;
for (PanelWithLineChart lineChart : scanLineChartMap.values())
{
((XYPlot) lineChart.getPlot()).getRangeAxis().setRange(0, rangeMax);
float[] monoisotopicIntensities =
new float[] { (float) rangeMax, rangeMax};
lineChart.addDataFloat(monoisotopicMzs, monoisotopicIntensities, "Monoisotopic light and heavy",
PanelWithLineChart.defaultShape, Color.GREEN);
if (nonMonoisotopicPeakMzs.length > 0)
{
float[] nonMonoisotopicIntensities =
new float[nonMonoisotopicPeakMzs.length];
Arrays.fill(nonMonoisotopicIntensities, rangeMax);
lineChart.addDataFloat(nonMonoisotopicPeakMzs, nonMonoisotopicIntensities,
"Nonmonoisotopic light and heavy", PanelWithLineChart.defaultShape, Color.ORANGE);
}
lineChart.getChart().removeLegend();
}
}
int lightFirstScanLineIndex = -1;
int lightLastScanLineIndex = -1;
int heavyFirstScanLineIndex = -1;
int heavyLastScanLineIndex = -1;
int numScansPadded = maxScan - minScan + 1;
double[] scanValuesPadded;
double[][] intensityValuesPadded;
setPaintScale(createHeatMapPaintScale());
if (numScansPadded == numScans)
{
scanValuesPadded = scanValues;
intensityValuesPadded = intensityValues;
if (lightFirstScanLine > 0)
{
for (int i=0; i<scanValues.length; i++)
{
if (scanValues[i] < lightFirstScanLine)
lightFirstScanLineIndex = i;
else break;
}
}
if (lightLastScanLine > 0)
{
for (int i=0; i<scanValues.length; i++)
{
if (scanValues[i] <= lightLastScanLine +1)
lightLastScanLineIndex = i;
else break;
}
}
if (heavyFirstScanLine > 0)
{
for (int i=0; i<scanValues.length; i++)
{
if (scanValues[i] < heavyFirstScanLine)
heavyFirstScanLineIndex = i;
else break;
}
}
if (heavyLastScanLine > 0)
{
for (int i=0; i<scanValues.length; i++)
{
if (scanValues[i] <= heavyLastScanLine +1)
heavyLastScanLineIndex = i;
else break;
}
}
}
else
{
_log.debug("Padding! unpadded: " + numScans + ", padded: " + numScansPadded);
scanValuesPadded = new double[numScansPadded];
intensityValuesPadded = new double[numScansPadded][numMzBins];
int unPaddedIndex = 0;
for (int i=0; i<scanValuesPadded.length; i++)
{
int scanValue = minScan + i;
scanValuesPadded[i] = scanValue;
if (unPaddedIndex < scanValues.length-1 && scanValue >= scanValues[unPaddedIndex+1])
unPaddedIndex++;
System.arraycopy(intensityValues[unPaddedIndex], 0, intensityValuesPadded[i], 0,
intensityValues[unPaddedIndex].length);
}
//add the lines for the scanlines, just outside the specified boundaries
if (lightFirstScanLine > 0)
{
lightFirstScanLineIndex = 0;
for (int i=0; i<scanValuesPadded.length; i++)
{
if (scanValuesPadded[i] < lightFirstScanLine)
{
lightFirstScanLineIndex = i;
}
else break;
}
}
if (lightLastScanLine > 0)
{
lightLastScanLineIndex = 0;
int nextScanAfterLine2 = 0;
for (int i=0; i<scanValues.length; i++)
if (scanValues[i] > lightLastScanLine)
{
nextScanAfterLine2 = (int) scanValues[i];
break;
}
if (nextScanAfterLine2 == 0)
lightLastScanLineIndex = 0;
else
{
for (int i=0; i<scanValuesPadded.length; i++)
{
if (scanValuesPadded[i] >= nextScanAfterLine2)
{
lightLastScanLineIndex = i;
break;
}
}
}
}
if (heavyFirstScanLine > 0)
{
heavyFirstScanLineIndex = 0;
for (int i=0; i<scanValuesPadded.length; i++)
{
if (scanValuesPadded[i] < heavyFirstScanLine)
{
heavyFirstScanLineIndex = i;
}
else break;
}
}
if (heavyLastScanLine > 0)
{
heavyLastScanLineIndex = 0;
int nextScanAfterLine2 = 0;
for (int i=0; i<scanValues.length; i++)
if (scanValues[i] > heavyLastScanLine)
{
nextScanAfterLine2 = (int) scanValues[i];
break;
}
if (nextScanAfterLine2 == 0)
heavyLastScanLineIndex = 0;
else
{
for (int i=0; i<scanValuesPadded.length; i++)
{
if (scanValuesPadded[i] >= nextScanAfterLine2)
{
heavyLastScanLineIndex = i;
break;
}
}
}
}
}
int lightMzIndex = 0;
for (lightMzIndex=0; lightMzIndex<intensityValuesPadded[0].length; lightMzIndex++)
if (mzValues[lightMzIndex] > lightMz)
break;
int heavyMzIndex = 0;
for (heavyMzIndex=0; heavyMzIndex<intensityValuesPadded[0].length; heavyMzIndex++)
if (mzValues[heavyMzIndex] > heavyMz)
break;
if (lightFirstScanLineIndex > 0)
shadeArea(intensityValuesPadded, 0, 0, lightFirstScanLineIndex, lightMzIndex);
if (lightLastScanLineIndex > 0)
shadeArea(intensityValuesPadded, lightLastScanLineIndex, 0, intensityValuesPadded.length-1, lightMzIndex);
if (heavyFirstScanLineIndex > 0)
shadeArea(intensityValuesPadded, 0, heavyMzIndex, heavyFirstScanLineIndex,
intensityValuesPadded[0].length-1);
if (lightLastScanLineIndex > 0)
shadeArea(intensityValuesPadded, heavyLastScanLineIndex, heavyMzIndex, intensityValuesPadded.length-1,
intensityValuesPadded[0].length-1);
//if light and heavy first scan are same, shade the remaining area on the left. Same with last scan
if (lightFirstScanLineIndex > 0 && heavyFirstScanLineIndex == lightFirstScanLineIndex)
shadeArea(intensityValuesPadded, 0, lightMzIndex+1, lightFirstScanLineIndex, heavyMzIndex-1);
if (lightLastScanLineIndex > 0 && heavyLastScanLineIndex == lightLastScanLineIndex)
shadeArea(intensityValuesPadded, lightLastScanLineIndex, lightMzIndex+1,
intensityValuesPadded.length-1, heavyMzIndex-1);
//special value for tick intensity
float intensityForTickMark = -2f;
float intensityForIdCross = -2f;
//cross for ID event
if (idEventScan > 0 && idEventMz > 0)
{
drawHeatmapCrossForEvent(scanValuesPadded, mzValues, intensityValuesPadded,
idEventScan, idEventMz, intensityForIdCross);
}
if (otherEventScans != null && !otherEventScans.isEmpty())
{
for (int i=0; i< otherEventScans.size(); i++)
drawHeatmapCrossForEvent(scanValuesPadded, mzValues, intensityValuesPadded,
otherEventScans.get(i), otherEventMZs.get(i), intensityForIdCross);
}
//tick marks for specified m/z's
if (lightMz > 0)
{
placeHeatmapTick(mzValues, intensityValuesPadded, lightMz, intensityForTickMark);
}
if (heavyMz > 0)
{
placeHeatmapTick(mzValues, intensityValuesPadded, heavyMz, intensityForTickMark);
}
if (peakMzs != null)
{
for (float mz : nonMonoisotopicPeakMzs)
placeHeatmapTick(mzValues, intensityValuesPadded, mz, intensityForTickMark);
}
if (shouldGenerate3DChart)
generate3DPlot(scanValuesPadded, mzValues, intensityValuesPadded);
_log.debug("Done loading spectrum in range.");
setData(scanValuesPadded, mzValues, intensityValuesPadded);
getChart().removeLegend();
//try {contourPlot.saveAllImagesToFiles(new File("/home/dhmay/temp/charts"));} catch(IOException e) {}
((XYPlot) _plot).getDomainAxis().setRange(minScan, maxScan);
((XYPlot) _plot).getRangeAxis().setRange(minMz, maxMz);
}
protected void placeHeatmapTick(double[] mzValues, double[][]intensityValuesPadded, float mzVal, float intensityForTickMark)
{
int closestLightMzIndex = Math.abs(Arrays.binarySearch(mzValues, mzVal));
int[] tickXVals = new int[] { 0, 1, 2, 3, 0, 1, 2, 3,
intensityValuesPadded.length-1, intensityValuesPadded.length-2,
intensityValuesPadded.length-3, intensityValuesPadded.length-4,
intensityValuesPadded.length-1, intensityValuesPadded.length-2,
intensityValuesPadded.length-3, intensityValuesPadded.length-4};
int indMaybePlus1 = Math.min(closestLightMzIndex+1, intensityValuesPadded[0].length-1);
int[] tickYVals = new int[] { closestLightMzIndex, closestLightMzIndex, closestLightMzIndex, closestLightMzIndex,
indMaybePlus1, indMaybePlus1, indMaybePlus1, indMaybePlus1,
closestLightMzIndex, closestLightMzIndex, closestLightMzIndex, closestLightMzIndex,
indMaybePlus1, indMaybePlus1, indMaybePlus1, indMaybePlus1};
for (int i=0; i<tickXVals.length; i++)
{
intensityValuesPadded[tickXVals[i]][tickYVals[i]] = intensityForTickMark;
}
}
/**
* Create a 3D plot for a quantitative event
* @param scanValuesPadded
* @param mzValues
* @param intensityValuesPadded
*/
protected void generate3DPlot(double[] scanValuesPadded, double[] mzValues, double[][] intensityValuesPadded)
{
_log.debug("Generating R contour plot...");
//float[][] intensityValuesPaddedAsFloat = new float[intensityValuesPadded.length][intensityValuesPadded[0].length];
//for (int i=0; i<intensityValuesPadded.length; i++)
// for (int j=0; j<intensityValuesPadded[0].length; j++)
// intensityValuesPaddedAsFloat[i][j] = (float) intensityValuesPadded[i][j];
//
//JFrame frame = SurfaceFrame.ShowSurfaceFrame(intensityValuesPaddedAsFloat);
//frame.setVisible(true);
contourPlot = new PanelWithRPerspectivePlot();
contourPlot.setName("3D");
contourPlot.setRotationAngle(contourPlotRotationAngle);
contourPlot.setTiltAngle(contourPlotTiltAngle);
contourPlot.setChartWidth(contourPlotWidth);
contourPlot.setChartHeight(contourPlotHeight);
contourPlot.setUseGradientForColor(true);
contourPlot.setShowBorders(false);
contourPlot.setShowBox(contourPlotShowAxes);
//contourPlot.setTiltAngles(Arrays.asList(new Integer[] { 82, 76, 70, 66, 60, 54, 49, 45, 41, 38, 35, 32, 30, 28, 27, 26, 25, 24, 23 }));
// contourPlot.setRotationAngles(Arrays.asList(new Integer[] {0, 5, 10, 15, 20, 25, 30, 35, 40, 45,
// 50, 55, 60, 65, 70, 75, 80, 85, 85}));
double[] twoZeroes = new double[] {0,0};
if (lightFirstScanLine > 0)
{
double[] line1XValues = new double[] {lightFirstScanLine, lightFirstScanLine};
double[] line1YValues = new double[] {minMz, lightMz};
contourPlot.addLine(line1XValues, line1YValues, twoZeroes, "red", PanelWithRPerspectivePlot.LINE_STYLE_DASHED);
}
if (lightLastScanLine > 0)
{
double[] line2XValues = new double[] {lightLastScanLine, lightLastScanLine};
double[] line2YValues = new double[] {minMz, lightMz};
contourPlot.addLine(line2XValues, line2YValues, twoZeroes, "red", PanelWithRPerspectivePlot.LINE_STYLE_DASHED);
}
if (heavyFirstScanLine > 0)
{
double[] line1XValues = new double[] {heavyFirstScanLine, heavyFirstScanLine};
double[] line1YValues = new double[] {heavyMz, maxMz};
contourPlot.addLine(line1XValues, line1YValues, twoZeroes, "red", PanelWithRPerspectivePlot.LINE_STYLE_DASHED);
}
if (heavyLastScanLine > 0)
{
double[] line2XValues = new double[] {heavyLastScanLine, heavyLastScanLine};
double[] line2YValues = new double[] {heavyMz, maxMz};
contourPlot.addLine(line2XValues, line2YValues, twoZeroes, "red", PanelWithRPerspectivePlot.LINE_STYLE_DASHED);
}
//draw a little X on the MS/MS event
if (idEventScan > 0 && idEventMz > 0)
{
drawContourCrossForEvent(idEventScan, idEventMz, "blue");
}
if (otherEventScans != null && !otherEventScans.isEmpty())
{
for (int i=0; i< otherEventScans.size(); i++)
drawContourCrossForEvent(otherEventScans.get(i), otherEventMZs.get(i), "yellow");
}
double closestLightMz = mzValues[Math.abs(Arrays.binarySearch(mzValues, lightMz))];
double closestHeavyMz = mzValues[Math.abs(Arrays.binarySearch(mzValues, heavyMz))];
//light and heavy monoisotopes
double[] tickXValues = new double[] { minScan-1, maxScan+1 };
double[] lightTickYValues = new double[] { closestLightMz, closestLightMz };
double[] heavyTickYValues = new double[] { closestHeavyMz, closestHeavyMz };
contourPlot.addLine(tickXValues, lightTickYValues, twoZeroes, "red");
contourPlot.addLine(tickXValues, heavyTickYValues, twoZeroes, "red");
contourPlot.plot(scanValuesPadded, mzValues, intensityValuesPadded);
_log.debug("Generated R contour plot.");
}
/**
* Invert the color of a region of intensity values
* @param intensityValuesPadded
* @param xMin
* @param yMin
* @param xMax
* @param yMax
*/
protected void shadeArea(double[][] intensityValuesPadded, int xMin, int yMin, int xMax, int yMax)
{
for (int x=xMin; x<=xMax; x++)
{
for (int y=yMin; y<=yMax; y++)
{
//Set intensity of background (<1% of max) in shaded region to special value
if (intensityValuesPadded[x][y] < upperZBound / 100f) intensityValuesPadded[x][y] = -1;
}
}
}
protected void drawHeatmapCrossForEvent(double[] scanValuesPadded, double[] mzValues, double[][] intensityValuesPadded,
int scan, float mz, float intensityForIdCross)
{
//cross for ID event
if (scan > 0 && mz > 0)
{
int closestEventMzIndex = Math.abs(Arrays.binarySearch(mzValues, mz));
int closestEventScanIndex = Math.abs(Arrays.binarySearch(scanValuesPadded, scan));
conditionallySetValueAt(intensityValuesPadded, closestEventScanIndex, closestEventMzIndex, intensityForIdCross);
conditionallySetValueAt(intensityValuesPadded, closestEventScanIndex-1, closestEventMzIndex-1, intensityForIdCross);
conditionallySetValueAt(intensityValuesPadded, closestEventScanIndex+1, closestEventMzIndex+1, intensityForIdCross);
conditionallySetValueAt(intensityValuesPadded, closestEventScanIndex-1, closestEventMzIndex+1, intensityForIdCross);
conditionallySetValueAt(intensityValuesPadded, closestEventScanIndex+1, closestEventMzIndex-1, intensityForIdCross);
}
}
protected void conditionallySetValueAt(double[][] intensityValuesPadded, int scanIndex, int mzIndex, float intensity)
{
int maxScanIndex = intensityValuesPadded.length-1;
int maxMzIndex = intensityValuesPadded[0].length-1;
if (scanIndex >= 0 && scanIndex <= maxScanIndex && mzIndex >= 0 && mzIndex <= maxMzIndex)
intensityValuesPadded[scanIndex][mzIndex] = intensity;
}
protected void drawContourCrossForEvent(int scan, float mz, String color)
{
double crossTop = mz+0.1;
double crossBottom = mz-0.1;
double crossLeft = scan-2;
double crossRight = scan+2;
double[] twoZeroes = new double[] {0,0};
contourPlot.addLine(new double[] {crossLeft, crossRight},
new double[] {crossTop, crossBottom}, twoZeroes, color);
contourPlot.addLine(new double[] {crossLeft, crossRight},
new double[] {crossBottom, crossTop}, twoZeroes, color);
}
/**
* Create a paint scale that cycles from white to blue in the positive range, defines a shade value at 1.0, and
* defaults to RED for everything else
* @return
*/
protected LookupPaintScale createHeatMapPaintScale()
{
LookupPaintScale result = new LookupPaintScale(-1, upperZBound*=1.04, Color.RED);
addValuesToPaintScale(result, 0, upperZBound, Color.WHITE, Color.BLUE);
//-1 is special value for background level
Color shadeColor = new Color(245, 245, 245);
addValuesToPaintScale(result, -1.1, -0.9, shadeColor, shadeColor);
return result;
}
/**
*
* @param imageWidthEachScan
* @param imageHeightEachScan
* @param maxTotalImageHeight a hard boundary on the total image height. If imageHeightEachScan is too big,
* given the total number of charts and this arg, it gets knocked down
* @param outputFile
* @throws java.io.IOException
*/
public void savePerScanSpectraImage(int imageWidthEachScan, int imageHeightEachScan, int maxTotalImageHeight,
File outputFile)
throws IOException
{
int numCharts = scanLineChartMap.size();
int widthPaddingForLabels = 50;
imageHeightEachScan = Math.min(imageHeightEachScan, maxTotalImageHeight / numCharts);
List<Integer> allScanNumbers = new ArrayList<Integer>(scanLineChartMap.keySet());
Collections.sort(allScanNumbers);
List<PanelWithChart> allCharts = new ArrayList<PanelWithChart>();
for (int scanNumber : allScanNumbers)
{
PanelWithLineChart scanChart = scanLineChartMap.get(scanNumber);
allCharts.add(scanChart);
scanChart.setSize(imageWidthEachScan - widthPaddingForLabels, imageHeightEachScan);
}
BufferedImage perScanChartImage = MultiChartDisplayPanel.createImageForAllCharts(allCharts);
BufferedImage perScanChartImageWithLabels = new BufferedImage(imageWidthEachScan,
perScanChartImage.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = perScanChartImageWithLabels.createGraphics();
g.drawImage(perScanChartImage, widthPaddingForLabels, 0, null);
g.setPaint(Color.WHITE);
g.drawRect(0, 0, widthPaddingForLabels, perScanChartImage.getHeight());
for (int i=0; i<allCharts.size(); i++)
{
int scanNumber = allScanNumbers.get(i);
int chartTop = i * imageHeightEachScan;
int chartMiddle = chartTop + (imageHeightEachScan / 2);
if (lightFirstScanLine > 0 && lightLastScanLine > 0)
{
if (scanNumber >= lightFirstScanLine && scanNumber <= lightLastScanLine)
g.setPaint(Color.GREEN);
else
g.setPaint(Color.RED);
}
else g.setPaint(Color.BLACK);
g.drawString("" + scanNumber, 5, chartMiddle);
}
g.dispose();
ImageIO.write(perScanChartImageWithLabels,"png",outputFile);
}
/* Tooltips don't work with XYBlockRenderer
protected class SpectrumToolTipGenerator implements XYZToolTipGenerator
{
protected double[] scanValues;
protected double[] mzValues;
public SpectrumToolTipGenerator(double[] scanValues, double[] mzValues)
{
this.scanValues = scanValues;
this.mzValues = mzValues;
}
public String generateToolTip(XYDataset data, int series, int item)
{
System.err.println("asdf1");
return "scan " + scanValues[series] + ", m/z " + mzValues[item];
}
public String generateToolTip(XYZDataset data, int series, int item)
{
System.err.println("asdf2");
return "scan " + scanValues[series] + ", m/z " + mzValues[item];
}
}
*/
public int getMinScan()
{
return minScan;
}
public void setMinScan(int minScan)
{
this.minScan = minScan;
}
public int getMaxScan()
{
return maxScan;
}
public void setMaxScan(int maxScan)
{
this.maxScan = maxScan;
}
public float getMinMz()
{
return minMz;
}
public void setMinMz(float minMz)
{
this.minMz = minMz;
}
public float getMaxMz()
{
return maxMz;
}
public void setMaxMz(float maxMz)
{
this.maxMz = maxMz;
}
public int getResolution()
{
return resolution;
}
public void setResolution(int resolution)
{
this.resolution = resolution;
}
public MSRun getRun()
{
return run;
}
public void setRun(MSRun run)
{
this.run = run;
}
public int getLightFirstScanLine()
{
return lightFirstScanLine;
}
public void setLightFirstScanLine(int lightFirstScanLine)
{
this.lightFirstScanLine = lightFirstScanLine;
}
public int getLightLastScanLine()
{
return lightLastScanLine;
}
public void setLightLastScanLine(int lightLastScanLine)
{
this.lightLastScanLine = lightLastScanLine;
}
public int getHeavyFirstScanLine()
{
return heavyFirstScanLine;
}
public void setHeavyFirstScanLine(int heavyFirstScanLine)
{
this.heavyFirstScanLine = heavyFirstScanLine;
}
public int getHeavyLastScanLine()
{
return heavyLastScanLine;
}
public void setHeavyLastScanLine(int heavyLastScanLine)
{
this.heavyLastScanLine = heavyLastScanLine;
}
public Map<Integer, PanelWithLineChart> getScanLineChartMap()
{
return scanLineChartMap;
}
public void setScanLineChartMap(Map<Integer, PanelWithLineChart> scanLineChartMap)
{
this.scanLineChartMap = scanLineChartMap;
}
public boolean isSpecifiedScanFoundMS1()
{
return specifiedScanFoundMS1;
}
public void setSpecifiedScanFoundMS1(boolean specifiedScanFoundMS1)
{
this.specifiedScanFoundMS1 = specifiedScanFoundMS1;
}
public boolean isGenerate3DChart()
{
return shouldGenerate3DChart;
}
public void setGenerate3DChart(boolean generate3DChart)
{
this.shouldGenerate3DChart = generate3DChart;
}
public PanelWithRPerspectivePlot getContourPlot()
{
return contourPlot;
}
public void setContourPlot(PanelWithRPerspectivePlot contourPlot)
{
this.contourPlot = contourPlot;
}
public int getContourPlotWidth()
{
return contourPlotWidth;
}
public void setContourPlotWidth(int contourPlotWidth)
{
this.contourPlotWidth = contourPlotWidth;
}
public int getContourPlotHeight()
{
return contourPlotHeight;
}
public void setContourPlotHeight(int contourPlotHeight)
{
this.contourPlotHeight = contourPlotHeight;
}
public boolean getContourPlotShowAxes()
{
return contourPlotShowAxes;
}
public void setContourPlotShowAxes(boolean contourPlotShowAxes)
{
this.contourPlotShowAxes = contourPlotShowAxes;
}
public int getContourPlotRotationAngle()
{
return contourPlotRotationAngle;
}
public void setContourPlotRotationAngle(int contourPlotRotationAngle)
{
this.contourPlotRotationAngle = contourPlotRotationAngle;
}
public int getContourPlotTiltAngle()
{
return contourPlotTiltAngle;
}
public void setContourPlotTiltAngle(int contourPlotTiltAngle)
{
this.contourPlotTiltAngle = contourPlotTiltAngle;
}
public boolean isGenerateLineCharts()
{
return shouldGenerateLineCharts;
}
public void setGenerateLineCharts(boolean generateLineCharts)
{
this.shouldGenerateLineCharts = generateLineCharts;
}
public int getIdEventScan()
{
return idEventScan;
}
public void setIdEventScan(int idEventScan)
{
this.idEventScan = idEventScan;
}
public float getIdEventMz()
{
return idEventMz;
}
public void setIdEventMz(float idEventMz)
{
this.idEventMz = idEventMz;
}
public List<Integer> getOtherEventScans()
{
return otherEventScans;
}
public void setOtherEventScans(List<Integer> otherEventScans)
{
this.otherEventScans = otherEventScans;
}
public List<Float> getOtherEventMZs()
{
return otherEventMZs;
}
public void setOtherEventMZs(List<Float> otherEventMZs)
{
this.otherEventMZs = otherEventMZs;
}
public PanelWithLineChart getIntensitySumChart()
{
return intensitySumChart;
}
public void setIntensitySumChart(PanelWithLineChart intensitySumChart)
{
this.intensitySumChart = intensitySumChart;
}
public float getPeakSeparationMass()
{
return peakSeparationMass;
}
public void setPeakSeparationMass(float peakSeparationMass)
{
this.peakSeparationMass = peakSeparationMass;
}
public float getPeakTolerancePPM()
{
return peakTolerancePPM;
}
public void setPeakTolerancePPM(float peakTolerancePPM)
{
this.peakTolerancePPM = peakTolerancePPM;
}
public int getCharge()
{
return charge;
}
public void setCharge(int charge)
{
this.charge = charge;
}
public float getRatioOnePeak()
{
return ratioOnePeak;
}
public int getNumSafePeaks() {
return numSafePeaks;
}
public void setNumSafePeaks(int numSafePeaks) {
this.numSafePeaks = numSafePeaks;
}
public float[] getPeakMzs() {
return peakMzs;
}
public void setPeakMzs(float[] peakMzs) {
this.peakMzs = peakMzs;
}
}