Skip to content

Commit 3cd6ab9

Browse files
gselzerctrueden
authored andcommitted
WIP: Features tests
1 parent 2364115 commit 3cd6ab9

13 files changed

Lines changed: 2223 additions & 0 deletions
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
/*
2+
* #%L
3+
* ImageJ software for multidimensional image processing and analysis.
4+
* %%
5+
* Copyright (C) 2014 - 2018 ImageJ developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package net.imagej.ops.features;
31+
32+
import java.io.IOException;
33+
import java.net.URISyntaxException;
34+
import java.nio.file.Files;
35+
import java.nio.file.Paths;
36+
import java.util.ArrayList;
37+
import java.util.HashMap;
38+
import java.util.List;
39+
import java.util.Map;
40+
import java.util.Random;
41+
42+
import net.imagej.mesh.Mesh;
43+
import net.imagej.mesh.naive.NaiveDoubleMesh;
44+
import net.imagej.ops.AbstractOpTest;
45+
import net.imagej.ops.OpService;
46+
import net.imglib2.Cursor;
47+
import net.imglib2.IterableInterval;
48+
import net.imglib2.RandomAccess;
49+
import net.imglib2.RandomAccessibleInterval;
50+
import net.imglib2.RealPoint;
51+
import net.imglib2.img.Img;
52+
import net.imglib2.img.array.ArrayCursor;
53+
import net.imglib2.img.array.ArrayImg;
54+
import net.imglib2.img.array.ArrayImgs;
55+
import net.imglib2.img.basictypeaccess.array.ByteArray;
56+
import net.imglib2.roi.EllipseRegionOfInterest;
57+
import net.imglib2.roi.geom.real.DefaultWritablePolygon2D;
58+
import net.imglib2.roi.geom.real.Polygon2D;
59+
import net.imglib2.roi.labeling.ImgLabeling;
60+
import net.imglib2.roi.labeling.LabelRegion;
61+
import net.imglib2.roi.labeling.LabelRegions;
62+
import net.imglib2.roi.labeling.LabelingType;
63+
import net.imglib2.type.logic.BitType;
64+
import net.imglib2.type.numeric.RealType;
65+
import net.imglib2.type.numeric.integer.IntType;
66+
import net.imglib2.type.numeric.integer.UnsignedByteType;
67+
import net.imglib2.type.numeric.real.FloatType;
68+
import net.imglib2.view.RandomAccessibleIntervalCursor;
69+
70+
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
71+
import org.junit.Before;
72+
import org.scijava.Context;
73+
import org.scijava.util.LongArray;
74+
75+
/**
76+
* @author Daniel Seebacher (University of Konstanz)
77+
* @author Andreas Graumann (University of Konstanz)
78+
*/
79+
public class AbstractFeatureTest extends AbstractOpTest {
80+
81+
protected static final boolean expensiveTestsEnabled =
82+
"enabled".equals(System.getProperty("imagej.ops.expensive.tests"));
83+
84+
/**
85+
* Really small number, used for assertEquals with floating or double values.
86+
*/
87+
protected static final double SMALL_DELTA = 1e-07;
88+
89+
/**
90+
* Medium small number, used for assertEquals with very little error margin.
91+
*/
92+
protected static final double MEDIUM_DELTA = 1e-5;
93+
94+
/**
95+
* Small number, used for assertEquals if a little error margin is allowed.
96+
*/
97+
protected static final double BIG_DELTA = 1e-3;
98+
99+
/**
100+
* Seed
101+
*/
102+
protected static final long SEED = 1234567890L;
103+
104+
/**
105+
* Some random images
106+
*/
107+
protected Img<UnsignedByteType> empty;
108+
protected Img<UnsignedByteType> constant;
109+
protected Img<UnsignedByteType> random;
110+
111+
protected Img<UnsignedByteType> empty3d;
112+
protected Img<UnsignedByteType> constant3d;
113+
protected Img<UnsignedByteType> random3d;
114+
115+
protected Img<UnsignedByteType> ellipse;
116+
protected Img<UnsignedByteType> rotatedEllipse;
117+
118+
@Before
119+
public void setup() {
120+
final ImageGenerator dataGenerator = new ImageGenerator(SEED);
121+
final long[] dim = new long[] { 100, 100 };
122+
final long[] dim3 = new long[] { 100, 100, 30 };
123+
124+
empty = dataGenerator.getEmptyUnsignedByteImg(dim);
125+
constant = dataGenerator.getConstantUnsignedByteImg(dim, 15);
126+
random = dataGenerator.getRandomUnsignedByteImg(dim);
127+
128+
empty3d = dataGenerator.getEmptyUnsignedByteImg(dim3);
129+
constant3d = dataGenerator.getConstantUnsignedByteImg(dim3, 15);
130+
random3d = dataGenerator.getRandomUnsignedByteImg(dim3);
131+
132+
double[] offset = new double[] { 0.0, 0.0 };
133+
double[] radii = new double[] { 20, 40 };
134+
ellipse = dataGenerator.getEllipsedBitImage(dim, radii, offset);
135+
136+
// translate and rotate ellipse
137+
offset = new double[] { 10.0, -10.0 };
138+
radii = new double[] { 40, 20 };
139+
rotatedEllipse = dataGenerator.getEllipsedBitImage(dim, radii, offset);
140+
}
141+
142+
@Override
143+
protected Context createContext() {
144+
return new Context(OpService.class);
145+
}
146+
147+
/**
148+
* Simple class to generate empty, randomly filled or constantly filled images
149+
* of various types.
150+
*
151+
* @author Daniel Seebacher (University of Konstanz)
152+
* @author Andreas Graumann (University of Konstanz)
153+
*/
154+
class ImageGenerator {
155+
156+
private final Random rand;
157+
158+
/**
159+
* Create the image generator with a predefined seed.
160+
*
161+
* @param seed a seed which is used by the random generator.
162+
*/
163+
public ImageGenerator(final long seed) {
164+
this.rand = new Random(seed);
165+
}
166+
167+
/**
168+
* @param dim a long array with the desired dimensions of the image
169+
* @return an empty {@link Img} of {@link UnsignedByteType}.
170+
*/
171+
public Img<UnsignedByteType> getEmptyUnsignedByteImg(final long[] dim) {
172+
return ArrayImgs.unsignedBytes(dim);
173+
}
174+
175+
/**
176+
* @param dim a long array with the desired dimensions of the image
177+
* @return an {@link Img} of {@link UnsignedByteType} filled with random
178+
* values.
179+
*/
180+
public Img<UnsignedByteType> getRandomUnsignedByteImg(final long[] dim) {
181+
final ArrayImg<UnsignedByteType, ByteArray> img = ArrayImgs.unsignedBytes(
182+
dim);
183+
184+
final UnsignedByteType type = img.firstElement();
185+
186+
final ArrayCursor<UnsignedByteType> cursor = img.cursor();
187+
while (cursor.hasNext()) {
188+
cursor.next().set(rand.nextInt((int) type.getMaxValue()));
189+
}
190+
191+
return img;
192+
}
193+
194+
/**
195+
* @param dim a long array with the desired dimensions of the image
196+
* @param constValue constant image value
197+
* @return an {@link Img} of {@link UnsignedByteType} filled with a constant
198+
* value.
199+
*/
200+
public Img<UnsignedByteType> getConstantUnsignedByteImg(final long[] dim,
201+
final int constValue)
202+
{
203+
final ArrayImg<UnsignedByteType, ByteArray> img = ArrayImgs.unsignedBytes(
204+
dim);
205+
206+
final UnsignedByteType type = img.firstElement();
207+
if (constValue < type.getMinValue() || constValue >= type.getMaxValue()) {
208+
throw new IllegalArgumentException("Can't create image for constant [" +
209+
constValue + "]");
210+
}
211+
212+
final ArrayCursor<UnsignedByteType> cursor = img.cursor();
213+
while (cursor.hasNext()) {
214+
cursor.next().set(constValue);
215+
}
216+
217+
return img;
218+
}
219+
220+
/**
221+
* @param dim dimensions of the image
222+
* @param radii of the ellipse
223+
* @param offset of the ellipse
224+
* @return an {@link Img} of {@link BitType} filled with a ellipse
225+
*/
226+
@SuppressWarnings({ "deprecation" })
227+
public Img<UnsignedByteType> getEllipsedBitImage(final long[] dim,
228+
final double[] radii, final double[] offset)
229+
{
230+
231+
// create empty bittype image with desired dimensions
232+
final ArrayImg<UnsignedByteType, ByteArray> img = ArrayImgs.unsignedBytes(
233+
dim);
234+
235+
// create ellipse
236+
final EllipseRegionOfInterest ellipse = new EllipseRegionOfInterest();
237+
ellipse.setRadii(radii);
238+
239+
// set origin in the center of image
240+
final double[] origin = new double[dim.length];
241+
for (int i = 0; i < dim.length; i++)
242+
origin[i] = dim[i] / 2;
243+
ellipse.setOrigin(origin);
244+
245+
// get iterable intervall and cursor of ellipse
246+
final IterableInterval<UnsignedByteType> ii = ellipse
247+
.getIterableIntervalOverROI(img);
248+
final Cursor<UnsignedByteType> cursor = ii.cursor();
249+
250+
// fill image with ellipse
251+
while (cursor.hasNext()) {
252+
cursor.next();
253+
cursor.get().set(255);
254+
}
255+
256+
return img;
257+
}
258+
}
259+
260+
protected static Img<FloatType> getTestImage2D() {
261+
return openFloatImg(AbstractFeatureTest.class, "2d_geometric_features_testlabel.tif");
262+
}
263+
264+
protected static Polygon2D getPolygon() {
265+
final List<RealPoint> vertices = new ArrayList<>();
266+
try {
267+
Files.lines(Paths.get(AbstractFeatureTest.class.getResource("2d_geometric_features_polygon.txt").toURI()))
268+
.forEach(l -> {
269+
String[] coord = l.split(" ");
270+
RealPoint v = new RealPoint(new double[]{ Double.parseDouble(coord[0]),
271+
Double.parseDouble(coord[1])});
272+
vertices.add(v);
273+
});
274+
} catch (IOException | URISyntaxException exc) {
275+
exc.printStackTrace();
276+
}
277+
return new DefaultWritablePolygon2D(vertices);
278+
}
279+
280+
protected static Img<FloatType> getTestImage3D() {
281+
return openFloatImg(AbstractFeatureTest.class, "3d_geometric_features_testlabel.tif");
282+
}
283+
284+
protected static Mesh getMesh() {
285+
final Mesh m = new NaiveDoubleMesh();
286+
// To prevent duplicates, map each (x, y, z) triple to its own index.
287+
final Map<Vector3D, Long> indexMap = new HashMap<>();
288+
final LongArray indices = new LongArray();
289+
try {
290+
Files.lines(Paths.get(AbstractFeatureTest.class.getResource("3d_geometric_features_mesh.txt").toURI()))
291+
.forEach(l -> {
292+
String[] coord = l.split(" ");
293+
final double x = Double.parseDouble(coord[0]);
294+
final double y = Double.parseDouble(coord[1]);
295+
final double z = Double.parseDouble(coord[2]);
296+
final Vector3D vertex = new Vector3D(x, y, z);
297+
final long vIndex = indexMap.computeIfAbsent(vertex, //
298+
v -> m.vertices().add(x, y, z));
299+
indices.add(vIndex);
300+
});
301+
} catch (IOException | URISyntaxException exc) {
302+
exc.printStackTrace();
303+
}
304+
for (int i = 0; i < indices.size(); i += 3) {
305+
final long v0 = indices.get(i);
306+
final long v1 = indices.get(i + 1);
307+
final long v2 = indices.get(i + 2);
308+
m.triangles().add(v0, v1, v2);
309+
}
310+
return m;
311+
}
312+
313+
protected static <T extends RealType<T>> LabelRegion<String> createLabelRegion(
314+
final RandomAccessibleInterval<T> interval, final float min, final float max, long... dims)
315+
{
316+
if (dims == null || dims.length == 0) {
317+
dims = new long[interval.numDimensions()];
318+
interval.dimensions(dims);
319+
}
320+
final ImgLabeling<String, IntType> labeling =
321+
new ImgLabeling<>(ArrayImgs.ints(dims));
322+
323+
final RandomAccess<LabelingType<String>> ra = labeling.randomAccess();
324+
final RandomAccessibleIntervalCursor<T> c = new RandomAccessibleIntervalCursor<>(interval);
325+
final long[] pos = new long[labeling.numDimensions()];
326+
while (c.hasNext()) {
327+
final T item = c.next();
328+
final float value = item.getRealFloat();
329+
if (value >= min && value <= max) {
330+
c.localize(pos);
331+
ra.setPosition(pos);
332+
ra.get().add("1");
333+
}
334+
}
335+
final LabelRegions<String> labelRegions = new LabelRegions<>(labeling);
336+
337+
return labelRegions.getLabelRegion("1");
338+
339+
}
340+
}

0 commit comments

Comments
 (0)