WFSS光谱 第0部分:最佳提取#

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

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

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

跨仪器: NIRSpec

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

引言#

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

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

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

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

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

本笔记本将从 NIRISS WFSS 的 后处理管道 产品开始,使用来自 spec level3 的 2D 矫正光谱。

最优提取需要在交叉色散方向上的源形态,这将通过与 WFSS 观测同时拍摄的直接图像获取。色散方向上的形态对于推断光谱分辨率也至关重要,这将通过模板拟合来获取红移和星族信息,在本组的第 3 个笔记本中进行。

注意: 我们假设 2D 矫正光谱的处理已经达到合理水平,即目标 2D 光谱上没有来自其他源的污染光通量,并且背景已经被减去。

%matplotlib inline  # 在Jupyter Notebook中内联显示Matplotlib绘图
import os  # 导入操作系统模块

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

from scipy.ndimage import rotate  # 从SciPy库导入旋转函数,用于图像处理

from scipy.optimize import curve_fit  # 从SciPy库导入曲线拟合函数

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

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

import astropy.wcs as wcs  # 导入Astropy的WCS模块,用于天文坐标转换

from astropy.io import ascii  # 从Astropy库导入ASCII文件处理模块

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

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

import specutils  # 导入specutils库

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

import astropy  # 导入Astropy库

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

版本应为#

  • specutils 1.11.0

  • astropy 5.3.4

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. 下载和加载数据:#

这些包括NIRISS的管道处理数据,以及来自图像步骤3的光度目录。

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文件内的每个文件
        zf.extract(member=item)  # 使用extract提取文件,避免使用extractall以防止路径问题(绝对路径或相对路径)
import os  # 导入操作系统模块
from astropy.io import fits  # 导入Astropy库中的fits模块,用于处理FITS文件
from astropy import wcs  # 导入Astropy库中的wcs模块,用于处理天文坐标系统
import numpy as np  # 导入NumPy库,用于数值计算

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

# 输出目录;
DIR_OUT = './output/'  # 输出目录路径

if not os.path.exists(DIR_OUT):  # 检查输出目录是否存在
    os.mkdir(DIR_OUT)  # 如果不存在,则创建输出目录

# 检测和科学图像的滤波器;
filt_det = 'f200w'  # 设置用于检测的滤波器

# 从直接图像中获取图像数组。用于最佳提取和掩膜。
# 此图像应已进行天空减法;否则,您将遇到最佳提取的错误结果。
infile = f'{DIR_DATA}l3_nis_{filt_det}_i2d_skysub.fits'  # 输入文件路径,包含天空减法后的数据
hdu = fits.open(infile)  # 打开FITS文件

# 这只是用于误差数组;
infile = f'{DIR_DATA}l3_nis_{filt_det}_i2d.fits'  # 输入文件路径,包含原始数据
hdu_err = fits.open(infile)  # 打开误差FITS文件

data = hdu[0].data  # 获取图像数据
imwcs = wcs.WCS(hdu[0].header, hdu)  # 创建WCS对象,用于处理图像的天文坐标

err = hdu_err[2].data  # 获取误差数据
weight = 1/np.square(err)  # 计算权重,权重为误差的平方的倒数

# 分割图
# 如果管道没有生成分割图,可以通过运行Photutils来准备。
segfile = f'{DIR_DATA}l3_nis_{filt_det}_i2d_seg.fits'  # 输入分割图文件路径
seghdu = fits.open(segfile)  # 打开分割图FITS文件
segdata = seghdu[0].data  # 获取分割图数据
# 从图像的第三级加载目录;

# 以获取源在像素坐标中的位置。

catfile = f'{DIR_DATA}l3_nis_{filt_det}_cat.ecsv'  # 构建目录文件的路径

fd = ascii.read(catfile)  # 读取目录文件
看起来您输入的内容不完整如果您有特定的Python代码需要我添加中文注释请提供代码段我将很乐意帮助您

制作宽带通量目录。#

  • 出于方便的考虑,我们将目录汇编成一个通量目录,该目录将在后续笔记本(01b)中使用。

  • 要运行此单元,您需要一个源的光度目录,该目录列出了每个滤光片的源位置和通量。目前,我使用的是在另一个笔记本中准备的目录。(”sources_extend_01.cat”)

  • 此目录也可以用于通用的光度红移/光谱能量分布拟合软件,如EAZY和gsf(见笔记本No.04)。

目前,我们使用一个输入目录。#

filts = ['f115w', 'f150w', 'f200w', 'f090w', 'f435w', 'f606w', 'f814w', 'f105w', 'f125w', 'f140w', 'f160w']  # 定义滤光片名称列表

eazy_filts = [309, 310, 311, 308, 1, 4, 6, 202, 203, 204, 205]  # 定义EAZY滤光片对应的索引

magzp = 25.0  # 目录中的星等零点

# 读取目录;

fd_input = ascii.read(f'{DIR_DATA}sources_extend_01.cat')  # 从指定路径读取源数据文件

ra_input = fd_input['x_or_RA']  # 获取输入数据中的RA(右升交角)

dec_input = fd_input['y_or_Dec']  # 获取输入数据中的Dec(天赤纬)

# 写入文件头;

fw = open(f'{DIR_OUT}l3_nis_flux.cat', 'w')  # 打开输出文件以写入

fw.write('# id')  # 写入文件头的ID

for ff in range(len(filts)):  # 遍历所有滤光片
    fw.write(f' F{eazy_filts[ff]} E{eazy_filts[ff]}')  # 写入每个滤光片的EAZY索引

fw.write('\n')  # 换行

# 写入内容;

for ii in range(len(fd['id'])):  # 遍历每个源的ID

    # 计算当前源与所有输入源之间的距离
    rtmp = np.sqrt((fd['sky_centroid'].ra.value[ii] - ra_input[:])**2 + (fd['sky_centroid'].dec.value[ii] - dec_input[:])**2)

    iix = np.argmin(rtmp)  # 找到距离最近的输入源索引

    for ff in range(len(filts)):  # 遍历所有滤光片

        if ff == 0:  # 如果是第一个滤光片
            fw.write(f"{fd['id'][ii]}")  # 写入源的ID

        mag = fd_input[f'niriss_{filts[ff]}_magnitude'][iix]  # 获取最近源的星等

        flux_nu = 10**((mag - magzp) / (-2.5))  # 计算对应的光通量

        # 当前目录未提供适当的误差;
        # 假设光通量的误差为5%.

        flux_err_nu = flux_nu * 0.05  # 计算光通量误差

        fw.write(f' {flux_nu:.5e} {flux_err_nu:.5e}')  # 写入光通量及其误差

    fw.write('\n')  # 换行

fw.close()  # 关闭输出文件

1. 加载 2D 光谱;#

# 选择滤光片、光栅和目标对象

filt = 'f200w'  # 设置滤光片为 'f200w'

# grism = 'G150R'  # 原本的光栅设置为 'G150R'

grism = 'G150C'  # 设置光栅为 'G150C'

id = '00004'  # 设置目标对象的ID

# 零索引的抖动编号 --- 测试数据有两个抖动位置,因此可以是0或1。

ndither = 0  # 设置抖动位置为0

# 构建文件路径,包含数据目录、滤光片、光栅和目标ID
file_2d = f'{DIR_DATA}l3_nis_{filt}_{grism}_s{id}_cal.fits'  

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

# 对齐光栅方向
#   - 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度

# 获取观测的方位角;
hd_2d = hdu_2d[1].header  # 获取FITS文件的头信息
PA_V3 = hd_2d['PA_V3']  # 从头信息中提取方位角PA_V3
PA_V3  # 输出方位角
plt.imshow(data_2d, vmin=0, vmax=1000)  # 显示二维数据,设置最小值为0,最大值为1000
# 获取源的光谱轮廓;

# 再次,y是交叉色散方向,x是色散方向。
y2d, x2d = data_2d.shape[:]  # 获取2D数据的形状,y和x的维度

# 切割出分割图;
iix = np.where(fd['id'] == int(id))[0][0]  # 找到与给定id匹配的索引

# 从图像3目录中获取目标位置;
ycen = fd['ycentroid'][iix].value  # 获取目标的y中心坐标
xcen = fd['xcentroid'][iix].value  # 获取目标的x中心坐标

# 切割大小 = 2D光谱的y方向;
rsq = y2d  # 设置切割大小为y方向的维度

# 从数据中切割出科学图像和分割图;
sci_cut = data[int(ycen-rsq/2.+0.5):int(ycen+rsq/2.+0.5), int(xcen-rsq/2.+0.5):int(xcen+rsq/2.+0.5)]  # 切割科学图像
seg_cut = segdata[int(ycen-rsq/2.+0.5):int(ycen+rsq/2.+0.5), int(xcen-rsq/2.+0.5):int(xcen+rsq/2.+0.5)]  # 切割分割图

# 根据Grism观测的PA旋转图像;
if grism == 'G150C':  # 如果使用G150C光栅
    sci_rot = rotate(sci_cut, PA_V3)  # 旋转科学图像
else:  # 否则
    sci_rot = rotate(sci_cut, PA_V3+90)  # 旋转科学图像,增加90度

WFSS光栅在下图中沿x轴方向分散。#

plt.imshow(sci_rot, vmin=0, vmax=1.0)  # 显示旋转后的科学图像,设置最小值为0,最大值为1.0

plt.title('Direct image')  # 设置图像标题为“直接图像”

plt.xlabel('Wavelength direction >>>', color='r', fontsize=18)  # 设置x轴标签为“波长方向 >>>”,字体颜色为红色,字体大小为18

plt.ylabel('Cross-dispersion direction >>>', color='r', fontsize=18)  # 设置y轴标签为“交叉色散方向 >>>”,字体颜色为红色,字体大小为18

2. 在不同的 x 位置获取光谱轮廓 — 这将用于最佳提取。#

for ii in range(sci_rot.shape[1]):  # 遍历sci_rot的每一列

    flux_tmp = sci_rot[:, ii]  # 获取当前列的光谱数据

    xx_tmp = np.arange(0, len(sci_rot[:, ii]), 1)  # 创建一个与光谱数据长度相同的数组,用于x轴

    plt.plot(xx_tmp, flux_tmp, label='x={}'.format(ii))  # 绘制当前列的光谱数据,并设置标签

plt.legend(loc=0, fontsize=8)  # 显示图例,位置为0,字体大小为8
# 沿着 x 方向(分散方向)求和

flux_y = np.zeros(len(sci_rot[:, 0]), 'float')  # 创建一个与 sci_rot 行数相同的零数组,用于存储 y 方向的 flux

for ii in range(sci_rot.shape[0]):  # 遍历 sci_rot 的每一行

    flux_y[ii] = np.sum(sci_rot[ii, :])  # 对每一行的所有列求和,并存储到 flux_y 中

# 如果需要,进行天空背景减法。

# sky = np.mean([flux_y[0], flux_y[-1]])  # 计算 flux_y 的首尾平均值,作为天空背景(注释掉)

# 归一化处理;

flux_y[:] /= flux_y.sum()  # 将 flux_y 中的每个元素除以其总和,实现归一化

plt.plot(xx_tmp, flux_y)  # 绘制 y 位置与源通量的关系图

plt.xlabel('y-position')  # 设置 x 轴标签为 'y-position'

plt.ylabel('Source Flux')  # 设置 y 轴标签为 'Source Flux'

3. 一维提取;#

显示管道 1D 提取作为示例;#

# 正常提取;

flux_disp1 = np.zeros(x2d, 'float')  # 初始化光谱通量数组

err_disp1 = np.zeros(x2d, 'float')    # 初始化误差数组

wave_disp1 = np.zeros(x2d, 'float')   # 初始化波长数组

    

for ii in range(x2d): # 在波长方向上循环

    mask_tmp = (dq_2d[:, ii] == 0) & (err_2d[:, ii] > 0)  # 创建掩膜,选择有效数据

    # 在一个盒子内求和;

    flux_disp1[ii] = np.sum(data_2d[:, ii][mask_tmp])  # 计算有效光谱通量的总和

    err_disp1[ii] = np.sqrt(np.sum(err_2d[:, ii][mask_tmp]**2))  # 计算有效误差的平方和的平方根

    wave_disp1[ii] = wave_2d[0, ii]  # 获取对应的波长值

plt.errorbar(wave_disp1, flux_disp1, yerr=err_disp1)  # 绘制带误差条的光谱图

plt.xlim(1.7, 2.3)  # 设置x轴范围

最优提取;#

# 根据 Horne(1986, PASP, 98, 609) 的方法进行处理

flux_disp = np.zeros(x2d, 'float')  # 初始化光谱通量数组
err_disp = np.zeros(x2d, 'float')    # 初始化误差数组
wave_disp = np.zeros(x2d, 'float')   # 初始化波长数组

# Sigma clipping(标准差剪切)
sig = 5.0  # 设置标准差阈值

for ii in range(x2d):  # ii : 波长元素索引
    # 创建掩膜; 
    # 1. DQ 数组(数据质量)
    # 2. 误差值
    # 3. CR 检测(宇宙射线)
    mask_tmp = (dq_2d[:, ii] == 0) & (err_2d[:, ii] > 0) & ((data_2d[:, ii] - flux_y[:] * flux_disp1[ii])**2 < sig**2 * err_2d[:, ii]**2)

    ivar = 1. / err_2d[:, ii]**2  # 计算逆方差

    num = flux_y[:] * data_2d[:, ii] * ivar  # 计算分子
    den = flux_y[:]**2 * ivar  # 计算分母

    # 计算光谱通量和误差
    flux_disp[ii] = num[mask_tmp].sum(axis=0) / den[mask_tmp].sum(axis=0)  # 计算有效光谱通量
    err_disp[ii] = np.sqrt(1./den[mask_tmp].sum(axis=0))  # 计算有效误差
    wave_disp[ii] = wave_2d[0, ii]  # 获取对应的波长

# 绘制误差条形图
plt.errorbar(wave_disp, flux_disp, yerr=err_disp)  # 绘制光谱通量与误差
plt.xlim(1.7, 2.3)  # 设置x轴范围
# 比较不同光谱数据的绘图

plt.errorbar(wave_disp, flux_disp, yerr=err_disp, color='r', label='Optimal')  # 绘制最佳光谱数据,红色线,带误差条

plt.errorbar(wave_disp1, flux_disp1, yerr=err_disp1, color='b', alpha=0.5, label='Box')  # 绘制盒子光谱数据,蓝色线,带误差条,透明度为0.5

plt.ylim(-10, 20000)  # 设置y轴的范围

plt.legend(loc=0)  # 显示图例,位置自动选择

plt.xlabel('Wavelength')  # 设置x轴标签为“波长”

4. 将一维光谱写入文件;#

file_1d = f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_1d_opt.fits'  # 定义输出文件名,包含目录和文件格式信息

# Now make it into a Spectrum1D instance.  # 创建一个Spectrum1D实例

obs = Spectrum1D(spectral_axis=wave_disp*u.um,  # 设置光谱轴,单位为微米
                 
                 flux=flux_disp*u.MJy,  # 设置光谱的通量,单位为MJy
                 
                 uncertainty=StdDevUncertainty(err_disp), unit='MJy')  # 设置不确定性,使用标准偏差不确定性

obs.write(file_1d, format='tabular-fits', overwrite=True)  # 将Spectrum1D实例写入文件,格式为tabular-fits,允许覆盖

5. 沿x轴的光谱轮廓 = 分散光谱的分辨率;#

由于WFSS没有光阑,任何分散光谱都受到源形态的影响。我们将在接下来的笔记本中需要对有效光谱分辨率的估计。在这里,我们尝试提前进行估算;

for ii in range(sci_rot.shape[0]):  # 遍历sci_rot数组的每一行

    flux_tmp = sci_rot[ii, :]  # 获取当前行的光谱数据

    xx_tmp = np.arange(0, len(sci_rot[ii, :]), 1)  # 创建一个与当前行长度相同的数组,用于x轴

    plt.plot(xx_tmp, flux_tmp, label=f'y={ii}')  # 绘制当前行的光谱数据,并添加标签

plt.legend(loc=1, fontsize=8)  # 显示图例,位置在右上角,字体大小为8

plt.xlabel('Wavelength direction')  # 设置x轴标签为“波长方向”

plt.title('Source light profile along dispersed direction', fontsize=14)  # 设置图表标题,字体大小为14

*除非您对空间分辨光谱感兴趣,否则您可以叠加数据并获得光谱轮廓作为一个良好的近似。#

# 沿着交叉色散方向求和

flux_x = np.zeros(len(sci_rot[0, :]), 'float')  # 创建一个与sci_rot的列数相同的零数组,用于存储每列的总和

for ii in range(sci_rot.shape[0]):  # 遍历sci_rot的每一行

    flux_x[ii] = np.sum(sci_rot[:, ii])  # 计算每一列的总和并存储到flux_x中

# 归一化;

flux_x[:] /= flux_x.sum()  # 将flux_x中的每个元素除以总和,以进行归一化

plt.plot(xx_tmp, flux_x, label='Convolution kernel')  # 绘制归一化后的flux_x与xx_tmp的关系图,并添加标签

plt.legend(loc=2)  # 显示图例,位置设置为2

使用Moffat函数拟合;#

# Fitting function with Moffat

# Moffat 函数定义
def moffat(xx, A, x0, gamma, alp):
    # 计算 Moffat 函数值
    yy = A * (1. + (xx - x0)**2 / gamma**2)**(-alp)
    return yy  # 返回计算结果

def fit_mof(xx, lsf):
    # 拟合 Moffat 函数
    # xx = lsf * 0  # 初始化 xx 为零(注释掉的代码)

    # for ii in range(len(lsf)):  # 注释掉的代码,原本用于计算 xx
    #    xx[ii] = ii - len(lsf) / 2.

    popt, pcov = curve_fit(moffat, xx, lsf)  # 使用 curve_fit 拟合 Moffat 函数
    return popt  # 返回拟合参数

def LSF_mof(xsf, lsf, f_plot=True):
    '''
    输入参数:
    =======
    xsf : 轮廓的 x 轴数据.
    lsf : 光强轮廓.
    '''

    # for ii in range(len(sci[0, :])):  # 注释掉的代码,原本用于计算 lsf
    #    lsf[ii] = np.mean(sci_rot[int(height/2.-5):int(height/2.+5), ii])
    #    xsf[ii] = ii - len(lsf) / 2.

    try:
        A, xm, gamma, alpha = fit_mof(xsf, lsf)  # 尝试拟合 Moffat 函数
    except RuntimeError:  # 捕获拟合失败的异常
        print('Fitting failed.')  # 输出拟合失败信息
        A, xm, gamma, alpha = -1, -1, -1, -1  # 设置参数为 -1 表示失败
        pass  # 继续执行

    if A > 0:  # 如果拟合参数 A 大于 0
        lsf_mod = moffat(xsf, A, 0, gamma, alpha)  # 计算模型光强轮廓

    if f_plot:  # 如果需要绘图
        yy = moffat(xsf, A, xm, gamma, alpha)  # 计算拟合的 Moffat 函数值
        plt.plot(xsf, yy, 'r.', ls='-', label='Data')  # 绘制数据点
        plt.plot(xsf, lsf_mod, 'b+', ls='-', label=f'Model: gamma={gamma:2f}\nalpha={alpha:2f}')  # 绘制拟合模型
        plt.legend()  # 添加图例
        plt.show()  # 显示图形

    return A, xm, gamma, alpha  # 返回拟合参数
# LSF, 线扩散函数

iix_peak = np.argmax(flux_x)  # 找到flux_x中最大值的索引

xx_tmp_shift = xx_tmp - xx_tmp[iix_peak]  # 将xx_tmp中的值减去峰值位置的值,以便中心化

A, xm, gamma, alpha = LSF_mof(xx_tmp_shift, flux_x)  # 调用LSF_mof函数计算参数A, xm, gamma和alpha
# 写入数据到文件

# 参数单位为像素

fm = open(f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_moffat.txt', 'w')  # 打开文件以写入模式

fm.write('# A x0 gamma alp\n')  # 写入文件头信息

fm.write('# Moffat function\n')  # 写入函数类型信息

fm.write(f'{A:.3f} {xm:.3f} {gamma:.3f} {alpha:.3f}\n')  # 写入Moffat函数的参数值,格式化为小数点后三位

fm.close()  # 关闭文件

对其他滤光片和其他天体重复上述操作。#

以下大列执行相同的过程,适用于其他滤光片和抖动位置。#

grism = 'G150C'  # 设置光栅类型为G150C

id = '00004'  # 设置ID

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

if not os.path.exists(DIR_OUT):  # 如果输出目录不存在
    os.mkdir(DIR_OUT)  # 创建输出目录

filts = ['f115w', 'f150w', 'f200w']  # 定义滤光片列表

ndithers = np.arange(0, 2, 1)  # 定义偏移量范围

sig = 5.0  # 设置信号阈值

for filt in filts:  # 遍历每个滤光片
    print(filt)  # 打印当前滤光片名称

    # 读取2D光谱数据
    file_2d = f'{DIR_DATA}l3_nis_{filt}_{grism}_s{id}_cal.fits'  # 构建2D光谱文件名
    hdu_2d = fits.open(file_2d)  # 打开2D光谱文件

    for ndither in ndithers:  # 遍历每个偏移量
        print(ndither)  # 打印当前偏移量

        if grism == 'G150C':  # 如果光栅类型为G150C
            # 如果光谱是水平的
            data_2d = hdu_2d[ndither*7+1].data  # 获取2D数据
            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)  # 旋转数据
            dq_2d = rotate(hdu_2d[ndither*7+2].data, 90)  # 旋转数据质量数组
            err_2d = rotate(hdu_2d[ndither*7+3].data, 90)  # 旋转误差数组
            wave_2d = rotate(hdu_2d[ndither*7+4].data, 90)  # 旋转波长数组

        y2d, x2d = data_2d.shape[:]  # 获取数据的形状

        plt.close()  # 关闭当前图形
        plt.imshow(data_2d, vmin=0, vmax=300)  # 显示2D数据图像
        plt.show()  # 展示图像

        # 重新提取2D图像
        # if ndither == 0:  # 如果是第一个偏移量
        rsq = y2d  # 设置方形区域的边长
        # 提取科学数据和分段数据
        sci_cut = data[int(ycen-rsq/2.+0.5):int(ycen+rsq/2.+0.5), int(xcen-rsq/2.+0.5):int(xcen+rsq/2.+0.5)]
        seg_cut = segdata[int(ycen-rsq/2.+0.5):int(ycen+rsq/2.+0.5), int(xcen-rsq/2.+0.5):int(xcen+rsq/2.+0.5)]

        # 不确定提取框的偏移是否是bug
        if grism == 'G150C':  # 如果光栅类型为G150C
            sci_rot = rotate(sci_cut, PA_V3+0)  # 旋转科学数据
        else:  # 如果光栅类型不是G150C
            sci_rot = rotate(sci_cut, PA_V3+0+90)  # 旋转科学数据

        # 这是为了光谱分辨率
        # 沿x轴获取光谱轮廓
        for ii in range(sci_rot.shape[0]):  # 遍历每一行
            flux_tmp = sci_rot[ii, :]  # 获取当前行的光谱数据
            xx_tmp = np.arange(0, len(sci_rot[ii, :]), 1)  # 创建x轴数组

        # 沿交叉色散方向求和
        flux_x = np.zeros(len(sci_rot[0, :]), 'float')  # 初始化光谱数据数组
        for ii in range(sci_rot.shape[0]):  # 遍历每一行
            flux_x[ii] = np.sum(sci_rot[ii, :])  # 计算每一行的总光谱

        # 归一化
        flux_x[:] /= flux_x.sum()  # 归一化光谱数据

        # LSF
        iix_peak = np.argmax(flux_x)  # 找到光谱峰值的索引
        xx_tmp_shift = xx_tmp - xx_tmp[iix_peak]  # 计算x轴的偏移量
        A, xm, gamma, alpha = LSF_mof(xx_tmp_shift, flux_x)  # 拟合Moffat函数

        if ndither == 0:  # 如果是第一个偏移量
            # 写入文件
            fm = open(f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_moffat.txt', 'w')  # 打开输出文件
            fm.write('# A x0 gamma alp\n')  # 写入标题
            fm.write(f'{A:.3f} {xm:.3f} {gamma:.3f} {alpha:.3f}\n')  # 写入拟合参数
            fm.close()  # 关闭文件

        # 这是为了最佳提取
        # 沿x(色散)方向求和
        flux_y = np.zeros(len(sci_rot[:, 0]), 'float')  # 初始化光谱数据数组
        for ii in range(sci_rot.shape[0]):  # 遍历每一行
            flux_y[ii] = np.sum(sci_rot[ii, :])  # 计算每一行的总光谱

        # 归一化
        flux_y[:] /= flux_y.sum()  # 归一化光谱数据

        # 根据Horne的方法
        flux_disp = np.zeros(x2d, 'float')  # 初始化光谱数据数组
        err_disp = np.zeros(x2d, 'float')  # 初始化误差数组
        wave_disp = np.zeros(x2d, 'float')  # 初始化波长数组

        for ii in range(x2d):  # 遍历每一列
            # 掩膜
            # 1. 数据质量数组
            # 2. 误差值
            # 3. CR检测
            mask_tmp = (dq_2d[:, ii] == 0) & (err_2d[:, ii] > 0)  # 创建掩膜
            ivar = 1. / err_2d[:, ii]**2  # 计算逆方差

            num = flux_y[:] * data_2d[:, ii] * ivar  # 计算分子
            den = flux_y[:]**2 * ivar  # 计算分母
            flux_disp[ii] = num[mask_tmp].sum(axis=0)/den[mask_tmp].sum(axis=0)  # 计算光谱数据
            err_disp[ii] = np.sqrt(1./den[mask_tmp].sum(axis=0))  # 计算误差
            wave_disp[ii] = wave_2d[0, ii]  # 获取波长

        plt.close()  # 关闭当前图形
        con_plot = (wave_disp > 0)  # 创建有效波长的掩膜
        plt.errorbar(wave_disp[con_plot], flux_disp[con_plot], yerr=err_disp[con_plot])  # 绘制误差条
        plt.ylim(-0, 3000)  # 设置y轴范围
        plt.show()  # 展示图像

        # 写入文件
        # 现在将其转换为Spectrum1D实例
        file_1d = f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_ndither{ndither}_1d_opt.fits'  # 构建1D光谱文件名

        if wave_disp[1] - wave_disp[0] < 0:  # 如果波长数组是递减的
            obs = Spectrum1D(spectral_axis=wave_disp[::-1]*u.um,  # 创建光谱对象
                             flux=flux_disp[::-1]*u.MJy,
                             uncertainty=StdDevUncertainty(err_disp[::-1]), unit='MJy')
        else:  # 如果波长数组是递增的
            obs = Spectrum1D(spectral_axis=wave_disp*u.um,  # 创建光谱对象
                             flux=flux_disp*u.MJy,
                             uncertainty=StdDevUncertainty(err_disp), unit='MJy')

        obs.write(file_1d, format='tabular-fits', overwrite=True)  # 写入1D光谱文件

另一个天体;#

吸收线星系

grism = 'G150C'  # 设置光栅类型为G150C

id = '00003'  # 设置ID

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

filts = ['f115w', 'f150w', 'f200w']  # 定义滤波器列表

ndithers = np.arange(0, 2, 1)  # 定义dither的范围,这里有两个dither

sig = 5.0  # 设置信号阈值

for filt in filts:  # 遍历每个滤波器

    print(filt)  # 打印当前滤波器

    # 读取2D光谱数据

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

    hdu_2d = fits.open(file_2d)  # 打开2D数据文件

    for ndither in ndithers:  # 遍历每个dither

        print(ndither)  # 打印当前dither

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

            # 如果光谱是水平的

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

            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)  # 旋转2D数据

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

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

            wave_2d = rotate(hdu_2d[ndither*7+4].data, 90)  # 旋转波长数组

        y2d, x2d = data_2d.shape[:]  # 获取2D数据的形状

        plt.close()  # 关闭当前图像

        plt.imshow(data_2d, vmin=0, vmax=20)  # 显示2D数据图像

        plt.show()  # 展示图像

        # 重新提取2D图像

        # if ndither == 0:

        rsq = y2d  # 设置提取区域的大小

        sci_cut = data[int(ycen-rsq/2.+0.5):int(ycen+rsq/2.+0.5), int(xcen-rsq/2.+0.5):int(xcen+rsq/2.+0.5)]  # 提取科学数据

        seg_cut = segdata[int(ycen-rsq/2.+0.5):int(ycen+rsq/2.+0.5), int(xcen-rsq/2.+0.5):int(xcen+rsq/2.+0.5)]  # 提取分段数据

        # 不确定提取框的偏移是否是bug

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

            sci_rot = rotate(sci_cut, PA_V3+0)  # 旋转科学数据

        else:  # 如果光栅不是G150C

            sci_rot = rotate(sci_cut, PA_V3+0+90)  # 旋转科学数据

        # 这是为了光谱分辨率

        # 沿x轴获取光谱轮廓

        for ii in range(sci_rot.shape[0]):  # 遍历每一行

            flux_tmp = sci_rot[ii, :]  # 获取当前行的光谱数据

            xx_tmp = np.arange(0, len(sci_rot[ii, :]), 1)  # 创建x轴坐标

        # 沿交叉色散方向求和

        flux_x = np.zeros(len(sci_rot[0, :]), 'float')  # 初始化光谱数据数组

        for ii in range(sci_rot.shape[0]):  # 遍历每一行

            flux_x[ii] = np.sum(sci_rot[ii, :])  # 计算每一行的总光谱

        # 归一化

        flux_x[:] /= flux_x.sum()  # 归一化光谱数据

        # LSF

        iix_peak = np.argmax(flux_x)  # 找到光谱数据的峰值索引

        xx_tmp_shift = xx_tmp - xx_tmp[iix_peak]  # 计算x轴的偏移量

        A, xm, gamma, alpha = LSF_mof(xx_tmp_shift, flux_x)  # 计算Moffat函数参数

        if ndither == 0:  # 如果是第一个dither

            # 写入文件

            fm = open(f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_moffat.txt', 'w')  # 打开输出文件

            fm.write('# A x0 gamma alp\n')  # 写入参数说明

            fm.write(f'{A:.3f} {xm:.3f} {gamma:.3f} {alpha:.3f}\n')  # 写入Moffat函数参数

            fm.close()  # 关闭文件

        # 这是为了最优提取

        # 沿x(色散)方向求和

        flux_y = np.zeros(len(sci_rot[:, 0]), 'float')  # 初始化光谱数据数组

        for ii in range(sci_rot.shape[0]):  # 遍历每一行

            flux_y[ii] = np.sum(sci_rot[ii, :])  # 计算每一行的总光谱

    

        # 归一化

        flux_y[:] /= flux_y.sum()  # 归一化光谱数据

        # 按照Horne的方法

        flux_disp = np.zeros(x2d, 'float')  # 初始化光谱数据数组

        err_disp = np.zeros(x2d, 'float')  # 初始化误差数组

        wave_disp = np.zeros(x2d, 'float')  # 初始化波长数组

        for ii in range(x2d):  # 遍历每个像素

            # 掩膜;

            # 1. 数据质量数组

            # 2. 误差值

            # 3. CR检测

            mask_tmp = (dq_2d[:, ii] == 0) & (err_2d[:, ii] > 0)  # 创建掩膜

            ivar = 1. / err_2d[:, ii]**2  # 计算逆方差

            num = flux_y[:] * data_2d[:, ii] * ivar  # 计算分子

            den = flux_y[:]**2 * ivar  # 计算分母

            flux_disp[ii] = num[mask_tmp].sum(axis=0)/den[mask_tmp].sum(axis=0)  # 计算光谱数据

            err_disp[ii] = np.sqrt(1./den[mask_tmp].sum(axis=0))  # 计算误差

            wave_disp[ii] = wave_2d[0, ii]  # 获取波长

        plt.close()  # 关闭当前图像

        con_plot = (wave_disp > 0)  # 创建有效波长的掩膜

        plt.errorbar(wave_disp[con_plot], flux_disp[con_plot], yerr=err_disp[con_plot])  # 绘制误差条图

        plt.ylim(-20, 100)  # 设置y轴范围

        plt.show()  # 展示图像

        # 写入:

        # 现在,将其转换为Spectrum1D实例。

        file_1d = f'{DIR_OUT}l3_nis_{filt}_{grism}_s{id}_ndither{ndither}_1d_opt.fits'  # 构建1D数据文件名

        if wave_disp[1] - wave_disp[0] < 0:  # 如果波长是递减的

            obs = Spectrum1D(spectral_axis=wave_disp[::-1]*u.um,  # 创建光谱对象,波长反向

                             flux=flux_disp[::-1]*u.MJy,  # 创建光谱对象,光谱反向

                             uncertainty=StdDevUncertainty(err_disp[::-1]), unit='MJy')  # 创建光谱对象,误差反向

        else:  # 如果波长是递增的

            obs = Spectrum1D(spectral_axis=wave_disp*u.um,  # 创建光谱对象,波长正常

                             flux=flux_disp*u.MJy,  # 创建光谱对象,光谱正常

                             uncertainty=StdDevUncertainty(err_disp), unit='MJy')  # 创建光谱对象,误差正常

        obs.write(file_1d, format='tabular-fits', overwrite=True)  # 写入1D光谱数据文件