Skip to content

Commit 1ba84f0

Browse files
authored
Merge pull request #26 from JuliaAstro/resample
Add resampling extension
2 parents 5c7e32d + fab8c54 commit 1ba84f0

File tree

8 files changed

+215
-85
lines changed

8 files changed

+215
-85
lines changed

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
[deps]
2+
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
23
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
34
FITSIO = "525bcba6-941b-5504-bd06-fd0dc1a4d2eb"
5+
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
46
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
57
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
68
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ deploydocs(;
3333
devbranch = "main",
3434
push_preview = true,
3535
versions = ["stable" => "v^", "v#.#"], # Restrict to minor releases
36-
)
36+
)

docs/src/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Here is a quick demo of some of our features.
2525
```jldoctest guide
2626
julia> using Spectra, FITSIO, Unitful, UnitfulAstro, Plots
2727
28-
julia> fitsurl = "https://dr14.sdss.org/optical/spectrum/view/data/format=fits/spec=lite?plateid=1323&mjd=52797&fiberid=12";
28+
julia> # fitsurl = "https://dr14.sdss.org/optical/spectrum/view/data/format=fits/spec=lite?plateid=1323&mjd=52797&fiberid=12";
2929
30-
julia> # download(fitsurl, "sdss.fits");
30+
julia> # f = FITS(HTTP.get(fitsurl).body)
3131
3232
julia> f = FITS("sdss.fits")
3333
File: sdss.fits
@@ -65,4 +65,4 @@ TODO
6565

6666
## Contributing
6767

68-
Please see [Contributing](@ref contrib) for information on contributing and extending Spectra.jl.
68+
Please see [Contributing](@ref contrib) for information on contributing and extending Spectra.jl.

docs/src/transforms.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Extinction
44

5-
By levaraging [DustExtinction.jl](https://github.com/juliaastro/dustextinction.jl) we can apply common reddening laws to our spectra.
5+
By leveraging [DustExtinction.jl](https://github.com/juliaastro/dustextinction.jl) we can apply common reddening laws to our spectra.
66

77
```jldoctest
88
julia> using Spectra, Unitful, Measurements, Random
@@ -59,3 +59,11 @@ redden!
5959
deredden
6060
deredden!
6161
```
62+
63+
## Resampling
64+
65+
External interpolators, e.g., from [DataInterpolations.jl](https://github.com/SciML/DataInterpolations.jl) or [Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl), can be used to resample spectra onto a given wavelength grid. Starting with a sample spectrum `spec`, we first create a [`SpectrumResampler`](@ref) object `resampler` which stores the initial spectrum and interpolator `interp` together. We then apply this object to the wavelength grid of our choice to produce the resampled spectrum. We show example usage in the docstring below:
66+
67+
```@docs
68+
SpectrumResampler
69+
```

src/Spectra.jl

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ module Spectra
22

33
# Uniform API
44
export AbstractSpectrum, Spectrum, spectrum, spectral_axis, flux_axis
5-
# spectra_single.jl, spectra_ifu.jl, spectra_echelle.jl, spectra_binned.jl
6-
export SingleSpectrum, IFUSpectrum, EchelleSpectrum, spectral_axis, flux_axis
7-
# utils.jl
5+
6+
# AbstractSpectrum types
7+
export SingleSpectrum, IFUSpectrum, EchelleSpectrum
8+
9+
# Transforms
10+
export SpectrumResampler, redden, redden!, deredden, deredden!
11+
12+
# Utilities
813
export blackbody #, line_flux, equivalent_width
9-
# fitting/fitting.jl
14+
15+
# Fitting
1016
#export continuum, continuum!
11-
# transforms/redden.jl
12-
export redden, redden!, deredden, deredden!
1317

1418
using RecipesBase: @recipe
1519
using Measurements: Measurements, Measurement
@@ -82,7 +86,7 @@ Return the spectral axis of `spec`.
8286
spectral_axis(spec::AbstractSpectrum) = spec.spectral_axis
8387

8488
"""
85-
flux(spec::AbstractSpectrum)
89+
flux_axis(spec::AbstractSpectrum)
8690
8791
Return the flux axis of `spec`.
8892
"""
@@ -294,8 +298,8 @@ function spectrum(spectral_axis::AbstractMatrix{<:Quantity}, flux_axis::Abstract
294298
Spectrum(spectral_axis, flux_axis, Dict{Symbol,Any}(kwds))
295299
end
296300

297-
# tools
298301
include("utils.jl")
302+
include("transforms/resampler.jl")
299303
include("transforms/transforms.jl")
300304
include("plotting.jl")
301305
#include("fitting/fitting.jl")

src/transforms/resampler.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
SpectrumResampler(spec::Spectrum, interp)
3+
4+
Type representing the spectrum `spec` with interpolator `interp`.
5+
6+
Interpolation methods from many packages can be used without issue. Below we show example usage of [DataInterpolations.jl](https://github.com/SciML/DataInterpolations.jl) and [Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl).
7+
8+
First, we set up an arbitrary spectrum and a linear interpolator from DataInterpolations.jl:
9+
10+
```jldoctest resampling
11+
julia> using Spectra: SpectrumResampler, spectrum, spectral_axis, flux_axis
12+
13+
julia> using DataInterpolations: LinearInterpolation, ExtrapolationType
14+
15+
julia> spec = spectrum([20, 40, 120, 160, 200], [1, 3, 7, 6, 20]);
16+
17+
julia> interp = LinearInterpolation(flux_axis(spec), spectral_axis(spec);
18+
extrapolation = ExtrapolationType.Constant);
19+
```
20+
21+
Now, we construct the `SpectrumResampler` and define the new wavelength grid that we want to resample the original spectrum to:
22+
23+
```jldoctest resampling
24+
julia> resampler = SpectrumResampler(spec, interp);
25+
26+
julia> wave_sampled = [10, 50, 90, 130, 140, 170, 210, 220, 230];
27+
```
28+
29+
To perform the resampling, you call the resampler with the desired wavelength grid.
30+
31+
```jldoctest resampling
32+
julia> result = resampler(wave_sampled);
33+
```
34+
35+
The resampled wavelength and flux can be obtained with the `wave` and `flux` methods.
36+
37+
```jldoctest resampling
38+
julia> result isa Spectrum
39+
true
40+
41+
julia> spectral_axis(result) == wave_sampled
42+
true
43+
44+
julia> flux_axis(result) == interp(wave_sampled)
45+
true
46+
```
47+
48+
Use of [Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl) follows the same general procedure, but using a different `interp`:
49+
50+
```jldoctest resampling
51+
julia> using Interpolations: linear_interpolation, Flat
52+
53+
julia> interp = linear_interpolation(spectral_axis(spec), flux_axis(spec); extrapolation_bc = Flat());
54+
55+
julia> resampler = SpectrumResampler(spec, interp);
56+
57+
julia> result = resampler(wave_sampled);
58+
59+
julia> result isa Spectrum
60+
true
61+
62+
julia> spectral_axis(result) == wave_sampled
63+
true
64+
65+
julia> flux_axis(result) == interp(wave_sampled)
66+
true
67+
```
68+
"""
69+
struct SpectrumResampler{A <: Spectrum, B}
70+
spectrum::A
71+
interp::B
72+
end
73+
74+
spectrum(s::SpectrumResampler) = s.spectrum
75+
spectral_axis(s::SpectrumResampler) = (spectral_axis spectrum)(s)
76+
flux_axis(s::SpectrumResampler) = (flux_axis spectrum)(s)
77+
meta(s::SpectrumResampler) = (meta spectrum)(s)
78+
79+
function (s::SpectrumResampler)(wave_sampled)
80+
flux_resampled = (s.interp)(wave_sampled)
81+
return Spectrum(wave_sampled, flux_resampled, meta(s))
82+
end
83+
84+
function Base.show(io::IO, s::SpectrumResampler)
85+
println(io, "SpectrumResampler(", (eltype spectral_axis)(s), ", ", (eltype flux_axis)(s), ")")
86+
println(io, " spec: ", (typeof spectrum)(s))
87+
print(io, " interpolator: ", typeof(s.interp))
88+
end

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[deps]
22
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
3+
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
34
DustExtinction = "fb44c06c-c62f-5397-83f5-69249e0a3c8e"
45
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
56
ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc"

test/transforms/resample.jl

Lines changed: 99 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,99 @@
1-
#function mock_spectrum(n::Int = Int(1e3); use_units::Bool = false)
2-
# wave = range(1e4, 3e4, length = n)
3-
# sigma = 0.1 .* sin.(wave)
4-
# T = 6700
5-
# flux = @. 1e14 / (wave^5 * (exp(1 / (wave * T)) - 1)) ± sigma
6-
# if use_units
7-
# wave *= u"angstrom"
8-
# flux *= u"erg/s/cm^2/angstrom"
9-
# end
10-
# spectrum(wave, flux, name="Test Spectrum")
11-
#end
12-
#
13-
#@testset "resample" begin
14-
# @testset "Resampling" begin
15-
# spec = mock_spectrum()
16-
# new_wave = range(minimum(spec.wave), maximum(spec.wave), length=Integer(length(spec.wave) ÷ 2.4))
17-
# res_spec = resample(spec, new_wave)
18-
#
19-
# @test res_spec.wave == new_wave
20-
# @test length(res_spec.flux) == length(new_wave)
21-
#
22-
# resample!(spec, new_wave)
23-
# @test res_spec.wave == spec.wave
24-
# @test res_spec.flux == spec.flux
25-
#
26-
# # Unitful
27-
# spec = mock_spectrum(use_units=true)
28-
# new_wave = range(minimum(spec.wave), maximum(spec.wave), length=Integer(length(spec.wave) ÷ 2.4))
29-
# @assert unit(eltype(new_wave)) == unit(eltype(spec.wave))
30-
# res_spec = resample(spec, new_wave)
31-
#
32-
# @test res_spec.wave == new_wave
33-
# @test length(res_spec.flux) == length(new_wave)
34-
#
35-
# resample!(spec, new_wave)
36-
# @test res_spec.wave == spec.wave
37-
# @test res_spec.flux == spec.flux
38-
#
39-
# # Test resampling to another Spectrum
40-
# spec1 = mock_spectrum(Integer(1e4))
41-
# spec2 = mock_spectrum(Integer(1e3))
42-
# res_spec = resample(spec1, spec2)
43-
# @test res_spec.wave == spec2.wave
44-
#
45-
# resample!(spec1, spec2)
46-
# @test spec1.wave == spec2.wave
47-
# @test spec1.flux == res_spec.flux
48-
#
49-
# # Unitful
50-
# spec1 = mock_spectrum(Integer(1e4), use_units=true)
51-
# spec2 = mock_spectrum(Integer(1e3), use_units=true)
52-
# res_spec = resample(spec1, spec2)
53-
#
54-
# @test res_spec.wave == spec2.wave
55-
# @test unit(eltype(spec1.flux)) == unit(eltype(spec2.flux)) == unit(eltype(res_spec.flux))
56-
#
57-
# # Test when spectra2 has different units
58-
# spec1 = mock_spectrum(Integer(1e4), use_units=true)
59-
# spec2 = mock_spectrum(Integer(1e3), use_units=true)
60-
# spec1.wave = uconvert.(u"cm", spec1.wave)
61-
# resample!(spec1, spec2)
62-
# @test ustrip.(spec1.wave) ≈ ustrip.(unit(eltype(spec1.wave)), spec2.wave)
63-
#
64-
# # address bug when doing the same thing but affecting spec2
65-
# spec1 = mock_spectrum(Integer(1e4), use_units=true)
66-
# spec2 = mock_spectrum(Integer(1e3), use_units=true)
67-
# spec2.wave = uconvert.(u"cm", spec2.wave)
68-
# resample!(spec1, spec2)
69-
# @test ustrip.(spec1.wave) ≈ ustrip.(unit(eltype(spec1.wave)), spec2.wave)
70-
#
71-
# end
72-
#end
1+
using Spectra: Spectra, AbstractSpectrum, SpectrumResampler, spectrum, spectral_axis, flux_axis
2+
using DataInterpolations: LinearInterpolation, ExtrapolationType
3+
using Unitful: @u_str, uconvert
4+
using UnitfulAstro
5+
using Measurements: ±
6+
7+
# TODO: See if it makes sense to have an exported API for resample/resample! even though we are using external interpolators.
8+
9+
function resample(spec, new_wave)
10+
interp = LinearInterpolation(flux_axis(spec), spectral_axis(spec); extrapolation = ExtrapolationType.Constant)
11+
resampler = SpectrumResampler(spec, interp)
12+
return resampler(new_wave)
13+
end
14+
15+
function resample(spec1::AbstractSpectrum, spec2::AbstractSpectrum)
16+
new_wave = spectral_axis(spec2)
17+
return resample(spec1, new_wave)
18+
end
19+
20+
function mock_spectrum(n::Int = Int(1e3); use_units::Bool = false)
21+
wave = range(1e4, 3e4, length = n)
22+
sigma = 0.1 .* sin.(wave)
23+
T = 6700
24+
flux = @. 1e14 / (wave^5 * (exp(1 / (wave * T)) - 1)) ± sigma
25+
if use_units
26+
wave *= u"angstrom"
27+
flux *= u"erg/s/cm^2/angstrom"
28+
end
29+
spectrum(wave, flux; name = "Test Spectrum")
30+
end
31+
32+
@testset "Resampler" begin
33+
spec = mock_spectrum()
34+
s, f = spectral_axis(spec), flux_axis(spec)
35+
interp = LinearInterpolation(f, s; extrapolation = ExtrapolationType.Constant)
36+
resampler = SpectrumResampler(spec, interp)
37+
expected = """
38+
SpectrumResampler(Float64, Measurements.Measurement{Float64})
39+
spec: Spectra.SingleSpectrum{Float64, Measurements.Measurement{Float64}}
40+
interpolator: DataInterpolations.LinearInterpolation{Vector{Measurements.Measurement{Float64}}, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Measurements.Measurement{Float64}}, Vector{Measurements.Measurement{Float64}}, Measurements.Measurement{Float64}}"""
41+
42+
@test sprint(show, resampler) == expected
43+
@test spectral_axis(resampler) == s
44+
@test flux_axis(resampler) == f
45+
end
46+
47+
@testset "Resampling" begin
48+
spec = mock_spectrum()
49+
s, f = spectral_axis(spec), flux_axis(spec)
50+
new_wave = range(minimum(s), maximum(s); length = Integer(length(s) ÷ 2.4))
51+
res_spec = resample(spec, new_wave)
52+
expected = """
53+
SingleSpectrum(Float64, Measurements.Measurement{Float64})
54+
spectral axis (416,): 10000.0 .. 30000.0
55+
flux axis (416,): 67.0 ± 0.031 .. 0.827 ± 0.08
56+
meta: Dict{Symbol, Any}(:name => "Test Spectrum")"""
57+
58+
@test sprint(show, res_spec) == expected
59+
@test spectral_axis(res_spec) == new_wave
60+
@test length(flux_axis(res_spec)) == length(new_wave)
61+
62+
# Unitful
63+
spec = mock_spectrum(; use_units = true)
64+
s, f = spectral_axis(spec), flux_axis(spec)
65+
new_wave = range(minimum(s), maximum(s); length = Integer(length(s) ÷ 2.4))
66+
@assert unit(eltype(new_wave)) == unit(eltype(s))
67+
res_spec = resample(spec, new_wave)
68+
69+
@test spectral_axis(res_spec) == new_wave
70+
@test length(flux_axis(res_spec)) == length(new_wave)
71+
72+
# Test resampling to another Spectrum
73+
spec1 = mock_spectrum(Integer(1e4))
74+
spec2 = mock_spectrum(Integer(1e3))
75+
res_spec = resample(spec1, spec2)
76+
@test spectral_axis(res_spec) == spectral_axis(spec2)
77+
78+
# Unitful
79+
spec1 = mock_spectrum(Integer(1e4); use_units = true)
80+
spec2 = mock_spectrum(Integer(1e3); use_units = true)
81+
res_spec = resample(spec1, spec2)
82+
83+
@test spectral_axis(res_spec) == spectral_axis(spec2)
84+
@test unit(eltype(flux_axis(spec1))) == unit(eltype(flux_axis(spec2))) == unit(eltype(flux_axis(res_spec)))
85+
86+
# Test when spectra2 has different units
87+
#spec1 = mock_spectrum(Integer(1e4), use_units=true)
88+
#spec2 = mock_spectrum(Integer(1e3), use_units=true)
89+
#spec1.wave = uconvert.(u"cm", spec1.wave)
90+
#resample!(spec1, spec2)
91+
#@test ustrip.(spectral_axis(spec1)) ≈ ustrip.(unit(eltype(spectral_axis(spec1))), spectral_axis(spec2))
92+
93+
# address bug when doing the same thing but affecting spec2
94+
#spec1 = mock_spectrum(Integer(1e4), use_units=true)
95+
#spec2 = mock_spectrum(Integer(1e3), use_units=true)
96+
#spec2.wave = uconvert.(u"cm", spec2.wave)
97+
#resample!(spec1, spec2)
98+
#@test ustrip.(spectral_axis(spec1)) ≈ ustrip.(unit(eltype(spectral_axis(spec1))), spectral_axis(spec2))
99+
end

0 commit comments

Comments
 (0)