Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Flips ND compatible and fix ND ScaleKeepAspect, ScaleFixed and PinOrigin #75

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/literate/projective/gallery.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,15 @@ tfms = [
o = showtransforms(tfms, (image, bbox))
```

### [`FlipX`](#), [`FlipY`](#), [`Reflect`](#)
### [`FlipX`](#), [`FlipY`](#), [`FlipZ`](#), [`FlipDim`](#), [`Reflect`](#)

Flip the data on the horizontally and vertically, respectively. More generally, reflect around an angle from the x-axis.

{cell=main result=false}
```julia
tfms = [
FlipX(),
FlipY(),
FlipX{2}(),
FlipY{2}(),
Reflect(30),
]
```
Expand Down
2 changes: 1 addition & 1 deletion docs/literate/projective/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We can break down most augmentation used in practive into a single (possibly sto
As an example, consider an image augmentation pipeline: A random horizontal flip, followed by a random resized crop. The latter resizes and crops (irregularly sized) images to a common size without distorting the aspect ratio.

```julia
Maybe(FlipX()) |> RandomResizeCrop((h, w))
Maybe(FlipX{2}()) |> RandomResizeCrop((h, w))
```

Let's pull apart the steps involved.
Expand Down
4 changes: 2 additions & 2 deletions docs/literate/projective/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Using projective transformations is as simple as any other transformations. Simply `compose` them:

```julia
Rotate(-10:10) |> ScaleRatio(0.7:0.1:1.2) |> FlipX() |> Crop((128, 128))
Rotate(-10:10) |> ScaleRatio(0.7:0.1:1.2) |> FlipX(2) |> Crop((128, 128))
```

The composition will automatically create a single projective transformation and evaluate only the cropped area.
Expand All @@ -14,7 +14,7 @@ Affine transformations are a subgroup of projective transformations that can be

- [`ScaleRatio`](#), [`ScaleKeepAspect`](#)
- [`Rotate`](#)
- [`FlipX`](#), [`FlipY`](#), [`Reflect`](#)
- [`FlipX`](#), [`FlipY`](#), [`FlipZ`](#), [`FlipDim`](#), [`Reflect`](#)
- [`WarpAffine`](#)

## Crops
Expand Down
2 changes: 1 addition & 1 deletion docs/literate/stochastic.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Let's say we have an image classification dataset. For most datasets, horizontal
```julia
using DataAugmentation, TestImages
item = Image(testimage("lighthouse"))
tfm = Maybe(FlipX())
tfm = Maybe(FlipX{2}())
titems = [apply(tfm, item) for _ in 1:8]
showgrid(titems; ncol = 4, npad = 16)
```
1 change: 1 addition & 0 deletions src/DataAugmentation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export Item,
WarpAffine,
FlipX,
FlipY,
FlipZ,
PinOrigin,
AdjustBrightness,
AdjustContrast,
Expand Down
60 changes: 45 additions & 15 deletions src/projective/affine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ struct ScaleKeepAspect{N} <: ProjectiveTransform
end


function getprojection(scale::ScaleKeepAspect{N}, bounds; randstate = nothing) where N
function getprojection(scale::ScaleKeepAspect{N}, bounds::Bounds{N}; randstate = nothing) where N
# If no scaling needs to be done, return a noop transform
scale.minlengths == length.(bounds.rs) && return IdentityTransformation()

# Offset `minlengths` by 1 to avoid black border on one side
ratio = maximum((scale.minlengths .+ 1) ./ length.(bounds.rs))
upperleft = SVector{N, Float32}(minimum.(bounds.rs)) .- 0.5
P = scaleprojection(Tuple(ratio for _ in 1:N))
if upperleft != SVector(0, 0)
if any(upperleft .!= 0)
P = P ∘ Translation((Float32.(P(upperleft)) .+ 0.5f0))
end
return P
Expand Down Expand Up @@ -75,11 +75,11 @@ struct ScaleFixed{N} <: ProjectiveTransform
end


function getprojection(scale::ScaleFixed, bounds; randstate = nothing)
function getprojection(scale::ScaleFixed, bounds::Bounds{N}; randstate = nothing) where N
ratios = (scale.sizes .+ 1) ./ length.(bounds.rs)
upperleft = SVector{2, Float32}(minimum.(bounds.rs)) .- 1
upperleft = SVector{N, Float32}(minimum.(bounds.rs)) .- 1
P = scaleprojection(ratios)
if upperleft != SVector(0, 0)
if any(upperleft .!= 0)
P = P ∘ Translation(-upperleft)
end
return P
Expand All @@ -88,7 +88,7 @@ end

function projectionbounds(tfm::ScaleFixed{N}, P, bounds::Bounds{N}; randstate = nothing) where N
bounds_ = transformbounds(bounds, P)
return offsetcropbounds(tfm.sizes, bounds_, (1., 1.))
return offsetcropbounds(tfm.sizes, bounds_, ntuple(_ -> 1., N))
end

"""
Expand Down Expand Up @@ -167,7 +167,7 @@ struct Reflect <: ProjectiveTransform
end


function getprojection(tfm::Reflect, bounds; randstate = getrandstate(tfm))
function getprojection(tfm::Reflect, bounds::Bounds{2}; randstate = getrandstate(tfm))
r = tfm.γ / 360 * 2pi
return centered(LinearMap(reflectionmatrix(r)), bounds)
end
Expand All @@ -178,24 +178,54 @@ end
Transform `P` so that is applied around the center of `bounds`
instead of the origin
"""
function centered(P, bounds::Bounds{2})
function centered(P, bounds::Bounds{N}) where N
upperleft = minimum.(bounds.rs)
bottomright = maximum.(bounds.rs)

midpoint = SVector{2, Float32}((bottomright .- upperleft) ./ 2) .+ SVector{2, Float32}(.5, .5)
midpoint = SVector{N, Float32}((bottomright .- upperleft) ./ 2) .+ .5f0
return recenter(P, midpoint)
end


FlipX() = Reflect(180)
FlipY() = Reflect(90)

function reflectionmatrix(r)
A = SMatrix{2, 2, Float32}(cos(2r), sin(2r), sin(2r), -cos(2r))
return round.(A; digits = 12)
end


"""
FlipDim{N}(dim)
Reflect `N` dimensional data along the axis of dimension `dim`. Must satisfy 1 <= `dim` <= `N`.
## Examples
```julia
tfm = FlipDim{2}(1)
```
"""
struct FlipDim{N} <: ProjectiveTransform
dim::Int
FlipDim{N}(dim) where N = 1 <= dim <= N ? new{N}(dim) : error("invalid dimension")
end

# 2D images use (r, c) = (y, x) convention
struct FlipX{N}
FlipX{N}() where N = FlipDim{N}(N==2 ? 2 : 1)
end

struct FlipY{N}
FlipY{N}() where N = FlipDim{N}(N==2 ? 1 : 2)
end

struct FlipZ{N}
FlipZ{N}() where N = FlipDim{N}(3)
end

function getprojection(tfm::FlipDim{N}, bounds::Bounds{N}; randstate = nothing) where N
arr = 1I(N)
arr[tfm.dim, tfm.dim] = -1
M = SMatrix{N, N, Float32}(arr)
return DataAugmentation.centered(LinearMap(M), bounds)
end


"""
PinOrigin()

Expand All @@ -213,8 +243,8 @@ at one.
"""
struct PinOrigin <: ProjectiveTransform end

function getprojection(::PinOrigin, bounds; randstate = nothing)
p = (-SVector{2, Float32}(minimum.(bounds.rs))) .+ 1
function getprojection(::PinOrigin, bounds::Bounds{N}; randstate = nothing) where N
p = (-SVector{N, Float32}(minimum.(bounds.rs))) .+ 1
P = Translation(p)
return P
end
Expand Down
69 changes: 59 additions & 10 deletions test/projective/affine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,60 @@ include("../imports.jl")
@test_nowarn apply!(buffer, tfm, image2)
end

@testset ExtendedTestSet "`RandomCrop` correct indices" begin
# Flipping and cropping should be the same as reverse-indexing
# the flipped dimension
tfm = FlipX() |> RandomCrop((64, 64)) |> PinOrigin()
img = rand(RGB, 64, 64)

@testset ExtendedTestSet "FlipX 2D correct indices" begin
tfm = FlipX{2}() |> RandomCrop((10,10)) |> PinOrigin()
img = rand(RGB, 10, 10)
item = Image(img)
@test_nowarn titem = apply(tfm, item)
titem = apply(tfm, item)
@test itemdata(titem) == img[:, end:-1:1]
end

@testset ExtendedTestSet "FlipY 2D correct indices" begin
tfm = FlipY{2}() |> RandomCrop((10,10)) |> PinOrigin()
img = rand(RGB, 10, 10)
item = Image(img)
@test_nowarn titem = apply(tfm, item)
titem = apply(tfm, item)
@test itemdata(titem) == img[end:-1:1, :]
end


@testset ExtendedTestSet "FlipX 3D correct indices" begin
tfm = FlipX{3}() |> RandomCrop((10,10,10)) |> PinOrigin()
img = rand(RGB, 10, 10, 10)
item = Image(img)
@test_nowarn titem = apply(tfm, item)
titem = apply(tfm, item)
timg = itemdata(titem)
rimg = img[:, end:-1:1]
@test titem.data == rimg
@test itemdata(titem) == img[end:-1:1, :, :]
end

@testset ExtendedTestSet "FlipY 3D correct indices" begin
tfm = FlipY{3}() |> RandomCrop((10,10,10)) |> PinOrigin()
img = rand(RGB, 10, 10, 10)
item = Image(img)
@test_nowarn titem = apply(tfm, item)
titem = apply(tfm, item)
@test itemdata(titem) == img[:, end:-1:1, :]
end

@testset ExtendedTestSet "FlipZ 3D correct indices" begin
tfm = FlipZ{3}() |> RandomCrop((10,10,10)) |> PinOrigin()
img = rand(RGB, 10, 10, 10)
item = Image(img)
@test_nowarn titem = apply(tfm, item)
titem = apply(tfm, item)
@test itemdata(titem) == img[:, :, end:-1:1]
end

@testset ExtendedTestSet "Double flip is identity" begin
tfm = FlipZ{3}() |> FlipZ{3}() |> RandomCrop((10,10,10)) |> PinOrigin()
img = rand(RGB, 10, 10, 10)
item = Image(img)
@test_nowarn titem = apply(tfm, item)
titem = apply(tfm, item)
@test itemdata(titem) == img
end
end

Expand All @@ -157,8 +201,8 @@ end
@testset ExtendedTestSet "2D" begin
tfms = compose(
Rotate(10),
FlipX(),
FlipY(),
FlipX{2}(),
FlipY{2}(),
ScaleRatio((.8, .8)),
WarpAffine(0.1),
Zoom((1., 1.2)),
Expand All @@ -177,8 +221,13 @@ end
)

tfms = compose(
FlipX{3}(),
FlipY{3}(),
FlipZ{3}(),
ScaleFixed((30, 40, 50)),
ScaleRatio((.8, .8, .8)),
ScaleKeepAspect((12, 10, 10)),
Zoom((1., 1.2)),
RandomCrop((10, 10, 10))
)
testprojective(tfms, items)
Expand Down