//
// ImarisHDFReader.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.in;
import java.io.IOException;
import java.util.Vector;
import loci.common.DataTools;
import loci.common.Location;
import loci.common.RandomAccessInputStream;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.MissingLibraryException;
import loci.formats.meta.MetadataStore;
import ome.xml.model.primitives.PositiveFloat;
import loci.formats.services.NetCDFService;
import loci.formats.services.NetCDFServiceImpl;
/**
* Reader for Bitplane Imaris 5.5 (HDF) files.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/ImarisHDFReader.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/ImarisHDFReader.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class ImarisHDFReader extends FormatReader {
// -- Constants --
public static final String HDF_MAGIC_STRING = "HDF";
private static final String[] DELIMITERS = {" ", "-", "."};
// -- Fields --
private double pixelSizeX, pixelSizeY, pixelSizeZ;
private double minX, minY, minZ, maxX, maxY, maxZ;
private int seriesCount;
private NetCDFService netcdf;
// channel parameters
private Vector<String> emWave, exWave, channelMin, channelMax;
private Vector<String> gain, pinhole, channelName, microscopyMode;
private Vector<double[]> colors;
private int lastChannel = 0;
// -- Constructor --
/** Constructs a new Imaris HDF reader. */
public ImarisHDFReader() {
super("Bitplane Imaris 5.5 (HDF)", "ims");
suffixSufficient = false;
domains = new String[] {FormatTools.UNKNOWN_DOMAIN};
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#getOptimalTileHeight() */
public int getOptimalTileHeight() {
FormatTools.assertId(currentId, true, 1);
return getSizeY();
}
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 8;
if (!FormatTools.validStream(stream, blockLen, false)) return false;
return stream.readString(blockLen).indexOf(HDF_MAGIC_STRING) >= 0;
}
/* @see loci.formats.IFormatReader#get8BitLookupTable() */
public byte[][] get8BitLookupTable() {
FormatTools.assertId(currentId, true, 1);
if (getPixelType() != FormatTools.UINT8 || !isIndexed()) return null;
if (lastChannel < 0 || lastChannel >= colors.size()) {
return null;
}
double[] color = colors.get(lastChannel);
byte[][] lut = new byte[3][256];
for (int c=0; c<lut.length; c++) {
double max = color[c] * 255;
for (int p=0; p<lut[c].length; p++) {
lut[c][p] = (byte) ((p / 255.0) * max);
}
}
return lut;
}
/* @see loci.formats.IFormatReaderget16BitLookupTable() */
public short[][] get16BitLookupTable() {
FormatTools.assertId(currentId, true, 1);
if (getPixelType() != FormatTools.UINT16 || !isIndexed()) return null;
if (lastChannel < 0 || lastChannel >= colors.size()) {
return null;
}
double[] color = colors.get(lastChannel);
short[][] lut = new short[3][65536];
for (int c=0; c<lut.length; c++) {
double max = color[c] * 65535;
for (int p=0; p<lut[c].length; p++) {
lut[c][p] = (short) ((p / 65535.0) * max);
}
}
return lut;
}
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*/
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);
lastChannel = getZCTCoords(no)[1];
// pixel data is stored in XYZ blocks
Object image = getImageData(no);
boolean big = !isLittleEndian();
int bpp = FormatTools.getBytesPerPixel(getPixelType());
for (int row=0; row<h; row++) {
int base = row * w * bpp;
if (image instanceof byte[][]) {
byte[][] data = (byte[][]) image;
byte[] rowData = data[row + y];
System.arraycopy(rowData, x, buf, row*w, w);
}
else if (image instanceof short[][]) {
short[][] data = (short[][]) image;
short[] rowData = data[row + y];
for (int i=0; i<w; i++) {
DataTools.unpackBytes(rowData[x + i], buf, base + 2*i, 2, big);
}
}
else if (image instanceof int[][]) {
int[][] data = (int[][]) image;
int[] rowData = data[row + y];
for (int i=0; i<w; i++) {
DataTools.unpackBytes(rowData[x + i], buf, base + i*4, 4, big);
}
}
else if (image instanceof float[][]) {
float[][] data = (float[][]) image;
float[] rowData = data[row + y];
for (int i=0; i<w; i++) {
int v = Float.floatToIntBits(rowData[x + i]);
DataTools.unpackBytes(v, buf, base + i*4, 4, big);
}
}
else if (image instanceof double[][]) {
double[][] data = (double[][]) image;
double[] rowData = data[row + y];
for (int i=0; i<w; i++) {
long v = Double.doubleToLongBits(rowData[x + i]);
DataTools.unpackBytes(v, buf, base + i * 8, 8, big);
}
}
}
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
seriesCount = 0;
pixelSizeX = pixelSizeY = pixelSizeZ = 0;
minX = minY = minZ = maxX = maxY = maxZ = 0;
if (netcdf != null) netcdf.close();
netcdf = null;
emWave = exWave = channelMin = channelMax = null;
gain = pinhole = channelName = microscopyMode = null;
colors = null;
lastChannel = 0;
}
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
try {
ServiceFactory factory = new ServiceFactory();
netcdf = factory.getInstance(NetCDFService.class);
netcdf.setFile(id);
}
catch (DependencyException e) {
throw new MissingLibraryException(NetCDFServiceImpl.NO_NETCDF_MSG, e);
}
pixelSizeX = pixelSizeY = pixelSizeZ = 1;
emWave = new Vector<String>();
exWave = new Vector<String>();
channelMin = new Vector<String>();
channelMax = new Vector<String>();
gain = new Vector<String>();
pinhole = new Vector<String>();
channelName = new Vector<String>();
microscopyMode = new Vector<String>();
colors = new Vector<double[]>();
seriesCount = 0;
// read all of the metadata key/value pairs
parseAttributes();
if (seriesCount > 1) {
CoreMetadata oldCore = core[0];
core = new CoreMetadata[seriesCount];
core[0] = oldCore;
for (int i=1; i<getSeriesCount(); i++) {
core[i] = new CoreMetadata();
}
for (int i=1; i<getSeriesCount(); i++) {
String groupPath =
"/DataSet/ResolutionLevel_" + i + "/TimePoint_0/Channel_0";
core[i].sizeX =
Integer.parseInt(netcdf.getAttributeValue(groupPath + "/ImageSizeX"));
core[i].sizeY =
Integer.parseInt(netcdf.getAttributeValue(groupPath + "/ImageSizeY"));
core[i].sizeZ =
Integer.parseInt(netcdf.getAttributeValue(groupPath + "/ImageSizeZ"));
core[i].imageCount = core[i].sizeZ * getSizeC() * getSizeT();
core[i].sizeC = getSizeC();
core[i].sizeT = getSizeT();
core[i].thumbnail = true;
}
}
core[0].imageCount = getSizeZ() * getSizeC() * getSizeT();
core[0].thumbnail = false;
core[0].dimensionOrder = "XYZCT";
// determine pixel type - this isn't stored in the metadata, so we need
// to check the pixels themselves
int type = -1;
Object pix = getImageData(0);
if (pix instanceof byte[][]) type = FormatTools.UINT8;
else if (pix instanceof short[][]) type = FormatTools.UINT16;
else if (pix instanceof int[][]) type = FormatTools.UINT32;
else if (pix instanceof float[][]) type = FormatTools.FLOAT;
else if (pix instanceof double[][]) type = FormatTools.DOUBLE;
else {
throw new FormatException("Unknown pixel type: " + pix);
}
for (int i=0; i<getSeriesCount(); i++) {
core[i].pixelType = type;
core[i].dimensionOrder = "XYZCT";
core[i].rgb = false;
core[i].thumbSizeX = 128;
core[i].thumbSizeY = 128;
core[i].orderCertain = true;
core[i].littleEndian = true;
core[i].interleaved = false;
core[i].indexed = colors.size() >= getSizeC();
}
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this);
String imageName = new Location(getCurrentFile()).getName();
for (int s=0; s<getSeriesCount(); s++) {
store.setImageName(imageName + " Resolution Level " + (s + 1), s);
MetadataTools.setDefaultCreationDate(store, id, s);
}
if (getMetadataOptions().getMetadataLevel() == MetadataLevel.MINIMUM) {
return;
}
int cIndex = 0;
for (int s=0; s<getSeriesCount(); s++) {
double px = pixelSizeX, py = pixelSizeY, pz = pixelSizeZ;
if (px == 1) px = (maxX - minX) / core[s].sizeX;
if (py == 1) py = (maxY - minY) / core[s].sizeY;
if (pz == 1) pz = (maxZ - minZ) / core[s].sizeZ;
store.setPixelsPhysicalSizeX(new PositiveFloat(px), s);
store.setPixelsPhysicalSizeY(new PositiveFloat(py), s);
store.setPixelsPhysicalSizeZ(new PositiveFloat(pz), s);
for (int i=0; i<core[s].sizeC; i++, cIndex++) {
Float gainValue = null;
Integer pinholeValue = null, emWaveValue = null, exWaveValue;
if (cIndex < gain.size()) {
try {
gainValue = new Float(gain.get(cIndex));
}
catch (NumberFormatException e) { }
}
if (cIndex < pinhole.size()) {
try {
pinholeValue = new Integer(pinhole.get(cIndex));
}
catch (NumberFormatException e) { }
}
if (cIndex < emWave.size()) {
try {
emWaveValue = new Integer(emWave.get(cIndex));
}
catch (NumberFormatException e) { }
}
if (cIndex < exWave.size()) {
try {
exWaveValue = new Integer(exWave.get(cIndex));
}
catch (NumberFormatException e) { }
}
// CHECK
/*
store.setLogicalChannelName((String) channelName.get(cIndex), s, i);
store.setDetectorSettingsGain(gainValue, s, i);
store.setLogicalChannelPinholeSize(pinholeValue, s, i);
store.setLogicalChannelMode((String) microscopyMode.get(cIndex), s, i);
store.setLogicalChannelEmWave(emWaveValue, s, i);
store.setLogicalChannelExWave(exWaveValue, s, i);
*/
Double minValue = null, maxValue = null;
if (cIndex < channelMin.size()) {
try {
minValue = new Double(channelMin.get(cIndex));
}
catch (NumberFormatException e) { }
}
if (cIndex < channelMax.size()) {
try {
maxValue = new Double(channelMax.get(cIndex));
}
catch (NumberFormatException e) { }
}
if (i < colors.size()) {
double[] color = colors.get(i);
int realColor = 0;
for (int cc=0; cc<color.length; cc++) {
realColor |= ((int) (color[cc] * 255) << (16 - cc * 8));
}
store.setChannelColor(realColor, s, i);
}
}
}
}
// -- Helper methods --
private Object getImageData(int no) throws FormatException {
int[] zct = getZCTCoords(no);
String path = "/DataSet/ResolutionLevel_" + series + "/TimePoint_" +
zct[2] + "/Channel_" + zct[1] + "/Data";
Object image = null;
int[] dimensions = new int[] {1, getSizeY(), getSizeX()};
int[] indices = new int[] {zct[0], 0, 0};
try {
image = netcdf.getArray(path, indices, dimensions);
}
catch (ServiceException e) {
throw new FormatException(e);
}
return image;
}
private void parseAttributes() {
Vector<String> attributes = netcdf.getAttributeList();
for (String attr : attributes) {
String name = attr.substring(attr.lastIndexOf("/") + 1);
String value = netcdf.getAttributeValue(attr);
if (value == null) continue;
value = value.trim();
if (name.equals("X")) {
core[0].sizeX = Integer.parseInt(value);
}
else if (name.equals("Y")) {
core[0].sizeY = Integer.parseInt(value);
}
else if (name.equals("Z")) {
core[0].sizeZ = Integer.parseInt(value);
}
else if (name.equals("FileTimePoints")) {
core[0].sizeT = Integer.parseInt(value);
}
else if (name.equals("RecordingEntrySampleSpacing")) {
pixelSizeX = Double.parseDouble(value);
}
else if (name.equals("RecordingEntryLineSpacing")) {
pixelSizeY = Double.parseDouble(value);
}
else if (name.equals("RecordingEntryPlaneSpacing")) {
pixelSizeZ = Double.parseDouble(value);
}
else if (name.equals("ExtMax0")) maxX = Double.parseDouble(value);
else if (name.equals("ExtMax1")) maxY = Double.parseDouble(value);
else if (name.equals("ExtMax2")) maxZ = Double.parseDouble(value);
else if (name.equals("ExtMin0")) minX = Double.parseDouble(value);
else if (name.equals("ExtMin1")) minY = Double.parseDouble(value);
else if (name.equals("ExtMin2")) minZ = Double.parseDouble(value);
if (attr.startsWith("/DataSet/ResolutionLevel_")) {
int slash = attr.indexOf("/", 25);
int n = Integer.parseInt(attr.substring(25, slash == -1 ?
attr.length() : slash));
if (n == seriesCount) seriesCount++;
}
if (attr.startsWith("/DataSetInfo/Channel_")) {
String originalValue = value;
for (String d : DELIMITERS) {
if (value.indexOf(d) != -1) {
value = value.substring(value.indexOf(d) + 1);
}
}
int underscore = attr.indexOf("_") + 1;
int cIndex = Integer.parseInt(attr.substring(underscore,
attr.indexOf("/", underscore)));
if (cIndex == getSizeC()) core[0].sizeC++;
if (name.equals("Gain")) gain.add(value);
else if (name.equals("LSMEmissionWavelength")) emWave.add(value);
else if (name.equals("LSMExcitationWavelength")) exWave.add(value);
else if (name.equals("Max")) channelMax.add(value);
else if (name.equals("Min")) channelMin.add(value);
else if (name.equals("Pinhole")) pinhole.add(value);
else if (name.equals("Name")) channelName.add(value);
else if (name.equals("MicroscopyMode")) microscopyMode.add(value);
else if (name.equals("Color")) {
double[] color = new double[3];
String[] intensity = originalValue.split(" ");
for (int i=0; i<intensity.length; i++) {
color[i] = Double.parseDouble(intensity[i]);
}
colors.add(color);
}
}
if (value != null) addGlobalMeta(name, value);
}
}
}