/*
* #%L
* OME Bio-Formats package for reading and converting biological file formats.
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package loci.formats.in;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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.MetadataStore;
import ome.units.quantity.Length;
import ome.xml.model.primitives.PositiveFloat;
import ome.xml.model.primitives.PositiveInteger;
import ome.xml.model.primitives.Timestamp;
/**
* L2DReader is the file format reader for Li-Cor L2D datasets.
*/
public class L2DReader extends FormatReader {
// -- Constants --
public static final String DATE_FORMAT = "yyyy, m, d";
private static final String LICOR_MAGIC_STRING = "LI-COR LI2D";
// -- Fields --
/** List of constituent TIFF files. */
private String[][] tiffs;
/** List of all metadata files in the dataset. */
private List<String>[] metadataFiles;
private MinimalTiffReader reader;
private int[] tileWidth, tileHeight;
// -- Constructor --
/** Construct a new L2D reader. */
public L2DReader() {
super("Li-Cor L2D", new String[] {"l2d", "scn", "tif"});
domains = new String[] {FormatTools.GEL_DOMAIN};
hasCompanionFiles = true;
suffixSufficient = false;
datasetDescription = "One .l2d file with one or more directories " +
"containing .tif/.tiff files";
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(String, boolean) */
@Override
public boolean isThisType(String name, boolean open) {
if (checkSuffix(name, "l2d") || checkSuffix(name, "scn")) {
return super.isThisType(name, open);
}
if (!open) return false;
Location location = new Location(name);
if (!location.exists()) {
return false;
}
Location parent = location.getAbsoluteFile().getParentFile();
String scanName = location.getName();
if (scanName.indexOf("_") >= 0) {
scanName = scanName.substring(0, scanName.lastIndexOf("_"));
}
boolean hasScan = new Location(parent, scanName + ".scn").exists();
return hasScan && new Location(parent, scanName).exists();
}
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = (int) Math.min(512, stream.length());
if (!FormatTools.validStream(stream, blockLen, false)) return false;
String check = stream.readString(blockLen);
return check.indexOf(LICOR_MAGIC_STRING) >= 0;
}
/* @see loci.formats.IFormatReader#isSingleFile(String) */
@Override
public boolean isSingleFile(String id) throws FormatException, IOException {
return false;
}
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*/
@Override
public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
reader.setId(tiffs[getSeries()][no]);
return reader.openBytes(0, buf, x, y, w, h);
}
/* @see loci.formats.IFormatReader#fileGroupOption(String) */
@Override
public int fileGroupOption(String id) throws FormatException, IOException {
return FormatTools.MUST_GROUP;
}
/* @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.add(currentId);
if (metadataFiles != null && getSeries() < metadataFiles.length) {
files.addAll(metadataFiles[getSeries()]);
}
if (!noPixels) {
for (String tiff : tiffs[getSeries()]) {
files.add(tiff);
}
}
return files.toArray(new String[files.size()]);
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (reader != null) reader.close(fileOnly);
if (!fileOnly) {
tiffs = null;
reader = null;
metadataFiles = null;
tileWidth = null;
tileHeight = null;
}
}
/* @see loci.formats.IFormatReader#getOptimalTileWidth() */
@Override
public int getOptimalTileWidth() {
FormatTools.assertId(currentId, true, 1);
return tileWidth[getSeries()];
}
/* @see loci.formats.IFormatReader#getOptimalTileHeight() */
@Override
public int getOptimalTileHeight() {
FormatTools.assertId(currentId, true, 1);
return tileHeight[getSeries()];
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
// NB: This format cannot be imported using omebf.
// See Trac ticket #266 for details.
if (!checkSuffix(id, "l2d") && isGroupFiles()) {
// find the corresponding .l2d file
Location parent = new Location(id).getAbsoluteFile().getParentFile();
parent = parent.getParentFile();
String[] list = parent.list();
for (String file : list) {
if (checkSuffix(file, "l2d")) {
initFile(new Location(parent, file).getAbsolutePath());
return;
}
}
throw new FormatException("Could not find .l2d file");
}
else if (!isGroupFiles()) {
super.initFile(id);
tiffs = new String[][] {{id}};
TiffReader r = new TiffReader();
r.setMetadataStore(getMetadataStore());
r.setId(id);
core = new ArrayList<CoreMetadata>(r.getCoreMetadataList());
metadataStore = r.getMetadataStore();
final Map<String, Object> globalMetadata = r.getGlobalMetadata();
for (final Map.Entry<String, Object> entry : globalMetadata.entrySet()) {
addGlobalMeta(entry.getKey(), entry.getValue());
}
r.close();
reader = new MinimalTiffReader();
return;
}
super.initFile(id);
String[] scans = getScanNames();
Location parent = new Location(id).getAbsoluteFile().getParentFile();
// remove scan names that do not correspond to existing directories
final List<String> validScans = new ArrayList<String>();
for (String s : scans) {
Location scanDir = new Location(parent, s);
if (scanDir.exists() && scanDir.isDirectory()) validScans.add(s);
}
scans = validScans.toArray(new String[validScans.size()]);
// read metadata from each scan
tiffs = new String[scans.length][];
metadataFiles = new List[scans.length];
core = new ArrayList<CoreMetadata>(scans.length);
String[] comments = new String[scans.length];
String[] wavelengths = new String[scans.length];
String[] dates = new String[scans.length];
String model = null;
tileWidth = new int[scans.length];
tileHeight = new int[scans.length];
core.clear();
for (int i=0; i<scans.length; i++) {
CoreMetadata ms = new CoreMetadata();
core.add(ms);
setSeries(i);
metadataFiles[i] = new ArrayList<String>();
String scanName = scans[i] + ".scn";
Location scanDir = new Location(parent, scans[i]);
// read .scn file from each scan
String scanPath = new Location(scanDir, scanName).getAbsolutePath();
addDirectory(scanDir.getAbsolutePath(), i);
String scanData = DataTools.readFile(scanPath);
String[] lines = scanData.split("\n");
for (String line : lines) {
if (!line.startsWith("#")) {
String key = line.substring(0, line.indexOf("="));
String value = line.substring(line.indexOf("=") + 1);
addSeriesMeta(key, value);
if (key.equals("ExperimentNames")) {
// TODO : parse experiment metadata - this is typically a list of
// overlay shapes, or analysis data
}
else if (key.equals("ImageNames")) {
tiffs[i] = value.split(",");
for (int t=0; t<tiffs[i].length; t++) {
tiffs[i][t] =
new Location(scanDir, tiffs[i][t].trim()).getAbsolutePath();
}
}
else if (key.equals("Comments")) {
comments[i] = value;
}
else if (key.equals("ScanDate")) {
dates[i] = value;
}
else if (key.equals("ScannerName")) {
model = value;
}
else if (key.equals("ScanChannels")) {
wavelengths[i] = value;
}
}
}
}
setSeries(0);
reader = new MinimalTiffReader();
MetadataStore store = makeFilterMetadata();
for (int i=0; i<getSeriesCount(); i++) {
CoreMetadata ms = core.get(i);
ms.imageCount = tiffs[i].length;
ms.sizeC = tiffs[i].length;
ms.sizeT = 1;
ms.sizeZ = 1;
ms.dimensionOrder = "XYCZT";
reader.setId(tiffs[i][0]);
ms.sizeX = reader.getSizeX();
ms.sizeY = reader.getSizeY();
ms.sizeC *= reader.getSizeC();
ms.rgb = reader.isRGB();
ms.indexed = reader.isIndexed();
ms.littleEndian = reader.isLittleEndian();
ms.pixelType = reader.getPixelType();
tileWidth[i] = reader.getOptimalTileWidth();
tileHeight[i] = reader.getOptimalTileHeight();
}
MetadataTools.populatePixels(store, this);
for (int i=0; i<getSeriesCount(); i++) {
store.setImageName(scans[i], i);
if (dates[i] != null) {
dates[i] = DateTools.formatDate(dates[i], DATE_FORMAT);
if (dates[i] != null) {
store.setImageAcquisitionDate(new Timestamp(dates[i]), i);
}
}
}
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
String instrumentID = MetadataTools.createLSID("Instrument", 0);
store.setInstrumentID(instrumentID, 0);
for (int i=0; i<getSeriesCount(); i++) {
store.setImageInstrumentRef(instrumentID, i);
store.setImageDescription(comments[i], i);
if (wavelengths[i] != null) {
String[] waves = wavelengths[i].split("[, ]");
if (waves.length < getEffectiveSizeC()) {
LOGGER.debug("Expected {} wavelengths; got {} wavelengths.",
getEffectiveSizeC(), waves.length);
}
for (int q=0; q<waves.length; q++) {
String laser = MetadataTools.createLSID("LightSource", 0, q);
store.setLaserID(laser, 0, q);
Double wave = new Double(waves[q].trim());
Length wavelength = FormatTools.getWavelength(wave);
if (wavelength != null) {
store.setLaserWavelength(wavelength, 0, q);
}
store.setLaserType(getLaserType("Other"), 0, q);
store.setLaserLaserMedium(getLaserMedium("Other"), 0, q);
store.setChannelLightSourceSettingsID(laser, i, q);
}
}
}
store.setMicroscopeModel(model, 0);
store.setMicroscopeType(getMicroscopeType("Other"), 0);
}
}
// -- Helper methods --
/**
* Recursively add all of the files in the given directory to the
* used file list.
*/
private void addDirectory(String path, int series) {
Location dir = new Location(path);
String[] files = dir.list();
if (files == null) return;
for (String f : files) {
Location file = new Location(path, f);
if (file.isDirectory()) {
addDirectory(file.getAbsolutePath(), series);
}
else if (checkSuffix(f, "scn")) {
metadataFiles[series].add(file.getAbsolutePath());
}
}
}
/** Return a list of scan names. */
private String[] getScanNames() throws IOException {
String[] scans = null;
String data = DataTools.readFile(currentId);
String[] lines = data.split("\n");
for (String line : lines) {
if (!line.startsWith("#")) {
String key = line.substring(0, line.indexOf("=")).trim();
String value = line.substring(line.indexOf("=") + 1).trim();
addGlobalMeta(key, value);
if (key.equals("ScanNames")) {
scans = value.split(",");
for (int i=0; i<scans.length; i++) {
scans[i] = scans[i].trim();
}
}
}
}
return scans;
}
}