//
// TileStitcher.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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import loci.common.Region;
import loci.formats.meta.IMetadata;
import loci.formats.meta.MetadataStore;
/**
*
* <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/TileStitcher.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/TileStitcher.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class TileStitcher extends ReaderWrapper {
// -- 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() */
public int getSizeX() {
return reader.getSizeX() * tileX;
}
/* @see IFormatReader#getSizeY() */
public int getSizeY() {
return reader.getSizeY() * tileY;
}
/* @see IFormatReader#getSeriesCount() */
public int getSeriesCount() {
if (tileX == 1 && tileY == 1) {
return reader.getSeriesCount();
}
return 1;
}
/* @see IFormatReader#openBytes(int) */
public byte[] openBytes(int no) throws FormatException, IOException {
return openBytes(no, 0, 0, getSizeX(), getSizeY());
}
/* @see IFormatReader#openBytes(int, byte[]) */
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) */
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 = new byte[w * h * ch * bpp];
return openBytes(no, newBuffer, x, y, w, h);
}
/* @see 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.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) */
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
if (meta.getPlateCount() > 0) {
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>();
ArrayList<Double> uniqueX = new ArrayList<Double>();
ArrayList<Double> uniqueY = new ArrayList<Double>();
boolean equalZs = true;
Double firstZ = meta.getPlanePositionZ(0, reader.getImageCount() - 1);
for (int i=0; i<reader.getSeriesCount(); i++) {
TileCoordinate coord = new TileCoordinate();
coord.x = meta.getPlanePositionX(i, reader.getImageCount() - 1);
coord.y = meta.getPlanePositionY(i, reader.getImageCount() - 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);
}
Double zPos = meta.getPlanePositionZ(i, reader.getImageCount() - 1);
if (firstZ == null) {
if (zPos != null) {
equalZs = false;
}
}
else {
if (!firstZ.equals(zPos)) {
equalZs = false;
}
}
}
tileX = uniqueX.size();
tileY = uniqueY.size();
if (!equalZs) {
tileX = 1;
tileY = 1;
return;
}
tileMap = new Integer[tileY][tileX];
Double[] xCoordinates = uniqueX.toArray(new Double[tileX]);
Arrays.sort(xCoordinates);
Double[] yCoordinates = uniqueY.toArray(new Double[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() */
public Class<?> getNativeDataType() {
return byte[].class;
}
// -- Helper classes --
class TileCoordinate {
public Double x;
public Double y;
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;
}
}
}