-
Notifications
You must be signed in to change notification settings - Fork 244
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
Allow reading and writing compressed tiles #3992
Changes from all commits
ec17e0d
082b95a
6c06e96
5c06784
f2cc370
6f676d9
a67df0f
fde8ea3
b433c11
5d79b3e
0628584
ffca24b
f5496ca
d75c969
4032d57
6ddf262
d9d8d79
deb1c46
5539188
e0b97a1
5b67096
6dcfbb6
a50d09c
a7a2fea
af837bf
2b1d661
3b63973
cd6cf1e
30f1c3d
0049264
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,6 +129,8 @@ public final class ImageConverter { | |
private boolean noSequential = false; | ||
private String swapOrder = null; | ||
private Byte fillColor = null; | ||
private boolean precompressed = false; | ||
private boolean tryPrecompressed = false; | ||
|
||
private IFormatReader reader; | ||
private MinMaxCalculator minMax; | ||
|
@@ -179,6 +181,7 @@ private boolean parseArgs(String[] args) { | |
else if (args[i].equals("-padded")) zeroPadding = true; | ||
else if (args[i].equals("-noflat")) flat = false; | ||
else if (args[i].equals("-no-sas")) originalMetadata = false; | ||
else if (args[i].equals("-precompressed")) precompressed = true; | ||
else if (args[i].equals("-cache")) useMemoizer = true; | ||
else if (args[i].equals("-cache-dir")) { | ||
cacheDir = args[++i]; | ||
|
@@ -340,6 +343,7 @@ private void printUsage() { | |
" [-option key value] [-novalid] [-validate] [-tilex tileSizeX]", | ||
" [-tiley tileSizeY] [-pyramid-scale scale]", | ||
" [-swap dimensionsOrderString] [-fill color]", | ||
" [-precompressed]", | ||
" [-pyramid-resolutions numResolutionLevels] in_file out_file", | ||
"", | ||
" -version: print the library version and exit", | ||
|
@@ -384,6 +388,10 @@ private void printUsage() { | |
" -no-sequential: do not assume that planes are written in sequential order", | ||
" -swap: override the default input dimension order; argument is f.i. XYCTZ", | ||
" -fill: byte value to use for undefined pixels (0-255)", | ||
" -precompressed: transfer compressed bytes from input dataset directly to output.", | ||
" Most input and output formats do not support this option.", | ||
" Do not use -crop, -fill, or -autoscale, or pyramid generation options", | ||
sbesson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
" with this option.", | ||
"", | ||
"The extension of the output file specifies the file format to use", | ||
"for the conversion. The list of available formats and extensions is:", | ||
|
@@ -455,6 +463,18 @@ public boolean testConvert(IFormatWriter writer, String[] args) | |
return false; | ||
} | ||
|
||
// TODO: there may be other options not compatible with -precompressed | ||
if (precompressed && | ||
(width_crop > 0 || height_crop > 0 || | ||
pyramidResolutions > 1 || | ||
fillColor != null || | ||
autoscale | ||
)) | ||
{ | ||
throw new UnsupportedOperationException("-precompressed not supported with " + | ||
"-autoscale, -crop, -fill, -pyramid-scale, -pyramid-resolutions"); | ||
} | ||
|
||
CommandLineTools.runUpgradeCheck(args); | ||
|
||
if (new Location(out).exists()) { | ||
|
@@ -712,8 +732,14 @@ else if (w instanceof DicomWriter) { | |
|
||
total += numImages; | ||
|
||
writer.setTileSizeX(saveTileWidth); | ||
writer.setTileSizeY(saveTileHeight); | ||
if (precompressed) { | ||
writer.setTileSizeX(reader.getOptimalTileWidth()); | ||
writer.setTileSizeY(reader.getOptimalTileHeight()); | ||
} | ||
else if (saveTileWidth > 0 && saveTileHeight > 0) { | ||
writer.setTileSizeX(saveTileWidth); | ||
writer.setTileSizeY(saveTileHeight); | ||
} | ||
|
||
int count = 0; | ||
for (int i=startPlane; i<endPlane; i++) { | ||
|
@@ -834,13 +860,28 @@ private long convertPlane(IFormatWriter writer, int index, int outputIndex, | |
} | ||
} | ||
|
||
tryPrecompressed = precompressed && FormatTools.canUsePrecompressedTiles(reader, writer, writer.getSeries(), writer.getResolution()); | ||
|
||
byte[] buf = getTile(reader, writer.getResolution(), index, | ||
xCoordinate, yCoordinate, width, height); | ||
|
||
// if we asked for precompressed tiles, but that wasn't possible, | ||
// then log that decompression/recompression happened | ||
// TODO: decide if an exception is better here? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least in my testing of the Additionally, there is still some significant gain associated to using precompressed tiles for the highest resolutions even if the lowest resolutions are decompressed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the risk of adding too much complexity, I could see having an option (flag or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for now having it logged is a better option than throwing the exception |
||
if (precompressed && !tryPrecompressed) { | ||
LOGGER.warn("Decompressed tile: series={}, resolution={}, x={}, y={}", | ||
writer.getSeries(), writer.getResolution(), xCoordinate, yCoordinate); | ||
} | ||
|
||
autoscalePlane(buf, index); | ||
applyLUT(writer); | ||
long m = System.currentTimeMillis(); | ||
writer.saveBytes(outputIndex, buf); | ||
if (tryPrecompressed) { | ||
writer.saveCompressedBytes(outputIndex, buf, 0, 0, reader.getSizeX(), reader.getSizeY()); | ||
} | ||
else { | ||
writer.saveBytes(outputIndex, buf); | ||
} | ||
return m; | ||
} | ||
|
||
|
@@ -886,16 +927,37 @@ private long convertTilePlane(IFormatWriter writer, int index, int outputIndex, | |
nYTiles++; | ||
} | ||
|
||
// only warn once if the whole resolution will need to | ||
// be decompressed and recompressed | ||
boolean canPrecompressResolution = precompressed && FormatTools.canUsePrecompressedTiles(reader, writer, writer.getSeries(), writer.getResolution()); | ||
if (precompressed && !canPrecompressResolution) { | ||
LOGGER.warn("Decompressing resolution: series={}, resolution={}", | ||
writer.getSeries(), writer.getResolution()); | ||
tryPrecompressed = false; | ||
} | ||
|
||
Long m = null; | ||
for (int y=0; y<nYTiles; y++) { | ||
for (int x=0; x<nXTiles; x++) { | ||
int tileX = xCoordinate + x * w; | ||
int tileY = yCoordinate + y * h; | ||
int tileWidth = x < nXTiles - 1 ? w : width - (w * x); | ||
int tileHeight = y < nYTiles - 1 ? h : height - (h * y); | ||
|
||
tryPrecompressed = precompressed && canPrecompressResolution && | ||
FormatTools.canUsePrecompressedTiles(reader, writer, writer.getSeries(), writer.getResolution()); | ||
byte[] buf = getTile(reader, writer.getResolution(), | ||
index, tileX, tileY, tileWidth, tileHeight); | ||
|
||
// if we asked for precompressed tiles, but that wasn't possible, | ||
// then log that decompression/recompression happened | ||
// this is mainly expected for edge tiles, which might be smaller than expected | ||
// TODO: decide if an exception is better here? | ||
if (precompressed && canPrecompressResolution && !tryPrecompressed) { | ||
LOGGER.warn("Decompressed tile: series={}, resolution={}, x={}, y={}", | ||
writer.getSeries(), writer.getResolution(), x, y); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This warning message is currently written for every single tile of the resolution but it applies to the entire resolution. Would it make sense to compute and display it outside the loop?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the whole resolution needs to be decompressed/recompressed, this should now only warn once. If some of the tiles in the resolution work with |
||
} | ||
|
||
String tileName = | ||
FormatTools.getTileFilename(x, y, y * nXTiles + x, currentFile); | ||
if (!currentFile.equals(tileName)) { | ||
|
@@ -957,18 +1019,8 @@ private long convertTilePlane(IFormatWriter writer, int index, int outputIndex, | |
outputY = 0; | ||
} | ||
|
||
if (writer instanceof TiffWriter) { | ||
((TiffWriter) writer).saveBytes(outputIndex, buf, | ||
outputX, outputY, tileWidth, tileHeight); | ||
} | ||
else if (writer instanceof ImageWriter) { | ||
if (baseWriter instanceof TiffWriter) { | ||
((TiffWriter) baseWriter).saveBytes(outputIndex, buf, | ||
outputX, outputY, tileWidth, tileHeight); | ||
} | ||
else { | ||
writer.saveBytes(outputIndex, buf, outputX, outputY, tileWidth, tileHeight); | ||
} | ||
if (tryPrecompressed) { | ||
writer.saveCompressedBytes(outputIndex, buf, outputX, outputY, tileWidth, tileHeight); | ||
} | ||
else { | ||
writer.saveBytes(outputIndex, buf, outputX, outputY, tileWidth, tileHeight); | ||
|
@@ -1033,7 +1085,7 @@ public int getTileColumns(String outputName) { | |
private void autoscalePlane(byte[] buf, int index) | ||
throws FormatException, IOException | ||
{ | ||
if (autoscale) { | ||
if (autoscale && !tryPrecompressed) { | ||
Double min = null; | ||
Double max = null; | ||
|
||
|
@@ -1123,8 +1175,17 @@ private byte[] getTile(IFormatReader reader, int resolution, | |
{ | ||
if (resolution < reader.getResolutionCount()) { | ||
reader.setResolution(resolution); | ||
int optimalWidth = reader.getOptimalTileWidth(); | ||
int optimalHeight = reader.getOptimalTileHeight(); | ||
if (tryPrecompressed) { | ||
return reader.openCompressedBytes(no, x / optimalWidth, y / optimalHeight); | ||
} | ||
tryPrecompressed = false; | ||
return reader.openBytes(no, x, y, w, h); | ||
} | ||
if (tryPrecompressed) { | ||
throw new UnsupportedOperationException("Cannot generate resolutions with precompressed tiles"); | ||
} | ||
reader.setResolution(0); | ||
IImageScaler scaler = new SimpleImageScaler(); | ||
int scale = (int) Math.pow(pyramidScale, resolution); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two quick brainstormy thoughts:
formatlist
that would print out a table of which of these interfaces is supported by each format.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree these would be nice targets for future work, but are well outside the scope of this PR and not realistic for 7.1.0. OK to open new issues for each, so that we can schedule for future releases?