/*
* #%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.EOFException;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import loci.common.Constants;
import loci.common.DataTools;
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.units.quantity.Time;
import ome.units.quantity.Length;
import ome.units.UNITS;
/**
* SlidebookReader is the file format reader for 3I Slidebook files.
* The strategies employed by this reader are highly suboptimal, as we
* have very little information on the Slidebook format.
*
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public class SlidebookReader extends FormatReader {
// -- Constants --
public static final int SLD_MAGIC_BYTES_1_0 = 0x006c;
public static final int SLD_MAGIC_BYTES_1_1 = 0x0100;
public static final int SLD_MAGIC_BYTES_1_2 = 0x0200;
public static final int SLD_MAGIC_BYTES_2_0 = 0x01f5;
public static final int SLD_MAGIC_BYTES_2_1 = 0x0102;
public static final long SLD_MAGIC_BYTES_3 = 0xf6010101L;
// -- Fields --
private List<Long> metadataOffsets;
private List<Long> pixelOffsets;
private List<Long> pixelLengths;
private List<Double> ndFilters;
private Map<Integer, String> imageDescriptions;
private long[][] planeOffset;
private boolean adjust = true;
private boolean isSpool;
private Map<Integer, Integer> metadataInPlanes;
// -- Constructor --
/** Constructs a new Slidebook reader. */
public SlidebookReader() {
super("Olympus Slidebook", new String[] {"sld", "spl"});
domains = new String[] {FormatTools.LM_DOMAIN};
suffixSufficient = false;
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 8;
stream.seek(4);
boolean littleEndian = stream.readString(2).equals("II");
if (!FormatTools.validStream(stream, blockLen, littleEndian)) return false;
int magicBytes1 = stream.readShort();
int magicBytes2 = stream.readShort();
return ((magicBytes2 & 0xff00) == SLD_MAGIC_BYTES_1_1 ||
(magicBytes2 & 0xff00) == SLD_MAGIC_BYTES_1_2) &&
(magicBytes1 == SLD_MAGIC_BYTES_1_0 ||
magicBytes1 == SLD_MAGIC_BYTES_2_0);
}
/**
* @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);
long offset = planeOffset[getSeries()][no];
in.seek(offset);
int[] zct = getZCTCoords(no);
// if this is a spool file, there may be an extra metadata block here
if (isSpool) {
long len = pixelLengths.get(getSeries());
long planeSize = FormatTools.getPlaneSize(this);
long diff = len - getImageCount() * planeSize;
in.seek(in.getFilePointer() - diff);
Integer[] keys = metadataInPlanes.keySet().toArray(new Integer[0]);
Arrays.sort(keys);
for (int key : keys) {
if (key < no) {
in.skipBytes(256);
}
}
// check next 8 blocks of 256 bytes, just in case
long beginning = in.getFilePointer();
for (int i=0; i<8; i++) {
if (in.getFilePointer() + 4 >= in.length()) {
in.seek(beginning);
break;
}
in.order(false);
long magicBytes = (long) in.readInt() & 0xffffffffL;
in.order(isLittleEndian());
if (magicBytes == SLD_MAGIC_BYTES_3 &&
!metadataInPlanes.containsValue(no)) {
metadataInPlanes.put(no, 0);
in.skipBytes(252);
break;
}
else if (i == 7) {
in.seek(beginning);
}
else {
in.skipBytes(252);
}
}
}
readPlane(in, x, y, w, h, buf);
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
metadataOffsets = pixelOffsets = pixelLengths = null;
ndFilters = null;
isSpool = false;
metadataInPlanes = null;
adjust = true;
planeOffset = null;
imageDescriptions = null;
}
}
// -- 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);
isSpool = checkSuffix(id, "spl");
if (isSpool) {
metadataInPlanes = new HashMap<Integer, Integer>();
}
LOGGER.info("Finding offsets to pixel data");
// Slidebook files appear to be comprised of four types of blocks:
// variable length pixel data blocks, 512 byte metadata blocks,
// 128 byte metadata blocks, and variable length metadata blocks.
//
// Fixed-length metadata blocks begin with a 2 byte identifier,
// e.g. 'i' or 'h'.
// Following this are two unknown bytes (usually 256), then a 2 byte
// endianness identifier - II or MM, for little or big endian, respectively.
// Presumably these blocks contain useful information, but for the most
// part we aren't sure what it is or how to extract it.
//
// Variable length metadata blocks begin with 0xffff and are
// (as far as I know) always between two fixed-length metadata blocks.
// These appear to be a relatively new addition to the format - they are
// only present in files received on/after March 30, 2008.
//
// Each pixel data block corresponds to one series.
// The first 'i' metadata block after each pixel data block contains
// the width and height of the planes in that block - this can (and does)
// vary between blocks.
//
// Z, C, and T sizes are computed heuristically based on the number of
// metadata blocks of a specific type.
in.skipBytes(4);
core.get(0).littleEndian = in.read() == 0x49;
in.order(isLittleEndian());
metadataOffsets = new ArrayList<Long>();
pixelOffsets = new ArrayList<Long>();
pixelLengths = new ArrayList<Long>();
ndFilters = new ArrayList<Double>();
imageDescriptions = new HashMap<Integer, String>();
in.seek(0);
// gather offsets to metadata and pixel data blocks
while (in.getFilePointer() < in.length() - 8) {
LOGGER.debug("Looking for block at {}", in.getFilePointer());
in.skipBytes(4);
int checkOne = in.read();
int checkTwo = in.read();
if ((checkOne == 'I' && checkTwo == 'I') ||
(checkOne == 'M' && checkTwo == 'M'))
{
LOGGER.debug("Found metadata offset: {}", (in.getFilePointer() - 6));
metadataOffsets.add(in.getFilePointer() - 6);
in.skipBytes(in.readShort() - 8);
}
else if (checkOne == -1 && checkTwo == -1) {
boolean foundBlock = false;
byte[] block = new byte[8192];
in.read(block);
while (!foundBlock) {
for (int i=0; i<block.length-2; i++) {
if ((block[i] == 'M' && block[i + 1] == 'M') ||
(block[i] == 'I' && block[i + 1] == 'I'))
{
foundBlock = true;
in.seek(in.getFilePointer() - block.length + i - 2);
LOGGER.debug("Found metadata offset: {}",
(in.getFilePointer() - 2));
metadataOffsets.add(in.getFilePointer() - 2);
in.skipBytes(in.readShort() - 5);
break;
}
}
if (!foundBlock) {
block[0] = block[block.length - 2];
block[1] = block[block.length - 1];
in.read(block, 2, block.length - 2);
}
}
}
else {
String s = null;
long fp = in.getFilePointer() - 6;
in.seek(fp);
int len = in.read();
if (len > 0 && len <= 32) {
s = in.readString(len);
}
if (s != null && s.indexOf("Annotation") != -1) {
if (s.equals("CTimelapseAnnotation")) {
in.skipBytes(41);
if (in.read() == 0) in.skipBytes(10);
else in.seek(in.getFilePointer() - 1);
}
else if (s.equals("CIntensityBarAnnotation")) {
in.skipBytes(56);
int n = in.read();
while (n == 0 || n < 6 || n > 0x80) n = in.read();
in.seek(in.getFilePointer() - 1);
}
else if (s.equals("CCubeAnnotation")) {
in.skipBytes(66);
int n = in.read();
if (n != 0) in.seek(in.getFilePointer() - 1);
}
else if (s.equals("CScaleBarAnnotation")) {
in.skipBytes(38);
int extra = in.read();
if (extra <= 16) in.skipBytes(3 + extra);
else in.skipBytes(2);
}
}
else if (s != null && s.indexOf("Decon") != -1) {
in.seek(fp);
while (in.read() != ']');
}
else {
if ((fp % 2) == 1) fp -= 2;
in.seek(fp);
// make sure there isn't another block nearby
String checkString = in.readString(64);
if (checkString.indexOf("II") != -1 ||
checkString.indexOf("MM") != -1)
{
int index = checkString.indexOf("II");
if (index == -1) index = checkString.indexOf("MM");
in.seek(fp + index - 4);
continue;
}
else in.seek(fp);
LOGGER.debug("Found pixel offset at {}", fp);
pixelOffsets.add(fp);
try {
byte[] buf = new byte[8192];
boolean found = false;
int n = in.read(buf);
while (!found && in.getFilePointer() < in.length()) {
for (int i=0; i<n-6; i++) {
if ((buf[i + 4] == 'I' && buf[i + 5] == 'I') ||
(buf[i + 4] == 'M' && buf[i + 5] == 'M'))
{
if (((buf[i] == 'h' || buf[i] == 'i') && buf[i + 1] == 0) ||
(buf[i] == 0 && (buf[i + 1] == 'h' || buf[i + 1] == 'i')))
{
found = true;
in.seek(in.getFilePointer() - n + i - 20);
if (buf[i] == 'i' || buf[i + 1] == 'i') {
pixelOffsets.remove(pixelOffsets.size() - 1);
}
break;
}
else if (((buf[i] == 'j' || buf[i] == 'k' || buf[i] == 'n') &&
buf[i + 1] == 0) || (buf[i] == 0 && (buf[i + 1] == 'j' ||
buf[i + 1] == 'k' || buf[i + 1] == 'n')) ||
(buf[i] == 'o' && buf[i + 1] == 'n'))
{
found = true;
pixelOffsets.remove(pixelOffsets.size() - 1);
in.seek(in.getFilePointer() - n + i - 20);
break;
}
}
}
if (!found) {
byte[] tmp = buf;
buf = new byte[8192];
System.arraycopy(tmp, tmp.length - 20, buf, 0, 20);
n = in.read(buf, 20, buf.length - 20);
}
}
if (in.getFilePointer() <= in.length()) {
if (pixelOffsets.size() > pixelLengths.size()) {
long length = in.getFilePointer() - fp;
if (((length / 2) % 2) == 1) {
pixelOffsets.set(pixelOffsets.size() - 1, fp + 2);
length -= 2;
}
if (length >= 1024) {
pixelLengths.add(length);
}
else pixelOffsets.remove(pixelOffsets.size() - 1);
}
}
else pixelOffsets.remove(pixelOffsets.size() - 1);
}
catch (EOFException e) {
pixelOffsets.remove(pixelOffsets.size() - 1);
}
}
}
}
final List<Long> orderedSeries = new ArrayList<Long>();
final ListMultimap<Long, Integer> uniqueSeries =
ArrayListMultimap.create();
for (int i=0; i<pixelOffsets.size(); i++) {
long length = pixelLengths.get(i).longValue();
long offset = pixelOffsets.get(i).longValue();
int padding = isSpool ? 0 : 7;
if (length + offset + padding > in.length()) {
pixelOffsets.remove(i);
pixelLengths.remove(i);
i--;
}
else {
final List<Integer> v = uniqueSeries.get(length);
if (v.isEmpty()) {
orderedSeries.add(length);
}
uniqueSeries.put(length, i);
}
}
if (pixelOffsets.size() > 1) {
boolean little = isLittleEndian();
int seriesCount = 0;
for (final Long key : orderedSeries) {
final List<Integer> pixelIndexes = uniqueSeries.get(key);
int nBlocks = pixelIndexes.size();
if (nBlocks == 0) {
nBlocks++;
}
seriesCount += nBlocks;
}
core.clear();
for (int i=0; i<seriesCount; i++) {
CoreMetadata ms = new CoreMetadata();
core.add(ms);
ms.littleEndian = little;
}
}
LOGGER.info("Determining dimensions");
// determine total number of pixel bytes
final Map<Integer, Float> pixelSize = new HashMap<Integer, Float>();
final Map<Integer, String> objectives = new HashMap<Integer, String>();
final Map<Integer, Integer> magnifications =
new HashMap<Integer, Integer>();
final List<Double> pixelSizeZ = new ArrayList<Double>();
final List<Integer> exposureTimes = new ArrayList<Integer>();
long pixelBytes = 0;
for (int i=0; i<pixelLengths.size(); i++) {
pixelBytes += pixelLengths.get(i).longValue();
}
String[] imageNames = new String[getSeriesCount()];
final List<String> channelNames = new ArrayList<String>();
int nextName = 0;
int[] sizeX = new int[pixelOffsets.size()];
int[] sizeY = new int[pixelOffsets.size()];
int[] sizeZ = new int[pixelOffsets.size()];
int[] sizeC = new int[pixelOffsets.size()];
int[] divValues = new int[pixelOffsets.size()];
// try to find the width and height
int iCount = 0;
int hCount = 0;
int uCount = 0;
int prevSeries = -1;
int prevSeriesU = -1;
int nextChannel = 0;
for (int i=0; i<metadataOffsets.size(); i++) {
long off = metadataOffsets.get(i).longValue();
if (isSpool && off == 0) {
off = 276;
}
in.seek(off);
long next = i == metadataOffsets.size() - 1 ? in.length() :
metadataOffsets.get(i + 1).longValue();
int totalBlocks = (int) ((next - off) / 128);
// if there are more than 100 blocks, we probably found a pixel block
// by accident (but we'll check the first block anyway)
//if (totalBlocks > 100) totalBlocks = 100;
for (int q=0; q<totalBlocks; q++) {
if (withinPixels(off + q * 128)) {
continue;
}
in.seek(off + (long) q * 128);
char n = (char) in.readShort();
while (n == 0 && in.getFilePointer() < off + (q + 1) * 128) {
n = (char) in.readShort();
}
if (in.getFilePointer() >= in.length() - 2) break;
if (n == 'i') {
iCount++;
in.skipBytes(70);
int expTime = in.readInt();
if (expTime > 0) {
exposureTimes.add(expTime);
}
in.skipBytes(20);
final Double size = (double) in.readFloat();
if (isGreaterThanEpsilon(size)) {
pixelSizeZ.add(size);
}
else {
pixelSizeZ.add(null);
}
in.seek(in.getFilePointer() - 20);
for (int j=0; j<pixelOffsets.size(); j++) {
long end = j == pixelOffsets.size() - 1 ? in.length() :
pixelOffsets.get(j + 1).longValue();
if (in.getFilePointer() < end) {
if (sizeX[j] == 0) {
int x = in.readShort();
int y = in.readShort();
if (x != 0 && y != 0) {
sizeX[j] = x;
sizeY[j] = y;
int checkX = in.readShort();
int checkY = in.readShort();
int div = in.readShort();
if (checkX == checkY) {
divValues[j] = div;
sizeX[j] /= (div == 0 ? 1 : div);
div = in.readShort();
sizeY[j] /= (div == 0 ? 1 : div);
}
}
else in.skipBytes(8);
}
if (prevSeries != j) {
iCount = 1;
}
prevSeries = j;
sizeC[j] = iCount;
break;
}
}
}
else if (n == 'u') {
uCount++;
for (int j=0; j<getSeriesCount(); j++) {
long end = j == getSeriesCount() - 1 ? in.length() :
pixelOffsets.get(j + 1).longValue();
if (in.getFilePointer() < end) {
if (prevSeriesU != j) {
uCount = 1;
}
prevSeriesU = j;
sizeZ[j] = uCount;
break;
}
}
}
else if (n == 'h') hCount++;
else if (n == 'j') {
in.skipBytes(2);
String check = in.readString(2);
if (check.equals("II") || check.equals("MM")) {
long pointer = in.getFilePointer();
// this block should contain an image name
in.skipBytes(10);
if (nextName < imageNames.length) {
String name = readCString().trim();
if (name.length() > 0) {
imageNames[nextName++] = name;
}
}
long fp = in.getFilePointer();
if ((in.getFilePointer() % 2) == 1) in.skipBytes(1);
while (in.readShort() == 0);
if (in.readShort() == 0) {
in.skipBytes(4);
}
else {
in.skipBytes(16);
}
long diff = in.getFilePointer() - fp;
if (diff > 123 && (fp % 2) == 0 && diff != 142 && diff != 143 &&
diff != 130)
{
in.seek(fp + 123);
}
int x = in.readInt();
int y = in.readInt();
if (x > 0x8000 || y > 0x8000) {
in.seek(in.getFilePointer() - 7);
x = in.readInt();
y = in.readInt();
}
else if (x == 0 || y == 0) {
in.seek(in.getFilePointer() - 27);
x = in.readInt();
y = in.readInt();
}
int div = in.readShort();
x /= (div == 0 || div > 0x100 ? 1 : div);
div = in.readShort();
y /= (div == 0 || div > 0x100 ? 1 : div);
if (x > 0x10000 || y > 0x10000) {
in.seek(in.getFilePointer() - 11);
x = in.readInt();
y = in.readInt();
div = in.readShort();
x /= (div == 0 ? 1 : div);
div = in.readShort();
y /= (div == 0 ? 1 : div);
if (x > 0x10000 || y > 0x10000) {
in.skipBytes(2);
x = in.readInt();
y = in.readInt();
div = in.readShort();
x /= (div == 0 ? 1 : div);
div = in.readShort();
y /= (div == 0 ? 1 : div);
}
}
if (nextName >= 1 && x > 16 && (x < sizeX[nextName - 1] ||
sizeX[nextName - 1] == 0) && y > 16 &&
(y < sizeY[nextName - 1] || sizeY[nextName - 1] == 0))
{
sizeX[nextName - 1] = x;
sizeY[nextName - 1] = y;
adjust = false;
}
in.seek(pointer + 214);
int validBits = in.readShort();
if (nextName >= 1 && core.get(nextName - 1).bitsPerPixel == 0 &&
validBits <= 16 && validBits > 0)
{
core.get(nextName - 1).bitsPerPixel = validBits;
}
}
}
else if (n == 'm') {
// this block should contain a channel name
if (in.getFilePointer() > pixelOffsets.get(0).longValue() || isSpool)
{
in.skipBytes(14);
String name = readCString().trim();
if (name.length() > 1) {
channelNames.add(name);
}
}
}
else if (n == 'd') {
// objective info and pixel size X/Y
in.skipBytes(6);
long fp = in.getFilePointer();
while (in.read() == 0);
in.seek(in.getFilePointer() - 1);
long nSkipped = in.getFilePointer() - fp;
if (nSkipped < 8) {
in.skipBytes((int) (8 - nSkipped));
}
String objective = readCString().trim();
in.seek(fp + 144);
float pixSize = in.readFloat();
int magnification = in.readShort();
int mult = 1;
if (pixelSize.size() < divValues.length) {
mult = divValues[pixelSize.size()];
}
float v = pixSize * mult;
if (isGreaterThanEpsilon(v)) {
pixelSize.put(nextName - 1, v);
objectives.put(nextName - 1, objective);
magnifications.put(nextName - 1, magnification);
}
}
else if (n == 'e') {
in.skipBytes(174);
ndFilters.add((double) in.readFloat());
in.skipBytes(40);
if (nextName >= 0 && nextName < getSeriesCount()) {
setSeries(nextName);
addSeriesMetaList("channel intensification", in.readShort());
}
}
else if (n == 'k') {
in.skipBytes(14);
if (nextName > 0) setSeries(nextName - 1);
addSeriesMeta("Mag. changer", readCString());
}
else if (n == 'n') {
long fp1 = in.getFilePointer();
in.seek(in.getFilePointer() - 3);
while (in.read() != 0) {
in.seek(in.getFilePointer() - 2);
}
long fp2 = in.getFilePointer();
int len = in.read() - 1;
int currentSeries = 0;
for (int j=0; j<pixelOffsets.size(); j++) {
long end = j == pixelOffsets.size() - 1 ? in.length() :
pixelOffsets.get(j + 1).longValue();
if (in.getFilePointer() < end) {
currentSeries = j;
break;
}
}
if (len > 0 && fp1 - fp2 != 2) {
if (fp2 < fp1) {
in.seek(in.getFilePointer() - 1);
String descr = readCString();
descr = descr.substring(0, descr.length() - 2);
if (!descr.endsWith("Annotatio")) {
imageDescriptions.put(currentSeries, descr.trim());
}
}
else {
imageDescriptions.put(currentSeries, in.readString(len).trim());
}
}
}
else if (isSpool) {
// spool files don't necessarily have block identifiers
for (int j=0; j<pixelOffsets.size(); j++) {
long end = j == pixelOffsets.size() - 1 ? in.length() :
pixelOffsets.get(j + 1).longValue();
if (in.getFilePointer() < end) {
in.skipBytes(14);
int check = in.readShort();
int x = in.readShort();
int y = in.readShort();
if (check == 0 && x > 16 && y > 16) {
sizeX[j] = x;
sizeY[j] = y;
}
adjust = false;
break;
}
}
}
}
}
// TODO: extend the name matching to include "* Timepoint *"
String currentName = imageNames[0];
ArrayList<CoreMetadata> realCore = new ArrayList<CoreMetadata>();
int t = 1;
boolean noFlattening =
currentName != null && currentName.equals("Untitled");
for (int i=1; i<getSeriesCount(); i++) {
if (imageNames[i] == null || !imageNames[i].equals(currentName) ||
noFlattening ||
(i == 1 && (sizeX[i - 1] != sizeX[i] || sizeY[i - 1] != sizeY[i] ||
sizeC[i - 1] != sizeC[i] || sizeZ[i - 1] != sizeZ[i])))
{
currentName = imageNames[i];
CoreMetadata nextCore = core.get(i - 1);
nextCore.sizeT = t;
realCore.add(nextCore);
if (t == 1) {
noFlattening = true;
}
t = 1;
if (i == 1) {
noFlattening = true;
}
}
else {
t++;
}
}
core.get(getSeriesCount() - 1).sizeT = t;
realCore.add(core.get(getSeriesCount() - 1));
boolean flattened = false;
if (core.size() != realCore.size() && !noFlattening) {
flattened = true;
core = realCore;
orderedSeries.clear();
uniqueSeries.clear();
int nextIndex = 0;
for (int i=0; i<core.size(); i++) {
long thisSeries = (long) i;
orderedSeries.add(thisSeries);
uniqueSeries.put(thisSeries, nextIndex);
long length = pixelLengths.get(nextIndex);
length *= core.get(i).sizeT;
pixelLengths.set(i, length);
nextIndex += core.get(i).sizeT;
}
}
planeOffset = new long[getSeriesCount()][];
boolean divByTwo = false;
boolean divZByTwo = false;
int nextPixelIndex = 0;
int nextBlock = 0;
int nextOffsetIndex = 0;
for (int i=0; i<getSeriesCount(); i++) {
setSeries(i);
CoreMetadata ms = core.get(i);
List<Integer> pixelIndexes =
uniqueSeries.get(orderedSeries.get(nextPixelIndex));
int nBlocks = pixelIndexes.size();
if (nextBlock >= nBlocks) {
nextPixelIndex++;
nextBlock = 0;
pixelIndexes = uniqueSeries.get(orderedSeries.get(nextPixelIndex));
nBlocks = pixelIndexes.size();
}
else {
nextBlock++;
}
int index =
pixelIndexes.size() == getSeriesCount() ? pixelIndexes.get(0) : i;
long pixels = pixelLengths.get(index).longValue() / 2;
boolean x = true;
ms.sizeX = sizeX[index];
ms.sizeY = sizeY[index];
ms.sizeC = sizeC[index];
ms.sizeZ = sizeZ[index];
if (getSizeC() > 64) {
// dimensions are probably incorrect
ms.sizeC = 1;
ms.sizeZ = 1;
ms.sizeX /= 2;
ms.sizeY /= 2;
}
boolean isMontage = false;
if (i > 1 && ((imageNames[i] != null &&
imageNames[i].startsWith("Montage")) || getSizeC() >= 32))
{
ms.sizeC = core.get(1).sizeC;
ms.sizeZ = core.get(1).sizeZ;
isMontage = true;
}
boolean cGreater = ms.sizeC > ms.sizeZ;
if (isSpool) {
if (ms.sizeC == 0) {
ms.sizeC = channelNames.size();
}
}
if (ms.sizeZ % nBlocks == 0 && nBlocks != getSizeC()) {
int z = ms.sizeZ / nBlocks;
if (z <= nBlocks) {
ms.sizeZ = z;
}
}
if (divByTwo) ms.sizeX /= 2;
if (divZByTwo && ms.sizeC > 1) {
ms.sizeZ = (int) ((pixels / (ms.sizeX * ms.sizeY)) / 2);
ms.sizeC = 2;
}
if (getSizeC() == 0) ms.sizeC = 1;
if (getSizeZ() == 0) ms.sizeZ = 1;
long plane = pixels / (getSizeC() * getSizeZ());
if (getSizeT() > 0) {
plane /= getSizeT();
}
if (getSizeX() * getSizeY() == pixels) {
if (getSizeC() == 2 && (getSizeX() % 2 == 0) && (getSizeY() % 2 == 0)) {
if (getSizeC() != getSizeZ()) {
ms.sizeX /= 2;
divByTwo = true;
}
else {
divZByTwo = true;
ms.sizeC = 1;
}
}
else {
ms.sizeC = 1;
}
ms.sizeZ = 1;
}
else if (getSizeX() * getSizeY() * getSizeZ() == pixels) {
if (getSizeC() == 2 && getSizeC() != getSizeZ() &&
(getSizeX() % 2 == 0) && (getSizeY() % 2 == 0) && (i == 0 ||
core.get(i - 1).sizeC > 1))
{
ms.sizeX /= 2;
divByTwo = true;
}
else {
ms.sizeC = 1;
ms.sizeZ = (int) (pixels / (getSizeX() * getSizeY()));
}
}
else if (getSizeX() * getSizeY() * getSizeC() == pixels) {
ms.sizeC = (int) (pixels / (getSizeX() * getSizeY()));
ms.sizeZ = 1;
}
else if ((getSizeX() / 2) * (getSizeY() / 2) * getSizeZ() == pixels) {
ms.sizeX /= 2;
ms.sizeY /= 2;
}
else if ((getSizeX() / 2) * (getSizeY() / 2) * getSizeC() *
getSizeZ() * getSizeT() == pixels)
{
ms.sizeX /= 2;
ms.sizeY /= 2;
}
else {
boolean validSizes = true;
try {
DataTools.safeMultiply32(getSizeX(), getSizeY());
}
catch (IllegalArgumentException e) {
validSizes = false;
}
if (getSizeX() == 0 || getSizeY() == 0 || !validSizes) {
ms.sizeX = sizeX[index] / 256;
ms.sizeY = sizeY[index] / 256;
}
long p = pixels / (getSizeX() * getSizeY());
if (pixels == p * getSizeX() * getSizeY()) {
if (p != getSizeC() * getSizeZ()) {
if (getSizeC() > 1 && core.get(i).sizeZ >= (p / (getSizeC() - 1)) &&
p >= getSizeC() - 1 && p > 2)
{
core.get(i).sizeC--;
core.get(i).sizeZ = (int) (p / getSizeC());
}
else if (p % getSizeC() != 0) {
core.get(i).sizeC = 1;
core.get(i).sizeZ = (int) p;
}
else if (ms.sizeZ == p + 1) {
ms.sizeC = 1;
ms.sizeZ = 1;
ms.sizeT = (int) p;
}
else if (getSizeC() > 1 &&
ms.sizeZ == (p / (getSizeC() - 1)) + 1)
{
ms.sizeC--;
ms.sizeZ = 1;
ms.sizeT = (int) (p / getSizeC());
}
else {
if (p > getSizeZ() && (p / getSizeZ() < getSizeZ() - 1)) {
ms.sizeT = (int) (p / getSizeC());
ms.sizeZ = 1;
}
else if (pixels % getSizeX() == 0 && pixels % getSizeY() == 0) {
while (getSizeX() * getSizeY() > plane) {
ms.sizeX /= 2;
ms.sizeY /= 2;
}
int originalX = getSizeX();
while (getSizeX() * getSizeY() < plane) {
ms.sizeX += originalX;
ms.sizeY = (int) (plane / getSizeX());
}
int newX = getSizeX() + originalX;
if (newX * (plane / newX) == plane && !flattened) {
ms.sizeX = newX;
ms.sizeY = (int) (plane / newX);
}
}
else if (!adjust) {
ms.sizeZ = (int) (p / getSizeC());
}
else if (isMontage) {
pixels /= getSizeC();
while (pixels != getSizeX() * getSizeY() ||
(getSizeY() / getSizeX() > 2))
{
ms.sizeX += 16;
ms.sizeY = (int) (pixels / getSizeX());
}
}
}
}
}
else if (isSpool) {
ms.sizeZ = (int) (p / getSizeC());
}
else if (p == 0) {
adjust = true;
if (getSizeC() > 1) {
if (getSizeC() == 3) {
ms.sizeC = 2;
}
else {
ms.sizeC = 1;
}
}
}
else {
if (ms.sizeC > 1 && p <= ms.sizeC) {
int z = getSizeZ();
ms.sizeZ = 1;
ms.sizeC = (int) p;
ms.sizeT = 1;
if (isMontage && pixels == getSizeX() * (pixels / getSizeX())) {
pixels /= getSizeC();
while (pixels != getSizeX() * getSizeY()) {
ms.sizeX -= 16;
ms.sizeY = (int) (pixels / getSizeX());
}
}
else if (!isMontage) {
ms.sizeZ = z;
adjust = true;
}
}
else if (isMontage) {
pixels /= (getSizeC() * getSizeZ());
int originalX = getSizeX();
int originalY = getSizeY();
boolean xGreater = getSizeX() > getSizeY();
while (getSizeX() * getSizeY() != 0 && (
pixels % (getSizeX() * getSizeY()) != 0 ||
((double) getSizeY() / getSizeX() > 2)))
{
ms.sizeX += originalX;
ms.sizeY = (int) (pixels / getSizeX());
if (!xGreater && getSizeX() >= getSizeY()) {
break;
}
}
if (getSizeX() * getSizeY() == 0) {
if (pixels != getSizeX() * getSizeY()) {
pixels *= getSizeC() * getSizeZ();
ms.sizeX = originalX;
ms.sizeY = originalY;
isMontage = false;
}
}
if (pixels % (originalX - (originalX / 4)) == 0) {
int newX = originalX - (originalX / 4);
int newY = (int) (pixels / newX);
if (newX * newY == pixels) {
ms.sizeX = newX;
ms.sizeY = newY;
isMontage = true;
adjust = false;
}
}
}
else if (p != getSizeZ() * getSizeC()) {
if (pixels % getSizeX() == 0 && pixels % getSizeY() == 0) {
while (getSizeX() * getSizeY() > plane) {
ms.sizeX /= 2;
ms.sizeY /= 2;
}
}
else {
ms.sizeZ = 1;
ms.sizeC = 1;
ms.sizeT = (int) p;
}
}
}
}
if (getSizeC() == 0) {
ms.sizeC = 1;
}
if (getSizeZ() == 0) {
ms.sizeZ = 1;
}
int div = getSizeC() * getSizeZ();
if (getSizeT() > 0) {
div *= getSizeT();
}
if (div > 1) {
plane = pixels / div;
}
long diff = 2 * (pixels - (getSizeX() * getSizeY() * div));
if ((pixelLengths.get(index).longValue() % 2) == 1) {
diff++;
}
if (Math.abs(diff) > plane / 2) {
diff = 0;
}
if (adjust && diff == 0) {
double ratio = (double) getSizeX() / getSizeY();
boolean widthGreater = getSizeX() > getSizeY();
while (getSizeX() * getSizeY() > plane) {
if (x) ms.sizeX /= 2;
else ms.sizeY /= 2;
x = !x;
}
if (getSizeX() * getSizeY() != plane) {
while (ratio - ((double) getSizeX() / getSizeY()) >= 0.01) {
boolean first = true;
while (first || getSizeX() * getSizeY() < plane ||
(getSizeX() < getSizeY() && widthGreater))
{
if (first) {
first = false;
}
ms.sizeX++;
ms.sizeY = (int) (plane / getSizeX());
}
}
}
}
int nPlanes = getSizeZ() * getSizeC();
ms.sizeT = (int) (pixels / (getSizeX() * getSizeY() * nPlanes));
while (getSizeX() * getSizeY() * nPlanes * getSizeT() > pixels) {
ms.sizeT--;
}
if (getSizeT() == 0) ms.sizeT = 1;
if (cGreater && getSizeC() == 1 && getSizeZ() > 1) {
ms.sizeC = getSizeZ();
ms.sizeZ = 1;
}
ms.imageCount = nPlanes * getSizeT();
ms.pixelType = FormatTools.UINT16;
ms.dimensionOrder = nBlocks > 1 ? "XYZCT" : "XYZTC";
ms.indexed = false;
ms.falseColor = false;
ms.metadataComplete = true;
planeOffset[i] = new long[getImageCount()];
int nextImage = 0;
Integer pixelIndex = i;
long offset = pixelOffsets.get(pixelIndex);
int planeSize = getSizeX() * getSizeY() * 2;
if (diff < planeSize) {
offset += diff;
}
else {
offset += (diff % planeSize);
}
long length = pixelLengths.get(pixelIndex);
int planes = (int) (length / planeSize);
if (planes > ms.imageCount) {
planes = ms.imageCount;
}
for (int p=0; p<planes; p++, nextImage++) {
int[] zct = getZCTCoords(p);
if (flattened && zct[0] == 0 && zct[1] == 0) {
offset = pixelOffsets.get(nextOffsetIndex++);
if (zct[2] > 0 && planeOffset[i][nextImage - 1] % 2 != offset % 2 &&
(offset - planeOffset[i][nextImage - 1] > 3 * getSizeX() * getSizeY()) &&
diff == 0)
{
diff = 31;
}
if (diff < planeSize) {
offset += diff;
}
else {
offset += (diff % planeSize);
}
planeOffset[i][nextImage] = offset;
}
else if (flattened && zct[0] == 0) {
int idx = getIndex(0, 0, zct[2]);
planeOffset[i][nextImage] = planeOffset[i][idx] + zct[1] * planeSize;
}
else if (flattened) {
planeOffset[i][nextImage] = planeOffset[i][nextImage - 1] + planeSize;
}
else if (nextImage < planeOffset[i].length) {
planeOffset[i][nextImage] = offset + p * planeSize;
}
}
}
setSeries(0);
if (pixelSizeZ.size() > 0) {
int seriesIndex = 0;
for (int q=0; q<getSeriesCount(); q++) {
CoreMetadata msq = core.get(q);
int inc = msq.sizeC * msq.sizeT;
if (seriesIndex + inc > pixelSizeZ.size()) {
int z = msq.sizeT;
msq.sizeT = msq.sizeZ;
msq.sizeZ = z;
inc = msq.sizeC * msq.sizeT;
}
seriesIndex += inc;
}
}
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this, true);
// populate Image data
for (int i=0; i<getSeriesCount(); i++) {
if (imageNames[i] != null) store.setImageName(imageNames[i], i);
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
for (int i=0; i<getSeriesCount(); i++) {
if (imageDescriptions.containsKey(i)) {
store.setImageDescription(imageDescriptions.get(i), i);
}
else {
store.setImageDescription("", i);
}
}
// link Instrument and Image
String instrumentID = MetadataTools.createLSID("Instrument", 0);
store.setInstrumentID(instrumentID, 0);
for (int i=0; i<getSeriesCount(); i++) {
store.setImageInstrumentRef(instrumentID, i);
}
int index = 0;
// populate Objective data
int objectiveIndex = 0;
for (int i=0; i<getSeriesCount(); i++) {
String objective = objectives.get(i);
if (objective != null) {
store.setObjectiveModel(objective, 0, objectiveIndex);
store.setObjectiveCorrection(
getCorrection("Other"), 0, objectiveIndex);
store.setObjectiveImmersion(getImmersion("Other"), 0, objectiveIndex);
if (magnifications != null && magnifications.get(i) > 0) {
store.setObjectiveNominalMagnification(
magnifications.get(i).doubleValue(), 0, objectiveIndex);
}
// link Objective to Image
String objectiveID =
MetadataTools.createLSID("Objective", 0, objectiveIndex);
store.setObjectiveID(objectiveID, 0, objectiveIndex);
if (i < getSeriesCount()) {
store.setObjectiveSettingsID(objectiveID, i);
}
objectiveIndex++;
}
}
// populate Dimensions data
int exposureIndex = exposureTimes.size() - channelNames.size();
if (exposureIndex >= 1) {
exposureIndex++;
}
for (int i=0; i<getSeriesCount(); i++) {
setSeries(i);
if (pixelSize.get(i) != null) {
final Double size = pixelSize.get(i).doubleValue();
Length x = FormatTools.getPhysicalSizeX(size);
Length y = FormatTools.getPhysicalSizeY(size);
if (x != null) {
store.setPixelsPhysicalSizeX(x, i);
}
if (y != null) {
store.setPixelsPhysicalSizeY(y, i);
}
}
int idx = 0;
for (int q=0; q<i; q++) {
idx += core.get(q).sizeC * core.get(q).sizeT;
}
if (idx < pixelSizeZ.size() && pixelSizeZ.get(idx) != null) {
Length z = FormatTools.getPhysicalSizeZ(pixelSizeZ.get(idx));
if (z != null) {
store.setPixelsPhysicalSizeZ(z, i);
}
}
for (int plane=0; plane<getImageCount(); plane++) {
int c = getZCTCoords(plane)[1];
if (exposureIndex + c < exposureTimes.size() &&
exposureIndex + c >= 0 &&
exposureTimes.get(exposureIndex + c) != null)
{
store.setPlaneExposureTime(
new Time(exposureTimes.get(exposureIndex + c).doubleValue(),
UNITS.S), i, plane);
}
}
exposureIndex += getSizeC();
}
setSeries(0);
// populate LogicalChannel data
for (int i=0; i<getSeriesCount(); i++) {
setSeries(i);
for (int c=0; c<getSizeC(); c++) {
if (index < channelNames.size() && channelNames.get(index) != null) {
store.setChannelName(channelNames.get(index), i, c);
addSeriesMetaList("channel", channelNames.get(index));
}
if (index < ndFilters.size() && ndFilters.get(index) != null) {
store.setChannelNDFilter(ndFilters.get(index), i, c);
addSeriesMeta("channel " + c + " Neutral density",
ndFilters.get(index));
}
index++;
}
}
setSeries(0);
}
}
// -- Helper methods --
private boolean withinPixels(long offset) {
for (int i=0; i<pixelOffsets.size(); i++) {
long pixelOffset = pixelOffsets.get(i).longValue();
long pixelLength = pixelLengths.get(i).longValue();
if (offset >= pixelOffset && offset < (pixelOffset + pixelLength)) {
return true;
}
}
return false;
}
/**
* Returns true if the given double is greater than epsilon (defined here as
* 0.000001), i.e. positive and not a very, very small number.
* Returns false if the given double is negative or less than epsilon.
* See also: http://en.wikipedia.org/wiki/(%CE%B5,_%CE%B4)-definition_of_limit
*/
private boolean isGreaterThanEpsilon(double v) {
return v - Constants.EPSILON > 0;
}
private String readCString() throws IOException {
return in.findString(true, 256, "\0");
}
}