//=====================================================================
// File: Bin.java
// Class: Bin
// 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 java.util.Hashtable;
import java.util.Enumeration;
/**
* This class represents a bin, which is simply a region defined in a gel.
* It also contains a ScoreFunction
. This is responsible for
* interpreting the data inside of the bin. The bin also contains the
* label assigned to each lane by the score function.
*
* @see ScoreFunction
*
* @author James J. Benham
* @version 1.0.0
* @date August 10, 1998
*/
public class Bin extends SortableData
{
private static double DEFAULT_RANGE = 2;
private double location;
private double range;
private String name;
private String scoreInfo[];
private Hashtable scoreLookup;
private ScoreFunction scoreMethod;
private boolean scored;
/**
* Creates a new bin with the specified parameters.
*
* @param loc the location, in bp, of the bin on the gel.
*/
public Bin(double loc)
{
this(loc, DEFAULT_RANGE);
}
/**
* Creates a new bin with the specified parameters.
*
* @param loc the location, in bp, of the bin on the gel.
* @param range the range of the bin, agan in bp.
*/
public Bin(double loc, double range)
{
this(loc, range, new SegregatingScore() );
}
/**
* Creates a new bin with the specified parameters.
*
* @param loc the location, in bp, of the bin on the gel.
* @param range the range of the bin, agan in bp.
* @param score the method used to score the bin.
*/
public Bin(double loc, double range, ScoreFunction score)
{
location = loc;
this.range = range;
scoreMethod = score;
name = "";
scoreLookup = new Hashtable(30);
scored = false;
}
/**
* Gives the value used to search/sort bins.
*
* @return the bins location
*/
public final double getSearchKey()
{
return location;
}
/**
* Gives the method used to score a bin. That is to say, how the bin
* interprets the presense/absence/other of peaks in a bin. For example,
* one could call all those with a peak 'A' and those without 'B'. The
* score method could count and return the number of 'A's and the number
* of 'B's.
*
* @return The method used to score the bin.
*
* @see Bin#setScoreMethod
*/
public ScoreFunction getScoreMethod()
{
return scoreMethod;
}
/**
* Sets the scoring method to the one specified.
*
* @param scorer The method to use on this bin.
*
* @see Bin#getScoreMethod
*/
public void setScoreMethod(ScoreFunction scorer)
{
scoreMethod = scorer;
scored = false;
}
/**
* Scores this bin using the scoring method set and the lanes specified.
*
* @param lanes the lanes to include in the bin.
*/
public void score(DataList lanes)
{
if(scoreMethod == null)
throw new MissingParameterError("No score method set!");
double min = location - range;
double max = location + range;
// determine if we should call the peaks again.
// should also determine if the peaks should be cleared first, to
// remove old peaks. Removing can also be handled by the peak
// locating algorithm.
boolean reLabel = true;
if(reLabel)
{
// Retrieve the default method.
PeakLocate peakFind = ((PeakLocate)
FeatureList.getPeakMgr().getDefault());
peakFind.findPeak(lanes, min, max);
}
// ===============Now score each one=================
Lane ln;
String label;
for(int i=0; i < lanes.size(); i++)
{
ln = (Lane) lanes.dataAt(i);
label = scoreMethod.score(ln.cutoffUnder(max),
ln.getPeaksInRange(min, max));
scoreLookup.put(ln, label);
}
scoreOverall(lanes);
scored = true;
}
/**
* Sets the overall scoring information for the bin. The manner in
* which each lane was scored is given to the score method currently
* set. It also gives a list of all of the peaks of interest to
* the scoring method. "Of interest" is defined by the score function
* by marking certain peaks. For example, the score method could count
* the number of A's and B's as well as provide a mean for the peak
* location.
*
* @param lanes the lanes to include in the overall scoring of this bin
*
* @see Bin#getScoreInfo
* @see ScoreFunction
* @see Peak#isMarked
*/
public void scoreOverall(DataList lanes)
{
double min = location - range;
double max = location + range;
//===========Make a list of all the interesting peaks==========
DataList markedPeaks = new DataList();
DataList peaks;
Peak pk;
for(int i=0; i < lanes.size(); i++)
{
peaks = ((Lane) lanes.dataAt(i)).getPeaksInRange(min, max);
for(int j=0; j < peaks.size(); j++)
{
pk = (Peak) peaks.dataAt(j);
if(pk.isMarked())
markedPeaks.addData(pk);
}
}
//============= Change all of the scores into an array =========
String labels[]; // the names in an array
// Get the names from the Hashtable
Enumeration namesEnum = scoreLookup.elements();
// Create an array of the approriate size
labels = new String[scoreLookup.size()];
// Copy the names from the Enumeration into the array
for(int i=0; i < scoreLookup.size(); i++)
{
labels[i] = (String) namesEnum.nextElement();
}
scoreInfo = scoreMethod.getInfo(labels, markedPeaks);
}
/**
* Gives the string representing how the given lane was scored.
*
* @param ln the lane to retrieve the score for
*
* @return the score found by the scoring method
*
* @see Bin#getScoreMethod
* @see Bin#setScoreMethod
*/
public String getScore(Lane ln)
{
// do some error checking here?
return (String) scoreLookup.get(ln);
}
/**
* Changes the scoring for the specified lane. Only the value in the
* table is changed by this method, so in future, it could be
* replaced if the table is changed and it will not affect any of the
* peaks. Appropriate values can be retrieved from the
* ScoreFuction
set for this bin.
*
* @param ln the lane to change
* @param value the new score for the lane, which should be one of the
* choices offered by the current score method.
*
* @see Bin#getScoreMethod
* @see ScoreFunction#getChoices
*/
public void setScore(Lane ln, String value)
{
scoreLookup.put(ln, value);
}
/**
* Gives the overall score info for the bin. The information is
* determined from from the peaks used to score the bin and the label
* assigned to each lane in the bin. Each entry in the array should
* be a separate line. The informatin could be as simple as the
* number of lanes, or it could be something like the mean location
* of all the peaks in the bin.
*
* @return an array containing information on the bin scoring.
*
* @see ScoreFunction
* @see Bin#scoreOverall
*/
public String[] getScoreInfo()
{
return scoreInfo;
}
/**
* Used to tell whether or not the score
method has been
* called on this bins current configuration. For example, if the bin
* is moved, it needs to be rescored.
*
* @return true
if the bin still has a valid scoring.
*/
public boolean isScored()
{
return scored;
}
/**
* Sets the bin so that the current scoring is either accepted as valid
* or invalidated.
*
* @param scored true
if the current scoring is to be valid.
*/
public void setScore(boolean scored)
{
this.scored = scored;
}
/**
* Gives the position of this bin in the gel.
*
* @return the location specified in bp.
*/
public double getLocation()
{
return location;
}
/**
* Sets the position/location of this bin to the one specified.
*
* @param location the new position for the bin
*/
public void setLocation(double location)
{
this.location = location;
scored = false; // invalidate scoring
}
/**
* Moves the bin to a new location based on it's present location.
*
* @param adjustment the distance to move the bin, which may be either
* positive or negative.
*/
public void adjustLocation(double adjustment)
{
location = location + adjustment;
scored = false; // invalidate scoring
}
/**
* Gives the range of the bin. The range of the bin is the distance from
* the location to the edge of the bin. Therefore, the bin is 2*range
* bp wide.
*
* @return the range as described above
*/
public double getRange()
{
return range;
}
/**
* Sets the range of the bin to the specified value. The range of the bin
* is the distance from the location to the edge of the bin. Therefore, the
* bin is 2*range bp wide.
*
* @param range the new value for the range as described above
*/
public void setRange(double range)
{
this.range = range;
scored = false; // invalidate scoring
}
/**
* Changes the range relative to the current value of the range. The range
* of the bin is the distance from the location to the edge of the bin.
* Therefore, the bin is 2*range bp wide.
*
* @param adjustment the value to increment the range by. This value
* can be either positive or negative.
*/
public void adjustRange(double adjustment)
{
range = range + adjustment;
scored = false; // invalidate scoring
}
/**
* Sets the name of the bin to the specified value. By default, a bin
* has a name of "", the empty String.
*
* @param name the new name for the bin.
*/
public void setName(String name)
{
this.name = name;
}
/**
* Gives the name of this bin. By default, a bin has a name of "", the
* empty String. In this case, the location of the bin is a much better
* identifier.
*
* @return the name of the bin.
*/
public String getName()
{
return name;
}
/**
* Produces a new bin identical to this one, except it has not been
* scored.
*
* @return a new bin with the same properties as this one.
*/
public Object clone()
{
Bin temp = new Bin(location, range, scoreMethod);
temp.setName(name);
return temp;
}
/**
* 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.writeDouble(location);
out.writeDouble(range);
out.writeUTF(name);
out.writeBoolean(scored);
out.writeUTF(scoreMethod.getName());
scoreMethod.write(out);
// Only write the scoring info out when the bin has been scored,
// otherwise we're writing worthless info.
if(scored)
{
// write out the score info
if(scoreInfo == null)
out.writeInt(0);
else
{
out.writeInt(scoreInfo.length);
for(int i=0; i < scoreInfo.length; i++)
out.writeUTF(scoreInfo[i]);
}
//get keys from the hashtable by retrieving the values and then
// maing pairs.
out.writeInt(scoreLookup.size());
java.util.Enumeration keys = scoreLookup.keys();
Object key;
for(int i=0; i < scoreLookup.size(); i++)
{
key = keys.nextElement();
// writes out the lane index
out.writeInt(key.hashCode());
// Now write out the value for that lane
out.writeUTF( (String) scoreLookup.get(key));
}
}
}
/**
* 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.
*
*
Warning: this method cannot read in the scoring values
* because of the way that they are stored, specifically by lane.
* However, the lanes are not available to this method, so the information
* cannot be stored. A class calling this method should make provisions
* to take care of the rest of the data stream. The method
* setScore
can be used to set the value, it takes a
* lane value pair and records it. The stream will be left at the
* start of the values. The first thing will be an integer giving the
* number of entries stored in the stream. It will be followed by
* pairs of laneIndexies and values. The laneIndexies are integers and
* the values are UTF-encoded Strings.
*
* @param in the input stream with the data for the class.
*
* @exception IOException occurs when a problem is encountered when
* writing to the stream.
*
* @see Bin#setScore
*/
public void read(DataInputStream in) throws IOException
{
location = in.readDouble();
range = in.readDouble();
name = in.readUTF();
scored = in.readBoolean();
String scoreName = in.readUTF();
try {
scoreMethod = (ScoreFunction) FeatureList.getScoreMgr().get(scoreName);
scoreMethod = (ScoreFunction) scoreMethod.clone();
scoreMethod.read(in);
}catch(java.util.NoSuchElementException e) {
throw new IOException("The scoring method " + scoreName + " could not" +
" be found.");
}
// if the bin isn't scored, then this stuff wasn't written because it
// would have been worthless, so obviously we don't need to read it in.
if(scored)
{
int length = in.readInt();
if(length == 0)
scoreInfo = null;
else
{
scoreInfo = new String[length];
for(int i=0; i < length; i++)
scoreInfo[i] = in.readUTF();
}
// read in the hashtable pairs.
// This can't be done here because we need the lane object to act
// as a key. We don't have the lanes. Just rely on the calling
// class to take care of it. Yuck!
// We can make a new hashtable though. We wouldn't want to mix
// old stuff with new
scoreLookup = new Hashtable(30);
}
else
{
// if it's not scored, set stuff
scoreInfo = null;
scoreLookup = new Hashtable(30);
}
}
}