/*
* #%L
* OME Bio-Formats manual and automated test suite.
* %%
* Copyright (C) 2006 - 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.tests.testng;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import loci.tests.testng.TestTools.TileLoopIteration;
import ome.xml.model.enums.DimensionOrder;
import ome.xml.model.primitives.PositiveInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import loci.common.services.ServiceFactory;
import loci.formats.FormatTools;
import loci.formats.in.TiffReader;
import loci.formats.meta.IMetadata;
import loci.formats.out.TiffWriter;
import loci.formats.services.OMEXMLService;
import loci.formats.tiff.IFD;
import loci.formats.tiff.TiffCompression;
/**
* Test grinding in a multi-threaded environment a JPEG-2000 encoded TIFF.
*
* @author Chris Allan <callan at blackcat dot ca>
*/
public class Jpeg2000GrindTest {
private static final Logger LOGGER =
LoggerFactory.getLogger(Jpeg2000GrindTest.class);
public static final String PIXEL_TYPE = "uint16";
public static final int SIZE_X = 5000;
public static final int SIZE_Y = 4000;
public static final int SIZE_Z = 1;
public static final int SIZE_C = 3;
public static final int SIZE_T = 1;
public static final int TILE_WIDTH = 256;
public static final int TILE_HEIGHT = 256;
public static final int THREAD_POOL_SIZE = 2;
private int bytesPerPixel;
private Map<Integer, String> hashDigests = new HashMap<Integer, String>();
private TiffWriter writer;
private File id;
/** All the IFDs we have used for each "plane". */
private Map<Integer, IFD> ifds = new HashMap<Integer, IFD>();
/** Last IFD we used during a tile write operation. */
private int lastIFD;
/** Thread pool executor service. */
private ExecutorService pool;
/**
* Initializes the writer.
* @param output The file where to write the compressed data.
* @param compression The compression to use.
* @param bigTiff Pass <code>true</code> to set the <code>bigTiff</code>
* flag, <code>false</code> otherwise.
* @throws Exception Thrown if an error occurred.
*/
private void initializeWriter(String output, String compression,
boolean bigTiff)
throws Exception
{
ServiceFactory sf = new ServiceFactory();
OMEXMLService service = sf.getInstance(OMEXMLService.class);
IMetadata metadata = service.createOMEXMLMetadata();
metadata.setImageID("Image:0", 0);
metadata.setPixelsID("Pixels:0", 0);
metadata.setPixelsBinDataBigEndian(true, 0, 0);
metadata.setPixelsDimensionOrder(DimensionOrder.XYZCT, 0);
metadata.setPixelsType(
ome.xml.model.enums.PixelType.fromString(PIXEL_TYPE), 0);
metadata.setPixelsSizeX(new PositiveInteger(SIZE_X), 0);
metadata.setPixelsSizeY(new PositiveInteger(SIZE_Y), 0);
metadata.setPixelsSizeZ(new PositiveInteger(1), 0);
metadata.setPixelsSizeC(new PositiveInteger(1), 0);
metadata.setPixelsSizeT(new PositiveInteger(SIZE_Z * SIZE_C * SIZE_T), 0);
metadata.setChannelID("Channel:0", 0, 0);
metadata.setChannelSamplesPerPixel(new PositiveInteger(1), 0, 0);
writer = new TiffWriter();
writer.setMetadataRetrieve(metadata);
writer.setCompression(compression);
writer.setWriteSequentially(false);
writer.setInterleaved(true);
writer.setBigTiff(bigTiff);
writer.setId(output);
bytesPerPixel = FormatTools.getBytesPerPixel(PIXEL_TYPE);
}
@BeforeClass
public void setup() throws Exception {
id = File.createTempFile(Jpeg2000GrindTest.class.getName(), ".tif");
initializeWriter(id.getAbsolutePath(),
TiffCompression.JPEG_2000.getCodecName(),
false);
}
@AfterClass
public void tearDown() throws Exception {
writer.close();
id.delete();
}
@Test(enabled=true)
public void testPyramidWriteTiles() throws Exception {
pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
short tileCount = (short) TestTools.forEachTile(new TileLoopIteration() {
@Override
public void run(int z, int c, int t, int x, int y, int tileWidth,
int tileHeight, int tileCount) {
int planeNumber = FormatTools.getIndex(
"XYZCT", SIZE_Z, SIZE_C, SIZE_T, SIZE_Z * SIZE_C * SIZE_T, z, c, t);
if (planeNumber != lastIFD) {
pool.shutdown();
try {
while (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
LOGGER.warn("Waiting for runnables to complete...");
}
} catch (InterruptedException e) {
LOGGER.error("Caught interuption while waiting for termination.");
}
pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
lastIFD = planeNumber;
}
pool.submit(new TileRunnable(
writer, z, c, t, x, y, tileWidth, tileHeight, tileCount));
}
}, SIZE_X, SIZE_Y, SIZE_Z, SIZE_C, SIZE_T, TILE_WIDTH, TILE_HEIGHT);
pool.shutdown();
while (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
LOGGER.warn("Waiting for runnables to complete...");
}
assertEquals(tileCount, 960);
writer.close();
}
/*
@Test(enabled=true)
public void testPyramidWriteTiles() throws Exception {
short tileCount = (short) TestTools.forEachTile(new TileLoopIteration() {
public void run(int z, int c, int t, int x, int y, int tileWidth,
int tileHeight, int tileCount) {
byte[] tile = new byte[tileWidth * tileHeight * bytesPerPixel];
ByteBuffer.wrap(tile).asShortBuffer().put(0, (short) tileCount);
hashDigests.put(tileCount, TestTools.md5(tile));
int planeNumber = FormatTools.getIndex(
"XYZCT", SIZE_Z, SIZE_C, SIZE_T, SIZE_Z * SIZE_C * SIZE_T, z, c, t);
IFD ifd;
synchronized (ifds) {
if (!ifds.containsKey(planeNumber)) {
ifd = new IFD();
ifd.put(IFD.TILE_WIDTH, TILE_WIDTH);
ifd.put(IFD.TILE_LENGTH, TILE_HEIGHT);
ifds.put(planeNumber, ifd);
}
ifd = ifds.get(planeNumber);
}
try {
writer.saveBytes(planeNumber, tile, ifd, x, y, tileWidth, tileHeight);
} catch (Exception e) {
throw new RuntimeException(e);
}
tileCount++;
}
}, SIZE_X, SIZE_Y, SIZE_Z, SIZE_C, SIZE_T, TILE_WIDTH, TILE_HEIGHT);
assertEquals(tileCount, 960);
writer.close();
}
*/
@Test(dependsOnMethods={"testPyramidWriteTiles"}, enabled=true)
public void testPyramidReadTilesMultiThreaded() throws Exception {
pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
for (int theC = 0; theC < SIZE_C; theC++) {
pool.execute(new ChannelRunnable(theC));
}
pool.shutdown();
while (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
LOGGER.warn("Waiting for channel runnables to complete...");
}
}
class TileRunnable implements Runnable {
private int tileNumber;
private TiffWriter writer;
private int z;
private int c;
private int t;
private int x;
private int y;
private int tileWidth;
private int tileHeight;
public TileRunnable(TiffWriter writer, int z, int c, int t, int x, int y,
int tileWidth, int tileHeight, int tileNumber) {
this.z = z;
this.c = c;
this.t = t;
this.x = x;
this.y = y;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.writer = writer;
this.tileNumber = tileNumber;
}
@Override
public void run() {
byte[] tile = new byte[tileWidth * tileHeight * bytesPerPixel];
ByteBuffer.wrap(tile).asShortBuffer().put(0, (short) tileNumber);
hashDigests.put(tileNumber, TestTools.md5(tile));
int planeNumber = FormatTools.getIndex(
"XYZCT", SIZE_Z, SIZE_C, SIZE_T, SIZE_Z * SIZE_C * SIZE_T, z, c, t);
IFD ifd;
synchronized (ifds) {
if (!ifds.containsKey(planeNumber)) {
ifd = new IFD();
ifd.put(IFD.TILE_WIDTH, TILE_WIDTH);
ifd.put(IFD.TILE_LENGTH, TILE_HEIGHT);
ifds.put(planeNumber, ifd);
}
ifd = ifds.get(planeNumber);
}
try {
writer.saveBytes(planeNumber, tile, ifd, x, y, tileWidth, tileHeight);
} catch (Exception e) {
LOGGER.error("Exception while writing tile", e);
throw new RuntimeException(e);
}
}
}
class ChannelRunnable implements Runnable {
private int theC;
public ChannelRunnable(int theC) {
this.theC = theC;
}
@Override
public void run() {
final TiffReader reader = new TiffReader();
try {
reader.setId(id.getAbsolutePath());
}
catch (Exception e) {
throw new RuntimeException(e);
}
assertEquals(reader.getImageCount(), SIZE_Z * SIZE_C * SIZE_T);
assertEquals(reader.getSeriesCount(), 6);
short tileCount = (short) TestTools.forEachTile(new TileLoopIteration() {
@Override
public void run(int z, int c, int t, int x, int y, int tileWidth,
int tileHeight, int tileCount) {
try {
tileCount += theC * 320;
int planeNumber = FormatTools.getIndex(
"XYZCT", SIZE_Z, SIZE_C, SIZE_T, SIZE_Z * SIZE_C * SIZE_T,
z, theC, t);
byte[] tile = null;
try {
tile = reader.openBytes(planeNumber, x, y, tileWidth,
tileHeight);
}
catch (Throwable throwable) {
fail(String.format("Failure reading tile z:%d c:%d t:%d "
+ "x:%d y:%d", z, theC, t, x, y), throwable);
}
String readDigest = TestTools.md5(tile);
String writtenDigest = hashDigests.get(tileCount);
if (!writtenDigest.equals(readDigest)) {
fail(String.format("Hash digest mismatch z:%d c:%d t:%d "
+ "x:%d y:%d -- %s != %s", z, theC, t, x, y, writtenDigest,
readDigest));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}, SIZE_X, SIZE_Y, SIZE_Z, 1, SIZE_T, TILE_WIDTH, TILE_HEIGHT);
assertEquals(tileCount, 320);
}
}
}