点源光度测量#

使用案例: 光度测量、交叉匹配目录、推导并应用光度零点。

数据: 使用 MIRAGE 模拟的 NIRCam 图像,并通过 JWST 流水线 处理大麦哲伦云 (LMC) 天体测量校准场。模拟使用了 4 点子像素抖动,针对三对宽滤光片:SW 通道的 F070W、F115W 和 F200W,以及 LW 通道的 F277W、F356W 和 F444W。我们仅模拟了 1 个 NIRCam SW 探测器(即 “NRCB1”)。在此示例中,我们使用两个 SW 滤光片(即 F115W 和 F200W)的 Level-3 图像(已校准和整形),并在每个图像中推导光度测量。其他滤光片的图像也可用,并可用于测试笔记本和/或不同滤光片组合。

工具: astropy, photutils。

跨仪器: NIRSpec, NIRISS, MIRI。

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

最终图表显示:

  • 仪器颜色-星等图及误差

  • 星等零点

  • 校准颜色-星等图(与输入颜色-星等图比较)

  • 输入与输出光度测量的比较

导入函数#

import os  # 导入操作系统模块

import sys  # 导入系统模块

import time  # 导入时间模块

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

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

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

import tarfile  # 导入tarfile模块,用于处理tar文件

import urllib.request  # 导入urllib.request模块,用于处理URL请求

import jwst  # 导入JWST相关模块

from jwst.datamodels import ImageModel  # 从JWST数据模型中导入图像模型

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

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

from astropy.visualization import (ZScaleInterval, SqrtStretch, ImageNormalize)  # 导入图像可视化相关模块

from astropy.visualization import simple_norm  # 导入简单归一化函数

from astropy.stats import sigma_clipped_stats, SigmaClip  # 导入统计分析相关模块

from astropy.coordinates import SkyCoord, match_coordinates_sky  # 导入天文坐标相关模块

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

from photutils.detection import DAOStarFinder  # 从Photutils库导入星点检测器

from photutils.background import MMMBackground, MADStdBackgroundRMS, Background2D  # 导入背景处理相关模块

from photutils.aperture import CircularAperture, CircularAnnulus, aperture_photometry  # 导入光度测量相关模块

导入绘图函数#

%matplotlib inline  # 在Jupyter Notebook中内联显示图形

from matplotlib import style, pyplot as plt  # 导入matplotlib的样式和绘图模块

import matplotlib.patches as patches  # 导入绘图补丁模块,用于添加形状

import matplotlib.ticker as ticker  # 导入刻度模块,用于自定义坐标轴刻度

# 设置图像的颜色映射为'viridis'
plt.rcParams['image.cmap'] = 'viridis'

# 设置图像的原点位置为左下角
plt.rcParams['image.origin'] = 'lower'

# 设置坐标轴标题和标签的字体大小为30
plt.rcParams['axes.titlesize'] = plt.rcParams['axes.labelsize'] = 30

# 设置x轴和y轴刻度标签的字体大小为30
plt.rcParams['xtick.labelsize'] = plt.rcParams['ytick.labelsize'] = 30

# 定义字体样式1
font1 = {'family': 'helvetica', 'color': 'black', 'weight': 'normal', 'size': '12'}

# 定义字体样式2
font2 = {'family': 'helvetica', 'color': 'black', 'weight': 'normal', 'size': '20'}

加载图像并创建一些有用的字典#

我们加载所有图像,并创建一个包含所有图像的字典,按探测器(detectors)和滤光片(filters)进行分类。这对于检查可用的探测器和滤光片非常有用,并帮助我们决定是否对所有图像进行光度测量(photometry),还是仅对子集进行(例如,仅对SW滤光片进行)。

我们还创建了一个包含一些分析所需参数的字典。该字典包含光度零点(photometric zeropoints,来自MIRAGE配置文件)和NIRCam点扩散函数(PSF)全宽半最大值(FWHM),这些数据来自NIRCam点扩散函数的JDox页面。FWHM是通过分析使用WebbPSF模拟的预期NIRCam PSF计算得出的。

注意:一旦每个探测器/滤光片的零点和FWHM值在调试完成后可用,该字典将会更新。

因此,我们有两个字典:

  • Level-3图像的字典

  • 其他一些有用参数的字典

# 初始化一个字典,用于存储不同探测器的图像数据
dict_images = {'NRCA1': {}, 'NRCA2': {}, 'NRCA3': {}, 'NRCA4': {}, 'NRCA5': {},
               'NRCB1': {}, 'NRCB2': {}, 'NRCB3': {}, 'NRCB4': {}, 'NRCB5': {}}

# 初始化短波和长波过滤器的字典
dict_filter_short = {}
dict_filter_long = {}

# 初始化短波和长波的过滤器和探测器列表
ff_short = []
det_short = []
det_long = []
ff_long = []
detlist_short = []
detlist_long = []
filtlist_short = []
filtlist_long = []

# 检查当前目录下是否存在已合并的fits文件
if not glob.glob('./*combined*fits'):
    print("Downloading images")  # 打印下载图像的提示

    # 定义图像数据的下载链接和保存路径
    boxlink_images_lev3 = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/stellar_photometry/images_level3.tar.gz'
    boxfile_images_lev3 = './images_level3.tar.gz'

    # 下载图像数据
    urllib.request.urlretrieve(boxlink_images_lev3, boxfile_images_lev3)

    # 解压下载的tar文件
    tar = tarfile.open(boxfile_images_lev3, 'r')
    tar.extractall(filter='data')  # 添加filter='data'以跳过绝对路径或相对路径

    # 设置图像目录
    images_dir = './'
    images = sorted(glob.glob(os.path.join(images_dir, "*combined*fits")))  # 获取所有合并的fits文件

else:
    # 如果已存在合并的fits文件,直接获取文件列表
    images_dir = './'
    images = sorted(glob.glob(os.path.join(images_dir, "*combined*fits")))

# 遍历所有图像文件
for image in images:
    im = fits.open(image)  # 打开fits文件
    f = im[0].header['FILTER']  # 获取过滤器信息
    d = im[0].header['DETECTOR']  # 获取探测器信息

    # 根据探测器类型进行重命名
    if d == 'NRCBLONG':
        d = 'NRCB5'
    elif d == 'NRCALONG':
        d = 'NRCA5'
    else:
        d = d

    wv = float(f[1:3])  # 获取波长信息

    # 根据波长将图像分类到短波或长波列表
    if wv > 24:
        ff_long.append(f)  # 添加到长波过滤器列表
        det_long.append(d)  # 添加到长波探测器列表
    else:
        ff_short.append(f)  # 添加到短波过滤器列表
        det_short.append(d)  # 添加到短波探测器列表

    # 获取短波和长波探测器的唯一列表
    detlist_short = sorted(list(dict.fromkeys(det_short)))
    detlist_long = sorted(list(dict.fromkeys(det_long)))

    # 初始化唯一过滤器列表
    unique_list_filters_short = []
    unique_list_filters_long = []

    # 为短波过滤器创建字典
    for x in ff_short:
        if x not in unique_list_filters_short:
            dict_filter_short.setdefault(x, {})

    # 为长波过滤器创建字典
    for x in ff_long:
        if x not in unique_list_filters_long:
            dict_filter_long.setdefault(x, {})

    # 将短波探测器与其对应的过滤器字典关联
    for d_s in detlist_short:
        dict_images[d_s] = dict_filter_short

    # 将长波探测器与其对应的过滤器字典关联
    for d_l in detlist_long:
        dict_images[d_l] = dict_filter_long

    # 获取短波和长波过滤器的唯一列表
    filtlist_short = sorted(list(dict.fromkeys(dict_filter_short)))
    filtlist_long = sorted(list(dict.fromkeys(dict_filter_long)))

    # 将图像添加到对应的探测器和过滤器字典中
    if len(dict_images[d][f]) == 0:
        dict_images[d][f] = {'images': [image]}  # 如果没有图像,初始化列表
    else:
        dict_images[d][f]['images'].append(image)  # 否则,添加图像到列表中

# 打印可用的探测器和过滤器信息
print("Available Detectors for SW channel:", detlist_short)  # 打印短波通道的可用探测器
print("Available Detectors for LW channel:", detlist_long)  # 打印长波通道的可用探测器
print("Available SW Filters:", filtlist_short)  # 打印可用的短波过滤器
print("Available LW Filters:", filtlist_long)  # 打印可用的长波过滤器
# 定义过滤器列表
filters = ['F070W', 'F090W', 'F115W', 'F140M', 'F150W2', 'F150W', 'F162M', 'F164N', 'F182M',
           'F187N', 'F200W', 'F210M', 'F212N', 'F250M', 'F277W', 'F300M', 'F322W2', 'F323N',
           'F335M', 'F356W', 'F360M', 'F405N', 'F410M', 'F430M', 'F444W', 'F460M', 'F466N', 'F470N', 'F480M']

# 定义点扩散函数的全宽半最大值(FWHM)
psf_fwhm = [0.987, 1.103, 1.298, 1.553, 1.628, 1.770, 1.801, 1.494, 1.990, 2.060, 2.141, 2.304, 2.341, 1.340,
            1.444, 1.585, 1.547, 1.711, 1.760, 1.830, 1.901, 2.165, 2.179, 2.300, 2.302, 2.459, 2.507, 2.535, 2.574]

# 定义模型A的零点磁量
zp_modA = [25.7977, 25.9686, 25.8419, 24.8878, 27.0048, 25.6536, 24.6957, 22.3073, 24.8258, 22.1775, 25.3677, 24.3296,
           22.1036, 22.7850, 23.5964, 24.8239, 23.6452, 25.3648, 20.8604, 23.5873, 24.3778, 23.4778, 20.5588,
           23.2749, 22.3584, 23.9731, 21.9502, 20.0428, 19.8869, 21.9002]

# 定义模型B的零点磁量
zp_modB = [25.7568, 25.9771, 25.8041, 24.8738, 26.9821, 25.6279, 24.6767, 22.2903, 24.8042, 22.1499, 25.3391, 24.2909,
           22.0574, 22.7596, 23.5011, 24.6792, 23.5769, 25.3455, 20.8631, 23.4885, 24.3883, 23.4555, 20.7007,
           23.2763, 22.4677, 24.1562, 22.0422, 20.1430, 20.0173, 22.4086]

# 创建一个字典,将过滤器与其对应的PSF FWHM和零点磁量关联起来
dict_utils = {filters[i]: {'psf fwhm': psf_fwhm[i], 'VegaMAG zp modA': zp_modA[i],
                           'VegaMAG zp modB': zp_modB[i]} for i in range(len(filters))}

选择用于分析的探测器和/或滤光片#

如果我们只对分析中的某些滤光片(和/或某些探测器)感兴趣,如本例所示,我们可以从字典中选择这些滤光片(探测器)的图像,并仅分析这些图像。

在这个特定的例子中,我们分析探测器 NRCB1 的滤光片 F115WF200W 的图像。

dets_short = ['NRCB1']  # 本例中感兴趣的探测器

filts_short = ['F115W', 'F200W']  # 本例中感兴趣的滤光片

显示图像#

为了检查我们的图像是否存在伪影并且可以用于分析,我们将其显示出来。

plt.figure(figsize=(14, 14))  # 创建一个14x14英寸的图形

for det in dets_short:  # 遍历短列表中的每个探测器

    for i, filt in enumerate(filts_short):  # 遍历短列表中的每个滤镜,并获取索引

        image = fits.open(dict_images[det][filt]['images'][0])  # 打开指定探测器和滤镜的图像文件
        data_sb = image[1].data  # 获取图像数据

        ax = plt.subplot(1, len(filts_short), i + 1)  # 创建子图,行数为1,列数为滤镜数量,当前索引为i+1

        plt.xlabel("X [px]", fontdict=font2)  # 设置X轴标签
        plt.ylabel("Y [px]", fontdict=font2)  # 设置Y轴标签
        plt.title(filt, fontdict=font2)  # 设置子图标题为当前滤镜名称

        norm = simple_norm(data_sb, 'sqrt', percent=99.)  # 使用平方根归一化数据,99%分位数

        ax.imshow(data_sb, norm=norm, cmap='Greys')  # 显示图像数据,使用灰度色图

plt.tight_layout()  # 调整子图布局以避免重叠

光圈光度法 (Aperture Photometry)#

有关使用 Photutils 进行光圈光度法的更多信息,请访问这里:光圈光度法

首先,我们创建一个有用的字典,其中包含:使用我们的查找算法检测到的源(DAOStarFinder),包含光圈光度结果的表格,以及一个最终的表格,包含检测到的源的位置(包括 x,y 和 RA,Dec)、仪器星等(instrumental magnitudes)及其误差。

dict_aper = {}  # 初始化一个空字典,用于存储光圈数据

for det in dets_short:  # 遍历短名称的探测器列表

    dict_aper.setdefault(det, {})  # 为每个探测器设置一个空字典,如果不存在则创建

    for j, filt in enumerate(filts_short):  # 遍历短名称的滤光片列表,并获取索引

        dict_aper[det].setdefault(filt, {})  # 为每个滤光片设置一个空字典,如果不存在则创建

        dict_aper[det][filt]['sources found'] = None  # 初始化“找到的源”键,值为None

        dict_aper[det][filt]['aperture phot table'] = None  # 初始化“光圈光度表”键,值为None

        dict_aper[det][filt]['final aperture phot table'] = None  # 初始化“最终光圈光度表”键,值为None

1. 寻找源#

DAOStarFinder 使用 DAOFIND (Stetson 1987) 算法在图像中检测星星。DAOFIND 在图像中搜索局部密度最大值,这些最大值的峰值幅度大于 threshold(大致上;阈值应用于卷积图像),并且具有与定义的二维高斯核相似的大小和形状。

注意:我们采用了作为背景估计器的函数 MMMBackground,该函数使用 DAOPHOT MMM 算法计算整个图像的背景。

在处理可变背景和/或需要屏蔽没有数据的区域(例如,如果我们正在分析包含所有 4 个 NIRCam SW 探测器的图像,即包含芯片间隙的图像)时,我们可以设置 var_bkg = True,并使用一种更复杂的算法来考虑这些问题。

请注意,来自管道的 Level-2 和 Level-3 图像的单位是 MJy/sr(因此是表面亮度)。图像的实际单位可以通过头文件关键字 BUNIT 检查。标量转换常数被复制到头文件关键字 PHOTMJSR,该关键字提供从 DN/s 到 megaJy/steradian 的转换。对于我们的分析,我们回退到 DN/s。

def find_stars(det='NRCA1', filt='F070W', var_bkg=False):
    # 定义查找星星的函数,参数包括探测器、滤光片和是否使用变背景

    bkgrms = MADStdBackgroundRMS()  # 初始化MAD标准背景RMS对象

    mmm_bkg = MMMBackground()  # 初始化MMM背景估计对象

    image = fits.open(dict_images[det][filt]['images'][i])  # 打开指定探测器和滤光片的图像文件
    data_sb = image[1].data  # 获取图像数据
    imh = image[1].header  # 获取图像头信息

    print("Finding sources on image {number}, filter {f}, detector {d}:".format(number=i + 1, f=filt, d=det))  # 打印当前处理的图像信息
    print("")  # 打印空行

    data = data_sb / imh['PHOTMJSR']  # 将图像数据转换为DN/S单位
    print("Conversion factor from {units} to DN/S for filter {f}:".format(units=imh['BUNIT'], f=filt), imh['PHOTMJSR'])  # 打印转换因子

    sigma_psf = dict_utils[filt]['psf fwhm']  # 获取滤光片的PSF FWHM值
    print("FWHM for the filter {f}:".format(f=filt), sigma_psf, "px")  # 打印FWHM值

    if var_bkg:  # 如果使用变背景
        print("Using 2D Background")  # 打印使用2D背景的信息
        sigma_clip = SigmaClip(sigma=3.)  # 初始化SigmaClip对象
        coverage_mask = (data == 0)  # 创建覆盖掩码,标记数据为0的区域

        # 计算2D背景
        bkg = Background2D(data, (25, 25), filter_size=(3, 3), sigma_clip=sigma_clip, bkg_estimator=mmm_bkg,
                           coverage_mask=coverage_mask, fill_value=0.0)

        data_bkgsub = data.copy()  # 复制数据
        data_bkgsub = data_bkgsub - bkg.background  # 从数据中减去背景

        _, _, std = sigma_clipped_stats(data_bkgsub)  # 计算经过sigma裁剪的统计量

    else:  # 如果不使用变背景
        std = bkgrms(data)  # 计算背景的标准差
        bkg = mmm_bkg(data)  # 估计背景

        data_bkgsub = data.copy()  # 复制数据
        data_bkgsub -= bkg  # 从数据中减去背景

    # 初始化DAOStarFinder以查找星源
    daofind = DAOStarFinder(threshold=th[j] * std, fwhm=sigma_psf, roundhi=1.0,
                            roundlo=-1.0, sharplo=0.30, sharphi=1.40)

    found_stars = daofind(data_bkgsub)  # 在背景减去后的数据中查找星源
    dict_aper[det][filt]['sources found'] = found_stars  # 将找到的星源存入字典

    print("")  # 打印空行
    print("Number of sources found in the image:", len(found_stars))  # 打印找到的星源数量
    print("-------------------------------------")  # 打印分隔线
    print("")  # 打印空行
tic = time.perf_counter()  # 记录开始时间

th = [10, 10]  # 两个滤波器的阈值水平(长度必须与分析的滤波器数量匹配)

for det in dets_short:  # 遍历短时间检测器列表

    for j, filt in enumerate(filts_short):  # 遍历短时间滤波器列表,并获取索引

        for i in np.arange(0, len(dict_images[det][filt]['images']), 1):  # 遍历当前检测器和滤波器的图像

            find_stars(det=det, filt=filt, var_bkg=False)  # 调用函数查找星星,背景变量设为False

toc = time.perf_counter()  # 记录结束时间

print("Elapsed Time for finding stars:", toc - tic)  # 输出查找星星的耗时

2. 光圈光度法 (Aperture Photometry)#

在下面的函数中,我们需要指定4个参数,以执行光圈光度法:用于分析的滤光片(即图像)、光圈半径以及用于确定局部背景的环形区域的内半径和外半径。光圈半径和环形区域的值取决于所采用的滤光片和用户的科学案例。

请注意,光圈对象支持多个位置,允许在每个位置执行多个光圈的光度测量。对于多个光圈,输出表的列名会附加位置索引。

def aperture_phot(det=det, filt=filt, radius=[3.5], sky_in=7, sky_out=10):
    # 定义一个函数,用于进行光度测量,输入参数包括探测器、滤光片、半径、内外天区半径

    positions = np.transpose((dict_aper[det][filt]['sources found']['xcentroid',
                              dict_aper[det][filt]['sources found']['ycentroid'])))
    # 获取源的位置坐标,x和y中心坐标

    image = fits.open(dict_images[det][filt]['images'][0])
    # 打开指定的图像文件

    data_sb = image[1].data
    # 获取图像数据

    imh = image[1].header
    # 获取图像头信息

    data = data_sb / imh['PHOTMJSR']
    # 将数据归一化,除以光度单位

    aa = np.argwhere(data < 0)
    # 找到数据中小于0的元素的索引

    for i in np.arange(0, len(aa), 1):
        data[aa[i][0], aa[i][1]] = 0
    # 将小于0的值置为0

    error = np.sqrt(data)
    # 计算数据的误差,使用平方根

    tic = time.perf_counter()
    # 记录开始时间

    table_aper = Table()
    # 创建一个空的表格,用于存储光度测量结果

    for rad in radius:
        print("Performing aperture photometry for filter {1}; radius r = {0} px".format(rad, filt))
        # 打印当前正在处理的滤光片和半径信息

        rr = str(rad)
        # 将半径转换为字符串

        aperture = CircularAperture(positions, r=rad)
        # 创建一个圆形光圈对象

        annulus_aperture = CircularAnnulus(positions, r_in=sky_in, r_out=sky_out)
        # 创建一个圆环天区对象

        annulus_mask = annulus_aperture.to_mask(method='center')
        # 将圆环天区转换为掩模

        bkg_median = []
        # 初始化背景中值列表

        bkg_stdev = []
        # 初始化背景标准差列表

        for mask in annulus_mask:
            annulus_data = mask.multiply(data)
            # 使用掩模提取圆环区域的数据

            annulus_data_1d = annulus_data[mask.data > 0]
            # 将掩模区域的数据展平为一维数组

            _, median_sigclip, stdev_sigclip = sigma_clipped_stats(annulus_data_1d)
            # 计算背景的中值和标准差,使用sigma剪切法

            bkg_median.append(median_sigclip)
            # 将中值添加到列表中

            bkg_stdev.append(stdev_sigclip)
            # 将标准差添加到列表中

        bkg_median = np.array(bkg_median)
        # 将背景中值列表转换为数组

        bkg_stdev = np.array(bkg_stdev)
        # 将背景标准差列表转换为数组

        phot = aperture_photometry(data, aperture, method='exact', error=error)
        # 进行光度测量,使用精确方法

        phot['annulus_median'] = bkg_median
        # 将背景中值添加到光度测量结果中

        phot['aper_bkg'] = bkg_median * aperture.area
        # 计算并添加光圈背景值

        phot['aper_sum_bkgsub'] = phot['aperture_sum'] - phot['aper_bkg']
        # 计算并添加背景校正后的光圈总和

        table_aper.add_column(phot['aperture_sum'], name='aper_sum_' + rr + 'px')
        # 将光圈总和添加到结果表中

        table_aper.add_column(phot['annulus_median'], name='annulus_median_' + rr + 'px')
        # 将背景中值添加到结果表中

        table_aper.add_column(phot['aper_bkg'], name='aper_bkg_' + rr + 'px')
        # 将光圈背景值添加到结果表中

        table_aper.add_column(phot['aper_sum_bkgsub'], name='aper_sum_bkgsub_' + rr + 'px')
        # 将背景校正后的光圈总和添加到结果表中

        error_poisson = phot['aperture_sum_err']
        # 获取光圈总和的泊松误差

        error_scatter_sky = aperture.area * bkg_stdev**2
        # 计算由于背景散射引起的误差

        error_mean_sky = bkg_stdev**2 * aperture.area**2 / annulus_aperture.area
        # 计算平均背景引起的误差

        fluxerr = np.sqrt(error_poisson + error_scatter_sky + error_mean_sky)
        # 计算总误差

        table_aper.add_column(fluxerr, name='flux_err_' + rr + 'px')
        # 将总误差添加到结果表中

        dict_aper[det][filt]['aperture phot table'] = table_aper
        # 将结果表存储到字典中

    toc = time.perf_counter()
    # 记录结束时间

    print("Time Elapsed:", toc - tic)
    # 打印耗时信息

    return
    # 返回函数结束
det = 'NRCB1'  # 设置探测器为NRCB1

filt1 = 'F115W'  # 设置第一个滤光片为F115W

filt2 = 'F200W'  # 设置第二个滤光片为F200W

# 对于第一个滤光片进行光度测量,使用半径为3.5的光圈,天空区域内外半径分别为7和10
aperture_phot(det=det, filt=filt1, radius=[3.5], sky_in=7, sky_out=10)

# 对于第二个滤光片进行光度测量,使用半径为4.0的光圈,天空区域内外半径分别为6和9
aperture_phot(det=det, filt=filt2, radius=[4.0], sky_in=6, sky_out=9)

3. 包含星等和位置的表格#

我们创建最终的表格,包含每个目录的位置信息、星等和误差。

radii = [3.5, 4.0]  # 定义半径列表

for det in dets_short:  # 遍历短时间检测列表

    for j, filt in enumerate(filts_short):  # 遍历短时间滤波器列表

        for i in np.arange(0, len(dict_images[det][filt]['images']), 1):  # 遍历图像索引

            radius = str(radii[j])  # 将当前半径转换为字符串

            image = fits.open(dict_images[det][filt]['images'][0])  # 打开图像文件
            data = image[1].data  # 获取图像数据

            image_model = ImageModel(dict_images[det][filt]['images'][0])  # 创建图像模型

            # 创建掩膜,筛选出有效的源
            mask = ((dict_aper[det][filt]['sources found']['xcentroid'] > 0) &
                    (dict_aper[det][filt]['sources found']['xcentroid'] < data.shape[1]) &
                    (dict_aper[det][filt]['sources found']['ycentroid'] > 0) &
                    (dict_aper[det][filt]['sources found']['ycentroid'] < data.shape[0]) &
                    (dict_aper[det][filt]['aperture phot table']['aper_sum_bkgsub_' + radius + 'px'] > 0))

            table_phot = Table()  # 创建一个新的表格对象

            # 将掩膜筛选后的x和y坐标存入表格
            table_phot['x'] = dict_aper[det][filt]['sources found']['xcentroid'][mask]
            table_phot['y'] = dict_aper[det][filt]['sources found']['ycentroid'][mask]

            # 使用WCS将x和y坐标转换为RA和Dec
            ra, dec = image_model.meta.wcs(table_phot['x'], table_phot['y'])
            table_phot['radec'] = SkyCoord(ra, dec, unit='deg')  # 将RA和Dec存入表格

            # 计算并存储光度
            table_phot[filt + '_inst'] = -2.5 * np.log10(dict_aper[det][filt]['aperture phot table']['aper_sum_bkgsub_' + radius + 'px'][mask])
            # 计算并存储光度误差
            table_phot['e' + filt + '_inst'] = 1.086 * (dict_aper[det][filt]['aperture phot table']['flux_err_' + radius + 'px'][mask] /
                                                        dict_aper[det][filt]['aperture phot table']['aper_sum_bkgsub_' + radius + 'px'][mask])

            dict_aper[det][filt]['final aperture phot table'] = table_phot  # 将最终的光度表存入字典

匹配源表#

我们交叉匹配目录以获得单一的颜色-亮度图(color-magnitude diagram)。

如果匹配之间的距离小于 0.5 像素(px),则来自两个滤光片的星体被关联。

max_sep = 0.015 * u.arcsec  # 定义最大分离角度为0.015角秒

# 匹配两个滤波器的天体坐标
idx_inst, d2d_inst, _ = match_coordinates_sky(dict_aper[det][filt1]['final aperture phot table']['radec'],
                                              dict_aper[det][filt2]['final aperture phot table']['radec'])

sep_constraint_inst = d2d_inst < max_sep  # 计算分离角度是否小于最大分离角度

matched_sources = Table()  # 创建一个新的表格以存储匹配的源

# 将匹配的源的坐标和其他信息添加到表格中
matched_sources['radec'] = dict_aper[det][filt1]['final aperture phot table']['radec'][sep_constraint_inst]  # 添加滤波器1的坐标
matched_sources['x_' + filt1] = dict_aper[det][filt1]['final aperture phot table']['x'][sep_constraint_inst]  # 添加滤波器1的x坐标
matched_sources['y_' + filt1] = dict_aper[det][filt1]['final aperture phot table']['y'][sep_constraint_inst]  # 添加滤波器1的y坐标
matched_sources['x_' + filt2] = dict_aper[det][filt2]['final aperture phot table']['x'][idx_inst[sep_constraint_inst]]  # 添加滤波器2的x坐标
matched_sources['y_' + filt2] = dict_aper[det][filt2]['final aperture phot table']['y'][idx_inst[sep_constraint_inst]]  # 添加滤波器2的y坐标
matched_sources[filt1 + '_inst'] = dict_aper[det][filt1]['final aperture phot table'][filt1 + '_inst'][sep_constraint_inst]  # 添加滤波器1的测量值
matched_sources['e' + filt1 + '_inst'] = dict_aper[det][filt1]['final aperture phot table']['e' + filt1 + '_inst'][sep_constraint_inst]  # 添加滤波器1的误差
matched_sources[filt2 + '_inst'] = dict_aper[det][filt2]['final aperture phot table'][filt2 + '_inst'][idx_inst[sep_constraint_inst]]  # 添加滤波器2的测量值
matched_sources['e' + filt2 + '_inst'] = dict_aper[det][filt2]['final aperture phot table']['e' + filt2 + '_inst'][idx_inst[sep_constraint_inst]]  # 添加滤波器2的误差

仪器颜色-星等图及误差#

在误差图中,灰色点表示在每个滤光片中所有检测的测量值,而黑色点表示在两个目录之间匹配的检测值。

plt.figure(figsize=(12, 14))  # 创建一个12x14英寸的图形

plt.clf()  # 清空当前图形

ax1 = plt.subplot(1, 2, 1)  # 创建1行2列的子图,选择第1个子图

ax1.set_xlabel(filt1 + '_inst -' + filt2 + '_inst', fontdict=font2)  # 设置x轴标签

ax1.set_ylabel(filt1 + '_inst', fontdict=font2)  # 设置y轴标签

xlim0 = -0.5  # x轴下限

xlim1 = 0.8   # x轴上限

ylim0 = -0.5  # y轴下限

ylim1 = -9    # y轴上限

ax1.set_xlim(xlim0, xlim1)  # 设置x轴范围

ax1.set_ylim(ylim0, ylim1)  # 设置y轴范围

ax1.xaxis.set_major_locator(ticker.AutoLocator())  # 设置x轴主刻度定位器

ax1.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置x轴次刻度定位器

ax1.yaxis.set_major_locator(ticker.AutoLocator())  # 设置y轴主刻度定位器

ax1.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置y轴次刻度定位器

ax1.scatter(matched_sources[filt1 + '_inst'] - matched_sources[filt2 + '_inst'], matched_sources[filt1 + '_inst'],  # 绘制散点图
            s=1, color='k')  # 设置点的大小和颜色

ax2 = plt.subplot(2, 2, 2)  # 创建2行2列的子图,选择第2个子图

ax2.set_xlabel(filt1 + '_inst', fontdict=font2)  # 设置x轴标签

ax2.set_ylabel('$\sigma$ ' + filt1, fontdict=font2)  # 设置y轴标签

xlim0 = -11  # x轴下限

xlim1 = -0.5  # x轴上限

ylim0 = -0.01  # y轴下限

ylim1 = 1  # y轴上限

ax2.set_xlim(xlim0, xlim1)  # 设置x轴范围

ax2.set_ylim(ylim0, ylim1)  # 设置y轴范围

ax2.xaxis.set_major_locator(ticker.AutoLocator())  # 设置x轴主刻度定位器

ax2.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置x轴次刻度定位器

ax2.yaxis.set_major_locator(ticker.AutoLocator())  # 设置y轴主刻度定位器

ax2.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置y轴次刻度定位器

ax2.scatter(dict_aper[det][filt1]['final aperture phot table'][filt1 + '_inst'],  # 绘制散点图
            dict_aper[det][filt1]['final aperture phot table']['e' + filt1 + '_inst'], s=1, c='gray')  # 设置点的大小和颜色

ax2.scatter(matched_sources[filt1 + '_inst'], matched_sources['e' + filt1 + '_inst'], s=1, c='k')  # 绘制散点图

ax3 = plt.subplot(2, 2, 4)  # 创建2行2列的子图,选择第4个子图

ax3.set_xlabel(filt2 + '_inst', fontdict=font2)  # 设置x轴标签

ax3.set_ylabel('$\sigma$ ' + filt2, fontdict=font2)  # 设置y轴标签

ax3.set_xlim(xlim0, xlim1)  # 设置x轴范围

ax3.set_ylim(ylim0, ylim1)  # 设置y轴范围

ax3.xaxis.set_major_locator(ticker.AutoLocator())  # 设置x轴主刻度定位器

ax3.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置x轴次刻度定位器

ax3.yaxis.set_major_locator(ticker.AutoLocator())  # 设置y轴主刻度定位器

ax3.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置y轴次刻度定位器

ax3.scatter(dict_aper[det][filt2]['final aperture phot table'][filt2 + '_inst'],  # 绘制散点图
            dict_aper[det][filt2]['final aperture phot table']['e' + filt2 + '_inst'], s=1, c='gray')  # 设置点的大小和颜色

ax3.scatter(matched_sources[filt2 + '_inst'], matched_sources['e' + filt2 + '_inst'], s=1, c='k')  # 绘制散点图

plt.tight_layout()  # 调整子图间距以避免重叠

光度零点 (Photometric Zeropoints)#

为了获得最终校准的颜色-星等图 (color-magnitude diagram),我们需要计算光度零点 (photometric zeropoints)。因此,我们需要对图像进行光圈光度测量 (aperture photometry),使用与包围能量 (encircled energy, EE) 值相对应的光圈半径,并应用适当的光圈修正 (aperture correction),因为所采用的光圈是有限的(上面字典中提供的值是针对无限光圈的)。然后,将其与上述确定的光圈光度测量进行比较。因此,我们可以将步骤总结如下:

  • 使用适当的半径(相当于某个EE值)进行光圈光度测量

  • 应用适当的光圈修正

  • 应用表格化的零点

  • 与光圈光度测量进行交叉匹配

注意:为了选择用于零点计算的恒星,我们可以使用CMD作为参考选择一个子样本,或者对一组明亮的孤立恒星进行光圈光度测量。要更改所使用的方法,我们可以分别设置 sample = cmd 或 sample = found。

加载孔径校正表#

注意:这些值是通过对合成的WebbPSF点扩散函数(PSFs)进行研究获得的。一旦我们获得了飞行中的测量数据,这些值将会更新。

import os  # 导入os模块,用于文件和目录操作
import urllib.request  # 导入urllib.request模块,用于下载文件
import pandas as pd  # 导入pandas库,用于数据处理

# 检查是否存在名为'aperture_correction_table.txt'的文件
if os.path.isfile('./aperture_correction_table.txt'):
    ap_tab = './aperture_correction_table.txt'  # 如果文件存在,设置文件路径

else:
    print("Downloading the aperture correction table")  # 如果文件不存在,打印下载提示

    # 定义要下载的文件链接
    boxlink_apcorr_table = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/stellar_photometry/aperture_correction_table.txt'
    boxfile_apcorr_table = './aperture_correction_table.txt'  # 定义下载后保存的文件路径

    # 下载文件并保存到指定路径
    urllib.request.urlretrieve(boxlink_apcorr_table, boxfile_apcorr_table)
    ap_tab = './aperture_correction_table.txt'  # 设置文件路径

# 读取'aperture_correction_table.txt'文件,使用空格作为分隔符
aper_table = pd.read_csv(ap_tab, header=None, sep='\s+', index_col=0,
                         names=['filter', 'pupil', 'wave', 'r10', 'r20', 'r30', 'r40', 'r50', 'r60', 'r70', 'r80',
                                'r85', 'r90', 'sky_flux_px', 'apcorr10', 'apcorr20', 'apcorr30', 'apcorr40',
                                'apcorr50', 'apcorr60', 'apcorr70', 'apcorr80', 'apcorr85', 'apcorr90', 'sky_in',
                                'sky_out'], comment='#', skiprows=0, usecols=range(0, 26))

# 显示数据表的前几行
aper_table.head()

1a. 使用CMD作为参考进行子采样#

注意:如果用户希望施加更严格的条件,可以在子样本中选择没有邻近星星距离小于X像素的星星,其中X可以适当固定,这可以通过设置dist_sel = True来实现。

限制星等和最近邻星的最大距离取决于用户的科学案例(即视场中的星星数量、拥挤程度、亮源数量等),必须相应地进行修改。

最终表格xy_cmd包含在两个滤光片中所选星星的位置(X,Y)。

xy_cmd = Table()  # 创建一个空的表格用于存储选中的星体坐标

mag_lim_bright = -7.0  # 明亮星体的亮度限制
mag_lim_faint = -4.5   # 微弱星体的亮度限制

# 创建一个掩码,筛选出亮度在限制范围内的星体
mask_xy = ((matched_sources[filt1 + '_inst'] < mag_lim_faint) & (matched_sources[filt1 + '_inst'] > mag_lim_bright))

# 将符合条件的星体坐标添加到xy_cmd表中
xy_cmd['x_' + filt1] = matched_sources['x_' + filt1][mask_xy]  # 添加filt1的x坐标
xy_cmd['y_' + filt1] = matched_sources['y_' + filt1][mask_xy]  # 添加filt1的y坐标
xy_cmd['x_' + filt2] = matched_sources['x_' + filt2][mask_xy]  # 添加filt2的x坐标
xy_cmd['y_' + filt2] = matched_sources['y_' + filt2][mask_xy]  # 添加filt2的y坐标

print("Number of stars selected from CMD:", len(xy_cmd))  # 打印选中的星体数量

dist_sel = False  # 是否计算距离的标志

if dist_sel:  # 如果选择计算距离
    print("")  # 打印空行
    print("Calculating closest neighbour distance")  # 打印计算最近邻距离的提示

    d_filt1 = []  # 存储filt1的最小距离
    d_filt2 = []  # 存储filt2的最小距离
    min_sep_filt1 = 10  # filt1的最小距离阈值
    min_sep_filt2 = 10  # filt2的最小距离阈值

    # 计算filt1的最小距离
    for i, j in zip(xy_cmd['x_' + filt1], xy_cmd['y_' + filt1]):
        sep = []  # 存储距离
        # 计算当前星体到所有其他星体的距离
        dist = np.sqrt((dict_aper[det][filt1]['sources found']['xcentroid'] - i)**2 + (dict_aper[det][filt1]['sources found']['ycentroid'] - j)**2)
        sep = np.sort(dist)[1:2][0]  # 找到最近的邻居距离
        d_filt1.append(sep)  # 将距离添加到列表中

    # 计算filt2的最小距离
    for i, j in zip(xy_cmd['x_' + filt2], xy_cmd['y_' + filt2]):
        sep = []  # 存储距离
        # 计算当前星体到所有其他星体的距离
        dist = np.sqrt((dict_aper[det][filt2]['sources found']['xcentroid'] - i)**2 + (dict_aper[det][filt2]['sources found']['ycentroid'] - j)**2)
        sep = np.sort(dist)[1:2][0]  # 找到最近的邻居距离
        d_filt2.append(sep)  # 将距离添加到列表中

    # 将最小距离添加到xy_cmd表中
    xy_cmd['min distance ' + filt1] = d_filt1
    xy_cmd['min distance ' + filt2] = d_filt2

    # 创建一个掩码,筛选出符合最小距离要求的星体
    mask_dist = ((xy_cmd['min distance ' + filt1] > min_sep_filt1) & (xy_cmd['min distance ' + filt2] > min_sep_filt2))

    xy_cmd = xy_cmd[mask_dist]  # 应用掩码,更新xy_cmd表

    print("")  # 打印空行
    print("Minimum distance required for filter {f}:".format(f=filt1), min_sep_filt1, "px")  # 打印filt1的最小距离要求
    print("Minimum distance required for filter {f}:".format(f=filt2), min_sep_filt2, "px")  # 打印filt2的最小距离要求
    print("")  # 打印空行
    print("Number of stars selected from CMD including distance constraints:", len(xy_cmd))  # 打印考虑距离约束后选中的星体数量

如我们之前所做,我们创建一个字典,其中包含每个图像的衍生光圈光度(aperture photometry,针对明亮恒星)的表格。

dict_zp = {}  # 初始化一个空字典,用于存储每个探测器和滤光片的相关信息

for det in dets_short:  # 遍历每个短探测器
    dict_zp.setdefault(det, {})  # 如果字典中没有该探测器,则初始化一个空字典

    for j, filt in enumerate(filts_short):  # 遍历每个短滤光片
        dict_zp[det].setdefault(filt, {})  # 如果该探测器下没有该滤光片,则初始化一个空字典

        dict_zp[det][filt]['sources found'] = None  # 初始化'找到的源'为None
        dict_zp[det][filt]['aperture phot table'] = None  # 初始化'光圈光度表'为None
        dict_zp[det][filt]['final aperture phot table'] = None  # 初始化'最终光圈光度表'为None
def find_bright_stars(det='NRCA1', filt='F070W', var_bkg=False, dist_sel=False):
    # 定义寻找亮星的函数,参数包括探测器、滤光片、是否使用变背景和是否选择距离

    bkgrms = MADStdBackgroundRMS()  # 初始化MAD标准背景RMS对象
    mmm_bkg = MMMBackground()  # 初始化MMM背景估计对象

    image = fits.open(dict_images[det][filt]['images'][i])  # 打开指定探测器和滤光片的图像文件
    data_sb = image[1].data  # 获取图像数据
    imh = image[1].header  # 获取图像头信息

    print("Finding sources on image {number}, filter {f}, detector {d}:".format(number=i + 1, f=filt, d=det))  # 打印当前处理的图像信息
    print("")  # 打印空行

    data = data_sb / imh['PHOTMJSR']  # 将图像数据转换为DN/s
    print("Conversion factor from {units} to DN/s for filter {f}:".format(units=imh['BUNIT'], f=filt), imh['PHOTMJSR'])  # 打印转换因子

    sigma_psf = dict_utils[filt]['psf fwhm']  # 获取滤光片的PSF FWHM

    print("FWHM for the filter {f}:".format(f=filt), sigma_psf, "px")  # 打印FWHM信息

    if var_bkg:  # 如果使用变背景
        print("Using 2D Background")  # 打印使用2D背景的信息
        sigma_clip = SigmaClip(sigma=3.)  # 初始化SigmaClip对象
        coverage_mask = (data == 0)  # 创建覆盖掩码,标记数据为0的区域

        # 计算2D背景
        bkg = Background2D(data, (25, 25), filter_size=(3, 3), sigma_clip=sigma_clip,
                           bkg_estimator=mmm_bkg, coverage_mask=coverage_mask, fill_value=0.0)

        data_bkgsub = data.copy()  # 复制数据
        data_bkgsub = data_bkgsub - bkg.background  # 从数据中减去背景

        _, _, std = sigma_clipped_stats(data_bkgsub)  # 计算背景减去后的标准差

    else:  # 如果不使用变背景
        std = bkgrms(data)  # 计算背景RMS
        bkg = mmm_bkg(data)  # 估计背景

        data_bkgsub = data.copy()  # 复制数据
        data_bkgsub -= bkg  # 从数据中减去背景

    # 初始化DAOStarFinder以寻找星源
    daofind = DAOStarFinder(threshold=th[j] * std, fwhm=sigma_psf, roundhi=1.0, roundlo=-1.0,
                            sharplo=0.30, sharphi=1.40)

    zp_stars = daofind(data_bkgsub)  # 在背景减去后的数据中寻找星源

    dict_zp[det][filt]['sources found'] = zp_stars  # 将找到的星源存入字典

    if dist_sel:  # 如果选择距离
        print("")  # 打印空行
        print("Calculating closest neighbour distance")  # 打印计算最近邻距离的信息

        d = []  # 初始化距离列表

        # 遍历找到的星源
        for xx, yy in zip(zp_stars['xcentroid'], zp_stars['ycentroid']):
            sep = []  # 初始化分离距离列表
            dist = np.sqrt((dict_aper[det][filt]['sources found']['xcentroid'] - xx)**2 +
                           (dict_aper[det][filt]['sources found']['ycentroid'] - yy)**2)  # 计算距离
            sep = np.sort(dist)[1:2][0]  # 获取最近的距离
            d.append(sep)  # 将距离添加到列表中

        zp_stars['min distance'] = d  # 将最小距离添加到星源数据中
        mask_dist = (zp_stars['min distance'] > min_sep[j])  # 创建距离掩码

        zp_stars = zp_stars[mask_dist]  # 应用距离掩码

        dict_zp[det][filt]['sources found'] = zp_stars  # 更新找到的星源字典

        print("Minimum distance required:", min_sep[j], "px")  # 打印所需的最小距离
        print("")  # 打印空行
        print("Number of bright isolated sources found in the image:", len(zp_stars))  # 打印找到的孤立亮星数量
        print("-----------------------------------------------------")  # 打印分隔线
        print("")  # 打印空行

    else:  # 如果不选择距离
        print("")  # 打印空行
        print("Number of bright sources found in the image:", len(zp_stars))  # 打印找到的亮星数量
        print("--------------------------------------------")  # 打印分隔线
        print("")  # 打印空行

阈值(threshold)和最近邻(closest neighbour)的最大距离(maximum distance)取决于用户的科学案例(science case)(即视场内的星星数量、拥挤程度、亮源数量等),必须相应地进行调整。

tic = time.perf_counter()  # 记录开始时间

th = [700, 500]  # 两个滤波器的阈值水平(长度必须与分析的滤波器数量匹配)

min_sep = [10, 10]  # zp 星星与最近邻星星之间可接受的最小间隔

# 遍历短时间检测列表
for det in dets_short:

    # 遍历短时间滤波器列表
    for j, filt in enumerate(filts_short):

        # 遍历当前图像字典中指定检测和滤波器的图像
        for i in np.arange(0, len(dict_images[det][filt]['images']), 1):

            # 查找明亮的星星,参数包括检测、滤波器、是否考虑背景变化、是否选择距离
            find_bright_stars(det=det, filt=filt, var_bkg=False, dist_sel=False)

toc = time.perf_counter()  # 记录结束时间

# 打印查找星星所用的时间
print("Elapsed Time for finding stars:", toc - tic)

明亮孤立星的光圈光度测量#

要选择我们希望在光圈光度测量中使用的星星样本,请修改 sample 关键字,使用 cmdfound

def aperture_phot_zp(det=det, filt=filt, sample='found'):
    # 定义一个函数,用于进行光度测量,输入参数包括探测器、滤光片和样本类型

    radii = [aper_table.loc[filt]['r70']]  # 获取对应滤光片的70%半径

    ees = '70'.split()  # 定义有效半径的列表
    ee_radii = dict(zip(ees, radii))  # 将有效半径与其对应的值组合成字典

    if sample == 'found':
        # 如果样本类型为'found',则获取已找到的源的位置
        positions = np.transpose((dict_zp[det][filt]['sources found']['xcentroid'],
                                  dict_zp[det][filt]['sources found']['ycentroid']))

    if sample == 'cmd':
        # 如果样本类型为'cmd',则获取命令中的源位置
        positions = np.transpose((xy_cmd['x_' + filt], xy_cmd['y_' + filt]))

    image = fits.open(dict_images[det][filt]['images'][0])  # 打开对应的图像文件
    data_sb = image[1].data  # 获取图像数据
    imh = image[1].header  # 获取图像头信息
    data = data_sb / imh['PHOTMJSR']  # 进行数据的光度校正

    aa = np.argwhere(data < 0)  # 查找数据中小于0的值的索引

    for i in np.arange(0, len(aa), 1):
        # 将小于0的值替换为0
        data[aa[i][0], aa[i][1]] = 0

    # 从光圈校正表中获取天空背景的内外半径
    sky = {"sky_in": aper_table.loc[filt]['r80'], "sky_out": aper_table.loc[filt]['r85']}

    tic = time.perf_counter()  # 记录开始时间

    table_zp = Table()  # 创建一个新的表格用于存储结果

    for ee, radius in ee_radii.items():
        # 对每个有效半径进行光度测量
        print("Performing aperture photometry for radius equivalent to EE = {0}% for filter {1}".format(ee, filt))
        aperture = CircularAperture(positions, r=radius)  # 创建光圈对象
        annulus_aperture = CircularAnnulus(positions, r_in=sky["sky_in"], r_out=sky["sky_out"])  # 创建环形光圈对象
        annulus_mask = annulus_aperture.to_mask(method='center')  # 生成环形光圈的掩膜

        bkg_median = []  # 初始化背景中值列表

        for mask in annulus_mask:
            # 对每个掩膜进行处理
            annulus_data = mask.multiply(data)  # 计算环形区域的数据
            annulus_data_1d = annulus_data[mask.data > 0]  # 提取有效数据
            _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d)  # 计算背景中值
            bkg_median.append(median_sigclip)  # 将中值添加到列表中

        bkg_median = np.array(bkg_median)  # 转换为数组

        phot = aperture_photometry(data, aperture, method='exact')  # 进行光度测量
        phot['annulus_median'] = bkg_median  # 添加背景中值到结果中
        phot['aper_bkg'] = bkg_median * aperture.area  # 计算光圈背景
        phot['aper_sum_bkgsub'] = phot['aperture_sum'] - phot['aper_bkg']  # 计算背景校正后的光度

        apcorr = [aper_table.loc[filt]['apcorr70']]  # 获取光圈校正因子

        phot['aper_sum_corrected'] = phot['aper_sum_bkgsub'] * apcorr  # 计算校正后的光度

        # 将结果添加到表格中
        table_zp.add_column(phot['aperture_sum'], name='aper_sum_' + ee)
        table_zp.add_column(phot['annulus_median'], name='annulus_median_' + ee)
        table_zp.add_column(phot['aper_bkg'], name='aper_bkg_ee_' + ee)
        table_zp.add_column(phot['aper_sum_bkgsub'], name='aper_sum_bkgsub_' + ee)
        table_zp.add_column(phot['aper_sum_corrected'], name='aper_sum_corrected_' + ee + '_' + filt)

        dict_zp[det][filt]['aperture phot table'] = table_zp  # 将结果表格存储到字典中

    toc = time.perf_counter()  # 记录结束时间
    print("Time Elapsed:", toc - tic)  # 输出耗时

    return  # 返回函数结束
# 使用指定的检测器和滤光片进行光度测量,样本为'cmd'
aperture_phot_zp(det=det, filt=filt1, sample='cmd')  # 对第一个滤光片进行光度测量

# 使用指定的检测器和第二个滤光片进行光度测量,样本为'cmd'
aperture_phot_zp(det=det, filt=filt2, sample='cmd')  # 对第二个滤光片进行光度测量
ee = '70'  # 设置等效光度的值

sample = 'cmd'  # 设置样本类型为'cmd'

for det in dets_short:  # 遍历短名称的探测器

    for j, filt in enumerate(filts_short):  # 遍历短名称的滤光片

        for i in np.arange(0, len(dict_images[det][filt]['images']), 1):  # 遍历图像列表

            image = fits.open(dict_images[det][filt]['images'][0])  # 打开图像文件

            data = image[1].data  # 获取图像数据

            image_model = ImageModel(dict_images[det][filt]['images'][0])  # 创建图像模型

            table_zp_mag = Table()  # 创建一个新的表格用于存储零点光度数据

            if sample == 'found':  # 如果样本类型为'found'

                mask = ((dict_zp[det][filt]['sources found']['xcentroid'] > 0) &  # 创建掩码,筛选有效的x坐标

                        (dict_zp[det][filt]['sources found']['xcentroid'] < data.shape[1]) &  # x坐标小于图像宽度

                        (dict_zp[det][filt]['sources found']['ycentroid'] > 0) &  # 筛选有效的y坐标

                        (dict_zp[det][filt]['sources found']['ycentroid'] < data.shape[0]) &  # y坐标小于图像高度

                        (dict_zp[det][filt]['aperture phot table']['aper_sum_corrected_' + ee + '_' + filt] > 0))  # 确保光度值大于0

                table_zp_mag['x'] = dict_zp[det][filt]['sources found']['xcentroid'][mask]  # 存储x坐标

                table_zp_mag['y'] = dict_zp[det][filt]['sources found']['ycentroid'][mask]  # 存储y坐标

                ra, dec = image_model.meta.wcs(table_zp_mag['x'], table_zp_mag['y'])  # 将像素坐标转换为天球坐标

                table_zp_mag['radec'] = SkyCoord(ra, dec, unit='deg')  # 存储天球坐标

                table_zp_mag[filt + '_inst'] = -2.5 * np.log10(dict_zp[det][filt]['aperture phot table']['aper_sum_corrected_' + ee + '_' + filt][mask])  # 计算并存储瞬时光度

                table_zp_mag[filt + '_zp'] = table_zp_mag[filt + '_inst'] + dict_utils[filt]['VegaMAG zp modB']  # 计算并存储零点光度

                dict_zp[det][filt]['final aperture phot table'] = table_zp_mag  # 更新字典中的最终光度表

            if sample == 'cmd':  # 如果样本类型为'cmd'

                mask = ((dict_zp[det][filt]['aperture phot table']['aper_sum_corrected_' + ee + '_' + filt] > 0))  # 创建掩码,筛选有效的光度值

                table_zp_mag['x'] = xy_cmd['x_' + filt][mask]  # 存储x坐标

                table_zp_mag['y'] = xy_cmd['y_' + filt][mask]  # 存储y坐标

                ra, dec = image_model.meta.wcs(table_zp_mag['x'], table_zp_mag['y'])  # 将像素坐标转换为天球坐标

                table_zp_mag['radec'] = SkyCoord(ra, dec, unit='deg')  # 存储天球坐标

                table_zp_mag[filt + '_inst'] = -2.5 * np.log10(dict_zp[det][filt]['aperture phot table']['aper_sum_corrected_' + ee + '_' + filt][mask])  # 计算并存储瞬时光度

                table_zp_mag[filt + '_zp'] = table_zp_mag[filt + '_inst'] + dict_utils[filt]['VegaMAG zp modB']  # 计算并存储零点光度

                dict_zp[det][filt]['final aperture phot table'] = table_zp_mag  # 更新字典中的最终光度表

推导零点 (Zeropoints)#

plt.figure(figsize=(14, 8))  # 创建一个14x8英寸的图形

plt.clf()  # 清除当前图形中的所有内容

ax1 = plt.subplot(2, 1, 1)  # 创建一个2行1列的子图,选择第一个子图

ax1.set_xlabel(filt1 + '_inst', fontdict=font2)  # 设置x轴标签
ax1.set_ylabel(filt1 + '_ZP', fontdict=font2)  # 设置y轴标签

# 匹配天文坐标,获取索引和距离
idx_zp_1, d2d_zp_1, _ = match_coordinates_sky(dict_zp[det][filt1]['final aperture phot table']['radec'],
                                              dict_aper[det][filt1]['final aperture phot table']['radec'])

sep_constraint_zp_1 = d2d_zp_1 < max_sep  # 设置距离约束条件

# 根据约束条件提取匹配的零点和观测值
f115w_apZP_matched = np.array(dict_zp[det][filt1]['final aperture phot table'][filt1 + '_zp'][sep_constraint_zp_1])
f115w_ap_matched = np.array(dict_aper[det][filt1]['final aperture phot table'][filt1 + '_inst'][idx_zp_1[sep_constraint_zp_1]])

diff_f115w = f115w_apZP_matched - f115w_ap_matched  # 计算差值
_, zp_f115w, zp_sigma_f115w = sigma_clipped_stats(diff_f115w)  # 计算零点和标准差

# 设置x轴和y轴的限制
xlim0 = np.min(f115w_ap_matched) - 0.25
xlim1 = np.max(f115w_ap_matched) + 0.25
ylim0 = np.mean(diff_f115w) - 0.35
ylim1 = np.mean(diff_f115w) + 0.35

ax1.set_xlim(xlim0, xlim1)  # 设置x轴范围
ax1.set_ylim(ylim0, ylim1)  # 设置y轴范围

# 设置坐标轴的刻度
ax1.xaxis.set_major_locator(ticker.AutoLocator())
ax1.xaxis.set_minor_locator(ticker.AutoMinorLocator())
ax1.yaxis.set_major_locator(ticker.AutoLocator())
ax1.yaxis.set_minor_locator(ticker.AutoMinorLocator())

ax1.scatter(f115w_ap_matched, diff_f115w, s=50, color='k')  # 绘制散点图
ax1.plot([xlim0, xlim1], [zp_f115w, zp_f115w], color='r', lw=5, ls='--')  # 绘制零点线
ax1.text(xlim0 + 0.05, ylim1 - 0.1, filt1 + ' Zeropoint = %5.3f $\pm $ %5.3f' % (zp_f115w, zp_sigma_f115w),
         color='k', fontdict=font2)  # 添加文本注释

ax2 = plt.subplot(2, 1, 2)  # 创建第二个子图

ax2.set_xlabel(filt2 + '_inst', fontdict=font2)  # 设置x轴标签
ax2.set_ylabel(filt2 + '_ZP', fontdict=font2)  # 设置y轴标签

# 匹配天文坐标,获取索引和距离
idx_zp_2, d2d_zp_2, _ = match_coordinates_sky(dict_zp[det][filt2]['final aperture phot table']['radec'],
                                              dict_aper[det][filt2]['final aperture phot table']['radec'])

sep_constraint_zp_2 = d2d_zp_2 < max_sep  # 设置距离约束条件

# 根据约束条件提取匹配的零点和观测值
f200w_apZP_matched = np.array(dict_zp[det][filt2]['final aperture phot table'][filt2 + '_zp'][sep_constraint_zp_2])
f200w_ap_matched = np.array(dict_aper[det][filt2]['final aperture phot table'][filt2 + '_inst'][idx_zp_2[sep_constraint_zp_2]])

diff_f200w = f200w_apZP_matched - f200w_ap_matched  # 计算差值
_, zp_f200w, zp_sigma_f200w = sigma_clipped_stats(diff_f200w)  # 计算零点和标准差

# 设置x轴和y轴的限制
xlim0 = np.min(f200w_ap_matched) - 0.25
xlim1 = np.max(f200w_ap_matched) + 0.25
ylim0 = np.mean(diff_f200w) - 0.35 
ylim1 = np.mean(diff_f200w) + 0.35

ax2.set_xlim(xlim0, xlim1)  # 设置x轴范围
ax2.set_ylim(ylim0, ylim1)  # 设置y轴范围

# 设置坐标轴的刻度
ax2.xaxis.set_major_locator(ticker.AutoLocator())
ax2.xaxis.set_minor_locator(ticker.AutoMinorLocator())
ax2.yaxis.set_major_locator(ticker.AutoLocator())
ax2.yaxis.set_minor_locator(ticker.AutoMinorLocator())

ax2.scatter(f200w_ap_matched, diff_f200w, s=50, color='k')  # 绘制散点图
ax2.plot([xlim0, xlim1], [zp_f200w, zp_f200w], color='r', lw=5, ls='--')  # 绘制零点线
ax2.text(xlim0 + 0.05, ylim1 - 0.1, filt2 + ' Zeropoint = %5.3f $\pm$ %5.3f' % (zp_f200w, zp_sigma_f200w), 
         color='k', fontdict=font2)  # 添加文本注释

plt.tight_layout()  # 调整子图间距

导入输入目录#

import os  # 导入os模块,用于文件和目录操作
import urllib.request  # 导入urllib.request模块,用于下载文件
import pandas as pd  # 导入pandas库,用于数据处理

# 检查当前目录下是否存在'pointsource.cat'文件
if os.path.isfile('./pointsource.cat'):
    input_cat = './pointsource.cat'  # 如果文件存在,设置输入目录为该文件

else:
    print("Downloading input pointsource catalog")  # 如果文件不存在,打印下载信息

    # 定义要下载的文件链接
    boxlink_input_cat = 'https://data.science.stsci.edu/redirect/JWST/jwst-data_analysis_tools/stellar_photometry/pointsource.cat'
    boxfile_input_cat = './pointsource.cat'  # 定义下载后保存的文件名

    # 下载文件并保存到指定路径
    urllib.request.urlretrieve(boxlink_input_cat, boxfile_input_cat)
    input_cat = './pointsource.cat'  # 设置输入目录为下载的文件

# 读取输入的点源目录文件,指定列名和数据格式
cat = pd.read_csv(input_cat, header=None, sep='\s+', names=['ra_in', 'dec_in', 'f070w_in', 'f115w_in',
                                                            'f200w_in', 'f277w_in', 'f356w_in', 'f444w_in'],
                  comment='#', skiprows=7, usecols=range(0, 8))

# 显示数据框的前几行
cat.head()  # 输出数据框的前5行
# 计算给定探测器和滤波器的最小和最大RA值
lim_ra_min = np.min(dict_aper[det][filt1]['final aperture phot table']['radec'].ra)  # 最小RA值
lim_ra_max = np.max(dict_aper[det][filt1]['final aperture phot table']['radec'].ra)  # 最大RA值

# 计算给定探测器和滤波器的最小和最大DEC值
lim_dec_min = np.min(dict_aper[det][filt1]['final aperture phot table']['radec'].dec)  # 最小DEC值
lim_dec_max = np.max(dict_aper[det][filt1]['final aperture phot table']['radec'].dec)  # 最大DEC值

# 根据RA和DEC的限制条件选择分类目录中的对象
cat_sel = cat[(cat['ra_in'] > lim_ra_min) & (cat['ra_in'] < lim_ra_max) & (cat['dec_in'] > lim_dec_min)  # RA范围条件
              & (cat['dec_in'] < lim_dec_max)]  # DEC范围条件

校准的颜色-星等图#

plt.figure(figsize=(12, 14))  # 创建一个12x14英寸的图形

plt.clf()  # 清空当前图形

font2 = {'size': '30'}  # 设置字体大小为30

ax1 = plt.subplot(1, 2, 1)  # 创建1行2列的子图,选择第一个子图

mag1_in = np.array(cat_sel['f115w_in'])  # 从cat_sel中获取f115w_in数据并转换为numpy数组
mag2_in = np.array(cat_sel['f200w_in'])  # 从cat_sel中获取f200w_in数据并转换为numpy数组
diff_in = mag1_in - mag2_in  # 计算f115w和f200w之间的差值

xlim0 = -0.25  # 设置x轴下限
xlim1 = 1.75   # 设置x轴上限
ylim0 = 25     # 设置y轴下限
ylim1 = 15     # 设置y轴上限
ax1.set_xlim(xlim0, xlim1)  # 应用x轴限制
ax1.set_ylim(ylim0, ylim1)   # 应用y轴限制

ax1.xaxis.set_major_locator(ticker.AutoLocator())  # 设置x轴主刻度定位器
ax1.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置x轴次刻度定位器
ax1.yaxis.set_major_locator(ticker.AutoLocator())  # 设置y轴主刻度定位器
ax1.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置y轴次刻度定位器

ax1.scatter(mag1_in - mag2_in, mag1_in, s=1, color='k')  # 绘制散点图,x为差值,y为f115w的值

ax1.set_xlabel(filt1 + ' - ' + filt2, fontdict=font2)  # 设置x轴标签
ax1.set_ylabel(filt1, fontdict=font2)  # 设置y轴标签
ax1.text(xlim0 + 0.15, 15.5, "Input", fontdict=font2)  # 在图中添加文本“Input”

ax2 = plt.subplot(1, 2, 2)  # 创建1行2列的子图,选择第二个子图

ax2.set_xlim(xlim0, xlim1)  # 应用x轴限制
ax2.set_ylim(ylim0, ylim1)   # 应用y轴限制

ax2.xaxis.set_major_locator(ticker.AutoLocator())  # 设置x轴主刻度定位器
ax2.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置x轴次刻度定位器
ax2.yaxis.set_major_locator(ticker.AutoLocator())  # 设置y轴主刻度定位器
ax2.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 设置y轴次刻度定位器

f115w = matched_sources[filt1 + '_inst'] + zp_f115w  # 计算f115w的值
f200w = matched_sources[filt2 + '_inst'] + zp_f200w  # 计算f200w的值

maglim = np.arange(18, 25, 1)  # 创建一个从18到25的范围,步长为1
mags = []  # 初始化mags列表
errs_mag = []  # 初始化errs_mag列表
errs_col = []  # 初始化errs_col列表

for i in np.arange(0, len(maglim) - 1, 1):  # 遍历maglim的每个区间
    mag = (maglim[i] + maglim[i + 1]) / 2  # 计算当前区间的中值
    err_mag1 = matched_sources['e' + filt1 + '_inst'][(f115w > maglim[i]) & (f115w < maglim[i + 1])]  # 获取f115w在当前区间的误差
    err_mag2 = matched_sources['e' + filt2 + '_inst'][(f115w > maglim[i]) & (f115w < maglim[i + 1])]  # 获取f200w在当前区间的误差
    err_mag = np.mean(err_mag1[i])  # 计算f115w的平均误差
    err_temp = np.sqrt(err_mag1**2 + err_mag2**2)  # 计算误差的平方和的平方根
    err_col = np.mean(err_temp[i])  # 计算颜色的平均误差

    errs_mag.append(err_mag)  # 将当前误差添加到errs_mag列表
    errs_col.append(err_col)  # 将当前颜色误差添加到errs_col列表
    mags.append(mag)  # 将当前中值添加到mags列表

col = [0] * (len(maglim) - 1)  # 初始化颜色列表,长度为maglim的长度减1

# ax2.errorbar(col, mags, yerr=errs_mag, xerr=errs_col, fmt='o', color = 'k')  # 绘制误差条(已注释)

ax2.scatter(f115w - f200w, f115w, s=1, color='k')  # 绘制散点图,x为f115w和f200w的差值,y为f115w的值
ax2.text(xlim0 + 0.15, 15.5, "Output", fontdict=font2)  # 在图中添加文本“Output”

ax2.set_xlabel(filt1 + ' - ' + filt2, fontdict=font2)  # 设置x轴标签
ax2.set_ylabel(filt1, fontdict=font2)  # 设置y轴标签

plt.tight_layout()  # 自动调整子图参数,使之填充整个图像区域

输入与输出光度的比较#

plt.figure(figsize=(14, 8))  # 创建一个14x8英寸的图形

plt.clf()  # 清除当前图形中的所有内容

ax1 = plt.subplot(2, 1, 1)  # 创建一个2行1列的子图,选择第一个子图

ax1.set_xlabel(filt1, fontdict=font2)  # 设置x轴标签为filt1
ax1.set_ylabel('$\Delta$ Mag', fontdict=font2)  # 设置y轴标签为Δ Mag

radec_input = SkyCoord(cat_sel['ra_in'], cat_sel['dec_in'], unit='deg')  # 创建SkyCoord对象,包含输入的RA和DEC坐标

idx_f115w_cfr, d2d_f115w_cfr, _ = match_coordinates_sky(radec_input, matched_sources['radec'])  # 匹配输入坐标与已匹配源的坐标

sep_f115w_cfr = d2d_f115w_cfr < max_sep  # 计算匹配源与输入源之间的距离,筛选出小于最大分离的源

f115w_inp_cfr = np.array(cat_sel['f115w_in'][sep_f115w_cfr])  # 获取输入的f115w数据
f115w_ap_cfr = np.array(f115w[idx_f115w_cfr[sep_f115w_cfr]])  # 获取匹配的f115w数据

diff_f115w_cfr = f115w_inp_cfr - f115w_ap_cfr  # 计算输入和匹配的f115w数据之间的差异
diff_f115w_cfr_sel = (f115w_inp_cfr - f115w_ap_cfr)[(f115w_ap_cfr > 17.75) & (f115w_ap_cfr < 21.5)]  # 筛选出特定范围内的差异

_, med_diff_f115w_cfr, sig_diff_f115w_cfr = sigma_clipped_stats(diff_f115w_cfr_sel)  # 计算中位数和标准差

xlim0 = 16  # 设置x轴下限
xlim1 = 24  # 设置x轴上限
ylim0 = np.mean(diff_f115w_cfr) - 0.5  # 设置y轴下限
ylim1 = np.mean(diff_f115w_cfr) + 0.5  # 设置y轴上限

ax1.set_xlim(xlim0, xlim1)  # 设置x轴范围
ax1.set_ylim(ylim0, ylim1)  # 设置y轴范围

ax1.xaxis.set_major_locator(ticker.AutoLocator())  # 自动选择x轴主刻度
ax1.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 自动选择x轴次刻度
ax1.yaxis.set_major_locator(ticker.AutoLocator())  # 自动选择y轴主刻度
ax1.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 自动选择y轴次刻度

ax1.scatter(f115w_ap_cfr, diff_f115w_cfr, s=5, color='k')  # 绘制散点图,显示f115w匹配数据与差异
ax1.plot([xlim0, xlim1], [0, 0], color='r', lw=5, ls='--')  # 绘制红色虚线,表示0差异线
ax1.plot([17.75, 17.75], [ylim0, ylim1], color='k', lw=1, ls='--')  # 绘制f115w下限虚线
ax1.plot([21.5, 21.5], [ylim0, ylim1], color='k', lw=1, ls='--')  # 绘制f115w上限虚线

ax1.text(xlim0 + 0.05, ylim1 - 0.15, filt1 + ' $\Delta$ Mag = %5.3f $\pm$ %5.3f'  # 在图中添加文本,显示filt1的差异统计
         % (med_diff_f115w_cfr, sig_diff_f115w_cfr), color='k', fontdict=font2)

ax2 = plt.subplot(2, 1, 2)  # 创建第二个子图

ax2.set_xlabel(filt2, fontdict=font2)  # 设置x轴标签为filt2
ax2.set_ylabel('$\Delta$ Mag', fontdict=font2)  # 设置y轴标签为Δ Mag

idx_f200w_cfr, d2d_f200w_cfr, _ = match_coordinates_sky(radec_input, matched_sources['radec'])  # 匹配输入坐标与已匹配源的坐标

sep_f200w_cfr = d2d_f200w_cfr < max_sep  # 计算匹配源与输入源之间的距离,筛选出小于最大分离的源

f200w_inp_cfr = np.array(cat_sel['f200w_in'][sep_f200w_cfr])  # 获取输入的f200w数据
f200w_ap_cfr = np.array(f200w[idx_f200w_cfr[sep_f200w_cfr]])  # 获取匹配的f200w数据

diff_f200w_cfr = f200w_inp_cfr - f200w_ap_cfr  # 计算输入和匹配的f200w数据之间的差异
diff_f200w_cfr_sel = (f200w_inp_cfr - f200w_ap_cfr)[(f200w_ap_cfr > 16.5) & (f200w_ap_cfr < 20.5)]  # 筛选出特定范围内的差异

_, med_diff_f200w_cfr, sig_diff_f200w_cfr = sigma_clipped_stats(diff_f200w_cfr_sel)  # 计算中位数和标准差

xlim0 = 16  # 设置x轴下限
xlim1 = 24  # 设置x轴上限
ylim0 = np.mean(diff_f200w_cfr) - 0.5  # 设置y轴下限
ylim1 = np.mean(diff_f200w_cfr) + 0.5  # 设置y轴上限

ax2.set_xlim(xlim0, xlim1)  # 设置x轴范围
ax2.set_ylim(ylim0, ylim1)  # 设置y轴范围

ax2.xaxis.set_major_locator(ticker.AutoLocator())  # 自动选择x轴主刻度
ax2.xaxis.set_minor_locator(ticker.AutoMinorLocator())  # 自动选择x轴次刻度
ax2.yaxis.set_major_locator(ticker.AutoLocator())  # 自动选择y轴主刻度
ax2.yaxis.set_minor_locator(ticker.AutoMinorLocator())  # 自动选择y轴次刻度

ax2.scatter(f200w_ap_cfr, diff_f200w_cfr, s=5, color='k')  # 绘制散点图,显示f200w匹配数据与差异
ax2.plot([xlim0, xlim1], [0, 0], color='r', lw=5, ls='--')  # 绘制红色虚线,表示0差异线
ax2.plot([16.5, 16.5], [ylim0, ylim1], color='k', lw=1, ls='--')  # 绘制f200w下限虚线
ax2.plot([20.5, 20.5], [ylim0, ylim1], color='k', lw=1, ls='--')  # 绘制f200w上限虚线

ax2.text(xlim0 + 0.05, ylim1 - 0.15, filt2 + ' $\Delta$ Mag = %5.3f $\pm$ %5.3f'  # 在图中添加文本,显示filt2的差异统计
         % (med_diff_f200w_cfr, sig_diff_f200w_cfr), color='k', fontdict=font2)

plt.tight_layout()  # 调整子图间距,使其更美观

关于此笔记本#

作者: Matteo Correnti, JWST/NIRCam STScI 科学家 II \

更新时间: 2021-01-15

页面顶部

空间望远镜标志