Skip to content

Commit 1f70965

Browse files
unwrap: Initialize image reliability in deterministic order (#617)
* unwrap: Initialize image reliability in deterministic order * correct some typos * Remove disused constructor * add tests for reproducibility for invalid input; valid input should be resistant to random noise * unwrap test cleanup * test with threads --------- Co-authored-by: wheeheee <104880306+wheeheee@users.noreply.github.com>
1 parent 73d58fb commit 1f70965

File tree

4 files changed

+74
-53
lines changed

4 files changed

+74
-53
lines changed

.github/workflows/CI.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ jobs:
1414
actions: write
1515
contents: read
1616

17+
env:
18+
JULIA_NUM_THREADS: 'auto'
19+
1720
strategy:
1821
fail-fast: false
1922
matrix:

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ Polynomials = "4"
3030
Random = "1.6"
3131
Reexport = "1.0"
3232
SpecialFunctions = "2.0"
33+
StableRNGs = "1"
3334
Statistics = "1.6"
3435
Test = "1.6"
3536
julia = "1.10"
3637

3738
[extras]
3839
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
3940
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
41+
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
4042
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4143

4244
[targets]
43-
test = ["DelimitedFiles", "OffsetArrays", "Test"]
45+
test = ["DelimitedFiles", "OffsetArrays", "StableRNGs", "Test"]

src/unwrap.jl

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ wrapping discontinuities across all `ndims(m)` dimensions.
4646
4747
A common usage for unwrapping across a singleton dimension is for a phase
4848
measurement over time, such as when
49-
comparing successive frames of a short-time-fourier-transform, as
49+
comparing successive frames of a short-time Fourier transform, as
5050
each frame is wrapped to stay within (-pi, pi].
5151
5252
A common usage for unwrapping across multiple dimensions is for a phase
53-
measurement of a scene, such as when retrieving the phase information of
53+
measurement of a scene, such as when retrieving the phase information
5454
of an image, as each pixel is wrapped to stay within (-pi, pi].
5555
5656
# Arguments
@@ -94,7 +94,7 @@ mutable struct Pixel{T}
9494
return pixel
9595
end
9696
end
97-
Pixel(v, rng) = Pixel{typeof(v)}(0, v, rand(rng), 1)
97+
Pixel(v, rel::Float64) = Pixel{typeof(v)}(0, v, rel, 1)
9898
@inline Base.length(p::Pixel) = p.head.groupsize
9999

100100
struct Edge{T}
@@ -146,8 +146,13 @@ end
146146
# function to broadcast
147147
function init_pixels(wrapped_image::AbstractArray{T, N}, rng) where {T, N}
148148
pixel_image = similar(wrapped_image, Pixel{T})
149-
Threads.@threads for i in eachindex(wrapped_image, pixel_image)
150-
pixel_image[i] = Pixel(wrapped_image[i], rng)
149+
150+
# Initialize reliability values before going parallel. This ensures that
151+
# reliability values are generated in a deterministic order.
152+
rels = rand(rng, Float64, size(wrapped_image))
153+
154+
Threads.@threads for i in eachindex(wrapped_image, pixel_image, rels)
155+
pixel_image[i] = Pixel(wrapped_image[i], rels[i])
151156
end
152157
return pixel_image
153158
end
@@ -248,8 +253,8 @@ function populate_edges!(edges::Vector{Edge{T}}, pixel_image::AbstractArray{Pixe
248253
end
249254

250255
function calculate_reliability(pixel_image::AbstractArray{T, N}, circular_dims, range) where {T, N}
251-
# get the shifted pixel indices in CartesinanIndex form
252-
# This gets all the nearest neighbors (CartesionIndex{N}() = one(CartesianIndex{N}))
256+
# get the shifted pixel indices in CartesianIndex form
257+
# This gets all the nearest neighbors
253258
one_cart = oneunit(CartesianIndex{N})
254259
pixel_shifts = -one_cart:one_cart
255260
image_inds = CartesianIndices(pixel_image)

test/unwrap.jl

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
using DSP, Test
2-
using Random: MersenneTwister
2+
using Random: MersenneTwister, default_rng, seed!
3+
using StableRNGs
4+
using Statistics: mean
35

46
@testset "Unwrap 1D" begin
5-
@test unwrap([0.1, 0.2, 0.3, 0.4]) [0.1, 0.2, 0.3, 0.4]
6-
@test unwrap([0.1, 0.2 + 2pi, 0.3, 0.4]) [0.1, 0.2, 0.3, 0.4]
7-
@test unwrap([0.1, 0.2 - 2pi, 0.3, 0.4]) [0.1, 0.2, 0.3, 0.4]
8-
@test unwrap([0.1, 0.2 - 2pi, 0.3 - 2pi, 0.4]) [0.1, 0.2, 0.3, 0.4]
9-
@test unwrap([0.1 + 2pi, 0.2, 0.3, 0.4]) [0.1 + 2pi, 0.2 + 2pi, 0.3 + 2pi, 0.4 + 2pi]
10-
@test unwrap([0.1, 0.2 + 6pi, 0.3, 0.4]) [0.1, 0.2, 0.3, 0.4]
7+
unwrapped = [0.1, 0.2, 0.3, 0.4]
8+
@test unwrap([0.1, 0.2, 0.3, 0.4]) unwrapped
9+
@test unwrap([0.1, 0.2 + 2pi, 0.3, 0.4]) unwrapped
10+
@test unwrap([0.1, 0.2 - 2pi, 0.3, 0.4]) unwrapped
11+
@test unwrap([0.1, 0.2 - 2pi, 0.3 - 2pi, 0.4]) unwrapped
12+
@test unwrap([0.1 + 2pi, 0.2, 0.3, 0.4]) unwrapped .+ 2pi
13+
@test unwrap([0.1, 0.2 + 6pi, 0.3, 0.4]) unwrapped
1114

1215
test_v = [0.1, 0.2, 0.3 + 2pi, 0.4]
1316
res_v = unwrap(test_v)
14-
@test test_v [0.1, 0.2, 0.3 + 2pi, 0.4]
17+
@test res_v unwrapped
18+
@test test_v == [0.1, 0.2, 0.3 + 2pi, 0.4]
19+
1520
res_v .= 0
1621
unwrap!(res_v, test_v)
17-
@test res_v [0.1, 0.2, 0.3, 0.4]
18-
@test test_v [0.1, 0.2, 0.3 + 2pi, 0.4]
19-
unwrap!(test_v)
20-
@test test_v [0.1, 0.2, 0.3, 0.4]
22+
@test res_v unwrapped
23+
@test test_v == [0.1, 0.2, 0.3 + 2pi, 0.4]
24+
25+
@test unwrap!(test_v) === test_v
26+
@test test_v unwrapped
2127

2228
# test unwrapping within multi-dimensional array
23-
wrapped = [0.1, 0.2 + 2pi, 0.3, 0.4]
24-
unwrapped = [0.1, 0.2, 0.3, 0.4]
25-
wrapped = hcat(wrapped, wrapped)
29+
wrapped = repeat([0.1, 0.2 + 2pi, 0.3, 0.4], 1, 2)
2630
unwrapped = hcat(unwrapped, unwrapped)
2731
@test unwrap(wrapped, dims=2) wrapped
2832
@test unwrap(wrapped, dims=1) unwrapped
@@ -101,39 +105,46 @@ end
101105

102106
@testset "Unwrap 3D" begin
103107
types = (Float32, Float64, BigFloat)
104-
f(x, y, z) = 0.1x^2 - 2y + 2z
108+
109+
f_nowrap(x, y, z) = 0.1x^2 - 2y + 2z
105110
f_wraparound2(x, y, z) = 5*sin(x) + 2*cos(y) + z
106111
f_wraparound3(x, y, z) = 5*sin(x) + 2*cos(y) - 4*cos(z)
112+
107113
for T in types
108114
grid = range(zero(T), stop=2convert(T, π), length=11)
109-
f_uw = f.(grid, grid', reshape(grid, 1, 1, :))
110-
f_wr = f_uw .% (2convert(T, π))
111-
uw_test = unwrap(f_wr, dims=1:3)
112-
offset = first(f_uw) - first(uw_test)
113-
@test (uw_test.+offset) f_uw rtol=eps(T) #oop, nowrap
114-
# test in-place version
115-
unwrap!(f_wr, dims=1:3)
116-
offset = first(f_uw) - first(f_wr)
117-
@test (f_wr.+offset) f_uw rtol=eps(T) #ip, nowrap
118-
119-
f_uw = f_wraparound2.(grid, grid', reshape(grid, 1, 1, :))
120-
f_wr = f_uw .% (2convert(T, π))
121-
uw_test = unwrap(f_wr, dims=1:3)
122-
offset = first(f_uw) - first(uw_test)
123-
@test (uw_test.+offset) f_uw #oop, 2wrap
124-
# test in-place version
125-
unwrap!(f_wr, dims=1:3, circular_dims=(true, true, false))
126-
offset = first(f_uw) - first(f_wr)
127-
@test (f_wr.+offset) f_uw #ip, 2wrap
128-
129-
f_uw = f_wraparound3.(grid, grid', reshape(grid, 1, 1, :))
130-
f_wr = f_uw .% (2convert(T, π))
131-
uw_test = unwrap(f_wr, dims=1:3, circular_dims=(true, true, true))
132-
offset = first(f_uw) - first(uw_test)
133-
@test (uw_test.+offset) f_uw #oop, 3wrap
134-
# test in-place version
135-
unwrap!(f_wr, dims=1:3, circular_dims=(true, true, true))
136-
offset = first(f_uw) - first(f_wr)
137-
@test (f_wr.+offset) f_uw #oop, 3wrap
115+
116+
for (func, circular_dims) in (
117+
(f_nowrap, (false, false, false)),
118+
(f_wraparound2, (true, true, false)),
119+
(f_wraparound3, (true, true, true))
120+
)
121+
f_uw = func.(grid, grid', reshape(grid, 1, 1, :))
122+
f_wr = f_uw .% (2convert(T, π))
123+
uw_test = unwrap(f_wr; dims=1:3, circular_dims)
124+
offset = first(f_uw) - first(uw_test)
125+
@test (uw_test .+ offset) f_uw rtol = eps(T) # oop
126+
127+
# test in-place version
128+
unwrap!(f_wr; dims=1:3, circular_dims)
129+
offset = first(f_uw) - first(f_wr)
130+
@test (f_wr .+ offset) f_uw rtol = eps(T) # ip
131+
end
132+
end
133+
end
134+
135+
@testset "reproducible unwrap" begin
136+
function measure(s, rng, seed=rand(UInt))
137+
u1 = unwrap(s; dims=1:ndims(s), rng=seed!(rng, seed))
138+
u2 = unwrap(s; dims=1:ndims(s), rng=seed!(rng, seed))
139+
return sqrt(mean(abs2, u1 - u2))
140+
end
141+
142+
x1 = 1234 * rand(10_000)
143+
x2 = 1357 * rand(200, 200)
144+
x3 = 1248 * rand(30, 30, 30)
145+
146+
for x in (x1, x2, x3)
147+
@test measure(x, default_rng()) == measure(x, MersenneTwister()) ==
148+
measure(x, StableRNG(10)) == 0.0
138149
end
139150
end

0 commit comments

Comments
 (0)