-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdered_drizzle.py
More file actions
226 lines (178 loc) · 7.17 KB
/
dered_drizzle.py
File metadata and controls
226 lines (178 loc) · 7.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import numpy as np
from numpy.lib.stride_tricks import as_strided
from astropy.io import fits
from astropy import units as u, constants as c
from speclite import redshift as slrs
import os, sys
import utils as ut
from importer import *
import manga_tools as m
def get_cube_lamgrid(plate, ifu, mpl_v='MPL-6'):
hdulist = m.load_drp_logcube(plate, ifu, mpl_v)
lamctrgrid = hdulist['WAVE'].data
hdulist.close()
return lamctrgrid
def get_evec_lamgrid():
hdulist = fits.open('pc_vecs.fits')
lamctrgrid = hdulist['LAM'].data
hdulist.close()
return lamctrgrid
def rolling_window(a, window, axis=-1):
'''
Return a windowed array.
Parameters:
a: A numpy array
window: The size of the window
Returns:
An array that has been windowed
'''
if axis == -1:
axis = a.ndim - 1
if 0 <= axis <= a.ndim - 1:
shape = (a.shape[:axis] + (a.shape[axis] - window + 1, window) +
a.shape[axis+1:])
strides = a.strides[:axis] + (a.strides[axis],) + a.strides[axis:]
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
else:
raise ValueError('rolling_window: axis out of bounds')
def find_redshift_bounds(evec_grid, cube_grid):
evec_loggrid = np.log(evec_grid)
cube_loggrid = np.log(cube_grid)
# find bounds on redshift subject to constraint that
# min(lcube) < zify(min(lrect), z) < max(lcube)
# AND
# min(lcube) < zify(max(lrect), z) < max(lcube)
# since max > min, this boils down to
# (1) shift(min(lrect)) > min(lcube) & (2) shift(max(lrect)) < max(lcube)
zmin = cube_loggrid.min() - evec_loggrid.min()
zmax = cube_loggrid.max() - evec_loggrid.max()
return zmin, zmax
def gen_flux(l):
f0 = 3500. / l
tau = gen_tau(l)
return f0 * np.exp(-tau)
def gen_ivar(l):
return 10. * (l / 5000.)
def gen_tau(l, loc=5000., w=2., tau0=1.):
tau = tau0 * np.exp(-((l - loc)**2. / (2. * w**2.)))
return tau
def load_real_restcubes(plate, ifu, MPL_v):
dered = MaNGA_deredshift.from_plateifu(plate, ifu, MPL_v)
l_rest, f_rest, ivar_rest = dered.transform_to_restframe(
l=dered.drp_l * u.AA, f=dered.flux, ivar=dered.ivar)
return l_rest, f_rest, ivar_rest
def redshift_fullcube(l, f, ivar, z_in, z_out):
data = np.empty_like(
f, dtype=[('l', float), ('f', float), ('ivar', float)])
data['l'] = l
data['f'] = f
data['ivar'] = ivar
rules = [dict(name='l', exponent=+1),
dict(name='f', exponent=-1),
dict(name='ivar', exponent=+2)]
res = slrs(data_in=data, rules=rules, z_in=z_in, z_out=z_out)
return res['l'], res['f'], res['ivar']
def drizzle_spec(lrest, lgrid):
logelrest = np.log(lrest)
loglrest = np.log10(lrest)
logelgrid = np.log(lgrid)
loglgrid = np.log10(lgrid)
dlogel = ut.determine_dlogl(loglgrid)
dlogl = ut.determine_dlogl(loglgrid)
def gen_logl(logl0, dlogl, nl):
logl = logl0 + dlogl * np.array(range(nl))
return logl
def restwithinrect(rect_lhs, rect_rhs, rest_lhs, rest_rhs):
'''
verify that each rectified-grid pixel is contained entirely within
the rest-frame grid
'''
within_lhs = rect_lhs >= rest_lhs.min()
within_rhs = rect_rhs <= rest_rhs.max()
return np.logical_and(within_lhs, within_rhs)
def get_logl_bin_edges(logl, dlogl):
ledges, redges = logl - 0.5 * dlogl, logl + 0.5 * dlogl
return ledges, redges
def find_rest_i0_lhs(grid0_ctr, rest_ctr, dlogl):
'''
find the left-most contributor to a rectified bin: this element's
lhs-edge will be the largest that also is smaller than the corresponding
rect-grid element's lhs-edge
args:
- grid0_ctr: center of first bin in rectified grid
- rest_ctr: center of rest-frame wavelength cube
- dlogl
'''
grid0_l = grid0_ctr - 0.5 * dlogl
rest_l = rest_ctr - 0.5 * dlogl
lt_grid_l = rest_l < grid0_l
rest_ctr_m = np.ma.masked_where(a=rest_ctr, condition=~lt_grid_l)
m, am = rest_ctr_m.max(axis=0), rest_ctr_m.argmax(axis=0)
return m, am
def find_left_fraction(grid0_ctr, rest_ctr, dlogl):
'''
find and return the fraction of each rectified grid bin that is covered by
the nearest rest-frame bin to the left
'''
rest_v0, rest_i0 = find_rest_i0_lhs(grid0_ctr, rest_ctr, dlogl)
grid0_l = grid0_ctr - 0.5 * dlogl
rest_v0_l = rest_v0 - 0.5 * dlogl
f = 1. - (grid0_l - rest_v0_l.data) / dlogl
return f
def covar_offdiag(f_r_or_l):
return f_r_or_l * (1. - f_r_or_l)
def drizzle_flux(grid_ctr, rest_ctr, wave_lin, flux_cube, ivar_cube):
'''
drizzle flux-densities and errors from one large cube into a smaller one.
This is based on Carnall (2017), plus additional assumption
of identical bin-width
args:
- grid_ctr: 1d, monotonic-increasing array of wavelength centers
for rectified grid
- rest_ctr: 3d cube of wavelength centers for perfectly-deredshifted
flux cube
- wave_lin: is wavelength in linear units?
- f_cube: 3d cube of flux-density
- ivar_cube: 3d cube of flux-density inverse-variance
'''
ivar0 = 1.
# transform
if wave_lin:
grid_ctr = np.log10(grid_ctr)
rest_ctr = np.log10(rest_ctr)
mapshape = rest_ctr.shape[1:]
nl_grid = len(grid_ctr)
dlogl = ut.determine_dlogl(grid_ctr)
# retrieve wavelength and index of left contributor to 0th rectified bin
# both are maps
lval_left, li_left = find_rest_i0_lhs(
grid0_ctr=grid_ctr[0], rest_ctr=rest_ctr, dlogl=dlogl)
# fraction of dl contributed by left
frac_left = find_left_fraction(grid_ctr[0], rest_ctr, dlogl)[None, :, :]
frac_right = 1. - frac_left
# construct map indexing arrays
II, JJ = np.meshgrid(*map(range, mapshape), indexing='ij')
II, JJ = II[None, ...], JJ[None, ...]
# all axis-0 indices for left-contributors
LL_all = np.arange(nl_grid + 1, dtype=int)[:, None, None] + \
li_left[None, :, :]
LL_all_split = np.array_split(LL_all, 100, axis=0)
# contributor arrays: we extract all at once because advanced indexing
# copies data, and this means we only have to do it once per flux or ivar
flux_all = np.concatenate([flux_cube[LL_sect, II, JJ]
for LL_sect in LL_all_split], axis=0)
ivar_all = np.concatenate([ivar_cube[LL_sect, II, JJ]
for LL_sect in LL_all_split], axis=0) + ivar0
# contributor arrays, with left and right side changing along axis 0
flux_c = rolling_window(flux_all, nl_grid, axis=0)
ivar_c = rolling_window(ivar_all, nl_grid, axis=0)
fracs = np.stack([frac_left, frac_right])
w = dlogl
# array-wise ops
# NOTE: In Carnall's equations, w_i is the width of the origin wavelength grid
# which can be dropped, since dlogl for origin and destination are both
# uniform and identical. For the sake of being explicit, w is kept.
flux_wtd = (fracs * w * flux_c).sum(axis=0) / (fracs * w).sum(axis=0)
var_wtd = (fracs**2. * w**2. / ivar_c).sum(axis=0) / ((fracs * w).sum(axis=0))**2.
ivar_wtd = 1. / var_wtd
return flux_wtd, ivar_wtd