Specviz 简单演示#

用例: 本笔记本演示了如何在Specviz中检查光谱,如何从笔记本的GUI中导出光谱,如何在GUI和笔记本中选择区域,以及如何在GUI中测量源的红移(redshift)。

数据: 来自NGDEEP调查的NIRISS 1D光谱。该数据集直接从经过默认JWST管道处理的MAST获取。

工具: specutils, jdaviz。

跨仪器: 所有仪器。

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

更新于: 2023/10/11

# 使用浏览器窗口的100%

from IPython.display import display, HTML  # 导入显示和HTML模块

# 设置容器宽度为100%
display(HTML("<style>.container { width:100% !important; }</style>"))  # 显示HTML样式

导入:

  • matplotlib 用于绘制数据

  • astropy 用于处理 FITS 文件、单位和表格

  • specutils 用于与 Specviz 的交互以及区域定义/提取

  • jdaviz 用于可视化工具 Specviz

# 绘图和表格处理

import matplotlib.pyplot as plt  # 导入绘图库

# 导入astropy库

import astropy  # 导入astropy库

import astropy.units as u  # 导入单位模块

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

from astropy.nddata import StdDevUncertainty  # 从astropy.nddata导入标准偏差不确定性类

from astropy.table import QTable  # 从astropy.table导入QTable类,用于处理表格数据

# 导入specutils库

import specutils  # 导入specutils库

from specutils import Spectrum1D, SpectralRegion  # 从specutils导入Spectrum1D和SpectralRegion类

from specutils.manipulation import extract_region  # 从specutils.manipulation导入提取区域的函数

# 导入viztools库

import jdaviz  # 导入jdaviz库

from jdaviz import Specviz  # 从jdaviz导入Specviz类,用于可视化光谱数据
# 自定义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})  # 禁用最大打开图形警告

检查版本

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

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

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

1. 加载NIRISS管道输出#

JWST/NIRISS数据存储在box上。我们使用x1d文件,该文件包含所有提取的1D光谱。

# 定义JWST数据文件的链接
filelink = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/specviz_notebook_gui_interaction/jw02079004002_11101_00001_nis_x1d.fits'

# 使用fits模块打开FITS文件
hdu = fits.open(filelink)

# 打印HDU列表的信息
hdu.info()

2. 打开Specviz并加载我们感兴趣的1D光谱#

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

viz.show()  # 显示光谱可视化界面

以下单元打开x1d文件的一个扩展(75),创建一个Spectrum1D对象,并将其加载到Specviz中。设置了一个掩膜,仅保留在F150W滤光片中具有良好灵敏度的光谱部分(1.34到1.66微米)。

for i in range(74, 75):  # 遍历指定范围的索引

    spec_load = hdu[i+1].data  # 从HDU中加载数据

    wave = spec_load['WAVELENGTH']  # 获取波长数据

    flux = spec_load['FLUX']  # 获取光通量数据

    error = spec_load['FLUX_ERROR']  # 获取光通量误差数据

    # 掩蔽波段内灵敏度较差的部分
    mask = ((wave > 1.34) & (wave < 1.66))  # 创建掩蔽掩码

    # 创建一维光谱对象,应用掩蔽
    spec1d = Spectrum1D(spectral_axis=wave[mask]*u.um,  # 设置光谱轴为掩蔽后的波长
                        flux=flux[mask]*u.Jy,  # 设置光通量为掩蔽后的光通量
                        uncertainty=StdDevUncertainty(error[mask]*u.Jy))  # 设置光通量的不确定性

    viz.load_data(spec1d, "NIRISS 1D {}".format(str(i+1)))  # 将数据加载到可视化工具中

3. 使用GUI和笔记本选择发射线#

我选择了大约从1.58到1.63微米的发射线区域。

指令: https://jdaviz.readthedocs.io/en/latest/specviz/displaying.html#defining-spectral-regions

查看此 SpecViz 实例中使用的数据#

dataout = viz.get_spectra(apply_slider_redshift=False)  # 获取光谱数据,不应用红移滑块

spec1d_line = dataout["NIRISS 1D 75"]  # 从数据中提取NIRISS 1D 75光谱
# 打印变量spec1d_line的内容
print(spec1d_line)

查看在GUI中定义的子集#

我包含了一个try-except语句,以防在没有人工交互的情况下运行笔记本。

try:
    # 获取光谱区域
    region = viz.get_spectral_regions()

    # 打印子集1的区域信息
    print(region['Subset 1'])

except KeyError:
    # 如果没有在GUI中定义区域,则打印错误信息
    print("No region defined in the GUI")

以编程方式选择相同区域#

我可以在任意边界之间定义自己的区域(cont_region)。我选择1.598微米(um)和1.621微米(um)。然后,我可以提取该区域内的光谱。

# 定义一个光谱区域,范围从1.598微米到1.621微米
cont_region = SpectralRegion(1.598*u.um, 1.621*u.um)

# 从光谱数据中提取指定的光谱区域
spec1d_el_code = extract_region(spec1d_line, cont_region)

# 打印提取的光谱区域数据
print(spec1d_el_code)

或者,我可以提取我在图形用户界面(GUI)中定义的区域(region[‘Subset 1’])中的光谱。

try:

    # 从spec1d_line中提取Subset 1区域的数据
    spec1d_el_viz = extract_region(spec1d_line, region['Subset 1'])

    # 打印提取的光谱数据
    print(spec1d_el_viz)

except KeyError:

    # 如果在GUI中未定义区域,则打印错误信息
    print("Region was not defined in the GUI")

    # 将spec1d_el_viz定义为spec1d_el_code
    spec1d_el_viz = spec1d_el_code

使用matplotlib绘制光谱及其子集#

plt.plot(spec1d_line.spectral_axis, spec1d_line.flux, label='data')  # 绘制数据的光谱轴和通量

plt.plot(spec1d_el_viz.spectral_axis, spec1d_el_viz.flux, label='subset defined in tool')  # 绘制工具定义的子集的光谱轴和通量

plt.plot(spec1d_el_code.spectral_axis, spec1d_el_code.flux, label='subset defined in code')  # 绘制代码定义的子集的光谱轴和通量

plt.legend()  # 显示图例

plt.xlabel("wavelength ({:latex})".format(spec1d_line.spectral_axis.unit))  # 设置x轴标签为波长,单位为光谱轴的单位

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

plt.title("NIRISS ID 75")  # 设置图表标题

plt.show()  # 显示图表

4. 在Specviz中使用红移滑块查找红移#

我首先打开一个新的Specviz实例,这样就不需要频繁上下滚动了。

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

viz2.show()  # 显示Specviz界面

我加载了仅有趣的光谱(spec1d_line)。

# 加载NIRISS 1D光谱线数据
viz2.load_data(spec1d_line, "NIRISS 1D lines")

我可以使用现有的谱线列表或定义我自己的谱线(我知道我需要 Hb4861.3 和 [OIII]4958.9,5006.8 双线),并通过调整红移滑块来将谱线列表中的谱线与光谱中的谱线匹配。谱线列表插件可以通过点击查看器右上角的插件图标找到。要输入仅这三条谱线,我可以使用“自定义”菜单。

以下是解释谱线列表的文档: https://jdaviz.readthedocs.io/en/latest/specviz/plugins.html#line-lists

我还可以通过编程方式定义感兴趣的谱线,如下方的单元格所示。

lt = QTable()  # 创建一个空的QTable对象

lt['linename'] = ['Hb', '[OIII]1', '[OIII]2']  # 添加谱线名称

lt['rest'] = [4861.3, 4958.9, 5006.8]*u.AA  # 添加谱线的静止波长,并指定单位为Ångström

viz2.load_line_list(lt)  # 加载谱线列表到viz2对象中

这些线条现在没有显示,因为它们的静止值超出了此处绘制的范围。我可以使用线列表插件中的红移滑块来移动这些线条。最好先在数字框中将红移设置为2,然后移动滑块以将线条放置在观测到的发射线之上。

从Spectrum1D对象中获取红移(redshift)#

# 获取应用红移滑块后的NIRISS 1D光谱数据
spec1d_redshift = viz2.get_spectra(apply_slider_redshift=True)["NIRISS 1D lines"]

# 打印获取的光谱数据
print(spec1d_redshift)

# 打印空行以增加可读性
print()

# 检查光谱的红移值是否为0.0
if spec1d_redshift.redshift != 0.0:
    # 如果红移不为0.0,打印红移值
    print("NIRISS 1D lines redshift=", spec1d_redshift.redshift)
else:
    # 如果红移未在GUI中定义,则在此处定义红移
    print("Redshift was not defined in GUI. Defining it here.")
    # 设置光谱的红移为2.2138
    spec1d_redshift.set_redshift_to(2.2138)
    # 打印设置后的红移值
    print("NIRISS 1D lines redshift=", spec1d_redshift.redshift)

5. 对光谱的连续谱进行建模#

我打开了另一个 Specviz 实例,并加载了之前使用的相同光谱。

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

viz3.show()  # 显示Specviz窗口
# 加载NIRISS 1D光谱数据
viz3.load_data(spec1d_line, "NIRISS 1D lines")

我可以使用图形用户界面(GUI)选择我看到的连续区域。挑战:选择一个不连续的子集,覆盖两个区间(1.35-1.55微米和1.63-1.65微米)。提示:在子集下拉菜单附近的顶部选择“添加”。

我可以在插件图标下使用模型拟合插件来对选定区域拟合线性模型。具体说明可以在这里找到:https://jdaviz.readthedocs.io/en/latest/specviz/plugins.html#model-fitting。完成此任务的具体步骤如下:

  • 在数据下选择子集 1

  • 在模型下选择 Linear1D

  • 点击添加模型

  • 在模型标签下输入模型名称(我选择“continuum”)

  • 点击拟合

我可以从正在使用的数据集中提取模型及其参数。

try:

    dataout3 = viz3.get_spectra()  # 获取光谱数据

    spectrum = dataout3["NIRISS 1D lines"]  # 获取NIRISS 1D光谱线数据,与之前加载的spec1d_lines相同

    continuum = dataout3["continuum"]  # 获取连续谱数据

    model_param = viz3.get_model_parameters()  # 获取模型参数

    print(continuum)  # 打印连续谱数据

    print(model_param['continuum'])  # 打印模型中的连续谱参数

except KeyError:  # 捕获键错误异常

    print("Continuum has not been created. Setting it to 0")  # 输出错误信息,表示连续谱未创建,设置为0

    continuum = Spectrum1D(spectral_axis=spectrum.spectral_axis, flux=0.*spectrum.flux)  # 创建一个零值的连续谱

我可以进行连续体减法并使用matplotlib绘制结果。如果在图形用户界面(GUI)中未定义连续体,这个操作将返回未更改的原始光谱。

spectrum_sub = spectrum - continuum  # 从光谱中减去连续背景,得到去除背景的光谱
plt.plot(spectrum_sub.spectral_axis, spectrum_sub.flux)  # 绘制光谱数据,x轴为光谱轴,y轴为通量

plt.hlines(0, 1.3, 1.7, color='black')  # 在y=0的位置绘制水平线,范围从1.3到1.7

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

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

plt.title("NIRISS ID 75")  # 设置图表标题

plt.show()  # 显示绘制的图表
空间望远镜标志