/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 - 2016 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.jaiext.classifier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import it.geosolutions.jaiext.piecewise.TransformationException; import it.geosolutions.jaiext.range.RangeFactory; import it.geosolutions.jaiext.testclasses.TestBase; import it.geosolutions.jaiext.testclasses.TestData; import it.geosolutions.rendered.viewer.RenderedImageBrowser; import java.awt.Color; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import javax.media.jai.JAI; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.PlanarImage; import javax.media.jai.RasterFactory; import javax.media.jai.RenderedOp; import javax.media.jai.TiledImage; import javax.xml.crypto.dsig.TransformException; import org.junit.Test; /** * Test class for the RasterClassifier operation * * @author Simone Giannecchini, GeoSolutions * @author Nicola Lagomarsini, GeoSolutions * * @source $URL$ */ public class RasterClassifierTest extends TestBase { private static final int TEST_NUM = 1; /** * Test with a synthetic image with Double Sample Model * * @throws IOException */ @Test public void testSyntheticDouble() throws IOException { // ///////////////////////////////////////////////////////////////////// // // This test uses a Double datatype raster. We are also using some // synthetic data where there is no NoData. // // ///////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////// // // Set the pixel values. Because we use only one tile with one band, // the // code below is pretty similar to the code we would have if we were // just setting the values in a matrix. // // ///////////////////////////////////////////////////////////////////// final BufferedImage image = getSyntheticDoubleImage(); for (int i = 0; i < TEST_NUM; i++) { // ///////////////////////////////////////////////////////////////////// // // Build the categories // // ///////////////////////////////////////////////////////////////////// LinearColorMap list = buildCategories(); // Operation creation final ParameterBlockJAI pbj = new ParameterBlockJAI( RasterClassifierOpImage.OPERATION_NAME); pbj.addSource(image); pbj.setParameter("Domain1D", list); final RenderedOp finalimage = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); if (INTERACTIVE) RenderedImageBrowser.showChain(finalimage, false, false, null); else finalimage.getTiles(); finalimage.dispose(); } } @Test public void testHugeDataset() throws IOException { final int width = 20000; final int height = 20000; ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_FLOAT); SampleModel sm = cm.createCompatibleSampleModel(512, 512); PlanarImage image = new TiledImage(0, 0, width, height, 0, 0, sm, cm); LinearColorMap list = buildCategories(); final ParameterBlockJAI pbj = new ParameterBlockJAI(RasterClassifierOpImage.OPERATION_NAME); pbj.addSource(image); pbj.setParameter("Domain1D", list); final RenderedOp finalImage = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); boolean success = true; try { Raster raster = finalImage.getTile(0, 0); assertNotNull(raster); } catch (RuntimeException ie) { // Before the getData fix the getTiles call was throwing an Exception // due to OOM on allocating that big raster success = false; } assertTrue(success); } /** * Synthetic Image with Double Sample Model * * @return {@linkplain BufferedImage} */ private BufferedImage getSyntheticDoubleImage() { final int width = 500; final int height = 500; // Create the raster final WritableRaster raster = RasterFactory.createBandedRaster(DataBuffer.TYPE_DOUBLE, width, height, 1, null); // Define the elements for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { raster.setSample(x, y, 0, (x + y)); } } // Define the colormodel final ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_DOUBLE); // Create the image final BufferedImage image = new BufferedImage(cm, raster, false, null); return image; } /** * Test with a synthetic image with Float Sample Model * * @throws IOException */ @Test public void testSyntheticFloat() throws IOException { // ///////////////////////////////////////////////////////////////////// // // This test uses a Float datatype raster. We are also using some // synthetic data where there is no NoData. // // ///////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////// // // Set the pixel values. Because we use only one tile with one band, // the // code below is pretty similar to the code we would have if we were // just setting the values in a matrix. // // ///////////////////////////////////////////////////////////////////// final BufferedImage image = getSyntheticFloatImage(); for (int i = 0; i < TEST_NUM; i++) { // ///////////////////////////////////////////////////////////////////// // // Build the categories // // ///////////////////////////////////////////////////////////////////// LinearColorMap list = buildCategories(); final ParameterBlockJAI pbj = new ParameterBlockJAI( RasterClassifierOpImage.OPERATION_NAME); pbj.addSource(image); pbj.setParameter("Domain1D", list); final RenderedOp finalimage = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); if (INTERACTIVE) RenderedImageBrowser.showChain(finalimage, false, false, null); else finalimage.getTiles(); finalimage.dispose(); } } private LinearColorMap buildCategories() { final LinearColorMapElement c0 = LinearColorMapElement.create("c0", Color.BLACK, RangeFactory.create(Double.NEGATIVE_INFINITY, false, 10, true), 0); final LinearColorMapElement c1 = LinearColorMapElement.create("c2", Color.blue, RangeFactory.create(10.0f, false, 100.0f, true), 1); final LinearColorMapElement c3 = LinearColorMapElement.create("c3", Color.green, RangeFactory.create(100.0f, false, 300.0f, true), 2); final LinearColorMapElement c4 = LinearColorMapElement.create("c4", new Color[] { Color.green, Color.red }, RangeFactory.create(300.0f, false, 400.0f, true), RangeFactory.create(3, 1000)); final LinearColorMapElement c5 = LinearColorMapElement.create("c5", new Color[] { Color.red, Color.white }, RangeFactory.create(400.0f, false, 1000.0f, true), RangeFactory.create(1001, 2000)); final LinearColorMapElement c6 = LinearColorMapElement.create("c6", Color.red, 1001.0f, 2001); final LinearColorMapElement c7 = LinearColorMapElement.create("nodata", new Color(0, 0, 0, 0), RangeFactory.create(Double.NaN, Double.NaN), 2201); final LinearColorMap list = new LinearColorMap("", new LinearColorMapElement[] { c0, c1, c3, c4, c5, c6 }, new LinearColorMapElement[] { c7 }); return list; } /** * Building a synthetic image upon a float Sample Model. * * @return {@linkplain BufferedImage} */ private BufferedImage getSyntheticFloatImage() { final int width = 500; final int height = 500; // Define the Raster final WritableRaster raster = RasterFactory.createBandedRaster(DataBuffer.TYPE_FLOAT, width, height, 1, null); // Populate raster for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { raster.setSample(x, y, 0, (x + y)); } } // Define the colormodel final ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_FLOAT); // Create the image final BufferedImage image = new BufferedImage(cm, raster, false, null); return image; } /** * NoData only test-case. * * @throws IOException * @throws TransformException */ @Test public void testNoDataOnly() throws IOException, TransformationException { // ///////////////////////////////////////////////////////////////////// // // We are covering here a case that can often be verified, i.e. the case // when only NoData values are known and thus explicitly mapped by the // user to a defined nodata DomainElement, but not the same for the // others. // In such case we want CategoryLists automatically map unknown data to // a Passthrough DomainElement, which identically maps raster data to // category // data. // // ///////////////////////////////////////////////////////////////////// for (int i = 0; i < TEST_NUM; i++) { final LinearColorMapElement n0 = LinearColorMapElement.create("nodata", new Color(0, 0, 0, 0), RangeFactory.create(Double.NaN, Double.NaN), 9999); final LinearColorMap list = new LinearColorMap("", new LinearColorMapElement[] { n0 }); double testNum = Math.random(); boolean exceptionThrown = false; try { assertEquals(list.transform(testNum), testNum, 0.0); } catch (Exception e) { exceptionThrown = true; } assertTrue(exceptionThrown); assertEquals(list.transform(Double.NaN), 9999, 0.0); } } /** * Spearfish test-case. * * @throws IOException */ @Test public void testSpearfish() throws IOException { // ///////////////////////////////////////////////////////////////////// // // This test is quite standard since the NoData category specified // is for NoData values since the input file is a GRASS file // where the missing values are represented by 255. The only strange thing // that we try here is that we map two different classes to the same // color with the same index. // // ///////////////////////////////////////////////////////////////////// final RenderedImage image = getSpearfhisDemo(); for (int i = 0; i < TEST_NUM; i++) { final LinearColorMapElement c0 = LinearColorMapElement.create("c0", Color.yellow, RangeFactory.create(0, true, 11, true), 5); final LinearColorMapElement c1 = LinearColorMapElement.create("c2", Color.blue, RangeFactory.create(11, false, 12, true), 1); final LinearColorMapElement c3 = LinearColorMapElement.create("c3", Color.green, RangeFactory.create(12, false, 14, true), 7); final LinearColorMapElement c4 = LinearColorMapElement.create("c4", Color.blue, RangeFactory.create(14, false, 16, true), 1); final LinearColorMapElement c5 = LinearColorMapElement.create("c4", Color.CYAN, RangeFactory.create(16, false, 255, false), 11); final LinearColorMapElement c6 = LinearColorMapElement.create("nodata", new Color(0, 0, 0, 0), RangeFactory.create(255, 255), 0); final LinearColorMap list = new LinearColorMap("", new LinearColorMapElement[] { c0, c1, c3, c4, c5 }, new LinearColorMapElement[] { c6 }); final ParameterBlockJAI pbj = new ParameterBlockJAI( RasterClassifierOpImage.OPERATION_NAME); pbj.addSource(image); pbj.setParameter("Domain1D", list); final RenderedOp finalimage = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); if (INTERACTIVE) RenderedImageBrowser.showChain(finalimage, false, false, null); else finalimage.getTiles(); finalimage.dispose(); } } /** * Building an image based on Spearfish data. * * @return {@linkplain BufferedImage} * * @throws IOException * @throws FileNotFoundException */ private RenderedImage getSpearfhisDemo() throws IOException, FileNotFoundException { File spearfish = TestData.file(this, "spearfish.png"); RenderedOp image = JAI.create("ImageRead", spearfish); return image; } /** * SWAN test-case. We generate an image similar to the SWAN dataset. * * @throws IOException */ @Test public void testSWAN() throws IOException { // ///////////////////////////////////////////////////////////////////// // // This test is interesting since it can be used to simulate the // case where someone specifies a ColorMap that overlaps with the native // NoData value. // // ///////////////////////////////////////////////////////////////////// final RenderedImage image = getSWANData(); for (int i = 0; i < TEST_NUM; i++) { final LinearColorMapElement c0 = LinearColorMapElement.create("c0", Color.green, RangeFactory.create(Double.NEGATIVE_INFINITY, 0.3), 51); final LinearColorMapElement c1 = LinearColorMapElement.create("c2", Color.yellow, RangeFactory.create(0.3, false, 0.6, true), 1); final LinearColorMapElement c1b = LinearColorMapElement.create("c2", Color.BLACK, RangeFactory.create(0.3, false, 0.6, true), 1); final LinearColorMapElement c1c = LinearColorMapElement.create("c2", Color.yellow, RangeFactory.create(0.3, false, 0.6, true), 1); assertFalse(c1.equals(c1b)); assertTrue(c1.equals(c1c)); final LinearColorMapElement c3 = LinearColorMapElement.create("c3", Color.red, RangeFactory.create(0.60, false, 0.90, true), 2); final LinearColorMapElement c4 = LinearColorMapElement.create("c4", Color.BLUE, RangeFactory.create(0.9, false, Double.POSITIVE_INFINITY, true), 3); final LinearColorMapElement nodata = LinearColorMapElement.create("nodata", new Color( 0, 0, 0, 0), RangeFactory.create(-9.0, -9.0), 4); final LinearColorMap list = new LinearColorMap("testSWAN", new LinearColorMapElement[] { c0, c1, c3, c4 }, new LinearColorMapElement[] { nodata }, new Color(0, 0, 0)); assertEquals(list.getSourceDimensions(), 1); assertEquals(list.getTargetDimensions(), 1); assertEquals(list.getName().toString(), "testSWAN"); assertNotNull(c0.toString()); final ParameterBlockJAI pbj = new ParameterBlockJAI( RasterClassifierOpImage.OPERATION_NAME); pbj.addSource(image); pbj.setParameter("Domain1D", list); boolean exceptionThrown = false; try { // // // forcing a bad band selection ... // // pbj.setParameter("bandIndex", new Integer(2)); final RenderedOp d = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); d.getTiles(); // we should not be here! } catch (Exception e) { exceptionThrown = true; // // // ... ok, Exception wanted! // // } assertTrue(exceptionThrown); pbj.setParameter("bandIndex", new Integer(0)); final RenderedOp finalimage = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); if (INTERACTIVE) RenderedImageBrowser.showChain(finalimage, false, false, null); else finalimage.getTiles(); finalimage.dispose(); } } /** * SWAN test-case. We generate an image similar to the SWAN dataset. * * @throws IOException */ @Test public void testSWANwithGap() throws IOException { // ///////////////////////////////////////////////////////////////////// // // This test is interesting since it can be used to simulate the // case where someone specifies a ColorMap that overlaps with the native // NoData value. For this SWAN data the NoData value is -9.0 and no // NaN which falls right into the first category. // // We overcome this problem by simply giving higher priority to the // NoData category over the other categories when doing a search for // the right category given a certain value. This force us to // first evaluate the no data category and then evaluate a possible // provided overlapping value. // // This test is also interesting since we create a color map by // providing output indexes that are not ordered and also that are not // all contained in a closed natural interval. As you can notice by // inspecting the different classes below there is an index, 51, which // is way outside the range of the others. // // ///////////////////////////////////////////////////////////////////// final RenderedImage image = getSWANData(); for (int i = 0; i < TEST_NUM; i++) { final LinearColorMapElement c0 = LinearColorMapElement.create("c0", Color.green, RangeFactory.create(Double.NEGATIVE_INFINITY, 0.3), 51); final LinearColorMapElement c1 = LinearColorMapElement.create("c2", Color.yellow, RangeFactory.create(0.3, false, 0.6, true), 1); final LinearColorMapElement c3 = LinearColorMapElement.create("c3", Color.red, RangeFactory.create(0.70, false, 0.90, true), 2); final LinearColorMapElement c4 = LinearColorMapElement.create("c4", Color.BLUE, RangeFactory.create(0.9, false, Double.POSITIVE_INFINITY, true), 3); final LinearColorMapElement nodata = LinearColorMapElement.create("nodata", Color.red, RangeFactory.create(-9.0, -9.0), 4); final LinearColorMap list = new LinearColorMap("testSWAN", new LinearColorMapElement[] { c0, c1, c3, c4 }, new LinearColorMapElement[] { nodata }, new Color(0, 0, 0, 0)); final ParameterBlockJAI pbj = new ParameterBlockJAI( RasterClassifierOpImage.OPERATION_NAME); pbj.addSource(image); pbj.setParameter("Domain1D", list); try { // // // forcing a bad band selection ... // // pbj.setParameter("bandIndex", new Integer(2)); final RenderedOp d = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); d.getTiles(); // we should not be here! assertTrue(false); } catch (Exception e) { // // // ... ok, Exception wanted! // // } pbj.setParameter("bandIndex", new Integer(0)); final RenderedOp finalimage = JAI.create(RasterClassifierOpImage.OPERATION_NAME, pbj); final IndexColorModel icm = (IndexColorModel) finalimage.getColorModel(); assertEquals(icm.getRed(4), 255); assertEquals(icm.getRed(2), 255); if (INTERACTIVE) RenderedImageBrowser.showChain(finalimage, false, false, null); else finalimage.getTiles(); finalimage.dispose(); } } /** * Building an image simulating SWAN data. * * @return {@linkplain BufferedImage} * */ private RenderedImage getSWANData() { final int width = 500; final int height = 500; // Build the raster final WritableRaster raster = RasterFactory.createBandedRaster(DataBuffer.TYPE_DOUBLE, width, height, 1, null); // Populate the raster for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (x == y || x == -y) { raster.setSample(x, y, 0, -9.0); } else { raster.setSample(x, y, 0, Math.random() * 5 - 5); } } } // Define the colormodel final ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_DOUBLE); // Create the image final BufferedImage image = new BufferedImage(cm, raster, false, null); return image; } }