下载宽场无缝光谱 (WFSS) 数据#

本笔记本使用 Python 的 astroquery.mast Observations 类,通过 MAST API 查询特定程序的特定数据产品。我们正在寻找 NGDEEP 程序(ID 2079)的 NIRISS 成像和 WFSS 文件。观测使用三种 NIRISS 滤光片:F115W、F150W 和 F200W,采用 GR150R 和 GR150C 光栅。一个 WFSS 观测序列 通常由一幅直接图像和同一阻挡滤光片下的光栅观测组成,以帮助识别场中的源。在程序 2079 中,曝光序列遵循以下模式:直接图像 -> GR150R -> 直接图像 -> GR150C -> 直接图像。

用例:使用 MAST 下载数据产品。

数据:来自程序 2079 观测 004 的 JWST/NIRISS 图像和光谱。

工具:astropy、astroquery、glob、matplotlib、numpy、os、pandas、(yaml)

跨仪器:所有

内容

作者:Camilla Pacifici (cpacifici@stsci.edu) & Rachel Plesha (rplesha@stsci.edu) & Jo Taylor (jotaylor@stsci.edu)

首次发布:2024 年 5 月

本笔记本受到 关于 MAST 的 JWebbinar 会议 的启发。

导入#

from astropy.io import fits  # 导入FITS文件处理库

from astroquery.mast import Observations  # 导入用于查询MAST(微波天文科学数据中心)的库

from matplotlib import pyplot as plt  # 导入绘图库用于数据可视化

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

import os  # 导入操作系统接口库

import glob  # 导入用于文件路径匹配的库

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

查询观测数据#

astroquery.mast中的观察类用于下载JWST数据。使用元数据(metadata)函数查看可用的搜索选项及其描述。

请注意,对于JWST,仪器名称具有特定格式。有关更多信息,请访问:https://outerspace.stsci.edu/display/MASTDOCS/JWST+Instrument+Names

# 获取观测数据的元数据
Observations.get_metadata("observations")  # 调用Observations类的get_metadata方法,获取指定观测的元数据

下载特定数据集的两种最常见方法是使用 提案 ID (proposal ID) 或使用 观测 ID (observation ID)

使用提案ID搜索#

# 选择提案ID、仪器和一些有用的关键词(在这种情况下是滤光片)。

obs_table = Observations.query_criteria(obs_collection=["JWST"],  # 查询JWST观测集合

                                        instrument_name=["NIRISS/IMAGE", "NIRISS/WFSS"],  # 查询NIRISS图像和WFSS仪器

                                        provenance_name=["CALJWST"], # 查询已执行的观测

                                        filters=['F115W', 'F150W', 'F200W'],  # 查询特定的滤光片

                                        proposal_id=[2079],  # 查询特定的提案ID

                                        )

print(len(obs_table), 'files found')  # 打印找到的文件数量

# 查看查询结果中感兴趣的几个列名的内容

obs_table[['obs_collection', 'instrument_name', 'filters', 'target_name', 'obs_id', 's_ra', 's_dec', 't_exptime', 'proposal_id']]  # 显示特定列的数据

使用观测ID搜索#

观测ID(obs_id)允许通过提案ID和观测ID灵活搜索,因为JWST文件名的结构使然。有关JWST文件命名约定的更多信息,请访问:https://jwst-pipeline.readthedocs.io/en/latest/jwst/data_products/file_naming.html。在本笔记本系列中,我们将仅使用程序2079中的两个观测之一(004)。

此外,在搜索条件中使用通配符也提供了灵活性。例如,我们可以指定“NIRISS*”,而不是同时指定“NIRISS/IMAGE”和“NIRISS/WFSS”,这样就可以同时匹配这两种文件模式。通配符在obs_id中也有效,因此我们不必列出所有不同的ID。

# 从特定的观测ID列表中获取下载文件的列表

obs_id_table = Observations.query_criteria(instrument_name=["NIRISS*"],  # 查询使用NIRISS仪器的观测
                                           provenance_name=["CALJWST"], # 查询已执行的观测数据
                                           obs_id=['jw02079-o004*'], # 搜索PID 2079的观测004
                                           ) 

# 这个数字会随着JWST管道和参考文件的更新而变化

print(len(obs_id_table), 'files found') # 打印找到的文件数量,预计约为613个文件

过滤和下载产品#

如果要下载的文件过多,API 将会超时。相反,最好将观测数据分批下载,一次下载一个。

batch_size = 5  # 每次最多下载5个文件,以最大化下载速度。

# 将文件列表 ``obs_id_table`` 按照批次大小进行分组。

obs_batches = [obs_id_table[i:i+batch_size] for i in range(0, len(obs_id_table), batch_size)]

print("有多少个批次?", len(obs_batches))  # 输出批次数量

single_group = obs_batches[0]  # 获取第一个批次,方便检查所获得的文件

print("检查第一个批次以确保它符合你想要下载的预期:") 

# 显示所需的列以检查数据
single_group['obs_collection', 'instrument_name', 'filters', 'target_name', 'obs_id', 's_ra', 's_dec', 't_exptime', 'proposal_id']

选择所需的产品类型。不同的级别如下:

  • 未校准文件 (uncalibrated files)

    • productType=[“SCIENCE”]

    • productSubGroupDescription=[‘UNCAL’]

    • calib_level=[1]

  • 速率图像 (rate images)

    • productType=[“SCIENCE”]

    • productSubGroupDescription=[‘RATE’]

    • calib_level=[2]

  • 光谱和成像的二级关联 (level 2 associations for both spectroscopy and imaging)

    • productType=[“INFO”]

    • productSubGroupDescription=[‘ASN’]

    • calib_level=[2]

  • 成像的三级关联 (level 3 associations for imaging)

    • productType=[“INFO”]

    • productSubGroupDescription=[‘ASN’]

    • dataproduct_type=[“image”]

    • calib_level=[3]

下载前的数据过滤#

# 创建一个字典,用于存储上述信息,以便检查过滤函数
file_dict = {
    'uncal': {  # 未校准数据
        'product_type': 'SCIENCE',  # 产品类型为科学数据
        'productSubGroupDescription': 'UNCAL',  # 产品子组描述为未校准
        'calib_level': [1]  # 校准级别为1
    },
    'rate': {  # 速率数据
        'product_type': 'SCIENCE',  # 产品类型为科学数据
        'productSubGroupDescription': 'RATE',  # 产品子组描述为速率
        'calib_level': [2]  # 校准级别为2
    },
    'level2_association': {  # 级别2关联数据
        'product_type': 'INFO',  # 产品类型为信息
        'productSubGroupDescription': 'ASN',  # 产品子组描述为关联
        'calib_level': [2]  # 校准级别为2
    },
    'level3_association': {  # 级别3关联数据
        'product_type': 'INFO',  # 产品类型为信息
        'productSubGroupDescription': 'ASN',  # 产品子组描述为关联
        'calib_level': [3]  # 校准级别为3
    },
}
# 查看每个不同级别的现有文件

files_to_download = []  # 初始化一个空列表,用于存储要下载的文件

for index, batch_exposure in enumerate(single_group):  # 遍历每个单组曝光数据

    print('*'*50)  # 打印分隔线

    print(f"Exposure #{index+1} ({batch_exposure['obs_id']})")  # 打印当前曝光的索引和观察ID

    # 从列表中提取产品名称以进行过滤
    products = Observations.get_product_list(batch_exposure)  # 获取当前曝光的产品列表

    for filetype, query_dict in file_dict.items():  # 遍历文件类型和查询字典

        print('File type:', filetype)  # 打印当前文件类型

        # 过滤产品以获取符合条件的文件
        filtered_products = Observations.filter_products(products,
                                                         productType=query_dict['product_type'],  # 产品类型
                                                         productSubGroupDescription=query_dict['productSubGroupDescription'],  # 产品子组描述
                                                         calib_level=query_dict['calib_level'],  # 校准级别
                                                         )

        print(filtered_products['productFilename'])  # 打印过滤后的产品文件名

        files_to_download.extend(filtered_products['productFilename'])  # 将文件名添加到下载列表中

        print()  # 打印空行以便于阅读

    print('*'*50)  # 打印分隔线

从上面可以看出,在观察列表 (obs_id_table) 中,每个曝光名称都有许多相关文件需要下载。这就是我们需要分批下载的原因。

下载数据#

要实际下载产品,请使用 Observations.download_products() 并提供过滤后的产品列表。

通常情况下,detector1 流水线 不需要进行调整,因此我们可以从 detector1 的输出,即速率文件(rate files)开始,而不是未校准文件(uncal files)。因此,我们只需要下载速率文件和关联文件。如果需要重新运行 detector1 流水线,则需要在 Observations.filter_products 调用中调整 productSubGroupDescriptioncalib_level 以下载未校准文件。

如果数据是专有的,您可能还需要设置您的 API 令牌。绝对不要将您的令牌提交到公共代码库。另一种方法是创建一个单独的配置文件(config_file.yaml),该文件仅对您可读,并包含一个键: ‘mast_token’ : API 令牌

要创建新的 API 令牌,请访问以下链接:

https://auth.mast.stsci.edu/token?suggested_name=Astroquery&suggested_scope=mast:exclusive_access

请注意,下载数据时需要使用版本 >= 0.4.7 的 astroquery,以便调用 flat=True。如果您更喜欢使用早期版本,请从调用中删除该行,下载数据,并将所有下载的子文件夹中的文件移动到由 download_dir 变量定义的单个目录中。

# 检查astroquery库的版本是否高于0.4.7。有关更多信息,请参见上面的说明

import astroquery  # 导入astroquery库

astroquery.__version__  # 获取并显示astroquery库的当前版本
import os  # 导入os模块,用于处理文件和目录

download_dir = 'data'  # 定义下载目录的名称

# 确保下载目录存在;如果不存在,则创建一个新目录
if not os.path.exists(download_dir):  # 检查下载目录是否存在
    os.mkdir(download_dir)  # 创建下载目录
# 现在让我们获取每批观测的产品,并筛选出仅感兴趣的产品。

for index, batch in enumerate(obs_batches):  # 遍历每个观测批次

    # 进度指示器...

    print('\n'+f'Batch #{index+1} / {len(obs_batches)}')  # 打印当前批次的索引和总批次数

    # 从我们的Astropy观测表中获取`obsid`标识符,以获取产品。

    obsids = batch['obsid']  # 获取当前批次的obsid列表

    print('Working with the following obsids:')  # 打印正在处理的obsid信息

    for number, obs_text in zip(obsids, batch['obs_id']):  # 遍历每个obsid及其对应的文本

        print(f"{number} : {obs_text}")  # 打印obsid及其描述

    # 获取产品列表 

    products = Observations.get_product_list(obsids)  # 获取与obsid相关的产品列表

    # 筛选产品以仅获取感兴趣的产品

    filtered_products = Observations.filter_products(products,  # 过滤产品列表

                                                     productType=["SCIENCE", "INFO"],  # 仅选择科学和信息类型的产品

                                                     productSubGroupDescription=["RATE", "ASN"], # 不使用"UNCAL",因为我们可以从速率文件开始

                                                     calib_level=[2, 3] # 不使用1,因为不获取UNCAL文件

                                                     )

    # 下载这些记录的产品。

    # `flat=True`选项将所有文件存储在指定的`download_dir`的单个目录中。

    manifest = Observations.download_products(filtered_products,  # 下载筛选后的产品

                                              download_dir=download_dir,  # 指定下载目录

                                              flat=True, # astroquery v0.4.7或更高版本仅适用

                                              ) 

    print('Products downloaded:\n', filtered_products['productFilename'])  # 打印下载的产品文件名

如果继续使用WFSS笔记本,我们需要再次确认是否已下载所有剩余笔记本所需的文件。应该已经下载了149个文件。

import glob  # 导入glob模块,用于文件路径匹配
import os    # 导入os模块,用于操作系统相关的功能

# 获取下载目录中所有以.fits和.json结尾的文件
downloaded_files = glob.glob(os.path.join(download_dir, '*.fits')) + glob.glob(os.path.join(download_dir, '*.json'))

# 打印下载的文件数量和下载目录
print(len(downloaded_files), 'files downloaded to:', download_dir)

检查下载的数据#

此功能的目的是让您更好地了解可用的数据。此外,您将能够使用此数据框(dataframe)选择特定的文件,以便更深入地查看您想要关注的模式(mode)。

ratefile_datadir = 'data/'  # 定义存放速率文件的目录

# 首先查找所有已下载的速率文件

rate_files = np.sort(glob.glob(os.path.join(ratefile_datadir, "*rate.fits")))  # 获取所有速率文件并排序

for file_num, ratefile in enumerate(rate_files):  # 遍历每个速率文件

    rate_hdr = fits.getheader(ratefile)  # 获取每个速率文件的主头信息

    # 我们想要存储的信息,这些信息可能对我们后续评估数据有用

    temp_hdr_dict = {"FILENAME": ratefile,  # 文件名

                     "TARG_RA": [rate_hdr["TARG_RA"]],  # 目标的赤经

                     "TARG_DEC": [rate_hdr["TARG_DEC"]],  # 目标的赤纬

                     "FILTER": [rate_hdr["FILTER"]],  # 使用的滤光片; GR150R/GR150C

                     "PUPIL": [rate_hdr["PUPIL"]],  # 使用的光阑; F090W, F115W, F140M, F150W F158M, F200W

                     "EXPSTART": [rate_hdr['EXPSTART']],  # 曝光开始时间 (MJD)

                     "PATT_NUM": [rate_hdr["PATT_NUM"]],  # 在WFSS中抖动模式内的位置编号

                     "NUMDTHPT": [rate_hdr["NUMDTHPT"]],  # 整个抖动模式中的点总数

                     "XOFFSET": [rate_hdr["XOFFSET"]],  # NIRISS的X偏移量(从模式起始位置,单位:角秒)

                     "YOFFSET": [rate_hdr["YOFFSET"]],  # NIRISS的Y偏移量(从模式起始位置,单位:角秒)

                     }

    # 将字典转换为pandas数据框

    if file_num == 0:  # 如果这是第一个文件,创建初始数据框

        rate_df = pd.DataFrame(temp_hdr_dict)  # 创建数据框

    else:  # 否则,为每个文件附加到数据框

        new_data_df = pd.DataFrame(temp_hdr_dict)  # 创建新的数据框

        # 合并两个数据框,创建一个包含所有数据的数据框

        rate_df = pd.concat([rate_df, new_data_df], ignore_index=True, axis=0)  # 合并数据框

rate_dfsort = rate_df.sort_values('EXPSTART', ignore_index=False)  # 按曝光开始时间排序数据框

# 将数据框保存到文件,以便后续读取

outfile = './list_ngdeep_rate.csv'  # 输出文件名

rate_dfsort.to_csv(outfile, sep=',', index=False)  # 保存数据框为CSV文件

print('Saved:', outfile)  # 打印保存信息

# 查看结果数据框

rate_dfsort  # 显示排序后的数据框

特别地,让我们来看一下这些速率文件遵循的观测序列。我们已经按曝光时间对上述文件进行了排序,因此它们在数据框中应该已经是按时间顺序排列的。

FILTER = CLEAR 表示直接图像,而 FILTER=GR150RFILTER=GR150C 表示分散图像。PUPIL 是使用的阻挡滤光片。前14个曝光构成了第一组序列:直接图像 -> 光栅 -> 直接图像 -> 光栅。对于分散图像和直接图像,还有多个抖动位置。多个直接图像的抖动将合并在 image3 中,而多个分散图像可以合并在 spec3 中。

# 显示 rate_df 数据框中指定列的前14行
rate_df[['EXPSTART', 'FILTER', 'PUPIL', 'PATT_NUM', 'XOFFSET', 'YOFFSET']].head(14)

下面展示的是前14个速率文件,以便直观了解上述序列。网格线作为视觉指导,帮助识别任何进行的抖动(dithers)。

# plot set up
fig = plt.figure(figsize=(20, 35))  # 创建一个20x35英寸的图形

cols = 3  # 每行的子图数量

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

# loop over the first 14 rate files and plot them
for plt_num, rf in enumerate(rate_dfsort[0:14]['FILENAME']):  # 遍历前14个速率文件

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

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

    # open the data and plot it
    with fits.open(rf) as hdu:  # 打开FITS文件
        data = hdu[1].data  # 获取数据

        data[np.isnan(data)] = 0  # 将NaN数据填充为0,以帮助matplotlib的颜色刻度

        ax.imshow(data, vmin=0, vmax=1.5, origin='lower')  # 显示数据图像,设置颜色范围

        # adding in grid lines as a visual aid
        for gridline in [500, 1000, 1500]:  # 添加网格线作为视觉辅助
            ax.axhline(gridline, color='black', alpha=0.5)  # 添加水平网格线
            ax.axvline(gridline, color='black', alpha=0.5)  # 添加垂直网格线

        ax.set_title(f"#{plt_num+1}: {hdu[0].header['FILTER']} {hdu[0].header['PUPIL']} Dither{hdu[0].header['PATT_NUM']}")  # 设置子图标题
空间望远镜标志