/*
* #%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.AssertJUnit.fail;
import java.io.File;
import loci.common.Location;
import loci.formats.ChannelFiller;
import loci.formats.ChannelSeparator;
import loci.formats.FormatTools;
import loci.formats.IFormatReader;
import loci.formats.ImageReader;
import loci.formats.MinMaxCalculator;
import loci.formats.ReaderWrapper;
import nl.javadude.assumeng.Assumption;
import nl.javadude.assumeng.AssumptionListener;
import org.perf4j.StopWatch;
import org.perf4j.slf4j.Slf4JStopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
/**
* Performs various <code>openBytes()</code> performance tests.
*
* @author Chris Allan <callan at blackcat dot ca>
*/
@Listeners(value = AssumptionListener.class)
public class OpenBytesPerformanceTest
{
private static final Logger LOGGER =
LoggerFactory.getLogger(OpenBytesPerformanceTest.class);
private String id;
private IFormatReader reader;
private int imageCount;
private int seriesCount;
private int sizeX;
private int sizeY;
private int sizeZ;
private int sizeC;
private int sizeT;
private int bpp;
private int topHalfSize;
private int bottomHalfSize;
private int topLeftQuarterSize;
private int topRightQuarterSize;
private int bottomLeftQuarterSize;
private int bottomRightQuarterSize;
private int optimalTileHeight;
private int optimalTileWidth;
private int planeSize;
private String filename;
private boolean memMap;
private boolean bigImage = false;
private void assertBlock(int blockSize, int posX, int posY, int width,
int height) throws Exception {
byte[] plane = new byte[planeSize];
byte[] buf = new byte[blockSize];
String planeDigest, bufDigest;
for (int i = 0; i < imageCount; i++) {
// Read the data as a full plane
reader.openBytes(i, plane);
// Read the data as a block
try {
reader.openBytes(i, buf, posX, posY, width, height);
} catch (Exception e) {
throw new RuntimeException(
String.format(
"openBytes(series:%d i:%d, buf.length:%d, x:%d, y:%d, w:%d, h:%d) "
+ "[sizeX: %d sizeY:%d bpp:%d] threw exception!",
reader.getSeries(), i, buf.length, posX, posY,
width, height, sizeX, sizeY, bpp), e);
}
// Compare hash digests
planeDigest = TestTools.md5(plane, sizeX, sizeY, posX, posY, width,
height, bpp);
bufDigest = TestTools.md5(buf, 0, width * height * bpp);
if (!planeDigest.equals(bufDigest)) {
fail(String.format("MD5:%d;%d len:%d %s != %s",
reader.getSeries(), i, blockSize, planeDigest,
bufDigest));
}
}
}
private void assertRows(int blockSize) throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
byte[] plane = new byte[planeSize];
byte[] buf = new byte[blockSize];
int maximumRowCount = buf.length / bpp / sizeX;
String planeDigest, bufDigest;
int pixelsToRead = sizeX * sizeY;
int width = sizeX, height, posX = 0, posY = 0, actualBufSize;
for (int i = 0; i < imageCount; i++) {
// Read the data as a full plane
reader.openBytes(i, plane);
int offset = 0;
while(pixelsToRead > 0) {
// Prepare our read metadata
height = maximumRowCount;
if ((posY + height) > sizeY)
{
height = sizeY - posY;
}
actualBufSize = bpp * height * width;
// Read the data as a block
try {
reader.openBytes(i, buf, posX, posY, width, height);
}
catch (Exception e) {
throw new RuntimeException(String.format(
"openBytes(series:%d i:%d, buf.length:%d, x:%d, y:%d, w:%d, " +
"h:%d) [sizeX: %d sizeY:%d bpp:%d] threw exception!",
series, i, buf.length, posX, posY, width, height, sizeX, sizeY,
bpp), e);
}
// Compare hash digests
planeDigest = TestTools.md5(plane, offset, actualBufSize);
bufDigest = TestTools.md5(buf, 0, actualBufSize);
if (!planeDigest.equals(bufDigest)) {
fail(String.format("MD5:%s;%d offset:%d len:%d %s != %s",
series, i, offset, actualBufSize, planeDigest, bufDigest));
}
// Update offsets, etc.
offset += actualBufSize;
posY += height;
pixelsToRead -= height * width;
}
}
}
}
private void assertSeries(int series) {
reader.setSeries(series);
sizeX = reader.getSizeX();
sizeY = reader.getSizeY();
sizeZ = reader.getSizeZ();
sizeC = reader.getSizeC();
sizeT = reader.getSizeT();
imageCount = reader.getImageCount();
bpp = FormatTools.getBytesPerPixel(reader.getPixelType());
planeSize = sizeX * sizeY * bpp;
topHalfSize = (sizeY / 2) * sizeX * bpp;
bottomHalfSize = (sizeY - (sizeY / 2)) * sizeX * bpp;
topLeftQuarterSize = (sizeY / 2) * (sizeX / 2) * bpp;
topRightQuarterSize = (sizeY / 2) * (sizeX - (sizeX / 2)) * bpp;
bottomLeftQuarterSize = (sizeY - (sizeY / 2)) * (sizeX / 2) * bpp;
bottomRightQuarterSize =
(sizeY - (sizeY / 2)) * (sizeX - (sizeX / 2)) * bpp;
if (!bigImage) {
bigImage = sizeX * sizeY > 9000000;
}
}
public boolean isNotBigImage() {
return !bigImage;
}
@Parameters({"id", "inMemory"})
@BeforeClass
public void init(String id, String inMemory) throws Exception {
this.id = id;
filename = new File(id).getName();
memMap = Boolean.parseBoolean(inMemory);
}
@AfterClass
public void tearDown() throws Exception {
Location.mapId(id, null);
reader.close();
}
@Test
public void setId() throws Exception {
reader = new ImageReader();
reader = new ChannelFiller(reader);
reader = new ChannelSeparator(reader);
reader = new MinMaxCalculator(reader);
if (memMap && reader.isSingleFile(id)) {
TestTools.mapFile(id);
}
StopWatch stopWatch = new Slf4JStopWatch();
reader.setId(id);
stopWatch.stop(String.format("%s.setId.%s",
((ReaderWrapper) reader).unwrap().getClass().getName(), filename));
seriesCount = reader.getSeriesCount();
}
@Test(dependsOnMethods={"setId"})
public void testOpenBytesAllTilesNewBuffer() throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
for (int image = 0; image < imageCount; image++) {
LOGGER.info("Reading from series {} image {}", series, image);
optimalTileWidth = reader.getOptimalTileWidth();
optimalTileHeight = reader.getOptimalTileHeight();
LOGGER.info("Optimal tile {}x{}", optimalTileWidth, optimalTileHeight);
int tilesWide = (int) Math.ceil((double) sizeX / optimalTileWidth);
int tilesHigh = (int) Math.ceil((double) sizeY / optimalTileHeight);
LOGGER.info("Tile counts {}x{}", tilesWide, tilesHigh);
int x, y = 0;
StopWatch stopWatch;
for (int tileX = 0; tileX < tilesWide; tileX++) {
for (int tileY = 0; tileY < tilesHigh; tileY++) {
x = tileX * optimalTileWidth;
y = tileY * optimalTileHeight;
int actualTileWidth =
(int) Math.min(optimalTileWidth, reader.getSizeX() - x);
int actualTileHeight =
(int) Math.min(optimalTileHeight, reader.getSizeY() - y);
LOGGER.info("Reading tile at {}x{}", x, y);
stopWatch = new Slf4JStopWatch(String.format(
"%s.alloc_tile.%s.[%d:%d]",
((ReaderWrapper) reader).unwrap().getClass().getName(),
filename, series, image));
reader.openBytes(0, x, y, actualTileWidth, actualTileHeight);
stopWatch.stop();
}
}
}
}
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testOpenBytesAllTilesPreAllocatedBuffer() throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
for (int image = 0; image < imageCount; image++) {
LOGGER.info("Reading from series {} image {}", series, image);
optimalTileWidth = reader.getOptimalTileWidth();
optimalTileHeight = reader.getOptimalTileHeight();
LOGGER.info("Optimal tile {}x{}", optimalTileWidth, optimalTileHeight);
int tilesWide = (int) Math.ceil((double) sizeX / optimalTileWidth);
int tilesHigh = (int) Math.ceil((double) sizeY / optimalTileHeight);
LOGGER.info("Tile counts {}x{}", tilesWide, tilesHigh);
int x, y = 0;
StopWatch stopWatch;
byte[] buf = new byte[optimalTileWidth * optimalTileHeight *
FormatTools.getBytesPerPixel(reader.getPixelType())];
LOGGER.info("Allocated buffer size: {}", buf.length);
for (int tileX = 0; tileX < tilesWide; tileX++) {
for (int tileY = 0; tileY < tilesHigh; tileY++) {
x = tileX * optimalTileWidth;
y = tileY * optimalTileHeight;
int actualTileWidth =
(int) Math.min(optimalTileWidth, reader.getSizeX() - x);
int actualTileHeight =
(int) Math.min(optimalTileHeight, reader.getSizeY() - y);
LOGGER.info("Reading tile at {}x{}", x, y);
stopWatch = new Slf4JStopWatch(String.format(
"%s.prealloc_tile.%s.[%d:%d]",
((ReaderWrapper) reader).unwrap().getClass().getName(),
filename, series, image));
reader.openBytes(image, buf, x, y, actualTileWidth,
actualTileHeight);
stopWatch.stop();
}
}
}
}
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testOpenBytesPlane() throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
byte[] plane = new byte[planeSize];
for (int i = 0; i < reader.getImageCount(); i++) {
reader.openBytes(i, plane);
}
}
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testOpenBytesHalfPlane() throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
byte[] plane = new byte[planeSize];
byte[] topHalfPlane = new byte[topHalfSize];
byte[] bottomHalfPlane = new byte[bottomHalfSize];
String planeDigest, halfPlaneDigest;
for (int i = 0; i < imageCount; i++) {
// Check the digest for the first half of the plane against a full
// plane
reader.openBytes(i, plane);
reader.openBytes(i, topHalfPlane, 0, 0, sizeX, sizeY / 2);
planeDigest = TestTools.md5(plane, 0, topHalfSize);
halfPlaneDigest = TestTools.md5(topHalfPlane, 0, topHalfSize);
if (!planeDigest.equals(halfPlaneDigest)) {
fail(String.format("First half MD5:%d;%d %s != %s",
series, i, planeDigest, halfPlaneDigest));
}
// Check the digest for the second half of the plane against a full
// plane
reader.openBytes(i, bottomHalfPlane, 0, sizeY / 2, sizeX,
sizeY - (sizeY / 2));
planeDigest = TestTools.md5(plane, topHalfSize, bottomHalfSize);
halfPlaneDigest = TestTools.md5(bottomHalfPlane, 0, bottomHalfSize);
if (!planeDigest.equals(halfPlaneDigest)) {
fail(String.format("Second half MD5:%d;%d %s != %s",
series, i, planeDigest, halfPlaneDigest));
}
}
}
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testQuartersActualSize() throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
assertBlock(topLeftQuarterSize, 0, 0, sizeX / 2, sizeY / 2);
assertBlock(topRightQuarterSize, sizeX / 2, 0,
sizeX - (sizeX / 2), sizeY / 2);
assertBlock(bottomLeftQuarterSize, 0, sizeY / 2,
sizeX / 2, (sizeY - (sizeY / 2)));
assertBlock(bottomRightQuarterSize, sizeX / 2, sizeY / 2,
sizeX - (sizeX / 2), sizeY - (sizeY / 2));
}
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testQuartersTwiceActualSize() throws Exception {
for (int series = 0; series < seriesCount; series++) {
assertSeries(series);
assertBlock(topLeftQuarterSize * 2, 0, 0, sizeX / 2, sizeY / 2);
assertBlock(topRightQuarterSize * 2, sizeX / 2, 0,
sizeX - (sizeX / 2), sizeY / 2);
assertBlock(bottomLeftQuarterSize * 2, 0, sizeY / 2,
sizeX / 2, (sizeY - (sizeY / 2)));
assertBlock(bottomRightQuarterSize * 2, sizeX / 2, sizeY / 2,
sizeX - (sizeX / 2), sizeY - (sizeY / 2));
}
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testOpenBytesBlocksByRow512KB() throws Exception {
assertRows(524288);
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testOpenBytesBlocksByRow1MB() throws Exception {
assertRows(1048576);
}
@Test(dependsOnMethods={"setId"})
@Assumption(methods = "isNotBigImage")
public void testOpenBytesBlocksByRowPlaneSize() throws Exception {
assertRows(sizeX * sizeY * bpp);
}
}