Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update progress listener API to provide tile counts at each start notification #221

Merged
merged 3 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 120 additions & 38 deletions src/main/java/com/glencoesoftware/bioformats2raw/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ public class Converter implements Callable<Integer> {
private volatile Path outputPath;

private IProgressListener progressListener;
private Map<Integer, int[]> tileCounts = new HashMap<Integer, int[]>();

// Option setters

Expand Down Expand Up @@ -1337,6 +1338,17 @@ public void convert()
root.writeAttributes(attributes);
}

// pre-calculate resolution and tile counts
long totalTiles = 0;
for (Integer index : seriesList) {
int[] counts = calculateTileCounts(index);
tileCounts.put(index, counts);
for (int count : counts) {
totalTiles += count;
}
}
getProgressListener().notifyStart(seriesList.size(), totalTiles);

for (Integer index : seriesList) {
try {
write(index);
Expand Down Expand Up @@ -1375,6 +1387,105 @@ public void convert()
}
}

/**
* Pre-calculate the number of resolutions and tiles for the
* given series index. This is useful for accurate progress reporting
* and failing faster for incompatible pixel/downsampling combinations.
*
* @param series series index
* @return array of tile counts; the array length is the number of resolutions
*/
private int[] calculateTileCounts(int series)
throws FormatException, IOException, InterruptedException,
EnumerationException
{
readers.forEach((reader) -> {
reader.setSeries(series);
});

IFormatReader workingReader = readers.take();
int resolutions = 1;
int sizeX;
int sizeY;
int sizeZ;
int imageCount;
try {
// calculate a reasonable pyramid depth if not specified as an argument
sizeX = workingReader.getSizeX();
sizeY = workingReader.getSizeY();
if (pyramidResolutions == null) {
if (workingReader.getResolutionCount() > 1
&& reuseExistingResolutions)
{
resolutions = workingReader.getResolutionCount();
}
else {
resolutions = calculateResolutions(sizeX, sizeY);
}
}
else {
resolutions = pyramidResolutions;

// check to make sure too many resolutions aren't being used
if ((int) (sizeX / Math.pow(PYRAMID_SCALE, resolutions)) == 0 ||
(int) (sizeY / Math.pow(PYRAMID_SCALE, resolutions)) == 0)
{
resolutions = calculateResolutions(sizeX, sizeY);
LOGGER.warn("Too many resolutions specified; reducing to {}",
resolutions);
}
}
LOGGER.info("Using {} pyramid resolutions", resolutions);
sizeZ = workingReader.getSizeZ();
imageCount = workingReader.getImageCount();
pixelType = workingReader.getPixelType();
}
finally {
readers.put(workingReader);
}

int[] resTileCounts = new int[resolutions];

if ((pixelType == FormatTools.INT8 || pixelType == FormatTools.INT32) &&
getDownsampling() != Downsampling.SIMPLE && resolutions > 0)
{
String type = FormatTools.getPixelTypeString(pixelType);
throw new UnsupportedOperationException(
"OpenCV does not support downsampling " + type + " data. " +
"See https://github.com/opencv/opencv/issues/7862");
}

for (int resCounter=0; resCounter<resolutions; resCounter++) {
final int resolution = resCounter;
int scale = (int) Math.pow(PYRAMID_SCALE, resolution);
int scaledWidth = sizeX / scale;
int scaledHeight = sizeY / scale;
int scaledDepth = sizeZ;

workingReader = readers.take();
try {
if (workingReader.getResolutionCount() > 1
&& reuseExistingResolutions)
{
workingReader.setResolution(resCounter);
scaledWidth = workingReader.getSizeX();
scaledHeight = workingReader.getSizeY();
scaledDepth = workingReader.getSizeZ();
}
}
finally {
readers.put(workingReader);
}

resTileCounts[resCounter] =
(int) Math.ceil((double) scaledWidth / tileWidth)
* (int) Math.ceil((double) scaledHeight / tileHeight)
* (int) Math.ceil((double) scaledDepth / chunkDepth)
* (imageCount / sizeZ);
}
return resTileCounts;
}

/**
* Convert the data specified by the given initialized reader to
* an intermediate form.
Expand Down Expand Up @@ -1835,9 +1946,15 @@ public void saveResolutions(int series)
throws FormatException, IOException, InterruptedException,
EnumerationException
{
getProgressListener().notifySeriesStart(series);
int[] resTileCounts = tileCounts.get(series);
int resolutions = resTileCounts.length;
int seriesTiles = 0;
for (int t : resTileCounts) {
seriesTiles += t;
}
getProgressListener().notifySeriesStart(series, resolutions, seriesTiles);

IFormatReader workingReader = readers.take();
int resolutions = 1;
int sizeX;
int sizeY;
int sizeZ;
Expand All @@ -1849,29 +1966,6 @@ public void saveResolutions(int series)
// calculate a reasonable pyramid depth if not specified as an argument
sizeX = workingReader.getSizeX();
sizeY = workingReader.getSizeY();
if (pyramidResolutions == null) {
if (workingReader.getResolutionCount() > 1
&& reuseExistingResolutions)
{
resolutions = workingReader.getResolutionCount();
}
else {
resolutions = calculateResolutions(sizeX, sizeY);
}
}
else {
resolutions = pyramidResolutions;

// check to make sure too many resolutions aren't being used
if ((int) (sizeX / Math.pow(PYRAMID_SCALE, resolutions)) == 0 ||
(int) (sizeY / Math.pow(PYRAMID_SCALE, resolutions)) == 0)
{
resolutions = calculateResolutions(sizeX, sizeY);
LOGGER.warn("Too many resolutions specified; reducing to {}",
resolutions);
}
}
LOGGER.info("Using {} pyramid resolutions", resolutions);
sizeZ = workingReader.getSizeZ();
sizeT = workingReader.getSizeT();
sizeC = workingReader.getSizeC();
Expand All @@ -1883,15 +1977,6 @@ public void saveResolutions(int series)
readers.put(workingReader);
}

if ((pixelType == FormatTools.INT8 || pixelType == FormatTools.INT32) &&
getDownsampling() != Downsampling.SIMPLE && resolutions > 0)
{
String type = FormatTools.getPixelTypeString(pixelType);
throw new UnsupportedOperationException(
"OpenCV does not support downsampling " + type + " data. " +
"See https://github.com/opencv/opencv/issues/7862");
}

LOGGER.info(
"Preparing to write pyramid sizeX {} (tileWidth: {}) " +
"sizeY {} (tileWidth: {}) sizeZ {} (tileDepth: {}) imageCount {}",
Expand Down Expand Up @@ -1958,10 +2043,7 @@ public void saveResolutions(int series)
ZarrArray.create(getRootPath().resolve(resolutionString), arrayParams);

nTile = new AtomicInteger(0);
tileCount = (int) Math.ceil((double) scaledWidth / tileWidth)
* (int) Math.ceil((double) scaledHeight / tileHeight)
* (int) Math.ceil((double) scaledDepth / chunkDepth)
* (imageCount / sizeZ);
tileCount = resTileCounts[resolution];

List<CompletableFuture<Void>> futures =
new ArrayList<CompletableFuture<Void>>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,23 @@

public interface IProgressListener extends EventListener {

/**
* Indicates the total number of chunks in this conversion operation.
* Includes all resolutions in all series.
*
* @param seriesCount total number of series
* @param chunkCount total number of chunks
*/
void notifyStart(int seriesCount, long chunkCount);

/**
* Indicates the beginning of processing a particular series.
*
* @param series the series index being processed
* @param resolutionCount total number of resolutions in this series
* @param chunkCount total number of chunks for all resolutions in this series
*/
void notifySeriesStart(int series);
void notifySeriesStart(int series, int resolutionCount, int chunkCount);

/**
* Indicates the end of processing a particular series.
Expand All @@ -29,9 +40,9 @@ public interface IProgressListener extends EventListener {
* Indicates the beginning of processing a particular resolution.
*
* @param resolution the resolution index being processed
* @param tileCount the total number of tiles in this resolution
* @param chunkCount the total number of chunks in this resolution
*/
void notifyResolutionStart(int resolution, int tileCount);
void notifyResolutionStart(int resolution, int chunkCount);

/**
* Indicates the end of processing a particular resolution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@
public class NoOpProgressListener implements IProgressListener {

@Override
public void notifySeriesStart(int series) {
public void notifyStart(int seriesCount, long chunkCount) {
}

@Override
public void notifySeriesStart(int series, int resolutionCount,
int chunkCount)
{
}

@Override
public void notifySeriesEnd(int series) {
}

@Override
public void notifyResolutionStart(int resolution, int tileCount) {
public void notifyResolutionStart(int resolution, int chunkCount) {
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,27 @@ public ProgressBarListener(String level) {
logLevel = level;
}

@Override
public void notifyStart(int seriesCount, long chunkCount) {
// intentional no-op
}

@Override
public void notifySeriesStart(int series) {
public void notifySeriesStart(int series, int resolutionCount,
int chunkCount)
{
currentSeries = series;
}

@Override
public void notifySeriesEnd(int series) {
// intentional no-op
}

@Override
public void notifyResolutionStart(int resolution, int tileCount) {
public void notifyResolutionStart(int resolution, int chunkCount) {
ProgressBarBuilder builder = new ProgressBarBuilder()
.setInitialMax(tileCount)
.setInitialMax(chunkCount)
.setTaskName(String.format("[%d/%d]", currentSeries, resolution));

if (!(logLevel.equals("OFF") ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ public class TestProgressListener implements IProgressListener {
private int startedTiles = 0;
private int completedTiles = 0;
private int expectedTileCount = 0;
private int seriesTiles = 0;
private long totalTiles = 0;

@Override
public void notifySeriesStart(int series) {
public void notifyStart(int seriesCount, long tileCount) {
totalTiles = tileCount;
}

@Override
public void notifySeriesStart(int series, int res, int tiles) {
seriesTiles = tiles;
}

@Override
Expand Down Expand Up @@ -60,8 +68,22 @@ public void notifyResolutionEnd(int resolution) {
*
* @return an array with one element per resolution
*/
public Integer[] getTileCounts() {
public Integer[] getChunkCounts() {
return finishedResolutions.toArray(new Integer[finishedResolutions.size()]);
}

/**
* @return the reported number of tiles for the most recent series
*/
public int getSeriesChunkCount() {
return seriesTiles;
}

/**
* @return the reported number of total tiles for the conversion
*/
public long getTotalChunkCount() {
return totalTiles;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -708,9 +708,15 @@ public void testProgressListener() throws Exception {
throw new RuntimeException(t);
}

Integer[] expectedTileCounts = new Integer[] {320, 80, 20, 5, 5, 5};
Integer[] tileCounts = listener.getTileCounts();
assertArrayEquals(expectedTileCounts, tileCounts);
Integer[] expectedChunkCounts = new Integer[] {320, 80, 20, 5, 5, 5};
Integer[] chunkCounts = listener.getChunkCounts();
assertArrayEquals(expectedChunkCounts, chunkCounts);
long totalChunkCount = 0;
for (Integer t : expectedChunkCounts) {
totalChunkCount += t;
}
assertEquals(totalChunkCount, listener.getTotalChunkCount());
assertEquals(totalChunkCount, listener.getSeriesChunkCount());
}

private int bytesPerPixel(DataType dataType) {
Expand Down