Skip to content

Commit eba02a9

Browse files
authored
Fix BoundingBox rotations (#95)
Bounding boxes are not transformed correctly with rotation transforms. This is due to only storing the min and max corners, and assuming that after rotations the corners still contain the minimum and maximum coordinates. This change stores all 4 corners (for 2D; 8 corners for 3D) in the BoundingBox. All of these corner points are transformed, and we can accurately calculate the min and max as needed (by itemdata). Fixes #94
1 parent ab7bd18 commit eba02a9

File tree

4 files changed

+42
-7
lines changed

4 files changed

+42
-7
lines changed

src/items/keypoints.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,14 @@ getbounds(keypoints::Keypoints) = keypoints.bounds
4040

4141

4242
function project(P, keypoints::Keypoints{N, T}, bounds::Bounds{N}) where {N, T}
43-
# TODO: convert back to `T`?
4443
return Keypoints(
45-
map(fmap(P), keypoints.data),
44+
collect(SVector{N, T}, map(fmap(P), keypoints.data)),
4645
bounds,
4746
)
4847
end
4948
function project!(buf, P, keypoints::Keypoints{N, T}, bounds::Bounds{N}) where {N, T}
50-
# TODO: convert back to `T`?
5149
return Keypoints(
52-
map(fmap(P), keypoints.data),
50+
collect(SVector{N, T}, map(fmap(P), keypoints.data)),
5351
bounds,
5452
)
5553
end
@@ -126,11 +124,16 @@ struct BoundingBox{N, T, S} <: ItemWrapper{Keypoints{N, T, S, 1}}
126124
item::Keypoints{N, T, S, 1}
127125
end
128126

129-
function BoundingBox(data::AbstractVector{<:SVector{N}}, bounds) where N
130-
length(data) == N || error("Give $N corner points for an $N-dimensional bounding box")
131-
BoundingBox(Keypoints(data, bounds))
127+
function BoundingBox(data::AbstractVector{<:SVector{N, T}}, bounds) where {N, T}
128+
length(data) == 2 || error("Give 2 corner points for an $N-dimensional bounding box")
129+
corners = reduce(vcat, Iterators.product(zip(data...)...))
130+
corners = collect(SVector{N, T}, corners)
131+
corners = SVector{2^N, SVector{N, T}}(corners)
132+
BoundingBox{N, T, SVector{N, T}}(Keypoints(corners, bounds))
132133
end
133134

135+
itemdata(box::BoundingBox{N, T}) where {N, T} =
136+
[min.(box.item.data...), max.(box.item.data...)]
134137

135138
Base.show(io::IO, item::BoundingBox{N, T}) where {N, T} =
136139
print(io, "BoundingBox{$N, $T}()")

test/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ 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+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
13+
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
1214
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1315
TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04"

test/imports.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ using TestSetExtensions
55
using CoordinateTransformations
66
using Colors
77
using FixedPointNumbers: N0f8
8+
using LinearAlgebra
9+
using Rotations
810

911

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

test/items/keypoints.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,31 @@ include("../imports.jl")
1010

1111
@test_nowarn apply(tfm, Polygon(keypoints))
1212
end
13+
14+
@testset ExtendedTestSet "$(N)D BoundingBox" for N in 2:4
15+
bounds = Bounds{N}(ntuple(_ -> -50:50, N))
16+
17+
min = SVector{N, Float64}(ntuple(i -> i==1 ? -20 : -10, N)) # [-20, -10, ...]
18+
max = SVector{N, Float64}(ntuple(i -> i==1 ? 20 : 10, N)) # [20, 10, ...]
19+
widebbox = BoundingBox(SVector{N, Float64}[min, max], bounds)
20+
21+
min = SVector{N, Float64}(ntuple(i -> i==2 ? -20 : -10, N)) # [-10, -20, ...]
22+
max = SVector{N, Float64}(ntuple(i -> i==2 ? 20 : 10, N)) # [10, 20, ...]
23+
tallbbox = BoundingBox(SVector{N, Float64}[min, max], bounds)
24+
25+
rotation = Matrix(1.0I, N, N)
26+
rotation[1:2, 1:2] = RotMatrix(pi/2.0)
27+
tfm90 = Project(LinearMap(rotation))
28+
@test_nowarn apply(tfm90, tallbbox)
29+
30+
# Rotation by 90 degrees should make the wide bounding box equal to the
31+
# tall bounding box and vice versa.
32+
transformed = apply(tfm90, widebbox)
33+
@test itemdata(transformed) itemdata(tallbbox)
34+
transformed = apply(tfm90, transformed)
35+
@test itemdata(transformed) itemdata(widebbox)
36+
transformed = apply(tfm90, transformed)
37+
@test itemdata(transformed) itemdata(tallbbox)
38+
transformed = apply(tfm90, transformed)
39+
@test itemdata(transformed) itemdata(widebbox)
40+
end

0 commit comments

Comments
 (0)