package picard.illumina.parser.readers; import picard.PicardException; import picard.util.UnsignedTypeUtil; import java.io.File; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.NoSuchElementException; /** * Reads a TileMetricsOut file commonly found in the InterOp directory of an Illumina Run Folder. This * reader DOES NOT try to interpret the metrics code or metrics value but instead returns them in what * is essentially a struct. * * File Format: * byte 0 (unsigned byte) = The version number which MUST be 2 or an exception will be thrown * byte 1 (unsigned byte) = The record size which must be 10 or an exception will be thrown * bytes 3 + (current_record * 10) to (current_record * 10 + 10) (TileMetrics Record) = The actual records each of size 10 that * get converted into IlluminaPhasingMetrics objects * * TileMetrics Record Format: * Each 10 byte record is of the following format: * byte 0-1 (unsigned short) = lane number * byte 2-3 (unsigned short) = tile number * byte 4-5 (unisgned short) = metrics code, see Theory of RTA document by Illumina for definition * byte 6-9 (float) = metrics value, see Theory of RTA document by Illumina for definition */ public class TileMetricsOutReader implements Iterator<TileMetricsOutReader.IlluminaTileMetrics> { private static final int HEADER_SIZE = 2; private static final int EXPECTED_RECORD_SIZE = 10; private static final int EXPECTED_VERSION = 2; private final BinaryFileIterator<ByteBuffer> bbIterator; /** * Return a TileMetricsOutReader for the specified file * @param tileMetricsOutFile The file to read */ public TileMetricsOutReader(final File tileMetricsOutFile) { bbIterator = MMapBackedIteratorFactory.getByteBufferIterator(HEADER_SIZE, EXPECTED_RECORD_SIZE, tileMetricsOutFile); final ByteBuffer header = bbIterator.getHeaderBytes(); //Get the version, should be EXPECTED_VERSION, which is 2 final int actualVersion = UnsignedTypeUtil.uByteToInt(header.get()); if(actualVersion != EXPECTED_VERSION) { throw new PicardException("TileMetricsOutReader expects the version number to be " + EXPECTED_VERSION + ". Actual Version in Header( " + actualVersion + ")" ); } final int actualRecordSize = UnsignedTypeUtil.uByteToInt(header.get()); if(EXPECTED_RECORD_SIZE != actualRecordSize) { throw new PicardException("TileMetricsOutReader expects the record size to be " + EXPECTED_RECORD_SIZE + ". Actual Record Size in Header( " + actualRecordSize + ")" ); } } public boolean hasNext() { return bbIterator.hasNext(); } public IlluminaTileMetrics next() { if(!hasNext()) { throw new NoSuchElementException(); } return new IlluminaTileMetrics(bbIterator.next()); } public void remove() { throw new UnsupportedOperationException(); } /** * IlluminaPhasingMetrics corresponds to a single record in a TileMetricsOut file */ public static class IlluminaTileMetrics { private final IlluminaLaneTileCode laneTileCode; private final float metricValue; public IlluminaTileMetrics(final ByteBuffer bb) { this(UnsignedTypeUtil.uShortToInt(bb.getShort()), UnsignedTypeUtil.uShortToInt(bb.getShort()), UnsignedTypeUtil.uShortToInt(bb.getShort()), bb.getFloat()); } public IlluminaTileMetrics(final int laneNumber, final int tileNumber, final int metricCode, final float metricValue) { this.laneTileCode = new IlluminaLaneTileCode(laneNumber, tileNumber, metricCode); this.metricValue = metricValue; } public int getLaneNumber() { return laneTileCode.getLaneNumber(); } public int getTileNumber() { return laneTileCode.getTileNumber(); } public int getMetricCode() { return laneTileCode.getMetricCode(); } public float getMetricValue() { return metricValue; } public IlluminaLaneTileCode getLaneTileCode() { return laneTileCode; } @Override public boolean equals(final Object o) { if (o instanceof IlluminaTileMetrics) { final IlluminaTileMetrics that = (IlluminaTileMetrics) o; return laneTileCode == that.laneTileCode && metricValue == that.metricValue; // Identical tile data should render exactly the same float. } else { return false; } } @Override public int hashCode() { return String.format("%s:%s:%s:%s", laneTileCode.getLaneNumber(), laneTileCode.getTileNumber(), laneTileCode.getMetricCode(), metricValue).hashCode(); // Slow but adequate. } } /** Helper class which captures the combination of a lane, tile & metric code */ public static class IlluminaLaneTileCode { private final int laneNumber; private final int tileNumber; private final int metricCode; public IlluminaLaneTileCode(final int laneNumber, final int tileNumber, final int metricCode) { this.laneNumber = laneNumber; this.tileNumber = tileNumber; this.metricCode = metricCode; } public int getLaneNumber() { return laneNumber; } public int getTileNumber() { return tileNumber; } public int getMetricCode() { return metricCode; } @Override public boolean equals(final Object o) { if (o instanceof IlluminaLaneTileCode) { final IlluminaLaneTileCode that = (IlluminaLaneTileCode) o; return laneNumber == that.laneNumber && tileNumber == that.tileNumber && metricCode == that.metricCode; } else { return false; } } @Override public int hashCode() { int result = laneNumber; result = 31 * result + tileNumber; result = 31 * result + metricCode; return result; } } }