Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe"
Transducers = "28d57a85-8fef-5791-bfe6-a80928e7c999"
TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"

Expand All @@ -32,6 +33,7 @@ Rotations = "0.13,1"
StaticArrays = "0.12,1"
Statistics = "1"
StatsBase = "0.28,0.29,0.30,0.31,0.32,0.33, 0.34"
TestItems = "1.0.0"
Transducers = "0.4"
TypedTables = "1"
julia = "1.10"
1 change: 1 addition & 0 deletions src/Photometry.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Photometry

using TestItems
using Reexport

include("aperture/Aperture.jl")
Expand Down
266 changes: 266 additions & 0 deletions src/aperture/Aperture.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Aperture

using TypedTables
using Transducers
using TestItems

export photometry,
Subpixel,
Expand Down Expand Up @@ -273,6 +274,271 @@ function photometry(aps::AbstractVector{<:AbstractAperture}, data::AbstractMatri
return Table(rows)
end

@testsnippet photometry begin
using Photometry.Aperture: CircularAperture,
CircularAnnulus,
EllipticalAperture,
EllipticalAnnulus,
RectangularAperture,
RectangularAnnulus,
Subpixel,
photometry
const APERTURES = [
CircularAperture,
CircularAnnulus,
EllipticalAperture,
EllipticalAnnulus,
RectangularAperture,
RectangularAnnulus
]

const PARAMS = [
(3),
(3, 5),
(3, 3, 0),
(3, 5, 4, 0),
(3, 5, 0),
(3, 5, 4, 0)
]

# Some helpers for testing
area(ap::CircularAperture) = π * ap.r^2
area(ap::CircularAnnulus) = π * (ap.r_out^2 - ap.r_in^2)
area(ap::EllipticalAperture) = π * ap.a * ap.b
area(ap::EllipticalAnnulus) = π * ap.a_out * ap.b_out - π * ap.a_in * ap.b_in
area(ap::RectangularAperture) = ap.w * ap.h
area(ap::RectangularAnnulus) = ap.w_out * ap.h_out - ap.w_in * ap.h_in
end

@testitem "aperture/Aperture: outside" setup=[photometry] begin
@testset "outside - $AP" for (AP, params) in zip(APERTURES, PARAMS)
data = ones(10, 10)
aperture = AP(-60, 60, params...)
@test photometry(aperture, data).aperture_sum ≈ 0
end
end

@testitem "aperture/Aperture: inside zeros" setup=[photometry] begin
@testset "inside zeros - $AP" for (AP, params) in zip(APERTURES, PARAMS)
data = zeros(40, 40)
aperture = AP(20.0, 20.0, params...)

table_cent = photometry(Subpixel(aperture), data)
table_sub = photometry(Subpixel(aperture, 10), data)
table_ex = photometry(aperture, data)


@test table_ex.aperture_sum ≈ 0
@test table_sub.aperture_sum ≈ 0
@test table_cent.aperture_sum ≈ 0
end
end

@testitem "aperture/Aperture: inside ones" setup=[photometry] begin
@testset "inside ones - $AP" for (AP, params) in zip(APERTURES, PARAMS)
data = ones(40, 40)
aperture = AP(20.0, 20.0, params...)

table_cent = photometry(Subpixel(aperture), data)
table_sub = photometry(Subpixel(aperture, 10), data)
table_ex = photometry(aperture, data)

true_flux = area(aperture)

@test table_ex.aperture_sum ≈ true_flux
@test table_sub.aperture_sum ≈ table_ex.aperture_sum atol = 0.1
@test table_cent.aperture_sum ≤ table_ex.aperture_sum
end
end

@testitem "aperture/Aperture: interface" setup=[photometry] begin
data = zeros(40, 40)
err = zeros(40, 40)
aperture = CircularAperture(20.0, 20.0, 5.0)

f = maximum
t1 = photometry(aperture, data)
t1_f = photometry(aperture, data; f)
t2 = photometry(aperture, data, err)
t2_f = photometry(aperture, data, err; f)

# 1.0 compat (no hasproperty function)
hasfunc = VERSION < v"1.1" ? haskey : hasproperty

@test !hasfunc(t1, :aperture_sum_err)
@test !hasfunc(t1_f, :aperture_sum_err)
@test t2.aperture_sum_err == 0
@test t2_f.aperture_sum_err == 0
@test propertynames(t1) == (:xcenter, :ycenter, :aperture_sum)
@test propertynames(t1_f) == (:xcenter, :ycenter, :aperture_sum, :aperture_f)
@test propertynames(t2) == (:xcenter, :ycenter, :aperture_sum, :aperture_sum_err)
@test propertynames(t2_f) == (:xcenter, :ycenter, :aperture_sum, :aperture_sum_err, :aperture_f)

apertures = CircularAperture.(20, 20, [1, 2, 3])
t1 = photometry(apertures, data)
t1_f = photometry(apertures, data; f)
t2 = photometry(apertures, data, err)
t2_f = photometry(apertures, data, err; f)

@test !hasfunc(t1, :aperture_sum_err)
@test !hasfunc(t1_f, :aperture_sum_err)
@test t2.aperture_sum_err == zeros(3)
@test t2_f.aperture_sum_err == zeros(3)
@test propertynames(t1) == (:xcenter, :ycenter, :aperture_sum)
@test propertynames(t1_f) == (:xcenter, :ycenter, :aperture_sum, :aperture_f)
@test propertynames(t2) == (:xcenter, :ycenter, :aperture_sum, :aperture_sum_err)
@test propertynames(t2_f) == (:xcenter, :ycenter, :aperture_sum, :aperture_sum_err, :aperture_f)
end

@testitem "aperture/Aperture: type stability" setup=[photometry] begin
@testset "type stability - $AP" for (AP, params) in zip(APERTURES, PARAMS)
data = zeros(40, 40)
err = zeros(40, 40)
aperture = AP(20.0, 20.0, params...)

@inferred photometry(Subpixel(aperture), data)
@inferred photometry(Subpixel(aperture, 10), data)
@inferred photometry(aperture, data)

@inferred photometry(Subpixel(aperture), data, err)
@inferred photometry(Subpixel(aperture, 10), data, err)
@inferred photometry(aperture, data, err)
end
end

@testitem "aperture/Aperture: photometry - circular" setup=[photometry] begin
function test_aperture(data, aperture)
error = ones(size(data))

table_cent = photometry(Subpixel(aperture), data, error)
table_sub = photometry(Subpixel(aperture, 10), data, error)
table_ex = photometry(aperture, data, error)

true_flux = area(aperture)
true_err = sqrt(true_flux)

@test table_ex.aperture_sum ≈ true_flux
@test table_sub.aperture_sum ≈ table_ex.aperture_sum rtol = 1e-3
@test table_cent.aperture_sum < table_ex.aperture_sum

@test table_ex.aperture_sum_err ≈ true_err
@test table_sub.aperture_sum_err ≈ table_ex.aperture_sum_err rtol = 1e-3
@test table_cent.aperture_sum_err < table_ex.aperture_sum_err
end

@testset "errors - CircularAperture" begin
data = ones(40, 40)
aperture = CircularAperture(20, 20, 10)
test_aperture(data, aperture)
end

@testset "errors - CircularAnnulus" begin
data = ones(40, 40)
aperture = CircularAnnulus(20, 20, 8, 10)
test_aperture(data, aperture)
end

@testset "partial overlap" begin
data = ones(20, 20)
error = ones(size(data))
positions = [10.5 10.5; 1 1; 1 20; 20 1; 20 20]
apertures = [CircularAperture(positions[i, :], 5) for i in axes(positions, 1)]

table = photometry(apertures, data, error)
@test table.aperture_sum[1] ≈ 25π
@test all(table.aperture_sum[2:end] .< 25π)
end
end # photometry - circular

@testitem "aperture/Aperture: photometry - elliptical" setup=[photometry] begin
function test_elliptical_aperture(data, aperture)
error = ones(size(data))

table_cent = photometry(Subpixel(aperture), data, error)
table_sub = photometry(Subpixel(aperture, 128), data, error)
table_ex = photometry(aperture, data, error)

true_flux = area(aperture)
true_err = sqrt(true_flux)

@test table_ex.aperture_sum ≈ true_flux
@test table_sub.aperture_sum ≈ true_flux rtol = 1e-3
@test table_cent.aperture_sum <= table_sub.aperture_sum

@test table_ex.aperture_sum_err ≈ true_err
@test table_sub.aperture_sum_err ≈ true_err rtol = 1e-3
@test table_cent.aperture_sum_err <= true_err
end

@testset "errors - EllipticalAperture" begin
data = ones(40, 40)
aperture = EllipticalAperture(20, 20, 10, 10, 0)
test_elliptical_aperture(data, aperture)

end

@testset "errors - EllipticalAnnulus" begin
data = ones(40, 40)
aperture = EllipticalAnnulus(20, 20, 8, 10, 10, 0)
test_elliptical_aperture(data, aperture)
end

@testset "partial overlap elliptical aperture" begin
data = ones(20, 20)
error = ones(size(data))
positions = [10.5 10.5; 1 1; 1 20; 20 1; 20 20]
apertures = [EllipticalAperture(positions[i, :], 5, 5) for i in axes(positions, 1)]

table = photometry(Subpixel.(apertures, 128), data, error)
@test table.aperture_sum[1] ≈ 25π rtol = 1e-3
@test all(table.aperture_sum[2:end] .< 25π)
end
end # photometry - elliptical

@testitem "aperture/Aperture: photometry - rectangular" setup=[photometry] begin
function test_aperture(data, aperture)
error = ones(size(data))

table_cent = photometry(Subpixel(aperture), data, error)
table_sub = photometry(Subpixel(aperture, 10), data, error)
table_ex = photometry(aperture, data, error)

true_flux = area(aperture)
true_err = sqrt(true_flux)

@test table_ex.aperture_sum ≈ true_flux
@test table_sub.aperture_sum ≈ true_flux rtol = 1e-2
@test table_cent.aperture_sum <= table_sub.aperture_sum

@test table_ex.aperture_sum_err ≈ true_err
@test table_sub.aperture_sum_err ≈ true_err rtol = 1e-2
@test table_cent.aperture_sum_err <= true_err
end

@testset "errors - RectangularAperture" begin
data = ones(40, 40)
aperture = RectangularAperture(20, 20, 10, 5, 0)
test_aperture(data, aperture)
end

@testset "errors - RectangularAnnulus" begin
data = ones(40, 40)
aperture = RectangularAnnulus(20, 20, 8, 10, 4, 0)
test_aperture(data, aperture)
end

@testset "partial overlap" begin
data = ones(20, 20)
error = ones(size(data))
positions = [10.5 10.5; 1 1; 1 20; 20 1; 20 20]
apertures = [RectangularAperture(positions[i, :], 10, 10, 0) for i in axes(positions, 1)]

table = photometry(Subpixel.(apertures, 64), data, error)
@test table.aperture_sum[1] ≈ 100 rtol = 1e-2
@test all(table.aperture_sum[2:end] .< 100)
end
end # photometry - circular

include("circular.jl")
include("elliptical.jl")
include("rectangle.jl")
Expand Down
61 changes: 61 additions & 0 deletions src/aperture/circular.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,64 @@ function bounds(c::CircularAnnulus)
ymax = ceil(Int, c.y + c.r_out - 0.5)
return (xmin, xmax, ymin, ymax)
end

@testsnippet circular begin
using Photometry.Aperture: CircularAperture, CircularAnnulus, Subpixel,
bounds, center, photometry
import Random
Random.seed!(8462852)
end

@testitem "aperture/circular: Apertures" setup=[circular] begin
ap_circ = CircularAperture(50, 40, 10)
@test center(ap_circ) == (50, 40)
@test bounds(ap_circ) == (40, 60, 30, 50)
@test size(ap_circ) == map(length, axes(ap_circ)) == (21, 21)
@test size(ap_circ, 1) == 21
@test all(axes(ap_circ) .== (40:60, 30:50))
@test eachindex(ap_circ) == CartesianIndex(40, 30):CartesianIndex(60, 50)

@test CircularAperture([50, 40], 10) == ap_circ

ap_ann = CircularAnnulus(50, 40, 5, 10)
@test center(ap_ann) == (50, 40)
@test bounds(ap_ann) == (40, 60, 30, 50)
@test size(ap_ann) == (21, 21)
@test all(axes(ap_ann) .== (40:60, 30:50))
@test eachindex(ap_ann) == CartesianIndex(40, 30):CartesianIndex(60, 50)

@test CircularAnnulus([50, 40], 5, 10) == ap_ann
end

@testitem "aperture/circular: broadcasting" setup=[circular] begin
ap = CircularAperture(3, 3, 2.5)
data = ones(5, 7)
weighted = ap .* data
@test weighted == data .* ap # commutative
# TODO: Investigate float precision issue in REPL vs. run from script
@test sum(weighted) ≈ sum(ap) == photometry(ap, data).aperture_sum
@test all(iszero, weighted[:, 6:7])
end

@testitem "aperture/circular: CircularAperture" setup=[circular] begin
c0 = CircularAperture(0, 0, 0)

@test sprint(show, c0) == "CircularAperture(0, 0, r=0)"

c1 = CircularAperture(0, 0, 1)

@test sprint(show, c1) == "CircularAperture(0, 0, r=1)"

sub_c1 = Subpixel(c1, 5)
@test sprint(show, sub_c1) == "Subpixel(CircularAperture(0, 0, r=1), 5)"
end

@testitem "aperture/circular: CircularAnnulus" setup=[circular] begin
c0 = CircularAnnulus(0, 0, 0, 0)

@test sprint(show, c0) == "CircularAnnulus(0, 0, r_in=0, r_out=0)"

c1 = CircularAnnulus(0, 0, 0, 1)

@test sprint(show, c1) == "CircularAnnulus(0, 0, r_in=0, r_out=1)"
end
Loading
Loading