运行图像处理管道并创建源目录#

在本次运行中,我们从已经通过探测器1管道校准的速率文件开始。这将节省时间,因为我们不需要编辑探测器1过程中执行的任何步骤。因此,WFSS(宽场分光光谱)运行的第一步校准是将速率文件的直接图像通过JWST管道的Image2和Image3步骤。这包括创建源目录,通常需要根据管道的默认值进行调整。没有良好的源目录将导致在分散的WFSS图像中源的提取不够优化。

用例:管道的默认参数未能提取预期的源,因此需要设置自定义参数以获得新的合成图像和源目录。

数据:JWST/NIRISS图像和来自程序2079观察004的光谱。这些数据应存储在一个名为data的单一目录中,可以从之前的笔记本00_niriss_mast_query_data_setup.ipynb下载。

工具:astropy, crds, glob, jdaviz, json, jwst, matplotlib, numpy, os, pandas, urllib, warnings, zipfile

跨仪器:NIRISS

内容

作者:Rachel Plesha (rplesha@stsci.edu), Camilla Pacifici (cpacifici@stsci.edu), JWebbinar笔记本。

首次发布:2024年5月

最后测试:此笔记本最后一次测试是在JWST管道版本1.12.5和CRDS上下文jwst_1225.pmap下进行的。

导入与数据设置#

CRDS 文档#

CRDS Documentation

此文档提供了关于 CRDSCalibration Reference Data System)的详细信息,特别是在 JWSTJames Webb Space Telescope)数据处理中的应用。CRDS 是一个用于管理和分发天文数据处理所需的参考文件的系统。

主要内容#

  • CRDS 的功能:CRDS 负责确保在数据处理过程中使用的所有参考文件都是最新的,并且与特定的科学目标和观测条件相匹配。

  • 参考文件的类型:CRDS 管理多种类型的参考文件,包括但不限于:

    • 校准文件(Calibration Files)

    • 掩蔽文件(Mask Files)

    • 增益文件(Gain Files)

  • 如何使用 CRDS:用户可以通过命令行工具或编程接口访问 CRDS,以获取所需的参考文件。

  • 更新和维护:CRDS 定期更新,以确保用户能够访问最新的校准数据和参考文件。

有关更详细的信息和使用指南,请访问 CRDS Documentation

# 更新CRDS路径到你的本地目录
%env CRDS_PATH=crds_cache  # 设置CRDS缓存路径为'crds_cache'

# 设置CRDS服务器的URL
%env CRDS_SERVER_URL=https://jwst-crds.stsci.edu  # 设置CRDS服务器的URL
import os  # 导入操作系统模块

import glob  # 导入用于文件路径操作的模块

import json  # 导入JSON处理模块

import warnings  # 导入警告模块

import urllib  # 导入用于URL处理的模块

import zipfile  # 导入用于处理ZIP文件的模块

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

import pandas as pd  # 导入Pandas库,用于数据处理和分析

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

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

from astropy.coordinates import SkyCoord  # 从Astropy导入天文坐标模块

from astropy.table import Table  # 从Astropy导入表格模块

from jdaviz import Imviz  # 从jdaviz导入Imviz模块,用于图像可视化

from matplotlib import pyplot as plt  # 从Matplotlib导入绘图模块

%matplotlib widget  # 使用交互式绘图

# %matplotlib inline  # 注释掉的代码,用于在Jupyter Notebook中内嵌绘图

from jwst.pipeline import Image2Pipeline  # 从JWST管道导入Image2Pipeline模块

from jwst.pipeline import Image3Pipeline  # 从JWST管道导入Image3Pipeline模块

检查您正在使用的JWST(詹姆斯·韦伯太空望远镜)管道版本。要查看可用的最新管道版本或安装以前的版本,请访问 GitHub。同时,请确认您使用的 CRDS(参考数据服务)版本CRDS文档 解释了如何在JWST管道中设置特定的上下文。如果这两个值与上述最后测试的说明不同,则此笔记本可能会存在差异。

import jwst  # 导入JWST相关库

import crds  # 导入CRDS(天文数据记录系统)库

print('JWST Pipeliene Version:', jwst.__version__)  # 打印JWST管道的版本信息

print('CRDS Context:', crds.get_context_name('jwst'))  # 打印当前使用的CRDS上下文

数据目录 data_dir 应该包含所有的关联文件和速率文件,并且放在一个单一的平面目录中。default_run_image3custom_run_image3 是我们稍后将用于校准数据的目录。它们被分开是为了便于比较这两种输出。

data_dir = 'data'  # 数据目录,存放处理后的数据

default_run_image3 = 'default_image3_calibrated'  # 默认图像3运行结果保存路径(在data_dir内部)

custom_run_image3 = 'custom_image3_calibrated'  # 自定义图像3运行结果保存路径(在data_dir内部)

关联文件期望满足以下条件:1) 所有数据都在同一目录中;2) 您在该目录中执行管道调用。由于这个原因,我们需要切换到数据目录以运行成像管道。

# 如果您尚未从笔记本00下载数据,请运行此单元格。否则,可以跳过它。

# 从Box下载未校准的数据到数据目录:

boxlink = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/niriss_wfss_advanced/niriss_wfss_advanced_01_input.zip'  # 数据链接

boxfile = os.path.basename(boxlink)  # 获取文件名

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

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

zf.extractall(path=data_dir)  # 解压到指定的数据目录

# 将从box文件下载的文件移动到顶层数据目录

box_download_dir = os.path.join(data_dir, boxfile.split('.zip')[0])  # 构建下载目录路径

for filename in glob.glob(os.path.join(box_download_dir, '*')):  # 遍历下载目录中的所有文件

    if '.csv' in filename:  # 如果文件是CSV格式

        # 移动到当前目录

        os.rename(filename, os.path.basename(filename))  # 重命名并移动到当前目录

    else:  # 如果文件不是CSV格式

        # 移动到数据目录 

        os.rename(filename, os.path.join(data_dir, os.path.basename(filename)))  # 重命名并移动到数据目录

# 现在删除不必要的文件

os.remove(boxfile)  # 删除下载的zip文件

os.rmdir(box_download_dir)  # 删除下载目录
# 从之前创建的csv文件中,找到我们想要用spec2进行校准的所有光栅观测的列表

listrate_file = 'list_ngdeep_rate.csv'  # 定义包含光栅观测列表的csv文件名

rate_df = pd.read_csv(listrate_file)  # 读取csv文件并将其存储为DataFrame
cwd = os.getcwd()  # 获取当前工作目录

if cwd != data_dir:  # 如果当前目录不是数据目录,则切换到数据目录

    try:

        os.chdir(data_dir)  # 尝试切换到数据目录

    except FileNotFoundError:  # 如果找不到指定的目录

        print(f"Not able to change into: {data_dir}.\nRemaining in: {cwd}")  # 打印错误信息,显示无法切换目录

        pass  # 继续执行后面的代码
for temp_dir in [default_run_image3, custom_run_image3]:  # 遍历默认和自定义的运行图像目录

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

        os.mkdir(temp_dir)  # 如果不存在,则创建该目录

默认成像处理管道运行#

首先,对所有使用WFSS数据观测的直接图像运行默认的image2和image3步骤。

运行默认图像2#

图像2是在直接图像速率文件上运行的。虽然您的程序应该有有效的关联文件可以从MAST下载,但如果出于任何原因您需要自己制作关联文件,请参见创建自定义ASN文件

查看 Level 2 图像关联文件#

首先,查看关联(ASN)文件,以更好地理解其中包含的所有内容。

对于 image2 关联文件,每个观察序列中的每个抖动位置应该有一个 ASN 文件,这由 曝光策略 设置。在这种情况下,ASN 文件的数量应该与 rate_df 中的直接图像数量相匹配(FILTER=CLEAR),因为每个直接图像在观察序列中处于独特的抖动位置(XOFFSET, YOFFSET)。对于该程序和观测,在更换光栅之前有一张只有一个抖动的直接图像,在更换光栅之间有一张带有四个抖动的直接图像,以及在序列末尾有一张带有三个抖动的直接图像。这导致每个观察序列总共有八张图像,而在使用阻挡滤光片 F115W -> F115W -> F150W -> F150W -> F200W 的观测中,有五个观察序列。

import glob  # 导入glob模块,用于文件路径匹配

# 查找当前目录下所有包含'image2'和'asn'的json文件
image2_asns = glob.glob('*image2*asn*.json')

# 打印找到的'image2' ASN文件的数量
print(len(image2_asns), 'Image2 ASN files')  # 应该有40个image2的asn文件

# 打印与'CLEAR'过滤器匹配的速率文件数量
print(len(rate_df[rate_df['FILTER'] == 'CLEAR']), 'Direct Image rate files')  # 速率文件的数量应与关联文件的数量匹配
# 查看其中一个关联文件

# 加载第一个关联文件的数据
asn_data = json.load(open(image2_asns[0]))

# 遍历关联数据中的每个键值对
for key, data in asn_data.items():
    # 打印键及其对应的数据
    print(f"{key} : {data}")

从这个关联中,我们可以得出关于观测的许多信息:

  1. asn_typeasn_rule,我们可以看到这是一个 image2 关联。

  2. degraded_status,我们可以看到没有需要排除在校准之外的曝光。

  3. constraints,我们可以看到这不是一个时间序列观测(TSO),该观测是程序 2079 的一部分,使用 NIRISS 进行观测,采用了 CLEAR(即 WFSS 的成像)和 F150W 滤光片。

  4. products,我们可以看到只有一个关联的曝光。这在 image2 中是典型的,因为通常每个观测序列的每个微调只有一个曝光。

# 特别是,查看关联文件中的产品文件名:

for product in asn_data['products']:  # 遍历关联数据中的每个产品

    for key, value in product.items():  # 遍历产品中的每个键值对

        if key == 'members':  # 如果键是'members'

            print(f"{key}:")  # 打印键名

            for member in value:  # 遍历成员列表

                print(f"    {member['expname']} {member['exptype']}")  # 打印每个成员的文件名和类型

        else:  # 如果键不是'members'

            print(f"{key}: {value}")  # 打印键名和对应的值

运行 image2#

rate.fits 产品将被校准为 cal.fits 文件。关于在图像处理(Image2)部分执行的步骤的更多信息,可以在 Image2 管道文档 中找到。

在这种情况下,我们将输出保存到运行管道的同一目录中,以便随后可以使用输出的 cal 文件来运行图像处理(Image3)管道。

for img2_asn in image2_asns:  # 遍历所有的image2关联文件

    # check if the calibrated file already exists  # 检查校准文件是否已经存在
    asn_data = json.load(open(img2_asn))  # 读取关联文件的内容

    cal_file = f"{asn_data['products'][0]['name']}_cal.fits"  # 构建校准文件的名称

    if os.path.exists(cal_file):  # 如果校准文件已存在
        print(cal_file, 'cal file already exists.')  # 输出文件已存在的提示
        continue  # 跳过当前循环,继续下一个关联文件

    # if not, calibrated with image2  # 如果不存在,则进行校准
    img2 = Image2Pipeline.call(img2_asn, save_results=True)  # 调用Image2Pipeline进行校准并保存结果

运行默认图像3#

查看 Level 3 关联文件#

内容与 image2 非常相似,但请注意,现在有更多的成员被关联在一起,并且它们使用的是来自 image2 的单个指向校准文件。image3 重新采样并组合来自所有抖动和观测序列的相同阻挡滤光片(NIRISS WFSS 的 PUPIL)图像,以形成单个图像,这导致了更少的 image3 关联文件。

import glob  # 导入glob模块,用于文件路径操作
import numpy as np  # 导入numpy库,用于数组和数值计算

# 查找当前目录下所有包含'image3'和'asn'的json文件
image3_asns = glob.glob('*image3*asn*.json')

# 打印找到的image3 ASN文件的数量,应该是3个
print(len(image3_asns), 'Image3 ASN files')  # 应该有3个image3关联文件

# 计算使用的唯一阻挡滤光片的数量,应该与image3关联文件数量匹配
uniq_filters = np.unique(rate_df[rate_df['FILTER'] == 'CLEAR']['PUPIL'])

# 打印使用的唯一滤光片数量及其名称
print(f"{len(uniq_filters)} unique filters used: {uniq_filters}")
# 查看其中一个关联文件

# 从第一个关联文件中加载数据
image3_asn_data = json.load(open(image3_asns[0]))

# 遍历关联文件中的每个键值对
for key, data in image3_asn_data.items():
    # 打印键和值
    print(f"{key} : {data}")
# 特别地,查看关联文件中的产品文件名:

for product in image3_asn_data['products']:  # 遍历产品列表

    for key, value in product.items():  # 遍历每个产品的键值对

        if key == 'members':  # 如果键是'members'

            print(f"{key}:")  # 打印键名

            for member in value:  # 遍历成员列表

                print(f"    {member['expname']} {member['exptype']}")  # 打印每个成员的文件名和类型

        else:  # 如果键不是'members'

            print(f"{key}: {value}")  # 打印键名和对应的值

运行 image3#

在Image3中,cal.fits个别指向文件将被校准为一个合并的i2d.fits图像。Image3步骤也是最终源目录创建的地方,因此我们可以更改一些输入参数以获得更精细的输出源目录。这将在下面的自定义成像管道运行部分中完成。然而,我们将首先使用默认参数对数据进行校准。有关在管道的Image3部分执行的步骤的更多信息,请参阅Image3管道文档

注意:Image3的运行可能需要一些时间

for img3_asn in image3_asns:  # 遍历所有的image3关联文件

    # check if the calibrated file already exists  # 检查校准文件是否已存在
    asn_data = json.load(open(img3_asn))  # 读取关联文件的JSON数据

    cal_file = os.path.join(default_run_image3, f"{asn_data['products'][0]['name']}_i2d.fits")  # 构建校准文件的路径

    if os.path.exists(cal_file):  # 如果校准文件已经存在
        print(cal_file, 'cal file already exists.')  # 打印文件已存在的信息
        continue  # 跳过当前循环,继续下一个

    # if not, calibrated with image3  # 如果不存在,则进行image3的校准
    img3 = Image3Pipeline.call(img3_asn, save_results=True, output_dir=default_run_image3)  # 调用Image3Pipeline进行校准并保存结果
# 移除不必要的文件以节省磁盘空间

for crf in glob.glob(os.path.join(default_run_image3, '*crf.fits')):  # 遍历匹配路径下所有以'crf.fits'结尾的文件

    os.remove(crf)  # 删除找到的文件

检查默认结果#

# 这些都是来自Image3管道的结果

image3_i2d = np.sort(glob.glob(os.path.join(default_run_image3, '*i2d.fits'))) # 获取多个dither/mosaic的组合图像并排序

image3_segm = np.sort(glob.glob(os.path.join(default_run_image3, '*segm.fits'))) # 获取定义源范围的分割图并排序

image3_cat = np.sort(glob.glob(os.path.join(default_run_image3, '*cat.ecsv'))) # 获取定义特定像素下源的RA/Dec的源目录并排序

Matplotlib#

Matplotlib 在某些方面存在局限性,而 ImViz 可能更适合您的需求——特别是如果您喜欢以 WCS 坐标查看事物的话。为了笔记本的目的,我们将重点介绍一些使用 matplotlib 包的关键领域。

使用 i2d 组合图像和由 Image3 生成的源目录,我们可以直观地检查管道找到的源位置是否令人满意。在以下图中,管道定义为扩展源的部分以橙红色显示,而管道定义为点源的部分则以灰色显示。这一定义会影响 WFSS 图像中的提取框。

fig = plt.figure(figsize=(10, 10))  # 创建一个10x10英寸的图形

cols = 2  # 设置每行的子图数量为2

rows = int(np.ceil(len(image3_i2d) / cols))  # 计算所需的行数,向上取整

for plt_num, img in enumerate(image3_i2d):  # 遍历每个图像及其索引

    # 确定子图的位置
    xpos = (plt_num % 40) % cols  # 计算当前子图在列中的位置
    ypos = ((plt_num % 40) // cols)  # 计算当前子图在行中的位置,使用整除以获得整数

    # 创建子图
    ax = plt.subplot2grid((rows, cols), (ypos, xpos))  # 在指定的网格位置创建子图

    # 绘制图像
    with fits.open(img) as hdu:  # 打开FITS文件
        ax.imshow(hdu[1].data, vmin=0, vmax=0.3, origin='lower')  # 显示图像数据,设置颜色范围和原点位置
        ax.set_title(f"obs{hdu[0].header['OBSERVTN']} {hdu[0].header['PUPIL']}")  # 设置子图标题

    # 同时绘制相关的目录
    cat = Table.read(img.replace('i2d.fits', 'cat.ecsv'))  # 读取对应的目录文件
    extended_sources = cat[cat['is_extended'] == 1]  # 筛选出扩展源,1表示为真
    point_sources = cat[cat['is_extended'] == 0]  # 筛选出点源,0表示为假

    # 绘制扩展源的散点图
    ax.scatter(extended_sources['xcentroid'], extended_sources['ycentroid'], s=20, facecolors='None', edgecolors='orangered', alpha=0.9)
    # 绘制点源的散点图
    ax.scatter(point_sources['xcentroid'], point_sources['ycentroid'], s=20, facecolors='None', edgecolors='dimgrey', alpha=0.9)

# 帮助调整坐标轴以避免重叠;如果这不起作用,可以手动设置
plt.tight_layout()  # 自动调整子图参数以使其适合图形区域

分割图(segmentation maps)也是Image3流程的产物,它们用于帮助确定源目录(source catalog)。让我们来看看这些图,以确保我们对它所定义的源感到满意。

在分割图中,每个蓝色斑块(blob)应该对应一个物理目标(physical target)。有些情况下,源可能会发生混叠(blended),在这种情况下,制作分割图和源目录的参数应该进行调整。下面的观测004 F200W滤光片图像中可以看到一个例子,其中两个星系在坐标大约为(1600, 1300)的位置混叠成一个源。关于这一点将在自定义成像流程运行中详细讨论。

请注意,由于滤光片偏移差异,下面切割图所显示的视场(field of views)在每个滤光片下是不同的。

# 我们将多次查看这个,因此将其定义为一个函数
def plot_image_and_segmentation_map(i2d_images, segm_images, xmin=1250, xmax=1750, ymin=1250, ymax=1750, cat_suffix='cat.ecsv'):
    
    cols = 2  # 设置每行的列数为2
    rows = len(i2d_images)  # 获取i2d_images的数量作为行数

    fig = plt.figure(figsize=(10, 10*(rows/2)))  # 创建一个图形对象,设置大小

    for plt_num, img in enumerate(np.sort(np.concatenate([segm_images, i2d_images]))):
        # 确定子图的位置
        xpos = (plt_num % 40) % cols  # 计算当前子图的x坐标
        ypos = ((plt_num % 40) // cols)  # 计算当前子图的y坐标,使用整除以获得整数

        # 创建子图
        ax = plt.subplot2grid((rows, cols), (ypos, xpos))

        if 'i2d' in img:  # 如果图像是i2d类型
            cat = Table.read(img.replace('i2d.fits', cat_suffix))  # 读取对应的目录
            cmap = 'gist_gray'  # 设置颜色映射为灰度
        else:
            cmap = 'tab20c_r'  # 设置颜色映射为分类颜色

        # 绘制图像
        with fits.open(img) as hdu:  # 打开FITS文件
            ax.imshow(hdu[1].data, vmin=0, vmax=0.3, origin='lower', cmap=cmap)  # 显示图像数据
            title = f"obs{hdu[0].header['OBSERVTN']} {hdu[0].header['PUPIL']}"  # 获取标题信息

        # 也绘制相关的目录
        extended_sources = cat[cat['is_extended'] == 1]  # 选择扩展源
        point_sources = cat[cat['is_extended'] == 0]  # 选择点源

        for color, sources in zip(['darkred', 'black'], [extended_sources, point_sources]):
            # 绘制源
            ax.scatter(sources['xcentroid'], sources['ycentroid'], s=20, facecolors='None', edgecolors=color, alpha=0.9)

            # 添加源标签
            for i, source_num in enumerate(sources['label']):
                ax.annotate(source_num, 
                            (sources['xcentroid'][i]+0.5, sources['ycentroid'][i]+0.5), 
                            fontsize=8,
                            color=color)  # 在源位置添加标签

        if 'i2d' in img:  # 如果是i2d图像
            ax.set_title(f"{title} combined image")  # 设置标题为组合图像
        else:
            ax.set_title(f"{title} segmentation map")  # 设置标题为分割图

        # 放大到较小的区域
        ax.set_xlim(xmin, xmax)  # 设置x轴范围
        ax.set_ylim(ymin, ymax)  # 设置y轴范围

    # 帮助使坐标轴不重叠;如果这不起作用,您也可以手动设置
    plt.tight_layout()  # 调整布局以避免重叠

    return fig  # 返回图形对象
# 调用函数绘制图像及其分割图
default_fig = plot_image_and_segmentation_map(image3_i2d, image3_segm)

ImViz#

与DS9类似,ImViz允许您交互式地查看这些图像及其对应的源目录。

imviz = Imviz()  # 创建 Imviz 实例

viewer = imviz.default_viewer  # 获取默认的查看器

# 绘制每个 i2d 图像
catalogs = []  # 用于绘制目录的列表
labels = []  # 用于绘制目录的标签列表

for img in image3_i2d:  # 遍历每个 i2d 图像
    print(f'Plotting: {img}')  # 打印当前绘制的图像名称

    # 获取观测编号和光学元件信息作为标签
    label = f"obs{fits.getval(img, 'OBSERVTN')} {fits.getval(img, 'PUPIL')}"

    with warnings.catch_warnings():  # 捕获警告
        warnings.simplefilter('ignore')  # 忽略警告
        imviz.load_data(img, data_label=label)  # 加载图像数据

    # 保存信息以便后续绘制目录
    catalogs.append(img.replace('i2d.fits', 'cat.ecsv'))  # 生成目录文件名并添加到列表
    labels.append(label)  # 添加标签到列表

# 这将图像对齐以使用 WCS 坐标;
# 图像需要先加载,但在添加标记之前
linking = imviz.plugins['Orientation']  # 获取方向插件
linking.link_type = 'WCS'  # 设置链接类型为 WCS

# 还要绘制相关的目录
# 由于在使用天空坐标时需要在 imviz 中链接,因此需要一个单独的循环
for catname, label in zip(catalogs, labels):  # 遍历目录名称和标签
    cat = Table.read(catname)  # 读取目录文件

    # 将表格格式化为 imviz 期望的格式
    sky_coords = Table({'coord': [SkyCoord(ra=cat['sky_centroid'].ra.degree,  # 创建天空坐标
                                           dec=cat['sky_centroid'].dec.degree,
                                           unit="deg")]})

    viewer.marker = {'color': 'orange', 'alpha': 1, 'markersize': 20, 'fill': False}  # 设置标记样式
    viewer.add_markers(sky_coords, use_skycoord=True, marker_name=f"{label} catalog")  # 添加标记到查看器

# 这将改变所有图像的拉伸
plotopt = imviz.plugins['Plot Options']  # 获取绘图选项插件
plotopt.select_all(viewers=True, layers=True)  # 选择所有查看器和图层
plotopt.stretch_preset = '99.5%'  # 设置拉伸预设为 99.5%

imviz.show()  # 显示 Imviz 窗口

自定义成像处理流程运行#

图像3#

尝试编辑几个参数,并将结果与上述默认运行进行比较,最初针对单个文件。

当我们调用 image3 管道时,可以对管道的特定步骤进行修改。在这种情况下,我们将编辑管道的 source_catalogtweakreg 步骤。关于可以调整的不同参数的解释,请参见下面的进一步信息,而默认值是该处列出的默认管道值和提供的参数参考文件的组合。

# 获取所有包含'image3'和'asn'的JSON文件,并按名称排序
image3_asns = np.sort(glob.glob('*image3*asn*.json'))

# 选择第二个asn文件进行处理
test_asn = image3_asns[1]

# 检查校准后的文件是否已经存在
asn_data = json.load(open(test_asn))  # 读取asn文件内容

# 构建i2d文件的路径
i2d_file = os.path.join(custom_run_image3, f"{asn_data['products'][0]['name']}_i2d.fits")

# 如果i2d文件已经存在,打印提示信息
if os.path.exists(i2d_file):
    print(i2d_file, 'i2d file already exists.')  # 输出文件已存在的信息
else:
    # 调用image3管道,进行处理,并添加一些新的修改
    cust_img3 = Image3Pipeline.call(test_asn,
                                    steps={
                                           'source_catalog': {'kernel_fwhm': 5.0,  # 设置源目录的FWHM
                                                              'snr_threshold': 10.0,  # 设置信噪比阈值
                                                              'npixels': 50,  # 设置像素数量
                                                              'deblend': True,  # 启用去混叠
                                                              },
                                           'tweakreg': {'snr_threshold': 20,  # 设置信噪比阈值
                                                        'abs_refcat': 'GAIADR3',  # 设置绝对参考目录
                                                        'save_catalogs': True,  # 保存目录
                                                        'searchrad': 3.0,  # 设置搜索半径
                                                        'kernel_fwhm': 2.302,  # 设置核的FWHM
                                                        'fitgeometry': 'shift',  # 设置拟合几何
                                                        },
                                           },
                                    save_results=True,  # 保存结果
                                    output_dir=custom_run_image3)  # 指定输出目录
# 导入必要的库
import os  # 用于处理文件和目录
import glob  # 用于查找符合特定规则的文件路径名

# 删除不必要的文件以节省磁盘空间
for crf in glob.glob(os.path.join(custom_run_image3, '*crf.fits')):  # 查找所有以'crf.fits'结尾的文件

    os.remove(crf)  # 删除找到的文件

检查自定义结果#

# 生成默认的i2d文件路径,将默认运行图像路径与i2d文件名结合
default_i2d = os.path.join(default_run_image3, os.path.basename(i2d_file))

# 创建一个列表,包含待比较的i2d文件
compare_i2ds = [i2d_file, default_i2d]

# 创建一个列表,包含待比较的分割文件,替换文件名中的'i2d.fits'为'segm.fits'
compare_segm = [i2d_file.replace('i2d.fits', 'segm.fits'), default_i2d.replace('i2d.fits', 'segm.fits')]

# 调用函数绘制图像和分割图,并将比较结果存储在compare_fig中
compare_fig = plot_image_and_segmentation_map(compare_i2ds, compare_segm)

下面的单元格使用Imviz来可视化类似的信息。

imviz = Imviz()  # 创建Imviz实例

viewer = imviz.default_viewer  # 获取默认查看器

# 遍历图像文件和标签
for img, label in zip([i2d_file, os.path.join(default_run_image3, os.path.basename(i2d_file))], ['custom', 'default']):
    
    print(f'Plotting: {img}')  # 打印当前绘制的图像文件名

    # 创建标题,包含观测编号和光学元件信息
    title = f"{label} obs{fits.getval(img, 'OBSERVTN')} {fits.getval(img, 'PUPIL')}"

    with warnings.catch_warnings():  # 捕获警告
        warnings.simplefilter('ignore')  # 忽略警告
        imviz.load_data(img, data_label=title)  # 加载数据到Imviz中

    # 这将图像对齐以使用WCS坐标
    linking = imviz.plugins['Orientation']  # 获取方向插件
    linking.link_type = 'WCS'  # 设置链接类型为WCS

    # 还绘制相关的目录
    cat = Table.read(img.replace('i2d.fits', 'cat.ecsv'))  # 读取对应的目录文件

    # 将表格格式化为imviz所需的格式
    t_xy = Table({'x': cat['xcentroid'],  # 提取x中心坐标
                  'y': cat['ycentroid']})  # 提取y中心坐标

    viewer.marker = {'color': 'orange', 'alpha': 1, 'markersize': 20, 'fill': False}  # 设置标记样式
    viewer.add_markers(t_xy, marker_name=f"{label} catalog")  # 添加标记到查看器中

# 这将改变所有图像的拉伸
plotopt = imviz.plugins['Plot Options']  # 获取绘图选项插件
plotopt.select_all(viewers=True, layers=True)  # 选择所有查看器和图层
plotopt.stretch_preset = '99.5%'  # 设置拉伸预设为99.5%

imviz.show()  # 显示Imviz界面

如果您对上述结果满意,请对剩余图像进行校准。

# 获取所有包含'image3'和'asn'的JSON文件,并按名称排序
image3_asns = np.sort(glob.glob('*image3*asn*.json'))

# 遍历每个找到的'image3'的asn文件
for img3_asn in image3_asns:

    # 检查校准后的文件是否已经存在
    asn_data = json.load(open(img3_asn))  # 读取asn文件内容为JSON格式

    # 构建输出文件的路径
    i2d_file = os.path.join(custom_run_image3, f"{asn_data['products'][0]['name']}_i2d.fits")

    # 如果输出文件已存在,打印提示并跳过该文件
    if os.path.exists(i2d_file):
        print(i2d_file, 'cal file already exists.')  # 输出已存在的文件信息
        continue  # 跳过后续处理,继续下一个文件

    # 调用image3处理管道,进行图像处理,并添加一些新的修改
    cust_img3 = Image3Pipeline.call(img3_asn,
                                    steps={  # 定义处理步骤
                                           'source_catalog': {'kernel_fwhm': 5.0,  # 源目录步骤的参数设置
                                                              'snr_threshold': 10.0,  # 信噪比阈值
                                                              'npixels': 50,  # 最小像素数
                                                              'deblend': True,  # 是否去混叠
                                                              },

                                           'tweakreg': {'snr_threshold': 20,  # 调整注册步骤的信噪比阈值
                                                        'abs_refcat': 'GAIADR3',  # 绝对参考目录
                                                        'save_catalogs': True,  # 是否保存目录
                                                        'searchrad': 3.0,  # 搜索半径
                                                        'kernel_fwhm': 2.302,  # 核函数的全宽半高
                                                        'fitgeometry': 'shift',  # 拟合几何形状
                                                        },

                                           },
                                    save_results=True,  # 保存结果
                                    output_dir=custom_run_image3)  # 指定输出目录
# 导入所需的库
import os  # 用于文件和目录操作
import glob  # 用于文件路径匹配

# 移除不必要的文件以节省磁盘空间
for crf in glob.glob(os.path.join(custom_run_image3, '*crf.fits')):  # 查找匹配的文件

    os.remove(crf)  # 删除找到的文件
# 这些都是来自Image3管道的结果

cust_image3_i2d = np.sort(glob.glob(os.path.join(custom_run_image3, '*i2d.fits')))  # 获取多个dither/mosaic合成图像的文件路径并排序

cust_image3_segm = np.sort(glob.glob(os.path.join(custom_run_image3, '*segm.fits')))  # 获取分割图的文件路径并排序,该图定义了源的范围

custom_fig = plot_image_and_segmentation_map(cust_image3_i2d, cust_image3_segm)  # 绘制图像及其分割图

进一步完善源目录#

在上述情况下,我们直接使用管道(pipeline)修改了结果。也许您想使用其他人的自定义源目录(source catalog),或者进一步修改管道输出的源目录。在这些情况下,我们需要修改spec2 ASN文件,以指向新的源目录,这将在spec2笔记本中讨论。此外,匹配不同目录中的所有源ID(source ID)也是很有用的。在这种情况下,管道创建了三个不同的目录,它们识别相同的RA/Dec或X/Y像素位置,但源ID不同,因此我们将编辑这些目录,使它们在源ID值上保持一致。这些额外的步骤并不总是必要的,但在NIRISS WFSS数据的分析中可能会有所帮助。

在目录中匹配源ID#

在上面的图中,您可以看到同一个源有多个源ID。在这里,我们希望匹配所有观测中的源ID,以确保无论我们查看哪个滤光片或观测,都在讨论同一个源。为此,我们使用 astropy 的 match_to_catalog_3d 函数并重新基准化标签。

第一步是决定一个基础目录,以便与所有其他目录进行匹配。在这里,我们将使用观测004 F115W滤光片。

custom_cats = np.sort(glob.glob(os.path.join(custom_run_image3, '*niriss_clear-f????_cat.ecsv'))) # 获取所有符合格式的cat文件路径并排序

print("All image3 catalogs:\n", custom_cats) # 打印所有找到的image3目录

base_cat = Table.read(custom_cats[0]) # 读取第一个catalog文件作为基础目录

# save out the base catalog with a new name to be consistent
base_cat_name = custom_cats[0].replace('cat.ecsv', 'source-match_cat.ecsv') # 替换文件名以生成新的基础目录名称

base_cat.write(base_cat_name, overwrite=True) # 将基础目录写入新文件,允许覆盖

print("\nBase catalog:", base_cat_name) # 打印新基础目录的名称

遍历剩余的目录,以基于天球坐标匹配 ID,匹配精度为 1 角秒以内。

max_sep = 1 * u.arcsec  # 设置最大分离角度为1弧秒,如果需要可以调整

base_sky = base_cat['sky_centroid']  # 获取基础目录中的天空中心坐标

for to_match_cat in custom_cats[1:]:  # 遍历自定义目录中的每个目录,从第二个开始

    # 读取目录
    other_cat = Table.read(to_match_cat)  # 读取当前匹配的目录
    other_sky = other_cat['sky_centroid']  # 获取当前目录中的天空中心坐标

    # 根据天空坐标找到两个目录之间的匹配源
    idx, d2d, d3d = base_sky.match_to_catalog_3d(other_sky)  # 进行3D匹配,返回索引和距离
    sep_constraint = d2d < max_sep  # 根据最大分离角度筛选匹配源

    base_matches = base_cat[sep_constraint]  # 获取基础目录中匹配的源
    other_matches = other_cat[idx[sep_constraint]]  # 获取其他目录中匹配的源

    # 重新设置ID值,使其相同
    other_matches['label'] = base_matches['label']  # 将匹配的标签从基础目录复制到其他目录

    # 保存新的目录
    match_cat_name = to_match_cat.replace('cat.ecsv', 'source-match_cat.ecsv')  # 修改文件名以保存匹配目录
    other_matches.write(match_cat_name, overwrite=True)  # 写入匹配目录,覆盖同名文件
    print('Saved:', match_cat_name)  # 打印保存的文件名

查看新的源标签编号。它们应该匹配!

# 调用函数绘制图像和分割图,并保存结果
new_cat_fig = plot_image_and_segmentation_map(
    cust_image3_i2d,  # 输入的图像数据
    cust_image3_segm,  # 输入的分割数据
    cat_suffix='source-match_cat.ecsv',  # 输出文件的后缀名
    xmin=1500,  # x轴最小值
    xmax=2000,  # x轴最大值
    ymin=800,   # y轴最小值
    ymax=1300   # y轴最大值
)

手动编辑源目录#

展望WFSS(宽场光谱仪)提取,我们可能在某一特定时刻只关心一到两个源。在这种情况下,缩减源目录至仅包含这些源会更有助于查看WFSS数据中提取的1-D光谱。

要开始,请查看当前某个滤光器的自定义源目录,以了解目录中包含的内容。

# 首先,查看F200W滤光器的当前自定义源目录

# 获取自定义运行目录中所有匹配的源目录文件,并按字母顺序排序,选择第三个文件
cat_name = np.sort(glob.glob(os.path.join(custom_run_image3, '*source-match_cat.ecsv')))[2]

# 读取源目录文件,存储为Table对象
cat = Table.read(cat_name)

# 打印源目录文件的名称
print(cat_name)

# 显示源目录的内容
cat

寻找源(source)可能有多种方法,以下是三种选项:

  1. 使用已知的赤经/赤纬(RA/Dec)来定位一个对象

  2. 使用已知的x/y位置来定位一个对象

  3. 使用对象的源ID(source ID)。请注意,我们在这里使用的是重新基准化的源目录(rebased source catalogs)中的ID。

这个概念可以扩展到通过源目录中包含的任何列进行过滤。

# 已知的赤经/赤纬

desired_ra = 53.15437048946369  # 目标赤经

desired_dec = -27.771689847051736  # 目标赤纬

# 创建一个SkyCoord对象,使用目标的赤经和赤纬
c = SkyCoord(ra=desired_ra*u.degree, dec=desired_dec*u.degree)

# 将目标坐标与目录中的天体坐标进行匹配,返回最近的天体ID和距离
nearest_id, distance_2d, distance_3d = c.match_to_catalog_sky(cat['sky_centroid']) 

# 从目录中提取与最近天体ID对应的相关信息
cat[['label', 'xcentroid', 'ycentroid', 'sky_centroid', 'is_extended', 'isophotal_abmag', 'isophotal_vegamag']][nearest_id]
# 在F200W图像中使用已知的X/Y像素位置(基于你定义的cat)

known_x = 1880  # 定义已知的X坐标

known_y = 1100  # 定义已知的Y坐标

# 计算每个天体到已知位置的距离
nearest_pos = [np.sqrt((x-known_x)**2 + (y-known_y)**2) for x, y in zip(cat['xcentroid'], cat['ycentroid'])]

# 找到距离最近的天体的索引
wh_nearest = np.where(np.array(nearest_pos) == min(nearest_pos))[0][0]

# 输出距离最近的天体的相关信息
cat[['label', 'xcentroid', 'ycentroid', 'sky_centroid', 'is_extended', 'isophotal_abmag', 'isophotal_vegamag']][wh_nearest]
# alternatively with a known source number
# 另外一种方法是使用已知的源编号

# note that this number might be different for you depending on pipeline versions and parameters changed.
# 请注意,这个编号可能会因管道版本和参数更改而有所不同。

# source = 109 # if you want to specify the number
# source = 109 # 如果你想指定一个编号

source = cat['label'][nearest_id] # to match the one chosen in this notebook
# source = cat['label'][nearest_id] # 从分类目录中获取最近的源编号,以匹配本笔记本中选择的编号

wh_source = np.where(np.array(cat['label'] == source))[0][0]
# wh_source = np.where(np.array(cat['label'] == source))[0][0] # 找到与源编号匹配的索引位置

cat[['label', 'xcentroid', 'ycentroid', 'sky_centroid', 'is_extended', 'isophotal_abmag', 'isophotal_vegamag']][wh_source]
# 从分类目录中提取相关信息,包括标签、中心坐标、天空坐标、是否扩展、等光度绝对星等和等光度维根星等

使用上述三种方法中的任意一种,编写新的目录。

  • 使用 RA/Dec: new_cat = Table(cat[nearest_id])

  • 使用 x/y: new_cat = Table(cat[wh_nearest])

  • 使用源 ID: new_cat = Table(cat[wh_source])

new_cat = Table(cat[wh_source])  # 将行实例转换回数据框

# 保存新目录,使用唯一名称
new_cat_name = cat_name.replace('cat.ecsv', f'source{source}_cat.ecsv')  # 替换文件名中的部分以生成新名称

new_cat.write(new_cat_name, overwrite=True)  # 将新目录写入文件,允许覆盖

print('Saved:', new_cat_name)  # 打印保存的文件名

一旦我们有了一个满意的更新源目录,我们就可以进入管道的 spec2 步骤。在运行 spec2 后,可能需要返回到这一步。让我们快速查看一下在接下来的笔记本中,我们将使用 spec2 提取的源。

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 6))  # 创建一个1行2列的子图,设置图形大小为9x6

img = cust_image3_i2d[-1]  # 获取最后一幅图像

cat = Table.read(new_cat_name)  # 读取新的目录数据

for ax in [ax1, ax2]:  # 遍历两个子图

    # plot the image  # 绘制图像
    with fits.open(img) as hdu:  # 打开图像文件
        ax.imshow(hdu[1].data, vmin=0, vmax=0.3, origin='lower')  # 显示图像数据,设置颜色范围和原点位置
        title = f"obs{hdu[0].header['OBSERVTN']} {hdu[0].header['PUPIL']}"  # 获取观测和光学元件信息

    # also plot the associated catalog  # 也绘制相关的目录
    extended_sources = cat[cat['is_extended'] == 1]  # 选择扩展源,1表示为真
    point_sources = cat[cat['is_extended'] == 0]  # 选择点源,0表示为假

    for color, sources in zip(['darkred', 'black'], [extended_sources, point_sources]):  # 遍历颜色和源
        # plotting the sources  # 绘制源
        ax.scatter(sources['xcentroid'], sources['ycentroid'], s=20, facecolors='None', edgecolors=color, alpha=0.9)  # 绘制源的散点图

        # adding source labels  # 添加源标签
        for i, source_num in enumerate(sources['label']):  # 遍历源标签
            ax.annotate(source_num,  # 注释源标签
                        (sources['xcentroid'][i]+0.5, sources['ycentroid'][i]+0.5),  # 设置标签位置
                        fontsize=8,  # 设置字体大小
                        color=color)  # 设置标签颜色

fig.suptitle("Speicifc Source to Extract with Spec2")  # 设置整个图形的标题

# zooming in on a smaller region  # 放大到一个较小的区域
ax2.set_xlim(known_x-50, known_x+50)  # 设置x轴范围
ax2.set_ylim(known_y-50, known_y+50)  # 设置y轴范围

fig.tight_layout()  # 调整子图间距
空间望远镜标志