//===================================================================== // 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 Cutoffs. 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."); } } }