astropy WCS 修复:备选 WCS(key=‘A‘/‘B‘)畸变关键字按后缀独立读取的完整解析 科学计算科研数据分析【免费下载链接】astropyAstronomy and astrophysics core library项目地址https://gitcode.com/gh_mirrors/as/astropy点击查看免费下载导读本文围绕 astropy 仓库中的修复条目 19228.bugfix.rst 展开当一张 FITS 图像同时包含主 WCS无后缀与备选 WCSkeyA、B等时原先的畸变distortion关键字解析会错误地把主 WCS 的畸变信息加载到备选 WCS 上甚至导致加载崩溃。修复后备选 WCS 会严格依据自身关键字后缀如CPDIS1A、DP1A独立读取畸变查找表。读完本文你将掌握 FITS 备选 WCS 畸变关键字的命名规则、astropy.wcs.WCS的底层解析逻辑以及如何构造、校验和复现这一修复场景。背景FITS 备选 WCS 与关键字后缀约定FITS 标准允许在同一图像头中存储多套世界坐标系统WCS。主 WCS 的关键字不带后缀如CTYPE1、CRPIX1、CDELT1而备选 WCS 通过在关键字名后追加单字母后缀A–Z来区分例如CTYPE1A、CRPIX1A、CDELT1A对应keyA的 WCS。在 astropy 中通过wcs.WCS(header, fobj, keyA)即可选择读取哪一套 WCSkey的取值是空格 或A–Z见 wcs.py 构造签名。关键问题在于畸变关键字同样遵循这一后缀规则而旧版本实现没有为畸变解析正确拼接后缀导致张冠李戴。Bug 描述备选 WCS 误载主畸变或直接崩溃修复条目 19228.bugfix.rst 原文明确了问题与修复效果Fixed WCS distortion keyword handling to respect alternate WCS key suffixes. Secondary WCS (keyA, B, etc.) now correctly read their own distortion keywords (CPDIS1A, DP1A, etc.) instead of incorrectly loading primary WCS distortion or crashing.即此前存在两类故障表现错误加载主畸变当备选 WCS如keyA没有自己的畸变关键字不存在CPDIS1A时解析器会错误地回退读取主 WCS 的CPDIS1/CPDIS2畸变查找表从而把主 WCS 的畸变错误地施加到备选 WCS 坐标变换上产生错误的天球坐标解析崩溃在部分关键字组合例如存在DP*系列辅助关键字但缺少带正确后缀的入口下解析过程会抛出异常中断加载。修复的核心是让_read_distortion_kw在构造畸变关键字名时把key后缀如A拼入CPDIS/DP等关键字使备选 WCS 只认自己的畸变关键字。修复原理源码级解析_read_distortion_kw的后缀拼接逻辑修复落点在WCS.__init__中调用畸变解析的位置构造时会以keykey传入_read_distortion_kw见 wcs.py#L632-L635det2im self._read_det2im_kw(header, fobj, errminerr) cpdis self._read_distortion_kw( header, fobj, distCPDIS, errminerr, keykey ) self._fix_pre2012_scamp_tpv(header) sip self._read_sip_kw(header, wcskeykey)而 _read_distortion_kw 的实现 是整个修复的核心其关键步骤如下key key.strip().upper() key_suffix key if dist CPDIS: d_kw DP err_kw CPERR else: d_kw DQ err_kw CQERR tables {} for i in range(1, self.naxis 1): axis_key_suffix str(i) key_suffix d_error_key err_kw axis_key_suffix ... distortion dist axis_key_suffix逐一拆解这段逻辑后缀统一归一化key先做strip().upper()保证a与A等价主/备选切换key 主 WCS时key_suffix为空关键字保持CPDIS1、CPERR1、DP1.EXTVER形式keyA时生成CPDIS1A、CPERR1A、DP1A.EXTVERkeyB时则对应CPDIS1B、DP1B.*依此类推按轴遍历对每个轴i分别构造err_kw str(i) key_suffix与dist str(i) key_suffix即每个轴独立判断误差阈值过滤err参数构造器中的minerr默认0.0——若CPERRiX值小于该阈值则该轴畸变被跳过tables[i] None回退读取扩展对DPiX.AXIS.i指示的轴序判断是否需要对WCSDVARR扩展数据做转置再从扩展头读取CRPIX/CRVAL/CDELT构建DistortionLookupTable对象清除已消费关键字读取后从 header 中删除对应的畸变关键字del header[...]避免它们被后续传给 WCSLIB 的核心 WCS 解析流程。关键关键字速查表以keyA的备选 WCS 为例修复后的正确关键字集合如下主 WCS 去掉A后缀即可关键字含义示例值CPDIS1A/CPDIS2A轴 1/2 的 prior 畸变类型声明LOOKUPCPERR1A/CPERR2A轴 1/2 的 prior 畸变最大误差0.001DP1A.EXTVER/DP2A.EXTVER指向WCSDVARR扩展的版本号2DP1A.NAXES/DP2A.NAXES畸变函数的自变量维数2DP1A.AXIS.1/DP1A.AXIS.2畸变函数各变量对应图像轴1/2WCSDVARR扩展畸变查找表数据所在扩展ver与EXTVER对应扩展中另有CRPIX1/2、CRVAL1/2、CDELT1/2_read_distortion_kw对dist参数还支持CQDISsequent 畸变对应DQ/CQERR关键字族这是同一套后缀逻辑的另一应用分支见 wcs.py#L1207-L1212。回归测试两条测试用例如何锁定修复行为修复的正确性由 test_wcs.py 中的两条测试 严格锁定它们分别覆盖了 bug 的两种故障表现1.test_alternate_wcs_distortion_key_isolation隔离性构造一个只有主 WCS 带畸变CPDIS1/CPDIS2LOOKUP并附DP1.EXTVER、DP1.NAXES、DP1.AXIS.1等、备选 WCS 没有任何畸变关键字注释明确写着 NOTE: No CPDIS1A, CPDIS2A keywords的头部。随后wcs_primary wcs.WCS(header, hdulist, key ) assert wcs_primary.cpdis1 is not None assert wcs_primary.cpdis2 is not None wcs_secondary wcs.WCS(header, hdulist, keyA) assert wcs_secondary.cpdis1 is None, \ Secondary WCS should NOT load primary WCS distortion when CPDIS1A is missing assert wcs_secondary.cpdis2 is None, \ Secondary WCS should NOT load primary WCS distortion when CPDIS2A is missing这正是对备选 WCS 不得回退加载主畸变这一修复点位的直接验证。2.test_alternate_wcs_distortion_independent独立性构造主 WCS 与备选 WCS 各自拥有独立畸变的头部主 WCS 用CPDIS1/DP1.EXTVER 1指向WCSDVARRver 1备选 WCS 用CPDIS1A/DP1A.EXTVER 2指向WCSDVARRver 2且两份查找表数据内容不同。测试断言wcs_secondary wcs.WCS(header, hdulist, keyA) assert wcs_secondary.cpdis1 is not None assert wcs_secondary.cpdis2 is not None assert wcs_primary.cpdis1 is not wcs_secondary.cpdis1 assert not np.array_equal(wcs_primary.cpdis1.data, wcs_secondary.cpdis1.data)即备选 WCS 必须从带A后缀的关键字独立加载自己的畸变且数据与主 WCS 完全隔离。修复前的实现无法通过这两条测试。实战构造一个可运行的备选 WCS 畸变加载示例以下示例可直接复制运行需 astropy 已安装完整复现上述两条测试的构造与验证思路import numpy as np from astropy.io import fits from astropy import wcs # 1. 主 HDU写入主 WCS 与备选 WCS (keyA) 的核心关键字 primary fits.PrimaryHDU() header primary.header header[NAXIS] 2 header[NAXIS1] 1024 header[NAXIS2] 1024 # 主 WCS无后缀 header[CTYPE1] RA---TAN header[CTYPE2] DEC--TAN header[CRPIX1] 512 header[CRPIX2] 512 header[CRVAL1] 150.0 header[CRVAL2] -35.0 header[CDELT1] -0.0002777778 header[CDELT2] 0.0002777778 # 备选 WCS带 A 后缀 header[CTYPE1A] HPLN-TAN header[CTYPE2A] HPLT-TAN header[CRPIX1A] 512 header[CRPIX2A] 512 header[CRVAL1A] 0.0 header[CRVAL2A] 0.0 header[CDELT1A] 0.5 header[CDELT2A] 0.5 # 2. 主 WCS 的畸变关键字无后缀指向 WCSDVARR ver 1 header[CPDIS1] LOOKUP header[CPDIS2] LOOKUP header[DP1.EXTVER] 1 header[DP1.NAXES] 2 header[DP1.AXIS.1] 1 header[DP1.AXIS.2] 2 header[DP2.EXTVER] 1 header[DP2.NAXES] 2 header[DP2.AXIS.1] 1 header[DP2.AXIS.2] 2 # 3. 备选 WCS 的畸变关键字带 A 后缀指向 WCSDVARR ver 2 header[CPDIS1A] LOOKUP header[CPDIS2A] LOOKUP header[DP1A.EXTVER] 2 header[DP1A.NAXES] 2 header[DP1A.AXIS.1] 1 header[DP1A.AXIS.2] 2 header[DP2A.EXTVER] 2 header[DP2A.NAXES] 2 header[DP2A.AXIS.1] 1 header[DP2A.AXIS.2] 2 # 4. 两份不同的畸变查找表数据129x129值不同以区分 dist_data_primary np.random.randn(129, 129).astype(np.float32) * 0.01 dist_data_secondary np.random.randn(129, 129).astype(np.float32) * 0.02 def make_dist_hdu(data, ver): hdu fits.ImageHDU(data, nameWCSDVARR) hdu.ver ver hdu.header[CRPIX1] 65.0 hdu.header[CRPIX2] 65.0 hdu.header[CRVAL1] 513.0 hdu.header[CRVAL2] 1.0 hdu.header[CDELT1] 8.0 hdu.header[CDELT2] 8.0 return hdu hdulist fits.HDUList([ primary, make_dist_hdu(dist_data_primary, 1), make_dist_hdu(dist_data_secondary, 2), ]) # 5. 分别加载并断言隔离性 wcs_primary wcs.WCS(header, hdulist, key ) wcs_secondary wcs.WCS(header, hdulist, keyA) assert wcs_primary.cpdis1 is not None assert wcs_secondary.cpdis1 is not None assert wcs_primary.cpdis1 is not wcs_secondary.cpdis1 assert not np.array_equal(wcs_primary.cpdis1.data, wcs_secondary.cpdis1.data) print(主 WCS 与备选 WCS 畸变独立加载成功)关键点fobj必须传入fits.HDUList否则LOOKUP类型畸变会抛出ValueErroran astropy.io.fits.HDUList is required for Lookup table distortion见 wcs.py#L1236-L1240。相关机制SIP 畸变同样遵循后缀规则本次修复还联动保证了一条既有约定SIP 畸变关键字的读取也支持备选后缀。_read_sip_kw接收wcskeykey参数见 wcs.py#L638 与 wcs.py#L1416-L1480并按CTYPE{nax}{wcskey}检查-SIP后缀一致性若检测到 SIP 系数但CTYPE缺少-SIP后缀会给出坐标可能不正确的提示日志。相关回归测试包括test_sip_with_altkey验证keyA时读取CTYPE1A RA---SIN-SIP与test_keyedsipkeyA时正确读取A_ORDER/A_i_j系数见 test_wcs.py#L1449-L1501。底层执行链畸变查找表如何进入坐标变换管线从 C 层看cpdis1/cpdis2是DistortionLookupTable对象最终以distortion_lookup_t*指针数组的形式挂入 WCS 管线pipeline.c#L22-L40。坐标变换时pix2foc阶段调用p4_pix2foc将像素坐标先经 prior 畸变cpdis映射到焦点平面astropy_wcs.c#L373管线中has_p4判断cpdis[0]/cpdis[1]是否非空决定是否启用该阶段pipeline.c#L92。因此cpdis1/cpdis2若被错误赋值旧 bug 的根源备选 WCS 的all_pix2world/all_world2pix坐标结果就会混入主 WCS 的畸变校正。使用注意事项与限制仅支持 2 维FITS WCS distortion paper 查找表与 SIP 畸变只支持 2 维若核心 WCS 维度大于 2构造会抛出ValueError提示通过naxis参数降维后使用wcs.py#L681-L692to_header不写出畸变to_header明确不写 distortion paper 信息需多 HDU 表示完整写出应使用to_fits经由_write_distortion_kw写出CPDISi、DPi.EXTVER、DPi.NAXES、DPi.AXIS.i及WCSDVARR扩展见 wcs.py#L1285-L1341多项式畸变未实现若CPDISiX的值不是LOOKUP如TPD等多项式类型当前实现仅发出AstropyUserWarningPolynomial distortion is not implemented不加载畸变wcs.py#L1272-L1276minerr阈值构造函数minerr默认0.0可跳过最大误差小于阈值的畸变可用于条件性忽略低精度畸变表。总结19228 号修复从根本上一并解决了备选 WCS 畸变关键字的命名解析与数据隔离两个问题_read_distortion_kw现在严格按照key后缀拼接CPDIS/CPERR/DP等关键字族使keyA/B等备选 WCS 只能读取自己的畸变绝不回退到主 WCS配套的隔离性与独立性两条回归测试保证了该行为长期稳定。对于处理多 WCS 天文图像如 HST/地面望远镜多 CCD 数据的开发者而言这套后缀规则与验证方法是构造合规 FITS 头、排查坐标偏差的重要依据。赞分享科学计算科研数据分析【免费下载链接】astropyAstronomy and astrophysics core library项目地址https://gitcode.com/gh_mirrors/as/astropy点击查看免费下载相关推荐Marketing Skills 之 image 技能实战AI 营销图像生成、优化与规模化工作流指南Marketing Skills 之 image 技能实战AI 营销图像生成、优化与规模化工作流指南 本文基于开源仓库 Marketing Skills 中的科学计算科研数据分析Astropy WCS preserve_unitsTrue 段错误修复解析set() 后设置 cdelt/crval 的单元缩放机制与 C 层缺陷Astropy WCS preserve_unitsTrue 段错误修复解析 set 后设置 cdelt / crval 的单元缩放机制与 C 层缺陷 导读科学计算科研数据分析Astropy WCS preserve_unitsTrue 下 to_header() 的修复编程方式设置单位与 set() 调用时序解析Astropy WCS preserve_unitsTrue 下 to_header 的修复编程方式设置单位与 set 调用时序解析 本文聚焦 astrop科学计算科研数据分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考