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

JPEG-2000 #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
<artifactId>jblosc</artifactId>
<version>1.0.1</version>
</dependency>

<dependency>
<groupId>org.openmicroscopy</groupId>
<artifactId>ome-codecs</artifactId>
<version>0.3.1</version>
</dependency>

<!-- <dependency>-->
<!-- <groupId>org.blosc</groupId>-->
<!-- <artifactId>jblosc</artifactId>-->
Expand Down Expand Up @@ -211,4 +218,4 @@
</plugins>
</build>

</project>
</project>
37 changes: 37 additions & 0 deletions src/main/java/com/bc/zarr/Compressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,41 @@ void passThrough(InputStream is, OutputStream os) throws IOException {
read = is.read(bytes);
}
}

public boolean booleanValue(Object v, boolean defaultValue) {
if (v == null) {
return defaultValue;
}
if (v instanceof Boolean) {
return (Boolean) v;
}
return Boolean.parseBoolean(v.toString());
}

public int intValue(Object v, int defaultValue) {
int value = defaultValue;
if (v != null) {
if (v instanceof String) {
value = Integer.parseInt((String) v);
}
else if (v instanceof Number) {
value = ((Number) v).intValue();
}
}
return value;
}

public double doubleValue(Object v, double defaultValue) {
double value = defaultValue;
if (v != null) {
if (v instanceof String) {
value = Double.parseDouble((String) v);
}
else if (v instanceof Number) {
value = ((Number) v).doubleValue();
}
}
return value;
}

}
119 changes: 119 additions & 0 deletions src/main/java/com/bc/zarr/CompressorFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.blosc.IBloscDll;
import org.blosc.JBlosc;

import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
Expand All @@ -47,6 +48,16 @@
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import ome.codecs.CodecException;
import ome.codecs.JPEG2000Codec;
import ome.codecs.JPEG2000CodecOptions;
import ome.codecs.gui.AWTImageTools;
import ome.codecs.services.JAIIIOService;
import ome.codecs.services.JAIIIOServiceImpl;

public class CompressorFactory {

public final static Compressor nullCompressor = new NullCompressor();
Expand Down Expand Up @@ -120,6 +131,9 @@ public static Compressor create(String id, Map<String, Object> properties) {
if ("blosc".equals(id)) {
return new BloscCompressor(properties);
}
if ("j2k".equals(id)) {
return new J2KCompressor(properties);
}
throw new IllegalArgumentException("Compressor id:'" + id + "' not supported.");
}

Expand Down Expand Up @@ -356,5 +370,110 @@ private BufferSizes cbufferSizes(ByteBuffer cbuffer) {
return bs;
}
}

static class J2KCompressor extends Compressor {
public static final String littleEndianKey = "littleEndian";
public static final String interleavedKey = "interleaved";
public static final String losslessKey = "lossless";
public static final String widthKey = "imageWidth";
public static final String heightKey = "imageHeight";
public static final String bitsPerSampleKey = "bitsPerSample";
public static final String channelsKey = "channels";
public static final String qualityKey = "quality";

// TODO: any way to copy these options from array metadata?
private boolean littleEndian;
private boolean interleaved;
private int width;
private int height;
private int bitsPerSample;
private int channels;
// specify lossless or quality, not both
private boolean lossless;
private double quality;

private J2KCompressor(Map<String, Object> map) {
littleEndian = booleanValue(map.get(littleEndianKey), false);
interleaved = booleanValue(map.get(interleavedKey), false);
width = intValue(map.get(widthKey), -1);
height = intValue(map.get(heightKey), -1);
bitsPerSample = intValue(map.get(bitsPerSampleKey), 8);
channels = intValue(map.get(channelsKey), 1);

// if neither quality nor lossless is defined, do lossless compression
quality = doubleValue(map.get(qualityKey), -1);
lossless = booleanValue(map.get(losslessKey), quality < 0);
}

@Override
public String toString() {
return "compressor=" + getId();
}

@Override
public String getId() {
return "j2k";
}

@Override
public void compress(InputStream is, OutputStream os) throws IOException {
try (ByteArrayOutputStream tmpOut = new ByteArrayOutputStream()) {
passThrough(is, tmpOut);
byte[] buffer = tmpOut.toByteArray();
JPEG2000Codec codec = new JPEG2000Codec();
JPEG2000CodecOptions options = getCodecOptions();
byte[] compressed = codec.compress(buffer, options);
os.write(compressed);
}
catch (CodecException e) {
throw new IOException(e);
}
}

@Override
public void uncompress(InputStream is, OutputStream os) throws IOException {
try {
JAIIIOService service = getService();
WritableRaster raster = (WritableRaster) service.readRaster(is, getCodecOptions());
byte[][] raw = AWTImageTools.getPixelBytes(raster, littleEndian);
for (byte[] channel : raw) {
os.write(channel);
}
}
catch (ServiceException e) {
throw new IOException(e);
}
}

private JPEG2000CodecOptions getCodecOptions() {
JPEG2000CodecOptions options = new JPEG2000CodecOptions();
options.interleaved = interleaved;
options.littleEndian = littleEndian;
options.width = width;
options.height = height;
options.channels = channels;
options.bitsPerSample = bitsPerSample;
if (quality >= 0) {
options.quality = quality;
}
options.lossless = lossless;
options.numDecompositionLevels = 1;

return JPEG2000CodecOptions.getDefaultOptions(options);
}

private JAIIIOService getService() throws IOException {
try {
ServiceFactory factory = new ServiceFactory();
return factory.getInstance(JAIIIOService.class);
}
catch (DependencyException de) {
throw new IOException(JAIIIOServiceImpl.NO_J2K_MSG, de);
}
}

}


}

46 changes: 46 additions & 0 deletions src/test/java/com/bc/zarr/CompressorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import static org.hamcrest.Matchers.*;
import static org.hamcrest.MatcherAssert.*;
import static org.junit.Assert.assertNotNull;

import com.bc.zarr.chunk.ZarrInputStreamAdapter;

Expand Down Expand Up @@ -155,6 +156,51 @@ public void writeRead_BloscCompressor() throws IOException {
assertThat(input, is(equalTo(uncompressed)));
}

@Test
public void writeRead_J2KCompressor() throws IOException {
final Map<String, Object> j2kProperties = new LinkedHashMap<>();
j2kProperties.put(CompressorFactory.J2KCompressor.widthKey, 11);
j2kProperties.put(CompressorFactory.J2KCompressor.heightKey, 5);
j2kProperties.put(CompressorFactory.J2KCompressor.bitsPerSampleKey, 16);
j2kProperties.put(CompressorFactory.J2KCompressor.channelsKey, 1);
j2kProperties.put(CompressorFactory.J2KCompressor.interleavedKey, false);
j2kProperties.put(CompressorFactory.J2KCompressor.littleEndianKey, false);
j2kProperties.put(CompressorFactory.J2KCompressor.losslessKey, true);

final Compressor compressor = CompressorFactory.create("j2k", j2kProperties);
final ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
final short[] input = {
100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100,
100, 22, 100, 22, 22, 22, 100, 100, 100, 22, 100
};
final MemoryCacheImageOutputStream iis = new MemoryCacheImageOutputStream(new ByteArrayOutputStream());
iis.setByteOrder(byteOrder);
iis.writeShorts(input, 0, input.length);
iis.seek(0);

ByteArrayOutputStream os;
ByteArrayInputStream is;

//write
os = new ByteArrayOutputStream();
compressor.compress(new ZarrInputStreamAdapter(iis), os);
final byte[] compressed = os.toByteArray();

//read
is = new ByteArrayInputStream(compressed);
os = new ByteArrayOutputStream();
compressor.uncompress(is, os);
final ByteArrayInputStream bais = new ByteArrayInputStream(os.toByteArray());
final MemoryCacheImageInputStream resultIis = new MemoryCacheImageInputStream(bais);
resultIis.setByteOrder(byteOrder);
final short[] uncompressed = new short[input.length];
resultIis.readFully(uncompressed, 0, uncompressed.length);
assertThat(input, is(equalTo(uncompressed)));
}

@Test
public void read_BloscCompressor_DefaultAvailable() throws IOException {
final Compressor compressor = CompressorFactory.create("blosc");
Expand Down