From 43ce9789bb29bc0ef127c68a858088b84c9a0720 Mon Sep 17 00:00:00 2001 From: Forrest Collman Date: Thu, 18 Jan 2018 14:18:02 -0800 Subject: [PATCH 1/3] added 16 bit tiff web endpoint --- .../janelia/alignment/RenderParameters.java | 9 ++- .../render/service/RenderImageService.java | 36 ++++++++++++ .../service/util/RenderServiceUtil.java | 56 ++++++++++++++----- 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/render-app/src/main/java/org/janelia/alignment/RenderParameters.java b/render-app/src/main/java/org/janelia/alignment/RenderParameters.java index e33a4f64b..45158e41b 100644 --- a/render-app/src/main/java/org/janelia/alignment/RenderParameters.java +++ b/render-app/src/main/java/org/janelia/alignment/RenderParameters.java @@ -696,14 +696,16 @@ public boolean hasMasks() { } return hasMasks; } - + public BufferedImage openTargetImage() { + return openTargetImage(BufferedImage.TYPE_INT_ARGB); + } /** * Opens the target/input image specified by these parameters or * creates a new (in-memory) image if no input image was specified. * * @return {@link BufferedImage} representation of the image. */ - public BufferedImage openTargetImage() { + public BufferedImage openTargetImage(final int imageType) { BufferedImage targetImage = null; @@ -715,12 +717,13 @@ public BufferedImage openTargetImage() { final double derivedScale = getScale(); final int targetWidth = (int) (derivedScale * width); final int targetHeight = (int) (derivedScale * height); - targetImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB); + targetImage = new BufferedImage(targetWidth, targetHeight, imageType); } return targetImage; } + /** * @return string representation of these parameters (only non-default values are included). */ 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 fd0bb338b..1dce6b229 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 @@ -362,6 +362,42 @@ public Response renderTiffImageForBox(@PathParam("owner") final String owner, } } + @Path("v1/owner/{owner}/project/{project}/stack/{stack}/z/{z}/box/{x},{y},{width},{height},{scale}/tiff16-image") + @GET + @Produces(RenderServiceUtil.IMAGE_TIFF_MIME_TYPE) + @ApiOperation( + tags = "Bounding Box Image APIs", + value = "Render TIFF image for the specified bounding box") + public Response renderTiff16ImageForBox(@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, + @QueryParam("filter") final Boolean filter, + @QueryParam("binaryMask") final Boolean binaryMask, + @QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender, + @QueryParam("minIntensity") final Double minIntensity, + @QueryParam("maxIntensity") final Double maxIntensity, + @QueryParam("channels") final String channels, + @Context final Request request) { + + LOG.info("renderTiffImageForBox: 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, filter, binaryMask, + minIntensity, maxIntensity, channels); + return RenderServiceUtil.renderTiffImage(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/RenderServiceUtil.java b/render-ws/src/main/java/org/janelia/render/service/util/RenderServiceUtil.java index e50389058..08ea2fe54 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 @@ -9,6 +9,7 @@ import org.janelia.alignment.ArgbRenderer; import org.janelia.alignment.BoundingBoxRenderer; import org.janelia.alignment.RenderParameters; +import org.janelia.alignment.ShortRenderer; import org.janelia.alignment.Utils; import org.janelia.render.service.model.IllegalServiceArgumentException; import org.janelia.render.service.model.ServiceException; @@ -86,21 +87,36 @@ public static Response renderPngImage(final RenderParameters renderParameters, responseHelper); } + public static Response renderTiffImage(final RenderParameters renderParameters, + final Integer maxTileSpecsToRender, + final ResponseHelper responseHelper) { +return renderTiffImage(renderParameters, maxTileSpecsToRender, responseHelper, false); +} + public static Response renderTiffImage(final RenderParameters renderParameters, final Integer maxTileSpecsToRender, - final ResponseHelper responseHelper) { + final ResponseHelper responseHelper, + final boolean render16bit) { return renderImageStream(renderParameters, Utils.TIFF_FORMAT, IMAGE_TIFF_MIME_TYPE, maxTileSpecsToRender, - responseHelper); + responseHelper, + render16bit); + } + public static Response renderImageStream(final RenderParameters renderParameters, + final String format, + final String mimeType, + final Integer maxTileSpecsToRender, + final ResponseHelper responseHelper) { + return renderImageStream(renderParameters, format, mimeType, maxTileSpecsToRender, responseHelper,false); } - public static Response renderImageStream(final RenderParameters renderParameters, final String format, final String mimeType, final Integer maxTileSpecsToRender, - final ResponseHelper responseHelper) { + final ResponseHelper responseHelper, + final boolean render16bit) { LOG.info("renderImageStream: entry, format={}, mimeType={}", format, mimeType); @@ -114,7 +130,8 @@ public static Response renderImageStream(final RenderParameters renderParameters (renderParameters.numberOfTileSpecs() > maxTileSpecsToRender); final BufferedImage targetImage = validateParametersAndRenderImage(renderParameters, - renderBoundingBoxesOnly); + renderBoundingBoxesOnly, + render16bit); final BufferedImageStreamingOutput out = new BufferedImageStreamingOutput(targetImage, format, @@ -149,9 +166,13 @@ public static Response streamImageFile(final File imageFile, return response; } - private static BufferedImage validateParametersAndRenderImage(final RenderParameters renderParameters, - final boolean renderBoundingBoxesOnly) + final boolean renderBoundingBoxesOnly){ + return validateParametersAndRenderImage(renderParameters, renderBoundingBoxesOnly,false); + } + private static BufferedImage validateParametersAndRenderImage(final RenderParameters renderParameters, + final boolean renderBoundingBoxesOnly, + final boolean render16bit) throws IllegalArgumentException, IllegalStateException { LOG.info("validateParametersAndRenderImage: entry, renderParameters={}", renderParameters); @@ -160,20 +181,29 @@ private static BufferedImage validateParametersAndRenderImage(final RenderParame renderParameters.validate(); renderParameters.setNumberOfThreads(1); // service requests should always be single threaded - final BufferedImage targetImage = renderParameters.openTargetImage(); + final BufferedImage targetImage; if (renderBoundingBoxesOnly) { - + targetImage = renderParameters.openTargetImage(); final BoundingBoxRenderer boundingBoxRenderer = new BoundingBoxRenderer(renderParameters, Color.GREEN); boundingBoxRenderer.render(targetImage); } else { // otherwise render the real thing ... - - ArgbRenderer.render(renderParameters, - targetImage, - SharedImageProcessorCache.getInstance()); + if (render16bit) { + targetImage = renderParameters.openTargetImage(BufferedImage.TYPE_USHORT_GRAY); + ShortRenderer.render(renderParameters, + targetImage, + SharedImageProcessorCache.getInstance()); + } + else{ + targetImage = renderParameters.openTargetImage(); + ArgbRenderer.render(renderParameters, + targetImage, + SharedImageProcessorCache.getInstance()); + } + } From b46817e6a3ac203ef3a62990359798098dfaf0b0 Mon Sep 17 00:00:00 2001 From: Eric Perlman Date: Mon, 29 Jan 2018 21:40:46 -0500 Subject: [PATCH 2/3] First pass of 16-bit PNG support. --- .../render/service/RenderImageService.java | 70 ++++++++++++++----- .../util/BufferedImageStreamingOutput.java | 70 +++++++++++++------ .../service/util/RenderServiceUtil.java | 22 ++++-- 3 files changed, 118 insertions(+), 44 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 1dce6b229..7eebcddc6 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 @@ -367,23 +367,23 @@ public Response renderTiffImageForBox(@PathParam("owner") final String owner, @Produces(RenderServiceUtil.IMAGE_TIFF_MIME_TYPE) @ApiOperation( tags = "Bounding Box Image APIs", - value = "Render TIFF image for the specified bounding box") + value = "Render 16-bit TIFF image for the specified bounding box") public Response renderTiff16ImageForBox(@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, - @QueryParam("filter") final Boolean filter, - @QueryParam("binaryMask") final Boolean binaryMask, - @QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender, - @QueryParam("minIntensity") final Double minIntensity, - @QueryParam("maxIntensity") final Double maxIntensity, - @QueryParam("channels") final String channels, - @Context final Request request) { + @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, + @QueryParam("filter") final Boolean filter, + @QueryParam("binaryMask") final Boolean binaryMask, + @QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender, + @QueryParam("minIntensity") final Double minIntensity, + @QueryParam("maxIntensity") final Double maxIntensity, + @QueryParam("channels") final String channels, + @Context final Request request) { LOG.info("renderTiffImageForBox: entry"); @@ -398,6 +398,44 @@ 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, + @QueryParam("filter") final Boolean filter, + @QueryParam("binaryMask") final Boolean binaryMask, + @QueryParam("maxTileSpecsToRender") final Integer maxTileSpecsToRender, + @QueryParam("minIntensity") final Double minIntensity, + @QueryParam("maxIntensity") final Double maxIntensity, + @QueryParam("channels") final String channels, + @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, filter, binaryMask, + minIntensity, maxIntensity, channels); + 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) 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..9b0f52df1 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,59 @@ 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.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); + ImageLineInt line = new ImageLineInt(imageInfo); + + final DataBufferUShort dataBuffer = (DataBufferUShort) bufferedImage.getRaster().getDataBuffer(); + if (dataBuffer.getNumBanks() != 1) { + throw new IOException("invalid number of banks (" + dataBuffer.getNumBanks() + "), must be 1"); + } + + for (int row = 0; row < imageInfo.rows; row++) { + for (int col = 0; col < imageInfo.cols; col++) { + // TODO: Can one directly write to line.scanline? (Look at pngj v2 docs). + ImageLineHelper.setValD(line, col, ImageLineHelper.int2double(line, dataBuffer.getElem(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 08ea2fe54..5bcb80e60 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 f68900164723db2f42bf5de6b0557faf30489e97 Mon Sep 17 00:00:00 2001 From: Eric Perlman Date: Tue, 30 Jan 2018 22:32:41 -0500 Subject: [PATCH 3/3] Improved write to scanline. --- .../render/service/util/BufferedImageStreamingOutput.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 9b0f52df1..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 @@ -121,17 +121,18 @@ public static void writePngImage(final BufferedImage bufferedImage, final PngWriter pngWriter = new PngWriter(outputStream, imageInfo); pngWriter.setCompLevel(compressionLevel); pngWriter.setFilterType(filterType); - ImageLineInt line = new ImageLineInt(imageInfo); final DataBufferUShort dataBuffer = (DataBufferUShort) bufferedImage.getRaster().getDataBuffer(); if (dataBuffer.getNumBanks() != 1) { throw new IOException("invalid number of banks (" + dataBuffer.getNumBanks() + "), must be 1"); } + 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++) { - // TODO: Can one directly write to line.scanline? (Look at pngj v2 docs). - ImageLineHelper.setValD(line, col, ImageLineHelper.int2double(line, dataBuffer.getElem(row * imageInfo.cols + col))); + scanline[col] = dataBuffer.getData()[row * imageInfo.cols + col]; } pngWriter.writeRow(line, row); }