//===================================================================== // 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 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. 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 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.
*
*
* http://www.visualtek.com/PNG/
*
* 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;
}
}
}
}
}
}
}