//===================================================================== // File: Lane.java // Class: Lane // Package: AFLPcore // // Author: James J. Benham // Date: January 4, 1999 // 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; /** * This class represents a lane. A lane contains a trace, as well as * a sample name, a gel name (for the original gel), a lane number, a * lane index (which is unique to a lane), cutoffs, and peaks. * *
The trace consists of a bunch of points indicating the height at.
* The first point will contain the height at time=0, the next point
* will be at time=timeInc, and so on. These different time increments
* are called scan numbers throughout the program. (The ABI 377 scans
* a point in the gel every 1.6 seconds.) The lane must also have
* a SizeFunction
, which is used to convert between scan
* numbers and sizes in bp. The lane can actually contain two traces.
* One is the raw data trace, which should not be modified, and the other
* is the normalized trace. Normalization is not provided in the lane file,
* but can be added as either a GelOperation
or a
* LaneOperation
. This allows easy implementation of different
* normilization algorithms.
*
*
The lane also contains a list of Cutoff
s. These are used
* to help locate peaks and to score bins. Each cutoff is defined by a
* starting position. A cutoff will apply to the region between its start
* position and either the start of the next cutoff in the lane or until
* the end of the lane. The list of cutoffs is sorted by start location.
*
*
The lane also contains a list of defined peaks. This list can be
* obtained and then manipulated by calling getPeaks()
. Again
* the lane itself does not find the peaks, but relies on other parts of
* the program to find them. This allows the peak locating algorithm to
* be changed easily.
*
* @see SizeFunction
* @see Cutoff
* @see Operation
*
* @author James J. Benham
* @version 1.1.0
* @date January 4, 1999
*/
public class Lane extends Data
{
/** Constant for lane color */
public final static int YELLOW = 2;
/** Constant for lane color */
public final static int RED = 3;
/** Constant for lane color */
public final static int BLUE = 0;
/** Constant for lane color */
public final static int GREEN = 1;
/** Constant to tell which type of trace the lane is using.*/
public final static int ORIGINAL = 0;
/** Constant to tell which type of trace the lane is using.*/
public final static int NORMALIZED = 1;
private static int laneCount = 0;
private String name;
private String gelName;
private int laneNum;
private int laneIndex;
private double totalSignal;
private double rawTrace[];
private double normTrace[];
private double trace[];
private int color;
private SizeFunction sizeFn;
private DataList peaks;
private DataList cutoffs;
/**
* Create a new lane with the specified trace. The default name will be
* "unknown", the gelName will be "__", and the laneNum will be 0. These
* can and should be changed with the setParamName
* methods.
*
* @param trace each value represent the intensity of the light read at
* a given point, either scan or size. This should be a raw trace,
* not a normalized one.
*/
public Lane(double trace[])
{
// set the trace
rawTrace = trace;
laneIndex = laneCount;
laneCount++;
// give the other stuff default values
color = YELLOW;
name = "unknown";
gelName = "__";
laneNum = 0;
totalSignal = -1;
this.trace = rawTrace;
normTrace = null;
sizeFn = new NoSize();
peaks = new DataList();
cutoffs = new DataList();
}
/**
* Gives the intensity of the light for the color at the specified
* size. This also corresponds to the height of a trace. The trace can
* either be the normalized trace or the original trace, depending on
* what the current trace is set to.
*
* @param size the location of interest, specified in bp
*
* @return the value of the trace at the given point. -1 will be returned
* if the specified point is not in the Lane.
*
* @see Lane#useTrace
*/
public double getHeightAt(double size)
{
int index = indexOf(size);
return( (index != -1) ? trace[index] : -1);
}
/**
* Uses the size method to find the index for the trace array that
* corresponds to that size.
*
* @param size the size in bp
*
* @return the index to the trace array, -1 if the index is outside of
* the array
*/
private int indexOf(double size)
{
int scan = sizeFn.getScan(size);
// Do some sizing and checking
if( (scan >= trace.length) || (scan < 0))
{
//DEBUG: System.out.println("Size param out of trace.");
return -1;
}
return scan;
}
/**
* Gives the name of the sample run on this lane. This can be set using
* setName
.
*
* @return the name, "unknown" by default
*/
public final String getName()
{
return name;
}
/**
* Set the name of the sample run on this lane to the specified string.
*
* @param newName the value to set the name to.
*/
public final void setName(String newName)
{
name = newName;
}
/**
* Gives the name of the Gel that this lane was extracted from. This can be
* set using setGelName
.
*
* @return the gel name, "__" by default
*/
public final String getGelName()
{
return gelName;
}
/**
* Set the name of the gel that the lane was on to the specified value.
*
* @param newName the value to set the name to.
*/
public final void setGelName(String newName)
{
gelName = newName;
}
/**
* Gives the number of the lane on the gel that contianed this information.
*
* return the lane number
*/
public final int getLaneNumber()
{
return laneNum;
}
/**
* Set the lane number of this lane to the specified value.
*
* @param newName the value to set the lane number to to.
*/
public final void setLaneNumber(int newLaneNum)
{
laneNum = newLaneNum;
}
/**
* Gives the color of the data in this lane. Possible values are
* RED
, YELLOW
, GREEN
, and
* BLUE
, which are defined in this class.
*
* @return the color of this lane.
*/
public final int getColor()
{
return color;
}
/**
* Set the color channel used with this lane to the specified value.
*
* @param the color, possible values are given in this class.
*/
public final void setColor(int color)
{
this.color = color;
}
/**
* Gives the object that represents the conversion between size in bp
* and scan number (trace index).
*
* @return the size object
*/
public SizeFunction getSizeFunction()
{
return sizeFn;
}
/**
* Set the size curve/function to be used by this lane.
*
* @param sizeFunction the new way to convert between scan number and size
*/
public void setSizeFunction(SizeFunction sizeFunction)
{
sizeFn = sizeFunction;
}
/**
* Produces a list of peaks that are in the given range. That is, for the
* peak location, x, min <= x <= max must be true. For every
* such peak, the peak is added to the list, which is sorted.
*
* @param min the minimum size in bp
* @param max the maximum size in bp
*
* @return a list of peaks in the range. If there are no peaks, the list
* will be empty.
*/
public DataList getPeaksInRange(double min, double max)
{
// This method works by finding the first peak that is less than the
// max, then finding the peak that is less than the first peak, and so
// on until either no more peaks are found or the location of the peak
// found dips below the minimum.
// place to store the peaks
DataList inRangeList = new DataList();
Peak tempPeak;
double currentSize = max;
// find the value closest to the max
tempPeak = (Peak) peaks.findNearestUnder(currentSize);
while( (tempPeak != null ) &&
(tempPeak.getLocation() >= min))
{
// add the peak
inRangeList.include(tempPeak);
// move the size to the peak we just found, and then look below that.
// so that we don't find the same one again.
currentSize = tempPeak.getLocation() - 1E-12;
// get the next peak
tempPeak = (Peak) peaks.findNearestUnder(currentSize);
}
return inRangeList;
}
/**
* Gives the list of peaks in the lane.
*
* @return the peak list.
*/
public DataList getPeaks()
{
return peaks;
}
/**
* Gives the maximum height in the specified range.
*
* @param minSize the size in bp of the lower bound
* @param maxSize the size in bp of the upper bound
*
* @return the maximum intensity, -1 if maxSize < minSize or if the
* sizes are out of bounds.
*/
public double getMaxHeight(double minSize, double maxSize)
{
return getMaxPoint(minSize, maxSize).height;
}
/**
* Gives the maximum height in the lane
*
* @return the maximum height
*/
public double getMaxGlobalHeight()
{
return findMaxPoint(0, trace.length - 1).height;
}
/**
* Gives the maximum point, (size, height) ,in the specified range.
*
* @param minSize the size in bp of the lower bound
* @param maxSize the size in bp of the upper bound
*
* @return the maximum intensity point, -1 for the height if
* maxSize < minSize or if the sizes are out of bounds.
*/
public TracePoint getMaxPoint(double minSize, double maxSize)
{
// Go from size to index into the array
int minIndex = indexOf(minSize);
int maxIndex = indexOf(maxSize);
// Check to see that minIndex is valid, max will automatically return
// -1 since the loop won't execute.
if(minIndex == -1)
return new TracePoint(-1, -1);
return findMaxPoint(minIndex, maxIndex);
}
/**
* Gives the maximum point, (scan, height), in the trace, based
* on the range given as scan numbers.
*
* @param minScan the lowest scan to include in the search
* @param maxScan the upper bound for the search (inclusive)
*
* @return the maximum intensity point, -1 for the height if
* maxSize < minSize or if the sizes are out of bounds.
*/
private TracePoint findMaxPoint(int minScan, int maxScan)
{
double max = -1; // initialize the maximum
int maxLocation = minScan; // initialze the index
// Find the maximum.
for(int i=minScan; i <= maxScan; i++)
{
if(trace[i] > max)
{
max = trace[i];
maxLocation = i;
}
}
return new TracePoint(maxLocation, max);
}
/**
* This returns the number of points in the trace. Therefore, anything
* that attempts to display more information than this will simply be
* scaling the points, instead of accessing intermedite points. However,
* since the trace can be 4000+ points long, that's a little hard to
* display in one image.
*
* @return the number of data points in the trace.
*/
public int getNumPoints()
{
return rawTrace.length;
}
/**
* Sets the trace to be used when retrieving height, finding peaks, etc.
* The trace can either be the original trace that came from some
* input file, or the normalized trace.
*
* @param trace_num indicates which trace to use, possiblities are
* ORIGINAL
or NORMALIZED
*
* @exception IllegalArgumentException occurs when trace_num
* is not one of the excepted values.
* @exception NoDataError occurs if this lane has not yet been
* normalized. If this is the case, then the normalized trace does not
* exist and therefore cannot be used.
*/
public void useTrace(int trace_num)
{
// check the argument
if( (trace_num != NORMALIZED) && (trace_num != ORIGINAL) )
{
throw new IllegalArgumentException("No such trace.");
}
// if it's normalized, make sure we have the data
if( (trace_num == NORMALIZED) && (normTrace == null))
{
throw new NoDataError("The normalized trace cannot be used" +
"because it does not exist.");
}
// no more errors, so just set it
if(trace_num == ORIGINAL)
trace = rawTrace;
else
trace = normTrace;
}
/**
* Set the normalized trace to the specified trace.
*
* @param trace the normalized version of the raw trace.
*/
public void setNormTrace(double trace[])
{
normTrace = trace;
}
/**
* Gives the current trace for this lane, which is either the normalized
* trace or the raw trace. The raw trace should NOT modified.
* Access is granted to it only to make creating a normalized trace
* easier.
*
* @return the lane trace
*
* @see Lane#useTrace
*/
public double[] getTrace()
{
return trace;
}
/**
* Gives an integer that is unique to this lane.
*
* @return an integer which identifies this lane.
*/
public int getLaneIndex()
{
return laneIndex;
}
/**
* Gives an integer that is equivalent to the lane index, which is a
* unique number for this lane.
*
* @return a code suitable for a hashtable.
*/
public int hashCode()
{
return laneIndex;
}
/**
* Gives the largest size, in bp, represented by this lane. Any size
* greater than this will not be in the lane data. Warning: If
* there is a round off error in the sizing function, converting the
* maximum size back into a value may result in exceeding the data size.
* This is of course depended on the size function.
*
* @return the maximum size
*/
public double getMaxSize()
{
return (sizeFn.getSize(trace.length - 1));
}
/**
* Gives the smallest size, in bp, represented by this lane. Any size
* less than this will not be in the lane data. Warning: If
* there is a round off error in the sizing function, converting the
* minimum size back into a value may result in an out of bounds problem.
* This is of course depended on the size function.
*
* @return the minimum size
*/
public double getMinSize()
{
return (sizeFn.getSize(0));
}
/**
* Clones this lane Not implemented
*/
public Object clone()
{
System.err.println("Lane clone not implemented.");
return this;
}
/**
* Gives the strength of the signal for this lane. The signal stength
* is found by summing every point in the trace. It is in effect, the
* area of the trace curve.
*
* @return a value equal to the strength of the signal in the lane.
*/
public double getTotalSignal()
{
// see if the value has been calculted yet.
if(totalSignal == -1)
{
totalSignal = 0;
for(int i=0; i < rawTrace.length; i++)
totalSignal += rawTrace[i];
}
return totalSignal;
}
/**
* Returns the list of cutoffs associated with this lane.
*
* @return the list, empty if there are no cutoffs set
*/
public DataList getCutoffs()
{
return cutoffs;
}
/**
* Adds the specified cutoff to the list of known cutoffs for this lane.
* Any cutoff can contian multiple levels.
*
* @param ct the cutoff to be added.
*/
public void addCutoff(Cutoff ct)
{
cutoffs.include(ct);
}
/**
* Gives the first cutoff in the lane that is less than or equal to the
* specified size.
*
* @param size the size which the cutoff must be lower than
*
* @return the first such cutoff found, null
if none exists.
*/
public Cutoff cutoffUnder(double size)
{
return (Cutoff) cutoffs.findNearestUnder(size);
}
/**
* 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.writeUTF(name);
out.writeInt(laneNum);
out.writeUTF(gelName);
out.writeInt(laneIndex);
out.writeInt(color);
out.writeBoolean( (trace == rawTrace) );
out.writeInt(rawTrace.length);
for(int i=0; i < rawTrace.length; i++)
out.writeShort( (short) rawTrace[i]);
if(normTrace == null)
out.writeInt(0);
else
{
out.writeInt(normTrace.length);
for(int i=0; i < normTrace.length; i++)
out.writeFloat( (float) normTrace[i]);
}
out.writeInt(peaks.size());
for(int i=0; i < peaks.size(); i++)
((Peak) peaks.dataAt(i)).write(out);
out.writeInt(cutoffs.size());
for(int i=0; i < cutoffs.size(); i++)
((Cutoff) cutoffs.dataAt(i)).write(out);
out.writeUTF(sizeFn.getName());
sizeFn.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
{
name = in.readUTF();
laneNum = in.readInt();
gelName = in.readUTF();
laneIndex = in.readInt();
// set to -1 so it forces a recomputation. Do this so we don't
// change the file format.
totalSignal = -1;
color = in.readInt();
boolean usingRaw = in.readBoolean();
int length = in.readInt();
rawTrace = new double[length];
for(int i=0; i < length; i++)
rawTrace[i] = in.readShort();
length = in.readInt();
if(length == 0)
normTrace = null;
else
{
normTrace = new double[length];
for(int i=0; i < length; i++)
normTrace[i] = in.readFloat();
}
// set the trace to the correct value
if(usingRaw)
trace = rawTrace;
else
trace = normTrace;
Peak pk;
length = in.readInt();
peaks = new DataList(length);
for(int i=0; i < length; i++)
{
pk = new Peak(0, 0, 0);
pk.read(in);
peaks.addData(pk);
}
Cutoff ct;
length = in.readInt();
cutoffs = new DataList(length);
for(int i=0; i < length; i++)
{
ct = new Cutoff(1, 1);
ct.read(in);
cutoffs.addData(ct);
}
String sizeName = in.readUTF();
try {
sizeFn = (SizeFunction) FeatureList.getSizeMgr().get(sizeName);
sizeFn = (SizeFunction) sizeFn.clone();
sizeFn.read(in);
} catch(java.util.NoSuchElementException e) {
throw new IOException("The sizing function " + sizeName + " could " +
"not be found.");
}
}
}