WFSS光谱 - 第2部分:交叉相关以确定红移#
用例: 最优提取光谱;红移测量;发射线图。简化版的 JDox 科学用例 # 33。
数据: JWST 模拟的 NIRISS 图像来自 MIRAGE,经过 JWST 校准管道 处理;星系团。
工具: specutils, astropy, pandas, emcee, lmfit, corner, h5py。
跨仪器: NIRSpec
文档: 本笔记本是 STScI 更大 后处理数据分析工具生态系统 的一部分。
引言#
本笔记本是关于 NIRISS WFSS 数据的一组中的第 3 个,共 4 个:
1D 最优提取,因为 JWST 管道仅提供框提取。最优提取提高了微弱源光谱的信噪比(S/N)。
合并并归一化 1D 光谱。
与模板进行交叉相关以获取红移。
空间分辨的发射线图。
本笔记本通过使用 specutils 模板相关性推导出具有多个发射线的星系的红移。该笔记本从第 2 个笔记本中获得的最优提取的 1D 光谱开始。
注意: 没有发射线的光谱(例如,前一个笔记本中的 ID 00003)可能会在此函数中失败。
%matplotlib inline # 在Jupyter Notebook中内联显示Matplotlib图形
import os # 导入操作系统模块,用于文件和目录操作
import numpy as np # 导入NumPy库,用于数值计算和数组操作
import h5py # 导入h5py库,用于处理HDF5文件格式
from astropy.io import fits # 从Astropy库导入FITS模块,用于处理FITS文件
from astropy.table import QTable # 从Astropy库导入QTable,用于处理表格数据
import astropy.units as u # 导入Astropy单位模块,用于物理量的单位处理
from astropy.nddata import StdDevUncertainty # 导入标准偏差不确定性类,用于处理不确定性数据
from astropy.modeling.polynomial import Chebyshev1D # 导入Chebyshev多项式模型,用于数据拟合
from astropy import constants as const # 导入Astropy常数模块,用于物理常数的使用
import astropy # 导入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
import specutils # 导入specutils库
from specutils.fitting import continuum # 从specutils库中导入连续谱拟合模块
from specutils.spectra.spectrum1d import Spectrum1D # 从specutils库中导入一维光谱类
from specutils.analysis import correlation # 从specutils库中导入相关性分析模块
from specutils.spectra.spectral_region import SpectralRegion # 从specutils库中导入光谱区域类
print("Specutils: ", specutils.__version__) # 打印specutils库的版本
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'
# 定义下载后保存的zip文件路径
boxfile = './output.zip'
# 下载zip文件
urllib.request.urlretrieve(boxlink, boxfile)
# 打开下载的zip文件
zf = zipfile.ZipFile(boxfile, 'r')
# 获取zip文件内所有文件的名称列表
list_names = zf.namelist()
# 遍历zip文件内的每个文件
for item in list_names:
# 提取当前文件,使用extract比extractall更安全,避免绝对路径或相对路径问题
zf.extract(member=item)
else:
# 如果输出目录已存在,打印提示信息
print('Already exists')
1. 打开最佳提取的1D光谱文本文件;#
这些是来自笔记本01a和01b的最佳提取、归一化的光谱。
DIR_OUT = './output/' # 输出目录
filt = 'f200w' # 选择的滤光片
grism = 'G150C' # 选择的光栅
# grism = 'G150R' # 另一种光栅(被注释掉)
id = '00004' # 文件标识符
# 构建1D光谱文件的路径
file_1d = f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_combine_1d_opt.fits'
# 打开FITS文件并读取数据部分
fd = fits.open(file_1d)[1].data
# 打开FITS文件并读取头部信息
hd = fits.open(file_1d)[1].header
fd # 输出数据
# 归一化观测光谱;仅用于视觉目的
flux_normalize = 500. # 设置归一化的光通量值为500
wave_200 = fd['wavelength'] # 从数据集中提取波长信息
flux_200 = fd['flux'] / flux_normalize # 归一化光谱数据
flux_err_200 = fd['uncertainty'] / flux_normalize # 归一化光谱不确定性数据
# 打开其他滤光片的文件。
filt = 'f150w' # 设置滤光片为f150w
# 构建文件名,包含输出目录、滤光片、光栅和ID
file_1d = f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_combine_1d_opt.fits'
# 打开FITS文件并读取数据
fd = fits.open(file_1d)[1].data
# 提取波长数据
wave_150 = fd['wavelength']
# 提取并归一化光谱数据
flux_150 = fd['flux'] / flux_normalize
# 提取并归一化光谱不确定性
flux_err_150 = fd['uncertainty'] / flux_normalize
#
filt = 'f115w' # 设置滤光片为f115w
# 构建文件名,包含输出目录、滤光片、光栅和ID
file_1d = f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_combine_1d_opt.fits'
# 打开FITS文件并读取数据
fd = fits.open(file_1d)[1].data
# 提取波长数据
wave_115 = fd['wavelength']
# 提取并归一化光谱数据
flux_115 = fd['flux'] / flux_normalize
# 提取并归一化光谱不确定性
flux_err_115 = fd['uncertainty'] / flux_normalize
# 绘制所有数据
plt.figure() # 创建一个新的图形
# 绘制F115W波段的光谱数据,带误差条
plt.errorbar(wave_115, flux_115, ls='-', color='lightblue', label='F115W')
# 绘制F150W波段的光谱数据,带误差条
plt.errorbar(wave_150, flux_150, ls='-', color='orange', label='F150W')
# 绘制F200W波段的光谱数据,带误差条
plt.errorbar(wave_200, flux_200, ls='-', color='purple', label='F200W')
# 填充F115W波段的误差区域
plt.fill_between(wave_115, -flux_err_115, flux_err_115, ls='-', color='gray', label='', alpha=0.3)
# 填充F150W波段的误差区域
plt.fill_between(wave_150, -flux_err_150, flux_err_150, ls='-', color='gray', label='', alpha=0.3)
# 填充F200W波段的误差区域
plt.fill_between(wave_200, -flux_err_200, flux_err_200, ls='-', color='gray', label='', alpha=0.3)
# 设置x轴标签
plt.xlabel('Wavelength / um')
# 设置y轴的范围
plt.ylim(0., 0.2)
# 显示图例,位置在左上角
plt.legend(loc=2)
连续谱是可见的。在模板相关之前,我们需要将其减去。#
观察到强发射线。(Hb+Oiii 双线在 ~1.55μm,混合的 Ha+Nii 在 ~2.1μm)
请注意,每个滤光片边缘的通量过剩并不真实,应予以屏蔽。
# F200W
spec_unit = u.MJy # 定义光谱单位为MJy
# 创建掩膜,选择波长在1.75到1.97微米和2.08到2.23微米之间的区域
mask = ((wave_200 > 1.75) & (wave_200 < 1.97)) | ((wave_200 > 2.08) & (wave_200 < 2.23))
# 使用掩膜选择的波长和通量创建光谱对象
obs_200 = Spectrum1D(spectral_axis=wave_200[mask]*u.um, flux=flux_200[mask]*spec_unit)
# 拟合光谱的连续背景,使用7阶的切比雪夫多项式
continuum_200 = continuum.fit_generic_continuum(obs_200, model=Chebyshev1D(7))
plt.figure() # 创建一个新的图形
# 绘制原始光谱数据的误差条,线型为实线,颜色为紫色,标签为'F200W'
plt.errorbar(wave_200, flux_200, ls='-', color='purple', label='F200W')
# 绘制拟合的连续背景,颜色为红色,标签为'Fitted continuum'
plt.plot(wave_200[mask], continuum_200(obs_200.spectral_axis), color='r', label='Fitted continuum')
plt.legend(loc=0) # 显示图例,位置自动选择
# 计算减去连续背景后的通量
flux_200_sub = flux_200 * spec_unit - continuum_200(wave_200*u.um)
# 创建新的掩膜,选择波长在1.75到2.23微米之间的区域
mask_200 = (wave_200 > 1.75) & (wave_200 < 2.23)
# F150W
spec_unit = u.MJy # 定义光谱单位为MJy
# 创建掩膜,选择波长在1.35到1.48微米和1.6到1.65微米之间的范围
mask = ((wave_150 > 1.35) & (wave_150 < 1.48)) | ((wave_150 > 1.6) & (wave_150 < 1.65))
# 使用掩膜选择的波长和流量创建Spectrum1D对象
obs_150 = Spectrum1D(spectral_axis=wave_150[mask]*u.um, flux=flux_150[mask]*spec_unit)
# 拟合通量的连续谱,使用7阶的切比雪夫多项式
continuum_150 = continuum.fit_generic_continuum(obs_150, model=Chebyshev1D(7))
# 从原始流量中减去拟合的连续谱,得到减去连续谱后的流量
flux_150_sub = flux_150 * spec_unit - continuum_150(wave_150 * u.um)
# 创建一个新的图形
plt.figure()
# 绘制原始流量的误差条图,线型为实线,颜色为橙色,标签为'F150W'
plt.errorbar(wave_150, flux_150, ls='-', color='orange', label='F150W')
# 绘制拟合的连续谱,颜色为红色,标签为'Fitted continuum'
plt.plot(wave_150[mask], continuum_150(obs_150.spectral_axis), color='r', label='Fitted continuum')
# 添加图例,位置为自动选择
plt.legend(loc=0)
# 创建掩膜,选择波长在1.35到1.65微米之间的范围
mask_150 = (wave_150 > 1.35) & (wave_150 < 1.65)
# F150W
spec_unit = u.MJy # 定义光谱单位为MJy
# 创建掩膜,选择特定波长范围内的数据
mask = ((wave_115 > 1.05) & (wave_115 < 1.13)) | ((wave_115 > 1.17) & (wave_115 < 1.25))
# 使用掩膜选择波长和flux数据,创建Spectrum1D对象
obs_115 = Spectrum1D(spectral_axis=wave_115[mask]*u.um, flux=flux_115[mask]*spec_unit)
# 拟合连续谱,使用Chebyshev多项式模型
continuum_115 = continuum.fit_generic_continuum(obs_115, model=Chebyshev1D(7))
# 创建图形
plt.figure()
# 绘制F115W的光谱数据,带误差条
plt.errorbar(wave_115, flux_115, ls='-', color='lightblue', label='F115W')
# 绘制拟合的连续谱
plt.plot(wave_115[mask], continuum_115(obs_115.spectral_axis), color='r', label='Fitted continuum')
plt.ylim(0, 0.5) # 设置y轴范围
plt.legend(loc=0) # 显示图例
# 计算减去连续谱后的flux
flux_115_sub = flux_115 * spec_unit - continuum_115(wave_115 * u.um)
# 创建掩膜,选择特定波长范围内的数据
mask_115 = (wave_115 > 1.02) & (wave_115 < 1.25)
# 绘制所有数据
plt.figure() # 创建一个新的图形
# 绘制F115W波段的光谱数据,带误差条
plt.errorbar(wave_115[mask_115], flux_115_sub[mask_115], ls='-', color='lightblue', label='F115W')
# 绘制F150W波段的光谱数据,带误差条
plt.errorbar(wave_150[mask_150], flux_150_sub[mask_150], ls='-', color='orange', label='F150W')
# 绘制F200W波段的光谱数据,带误差条
plt.errorbar(wave_200[mask_200], flux_200_sub[mask_200], ls='-', color='purple', label='F200W')
# 填充F115W波段的误差区域
plt.fill_between(wave_115, -flux_err_115, flux_err_115, ls='-', color='gray', label='', alpha=0.3)
# 填充F150W波段的误差区域
plt.fill_between(wave_150, -flux_err_150, flux_err_150, ls='-', color='gray', label='', alpha=0.3)
# 填充F200W波段的误差区域
plt.fill_between(wave_200, -flux_err_200, flux_err_200, ls='-', color='gray', label='', alpha=0.3)
# 设置x轴标签
plt.xlabel('Wavelength / um')
# 显示图例,位置在左上角
plt.legend(loc=2)
# 连接数组
# 使用掩码将波长数组连接起来
wave_obs = np.concatenate([wave_115[mask_115], wave_150[mask_150], wave_200[mask_200]])
# 使用掩码将流量数组连接起来
flux_obs = np.concatenate([flux_115_sub[mask_115], flux_150_sub[mask_150], flux_200_sub[mask_200]])
# 使用掩码将流量误差数组连接起来
flux_err_obs = np.concatenate([flux_err_115[mask_115], flux_err_150[mask_150], flux_err_200[mask_200]])
# 创建一个新的图形
plt.figure()
# 绘制波长与流量的关系图
plt.plot(wave_obs, flux_obs)
# 在流量误差范围内填充区域,使用灰色表示
plt.fill_between(wave_obs, -flux_err_obs, flux_err_obs, ls='-', color='gray', label='', alpha=0.3)
wht_obs = 1 / flux_err_obs**2 # 计算权重,权重为观测误差的倒数平方
spec_unit = u.MJy # 定义光谱单位为MJy(兆焦耳每平方弧秒)
# 创建一个QTable,包含波长、光谱、权重和不确定性
dataspec = QTable([wave_obs*u.um, flux_obs*spec_unit, wht_obs, flux_err_obs],
names=('wavelength', 'flux', 'weight', 'uncertainty'))
dataspec_sub = dataspec[dataspec['weight'] > 0.] # 筛选出权重大于0的观测数据
# 现在将其转换为Spectrum1D实例
p_obs = Spectrum1D(spectral_axis=dataspec_sub['wavelength'], # 设置光谱轴为波长
flux=dataspec_sub['flux'], # 设置光谱值为光谱
uncertainty=StdDevUncertainty(dataspec_sub['uncertainty']), # 设置不确定性为标准偏差不确定性
unit='MJy') # 设置单位为MJy
使用输入模板进行交叉相关;#
(另一个关于Specutils交叉相关的笔记本,由Ivo Busko编写: https://spacetelescope.github.io/jdat_notebooks/pages/redshift_crosscorr/redshift_crosscorr.html)
加载合成模板;#
目前为了测试功能,我们使用用于模拟的输入光谱模板。
def read_hdf5(filename):
# 定义一个函数,用于读取HDF5文件并提取数据
contents = {} # 创建一个空字典,用于存储提取的数据
with h5py.File(filename, 'r') as file_obj: # 以只读模式打开HDF5文件
for key in file_obj.keys(): # 遍历文件中的所有键
dataset = file_obj[key] # 获取当前键对应的数据集
try:
wave_units_string = dataset.attrs['wavelength_units'] # 尝试获取波长单位属性
except KeyError:
wave_units_string = 'micron' # 如果没有找到,默认设置为'micron'
# 捕获常见错误
if wave_units_string.lower() in ['microns', 'angstroms', 'nanometers']: # 检查单位是否在常见单位中
wave_units_string = wave_units_string[0:-1] # 去掉单位的复数形式
# 获取数据
waves = dataset[0] # 提取波长数据
fluxes = dataset[1] # 提取通量数据
contents[int(key)] = {'wavelengths': waves, 'fluxes': fluxes} # 将提取的数据存入字典
return contents # 返回包含所有数据的字典
# 下载文件,如果尚不存在。
if not os.path.exists('./pipeline_products'): # 检查'./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文件路径
# 下载zip文件
urllib.request.urlretrieve(boxlink, boxfile)
zf = zipfile.ZipFile(boxfile, 'r') # 打开下载的zip文件
list_names = zf.namelist() # 获取zip文件中所有文件的名称列表
for item in list_names: # 遍历zip文件中的每个文件
# 使用extract提取单个文件,而不是extractall,因为在文件具有绝对(/)或相对(..)路径时,extract更安全
zf.extract(member=item)
# 模板文件路径
file_temp = './pipeline_products/source_sed_file_from_sources_extend_01_and_sed_file.hdf5'
# 读取HDF5文件的内容
content = read_hdf5(file_temp)
# 输出读取的内容
content
# 模板已经为模拟进行了红移处理。
# 现在将其蓝移到 z=0;
z_input = 2.1 # 输入的红移值
iix = 10709 # 特定模板的索引
flux_all = content[iix]['fluxes'] # 获取特定模板的光谱通量
wave_all = content[iix]['wavelengths'] / (1. + z_input) * 1e4 # 计算蓝移后的波长并转换为合适的单位
plt.plot(wave_all, flux_all, color='orange') # 绘制蓝移后的光谱
plt.xlim(2000, 8000) # 设置x轴的范围
# 切割模板以获得更好的拟合
con_tmp = (wave_all > 3600) & (wave_all < 13000) # 选择波长在3600到13000之间的索引
flux = flux_all[con_tmp] # 根据选择的索引提取flux数据
wave = wave_all[con_tmp] # 根据选择的索引提取wave数据
flux /= flux.max() # 将flux归一化到最大值为1
wave *= 1.e-4 # 将波长转换为微米(um),with_spectral_unit不进行转换
factor = p_obs.flux.unit # 获取观测数据的flux单位以进行归一化
template = Spectrum1D(spectral_axis=wave*u.um, flux=flux*factor) # 创建一个Spectrum1D对象,包含波长和flux
plt.figure() # 创建一个新的图形
plt.plot(template.spectral_axis, template.flux, color='orange') # 绘制模板的光谱轴和flux,颜色为橙色
plt.xlabel(template.spectral_axis.unit) # 设置x轴标签为波长单位
将模板分箱到粗数组中,如果您有太多元素并导致下面的问题;#
-> 在这个例子中,这是不必要的。#
# 如果你想要进行分箱处理,将此设置为True;
if False: # 检查是否启用分箱处理
wave_bin = np.arange(wave.min(), wave.max(), 20.0) # 创建波长分箱数组,从最小波长到最大波长,步长为20.0
flux_bin = np.interp(wave_bin, wave, flux) # 使用插值方法计算分箱后的光谱数据
else: # 如果不进行分箱处理
wave_bin = wave # 直接使用原始波长数据
flux_bin = flux # 直接使用原始光谱数据
factor = p_obs.flux.unit # 获取观测数据的单位,以便将模板归一化到合理范围
template = Spectrum1D(spectral_axis=wave_bin*u.um, flux=flux_bin*factor) # 创建一个Spectrum1D对象,包含波长和光谱数据
plt.figure() # 创建一个新的图形
plt.plot(template.spectral_axis, template.flux, color='orange') # 绘制光谱数据,颜色为橙色
plt.xlabel(template.spectral_axis.unit) # 设置x轴标签为波长单位
# 对拟合温度进行连续谱拟合
# 定义排除的光谱区域
regions = [SpectralRegion(0.37*u.um, 0.452*u.um), # 第一段光谱区域
SpectralRegion(0.476*u.um, 0.53*u.um), # 第二段光谱区域
SpectralRegion(0.645*u.um, 0.67*u.um)] # 第三段光谱区域
# 使用Chebyshev多项式模型拟合连续谱,排除指定的光谱区域
continuum_model = continuum.fit_generic_continuum(template, model=Chebyshev1D(3), exclude_regions=regions)
# 从模板中减去拟合的连续谱,得到去除连续谱后的模板
p_template = template - continuum_model(template.spectral_axis)
# 创建一个新的图形
plt.figure()
# 绘制去除连续谱后的模板光谱
plt.plot(p_template.spectral_axis, p_template.flux, color='orange')
# 设置x轴标签为光谱轴的单位
plt.xlabel(p_template.spectral_axis.unit)
# 设置图形标题
plt.title('Continuum subtracted template')
# 将数据数组转换为Specutils格式
sflux = p_template.flux # 获取模板的光谱通量
# 创建一个Spectrum1D对象,包含光谱轴和归一化后的光谱通量
p_smooth_template = Spectrum1D(spectral_axis=p_template.spectral_axis, # 设置光谱轴
flux=sflux/sflux.max()) # 归一化光谱通量
plt.figure() # 创建一个新的图形
plt.plot(p_smooth_template.spectral_axis, p_smooth_template.flux, color='b', label='Smoothed template') # 绘制平滑模板的光谱,颜色为蓝色
plt.plot(p_obs.spectral_axis, p_obs.flux, color='red', label='Spectrum') # 绘制观测光谱,颜色为红色
plt.xlabel(p_smooth_template.spectral_axis.unit) # 设置x轴标签为光谱轴的单位
plt.legend(loc=0) # 显示图例,位置自动选择
注意:上述两个输入光谱必须有重叠部分,即使只有一小部分,以确保Specutils正常工作。#
使用specutils功能进行交叉相关;#
# 在没有额外规格的情况下,整个模板和整个光谱
# 将被包含在相关性计算中。这通常会导致
# 执行时间显著增加。建议将模板裁剪
# 以仅在有用区域内工作。
# 进行模板相关性计算,返回相关系数和滞后值
corr, lag = correlation.template_correlate(p_obs, p_smooth_template)
plt.figure() # 创建一个新的图形窗口
plt.gcf().set_size_inches((8., 4.)) # 设置图形的尺寸为8x4英寸
plt.plot(lag, corr, linewidth=0.5) # 绘制lag与corr的关系图,线宽为0.5
plt.xlabel(lag.unit) # 设置x轴标签为lag的单位
# 基于最大值计算红移
index_peak = np.argmax(corr) # 找到相关性数组中的最大值索引
v = lag[index_peak] # 根据最大值索引获取对应的速度
z = v / const.c.to('km/s') # 计算红移,使用光速进行单位转换
print("Peak maximum at: ", v) # 输出峰值最大值
print("Redshift from peak maximum: ", z) # 输出从峰值最大值计算得到的红移
# 基于抛物线拟合的红移计算
n = 8 # 定义相关最大值左右各取8个点
peak_lags = lag[index_peak-n:index_peak+n+1].value # 获取最大值附近的延迟值
peak_vals = corr[index_peak-n:index_peak+n+1].value # 获取最大值附近的相关值
p = np.polyfit(peak_lags, peak_vals, deg=2) # 对延迟值和相关值进行二次多项式拟合
roots = np.roots(p) # 计算拟合多项式的根
v_fit = np.mean(roots) * u.km/u.s # 计算最大值对应的速度,取根的平均值
z = v_fit / const.c.to('km/s') # 计算红移,速度与光速的比值
print("") # 打印空行
print("Parabolic fit with maximum at: ", v_fit) # 输出抛物线拟合的最大值
print("Redshift from parabolic fit: ", z) # 输出通过抛物线拟合得到的红移
它看起来怎么样?#
print('z =', z) # 打印红移值 z
plt.figure() # 创建一个新的图形
plt.plot( # 绘制模板光谱
template.spectral_axis * (1. + z), # 将模板的光谱轴乘以 (1 + z) 以进行红移校正
p_smooth_template.flux / np.max(p_smooth_template.flux), # 归一化模板的光谱通量
color='b', label='Template' # 设置颜色为蓝色,并标记为 'Template'
)
plt.plot(p_obs.spectral_axis, p_obs.flux / np.max(p_obs.flux), label='Observed') # 绘制观测光谱并归一化
plt.legend(loc=2) # 显示图例,位置在左上角
plt.xlabel(p_obs.spectral_axis.unit) # 设置 x 轴标签为观测光谱的单位