|
1 | 1 |
|
| 2 | +# Some initial support for Healpix fields. |
| 3 | +# The main functionality of broadcasting, indexing, and projection for |
| 4 | +# a few field types is implemented, but not much beyond that. |
| 5 | + |
| 6 | + |
2 | 7 | @init global hp = lazy_pyimport("healpy") |
3 | 8 |
|
4 | | -function θϕ_to_xy((θ,ϕ)) |
5 | | - r = 2cos(θ/2) |
6 | | - x = r*cos(ϕ) |
7 | | - y = -r*sin(ϕ) |
8 | | - x, y |
| 9 | +struct ProjHealpix <: FieldMetadata |
| 10 | + Nside :: Int |
| 11 | +end |
| 12 | +typealias_def(::Type{<:ProjHealpix}) = "ProjHealpix" |
| 13 | + |
| 14 | + |
| 15 | +# spin-0 |
| 16 | +const HealpixMap{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{Map, M, T, A} |
| 17 | +const HealpixFourier{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{Fourier, M, T, A} |
| 18 | +# spin-2 |
| 19 | +const HealpixQUMap{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{QUMap, M, T, A} |
| 20 | +const HealpixQUFourier{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{QUFourier, M, T, A} |
| 21 | +const HealpixEBMap{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{EBMap, M, T, A} |
| 22 | +const HealpixEBFourier{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{EBFourier, M, T, A} |
| 23 | +# spin-(0,2) |
| 24 | +const HealpixIQUMap{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{IQUMap, M, T, A} |
| 25 | +const HealpixIQUFourier{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{IQUFourier, M, T, A} |
| 26 | +const HealpixIEBMap{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{IEBMap, M, T, A} |
| 27 | +const HealpixIEBFourier{ M<:ProjHealpix, T, A<:AbstractArray{T} } = BaseField{IEBFourier, M, T, A} |
| 28 | + |
| 29 | +const HealpixField{B, M<:ProjHealpix, T, A<:AbstractArray{T}} = BaseField{B, M, T, A} |
| 30 | + |
| 31 | +## constructing from arrays |
| 32 | +# spin-0 |
| 33 | +function HealpixMap(I::A) where {T, A<:AbstractArray{T}} |
| 34 | + HealpixMap(I, ProjHealpix(hp.npix2nside(length(I)))) |
| 35 | +end |
| 36 | +# spin-2 |
| 37 | +function HealpixField{B}(X::A, Y::A) where {T, A<:AbstractArray{T}, B<:Basis2Prod{<:Union{𝐐𝐔,𝐄𝐁},Map}} |
| 38 | + HealpixField{B}(cat(X, Y, dims=Val(2)), ProjHealpix(hp.npix2nside(length(X)))) |
| 39 | +end |
| 40 | +# spin-(0,2) |
| 41 | +function HealpixField{B}(I::A, X::A, Y::A) where {T, A<:AbstractArray{T}, B<:Basis3Prod{𝐈,<:Union{𝐐𝐔,𝐄𝐁},Map}} |
| 42 | + HealpixField{B}(cat(I, X, Y, dims=Val(2)), ProjHealpix(hp.npix2nside(length(I)))) |
9 | 43 | end |
10 | 44 |
|
11 | | -function xy_to_θϕ((x,y)) |
| 45 | +### pretty printing |
| 46 | +typealias_def(::Type{F}) where {B,M<:ProjHealpix,T,A,F<:HealpixField{B,M,T,A}} = "Healpix$(typealias(B)){$(typealias(A)),$(typealias(M))}" |
| 47 | +function Base.summary(io::IO, f::HealpixField) |
| 48 | + @unpack Nside = f |
| 49 | + print(io, "$(length(f))-element Nside=$Nside ") |
| 50 | + Base.showarg(io, f, true) |
| 51 | +end |
| 52 | + |
| 53 | +# useful for enumerating some cases below |
| 54 | +_healpix_sub_components = [ |
| 55 | + (:HealpixMap, ("Ix"=>:, "I"=>:)), |
| 56 | + (:HealpixFourier, ("Il"=>:, "I"=>:)), |
| 57 | + (:HealpixQUMap, ("Qx"=>1, "Ux"=>2, "Q" =>1, "U"=>2, "P"=>:)), |
| 58 | + (:HealpixQUFourier, ("Ql"=>1, "Ul"=>2, "Q" =>1, "U"=>2, "P"=>:)), |
| 59 | + (:HealpixEBMap, ("Ex"=>1, "Bx"=>2, "E" =>1, "B"=>2, "P"=>:)), |
| 60 | + (:HealpixEBFourier, ("El"=>1, "Bl"=>2, "E" =>1, "B"=>2, "P"=>:)), |
| 61 | + (:HealpixIQUMap, ("Ix"=>1, "Qx"=>2, "Ux"=>3, "I"=>1, "Q"=>2, "U"=>3, "P"=>2:3, "IP"=>:)), |
| 62 | + (:HealpixIQUFourier, ("Il"=>1, "Ql"=>2, "Ul"=>3, "I"=>1, "Q"=>2, "U"=>3, "P"=>2:3, "IP"=>:)), |
| 63 | + (:HealpixIEBMap, ("Ix"=>1, "Ex"=>2, "Bx"=>3, "I"=>1, "E"=>2, "B"=>3, "P"=>2:3, "IP"=>:)), |
| 64 | + (:HealpixIEBFourier, ("Il"=>1, "El"=>2, "Bl"=>3, "I"=>1, "E"=>2, "B"=>3, "P"=>2:3, "IP"=>:)), |
| 65 | +] |
| 66 | +# sub-components |
| 67 | +for (F, props) in _healpix_sub_components |
| 68 | + for (k,I) in props |
| 69 | + body = if k[end] in "xl" |
| 70 | + I==(:) ? :(getfield(f,:arr)) : :(view(getfield(f,:arr), :, $I)) |
| 71 | + else |
| 72 | + I==(:) ? :f : :($(HealpixField{k=="P" ? Basis2Prod{basis(@eval($F)).parameters[end-1:end]...} : basis(@eval($F)).parameters[end]})(view(getfield(f,:arr), :, $I), f.metadata)) |
| 73 | + end |
| 74 | + @eval getproperty(f::$F, ::Val{$(QuoteNode(Symbol(k)))}) = $body |
| 75 | + end |
| 76 | +end |
| 77 | +getproperty(f::HealpixField, ::Val{:proj}) = getfield(f,:metadata) |
| 78 | + |
| 79 | + |
| 80 | + |
| 81 | +### broadcasting |
| 82 | + |
| 83 | +function promote_metadata_strict(metadata₁::ProjHealpix, metadata₂::ProjHealpix) |
| 84 | + if metadata₁ === metadata₂ |
| 85 | + metadata₁ |
| 86 | + else |
| 87 | + error("Can't broadcast Healpix maps with two different Nsides ($metadata₁.Nside, $metadata₂.Nside).") |
| 88 | + end |
| 89 | +end |
| 90 | + |
| 91 | + |
| 92 | +### Projection |
| 93 | + |
| 94 | +## Coordinate transformations |
| 95 | + |
| 96 | +# choices of negative signs are such that the default projection is |
| 97 | +# centered on the center of a healpy.mollview plot and oriented the |
| 98 | +# same way. |
| 99 | + |
| 100 | +function xy_to_θϕ(::ProjLambert, x, y) |
12 | 101 | r = sqrt(x^2+y^2) |
13 | 102 | θ = 2*acos(r/2) |
14 | | - ϕ = -atan(y,x) |
| 103 | + ϕ = atan(-x,-y) |
15 | 104 | θ, ϕ |
16 | 105 | end |
17 | 106 |
|
18 | | -function healpix_to_flat(healpix_map::Vector{T}, proj::ProjLambert{T}; rots=[((0,90,0),)]) where {T} |
19 | | - |
20 | | - Nside_sphere = hp.npix2nside(length(healpix_map)) |
21 | | - @unpack Δx, Ny, Nx = proj |
22 | | - |
23 | | - # compute projection coordinate mapping |
24 | | - ys = @. Δx * ((-Nx÷2:Nx÷2-1) + 0.5) # x/y switch here intentional |
25 | | - xs = @. Δx * ((-Ny÷2:Ny÷2-1) + 0.5) |
26 | | - xys = tuple.(xs,ys')[:] |
27 | | - θϕs = xy_to_θϕ.(xys) |
28 | | - (θs, ϕs) = first.(θϕs), last.(θϕs) |
29 | | - |
30 | | - # the default rots=[(0,90,0)] makes it so you get a view of the |
31 | | - # equator, to match Helapy's azeqview convention |
32 | | - R = prod([hp.Rotator(rot..., eulertype="ZYX") for rot in rots]) |
33 | | - (θs′, ϕs′) = eachrow(R.get_inverse()(θs, ϕs)) |
34 | | - |
35 | | - # interpolate map |
36 | | - FlatMap(reshape(hp.get_interp_val(healpix_map, θs′, ϕs′), Ny, Nx), proj) |
37 | | - |
| 107 | +function θϕ_to_xy(::ProjLambert, θ, ϕ) |
| 108 | + r = 2cos(θ/2) |
| 109 | + x = -r*sin(ϕ) |
| 110 | + y = -r*cos(ϕ) |
| 111 | + x, y |
38 | 112 | end |
39 | 113 |
|
40 | | -function healpix_pixel_centers_to_flat(f::FlatField, Nside_sphere; rots=[((0,90,0),)], healpix_pixels=0:(12*Nside_sphere^2-1)) |
41 | | - |
42 | | - @unpack Δx, Ny, Nx = f |
| 114 | +# adding more flat projections in the future should be as easy as |
| 115 | +# defining pairs of functions like the above two for other cases |
43 | 116 |
|
44 | | - (θs, ϕs) = hp.pix2ang(Nside_sphere, healpix_pixels) |
45 | | - |
46 | | - # the default rots=[(0,90,0)] makes it so you get a view of the |
47 | | - # equator, to match Helapy's azeqview convention |
48 | | - R = prod([hp.Rotator(rot..., eulertype="ZYX") for rot in rots]) |
49 | | - (θs′, ϕs′) = eachrow(R(θs, ϕs)) |
50 | 117 |
|
51 | | - # compute projection coordinate mapping |
52 | | - # (using Ny for xs and vice-versa intentional) |
53 | | - xys = @. θϕ_to_xy(tuple(θs′, ϕs′)) |
54 | | - xs = @. first(xys) / Δx + Ny÷2 + 0.5 # x/y switch here intentional |
55 | | - ys = @. last(xys) / Δx + Nx÷2 + 0.5 |
| 118 | +function xy_to_θϕ(flat_proj::FlatProj; rotator) |
| 119 | + @unpack Δx, Ny, Nx = flat_proj |
| 120 | + xs = @. Δx * ((-Nx÷2:Nx÷2-1) + 0.5) |
| 121 | + ys = @. Δx * ((-Ny÷2:Ny÷2-1) + 0.5) |
| 122 | + θϕs = xy_to_θϕ.(Ref(flat_proj), xs, ys') |
| 123 | + (eachrow(rotator.get_inverse()(first.(θϕs)[:], last.(θϕs)[:]))...,) |
| 124 | +end |
56 | 125 |
|
| 126 | +function θϕ_to_xy((hpx_proj, flat_proj)::Pair{<:ProjHealpix, <:FlatProj}; rotator) |
| 127 | + @unpack Δx, Ny, Nx = flat_proj |
| 128 | + @unpack Nside = hpx_proj |
| 129 | + (θs, ϕs) = hp.pix2ang(Nside, 0:(12*Nside^2-1)) |
| 130 | + (θs′, ϕs′) = eachrow(rotator(θs, ϕs)) |
| 131 | + xys = θϕ_to_xy.(Ref(flat_proj), θs′, ϕs′) |
| 132 | + xs = @. first(xys) / Δx + Nx÷2 + 0.5 |
| 133 | + ys = @. last(xys) / Δx + Ny÷2 + 0.5 |
57 | 134 | (xs, ys) |
| 135 | +end |
| 136 | + |
| 137 | + |
| 138 | +## Healpix => Flat |
| 139 | + |
| 140 | +""" |
| 141 | + project(healpix_map::HealpixField => flat_proj::FlatProj; [rotator]) |
58 | 142 |
|
| 143 | +Project `healpix_map` to a flat projection specified by `flat_proj`. |
| 144 | +E.g.: |
| 145 | +
|
| 146 | + healpix_map = HealpixMap(rand(12*2048^2)) |
| 147 | + flat_proj = ProjLambert(Ny=128, Nx=128, θpix=3, T=Float32) |
| 148 | + f = project(healpix_map => flat_proj; rotator=pyimport("healpy").Rotator((0,28,23))) |
| 149 | +
|
| 150 | +The use of `=>` is to help remember in which order the arguments are |
| 151 | +specified. Optional keyword argument `rotator` is a `healpy.Rotator` |
| 152 | +object specifying a rotation which rotates the north pole to the |
| 153 | +center of the desired field. If `healpix_map` is a `HealpixQU`, Q/U |
| 154 | +polarization angles are rotated to be aligned with the local |
| 155 | +coordinates (sometimes called "polarization flattening"). |
| 156 | +""" |
| 157 | +function project((hpx_map, flat_proj)::Pair{<:HealpixMap,<:ProjLambert{T}}; rotator=hp.Rotator((0,90,0))) where {T} |
| 158 | + _project(xy_to_θϕ(flat_proj; rotator), hpx_map => flat_proj) |
| 159 | +end |
| 160 | + |
| 161 | +function project((hpx_map, flat_proj)::Pair{<:HealpixQUMap,<:ProjLambert{T}}; rotator=hp.Rotator((0,90,0))) where {T} |
| 162 | + @unpack Ny, Nx = flat_proj |
| 163 | + (θs, ϕs) = xy_to_θϕ(flat_proj; rotator) |
| 164 | + Q = _project((θs, ϕs), hpx_map.Q => flat_proj) |
| 165 | + U = _project((θs, ϕs), hpx_map.U => flat_proj) |
| 166 | + ψpol = permutedims(reshape((hp.Rotator((0,-90,0)) * rotator).angle_ref(θs, ϕs), Nx, Ny)) |
| 167 | + QU_flat = @. (Q.arr + im * U.arr) * exp(im * 2 * ψpol) |
| 168 | + FlatQUMap(real.(QU_flat), imag.(QU_flat), flat_proj) |
| 169 | +end |
| 170 | + |
| 171 | +function _project((θs,ϕs)::Tuple, (hpx_map, flat_proj)::Pair{<:HealpixMap,<:ProjLambert{T}}) where {T} |
| 172 | + @unpack Ny, Nx = flat_proj |
| 173 | + FlatMap(T.(permutedims(reshape(hp.get_interp_val(collect(hpx_map), θs, ϕs), Nx, Ny))), flat_proj) |
59 | 174 | end |
60 | 175 |
|
61 | 176 |
|
62 | | -function flat_to_healpix(f::FlatS0, Nside_sphere; kwargs...) |
63 | | - # get pixel centers of Healpix pixels in 2D map |
64 | | - xs,ys = healpix_pixel_centers_to_flat(f, Nside_sphere; kwargs...) |
65 | | - # interpolate map |
66 | | - @ondemand(Images.bilinear_interpolation).(Ref(f[:Ix]), xs, ys) |
| 177 | +## Flat => Healpix |
| 178 | + |
| 179 | +""" |
| 180 | + project(flat_map::FlatField => healpix_proj::ProjHealpix; [rotator]) |
| 181 | +
|
| 182 | +Reproject a `flat_map` back onto the sphere in a Healpix projection |
| 183 | +specified by `healpix_proj`. E.g. |
| 184 | +
|
| 185 | + flat_map = FlatMap(rand(128,128)) |
| 186 | + f = project(flat_map => ProjHealpix(2048); rotator=pyimport("healpy").Rotator((0,28,23))) |
| 187 | +
|
| 188 | +The use of `=>` is to help remember in which order the arguments are |
| 189 | +specified. Optional keyword argument `rotator` is a `healpy.Rotator` |
| 190 | +object specifying a rotation which rotates the north pole to the |
| 191 | +center of the desired field. |
| 192 | +""" |
| 193 | +function project((flat_map, hpx_proj)::Pair{<:FlatS0, <:ProjHealpix}; rotator=hp.Rotator((0,90,0))) |
| 194 | + (xs, ys) = θϕ_to_xy(hpx_proj => flat_map.proj; rotator) |
| 195 | + HealpixMap(@ondemand(Images.bilinear_interpolation).(Ref(cpu(flat_map[:Ix])), ys, xs), hpx_proj) |
67 | 196 | end |
68 | 197 |
|
0 commit comments