Move the processing code under pipeline/
Preparing to merge this repository into a combined astrophotography repo. session-scripts/ becomes pipeline/ because the scripts import layout.py from their own directory and must stay together, and because 'pipeline' says what it is rather than how it came about. observing/ stays at the top level: observing plans are not processing code.
This commit is contained in:
parent
653ca103cd
commit
c6299f41ab
53 changed files with 0 additions and 0 deletions
163
pipeline/sb_render_tail.py
Normal file
163
pipeline/sb_render_tail.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
"""Render section of sb_residual.py (imported and executed by it).
|
||||
|
||||
Three views of the same residual, each answering a different question:
|
||||
|
||||
(1) raw residual -- how well does the ellipse model fit?
|
||||
(2) plane-removed residual -- restores the sky pedestal that the
|
||||
stacking plane fit swallowed
|
||||
(3) azimuthal-median-subtracted -- the shell-hunting view. Subtracting
|
||||
the residual's own median as a
|
||||
function of a forces zero mean at
|
||||
every radius, so ONLY azimuthal
|
||||
structure survives. Any perfectly
|
||||
circular feature is removed with it.
|
||||
"""
|
||||
import numpy as np
|
||||
from astropy.io import fits
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.ndimage import gaussian_filter, binary_erosion
|
||||
from sb_common import *
|
||||
|
||||
|
||||
def run(ns):
|
||||
g = ns # namespace dict from sb_residual
|
||||
res, model, a_map = g['res'], g['model'], g['a_map']
|
||||
L, star, dust = g['L'], g['star'], g['dust']
|
||||
XC, YC, PEDL = g['XC'], g['YC'], g['PEDL']
|
||||
A_OUT, tab, binned = g['A_OUT'], g['tab'], g['binned']
|
||||
|
||||
# -- plane removal (restores the sky pedestal absorbed by the stacking fit)
|
||||
yy, xx = np.mgrid[0:3194, 0:4788]
|
||||
fitreg = (a_map > 1450) & ~star & ~dust
|
||||
A = np.c_[np.ones(fitreg.sum()), xx[fitreg].ravel()/1000., yy[fitreg].ravel()/1000.]
|
||||
coef, *_ = np.linalg.lstsq(A, res[fitreg].ravel(), rcond=None)
|
||||
plane = (coef[0] + coef[1]*xx/1000. + coef[2]*yy/1000.).astype(np.float32)
|
||||
print('residual plane removed: %+.2f %+.2f*x/1000 %+.2f*y/1000 ADU/px'
|
||||
% tuple(coef))
|
||||
resf = (res - plane).astype(np.float32)
|
||||
fits.PrimaryHDU(resf).writeto(path('sb-residual-flat.fits'), overwrite=True)
|
||||
del xx, yy, plane, A
|
||||
|
||||
# -- binned maps
|
||||
r4 = binned(resf, star, 4)
|
||||
r16 = binned(resf, star, 16)
|
||||
a16 = binned(a_map, np.zeros_like(star), 16)
|
||||
d16 = binned(dust.astype(np.float32), np.zeros_like(star), 16)
|
||||
|
||||
# -- azimuthal median removal
|
||||
abin = np.geomspace(20, 3000, 80)
|
||||
ibn = np.digitize(a16, abin)
|
||||
azmed = np.full(len(abin)-1, np.nan)
|
||||
for k in range(1, len(abin)):
|
||||
m = (ibn == k) & np.isfinite(r16) & (d16 < 0.3)
|
||||
if m.sum() >= 12:
|
||||
azmed[k-1] = np.median(r16[m])
|
||||
acb = np.sqrt(abin[1:]*abin[:-1])
|
||||
gm = np.isfinite(azmed)
|
||||
resd = r16 - np.interp(a16, acb[gm], azmed[gm])
|
||||
sig = float(np.nanstd(resd[np.isfinite(resd) & (a16 > 1500)]))
|
||||
smd = gaussian_filter(np.nan_to_num(resd), 1.0)
|
||||
smd[~np.isfinite(resd)] = np.nan
|
||||
np.save(path('_resd16.npy'), resd)
|
||||
np.save(path('_a16.npy'), a16)
|
||||
np.save(path('_s16.npy'), np.array([sig]))
|
||||
print('deep-residual noise (16x16 bins, a>1500 px): %.2f ADU/px -> mu %.2f'
|
||||
% (sig, mu(sig)))
|
||||
|
||||
NV, EV = north_east_pixel()
|
||||
CUT = 1560
|
||||
sl = (slice(int(YC)-CUT, int(YC)+CUT), slice(int(XC)-CUT, int(XC)+CUT))
|
||||
ext = [-CUT*PIXSCALE/60, CUT*PIXSCALE/60]*2
|
||||
fullext = [-4788/2*PIXSCALE/60, 4788/2*PIXSCALE/60,
|
||||
-3194/2*PIXSCALE/60, 3194/2*PIXSCALE/60]
|
||||
|
||||
def compass(ax, x=0.885, y=0.115, Ln=0.07, c='k'):
|
||||
for v, lab in [(NV, 'N'), (EV, 'E')]:
|
||||
ax.annotate('', xy=(x+Ln*v[0], y+Ln*v[1]), xytext=(x, y),
|
||||
xycoords='axes fraction', textcoords='axes fraction',
|
||||
arrowprops=dict(arrowstyle='->', color=c, lw=1.4))
|
||||
ax.annotate(lab, xy=(x+1.45*Ln*v[0], y+1.45*Ln*v[1]), color=c,
|
||||
xycoords='axes fraction', ha='center', va='center',
|
||||
fontsize=10)
|
||||
|
||||
def cutb(arr, B):
|
||||
return arr[int((YC-CUT)/B):int((YC+CUT)/B), int((XC-CUT)/B):int((XC+CUT)/B)]
|
||||
|
||||
# ---------------------------------------------------------- 4-panel figure
|
||||
fig, axs = plt.subplots(2, 2, figsize=(15.5, 15.0))
|
||||
axs = axs.ravel()
|
||||
axs[0].imshow(np.arcsinh(np.clip(L[sl]-PEDL, 0, None)/25), origin='lower',
|
||||
cmap='gray', extent=ext)
|
||||
axs[0].set_title('(a) luminance master, arcsinh stretch')
|
||||
axs[1].imshow(np.arcsinh(np.clip(model[sl], 0, None)/25), origin='lower',
|
||||
cmap='gray', extent=ext)
|
||||
axs[1].set_title('(b) smooth elliptical model built from the isophotes')
|
||||
im = axs[2].imshow(cutb(r4, 4), origin='lower', cmap='RdBu_r', vmin=-220,
|
||||
vmax=220, extent=ext)
|
||||
axs[2].set_title('(c) residual, 4x4 binned: the dust lane dominates')
|
||||
plt.colorbar(im, ax=axs[2], fraction=.046, label='ADU/px')
|
||||
im = axs[3].imshow(cutb(smd, 16), origin='lower', cmap='RdBu_r',
|
||||
vmin=-3*sig, vmax=3*sig, extent=ext)
|
||||
axs[3].contour(cutb(d16, 16), levels=[0.5], colors='0.35', linewidths=.8,
|
||||
extent=ext, origin='lower')
|
||||
axs[3].set_title('(d) residual, 16x16 binned, azimuthal median removed, '
|
||||
'+-3 sigma' + chr(10) +
|
||||
'1 sigma = %.2f ADU/px = %.1f mag/arcsec2 '
|
||||
'(grey outline = dust mask)' % (sig, mu(sig)))
|
||||
plt.colorbar(im, ax=axs[3], fraction=.046, label='ADU/px')
|
||||
for a in axs:
|
||||
a.set_xlabel('arcmin')
|
||||
a.set_ylabel('arcmin')
|
||||
compass(a, c='w' if a in (axs[0], axs[1]) else 'k')
|
||||
fig.suptitle('NGC 5128: smooth elliptical model and its residual', fontsize=14)
|
||||
fig.tight_layout()
|
||||
fig.savefig(path('NGC5128-sb-model-residual.png'), dpi=115)
|
||||
plt.close(fig)
|
||||
|
||||
# ------------------------------------------------------- deep single panel
|
||||
fig, ax = plt.subplots(figsize=(13.5, 9.6))
|
||||
im = ax.imshow(smd, origin='lower', cmap='RdBu_r', vmin=-3*sig, vmax=3*sig,
|
||||
extent=fullext)
|
||||
ax.contour(d16, levels=[0.5], colors='0.3', linewidths=.9, extent=fullext,
|
||||
origin='lower')
|
||||
th = np.linspace(0, 2*np.pi, 400)
|
||||
ax.plot(1250*np.cos(th)*PIXSCALE/60, 1000*np.sin(th)*PIXSCALE/60, 'k--',
|
||||
lw=1.1, alpha=.7, label='sky-plane fit exclusion ellipse (1250x1000 px)')
|
||||
ax.plot(A_OUT*np.cos(th)*PIXSCALE/60, A_OUT*0.765*np.sin(th)*PIXSCALE/60,
|
||||
'-', color='0.25', lw=1.1, alpha=.85,
|
||||
label='profile reliability limit, a = %.0f px' % A_OUT)
|
||||
ax.plot([], [], '-', color='0.3', lw=.9, label='dust-lane mask')
|
||||
ax.legend(fontsize=9, loc='lower left', framealpha=.9)
|
||||
plt.colorbar(im, ax=ax, fraction=.035, label='residual [ADU/px]')
|
||||
ax.set_xlabel('arcmin')
|
||||
ax.set_ylabel('arcmin')
|
||||
compass(ax, x=0.945, y=0.84, Ln=0.05)
|
||||
ax.set_title('NGC 5128: isophote model AND the residual azimuthal median '
|
||||
'removed' + chr(10) +
|
||||
'16x16 binned (8.6"/bin), stars masked, +-3 sigma; only '
|
||||
'azimuthal structure survives. 1 sigma = %.2f ADU/px = '
|
||||
'%.1f mag/arcsec2' % (sig, mu(sig)))
|
||||
fig.tight_layout()
|
||||
fig.savefig(path('NGC5128-sb-residual-deep.png'), dpi=125)
|
||||
plt.close(fig)
|
||||
|
||||
# ------------------------------------------------------ quantify structure
|
||||
print('')
|
||||
print('azimuthal residual statistics (16x16 bins, azimuthal median removed,')
|
||||
print('dust-lane bins excluded):')
|
||||
ok = np.isfinite(resd) & (d16 < 0.3)
|
||||
for lo, hi in [(100, 200), (200, 400), (400, 600), (600, 800), (800, 1000),
|
||||
(1000, 1250), (1250, 1600), (1600, 2200)]:
|
||||
m = ok & (a16 >= lo) & (a16 < hi)
|
||||
if m.sum() < 20:
|
||||
continue
|
||||
md = np.interp(np.clip(a16[m], tab['sma'][0], tab['sma'][-1]),
|
||||
tab['sma'], tab['intens'])
|
||||
print(' a=%4d-%4d px (%4.1f-%4.1f arcmin): rms %6.2f ADU/px = %4.1f%% '
|
||||
'of the model, max |dev| %4.1f sigma, n=%d'
|
||||
% (lo, hi, lo*PIXSCALE/60, hi*PIXSCALE/60, np.nanstd(resd[m]),
|
||||
100*np.nanstd(resd[m])/np.mean(md),
|
||||
np.nanmax(np.abs(resd[m]))/sig, m.sum()))
|
||||
print('')
|
||||
print('wrote sb-model.fits, sb-residual.fits, sb-residual-flat.fits,')
|
||||
print(' NGC5128-sb-model-residual.png, NGC5128-sb-residual-deep.png')
|
||||
Loading…
Add table
Add a link
Reference in a new issue