/*
* #%L
* OME Bio-Formats package for reading and converting biological file formats.
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* 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, either version 2 of the
* License, or (at your option) any later version.
*
* 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, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package loci.formats.in;
import java.io.IOException;
import loci.common.RandomAccessInputStream;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataStore;
import ome.xml.model.primitives.PositiveFloat;
import ome.units.quantity.Length;
import ome.units.UNITS;
/**
* PQBinReader is the file format reader for PicoQuant .bin files.
*
* Please Note: This format holds FLIM data arranged so that each decay is stored contiguously.
* Therefore, as in other FLIM format readers e.g. SDTReader.java, on the first call to openBytes
* the whole data cube ( x,y,t) (NB actually t not real-time T) is loaded from the file to a buffer.
* On further calls to openBytes the appropriate 2D (x,y)plane (timebin) is returned from this buffer.
* This is in the interest of significantly improved performance when all the planes are requested one after another.
* There will be a performance cost if a single plane is requested but this is highly unlikely for FLIM data.
*/
public class PQBinReader extends FormatReader {
// -- Constants --
public static final int HEADER_SIZE = 20;
// -- Fields --
/*
* Number of time bins in lifetime histogram.
*/
protected int timeBins;
/*
* Array to hold re-ordered data for all the timeBins
*/
protected byte[] dataStore = null;
/**
* Whether to pre-load all lifetime bins for faster loading.
*/
protected boolean preLoad = true;
// -- Constructor --
/**
* Constructs a new PQBin reader.
*/
public PQBinReader() {
super("PicoQuant Bin", "bin");
domains = new String[]{FormatTools.FLIM_DOMAIN};
suffixSufficient = false;
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
long fileLength = stream.length();
int bpp = FormatTools.getBytesPerPixel(FormatTools.UINT32);
stream.order(true);
// Header
int sizeX = stream.readInt();
int sizeY = stream.readInt();
float pixResol = stream.readFloat(); // resolution of time axis of every Decay (in ns)
int sizeT = stream.readInt();
if (( sizeX * sizeY * sizeT * bpp) + HEADER_SIZE == fileLength) {
return true;
}
else {
return false;
}
}
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*/
@Override
public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
int sizeX = getSizeX();
int sizeY = getSizeY();
int bpp = FormatTools.getBytesPerPixel(getPixelType());
boolean little = isLittleEndian();
int planeSize = sizeX * sizeY * timeBins * bpp;
int timeBin = no;
int binSize = sizeX * sizeY * bpp; // size in Bytes of a single 2D timebin.
// if data is of a manageable size then pre-load for performance
if (preLoad) {
if (dataStore == null)
{
// The whole plane (all timebins) is copied into storage
// to allow different sub-plane sizes to be used for different timebins
dataStore = new byte[planeSize];
byte[] rowBuf = new byte[bpp * timeBins * sizeX];
in.seek(HEADER_SIZE);
for (int row = 0; row < sizeY; row++) {
in.read(rowBuf);
int input = 0;
for (int col = 0; col < sizeX; col++) {
// set output to first pixel of this row in 2D plane
// corresponding to zeroth timeBin
int output = (row * sizeX + col) * bpp;
for (int t = 0; t < timeBins; t++) {
for (int bb = 0; bb < bpp; bb++) {
dataStore[output + bb] = rowBuf[input + bb];
}
output += binSize;
input += bpp;
}
}
}
}
// chanStore loaded
// copy 2D plane from chanStore into buf
int iLineSize = sizeX * bpp;
int oLineSize = w * bpp;
// offset to correct timebin yth line and xth pixel
int input = (binSize * timeBin) + (y * iLineSize) + (x * bpp);
int output = 0;
for (int row = 0; row < h; row++) {
System.arraycopy(dataStore, input, buf, output, oLineSize);
input += iLineSize;
output += oLineSize;
}
} // endif preLoad
else { // load each plane individually for large data
byte[] rowBuf = new byte[bpp * timeBins * w];
in.seek(HEADER_SIZE + (y * sizeX * bpp * timeBins));
for (int row = 0; row < h; row++) {
in.skipBytes(x * bpp * timeBins);
in.read(rowBuf);
for (int col = 0; col < w; col++) {
int output = (row * w + col) * bpp;
int input = (col * timeBins + timeBin) * bpp;
for (int bb = 0; bb < bpp; bb++) {
buf[output + bb] = rowBuf[input + bb];
}
}
in.skipBytes(bpp * timeBins * (sizeX - x - w));
}
} // end else
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
// init preLoading
dataStore = null;
timeBins = 0;
preLoad = true;
}
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
in = new RandomAccessInputStream(id);
CoreMetadata m = core.get(0);
m.littleEndian = true;
in.order(isLittleEndian());
LOGGER.info("Reading header PQBin");
// Header
m.sizeX = in.readInt();
m.sizeY = in.readInt();
float pixResol = in.readFloat(); // Resolution of every Pixel in Image (in µm)
m.sizeT = in.readInt(); // Number of DataPoints per Decay
float timeResol = in.readFloat(); // resolution of time axis of every Decay (in ns)
timeBins = m.sizeT;
m.sizeZ = 1;
m.sizeC = 1;
m.dimensionOrder = "XYZCT";
m.pixelType = FormatTools.UINT32;
m.rgb = false;
m.imageCount = m.sizeT;
m.indexed = false;
m.falseColor = false;
m.metadataComplete = true;
m.moduloT.type = FormatTools.LIFETIME;
m.moduloT.parentType = FormatTools.SPECTRA;
m.moduloT.typeDescription = "TCSPC";
m.moduloT.start = 0;
m.moduloT.step = timeResol * 1000; // Convert to ps
m.moduloT.end = m.moduloT.step * (m.sizeT - 1);
m.moduloT.unit = "ps";
// disable pre-load mode for very large files
// threshold is set to smaller than the size of the largest test file currently available
if ( m.sizeX * m.sizeY * m.sizeT > (900 * 200 * 200)) {
preLoad = false;
}
else {
preLoad = true;
}
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this);
Length pRpf = FormatTools.getPhysicalSizeX((double)pixResol);
store.setPixelsPhysicalSizeX(pRpf, 0);
store.setPixelsPhysicalSizeY(pRpf, 0);
}
}