/* * 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.toolbox.proteomics.gui; import org.fhcrc.cpl.toolbox.datastructure.FloatArray; import org.fhcrc.cpl.toolbox.datastructure.FloatRange; import org.fhcrc.cpl.toolbox.proteomics.feature.Spectrum; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.*; import java.util.Arrays; /** */ public class IntensityPlot implements java.io.Serializable { public static final String[] COLOR_SCHEMES = {"Cyan", "Grayscale", "Fire", "Rainbow", "Fancy"}; FloatArray x = new FloatArray(); FloatArray y = new FloatArray(); FloatArray z = new FloatArray(); public void setData(FloatArray x, FloatArray y, FloatArray z) { if (x.size() != y.size() || x.size() != z.size()) throw new IllegalArgumentException(); this.x = x; this.y = y; this.z = z; } int parse(Reader in) throws IOException { StreamTokenizer tokenizer = new StreamTokenizer(in); tokenizer.parseNumbers(); int tt; int c = 0; while (StreamTokenizer.TT_EOF != (tt = tokenizer.nextToken())) { if (StreamTokenizer.TT_NUMBER != tt) continue; int m = c % 3; c++; switch (m) { case 0: x.add((float)tokenizer.nval); break; case 1: y.add((float)tokenizer.nval); break; case 2: z.add((float)tokenizer.nval); break; } } x.setSize(z.size()); y.setSize(z.size()); return x.size(); } private final int bound(int a, int n, int x) { return a > x ? x : a < n ? n : a; } private final float bound(float x) { return x > 1.0 ? 1.0F : x < 0.0 ? 0.0F : x; } public void plotLog(BufferedImage image, float threshold, int yscale, String scheme) { ColorMap colorMap = mapForScheme(scheme); int type = image.getType(); //dhmay removing these checks along with adding support for blingy main image colors // if (type != BufferedImage.TYPE_USHORT_GRAY && // type != BufferedImage.TYPE_BYTE_GRAY && // type != BufferedImage.TYPE_3BYTE_BGR) // throw new IllegalArgumentException(); int width = image.getWidth(); int height = image.getHeight(); WritableRaster r = image.getRaster(); int maxColor = image.getType() == BufferedImage.TYPE_USHORT_GRAY ? (1 << 16) - 1 : (1 << 8) - 1; float nullColor = maxColor + 1.0F; FloatRange rangeZ = z.getRange(); threshold = Math.max(Math.max(1, threshold), rangeZ.min); rangeZ.max = Math.max(threshold + 1, rangeZ.max); double logMaxIntensity = Math.log(rangeZ.max); double logMinIntensity = Math.log(threshold); double scale1 = 1.0 / (logMaxIntensity - logMinIntensity); double scale2 = 1.0 / Math.log(1 + rangeZ.max - threshold); float gray; int length = x.size(); int xcurrent = width; float[] samples = new float[height]; for (int i = 0; i < length; i++) { float intensity = z.get(i); int xval = (int)x.get(i); int yval = (int)Math.round(y.get(i) * yscale); if (yval < 0 || yval >= height) continue; if (intensity <= threshold) gray = 0.0F; else { if (false) // too linear as threshold increases gray = (float)((Math.log((double)intensity) - logMinIntensity) * scale1); else // grainer, but more detail gray = (float)(Math.log(1 + intensity - threshold) * scale2); gray = bound(gray); } if (xval != xcurrent) { if (xcurrent < width) { if (yscale > 1) interpolate(samples, maxColor); plotColumn(r, xcurrent, height, samples, colorMap, maxColor); } Arrays.fill(samples, yscale > 1 ? nullColor : maxColor); xcurrent = xval; } int y = height - 1 - yval; samples[y] = Math.min(samples[y], maxColor - gray * maxColor); } if (xcurrent < width) { if (yscale > 1) interpolate(samples, maxColor); plotColumn(r, xcurrent, height, samples, colorMap, maxColor); } } private void plotColumn(java.awt.image.WritableRaster r, int xcurrent, int height, float[] samples, ColorMap scheme, int maxColor) { int b = r.getNumBands(); float colors[][] = scheme.remap(samples, maxColor); for (int i=0 ; i<b && i<colors.length ; i++) r.setSamples(xcurrent, 0, 1, height, i, colors[i]); } public void plotSqrt(BufferedImage image, float threshold, int yscale, String scheme) { ColorMap colorMap = mapForScheme(scheme); int type = image.getType(); if (type != BufferedImage.TYPE_USHORT_GRAY && type != BufferedImage.TYPE_BYTE_GRAY && type != BufferedImage.TYPE_3BYTE_BGR) throw new IllegalArgumentException(); int width = image.getWidth(); int height = image.getHeight(); WritableRaster r = image.getRaster(); int maxColor = image.getType() == BufferedImage.TYPE_USHORT_GRAY ? (1 << 16) - 1 : (1 << 8) - 1; float nullColor = maxColor + 1.0F; FloatRange rangeZ = z.getRange(); threshold = Math.max(Math.max(1, threshold), rangeZ.min); rangeZ.max = Math.max(threshold + 1, rangeZ.max); // double logMaxIntensity = Math.sqrt(rangeZ.max); // double logMinIntensity = Math.sqrt(threshold); double scale2 = 1.0 / Math.sqrt(rangeZ.max); float gray; int length = x.size(); int xcurrent = width; float[] samples = new float[height]; for (int i = 0; i < length; i++) { float intensity = z.get(i); int xval = (int)x.get(i); int yval = (int)Math.round(y.get(i) * yscale); if (yval < 0 || yval >= height) continue; if (intensity <= threshold) gray = 0.0F; else { gray = (float)(Math.sqrt(intensity) * scale2); gray = bound(gray); } if (xval != xcurrent) { if (xcurrent < width) { if (yscale > 1) interpolate(samples, maxColor); plotColumn(r, xcurrent, height, samples, colorMap, maxColor); } Arrays.fill(samples, yscale > 1 ? nullColor : maxColor); xcurrent = xval; } int y = height - 1 - yval; samples[y] = Math.min(samples[y], maxColor - gray * maxColor); } if (xcurrent < width) { if (yscale > 1) interpolate(samples, maxColor); plotColumn(r, xcurrent, height, samples, colorMap, maxColor); } } private void interpolate(float[] samples, int maxColor) { float nullColor = maxColor + 1; int height = samples.length; float p = nullColor; float c = samples[0]; float n; for (int j = 0; j < height; j++) { n = j < height - 1 ? samples[j + 1] : nullColor; if (nullColor == c) { if (nullColor != p && nullColor != n) samples[j] = (p + n) / 2.0F; else samples[j] = maxColor; } p = c; c = n; } } public void plot(BufferedImage image, float threshold) { int width = image.getWidth(); int height = image.getHeight(); WritableRaster r = image.getRaster(); int maxColor = image.getType() == BufferedImage.TYPE_BYTE_GRAY ? 0x000000ff : 0x0000ffff; FloatRange rangeZ = z.getRange(); double scaleIntensity = maxColor / (rangeZ.max - threshold); int gray; int length = x.size(); for (int i = 0; i < length; i++) { float intensity = z.get(i); if (intensity < threshold) continue; gray = (int)((intensity - threshold) * scaleIntensity); gray = bound(gray, 0, maxColor); int xval = (int)x.get(i); int yval = (int)y.get(i); if (xval < width && yval < height) { // r.setSample(xval, height - yval - 1, 0, maxColor - gray); r.setSample(xval, height - yval - 1, 0, 0); } } Graphics gfx = image.getGraphics(); gfx.setColor(Color.BLACK); } public BufferedImage plot(float threshold, boolean log, String colorScheme) { FloatRange rangeX = x.getRange(); FloatRange rangeY = y.getRange(); int width = (int)rangeX.max + 1; int height = (int)rangeY.max + 1; // TYPE_USHORT_GRAY does not seem to save reliably! int type = BufferedImage.TYPE_INT_RGB; BufferedImage image = new BufferedImage(width, height, type); Graphics gfx = image.getGraphics(); gfx.setColor(Color.WHITE); gfx.fillRect(0, 0, width, height); if (log) { plotLog(image, threshold, 1, colorScheme); } else plot(image, threshold); return image; } public void plot(OutputStream out, String format, boolean log, String colorScheme) throws IOException { BufferedImage image = plot(100, log, colorScheme); writePlot(out, image, format); } public static void writePlot(OutputStream out, Image image, String format) throws IOException { if (!(image instanceof BufferedImage)) throw new java.lang.UnsupportedOperationException(); ImageIO.write((BufferedImage)image, format, out); } public static void writePlot(File f, Image image, String format) throws IOException { // if (!f.exists()) // f.createNewFile(); // else if (!f.isFile()) // throw new IOException("Not a file: " + f.getPath()); if (!(image instanceof BufferedImage)) throw new java.lang.UnsupportedOperationException(); ImageIO.write((BufferedImage)image, format, f); } // public static void main(String[] args) // throws java.io.FileNotFoundException, java.io.IOException // { // String pathIn = null, pathOut = null; // boolean log = false; // // if (args.length < 2 || args.length > 3) // { // usage(); // } // for (int i = 0; i < args.length; i++) // { // if ("-log".equals(args[i])) // log = true; // else if (pathIn == null) // pathIn = args[i]; // else // pathOut = args[i]; // } // if (pathIn == null || pathOut == null) // usage(); // // run(pathIn, pathOut, log); // } private static void usage() { System.err.println("usage: java org.fhcrc.cpl.toolbox.proteomics.gui.IntensityPlot [-log] input.tsv output.[tiff|png]"); System.exit(1); } private static void run(String pathIn, String pathOut, boolean log, String colorScheme) throws IOException { long start = System.currentTimeMillis(); IntensityPlot plot = new IntensityPlot(); Reader in = new FileReader(pathIn); int rows = plot.parse(new FileReader(pathIn)); System.out.println("read " + rows + " rows."); in.close(); OutputStream out = new FileOutputStream(pathOut); String ext = pathOut.substring(pathOut.indexOf('.') + 1); plot.plot(out, ext, log, colorScheme); out.close(); File f = new File(pathOut); System.out.println("wrote " + f.getAbsolutePath() + ", " + f.length() + " bytes."); System.out.println("" + ((System.currentTimeMillis() - start) / 1000.0) + " seconds."); } /** * for performance we use instances of color maps (rather than singletons) * we cache the output array for reuse */ public static abstract class ColorMap { private float[] remap(float[] x, float[] out, float[] y, float range) { out = Spectrum.realloc(out, x.length); float z, dz; for (int i = 0; i < x.length; i++) { z = x[i] / range * ((float)(y.length - 1)); int b = (int)(z); if (b > y.length - 2) b = y.length - 2; if (b < 0) b = 0; dz = Math.max(Math.min(z - (float)b, 1), 0); out[i] = range * ((1 - dz) * y[b] + dz * y[b + 1]); } return out; } public float[][] remap(float[] x, float max) { if (null == out) out = new float[3][x.length]; out[0] = remap(x, out[0], _rmap, max); out[1] = remap(x, out[1], _gmap, max); out[2] = remap(x, out[2], _bmap, max); return out; } float[] _rmap; float[] _gmap; float[] _bmap; float[][] out; } public static class RainbowMap extends ColorMap { public RainbowMap() { _rmap = new float[] {1, 1, 0, 0, 0, 1, 1}; //red _gmap = new float[] {0, 1, 1, 1, 0, 0, 1}; //green _bmap = new float[] {0, 0, 0, 1, 1, 1, 1}; //blue } } public static class FireMap extends ColorMap { public FireMap() { _rmap = new float[] {1, 1, 1, 1}; //red _gmap = new float[] {0, 0.5f, 1, 1}; //green _bmap = new float[] {0, 0, 0, 1}; //blue } } public static class FancyMap extends ColorMap { public FancyMap() { _rmap = new float[] {.5f, .2f, .625f, .93f}; //red _gmap = new float[] {.1f, 0.2f, .8f, .95f}; //green _bmap = new float[] {0.2f, .65f, .7f, 1}; //blue } } public static class InvFireMap extends ColorMap { public InvFireMap() { _rmap = new float[] {1, 1, 1, 1, 1, 1}; //red _gmap = new float[] {1, 1, 0.75f, 0.5f, 0.25f, 0}; //green _bmap = new float[] {1, 0, 0, 0, 0, 0}; //blue } } public static class GrayMap extends ColorMap { public float[][] remap(float[] x, float max) { return new float[][] {x, x, x}; } } public static class CyanMap extends ColorMap { public float[][] remap(float[] x, float max) { return new float[][] {x}; } } public static ColorMap mapForScheme(String name) { if (name.equals("Rainbow")) return new RainbowMap(); else if (name.equals("Fire")) return new FireMap(); else if (name.equals("Cyan")) return new CyanMap(); else if (name.equals("Heat")) return new InvFireMap(); else if (name.equals("Fancy")) return new FancyMap(); else return new GrayMap(); } }