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 14360e8fc..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 @@ -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) @@ -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, 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..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,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; @@ -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); @@ -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); @@ -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"); + } + } } 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 184fc5aaf..fb40b254c 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 = "application/octet-stream"; public static void throwServiceException(final Throwable t) throws ServiceException { @@ -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, @@ -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, @@ -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(); } @@ -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()); } - }