diff --git a/src/projective/affine.jl b/src/projective/affine.jl index 846dfc4..51a77ae 100644 --- a/src/projective/affine.jl +++ b/src/projective/affine.jl @@ -10,7 +10,7 @@ end """ - ScaleRatio(minlengths) <: ProjectiveTransform + ScaleRatio(ratios) <: ProjectiveTransform Scales the aspect ratio """ @@ -18,16 +18,22 @@ struct ScaleRatio{N} <: ProjectiveTransform ratios::NTuple{N} end +# This allows for roundtrip through ScaleFixed and avoids code duplication +fixed_sizes(scale::ScaleRatio, bounds::Bounds) = round.(Int, scale.ratios .* length.(bounds.rs)) -function getprojection(scale::ScaleRatio, bounds; randstate = nothing) - return scaleprojection(scale.ratios) +function getprojection(scale::ScaleRatio{N}, bounds::Bounds{N}; randstate = nothing) where N + return getprojection(ScaleFixed{N}(fixed_sizes(scale, bounds)), bounds; randstate) +end + +function projectionbounds(tfm::ScaleRatio{N}, P, bounds::Bounds{N}; randstate = nothing) where N + projectionbounds(ScaleFixed{N}(fixed_sizes(tfm, bounds)), P, bounds; randstate) end """ ScaleKeepAspect(minlengths) <: ProjectiveTransform -Scales the shortest side of `item` to `minlengths`, keeping the -original aspect ratio. +Scales the sides of `item` to be as least as large as `minlengths`, +keeping the original aspect ratio. ## Examples @@ -42,28 +48,15 @@ struct ScaleKeepAspect{N} <: ProjectiveTransform minlengths::NTuple{N, Int} end +# This allows for roundtrip through ScaleFixed and avoids code duplication +fixed_sizes(scale::ScaleKeepAspect, bounds::Bounds) = round.(Int, maximum(scale.minlengths ./ length.(bounds.rs)) .* length.(bounds.rs)) 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 any(upperleft .!= 0) - P = P ∘ Translation((Float32.(P(upperleft)) .+ 0.5f0)) - end - return P + getprojection(ScaleFixed{N}(fixed_sizes(scale, bounds)), bounds; randstate) end function projectionbounds(tfm::ScaleKeepAspect{N}, P, bounds::Bounds{N}; randstate = nothing) where N - origsz = length.(bounds.rs) - ratio = maximum((tfm.minlengths) ./ origsz) - sz = round.(Int, ratio .* origsz) - bounds_ = transformbounds(bounds, P) - bs_ = offsetcropbounds(sz, bounds_, ntuple(_ -> 0.5, N)) - return bs_ + projectionbounds(ScaleFixed{N}(fixed_sizes(tfm, bounds)), P, bounds; randstate) end """ @@ -80,6 +73,9 @@ end function getprojection(scale::ScaleFixed, bounds::Bounds{N}; randstate = nothing) where N + # If no scaling needs to be done, return a noop transform + (scale.sizes == length.(bounds.rs)) && return IdentityTransformation() + ratios = (scale.sizes .+ 1) ./ length.(bounds.rs) upperleft = SVector{N, Float32}(minimum.(bounds.rs)) .- 1 P = scaleprojection(ratios) @@ -92,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_, ntuple(_ -> 1., N)) + return offsetcropbounds(tfm.sizes, bounds_, ntuple(_ -> 0.5, N)) end """ diff --git a/src/projective/compose.jl b/src/projective/compose.jl index 48b62cf..d8d078f 100644 --- a/src/projective/compose.jl +++ b/src/projective/compose.jl @@ -54,5 +54,12 @@ function getprojection( end function projectionbounds(composed::ComposedProjectiveTransform, P, bounds; randstate = getrandstate(composed)) - return transformbounds(bounds, P) + @assert length(composed.tfms) == length(randstate) + P = CoordinateTransformations.IdentityTransformation() + for (tfm, r) in zip(composed.tfms, randstate) + P_tfm = getprojection(tfm, bounds; randstate = r) + bounds = projectionbounds(tfm, P_tfm, bounds; randstate = r) + P = P_tfm ∘ P + end + return bounds end diff --git a/test/projective/affine.jl b/test/projective/affine.jl index 8f62e20..519d5d2 100644 --- a/test/projective/affine.jl +++ b/test/projective/affine.jl @@ -76,18 +76,33 @@ include("../imports.jl") @test_nowarn apply(tfm, keypoints) timage = apply(tfm, image) tkeypoints = apply(tfm, keypoints) - @test length.(getbounds(timage).rs) == (25, 25) + @test !any(isnan.(timage |> itemdata)) + @test getbounds(timage).rs == (1:25, 1:25) @test getbounds(timage) == getbounds(tkeypoints) testprojective(tfm) end + @testset ExtendedTestSet "ScaleRatio" begin tfm = ScaleRatio((1/2, 1/2)) @test_nowarn apply(tfm, image) @test_nowarn apply(tfm, keypoints) timage = apply(tfm, image) tkeypoints = apply(tfm, keypoints) - @test getbounds(timage).rs == (0:25, 0:25) + @test !any(isnan.(timage |> itemdata)) + @test getbounds(timage).rs == (1:25, 1:25) + @test getbounds(timage) == getbounds(tkeypoints) + testprojective(tfm) + end + + @testset ExtendedTestSet "ScaleRatioTwice" begin + tfm = ScaleRatio((4/5, 4/5)) |> ScaleRatio((1/2, 1/2)) + @test_nowarn apply(tfm, image) + @test_nowarn apply(tfm, keypoints) + timage = apply(tfm, image) + tkeypoints = apply(tfm, keypoints) + @test !any(isnan.(timage |> itemdata)) + @test getbounds(timage).rs == (1:20, 1:20) @test getbounds(timage) == getbounds(tkeypoints) testprojective(tfm) end @@ -96,10 +111,15 @@ include("../imports.jl") tfm = ScaleKeepAspect((32, 32)) img = rand(RGB{N0f8}, 64, 96) - @test apply(tfm, Image(img)) |> itemdata |> size == (32, 48) - + timg = apply(tfm, Image(img)) |> itemdata + timg = apply(tfm, Image(img)) + @test getbounds(timg).rs == (1:32, 1:48) + @test !any(isnan.(timg |> itemdata)) img = rand(RGB{N0f8}, 196, 196) - @test apply(tfm, Image(img)) |> itemdata |> size == (32, 32) + timg = apply(tfm, Image(img)) |> itemdata + timg = apply(tfm, Image(img)) + @test getbounds(timg).rs == (1:32, 1:32) + @test !any(isnan.(timg |> itemdata)) end end