Skip to content

Commit

Permalink
Update compression API to be a little more flexible
Browse files Browse the repository at this point in the history
The compression type now uses the new CompressionType enum,
and compression options can be set with a CodecOptions object (that includes quality).
The command line "--quality" will be wrapped in a CodecOptions behind the scenes,
but this does not add any other command line arguments for setting compression options.
  • Loading branch information
melissalinkert committed Mar 27, 2023
1 parent 399f418 commit 0344585
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2023 Glencoe Software, Inc. All rights reserved.
*
* This software is distributed under the terms described by the LICENSE.txt
* file you can find at the root of the distribution bundle. If the file is
* missing please request a copy by contacting [email protected]
*/

package com.glencoesoftware.pyramid;

import loci.formats.codec.CodecOptions;
import loci.formats.codec.JPEG2000CodecOptions;

import picocli.CommandLine.ITypeConverter;

/**
* Convert a string to a CodecOptions.
*/
public class CompressionQualityConverter
implements ITypeConverter<CodecOptions>
{
@Override
public CodecOptions convert(String value) throws Exception {
// JPEG2000CodecOptions used here as it's the only way to pass
// a quality value through to the JPEG-2000 codecs
// this could be changed later if/when we support options on other codecs
CodecOptions options = JPEG2000CodecOptions.getDefaultOptions();
options.quality = Double.parseDouble(value);
return options;
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/glencoesoftware/pyramid/CompressionType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2019-2020 Glencoe Software, Inc. All rights reserved.
*
* This software is distributed under the terms described by the LICENSE.txt
* file you can find at the root of the distribution bundle. If the file is
* missing please request a copy by contacting [email protected]
*/
package com.glencoesoftware.pyramid;

import java.util.EnumSet;
import loci.formats.out.TiffWriter;
import loci.formats.tiff.TiffCompression;

/**
* List of valid compression types for the output OME-TIFF file.
*/
public enum CompressionType {
UNCOMPRESSED(
TiffWriter.COMPRESSION_UNCOMPRESSED, TiffCompression.UNCOMPRESSED),
LZW(TiffWriter.COMPRESSION_LZW, TiffCompression.LZW),
JPEG(TiffWriter.COMPRESSION_JPEG, TiffCompression.JPEG),
JPEG_2000(TiffWriter.COMPRESSION_J2K, TiffCompression.JPEG_2000),
JPEG_2000_LOSSY(
TiffWriter.COMPRESSION_J2K_LOSSY, TiffCompression.JPEG_2000_LOSSY);

private String compressionName;
private TiffCompression compressionType;

/**
* Construct a list of valid compression types.
*
* @param name compression name (used in command line arguments)
* @param type corresponding TIFF compression
*/
private CompressionType(String name, TiffCompression type) {
compressionName = name;
compressionType = type;
}

/**
* Find the compression corresponding to the given name.
* If there is no matching name, return the uncompressed type.
*
* @param compressionName desired compression name
* @return corresponding CompressionType, or UNCOMPRESSED if no match
*/
public static CompressionType lookup(String compressionName) {
for (CompressionType t : EnumSet.allOf(CompressionType.class)) {
if (t.getName().equals(compressionName)) {
return t;
}
}
return UNCOMPRESSED;
}

/**
* @return name of this compression type
*/
public String getName() {
return compressionName;
}

/**
* @return TiffCompression for this compression type
*/
public TiffCompression getTIFFCompression() {
return compressionType;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2023 Glencoe Software, Inc. All rights reserved.
*
* This software is distributed under the terms described by the LICENSE.txt
* file you can find at the root of the distribution bundle. If the file is
* missing please request a copy by contacting [email protected]
*/

package com.glencoesoftware.pyramid;

import picocli.CommandLine.ITypeConverter;

/**
* Convert a string to a CompressionType.
*/
public class CompressionTypeConverter
implements ITypeConverter<CompressionType>
{
@Override
public CompressionType convert(String value) throws Exception {
return CompressionType.lookup(value);
}
}
62 changes: 0 additions & 62 deletions src/main/java/com/glencoesoftware/pyramid/CompressionTypes.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ public class PyramidFromDirectoryWriter implements Callable<Void> {
private volatile String logLevel = "WARN";
private volatile boolean progressBars = false;
boolean printVersion = false;
String compression = "LZW";
Double compressionQuality;
CompressionType compression = CompressionType.LZW;
CodecOptions compressionOptions;
boolean legacy = false;
int maxWorkers = Runtime.getRuntime().availableProcessors();
boolean rgb = false;
Expand Down Expand Up @@ -223,23 +223,26 @@ public void setPrintVersionOnly(boolean versionOnly) {

/**
* Set the compression type for the output OME-TIFF. Defaults to LZW.
* Valid types are defined in the CompressionTypes class.
* Valid types are defined in the CompressionType enum.
*
* @param compressionType compression type
*/
@Option(
names = "--compression",
completionCandidates = CompressionTypes.class,
description = "Compression type for output OME-TIFF file " +
"(${COMPLETION-CANDIDATES}; default: ${DEFAULT-VALUE})",
converter = CompressionTypeConverter.class,
defaultValue = "LZW"
)
public void setCompression(String compressionType) {
public void setCompression(CompressionType compressionType) {
compression = compressionType;
}

/**
* Set the compression quality. The interpretation of the quality value
* Set the compression options.
*
* When using the command line "--quality" option, the quality value will be
* wrapped in a CodecOptions. The interpretation of the quality value
* depends upon the selected compression type.
*
* This value currently only applies to "JPEG-2000 Lossy" compression,
Expand All @@ -250,14 +253,20 @@ public void setCompression(String compressionType) {
* This is equivalent to lossless compression; to see truly lossy compression,
* the quality should be set to less than the bit depth of the input image.
*
* @param quality compression quality
* Options other than quality may be specified in this object, but their
* interpretation will also depend upon the compression type selected.
* Options that conflict with the input data (e.g. bits per pixel)
* will be ignored.
*
* @param options compression options
*/
@Option(
names = "--quality",
converter = CompressionQualityConverter.class,
description = "Compression quality"
)
public void setCompressionQuality(Double quality) {
compressionQuality = quality;
public void setCompressionOptions(CodecOptions options) {
compressionOptions = options;
}

/**
Expand Down Expand Up @@ -343,15 +352,15 @@ public boolean getPrintVersionOnly() {
/**
* @return compression type
*/
public String getCompression() {
public CompressionType getCompression() {
return compression;
}

/**
* @return compression quality
* @return compression options
*/
public Double getCompressionQuality() {
return compressionQuality;
public CodecOptions getCompressionOptions() {
return compressionOptions;
}

/**
Expand Down Expand Up @@ -1204,8 +1213,7 @@ private IFD makeIFD(PyramidSeries s, int resolution, int plane)
ifd.put(IFD.IMAGE_LENGTH, (long) descriptor.sizeY);
ifd.put(IFD.TILE_WIDTH, descriptor.tileSizeX);
ifd.put(IFD.TILE_LENGTH, descriptor.tileSizeY);
ifd.put(IFD.COMPRESSION,
CompressionTypes.getTIFFCompression(compression).getCode());
ifd.put(IFD.COMPRESSION, compression.getTIFFCompression().getCode());

ifd.put(IFD.PLANAR_CONFIGURATION, s.rgb ? 2 : 1);

Expand Down Expand Up @@ -1296,9 +1304,9 @@ private void writeTile(PyramidSeries s,
s.index, imageNumber, tileIndex);

IFD ifd = s.ifds[resolution].get(imageNumber);
TiffCompression tiffCompression =
CompressionTypes.getTIFFCompression(compression);
CodecOptions options = tiffCompression.getCompressionCodecOptions(ifd);
TiffCompression tiffCompression = compression.getTIFFCompression();
CodecOptions options =
tiffCompression.getCompressionCodecOptions(ifd, compressionOptions);

// buffer has been padded to full tile width before calling writeTile
// but is not necessarily full tile height (if in the bottom row)
Expand All @@ -1307,9 +1315,6 @@ private void writeTile(PyramidSeries s,
options.height = buffer.length / (options.width * bpp);
options.bitsPerSample = bpp * 8;
options.channels = 1;
if (compressionQuality != null) {
options.quality = compressionQuality;
}

byte[] realTile = tiffCompression.compress(buffer, options);
LOG.debug(" writing {} compressed bytes at {}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.glencoesoftware.bioformats2raw.Converter;
import com.glencoesoftware.pyramid.CompressionType;
import com.glencoesoftware.pyramid.PyramidFromDirectoryWriter;

import loci.common.DataTools;
Expand Down Expand Up @@ -509,13 +510,30 @@ public void testOptionsAPI() throws Exception {
PyramidFromDirectoryWriter apiConverter = new PyramidFromDirectoryWriter();
apiConverter.setInputPath(output.toString());
apiConverter.setOutputPath(outputOmeTiff.toString());
apiConverter.setCompression("raw");
apiConverter.setCompression(CompressionType.UNCOMPRESSED);
apiConverter.setRGB(true);
apiConverter.call();

iteratePixels();
}

/**
* Test "--quality" command line option.
*/
@Test
public void testCompressionQuality() throws Exception {
input = fake("sizeC", "3", "rgb", "3");
assertBioFormats2Raw();
assertTool("--compression", "JPEG-2000", "--quality", "0.25", "--rgb");

try (ImageReader reader = new ImageReader()) {
reader.setFlattenedResolutions(false);
reader.setId(outputOmeTiff.toString());
Assert.assertEquals(1, reader.getSeriesCount());
Assert.assertEquals(3, reader.getRGBChannelCount());
}
}

/**
* Test "--version" with no other arguments.
* Does not test the version values, just makes sure that an exception
Expand Down

0 comments on commit 0344585

Please sign in to comment.