WFSS 光谱 第三部分:发射线图谱#

用例: 最优提取光谱;红移测量;发射线图。简化版的 JDox 科学用例 # 33

数据: JWST 模拟的 NIRISS 图像来自 MIRAGE,经过 JWST 校准管道 处理;星系团。

工具: specutils, astropy, pandas, emcee, lmfit, corner, h5py。

跨仪器: NIRSpec

文档: 本笔记本是 STScI 更大 后处理数据分析工具生态系统 的一部分。

引言#

本笔记本是关于 NIRISS WFSS 数据的一组 4 个笔记本中的第 4 个:

  1. 1D 最优提取,因为 JWST 管道仅提供框提取。最优提取提高了微弱源光谱的信噪比(S/N)。

  2. 合并和归一化 1D 光谱。

  3. 将星系与模板进行交叉关联以获取红移。

  4. 空间分辨的发射线图。

本笔记本从 NIRISS WFSS 光谱的 2D 剪切图中获取发射线图。本笔记本将需要 NIRISS WFSS 光谱的 2D 剪切图(例如,l3_nis_f150w_G150C_s00004_cal.fits;管道 spec3 产品)。

注意: 在这个例子中,笔记本仅使用在一个抖动位置直接从当前版本的管道(构建 7.5)获得的 2D 矫正光谱,但也可以从所有抖动位置堆叠的光谱开始,以提高信噪比。

%matplotlib inline  # 在Jupyter Notebook中内联显示Matplotlib图形
import os  # 导入操作系统模块,用于文件和目录操作

import numpy as np  # 导入NumPy库,用于数值计算和数组操作

from astropy.io import fits  # 从Astropy库导入FITS模块,用于处理FITS文件

import astropy.units as u  # 导入Astropy单位模块,用于物理量的单位处理

from astropy.modeling import models  # 从Astropy建模模块导入模型类

from astropy.modeling.polynomial import Chebyshev1D  # 导入一维切比雪夫多项式模型

from specutils.spectra import Spectrum1D  # 从specutils库导入一维光谱类

from specutils.fitting import fit_lines  # 从specutils库导入拟合谱线的函数

from specutils.fitting import continuum  # 从specutils库导入连续谱拟合的函数

from astropy import __version__ as astropy_version  # 获取Astropy库的版本信息

print('astropy', astropy_version)  # 打印Astropy库的版本信息
import matplotlib.pyplot as plt  # 导入绘图库 matplotlib 的 pyplot 模块

import matplotlib as mpl  # 导入 matplotlib 库

# 设置保存图像时的分辨率为 80 dpi
mpl.rcParams['savefig.dpi'] = 80  

# 设置图像显示时的分辨率为 80 dpi
mpl.rcParams['figure.dpi'] = 80

0. 下载笔记本 01 产品#

这些也可以通过运行笔记本来获取。

import os  # 导入os模块,用于文件和目录操作

# 检查输出目录是否存在
if not os.path.exists('./output'):
    
    import zipfile  # 导入zipfile模块,用于处理zip文件
    import urllib.request  # 导入urllib.request模块,用于下载文件

    # 设置要下载的zip文件的链接
    boxlink = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/NIRISS_lensing_cluster/output.zip'
    
    # 设置下载后保存的文件名
    boxfile = './output.zip'
    
    # 下载zip文件
    urllib.request.urlretrieve(boxlink, boxfile)
    
    # 打开下载的zip文件
    zf = zipfile.ZipFile(boxfile, 'r')
    
    # 获取zip文件内所有文件的名称列表
    list_names = zf.namelist()
    
    # 遍历文件名称列表
    for item in list_names:
        # 提取zip文件中的每个文件,使用extract比extractall更安全
        zf.extract(member=item)  # 提取文件

else:
    # 如果输出目录已存在,打印提示信息
    print('Already exists')
# 下载文件,如果尚不存在

if not os.path.exists('./pipeline_products'):  # 检查目录是否存在

    import zipfile  # 导入zipfile模块,用于处理zip文件
    import urllib.request  # 导入urllib.request模块,用于下载文件

    # 定义要下载的zip文件的链接
    boxlink = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/NIRISS_lensing_cluster/pipeline_products.zip'

    # 定义下载后保存的文件名
    boxfile = './pipeline_products.zip'

    # 下载zip文件
    urllib.request.urlretrieve(boxlink, boxfile)

    # 打开下载的zip文件
    zf = zipfile.ZipFile(boxfile, 'r')

    # 获取zip文件内的文件名列表
    list_names = zf.namelist()

    # 遍历文件名列表,逐个提取文件
    for item in list_names:
        zf.extract(member=item)  # 使用extract提取文件,避免使用extractall以防止文件路径问题
# 选择目标

DIR_OUT = './output/'  # 输出目录

filt = 'f200w'  # 选择的滤光片

grism = 'G150C'  # 选择的光栅

# grism = 'G150R'  # 可选的另一种光栅

id = '00004'  # 目标ID

1. 获取Hα发射线图;#

# 打开二维文件;

# 可以在 No.01a 下载。

DIR_DATA = './pipeline_products/'  # 数据目录

# 零索引的dither编号 --- 测试数据有两个dither位置。

ndither = 0

# 文件名

file_2d = f'{DIR_DATA}l3_nis_{filt}_{grism}_s{id}_cal.fits'  # 构建文件路径

hdu_2d = fits.open(file_2d)  # 打开FITS文件

# 对齐光栅方向

#   - x方向 = 色散(波长)方向。

#   - y方向 = 交叉色散。

# 在这个笔记本中。
    

if grism == 'G150C':  # 如果光栅是G150C

    # 如果光谱是水平的;

    data_2d = hdu_2d[ndither*7+1].data  # 获取二维数据

    dq_2d = hdu_2d[ndither*7+2].data  # 获取数据质量数组

    err_2d = hdu_2d[ndither*7+3].data  # 获取误差数组

    wave_2d = hdu_2d[ndither*7+4].data  # 获取波长数组

else:  # 如果光栅不是G150C

    data_2d = rotate(hdu_2d[ndither*7+1].data, 90)  # 将数据旋转90度

    dq_2d = rotate(hdu_2d[ndither*7+2].data, 90)  # 将数据质量数组旋转90度

    err_2d = rotate(hdu_2d[ndither*7+3].data, 90)  # 将误差数组旋转90度

    wave_2d = rotate(hdu_2d[ndither*7+4].data, 90)  # 将波长数组旋转90度
plt.imshow(data_2d[:, :])  # 显示二维数据数组的图像

在每个y位置减去连续背景:#

根据源的亮度,有两种选择;

    1. 在每个交叉色散列的位置减去估计的连续背景。这适用于亮源。

    1. 减去一个单一的连续背景(“主连续背景”),假设其形状在交叉色散方向上与一维提取的形状相同。这对于暗源是必需的。

由于我们清楚地看到源的连续背景,这里我们演示选项1。

# 我们希望在y位置查看光谱

# 作为示例;
yy = 10  # 设置y位置为10

spec_unit = u.dimensionless_unscaled  # 定义光谱单位为无量纲

# 创建掩膜,选择特定波长范围内的数据
mask_line = ((wave_2d[yy, :] > 1.75) & (wave_2d[yy, :] < 1.97)) | ((wave_2d[yy, :] > 2.08) & (wave_2d[yy, :] < 2.23))

# 创建光谱对象,包含波长和对应的flux数据
obs = Spectrum1D(spectral_axis=wave_2d[yy, :][mask_line]*u.um, flux=data_2d[yy, :][mask_line]*spec_unit)

# 拟合连续谱,使用7阶切比雪夫多项式模型
cont = continuum.fit_generic_continuum(obs, model=Chebyshev1D(7))

# 绘制观察到的光谱数据
plt.plot(wave_2d[yy, :], data_2d[yy, :], color='r', label=f'Observed at y={yy}')

# 绘制拟合的连续谱
plt.plot(wave_2d[yy, :][mask_line]*u.um, cont(wave_2d[yy, :][mask_line]*u.um), color='b', label='Fitted continuum')

# 添加图例
plt.legend(loc=0)

# 设置x轴范围
plt.xlim(1.6, 2.3)
# 计算并返回经过掩膜处理的二维波长数据的值,单位为微米
cont(wave_2d[yy, :][mask_line]*u.um).value
# 沿y轴重复此操作;

flux_cont2d = data_2d[:, :] * 0  # 初始化一个与data_2d相同形状的数组,所有元素为0

for yy in range(len(data_2d[:, 0])):  # 遍历data_2d的每一行(每个y坐标)

    # 创建掩膜,选择波长在1.75到1.97和2.08到2.23之间的元素
    mask_line = ((wave_2d[yy, :] > 1.75) & (wave_2d[yy, :] < 1.97)) | ((wave_2d[yy, :] > 2.08) & (wave_2d[yy, :] < 2.23))

    # 创建一个Spectrum1D对象,包含选定波长范围内的光谱数据
    obs = Spectrum1D(spectral_axis=wave_2d[yy, :][mask_line]*u.um, flux=data_2d[yy, :][mask_line]*spec_unit)

    # 使用Chebyshev1D模型拟合连续谱
    cont = continuum.fit_generic_continuum(obs, model=Chebyshev1D(7))

    # 将拟合的连续谱值存储到flux_cont2d的相应行
    flux_cont2d[yy, :] = cont(wave_2d[yy, :]*u.um).value
plt.imshow(flux_cont2d[:, :])  # 显示二维光谱数据的图像

plt.title('Fitted continuum')  # 设置图像标题为“拟合的连续谱”
plt.imshow(data_2d[:, :] - flux_cont2d[:, :])  # 显示二维数据图像,减去连续谱数据

plt.title('Continuum subtracted spectrum')  # 设置图像标题为“减去连续谱的光谱”

从去除连续谱的光谱中提取Hα发射;#

# 切割Ha线图

rsq = data_2d.shape[0]  # 获取数据的行数,假设为正方形

cut_ha = np.zeros((rsq, rsq), 'float32')  # 创建一个全零的数组,用于存储切割后的Ha图

zin = 2.1  # 从Notebook No.3中获取的红移值
lamcen = 0.6564 * (1. + zin)  # 计算中心波长,单位为微米

for yy in range(len(data_2d[:, 0])):  # 遍历每一行(每个y像素)

    # 由于波长数组可能倾斜,因此必须在每个y像素上执行此操作
    index_lamcen = np.argmin(np.abs(lamcen - wave_2d[yy, :]))  # 找到与中心波长最接近的波长索引

    # 从数据中减去连续光通量,并切割出相应的Ha线数据
    cut_ha[yy, :] = (data_2d - flux_cont2d)[yy, int(index_lamcen - rsq / 2.):int(index_lamcen + rsq / 2.)]

plt.imshow(cut_ha)  # 显示切割后的Ha图
plt.title('H$\\alpha$ map')  # 设置图表标题为Hα图

2. 获取Hβ和OIII图#

这更具挑战性,因为这些谱线彼此靠得很近。理想情况下,应该优先考虑迭代过程,但在这里我们使用Specutils的双高斯成分拟合,方法与Hα相似。

filt = 'f150w'  # 定义滤波器类型为'f150w'

file_2d = f'{DIR_DATA}l3_nis_{filt}_{grism}_s{id}_cal.fits'  # 构建2D数据文件路径

hdu_2d = fits.open(file_2d)  # 打开FITS文件并读取数据

# 对于光栅方向进行对齐
#   - x方向 = 色散(波长)方向
#   - y方向 = 横向色散
# 在这个笔记本中进行处理

if grism == 'G150C':  # 如果光栅类型为'G150C'
    # 如果光谱是水平的;
    data_2d = hdu_2d[ndither*7+1].data  # 读取数据部分
    dq_2d = hdu_2d[ndither*7+2].data  # 读取数据质量部分
    err_2d = hdu_2d[ndither*7+3].data  # 读取误差部分
    wave_2d = hdu_2d[ndither*7+4].data  # 读取波长部分
else:  # 如果光栅类型不是'G150C'
    data_2d = rotate(hdu_2d[ndither*7+1].data, 90)  # 将数据旋转90度
    dq_2d = rotate(hdu_2d[ndither*7+2].data, 90)  # 将数据质量部分旋转90度
    err_2d = rotate(hdu_2d[ndither*7+3].data, 90)  # 将误差部分旋转90度
    wave_2d = rotate(hdu_2d[ndither*7+4].data, 90)  # 将波长部分旋转90度

# !! 注意提取的光谱波长方向已翻转 !!
plt.imshow(data_2d[:, ::-1])  # 显示数据图像,反转列以修正波长方向
plt.title(f'{filt}')  # 设置图像标题为滤波器类型

在上面的图中,您可以看到Oiii双线和Hbeta。#

2a. 获取连续谱;#

yy = 10  # 示例行,选择y轴的索引为10

spec_unit = u.dimensionless_unscaled  # 设置光谱单位为无量纲

# 创建掩膜,选择特定波长范围内的线
mask_line = ((wave_2d[yy, :] > 1.35) & (wave_2d[yy, :] < 1.48)) | ((wave_2d[yy, :] > 1.6) & (wave_2d[yy, :] < 1.65))

# 使用掩膜选择的波长和数据创建光谱对象
obs = Spectrum1D(spectral_axis=wave_2d[yy, :][mask_line]*u.um, flux=data_2d[yy, :][mask_line]*spec_unit)

# 拟合连续谱,使用7阶切比雪夫多项式模型
cont = continuum.fit_generic_continuum(obs, model=Chebyshev1D(7))

# 绘制观测数据,y轴为yy的索引
plt.plot(wave_2d[yy, :], data_2d[yy, :], color='r', label=f'Observed at y={yy}')

# 绘制拟合的连续谱
plt.plot(wave_2d[yy, :][mask_line]*u.um, cont(wave_2d[yy, :][mask_line]*u.um), color='b', label='Fitted continuum')

# 添加图例
plt.legend(loc=0)

# 设置x轴范围
plt.xlim(1.2, 1.8)
# 沿y轴重复此操作;

flux_cont2d_150 = data_2d[:, :] * 0  # 初始化一个与data_2d相同形状的数组,所有元素为0

for yy in range(len(data_2d[:, 0])):  # 遍历data_2d的每一行(即每个y坐标)

    # 创建一个掩膜,选择波长在1.35到1.48微米和1.6到1.65微米之间的元素
    mask_line = ((wave_2d[yy, :] > 1.35) & (wave_2d[yy, :] < 1.48)) | ((wave_2d[yy, :] > 1.6) & (wave_2d[yy, :] < 1.65))

    # 创建一个Spectrum1D对象,包含选定波长范围内的光谱数据
    obs = Spectrum1D(spectral_axis=wave_2d[yy, :][mask_line]*u.um, flux=data_2d[yy, :][mask_line]*spec_unit)

    # 使用Chebyshev1D模型拟合光谱的连续背景
    cont = continuum.fit_generic_continuum(obs, model=Chebyshev1D(7))

    # 将拟合的连续背景值存储到flux_cont2d_150的相应行中
    flux_cont2d_150[yy, :] = cont(wave_2d[yy, :]*u.um).value

# 使用imshow函数显示flux_cont2d_150的图像,y轴翻转
plt.imshow(flux_cont2d_150[:, ::-1])
line_2d = data_2d[:, :] - flux_cont2d_150[:, :]  # 从二维数据中减去150波段的连续光通量

plt.imshow(line_2d[:, ::-1])  # 显示反转后的二维数据图像

2b. 拟合发射线,使用多组分高斯函数;#

yy = 10  # 设置要处理的光谱行索引

# 拟合光谱

con = (1.4 < wave_2d[yy, :]) & (wave_2d[yy, :] < 1.65)  # 选择波长范围在1.4到1.65微米之间的像素

spectrum_cut = Spectrum1D(flux=line_2d[yy, :][con]*spec_unit,  # 创建一个Spectrum1D对象,包含选定波长范围内的光谱数据
                          spectral_axis=wave_2d[yy, :][con]*u.um)  # 设置光谱轴为选定波长范围

# !!! 可能需要对初始值进行一些调整,以成功运行拟合;

# 对于Hb(氢β线)

g1_init = models.Gaussian1D(amplitude=100*spec_unit, mean=1.51*u.um, stddev=0.009*u.um)  # 初始化Hb的高斯模型

# 对于O3蓝(氧III蓝线)

g2_init = models.Gaussian1D(amplitude=80.*spec_unit, mean=1.53*u.um, stddev=0.006*u.um)  # 初始化O3蓝的高斯模型

# 对于O3红(氧III红线)

g3_init = models.Gaussian1D(amplitude=200.*spec_unit, mean=1.55*u.um, stddev=0.006*u.um)  # 初始化O3红的高斯模型

g123_fit = fit_lines(spectrum_cut, g1_init+g2_init+g3_init)  # 拟合组合的高斯模型到光谱数据
y_fit = g123_fit(wave_2d[yy, :]*u.um)  # 计算拟合后的光谱值

print(g123_fit)  # 输出拟合结果
# 单独绘制图形?

plt.plot(wave_2d[yy, :], line_2d[yy, :], marker='.', ls='', color='r', label=f'Observed at y={yy}')  # 绘制观测数据,y轴位置为yy,红色点标记

plt.plot(wave_2d[yy, :], y_fit, color='b', label='Fit', zorder=-2, alpha=0.4, lw=5)  # 绘制拟合曲线,蓝色,透明度0.4,线宽5

y_fit1 = g123_fit[0](wave_2d[yy, :]*u.um)  # 计算第一个拟合函数的值

plt.plot(wave_2d[yy, :], y_fit1, color='g', label='1')  # 绘制第一个拟合曲线,绿色

y_fit1 = g123_fit[1](wave_2d[yy, :]*u.um)  # 计算第二个拟合函数的值

plt.plot(wave_2d[yy, :], y_fit1, color='orange', label='2')  # 绘制第二个拟合曲线,橙色

y_fit1 = g123_fit[2](wave_2d[yy, :]*u.um)  # 计算第三个拟合函数的值

plt.plot(wave_2d[yy, :], y_fit1, color='purple', label='3')  # 绘制第三个拟合曲线,紫色

plt.xlim(1.4, 1.6)  # 设置x轴范围为1.4到1.6

plt.title('Single fit peak')  # 设置图表标题

plt.grid(True)  # 显示网格

plt.legend(loc=2)  # 显示图例,位置在左上角
# 打印g123_fit列表中的第一个元素
print(g123_fit[0])  

# 打印g123_fit列表中的第二个元素
print(g123_fit[1])  

# 打印g123_fit列表中的第三个元素
print(g123_fit[2])

拟合中央阵列效果良好。沿y轴重复此操作,并获取发射线图,方式与Hα相同。#

# 切割Hb和Oiii线图

rsq = data_2d.shape[0]  # 获取数据的维度

cut_hb = np.zeros((rsq, rsq), 'float32')  # 初始化Hb切割图像数组

cut_o3b = np.zeros((rsq, rsq), 'float32')  # 初始化O3蓝切割图像数组

cut_o3r = np.zeros((rsq, rsq), 'float32')  # 初始化O3红切割图像数组

zin = 2.1  # 从Notebook No.2获得的红移估计值,通过交叉相关得到

lamcen_hb = 0.4862680 * (1. + zin)  # 计算Hb线的中心波长

lamcen_o3b = 0.4960295 * (1. + zin)  # 计算O3蓝线的中心波长

lamcen_o3r = 0.5008240 * (1. + zin)  # 计算O3红线的中心波长

for yy in range(len(data_2d[:, 0])):  # 遍历每一行数据

    # 拟合光谱

    con = (1.4 < wave_2d[yy, :]) & (wave_2d[yy, :] < 1.65)  # 选择波长范围

    spectrum_cut = Spectrum1D(flux=line_2d[yy, :][con]*spec_unit,  # 创建光谱对象
                              spectral_axis=wave_2d[yy, :][con]*u.um)  # 设置光谱轴

    # !!! 可能需要对初始值进行一些调整,以成功运行拟合

    # 对于Hb线

    g1_init = models.Gaussian1D(amplitude=100*spec_unit, mean=1.51*u.um, stddev=0.009*u.um)  # 初始化Hb高斯模型

    # 对于O3蓝线

    g2_init = models.Gaussian1D(amplitude=80.*spec_unit, mean=1.53*u.um, stddev=0.006*u.um)  # 初始化O3蓝高斯模型

    # 对于O3红线

    g3_init = models.Gaussian1D(amplitude=200.*spec_unit, mean=1.55*u.um, stddev=0.006*u.um)  # 初始化O3红高斯模型

    g123_fit = fit_lines(spectrum_cut, g1_init+g2_init+g3_init)  # 拟合所有高斯模型

    y_fit = g123_fit(wave_2d[yy, :]*u.um)  # 计算拟合结果

    # 这必须在每个y像素上进行,因为波长数组可能会倾斜

    index_lamcen_hb = np.argmin(np.abs(lamcen_hb - wave_2d[yy, :]))  # 找到Hb中心波长的索引

    cut_hb[yy, :] = g123_fit[0](wave_2d[yy, :]*u.um)[int(index_lamcen_hb-rsq/2.):int(index_lamcen_hb+rsq/2.)]  # 提取Hb切割图像

    index_lamcen_o3b = np.argmin(np.abs(lamcen_o3b - wave_2d[yy, :]))  # 找到O3蓝中心波长的索引

    cut_o3b[yy, :] = g123_fit[1](wave_2d[yy, :]*u.um)[int(index_lamcen_o3b-rsq/2.):int(index_lamcen_o3b+rsq/2.)]  # 提取O3蓝切割图像

    index_lamcen_o3r = np.argmin(np.abs(lamcen_o3r - wave_2d[yy, :]))  # 找到O3红中心波长的索引

    cut_o3r[yy, :] = g123_fit[2](wave_2d[yy, :]*u.um)[int(index_lamcen_o3r-rsq/2.):int(index_lamcen_o3r+rsq/2.)]  # 提取O3红切割图像
plt.imshow(cut_hb)  # 显示切割后的Hβ图像

plt.title('H$\\beta$ map')  # 设置图像标题为Hβ图
plt.imshow(cut_o3b)  # 显示Oiii 4960的图像数据

plt.title('Oiii 4960')  # 设置图像标题为'Oiii 4960'
plt.imshow(cut_o3r)  # 显示切割后的Oiii 5008图像

plt.title('Oiii 5008')  # 设置图像标题为'Oiii 5008'

摘要;#

如上所示,除了中心区域以外的区域看起来不太对。 这主要是由于多组分拟合失败,特别是对于Oiii双线。

为了改善拟合,可以采取以下措施:

  • 在每个y轴上反复拟合并检查拟合,直到收敛为止,

  • 使用MCMC进行更深入的拟合,这也可以通过设置先验来固定两个Oiii线的比率。

  • 或者减少组分的数量,特别是在源位置边缘的Oiii双线区域,那里线条是混合的。