DJA NIRSpec MSA Extractions v2

spectroscopy nirspec release

(This page is auto-generated from the Jupyter notebook nirspec-extractions-v2.ipynb.)

We have reprocessed all of the public NIRSpec datasets from the earlier DJA release with the latest jwst pipeline and calibration files and updated msaexp v0.7 release.

Some of the major differences with respect to the previous reductions are as follows:

  • Improved initial Level1 processing with snowballs identified and masked in individual exposure groups using the snowblind module. The uncal.fits files are downloaded from MAST and processed automatically with grizli.aws.recalibrate.do_recalibrate.
  • The 2D extraction and rectification is handled in msaexp.slit_combine. The main algorithmic difference of the v2 extractions is that the background is removed by taking differences of the original 2D slitlet cutouts before drizzle resampling, i.e., differences between the cross-dispersion nods.
  • Then the final spectra are combined with a 2D weighted nearest-neighbor resampling to rectify them along rows of the 2D array, but where all wavelength bins are fully independent, i.e., no correlated noise from drizzling.
  • The 2D profile for the optimal extraction is also determined along the curved traces of the original spectral cutouts and rebinned/rectified in the same way as the data. The final 1D spectrum is the optimal extraction using this profile.
  • msaexp now has its own path loss correction that uses the width of the fitted profile along with the predicted intra-shutter position. The final spectra tend to lie much closer to the scale set by the photometry than the previous versions, though for many applications you’d probably still want to scale to whatever photometry yourself.
  • For programs that obtain spectra of a particular source with multiple dispersers (e.g., prism and gratings), the centering and source width of the optimal extraction profile is determined from the spectrum with the highest median S/N (usually the prism) and extractions in the other gratings are forced with those profile parameters.

Data release

The compilation of extracted spectra and redshift measurements is provided at nirspec_graded_v2.html.

The redshift quality grades have been copied from those of the previous release where the redshift measurements themselves agree to within tight tolerances, and extractions from any new programs since the first release were visually inspected and graded.

Note

The v2 spectra are affected by a bug that causes the uncertainties in the derived products to be too large by roughly a factor of f = N^{1/4}, where N is the number of combined exposures grouped by source_name / detector / grating / filter / MSA plan. For a simple set of three exposures with the 3-Shutter-Nod dither pattern, f = 3^{1/4} ~ 1.3, i.e., the tabulated uncertainties are roughly 1.3× too large. The effect of the bug is larger for deeper programs; for typical UNCOVER extractions f = 18^{1/4} ~ 2. For more information, see msaexp PR#54.

Observing programs

Program DJA root N Grating
2756 abell2744-ddt-v2 118 PRISM-CLEAR
1810 bluejay-north-v2 519 G140M-F100LP
  bluejay-south-v2   G235M-F170LP
      G395M-F290LP
1747 borg-0314m6712-v2 205 PRISM-CLEAR
  borg-0859p4114-v2    
  borg-1033p5051-v2    
  borg-1437p5044-v2    
  borg-2203p1851-v2    
2750 ceers-ddt-v2 251 PRISM-CLEAR
1345 ceers-v2 2236 G140M-F100LP
      G235M-F170LP
      G395M-F290LP
      PRISM-CLEAR
1871 gdn-chisholm-v2 44 G235H-F170LP
      G395H-F290LP
2198 gds-barrufet-s156-v2 137 PRISM-CLEAR
  gds-barrufet-s67-v2    
1210 gds-deep-v2 1219 G140M-F070LP
      G235M-F170LP
      G395H-F290LP
      G395M-F290LP
      PRISM-CLEAR
6541 gds-egami-ddt-v2 345 PRISM-CLEAR
3215 gds-udeep-v2 821 G140M-F070LP
      G395M-F290LP
      PRISM-CLEAR
2565 glazebrook-cos-obs2-v2 466 PRISM-CLEAR
  glazebrook-cos-obs3-v2    
  glazebrook-v2    
1211 goodsn-wide-v2 186 PRISM-CLEAR
1181 jades-gdn2-v2 4176 G140M-F070LP
  jades-gdn-v2   G235M-F170LP
      G395H-F290LP
      G395M-F290LP
      PRISM-CLEAR
1286 jades-gds1-v2 916 G140M-F070LP
      G235M-F170LP
      G395H-F290LP
      G395M-F290LP
      PRISM-CLEAR
1180 jades-gds-wide2-v2 4779 G140M-F070LP
  jades-gds-wide-v2   G235M-F170LP
      G395M-F290LP
      PRISM-CLEAR
4246 macsj0647-hr-v2 44 G395H-F290LP
1433 macsj0647-v2 140 PRISM-CLEAR
4557 pearls-transients-v2 210 PRISM-CLEAR
4233 rubies-uds1-v2 1480 G395M-F290LP
  rubies-uds2-v2   PRISM-CLEAR
  rubies-uds3-v2    
2767 rxj2129-ddt-v2 241 G140M-F070LP
      G140M-F100LP
      PRISM-CLEAR
2736 smacs0723-ero-v2 102 G235M-F170LP
      G395M-F290LP
4446 snh0pe-v2 116 G140M-F100LP
      G235M-F170LP
      PRISM-CLEAR
2110 suspense-kriek-v2 45 G140M-F100LP
2561 uncover-v2 766 PRISM-CLEAR
import os
if os.path.exists('gbrammer' in os.environ['HOME']) & False:
    
    # Extract summary from database
    from grizli.aws import db
    import pyperclip

    # db queries require DB credentials
    nre = db.SQL("""select SUBSTR(MIN(dataset), 4, 4) as pid,
                           array_agg(DISTINCT(root)) as root,
                           count(*) as N,
                           array_agg(DISTINCT(grating || '-' || filter)) as Grating
    FROM nirspec_extractions WHERE ROOT LIKE '%%v2' AND dataset not like 'jw01208%%'
    GROUP BY SUBSTR(dataset, 4, 4)
    ORDER BY MAX(root)""")
    
    nre['root'] = ['\n'.join(r) for r in nre['root']]
    nre['grating'] = ['\n'.join(r) for r in nre['grating']]
    nre.rename_column('root','DJA root')
    url = '[{pid}](https://www.stsci.edu/cgi-bin/get-proposal-info?id={pid}&observatory=JWST)'
    prog = [url.format(**row) for row in nre]
    nre['Program'] = prog
    
    pyperclip.copy('## Observing programs\n\n' +
                   nre['Program','DJA root','n','grating'].to_pandas(index=False).to_markdown(index=False))
%matplotlib inline

import os
import yaml

import numpy as np
import matplotlib.pyplot as plt

import warnings
warnings.filterwarnings('ignore')

import astropy.io.fits as pyfits

import grizli
import grizli.catalog
from grizli import utils

import eazy
import msaexp
import msaexp.spectrum

print(f'grizli version: {grizli.__version__}')
print(f'eazy-py version: {eazy.__version__}')
print(f'msaexp version: {msaexp.__version__}')
grizli version: 1.11.3.dev2+gd57387c.d20240222
eazy-py version: 0.6.8.dev1+g3fb0ad2.d20240129
msaexp version: 0.7.3.dev0+g3e012ec.d20240222

Compare v1 and v2

  • The v2 extractions are generally cleaner with fewer non-Gaussian outliers
  • The normalization of the v2 extractions is generally both “brighter” and “redder” as a result of the internal wavelength-dependent path loss correction
z = 3.7616 
_prefix = 'https://s3.amazonaws.com/msaexp-nirspec/extractions/'
v2_file = _prefix + 'ceers-ddt-v2/ceers-ddt-v2_prism-clear_2750_1598.spec.fits'

v1_file = v2_file.replace('ceers-v2','ceers-lr-v1').replace('-v2','-v1')
print(v1_file + '\n' + v2_file)

sp1 = msaexp.spectrum.SpectrumSampler(v1_file)
sp2 = msaexp.spectrum.SpectrumSampler(v2_file)

f1 = sp1.drizzled_hdu_figure(z=z, unit='fnu')
f2 = sp2.drizzled_hdu_figure(z=z, unit='fnu')
https://s3.amazonaws.com/msaexp-nirspec/extractions/ceers-ddt-v1/ceers-ddt-v1_prism-clear_2750_1598.spec.fits
https://s3.amazonaws.com/msaexp-nirspec/extractions/ceers-ddt-v2/ceers-ddt-v2_prism-clear_2750_1598.spec.fits

import msaexp.slit_combine
from msaexp.slit_combine import slit_prf_fraction

fig, axes = plt.subplots(2,1,figsize=(8,4), sharex=True)

ax = axes[0]
ax.plot(sp1['wave'], sp1['flux'], label='v1')
ax.plot(sp2['wave'], sp2['flux'], label='v2')
leg = ax.legend(loc='upper left')
leg.set_title(sp2.meta['SRCNAME'])
ax.grid()

# Ratios
axes[1].plot(sp2['wave'], sp2['flux'] / sp1['flux'], label=r'v2 / v1', color='pink')

path_corr = slit_prf_fraction(sp2.spec['wave'].astype(float),
                              sigma=sp2.spec.meta['SIGMA'],
                              x_pos=sp2.spec.meta['SRCXPOS'],
                              slit_width=0.2,
                              pixel_scale=msaexp.slit_combine.PIX_SCALE,
                              verbose=False)

axes[1].plot(sp2.spec['wave'], 1./path_corr, color='plum', label='v2 path loss correction')
axes[1].grid()
axes[1].legend(loc='upper left')

fig.tight_layout(pad=1)

Compare gratings

All v2 extractions with different gratings for a particular source + program now have the same root, rather than being split in some cases. For example, all of the CEERS (ERS-1345) spectra now have root = ceers-v2 where they were split between the prisms (ceers-lr-v1) and gratings (ceers-mr-v1) before.

Note: There remains an msaexp bug that causes the tick intervals on the automatic grating figures plotted below to look strange. The minor ticks are evenly spaced in 0.05µm intervals, but the labels are rounded to a single decimal place (e.g., 3.75 becomes 3.8).

z = 2.0611
_prefix = 'https://s3.amazonaws.com/msaexp-nirspec/extractions/'
prism_file = _prefix + 'ceers-v2/ceers-v2_prism-clear_1345_3506.spec.fits'

sp = {}
for grating in ['prism-clear','g140m-f100lp','g235m-f170lp','g395m-f290lp']:
    file = prism_file.replace('prism-clear',grating)
    print(file)
    sp[grating] = msaexp.spectrum.SpectrumSampler(file)

    fig = sp[grating].drizzled_hdu_figure(z=z, unit='fnu')
https://s3.amazonaws.com/msaexp-nirspec/extractions/ceers-v2/ceers-v2_prism-clear_1345_3506.spec.fits
https://s3.amazonaws.com/msaexp-nirspec/extractions/ceers-v2/ceers-v2_g140m-f100lp_1345_3506.spec.fits
https://s3.amazonaws.com/msaexp-nirspec/extractions/ceers-v2/ceers-v2_g235m-f170lp_1345_3506.spec.fits
https://s3.amazonaws.com/msaexp-nirspec/extractions/ceers-v2/ceers-v2_g395m-f290lp_1345_3506.spec.fits

fig, axes = plt.subplots(4,1,figsize=(8,8))

colors = ['0.2','steelblue','orange','tomato']

for i, grating in enumerate(sp):
    sp[grating]['flux'][~sp[grating].valid] = np.nan
    for ax in axes:
        ax.plot(sp[grating]['wave'], sp[grating]['flux'], alpha=0.5, label=grating, color=colors[i])

leg = axes[3].legend(ncol=4, fontsize=8)
axes[0].set_title(sp[grating].meta['SRCNAME'])

axes[0].set_xlim(0.9, 1.9); axes[0].set_ylim(-1.2, 12)
axes[1].set_xlim(1.85, 3.2); axes[1].set_ylim(-0.5, 5)
axes[2].set_xlim(2.95, 5.3); axes[2].set_ylim(-0.8, 8)
axes[3].set_ylim(-3,30)
axes[3].set_xlabel(r'$\lambda_\mathrm{obs}~[\mu\mathrm{m}]$')

for ax in axes:
    ax.grid()
    ax.set_ylabel(r'$f_\nu~[\mu\mathrm{Jy}]$')

fig.tight_layout(pad=1)