/* * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * * 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 boofcv.alg.misc; import boofcv.core.image.FactoryGImageGray; import boofcv.core.image.GImageGray; import boofcv.core.image.GeneralizedImageOps; import boofcv.struct.image.ImageBase; import boofcv.struct.image.ImageGray; import boofcv.struct.image.ImageInterleaved; import org.junit.Test; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Random; import static org.junit.Assert.*; /** * @author Peter Abeles */ public class TestImageMiscOps { int width = 10; int height = 15; int numBands = 3; Random rand = new Random(234); @Test public void checkAll() { int numExpected = 19*6 + 4*8; Method methods[] = ImageMiscOps.class.getMethods(); // sanity check to make sure the functions are being found int numFound = 0; for (Method m : methods) { if( !isTestMethod(m)) continue; try { // System.out.println(m.getName()); if( m.getName().compareTo("copy") == 0 ) { testCopy(m); } else if( m.getName().compareTo("fill") == 0 ) { testFill(m); } else if( m.getName().compareTo("fillBand") == 0 ) { testFillBand(m); } else if( m.getName().compareTo("insertBand") == 0 ) { testInsertBand(m); } else if( m.getName().compareTo("fillBorder") == 0 ) { testFillBorder(m); } else if( m.getName().compareTo("fillRectangle") == 0 ) { testFillRectangle(m); } else if( m.getName().compareTo("fillUniform") == 0 ) { testFillUniform(m); } else if( m.getName().compareTo("fillGaussian") == 0 ) { testFillGaussian(m); } else if( m.getName().compareTo("addUniform") == 0 ) { testAddUniform(m); } else if( m.getName().compareTo("addGaussian") == 0 ) { testAddGaussian(m); } else if( m.getName().compareTo("flipVertical") == 0 ) { testFlipVertical(m); } else if( m.getName().compareTo("flipHorizontal") == 0 ) { testFlipHorizontal(m); } else if( m.getName().compareTo("rotateCW") == 0 ) { testRotateCW(m); } else if( m.getName().compareTo("rotateCCW") == 0 ) { testRotateCCW(m); } else { throw new RuntimeException("Unknown function"); } } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } numFound++; } // update this as needed when new functions are added if(numExpected != numFound) throw new RuntimeException("Unexpected number of methods: Found "+numFound+" expected "+numExpected); } private boolean isTestMethod(Method m ) { Class<?> param[] = m.getParameterTypes(); if( param.length < 1 ) return false; for( int i = 0; i < param.length; i++ ) { if( ImageBase.class.isAssignableFrom(param[i] )) return true; } return false; } private void testCopy( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray src = GeneralizedImageOps.createSingleBand(paramTypes[6], width, height); ImageGray dst = GeneralizedImageOps.createSingleBand(paramTypes[7], width, height); GImageMiscOps.fillUniform(src, rand, 0,20); GImageMiscOps.fillUniform(dst, rand, 0,20); int w = 5,h=8; int x0=1,y0=2; int x1=3,y1=4; m.invoke(null,1,2,3,4,w,h,src,dst); GImageGray a = FactoryGImageGray.wrap(src); GImageGray b = FactoryGImageGray.wrap(dst); for( int i = 0; i < h; i++ ) { for( int j = 0; j < w; j++ ) { assertEquals(a.get(x0+j,y0+i).doubleValue(),b.get(x1+j,y1+i).doubleValue(),1e-4); } } } private void testFill( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( ImageGray.class.isAssignableFrom(paramTypes[0])) { testFill_Single(m); } else { if( paramTypes[1].isArray()) testFill_Interleaved_array(m); else testFill_Interleaved(m); } } private void testFill_Single( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); GImageMiscOps.fillUniform(orig, rand, 0, 20); if( orig.getDataType().isInteger()) { m.invoke(null,orig,10); } else { m.invoke(null,orig,10.0f); } GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { assertEquals(10.0,a.get(j,i).doubleValue(),1e-4); } } } private void testFill_Interleaved(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, numBands); GImageMiscOps.fillUniform(orig, rand, 0, 20); if( orig.getDataType().isInteger()) { m.invoke(null,orig,10); } else { m.invoke(null,orig,10.0f); } for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for( int band = 0; band < numBands; band++ ) { double value = GeneralizedImageOps.get(orig,j,i,band); assertEquals(10.0,value,1e-4); } } } } private void testFill_Interleaved_array(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, numBands); GImageMiscOps.fillUniform(orig, rand, 0,20); Object array = Array.newInstance(paramTypes[1].getComponentType(), numBands); for (int i = 0; i < numBands; i++) { Array.set(array, i, 2 * i + 1); } m.invoke(null, orig, array); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for( int band = 0; band < numBands; band++ ) { double value = GeneralizedImageOps.get(orig,j,i,band); assertEquals(2*band+1,value,1e-4); } } } } private void testFillBand(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, numBands); for (int band = 0; band < numBands; band++) { GImageMiscOps.fillUniform(orig, rand, 0, 20); if( orig.getDataType().isInteger()) { m.invoke(null,orig,band,10); } else { m.invoke(null,orig,band,10.0f); } int numMatch = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for (int k = 0; k < numBands; k++) { double value = GeneralizedImageOps.get(orig,j,i,k); if( k == band ) { assertEquals(10.0,value,1e-4); } else { if( 10.0 == value ) numMatch++; } } } } assertFalse(numMatch > width * height * (numBands - 1) / 5); } } private void testInsertBand(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray input = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); ImageInterleaved output = GeneralizedImageOps.createInterleaved(paramTypes[2], width, height, numBands); GImageMiscOps.fillUniform(input, rand, 0, 20); for (int band = 0; band < numBands; band++) { GImageMiscOps.fillUniform(output, rand, 0, 20); m.invoke(null, input, band, output); int numMatch = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { double valueIn = GeneralizedImageOps.get(input,j,i); for (int k = 0; k < numBands; k++) { double valueOut = GeneralizedImageOps.get(output,j,i,k); if( k == band ) { assertEquals(valueIn,valueOut,1e-4); } else { if( valueIn == valueOut ) numMatch++; } } } } assertFalse(numMatch > width * height * (numBands - 1) / 5); } } private void testFillBorder( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); GImageMiscOps.fill(orig, 4); int r = 2; if( orig.getDataType().isInteger()) { m.invoke(null,orig,5,r); } else { m.invoke(null,orig,5,r); } GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { if( j < r || i < r || j >= width-r || i >= height-r ) assertEquals(i+" "+j,5,a.get(j,i).doubleValue(),1e-4); else assertEquals(4,a.get(j,i).doubleValue(),1e-4); } } } private void testFillRectangle( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( ImageGray.class.isAssignableFrom(paramTypes[0])) { testFillRectangle_Single(m); } else { testFillRectangle_Interleaved(m); } } private void testFillRectangle_Single( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); int x0 = 2; int y0 = 3; int width = 5; int height = 6; if( orig.getDataType().isInteger() ) { m.invoke(null,orig,10,x0,y0,width,height); } else { m.invoke(null,orig,10.0f,x0,y0,width,height); } GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { if( j < x0 || i < y0 || i >= (x0+width) || j >= (y0+height )) assertEquals(j+" "+i,0.0,a.get(j,i).doubleValue(),1e-4); else assertEquals(10.0,a.get(j,i).doubleValue(),1e-4); } } } private void testFillRectangle_Interleaved( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, 2); int x0 = 2; int y0 = 3; int width = 5; int height = 6; Class numParam = paramTypes[1]; if( numParam == byte.class ) { m.invoke(null, orig, (byte) 10, x0, y0, width, height); } else if( numParam == short.class ) { m.invoke(null,orig,(short)10,x0,y0,width,height); } else if( numParam == int.class ) { m.invoke(null,orig,10,x0,y0,width,height); } else if( numParam == long.class ) { m.invoke(null,orig,(long)10,x0,y0,width,height); } else { m.invoke(null,orig,10.0f,x0,y0,width,height); } for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for (int band = 0; band < orig.numBands; band++) { double value = GeneralizedImageOps.get(orig,j,i,band); if( j < x0 || i < y0 || i >= (x0+width) || j >= (y0+height )) assertEquals(j+" "+i,0.0,value,1e-4); else assertEquals(10.0,value,1e-4); } } } } private void testFillUniform( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( ImageGray.class.isAssignableFrom(paramTypes[0])) { testFillUniform_Single(m); } else { testFillUniform_Interleaved(m); } } private void testFillUniform_Single(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); if( orig.getDataType().isInteger() ) { if( orig.getDataType().isSigned() ) m.invoke(null,orig,rand,-10,10); else { m.invoke(null,orig,rand,1,10); } } else { m.invoke(null,orig,rand,-10,10); } int numZero = 0; GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { double value = a.get(j,i).doubleValue(); assertTrue("value = "+value,value>=-10 && value < 10); if( value == 0 ) numZero++; } } assertTrue( numZero < width*height ); } private void testFillUniform_Interleaved(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, numBands); if( orig.getDataType().isInteger() ) { if( orig.getDataType().isSigned() ) m.invoke(null,orig,rand,-10,10); else { m.invoke(null,orig,rand,1,10); } } else { m.invoke(null,orig,rand,-10,10); } int numZero = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for( int band = 0; band < numBands; band++ ) { double value = GeneralizedImageOps.get(orig,j,i,band); assertTrue("value = "+value,value>=-10 && value < 10); if( value == 0 ) numZero++; } } } assertTrue( numZero < width*height*numBands ); } private void testFillGaussian( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( ImageGray.class.isAssignableFrom(paramTypes[0])) { testFillGaussian_Single(m); } else { testFillGaussian_Interleaved(m); } } private void testFillGaussian_Single(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); if( orig.getDataType().isSigned() ) m.invoke(null,orig,rand,0,5,-2,2); else { m.invoke(null,orig,rand,5,7,0,12); } int numZero = 0; GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { double value = a.get(j,i).doubleValue(); if( orig.getDataType().isSigned() ) { assertTrue("value = "+value,value>=-2 && value <= 2); } else { assertTrue("value = "+value,value>=0 && value <= 12); } if( value == 0 ) numZero++; } } assertTrue( numZero < width*height ); } private void testFillGaussian_Interleaved(Method m) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, 2); if( orig.getDataType().isSigned() ) m.invoke(null,orig,rand,0,5,-2,2); else { m.invoke(null,orig,rand,5,7,0,12); } int numZero = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for (int band = 0; band < orig.numBands; band++) { double value = GeneralizedImageOps.get(orig,j,i,band); if( orig.getDataType().isSigned() ) { assertTrue("value = "+value,value>=-2 && value <= 2); } else { assertTrue("value = "+value,value>=0 && value <= 12); } if( value == 0 ) numZero++; } } } assertTrue( numZero < width*height*2 ); } private void testAddUniform( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( ImageGray.class.isAssignableFrom(paramTypes[0])) { testAddUniform_Single(m); } else { testAddUniform_Interleaved(m); } } private void testAddUniform_Single( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); GImageMiscOps.fill(orig, 1); if( orig.getDataType().isInteger() ) { m.invoke(null,orig,rand,1,10); } else { m.invoke(null,orig,rand,1,10); } GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { double value = a.get(j,i).doubleValue(); assertTrue(value>=-2 && value <= 11); } } } private void testAddUniform_Interleaved( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, 2); GImageMiscOps.fill(orig, 1); if( orig.getDataType().isInteger() ) { m.invoke(null,orig,rand,1,10); } else { m.invoke(null,orig,rand,1,10); } for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for (int band = 0; band < orig.numBands; band++) { double value = GeneralizedImageOps.get(orig,j,i,band); assertTrue(value>=-2 && value <= 11); } } } } private void testAddGaussian( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( ImageGray.class.isAssignableFrom(paramTypes[0])) { testAddGaussian_Single(m); } else { testAddGaussian_Interleaved(m); } } private void testAddGaussian_Single( Method m ) throws InvocationTargetException, IllegalAccessException { double mean = 10; Class paramTypes[] = m.getParameterTypes(); ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); GImageMiscOps.fill(orig,mean); m.invoke(null,orig,rand,2.0,0,255); double stdev2 = 0; GImageGray a = FactoryGImageGray.wrap(orig); for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { double value = a.get(j,i).doubleValue(); stdev2 += (value-mean)*(value-mean); } } GImageMiscOps.fill(orig,mean); m.invoke(null, orig, rand, 10.0, 0, 255); double stdev10 = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { double value = a.get(j,i).doubleValue(); stdev10 += (value-mean)*(value-mean); } } // see if the gaussian with the larger variance creates a noisier image assertTrue(stdev2<stdev10); } private void testAddGaussian_Interleaved( Method m ) throws InvocationTargetException, IllegalAccessException { double mean = 10; Class paramTypes[] = m.getParameterTypes(); ImageInterleaved orig = GeneralizedImageOps.createInterleaved(paramTypes[0], width, height, 2); GImageMiscOps.fill(orig,mean); m.invoke(null,orig,rand,2.0,0,255); double stdev2 = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for (int band = 0; band < orig.numBands; band++) { double value = GeneralizedImageOps.get(orig,j,i,band); stdev2 += (value-mean)*(value-mean); } } } GImageMiscOps.fill(orig,mean); m.invoke(null, orig, rand, 10.0, 0, 255); double stdev10 = 0; for( int i = 0; i < height; i++ ) { for( int j = 0; j < width; j++ ) { for (int band = 0; band < orig.numBands; band++) { double value = GeneralizedImageOps.get(orig, j, i, band); stdev10 += (value - mean) * (value - mean); } } } // see if the gaussian with the larger variance creates a noisier image assertTrue(stdev2<stdev10); } private void testFlipVertical(Method m) throws InvocationTargetException, IllegalAccessException { // test with an even and odd height testFlipVertical(m, height); testFlipVertical(m, height + 1); } private void testFlipVertical(Method m, int height) throws IllegalAccessException, InvocationTargetException { Class paramTypes[] = m.getParameterTypes(); ImageGray imgA = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); GImageMiscOps.fillUniform(imgA, rand, 0, 100); ImageGray imgB = (ImageGray)imgA.clone(); m.invoke(null, imgB); for( int y = 0; y < height; y++ ) { for( int x = 0; x < width; x++ ) { double valA = GeneralizedImageOps.get(imgA,x,height-y-1); double valB = GeneralizedImageOps.get(imgB,x,y); assertTrue(valA==valB); } } } private void testFlipHorizontal(Method m) throws InvocationTargetException, IllegalAccessException { // test with an even and odd height testFlipHorizontal(m, width); testFlipHorizontal(m, width + 1); } private void testFlipHorizontal(Method m, int width) throws IllegalAccessException, InvocationTargetException { Class paramTypes[] = m.getParameterTypes(); ImageGray imgA = GeneralizedImageOps.createSingleBand(paramTypes[0], width, height); GImageMiscOps.fillUniform(imgA, rand, 0, 100); ImageGray imgB = (ImageGray)imgA.clone(); m.invoke(null, imgB); for( int y = 0; y < height; y++ ) { for( int x = 0; x < width; x++ ) { double valA = GeneralizedImageOps.get(imgA,width-x-1,y); double valB = GeneralizedImageOps.get(imgB,x,y); assertTrue(valA==valB); } } } public void testRotateCW( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( paramTypes.length == 1 ) { testRotateCW_one(m); } else { testRotateCW_two(m); } } public void testRotateCW_one( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); // test even and odd width for (int i = 0; i < 2; i++) { int w = 6+i; ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], w, w); GImageMiscOps.fillUniform(orig, rand, 0, 10); ImageGray rotated = (ImageGray)orig.clone(); m.invoke(null, rotated); for (int y = 0; y < w; y++) { for (int x = 0; x < w; x++) { double expected = GeneralizedImageOps.get(orig,x,y); double found = GeneralizedImageOps.get(rotated,w-y-1,x); assertEquals(x+" "+y,expected,found,1e-8); } } } } public void testRotateCW_two( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray a = GeneralizedImageOps.createSingleBand(paramTypes[0], 3, 2); ImageGray b = GeneralizedImageOps.createSingleBand(paramTypes[0], 2, 3); for (int i = 0; i < 6; i++) { GeneralizedImageOps.set(a,i%3,i/3,i); } m.invoke(null,a,b); assertEquals(3,GeneralizedImageOps.get(b,0,0),1e-8); assertEquals(0,GeneralizedImageOps.get(b,1,0),1e-8); assertEquals(4,GeneralizedImageOps.get(b,0,1),1e-8); assertEquals(1,GeneralizedImageOps.get(b,1,1),1e-8); assertEquals(5,GeneralizedImageOps.get(b,0,2),1e-8); assertEquals(2,GeneralizedImageOps.get(b,1,2),1e-8); } public void testRotateCCW( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); if( paramTypes.length == 1 ) { testRotateCCW_one(m); } else { testRotateCCW_two(m); } } public void testRotateCCW_one( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); // test even and odd width for (int i = 0; i < 2; i++) { int w = 6+i; ImageGray orig = GeneralizedImageOps.createSingleBand(paramTypes[0], w, w); GImageMiscOps.fillUniform(orig, rand, 0, 10); ImageGray rotated = (ImageGray)orig.clone(); m.invoke(null,rotated); for (int y = 0; y < w; y++) { for (int x = 0; x < w; x++) { double expected = GeneralizedImageOps.get(orig,x,y); double found = GeneralizedImageOps.get(rotated,y,w-x-1); assertEquals(expected,found,1e-8); } } } } public void testRotateCCW_two( Method m ) throws InvocationTargetException, IllegalAccessException { Class paramTypes[] = m.getParameterTypes(); ImageGray a = GeneralizedImageOps.createSingleBand(paramTypes[0], 3, 2); ImageGray b = GeneralizedImageOps.createSingleBand(paramTypes[0], 2, 3); for (int i = 0; i < 6; i++) { GeneralizedImageOps.set(a,i%3,i/3,i); } m.invoke(null,a,b); assertEquals(2,GeneralizedImageOps.get(b,0,0),1e-8); assertEquals(5,GeneralizedImageOps.get(b,1,0),1e-8); assertEquals(1,GeneralizedImageOps.get(b,0,1),1e-8); assertEquals(4,GeneralizedImageOps.get(b,1,1),1e-8); assertEquals(0,GeneralizedImageOps.get(b,0,2),1e-8); assertEquals(3,GeneralizedImageOps.get(b,1,2),1e-8); } }