//===================================================================== // File: ABILaneFilter.java // Class: ABILaneFilter // Package: AFLPcore // // Author: James J. Benham // Date: October 16, 2000 // Contact: james_benham@hmc.edu // // Genographer v1.6 - Computer assisted scoring of gels. // Changes Copyright (C) 2001 James J. Benham // Copyright (C) 1993 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.File; import java.io.RandomAccessFile; import java.io.IOException; import java.util.NoSuchElementException; /** * This class reads data from a ABI 3100 and a 3700 capillary machine. * As far as the program is concerned, a capilary is identical to * a lane in a gel. * * The file format for the file is nearly identical to the format for the * ABI 377 (the ABILaneFilter class), and the description will not be * duplicated here. * * The major difference is that the LANS data does not always seem to be * present. This means that the color of the size standard must be selected * by the user. * * @see ABILaneFilter */ public class ABI3x00Filter extends ImportFilter { // Variables from parent class //private protected int filetype; // the type, see constants above //private protected String name; // the name of this filter //private protected String descript; // a brief description //private protected File helpFile; // represents the file that contains // the help info for this filter. // Used to indentify the different entries of interest in the ABI file and // store the index into an array that contains the info. private static int NUM_ENTRIES = 10; // the number of entries. private static final int DATA = 0; private static final int GELN = 1; private static final int LANE = 2; private static final int LANS = 3; private static final int PEAK1 = 4; // Keep these 4 in order! private static final int PEAK2 = 5; private static final int PEAK3 = 6; private static final int PEAK4 = 7; private static final int SpNm = 8; private static final int StdF = 9; private ABIIndexEntry entries[]; /** color channel */ public static final int YELLOW = 2; /** color channel */ public static final int RED = 3; /** color channel */ public static final int BLUE = 0; /** color channel */ public static final int GREEN = 1; public static final int ALL = 4; private int colorChannel=0; private int stdChannel=0; private String standardName; private SizeFunction sizeFn; /** * Creates a new filter to read in ABI files. */ public ABI3x00Filter() { // Initialize the variables for this filter filetype = LANE; name = "ABI 3100/3700"; descript = "Reads lane files from ABI 3100 or ABI3700."; helpFile = "abi3x00.html"; // Options must be set. options = null; standardName = "not set"; sizeFn = null; } /** * Access the name of the filter. * * @return name of the import filter */ public String getName() { return name; } /** * Returns the type of input file supported by this filter In this case * ImportFilter.LANE, since the filter reads in lane data. * * @return constant LANE. */ public int getFileType() { return filetype; } /** * Retrieves a short, approximately one sentence, description of the filter. * * @return the description */ public String getDescription() { return descript; } /** * The help file describes which files the filter reads and the options * that this filter accepts. * * @return File that contains the help information, either html or * plaintext. */ public String getHelpFile() { return helpFile; } /** * Returns the options for this filter, which includes the color of the * data, the size function to use, and the size standard. The first * option is the color to read, which can be one of four possilbe * values: Red, Blue, Green, or Yellow. The color choice is given as * a Option of type CHOICE. The second option * allows the color of the size standard to be selected. The third * option is also of type CHOICE. It tells which size * method should be used to compute the size of the fragements. Please * see the help files and the code for the size functions for a * description of how the work. The third option describes the size * standard to use. This simply gives the program a list of values. * These are stored in a file called "standards.cfg" Possible values * for all of these options are read in from the * FeatureList class. * * @return an array containing the options described above. * * @see Option * @see FeatureList * @see SizeFunction * @see SizeStandard */ public Option[] getOptions() { Option[] returnOpts = new Option[4]; // Pick the color String[] colors = new String[5]; colors[RED] = "Red"; colors[BLUE] = "Blue"; colors[GREEN] = "Green"; colors[YELLOW] = "Yellow"; colors[ALL] = "All"; Option param = new Option("Color", Option.CHOICE, true, colors, "Blue"); returnOpts[0] = param; String [] stdColors = new String[4]; stdColors[RED] = "Red"; stdColors[BLUE] = "Blue"; stdColors[GREEN] = "Green"; stdColors[YELLOW] = "Yellow"; param = new Option("Standard Color", Option.CHOICE, true, stdColors, "Red"); returnOpts[1] = param; // The size function option, possiblities retrieved from the // feature list. param = new Option("Size Method", Option.CHOICE, true, FeatureList.getSizeMgr().getNames(), FeatureList.getSizeMgr().getDefaultName()); returnOpts[2] = param; // the size standards defined try { param = new Option("Size Standard", Option.CHOICE, true, FeatureList.getStandardMgr().getNames()); } catch(IOException e) { throw new MissingParameterError("Error accessing standards file. " + e.getMessage()); } returnOpts[3] = param; return returnOpts; } /** * Sets the parameters for the filter to the specified values, including * color. The color must be set before this filter can run. The option * representing the color should have a string value naming the color. * The size function must also be set for the filter to work. It * must contain the name of a valid SizeFunction. Note that * the name is not the class name of the SizeFunction, but * the name each SizeFunction stores internally. The * third option must also be set. * * @param opts an array of length 4 which contains the options * mentioned above and described in getOptions() * The order must be: color, size function, size standard. * * @exception MissingParameterError occurs when the filter fails to * extract a string from the first option in opts. * @exception IllegalArgumentException occurs when a string is found but * cannot be matched to one of the colors: Red, Blue, Green, or Yellow. * Or if an array with length not equal to 3 is given as * opts, or if the specified size function, the second * option, could not be matched to a defined size function. */ public void setOptions(Option[] opts) { // Check the length. if(opts.length != 4) throw new IllegalArgumentException("Invalid options for ABI Lane " + "Filter. 4 options expected, but " + opts.length + " were provided."); // extract the option String value = opts[0].getStringValue(); // store the options options = opts; // check to make sure we have a string if (value == null) throw new MissingParameterError("Color not provided as parameter to " + "ABI 3x00 Filter."); if(value.equalsIgnoreCase("Red")) colorChannel = RED; else if(value.equalsIgnoreCase("Blue")) colorChannel = BLUE; else if(value.equalsIgnoreCase("Green")) colorChannel = GREEN; else if(value.equalsIgnoreCase("Yellow")) colorChannel = YELLOW; else if(value.equalsIgnoreCase("All")) colorChannel = ALL; else { // didn't match a color, so something is wrong. // set the options back to null since the ones we got were no good. options = null; // and complain throw new IllegalArgumentException("Invalid color specified for ABI" + " 3x00 Filter."); } // Size standard color // extract the option value = opts[1].getStringValue(); // check to make sure we have a string if (value == null) throw new MissingParameterError("Color not provided as parameter to " + "ABI 3x00 Filter."); if(value.equalsIgnoreCase("Red")) stdChannel = RED; else if(value.equalsIgnoreCase("Blue")) stdChannel = BLUE; else if(value.equalsIgnoreCase("Green")) stdChannel = GREEN; else if(value.equalsIgnoreCase("Yellow")) stdChannel = YELLOW; else { // didn't match a color, so something is wrong. // set the options back to null since the ones we got were no good. options = null; // and complain throw new IllegalArgumentException("Invalid color specified for ABI" + " 3100/3700 Size Standard."); } // Next should be the size function String sizeFnName = opts[2].getStringValue(); try { sizeFn = (SizeFunction) FeatureList.getSizeMgr().get(sizeFnName); } catch(NoSuchElementException e) { options = null; throw new IllegalArgumentException("Invalid sizing function specified" + " for ABI 3x00 Filter. "); } // The final option is the size standard definition standardName = opts[3].getStringValue(); // this will be checked later } /** * This is the method that is called to preform the actual reading of the * file. The data in the file represents data from a single lane. The * options/parameters required for the filter should be set using * setOptions, and if they are not, an exception will be * thrown. * * @param inputFile The file that contains the lane data. * * @return a Lane object with all of the appropriate information. * * @exception MissingParameterError occurs if the options are not * set. Since this includes the required color, the filter cannot * read in the lane. * @exception IOException If an error is encountered in the file, * then this exception will be thrown */ public Lane [] readLane(File inputFile) throws IOException { Lane newLane; Lane [] laneArray; boolean allChannels; long indexOffset; long indexLength; DataList stdPoints; int peakIndex; SizeStandard sizeStd; SizeFunction sizeFn; // Make sure we have options set, including the color channel if(options == null) throw new MissingParameterError("The color for the filter must be set" + " before the filter can work."); // Open the file. Set the mode to read only. RandomAccessFile in = new RandomAccessFile(inputFile, "r"); // Check the file type. They all seem to start with "ABIF", which // becomes 0x41424946 in hex. int magicNum = in.readInt(); if( magicNum != 0x41424946) throw new IOException("This does not appear to be an ABI lane file." + " See help for more info."); // Get the length of the index of types. in.seek(18); indexLength = (long) in.readInt(); // Get the location of the index. in.seek(26); indexOffset = (long) in.readInt(); if(colorChannel == ALL){ allChannels = true; colorChannel = -1; laneArray = new Lane[4]; for(int i = 0; i < 4; i++) laneArray[i] = null; } else{ allChannels = false; laneArray = new Lane[1]; } // All channels code added by Philip DeCamp for(int i = 0; i < 4; i++) { if(allChannels){ for(;;){ colorChannel++; if(colorChannel > 3) break; entries = readRecords(indexOffset, indexLength, in); if(colorChannel != stdChannel){ try{ checkForColor(); break; } catch(IOException e){ // The color is not prsent // No action needed } } } if(colorChannel > 3) break; } else { entries = readRecords(indexOffset, indexLength, in); } // Move to the location of the Data in.seek(entries[DATA].dataOffset); int traceSize = (int) entries[DATA].numElements; //System.out.println("Found " + traceSize + " data elements."); double[] trace = new double[traceSize]; int traceValue; for(int j= 0; j < entries[DATA].numElements; j++){ // WARNING: We shouldn't get Negative values here, but // we did (> 32768). Just zero them. traceValue = in.readUnsignedShort(); trace[j] = (double) ((traceValue > 32768) ? 0 : traceValue); } newLane = new Lane(trace); // Read in the Sample name. if(entries[SpNm].dataOffset == 0) { newLane.setName("unknown"); System.out.println("Sample Name not found."); } else { if(entries[SpNm].numElements > 4) newLane.setName(readPString(entries[SpNm].dataOffset, in)); else newLane.setName(readPString(entries[SpNm].dataOffset)); } // Read Lane Number if(entries[LANE].dataOffset == 0 && entries[LANE].numElements == 0) { //System.out.println("Lane number not found."); newLane.setLaneNumber(0); } else { newLane.setLaneNumber( (int)(entries[LANE].dataOffset >> 16) ); //System.out.println("Lane number: " + newLane.getLaneNumber()); } peakIndex = PEAK1 + stdChannel; //System.out.println("Accessing standard peaks for channel " + peakIndex); //=========== Read in the standard peaks=========== // get the size standard. try{ sizeStd = (SizeStandard) FeatureList.getStandardMgr().get(standardName); } catch(NoSuchElementException e) { throw new IOException("Unknown size standard! '" + standardName + "'"); } //System.out.println("Peak Record Length: " + entries[peakIndex].recLength); long numPeaks = entries[peakIndex].recLength/96; //System.out.println("Number of Peaks: " + numPeaks); stdPoints = new DataList(); Peak pk; for(int j=0; j < numPeaks; j++) { in.seek(entries[peakIndex].dataOffset + j*96); pk = readPeak(in); //System.out.print(" Reading Peak Location: " + pk.getLocation()); //System.out.print(" Scan: " + pk.getArea()); //System.out.print(" Height: " + pk.getHeight()); if(sizeStd.contains(pk.getLocation())) { stdPoints.addData(pk); //System.out.print("*"); } //System.out.println(); } // Set the color channel newLane.setColor(colorChannel); //================= set the size function ============== String sizeName = options[2].getStringValue(); sizeFn = (SizeFunction) FeatureList.getSizeMgr().get(sizeName); sizeFn = (SizeFunction) sizeFn.clone(); sizeFn.init(stdPoints); sizeFn.setMaxScan(newLane.getNumPoints() - 1); newLane.setSizeFunction(sizeFn); //==================clean up============================= laneArray[i] = newLane; if(!allChannels) break; } in.close(); if(allChannels){ for(int i = 0; i < 4; i++){ if(laneArray[i] != null){ allChannels = false; break; } } if(allChannels) throw new IOException("No Data Channels Could be Found"); } //System.out.println("Lane Read Completed."); return laneArray; } /** * This filter does not read gels. * * @return Always null */ public Gel readGel(File inputFile) throws IOException { return null; } /** * Parses the records portion of the ABI file to gather information about * several different data structures in the file. The important pieces of * information are, where the data structure is in the file, and how big * it is. All of the records start with a four charachter ASCII value. * the records of interest are DATA, which stores the trace information; * SMPL, which is the name of the sample; GELN, which is the name of the * gel on which the sample was run; and LANE, which stores the lane * number of the sample. In some cases, these identifers are repeated. * For example, a file can have up to 12 DATA entries, but each one has * a tag number to seperate it. Only one of these contains the information * we want. * * The parser works be converting strings like "DATA" into a long value, * which is the ASCII representation of the string. It then compares * this to the first four bytes of each record. On a match, it will * look at the rest of the record and decide to either keep it or discard * it. More details are in the code for those interested. * * @param indexOffset the location from the beginning of the file to * the start of the index of records. ie, the location of the first * record * @param indexLength the number of records in the index * @param in the ABI trace file. * * @return the records of interest stored in ABIIndexEntry(s). Only * the portions of the records that are needed are returned. * * @exception IOException could come from RandomAccessFile methods or * from the method itself MODIFY after complition */ private ABIIndexEntry[] readRecords(long indexOffset, long indexLength, RandomAccessFile in) throws IOException { // Create the structure to hold the records for the entries. ABIIndexEntry record[] = new ABIIndexEntry[NUM_ENTRIES]; // Add the values that we are looking for // in some cases, we are looking for certain records. For example: // the correct color channel's data entry is 9 + colorChannel // which represents the matrix corrected data in the file. // The file actully contains anywhere from 8-12 DATA records, // but the tag number determines which one we want. record[DATA] = new ABIIndexEntry("DATA", 9 + colorChannel); // Go for the raw data... //record[DATA] = new ABIIndexEntry("DATA", 1 + colorChannel); record[GELN] = new ABIIndexEntry("GELN"); record[LANE] = new ABIIndexEntry("LANE"); record[LANS] = new ABIIndexEntry("LANS"); record[PEAK1] = new ABIIndexEntry("PEAK", 1); record[PEAK2] = new ABIIndexEntry("PEAK", 2); record[PEAK3] = new ABIIndexEntry("PEAK", 3); record[PEAK4] = new ABIIndexEntry("PEAK", 4); record[SpNm] = new ABIIndexEntry("SpNm"); record[StdF] = new ABIIndexEntry("StdF"); // Variables to temporarly hold the record info while we decide if the // record is valid. long nameKey; long tag; long numElem; long length; long offset; // Go to the start of the index in.seek(indexOffset); // Look for records that begin with the name that we're interested in // and then look at that record more carefully. If it doesn't look valid, // we won't copy the temporary values to anything permenant. for(int count=0; count < indexLength; count++) { // read in the name nameKey = (long) in.readInt(); // Read in the other info tag = (long) in.readInt(); in.skipBytes(4); // skip to the next interesting part numElem = (long) in.readInt(); length = (long) in.readInt(); offset = (long) in.readInt(); // Now see if the name matches any of the ones we're looking for by // comparing it to each entry in the array. for(int i=0; i < NUM_ENTRIES; i++) { if(nameKey == record[i].nameKey) { // Make sure we have the data record we want. // If the tag is set, check to see if it matches. if( (offset != 0) && !((record[i].matchTagNumber()) && (tag != record[i].tagNumber))) { // Make sure the data points to something // good. There seem to be a lot of entries that // don't point to anything. For example, some // files contain multiple SMPL entries, but only // one of these has a non-null data pointer. If // the record points to something, store the // temporary values. record[i].tagNumber = tag; record[i].numElements = numElem; record[i].recLength = length; record[i].dataOffset = offset; // we only need to store it once, so don't go through // the inner loop extra times. break; } } //if (name matches one we're interested in. } // for(every entry in the record array) // move to the next record in.seek(indexOffset + count*28); } // for every record return record; } /** * Check to make sure we found the color channel. If we don't throw an * exception. This could happen because not every file has every channel * for the processed color data. In this case, the offset will be zero * since it was never assigned a value. * * @exception IOException occurs when the filter cannot find the color * channel specified with setColorChannel in the file. */ private void checkForColor() throws IOException { if( entries[DATA].dataOffset == 0) { String errorMsg=""; switch(colorChannel) { case RED: errorMsg = "red"; break; case BLUE: errorMsg = "blue"; break; case GREEN: errorMsg = "green"; break; case YELLOW: errorMsg = "yellow"; break; } errorMsg = "Could not find the color " + errorMsg + " in the file."; throw new IOException(errorMsg); } } /** * Read in a peak from the file. A peak in the ABI file is 96 bytes * long. The first 4 bytes are used to store the scan number as 32-bit * integer. This scan number is different than the one displayed by the * ABI programs. It is 1000 less, but the number 1000 could vary. 1000 is * also the value stored in OFFS. The next two bytes are the height, as * a 16-bit integer. I don't know what the next 12 bytes are. After that, * the peak area is stored as a 32-bit integer. Skip four bytes again. * we then have the size of the peak, in bp. This is a IEEE 754 single * precision float. * *
   *   Value     Start   Length(bytes)    Type
   *   scan        0           4           integer (1000 + this value)
   *   height      4           2           integer
   *   area       18           4           integer
   *   size       26           4           IEEE 754 single-percision float
   * 
* * @param in the input source * * @return a peak, with the size/location and height read from the file * and the area set as the scan number, not the area. * * @exception IOException occurs if the file cannot be read. */ public Peak readPeak(RandomAccessFile in) throws IOException { int scan; int height; int area; double size; scan = in.readInt(); height = in.readUnsignedShort(); in.skipBytes(12); area = in.readInt(); in.skipBytes(4); size = in.readFloat(); return new Peak(size, (double) height, (double) scan); } /** * Read in a Pascal type string, where the first byte is the length of * the string, and the rest are the charachters. This is accomplised by * reading in the bytes (unsigned) and converting them into a charachter * array of the correct length, and then turing the character arrray * into a String. * * @param location where the string is in the file, relative to the * beginning of the file. * @param in the file with the information. * * @return the string as a String object. * * @exception IOException occurs if location can not * be reached for some reason. */ String readPString(long location, RandomAccessFile in) throws IOException { // Move to the correct location in.seek(location); // Read in the length and set up the array int length = in.readUnsignedByte(); char gelname[] = new char[length]; // fill the array. for(int i=0; i < length; i++) gelname[i] = (char) in.readUnsignedByte(); return new String(gelname); } /** * This converts a long integer into a string. This is used when the * dataOffset contains the actual data. This will happen if it is * an extremely short string, < 3 characters. The format is as follows, * the bits 24-31 contain the length of the string, and the bits following * contain the sequence character. It is perhaps easier to think of it * as 8 bytes. The first four high-order bytes are not used. A long is * used to store the original 32-bit data so sign wrapping can be avoided. * of the lower 4 bytes, say 3, 2, 1 and 0. With 0 being the low-order byte, * the length of the string is stored in byte 3, while the characters are * stored in 2, 1, and 0 as neccessary. * * @param stringBits a data structure matching that specified above. * * @return the string contained in the bits */ String readPString(long stringBits) { int length = (int) (stringBits >>> 24); char name[] = new char[length]; // fil the array. for(int i=0; i < length; i++) name[i] = (char) ((stringBits >>> ( (2 - i)*8 )) & 0x00000000000000ff); return new String(name); } }