/*
* The MIT License
*
* Copyright (c) 2012 The Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package picard.illumina.parser.readers;
import htsjdk.samtools.util.CloseableIterator;
import picard.PicardException;
import java.io.File;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The position files of Illumina are nearly the same form: Pos files consist of text based tabbed
* x-y coordinate float pairs, locs files are binary x-y float pairs, clocs are compressed binary
* x-y float pairs. Each of these file types we read sequentially and are really concerned with
* iterating over the coordinates and returning them as as they would appear in a QSeq file.
* Therefore, this abstract base class provides the basic functionality for iterating over
* the values found in these files and converting them into qseq style coordinates.
*
* Currently these readers also return lane/tile but this will be unnecessary in future releases.
*/
public abstract class AbstractIlluminaPositionFileReader implements CloseableIterator<AbstractIlluminaPositionFileReader.PositionInfo> {
public static final float MAX_POS = 9999999.99f;
/**
* At least one NextSeq run produced a small negative value for y coordinate (-5), so allow small
* negative values and see what happens.
*/
public static final float MIN_POS = -10.0f;
public class PositionInfo {
/** The x-position as it occurs in the file being read */
public final float xPos;
/** The y-position as it occurs in the file being read*/
public final float yPos;
/** The lane, which is determined from the file name*/
public final int lane;
/** The tile, which is determined from the file name*/
public final int tile;
/** The QSeq style x-coordinat, an integer = Math.round(xPos*10 + 1000)*/
public final int xQseqCoord;
/** The QSeq style y-coordinates, an integer = Math.round(yPos*10 + 1000)*/
public final int yQseqCoord;
public PositionInfo(final float x, final float y, final int lane, final int tile) {
if(x < MIN_POS || y < MIN_POS || x > MAX_POS || y > MAX_POS) {
throw new IllegalArgumentException(
String.format("Cluster location not in the range %f..%f. x: %f; y: %f; lane: %d; tile: %d",
MIN_POS, MAX_POS, x, y, lane, tile));
}
this.xPos = x;
this.yPos = y;
this.xQseqCoord = posToQSeqCoord(x);
this.yQseqCoord = posToQSeqCoord(y);
this.lane = lane;
this.tile = tile;
}
/** Convert a value in float form as it occurs in pos,locs,and clocs files into integer as it is found in QSeqs */
private int posToQSeqCoord(final float pos) {
return Math.round(pos * 10 + 1000);
}
public boolean equals(final Object other) {
if(other == null || other.getClass() != AbstractIlluminaPositionFileReader.PositionInfo.class) {
return false;
}
if(other == this) return true;
final PositionInfo otherPi = (PositionInfo) other;
return this.xPos == otherPi.xPos && this.yPos == otherPi.yPos &&
this.lane == otherPi.lane && this.tile == otherPi.tile &&
this.xQseqCoord == otherPi.xQseqCoord && this.yQseqCoord == otherPi.yQseqCoord;
}
}
//Note: Perhaps use the IlluminaFileUtil to do this part
private static final Pattern FileNamePattern = Pattern.compile("^s_(\\d+)_(\\d+)(_pos\\.txt|\\.locs|\\.clocs|_pos\\.txt.gz|_pos\\.txt.bz2)$");
private final File file;
private final int lane;
private final int tile;
public AbstractIlluminaPositionFileReader(final File file) {
this.file = file;
final int [] laneAndTile = fileNameToLaneAndTile(file.getName());
lane = laneAndTile[0];
tile = laneAndTile[1];
}
/**
* Use this ctor if lane and tile are not discernible from file name.
* @param file
* @param lane
* @param tile
*/
public AbstractIlluminaPositionFileReader(final File file, final int lane, final int tile) {
this.file = file;
this.lane = lane;
this.tile = tile;
}
public int getTile() {
return tile;
}
public int getLane() {
return lane;
}
public File getFile() {
return file;
}
/** Extract the lane/tile from the given filename **/
private int [] fileNameToLaneAndTile(final String fileName) {
final String [] tokens = fileName.split(File.pathSeparator);
final Matcher matcher = FileNamePattern.matcher(tokens[tokens.length - 1]);
if(!matcher.matches()) {
throw new PicardException("File name not of the right structure: <filePath>/s_<lane>_<tile>(_pos.txt|_pos.txt.gz|_pos.txt.bz2.locs|.clocs). File name (" + fileName + ")");
}
return new int[]{Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2))};
}
/** Return the next set of coordinates in a given file. **/
public final PositionInfo next() {
if(! hasNext()) {
throw new NoSuchElementException("No such cluster, cluster count(" + makeExceptionMsg() +")");
}
return unsafeNextInfo();
}
/** Returns the next position info. Implementations of this method do not need to call hasNext since
* it is called in next() */
protected abstract PositionInfo unsafeNextInfo();
/** Create a string that will be included in any NoSuchElementException thrown by the next() method */
protected abstract String makeExceptionMsg();
/** Return true if the file has more elements to return, false otherwise */
public abstract boolean hasNext();
public void remove() {
throw new UnsupportedOperationException();
}
}