はじめに
Chandra 解析を始めると、まず気になるのは「観測がいつ行われたのか」「露出時間はどれくらいか」「どのチップで観測されたのか」といった基本的なメタ情報です。
これらは CIAO のコマンドや FITS ヘッダを直接確認したり、Chandra 解析定番の ChaSeR などで調べたりすることができます。
ただ、解析の初期段階では 複数の ObsID をまとめて整理したいことも多く、また既存のサービスでは対応していない 光軸位置(RA/Dec/Roll)など解析に重要な追加情報 も一緒に確認できると便利です。
そこで本記事では、Python を使って FITS ヘッダから観測情報を抜き出し、Markdown や LaTeX の表として出力する方法 を紹介します。
研究ノートや論文用にそのまま利用できるフォーマットを作成できるので、解析の初期チェックやデータ整理にぜひご活用ください。
方法
ここでは、すでに CIAO で fluximage
を実行して broad_flux.img
などのイメージファイルがあることを前提に進めます。
このスクリプトでは以下を行います:
-
FITS ヘッダから観測メタ情報を抽出
- Obs ID, Start Date, Exposure, Frame Time
- RA, Dec, Roll
- Instrument, Detnum, Grating, Mode
-
数値を所定の桁数に整形(例: 小数点以下 2 桁や 3 桁)
-
出力形式を plain text / Markdown / LaTeX から切り替え可能
-
必要に応じて列を選んで出力
Instrument についての注意
本コードでは、ACIS の場合に ACIS-I / ACIS-S / ACIS-I/S の3種類に分類する機能を入れています。
一次情報は Detnum に記録されており、開いているチップ番号がそこに含まれています。
-
ACIS の構成例
- ACIS-I: 0, 1, 2, 3
- ACIS-S: 4, 5, 6, 7, 8, 9
詳しくは Proposer’s Observatory Guide (ACIS) を参照してください。
(出典: POG ACIS)
この記事のコードでは ACIS-I と ACIS-S が同時に開いているケースでは、Instrument 列を「ACIS-I/S」としています(RA/Dec などの情報を組み合わせれば自動判定も可能だと思いますが、処理が複雑になり誤判定のリスクもあるため、本記事では含めていません。)
基本的には、解析では 実際にイメージを開いて確認するのが確実です。
このコードはあくまで簡易的なメタ情報の整理を目的としているため、参考用に Detnum を列として残しています。
コード全文
from astropy.io import fits
from astropy.time import Time
import pandas as pd
import glob
# -----------------------------
# Instrument 判定
# -----------------------------
def parse_instrument(hdr):
inst = hdr.get("INSTRUME", "N/A")
det = hdr.get("DETNAM", "")
if inst == "ACIS":
has_i = any(ch in det for ch in ["0","1","2","3"])
has_s = any(ch in det for ch in ["4","5","6","7","8","9"])
if has_i and not has_s:
inst = "ACIS-I"
elif has_s and not has_i:
inst = "ACIS-S"
elif has_i and has_s:
inst = "ACIS-I/S"
else:
inst = "ACIS"
elif inst == "HRC":
if "HRC-I" in det:
inst = "HRC-I"
elif "HRC-S" in det:
inst = "HRC-S"
else:
inst = "HRC"
return inst
# -----------------------------
# 表示用フォーマット関数
# -----------------------------
def fmt(x, spec):
if isinstance(x, (int, float)):
return format(x, spec)
return x # "N/A"など文字列はそのまま返す
# -----------------------------
# 表出力関数
# -----------------------------
def show_table(df, columns=None, mode="markdown"):
"""観測情報を選択列+選択フォーマットで出力"""
if columns is None:
subdf = df
else:
subdf = df[columns]
title = f"Showing columns: {', '.join(subdf.columns)}"
print("\n" + "="*80)
print(f"=== {title} ({mode}) ===")
print("="*80 + "\n")
if mode == "plain":
print(subdf.to_string(index=False))
elif mode == "markdown":
print(subdf.to_markdown(index=False, disable_numparse=True))
elif mode == "latex":
print(subdf.to_latex(index=False))
else:
raise ValueError(f"Unknown mode: {mode}")
# -----------------------------
# メイン処理
# -----------------------------
# Chandra の fluximage 出力 (broad_flux.imgなど) を対象にする
files = sorted(glob.glob("/path/to/obs/*/fluxed/broad_flux.img"))
rows = []
for f in files:
with fits.open(f) as hdul:
hdr = hdul[0].header
obsid = hdr.get("OBS_ID")
# Start Date & MJD
start_date = hdr.get("DATE-OBS")
mjd = None
if start_date:
t = Time(start_date)
start_date = t.strftime("%Y %b %d")
mjd = t.mjd
exp = hdr.get("EXPOSURE", 0.0) / 1000.0 # 秒→ks
frame = hdr.get("EXPTIME", "N/A")
ra = hdr.get("RA_PNT")
dec = hdr.get("DEC_PNT")
roll = hdr.get("ROLL_PNT")
inst = parse_instrument(hdr)
det = hdr.get("DETNAM", "")
grat = hdr.get("GRATING", "NONE")
mode = hdr.get("DATAMODE")
rows.append([obsid, start_date, mjd, exp, frame, ra, dec, roll, inst, det, grat, mode])
df = pd.DataFrame(rows, columns=[
"ObsID", # 観測ID
"Start Date", # 観測開始日 (YYYY Mon DD)
"MJD", # Modified Julian Date
"Exposure [ks]", # 露出時間
"Frame Time [s]",# フレーム時間
"RA [deg]", # 指向点 (赤経)
"Dec [deg]", # 指向点 (赤緯)
"Roll [deg]", # ロール角
"Instrument", # ACIS/HRC など
"Detnum", # DETNAM (チップ構成をそのまま表示)
"Grating", # HETG/LETG など
"Mode" # DATAMODE (FAINT/VFAINT etc.)
])
# -----------------------------
# フォーマット調整
# -----------------------------
df["MJD"] = df["MJD"].map(lambda x: fmt(x, ".2f"))
df["Exposure [ks]"] = df["Exposure [ks]"].map(lambda x: fmt(x, ".2f"))
df["Frame Time [s]"] = df["Frame Time [s]"].map(lambda x: fmt(x, ".1f"))
df["RA [deg]"] = df["RA [deg]"].map(lambda x: fmt(x, ".3f"))
df["Dec [deg]"] = df["Dec [deg]"].map(lambda x: fmt(x, ".3f"))
df["Roll [deg]"] = df["Roll [deg]"].map(lambda x: fmt(x, ".2f"))
# 日付ソート
df["Start Date"] = pd.to_datetime(df["Start Date"], format="%Y %b %d", errors="coerce")
df = df.sort_values("Start Date")
df["Start Date"] = df["Start Date"].dt.strftime("%Y %b %d")
# -----------------------------
# 出力例
# -----------------------------
show_table(df, mode="plain")
show_table(df, mode="markdown")
show_table(df, mode="latex")
# 必要な列だけ選んで出力も可能
show_table(df, columns=["ObsID", "Start Date", "MJD", "Exposure [ks]"], mode="plain")
使い方
files
にイメージファイル(例: broad_flux.img
)のリストを指定すると、このコードはそのまま動作します。
特に glob
を使えば、ディレクトリ構造が整理されている環境では自動的にファイルを見つけてくれるので便利です。
出力例
Plain text
ObsID Start Date MJD Exposure [ks] Frame Time [s] RA [deg] Dec [deg] Roll [deg] Instrument Detnum Grating Mode
114 2000 Jan 30 51573.45 49.93 3.2 350.916 58.793 323.38 ACIS-S ACIS-7 NONE GRADED
9773 2007 Dec 08 54442.53 24.84 3.0 350.875 58.784 278.13 ACIS-S ACIS-7 NONE GRADED
19903 2016 Oct 20 57681.19 24.65 3.0 350.816 58.791 214.20 ACIS-S ACIS-78 NONE GRADED
Markdown
| ObsID | Start Date | MJD | Exposure [ks] | Frame Time [s] | RA [deg] | Dec [deg] | Roll [deg] | Instrument | Detnum | Grating | Mode |
|:---------|:-------------|:---------|:----------------|:-----------------|:-----------|:------------|:-------------|:-------------|:---------|:----------|:-------|
| 114 | 2000 Jan 30 | 51573.45 | 49.93 | 3.2 | 350.916 | 58.793 | 323.38 | ACIS-S | ACIS-7 | NONE | GRADED |
| 9773 | 2007 Dec 08 | 54442.53 | 24.84 | 3.0 | 350.875 | 58.784 | 278.13 | ACIS-S | ACIS-7 | NONE | GRADED |
| 19903 | 2016 Oct 20 | 57681.19 | 24.65 | 3.0 | 350.816 | 58.791 | 214.20 | ACIS-S | ACIS-78 | NONE | GRADED |
LaTeX
\begin{tabular}{llllllllllll}
\toprule
ObsID & Start Date & MJD & Exposure [ks] & Frame Time [s] & RA [deg] & Dec [deg] & Roll [deg] & Instrument & Detnum & Grating & Mode \\
\midrule
114 & 2000 Jan 30 & 51573.45 & 49.93 & 3.2 & 350.916 & 58.793 & 323.38 & ACIS-S & ACIS-7 & NONE & GRADED \\
9773 & 2007 Dec 08 & 54442.53 & 24.84 & 3.0 & 350.875 & 58.784 & 278.13 & ACIS-S & ACIS-7 & NONE & GRADED \\
19903 & 2016 Oct 20 & 57681.19 & 24.65 & 3.0 & 350.816 & 58.791 & 214.20 & ACIS-S & ACIS-78 & NONE & GRADED \\
\bottomrule
\end{tabular}
補足
表に含めると便利な列
論文や解析ノートでは、最低限以下を含めておくと便利です:
- ObsID
- Start Date
- Exposure
- Instrument
- RA, Dec, Roll
解析の種類に応じて追加列を設けるとさらに有用です。たとえば MJD を入れて時系列解析を整理したり、Orbital Phase を含めることで連星系の研究に直結する表を作成できます。
イメージング解析では光軸情報が重要(RA/Dec/Roll)
Chandra では 光軸(pointing)の位置が PSF 形状や空間分解能に直結するため、イメージング解析では特に重要です。
光軸の簡単な確認方法については、以下の記事でもまとめていますので参考にしてください。
論文時は解析データの DOI の発行が求められる場合もある
DOI (Digital Object Identifier) は、論文やデータセットを一意に識別し、URL として恒久的にアクセス可能にする仕組みです。
論文に DOI が付与されているのは一般的ですが、近年では 観測データ に対しても DOI を発行 し、論文内で引用することが求められたり推奨される場合もあります。
特に Chandra 衛星のデータを利用した場合、論文投稿時に CDA DOI の引用を求められるケースがあります。
必ずしも全てのジャーナルで必須ではありませんが、DOI を付与しておくと、論文の読者や他の研究者がクリックするだけで観測情報に直接アクセスできるため、研究の再現性や参照性が大きく向上します。
私自身も Sakai et al. (2024) にて DOI を付与しました:
DOI: https://doi.org/10.25574/cdc.280
DOI の発行には数日かかるため多少の手間はありますが、研究の透明性や参照性が高まる点は魅力的で、論文の補足情報として役立つ場面も多いと思います。
DOI の発行方法や詳細は公式ページをご参照ください:
おわりに
本記事で紹介したコードはあくまで一例ですが、観測対象や解析目的に応じて列を追加・変更することでさらに活用できます。
ぜひご自身の研究スタイルに合わせてカスタマイズしながら、Chandra データ解析をより快適に、そして楽しく進めるための参考になれば幸いです。