From a9e9a8f449f935198ec1e15d4dfb2866b4a945c6 Mon Sep 17 00:00:00 2001 From: Eric Trautman Date: Wed, 22 Mar 2017 12:00:45 -0400 Subject: [PATCH] Correct stupid width and height calculation bug when deriving width and height for tile render parameters. Refactor core logic into separate method to simplify testing. Add test to catch bug. This fixes #24. --- .../render/service/TileDataService.java | 143 +++++++++--------- .../render/service/TileDataServiceTest.java | 49 ++++++ 2 files changed, 123 insertions(+), 69 deletions(-) create mode 100644 render-ws/src/test/java/org/janelia/render/service/TileDataServiceTest.java diff --git a/render-ws/src/main/java/org/janelia/render/service/TileDataService.java b/render-ws/src/main/java/org/janelia/render/service/TileDataService.java index 5137e93b9..4c7439c4c 100644 --- a/render-ws/src/main/java/org/janelia/render/service/TileDataService.java +++ b/render-ws/src/main/java/org/janelia/render/service/TileDataService.java @@ -142,80 +142,13 @@ public RenderParameters getRenderParameters(@PathParam("owner") final String own RenderParameters parameters = null; try { - final TileSpec tileSpec = getTileSpec(owner, project, stack, tileId, true); - tileSpec.flattenTransforms(); - - double tileRenderX = tileSpec.getMinX(); - double tileRenderY = tileSpec.getMinY(); - - int tileRenderWidth; - if (width == null) { - tileRenderWidth = (int) (tileSpec.getMaxX() - tileSpec.getMinX()); - } else { - tileRenderWidth = width; - } - - int tileRenderHeight; - if (height == null) { - tileRenderHeight = (int) (tileSpec.getMaxY() - tileSpec.getMinY()); - } else { - tileRenderHeight = height; - } - final StackId stackId = new StackId(owner, project, stack); final StackMetaData stackMetaData = getStackMetaData(stackId); - if ((normalizeForMatching != null) && normalizeForMatching) { - - // When deriving point matches for a tile pair in which each tile has different lens correction - // transformations, the bounding box for each rendered tile needs to be normalized. - // - // Normalization is achieved by: - // (1) Removing the last transform spec (assumed to be an affine that positions the tile in the world). - // (2) Setting the render start coordinate to (0,0). - // (3) Padding the raw tile width and height by multiplying a normalization factor (1.05). - // Assuming that all tiles in a stack have the same raw width and height, this ensures - // that normalized tiles also have the same width and height with a little extra room - // for edges that are rotated/skewed by lens correction. - // (4) Ensuring that the normalized width and height are even by adding a pixel as needed. - // - // Consistent and even tile sizes are currently a requirement for generating DMesh point matches. - - final double normalizationFactor = 1.05; - if (width == null) { - tileRenderWidth = (int) (tileSpec.getWidth() * normalizationFactor); - } - if (height == null) { - tileRenderHeight = (int) (tileSpec.getHeight() * normalizationFactor); - } - - tileSpec.removeLastTransformSpec(); + final TileSpec tileSpec = getTileSpec(owner, project, stack, tileId, true); - // If the tile still has more than 3 transforms, remove all but the last 3. - // This assumes that the last 3 transforms are for lens correction. - // Hopefully at some point we'll label transforms so that it is possible to - // explicitly include only lens correction transforms. - while (tileSpec.getTransforms().size() > 3) { - tileSpec.removeLastTransformSpec(); - } - - tileRenderX = 0; - tileRenderY = 0; - - if ((tileRenderWidth % 2) == 1) { - tileRenderWidth++; - } - if ((tileRenderHeight % 2) == 1) { - tileRenderHeight++; - } - } + parameters = getCoreTileRenderParameters(width, height, scale, normalizeForMatching, tileSpec); - parameters = new RenderParameters(null, - tileRenderX, - tileRenderY, - tileRenderWidth, - tileRenderHeight, - scale); parameters.setDoFilter(filter); parameters.setBinaryMask(binaryMask); parameters.setExcludeMask(excludeMask); @@ -340,6 +273,78 @@ public RenderParameters getTileWithNeighborsRenderParameters(@PathParam("owner") return parameters; } + protected static RenderParameters getCoreTileRenderParameters(final Integer width, + final Integer height, + final Double scale, + final Boolean normalizeForMatching, + final TileSpec tileSpec) { + tileSpec.flattenTransforms(); + + double tileRenderX = tileSpec.getMinX(); + double tileRenderY = tileSpec.getMinY(); + + int tileRenderWidth; + if (width == null) { + tileRenderWidth = (int) (tileSpec.getMaxX() - tileSpec.getMinX() + 1); + } else { + tileRenderWidth = width; + } + + int tileRenderHeight; + if (height == null) { + tileRenderHeight = (int) (tileSpec.getMaxY() - tileSpec.getMinY() + 1); + } else { + tileRenderHeight = height; + } + + if ((normalizeForMatching != null) && normalizeForMatching) { + + // When deriving point matches for a tile pair in which each tile has different lens correction + // transformations, the bounding box for each rendered tile needs to be normalized. + // + // Normalization is achieved by: + // (1) Removing the last transform spec (assumed to be an affine that positions the tile in the world). + // (2) Setting the render start coordinate to (0,0). + // (3) Padding the raw tile width and height by multiplying a normalization factor (1.05). + // Assuming that all tiles in a stack have the same raw width and height, this ensures + // that normalized tiles also have the same width and height with a little extra room + // for edges that are rotated/skewed by lens correction. + // (4) Ensuring that the normalized width and height are even by adding a pixel as needed. + // + // Consistent and even tile sizes are currently a requirement for generating DMesh point matches. + + final double normalizationFactor = 1.05; + if (width == null) { + tileRenderWidth = (int) (tileSpec.getWidth() * normalizationFactor); + } + if (height == null) { + tileRenderHeight = (int) (tileSpec.getHeight() * normalizationFactor); + } + + tileSpec.removeLastTransformSpec(); + + // If the tile still has more than 3 transforms, remove all but the last 3. + // This assumes that the last 3 transforms are for lens correction. + // Hopefully at some point we'll label transforms so that it is possible to + // explicitly include only lens correction transforms. + while (tileSpec.getTransforms().size() > 3) { + tileSpec.removeLastTransformSpec(); + } + + tileRenderX = 0; + tileRenderY = 0; + + if ((tileRenderWidth % 2) == 1) { + tileRenderWidth++; + } + if ((tileRenderHeight % 2) == 1) { + tileRenderHeight++; + } + } + + return new RenderParameters(null, tileRenderX, tileRenderY, tileRenderWidth, tileRenderHeight, scale); + } + private StackMetaData getStackMetaData(final StackId stackId) throws ObjectNotFoundException { diff --git a/render-ws/src/test/java/org/janelia/render/service/TileDataServiceTest.java b/render-ws/src/test/java/org/janelia/render/service/TileDataServiceTest.java new file mode 100644 index 000000000..8f6139db7 --- /dev/null +++ b/render-ws/src/test/java/org/janelia/render/service/TileDataServiceTest.java @@ -0,0 +1,49 @@ +package org.janelia.render.service; + +import org.janelia.alignment.RenderParameters; +import org.janelia.alignment.spec.TileSpec; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests the {@link TileDataService} class. + * + * @author Eric Trautman + */ +public class TileDataServiceTest { + + @Test + public void testGetCoreTileRenderParameters() throws Exception { + + final String json = + "{\n" + + " \"tileId\" : \"1,3484_aligned_0_1_flip\",\n" + + " \"z\" : 3484.0, \"minX\" : 1896.0, \"minY\" : 876.0, \"maxX\" : 2919.0, \"maxY\" : 1899.0,\n" + + " \"width\" : 1024.0, \"height\" : 1024.0,\n" + + " \"mipmapLevels\" : {\n" + + " \"0\" : {\n" + + " \"imageUrl\" : \"file:///data/nc-em/russelt/20170227_Princeton_Pinky40/4_aligned_tiled/1,3484_aligned_0_1_flip.png\"\n" + + " }\n" + + " },\n" + + " \"transforms\" : {\n" + + " \"type\" : \"list\",\n" + + " \"specList\" : [ {\n" + + " \"className\" : \"mpicbg.trakem2.transform.AffineModel2D\",\n" + + " \"dataString\" : \"1.0000000000 0.0000000000 0.0000000000 1.0000000000 1896.0000000000 -876.0000000000\"\n" + + " }, {\n" + + " \"className\" : \"mpicbg.trakem2.transform.AffineModel2D\",\n" + + " \"dataString\" : \"1.0000000000 0.0000000000 0.0000000000 1.0000000000 0.0000000000 1752.0000000000\"\n" + + " } ]\n" + + " }\n" + + "}"; + + final TileSpec tileSpec = TileSpec.fromJson(json); + + final RenderParameters renderParameters = + TileDataService.getCoreTileRenderParameters(null, null, null, null, tileSpec); + + Assert.assertEquals("invalid width for tile", 1024, renderParameters.getWidth()); + Assert.assertEquals("invalid height for tile", 1024, renderParameters.getHeight()); + } + +}