//===================================================================== // File: Gel.java // Class: Gel // Package: AFLPcore // // Author: James J. Benham // Date: August 10, 1998 // Contact: james_benham@hmc.edu // // Genographer v1.0 - Computer assisted scoring of gels. // Copyright (C) 1998 Montana State University // // 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; version 2 // of the License. // // 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, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // The GNU General Public License is distributed in the file GPL //===================================================================== package AFLPcore; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import com.visualtek.png.*; /** * A gel contains most of the information needed by the program. It contains * a set of lanes and a set of bins. Additionally, the gel contains * methods to convert the lanes into an array which can be converted into * an image. The image can be controlled by setting the size range, borders, * and intensity. Note: the gel will NOT check to see if the size * ranges specified are valid. The image can also be written out in a PPM * image format. This format is not widely used, but it is easy to code. * More formats may be added in the future. Finally, the gel can be written * to an output stream as well as read in from an input stream. As mentioned * before, this effectively writes all of the critical data for the program. * * @see Bin * @see Lane * * @author James J. Benham * @version 1.0.0 * @date January 8, 1999 */ public class Gel extends Data { private DataList lanes; private DataList selectedLanes; private DataList bins; private int numLanes; private double maxGlobalIntensity; private int laneWidth; private int laneBorder; private int gelTopBorder; private int bgColor; private double minSize; private double maxSize; private double intensity; private double sizeInc; //scale of bp per pixel for pixel array private int pix[]; // the array representing the image /** * Create a new Gel. */ public Gel() { laneWidth = 10; laneBorder = 3; gelTopBorder = 5; numLanes = 0; minSize = 0; maxSize = 10; intensity = 1; bgColor = 0xff000000; //black // stores the maximum value in the gel maxGlobalIntensity = 0; lanes = new DataList(); selectedLanes = new DataList(); bins = new DataList(); } /** * Adds a lane to this gel. * * @param ln the lane to add */ public void addLane(Lane ln) { // keep track of how many lanes we have, even though lanes.length() // should get us the same value. numLanes++; lanes.addData(ln); double maxValue = ln.getMaxGlobalHeight(); if (maxValue > maxGlobalIntensity) maxGlobalIntensity = maxValue; // This invalidates all of the scoring for the bins for(int i=0; i < bins.size(); i++) ((Bin) bins.dataAt(i)).setScore(false); } public void removeLane(Lane ln) { boolean found; // remove from both lanes and selected lanes, if it isn't there, // we won't decrease the number of lanes. found = lanes.removeElement(ln); selectedLanes.removeElement(ln); if(found) numLanes--; } /** * Gives the lane at the specified index. */ public Lane laneAt(int index) { return (Lane) lanes.dataAt(index); } /** * Gives a list of all of the lanes in the gel. The list is not sorted * and is in the same order as the lanes appear in the gel, left from right. * * @return the lanes in the gel. Empty if there are none. */ public DataList getLanes() { return lanes; } /** * Gives a list of all of the selected lanes in the gel. The list is not * sorted and is in the same order as the lanes appear in the gel, left * from right, except not every lane is included in this list. * * @return the lanes in that are selected. Empty if there are none. */ public DataList getSelectedLanes() { return selectedLanes; } /** * Changes the selection state of the specified lane. If the lane is * already selected, it is removed from the list of selected lanes. * If it is not selected, it is added to the list. The order of the * selected lanes is identical to the the ordering of the lanes in the * gel. *

The list is updated when a lane is added by moving through * every lane in the gel, seeing if it should be selected, and then * adding it to a new list if it should. Finally, the new list becomes * the list of selected lanes. * * @param ln the lane to toggle */ public void toggleSelectedLane(Lane ln) { int index = selectedLanes.indexOf(ln); if(index == -1) { DataList newSelection = new DataList(); int selectIndex; // move through the lanes and look for lanes in the selected // list for(index = 0; index < lanes.size(); index++) { // See if it is in the the selected list Lane currentLane = (Lane) lanes.elementAt(index); selectIndex = selectedLanes.indexOf(currentLane); if(selectIndex != -1) newSelection.addElement(selectedLanes.elementAt(selectIndex)); else if(currentLane == ln) { // see if it is the lane to add. newSelection.addElement(ln); } } selectedLanes = newSelection; } else { // the lane is in the list, so remove it selectedLanes.removeElementAt(index); } } /** * Adds the specified bin to the gel. * * @param bin the new bin, complete with size and width set. */ public void addBin(Bin bin) { bins.include(bin); } /** * Removes the specified bin from the list of bins for this gel. * * @param bin the bin to remove */ public void removeBin(Bin bin) { bins.removeElement(bin); } /** * Gives a list of all the bins in the gel. * * @return the bins in the gel. If there are none, the list will be * empty. */ public DataList getBins() { return bins; } /** * Gives the bin at the specified location. It works be searching the * bin list for the specified size. If the size is found, that bin is * returned. Otherwise, it retrieves the bin smaller than size * and checks to see if size is within that range. If it is not, the * first bin larger than size is checked in s similar manner. * * @param size the location to look for bins at * * @return the bin that contains the given size. null if no * such bin exists. */ public Bin binAtSize(double size) { // Make sure it isn't an empty list if(bins.isEmpty()) return null; Bin tempBin; SearchReturn searchResult = bins.find(size); // If we found it, we are certainly within the range, if(searchResult.location != -1) return (Bin) bins.elementAt(searchResult.location); // Check the one below (if |x-a| <= epsilon) tempBin = (Bin) bins.elementAt(searchResult.insertIndex); if( Math.abs(size - tempBin.getSearchKey()) <= tempBin.getRange()) return (Bin) bins.elementAt(searchResult.insertIndex); // Now check the one above // First see that we don't go over the top if( (searchResult.insertIndex + 1) >= bins.size()) return null; tempBin = (Bin) bins.elementAt(searchResult.insertIndex +1); if( Math.abs(size - tempBin.getSearchKey()) <= tempBin.getRange()) return (Bin) bins.elementAt(searchResult.insertIndex + 1); // We didn't find it return null; } /** * Gives an array of pixels that represents the gel as an image. This * can easily be converted into an Image using the * MemoryImageSource. The array is one dimensional, with * the form value(x,y) = pix[y*width + x] The data in the lanes * of the gel are converted from heights to color values. The image * will represent the data from minSize to maxSize which where set * for this gel with setMinSize and setMaxSize. * The color scaling is contolled by the intensity with * setIntensity. * * @param length the length in pixels of the image in the array * */ public int[] getPixels(int length) { return getPixels(minSize, maxSize, length, intensity); } /** * Gives an array of pixels that represents the gel as an image. This * can easily be converted into an Image using the * MemoryImageSource. The array is one dimensional, with * the form value(x,y) = pix[y*width + x] The data in the lanes * of the gel are converted from heights to color values. The image * will represent the data from minSize to maxSize. * * @param minSize the size of the smallest fragement to display * @param maxSize the size of the largest tragement to display * @param length the length, in pixels, of the gel image. * @param maxHeight the value which will give the brightest color * Used to determine the color for the lane. * * @see Gel#writeLane * @see MemoryImageSource */ public int[] getPixels(double minSize, double maxSize, int length, double maxHeight) { double scaleFactor; // convert from height to color int traceLength; // the length, in pixels, of the lane //===========DEBUG================= //System.out.println("maxHeight: " + maxHeight); //System.out.println("minSize: " + minSize); //System.out.println("maxSize: " + maxSize); //System.out.println("imgLength: " + length); //===========DEBUG================= // Used to convert trace heights to colors scaleFactor = 255/maxHeight; // ===============Create the array================ // Get the sizes, border on both sides, so add extra laneBorder int width = numLanes*(laneWidth + laneBorder) + laneBorder; int height = length; pix = new int[width*height]; //===========DEBUG================= //System.out.println("scaleFactor: " + scaleFactor); //System.out.println("width: " + width); //System.out.println("height: " + height); //===========DEBUG================= // ===============Get size scale================== // This line causes the JIT from Symantec NOT to screw up. It can // be added in so that the JIT can be used. However, this // error seems to have gone away now. Still, look here for // mysterious problems. Use caution // //System.out.println("Equals: " + ((400 == length) ? "Yes" : "No")); sizeInc = (maxSize - minSize)/(length - 1); // Find the length of the lane in pixels traceLength = length - gelTopBorder; //===========DEBUG================= //System.out.println("sizeInc: " + sizeInc); //===========DEBUG================= //===============Write in the top border=========== int stopPos = width*gelTopBorder; // move out of loop. I don't trust the for(int i=0; i < stopPos; i++) // compiler to optimize. pix[i] = bgColor; //===============Write in the bottom border======== for(int i = (traceLength*width); i < (width*height); i++) pix[i] = (0xff << 24); //===============Write out lanes and vertical borders=========== // Note that this will not happen if there are no lanes, but the // final border outside of this loop will. for(int i=0; i < numLanes; i++) { writeBorder(pix, traceLength, i, width); writeLane(pix, traceLength, (Lane) lanes.dataAt(i), i, width, scaleFactor, maxSize); } //===============Write the final vertical border=========== writeBorder(pix, traceLength, numLanes, width); //===========DEBUG================= //System.out.println("Pixels created."); //===========DEBUG================= return pix; } /** * Writes the specified lane data into the specified pixel array at the * given location. Since the destination is not the same size as the * source, some points from the lane will be skipped or used multiple * times. The former is more likely since lanes usually consist of several * thousand points. The height at the point in a lane is converted to * a color, where the height of the point determines the brightness of * the color. For color values that exceed 255, they will be clipped to * 255. * * @param pix the array representing the image * @param length the length in pixels of the lane on the image * @param lane the lane with the data of interest * @param horzIndex determines the horizontal position in the image * to write the lane to. It uses the laneWidth and * laneBorder as well. * @param width the width of the image in the pixel array * @param scaleFactor the value to multiply the height by to get the * color value, which maxes out at 255. * @param maxSize the maximum size, in bp, for the lane. This size * will be the first value shown in the lane. */ protected void writeLane(int pix[], int length, Lane lane, int horzIndex, int width, double scaleFactor, double maxSize) { int colorValue; double size = maxSize; for(int i=0; i < length; i++) { // set the correct color value, between 0 and 255. colorValue = (int) (lane.getHeightAt(size) * scaleFactor); // check to see if the colorValue is still less than 255, if not, // clip it. if(colorValue > 255) colorValue = 255; // now make it the right color switch (lane.getColor()) { case Lane.RED: colorValue = colorValue << 16; break; case Lane.GREEN: colorValue = colorValue << 8; break; case Lane.BLUE: // Value is already in the correct position break; case Lane.YELLOW: colorValue = (colorValue << 16) | (colorValue << 8); break; } // set the transparency value to 255=0xff, not transparent. colorValue = (0xff << 24) | colorValue; // Write the value to the correct position in the pix array. // Since the pix array represents a 2D image, the position is // pix[y*width + x], with the standard computer graphic coordinate // system. // y = i, the row in the image // x = the start of the lane + j int horzOffset = horzIndex*(laneBorder + laneWidth) + laneBorder; for(int j=0; j < laneWidth; j++) { pix[i*width + horzOffset + j] = colorValue; } // Move to the next size size -= sizeInc; } } /** * Writes the border to the specified pixel array. The other parameters * help determine where to write in the pixel array. * * @param pix the pixel array, represents the gel image. * @param length the length in pixels of the lane graphic and the * vertical border. * @param horzIndex a number indicating which border to write, the 0th, * 1st, 2nd, etc. This allows the method to determine * the x-coordinate of the border. * @param width this is the width of the image in pix. */ private void writeBorder(int pix[], int length, int horzIndex, int width) { int strip[] = new int[laneBorder]; // make a little strip for(int i=0; i < laneBorder; i++) strip[i] = bgColor; // find the horizontal offset int offset = horzIndex * (laneWidth + laneBorder); // copy the strip all the way down. for(int i=0; i < length; i++) { System.arraycopy(strip, 0, pix, i*width + offset, laneBorder); } } /** * Gives the width of a lane, in pixels. This value is used to determine * how wide to make the lanes in the gel image. * * @return the width * */ public int getLaneWidth() { return laneWidth; } /** * Sets the width of a lane in the gel image. * * @param value the width in pixels * * @see Gel#getPixels */ public void setLaneWidth(int value) { laneWidth = value; } /** * Gives the spacing between two lanes in the gel image. * * @return the width in pixels between two lanes in the gel image, also * the left and right borders. * * @see Gel#getPixels */ public int getLaneBorder() { return laneBorder; } /** * Sets the spacing between lanes as well as the left and right border * on the gel image. * * @param value the width of the spacing, in pixels * * @see Gel#getPixels */ public void setLaneBorder(int value) { laneBorder = value; } /** * Gives the height of the border on the top and bottom of the gel * image. * * @return the height in pixels. * * @see Gel#getPixels */ public int getGelTopBorder() { return gelTopBorder; } /** * Sets the border on the top and bottom of the gel image to the * specified value. * * @param value the height of the border in pixels * * @see Gel#getPixels */ public void setGelTopBorder(int value) { gelTopBorder = value; } /** * Gives the maximum intensity in the gel between the two * specified sizes. * * @param minSize the lower bound for searching, in bp * @param maxSize the upper bound for searching, in bp * * @return the maximum intensity in the gel, -1 if the sizes are invalid * or if the max < min. */ public double getMaxIntensity(double minSize, double maxSize) { // initalize the max double gelMax = -1; double laneMax; // go through all of the lanes, and compare the lane max to the gel // max. for(int i=0; i < numLanes; i++) { laneMax = ((Lane) lanes.dataAt(i)).getMaxHeight(minSize, maxSize); if(laneMax > gelMax) gelMax = laneMax; } return gelMax; } /** * Gives the maximum intensity of the gel. This method will execute * considerable faster than getMaxIntensity(double, double) * because this value is stored in the gel and updated only when * lanes are added. However, the slower method will scan for the * maximum in the current lanes and in a limited range. * * @return the maximum intensity */ public double getGlobalMaxIntensity() { return maxGlobalIntensity; } /** * Gives the number of lanes stored in this gel. * * @return the number of lanes */ public int getNumLanes() { return numLanes; } /** * Gives the size in bp represented by each pixel in the pixel * array. * * @return the scale of the image, bps per pixel. */ public double getSizeInc() { return sizeInc; } /** * Gives the smallest size that will be included in the gel image when * it is produced. * * @return the size in bp * * @see Gel#getPixels */ public double getMinSize() { return minSize; } /** * Sets the smallest size that will be included in the gel image when * it is produced. This method will not check to see if the * specified value is valid for this gel. * * @param minSize the size in bp * * @see Gel#getPixels */ public void setMinSize(double minSize) { this.minSize = minSize; } /** * Gives the largest size that will be included in the gel image when * it is produced. * * @return the size in bp * * @see Gel#getPixels */ public double getMaxSize() { return maxSize; } /** * Sets the largest size that will be included in the gel image when * it is produced. This method will not check to see if the * specified value is valid for this gel. * * @param maxSize the size in bp * * @see Gel#getPixels */ public void setMaxSize(double maxSize) { this.maxSize = maxSize; } /** * Gives the height to which all values will be scaled to transform * them into colors. The smaller this number is, the brighter a gel * will appear. Remember that anything above this value will simply * be clipped to the maximum color value. * * @return the maximum expected height of a trace. * * @see Gel#getPixels */ public double getIntensity() { return intensity; } /** * Sets the intensity to the specified value. The intensity is used when * the pixel array is assebled. It is the value which corresponds to the * brightest color on the gel. Any value below it will be dimmer, and any * value above it will have the same intensity. * * @param intensity the new value * * @see Gel#getPixels */ public void setIntensity(double intensity) { this.intensity = intensity; } /** * Gives the color of the background for the gel. * * @return an integer representing the color, in the form * 0xAARRGGBB, in hex, where 'A' represents the alpha value, * 'R' the red, 'G', the green, and 'B' the blue. */ public int getBackGroundColor() { return bgColor; } /** * Set the background color to the specified value. * * @param color the color as a 32-bit value, in the form 0xAARRGGBB, for * alpha, red, green, and blue components. */ public void setBackGroundColor(int color) { bgColor = color; } /** * Clones the gel Not Implemented. */ public Object clone() { System.out.println("Gel clone not implemented."); return this; } /** * Writes the gel image in a PPM format to the specified output * stream. If the pixels have been created using getPixels * then that will be used to write the PPM. Otherwise, an image of * length 500 will be created. * * @param out the stream to write the PPM to * * @exception IOException occurs if problems writing to the stream are * incountered. */ public void writePPM(DataOutputStream out) throws IOException { // check and see if the pixels have been created if(pix == null) pix = getPixels(500); // Write out some of the info // the magic number out.write("P6\n".getBytes()); // Now a couple of comments. // Remove the comments, since LView can't handle them. //out.write("# Created by Gene Score.\n".getBytes()); //out.write(("# " + numLanes + " lanes with sizes from " + minSize // + " to " + maxSize + ".\n").getBytes()); //out.write(("# Max Height of " + intensity + "\n").getBytes()); int width = numLanes*(laneWidth + laneBorder) + laneBorder; int height = pix.length/width; // the width and the height out.write(("" + width + " " + height + "\n").getBytes()); // the maximum color value out.write("255\n".getBytes()); // now for the pixel info byte color[] = new byte[3]; // each triple. for(int i=0; i < pix.length; i++) { color[0] = (byte) (pix[i] >> 16); //red color[1] = (byte) (pix[i] >> 8); //blue color[2] = (byte) (pix[i]); //green out.write(color); } } /** * Writes the gel image in a PNG format to the specified output * stream. If the pixels have been created using getPixels * then that will be used to write the PNG. Otherwise, an image of * length 500 will be created. * *

To do the actual writing, the method uses a library produced * by VisualTek. The library is specified in full here, as well * as the java.awt.Image class. My thanks to them. * *

Java PNG by VisualTek Solutions Inc.
* http://www.visualtek.com/PNG/ * *

An image is created from the pixels and it is then passed to * the libaray. * * @param out the stream to write the PPM to * * @exception IOException occurs if problems writing to the stream are * incountered. */ public void writePNG(DataOutputStream out) throws IOException { // java.awt.Image gelImage; java.awt.image.MemoryImageSource imgSource; // java.awt.Tookit tk; com.visualtek.png.PNGEncoder pngWriter; // check and see if the pixels have been created if(pix == null) pix = getPixels(500); // get the width int width = numLanes*(laneWidth + laneBorder) + laneBorder; // get the height int length = pix.length/width; // Make the new image and store it. imgSource = new java.awt.image.MemoryImageSource(width, length, pix, 0, width); // tk = java.awt.Toolkit.getDefaultToolkit(); // gelImage = tk.createImage(imgSource); pngWriter = new com.visualtek.png.PNGEncoder(imgSource, out); pngWriter.encode(); } /** * Writes all of the information this class needs to store in order * to be recreated. This will be used for things like storing the * class in a file. Therefore, the write should output enough information * so that read can recreate the essential properties of this * class. * * @param out the destination to write the data to. * * @exception IOException occurs when a problem is encountered when * writing to the stream. */ public void write(DataOutputStream out) throws IOException { out.writeInt(numLanes); out.writeDouble(maxGlobalIntensity); out.writeInt(laneWidth); out.writeInt(laneBorder); out.writeInt(gelTopBorder); out.writeInt(bgColor); out.writeDouble(minSize); out.writeDouble(maxSize); out.writeDouble(intensity); out.writeDouble(sizeInc); // Lanes for(int i=0; i < numLanes; i++) ((Lane) lanes.dataAt(i)).write(out); // Selected Lanes out.writeInt(selectedLanes.size()); for(int i=0; i < selectedLanes.size(); i++) out.writeShort( (short) ((Lane) selectedLanes.dataAt(i)).getLaneIndex()); // Bins out.writeInt(bins.size()); for(int i=0; i < bins.size(); i++) ((Bin) bins.dataAt(i)).write(out); } /** * Reads in the properties of this class from the specified input stream. * The stream data should have been created by write. This * will retrieve this classes state from the input stream. All of the * current data in this class will be replaced by the data from the * stream. * * @param in the input stream with the data for the class. * * @exception IOException occurs when a problem is encountered when * writing to the stream. */ public void read(DataInputStream in) throws IOException { numLanes = in.readInt(); maxGlobalIntensity = in.readDouble(); laneWidth = in.readInt(); laneBorder = in.readInt(); gelTopBorder = in.readInt(); bgColor = in.readInt(); minSize = in.readDouble(); maxSize = in.readDouble(); intensity = in.readDouble(); sizeInc = in.readDouble(); Lane ln; lanes = new DataList(numLanes); for(int i=0; i < numLanes; i++) { ln = new Lane(null); ln.read(in); lanes.addData(ln); } // selected lanes int length = in.readInt(); int laneID; selectedLanes = new DataList(8); for(int i=0; i < length; i++) { laneID = in.readShort(); // try to match it to one of the lanes for(int j=0; j < numLanes; j++) { ln = (Lane) lanes.dataAt(j); if(laneID == ln.getLaneIndex()) { selectedLanes.addData(ln); break; } } } // Bins Bin b; length = in.readInt(); int numLanes; bins = new DataList(length); for(int i=0; i < length; i++) { b = new Bin(1); b.read(in); bins.addData(b); if(b.isScored()) { // get the scoring info, which can't be done by the bin. // this is a horrible hack, but it keeps nicer ways would make // the class way more open than I want it to be numLanes = in.readInt(); int keyID; String value; for(int j=0; j < numLanes; j++) { keyID = in.readInt(); value = in.readUTF(); // try to match the number to one of the lanes for(int k=0; k < numLanes; k++) { ln = (Lane) lanes.dataAt(k); if(keyID == ln.getLaneIndex()) { b.setScore(ln, value); break; } } } } } } }