Skip to content

Commit

Permalink
Merge pull request #100 from perlman/png16_raw16
Browse files Browse the repository at this point in the history
16-bit PNG and raw image support
  • Loading branch information
trautmane authored Jul 9, 2019
2 parents a0cf4cc + 71dd45a commit 8a8349b
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 30 deletions.
1 change: 1 addition & 0 deletions render-app/src/main/java/org/janelia/alignment/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class Utils {
public static final String PNG_FORMAT = "png";
public static final String TIFF_FORMAT = "tiff";
public static final String TIF_FORMAT = "tif";
public static final String RAW_FORMAT = "raw";

private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,106 @@ public Response renderTiff16ImageForBox(@PathParam("owner") final String owner,
return responseHelper.getNotModifiedResponse();
}
}

@Path("v1/owner/{owner}/project/{project}/stack/{stack}/z/{z}/box/{x},{y},{width},{height},{scale}/png16-image")
@GET
@Produces(RenderServiceUtil.IMAGE_PNG_MIME_TYPE)
@ApiOperation(
tags = "Bounding Box Image APIs",
value = "Render 16-bit PNG image for the specified bounding box")
public Response renderPng16ImageForBox(@PathParam("owner") final String owner,
@PathParam("project") final String project,
@PathParam("stack") final String stack,
@PathParam("x") final Double x,
@PathParam("y") final Double y,
@PathParam("z") final Double z,
@PathParam("width") final Integer width,
@PathParam("height") final Integer height,
@PathParam("scale") final Double scale,
@BeanParam final RenderQueryParameters renderQueryParameters,
@QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender,
@Context final Request request) {

LOG.info("renderPng16ImageForBox: entry");

final ResponseHelper responseHelper = new ResponseHelper(request, getStackMetaData(owner, project, stack));
if (responseHelper.isModified()) {
final RenderParameters renderParameters =
getRenderParametersForGroupBox(owner, project, stack, null,
x, y, z, width, height, scale,
renderQueryParameters);
return RenderServiceUtil.renderPngImage(renderParameters, maxTileSpecsToRender, responseHelper, true);
} else {
return responseHelper.getNotModifiedResponse();
}
}

@Path("v1/owner/{owner}/project/{project}/stack/{stack}/z/{z}/box/{x},{y},{width},{height},{scale}/raw16-image")
@GET
@Produces(RenderServiceUtil.IMAGE_RAW_MIME_TYPE)
@ApiOperation(
tags = "Bounding Box Image APIs",
value = "Render 16-bit raw image for the specified bounding box")
public Response renderRaw16ImageForBox(@PathParam("owner") final String owner,
@PathParam("project") final String project,
@PathParam("stack") final String stack,
@PathParam("x") final Double x,
@PathParam("y") final Double y,
@PathParam("z") final Double z,
@PathParam("width") final Integer width,
@PathParam("height") final Integer height,
@PathParam("scale") final Double scale,
@BeanParam final RenderQueryParameters renderQueryParameters,
@QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender,
@Context final Request request) {

LOG.info("renderRaw16ImageForBox: entry");

final ResponseHelper responseHelper = new ResponseHelper(request, getStackMetaData(owner, project, stack));
if (responseHelper.isModified()) {
final RenderParameters renderParameters =
getRenderParametersForGroupBox(owner, project, stack, null,
x, y, z, width, height, scale,
renderQueryParameters);
return RenderServiceUtil.renderRawImage(renderParameters, maxTileSpecsToRender, responseHelper, true);
} else {
return responseHelper.getNotModifiedResponse();
}
}

@Path("v1/owner/{owner}/project/{project}/stack/{stack}/z/{z}/box/{x},{y},{width},{height},{scale}/raw-image")
@GET
@Produces(RenderServiceUtil.IMAGE_RAW_MIME_TYPE)
@ApiOperation(
tags = "Bounding Box Image APIs",
value = "Render raw image for the specified bounding box")
public Response renderRawImageForBox(@PathParam("owner") final String owner,
@PathParam("project") final String project,
@PathParam("stack") final String stack,
@PathParam("x") final Double x,
@PathParam("y") final Double y,
@PathParam("z") final Double z,
@PathParam("width") final Integer width,
@PathParam("height") final Integer height,
@PathParam("scale") final Double scale,
@BeanParam final RenderQueryParameters renderQueryParameters,
@QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender,
@Context final Request request) {

LOG.info("renderRawImageForBox: entry");

final ResponseHelper responseHelper = new ResponseHelper(request, getStackMetaData(owner, project, stack));
if (responseHelper.isModified()) {
final RenderParameters renderParameters =
getRenderParametersForGroupBox(owner, project, stack, null,
x, y, z, width, height, scale,
renderQueryParameters);
return RenderServiceUtil.renderRawImage(renderParameters, maxTileSpecsToRender, responseHelper, false);
} else {
return responseHelper.getNotModifiedResponse();
}
}

@Path("v1/owner/{owner}/project/{project}/stack/{stack}/dvid/imagetile/raw/xy/{width}_{height}/{x}_{y}_{z}/tif")
@GET
@Produces(RenderServiceUtil.IMAGE_TIFF_MIME_TYPE)
Expand Down Expand Up @@ -701,7 +801,6 @@ private Response renderLargeDataTileSource(final String owner,
if (maxTileSpecsToRender == null) {
maxTileSpecsToRender = DEFAULT_MAX_TILE_SPECS_FOR_LARGE_DATA;
}

return RenderServiceUtil.renderImageStream(renderParameters,
format,
mimeType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.janelia.render.service.util;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferUShort;
import java.awt.image.SinglePixelPackedSampleModel;
import java.io.IOException;
import java.io.OutputStream;
import java.io.DataOutputStream;

import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
Expand Down Expand Up @@ -51,7 +54,9 @@ public void write(final OutputStream outputStream)

LOG.info("write: entry");

if (Utils.PNG_FORMAT.equals(format)) {
if (Utils.RAW_FORMAT.equals(format)) {
writeRawImage(targetImage, outputStream);
} else if (Utils.PNG_FORMAT.equals(format)) {
writePngImage(targetImage, 6, FilterType.FILTER_PAETH, outputStream);
} else if (Utils.TIFF_FORMAT.equals(format)) {
Utils.writeTiffImage(targetImage, outputStream);
Expand Down Expand Up @@ -86,34 +91,60 @@ public static void writePngImage(final BufferedImage bufferedImage,
final OutputStream outputStream)
throws IOException {

if (bufferedImage.getType() != BufferedImage.TYPE_INT_ARGB) {
throw new IOException("invalid image type (" + bufferedImage.getType() +
"), must be BufferedImage.TYPE_INT_ARGB");
}

final ImageInfo imageInfo = new ImageInfo(bufferedImage.getWidth(), bufferedImage.getHeight(), 8, true);

final PngWriter pngWriter = new PngWriter(outputStream, imageInfo);
pngWriter.setCompLevel(compressionLevel);
pngWriter.setFilterType(filterType);
if (bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) {
// Existing code for TYPE_INT_ARGB
final ImageInfo imageInfo = new ImageInfo(bufferedImage.getWidth(), bufferedImage.getHeight(), 8, true);

final DataBufferInt dataBuffer =((DataBufferInt) bufferedImage.getRaster().getDataBuffer());
if (dataBuffer.getNumBanks() != 1) {
throw new IOException("invalid number of banks (" + dataBuffer.getNumBanks() + "), must be 1");
}
final PngWriter pngWriter = new PngWriter(outputStream, imageInfo);
pngWriter.setCompLevel(compressionLevel);
pngWriter.setFilterType(filterType);

final DataBufferInt dataBuffer = (DataBufferInt) bufferedImage.getRaster().getDataBuffer();
if (dataBuffer.getNumBanks() != 1) {
throw new IOException("invalid number of banks (" + dataBuffer.getNumBanks() + "), must be 1");
}

final SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) bufferedImage.getSampleModel();
final ImageLineInt line = new ImageLineInt(imageInfo);
final int[] data = dataBuffer.getData();
for (int row = 0; row < imageInfo.rows; row++) {
int elem = sampleModel.getOffset(0, row);
for (int col = 0; col < imageInfo.cols; col++) {
final int sample = data[elem++];
ImageLineHelper.setPixelRGBA8(line, col, sample);
final SinglePixelPackedSampleModel sampleModel = (SinglePixelPackedSampleModel) bufferedImage.getSampleModel();
final ImageLineInt line = new ImageLineInt(imageInfo);
final int[] data = dataBuffer.getData();
for (int row = 0; row < imageInfo.rows; row++) {
int elem = sampleModel.getOffset(0, row);
for (int col = 0; col < imageInfo.cols; col++) {
final int sample = data[elem++];
ImageLineHelper.setPixelRGBA8(line, col, sample);
}
pngWriter.writeRow(line, row);
}
pngWriter.end();

} else if (bufferedImage.getType() == BufferedImage.TYPE_USHORT_GRAY) {
// Modified code to avoid "java.awt.image.DataBufferUShort cannot be cast to java.awt.image.DataBufferInt"
final ImageInfo imageInfo = new ImageInfo(bufferedImage.getWidth(), bufferedImage.getHeight(), 16, false, true, false);
final PngWriter pngWriter = new PngWriter(outputStream, imageInfo);
pngWriter.setCompLevel(compressionLevel);
pngWriter.setFilterType(filterType);

final DataBufferUShort dataBuffer = (DataBufferUShort) bufferedImage.getRaster().getDataBuffer();
if (dataBuffer.getNumBanks() != 1) {
throw new IOException("invalid number of banks (" + dataBuffer.getNumBanks() + "), must be 1");
}
pngWriter.writeRow(line, row);

final int [] scanline = new int[imageInfo.cols];
ImageLineInt line = new ImageLineInt(imageInfo, scanline);

for (int row = 0; row < imageInfo.rows; row++) {
for (int col = 0; col < imageInfo.cols; col++) {
scanline[col] = dataBuffer.getData()[row * imageInfo.cols + col];
}
pngWriter.writeRow(line, row);
}
pngWriter.end();
} else {
throw new IOException("invalid image type (" + bufferedImage.getType() +
"), must be BufferedImage.TYPE_INT_ARGB or BufferedImage.TYPE_USHORT_GRAY");
}
pngWriter.end();

// // This looked like a nicer option, but only works for DataBufferByte (not DataBufferInt)
// final ImageLineSetARGBbi lines = new ImageLineSetARGBbi(bufferedImage, imageInfo);
Expand All @@ -123,4 +154,37 @@ public static void writePngImage(final BufferedImage bufferedImage,

private static final Logger LOG = LoggerFactory.getLogger(BufferedImageStreamingOutput.class);

/**
* Writes raw bytes from {@link BufferedImage} to the specified {@link OutputStream}.
*
* @param bufferedImage image to write.
* @param outputStream target stream.
*
* @throws IOException
* if the image is not ARGB or it's data buffer contains the wrong number of banks.
*/
public static void writeRawImage(final BufferedImage bufferedImage,
final OutputStream outputStream)
throws IOException {

DataOutputStream dataStream = new DataOutputStream(outputStream);

if (bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) {
final DataBufferInt dataBuffer = (DataBufferInt) bufferedImage.getRaster().getDataBuffer();
for (int i:dataBuffer.getData()){
dataStream.writeInt(i);
}
} else if (bufferedImage.getType() == BufferedImage.TYPE_BYTE_GRAY) {
final DataBufferByte dataBuffer = (DataBufferByte) bufferedImage.getRaster().getDataBuffer();
dataStream.write(dataBuffer.getData());
} else if (bufferedImage.getType() == BufferedImage.TYPE_USHORT_GRAY) {
final DataBufferUShort dataBuffer = (DataBufferUShort) bufferedImage.getRaster().getDataBuffer();
for (int i:dataBuffer.getData()){
dataStream.writeShort(i);
}
} else {
throw new IOException("invalid image type (" + bufferedImage.getType() +
"), must be BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_BYTE_GRAY or BufferedImage.TYPE_USHORT_GRAY");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class RenderServiceUtil {
public static final String IMAGE_JPEG_MIME_TYPE = "image/jpeg";
public static final String IMAGE_PNG_MIME_TYPE = "image/png";
public static final String IMAGE_TIFF_MIME_TYPE = "image/tiff";
public static final String IMAGE_RAW_MIME_TYPE = "application/octet-stream";

public static void throwServiceException(final Throwable t)
throws ServiceException {
Expand Down Expand Up @@ -81,11 +82,38 @@ public static Response renderJpegImage(final RenderParameters renderParameters,
public static Response renderPngImage(final RenderParameters renderParameters,
final Integer maxTileSpecsToRender,
final ResponseHelper responseHelper) {
return renderPngImage(renderParameters, maxTileSpecsToRender, responseHelper, false);
}

public static Response renderPngImage(final RenderParameters renderParameters,
final Integer maxTileSpecsToRender,
final ResponseHelper responseHelper,
final boolean render16bit) {
return renderImageStream(renderParameters,
Utils.PNG_FORMAT,
IMAGE_PNG_MIME_TYPE,
maxTileSpecsToRender,
responseHelper);
responseHelper,
render16bit);
}

public static Response renderRawImage(final RenderParameters renderParameters,
final Integer maxTileSpecsToRender,
final ResponseHelper responseHelper) {
return renderRawImage(renderParameters, maxTileSpecsToRender, responseHelper, false);

}

public static Response renderRawImage(final RenderParameters renderParameters,
final Integer maxTileSpecsToRender,
final ResponseHelper responseHelper,
final boolean render16bit) {
return renderImageStream(renderParameters,
Utils.RAW_FORMAT,
IMAGE_RAW_MIME_TYPE,
maxTileSpecsToRender,
responseHelper,
render16bit);
}

public static Response renderTiffImage(final RenderParameters renderParameters,
Expand All @@ -111,7 +139,7 @@ public static Response renderImageStream(final RenderParameters renderParameters
final String mimeType,
final Integer maxTileSpecsToRender,
final ResponseHelper responseHelper) {
return renderImageStream(renderParameters, format, mimeType, maxTileSpecsToRender, responseHelper,false);
return renderImageStream(renderParameters, format, mimeType, maxTileSpecsToRender, responseHelper, false);
}

private static Response renderImageStream(final RenderParameters renderParameters,
Expand Down Expand Up @@ -196,7 +224,7 @@ private static BufferedImage validateParametersAndRenderImage(final RenderParame
renderParameters.initializeDerivedValues();

// only validate source images and masks if we are rendering real data
if (! renderBoundingBoxesOnly) {
if (!renderBoundingBoxesOnly) {
renderParameters.validate();
}

Expand All @@ -217,14 +245,12 @@ private static BufferedImage validateParametersAndRenderImage(final RenderParame
ShortRenderer.render(renderParameters,
targetImage,
SharedImageProcessorCache.getInstance());
}
else{
} else {
targetImage = renderParameters.openTargetImage();
ArgbRenderer.render(renderParameters,
targetImage,
SharedImageProcessorCache.getInstance());
}


}

Expand Down

0 comments on commit 8a8349b

Please sign in to comment.