Skip to content

Commit 20975e4

Browse files
authored
Make interpolation and extrapolation behaviour configurable (#97)
This adds `interpolate` and `extrapolate` keyword arguments to to the Item types (Image, MaskBinary, MaskMulti). Resolves #60
1 parent 6d25ba2 commit 20975e4

File tree

7 files changed

+252
-60
lines changed

7 files changed

+252
-60
lines changed

docs/src/projective/gallery.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,45 @@ tfm = CenterCrop((196, 196))
2626
image = Image(imagedata)
2727
apply(tfm, image) |> itemdata
2828
```
29+
Customization of how pixel values are interpolated and extrapolated during
30+
transformations is done with the Item types ([`Image`](@ref),
31+
[`MaskBinary`](@ref), [`MaskMulti`](@ref)). For example, if we scale the image
32+
we can see how the interpolation affects how values of projected pixels are
33+
calculated.
34+
```@example deps
35+
using Interpolations: BSpline, Constant, Linear
36+
tfm = ScaleFixed((2000, 2000)) |> CenterCrop((200, 200))
37+
showgrid(
38+
[
39+
# Default is linear interpolation for Image
40+
apply(tfm, Image(imagedata)),
41+
# Nearest neighbor interpolation
42+
apply(tfm, Image(imagedata; interpolate=BSpline(Constant()))),
43+
# Linear interpolation
44+
apply(tfm, Image(imagedata; interpolate=BSpline(Linear()))),
45+
];
46+
ncol=3,
47+
npad=8,
48+
)
49+
```
50+
Similarly, if we crop to a larger region than the image, we can see how
51+
extrapolation affects how pixel values are calculated in the regions outside
52+
the original image bounds.
53+
```@example deps
54+
import Interpolations
55+
tfm = CenterCrop((400, 400))
56+
showgrid(
57+
[
58+
apply(tfm, Image(imagedata)),
59+
apply(tfm, Image(imagedata; extrapolate=1)),
60+
apply(tfm, Image(imagedata; extrapolate=Interpolations.Flat())),
61+
apply(tfm, Image(imagedata; extrapolate=Interpolations.Periodic())),
62+
apply(tfm, Image(imagedata; extrapolate=Interpolations.Reflect())),
63+
];
64+
ncol=5,
65+
npad=8,
66+
)
67+
```
2968

3069
Now let's say we want to train a light house detector and have a bounding box for the light house. We can use the [`BoundingBox`](@ref) item to represent it. It takes the two corners of the bounding rectangle as the first argument. As the second argument we have to pass the size of the corresponding image.
3170

src/items/image.jl

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,21 @@
44
# correspond to the image axes.
55

66
"""
7-
Image(image[, bounds])
8-
9-
Item representing an N-dimensional image with element type T.
7+
Image(image[, bounds]; interpolate=BSpline(Linear()), extrapolate=zero(T))
8+
9+
Item representing an N-dimensional image with element type T. Optionally, the
10+
interpolation and extrapolation method can be provided. Interpolation here
11+
refers to how the values of projected pixels that fall into the transformed
12+
content bounds are calculated. Extrapolation refers to how to assign values
13+
that fall outside the projected content bounds. The default is linear
14+
interpolation and to fill new regions with zero.
15+
16+
!!! info
17+
The `Interpolations` package provides numerous methods for use with
18+
the `interpolate` and `extrapolate` keyword arguments. For instance,
19+
`BSpline(Linear())` and `BSpline(Constant())` provide linear and nearest
20+
neighbor interpolation, respectively. In addition `Flat()`, `Reflect()` and
21+
`Periodic()` boundary conditions are available for extrapolation.
1022
1123
## Examples
1224
@@ -30,14 +42,24 @@ showitems(item)
3042
struct Image{N,T} <: AbstractArrayItem{N,T}
3143
data::AbstractArray{T,N}
3244
bounds::Bounds{N}
45+
interpolate::Interpolations.InterpolationType
46+
extrapolate::ImageTransformations.FillType
3347
end
3448

35-
Image(data) = Image(data, Bounds(axes(data)))
36-
37-
function Image(data::AbstractArray{T,N}, sz::NTuple{N,Int}) where {T,N}
38-
return Image(data, Bounds(sz))
49+
function Image(
50+
data::AbstractArray{T,N},
51+
bounds::Bounds{N};
52+
interpolate::Interpolations.InterpolationType=BSpline(Linear()),
53+
extrapolate::ImageTransformations.FillType=zero(T),
54+
) where {T,N}
55+
return Image(data, bounds, interpolate, extrapolate)
3956
end
4057

58+
Image(data; kwargs...) = Image(data, Bounds(axes(data)); kwargs...)
59+
60+
function Image(data::AbstractArray{T,N}, sz::NTuple{N,Int}; kwargs...) where {T,N}
61+
return Image(data, Bounds(sz); kwargs...)
62+
end
4163

4264
Base.show(io::IO, item::Image{N,T}) where {N,T} =
4365
print(io, "Image{$N, $T}() with bounds $(item.bounds)")
@@ -68,7 +90,8 @@ function project(P, image::Image{N, T}, bounds::Bounds) where {N, T}
6890
itemdata(image),
6991
inv(P),
7092
bounds.rs;
71-
fillvalue = zero(T))
93+
method=image.interpolate,
94+
fillvalue=image.extrapolate)
7295
return Image(data_, bounds)
7396
end
7497

@@ -79,7 +102,7 @@ function project!(bufimage::Image, P, image::Image{N, T}, bounds::Bounds{N}) whe
79102
a = OffsetArray(parent(itemdata(bufimage)), bounds.rs)
80103
res = warp!(
81104
a,
82-
box_extrapolation(itemdata(image); fillvalue=zero(T)),
105+
box_extrapolation(itemdata(image); method=image.interpolate, fillvalue=image.extrapolate),
83106
inv(P),
84107
)
85108
return Image(res, bounds)

src/items/mask.jl

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
"""
2-
MaskMulti(a, [classes])
3-
4-
An `N`-dimensional multilabel mask with labels `classes`.
2+
MaskMulti(a, [classes]; interpolate=BSpline(Constant()), extrapolate=Flat())
3+
4+
An `N`-dimensional multilabel mask with labels `classes`. Optionally, the
5+
interpolation and extrapolation method can be provided. Interpolation here
6+
refers to how the values of projected pixels that fall into the transformed
7+
content bounds are calculated. Extrapolation refers to how to assign values
8+
that fall outside the projected content bounds. The default is nearest neighbor
9+
interpolation and flat extrapolation of the edges into new regions.
10+
11+
!!! info
12+
The `Interpolations` package provides numerous methods for use with
13+
the `interpolate` and `extrapolate` keyword arguments. For instance,
14+
`BSpline(Linear())` and `BSpline(Constant())` provide linear and nearest
15+
neighbor interpolation, respectively. In addition `Flat()`, `Reflect()` and
16+
`Periodic()` boundary conditions are available for extrapolation.
517
618
## Examples
719
8-
{cell=MaskMulti}
920
```julia
1021
using DataAugmentation
1122
1223
mask = MaskMulti(rand(1:3, 100, 100))
1324
```
14-
{cell=MaskMulti}
1525
```julia
1626
showitems(mask)
1727
```
@@ -20,19 +30,30 @@ struct MaskMulti{N, T<:Integer, U} <: AbstractArrayItem{N, T}
2030
data::AbstractArray{T, N}
2131
classes::AbstractVector{U}
2232
bounds::Bounds{N}
33+
interpolate::Interpolations.InterpolationType
34+
extrapolate::ImageTransformations.FillType
2335
end
2436

37+
function MaskMulti(
38+
data::AbstractArray{T,N},
39+
classes::AbstractVector{U},
40+
bounds::Bounds{N};
41+
interpolate::Interpolations.InterpolationType=BSpline(Constant()),
42+
extrapolate::ImageTransformations.FillType=Flat(),
43+
) where {N, T<:Integer, U}
44+
return MaskMulti(data, classes, bounds, interpolate, extrapolate)
45+
end
2546

26-
function MaskMulti(a::AbstractArray, classes = unique(a))
47+
function MaskMulti(a::AbstractArray, classes = unique(a); kwargs...)
2748
bounds = Bounds(size(a))
2849
minimum(a) >= 1 || error("Class values must start at 1")
29-
return MaskMulti(a, classes, bounds)
50+
return MaskMulti(a, classes, bounds; kwargs...)
3051
end
3152

32-
MaskMulti(a::AbstractArray{<:Gray{T}}, args...) where T = MaskMulti(reinterpret(T, a), args...)
33-
MaskMulti(a::AbstractArray{<:Normed{T}}, args...) where T = MaskMulti(reinterpret(T, a), args...)
34-
MaskMulti(a::IndirectArray, classes = a.values, bounds = Bounds(size(a))) =
35-
MaskMulti(a.index, classes, bounds)
53+
MaskMulti(a::AbstractArray{<:Gray{T}}, args...; kwargs...) where T = MaskMulti(reinterpret(T, a), args...; kwargs...)
54+
MaskMulti(a::AbstractArray{<:Normed{T}}, args...; kwargs...) where T = MaskMulti(reinterpret(T, a), args...; kwargs...)
55+
MaskMulti(a::IndirectArray, classes = a.values, bounds = Bounds(size(a)); kwargs...) =
56+
MaskMulti(a.index, classes, bounds; kwargs...)
3657

3758
Base.show(io::IO, mask::MaskMulti{N, T}) where {N, T} =
3859
print(io, "MaskMulti{$N, $T}() with size $(size(itemdata(mask))) and $(length(mask.classes)) classes")
@@ -41,12 +62,15 @@ Base.show(io::IO, mask::MaskMulti{N, T}) where {N, T} =
4162
getbounds(mask::MaskMulti) = mask.bounds
4263

4364

44-
function project(P, mask::MaskMulti, bounds::Bounds)
45-
a = itemdata(mask)
46-
etp = mask_extrapolation(a)
47-
res = warp(etp, inv(P), bounds.rs)
65+
function project(P, mask::MaskMulti{N, T, U}, bounds::Bounds{N}) where {N, T, U}
66+
res = warp(
67+
itemdata(mask),
68+
inv(P),
69+
bounds.rs;
70+
method=mask.interpolate,
71+
fillvalue=mask.extrapolate)
4872
return MaskMulti(
49-
res,
73+
convert.(T, res),
5074
mask.classes,
5175
bounds
5276
)
@@ -57,7 +81,7 @@ function project!(bufmask::MaskMulti, P, mask::MaskMulti, bounds)
5781
a = OffsetArray(parent(itemdata(bufmask)), bounds.rs)
5882
warp!(
5983
a,
60-
mask_extrapolation(itemdata(mask)),
84+
box_extrapolation(itemdata(mask); method=mask.interpolate, fillvalue=mask.extrapolate),
6185
inv(P),
6286
)
6387
return MaskMulti(
@@ -80,30 +104,47 @@ end
80104
# ## Binary masks
81105

82106
"""
83-
MaskBinary(a)
84-
85-
An `N`-dimensional binary mask.
107+
MaskBinary(a; interpolate=BSpline(Constant()), extrapolate=Flat())
108+
109+
An `N`-dimensional binary mask. Optionally, the interpolation and extrapolation
110+
method can be provided. Interpolation here refers to how the values of
111+
projected pixels that fall into the transformed content bounds are calculated.
112+
Extrapolation refers to how to assign values that fall outside the projected
113+
content bounds. The default is nearest neighbor interpolation and flat
114+
extrapolation of the edges into new regions.
115+
116+
!!! info
117+
The `Interpolations` package provides numerous methods for use with
118+
the `interpolate` and `extrapolate` keyword arguments. For instance,
119+
`BSpline(Linear())` and `BSpline(Constant())` provide linear and nearest
120+
neighbor interpolation, respectively. In addition `Flat()`, `Reflect()` and
121+
`Periodic()` boundary conditions are available for extrapolation.
86122
87123
## Examples
88124
89-
{cell=MaskMulti}
90125
```julia
91126
using DataAugmentation
92127
93128
mask = MaskBinary(rand(Bool, 100, 100))
94129
```
95-
{cell=MaskMulti}
96130
```julia
97131
showitems(mask)
98132
```
99133
"""
100134
struct MaskBinary{N} <: AbstractArrayItem{N, Bool}
101135
data::AbstractArray{Bool, N}
102136
bounds::Bounds{N}
137+
interpolate::Interpolations.InterpolationType
138+
extrapolate::ImageTransformations.FillType
103139
end
104140

105-
function MaskBinary(a::AbstractArray{Bool, N}, bounds = Bounds(size(a))) where N
106-
return MaskBinary(a, bounds)
141+
function MaskBinary(
142+
a::AbstractArray,
143+
bounds = Bounds(size(a));
144+
interpolate::Interpolations.InterpolationType=BSpline(Constant()),
145+
extrapolate::ImageTransformations.FillType=Flat(),
146+
)
147+
return MaskBinary(a, bounds, interpolate, extrapolate)
107148
end
108149

109150
Base.show(io::IO, mask::MaskBinary{N}) where {N} =
@@ -112,19 +153,21 @@ Base.show(io::IO, mask::MaskBinary{N}) where {N} =
112153
getbounds(mask::MaskBinary) = mask.bounds
113154

114155
function project(P, mask::MaskBinary, bounds::Bounds)
115-
etp = mask_extrapolation(itemdata(mask))
116-
return MaskBinary(
117-
warp(etp, inv(P), bounds.rs),
118-
bounds,
119-
)
156+
res = warp(
157+
itemdata(mask),
158+
inv(P),
159+
bounds.rs;
160+
method=mask.interpolate,
161+
fillvalue=mask.extrapolate)
162+
return MaskBinary(convert.(Bool, res), bounds)
120163
end
121164

122165

123166
function project!(bufmask::MaskBinary, P, mask::MaskBinary, bounds)
124167
a = OffsetArray(parent(itemdata(bufmask)), bounds.rs)
125-
res = warp!(
168+
warp!(
126169
a,
127-
mask_extrapolation(itemdata(mask)),
170+
box_extrapolation(itemdata(mask); method=mask.interpolate, fillvalue=mask.extrapolate),
128171
inv(P),
129172
)
130173
return MaskBinary(
@@ -136,15 +179,3 @@ end
136179
function showitem!(img, mask::MaskBinary)
137180
showimage!(img, colorview(Gray, itemdata(mask)))
138181
end
139-
# ## Helpers
140-
141-
142-
function mask_extrapolation(
143-
mask::AbstractArray{T};
144-
t = T,
145-
degree = Constant(),
146-
boundary = Flat()) where T
147-
itp = interpolate(t, t, mask, BSpline(degree))
148-
etp = extrapolate(itp, Flat())
149-
return etp
150-
end

src/testing.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,19 @@ function testitem end
103103
testitem(::Type{ArrayItem}) = testitem(ArrayItem{2, Float32})
104104
testitem(::Type{ArrayItem{N, T}}) where {N, T} = ArrayItem(rand(T, ntuple(i -> 16, N)))
105105

106-
testitem(::Type{Image}) = testitem(Image{2, RGB{N0f8}})
107-
testitem(::Type{Image{N, T}}) where {N, T} = Image(rand(T, ntuple(i -> 16, N)))
106+
testitem(::Type{Image}; kwargs...) = testitem(Image{2, RGB{N0f8}}; kwargs...)
107+
testitem(::Type{Image{N}}; kwargs...) where {N} = testitem(Image{N, RGB{N0f8}}; kwargs...)
108+
testitem(::Type{Image{N, T}}; kwargs...) where {N, T} = Image(rand(T, ntuple(i -> 16, N)); kwargs...)
108109

109-
testitem(::Type{MaskBinary}) = testitem(MaskBinary{2})
110-
testitem(::Type{MaskBinary{N}}) where {N} = MaskBinary(rand(Bool, ntuple(i -> 16, N)))
110+
testitem(::Type{MaskBinary}; kwargs...) = testitem(MaskBinary{2}; kwargs...)
111+
testitem(::Type{MaskBinary{N}}; kwargs...) where {N} = MaskBinary(rand(Bool, ntuple(i -> 16, N)); kwargs...)
111112

112-
testitem(::Type{MaskMulti}) = testitem(MaskMulti{2, UInt8})
113-
function testitem(::Type{MaskMulti{N, T}}) where {N, T}
113+
testitem(::Type{MaskMulti}; kwargs...) = testitem(MaskMulti{2, UInt8}; kwargs...)
114+
testitem(::Type{MaskMulti{N}}; kwargs...) where {N} = testitem(MaskMulti{N, UInt8}; kwargs...)
115+
function testitem(::Type{MaskMulti{N, T}}; kwargs...) where {N, T}
114116
n = rand(2:10)
115117
data = T.(rand(1:n, ntuple(i -> 16, N)))
116-
MaskMulti(data, 1:n)
118+
MaskMulti(data, 1:n; kwargs...)
117119
end
118120

119121

test/Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
88
DataAugmentation = "88a5189c-e7ff-4f85-ac6b-e6158070f02e"
99
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
1010
ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
11+
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
1112
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
12-
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
13+
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
1314
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
15+
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
16+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1417
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1518
TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04"

test/imports.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ using Colors
77
using FixedPointNumbers: N0f8
88
using LinearAlgebra
99
using Rotations
10+
using Statistics
11+
using OffsetArrays
12+
import Interpolations
1013

1114

1215
using DataAugmentation: Item, Transform, getrandstate, itemdata, setdata, ComposedProjectiveTransform,

0 commit comments

Comments
 (0)