//
// DeltavisionReader.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.DateTools;
import loci.common.Location;
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.IMinMaxStore;
import loci.formats.meta.MetadataStore;
import ome.xml.model.primitives.PositiveFloat;
import ome.xml.model.primitives.PositiveInteger;
/**
* DeltavisionReader is the file format reader for Deltavision 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/DeltavisionReader.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/DeltavisionReader.java;hb=HEAD">Gitweb</a></dd></dl>
*
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public class DeltavisionReader extends FormatReader {
// -- Constants --
public static final int DV_MAGIC_BYTES_1 = 0xa0c0;
public static final int DV_MAGIC_BYTES_2 = 0xc0a0;
public static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy";
private static final short LITTLE_ENDIAN = -16224;
private static final int HEADER_LENGTH = 1024;
private static final String[] IMAGE_TYPES = new String[] {
"normal", "Tilt-series", "Stereo tilt-series", "Averaged images",
"Averaged stereo pairs"
};
// -- Fields --
/** Size of extended header. */
private int extSize;
/** Size of one wave in the extended header. */
protected int wSize;
/** Size of one z section in the extended header. */
protected int zSize;
/** Size of one time element in the extended header. */
protected int tSize;
/**
* The number of ints in each extended header section. These fields appear
* to be all blank but need to be skipped to get to the floats afterwards
*/
protected int numIntsPerSection;
protected int numFloatsPerSection;
/** Initialize an array of Extended Header Field structures. */
protected DVExtHdrFields[][][] extHdrFields = null;
private Double[] ndFilters;
private int[] lengths;
private String logFile;
private String deconvolutionLogFile;
// -- Constructor --
/** Constructs a new Deltavision reader. */
public DeltavisionReader() {
super("Deltavision",
new String[] {"dv", "r3d", "r3d_d3d", "dv.log", "r3d.log"});
suffixNecessary = false;
domains = new String[] {FormatTools.LM_DOMAIN};
hasCompanionFiles = true;
datasetDescription = "One .dv, .r3d, or .d3d file and up to two " +
"optional .log files";
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isSingleFile(String) */
public boolean isSingleFile(String id) throws FormatException, IOException {
return false;
}
/* @see loci.formats.IFormatReader#fileGroupOption(String) */
public int fileGroupOption(String id) throws FormatException, IOException {
return FormatTools.MUST_GROUP;
}
/* @see loci.formats.IFormatReader#isThisType(String, boolean) */
public boolean isThisType(String name, boolean open) {
if (checkSuffix(name, "dv.log") || checkSuffix(name, "r3d.log") ||
name.endsWith("_log.txt"))
{
return true;
}
if (checkSuffix(name, "pnl")) return false;
return super.isThisType(name, open);
}
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 98;
if (!FormatTools.validStream(stream, blockLen, false)) return false;
stream.seek(96);
int magic = stream.readShort() & 0xffff;
return magic == DV_MAGIC_BYTES_1 || magic == DV_MAGIC_BYTES_2;
}
/* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */
public String[] getSeriesUsedFiles(boolean noPixels) {
FormatTools.assertId(currentId, true, 1);
Vector<String> files = new Vector<String>();
if (!noPixels) files.add(currentId);
if (logFile != null) files.add(logFile);
if (deconvolutionLogFile != null) files.add(deconvolutionLogFile);
return files.toArray(new String[files.size()]);
}
/**
* @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);
int[] coords = getZCTCoords(no);
int[] newCoords = new int[4];
int coordIndex = 0;
int dimIndex = 2;
while (coordIndex < newCoords.length) {
char dim = getDimensionOrder().charAt(dimIndex++);
switch (dim) {
case 'Z':
newCoords[coordIndex++] = coords[0];
break;
case 'C':
newCoords[coordIndex++] = coords[1];
break;
case 'T':
newCoords[coordIndex++] = getSeries();
newCoords[coordIndex++] = coords[2];
break;
}
}
int planeIndex = FormatTools.positionToRaster(lengths, newCoords);
// read the image plane's pixel data
long planeSize = (long) FormatTools.getPlaneSize(this);
long planeOffset = planeSize * planeIndex;
long offset = planeOffset + HEADER_LENGTH + extSize;
if (offset < in.length()) {
in.seek(HEADER_LENGTH + extSize + planeOffset);
readPlane(in, x, y, w, h, buf);
}
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
extSize = wSize = zSize = tSize = 0;
numIntsPerSection = numFloatsPerSection = 0;
extHdrFields = null;
ndFilters = null;
logFile = deconvolutionLogFile = null;
lengths = null;
}
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
if (!checkSuffix(id, "dv")) {
if (checkSuffix(id, "dv.log") || checkSuffix(id, "r3d.log")) {
id = id.substring(0, id.lastIndexOf("."));
}
else if (id.endsWith("_log.txt")) {
id = id.substring(0, id.lastIndexOf("_")) + ".dv";
}
Location file = new Location(id).getAbsoluteFile();
if (!file.exists()) {
Location dir = file.getParentFile();
String[] list = dir.list(true);
String name = file.getName();
name = name.substring(0, name.lastIndexOf("."));
for (String f : list) {
if (checkSuffix(f, "dv") && f.startsWith(name)) {
id = new Location(dir, f).getAbsolutePath();
break;
}
}
}
}
super.initFile(id);
findLogFiles();
in = new RandomAccessInputStream(currentId);
initPixels();
MetadataLevel metadataLevel = metadataOptions.getMetadataLevel();
if (metadataLevel != MetadataLevel.MINIMUM) {
initExtraMetadata();
}
}
protected void initPixels() throws FormatException, IOException {
LOGGER.info("Reading header");
MetadataStore store = makeFilterMetadata();
in.seek(96);
in.order(true);
boolean little = in.readShort() == LITTLE_ENDIAN;
in.order(little);
in.seek(0);
int sizeX = in.readInt();
int sizeY = in.readInt();
int imageCount = in.readInt();
int filePixelType = in.readInt();
in.seek(180);
int rawSizeT = in.readShort();
int sizeT = rawSizeT == 0 ? 1 : rawSizeT;
int sequence = in.readShort();
in.seek(92);
extSize = in.readInt();
in.seek(196);
int rawSizeC = in.readShort();
int sizeC = rawSizeC == 0 ? 1 : rawSizeC;
// --- compute some secondary values ---
String imageSequence = getImageSequence(sequence);
int sizeZ = imageCount / (sizeC * sizeT);
// --- populate core metadata ---
LOGGER.info("Populating core metadata");
core[0].littleEndian = little;
core[0].sizeX = sizeX;
core[0].sizeY = sizeY;
core[0].imageCount = imageCount;
String pixel = getPixelString(filePixelType);
core[0].pixelType = getPixelType(filePixelType);
core[0].dimensionOrder = "XY" + imageSequence.replaceAll("W", "C");
int planeSize =
getSizeX() * getSizeY() * FormatTools.getBytesPerPixel(getPixelType());
int realPlaneCount =
(int) ((in.length() - HEADER_LENGTH - extSize) / planeSize);
if (realPlaneCount < getImageCount()) {
LOGGER.debug("Truncated file");
core[0].imageCount = realPlaneCount;
if (sizeZ == 1) {
sizeT = realPlaneCount / sizeC;
}
else if (sizeT == 1) {
sizeZ = realPlaneCount / sizeC;
if ((realPlaneCount % sizeC) != 0) {
sizeZ++;
core[0].imageCount = sizeZ * sizeC;
}
}
else if (getDimensionOrder().indexOf("Z") <
getDimensionOrder().indexOf("T"))
{
sizeZ = realPlaneCount / (sizeC * sizeT);
if (sizeZ == 0) {
sizeT = 1;
sizeZ = realPlaneCount / sizeC;
if ((realPlaneCount % sizeC) != 0) {
sizeZ++;
core[0].imageCount = sizeZ * sizeC;
}
}
if (getImageCount() > (sizeZ * sizeC * sizeT)) {
core[0].imageCount = imageCount;
sizeC = rawSizeC == 0 ? 1 : rawSizeC;
sizeT = rawSizeT == 0 ? 1 : rawSizeT;
sizeZ = getImageCount() / (sizeC * sizeT);
}
}
else {
sizeT = realPlaneCount / (sizeC * sizeZ);
}
}
core[0].sizeT = sizeT;
core[0].sizeC = sizeC;
core[0].sizeZ = sizeZ;
core[0].rgb = false;
core[0].interleaved = false;
core[0].metadataComplete = true;
core[0].indexed = false;
core[0].falseColor = false;
// --- parse extended header ---
in.seek(128);
numIntsPerSection = in.readShort();
numFloatsPerSection = in.readShort();
LOGGER.info("Reading extended header");
setOffsetInfo(sequence, getSizeZ(), getSizeC(), getSizeT());
extHdrFields = new DVExtHdrFields[getSizeZ()][getSizeC()][getSizeT()];
ndFilters = new Double[getSizeC()];
Vector<Float> uniqueTileX = new Vector<Float>();
Vector<Float> uniqueTileY = new Vector<Float>();
// Run through every image and fill in the
// Extended Header information array for that image
int offset = HEADER_LENGTH + numIntsPerSection * 4;
for (int i=0; i<getImageCount(); i++) {
int[] coords = getZCTCoords(i);
int z = coords[0];
int w = coords[1];
int t = coords[2];
// -- read in the extended header data --
in.seek(offset + getTotalOffset(z, w, t));
DVExtHdrFields hdr = new DVExtHdrFields(in);
extHdrFields[z][w][t] = hdr;
if (!uniqueTileX.contains(hdr.stageXCoord)) {
uniqueTileX.add(hdr.stageXCoord);
}
if (!uniqueTileY.contains(hdr.stageYCoord)) {
uniqueTileY.add(hdr.stageYCoord);
}
}
int nStagePositions = uniqueTileX.size() * uniqueTileY.size();
if (nStagePositions > 0 && nStagePositions <= getSizeT()) {
int t = getSizeT();
core[0].sizeT /= nStagePositions;
if (getSizeT() * nStagePositions != t) {
core[0].sizeT = t;
nStagePositions = 1;
}
else {
core[0].imageCount /= nStagePositions;
}
if (nStagePositions > 1) {
CoreMetadata originalCore = core[0];
core = new CoreMetadata[nStagePositions];
for (int i=0; i<core.length; i++) {
core[i] = originalCore;
}
}
}
lengths = new int[4];
int lengthIndex = 0;
int dimIndex = 0;
while (lengthIndex < lengths.length) {
char dim = imageSequence.charAt(dimIndex++);
switch (dim) {
case 'Z':
lengths[lengthIndex++] = getSizeZ();
break;
case 'W':
lengths[lengthIndex++] = getSizeC();
break;
case 'T':
lengths[lengthIndex++] = getSeriesCount();
lengths[lengthIndex++] = getSizeT();
break;
}
}
// --- populate original metadata ---
LOGGER.info("Populating original metadata");
addGlobalMeta("ImageWidth", sizeX);
addGlobalMeta("ImageHeight", sizeY);
addGlobalMeta("NumberOfImages", imageCount);
addGlobalMeta("PixelType", pixel);
addGlobalMeta("Number of timepoints", rawSizeT);
addGlobalMeta("Image sequence", imageSequence);
addGlobalMeta("Number of wavelengths", rawSizeC);
addGlobalMeta("Number of focal planes", sizeZ);
for (int series=0; series<getSeriesCount(); series++) {
setSeries(series);
for (int plane=0; plane<getImageCount(); plane++) {
int[] coords = getZCTCoords(plane);
int tIndex = getSeriesCount() * coords[2] + series;
DVExtHdrFields hdr = extHdrFields[coords[0]][coords[1]][tIndex];
// -- record original metadata --
// NB: It adds a little overhead to record the extended headers into the
// original metadata table, but not as much as registering every header
// field individually. With this approach it is still easy to
// programmatically access any given extended header field.
String prefix =
"Extended header Z" + coords[0] + " W" + coords[1] + " T" + coords[2];
addSeriesMeta(prefix, hdr);
String position = " position for position #" + (series + 1);
addGlobalMeta("X" + position, hdr.stageXCoord);
addGlobalMeta("Y" + position, hdr.stageYCoord);
addGlobalMeta("Z" + position, hdr.stageZCoord);
}
}
setSeries(0);
// --- populate OME metadata ---
LOGGER.info("Populating OME metadata");
MetadataTools.populatePixels(store, this, true);
MetadataTools.setDefaultCreationDate(store, currentId, 0);
// 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);
}
}
protected void initExtraMetadata() throws FormatException, IOException {
MetadataStore store = makeFilterMetadata();
// --- read in the image header data ---
LOGGER.info("Reading header");
in.seek(16);
int subImageStartX = in.readInt();
int subImageStartY = in.readInt();
int subImageStartZ = in.readInt();
int pixelSamplingX = in.readInt();
int pixelSamplingY = in.readInt();
int pixelSamplingZ = in.readInt();
float pixX = in.readFloat();
float pixY = in.readFloat();
float pixZ = in.readFloat();
float xAxisAngle = in.readFloat();
float yAxisAngle = in.readFloat();
float zAxisAngle = in.readFloat();
int xAxisSeq = in.readInt();
int yAxisSeq = in.readInt();
int zAxisSeq = in.readInt();
float[] minWave = new float[5];
float[] maxWave = new float[5];
minWave[0] = in.readFloat();
maxWave[0] = in.readFloat();
float meanIntensity = in.readFloat();
int spaceGroupNumber = in.readInt();
in.seek(132);
short numSubResSets = in.readShort();
short zAxisReductionQuotient = in.readShort();
for (int i=1; i<=3; i++) {
minWave[i] = in.readFloat();
maxWave[i] = in.readFloat();
}
int type = in.readShort();
int lensID = in.readShort();
in.seek(172);
minWave[4] = in.readFloat();
maxWave[4] = in.readFloat();
in.seek(184);
float xTiltAngle = in.readFloat();
float yTiltAngle = in.readFloat();
float zTiltAngle = in.readFloat();
in.skipBytes(2);
short[] waves = new short[5];
for (int i=0; i<waves.length; i++) {
waves[i] = in.readShort();
}
float xOrigin = in.readFloat();
float yOrigin = in.readFloat();
float zOrigin = in.readFloat();
in.skipBytes(4);
String[] title = new String[10];
for (int i=0; i<title.length; i++) {
// Make sure that "null" characters are stripped out
title[i] = in.readString(80).replaceAll("\0", "");
}
// --- compute some secondary values ---
String imageType =
type < IMAGE_TYPES.length ? IMAGE_TYPES[type] : "unknown";
String imageDesc = title[0];
if (imageDesc != null && imageDesc.length() == 0) imageDesc = null;
// --- populate original metadata ---
LOGGER.info("Populating original metadata");
addGlobalMeta("Sub-image starting point (X)", subImageStartX);
addGlobalMeta("Sub-image starting point (Y)", subImageStartY);
addGlobalMeta("Sub-image starting point (Z)", subImageStartZ);
addGlobalMeta("Pixel sampling size (X)", pixelSamplingX);
addGlobalMeta("Pixel sampling size (Y)", pixelSamplingY);
addGlobalMeta("Pixel sampling size (Z)", pixelSamplingZ);
addGlobalMeta("X element length (in um)", pixX);
addGlobalMeta("Y element length (in um)", pixY);
addGlobalMeta("Z element length (in um)", pixZ);
addGlobalMeta("X axis angle", xAxisAngle);
addGlobalMeta("Y axis angle", yAxisAngle);
addGlobalMeta("Z axis angle", zAxisAngle);
addGlobalMeta("Column axis sequence", xAxisSeq);
addGlobalMeta("Row axis sequence", yAxisSeq);
addGlobalMeta("Section axis sequence", zAxisSeq);
addGlobalMeta("Image Type", imageType);
addGlobalMeta("Lens ID Number", lensID);
addGlobalMeta("X axis tilt angle", xTiltAngle);
addGlobalMeta("Y axis tilt angle", yTiltAngle);
addGlobalMeta("Z axis tilt angle", zTiltAngle);
for (int i=0; i<waves.length; i++) {
addGlobalMeta("Wavelength " + (i + 1) + " (in nm)", waves[i]);
}
addGlobalMeta("X origin (in um)", xOrigin);
addGlobalMeta("Y origin (in um)", yOrigin);
addGlobalMeta("Z origin (in um)", zOrigin);
for (int i=0; i<title.length; i++) {
addGlobalMeta("Title " + (i + 1), title[i]);
}
for (int i=0; i<minWave.length; i++) {
addGlobalMeta("Wavelength " + (i + 1) + " min. intensity", minWave[i]);
addGlobalMeta("Wavelength " + (i + 1) + " max. intensity", maxWave[i]);
}
addGlobalMeta("Wavelength 1 mean intensity", meanIntensity);
addGlobalMeta("Space group number", spaceGroupNumber);
addGlobalMeta("Number of Sub-resolution sets", numSubResSets);
addGlobalMeta("Z axis reduction quotient", zAxisReductionQuotient);
// --- populate OME metadata ---
LOGGER.info("Populating OME metadata");
for (int series=0; series<getSeriesCount(); series++) {
if (store instanceof IMinMaxStore) {
IMinMaxStore minMaxStore = (IMinMaxStore) store;
for (int i=0; i<minWave.length; i++) {
if (i < getEffectiveSizeC()) {
minMaxStore.setChannelGlobalMinMax(
i, minWave[i], maxWave[i], series);
}
}
}
store.setPixelsPhysicalSizeX(new PositiveFloat(new Double(pixX)), series);
store.setPixelsPhysicalSizeY(new PositiveFloat(new Double(pixY)), series);
store.setPixelsPhysicalSizeZ(new PositiveFloat(new Double(pixZ)), series);
store.setImageDescription(imageDesc, series);
}
// if matching log file exists, extract key/value pairs from it
boolean logFound = isGroupFiles() ? parseLogFile(store) : false;
if (isGroupFiles()) parseDeconvolutionLog(store);
for (int series=0; series<getSeriesCount(); series++) {
for (int i=0; i<getImageCount(); i++) {
int[] coords = getZCTCoords(i);
int tIndex = getSeriesCount() * coords[2] + series;
DVExtHdrFields hdr = extHdrFields[coords[0]][coords[1]][tIndex];
// plane timing
if (!logFound) {
store.setPlaneDeltaT(new Double(hdr.timeStampSeconds), series, i);
}
store.setPlaneExposureTime(new Double(hdr.expTime), series, i);
// stage position
if (!logFound || getSeriesCount() > 1) {
store.setPlanePositionX(new Double(hdr.stageXCoord), series, i);
store.setPlanePositionY(new Double(hdr.stageYCoord), series, i);
store.setPlanePositionZ(new Double(hdr.stageZCoord), series, i);
}
}
for (int w=0; w<getSizeC(); w++) {
DVExtHdrFields hdrC = extHdrFields[0][w][series];
if ((int) waves[w] > 0) {
store.setChannelEmissionWavelength(
new PositiveInteger((int) waves[w]), series, w);
}
if ((int) hdrC.exWavelen > 0) {
store.setChannelExcitationWavelength(
new PositiveInteger((int) hdrC.exWavelen), series, w);
}
if (ndFilters[w] == null) ndFilters[w] = new Double(hdrC.ndFilter);
store.setChannelNDFilter(ndFilters[w], series, w);
}
}
}
// -- Helper methods --
/** Get a descriptive string representing the pixel type. */
private String getPixelString(int filePixelType) {
switch (filePixelType) {
case 0:
return "8 bit unsigned integer";
case 1:
return "16 bit signed integer";
case 2:
return "32 bit floating point";
case 3:
return "16 bit complex";
case 4:
return "64 bit complex";
case 6:
return "16 bit unsigned integer";
}
return "unknown";
}
/** Get the OME pixel type from the pixel type stored in the file. */
private int getPixelType(int filePixelType) {
switch (filePixelType) {
case 0:
return FormatTools.UINT8;
case 1:
return FormatTools.INT16;
case 2:
return FormatTools.FLOAT;
case 3:
return FormatTools.INT16;
case 4:
return FormatTools.FLOAT;
case 6:
return FormatTools.UINT16;
}
return FormatTools.UINT8;
}
/** Get the image sequence string. */
private String getImageSequence(int imageSequence) {
switch (imageSequence) {
case 0:
return "ZTW";
case 1:
return "WZT";
case 2:
return "ZWT";
case 65536:
return "WZT";
}
return "ZTW";
}
/**
* This method calculates the size of a w, t, z section depending on which
* sequence is being used (either ZTW, WZT, or ZWT)
* @param imgSequence
* @param numZSections
* @param numWaves
* @param numTimes
*/
private void setOffsetInfo(int imgSequence, int numZSections,
int numWaves, int numTimes)
{
int smallOffset = (numIntsPerSection + numFloatsPerSection) * 4;
switch (imgSequence) {
// ZTW sequence
case 0:
zSize = smallOffset;
tSize = zSize * numZSections;
wSize = tSize * numTimes;
break;
// WZT sequence
case 1:
wSize = smallOffset;
zSize = wSize * numWaves;
tSize = zSize * numZSections;
break;
// ZWT sequence
case 2:
zSize = smallOffset;
wSize = zSize * numZSections;
tSize = wSize * numWaves;
break;
}
}
/**
* Given any specific Z, W, and T of a plane, determine the totalOffset from
* the start of the extended header.
* @param currentZ
* @param currentW
* @param currentT
*/
private int getTotalOffset(int currentZ, int currentW, int currentT) {
return (zSize * currentZ) + (wSize * currentW) + (tSize * currentT);
}
/** Find the log files. */
private void findLogFiles() throws IOException {
if (getCurrentFile().lastIndexOf(".") == -1) {
// The current file name has no extension, skip trying to find the
// log file(s).
logFile = null;
deconvolutionLogFile = null;
return;
}
if (getCurrentFile().endsWith("_D3D.dv")) {
logFile = getCurrentFile();
logFile = logFile.substring(0, logFile.indexOf("_D3D.dv")) + ".dv.log";
}
else {
logFile = getCurrentFile() + ".log";
if (!new Location(logFile).exists()) {
logFile = getCurrentFile();
logFile = logFile.substring(0, logFile.lastIndexOf(".")) + ".log";
}
}
if (!new Location(logFile).exists()) logFile = null;
int dot = getCurrentFile().lastIndexOf(".");
String base = getCurrentFile().substring(0, dot);
deconvolutionLogFile = base + "_log.txt";
if (!new Location(deconvolutionLogFile).exists()) {
deconvolutionLogFile = null;
}
}
/** Extract metadata from associated log file, if it exists. */
private boolean parseLogFile(MetadataStore store)
throws FormatException, IOException
{
if (logFile == null || !new Location(logFile).exists()) {
logFile = null;
return false;
}
LOGGER.info("Parsing log file");
String[] lines = DataTools.readFile(logFile).split("[\r\n]");
String key, value = "", prefix = "";
int currentImage = 0;
for (String line : lines) {
int colon = line.indexOf(":");
if (colon != -1) {
if (line.startsWith("Created")) {
key = "Created";
colon = 6;
}
else key = line.substring(0, colon).trim();
value = line.substring(colon + 1).trim();
if (value.equals("") && !key.equals("")) prefix = key;
addGlobalMeta(prefix + " " + key, value);
// Objective properties
if (key.equals("Objective")) {
// assume first word is the manufacturer's name
int space = value.indexOf(" ");
if (space != -1) {
String manufacturer = value.substring(0, space);
String extra = value.substring(space + 1);
String[] tokens = extra.split(",");
store.setObjectiveManufacturer(manufacturer, 0, 0);
String magnification = "", na = "";
if (tokens.length >= 1) {
int end = tokens[0].indexOf("X");
if (end > 0) magnification = tokens[0].substring(0, end);
int start = tokens[0].indexOf("/");
if (start >= 0) na = tokens[0].substring(start + 1);
}
try {
store.setObjectiveNominalMagnification(
PositiveInteger.valueOf(magnification), 0, 0);
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse magnification '{}'", magnification);
}
try {
store.setObjectiveLensNA(new Double(na), 0, 0);
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse N.A. '{}'", na);
}
if (tokens.length >= 2) {
store.setObjectiveCorrection(getCorrection(tokens[1]), 0, 0);
}
// TODO: Token #2 is the microscope model name.
if (tokens.length > 3) store.setObjectiveModel(tokens[3], 0, 0);
}
}
else if (key.equalsIgnoreCase("Lens ID")) {
if (value.indexOf(",") != -1) {
value = value.substring(0, value.indexOf(","));
}
String objectiveID = "Objective:" + value;
store.setObjectiveID(objectiveID, 0, 0);
for (int series=0; series<getSeriesCount(); series++) {
store.setImageObjectiveSettingsID(objectiveID, series);
}
store.setObjectiveCorrection(getCorrection("Other"), 0, 0);
store.setObjectiveImmersion(getImmersion("Other"), 0, 0);
}
// Image properties
else if (key.equals("Pixel Size")) {
String[] pixelSizes = value.split(" ");
for (int q=0; q<pixelSizes.length; q++) {
Double size = null;
try {
size = new Double(pixelSizes[q].trim());
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse pixel size '{}'",
pixelSizes[q].trim());
}
if (q == 0) {
for (int series=0; series<getSeriesCount(); series++) {
store.setPixelsPhysicalSizeX(new PositiveFloat(size), series);
}
}
if (q == 1) {
for (int series=0; series<getSeriesCount(); series++) {
store.setPixelsPhysicalSizeY(new PositiveFloat(size), series);
}
}
if (q == 2) {
for (int series=0; series<getSeriesCount(); series++) {
store.setPixelsPhysicalSizeZ(new PositiveFloat(size), series);
}
}
}
}
else if (key.equals("Binning")) {
store.setDetectorType(getDetectorType("Other"), 0, 0);
String detectorID = MetadataTools.createLSID("Detector", 0, 0);
store.setDetectorID(detectorID, 0, 0);
for (int series=0; series<getSeriesCount(); series++) {
for (int c=0; c<getSizeC(); c++) {
store.setDetectorSettingsBinning(getBinning(value), series, c);
// link DetectorSettings to an actual Detector
store.setDetectorSettingsID(detectorID, series, c);
}
}
}
// Camera properties
else if (key.equals("Type")) {
store.setDetectorModel(value, 0, 0);
}
else if (key.equals("Gain")) {
value = value.replaceAll("X", "");
try {
for (int series=0; series<getSeriesCount(); series++) {
for (int c=0; c<getSizeC(); c++) {
store.setDetectorSettingsGain(new Double(value), series, c);
}
}
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse gain '{}'", value);
}
}
else if (key.equals("Speed")) {
value = value.replaceAll("KHz", "");
try {
double mhz = Double.parseDouble(value) / 1000;
for (int series=0; series<getSeriesCount(); series++) {
for (int c=0; c<getSizeC(); c++) {
store.setDetectorSettingsReadOutRate(mhz, series, c);
}
}
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse read-out rate '{}'", value);
}
}
else if (key.equals("Temp Setting")) {
value = value.replaceAll("C", "").trim();
try {
// this is the camera temperature, not the environment temperature
//store.setImagingEnvironmentTemperature(value, 0);
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse temperature '{}'", value);
}
}
// Plane properties
else if (key.equals("Time Point")) {
int space = value.indexOf(" ");
if (space >= 0) value = value.substring(0, space);
try {
if (currentImage < getImageCount()) {
for (int series=0; series<getSeriesCount(); series++) {
store.setPlaneDeltaT(new Double(value), series, currentImage);
}
}
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse timestamp '{}'", value);
}
}
else if (key.equals("EM filter")) {
int cIndex = 0;
try {
cIndex = getZCTCoords(currentImage)[1];
}
catch (IllegalArgumentException e) {
LOGGER.debug("", e);
}
for (int series=0; series<getSeriesCount(); series++) {
store.setChannelName(value, series, cIndex);
}
}
else if (key.equals("ND filter")) {
value = value.replaceAll("%", "");
try {
int cIndex = getZCTCoords(currentImage)[1];
double nd = Double.parseDouble(value);
ndFilters[cIndex] = new Double(nd / 100);
}
catch (NumberFormatException exc) {
LOGGER.warn("Could not parse ND filter '{}'", value);
}
catch (IllegalArgumentException e) {
LOGGER.debug("", e);
}
}
else if (key.equals("Stage coordinates")) {
if (value.length() > 1) {
value = value.substring(1, value.length() - 1);
}
String[] coords = value.split(",");
for (int i=0; i<coords.length; i++) {
Double p = null;
try {
p = new Double(coords[i].trim());
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse stage coordinate '{}'", coords[i]);
}
if (currentImage < getImageCount() && getSeriesCount() == 1) {
// NB: the positions are intentionally only populated for the
// first series. Positions for the other series are parsed
// from the extended header.
if (i == 0) {
store.setPlanePositionX(p, 0, currentImage);
}
if (i == 1) {
store.setPlanePositionY(p, 0, currentImage);
}
if (i == 2) {
store.setPlanePositionZ(p, 0, currentImage);
}
}
}
currentImage++;
}
}
else if (line.startsWith("Image")) prefix = line;
else if (line.startsWith("Created")) {
if (line.length() > 8) line = line.substring(8).trim();
String date = DateTools.formatDate(line, DATE_FORMAT);
if (date != null) {
for (int series=0; series<getSeriesCount(); series++) {
store.setImageAcquiredDate(date, series);
}
}
else {
LOGGER.warn("Could not parse date '{}'", line);
}
}
}
return true;
}
/** Parse deconvolution output, if it exists. */
private void parseDeconvolutionLog(MetadataStore store) throws IOException {
if (deconvolutionLogFile == null ||
!new Location(deconvolutionLogFile).exists())
{
return;
}
LOGGER.info("Parsing deconvolution log file");
RandomAccessInputStream s =
new RandomAccessInputStream(deconvolutionLogFile);
boolean doStatistics = false;
int cc = 0, tt = 0;
String previousLine = null;
while (s.getFilePointer() < s.length() - 1) {
String line = s.readLine();
if (line == null || line.length() == 0) continue;
if (doStatistics) {
String[] keys = line.split(" ");
Vector<String> realKeys = new Vector<String>();
for (int i=0; i<keys.length; i++) {
keys[i] = keys[i].trim();
if (keys[i].length() > 0) realKeys.add(keys[i]);
}
keys = realKeys.toArray(new String[0]);
s.readLine();
line = s.readLine().trim();
while (line != null && line.length() != 0) {
String[] values = line.split(" ");
Vector<String> realValues = new Vector<String>();
for (int i=0; i<values.length; i++) {
values[i] = values[i].trim();
if (values[i].length() > 0) { realValues.add(values[i]); }
}
values = realValues.toArray(new String[0]);
try {
if (values.length > 0) {
int zz = Integer.parseInt(values[0]) - 1;
int index = getIndex(zz, cc, tt);
for (int i=1; i<keys.length; i++) {
addGlobalMeta("Plane " + index + " " + keys[i], values[i]);
}
}
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse Z position '{}'", values[0]);
}
catch (IllegalArgumentException iae) {
LOGGER.debug("", iae);
}
line = s.readLine().trim();
}
}
else {
int index = line.indexOf(".\t");
if (index != -1) {
String key = line.substring(0, index).trim();
String value = line.substring(index + 2).trim();
// remove trailing dots from key
while (key.endsWith(".")) {
key = key.substring(0, key.length() - 1);
}
if (previousLine != null &&
(previousLine.endsWith("Deconvolution Results:") ||
previousLine.endsWith("open OTF")))
{
addGlobalMeta(previousLine + " " + key, value);
}
else addGlobalMeta(key, value);
}
}
if (line.indexOf("correcting time point\t") != -1) {
int index = line.indexOf("time point\t") + 11;
if (index > 10) {
String t = line.substring(index, line.indexOf(",", index));
try {
tt = Integer.parseInt(t) - 1;
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse timepoint '{}'", t);
}
index = line.indexOf("wavelength\t") + 11;
if (index > 10) {
String c = line.substring(index, line.indexOf(".", index));
try {
cc = Integer.parseInt(c) - 1;
}
catch (NumberFormatException e) {
LOGGER.warn("Could not parse channel position '{}'", c);
}
}
}
}
if (line.length() > 0 && line.indexOf(".") == -1) previousLine = line;
doStatistics = line.endsWith("- reading image data...");
}
s.close();
}
private void readWavelength(int channel, MetadataStore store)
throws FormatException, IOException
{
float min = in.readFloat();
float max = in.readFloat();
addGlobalMeta("Wavelength " + (channel + 1) + " min. intensity", min);
addGlobalMeta("Wavelength " + (channel + 1) + " max. intensity", max);
if (store instanceof IMinMaxStore) {
((IMinMaxStore) store).setChannelGlobalMinMax(channel, min, max, 0);
}
}
// -- Helper classes --
/**
* This private class structure holds the details for the extended header
* @author Brian W. Loranger
*/
private class DVExtHdrFields {
private int offsetWithInts;
/** Photosensor reading. Typically in mV. */
public float photosensorReading;
/** Time stamp in seconds since the experiment began. */
public float timeStampSeconds;
/** X stage coordinates. */
public float stageXCoord;
/** Y stage coordinates. */
public float stageYCoord;
/** Z stage coordinates. */
public float stageZCoord;
/** Minimum intensity */
public float minInten;
/** Maxiumum intensity. */
public float maxInten;
/** Exposure time in milliseconds. */
public float expTime;
/** Neutral density value. */
public float ndFilter;
/** Excitation filter wavelength. */
public float exWavelen;
/** Emission filter wavelength. */
public float emWavelen;
/** Intensity scaling factor. Usually 1. */
public float intenScaling;
/** Energy conversion factor. Usually 1. */
public float energyConvFactor;
/**
* Helper function which overrides toString, printing out the values in
* the header section.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("photosensorReading: ");
sb.append(photosensorReading);
sb.append("\ntimeStampSeconds: ");
sb.append(timeStampSeconds);
sb.append("\nstageXCoord: ");
sb.append(stageXCoord);
sb.append("\nstageYCoord: ");
sb.append(stageYCoord);
sb.append("\nstageZCoord: ");
sb.append(stageZCoord);
sb.append("\nminInten: ");
sb.append(minInten);
sb.append("\nmaxInten: ");
sb.append(maxInten);
sb.append("\nexpTime: ");
sb.append(expTime);
sb.append("\nndFilter: ");
sb.append(ndFilter);
sb.append("\nexWavelen: ");
sb.append(exWavelen);
sb.append("\nemWavelen: ");
sb.append(emWavelen);
sb.append("\nintenScaling: ");
sb.append(intenScaling);
sb.append("\nenergyConvFactor: ");
sb.append(energyConvFactor);
return sb.toString();
}
private DVExtHdrFields(RandomAccessInputStream in) {
try {
// NB: this is consistent with the Deltavision Opener plugin
// for ImageJ (http://rsb.info.nih.gov/ij/plugins/track/delta.html)
photosensorReading = in.readFloat();
timeStampSeconds = in.readFloat();
stageXCoord = in.readFloat();
stageYCoord = in.readFloat();
stageZCoord = in.readFloat();
minInten = in.readFloat();
maxInten = in.readFloat();
in.skipBytes(4);
expTime = in.readFloat() / 1000;
ndFilter = in.readFloat() / 100;
exWavelen = in.readFloat();
emWavelen = in.readFloat();
intenScaling = in.readFloat();
energyConvFactor = in.readFloat();
}
catch (IOException e) {
LOGGER.debug("Could not parse extended header", e);
}
}
}
}