diff --git a/Project.toml b/Project.toml index 12c835af..3bcb3a89 100644 --- a/Project.toml +++ b/Project.toml @@ -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" @@ -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" diff --git a/src/Photometry.jl b/src/Photometry.jl index aaec2a9e..065fc638 100644 --- a/src/Photometry.jl +++ b/src/Photometry.jl @@ -1,5 +1,6 @@ module Photometry +using TestItems using Reexport include("aperture/Aperture.jl") diff --git a/src/aperture/Aperture.jl b/src/aperture/Aperture.jl index 1c69fb0b..84a8d099 100644 --- a/src/aperture/Aperture.jl +++ b/src/aperture/Aperture.jl @@ -6,6 +6,7 @@ module Aperture using TypedTables using Transducers +using TestItems export photometry, Subpixel, @@ -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") diff --git a/src/aperture/circular.jl b/src/aperture/circular.jl index 181f61be..a5189c42 100644 --- a/src/aperture/circular.jl +++ b/src/aperture/circular.jl @@ -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 diff --git a/src/aperture/elliptical.jl b/src/aperture/elliptical.jl index e38d55fe..8acb8e94 100644 --- a/src/aperture/elliptical.jl +++ b/src/aperture/elliptical.jl @@ -219,3 +219,55 @@ function partial(sub_ap::Subpixel{T,<:EllipticalAnnulus}, x, y) where T f2 = elliptical_overlap_single_subpixel(x - 0.5, y - 0.5, x + 0.5, y + 0.5, coeffs_in..., sub_ap.N) return f1 - f2 end + +@testsnippet elliptical begin + using Photometry.Aperture: EllipticalAperture, EllipticalAnnulus, + bounds, center, oblique_coefficients, photometry +end + +@testitem "aperture/elliptical: Apertures" setup=[elliptical] begin + ap_ellipse = EllipticalAperture(0, 0, 20, 10, 0) + @test center(ap_ellipse) == (0, 0) + @test bounds(ap_ellipse) == (-20, 20, -10, 10) + @test size(ap_ellipse) == (41, 21) + @test size(ap_ellipse, 1) == 41 + @test all(axes(ap_ellipse) .== (-20:20, -10:10)) + @test eachindex(ap_ellipse) == CartesianIndex(-20, -10):CartesianIndex(20, 10) + + @test EllipticalAperture([0, 0], 20, 10, 0) == ap_ellipse + + ap_ellipse = EllipticalAperture(0, 0, 2, 1, 45) + @test bounds(ap_ellipse) == (-1, 1, -1, 1) +end + +@testitem "aperture/elliptical: Elliptical Aperture" setup=[elliptical] begin + e = EllipticalAperture(0, 0, 20, 10, 0) + @test sprint(show, e) == "EllipticalAperture(0, 0, a=20, b=10, θ=0°)" +end + +@testitem "aperture/elliptical: oblique_coefficients" setup=[elliptical] begin + @test all(oblique_coefficients(2, 2, 0) .≈ (0.25, 0.25, 0.0)) + @test all(oblique_coefficients(2, 2, 90) .≈ (0.25, 0.25, 0.0)) + @test all(oblique_coefficients(2, 1, 30) .≈ (7 / 16, 13 / 16, -6sqrt(3) / 16)) +end + +@testitem "aperture/elliptical: Elliptical Annulus" setup=[elliptical] begin + e0 = EllipticalAnnulus(0, 0, 8, 16, 4, 45) + @test sprint(show, e0) == "EllipticalAnnulus(0.0, 0.0, a_in=8.0, a_out=16.0, b_in=2.0, b_out=4.0, θ=45.0°)" + @test EllipticalAnnulus([0, 0], 8, 16, 4, 45) == EllipticalAnnulus(0, 0, 8, 16, 4, 45) +end + +@testitem "aperture/elliptical: Elliptical Annulus bounding box" setup=[elliptical] begin + e = EllipticalAnnulus(0, 0, 8, 16, 4, 0) + @test center(e) == (0, 0) + @test bounds(e) == (-16, 16, -4, 4) + @test size(e) == (33, 9) + @test all(axes(e) .== (-16:16, -4:4)) + @test eachindex(e) == CartesianIndex(-16, -4):CartesianIndex(16, 4) +end + +@testitem "aperture/elliptical: regression circular ellipse" setup=[elliptical] begin + # some weird bug where centered on-grid past a certain size (and rotated) would fail + e = EllipticalAperture(5.5, 5.5, 4, 4, 20) + @test sum(e) ≈ photometry(e, ones(9, 9)).aperture_sum # just a test that no errors occur +end diff --git a/src/aperture/overlap.jl b/src/aperture/overlap.jl index eb6b3c29..cfd7dcc4 100644 --- a/src/aperture/overlap.jl +++ b/src/aperture/overlap.jl @@ -426,3 +426,189 @@ function intersection_area(X::LazySet, Y::LazySet) vY = convert(VPolygon, Y) return area(intersection(vX, vY)) end + +@testsnippet overlap begin + using Photometry.Aperture: CircularAperture, + EllipticalAperture, + Subpixel, + circular_overlap_core, + circular_overlap_single_exact, + circular_overlap_single_subpixel, + area_arc, + area_triangle, + inside_ellipse, + inside_triangle, + inside_rectangle, + circle_line, + circle_segment, + circle_segment_single2, + triangle_unitcircle_overlap, + elliptical_overlap_exact, + elliptical_overlap_single_subpixel, + rectangular_overlap_exact, + rectangular_overlap_single_subpixel + + const DATA_DIR = joinpath("..", "..", "test", "data") +end + +@testitem "aperture/overlap: overlap - circular" setup=[overlap] begin + @testset "circular overlap" for r in 0:10:100, use_subpixel in [true, false] + + ap = CircularAperture(0, 0, r) + if use_subpixel + ap = Subpixel(ap, 10) + end + @test all(p -> 0 ≤ p ≤ 1, ap) + end + + @testset "overlap core (r = $r)" for r in 1:10 + @test circular_overlap_core(0, 0, 0, 0, r) ≈ 0 + @test circular_overlap_single_exact(0, 0, r, r, r) ≈ r^2 * π / 4 + @test circular_overlap_core(0, 0, r, r, r) ≈ r^2 * π / 4 + end + + @testset "overlap single exact" begin + @test circular_overlap_single_exact(0, 0, 0, 0, 1) ≈ 0 + @test circular_overlap_single_exact(0, 0, 20, 20, 15) ≈ 176.714586764429 + end + + @testset "overlap subpixel" begin + @test circular_overlap_single_subpixel(0, 0, 0, 0, 1, 1) ≈ 1 + + @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 2) ≈ 0.25 + @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 5) ≈ 0.44 + @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 10) ≈ 0.43 + @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 100) ≈ 0.4423 + end + + @testset "type stability" begin + r = 5 + @inferred circular_overlap_core(0, 0, r, r, r) + @inferred circular_overlap_single_exact(0, 0, r, r, r) + @inferred circular_overlap_single_subpixel(0, 0, 20, 20, 15, 5) + end +end # circles + +@testitem "aperture/overlap: overlap - elliptical" setup=[overlap] begin + @testset "elliptical overlap" for r in 0:10:100, use_subpixel in [true, false] + ap = EllipticalAperture(0, 0, r, r) + if use_subpixel + ap = Subpixel(ap, 10) + end + @test all(p -> 0 ≤ p ≤ 1, ap) + end + + @testset "overlap subpixel (elliptical apperture)" begin + @test elliptical_overlap_single_subpixel(0, 0, 0, 0, 1, 1, 0, 1) ≈ 1 + + @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 2) ≈ 0.25 + @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 5) ≈ 0.44 + @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 10) ≈ 0.43 + @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 100) ≈ 0.4423 + end + + @testset "circle line" begin + for line in readlines(joinpath(DATA_DIR, "circle_line.csv")) + tokens = split(line, ',') + numbers = parse.(Float64, tokens) + x1, y1, x2, y2 = numbers[1:4] + point1, point2 = circle_line(x1, y1, x2, y2) + @test point1[1] ≈ numbers[5] atol = 1e-6 + @test point1[2] ≈ numbers[6] atol = 1e-6 + @test point2[1] ≈ numbers[7] atol = 1e-6 + @test point2[2] ≈ numbers[8] atol = 1e-6 + end + end + + @testset "circle segment" begin + for line in readlines(joinpath(DATA_DIR, "circle_segment.csv")) + tokens = split(line, ',') + numbers = parse.(Float64, tokens) + x1, y1, x2, y2 = numbers[1:4] + point1, point2 = circle_segment(x1, y1, x2, y2) + @test point1[1] ≈ numbers[5] atol = 1e-6 + @test point1[2] ≈ numbers[6] atol = 1e-6 + @test point2[1] ≈ numbers[7] atol = 1e-6 + @test point2[2] ≈ numbers[8] atol = 1e-6 + end + end + + @testset "circle segment single" begin + for line in readlines(joinpath(DATA_DIR, "circle_segment_single.csv")) + tokens = split(line, ',') + numbers = parse.(Float64, tokens) + x1, y1, x2, y2 = numbers[1:4] + point1 = circle_segment_single2(x1, y1, x2, y2) + @test point1[1] ≈ numbers[5] atol = 1e-6 + @test point1[2] ≈ numbers[6] atol = 1e-6 + end + end + + @testset "triangle unitcircle overlap" begin + for line in readlines(joinpath(DATA_DIR, "triangle_unitcircle_overlap.csv")) + tokens = split(line, ',') + numbers = parse.(Float64, tokens) + x1, y1, x2, y2, x3, y3 = numbers[1:6] + @test triangle_unitcircle_overlap(x1, y1, x2, y2, x3, y3) ≈ numbers[end] atol = 1e-6 + end + end + + @testset "elliptical overlap" begin + @test elliptical_overlap_exact(0.5, 2.5, 1.5, 3.5, 3.0, 3.0, 0) ≈ 0.311725 atol = 1e-6 + @test elliptical_overlap_exact(0, 2, 1, 3, 3.0, 3.0, 0) ≈ 0.943480 atol = 1e-6 + end +end # overlap elliptical + +@testitem "aperture/overlap: overlap - rectangular" setup=[overlap] begin + @testset "exact" begin + @test rectangular_overlap_exact(0, 0, 1, 1, 1, 1, 0) ≈ 0.25 + end + + @testset "type stability" begin + @inferred rectangular_overlap_exact(0, 0, 1, 1, 2, 2, 0) + @inferred rectangular_overlap_single_subpixel(0, 0, 1, 1, 2, 2, 0, 5) + end +end # overlap rectangular + +@testitem "aperture/overlap: overlap - utils" setup=[overlap] begin + @testset "inside triangle" begin + t1 = [0, 0, 0, 1, 1, 0] + @test inside_triangle(0, 0, t1...) + @test inside_triangle(0.1, 0.1, t1...) + @test inside_triangle(0.25, 0.25, t1...) + end + + + @testset "area triangle" begin + @test area_triangle(0, 0, 0, 0, 0, 0) ≈ 0 + @test area_triangle(0, 0, 1, 0, 0, 1) ≈ 0.5 + @test area_triangle(1, 1, 1, 4, 6, 1) ≈ 7.5 + + end + + @testset "area arc (r = $r)" for r in 1:10 + @test area_arc(0, 0, 0, 0, r) ≈ 0 + @test area_arc(0, r, r, 0, r) ≈ r^2 / 2 * (π / 2 - 1) + end + + @testset "inside ellipse" begin + @test inside_ellipse(0, 0, 0, 0, 1, 1, 1) + @test !inside_ellipse(10, 10, 5, 5, 1, 1, 1) + @test !inside_ellipse(5, 3, 0, 0, 1 / 16, 1 / 32, 0) + @test inside_ellipse(0, 0, 0, 0, 5, 6.2, 0) + @test inside_ellipse(1, 2, 0, 0, 1 / 37, 1 / 36, -1 / 80) + end + + @testset "inside rectangle" begin + @test inside_rectangle(0, 0, 3, 4, 0) + @test !inside_rectangle(10, 10, 3, 4, 0) + end + + @testset "type stability" begin + @inferred area_arc(0, 0, 0, 0, 10) + @inferred area_triangle(0, 0, 1, 0, 0, 1) + @inferred inside_triangle(0, 0, 0, 0, 0, 1, 1, 0) + @inferred inside_ellipse(0, 0, 5, 5, 1, 1, 1) + @inferred inside_rectangle(0, 0, 3, 4, 0) + end +end # overlap utils diff --git a/src/aperture/plotting.jl b/src/aperture/plotting.jl index 160a4cee..d6660370 100644 --- a/src/aperture/plotting.jl +++ b/src/aperture/plotting.jl @@ -141,3 +141,51 @@ end @series begin ap end end end + +@testsnippet plotting begin + using Photometry.Aperture: CircularAperture, + CircularAnnulus, + EllipticalAperture, + EllipticalAnnulus, + RectangularAperture, + RectangularAnnulus + using RecipesBase: apply_recipe + using Statistics + + const APERTURES = [ + CircularAperture(3, 3, 3), + CircularAnnulus(3, 3, 2, 4), + EllipticalAperture(3, 3, 4, 2, 45), + EllipticalAnnulus(3, 3, 3, 4, 2, -26), + RectangularAperture(3, 3, 3, 4, 15), + RectangularAnnulus(3, 3, 3, 4, 2, -5) + ] +end + +@testitem "aperture/plotting: Aperture Plots" setup=[plotting] begin + @testset "Aperture Plots - $(typeof(ap))" for ap in APERTURES + recipes = apply_recipe(Dict{Symbol,Any}(), ap) + for rec in recipes + @test getfield(rec, 1) == Dict{Symbol,Any}( + :seriescolor => :red, + :fillcolor => nothing, + :linecolor => :match, + :label => "", + :seriestype => :shape, + :aspect_ratio => :equal) + + # test to make sure our position is correct (should be +0.5 the given (x,y)) + points = rec.args[1] + x_tot = y_tot = 0 + for point in points + x_tot += point[1] + y_tot += point[2] + end + N = length(points) + x_mean = x_tot / N + y_mean = y_tot / N + @test x_mean ≈ 3.5 atol = x_mean / sqrt(N) + @test y_mean ≈ 3.5 atol = y_mean / sqrt(N) + end + end +end diff --git a/src/aperture/rectangle.jl b/src/aperture/rectangle.jl index 640841fc..13718edc 100644 --- a/src/aperture/rectangle.jl +++ b/src/aperture/rectangle.jl @@ -207,3 +207,36 @@ function partial(sub_ap::Subpixel{T,<:RectangularAnnulus}, x, y) where T ap.w_in, ap.h_in, ap.theta, sub_ap.N) return f1 - f2 end + +@testsnippet rectangular begin + using Photometry.Aperture: RectangularAperture, RectangularAnnulus, + bounds, center +end + +@testitem "aperture/rectangular: Apertures" setup=[rectangular] begin + ap_rect = RectangularAperture(50, 40, 10, 10, 0) + @test center(ap_rect) == (50, 40) + @test bounds(ap_rect) == (45, 55, 35, 45) + @test size(ap_rect) == (11, 11) + @test size(ap_rect, 1) == 11 + @test RectangularAperture([50, 40], 10, 10, 0) == ap_rect + + ap_ann = RectangularAnnulus(50, 40, 5, 10, 10, 0) + @test bounds(ap_ann) == (45, 55, 35, 45) + @test size(ap_ann) == (11, 11) + @test RectangularAnnulus([50, 40], 5, 10, 10, 0) == ap_ann +end + +@testitem "aperture/rectangular: Rectangular Aperture" setup=[rectangular] begin + ap0 = RectangularAperture(0, 0, 0, 0, 0) + @test sprint(show, ap0) == "RectangularAperture(0, 0, w=0, h=0, θ=0°)" + + ap1 = RectangularAperture(0, 0, 1, 1, 0) + @test sprint(show, ap1) == "RectangularAperture(0, 0, w=1, h=1, θ=0°)" +end + +@testitem "aperture/rectangular: Rectangular Annulus" setup=[rectangular] begin + ap1 = RectangularAnnulus(0, 0, 1, 1, 1, 0) + @test center(ap1) == (0, 0) + @test sprint(show, ap1) == "RectangularAnnulus(0.0, 0.0, w_in=1.0, w_out=1.0, h_in=1.0, h_out=1.0, θ=0.0°)" +end diff --git a/src/background/Background.jl b/src/background/Background.jl index f395ec4b..38be2a75 100644 --- a/src/background/Background.jl +++ b/src/background/Background.jl @@ -2,6 +2,7 @@ module Background using Statistics using ImageFiltering: padarray, Fill, mapwindow +using TestItems export estimate_background, sigma_clip, @@ -294,4 +295,88 @@ function sigma_clip( return sigma_clip!(float(copy(x)), sigma_low, sigma_high; fill = fill, center = center, std = std) end +@testsnippet background begin + import Photometry.Background: MMMBackground, BiweightLocationBackground, SourceExtractorBackground, + StdRMS, MADStdRMS, BiweightScaleRMS, + ZoomInterpolator, + sigma_clip, estimate_background + import StatsBase: median, mean + import Random + Random.seed!(8462852) +end + +@testitem "background/Background: sigma clipping" setup=[background] begin + x = [1, 2, 3] + @test sigma_clip(x, 1, 1) ≈ [1.0, 2.0, 3.0] rtol = 1e-4 + @test sigma_clip(x, 1) == sigma_clip(x, 1, 1) + + y = [1, 2, 3, 4, 5, 6] + @test sigma_clip(y, 1, 1) ≈ [1.62917, 2.0, 3.0, 4.0, 5.0, 5.37083] rtol = 1e-4 + + # using different center + @test sigma_clip(y, 1, center = 4) ≈ [2.1291713066130296, 2.1291713066130296, 3, 4, 5, 5.87082869338697] + # using different std + @test sigma_clip(y, 1, std = 1) ≈ [2.5, 2.5, 3, 4, 4.5, 4.5] + + @test sum(sigma_clip(y, 1, fill = NaN) .=== NaN) == 2 + @test sum(sigma_clip(y, 1, fill = NaN) .=== NaN) == 2 + + # `sigma_clip` should not mutate + z1 = [0.1, 0.2, 3.0, 4.0, 0.2, 0.1] + z2 = copy(z1) + sigma_clip(z1, 1) + @test z1 == z2 +end + +@testitem "background/Background: estimate_background interface" setup=[background] begin + data = ones(100, 100) + + @test all(estimate_background(data, 20) .== estimate_background(data, (20, 20))) + @test all(estimate_background(data, 20, filter_size = 3) .== estimate_background(data, (20, 20), filter_size = (3, 3))) + @test size(estimate_background(data, 19, edge_method = :pad)[1]) == (114, 114) + @test size(estimate_background(data, 19, edge_method = :crop)[1]) == (95, 95) + X = rand(100, 100) + @test estimate_background(data, location = median, rms = mean) == estimate_background(data, location = mean, rms = median)[[2, 1]] + @test_throws MethodError estimate_background(data, (4, 4), edge_method = :yeet) + @test_throws MethodError estimate_background(data, (4, 4, 4)) + @test_throws ErrorException estimate_background(data, 4, filter_size = 2) + + nan_bkg, nan_rms = estimate_background(fill(NaN, 100, 100), 20) + @test nan_bkg ≈ zeros(100, 100) + @test nan_rms ≈ zeros(100, 100) +end + +@testitem "background/Background: flat background" setup=[background] begin + @testset "flat background - $B, $S" for B in [median, mean, MMMBackground(), BiweightLocationBackground(), SourceExtractorBackground()], S in [StdRMS(), MADStdRMS(), BiweightScaleRMS()] + data = ones(100, 100) + + bk, rms = estimate_background(data, location = B, rms = S) + @test bk ≈ 1 + @test rms ≈ 0 + + bk, rms = estimate_background(data, (25, 25), location = B, rms = S) + @test median(bk) ≈ 1 + @test median(rms) ≈ 0 + end +end + +@testitem "background/Background: regression" setup=[background] begin + # regression test for failing to recognize NaN32 (or NaN16) + data = ones(Float32, 100, 100) + bk, rms = estimate_background(data, 3, edge_method = :pad) + @test !any(isnan.(bk)) + @test !any(isnan.(rms)) +end + +@testitem "background/Background: interpolators" setup=[background] begin + @testset "zoom interface" begin + @test ZoomInterpolator(3) == ZoomInterpolator(3, 3) == ZoomInterpolator((3, 3)) + end + + @testset "trivial ones" begin + z = ZoomInterpolator(4, 3) + @test z(ones(3, 3)) ≈ ones(12, 9) + end +end + end # Background diff --git a/src/background/estimators.jl b/src/background/estimators.jl index dc605436..50dbf2e7 100644 --- a/src/background/estimators.jl +++ b/src/background/estimators.jl @@ -252,3 +252,81 @@ end _biweight_scale(x::AbstractArray, c, M, ::Colon) = biweight_scale(x, c, M) _biweight_scale(x::AbstractArray, c, M, dims) = mapslices(x->biweight_scale(x, c, M), x, dims=dims) (alg::BiweightScaleRMS)(data; dims = :) = _biweight_scale(data, alg.c, alg.M, dims) + +@testsnippet estimators begin + using Photometry.Background: MMMBackground, SourceExtractorBackground, BiweightLocationBackground, + StdRMS, MADStdRMS, BiweightScaleRMS + using StatsBase: mad, median, std +end + +@testitem "background/estimators: Trivial estimator" setup=[estimators] begin + @testset "Trivial $E" for E in [MMMBackground, SourceExtractorBackground, BiweightLocationBackground] + estimator = E() + + @test estimator(ones(1)) == 1 + + data = ones(10, 10) + + @test estimator(data) ≈ 1.0 + @test estimator(data, dims = 1) ≈ ones(1, 10) + @test estimator(data, dims = 2) ≈ ones(10) + + data = zeros(10, 10) + + @test estimator(data) ≈ 0.0 + @test estimator(data, dims = 1) ≈ zeros(1, 10) + @test estimator(data, dims = 2) ≈ zeros(10) + + data = randn(100, 100) + end +end + +@testitem "background/estimators: SourceExtractorBackground" setup=[estimators] begin + # test skewed distribution + data = float(collect(1:100)) + data[71:end] .= 1e7 + + @test SourceExtractorBackground()(data) ≈ median(data) +end + +@testitem "background/estimators: BiweightLocationBackground" setup=[estimators] begin + b = BiweightLocationBackground() + @test b([1, 3, 5, 500, 2]) ≈ 2.745 atol = 1e-3 +end + +############################################################################### +# RMS Estimators + +@testitem "background/estimators: Trivial RMS estimator" setup=[estimators] begin + @testset "Trivial $E" for E in [StdRMS, MADStdRMS, BiweightScaleRMS] + estimator = E() + + @test estimator(ones(1)) == 0 + + data = ones(10, 10) + @test estimator(data) ≈ 0.0 + @test estimator(data, dims = 1) ≈ zeros(1, 10) + @test estimator(data, dims = 2) ≈ zeros(10) + + + data = randn(10000, 10000) + @test estimator(data) ≈ 1 atol = 3e-2 + end +end + +@testitem "background/estimators: StdRMS" setup=[estimators] begin + s = StdRMS() + data = randn(100) + @test s(data) == std(data, corrected = false) +end + +@testitem "background/estimators: MADStdRMS" setup=[estimators] begin + s = MADStdRMS() + data = randn(100) + @test s(data) == mad(data, normalize = true) +end + +@testitem "background/estimators: BiweightScaleRMS" setup=[estimators] begin + s = BiweightScaleRMS() + @test s([1, 3, 5, 500, 2]) ≈ 1.70992562072 +end diff --git a/src/background/interpolators.jl b/src/background/interpolators.jl index 92c57d8d..ba33090e 100644 --- a/src/background/interpolators.jl +++ b/src/background/interpolators.jl @@ -173,3 +173,41 @@ function (itp::ShepardIDWInterpolator{T})(points...) where T return num / den end + +@testsnippet interpolators begin + import Photometry.Background: IDWInterpolator, ShepardIDWInterpolator + import Random + Random.seed!(8462852) +end + +@testitem "background/interpolators: IDWInterpolator" setup=[interpolators] begin + @test IDWInterpolator((2, 3), k = 4)(ones(3, 2)) == ones(6, 6) + @test IDWInterpolator(2)(ones(3, 4)) == IDWInterpolator((2, 2))(ones(3, 4)) + @test_throws DomainError IDWInterpolator(2)(ones(3, 2)) + + # interface + @test IDWInterpolator((2, 3)) == IDWInterpolator(2, 3) + @test IDWInterpolator((2, 3), k = 4) == IDWInterpolator(2, 3, k = 4) +end + +@testitem "background/interpolators: ShepardIDWInterpolator" setup=[interpolators] begin + # 1D + x = rand(100) + y = sin.(x) + it = ShepardIDWInterpolator(x', y) + @test it(0.4) ≈ sin(0.4) atol = 1e-2 + @test it.(0.2:0.1:0.5) ≈ sin.(0.2:0.1:0.5) atol = 1e-2 + + # 2D + x = rand(2, 10000) + y = sin.(x[1, :] .+ x[2, :]) + it = ShepardIDWInterpolator(x, y) + @test it(0.5, 0.6) ≈ sin(0.5 + 0.6) atol = 1e-2 + + # interface + + #= Warning! These are not accurate for use as a standard interpolator, + but are what we need for our use with images =# + @test size(it) == (10000,) + @test axes(it) == axes(y) +end diff --git a/src/detection/Detection.jl b/src/detection/Detection.jl index a233f74f..a721a027 100644 --- a/src/detection/Detection.jl +++ b/src/detection/Detection.jl @@ -3,6 +3,7 @@ module Detection using Parameters using ImageFiltering using TypedTables +using TestItems export PeakMesh, extract_sources @@ -65,4 +66,39 @@ function extract_sources(alg::PeakMesh, data::AbstractMatrix{T}, error = zeros(T return Table(rows) end +@testsnippet detection begin + using Photometry.Detection: PeakMesh, extract_sources + import Random + Random.seed!(8462852) +end + +@testitem "detection/Detection: peak finding" setup=[detection] begin + @testset "peak finding - $P" for P in [PeakMesh()] + data = randn(100, 100) + idxs = [1, 700, 1524] + fake_peaks = randn(length(idxs)) .+ 10 + data[idxs] .= fake_peaks + + table = extract_sources(P, data) + @test table.value[1:3] == sort(fake_peaks, rev = true) + end +end + +@testitem "detection/Detection: Peak Mesh" setup=[detection] begin + @test PeakMesh(box_size = 3) == PeakMesh(box_size = (3, 3)) +end + +@testitem "detection/Detection: interface" setup=[detection] begin + @testset "interface - $P" for P in [PeakMesh()] + data = randn(100, 100) + idxs = [1, 700, 1524] + fake_peaks = randn(length(idxs)) .+ 10 + data[idxs] .= fake_peaks + + table = extract_sources(P, data) + table2 = extract_sources(P, data, zeros(100, 100)) + @test table == table2 + end +end + end # module Detection diff --git a/test/Project.toml b/test/Project.toml index f9dd5e9c..dd4c03bd 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,11 @@ [deps] +Photometry = "af68cb61-81ac-52ed-8703-edc140936be4" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" + +[source] +Photometry = {path = ".."} diff --git a/test/aperture/circular.jl b/test/aperture/circular.jl deleted file mode 100644 index e84218ab..00000000 --- a/test/aperture/circular.jl +++ /dev/null @@ -1,56 +0,0 @@ -using Photometry.Aperture: bounds, center - -@testset "Apertures" 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 - -@testset "broadcasting" begin - ap = CircularAperture(3, 3, 2.5) - data = ones(5, 7) - weighted = ap .* data - @test weighted == data .* ap # commutative - @test sum(weighted) == sum(ap) == photometry(ap, data).aperture_sum - @test maximum(weighted) == maximum(ap) == photometry(ap, data; f = maximum).aperture_f - @test all(iszero, weighted[:, 6:7]) - -end - -@testset "Circle Aperture" 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 - -@testset "Circle Annulus" 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 diff --git a/test/aperture/elliptical.jl b/test/aperture/elliptical.jl deleted file mode 100644 index 51bb89e9..00000000 --- a/test/aperture/elliptical.jl +++ /dev/null @@ -1,48 +0,0 @@ -using Photometry.Aperture: bounds, center, oblique_coefficients - -@testset "Apertures" begin - ap_ellipse = EllipticalAperture(0, 0, 20, 10, 0) - @test center(ap_ellipse) == (0, 0) - @test bounds(ap_ellipse) == (-20, 20, -10, 10) - @test size(ap_ellipse) == (41, 21) - @test size(ap_ellipse, 1) == 41 - @test all(axes(ap_ellipse) .== (-20:20, -10:10)) - @test eachindex(ap_ellipse) == CartesianIndex(-20, -10):CartesianIndex(20, 10) - - @test EllipticalAperture([0, 0], 20, 10, 0) == ap_ellipse - - ap_ellipse = EllipticalAperture(0, 0, 2, 1, 45) - @test bounds(ap_ellipse) == (-1, 1, -1, 1) -end - -@testset "Elliptical Aperture" begin - e = EllipticalAperture(0, 0, 20, 10, 0) - @test sprint(show, e) == "EllipticalAperture(0, 0, a=20, b=10, θ=0°)" -end - -@testset "oblique_coefficients" begin - @test all(oblique_coefficients(2, 2, 0) .≈ (0.25, 0.25, 0.0)) - @test all(oblique_coefficients(2, 2, 90) .≈ (0.25, 0.25, 0.0)) - @test all(oblique_coefficients(2, 1, 30) .≈ (7 / 16, 13 / 16, -6sqrt(3) / 16)) -end - -@testset "Elliptical Annulus" begin - e0 = EllipticalAnnulus(0, 0, 8, 16, 4, 45) - @test sprint(show, e0) == "EllipticalAnnulus(0.0, 0.0, a_in=8.0, a_out=16.0, b_in=2.0, b_out=4.0, θ=45.0°)" - @test EllipticalAnnulus([0, 0], 8, 16, 4, 45) == EllipticalAnnulus(0, 0, 8, 16, 4, 45) -end - -@testset "Elliptical Annulus bounding box" begin - e = EllipticalAnnulus(0, 0, 8, 16, 4, 0) - @test center(e) == (0, 0) - @test bounds(e) == (-16, 16, -4, 4) - @test size(e) == (33, 9) - @test all(axes(e) .== (-16:16, -4:4)) - @test eachindex(e) == CartesianIndex(-16, -4):CartesianIndex(16, 4) -end - -@testset "regression circular ellipse" begin - # some weird bug where centered on-grid past a certain size (and rotated) would fail - e = EllipticalAperture(5.5, 5.5, 4, 4, 20) - @test sum(e) ≈ photometry(e, ones(9, 9)).aperture_sum # just a test that no errors occur -end diff --git a/test/aperture/overlap.jl b/test/aperture/overlap.jl deleted file mode 100644 index 0f06ff3a..00000000 --- a/test/aperture/overlap.jl +++ /dev/null @@ -1,182 +0,0 @@ -using Photometry.Aperture: circular_overlap_core, - circular_overlap_single_exact, - circular_overlap_single_subpixel, - area_arc, - area_triangle, - inside_ellipse, - inside_triangle, - inside_rectangle, - circle_line, - circle_segment, - circle_segment_single2, - triangle_unitcircle_overlap, - elliptical_overlap_exact, - elliptical_overlap_single_subpixel, - rectangular_overlap_exact, - rectangular_overlap_single_subpixel - -@testset "overlap - circular" begin - - @testset "circular overlap" for r in 0:10:100, use_subpixel in [true, false] - - ap = CircularAperture(0, 0, r) - if use_subpixel - ap = Subpixel(ap, 10) - end - @test all(p -> 0 ≤ p ≤ 1, ap) - end - - @testset "overlap core (r = $r)" for r in 1:10 - @test circular_overlap_core(0, 0, 0, 0, r) ≈ 0 - @test circular_overlap_single_exact(0, 0, r, r, r) ≈ r^2 * π / 4 - @test circular_overlap_core(0, 0, r, r, r) ≈ r^2 * π / 4 - end - - @testset "overlap single exact" begin - @test circular_overlap_single_exact(0, 0, 0, 0, 1) ≈ 0 - @test circular_overlap_single_exact(0, 0, 20, 20, 15) ≈ 176.714586764429 - end - - @testset "overlap subpixel" begin - @test circular_overlap_single_subpixel(0, 0, 0, 0, 1, 1) ≈ 1 - - @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 2) ≈ 0.25 - @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 5) ≈ 0.44 - @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 10) ≈ 0.43 - @test circular_overlap_single_subpixel(0, 0, 20, 20, 15, 100) ≈ 0.4423 - end - - @testset "type stability" begin - r = 5 - @inferred circular_overlap_core(0, 0, r, r, r) - @inferred circular_overlap_single_exact(0, 0, r, r, r) - @inferred circular_overlap_single_subpixel(0, 0, 20, 20, 15, 5) - end - -end # circles - -@testset "overlap - elliptical" begin - - @testset "elliptical overlap" for r in 0:10:100, use_subpixel in [true, false] - ap = EllipticalAperture(0, 0, r, r) - if use_subpixel - ap = Subpixel(ap, 10) - end - @test all(p -> 0 ≤ p ≤ 1, ap) - end - - @testset "overlap subpixel (elliptical apperture)" begin - @test elliptical_overlap_single_subpixel(0, 0, 0, 0, 1, 1, 0, 1) ≈ 1 - - @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 2) ≈ 0.25 - @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 5) ≈ 0.44 - @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 10) ≈ 0.43 - @test elliptical_overlap_single_subpixel(0, 0, 20, 20, 1 / 225, 1 / 225, 0, 100) ≈ 0.4423 - end - - @testset "circle line" begin - for line in readlines(joinpath(@__DIR__, "..", "data", "circle_line.csv")) - tokens = split(line, ',') - numbers = parse.(Float64, tokens) - x1, y1, x2, y2 = numbers[1:4] - point1, point2 = circle_line(x1, y1, x2, y2) - @test point1[1] ≈ numbers[5] atol = 1e-6 - @test point1[2] ≈ numbers[6] atol = 1e-6 - @test point2[1] ≈ numbers[7] atol = 1e-6 - @test point2[2] ≈ numbers[8] atol = 1e-6 - end - end - - @testset "circle segment" begin - for line in readlines(joinpath(@__DIR__, "..", "data", "circle_segment.csv")) - tokens = split(line, ',') - numbers = parse.(Float64, tokens) - x1, y1, x2, y2 = numbers[1:4] - point1, point2 = circle_segment(x1, y1, x2, y2) - @test point1[1] ≈ numbers[5] atol = 1e-6 - @test point1[2] ≈ numbers[6] atol = 1e-6 - @test point2[1] ≈ numbers[7] atol = 1e-6 - @test point2[2] ≈ numbers[8] atol = 1e-6 - end - end - - @testset "circle segment single" begin - for line in readlines(joinpath(@__DIR__, "..", "data", "circle_segment_single.csv")) - tokens = split(line, ',') - numbers = parse.(Float64, tokens) - x1, y1, x2, y2 = numbers[1:4] - point1 = circle_segment_single2(x1, y1, x2, y2) - @test point1[1] ≈ numbers[5] atol = 1e-6 - @test point1[2] ≈ numbers[6] atol = 1e-6 - end - end - - @testset "triangle unitcircle overlap" begin - for line in readlines(joinpath(@__DIR__, "..", "data", "triangle_unitcircle_overlap.csv")) - tokens = split(line, ',') - numbers = parse.(Float64, tokens) - x1, y1, x2, y2, x3, y3 = numbers[1:6] - @test triangle_unitcircle_overlap(x1, y1, x2, y2, x3, y3) ≈ numbers[end] atol = 1e-6 - end - end - @testset "elliptical overlap" begin - @test elliptical_overlap_exact(0.5, 2.5, 1.5, 3.5, 3.0, 3.0, 0) ≈ 0.311725 atol = 1e-6 - @test elliptical_overlap_exact(0, 2, 1, 3, 3.0, 3.0, 0) ≈ 0.943480 atol = 1e-6 - end -end # overlap elliptical - -@testset "overlap - rectangular" begin - @testset "exact" begin - @test rectangular_overlap_exact(0, 0, 1, 1, 1, 1, 0) ≈ 0.25 - end - - @testset "type stability" begin - @inferred rectangular_overlap_exact(0, 0, 1, 1, 2, 2, 0) - @inferred rectangular_overlap_single_subpixel(0, 0, 1, 1, 2, 2, 0, 5) - end -end # overlap rectangular - -@testset "overlap - utils" begin - - @testset "inside triangle" begin - t1 = [0, 0, 0, 1, 1, 0] - @test inside_triangle(0, 0, t1...) - @test inside_triangle(0.1, 0.1, t1...) - @test inside_triangle(0.25, 0.25, t1...) - end - - - @testset "area triangle" begin - @test area_triangle(0, 0, 0, 0, 0, 0) ≈ 0 - @test area_triangle(0, 0, 1, 0, 0, 1) ≈ 0.5 - @test area_triangle(1, 1, 1, 4, 6, 1) ≈ 7.5 - - end - - @testset "area arc (r = $r)" for r in 1:10 - @test area_arc(0, 0, 0, 0, r) ≈ 0 - @test area_arc(0, r, r, 0, r) ≈ r^2 / 2 * (π / 2 - 1) - end - - @testset "inside ellipse" begin - @test inside_ellipse(0, 0, 0, 0, 1, 1, 1) - @test !inside_ellipse(10, 10, 5, 5, 1, 1, 1) - @test !inside_ellipse(5, 3, 0, 0, 1 / 16, 1 / 32, 0) - @test inside_ellipse(0, 0, 0, 0, 5, 6.2, 0) - @test inside_ellipse(1, 2, 0, 0, 1 / 37, 1 / 36, -1 / 80) - end - - @testset "inside rectangle" begin - @test inside_rectangle(0, 0, 3, 4, 0) - @test !inside_rectangle(10, 10, 3, 4, 0) - end - - @testset "type stability" begin - @inferred area_arc(0, 0, 0, 0, 10) - @inferred area_triangle(0, 0, 1, 0, 0, 1) - @inferred inside_triangle(0, 0, 0, 0, 0, 1, 1, 0) - @inferred inside_ellipse(0, 0, 5, 5, 1, 1, 1) - @inferred inside_rectangle(0, 0, 3, 4, 0) - end - -end # overlap utils diff --git a/test/aperture/photometry.jl b/test/aperture/photometry.jl deleted file mode 100644 index c5d01253..00000000 --- a/test/aperture/photometry.jl +++ /dev/null @@ -1,283 +0,0 @@ -APERTURES = [ - CircularAperture, - CircularAnnulus, - EllipticalAperture, - EllipticalAnnulus, - RectangularAperture, - RectangularAnnulus -] - -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 - -@testset "outside - $AP" for (AP, params) in zip(APERTURES, PARAMS) - data = ones(10, 10) - err = zeros(10, 10) - aperture = AP(-60, 60, params...) - t = photometry(aperture, data) - t_err = photometry(aperture, data, err) - t_f = photometry(aperture, data; f = maximum) - t_f_err = photometry(aperture, data, err; f = maximum) - - # TODO: Only return aperture_sum_err when err is passed - @test t.aperture_sum ≈ 0 - @test propertynames(t) == (:xcenter, :ycenter, :aperture_sum) - - @test t_err.aperture_sum ≈ 0 - @test isnan(t_err.aperture_sum_err) - @test propertynames(t_err) == (:xcenter, :ycenter, :aperture_sum, :aperture_sum_err) - - @test t_f.aperture_f ≈ 0 - @test propertynames(t_f) == (:xcenter, :ycenter, :aperture_sum, :aperture_f) - - @test t_f_err.aperture_f ≈ 0 - @test isnan(t_f_err.aperture_sum_err) - @test propertynames(t_f_err) == (:xcenter, :ycenter, :aperture_sum, :aperture_sum_err, :aperture_f) -end - -@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) - - f = maximum - table_cent_f = photometry(Subpixel(aperture), data; f) - table_sub_f = photometry(Subpixel(aperture, 10), data; f) - table_ex_f = photometry(aperture, data; f) - - @test table_ex.aperture_sum ≈ 0 - @test table_sub.aperture_sum ≈ 0 - @test table_cent.aperture_sum ≈ 0 - - @test table_ex_f.aperture_sum ≈ 0 - @test table_sub_f.aperture_sum ≈ 0 - @test table_cent_f.aperture_sum ≈ 0 -end - -@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) - - f = sum - table_cent_f = photometry(Subpixel(aperture), data; f) - table_sub_f = photometry(Subpixel(aperture, 10), data; f) - - 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_sub_f.aperture_sum ≈ table_ex.aperture_sum atol = 0.1 - @test table_cent.aperture_sum ≤ table_ex.aperture_sum - @test table_cent_f.aperture_sum ≤ table_ex.aperture_sum -end - - - -@testset "interface" 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 - -# @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 - -@testset "photometry - circular" 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 - -@testset "photometry - elliptical" 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 - -@testset "photometry - rectangular" 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 diff --git a/test/aperture/plots.jl b/test/aperture/plots.jl deleted file mode 100644 index dace6f17..00000000 --- a/test/aperture/plots.jl +++ /dev/null @@ -1,37 +0,0 @@ -using RecipesBase: apply_recipe -using Statistics - -APERTURES = [ - CircularAperture(3, 3, 3), - CircularAnnulus(3, 3, 2, 4), - EllipticalAperture(3, 3, 4, 2, 45), - EllipticalAnnulus(3, 3, 3, 4, 2, -26), - RectangularAperture(3, 3, 3, 4, 15), - RectangularAnnulus(3, 3, 3, 4, 2, -5) -] - -@testset "Aperture Plots - $(typeof(ap))" for ap in APERTURES - recipes = apply_recipe(Dict{Symbol,Any}(), ap) - for rec in recipes - @test getfield(rec, 1) == Dict{Symbol,Any}( - :seriescolor => :red, - :fillcolor => nothing, - :linecolor => :match, - :label => "", - :seriestype => :shape, - :aspect_ratio => :equal) - - # test to make sure our position is correct (should be +0.5 the given (x,y)) - points = rec.args[1] - x_tot = y_tot = 0 - for point in points - x_tot += point[1] - y_tot += point[2] - end - N = length(points) - x_mean = x_tot / N - y_mean = y_tot / N - @test x_mean ≈ 3.5 atol = x_mean / sqrt(N) - @test y_mean ≈ 3.5 atol = y_mean / sqrt(N) - end -end diff --git a/test/aperture/rectangular.jl b/test/aperture/rectangular.jl deleted file mode 100644 index 803ced15..00000000 --- a/test/aperture/rectangular.jl +++ /dev/null @@ -1,32 +0,0 @@ -using Photometry.Aperture: bounds - -@testset "Apertures" begin - ap_rect = RectangularAperture(50, 40, 10, 10, 0) - @test center(ap_rect) == (50, 40) - @test bounds(ap_rect) == (45, 55, 35, 45) - @test size(ap_rect) == (11, 11) - @test size(ap_rect, 1) == 11 - @test RectangularAperture([50, 40], 10, 10, 0) == ap_rect - - ap_ann = RectangularAnnulus(50, 40, 5, 10, 10, 0) - @test bounds(ap_ann) == (45, 55, 35, 45) - @test size(ap_ann) == (11, 11) - @test RectangularAnnulus([50, 40], 5, 10, 10, 0) == ap_ann -end - -@testset "Rectangle Aperture" begin - ap0 = RectangularAperture(0, 0, 0, 0, 0) - - @test sprint(show, ap0) == "RectangularAperture(0, 0, w=0, h=0, θ=0°)" - - ap1 = RectangularAperture(0, 0, 1, 1, 0) - - @test sprint(show, ap1) == "RectangularAperture(0, 0, w=1, h=1, θ=0°)" -end - -@testset "Rectangle Annulus" begin - - ap1 = RectangularAnnulus(0, 0, 1, 1, 1, 0) - @test center(ap1) == (0, 0) - @test sprint(show, ap1) == "RectangularAnnulus(0.0, 0.0, w_in=1.0, w_out=1.0, h_in=1.0, h_out=1.0, θ=0.0°)" -end diff --git a/test/background/background.jl b/test/background/background.jl deleted file mode 100644 index 9db1eef4..00000000 --- a/test/background/background.jl +++ /dev/null @@ -1,72 +0,0 @@ - -@testset "sigma clipping" begin - x = [1, 2, 3] - @test sigma_clip(x, 1, 1) ≈ [1.0, 2.0, 3.0] rtol = 1e-4 - @test sigma_clip(x, 1) == sigma_clip(x, 1, 1) - - y = [1, 2, 3, 4, 5, 6] - @test sigma_clip(y, 1, 1) ≈ [1.62917, 2.0, 3.0, 4.0, 5.0, 5.37083] rtol = 1e-4 - - # using different center - @test sigma_clip(y, 1, center = 4) ≈ [2.1291713066130296, 2.1291713066130296, 3, 4, 5, 5.87082869338697] - # using different std - @test sigma_clip(y, 1, std = 1) ≈ [2.5, 2.5, 3, 4, 4.5, 4.5] - - @test sum(sigma_clip(y, 1, fill = NaN) .=== NaN) == 2 - @test sum(sigma_clip(y, 1, fill = NaN) .=== NaN) == 2 - - # `sigma_clip` should not mutate - z1 = [0.1, 0.2, 3.0, 4.0, 0.2, 0.1] - z2 = copy(z1) - sigma_clip(z1, 1) - @test z1 == z2 -end - -@testset "estimate_background interface" begin - data = ones(100, 100) - - @test all(estimate_background(data, 20) .== estimate_background(data, (20, 20))) - @test all(estimate_background(data, 20, filter_size = 3) .== estimate_background(data, (20, 20), filter_size = (3, 3))) - @test size(estimate_background(data, 19, edge_method = :pad)[1]) == (114, 114) - @test size(estimate_background(data, 19, edge_method = :crop)[1]) == (95, 95) - X = rand(100, 100) - @test estimate_background(data, location = median, rms = mean) == estimate_background(data, location = mean, rms = median)[[2, 1]] - @test_throws MethodError estimate_background(data, (4, 4), edge_method = :yeet) - @test_throws MethodError estimate_background(data, (4, 4, 4)) - @test_throws ErrorException estimate_background(data, 4, filter_size = 2) - - nan_bkg, nan_rms = estimate_background(fill(NaN, 100, 100), 20) - @test nan_bkg ≈ zeros(100, 100) - @test nan_rms ≈ zeros(100, 100) -end - -@testset "flat background - $B, $S" for B in [median, mean, MMMBackground(), BiweightLocationBackground(), SourceExtractorBackground()], S in [StdRMS(), MADStdRMS(), BiweightScaleRMS()] - data = ones(100, 100) - - bk, rms = estimate_background(data, location = B, rms = S) - @test bk ≈ 1 - @test rms ≈ 0 - - bk, rms = estimate_background(data, (25, 25), location = B, rms = S) - @test median(bk) ≈ 1 - @test median(rms) ≈ 0 -end - -# regression test for failing to recognize NaN32 (or NaN16) -data = ones(Float32, 100, 100) -bk, rms = estimate_background(data, 3, edge_method = :pad) -@test !any(isnan.(bk)) -@test !any(isnan.(rms)) - -@testset "interpolators" begin - - @testset "zoom interface" begin - @test ZoomInterpolator(3) == ZoomInterpolator(3, 3) == ZoomInterpolator((3, 3)) - end - - @testset "trivial ones" begin - z = ZoomInterpolator(4, 3) - @test z(ones(3, 3)) ≈ ones(12, 9) - end - -end diff --git a/test/background/estimators.jl b/test/background/estimators.jl deleted file mode 100644 index 6c2d696f..00000000 --- a/test/background/estimators.jl +++ /dev/null @@ -1,73 +0,0 @@ -using StatsBase: mad - -############################################################################### -# Location Estimators - -@testset "Trivial $E" for E in [MMMBackground, SourceExtractorBackground, BiweightLocationBackground] - estimator = E() - - @test estimator(ones(1)) == 1 - - data = ones(10, 10) - - @test estimator(data) ≈ 1.0 - @test estimator(data, dims = 1) ≈ ones(1, 10) - @test estimator(data, dims = 2) ≈ ones(10) - - data = zeros(10, 10) - - @test estimator(data) ≈ 0.0 - @test estimator(data, dims = 1) ≈ zeros(1, 10) - @test estimator(data, dims = 2) ≈ zeros(10) - - data = randn(100, 100) -end - -@testset "SourceExtractorBackground" begin - # test skewed distribution - data = float(collect(1:100)) - data[71:end] .= 1e7 - - @test SourceExtractorBackground()(data) ≈ median(data) -end - -@testset "BiweightLocationBackground" begin - b = BiweightLocationBackground() - @test b([1, 3, 5, 500, 2]) ≈ 2.745 atol = 1e-3 -end - -############################################################################### -# RMS Estimators - - -@testset "Trivial $E" for E in [StdRMS, MADStdRMS, BiweightScaleRMS] - estimator = E() - - @test estimator(ones(1)) == 0 - - data = ones(10, 10) - @test estimator(data) ≈ 0.0 - @test estimator(data, dims = 1) ≈ zeros(1, 10) - @test estimator(data, dims = 2) ≈ zeros(10) - - - data = randn(10000, 10000) - @test estimator(data) ≈ 1 atol = 3e-2 -end - -@testset "StdRMS" begin - s = StdRMS() - data = randn(100) - @test s(data) == std(data, corrected = false) -end - -@testset "MADStdRMS" begin - s = MADStdRMS() - data = randn(100) - @test s(data) == mad(data, normalize = true) -end - -@testset "BiweightScaleRMS" begin - s = BiweightScaleRMS() - @test s([1, 3, 5, 500, 2]) ≈ 1.70992562072 -end diff --git a/test/background/interpolators.jl b/test/background/interpolators.jl deleted file mode 100644 index d6598a83..00000000 --- a/test/background/interpolators.jl +++ /dev/null @@ -1,33 +0,0 @@ -import Photometry.Background: ShepardIDWInterpolator - -@testset "IDWInterpolator" begin - @test IDWInterpolator((2, 3), k = 4)(ones(3, 2)) == ones(6, 6) - @test IDWInterpolator(2)(ones(3, 4)) == IDWInterpolator((2, 2))(ones(3, 4)) - @test_throws DomainError IDWInterpolator(2)(ones(3, 2)) - - # interface - @test IDWInterpolator((2, 3)) == IDWInterpolator(2, 3) - @test IDWInterpolator((2, 3), k = 4) == IDWInterpolator(2, 3, k = 4) -end - -@testset "ShepardIDWInterpolator" begin - # 1D - x = rand(100) - y = sin.(x) - it = ShepardIDWInterpolator(x', y) - @test it(0.4) ≈ sin(0.4) atol = 1e-2 - @test it.(0.2:0.1:0.5) ≈ sin.(0.2:0.1:0.5) atol = 1e-2 - - # 2D - x = rand(2, 10000) - y = sin.(x[1, :] .+ x[2, :]) - it = ShepardIDWInterpolator(x, y) - @test it(0.5, 0.6) ≈ sin(0.5 + 0.6) atol = 1e-2 - - # interface - - #= Warning! These are not accurate for use as a standard interpolator, - but are what we need for our use with images =# - @test size(it) == (10000,) - @test axes(it) == axes(y) -end diff --git a/test/detection/detection.jl b/test/detection/detection.jl deleted file mode 100644 index 42c2a70f..00000000 --- a/test/detection/detection.jl +++ /dev/null @@ -1,24 +0,0 @@ -@testset "peak finding - $P" for P in [PeakMesh()] - data = randn(100, 100) - idxs = [1, 700, 1524] - fake_peaks = randn(length(idxs)) .+ 10 - data[idxs] .= fake_peaks - - table = extract_sources(P, data) - @test table.value[1:3] == sort(fake_peaks, rev = true) -end - -@testset "Peak Mesh" begin - @test PeakMesh(box_size = 3) == PeakMesh(box_size = (3, 3)) -end - -@testset "interface - $P" for P in [PeakMesh()] - data = randn(100, 100) - idxs = [1, 700, 1524] - fake_peaks = randn(length(idxs)) .+ 10 - data[idxs] .= fake_peaks - - table = extract_sources(P, data) - table2 = extract_sources(P, data, zeros(100, 100)) - @test table == table2 -end diff --git a/test/runtests.jl b/test/runtests.jl index 504bfeb6..082dbee0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,25 +1,8 @@ using Photometry -using Test using Random using Statistics +using TestItemRunner Random.seed!(8462852) -@testset "Aperture Photometry" begin - include("aperture/overlap.jl") - include("aperture/circular.jl") - include("aperture/photometry.jl") - include("aperture/elliptical.jl") - include("aperture/rectangular.jl") - include("aperture/plots.jl") -end - -@testset "Background Estimation" begin - include("background/background.jl") - include("background/estimators.jl") - include("background/interpolators.jl") -end - -@testset "Source Detection" begin - include("detection/detection.jl") -end +@run_package_tests