/*
* Copyright (c) 2007-2012 The Broad Institute, Inc.
* SOFTWARE COPYRIGHT NOTICE
* This software and its documentation are the copyright of the Broad Institute, Inc. All rights are reserved.
*
* This software is supplied without any warranty or guaranteed support whatsoever. The Broad Institute is not responsible for its use, misuse, or functionality.
*
* This software is licensed under the terms of the GNU Lesser General Public License (LGPL),
* Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php.
*/
package org.broad.igv.bbfile;
import htsjdk.samtools.seekablestream.SeekableStream;
import org.apache.log4j.Logger;
import org.broad.igv.util.CompressionUtils;
import htsjdk.tribble.util.LittleEndianInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Created by IntelliJ IDEA.
* User: Owner
* Date: May 5, 2010
* Time: 8:27:56 PM
* To change this template use File | Settings | File Templates.
*/
/*
* Container class for reading and storing a block of zoom level data records.
* */
public class ZoomDataBlock {
private static Logger log = Logger.getLogger(ZoomDataBlock.class);
// Bed data block access variables - for reading in bed records from a file
private long fileOffset; // data block file offset
private long dataBlockSize; // byte size for data block specified in the R+ leaf
private boolean isLowToHigh; // if true, data is low to high byte order; else high to low
// defines the zoom level source chromosomes
private int zoomLevel; // zoom level for the R+ chromosome data location tree
private HashMap<Integer, String> chromosomeMap; // map of chromosome ID's and corresponding names
private RPTreeLeafNodeItem leafHitItem; //R+ leaf item with chromosome region and file data location
// Provides uncompressed byte stream data reader
private byte[] zoomBuffer; // buffer containing leaf block data uncompressed
private int remDataSize; // number of unread decompressed data bytes
// byte stream readers
private LittleEndianInputStream lbdis = null; // low to high byte stream reader
private DataInputStream dis = null; // high to low byte stream reader
// Bed data extraction members
private ArrayList<ZoomDataRecord> zoomDataList; // array of zoom level data
/*
* Constructor for Bed data block reader.
*
* Parameters:
* zoomLevel - zoom level for data block
* fis - file input stream handle
* leafItem - R+ tree leaf item containing block data file location
* chromIDTree - B+ chromosome index tree returns chromosome ID's for names
* isLowToHigh - byte order is low to high if true; else high to low
* uncompressBufSize - byte size for decompression buffer; else 0 for uncompressed
* */
public ZoomDataBlock(int zoomLevel, SeekableStream fis, RPTreeLeafNodeItem leafHitItem,
HashMap<Integer, String> chromosomeMap, boolean isLowToHigh, int uncompressBufSize) {
this.zoomLevel = zoomLevel;
this.leafHitItem = leafHitItem;
this.chromosomeMap = chromosomeMap;
this.isLowToHigh = isLowToHigh;
fileOffset = this.leafHitItem.getDataOffset();
dataBlockSize = this.leafHitItem.geDataSize();
byte[] buffer = new byte[(int) dataBlockSize];
// read Bed data block into a buffer
try {
fis.seek(fileOffset);
fis.readFully(buffer);
// decompress if necessary - the buffer size is 0 for uncomressed data
// Note: BBFile Table C specifies a decompression buffer size
if (uncompressBufSize > 0)
zoomBuffer = (new CompressionUtils()).decompress(buffer, uncompressBufSize);
else
zoomBuffer = buffer; // use uncompressed read buffer directly
} catch (IOException ex) {
log.error("Error reading Zoom level " + this.zoomLevel + " data for leaf item ", ex);
String error = String.format("Error reading zoom level %d data for leaf item %d\n", this.zoomLevel);
throw new RuntimeException(error, ex);
}
// wrap the bed buffer as an input stream
if (this.isLowToHigh)
lbdis = new LittleEndianInputStream(new ByteArrayInputStream(zoomBuffer));
else
dis = new DataInputStream(new ByteArrayInputStream(zoomBuffer));
// initialize unread data size
remDataSize = zoomBuffer.length;
// use method getZoomData to extract block data
}
/*
* Method returns all zoom level data within the decompressed block buffer
*
* Parameters:
* selectionRegion - chromosome region for selecting zoom level data records
* contained - indicates selected data must be contained in selection region
* if true, else may intersect selection region
*
* Returns:
* zoom data records in the data block
*
* Note: Remaining bytes to data block are used to determine end of reading
* since a zoom record count for the data block is not known.
* */
public ArrayList<ZoomDataRecord> getZoomData(RPChromosomeRegion selectionRegion,
boolean contained) {
int chromID, chromStart, chromEnd, validCount;
float minVal, maxVal, sumData, sumSquares;
int itemHitValue;
int recordNumber = 0;
// allocate the bed feature array list
zoomDataList = new ArrayList<ZoomDataRecord>();
// check if all leaf items are selection hits
RPChromosomeRegion itemRegion = new RPChromosomeRegion(leafHitItem.getChromosomeBounds());
int leafHitValue = itemRegion.compareRegions(selectionRegion);
try {
//for(int index = 0; mRemDataSize >= ZoomDataRecord.RECORD_SIZE; ++index) {
for (int index = 0; remDataSize > 0; ++index) {
recordNumber = index + 1;
if (isLowToHigh) { // buffer stream arranged low to high bytes
chromID = lbdis.readInt();
chromStart = lbdis.readInt();
chromEnd = lbdis.readInt();
validCount = lbdis.readInt();
minVal = lbdis.readFloat();
maxVal = lbdis.readFloat();
sumData = lbdis.readFloat();
sumSquares = lbdis.readFloat();
} else { // buffer stream arranged high to low bytes
chromID = dis.readInt();
chromStart = dis.readInt();
chromEnd = dis.readInt();
validCount = dis.readInt();
minVal = dis.readFloat();
maxVal = dis.readFloat();
sumData = dis.readFloat();
sumSquares = dis.readFloat();
}
if (leafHitValue == 0) { // contained leaf region always a hit
String chromName = chromosomeMap.get(chromID);
ZoomDataRecord zoomRecord = new ZoomDataRecord(zoomLevel, recordNumber, chromName,
chromID, chromStart, chromEnd, validCount, minVal, maxVal, sumData, sumSquares);
zoomDataList.add(zoomRecord);
} else { // test for hit
itemRegion = new RPChromosomeRegion(chromID, chromStart, chromID, chromEnd);
itemHitValue = itemRegion.compareRegions(selectionRegion);
// itemHitValue < 2 for intersection; itemHitValue == 0 for is contained
if (!contained && Math.abs(itemHitValue) < 2 || itemHitValue == 0) {
String chromName = chromosomeMap.get(chromID);
ZoomDataRecord zoomRecord = new ZoomDataRecord(zoomLevel, recordNumber, chromName,
chromID, chromStart, chromEnd, validCount, minVal, maxVal, sumData, sumSquares);
zoomDataList.add(zoomRecord);
}
}
// compute data block remainder fom size item read
remDataSize -= ZoomDataRecord.RECORD_SIZE;
}
} catch (IOException ex) {
log.error("Read error for zoom level " + zoomLevel + " leaf item " );
// accept this as an end of block condition unless no items were read
if (recordNumber == 1)
throw new RuntimeException("Read error for zoom level " + zoomLevel + " leaf item ");
}
return zoomDataList;
}
public void print() {
log.debug("Zoom Level " + zoomLevel + "data for leaf item :");
for (int index = 0; index <= zoomDataList.size(); ++index) {
// zoom data records print themselves
zoomDataList.get(index).print();
}
}
}