/*
* #%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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import loci.common.DataTools;
import loci.common.Location;
import loci.common.services.DependencyException;
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.meta.MetadataStore;
import loci.formats.services.MDBService;
import loci.formats.tiff.IFD;
import loci.formats.tiff.IFDList;
import loci.formats.tiff.PhotoInterp;
import loci.formats.tiff.TiffParser;
import ome.xml.model.primitives.PositiveFloat;
import ome.units.quantity.Length;
/**
* APLReader is the file format reader for Olympus APL files.
*/
public class APLReader extends FormatReader {
// -- Constants --
private static final String[] METADATA_SUFFIXES =
new String[] {"apl", "tnb", "mtb" };
// -- Fields --
private String[] tiffFiles;
private String[] xmlFiles;
private transient TiffParser[] parser;
private IFDList[] ifds;
private List<String> used;
// -- Constructor --
/** Constructs a new APL reader. */
public APLReader() {
super("Olympus APL", new String[] {"apl", "tnb", "mtb", "tif"});
domains = new String[] {FormatTools.LM_DOMAIN};
hasCompanionFiles = true;
suffixSufficient = false;
datasetDescription = "One .apl file, one .mtb file, one .tnb file, and " +
"a directory containing one or more .tif files";
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(String, boolean) */
@Override
public boolean isThisType(String name, boolean open) {
if (checkSuffix(name, METADATA_SUFFIXES)) return true;
if (checkSuffix(name, "tif") && open) {
Location file = new Location(name).getAbsoluteFile();
Location parent = file.getParentFile();
if (parent != null) {
try {
parent = parent.getParentFile();
parent = parent.getParentFile();
}
catch (NullPointerException e) {
return false;
}
Location aplFile = new Location(parent, parent.getName() + ".apl");
return aplFile.exists();
}
}
return false;
}
/* @see loci.formats.IFormatReader#isSingleFile(String) */
@Override
public boolean isSingleFile(String id) throws FormatException, IOException {
return false;
}
/* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */
@Override
public String[] getSeriesUsedFiles(boolean noPixels) {
FormatTools.assertId(currentId, true, 1);
final List<String> files = new ArrayList<String>();
files.addAll(used);
if (getSeries() < xmlFiles.length) {
Location xmlFile = new Location(xmlFiles[getSeries()]);
if (xmlFile.exists() && !xmlFile.isDirectory()) {
files.add(xmlFiles[getSeries()]);
}
}
if (!noPixels && getSeries() < tiffFiles.length &&
new Location(tiffFiles[getSeries()]).exists())
{
files.add(tiffFiles[getSeries()]);
}
return files.toArray(new String[files.size()]);
}
/**
* @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);
if (parser == null) {
parser = new TiffParser[getSeriesCount()];
}
if (parser[getSeries()] == null) {
parser[getSeries()] = new TiffParser(tiffFiles[getSeries()]);
parser[getSeries()].setDoCaching(false);
}
IFD ifd = ifds[getSeries()].get(no);
return parser[getSeries()].getSamples(ifd, buf, x, y, w, h);
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
tiffFiles = null;
xmlFiles = null;
used = null;
ifds = null;
if (parser != null) {
for (TiffParser p : parser) {
if (p != null) {
p.getStream().close();
}
}
}
parser = null;
}
}
/* @see loci.formats.IFormatReader#fileGroupOption(String) */
@Override
public int fileGroupOption(String id) throws FormatException, IOException {
return FormatTools.MUST_GROUP;
}
/* @see loci.formats.IFormatReader#getOptimalTileWidth() */
@Override
public int getOptimalTileWidth() {
FormatTools.assertId(currentId, true, 1);
try {
return (int) ifds[getSeries()].get(0).getTileWidth();
}
catch (FormatException e) {
LOGGER.debug("Could not retrieve tile width", e);
}
return super.getOptimalTileWidth();
}
/* @see loci.formats.IFormatReader#getOptimalTileHeight() */
@Override
public int getOptimalTileHeight() {
FormatTools.assertId(currentId, true, 1);
try {
return (int) ifds[getSeries()].get(0).getTileLength();
}
catch (FormatException e) {
LOGGER.debug("Could not retrieve tile height", e);
}
return super.getOptimalTileHeight();
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
LOGGER.debug("Initializing {}", id);
// find the corresponding .mtb file
if (!checkSuffix(id, "mtb")) {
if (checkSuffix(id, METADATA_SUFFIXES)) {
int separator = id.lastIndexOf(File.separator);
if (separator < 0) separator = 0;
int underscore = id.lastIndexOf("_");
if (underscore < separator || checkSuffix(id, "apl")) {
underscore = id.lastIndexOf(".");
}
String mtbFile = id.substring(0, underscore) + "_d.mtb";
if (!new Location(mtbFile).exists()) {
throw new FormatException(".mtb file not found");
}
currentId = new Location(mtbFile).getAbsolutePath();
}
else {
Location parent = new Location(id).getAbsoluteFile().getParentFile();
parent = parent.getParentFile();
String[] list = parent.list(true);
for (String f : list) {
if (checkSuffix(f, "mtb")) {
currentId = new Location(parent, f).getAbsolutePath();
break;
}
}
if (!checkSuffix(currentId, "mtb")) {
throw new FormatException(".mtb file not found");
}
}
}
String mtb = new Location(currentId).getAbsolutePath();
LOGGER.debug("Reading .mtb file '{}'", mtb);
MDBService mdb = null;
try {
ServiceFactory factory = new ServiceFactory();
mdb = factory.getInstance(MDBService.class);
}
catch (DependencyException de) {
throw new FormatException("MDB Tools Java library not found", de);
}
String[] columnNames = null;
List<String[]> rows = null;
try {
mdb.initialize(mtb);
rows = mdb.parseDatabase().get(0);
columnNames = rows.get(0);
String[] tmpNames = columnNames;
columnNames = new String[tmpNames.length - 1];
System.arraycopy(tmpNames, 1, columnNames, 0, columnNames.length);
}
finally {
mdb.close();
}
// add full table to metadata hashtable
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
for (int i=1; i<rows.size(); i++) {
String[] row = rows.get(i);
for (int q=0; q<row.length; q++) {
addGlobalMetaList(columnNames[q], row[q]);
}
}
}
used = new ArrayList<String>();
used.add(mtb);
String tnb = mtb.substring(0, mtb.lastIndexOf("."));
if (tnb.lastIndexOf("_") > tnb.lastIndexOf(File.separator)) {
tnb = tnb.substring(0, tnb.lastIndexOf("_"));
}
used.add(tnb + "_1.tnb");
used.add(tnb + ".apl");
String idPath = new Location(id).getAbsolutePath();
if (!used.contains(idPath) && checkSuffix(idPath, METADATA_SUFFIXES)) {
used.add(idPath);
}
// calculate indexes to relevant metadata
int calibrationUnit = DataTools.indexOf(columnNames, "Calibration Unit");
int colorChannels = DataTools.indexOf(columnNames, "Color Channels");
int frames = DataTools.indexOf(columnNames, "Frames");
int calibratedHeight = DataTools.indexOf(columnNames, "Height");
int calibratedWidth = DataTools.indexOf(columnNames, "Width");
int path = DataTools.indexOf(columnNames, "Image Path");
int filename = DataTools.indexOf(columnNames, "File Name");
int magnification = DataTools.indexOf(columnNames, "Magnification");
int width = DataTools.indexOf(columnNames, "X-Resolution");
int height = DataTools.indexOf(columnNames, "Y-Resolution");
int imageName = DataTools.indexOf(columnNames, "Image Name");
int zLayers = DataTools.indexOf(columnNames, "Z-Layers");
String parentDirectory = mtb.substring(0, mtb.lastIndexOf(File.separator));
// look for the directory that contains TIFF and XML files
LOGGER.debug("Searching {} for a directory with TIFFs", parentDirectory);
Location dir = new Location(parentDirectory);
String topDirectory = null;
int index = 2;
String pathName = rows.get(index++)[path].trim();
while (pathName.equals("") && index < rows.size()) {
pathName = rows.get(index++)[path].trim();
}
pathName = pathName.replace('\\', File.separatorChar);
pathName = pathName.replaceAll("/", File.separator);
String[] dirs = pathName.split(
File.separatorChar == '\\' ? "\\\\" : File.separator);
for (int i=dirs.length-1; i>=0; i--) {
if (dirs[i].indexOf("_DocumentFiles") > 0) {
Location file = new Location(dir, dirs[i]);
if (file.exists()) {
topDirectory = file.getAbsolutePath();
break;
}
}
}
if (topDirectory == null) {
String[] list = dir.list();
for (String f : list) {
LOGGER.debug(" '{}'", f);
Location file = new Location(dir, f);
if (file.isDirectory() && f.indexOf("_DocumentFiles") > 0) {
topDirectory = file.getAbsolutePath();
LOGGER.debug("Found {}", topDirectory);
break;
}
}
}
if (topDirectory == null) {
throw new FormatException("Could not find a directory with TIFF files.");
}
final List<Integer> seriesIndexes = new ArrayList<Integer>();
for (int i=1; i<rows.size(); i++) {
String file = rows.get(i)[filename].trim();
if (file.equals("")) continue;
file = topDirectory + File.separator + file;
if (new Location(file).exists() && checkSuffix(file, "tif")) {
seriesIndexes.add(i);
}
}
int seriesCount = seriesIndexes.size();
core.clear();
tiffFiles = new String[seriesCount];
xmlFiles = new String[seriesCount];
parser = new TiffParser[seriesCount];
ifds = new IFDList[seriesCount];
for (int i=0; i<seriesCount; i++) {
CoreMetadata ms = new CoreMetadata();
core.add(ms);
int secondRow = seriesIndexes.get(i);
int firstRow = secondRow - 1;
String[] row2 = rows.get(firstRow);
String[] row3 = rows.get(secondRow);
ms.sizeT = parseDimension(row3[frames]);
ms.sizeZ = parseDimension(row3[zLayers]);
ms.sizeC = parseDimension(row3[colorChannels]);
ms.dimensionOrder = "XYCZT";
if (ms.sizeZ == 0) ms.sizeZ = 1;
if (ms.sizeC == 0) ms.sizeC = 1;
if (ms.sizeT == 0) ms.sizeT = 1;
xmlFiles[i] = topDirectory + File.separator + row2[filename];
tiffFiles[i] = topDirectory + File.separator + row3[filename];
parser[i] = new TiffParser(tiffFiles[i]);
parser[i].setDoCaching(false);
ifds[i] = parser[i].getIFDs();
for (IFD ifd : ifds[i]) {
parser[i].fillInIFD(ifd);
}
// get core metadata from TIFF file
IFD ifd = ifds[i].get(0);
PhotoInterp photo = ifd.getPhotometricInterpretation();
int samples = ifd.getSamplesPerPixel();
ms.sizeX = (int) ifd.getImageWidth();
ms.sizeY = (int) ifd.getImageLength();
ms.rgb = samples > 1 || photo == PhotoInterp.RGB;
ms.pixelType = ifd.getPixelType();
ms.littleEndian = ifd.isLittleEndian();
ms.indexed = photo == PhotoInterp.RGB_PALETTE &&
ifd.containsKey(IFD.COLOR_MAP);
ms.imageCount = ifds[i].size();
if (ms.sizeZ * ms.sizeT * (ms.rgb ? 1 : ms.sizeC) !=
ms.imageCount)
{
ms.sizeT = ms.imageCount / (ms.rgb ? 1 : ms.sizeC);
ms.sizeZ = 1;
}
}
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this);
for (int i=0; i<seriesCount; i++) {
String[] row = rows.get(seriesIndexes.get(i));
// populate Image data
MetadataTools.setDefaultCreationDate(store, mtb, i);
store.setImageName(row[imageName].trim(), i);
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
// populate Dimensions data
// calculate physical X and Y sizes
double realWidth = Double.parseDouble(row[calibratedWidth]);
double realHeight = Double.parseDouble(row[calibratedHeight]);
String units = row[calibrationUnit];
CoreMetadata ms = core.get(i);
double px = realWidth / ms.sizeX;
double py = realHeight / ms.sizeY;
if (units.equals("mm")) {
px *= 1000;
py *= 1000;
}
// TODO : add cases for other units
Length physicalSizeX = FormatTools.getPhysicalSizeX(px);
Length physicalSizeY = FormatTools.getPhysicalSizeY(py);
if (physicalSizeX != null) {
store.setPixelsPhysicalSizeX(physicalSizeX, i);
}
if (physicalSizeY != null) {
store.setPixelsPhysicalSizeY(physicalSizeY, i);
}
}
}
}
// -- Helper methods --
/**
* Parse an integer from the given dimension String.
*
* @return the parsed integer, or 1 if parsing failed.
*/
private int parseDimension(String dim) {
try {
return Integer.parseInt(dim);
}
catch (NumberFormatException e) {
return 1;
}
}
}