-
Notifications
You must be signed in to change notification settings - Fork 5
Add multifrequency capabilities for ContinuousImage #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c9e65c6
c3e3349
e952b98
2e51c32
9ea6528
3458ed7
31313cb
229939b
acc9c52
b80c552
370ae7e
7d4edd1
d5400a0
8319383
842506f
cf4369c
7064228
189a88f
e846525
ddb4fdf
3fc1b34
cdcae3c
f60c0bd
152d59f
e77415e
ed25c35
687fd29
cfa1290
9ab7666
1f9f3dc
2bbd3ba
22ea90d
0d834d8
dc733ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,4 +9,3 @@ | |
| examples/Manifest.toml | ||
| .vscode/launch.json | ||
| .vscode/settings.json | ||
| .vscode/settings.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| export Multifrequency, TaylorSpectral, applyspectral, applyspectral!, generatemodel, | ||
| visibilitymap_numeric, build_imagecube, mfimagepixels | ||
|
|
||
| """Abstract type to hold all multifrequency spectral models""" | ||
| abstract type AbstractSpectralModel end | ||
|
|
||
| """ | ||
| $(TYPEDEF) | ||
| Spectral Model object. | ||
|
|
||
| # Fields | ||
| $(FIELDS) | ||
| """ | ||
| struct Multifrequency{B<:ContinuousImage,F<:Number,S<:FrequencyParams} <: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just an idea and if this is too much work don't worry about it, but how to do you feel about removing this function visibilitymap_numeric(m::Multifrequency, g::AbstractFourierDualDomain)with function visibilitymap_numeric(m::ContinuousImage{<:DomainParams}, g::AbstractFourierDualDomain)and then using the |
||
| ComradeBase.AbstractModel | ||
| """ | ||
| Base image model (ContinuousImage only) | ||
| """ | ||
| base::B | ||
| """ | ||
| The base image reference frequency. | ||
| """ | ||
| ν0::F | ||
| """ | ||
| Multifrequency spectral model | ||
| """ | ||
| spec::S | ||
| end | ||
|
|
||
| # defining the mandatory methods for a Comrade AbstractModel | ||
| visanalytic(::Type{Multifrequency{B}}) where {B} = NotAnalytic() | ||
| imanalytic(::Type{Multifrequency{B}}) where {B} = imanalytic(B) | ||
| radialextent(::Type{Multifrequency{B}}) where {B} = radialextent(B) | ||
| flux(::Type{Multifrequency{B}}) where {B} = flux(B) | ||
|
|
||
| function intensity_point(M::Multifrequency, p) | ||
| I0 = parent(M.base) | ||
| I_img = applyspectral(I0, M.spec, p.Fr) | ||
| I_model = ContinuousImage(I_img, M.base.kernel) | ||
| return intensity_point(I_model, p) | ||
| end | ||
|
|
||
| #visibility_point(M::Multifrequency{B},p) where {B} = visibility_point(B) | ||
|
|
||
| #""" | ||
| # $(TYPEDEF) | ||
| #Taylor expansion spectral model of order n for multifrequency imaging. Applies to Continuous Image models only. | ||
| # | ||
| #This is the same multifrequency model implemented in ehtim (Chael et al., 2023). | ||
| # | ||
| # Fields | ||
| #$(FIELDS) | ||
| #""" | ||
| #struct TaylorSpectral{C<:NTuple, ν0<:Real} <: AbstractSpectralModel | ||
| # """ | ||
| # A tuple containing the Taylor expansion coefficients. | ||
| # c[1] is the spectral index α, c[2] is the spectral curvature β, etc. | ||
| # The coeffecients can be either constant values, or arrays with dimensions equal to that of the base image I0. | ||
| # """ | ||
| # c::C | ||
| # """ | ||
| # The expansion reference frequency. | ||
| # """ | ||
| # ν0::C | ||
| #end | ||
|
|
||
| function order(::TaylorSpectral{N}) where {N} | ||
| return N | ||
| end | ||
|
|
||
| """ | ||
| Applies Taylor Series spectral model to image data. | ||
|
|
||
| Generates image data at frequency ν with respect to the reference frequency ν0. | ||
| """ | ||
| function applyspectral(I0::AbstractArray, spec::TaylorSpectral, ν::N) where {N<:Number} | ||
| data = copy(I0) | ||
| applyspectral!(data, spec, ν) | ||
| return data | ||
| end | ||
|
|
||
| function exparg(c::T, xlist::NTuple, i::Number) where {T<:Tuple{Vararg{<:AbstractArray}}} | ||
| return sum(getindex.(c, i) .* xlist) | ||
| end | ||
|
|
||
| function exparg(c::T, xlist::NTuple, i::Number) where {T<:Tuple{Vararg{<:Number}}} | ||
| return sum(c .* xlist) | ||
| end | ||
|
|
||
| function applyspectral!(I0::AbstractArray, spec::TaylorSpectral, ν::Number) | ||
| ν0 = spec.freq0 | ||
| x = log(ν / ν0) # frequency to evaluate taylor expansion | ||
| c = spec.index | ||
|
|
||
| n = order(spec) | ||
| xlist = ntuple(i -> x^i, n) # creating a tuple to hold the frequency powers | ||
|
|
||
| for i in eachindex(I0) # doing expansion one pixel at a time | ||
| I0[i] = I0[i] * exp(exparg(c, xlist, i)) | ||
| end | ||
| return I0 | ||
| end | ||
|
|
||
| """Given a multifrequency model (base image & spectral model), creates a new Continuous image model at frequency ν.""" | ||
| function generatemodel(MF::Multifrequency, ν::N) where {N<:Number} | ||
| image = parent(MF.base) # ContinuousImage -> SpatialIntensityMap | ||
| I0 = parent(image) # SpatialIntensityMap -> Array | ||
|
|
||
| data = applyspectral(I0, MF.spec, ν) # base image model, spectral model, frequency to generate new image | ||
|
|
||
| new_intensitymap = rebuild(image; data=data) | ||
| return ContinuousImage(new_intensitymap, MF.base.kernel) | ||
| end | ||
|
|
||
| function visibilitymap_numeric(m::Multifrequency{<:ContinuousImage}, | ||
| grid::AbstractFourierDualDomain) | ||
| checkspatialgrid(axisdims(m.base), grid.imgdomain) # compare base image dimensions to spatial dimensions of data cube | ||
| imgcube = build_imagecube(m, grid.imgdomain) | ||
| vis = applyft(forward_plan(grid), imgcube) | ||
| return applypulse!(vis, m.base.kernel, grid) | ||
| end | ||
|
|
||
| function checkspatialgrid(imgdims, grid) | ||
| return !(dims(imgdims) == dims(grid)[1:2]) && | ||
| throw(ArgumentError("The image dimensions in `ContinuousImage`\n" * | ||
| "and the spatial dimensions of the visibility grid passed to `visibilitymap`\n" * | ||
| "do not match. This is not currently supported.")) | ||
| end | ||
|
|
||
| """ | ||
| Build a multifrequency image cube to hold images at all frequencies. | ||
| """ | ||
| function build_imagecube(m::Multifrequency, mfgrid::RectiGrid) | ||
| I0 = parent(m.base) # base image IntensityMap | ||
| spec = m.spec | ||
| νlist = mfgrid.Fr | ||
|
|
||
| imgcube = allocate_imgmap(m.base, mfgrid) # build 3D cube of IntensityMap objects | ||
|
|
||
| for i in eachindex(νlist) # setting the image each frequency to first equal the base image and then apply spectral model | ||
| imgcube[:, :, i] .= I0 | ||
| applyspectral!(@view(imgcube[:, :, i]), spec, νlist[i]) # @view modifies existing image cube inplace --- don't create new object | ||
| end | ||
|
|
||
| return imgcube | ||
| end | ||
|
|
||
| function mfimagepixels(fovx::Real, fovy::Real, nx::Integer, ny::Integer, | ||
| νlist::AbstractVector, | ||
| x0::Real=0, y0::Real=0; executor=Serial(), | ||
| header=ComradeBase.NoHeader()) | ||
| @assert (nx > 0) && (ny > 0) "Number of pixels must be positive" | ||
|
|
||
| psizex = fovx / nx | ||
| psizey = fovy / ny | ||
|
|
||
| xitr = X(LinRange(-fovx / 2 + psizex / 2 - x0, fovx / 2 - psizex / 2 - x0, nx)) | ||
| yitr = Y(LinRange(-fovy / 2 + psizey / 2 - y0, fovy / 2 - psizey / 2 - y0, ny)) | ||
| νlist = Fr(νlist) | ||
| grid = RectiGrid((X=xitr, Y=yitr, Fr=νlist); executor, header) | ||
| return grid | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| function test_methods(m::Multifrequency{B}) where {B} | ||
| @test visanalytic(m) == NotAnalytic() | ||
| @test imanalysic(m) == imanalytic(B) | ||
| @test radialextent(m) == radialextent(B) | ||
| @test flux(m) = flux(B) | ||
| GC.gc() | ||
| return nothing | ||
| end | ||
|
|
||
| #function test_ContinuousImageTaylorSpectral() | ||
| # return TaylorSpectral(index::NTuple{N}, freq0::Real) | ||
| #end | ||
|
|
||
| function gaussmodel(θ) | ||
| (; f, σG) = θ | ||
| g = f * stretched(Gaussian(), σG, σG) | ||
| return g | ||
| end | ||
|
|
||
| # unit test consisting of a constant spectral index gaussian | ||
|
|
||
| @testset "Multifrequency Gaussian: Taylor Expansion" begin | ||
| ν0 = 8e9 # reference frequency (Hz) | ||
| ν = 12e9 # target frequency (Hz) | ||
| νlist = [ν0, ν] | ||
|
|
||
| # testing mfimagepixels & generating a multifrequency image grid | ||
| g = imagepixels(μas2rad(1000.0), μas2rad(1000.0), 256, 256) | ||
| mfgrid = mfimagepixels(μas2rad(1000.0), μas2rad(1000.0), 256, 256, νlist) # build multifrequency image grid | ||
|
|
||
| @test dims(g)[1].val ≈ dims(mfgrid)[1].val | ||
| @test dims(g)[2].val ≈ dims(mfgrid)[2].val | ||
| @test dims(mfgrid)[3].val == νlist | ||
|
|
||
| # 8 GHz Gaussian with total flux = 1.2 Jy | ||
| θ1 = (f=1.2, σG=μas2rad(100)) | ||
| gauss1 = intensitymap(gaussmodel(θ1), g) | ||
| gaussmodel1 = ContinuousImage(gauss1, BSplinePulse{3}()) | ||
|
|
||
| # 12 GHz Gaussian with total flux = 1.6 Jy | ||
| θ2 = (f=1.6, σG=μas2rad(100)) | ||
| gauss2truth = intensitymap(gaussmodel(θ2), g) | ||
| gaussmodel2truth = ContinuousImage(gauss2truth, BSplinePulse{3}()) | ||
|
|
||
| # testing spectral index map: constant spectral index and 0 spectral curvature | ||
| α0 = log(1.6 / 1.2) / log(12 / 8) | ||
| α = fill(α0, size(gauss1)) # spectral index map | ||
| β0 = 0.0 | ||
| β = fill(β0, size(gauss1)) # spectral curvature map | ||
|
|
||
| spec1 = TaylorSpectral((α, β), ν0) | ||
| spec2 = TaylorSpectral((α0, β0), ν0) | ||
|
|
||
| @test VLBISkyModels.order(spec1) == 2 | ||
| @test VLBISkyModels.order(spec2) == 2 | ||
|
|
||
| # create a multifrequency object | ||
| mfgauss1 = Multifrequency(gaussmodel1, ν0, spec1) | ||
| mfgauss2 = Multifrequency(gaussmodel1, ν0, spec2) | ||
|
|
||
| # test intensity_point | ||
| p = (; X=0, Y=0, Fr=ν) | ||
| @test VLBISkyModels.intensity_point(mfgauss1, p) == | ||
| VLBISkyModels.intensity_point(gaussmodel2truth, p) | ||
| @test VLBISkyModels.intensity_point(mfgauss2, p) == | ||
| VLBISkyModels.intensity_point(gaussmodel2truth, p) | ||
|
|
||
| # test generatemodel | ||
| # generate model at new frequency & compare with ground truth | ||
| @test parent(generatemodel(mfgauss1, ν)) ≈ gauss2truth | ||
| @test parent(generatemodel(mfgauss2, ν)) ≈ gauss2truth | ||
|
|
||
| # test visibilitymap | ||
| # generate 100 visibilities: half at 8 GHz and half at 12 GHz | ||
| u = range(1, 10, 50) * 1e7 # random visibilities | ||
| v = range(1, 10, 50) * 1e7 | ||
| f = similar(u) # each uv point has a frequency associated with it | ||
| f[1:25] .= νlist[1] | ||
| f[26:50] .= νlist[2] | ||
|
|
||
| fdd8 = FourierDualDomain(axisdims(gauss1), UnstructuredDomain((U=u[1:25], V=v[1:25])), | ||
| NFFTAlg()) | ||
| fdd12 = FourierDualDomain(axisdims(gauss2truth), | ||
| UnstructuredDomain((U=u[26:50], V=v[26:50])), NFFTAlg()) | ||
| mffdd = FourierDualDomain(RectiGrid((X=g.X, Y=g.Y, Fr=νlist)), | ||
| UnstructuredDomain((U=u, V=v, Fr=f)), NFFTAlg()) | ||
|
|
||
| # comparing multifrequency to single frequency results | ||
| @test visibilitymap(mfgauss1, mffdd)[1:25] ≈ visibilitymap(gaussmodel1, fdd8) | ||
| @test visibilitymap(mfgauss1, mffdd)[26:50] ≈ visibilitymap(gaussmodel2truth, fdd12) | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -193,4 +193,5 @@ end | |
| include("viz.jl") | ||
| include("io.jl") | ||
| include("stokesintensitymap.jl") | ||
| include("multifrequency.jl") | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to include VLBIImagePriors as a dep?