From df5b1c4b8572937555b0b07d8c9a5d8f4bcaf707 Mon Sep 17 00:00:00 2001 From: Paul Novotny Date: Sat, 28 Sep 2024 11:02:28 -0400 Subject: [PATCH] Optimize multiple crops Update CroppedProjectiveTranform to optimize projective transforms followed by multiple crops, not just one crop. For instance: Rotate(10) |> CenterCrop((100, 100)) |> RandomCrop((50, 50)) Is now optimized to warp only into the 50x50 region, instead of to the 100x100 region. --- src/projective/crop.jl | 68 ++++++++++++++++++++++------------------- test/projective/crop.jl | 40 +++++++++++++++++++----- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/projective/crop.jl b/src/projective/crop.jl index 3b63b180..60e60ba6 100644 --- a/src/projective/crop.jl +++ b/src/projective/crop.jl @@ -24,7 +24,7 @@ function apply(crop::Crop, item::Item; randstate = getrandstate(crop)) return apply( Project(CoordinateTransformations.IdentityTransformation()) |> crop, item; - randstate = (nothing, randstate)) + randstate = (nothing, (randstate,))) end @@ -50,14 +50,14 @@ end -struct CroppedProjectiveTransform{P<:ProjectiveTransform, C<:AbstractCrop} <: ProjectiveTransform +struct CroppedProjectiveTransform{P<:ProjectiveTransform, C<:Tuple} <: ProjectiveTransform tfm::P - crop::C + crops::C end function getrandstate(cropped::CroppedProjectiveTransform) - return (getrandstate(cropped.tfm), getrandstate(cropped.crop)) + return (getrandstate(cropped.tfm), getrandstate.(cropped.crops)) end @@ -69,49 +69,55 @@ function getprojection( return getprojection(cropped.tfm, bounds; randstate = tfmstate) end - function projectionbounds( - cropped::CroppedProjectiveTransform{PT, C}, + cropped::CroppedProjectiveTransform, P, bounds; - randstate = getrandstate(cropped)) where {PT, C<:Crop} - tfmstate, cropstate = randstate + randstate = getrandstate(cropped)) + tfmstate, cropstates = randstate bounds_ = projectionbounds(cropped.tfm, P, bounds; randstate = tfmstate) - return offsetcropbounds(cropped.crop.size, bounds_, cropstate) + for (crop, cropstate) in zip(cropped.crops, cropstates) + bounds_ = cropbounds(crop, bounds_; randstate = cropstate) + end + return bounds_ end +compose(tfm::ProjectiveTransform, crop::AbstractCrop) = CroppedProjectiveTransform(tfm, (crop,)) +compose(tfm::ProjectiveTransform, cropped::CroppedProjectiveTransform) = + CroppedProjectiveTransform(tfm |> cropped.tfm, cropped.crops) -function projectionbounds( - cropped::CroppedProjectiveTransform{PT, PadDivisible}, - P, - bounds; - randstate = getrandstate(cropped)) where {PT} - tfmstate, cropstate = randstate - bounds_ = projectionbounds(cropped.tfm, P, bounds; randstate = tfmstate) - ranges = bounds_.rs +function compose(composed::ComposedProjectiveTransform, cropped::CroppedProjectiveTransform) + return CroppedProjectiveTransform(composed |> cropped.tfm, cropped.crops) +end - sz = length.(ranges) - pad = (cropped.crop.by .- (sz .% cropped.crop.by)) .% cropped.crop.by +function compose(cropped::CroppedProjectiveTransform, crop::AbstractCrop) + return CroppedProjectiveTransform(cropped.tfm, (cropped.crops..., crop)) +end - start = minimum.(ranges) - end_ = start .+ sz .+ pad .- 1 - rs = UnitRange.(start, end_) - return Bounds(rs) +function compose(cropped::CroppedProjectiveTransform, projective::ProjectiveTransform) + return Sequence(cropped, projective) end +function compose(cropped::CroppedProjectiveTransform, composed::ComposedProjectiveTransform) + return Sequence(cropped, composed) +end -compose(tfm::ProjectiveTransform, crop::AbstractCrop) = CroppedProjectiveTransform(tfm, crop) -compose(tfm::ProjectiveTransform, crop::CroppedProjectiveTransform) = - CroppedProjectiveTransform(tfm |> crop.tfm, crop.crop) +cropbounds(crop::Crop, bounds::Bounds; randstate=getrandstate(crop)) = offsetcropbounds(crop.size, bounds, randstate) -function compose(composed::ComposedProjectiveTransform, cropped::CroppedProjectiveTransform) - return CroppedProjectiveTransform(composed |> cropped.tfm, cropped.crop) +function cropbounds( + crop::PadDivisible, + bounds::Bounds; + randstate = getrandstate(crop)) + ranges = bounds.rs -end + sz = length.(ranges) + pad = (crop.by .- (sz .% crop.by)) .% crop.by -function compose(cropped::CroppedProjectiveTransform, projective::ProjectiveTransform) - return Sequence(cropped, projective) + start = minimum.(ranges) + end_ = start .+ sz .+ pad .- 1 + rs = UnitRange.(start, end_) + return Bounds(rs) end diff --git a/test/projective/crop.jl b/test/projective/crop.jl index 790550dc..5ecf1bbd 100644 --- a/test/projective/crop.jl +++ b/test/projective/crop.jl @@ -11,13 +11,39 @@ end @testset ExtendedTestSet "CroppedProjectiveTransform" begin - image = Image(rand(100, 100)) - tfm = Project(Translation(20, 20)) - crop = CenterCrop((50, 50)) - @test tfm |> crop isa CroppedProjectiveTransform - - cropped = tfm |> crop - @test_nowarn apply(cropped, image) + @testset ExtendedTestSet "apply" begin + image = Image(rand(100, 100)) + tfm = Project(Translation(20, 20)) + crop = CenterCrop((50, 50)) + @test tfm |> crop isa CroppedProjectiveTransform + + cropped = tfm |> crop + @test_nowarn apply(cropped, image) + end + + @testset ExtendedTestSet "multiple crops $(N)D" for N in 2:3 + image = Image(rand(ntuple(_->100, N)...)) + tfms = [ + Project(Translation(ntuple(_->10, N)...)), + CenterCrop(ntuple(_->50, N)), + Crop(ntuple(_->30, N)), + ] + + # Apply transformatations as composed CroppedProjectiveTransform + composed = compose(tfms...) + @test composed isa CroppedProjectiveTransform + @test_nowarn apply(composed, image) + composedoutput = apply(composed, image) |> itemdata + + # Apply transformatations one at a time + sequenceoutput = image + for tfm in tfms + sequenceoutput = apply(tfm, sequenceoutput) + end + sequenceoutput = sequenceoutput |> itemdata + + @test composedoutput ≈ sequenceoutput + end end @testset ExtendedTestSet "PadDivisible" begin