Skip to content

Commit 8e4b4ac

Browse files
authored
Merge pull request #58 from JuliaGeometry/sjk/clean3
Sjk/clean3
2 parents cf4156b + 6f0f27c commit 8e4b4ac

File tree

4 files changed

+128
-133
lines changed

4 files changed

+128
-133
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ julia = "0.7, 1"
1212
[extras]
1313
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1414
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
15+
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
1516

1617
[targets]
17-
test = ["LinearAlgebra", "Test"]
18+
test = ["LinearAlgebra", "StatsBase", "Test"]

src/Contour.jl

Lines changed: 52 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ module Contour
22

33
using StaticArrays
44

5+
include("interpolate.jl")
6+
57
export
68
ContourLevel,
79
Curve2,
@@ -144,42 +146,48 @@ const WN, WS, WE = W|N, W|S, W|E
144146
const NWSE = NW | 0x10 # special (ambiguous case)
145147
const NESW = NE | 0x10 # special (ambiguous case)
146148

147-
const dirStr = ["N", "S", "NS", "E", "NE", "NS", "Invalid crossing",
148-
"W", "NW", "SW", "Invalid crossing", "WE"]
149+
# Maps cell type to crossing types for non-ambiguous cells
150+
const edge_LUT = (SW, SE, EW, NE, 0x0, NS, NW, NW, NS, 0x0, NE, EW, SE, SW)
149151

150152
# The way a contour crossing goes through a cell is labeled
151153
# by combining compass directions (e.g. a NW crossing connects
152154
# the N edge and W edges of the cell). The Cell type records
153155
# the type of crossing that a cell contains. While most
154156
# cells will have only one crossing, cell type 5 and 10 will
155157
# have two crossings.
156-
function get_next_edge!(cells::Dict, xi, yi, entry_edge::UInt8)
157-
key = (xi,yi)
158-
cell = cells[key]
158+
function get_next_edge!(cells::Dict, key, entry_edge::UInt8)
159+
cell = pop!(cells, key)
159160
if cell == NWSE
160-
if !iszero(NW & entry_edge)
161+
if entry_edge == N || entry_edge == W
161162
cells[key] = SE
162-
return NW entry_edge
163-
else #Nw
163+
cell = NW
164+
else #SE
164165
cells[key] = NW
165-
return SE entry_edge
166+
cell = SE
166167
end
167168
elseif cell == NESW
168-
if !iszero(NE & entry_edge)
169+
if entry_edge == N || entry_edge == E
169170
cells[key] = SW
170-
return NE entry_edge
171+
cell = NE
171172
else #SW
172173
cells[key] = NE
173-
return SW entry_edge
174+
cell = SW
174175
end
175-
else
176-
next_edge = cell entry_edge
177-
delete!(cells, key)
178-
return next_edge
179176
end
177+
return cell entry_edge
180178
end
181179

182-
function get_first_crossing(cell)
180+
# N, S, E, W
181+
const next_map = ((0,1), (0,-1), (1,0), (-1,0))
182+
const next_edge = (S,N,W,E)
183+
184+
@inline function advance_edge(ind, edge)
185+
n = trailing_zeros(edge) + 1
186+
nt = ind .+ next_map[n]
187+
return nt, next_edge[n]
188+
end
189+
190+
@inline function get_first_crossing(cell)
183191
if cell == NWSE
184192
return NW
185193
elseif cell == NESW
@@ -189,10 +197,7 @@ function get_first_crossing(cell)
189197
end
190198
end
191199

192-
# Maps cell type to crossing types for non-ambiguous cells
193-
const edge_LUT = (SW, SE, EW, NE, 0x0, NS, NW, NW, NS, 0x0, NE, EW, SE, SW)
194-
195-
function _get_case(z, h)
200+
@inline function _get_case(z, h)
196201
case = z[1] > h ? 0x01 : 0x00
197202
z[2] > h && (case |= 0x02)
198203
z[3] > h && (case |= 0x04)
@@ -229,67 +234,12 @@ function get_level_cells(z, h::Number)
229234
return cells
230235
end
231236

232-
# Some constants used by trace_contour
233-
234-
const fwd, rev = (false, true)
235-
236-
function add_vertex!(curve::Curve2{T}, pos::Tuple{T,T}, dir) where {T}
237-
if dir == fwd
238-
push!(curve.vertices, SVector{2,T}(pos...))
239-
else
240-
pushfirst!(curve.vertices, SVector{2,T}(pos...))
241-
end
242-
end
243-
244-
# Given the row and column indices of the lower left
245-
# vertex, add the location where the contour level
246-
# crosses the specified edge.
247-
function interpolate(x, y, z::AbstractMatrix{T}, h::Number, xi::Int, yi::Int, edge::UInt8) where {T <: AbstractFloat}
248-
@inbounds if edge == W
249-
y_interp = y[yi] + (y[yi + 1] - y[yi]) * (h - z[xi, yi]) / (z[xi, yi + 1] - z[xi, yi])
250-
x_interp = x[xi]
251-
elseif edge == E
252-
y_interp = y[yi] + (y[yi + 1] - y[yi]) * (h - z[xi + 1, yi]) / (z[xi + 1, yi + 1] - z[xi + 1, yi])
253-
x_interp = x[xi + 1]
254-
elseif edge == N
255-
y_interp = y[yi + 1]
256-
x_interp = x[xi] + (x[xi + 1] - x[xi]) * (h - z[xi, yi + 1]) / (z[xi + 1, yi + 1] - z[xi, yi + 1])
257-
elseif edge == S
258-
y_interp = y[yi]
259-
x_interp = x[xi] + (x[xi + 1] - x[xi]) * (h - z[xi, yi]) / (z[xi + 1, yi] - z[xi, yi])
260-
end
261-
262-
return x_interp, y_interp
263-
end
264-
265-
function interpolate(x::AbstractMatrix{T}, y::AbstractMatrix{T}, z::AbstractMatrix{T}, h::Number, xi::Int, yi::Int, edge::UInt8) where {T <: AbstractFloat}
266-
if edge == W
267-
Δ = [y[xi, yi+1] - y[xi, yi ], x[xi, yi+1] - x[xi, yi ]].*(h - z[xi, yi ])/(z[xi, yi+1] - z[xi, yi ])
268-
y_interp = y[xi,yi] + Δ[1]
269-
x_interp = x[xi,yi] + Δ[2]
270-
elseif edge == E
271-
Δ = [y[xi+1,yi+1] - y[xi+1,yi ], x[xi+1,yi+1] - x[xi+1,yi ]].*(h - z[xi+1,yi ])/(z[xi+1,yi+1] - z[xi+1,yi ])
272-
y_interp = y[xi+1,yi] + Δ[1]
273-
x_interp = x[xi+1,yi] + Δ[2]
274-
elseif edge == N
275-
Δ = [y[xi+1,yi+1] - y[xi, yi+1], x[xi+1,yi+1] - x[xi, yi+1]].*(h - z[xi, yi+1])/(z[xi+1,yi+1] - z[xi, yi+1])
276-
y_interp = y[xi,yi+1] + Δ[1]
277-
x_interp = x[xi,yi+1] + Δ[2]
278-
elseif edge == S
279-
Δ = [y[xi+1,yi ] - y[xi, yi ], x[xi+1,yi ] - x[xi, yi ]].*(h - z[xi, yi ])/(z[xi+1,yi ] - z[xi, yi ])
280-
y_interp = y[xi,yi] + Δ[1]
281-
x_interp = x[xi,yi] + Δ[2]
282-
end
283-
284-
return x_interp, y_interp
285-
end
286-
287237

288238
# Given a cell and a starting edge, we follow the contour line until we either
289239
# hit the boundary of the input data, or we form a closed contour.
290-
function chase!(cells, curve, x, y, z, h, xi_start, yi_start, entry_edge, xi_max, yi_max, dir)
240+
function chase!(cells, curve, x, y, z, h, start, entry_edge, xi_max, yi_max, ::Type{VT}) where VT
291241

292-
xi, yi = xi_start, yi_start
242+
ind = start
293243

294244
# When the contour loops back to the starting cell, it is possible
295245
# for it to not intersect with itself. This happens if the starting
@@ -298,92 +248,62 @@ function chase!(cells, curve, x, y, z, h, xi_start, yi_start, entry_edge, xi_max
298248
loopback_edge = entry_edge
299249

300250
@inbounds while true
301-
exit_edge = get_next_edge!(cells, xi, yi, entry_edge)
302-
303-
add_vertex!(curve, interpolate(x, y, z, h, xi, yi, exit_edge), dir)
304-
305-
if exit_edge == N
306-
yi += 1
307-
entry_edge = S
308-
elseif exit_edge == S
309-
yi -= 1
310-
entry_edge = N
311-
elseif exit_edge == E
312-
xi += 1
313-
entry_edge = W
314-
elseif exit_edge == W
315-
xi -= 1
316-
entry_edge = E
317-
end
318-
!((xi, yi, entry_edge) != (xi_start, yi_start, loopback_edge) &&
319-
0 < yi < yi_max && 0 < xi < xi_max) && break
251+
exit_edge = get_next_edge!(cells, ind, entry_edge)
252+
253+
push!(curve, interpolate(x, y, z, h, ind, exit_edge, VT))
254+
255+
ind, entry_edge = advance_edge(ind, exit_edge)
256+
257+
!((ind[1], ind[2], entry_edge) != (start[1], start[2], loopback_edge) &&
258+
0 < ind[2] < yi_max && 0 < ind[1] < xi_max) && break
320259
end
321260

322-
return xi, yi
261+
return ind
323262
end
324263

325264

326265
function trace_contour(x, y, z, h::Number, cells::Dict)
327266

328267
contours = ContourLevel(h)
329268

330-
(xi_max, yi_max) = size(z)
269+
(xi_max, yi_max) = size(z)::Tuple{Int,Int}
270+
271+
VT = SVector{2,promote_type(map(eltype, (x, y, z))...)}
331272

332273
# When tracing out contours, this algorithm picks an arbitrary
333274
# starting cell, then first follows the contour in one direction
334275
# until it either ends up where it started # or at one of the boundaries.
335276
# It then tries to trace the contour in the opposite direction.
336277

337278
@inbounds while length(cells) > 0
338-
contour = Curve2(promote_type(map(eltype, (x, y, z))...))
279+
contour_arr = VT[]
339280

340281
# Pick initial box
341-
(xi_0, yi_0), cell = first(cells)
342-
(xi, yi) = (xi_0, yi_0)
282+
ind, cell = first(cells)
343283

344284
# Pick a starting edge
345285
crossing = get_first_crossing(cell)
346-
starting_edge = UInt8(0)
347-
for edge in (N, S, E, W)
348-
if !iszero(edge & crossing)
349-
starting_edge = edge
350-
break
351-
end
352-
end
286+
starting_edge = 0x01 << trailing_zeros(crossing)
353287

354288
# Add the contour entry location for cell (xi_0,yi_0)
355-
add_vertex!(contour, interpolate(x, y, z, h, xi_0, yi_0, starting_edge), fwd)
289+
push!(contour_arr, interpolate(x, y, z, h, ind, starting_edge, VT))
356290

357291
# Start trace in forward direction
358-
(xi_end, yi_end) = chase!(cells, contour, x, y, z, h, xi, yi, starting_edge, xi_max, yi_max, fwd)
292+
ind_end = chase!(cells, contour_arr, x, y, z, h, ind, starting_edge, xi_max, yi_max, VT)
359293

360-
if (xi_end, yi_end) == (xi_0, yi_0)
361-
push!(contours.lines, contour)
294+
if ind == ind_end
295+
push!(contours.lines, Curve2(contour_arr))
362296
continue
363297
end
364298

365-
if starting_edge == N
366-
yi = yi_0 + 1
367-
starting_edge = S
368-
elseif starting_edge == S
369-
yi = yi_0 - 1
370-
starting_edge = N
371-
elseif starting_edge == E
372-
xi = xi_0 + 1
373-
starting_edge = W
374-
elseif starting_edge == W
375-
xi = xi_0 - 1
376-
starting_edge = E
377-
end
299+
ind, starting_edge = advance_edge(ind, starting_edge)
378300

379-
if !(0 < yi < yi_max && 0 < xi < xi_max)
380-
push!(contours.lines, contour)
381-
continue
301+
if 0 < ind[2] < yi_max && 0 < ind[1] < xi_max
302+
# Start trace in reverse direction
303+
chase!(cells, reverse!(contour_arr), x, y, z, h, ind, starting_edge, xi_max, yi_max, VT)
382304
end
383305

384-
# Start trace in reverse direction
385-
(xi, yi) = chase!(cells, contour, x, y, z, h, xi, yi, starting_edge, xi_max, yi_max, rev)
386-
push!(contours.lines, contour)
306+
push!(contours.lines, Curve2(contour_arr))
387307
end
388308

389309
return contours

src/interpolate.jl

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Given the row and column indices of the lower left
2+
# vertex, add the location where the contour level
3+
# crosses the specified edge.
4+
function interpolate(x, y, z::AbstractMatrix, h::Number, ind, edge::UInt8, ::Type{VT}) where {VT}
5+
xi, yi = ind
6+
@inbounds if edge == W
7+
y_interp = y[yi] + (y[yi + 1] - y[yi]) * (h - z[xi, yi]) / (z[xi, yi + 1] - z[xi, yi])
8+
x_interp = x[xi]
9+
elseif edge == E
10+
y_interp = y[yi] + (y[yi + 1] - y[yi]) * (h - z[xi + 1, yi]) / (z[xi + 1, yi + 1] - z[xi + 1, yi])
11+
x_interp = x[xi + 1]
12+
elseif edge == N
13+
y_interp = y[yi + 1]
14+
x_interp = x[xi] + (x[xi + 1] - x[xi]) * (h - z[xi, yi + 1]) / (z[xi + 1, yi + 1] - z[xi, yi + 1])
15+
elseif edge == S
16+
y_interp = y[yi]
17+
x_interp = x[xi] + (x[xi + 1] - x[xi]) * (h - z[xi, yi]) / (z[xi + 1, yi] - z[xi, yi])
18+
end
19+
20+
return VT(x_interp, y_interp)
21+
end
22+
23+
function interpolate(x::AbstractRange, y::AbstractRange, z::AbstractMatrix, h::Number, ind, edge::UInt8, ::Type{VT}) where {VT}
24+
xi, yi = ind
25+
@inbounds if edge == W
26+
y_interp = y[yi] + step(y) * (h - z[xi, yi]) / (z[xi, yi + 1] - z[xi, yi])
27+
x_interp = x[xi]
28+
elseif edge == E
29+
y_interp = y[yi] + step(y) * (h - z[xi + 1, yi]) / (z[xi + 1, yi + 1] - z[xi + 1, yi])
30+
x_interp = x[xi + 1]
31+
elseif edge == N
32+
y_interp = y[yi + 1]
33+
x_interp = x[xi] + step(x) * (h - z[xi, yi + 1]) / (z[xi + 1, yi + 1] - z[xi, yi + 1])
34+
elseif edge == S
35+
y_interp = y[yi]
36+
x_interp = x[xi] + step(x) * (h - z[xi, yi]) / (z[xi + 1, yi] - z[xi, yi])
37+
end
38+
39+
return VT(x_interp, y_interp)
40+
end
41+
42+
function interpolate(x::AbstractMatrix, y::AbstractMatrix, z::AbstractMatrix, h::Number, ind, edge::UInt8, ::Type{VT}) where {VT}
43+
xi, yi = ind
44+
@inbounds if edge == W
45+
Δ = [y[xi, yi+1] - y[xi, yi ], x[xi, yi+1] - x[xi, yi ]].*(h - z[xi, yi ])/(z[xi, yi+1] - z[xi, yi ])
46+
y_interp = y[xi,yi] + Δ[1]
47+
x_interp = x[xi,yi] + Δ[2]
48+
elseif edge == E
49+
Δ = [y[xi+1,yi+1] - y[xi+1,yi ], x[xi+1,yi+1] - x[xi+1,yi ]].*(h - z[xi+1,yi ])/(z[xi+1,yi+1] - z[xi+1,yi ])
50+
y_interp = y[xi+1,yi] + Δ[1]
51+
x_interp = x[xi+1,yi] + Δ[2]
52+
elseif edge == N
53+
Δ = [y[xi+1,yi+1] - y[xi, yi+1], x[xi+1,yi+1] - x[xi, yi+1]].*(h - z[xi, yi+1])/(z[xi+1,yi+1] - z[xi, yi+1])
54+
y_interp = y[xi,yi+1] + Δ[1]
55+
x_interp = x[xi,yi+1] + Δ[2]
56+
elseif edge == S
57+
Δ = [y[xi+1,yi ] - y[xi, yi ], x[xi+1,yi ] - x[xi, yi ]].*(h - z[xi, yi ])/(z[xi+1,yi ] - z[xi, yi ])
58+
y_interp = y[xi,yi] + Δ[1]
59+
x_interp = x[xi,yi] + Δ[2]
60+
end
61+
62+
return VT(x_interp, y_interp)
63+
end

test/verify_vertices.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,15 @@ for i in eachindex(cts_ct)
215215
@test length(cts.contours[i].lines) == cts_ct[i]
216216
@test all(lines_ct[i] .== [length(c.vertices) for c in cts.contours[i].lines])
217217
end
218+
219+
220+
# support non-float z
221+
using StatsBase
222+
223+
N = 10000
224+
x = randn(N)
225+
y = randn(N)
226+
H = fit(Histogram, (x, y), closed = :left)
227+
contours(midpoints.(H.edges)..., H.weights)
228+
218229
end

0 commit comments

Comments
 (0)