红移和模板拟合#

这个笔记本涵盖了用户如何使用可视化工具 Jdaviz 或通过编程方式使用 Specutils 来测量源的红移(redshift)的基本示例。

用例:使用两种不同的方法从星系的光谱中测量红移。

数据:来自程序2736的JWST/NIRSpec光谱。

工具:jdaviz,specutils。

跨仪器:NIRISS,NIRCam。

内容

作者:Camilla Pacifici (cpacifici@stsci.edu)

更新:2024年11月18日

资源和文档#

此笔记本使用了来自 SpecutilsJdaviz 的功能。空间望远镜科学研究所(Space Telescope Science Institute)的开发人员可以通过 JWST 帮助中心 回答问题并解决问题。如果您希望提供反馈或报告问题,您也可以直接在 Github 上提交问题,分别针对 SpecutilsJdaviz

安装#

此笔记本提取自JWebbinar材料

要运行此笔记本,您需要创建一个包含jdaviz包的环境,按照以下说明进行操作。

conda create -n jdaviz python=3.11

conda activate jdaviz

从最新版本安装

pip install jdaviz

或从git安装

pip install git+https://github.com/spacetelescope/jdaviz.git

导入#

# general os
import tempfile  # 导入临时文件模块

# numpy
import numpy as np  # 导入numpy库,用于数值计算

# astroquery
from astroquery.mast import Observations  # 从astroquery库导入Observations模块,用于查询JWST观测数据

# specviz
import jdaviz  # 导入jdaviz库,以便后续获取版本号
from jdaviz import Specviz  # 从jdaviz库导入Specviz类,用于光谱数据可视化

# astropy
import astropy  # 导入astropy库,以便后续获取版本号
import astropy.units as u  # 导入astropy单位模块
from astropy.io import ascii, fits  # 从astropy.io导入ascii和fits模块,用于文件读写
from astropy.utils.data import download_file  # 导入下载文件的工具
from astropy.modeling.models import Linear1D  # 导入线性模型
from astropy.nddata import StdDevUncertainty  # 导入标准偏差不确定性类

# specutils
import specutils  # 导入specutils库,以便后续获取版本号
from specutils import Spectrum1D, SpectralRegion  # 从specutils导入Spectrum1D和SpectralRegion类
from specutils.fitting import fit_generic_continuum  # 导入通用连续谱拟合工具
from specutils.analysis import correlation  # 导入相关性分析工具
from specutils.manipulation import extract_region  # 导入提取光谱区域的工具

# glue
from glue.core.roi import XRangeROI  # 从glue库导入XRangeROI类,用于定义X轴范围的感兴趣区域

# matplotlib
from matplotlib import pyplot as plt  # 导入pyplot模块,用于绘图

# display
from IPython.display import display, HTML  # 导入显示和HTML模块,用于在Jupyter Notebook中展示内容
# 自定义matplotlib样式

plt.rcParams["figure.figsize"] = (10, 5)  # 设置图形的默认大小为10x5英寸

params = {
    'legend.fontsize': '18',  # 设置图例字体大小为18
    'axes.labelsize': '18',   # 设置坐标轴标签字体大小为18
    'axes.titlesize': '18',   # 设置坐标轴标题字体大小为18
    'xtick.labelsize': '18',   # 设置x轴刻度标签字体大小为18
    'ytick.labelsize': '18',   # 设置y轴刻度标签字体大小为18
    'lines.linewidth': 2,      # 设置线条宽度为2
    'axes.linewidth': 2,       # 设置坐标轴线宽度为2
    'animation.html': 'html5', # 设置动画输出格式为html5
    'figure.figsize': (8, 6)   # 设置图形的默认大小为8x6英寸
}

plt.rcParams.update(params)  # 更新matplotlib的配置参数

plt.rcParams.update({'figure.max_open_warning': 0})  # 关闭最大打开图形的警告

# 确保我们的笔记本使用浏览器的全宽
display(HTML("<style>.container { width:100% !important; }</style>"))  # 设置容器宽度为100%

版本:#

# 打印jdaviz库的版本
print("jdaviz:", jdaviz.__version__)

# 打印astropy库的版本
print("astropy:", astropy.__version__)

# 打印specutils库的版本
print("specutils:", specutils.__version__)

获取示例数据#

在这里,我们下载来自早期发布观测数据项目2736的光谱,以及我们将用作红移测量模板的模型光谱。该模板基于简单恒星群体模型的组合,包括发射线,正如在Pacifici et al. (2012)中所做的那样。

# 选择机器上的特定目录或临时目录
data_dir = tempfile.gettempdir()  # 获取临时目录路径

# 从MAST获取文件
fn = "jw02736-o007_s000009239_nirspec_f170lp-g235m_x1d.fits"  # 指定要下载的文件名

# 下载指定的JWST文件到本地路径
result = Observations.download_file(f"mast:JWST/product/{fn}", local_path=f'{data_dir}/{fn}')  # 下载文件并保存到临时目录

# 下载模板文件并缓存
fn_template = download_file('https://stsci.box.com/shared/static/3rkurzwl0l79j70ddemxafhpln7ljle7.dat', cache=True)  # 下载模板文件并启用缓存

Jdaviz 默认会读取表面亮度扩展(以 MJy/sr 为单位),但我更喜欢读取通量扩展(以 Jy 为单位)。我自己创建一个 Spectrum1D 对象来读取文件。

hdu = fits.open(f'{data_dir}/{fn}')  # 打开指定路径下的FITS文件

wave = hdu[1].data['WAVELENGTH'] * u.Unit(hdu[1].header['TUNIT1'])  # 获取波长数据并添加单位

flux = hdu[1].data['FLUX'] * u.Unit(hdu[1].header['TUNIT2'])  # 获取光谱通量数据并添加单位

# std = hdu[1].data['FLUX_ERROR'] * u.Unit(hdu[1].header['TUNIT3']) # 这些值都是nan,定义一个人工的不确定性

fluxobs = Spectrum1D(spectral_axis=wave,  # 创建一个Spectrum1D对象,指定光谱轴为波长
                     flux=flux,  # 指定光谱通量
                     uncertainty=StdDevUncertainty(0.1*flux))  # 添加不确定性,这里使用0.1倍的光谱通量作为不确定性

fluxobs  # 输出光谱对象

使用Specviz进行“目测”红移测量#

Specviz将允许您将谱线波长与您在光谱中看到的发射线进行匹配。您可以使用红移滑块在线列表插件中完成此操作。但首先,让我们在Specviz中打开光谱

# 调用应用程序

viz = Specviz()  # 创建Specviz实例

viz.show()  # 显示应用程序界面
# 加载光谱

# viz.load_data(f'{data_dir}/{fn}', data_label="NIRSpec") # 从文件直接加载表面亮度

viz.load_data(fluxobs, data_label='NIRSpec')  # 加载观测到的光谱数据,标签为'NIRSpec'

现在我们需要:

线列表插件在右侧菜单中
  • 选择预加载的谱线或添加自定义谱线(谱线不会在查看器中显示,因为它们是在静止框架下绘制的)

    • 提示:选择 Ha 及其周围的 NII 线

选择 SDSS 列表 选择两个 [NII] 线和 H α 线
  • 输入一个猜测的红移

输入一个红移的猜测(红移=2.4)
  • 移动滑块以获得更好的匹配

使用滑块调整红移以匹配光谱中的发射线 使用缩放工具找到更好的发射线匹配

使用交叉相关法测量红移#

在天文学中,使用交叉相关算法测量红移是非常常见的。IRAF在其 xcsao 任务中使用了这种方法。在这里,我们使用Specutils的 模板交叉相关 函数来推导我们源的红移。在运行相关算法之前,我们需要做几件事:

  • 获取用于相关的模板光谱

  • 从模板光谱和观测光谱中减去连续谱

  • 确保光谱在波长上有一定的重叠

获取模板并准备使用#

该模板用于交叉相关,因此可以进行重新归一化以便于使用。单位必须与观测光谱的单位相匹配。我们可以在 erg/(s cm² Å) 中进行连续体减法,因为连续体接近线性,然后通过 Jdaviz 运行以获得适当的转换。

# 定义单位
spec_unit = u.erg / u.s / u.cm**2 / u.AA  # 每秒每平方厘米每埃的能量单位

# 使用ascii函数读取光谱
template = ascii.read(fn_template)  # 从指定文件读取光谱数据

# 创建Spectrum1D对象
template = Spectrum1D(spectral_axis=template['col1']/1E4*u.um,  # 将波长列转换为微米并创建光谱轴
                      flux=(template['col2']/1E24)*spec_unit)  # 将flux列归一化到接近观测光谱的单位
# 剪切到有用的范围 - 模板和观测数据必须重叠,因此我们选择2.4微米的范围。

# 创建一个布尔数组,选择光谱轴在0.45到2.4微米之间的部分
use_tmp = (template.spectral_axis.value > 0.45) & (template.spectral_axis.value < 2.4)

# 使用布尔数组剪切模板,生成新的Spectrum1D对象
template_cut = Spectrum1D(spectral_axis=template.spectral_axis[use_tmp], flux=template.flux[use_tmp])
# 查看光谱

plt.figure(figsize=[10, 6])  # 创建一个10x6英寸的图形

plt.plot(template_cut.spectral_axis, template_cut.flux)  # 绘制光谱轴与通量的关系

plt.xlabel("wavelength ({:latex})".format(template_cut.spectral_axis.unit))  # 设置x轴标签为波长,并显示单位

plt.ylabel("flux ({:latex})".format(template_cut.flux.unit))  # 设置y轴标签为通量,并显示单位

plt.title("Template")  # 设置图形标题为“模板”

plt.show()  # 显示图形

该图显示了模板光谱(通量 vs 波长)延伸至2.4微米,以便与观测光谱有一定的波长重叠。

# 减去连续背景

# 创建一个掩膜,选择波长在0.70到2.40之间的部分
mask_temp = ((template_cut.spectral_axis.value > 0.70) & (template_cut.spectral_axis.value < 2.40))

# 使用掩膜从模板中提取光谱轴和通量
template_forcont = Spectrum1D(spectral_axis=template_cut.spectral_axis[mask_temp], flux=template_cut.flux[mask_temp])

# 使用fit_generic_continuum函数拟合连续背景
fit_temp = fit_generic_continuum(template_forcont, model=Linear1D())

# 计算拟合的连续背景
cont_temp = fit_temp(template_cut.spectral_axis)

# 从原始模板中减去连续背景
template_sub = template_cut - cont_temp

# 创建一个新的图形,设置图形大小
plt.figure(figsize=[10, 6])

# 绘制原始模板的光谱
plt.plot(template_cut.spectral_axis, template_cut.flux)

# 绘制拟合的连续背景
plt.plot(template_cut.spectral_axis, cont_temp)

# 设置x轴标签,显示波长单位
plt.xlabel("wavelength ({:latex})".format(template_sub.spectral_axis.unit))

# 设置y轴标签,显示通量单位
plt.ylabel("flux ({:latex})".format(template_sub.flux.unit))

# 设置图形标题
plt.title("Plot template and continuum")

# 显示图形
plt.show()

该图显示了模板光谱和最佳拟合的连续谱。

# 打印 Spectrum1D 对象
print(template_sub)  # 输出 template_sub 对象的内容
# 查看光谱

plt.figure(figsize=[10, 6])  # 创建一个10x6英寸的图形

plt.plot(template_sub.spectral_axis, template_sub.flux)  # 绘制光谱轴与通量的关系图

plt.xlabel("wavelength ({:latex})".format(template_sub.spectral_axis.unit))  # 设置x轴标签为波长,并显示单位

plt.ylabel("flux ({:latex})".format(template_sub.flux.unit))  # 设置y轴标签为通量,并显示单位

plt.title("Continuum subtracted template")  # 设置图形标题为“去除连续光谱的模板”

plt.show()  # 显示图形

该图显示了在减去拟合的连续谱后,模板光谱(通量 vs 波长)。

从观测光谱中减去连续谱#

我们可以采用不同的方法,通过 SpectralRegion 来实现这一点。同时,如果观测光谱中未包含不确定性,我们也需要将其纳入考虑。

# 定义光谱区域
region = SpectralRegion(2.0*u.um, 3.0*u.um)  # 创建一个光谱区域,从2.0微米到3.0微米

# 提取该区域的光谱数据
spec1d_cont = extract_region(fluxobs, region)  # 从观测数据中提取指定光谱区域的数据

# 运行拟合函数
fit_obs = fit_generic_continuum(spec1d_cont, model=Linear1D(5))  # 使用线性模型拟合提取的光谱数据

# 将拟合结果应用于光谱轴
cont_obs = fit_obs(fluxobs.spectral_axis)  # 在光谱轴上应用拟合结果,得到连续谱

# 从观测数据中减去连续谱
spec1d_sub = fluxobs - cont_obs  # 计算减去连续谱后的光谱数据
# 查看光谱

plt.figure(figsize=[10, 6])  # 创建一个10x6英寸的图形

plt.plot(spec1d_sub.spectral_axis, spec1d_sub.flux)  # 绘制光谱轴与通量的关系图

plt.xlabel("wavelength ({:latex})".format(spec1d_sub.spectral_axis.unit))  # 设置x轴标签为波长,并显示单位

plt.ylabel("flux ({:latex})".format(spec1d_sub.flux.unit))  # 设置y轴标签为通量,并显示单位

plt.title("Continuum subtracted observed spectrum")  # 设置图形标题为“去除连续光谱的观测光谱”

plt.show()  # 显示图形

该图显示了在减去拟合的连续谱后观察到的光谱(通量 vs 波长)。

清理光谱#

最好去除可能看起来像发射/吸收线的伪影,并去除大的间隙。可以使用图形用户界面(GUI)选择光谱的干净部分。如果没有手动执行此操作,以下单元将以编程方式处理它。

选择光谱的干净区域
viz2 = Specviz()  # 创建一个Specviz对象,用于可视化光谱数据

viz2.load_data(spec1d_sub, data_label='spectrum continuum subtracted')  # 加载经过连续谱减去处理的光谱数据,并设置数据标签

viz2.show()  # 显示光谱数据的可视化结果
# 如果没有手动创建感兴趣区域,则创建一个子集

try:
    # 获取经过连续谱减法处理的光谱数据子集
    region1 = viz2.get_spectra(data_label='spectrum continuum subtracted', subset_to_apply='Subset 1')

    # 打印获取的光谱数据子集
    print(region1)

    # 标记子集存在
    region1_exists = True

except Exception:
    # 如果没有选定的子集,打印提示信息
    print("There are no subsets selected.")

    # 标记子集不存在
    region1_exists = False

# 如果子集不存在,则为掩蔽伪影的光谱区域
if region1_exists is False:
    # 获取光谱查看器
    sv = viz2.app.get_viewer('spectrum-viewer')

    # 清空当前活动子集的选择
    sv.toolbar_active_subset.selected = []

    # 应用感兴趣区域(ROI),设置X轴范围
    sv.apply_roi(XRangeROI(2.24, 3.13))
# 获取带掩膜的光谱

spec1d_region = viz2.get_spectral_regions()  # 从可视化工具获取光谱区域

spec1d_masked = extract_region(spec1d_sub, spec1d_region['Subset 1'], return_single_spectrum=True)  # 提取指定区域的光谱,返回单一光谱

我们在Specviz的新实例中可视化观察到的和模板的连续谱减去背景的光谱。点击主页按钮以查看整个波长范围。模板光谱的单位将会更改,以匹配观察到的光谱。

viz3 = Specviz()  # 创建一个Specviz对象,用于可视化光谱数据

viz3.load_data(spec1d_masked, data_label='observation')  # 加载观测数据,使用标签'observation'

viz3.load_data(template_sub, data_label='template')  # 加载模板数据,使用标签'template'

viz3.show()  # 显示可视化界面
# 从viz3获取名为'template'的数据,并使用显示单位
template_newunit = viz3.get_data('template', use_display_units=True)

# 输出获取的数据
template_newunit

运行交叉相关函数#

# 调用函数进行模板相关性计算
corr, lag = correlation.template_correlate(spec1d_masked, template_newunit, lag_units=u.one)

# 绘制相关性图
plt.plot(lag, corr)  # 绘制滞后与相关性的关系图

plt.xlabel("Redshift")  # 设置x轴标签为红移
plt.ylabel("Correlation")  # 设置y轴标签为相关性
plt.show()  # 显示图形

该图显示了相关值与红移(redshift)之间的关系。尖峰(大约在红移2.5处)表示观察到的光谱与模板光谱最佳相关的值。

# 基于最大值计算红移

index_peak = np.argmax(corr)  # 找到相关数组中最大值的索引

z = lag[index_peak]  # 根据索引获取对应的红移值

print("Redshift from peak maximum: ", z)  # 输出从峰值最大值计算得到的红移
# 红移模板子
template_sub_z = Spectrum1D(spectral_axis=template_sub.spectral_axis * (1. + z),  # 计算红移后的光谱轴
                            flux=template_sub.flux)  # 保持原始的光谱通量
# 可视化红移模板和观测光谱

viz4 = Specviz()  # 创建Specviz对象,用于可视化光谱数据

viz4.load_data(spec1d_masked, data_label='Observed spectrum')  # 加载观测光谱数据,并标记为'Observed spectrum'

viz4.load_data(template_sub_z, data_label='Redshifted best template')  # 加载红移后的最佳模板数据,并标记为'Redshifted best template'

viz4.show()  # 显示可视化结果
太空望远镜标志