From ea2324a3833892d0e8c0ba0a05f3cea7874535c1 Mon Sep 17 00:00:00 2001 From: Isabelle Tan Date: Tue, 3 Jan 2023 16:20:25 +0100 Subject: [PATCH 1/6] Fix getprojection() methods for ND case for ScaleKeepAspect, ScaleFixed and PinOrigin. Also add ScaleFixed and Zoom to the 3D test to catch the bug. --- src/projective/affine.jl | 18 +++++++++--------- test/projective/affine.jl | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/projective/affine.jl b/src/projective/affine.jl index 6d92e687..940ca455 100644 --- a/src/projective/affine.jl +++ b/src/projective/affine.jl @@ -39,7 +39,7 @@ 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() @@ -47,7 +47,7 @@ function getprojection(scale::ScaleKeepAspect{N}, bounds; randstate = nothing) w 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 @@ -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 @@ -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 """ @@ -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 @@ -213,8 +213,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 diff --git a/test/projective/affine.jl b/test/projective/affine.jl index a8c0ae09..bd09dd74 100644 --- a/test/projective/affine.jl +++ b/test/projective/affine.jl @@ -177,8 +177,10 @@ end ) tfms = compose( + ScaleFixed((30, 40, 50)), ScaleRatio((.8, .8, .8)), ScaleKeepAspect((12, 10, 10)), + Zoom((1., 1.2)), RandomCrop((10, 10, 10)) ) testprojective(tfms, items) From 424ea6f2075a3a5fa692b5327cfe3e65933a50d7 Mon Sep 17 00:00:00 2001 From: Isabelle Tan Date: Tue, 3 Jan 2023 16:36:13 +0100 Subject: [PATCH 2/6] make centered() ND compatible, add Flips for ND data and test them in the 3D test --- docs/literate/projective/gallery.md | 6 +++--- docs/literate/projective/intro.md | 2 +- docs/literate/projective/usage.md | 4 ++-- docs/literate/stochastic.md | 2 +- src/DataAugmentation.jl | 1 + src/projective/affine.jl | 33 +++++++++++++++++++++++------ test/projective/affine.jl | 9 +++++--- 7 files changed, 41 insertions(+), 16 deletions(-) diff --git a/docs/literate/projective/gallery.md b/docs/literate/projective/gallery.md index 47fb3736..59e99b0f 100644 --- a/docs/literate/projective/gallery.md +++ b/docs/literate/projective/gallery.md @@ -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), ] ``` diff --git a/docs/literate/projective/intro.md b/docs/literate/projective/intro.md index 7a2a8c61..a1f96796 100644 --- a/docs/literate/projective/intro.md +++ b/docs/literate/projective/intro.md @@ -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. diff --git a/docs/literate/projective/usage.md b/docs/literate/projective/usage.md index eb5f3699..257e524f 100644 --- a/docs/literate/projective/usage.md +++ b/docs/literate/projective/usage.md @@ -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. @@ -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 diff --git a/docs/literate/stochastic.md b/docs/literate/stochastic.md index b9d04dc8..05c0b34f 100644 --- a/docs/literate/stochastic.md +++ b/docs/literate/stochastic.md @@ -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) ``` \ No newline at end of file diff --git a/src/DataAugmentation.jl b/src/DataAugmentation.jl index d2df407e..5f295fd6 100644 --- a/src/DataAugmentation.jl +++ b/src/DataAugmentation.jl @@ -82,6 +82,7 @@ export Item, WarpAffine, FlipX, FlipY, + FlipZ, PinOrigin, AdjustBrightness, AdjustContrast, diff --git a/src/projective/affine.jl b/src/projective/affine.jl index 940ca455..7491082f 100644 --- a/src/projective/affine.jl +++ b/src/projective/affine.jl @@ -178,24 +178,45 @@ 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}() +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 +FlipX(N) = FlipDim{N}(N==2 ? 2 : 1) +FlipY(N) = FlipDim{N}(N==2 ? 1 : 2) +FlipZ(N) = FlipDim{N}(3) + +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() diff --git a/test/projective/affine.jl b/test/projective/affine.jl index bd09dd74..36b27eb1 100644 --- a/test/projective/affine.jl +++ b/test/projective/affine.jl @@ -142,7 +142,7 @@ include("../imports.jl") @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() + tfm = FlipX(2) |> RandomCrop((64, 64)) |> PinOrigin() img = rand(RGB, 64, 64) item = Image(img) titem = apply(tfm, item) @@ -157,8 +157,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)), @@ -177,6 +177,9 @@ end ) tfms = compose( + FlipX(3), + FlipY(3), + FlipZ(3), ScaleFixed((30, 40, 50)), ScaleRatio((.8, .8, .8)), ScaleKeepAspect((12, 10, 10)), From c05656f1b1d4d18aace441a8af9bea957efa953b Mon Sep 17 00:00:00 2001 From: Isabelle Tan Date: Tue, 10 Jan 2023 15:27:58 +0100 Subject: [PATCH 3/6] Change N from argument to parametric type for FlipX, FlipY and FlipZ constructors, to match the FlipDim definition --- docs/literate/projective/gallery.md | 4 ++-- docs/literate/projective/intro.md | 2 +- docs/literate/stochastic.md | 2 +- src/projective/affine.jl | 19 ++++++++++++++----- test/projective/affine.jl | 12 ++++++------ 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/docs/literate/projective/gallery.md b/docs/literate/projective/gallery.md index 59e99b0f..5301029e 100644 --- a/docs/literate/projective/gallery.md +++ b/docs/literate/projective/gallery.md @@ -154,8 +154,8 @@ Flip the data on the horizontally and vertically, respectively. More generally, {cell=main result=false} ```julia tfms = [ - FlipX(2), - FlipY(2), + FlipX{2}(), + FlipY{2}(), Reflect(30), ] ``` diff --git a/docs/literate/projective/intro.md b/docs/literate/projective/intro.md index a1f96796..32bc7911 100644 --- a/docs/literate/projective/intro.md +++ b/docs/literate/projective/intro.md @@ -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(2)) |> RandomResizeCrop((h, w)) +Maybe(FlipX{2}()) |> RandomResizeCrop((h, w)) ``` Let's pull apart the steps involved. diff --git a/docs/literate/stochastic.md b/docs/literate/stochastic.md index 05c0b34f..420c4ca2 100644 --- a/docs/literate/stochastic.md +++ b/docs/literate/stochastic.md @@ -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(2)) +tfm = Maybe(FlipX{2}()) titems = [apply(tfm, item) for _ in 1:8] showgrid(titems; ncol = 4, npad = 16) ``` \ No newline at end of file diff --git a/src/projective/affine.jl b/src/projective/affine.jl index 7491082f..53f5d31b 100644 --- a/src/projective/affine.jl +++ b/src/projective/affine.jl @@ -193,7 +193,7 @@ end """ - FlipDim{N}() + FlipDim{N}(dim) Reflect `N` dimensional data along the axis of dimension `dim`. Must satisfy 1 <= `dim` <= `N`. ## Examples ```julia @@ -202,12 +202,21 @@ tfm = FlipDim{2}(1) """ struct FlipDim{N} <: ProjectiveTransform dim::Int - FlipDim{N}(dim) where {N} = 1 <= dim <= N ? new{N}(dim) : error("invalid dimension") + FlipDim{N}(dim) where N = 1 <= dim <= N ? new{N}(dim) : error("invalid dimension") end + # 2D images use (r, c) = (y, x) convention -FlipX(N) = FlipDim{N}(N==2 ? 2 : 1) -FlipY(N) = FlipDim{N}(N==2 ? 1 : 2) -FlipZ(N) = FlipDim{N}(3) +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) diff --git a/test/projective/affine.jl b/test/projective/affine.jl index 36b27eb1..926cbd2d 100644 --- a/test/projective/affine.jl +++ b/test/projective/affine.jl @@ -142,7 +142,7 @@ include("../imports.jl") @testset ExtendedTestSet "`RandomCrop` correct indices" begin # Flipping and cropping should be the same as reverse-indexing # the flipped dimension - tfm = FlipX(2) |> RandomCrop((64, 64)) |> PinOrigin() + tfm = FlipX{2}() |> RandomCrop((64, 64)) |> PinOrigin() img = rand(RGB, 64, 64) item = Image(img) titem = apply(tfm, item) @@ -157,8 +157,8 @@ end @testset ExtendedTestSet "2D" begin tfms = compose( Rotate(10), - FlipX(2), - FlipY(2), + FlipX{2}(), + FlipY{2}(), ScaleRatio((.8, .8)), WarpAffine(0.1), Zoom((1., 1.2)), @@ -177,9 +177,9 @@ end ) tfms = compose( - FlipX(3), - FlipY(3), - FlipZ(3), + FlipX{3}(), + FlipY{3}(), + FlipZ{3}(), ScaleFixed((30, 40, 50)), ScaleRatio((.8, .8, .8)), ScaleKeepAspect((12, 10, 10)), From 5458c76404d230419c14285acc9f8575f0fd340e Mon Sep 17 00:00:00 2001 From: Isabelle Tan Date: Wed, 11 Jan 2023 16:29:52 +0100 Subject: [PATCH 4/6] add 2D and 3D test for flips: flip dimension size of 1 means identity operation --- test/projective/affine.jl | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/projective/affine.jl b/test/projective/affine.jl index 926cbd2d..8b7baa3e 100644 --- a/test/projective/affine.jl +++ b/test/projective/affine.jl @@ -139,8 +139,26 @@ include("../imports.jl") @test_nowarn apply!(buffer, tfm, image2) end + @testset ExtendedTestSet "FlipX 2D: flip dimension of 1 = identity" begin + tfm = FlipX{2}() + img = rand(RGB, 10, 1) + item = Image(img) + @test_nowarn titem = apply(tfm, item) + titem = apply(tfm, item) + @test itemdata(titem) == img + end + + @testset ExtendedTestSet "FlipZ 3D: flip dimension of 1 = identity" begin + tfm = FlipZ{3}() + img = rand(RGB, 10, 10, 1) + item = Image(img) + @test_nowarn titem = apply(tfm, item) + titem = apply(tfm, item) + @test itemdata(titem) == img + end + @testset ExtendedTestSet "`RandomCrop` correct indices" begin - # Flipping and cropping should be the same as reverse-indexing + # Flipping followed by cropping should be the same as reverse-indexing # the flipped dimension tfm = FlipX{2}() |> RandomCrop((64, 64)) |> PinOrigin() img = rand(RGB, 64, 64) From a514e33240dcffcb0e5e8303e33a1d5cb706a5f0 Mon Sep 17 00:00:00 2001 From: Isabelle Tan Date: Mon, 6 Mar 2023 17:27:50 +0100 Subject: [PATCH 5/6] add tests for all 2D and 3D flip indices, add test for double flip = identity --- test/projective/affine.jl | 58 ++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/test/projective/affine.jl b/test/projective/affine.jl index 8b7baa3e..6b8f9bc9 100644 --- a/test/projective/affine.jl +++ b/test/projective/affine.jl @@ -139,34 +139,60 @@ include("../imports.jl") @test_nowarn apply!(buffer, tfm, image2) end - @testset ExtendedTestSet "FlipX 2D: flip dimension of 1 = identity" begin - tfm = FlipX{2}() - img = rand(RGB, 10, 1) + + @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 + @test itemdata(titem) == img[:, end:-1:1] end - @testset ExtendedTestSet "FlipZ 3D: flip dimension of 1 = identity" begin - tfm = FlipZ{3}() - img = rand(RGB, 10, 10, 1) + @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 + @test itemdata(titem) == img[end:-1:1, :] end - @testset ExtendedTestSet "`RandomCrop` correct indices" begin - # Flipping followed by cropping should be the same as reverse-indexing - # the flipped dimension - tfm = FlipX{2}() |> RandomCrop((64, 64)) |> PinOrigin() - img = rand(RGB, 64, 64) + + @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 From 65eb33cf1d22a9eb57c376c3a819aa9444d26201 Mon Sep 17 00:00:00 2001 From: Paul Novotny Date: Sat, 28 Sep 2024 10:26:23 -0400 Subject: [PATCH 6/6] Fix Flips documentation and tests errors caused by master merge This adds missing documentation for the Flip transformations, and adds FlipDim to the reference page. Also, fixes the "Big Pipeline/3D" unit test that was failing due to not being able to compose two ComposedProjectiveTransforms. --- docs/src/ref.md | 1 + docs/src/transformations.md | 1 + src/DataAugmentation.jl | 1 + src/projective/affine.jl | 23 ++++++++++++++++++++++- src/projective/compose.jl | 3 +++ 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/src/ref.md b/docs/src/ref.md index 2790a29c..433e0425 100644 --- a/docs/src/ref.md +++ b/docs/src/ref.md @@ -6,6 +6,7 @@ BoundingBox CenterCrop CenterResizeCrop Crop +FlipDim FlipX FlipY FlipZ diff --git a/docs/src/transformations.md b/docs/src/transformations.md index 472c51c1..13e154bc 100644 --- a/docs/src/transformations.md +++ b/docs/src/transformations.md @@ -26,6 +26,7 @@ Projective transformations include: Affine transformations are a subgroup of projective transformations that can be composed very efficiently: composing two affine transformations results in another affine transformation. Affine transformations can represent translation, scaling, reflection and rotation. Available `Transform`s are: ```@docs; canonical=false +FlipDim FlipX FlipY FlipZ diff --git a/src/DataAugmentation.jl b/src/DataAugmentation.jl index 76ed2b9f..2f307f37 100644 --- a/src/DataAugmentation.jl +++ b/src/DataAugmentation.jl @@ -84,6 +84,7 @@ export Item, apply, Reflect, WarpAffine, + FlipDim, FlipX, FlipY, FlipZ, diff --git a/src/projective/affine.jl b/src/projective/affine.jl index b9352ca7..e53798ca 100644 --- a/src/projective/affine.jl +++ b/src/projective/affine.jl @@ -258,8 +258,11 @@ 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) ``` @@ -269,15 +272,33 @@ struct FlipDim{N} <: ProjectiveTransform FlipDim{N}(dim) where N = 1 <= dim <= N ? new{N}(dim) : error("invalid dimension") end -# 2D images use (r, c) = (y, x) convention +""" + FlipX{N}() + +Flip `N` dimensional data along the x-axis. 2D images use (r, c) = (y, x) +convention such that x-axis flips occur along the second dimension. For N >= 3, +x-axis flips occur along the first dimension. +""" struct FlipX{N} FlipX{N}() where N = FlipDim{N}(N==2 ? 2 : 1) end +""" + FlipY{N}() + +Flip `N` dimensional data along the y-axis. 2D images use (r, c) = (y, x) +convention such that y-axis flips occur along the first dimension. For N >= 3, +y-axis flips occur along the second dimension. +""" struct FlipY{N} FlipY{N}() where N = FlipDim{N}(N==2 ? 1 : 2) end +""" + FlipZ{N}() + +Flip `N` dimensional data along the z-axis. +""" struct FlipZ{N} FlipZ{N}() where N = FlipDim{N}(3) end diff --git a/src/projective/compose.jl b/src/projective/compose.jl index 4562b30e..48b62cf7 100644 --- a/src/projective/compose.jl +++ b/src/projective/compose.jl @@ -26,6 +26,9 @@ compose(composed::ComposedProjectiveTransform, tfm::ProjectiveTransform) = compose(tfm::ProjectiveTransform, composed::ComposedProjectiveTransform) = ComposedProjectiveTransform(tfm, composed.tfms...) +compose(composed1::ComposedProjectiveTransform, composed2::ComposedProjectiveTransform) = + ComposedProjectiveTransform(composed1.tfms..., composed2.tfms...) + # The random state is collected from the transformations that make up the # `ComposedProjectiveTransform`: