From 363efafcad8dcf59add5592bf2a48908db6c6b10 Mon Sep 17 00:00:00 2001 From: Eric Perlman Date: Mon, 29 Jan 2018 21:40:46 -0500 Subject: [PATCH 1/3] 16-bit PNG support. --- .../render/service/RenderImageService.java | 35 ++++++++- .../util/BufferedImageStreamingOutput.java | 71 +++++++++++++------ .../service/util/RenderServiceUtil.java | 22 ++++-- 3 files changed, 99 insertions(+), 29 deletions(-) diff --git a/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java b/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java index 14360e8fc..3ca2a8d4e 100644 --- a/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java +++ b/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java @@ -364,6 +364,40 @@ 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("renderPngImageForBox: 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}/dvid/imagetile/raw/xy/{width}_{height}/{x}_{y}_{z}/tif") @GET @Produces(RenderServiceUtil.IMAGE_TIFF_MIME_TYPE) @@ -701,7 +735,6 @@ private Response renderLargeDataTileSource(final String owner, if (maxTileSpecsToRender == null) { maxTileSpecsToRender = DEFAULT_MAX_TILE_SPECS_FOR_LARGE_DATA; } - return RenderServiceUtil.renderImageStream(renderParameters, format, mimeType, diff --git a/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java b/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java index 8daa3fc81..3da559647 100644 --- a/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java +++ b/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java @@ -2,6 +2,7 @@ import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferUShort; import java.awt.image.SinglePixelPackedSampleModel; import java.io.IOException; import java.io.OutputStream; @@ -86,34 +87,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 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 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); + } + 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); diff --git a/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java b/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java index 0d7bfd6f7..00fe58e74 100644 --- a/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java +++ b/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java @@ -79,19 +79,28 @@ public static Response renderJpegImage(final RenderParameters renderParameters, public static Response renderPngImage(final RenderParameters renderParameters, final Integer maxTileSpecsToRender, - final ResponseHelper responseHelper) { + 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 renderTiffImage(final RenderParameters renderParameters, - final Integer maxTileSpecsToRender, - final ResponseHelper responseHelper) { -return renderTiffImage(renderParameters, maxTileSpecsToRender, responseHelper, false); -} + final Integer maxTileSpecsToRender, + final ResponseHelper responseHelper) { + return renderTiffImage(renderParameters, maxTileSpecsToRender, responseHelper, false); + } public static Response renderTiffImage(final RenderParameters renderParameters, final Integer maxTileSpecsToRender, @@ -104,6 +113,7 @@ public static Response renderTiffImage(final RenderParameters renderParameters, responseHelper, render16bit); } + public static Response renderImageStream(final RenderParameters renderParameters, final String format, final String mimeType, From fca632843c31bd0ab8616f4b36d6f5d62c4284cc Mon Sep 17 00:00:00 2001 From: Eric Perlman Date: Sun, 23 Jun 2019 11:13:27 -0400 Subject: [PATCH 2/3] First pass at raw data. --- .../java/org/janelia/alignment/Utils.java | 1 + .../render/service/RenderImageService.java | 35 +++++++++++++++++- .../util/BufferedImageStreamingOutput.java | 37 ++++++++++++++++++- .../service/util/RenderServiceUtil.java | 22 ++++++++++- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/render-app/src/main/java/org/janelia/alignment/Utils.java b/render-app/src/main/java/org/janelia/alignment/Utils.java index 6ce2852e6..5d198ebfb 100644 --- a/render-app/src/main/java/org/janelia/alignment/Utils.java +++ b/render-app/src/main/java/org/janelia/alignment/Utils.java @@ -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); diff --git a/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java b/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java index 3ca2a8d4e..5c2396e63 100644 --- a/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java +++ b/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java @@ -384,7 +384,7 @@ public Response renderPng16ImageForBox(@PathParam("owner") final String owner, @QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender, @Context final Request request) { - LOG.info("renderPngImageForBox: entry"); + LOG.info("renderPng16ImageForBox: entry"); final ResponseHelper responseHelper = new ResponseHelper(request, getStackMetaData(owner, project, stack)); if (responseHelper.isModified()) { @@ -398,6 +398,39 @@ public Response renderPng16ImageForBox(@PathParam("owner") final String owner, } } + @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("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, true); + } 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) diff --git a/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java b/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java index 3da559647..396aaedab 100644 --- a/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java +++ b/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java @@ -6,6 +6,7 @@ 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; @@ -52,7 +53,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); @@ -97,7 +100,7 @@ public static void writePngImage(final BufferedImage bufferedImage, pngWriter.setCompLevel(compressionLevel); pngWriter.setFilterType(filterType); - final DataBufferInt dataBuffer =((DataBufferInt) bufferedImage.getRaster().getDataBuffer()); + final DataBufferInt dataBuffer = (DataBufferInt) bufferedImage.getRaster().getDataBuffer(); if (dataBuffer.getNumBanks() != 1) { throw new IOException("invalid number of banks (" + dataBuffer.getNumBanks() + "), must be 1"); } @@ -150,4 +153,34 @@ 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_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 or BufferedImage.TYPE_USHORT_GRAY"); + } + } } diff --git a/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java b/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java index 00fe58e74..849c21a46 100644 --- a/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java +++ b/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java @@ -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 = "iapplication/octet-stream"; public static void throwServiceException(final Throwable t) throws ServiceException { @@ -96,6 +97,25 @@ public static Response renderPngImage(final RenderParameters renderParameters, 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, final Integer maxTileSpecsToRender, final ResponseHelper responseHelper) { @@ -239,4 +259,4 @@ private static BufferedImage validateParametersAndRenderImage(final RenderParame } private static final Logger LOG = LoggerFactory.getLogger(RenderServiceUtil.class); -} \ No newline at end of file +} From f53430f262ac1d053fe108391a55a868be93a183 Mon Sep 17 00:00:00 2001 From: Eric Perlman Date: Mon, 24 Jun 2019 08:42:08 -0400 Subject: [PATCH 3/3] Support for RGBA and (unused) U8 gray. --- .../render/service/RenderImageService.java | 35 ++++++++++++++++++- .../util/BufferedImageStreamingOutput.java | 6 +++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java b/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java index 5c2396e63..0749d70d6 100644 --- a/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java +++ b/render-ws/src/main/java/org/janelia/render/service/RenderImageService.java @@ -417,7 +417,7 @@ public Response renderRaw16ImageForBox(@PathParam("owner") final String owner, @QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender, @Context final Request request) { - LOG.info("renderRawImageForBox: entry"); + LOG.info("renderRaw16ImageForBox: entry"); final ResponseHelper responseHelper = new ResponseHelper(request, getStackMetaData(owner, project, stack)); if (responseHelper.isModified()) { @@ -431,6 +431,39 @@ public Response renderRaw16ImageForBox(@PathParam("owner") final String owner, } } + @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) diff --git a/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java b/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java index 396aaedab..c829b578d 100644 --- a/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java +++ b/render-ws/src/main/java/org/janelia/render/service/util/BufferedImageStreamingOutput.java @@ -1,6 +1,7 @@ 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; @@ -173,6 +174,9 @@ public static void writeRawImage(final BufferedImage bufferedImage, 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()){ @@ -180,7 +184,7 @@ public static void writeRawImage(final BufferedImage bufferedImage, } } else { throw new IOException("invalid image type (" + bufferedImage.getType() + - "), must be BufferedImage.TYPE_INT_ARGB or BufferedImage.TYPE_USHORT_GRAY"); + "), must be BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_BYTE_GRAY or BufferedImage.TYPE_USHORT_GRAY"); } } }