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

16-bit PNG and raw image support #100

Merged
merged 4 commits into from
Jul 9, 2019
Merged
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
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