Skip to content

Commit 3781e52

Browse files
committed
Add Hough transform
1 parent 631d746 commit 3781e52

File tree

4 files changed

+509
-0
lines changed

4 files changed

+509
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
/.project
33
/.settings/
44
/target/
5+
6+
# IntelliJ
7+
/.idea/
8+
*.iml

pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,27 @@
3737
<groupId>net.imglib2</groupId>
3838
<artifactId>imglib2-realtransform</artifactId>
3939
</dependency>
40+
<dependency>
41+
<groupId>net.imglib2</groupId>
42+
<artifactId>imglib2-algorithm-gpl</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>net.imglib2</groupId>
46+
<artifactId>imglib2-ij</artifactId>
47+
</dependency>
4048

4149
<!-- ImageJ dependencies -->
4250
<dependency>
4351
<groupId>net.imagej</groupId>
4452
<artifactId>imagej-common</artifactId>
4553
</dependency>
4654

55+
<!-- SCIFIO dependencies -->
56+
<dependency>
57+
<groupId>io.scif</groupId>
58+
<artifactId>scifio</artifactId>
59+
</dependency>
60+
4761
<!-- Third-party dependencies -->
4862
<dependency>
4963
<groupId>gov.nist.math</groupId>
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/*
2+
* #%L
3+
* ImgLib2: a general-purpose, multidimensional image processing library.
4+
* %%
5+
* Copyright (C) 2009 - 2016 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld,
6+
* John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke,
7+
* Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner,
8+
* Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert,
9+
* Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin,
10+
* Jean-Yves Tinevez and Michael Zinsmaier.
11+
* %%
12+
* Redistribution and use in source and binary forms, with or without
13+
* modification, are permitted provided that the following conditions are met:
14+
*
15+
* 1. Redistributions of source code must retain the above copyright notice,
16+
* this list of conditions and the following disclaimer.
17+
* 2. Redistributions in binary form must reproduce the above copyright notice,
18+
* this list of conditions and the following disclaimer in the documentation
19+
* and/or other materials provided with the distribution.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
25+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31+
* POSSIBILITY OF SUCH DAMAGE.
32+
* #L%
33+
*/
34+
package net.imglib2.algorithm.hough;
35+
36+
import java.util.ArrayList;
37+
38+
import net.imglib2.Cursor;
39+
import net.imglib2.img.Img;
40+
import net.imglib2.img.ImgFactory;
41+
import net.imglib2.type.Type;
42+
import net.imglib2.type.numeric.RealType;
43+
import net.imglib2.type.NativeType;
44+
import net.imglib2.type.numeric.integer.IntType;
45+
import net.imglib2.type.numeric.integer.LongType;
46+
import net.imglib2.type.numeric.integer.ShortType;
47+
48+
/**
49+
* A class that extends {@link HoughTransform} to handle Hough Line voting over an edge map.
50+
* This implementation uses a threshold to determine whether a pixel at a certain point is
51+
* an edge or not. Comparison is strictly-greater-than. This implementation is fairly dumb
52+
* in that it does not take gradients into account. The threshold used is the default value
53+
* returned by calling the constructor for the {@link Type} of the input {@link Img}.
54+
*
55+
* Vote space here has two dimensions: rho and theta. Theta is measured in radians
56+
* [-pi/2 pi/2), rho is measured in [-rhoMax, rhoMax).
57+
*
58+
* Lines are modeled as
59+
*
60+
* l(t) = | x | = rho * | cos(theta) | + t * | sin(theta) |
61+
* | y | | -sin(theta) | | cos(theta) |
62+
*
63+
* In other words, rho represents the signed minimum distance from the image origin to the line,
64+
* and theta indicates the angle between the row-axis and the minimum offset vector.
65+
*
66+
* For a given point, then, votes are placed along the curve
67+
*
68+
* rho = y * sin(theta) + x * cos(theta)
69+
* @Override
70+
*
71+
*/
72+
public class HoughLineTransform<S extends RealType<S> & NativeType<S>, T extends Type<T> & Comparable<T>>
73+
extends HoughTransform<S, T> {
74+
public static final int DEFAULT_THETA = 180;
75+
public final double dTheta;
76+
public final double dRho;
77+
private final T threshold;
78+
private final int nRho;
79+
private final int nTheta;
80+
private final double[] rho;
81+
private final double[] theta;
82+
private ArrayList<double[]> rtPeaks;
83+
84+
final private static float computeLength(final long[] position) {
85+
float dist = 0;
86+
87+
for (int d = 0; d < position.length; ++d) {
88+
final long pos = position[d];
89+
90+
dist += pos * pos;
91+
}
92+
93+
return (float) Math.sqrt(dist);
94+
}
95+
96+
/**
97+
* Calculates a default number of rho bins, which corresponds to a resolution of one pixel.
98+
*
99+
* @param inputImage the {@link Img} in question.
100+
* @return default number of rho bins.
101+
*/
102+
public static int defaultRho(final Img<?> inputImage) {
103+
final long[] dims = new long[inputImage.numDimensions()];
104+
inputImage.dimensions(dims);
105+
return (int) (2 * computeLength(dims));
106+
}
107+
108+
109+
/**
110+
* Creates a default {@link HoughLineTransform} with {@ShortType} vote space.
111+
*
112+
* @param <T> the {@link Type} of the {@link Img} in question.
113+
* @param inputImage the {@link Img} to perform the Hough Line Transform against.
114+
* @return a default {@link HoughLineTransform} with {@link IntType} vote space.
115+
*/
116+
public static <T extends Type<T> & Comparable<T>> HoughLineTransform<ShortType, T> shortHoughLine(final Img<T> inputImage) {
117+
return new HoughLineTransform<>(inputImage, new ShortType());
118+
}
119+
120+
/**
121+
* Creates a default {@link HoughLineTransform} with {@IntType} vote space.
122+
*
123+
* @param <T> the {@link Type} of the {@link Img} in question.
124+
* @param inputImage the {@link Img} to perform the Hough Line Transform against.
125+
* @return a default {@link HoughLineTransform} with {@link IntType} vote space.
126+
*/
127+
public static <T extends Type<T> & Comparable<T>> HoughLineTransform<IntType, T> integerHoughLine(final Img<T> inputImage) {
128+
return new HoughLineTransform<>(inputImage, new IntType());
129+
}
130+
131+
/**
132+
* Creates a default {@link HoughLineTransform} with {@link LongType} vote space.
133+
*
134+
* @param <T> the {@link Type} of the {@link Img} in question.
135+
* @param inputImage the {@link Img} to perform the Hough Line Transform against.
136+
* @return a default {@link HoughLineTransform} with {@link LongType} vote space.
137+
* <p>
138+
* Use this for voting against large images, but reasonably small vote space. If you need a big
139+
* voting space, it would be better to create a {@link HoughLineTransform} instantiated with an
140+
* {@link ImgFactory} capable of handling it.
141+
*/
142+
public static <T extends Type<T> & Comparable<T>> HoughLineTransform<LongType, T> longHoughLine(final Img<T> inputImage) {
143+
return new HoughLineTransform<>(inputImage, new LongType());
144+
}
145+
146+
/**
147+
* Create a {@link HoughLineTransform} to operate against a given {@link Img}, with
148+
* a specific {@link Type} of vote space.
149+
* Defaults are used for rho- and theta-resolution.
150+
*
151+
* @param inputImage the {@link Img} to operate against.
152+
* @param type the {@link Type} for the vote space.
153+
*/
154+
public HoughLineTransform(final Img<T> inputImage, final S type) {
155+
this(inputImage, DEFAULT_THETA, type);
156+
}
157+
158+
/**
159+
* Create a {@link HoughLineTransform} to operate against a given {@link Img}, with
160+
* a specific {@link Type} of vote space and theta-resolution.
161+
* Rho-resolution is set to the default.
162+
*
163+
* @param inputImage the {@link Img} to operate against.
164+
* @param theta the number of bins for theta-resolution.
165+
* @param type the {@link Type} for the vote space.
166+
*/
167+
public HoughLineTransform(final Img<T> inputImage, final int theta, final S type) {
168+
this(inputImage, defaultRho(inputImage), theta, type);
169+
}
170+
171+
/**
172+
* Create a {@link HoughLineTransform} to operate against a given {@link Img}, with
173+
* a specific {@link Type} of vote space and rho- and theta-resolution.
174+
*
175+
* @param inputImage the {@link Img} to operate against.
176+
* @param inNRho the number of bins for rho resolution.
177+
* @param inNTheta the number of bins for theta resolution.
178+
* @param type the {@link Type} for the vote space.
179+
*/
180+
public HoughLineTransform(final Img<T> inputImage, final int inNRho, final int inNTheta, final S type) {
181+
// Call the base constructor
182+
super(inputImage, new long[]{inNRho, inNTheta}, type);
183+
184+
// Theta by definition is in [0..pi].
185+
dTheta = Math.PI / (double) inNTheta;
186+
187+
// The furthest a point can be from the origin is the length calculated from the dimensions of the Image.
188+
final long[] dims = new long[inputImage.numDimensions()];
189+
inputImage.dimensions(dims);
190+
dRho = 2 * computeLength(dims) / (double) inNRho;
191+
threshold = inputImage.firstElement().createVariable();
192+
nRho = inNRho;
193+
nTheta = inNTheta;
194+
theta = new double[inNTheta];
195+
rho = new double[inNRho];
196+
rtPeaks = null;
197+
}
198+
199+
/**
200+
* Create a {@link HoughLineTransform} to operate against a given {@link Img}, with
201+
* a specific {@link ImgFactory} for the vote space, and specific rho- and theta-resolution.
202+
*
203+
* @param inputImage the {@link Img} to operate against.
204+
* @param factory the {@link ImgFactory} object.
205+
* @param inNRho the number of bisn for rho resolution.
206+
* @param inNTheta the number of bins for theta resolution.
207+
* @param type the {@link Type} for the vote space.
208+
*/
209+
public HoughLineTransform(final Img<T> inputImage, final ImgFactory<S> factory, final S type,
210+
final int inNRho, final int inNTheta) {
211+
// Call the base constructor
212+
super(inputImage, new long[]{inNRho, inNTheta}, factory, type);
213+
214+
dTheta = Math.PI / (double) inNTheta;
215+
216+
final long[] dims = new long[inputImage.numDimensions()];
217+
inputImage.dimensions(dims);
218+
dRho = 2 * computeLength(dims) / (double) inNRho;
219+
threshold = inputImage.firstElement().createVariable();
220+
nRho = inNRho;
221+
nTheta = inNTheta;
222+
theta = new double[inNTheta];
223+
rho = new double[inNRho];
224+
rtPeaks = null;
225+
}
226+
227+
public void setThreshold(final T inThreshold) {
228+
threshold.set(inThreshold);
229+
}
230+
231+
@Override
232+
public boolean process() {
233+
final Cursor<T> imageCursor = getImage().localizingCursor();
234+
final long[] position = new long[getImage().numDimensions()];
235+
final double minTheta = -Math.PI / 2;
236+
237+
final long[] dims = new long[getImage().numDimensions()];
238+
getImage().dimensions(dims);
239+
final double minRho = -computeLength(dims);
240+
241+
final long sTime = System.currentTimeMillis();
242+
boolean success;
243+
244+
for (int t = 0; t < nTheta; ++t) {
245+
theta[t] = dTheta * (double) t + minTheta;
246+
}
247+
248+
for (int r = 0; r < nRho; ++r) {
249+
rho[r] = dRho * (double) r + minRho;
250+
}
251+
252+
while (imageCursor.hasNext()) {
253+
double fRho;
254+
int r;
255+
int[] voteLoc = new int[2];
256+
257+
imageCursor.fwd();
258+
imageCursor.localize(position);
259+
260+
for (int t = 0; t < nTheta; ++t) {
261+
if (imageCursor.get().compareTo(threshold) > 0) {
262+
fRho = Math.cos(theta[t]) * (double) position[0] + Math.sin(theta[t]) * (double) position[1];
263+
r = Math.round((float) ((fRho - minRho) / dRho));
264+
voteLoc[0] = r;
265+
voteLoc[1] = t;
266+
// place vote
267+
super.placeVote(voteLoc);
268+
}
269+
}
270+
}
271+
// pick peaks
272+
success = super.pickPeaks();
273+
super.pTime = System.currentTimeMillis() - sTime;
274+
return success;
275+
}
276+
277+
public ArrayList<double[]> getTranslatedPeakList() {
278+
if (rtPeaks == null) {
279+
ArrayList<long[]> peaks = getPeakList();
280+
rtPeaks = new ArrayList<>(peaks.size());
281+
for (long[] irt : peaks) {
282+
double[] rt = new double[2];
283+
rt[0] = rho[(int) irt[0]];
284+
rt[1] = theta[(int) irt[1]];
285+
rtPeaks.add(rt);
286+
}
287+
}
288+
289+
return rtPeaks;
290+
}
291+
292+
}

0 commit comments

Comments
 (0)