Skip to content

Commit 31f7117

Browse files
committed
Add Hough transform
1 parent 9694f3b commit 31f7117

File tree

7 files changed

+1147
-1
lines changed

7 files changed

+1147
-1
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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
<relativePath />
1010
</parent>
1111

12-
<groupId>net.imglib2</groupId>
1312
<artifactId>imglib2-algorithm</artifactId>
1413
<version>0.6.4-SNAPSHOT</version>
1514

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
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.NativeType;
42+
import net.imglib2.type.Type;
43+
import net.imglib2.type.numeric.RealType;
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
50+
* an edge map. This implementation uses a threshold to determine whether a
51+
* pixel at a certain point is an edge or not. Comparison is
52+
* strictly-greater-than. This implementation is fairly dumb in that it does not
53+
* take gradients into account. The threshold used is the default value returned
54+
* by calling the constructor for the {@link Type} of the input {@link Img}.
55+
* <p>
56+
* Vote space here has two dimensions: {@code rho} and {@code theta}.
57+
* {@code theta} is measured in radians {@code [-pi/2 pi/2)}, {@code rho} is
58+
* measured in {@code [-rhoMax, rhoMax)}.
59+
* </p>
60+
* <p>
61+
* Lines are modeled as
62+
* </p>
63+
*
64+
* <pre>
65+
* l(t) = | x | = rho * | cos(theta) | + t * | sin(theta) |
66+
* | y | | -sin(theta) | | cos(theta) |
67+
* </pre>
68+
* <p>
69+
* In other words, {@code rho} represents the signed minimum distance from the
70+
* image origin to the line, and {@code theta} indicates the angle between the
71+
* row-axis and the minimum offset vector.
72+
* </p>
73+
* <p>
74+
* For a given point, then, votes are placed along the curve
75+
* </p>
76+
*
77+
* <pre>
78+
* rho = y * sin( theta ) + x * cos( theta )
79+
* </pre>
80+
*/
81+
public class HoughLineTransform< S extends RealType< S > & NativeType< S >, T extends Type< T > & Comparable< T > > extends HoughTransform< S, T >
82+
{
83+
public static final int DEFAULT_THETA = 180;
84+
85+
public final double dTheta;
86+
87+
public final double dRho;
88+
89+
private final T threshold;
90+
91+
private final int nRho;
92+
93+
private final int nTheta;
94+
95+
private final double[] rho;
96+
97+
private final double[] theta;
98+
99+
private ArrayList< double[] > rtPeaks;
100+
101+
final private static float computeLength( final long[] position )
102+
{
103+
float dist = 0;
104+
105+
for ( int d = 0; d < position.length; ++d )
106+
{
107+
final long pos = position[ d ];
108+
109+
dist += pos * pos;
110+
}
111+
112+
return ( float ) Math.sqrt( dist );
113+
}
114+
115+
/**
116+
* Calculates a default number of rho bins, which corresponds to a
117+
* resolution of one pixel.
118+
*
119+
* @param inputImage
120+
* the {@link Img} in question.
121+
* @return default number of rho bins.
122+
*/
123+
public static int defaultRho( final Img< ? > inputImage )
124+
{
125+
final long[] dims = new long[ inputImage.numDimensions() ];
126+
inputImage.dimensions( dims );
127+
return ( int ) ( 2 * computeLength( dims ) );
128+
}
129+
130+
/**
131+
* Creates a default {@link HoughLineTransform} with {@ShortType} vote
132+
* space.
133+
*
134+
* @param <T>
135+
* the {@link Type} of the {@link Img} in question.
136+
* @param inputImage
137+
* the {@link Img} to perform the Hough Line Transform against.
138+
* @return a default {@link HoughLineTransform} with {@link IntType} vote
139+
* space.
140+
*/
141+
public static < T extends Type< T > & Comparable< T > > HoughLineTransform< ShortType, T > shortHoughLine( final Img< T > inputImage )
142+
{
143+
return new HoughLineTransform<>( inputImage, new ShortType() );
144+
}
145+
146+
/**
147+
* Creates a default {@link HoughLineTransform} with {@IntType} vote space.
148+
*
149+
* @param <T>
150+
* the {@link Type} of the {@link Img} in question.
151+
* @param inputImage
152+
* the {@link Img} to perform the Hough Line Transform against.
153+
* @return a default {@link HoughLineTransform} with {@link IntType} vote
154+
* space.
155+
*/
156+
public static < T extends Type< T > & Comparable< T > > HoughLineTransform< IntType, T > integerHoughLine( final Img< T > inputImage )
157+
{
158+
return new HoughLineTransform<>( inputImage, new IntType() );
159+
}
160+
161+
/**
162+
* Creates a default {@link HoughLineTransform} with {@link LongType} vote
163+
* space.
164+
*
165+
* @param <T>
166+
* the {@link Type} of the {@link Img} in question.
167+
* @param inputImage
168+
* the {@link Img} to perform the Hough Line Transform against.
169+
* @return a default {@link HoughLineTransform} with {@link LongType} vote
170+
* space.
171+
* <p>
172+
* Use this for voting against large images, but reasonably small
173+
* vote space. If you need a big voting space, it would be better to
174+
* create a {@link HoughLineTransform} instantiated with an
175+
* {@link ImgFactory} capable of handling it.
176+
*/
177+
public static < T extends Type< T > & Comparable< T > > HoughLineTransform< LongType, T > longHoughLine( final Img< T > inputImage )
178+
{
179+
return new HoughLineTransform<>( inputImage, new LongType() );
180+
}
181+
182+
/**
183+
* Create a {@link HoughLineTransform} to operate against a given
184+
* {@link Img}, with a specific {@link Type} of vote space. Defaults are
185+
* used for rho- and theta-resolution.
186+
*
187+
* @param inputImage
188+
* the {@link Img} to operate against.
189+
* @param type
190+
* the {@link Type} for the vote space.
191+
*/
192+
public HoughLineTransform( final Img< T > inputImage, final S type )
193+
{
194+
this( inputImage, DEFAULT_THETA, type );
195+
}
196+
197+
/**
198+
* Create a {@link HoughLineTransform} to operate against a given
199+
* {@link Img}, with a specific {@link Type} of vote space and
200+
* theta-resolution. Rho-resolution is set to the default.
201+
*
202+
* @param inputImage
203+
* the {@link Img} to operate against.
204+
* @param theta
205+
* the number of bins for theta-resolution.
206+
* @param type
207+
* the {@link Type} for the vote space.
208+
*/
209+
public HoughLineTransform( final Img< T > inputImage, final int theta, final S type )
210+
{
211+
this( inputImage, defaultRho( inputImage ), theta, type );
212+
}
213+
214+
/**
215+
* Create a {@link HoughLineTransform} to operate against a given
216+
* {@link Img}, with a specific {@link Type} of vote space and rho- and
217+
* theta-resolution.
218+
*
219+
* @param inputImage
220+
* the {@link Img} to operate against.
221+
* @param inNRho
222+
* the number of bins for rho resolution.
223+
* @param inNTheta
224+
* the number of bins for theta resolution.
225+
* @param type
226+
* the {@link Type} for the vote space.
227+
*/
228+
public HoughLineTransform( final Img< T > inputImage, final int inNRho, final int inNTheta, final S type )
229+
{
230+
// Call the base constructor
231+
super( inputImage, new long[] { inNRho, inNTheta }, type );
232+
233+
// Theta by definition is in [0..pi].
234+
dTheta = Math.PI / inNTheta;
235+
236+
// The furthest a point can be from the origin is the length calculated
237+
// from the dimensions of the Image.
238+
final long[] dims = new long[ inputImage.numDimensions() ];
239+
inputImage.dimensions( dims );
240+
dRho = 2 * computeLength( dims ) / ( double ) inNRho;
241+
threshold = inputImage.firstElement().createVariable();
242+
nRho = inNRho;
243+
nTheta = inNTheta;
244+
theta = new double[ inNTheta ];
245+
rho = new double[ inNRho ];
246+
rtPeaks = null;
247+
}
248+
249+
/**
250+
* Create a {@link HoughLineTransform} to operate against a given
251+
* {@link Img}, with a specific {@link ImgFactory} for the vote space, and
252+
* specific rho- and theta-resolution.
253+
*
254+
* @param inputImage
255+
* the {@link Img} to operate against.
256+
* @param factory
257+
* the {@link ImgFactory} object.
258+
* @param inNRho
259+
* the number of bisn for rho resolution.
260+
* @param inNTheta
261+
* the number of bins for theta resolution.
262+
* @param type
263+
* the {@link Type} for the vote space.
264+
*/
265+
public HoughLineTransform( final Img< T > inputImage, final ImgFactory< S > factory, final S type, final int inNRho, final int inNTheta )
266+
{
267+
// Call the base constructor
268+
super( inputImage, new long[] { inNRho, inNTheta }, factory, type );
269+
270+
dTheta = Math.PI / inNTheta;
271+
272+
final long[] dims = new long[ inputImage.numDimensions() ];
273+
inputImage.dimensions( dims );
274+
dRho = 2 * computeLength( dims ) / ( double ) inNRho;
275+
threshold = inputImage.firstElement().createVariable();
276+
nRho = inNRho;
277+
nTheta = inNTheta;
278+
theta = new double[ inNTheta ];
279+
rho = new double[ inNRho ];
280+
rtPeaks = null;
281+
}
282+
283+
public void setThreshold( final T inThreshold )
284+
{
285+
threshold.set( inThreshold );
286+
}
287+
288+
@Override
289+
public boolean process()
290+
{
291+
final Cursor< T > imageCursor = getImage().localizingCursor();
292+
final long[] position = new long[ getImage().numDimensions() ];
293+
final double minTheta = -Math.PI / 2;
294+
295+
final long[] dims = new long[ getImage().numDimensions() ];
296+
getImage().dimensions( dims );
297+
final double minRho = -computeLength( dims );
298+
299+
final long sTime = System.currentTimeMillis();
300+
boolean success;
301+
302+
for ( int t = 0; t < nTheta; ++t )
303+
{
304+
theta[ t ] = dTheta * t + minTheta;
305+
}
306+
307+
for ( int r = 0; r < nRho; ++r )
308+
{
309+
rho[ r ] = dRho * r + minRho;
310+
}
311+
312+
while ( imageCursor.hasNext() )
313+
{
314+
double fRho;
315+
int r;
316+
final int[] voteLoc = new int[ 2 ];
317+
318+
imageCursor.fwd();
319+
imageCursor.localize( position );
320+
321+
for ( int t = 0; t < nTheta; ++t )
322+
{
323+
if ( imageCursor.get().compareTo( threshold ) > 0 )
324+
{
325+
fRho = Math.cos( theta[ t ] ) * position[ 0 ] + Math.sin( theta[ t ] ) * position[ 1 ];
326+
r = Math.round( ( float ) ( ( fRho - minRho ) / dRho ) );
327+
voteLoc[ 0 ] = r;
328+
voteLoc[ 1 ] = t;
329+
// place vote
330+
super.placeVote( voteLoc );
331+
}
332+
}
333+
}
334+
// pick peaks
335+
success = super.pickPeaks();
336+
super.pTime = System.currentTimeMillis() - sTime;
337+
return success;
338+
}
339+
340+
public ArrayList< double[] > getTranslatedPeakList()
341+
{
342+
if ( rtPeaks == null )
343+
{
344+
final ArrayList< long[] > peaks = getPeakList();
345+
rtPeaks = new ArrayList<>( peaks.size() );
346+
for ( final long[] irt : peaks )
347+
{
348+
final double[] rt = new double[ 2 ];
349+
rt[ 0 ] = rho[ ( int ) irt[ 0 ] ];
350+
rt[ 1 ] = theta[ ( int ) irt[ 1 ] ];
351+
rtPeaks.add( rt );
352+
}
353+
}
354+
355+
return rtPeaks;
356+
}
357+
358+
}

0 commit comments

Comments
 (0)