/*
* #%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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import loci.common.DataTools;
import loci.common.Region;
import loci.formats.meta.IMetadata;
import loci.formats.meta.MetadataStore;
import ome.units.quantity.Length;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*/
public class TileStitcher extends ReaderWrapper {
// -- Constants --
private static final Logger LOGGER =
LoggerFactory.getLogger(TileStitcher.class);
// -- Fields --
private int tileX = 0;
private int tileY = 0;
private Integer[][] tileMap;
// -- Utility methods --
/** Converts the given reader into a TileStitcher, wrapping if needed. */
public static TileStitcher makeTileStitcher(IFormatReader r) {
if (r instanceof TileStitcher) return (TileStitcher) r;
return new TileStitcher(r);
}
// -- Constructor --
/** Constructs a TileStitcher around a new image reader. */
public TileStitcher() { super(); }
/** Constructs a TileStitcher with the given reader. */
public TileStitcher(IFormatReader r) { super(r); }
// -- IFormatReader API methods --
/* @see IFormatReader#getSizeX() */
@Override
public int getSizeX() {
return reader.getSizeX() * tileX;
}
/* @see IFormatReader#getSizeY() */
@Override
public int getSizeY() {
return reader.getSizeY() * tileY;
}
/* @see IFormatReader#getSeriesCount() */
@Override
public int getSeriesCount() {
if (tileX == 1 && tileY == 1) {
return reader.getSeriesCount();
}
return 1;
}
/* @see IFormatReader#openBytes(int) */
@Override
public byte[] openBytes(int no) throws FormatException, IOException {
return openBytes(no, 0, 0, getSizeX(), getSizeY());
}
/* @see IFormatReader#openBytes(int, byte[]) */
@Override
public byte[] openBytes(int no, byte[] buf)
throws FormatException, IOException
{
return openBytes(no , buf, 0, 0, getSizeX(), getSizeY());
}
/* @see IFormatReader#openBytes(int, int, int, int, int) */
@Override
public byte[] openBytes(int no, int x, int y, int w, int h)
throws FormatException, IOException
{
int bpp = FormatTools.getBytesPerPixel(getPixelType());
int ch = getRGBChannelCount();
byte[] newBuffer = DataTools.allocate(w, h, ch, bpp);
return openBytes(no, newBuffer, x, y, w, h);
}
/* @see 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.assertId(getCurrentFile(), true, 2);
if (tileX == 1 && tileY == 1) {
return super.openBytes(no, buf, x, y, w, h);
}
byte[] tileBuf = new byte[buf.length / tileX * tileY];
int tw = reader.getSizeX();
int th = reader.getSizeY();
Region image = new Region(x, y, w, h);
int pixelType = getPixelType();
int pixel = getRGBChannelCount() * FormatTools.getBytesPerPixel(pixelType);
int outputRowLen = w * pixel;
int outputRow = 0, outputCol = 0;
Region intersection = null;
for (int ty=0; ty<tileY; ty++) {
for (int tx=0; tx<tileX; tx++) {
Region tile = new Region(tx * tw, ty * th, tw, th);
if (!tile.intersects(image)) {
continue;
}
intersection = tile.intersection(image);
int rowLen = pixel * (int) Math.min(intersection.width, tw);
if (tileMap[ty][tx] == null) {
outputCol += rowLen;
continue;
}
reader.setSeries(tileMap[ty][tx]);
reader.openBytes(no, tileBuf, 0, 0, tw, th);
int outputOffset = outputRowLen * outputRow + outputCol;
for (int row=0; row<intersection.height; row++) {
int realRow = row + intersection.y - tile.y;
int inputOffset = pixel * (realRow * tw + tx);
System.arraycopy(tileBuf, inputOffset, buf, outputOffset, rowLen);
outputOffset += outputRowLen;
}
outputCol += rowLen;
}
if (intersection != null) {
outputRow += intersection.height;
outputCol = 0;
}
}
return buf;
}
/* @see IFormatReader#setId(String) */
@Override
public void setId(String id) throws FormatException, IOException {
super.setId(id);
MetadataStore store = getMetadataStore();
if (!(store instanceof IMetadata) || reader.getSeriesCount() == 1) {
tileX = 1;
tileY = 1;
return;
}
IMetadata meta = (IMetadata) store;
// don't even think about stitching HCS data, as it quickly gets complicated
//
// it might be worth improving this in the future so that fields are
// stitched, but plates/wells are left alone, but for now it is easy
// enough to just ignore HCS data with multiple plates and/or wells
if (meta.getPlateCount() > 1 ||
(meta.getPlateCount() == 1 && meta.getWellCount(0) > 1))
{
tileX = 1;
tileY = 1;
return;
}
// now make sure that all of the series have the same dimensions
boolean equalDimensions = true;
for (int i=1; i<meta.getImageCount(); i++) {
if (!meta.getPixelsSizeX(i).equals(meta.getPixelsSizeX(0))) {
equalDimensions = false;
}
if (!meta.getPixelsSizeY(i).equals(meta.getPixelsSizeY(0))) {
equalDimensions = false;
}
if (!meta.getPixelsSizeZ(i).equals(meta.getPixelsSizeZ(0))) {
equalDimensions = false;
}
if (!meta.getPixelsSizeC(i).equals(meta.getPixelsSizeC(0))) {
equalDimensions = false;
}
if (!meta.getPixelsSizeT(i).equals(meta.getPixelsSizeT(0))) {
equalDimensions = false;
}
if (!meta.getPixelsType(i).equals(meta.getPixelsType(0))) {
equalDimensions = false;
}
if (!equalDimensions) break;
}
if (!equalDimensions) {
tileX = 1;
tileY = 1;
return;
}
ArrayList<TileCoordinate> tiles = new ArrayList<TileCoordinate>();
final List<Length> uniqueX = new ArrayList<Length>();
final List<Length> uniqueY = new ArrayList<Length>();
boolean equalZs = true;
final Length firstZ = meta.getPlanePositionZ(0, meta.getPlaneCount(0) - 1);
for (int i=0; i<reader.getSeriesCount(); i++) {
TileCoordinate coord = new TileCoordinate();
coord.x = meta.getPlanePositionX(i, meta.getPlaneCount(i) - 1);
coord.y = meta.getPlanePositionY(i, meta.getPlaneCount(i) - 1);
tiles.add(coord);
if (coord.x != null && !uniqueX.contains(coord.x)) {
uniqueX.add(coord.x);
}
if (coord.y != null && !uniqueY.contains(coord.y)) {
uniqueY.add(coord.y);
}
final Length zPos = meta.getPlanePositionZ(i, meta.getPlaneCount(i) - 1);
if (firstZ == null) {
if (zPos != null) {
equalZs = false;
}
}
else {
if (!firstZ.equals(zPos)) {
equalZs = false;
}
}
}
tileX = uniqueX.size();
tileY = uniqueY.size();
if (!equalZs) {
LOGGER.warn("Z positions not equal");
}
tileMap = new Integer[tileY][tileX];
final Length[] xCoordinates = uniqueX.toArray(new Length[tileX]);
Arrays.sort(xCoordinates);
final Length[] yCoordinates = uniqueY.toArray(new Length[tileY]);
Arrays.sort(yCoordinates);
for (int row=0; row<tileMap.length; row++) {
for (int col=0; col<tileMap[row].length; col++) {
TileCoordinate coordinate = new TileCoordinate();
coordinate.x = xCoordinates[col];
coordinate.y = yCoordinates[row];
for (int tile=0; tile<tiles.size(); tile++) {
if (tiles.get(tile).equals(coordinate)) {
tileMap[row][col] = tile;
}
}
}
}
}
// -- IFormatHandler API methods --
/* @see IFormatHandler#getNativeDataType() */
@Override
public Class<?> getNativeDataType() {
return byte[].class;
}
// -- Helper classes --
class TileCoordinate {
public Length x;
public Length y;
@Override
public boolean equals(Object o) {
if (!(o instanceof TileCoordinate)) {
return false;
}
TileCoordinate tile = (TileCoordinate) o;
boolean xEqual = x == null ? tile.x == null : x.equals(tile.x);
boolean yEqual = y == null ? tile.y == null : y.equals(tile.y);
return xEqual && yEqual;
}
}
}