Skip to content

Commit 7a37b89

Browse files
authored
Merge pull request #31 from bionanoimaging/fix_reample_czt
fixed a problem with czt which causes wrap-around. This had an effect…
2 parents 87352d2 + 2ac1d42 commit 7a37b89

File tree

6 files changed

+131
-48
lines changed

6 files changed

+131
-48
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ makedocs(modules = [FourierTools],
1313
"Image Shearing with FFTs" => "shear.md",
1414
"Image Rotation with FFTs" => "rotate.md",
1515
"NFFT" => "nfft.md",
16+
"CZT" => "czt.md",
1617
"Fractional Fourier Transform" => "fractional.md",
1718
"Utility Functions" => "utils.md",
1819
]

docs/src/czt.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# CZTs
2+
Chirp Z Transformations: Allows Fourier-transformation and at the same time zooming into the result,
3+
which is why it is also called the Zoomed-FFT algorithm.
4+
The algorithm is loosely based on a publication [Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86] and a 2D Matlab Version written by N.G. Worku & H. Gross, with their consent (28. Oct. 2020) to make it openly available. It currently needs three FFTs to perform its work.
5+
As one of these FFTs only depends on the datasize and zoom parameters, it can be moved to a plan in future implementations.
6+
7+
```@docs
8+
FourierTools.czt
9+
FourierTools.iczt
10+
FourierTools.czt_1d
11+
```

docs/src/resampling.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,17 @@ The right image is the upsampled version of the left one.
6767
![](assets/image_low_res.png)
6868
![](assets/image_high_res.png)
6969

70+
There are fast versions (`upsample2`) for upsampling by a factor of 2.
7071

7172

7273
## Function References
7374
```@docs
7475
FourierTools.resample
76+
FourierTools.resample_by_FFT
77+
FourierTools.resample_by_RFFT
78+
FourierTools.resample_nfft
79+
FourierTools.resample_czt
7580
FourierTools.upsample2
81+
FourierTools.upsample2_abs2
82+
FourierTools.barrel_pin
7683
```

src/czt.jl

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
export czt, iczt
22

33
"""
4-
czt_1d(xin , scaled , d)
4+
czt_1d(xin , scaled , d; remove_wrap=false, pad_value=zero(eltype(xin)))
55
66
Chirp z transform along a single direction d of an ND array `xin` into the ND array 'xout'.
77
Note that xin and xout can be the same array for inplace operations.
88
Note that the result type is defined by `eltype(xin)` and not by `scales`.
9-
This code is based on a 2D Matlab version of the CZT, written by H. Gross et al.
10-
11-
#References: Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86
9+
10+
# References: Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86
11+
This code is loosely based on a 2D Matlab version of the CZT, written by N.G. Worku & H. Gross
12+
with their consent (28. Oct. 2020) to make it openly available.
1213
1314
# Arguments:
1415
+ `xin`: array to transform
1516
+ `scaled`: factor to zoom into during the 1-dimensional czt.
1617
+ `d`: single dimension to transform (as a tuple)
18+
+ `remove_wrap`: if true, the wrapped places will be set to pad_value
19+
+ `pad_value`: the value that the wrap-around will be set to if `remove_wrap` is `true`.
1720
1821
"""
19-
function czt_1d(xin, scaled, d)
22+
function czt_1d(xin, scaled, d; remove_wrap=false, pad_value=zero(eltype(xin)))
2023
sz=size(xin)
2124
# returns the real datatype
2225
rtype = real(eltype(xin))
@@ -72,28 +75,37 @@ function czt_1d(xin, scaled, d)
7275
NDTools.slice(xout,d, o) .-= NDTools.slice(xin,d, 1) .* (1im).^mod(o-midp,4)
7376
end
7477
end
75-
return xout
78+
if remove_wrap && (scaled < 1.0)
79+
nsz = Tuple(d == nd ? ceil(Int64, scaled * size(xin,d)) : size(xin,nd) for nd=1:ndims(xin))
80+
return select_region(select_region(xout, new_size=nsz), new_size=size(xout), pad_value=pad_value)
81+
else
82+
return xout
83+
end
7684
# xout .= g[dsize:(2*dsize-1)] .* reorient(fak, d, Val(ndims(xin)))
7785
end
7886

7987
"""
80-
czt(xin , scale, dims=1:length(size(xin)))
88+
czt(xin , scale, dims=1:length(size(xin)), remove_wrap=false)
8189
8290
Chirp z transform of the ND array `xin`
8391
This code is based on a 2D Matlab version of the CZT, written by H. Gross.
8492
The tuple `scale` defines the zoom factors in the Fourier domain. Each has to be bigger than one.
8593
86-
#See also: iczt, czt_1d
94+
# See also: `iczt`, `czt_1d`
8795
88-
#References: Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86
96+
# References: Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86
8997
9098
# Arguments:
9199
+ `xin`: array to transform
92100
+ `scale`: a tuple of factors (one for each dimension) to zoom into during the czt.
101+
Note that a factor of nothing (or 1.0) needs to be provided, if a dimension is not transformed.
93102
+ `dims`: a tuple of dimensions over which to apply the czt.
103+
+ `remove_wrap`: if true, the wrapped places will be set to zero.
104+
Note that the `pad_value` argument is only allowed for czt_1d to not cause confusion.
105+
106+
# Example:
94107
95-
#Example:
96-
```jdoctest
108+
```jldoctest
97109
julia> using IndexFunArrays
98110
99111
julia> sz = (10,10);
@@ -127,32 +139,46 @@ julia> zoomed = real.(ift(xft))
127139
0.0239759 -0.028264 0.0541186 -0.0116475 -0.261294 0.312719 -0.261294 -0.0116475 0.0541186 -0.028264
128140
```
129141
"""
130-
function czt(xin::Array{T,N}, scale, dims=1:length(size(xin)))::Array{complex(T),N} where {T,N}
142+
function czt(xin::Array{T,N}, scale, dims=1:length(size(xin));
143+
remove_wrap=false)::Array{complex(T),N} where {T,N}
131144
xout = xin
145+
if length(scale) != ndims(xin)
146+
error("Every of the $(ndims(xin)) dimension needs exactly one corresponding scale (zoom) factor, which should be equal to 1.0 for dimensions not contained in the dims argument.")
147+
end
148+
for d = 1:ndims(xin)
149+
if !(d in dims) && scale[d] != 1.0 && !isnothing(scale[d])
150+
error("The scale factor $(scale[d]) needs to be nothing or 1.0, if this dimension is not in the list of dimensions to transform.")
151+
end
152+
end
132153
for d in dims
133-
xout = czt_1d(xout, scale[d], d)
154+
xout = czt_1d(xout, scale[d], d; remove_wrap=remove_wrap)
134155
end
135156
return xout
136157
end
137158

138159
"""
139-
iczt(xin , scale, dims=1:length(size(xin)))
160+
iczt(xin , scale, dims=1:length(size(xin)); remove_wrap=false)
140161
141162
Inverse chirp z transform of the ND array `xin`
142163
This code is based on a 2D Matlab version of the CZT, written by H. Gross.
143164
The tuple `scale` defines the zoom factors in the Fourier domain. Each has to be bigger than one.
144165
145-
#References: Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86
166+
# References: Rabiner, Schafer, Rader, The Cirp z-Transform Algorithm, IEEE Trans AU 17(1969) p. 86
146167
147168
# Arguments:
148169
+ `xin`: array to transform
149170
+ `scale`: a tuple of factors (one for each dimension) of the the inverse czt.
171+
Note that a factor of nothing (or 1.0) needs to be provided, if a dimension is not transformed.
150172
+ `dims`: a tuple of dimensions over which to apply the inverse czt.
173+
+ `remove_wrap`: if true, the wrapped places will be set to zero.
174+
Note that the `pad_value` argument is only allowed for 1d czts to not cause confusion.
151175
152-
#See also: czt, czt_1d
176+
See also: `czt`, `czt_1d`
177+
178+
# Example
179+
180+
```jldoctest
153181
154-
#Example:
155-
```jdoctest
156182
julia> using IndexFunArrays
157183
158184
julia> sz = (10,10);
@@ -184,8 +210,8 @@ julia> iczt(xft,(1.2,1.3))
184210
-0.0275957+0.169775im 1.04314+0.130321im 1.13205-0.151519im 0.80774+0.0124851im 1.00574+0.0629632im 0.790545-0.283668im 1.1463+0.0940003im 1.03899+0.0589268im
185211
0.130009-0.120643im 0.450186-0.111656im 0.986722+0.0414382im 1.24013+0.14664im 1.06002+0.0348813im 1.25999+0.166495im 0.970263+0.0249785im 0.454973-0.106869im
186212
-0.0965531+0.0404296im -0.159713+0.0637132im 0.48095+0.0775406im 0.67753-0.263814im 0.77553-0.121603im 0.660335-0.00736904im 0.495205-0.135059im -0.163859+0.125535im
187-
```
213+
```
188214
"""
189-
function iczt( xin , scale, dims=1:length(size(xin)))
190-
conj(czt(conj(xin), scale, dims)) / prod(size(xin))
215+
function iczt( xin , scale, dims=1:length(size(xin)); remove_wrap=false)
216+
conj(czt(conj(xin), scale, dims; remove_wrap=remove_wrap)) / prod(size(xin))
191217
end

src/resampling.jl

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
export resample
22
export resample_by_FFT
33
export resample_by_RFFT
4-
export upsample2_abs2
54
export upsample2
6-
export upsample2_1D
5+
export upsample2_abs2
76
export resample_nfft
87
export resample_czt
98
export barrel_pin
109

11-
1210
"""
1311
resample(arr, new_size [, normalize=true])
1412
@@ -138,7 +136,8 @@ By default the first pixel maintains its position. However, this leads to a shif
138136
`fix_center=true` can be used to remedy this and the result array center position will agree to the source array center position.
139137
`keep_singleton=true` will not upsample dimensions of size one.
140138
Note that upsample2 is based on Fourier-shifting and you may have to deal with wrap-around problems.
141-
```jdoctest
139+
140+
```jldoctest
142141
julia> upsample2(collect(collect(1.0:9.0)'))
143142
2×18 Matrix{Float64}:
144143
1.0 0.24123 2.0 3.24123 3.0 2.93582 4.0 5.0 5.0 5.0 6.0 7.06418 7.0 6.75877 8.0 9.75877 9.0 5.0
@@ -147,7 +146,7 @@ julia> upsample2(collect(collect(1.0:9.0)'))
147146
julia> upsample2(collect(collect(1.0:9.0)'); fix_center=true, keep_singleton=true)
148147
1×18 Matrix{Float64}:
149148
5.0 1.0 0.24123 2.0 3.24123 3.0 2.93582 4.0 5.0 5.0 5.0 6.0 7.06418 7.0 6.75877 8.0 9.75877 9.0
150-
```
149+
```
151150
"""
152151
function upsample2(mat::AbstractArray{T, N}; dims=1:N, fix_center=false, keep_singleton=false) where {T,N}
153152
res = mat
@@ -167,18 +166,30 @@ function upsample2_abs2(mat::AbstractArray{T, N}; dims=1:N) where {T,N}
167166
end
168167

169168
"""
170-
resample_czt(arr, rel_zoom; shear=nothing, shear_dim=nothing, fix_nyquist=false, new_size = size(arr), rel_pad=0.2)
169+
resample_czt(arr, rel_zoom; shear=nothing, shear_dim=nothing, fix_nyquist=false, new_size = size(arr),
170+
do_damp=false, rel_pad=0.2, remove_wrap=true)
171171
172172
resamples the image with fixed factors or a list of separable functions using the chirp z transform algorithm.
173173
The data is first padded by a relative amount `rel_pad` which is needed to avoid wrap-around problems.
174174
As opposed to `resample()`, this routine allows for arbitrary non-integer zoom factors.
175-
It is reasonably fast but only allows a stretch (via `rel_zoom`) and a shift (via `shear` in pixels) per line or column
175+
It is reasonably fast but only allows a stretch (via `rel_zoom`) and a shift (via `shear` in pixels) per line or column.
176176
177177
Note that each entry of the tuple in `rel_zoom` or `shear` describes the zoom or shear to apply to all other dimensions individually
178178
per entry along this dimension number.
179179
180+
# Arguments:
181+
+ `arr`: array to resample
182+
+ `rel_zoom`: factors to zoom as a tuple or a tuple of functions defining the zooms
183+
+ `shear`: a tuple of shears or a tuple of shear functions defining the shears
184+
+ `shear_dim`: which dimension to shear
185+
+ `fix_nyquist`: defines whether to apply `fix_nyquist` when using the apply_shift_strength! function.
186+
+ `do_damp`: applies a padding and damping outside the region to zoom, to avoid artefacts
187+
+ `rel_pad`: amount of padding to apply, if `do_damp` is true
188+
+ `remove_wrap`: removes the wrap-around when zooming out.
189+
+ `new_size`: size of the result array. If not provided the same as the input size will be used.
190+
180191
# Examples
181-
```jdoctest
192+
```jldoctest
182193
julia> using TestImages, NDTools, View5D
183194
184195
julia> a = Float32.(testimage("resolution"));
@@ -192,21 +203,30 @@ julia> d = resample_czt(a, (x->1.0,x->1.0), shear=(x->50*x^2,0.0)); # a more com
192203
julia> @ve a,b,c,d # visualize distortions
193204
```
194205
"""
195-
function resample_czt(arr::AbstractArray{T,N}, rel_zoom; shear=nothing, shear_dim=nothing, fix_nyquist=false, new_size = size(arr), rel_pad=0.2, do_damp=false, center=CtrMid) where {T,N}
206+
function resample_czt(arr::AbstractArray{T,N}, rel_zoom;
207+
shear=nothing, shear_dim=nothing, fix_nyquist=false, new_size = size(arr),
208+
rel_pad=0.2, do_damp=false, center=CtrMid, remove_wrap=true, pad_value=zero(eltype(arr))) where {T,N}
196209
RT = real(T)
197210
orig_size = size(arr)
198211
if do_damp
199212
arr = damp_edge_outside(arr, rel_pad)
200213
else
201214
arr = copy(arr)
202215
end
203-
for d in 1:length(rel_zoom)
216+
for d in eachindex(rel_zoom)
204217
sd = mod(d,ndims(arr))+1
205218
if !isnothing(shear_dim)
206219
sd = shear_dim[d]
207220
end
208221
my_zoom = 1.0
209-
# case of a list of zoom numbers
222+
# resize the array before zooming, in case the result array is larger
223+
# This may be a bit wasteful depending on the zoom factors, but this case also
224+
# covers the zoomed shearing case
225+
if (new_size[d] > size(arr,d))
226+
nz = Tuple(d == nd ? new_size[nd] : size(arr,nd) for nd=1:ndims(arr))
227+
arr = collect(select_region(arr, new_size=nz))
228+
end
229+
# case of a list (or tuple) of zoom numbers
210230
if (isa(rel_zoom, Tuple) && isa(rel_zoom[1], Number)) || isa(rel_zoom, Number)
211231
my_zoom = rel_zoom[d]
212232
f_res = ift(arr, d)
@@ -219,9 +239,9 @@ function resample_czt(arr::AbstractArray{T,N}, rel_zoom; shear=nothing, shear_di
219239
FourierTools.apply_shift_strength!(f_res, f_res, shifts, d, sd, -myshear, fix_nyquist)
220240
end
221241
if T<:Real
222-
f_res = real(FourierTools.czt_1d(f_res, my_zoom, d))
242+
f_res = real(FourierTools.czt_1d(f_res, my_zoom, d; remove_wrap=remove_wrap, pad_value=pad_value))
223243
else
224-
f_res = T.(FourierTools.czt_1d(f_res, my_zoom, d))
244+
f_res = FourierTools.czt_1d(f_res, my_zoom, d; remove_wrap=remove_wrap, pad_value=pad_value)
225245
end
226246
select_region!(f_res, arr)
227247
# case of position dependent zoom functions
@@ -251,9 +271,9 @@ function resample_czt(arr::AbstractArray{T,N}, rel_zoom; shear=nothing, shear_di
251271
end
252272
f_res = let
253273
if T<:Real
254-
real(FourierTools.czt_1d(f_res, my_zoom, 1))
274+
real(FourierTools.czt_1d(f_res, my_zoom, 1; remove_wrap=remove_wrap, pad_value=pad_value))
255275
else
256-
FourierTools.czt_1d(f_res, my_zoom, 1)
276+
FourierTools.czt_1d(f_res, my_zoom, 1; remove_wrap=remove_wrap, pad_value=pad_value)
257277
end
258278
end
259279
select_region!(f_res, slice)
@@ -271,11 +291,12 @@ end
271291
"""
272292
barrel_pin(arr, rel=0.5)
273293
274-
emulates a barrel (`rel>0`) or a pincushion (`rel<0`) distortion. The distortions are calculated using `resample_czt()` with separable quadratic zooms.
294+
emulates a barrel (`rel>0`) or a pincushion (`rel<0`) distortion. The distortions are calculated
295+
using `resample_czt()` with separable quadratic zooms.
275296
276297
See also: `resample_czt()`
277298
# Examples
278-
```jdoctest
299+
```jldoctest
279300
julia> using TestImages, NDTools, View5D
280301
281302
julia> a = Float32.(testimage("resolution"))
@@ -297,19 +318,23 @@ end
297318
"""
298319
resample_nfft(img, new_pos, dst_size=nothing; pixel_coords=false, is_local_shift=false, is_src_coords=true, reltol=1e-9)
299320
300-
resamples an ND-array to a set of new positions `new_pos` measured in either in pixels (`pixel_coords=true`) or relative (Fourier-) image coordinates (`pixel_coords=false`).
321+
resamples an ND-array to a set of new positions `new_pos` measured in either in pixels (`pixel_coords=true`)
322+
or relative (Fourier-) image coordinates (`pixel_coords=false`).
301323
`new_pos` can be
302324
+ an array of `Tuples` specifying the zoom along each direction
303-
+ an `N+1` dimensional array (for `N`-dimensional imput data `img`) of destination postions, the last dimension enumerating the respective destination corrdinate dimension.
325+
+ an `N+1` dimensional array (for `N`-dimensional imput data `img`) of destination postions, the last dimension
326+
enumerating the respective destination corrdinate dimension.
304327
+ a function accepting a coordinate `Tuple` and yielding a destination position `Tuple`.
305328
306-
`resample_nfft` can perform a large range of possible resamplings. Note that the default setting is `is_src_coords=true` which means that the source coordinates of each destination
307-
position have to be specified. This has the advantage that the result has usually less artefacts, but the positions may be more less convenient to specify.
329+
`resample_nfft` can perform a large range of possible resamplings. Note that the default setting is `is_src_coords=true`
330+
which means that the source coordinates of each destination position have to be specified. This has the advantage that
331+
the result has usually less artefacts, but the positions may be more less convenient to specify.
308332
309333
# Arguements
310334
+ `img`: the image to apply resampling to
311335
+ `new_pos``: specifies the resampling. See description above.
312-
+ `dst_size`: this argument optionally defines the output size. If you require a different result size for `new_pos` being a function or with `is_src_coords=true`, state it here. By defaul (`dst_size=nothing`) the
336+
+ `dst_size`: this argument optionally defines the output size. If you require a different result size
337+
for `new_pos` being a function or with `is_src_coords=true`, state it here. By defaul (`dst_size=nothing`) the
313338
destination size will be inferred form the argument `new_pos` or assumed to be `size(img)`.
314339
+ `is_local_shift`: specifies, whether the resampling coordinates refer to a relative shift or absoluter coordinates
315340
+ `is_in_pixels`: specifies whether the coordinates (or relative distances) are given in pixel pitch units (`is_in_pixels=true`) or in units relative to the array sizes (Fourier convention)
@@ -318,7 +343,7 @@ position have to be specified. This has the advantage that the result has usuall
318343
319344
See also: `resample`, `resample_czt`
320345
# Examples
321-
```julia-repl
346+
```jldoctest
322347
julia> using FourierTools, TestImages, NDTools, View5D, IndexFunArrays
323348
324349
julia> a = Float32.(testimage("resolution"));
@@ -363,7 +388,9 @@ julia> f = resample_nfft(a, new_pos, is_src_coords=false);
363388
julia> @ve a e f
364389
```
365390
"""
366-
function resample_nfft(img::AbstractArray{T,D}, new_pos::AbstractArray{T2,D2}, dst_size=nothing; pad_value=nothing, is_in_pixels=false, is_local_shift=false, is_src_coords=true, reltol=1e-9)::AbstractArray{T,D} where {T,D,T2,D2} #
391+
function resample_nfft(img::AbstractArray{T,D}, new_pos::AbstractArray{T2,D2}, dst_size=nothing;
392+
pad_value=nothing, is_in_pixels=false, is_local_shift=false, is_src_coords=true,
393+
reltol=1e-9)::AbstractArray{T,D} where {T,D,T2,D2} #
367394

368395
Fimg = let
369396
if is_src_coords
@@ -383,7 +410,9 @@ function resample_nfft(img::AbstractArray{T,D}, new_pos::AbstractArray{T2,D2}, d
383410
return img
384411
end
385412

386-
function resample_nfft(img::AbstractArray{T,D}, new_pos_fkt::Function, dst_size=nothing; pad_value=nothing, is_in_pixels=false, is_local_shift=false, is_src_coords=true, reltol=1e-9)::AbstractArray{T,D} where {T,D} #
413+
function resample_nfft(img::AbstractArray{T,D}, new_pos_fkt::Function, dst_size=nothing;
414+
pad_value=nothing, is_in_pixels=false, is_local_shift=false, is_src_coords=true,
415+
reltol=1e-9)::AbstractArray{T,D} where {T,D} #
387416
Fimg = let
388417
if is_src_coords
389418
p = plan_nfft_nd(img, new_pos_fkt, dst_size; pad_value=pad_value, is_in_pixels=is_in_pixels, is_local_shift=is_local_shift, is_adjoint=false, reltol=reltol)

0 commit comments

Comments
 (0)