SLLIB + SFITSIO を使ったデータ解析講習会 [part3]

目的、ゴール

昨日までの講習 でざっとSLLIB/SFITSIOの機能の紹介があったと思います。

今日の講習では、実際にSLLIB/SFITSIOを使いデータ解析プログラムを作りSLLIB/SFITSIOの使い方を身につけていきましょう。 ゴールは下の図左端の銀河の形状測定です。
など文章にする

天体の形状測定

大きさ vs. 明るさ
これはイマイチだけどここに載せる?

cosmic-ray潰し前

cosmic-ray潰し後

本講習の内容

このチュートリアルに必要な物

一次処理概要(Suprime-Cam)

※ 本講習では例としてSuprime-Camで撮られたデータに対して処理していきます。 一次処理の内容は装置によって異なるので 他の装置のデータ用のプログラムを書く際は装置にあった一次処理を調べて下さい。

生データ

まずは実際にこれから処理する生データを見てみましょう。 今回の解析で使うデータセットを http://anela.mtk.nao.ac.jp/michitaro/sfitsio-text/data.tar からダウンロードして展開して下さい。
mkdir ~/sfitsio-practice
cd ~/sfitsio-practice
wget http://anela.mtk.nao.ac.jp/michitaro/sfitsio-text/data.tar
tar xvf data.tar
展開後のdata/object.fitsが今回の解析対象の生データです。 (それ以外のFITSファイルは後述のフラットデータです。) ds9で開いて中身を確認してみましょう。
ds9 data/object.fits
全体を適当なスケールで見るには次のようにして下さい。
  1. メニューから「Zoom/Zoom to Fit Frame」を選ぶ
  2. メニューから「Scale/ZScale」を選ぶ (Scale/ZScale...ではない)
まず黒い帯で領域が4つに分割されていることに気付くと思います。 この4つの領域は別々のアンプで読み出されていて、それぞれのアンプでバイアスやゲインが違うため領域間で段差が見えてます。 また左上が少し暗くなっていますがこれは周辺減光によるものです。 ヘッダーも確認してみましょう。
  1. メニューから「File/Display Header...」を選ぶ
BITPIX=16, BZERO=32768.0, BSCALE=1.0からこのファイルのデータが16bit整数、レンジが0...65535ということがわかります。 各ピクセルの値はそのピクセルにカーソルを合わせると、Value欄に表示されます。
「物理値 = BZERO + BSCALE × 配列値」となります。(FITSの手引き 第5.3版 p.48)

object.fits

Suprime-CamのCCD
http://atc.mtk.nao.ac.jp/ccd/FDCCD/top.html より

object.fitsのヘッダ
これらのデータのもとのframe IDは次の通りです。
lrwxr-xr-x  1 michitaro  staff  25 Jul 12 12:45 data/flat-1.fits -> frameid/SUPA01270727.fits
lrwxr-xr-x  1 michitaro  staff  25 Jul 12 12:45 data/flat-2.fits -> frameid/SUPA01270737.fits
lrwxr-xr-x  1 michitaro  staff  25 Jul 12 12:45 data/flat-3.fits -> frameid/SUPA01270747.fits
lrwxr-xr-x  1 michitaro  staff  25 Jul 12 12:45 data/flat-4.fits -> frameid/SUPA01270757.fits
lrwxr-xr-x  1 michitaro  staff  25 Jul 12 12:45 data/flat-5.fits -> frameid/SUPA01270767.fits
lrwxr-xr-x  1 michitaro  staff  25 Jul 12 12:44 data/object.fits -> frameid/SUPA01269387.fits

一次処理とは

今見た通りCCDの生データは装置の仕組み上入ってくるシグナルがあり空からの信号がそのまま記録されているわけではありません。 このCCDの生データを空からの信号に比例するデータに補正することをここでは一次処理と呼んでいます。 一次処理をするためには生データが空からの信号の他にどういうシグナルが乗っているのかを知る必要があります。

CCD生データの内訳

Suprime-CamのCCDの生データの各ピクセルのカウントは次の様な内訳になっています。 $$ s(x, y) = B(x, y) + G(x, y) I(x, y) $$
$s(x, y)$
生データの座標($x, y$)のピクセルのカウント。
$B(x, y)$
バイアス。受光素子からの電荷読み出し時にアナログデジタル変換の都合上足された値。
$G(x, y)$
感度。CCDの各ピクセル毎の感度、チップ上のゴミや周辺減光の効果など。これはショットによらず固定されているもの考えられる。
$I(x, y)$
空からの信号。これが知りたい。積分時間に比例する。
生データ$s(x, y)$から空からの信号$I(x, y)$を得るためには$B(x, y), G(x, y)$が必要です。 これらのデータはどのように取得できるのか見ていきます。

$B(x, y)$ : バイアス

バイアスの値はオーバースキャン領域からわかります。 オーバースキャン領域とはCCDの生データの有効領域のすぐ隣のカウントの低い領域で、これはCCDがデータを出力する際に後の補正のために書き出しています。 オーバースキャン領域の横一行の平均がその横一行の有効領域のバイアスのよい推定値となります。
(ちなみに、medianを使う流儀もあります。SDFREDはmedian。)
$$ B(x, y) = \frac 1 {w} \sum_{x'=1}^w A(x', y) $$
$w$
オーバースキャン領域の横ピクセル数
$A(x, y)$
オーバースキャン領域のカウント
Suprime-CamのCCDはアンプが4つあり、それぞれのアンプに対応した有効領域とオーバースキャン領域があります。 データのどの領域がどのアンプに対応するかなどは実習のところで述べます。

オーバースキャン概念図

$G(x, y)$: 感度

感度$G(x, y)$を調べるには$I(x, y) = C_{\mathrm{const.}}$であるショットを使います。 これは下の写真のような一様な光源や夕方・明け方(薄明)の空などを撮ることで得られます。 そうして得られたショットをフラットと呼びます。 先ほどダウンロードしたデータの中にも数枚このフラットの生データが含まれているのでds9で開いて中身を確認してみましょう。
ds9 data/flat-1.fits
あるフラット$i$のピクセル座標$x, y$のカウントを$g_i(x, y)$とすると(1)の$s(x, y)$が$g_i(x, y)$、$I(x, y)$が$C_i$となって $$ G_i(x, y) = \frac 1 {C_i} \left( g_i(x, y) - B_i(x, y) \right) $$ となります。 $B_i(x, y)$は上で述べた通りに決まっているので$G_i(x, y)$が得られます。 $G_i(x, y)$はピクセルの感度ムラや周辺減光などに由来するものでショットによらず一定と考えられるので、あるフラットから 得られた$G_i(x, y)$を他のショットの一次処理に使うことが出来ます。 しかし単一のフラットにはcosmic-rayが(夕方・明け方(薄明)の空なら星も)乗ってしまっています。 それらを取り除くために数枚から得られた$G_i(x, y)$をそれぞれ中央値で規格化したものを重ねて中央値をとり それを$G(x, y)$として使います。これをマスターフラットと呼ぶことにします。 $$ G(x, y) = \mathrm{median}\left(\frac 1 {M(G_1)} G_1(x, y), \frac 1 {M(G_2)} G_2(x, y), \cdots\right) $$ $M(G_i)$は$G_i(x, y)$の中央値を表します。
** 書く必要はないかもしれませんが、ナイトスカイ自身を使うフラットも  よく使われるので一応念頭に置いておくとよいと思います。  オブジェクトフラット、セルフフラット、などとも呼ばれます。 (実は大半のSCユーザはこれまで可能な場合はナイトスカイフラットを  使ってきています)

※ ややこしいですが、高さを合わせてstackするということです。 数枚でstackするのはcosmic-rayを消す他にSNをあげるというのも重要な目的です。

ドームフラットの撮影

ドームフラットの例
flat-1.fits

フラット上のcosmic-ray
2枚の同じ領域のフラット

中央値でstack
cosmic-rayが消える

一次処理実習(Suprime-Cam)

ここで今までのまとめを再掲します。 \begin{eqnarray} I(x, y) &=& \frac 1 {G(x, y)} \left( s(x, y) - B(x, y) \right) \\ G_i(x, y) &=& \frac 1 {C_i} \left( g_i(x, y) - B_i(x, y) \right) \\ G(x, y) &=& \mathrm{median}\left(\frac 1 {M(G_1)} G_1(x, y), \frac 1 {M(G_2)} G_2(x, y), \cdots \right) \end{eqnarray} ここで \begin{eqnarray} s'(x, y) &=& s(x, y) - B(x, y) \\ g_i'(x, y) &=& g_i(x, y) - B_i(x, y) \end{eqnarray} とすると、まとめの式は \begin{eqnarray} I(x, y) &=& \frac 1 {G(x, y)} s'(x, y) \\ G(x, y) &=& \mathrm{median} \left( \frac 1 {M(g_1')} g_1'(x, y), \frac 1 {M(g_2')} g_2'(x, y), \cdots \right) \end{eqnarray} となります。 これから実際にSFITSIOで一次処理を行うプログラムを作成していきますが、まずは何を作るか列挙しておきます。
  1. 単一のFITSからバイアスを引き算して書き出すプログラム
  2. 複数枚のバイアス補正済みフラットの中央値のstackを作るプログラム
  3. バイアス補正済みの解析対象データとマスターフラットを引数に感度補正をするプログラム
それでは、いよいよコーディングです。

バイアス補正

好きなエディタでdebias.ccという名前でコードを書いていきましょう。 このプログラムは
./debias INPUT OUTPUT
のように実行され、INPUTののFITSファイルを読み込みバイアスを引いてOUTPUTに書き出すというものです。 まずは引数が2つでなければ警告を吐いて終了する所まで。
#include <sli/fitscc.h>
#include <stdlib.h>

using namespace sli;

int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    return 0;
}
download
sli__eprintfはprintfとだいだい同じですが出力先が標準出力ではなく標準エラー出力になっています。
コンパイル & 引数なしで実行してエラーが表示される所まで確認して下さい。
s++ debias.cc -lsfitsio
./debias
usage: ./debias INPUT OUTPUT
次はここまで進みましょう。
--- code/debias-1.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/debias-2.cc	2015-11-16 14:39:41.000000000 +0900
@@ -3,6 +3,12 @@
 
 using namespace sli;
 
+
+static void debias(fits_image &hdu)
+{
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
@@ -10,5 +16,10 @@
         exit(1);
     }
 
+    fitscc fits;
+    fits.read_stream(argv[1]);
+    debias(fits.image(0L));
+    fits.write_stream(argv[2]);
+
     return 0;
 }
#include <sli/fitscc.h>
#include <stdlib.h>

using namespace sli;


static void debias(fits_image &hdu)
{
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc fits;
    fits.read_stream(argv[1]);
    debias(fits.image(0L));
    fits.write_stream(argv[2]);

    return 0;
}
download
fitscc , fits_image はそれぞれFITSファイル、ImageHDUに対応するクラスです。 どういうメンバー関数があるかリンク先のページで確認してみて下さい。 debias()には各アンプ毎にオーバースキャン領域からバイアス値を求め、それを有効領域から引いていくという処理を実装します。

データ内のどの領域が有効領域、オーバスキャン領域領域かはヘッダに書いてあります。 領域とキーワードの関係の図も確認して下さい。 有効領域のキーワードは「S_EFMN11」「S_EFMX32」など、オーバスキャン領域のキーワードは「S_OSMN11」「S_OSMX42」などです。 5,6文字目のMN, MXがmin, maxの略で7文字目がアンプの番号、8文字目が軸の番号(Xが1, Yが2)です。

領域とキーワードの関係
※ Suprime-Camのチップでも別の順番でアンプが並んでいる物があります
まずはヘッダーキーワードから各アンプの有効領域、オーバースキャン領域のX座標を取得し書き出す所まで作ってみましょう。
--- code/debias-2.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/debias-3.cc	2015-11-16 14:39:41.000000000 +0900
@@ -4,8 +4,20 @@
 using namespace sli;
 
 
+static const int amp_n = 4; // アンプの数
+
+
 static void debias(fits_image &hdu)
 {
+    for (int amp = 1;  amp <= amp_n;  amp++) {
+        const int os_min_x = hdu.headerf("S_OSMN%d1", amp).lvalue(),
+                  os_max_x = hdu.headerf("S_OSMX%d1", amp).lvalue(),
+                  ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
+                  ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue();
+        sli__eprintf(
+            "amp = %d    os_min_x = % 5d    os_max_x = % 5d    ef_min_x = % 5d    ef_max_x = % 5d\n",
+            amp, os_min_x, os_max_x, ef_min_x, ef_max_x);
+    }
 }
 
 
#include <sli/fitscc.h>
#include <stdlib.h>

using namespace sli;


static const int amp_n = 4; // アンプの数


static void debias(fits_image &hdu)
{
    for (int amp = 1;  amp <= amp_n;  amp++) {
        const int os_min_x = hdu.headerf("S_OSMN%d1", amp).lvalue(),
                  os_max_x = hdu.headerf("S_OSMX%d1", amp).lvalue(),
                  ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
                  ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue();
        sli__eprintf(
            "amp = %d    os_min_x = % 5d    os_max_x = % 5d    ef_min_x = % 5d    ef_max_x = % 5d\n",
            amp, os_min_x, os_max_x, ef_min_x, ef_max_x);
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc fits;
    fits.read_stream(argv[1]);
    debias(fits.image(0L));
    fits.write_stream(argv[2]);

    return 0;
}
download
コンパイル & 引数をちゃんとつけて実行してみましょう。
s++ debias.cc -lsfitsio / data/object.fits debiased.fits
# これは
# s++ debias.cc -lsfitsio
# ./debias data/object.fits debiased.fits
# と同じ
amp = 1    os_min_x =   521    os_max_x =   568    ef_min_x =     9    ef_max_x =   520
amp = 2    os_min_x =   569    os_max_x =   616    ef_min_x =   617    ef_max_x =  1128
amp = 3    os_min_x =  1657    os_max_x =  1704    ef_min_x =  1145    ef_max_x =  1656
amp = 4    os_min_x =  1705    os_max_x =  1752    ef_min_x =  1753    ef_max_x =  2264
ちゃんと出力されたでしょうか?
ds9でヘッダを表示し出力とヘッダの内容が一致しているか確かめて下さい。
ds9 data/object.fits
  1. メニューから「File/Display Header...」を選ぶ
次はオーバースキャン領域の横1行のピクセルの平均で有効領域を引いていきます。 FITSヘッダのピクセル座標は0でなく1始まりなことに気をつけてください。
--- code/debias-3.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/debias-4.cc	2015-11-16 14:39:41.000000000 +0900
@@ -1,4 +1,5 @@
 #include <sli/fitscc.h>
+#include <sli/mdarray_statistics.h>
 #include <stdlib.h>
 
 using namespace sli;
@@ -9,6 +10,8 @@
 
 static void debias(fits_image &hdu)
 {
+    hdu.convert_type(FITS::FLOAT_T);
+    mdarray_float &data = hdu.float_array();
     for (int amp = 1;  amp <= amp_n;  amp++) {
         const int os_min_x = hdu.headerf("S_OSMN%d1", amp).lvalue(),
                   os_max_x = hdu.headerf("S_OSMX%d1", amp).lvalue(),
@@ -17,6 +20,13 @@
         sli__eprintf(
             "amp = %d    os_min_x = % 5d    os_max_x = % 5d    ef_min_x = % 5d    ef_max_x = % 5d\n",
             amp, os_min_x, os_max_x, ef_min_x, ef_max_x);
+        mdarray_float overscan_region, mean_x;
+        overscan_region = data.section(os_min_x - 1, os_max_x - os_min_x + 1);
+        mean_x = md_mean_x(overscan_region);
+        for (unsigned y = 0;  y < data.length(1);  y++) {
+            for (int x = ef_min_x - 1;  x < ef_max_x;  x++)
+                data(x, y) -= mean_x(0, y);
+        }
     }
 }
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>

using namespace sli;


static const int amp_n = 4; // アンプの数


static void debias(fits_image &hdu)
{
    hdu.convert_type(FITS::FLOAT_T);
    mdarray_float &data = hdu.float_array();
    for (int amp = 1;  amp <= amp_n;  amp++) {
        const int os_min_x = hdu.headerf("S_OSMN%d1", amp).lvalue(),
                  os_max_x = hdu.headerf("S_OSMX%d1", amp).lvalue(),
                  ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
                  ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue();
        sli__eprintf(
            "amp = %d    os_min_x = % 5d    os_max_x = % 5d    ef_min_x = % 5d    ef_max_x = % 5d\n",
            amp, os_min_x, os_max_x, ef_min_x, ef_max_x);
        mdarray_float overscan_region, mean_x;
        overscan_region = data.section(os_min_x - 1, os_max_x - os_min_x + 1);
        mean_x = md_mean_x(overscan_region);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (int x = ef_min_x - 1;  x < ef_max_x;  x++)
                data(x, y) -= mean_x(0, y);
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc fits;
    fits.read_stream(argv[1]);
    debias(fits.image(0L));
    fits.write_stream(argv[2]);

    return 0;
}
download
ds9でちゃんと引かれているか確認してみましょう。
s++ debias.cc -lsfitsio / data/object.fits debiased.fits
ds9 data/object.fits debiased.fits
バイアスを引いた後はオーバースキャン領域はもう使わないので切り取ってしまいましょう。

オーバースキャン領域を切り取り有効領域だけにする
--- code/debias-4.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/debias-5.cc	2015-11-16 14:39:41.000000000 +0900
@@ -31,6 +31,28 @@
 }
 
 
+static void drop_overscan(fits_image &hdu) {
+    mdarray_float &data = hdu.float_array();
+
+    // y方向で切り詰め
+    const int ef_min_y = hdu.headerf("S_EFMN12").lvalue(),
+              ef_max_y = hdu.headerf("S_EFMX12").lvalue();
+    data.crop(1, ef_min_y - 1, ef_max_y - ef_min_y + 1);
+
+    // x方向で切り詰め
+    int filled_width = 0;
+    for (int amp = 1;  amp <= amp_n;  amp++) {
+       const int ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
+                 ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue(),
+                 width = ef_max_x - ef_min_x + 1;
+
+        data.move(ef_min_x - 1, width, filled_width, false);
+        filled_width += width;
+    }
+    data.crop(0, 0, filled_width);
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
@@ -41,6 +63,7 @@
     fitscc fits;
     fits.read_stream(argv[1]);
     debias(fits.image(0L));
+    drop_overscan(fits.image(0L));
     fits.write_stream(argv[2]);
 
     return 0;
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>

using namespace sli;


static const int amp_n = 4; // アンプの数


static void debias(fits_image &hdu)
{
    hdu.convert_type(FITS::FLOAT_T);
    mdarray_float &data = hdu.float_array();
    for (int amp = 1;  amp <= amp_n;  amp++) {
        const int os_min_x = hdu.headerf("S_OSMN%d1", amp).lvalue(),
                  os_max_x = hdu.headerf("S_OSMX%d1", amp).lvalue(),
                  ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
                  ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue();
        sli__eprintf(
            "amp = %d    os_min_x = % 5d    os_max_x = % 5d    ef_min_x = % 5d    ef_max_x = % 5d\n",
            amp, os_min_x, os_max_x, ef_min_x, ef_max_x);
        mdarray_float overscan_region, mean_x;
        overscan_region = data.section(os_min_x - 1, os_max_x - os_min_x + 1);
        mean_x = md_mean_x(overscan_region);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (int x = ef_min_x - 1;  x < ef_max_x;  x++)
                data(x, y) -= mean_x(0, y);
        }
    }
}


static void drop_overscan(fits_image &hdu) {
    mdarray_float &data = hdu.float_array();

    // y方向で切り詰め
    const int ef_min_y = hdu.headerf("S_EFMN12").lvalue(),
              ef_max_y = hdu.headerf("S_EFMX12").lvalue();
    data.crop(1, ef_min_y - 1, ef_max_y - ef_min_y + 1);

    // x方向で切り詰め
    int filled_width = 0;
    for (int amp = 1;  amp <= amp_n;  amp++) {
       const int ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
                 ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue(),
                 width = ef_max_x - ef_min_x + 1;

        data.move(ef_min_x - 1, width, filled_width, false);
        filled_width += width;
    }
    data.crop(0, 0, filled_width);
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc fits;
    fits.read_stream(argv[1]);
    debias(fits.image(0L));
    drop_overscan(fits.image(0L));
    fits.write_stream(argv[2]);

    return 0;
}
download
できたらオーバスキャン領域が切り取られつながっていることをds9で確認してみましょう。 次の図のようになっていればOKです。

元データ

オーバースキャン領域が切り取られた
式に戻ると$s(x, y)$から$s'(x, y)$、$g(x, y)$から$g'(x, y)$が出来たということです。

ここで手元の全データにdebiasをかけてしまいましょう。 今後の処理は全てバイアス補正済みのデータに対してのものなので。
./debias data/object.fits data/debiased-object.fits
./debias data/flat-1.fits data/debiased-flat-1.fits
./debias data/flat-2.fits data/debiased-flat-2.fits
./debias data/flat-3.fits data/debiased-flat-3.fits
./debias data/flat-4.fits data/debiased-flat-4.fits
./debias data/flat-5.fits data/debiased-flat-5.fits
# または
# for i in data/*.fits ; do ./debias $i data/debiased-$(basename $i) ; done

マスターフラット

マスターフラットは次の式で作られるのでした。 $$ G(x, y) = \mathrm{median} \left( \frac 1 {M(g_1')} g_1'(x, y), \frac 1 {M(g_2')} g_2'(x, y), \cdots \right) $$ ここで作るプログラムは次のように実行されバイアス補正済みの数枚のフラットを中央値でスタックして書き出すものです。
./combine-flat OUTPUT FLAT1 FLAT2 ...
引数と式の関係ももう一度あげておきます。
$G(x, y)$ OUTPUT
$g_1'(x, y)$ FLAT1
$g_2'(x, y)$ FLAT2
それではコーディングです。 今回のソースコードのファイル名はcombine-flat.ccです。 また、まずは引数が2個以上ないと警告をだして終了する所まで。
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>

using namespace sli;


int main(int argc, char *argv[])
{
    if (argc < 3) {
        sli__eprintf("usage: %s OUTPUT FLAT1 FLAT2 ...\n", argv[0]);
        exit(1);
    }

    return 0;
}
download
s++ combine-flat.cc -lsfitsio
./combine-flat arg1
コンパイル & 引数なしで実行してエラーが出るのを確認したら次はここまで進みましょう。
--- code/combine-flat-1.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/combine-flat-2.cc	2015-11-16 14:39:41.000000000 +0900
@@ -12,5 +12,31 @@
         exit(1);
     }
 
+    // コマンド引数とargv, argcの関係
+    // ./combine-float out.fits flat1.fits flat2.fits flat3.fits
+    // |               |        |          |          |
+    // argv[0]         argv[1]  argv[2]    argv[3]    argv[4]
+    // argc == 5
+
+    const int n_flat = argc - 2;
+
+    fitscc fits;
+    mdarray_float stack;
+
+    for (int i = 0;  i < n_flat;  i++) {
+        const char *flat_file = argv[i + 2];
+        fits.read_stream(flat_file);
+
+        const mdarray_float &data = fits.image(0L).float_array();
+
+        // stack に data を積み上げる処理をここに実装
+
+    }
+
+    // 積み上がったstackをコンバインし
+    // それをfitsにセットする処理をここに実装
+
+    fits.write_stream(argv[1]);
+
     return 0;
 }
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>

using namespace sli;


int main(int argc, char *argv[])
{
    if (argc < 3) {
        sli__eprintf("usage: %s OUTPUT FLAT1 FLAT2 ...\n", argv[0]);
        exit(1);
    }

    // コマンド引数とargv, argcの関係
    // ./combine-float out.fits flat1.fits flat2.fits flat3.fits
    // |               |        |          |          |
    // argv[0]         argv[1]  argv[2]    argv[3]    argv[4]
    // argc == 5

    const int n_flat = argc - 2;

    fitscc fits;
    mdarray_float stack;

    for (int i = 0;  i < n_flat;  i++) {
        const char *flat_file = argv[i + 2];
        fits.read_stream(flat_file);

        const mdarray_float &data = fits.image(0L).float_array();

        // stack に data を積み上げる処理をここに実装

    }

    // 積み上がったstackをコンバインし
    // それをfitsにセットする処理をここに実装

    fits.write_stream(argv[1]);

    return 0;
}
download

課題1

コード中のコメントの内容を実装しましょう。
--- code/combine-flat-2.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/combine-flat-3.cc	2015-11-16 14:39:41.000000000 +0900
@@ -29,12 +29,14 @@
 
         const mdarray_float &data = fits.image(0L).float_array();
 
-        // stack に data を積み上げる処理をここに実装
+        if (i == 0)
+            stack.resize_3d(data.length(0), data.length(1), n_flat);
 
+        stack.paste(data / md_median(data), 0, 0, i);
     }
 
-    // 積み上がったstackをコンバインし
-    // それをfitsにセットする処理をここに実装
+    stack.dprint(); // stackの内容を確認
+    fits.image(0L).float_array() = md_median_small_z(stack);
 
     fits.write_stream(argv[1]);
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>

using namespace sli;


int main(int argc, char *argv[])
{
    if (argc < 3) {
        sli__eprintf("usage: %s OUTPUT FLAT1 FLAT2 ...\n", argv[0]);
        exit(1);
    }

    // コマンド引数とargv, argcの関係
    // ./combine-float out.fits flat1.fits flat2.fits flat3.fits
    // |               |        |          |          |
    // argv[0]         argv[1]  argv[2]    argv[3]    argv[4]
    // argc == 5

    const int n_flat = argc - 2;

    fitscc fits;
    mdarray_float stack;

    for (int i = 0;  i < n_flat;  i++) {
        const char *flat_file = argv[i + 2];
        fits.read_stream(flat_file);

        const mdarray_float &data = fits.image(0L).float_array();

        if (i == 0)
            stack.resize_3d(data.length(0), data.length(1), n_flat);

        stack.paste(data / md_median(data), 0, 0, i);
    }

    stack.dprint(); // stackの内容を確認
    fits.image(0L).float_array() = md_median_small_z(stack);

    fits.write_stream(argv[1]);

    return 0;
}
download
できたらコンパイル & 実行してみましょう。
s++ combine-flat.cc -lsfitsio / data/master-flat.fits data/debiased-flat-*.fits
master-flat.fitsをds9で開いてcosmic-rayが消えていることを確認します。
ds9 data/debiased-flat-{2,3}.fits data/master-flat.fits
  1. メニューから「Frame/Lock/Frame/Image」を選ぶ
  2. メニューから「Frame/Lock/Scale」を選ぶ
  3. メニューから「Frame/Lock/Colorbar」を選ぶ

debiased-flat-1.fits debiased-flat-2.fits
master-flat.fits
単一フラットでは写っていたcosmic-rayが master-flatでは消えている
画像をコンバインするコードは SFITSIO作者による実装 もSFITSIOの example の中にあります。

Flat fielding

一次処理最後のプログラムです。バイアス補正済みのオブジェクトフレームをマスターフラットで割るだけです。 $$ I(x, y) = \frac 1 {G(x, y)} s'(x, y) $$ ここで作るプログラムは次のように実行され バイアス補正済みのオブジェクトフレームをマスターフラットで補正します。
./flat-field OBJECT MASTER_FLAT OUTPUT
今回のソースコードのファイル名はflat-field.ccです。

課題2

flat-field.ccを完成させて下さい。
#include <sli/fitscc.h>
#include <stdlib.h>

using namespace sli;


int main(int argc, char *argv[])
{
    if (argc != 4) {
        sli__eprintf("usage: %s OBJECT MASTER_FLAT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc object;
    object.read_stream(argv[1]);

    fitscc master_flat;
    master_flat.read_stream(argv[2]);

    object.image(0L).float_array() /= master_flat.image(0L).float_array();
    object.write_stream(argv[3]);

    return 0;
}
download
s++ flat-field.cc -lsfitsio / data/debiased-object.fits data/master-flat.fits data/corr.fits
できたら補正済みのデータをds9で確認して下さい。

ds9 data/corr.fits
ちゃんと段差がなくなって、空も平らになったでしょうか?

生データ

バイアス補正後

flat-field後
ここまでが一次処理です。 これで空からの信号に比例するデータにすることが出来ました。

お疲れ様でした。

天体検出概要

一次処理が出来たので天体検出に入りましょう。 今回の天体検出では一次処理したデータに写っている天体のflux、重心、下の図の$a, b, \theta$を測ります。

検出結果

a, b, θ
http://www.kaggle.com/c/mdm/details/ellipticity より
大まかに手順を書くと次のようになります。
  1. マーク
    画像からある閾値以上の値をもったピクセルをマークする
  2. ピックアップ
    マークされたピクセルがある閾値以上連続している場所を探し、そこを天体だとする
  3. 計測
    天体だとされた領域のピクセルの値からflux、重心などの量を測る
詳しくは実習で見ていきます。

天体検出実習

sky引き

一次処理済みの画像をds9で開いて空の領域のカウントを見て下さい。
ds9 data/corr.fits
  1. マウスカーソルを星でない空の領域に合わせds9のValue欄を確認する
おおよそ9500くらいになっていると思います。 この値は空自身の明るさによる物ですが、今後の処理のためにここでskyの明るさ(sky level)を見積もって画像全体から引いてしまいましょう。
このプログラムはdetection.ccという名前で実装していきます。 まずはここまで作って下さい。
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    fits.write_stream(argv[2]);
    return 0;
}
download

課題3

subtract_sky()を完成させて下さい。 subtract_sky()は第1引数にsky引きをするHDU, 第2引数のポインタにskyの標準偏差を書き込みます。
sky level、skyの標準偏差は次のようにして求めます。
  1. 画像の平均値を出す(画像のほとんどがskyなのでこれが大体のskylevel)
  2. 画像の平均値から2σ以上離れているピクセルを除外(天体を除外)
  3. 1, 2を5回繰り返す
  4. 残りの平均値をsky levelとする
  5. 残りの標準偏差をskyの標準偏差とする
画像の平均値 clipped meanもありますが、 何らかの方法で場所ごとのmodeを出して、それをスカイレベルとする アプローチも多いですので一応念頭に置いておくとよいと思います。
--- code/detection-1.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-2.cc	2015-11-16 14:39:41.000000000 +0900
@@ -7,6 +7,21 @@
 
 
 static void subtract_sky(fits_image &hdu, double *sky_stddev) {
+    mdarray_float data = hdu.float_array();
+    const int iter_n = 5;
+    const double sigma = 2.0;
+    for (int i = 0;  i < iter_n;  i++) {
+        double mean = md_mean(data),
+               stddev = md_stddev(data);
+        for (unsigned y = 0;  y < data.length(1);  y++) {
+            for (unsigned x = 0;  x < data.length(0);  x++) {
+                if ((data(x, y) - mean) / stddev > sigma)
+                    data(x, y) = NAN;
+            }
+        }
+    }
+    hdu.float_array() -= md_mean(data);
+    *sky_stddev = md_stddev(data);
 }
 
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    fits.write_stream(argv[2]);
    return 0;
}
download
できたらds9で開いてsky領域のカウントが0付近になっていることを確認して下さい。
View / {Horizontal, Virtical} Layoutがわかりやすい(順子さん)
s++ detection.cc -lsfitsio / data/corr.fits data/detection.fits
skylevel = 0.0 +/- 84.312809
ds9 data/corr.fits data/detection.fits
  1. メニューから「View/Horizontal Graph」を選ぶ
  2. マウスカーソルを星でない空の領域に合わせds9のValue欄を確認する

マーク

次はある閾値以上のカウントをもったピクセルにマークします(印を付けます)。 マークは画像とは別のHDUに保存することにしましょう。

マスク

今日の講習で今まで扱ってきたFITSはどれもHDUの数は1つだけでした(fits.image(0L))。 そこでマークの情報を保存するHDUを2つ目に置くことにし、 1つ目のHDUを画像HDU、2つ目のHDUをマスクHDUと呼ぶことにします。
コード上でのアクセス 呼び名 データの型 役割
fits.image(0L) 画像HDU FITS::FLOAT_T 画像のピクセル値を保存
fits.image(1L) マスクHDU FITS::BYTE_T 画像のピクセルについてのマークを保存
マスクHDUのデータ型はFITS::BYTE_Tで対応するCの型はunsigned charです。 1つのピクセルに対するデータ長は8bit(1byte)で、その8bitの各bitに次のように名前を割り当てます。(各名前の意味は後々説明します。)
ビット番号 名前
0 DETECTED
1 SOURCE
2 SATURATED
3 COSMICRAY
4 - 7 特に決めていない
※ ビット番号は1の位から0始まりで数えています

例えばあるピクセルのマスク値が6だったときのマスク値の意味は次のように考えます。
  1. 6(10)は2進数で00000110(2)
  2. 1になっているビットは1番目と2番目
  3. SOURCESATURATEDのマークがついている
次のように定数を定義してあれば
#ifndef _NAOJ_SEMINAR_MASK_INCLUDE_
#define _NAOJ_SEMINAR_MASK_INCLUDE_

enum {
    DETECTED  = 1, // 2**0
    SOURCE    = 2, // 2**1
    SATURATED = 4, // 2**2
    COSMICRAY = 8  // 2**3
};

#endif
download
あるピクセルにDETECTEDマークがついているかは次のように判定できます。
mdarray_uchar &mask = fits.image(1L).uchar_array();

if (mask(x, y) & DETECTED) {
    // ピクセルx, yがDETECTEDにマークされている
}
else {
    // ピクセルx, yがDETECTEDにマークされていない
}
download
またあるピクセルのDETECTEDに対応するビットを1または0にするには次のようにします。
// DETECTED bit を1にする
mask(x, y) |= DETECTED;

// DETECTED bit を0にする
mask(x, y) &= ~DETECTED;
download
ビット演算については↓が参考になります。
ビット演算子 - 演算子 - C言語 入門

実装

では、マスクを使ってある閾値以上の値をDETECTEDでマークする部分を実装しましょう。 まずは以下の内容でmask.hを作って下さい。
#ifndef _NAOJ_SEMINAR_MASK_INCLUDE_
#define _NAOJ_SEMINAR_MASK_INCLUDE_

enum {
    DETECTED  = 1, // 2**0
    SOURCE    = 2, // 2**1
    SATURATED = 4, // 2**2
    COSMICRAY = 8  // 2**3
};

#endif
download
次にdetection.ccに書き足してここまですすめて下さい。
--- code/detection-2.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-3.cc	2015-11-16 14:39:41.000000000 +0900
@@ -1,6 +1,7 @@
 #include <sli/fitscc.h>
 #include <sli/mdarray_statistics.h>
 #include <stdlib.h>
+#include "mask.h"
 
 
 using namespace sli;
@@ -25,6 +26,18 @@
 }
 
 
+static void mark_detected(fitscc &fits, double sky_stddev) {
+    mdarray_float &data = fits.image(0L).float_array();
+
+    if (fits.length() < 2)
+        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));
+
+    mdarray_uchar &mask = fits.image(1L).uchar_array();
+
+    // ある閾値より高い値をもつピクセルをDETECTEDにマークするようここに実装
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
@@ -38,6 +51,7 @@
     fits.read_stream(argv[1]);
     subtract_sky(fits.image(0L), &sky_stddev);
     sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
+    mark_detected(fits, sky_stddev);
     fits.write_stream(argv[2]);
     return 0;
 }
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    // ある閾値より高い値をもつピクセルをDETECTEDにマークするようここに実装
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    fits.write_stream(argv[2]);
    return 0;
}
download
mark_detected()は第1引数のfitsにHDUが2個以上なければ型がFITS::BYTE_Tで寸法が画像HDUと同じHDUを追加する所まで出来ています。

課題4

画像HDUのピクセル値がsky_stddevの3倍以上のピクセルにDETECTEDマークをつけるよう実装しdetection.ccのmark_detected()を完成させて下さい。
--- code/detection-3.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-4.cc	2015-11-16 14:39:41.000000000 +0900
@@ -27,6 +27,8 @@
 
 
 static void mark_detected(fitscc &fits, double sky_stddev) {
+    const double threshold = 3.0;
+
     mdarray_float &data = fits.image(0L).float_array();
 
     if (fits.length() < 2)
@@ -34,7 +36,12 @@
 
     mdarray_uchar &mask = fits.image(1L).uchar_array();
 
-    // ある閾値より高い値をもつピクセルをDETECTEDにマークするようここに実装
+    for (unsigned y = 0;  y < data.length(1);  y++) {
+        for (unsigned x = 0 ;  x < data.length(0);  x++) {
+            if (data(x, y) > sky_stddev * threshold)
+                mask(x, y) |= DETECTED;
+        }
+    }
 }
 
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    fits.write_stream(argv[2]);
    return 0;
}
download
できたらマスクの様子を確認してみましょう。 今回はds9ではなく./data/ds9maskコマンドを使います。

ds9maskコマンドはds9上で2番目のHDUの情報を半透明にオーバーレイするプログラムです。
# ds9mask.cppのコンパイル
s++ data/ds9mask.cpp -lsfitsio
# detection.ccのコンパイル & 実行
s++ detection.cc -lsfitsio / data/corr.fits data/detected.fits
./data/ds9mask data/detected.fits

青い領域がDETECTEDのマークがついている所
今回はマスク値がDETECTEDしかなかったので色がついている所は青1色ですが他のマスク値があれば別の色でオーバーレイされます。

※ マスクの透明度は次のようにして変更できます。
  1. メニューの「Analysis/Mask Parameters」を選ぶ
  2. Transparencyを変更
ds9maskのbit番号と色の関係は次のようになっています。
	"BLUE",       // 1   detected
	"RED",        // 2   source
	"GREEN",      // 3   saturated
	"CYAN",       // 4   cosmic-ray
	"BLUE",       // 5
	"RED",        // 6
	"GREEN"       // 7
download

ピックアップ

明るいピクセルにDETECTEDのマークが出来たら次はそのDETECTEDマークがついているピクセルのうち、ある大きさ以上連続している領域を探してきます。

連続した領域を探すのにはどうしたらいいでしょうか? 下の図のようなちょっと複雑な形をした領域を例に連続するピクセルをピックアップする方法を見ていきましょう。 ピックアップしたピクセルはそのピクセル座標を可変長の配列に追加していくことで記録していきます。

赤丸で囲われている部分がそれぞれ連続したDETECTED領域

複雑な形をした領域の例
マス目の中の数字は配列に追加された順番です。
  1. まずマークのついたあるピクセルをピックアップ
  2. 周囲の8ピクセルがマークされているかチェックし、チェックされていればそれらをピックアップ
  3. 配列の2番目の要素(4,8)の周囲8ピクセルをチェックし、チェックされていればそれらをピックアップ
  4. 配列の3番目の要素(5,8)の周囲8ピクセルをチェックし、チェックされていればそれらをピックアップ
  5. 配列の4番目の要素(3,7)の周囲8ピクセルを...これを配列の終わりまで繰り返す
  6. おわり

流れ 1.

流れ 2.

流れ 3.

流れ 4.

流れ 6.
ちょっとややこしいかもしれませんが、これをコードにすると次のようになります。
--- code/detection-4.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-5.cc	2015-11-16 14:39:41.000000000 +0900
@@ -1,6 +1,7 @@
 #include <sli/fitscc.h>
 #include <sli/mdarray_statistics.h>
 #include <stdlib.h>
+#include <vector> // 可変長配列
 #include "mask.h"
 
 
@@ -45,6 +46,46 @@
 }
 
 
+typedef struct {
+    int x;
+    int y;
+} point_t;
+
+
+static void pickup_connecting_pixels(fitscc &fits) {
+    mdarray_uchar mask = fits.image(1L).uchar_array();
+
+    for (unsigned y = 0;  y < mask.length(1);  y++) {
+        for (unsigned x = 0;  x < mask.length(0);  x++) {
+            if (mask(x, y) & DETECTED) {
+                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
+                point_t p;
+                p.x = x;
+                p.y = y;
+                pixels.push_back(p);             // 最初のピクセルをピックアップ
+                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
+                for (unsigned done = 0;  done < pixels.size();  done++) {
+                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
+                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
+                            int xxx = pixels[done].x + xx,
+                                yyy = pixels[done].y + yy;
+                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
+                                point_t p;
+                                p.x = xxx;
+                                p.y = yyy;
+                                pixels.push_back(p);                // ピックアップ
+                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
+                            }
+                        }
+                    }
+                }
+                sli__eprintf("area = %d pixels\n", pixels.size()); // 領域のピクセル数を表示
+            }
+        }
+    }
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
@@ -59,6 +100,7 @@
     subtract_sky(fits.image(0L), &sky_stddev);
     sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
     mark_detected(fits, sky_stddev);
+    pickup_connecting_pixels(fits);
     fits.write_stream(argv[2]);
     return 0;
 }
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void pickup_connecting_pixels(fitscc &fits) {
    mdarray_uchar mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                sli__eprintf("area = %d pixels\n", pixels.size()); // 領域のピクセル数を表示
            }
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download

コンパイル & 実行し連続したピクセルの数が出力されるのを確認して下さい。
s++ detection.cc -lsfitsio / data/corr.fits data/detected.fits

課題5

pickup_connecting_pixels()を変更して、連続したDETECTED領域が20ピクセル以上だったらその領域にSOURCEのマークをして下さい。
--- code/detection-5.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-6.cc	2015-11-16 14:39:41.000000000 +0900
@@ -53,7 +53,9 @@
 
 
 static void pickup_connecting_pixels(fitscc &fits) {
-    mdarray_uchar mask = fits.image(1L).uchar_array();
+    const unsigned min_area = 20;
+    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
+    mdarray_uchar mask = mask_ref;
 
     for (unsigned y = 0;  y < mask.length(1);  y++) {
         for (unsigned x = 0;  x < mask.length(0);  x++) {
@@ -79,7 +81,10 @@
                         }
                     }
                 }
-                sli__eprintf("area = %d pixels\n", pixels.size()); // 領域のピクセル数を表示
+                if (pixels.size() >= min_area) {
+                    for (unsigned i = 0;  i < pixels.size();  i++)
+                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
+                }
             }
         }
     }
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                }
            }
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
できたらds9maskでSOURCEの領域を確認してみて下さい。
s++ detection.cc -lsfitsio / data/corr.fits data/detected.fits
./data/ds9mask data/detected.fits

20pixel以上の領域はDETECTEDSOURCEのマークが
それより小さい領域はDETECTEDのマークのみついている
これで画像のどの領域を1つの天体として測ればいいかわかりました。

計測

これで画像のどの領域を1つの天体として測ればいいかわかりました。 では、1つの天体として考えられるピクセル列からfluxと重心を求めましょう。 $j$番目の天体のflux $f_j$と重心$\vec{c}_j$の定義は次のようになります。 \begin{eqnarray} f_j &=& \sum _{(x, y) \in S_j} I(x, y) \\ \vec{c}_j &=& \frac 1 {f_j} \sum _{(x, y) \in S_j} I(x, y) \left( \begin{array}{c} x \\ y \end{array} \right) \end{eqnarray}
$\vec {c}_j$のcはcentroidのc
$S_j$は$j$番目の天体の領域($j$番目のSOURCEにマークされた領域)です。 detection.ccに書き足して次の所まですすめて下さい。
--- code/detection-6.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-7.cc	2015-11-16 14:39:41.000000000 +0900
@@ -52,8 +52,14 @@
 } point_t;
 
 
+
+static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
+}
+
+
 static void pickup_connecting_pixels(fitscc &fits) {
     const unsigned min_area = 20;
+    mdarray_float &data = fits.image(0L).float_array();
     mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
     mdarray_uchar mask = mask_ref;
 
@@ -84,6 +90,7 @@
                 if (pixels.size() >= min_area) {
                     for (unsigned i = 0;  i < pixels.size();  i++)
                         mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
+                    measure(pixels, data);
                 }
             }
         }
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;



static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download

課題6

measure()を次のように実装して下さい。
  1. 第1引数のpixelsの最小最大のx, yを計算し次の変数に保存する
  2. min_x, max_x, min_y, max_yで決まる長方形領域を$S_j$としflux, 重心を計算する
  3. flux, 重心を次のフォーマットで標準出力に書き出す
    printf("% e % e % e\n", cx, cy, flux);
    cx, cyは重心のx座標、y座標
--- code/detection-7.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-8.cc	2015-11-16 14:39:41.000000000 +0900
@@ -2,6 +2,8 @@
 #include <sli/mdarray_statistics.h>
 #include <stdlib.h>
 #include <vector> // 可変長配列
+#include <stdio.h>
+#include <assert.h>
 #include "mask.h"
 
 
@@ -52,8 +54,37 @@
 } point_t;
 
 
+static void containing_box(const std::vector<point_t> &pixels, int *min_x, int *max_x, int *min_y, int *max_y) {
+    assert(pixels.size() > 0); // assertの括弧内の条件が満たされていなければその場で終了する
+    *min_x = *max_x = pixels[0].x;
+    *min_y = *max_y = pixels[0].y;
+    for (unsigned i = 0;  i < pixels.size();  i++) {
+        if (*min_x > pixels[i].x) *min_x = pixels[i].x;
+        if (*max_x < pixels[i].x) *max_x = pixels[i].x;
+        if (*min_y > pixels[i].y) *min_y = pixels[i].y;
+        if (*max_y < pixels[i].y) *max_y = pixels[i].y;
+    }
+}
+
 
 static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
+    // pixelsを含む長方形領域を計算
+    int min_x, max_x, min_y, max_y;
+    containing_box(pixels, &min_x, &max_x, &min_y, &max_y);
+
+    // centroidを計算
+    double cx = 0., cy = 0., flux = 0.;
+    for (int x = min_x;  x <= max_x;  x++) {
+        for (int y = min_y;  y <= max_y;  y++) {
+            cx += x * data(x, y);
+            cy += y * data(x, y);
+            flux += data(x, y);
+        }
+    }
+    cx /= flux;
+    cy /= flux;
+
+    printf("% e % e % e\n", cx, cy, flux);
 }
 
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include <stdio.h>
#include <assert.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void containing_box(const std::vector<point_t> &pixels, int *min_x, int *max_x, int *min_y, int *max_y) {
    assert(pixels.size() > 0); // assertの括弧内の条件が満たされていなければその場で終了する
    *min_x = *max_x = pixels[0].x;
    *min_y = *max_y = pixels[0].y;
    for (unsigned i = 0;  i < pixels.size();  i++) {
        if (*min_x > pixels[i].x) *min_x = pixels[i].x;
        if (*max_x < pixels[i].x) *max_x = pixels[i].x;
        if (*min_y > pixels[i].y) *min_y = pixels[i].y;
        if (*max_y < pixels[i].y) *max_y = pixels[i].y;
    }
}


static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
    // pixelsを含む長方形領域を計算
    int min_x, max_x, min_y, max_y;
    containing_box(pixels, &min_x, &max_x, &min_y, &max_y);

    // centroidを計算
    double cx = 0., cy = 0., flux = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            cx += x * data(x, y);
            cy += y * data(x, y);
            flux += data(x, y);
        }
    }
    cx /= flux;
    cy /= flux;

    printf("% e % e % e\n", cx, cy, flux);
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
C++の標準ライブラリには要素の大小関係を与えることで配列の最大値、最小値を探してきてくれる物があります。 それを使っても楽です。
--- code/detection-7.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-8-2.cc	2015-11-16 14:39:41.000000000 +0900
@@ -2,6 +2,8 @@
 #include <sli/mdarray_statistics.h>
 #include <stdlib.h>
 #include <vector> // 可変長配列
+#include <algorithm>
+#include <stdio.h>
 #include "mask.h"
 
 
@@ -51,9 +53,30 @@
     int y;
 } point_t;
 
+bool point_compare_x(const point_t &a, const point_t &b) { return a.x < b.x; }
+bool point_compare_y(const point_t &a, const point_t &b) { return a.y < b.y; }
 
 
 static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
+    // http://en.cppreference.com/w/cpp/algorithm/max_element
+    int min_x = std::min_element(pixels.begin(), pixels.end(), point_compare_x)->x,
+        max_x = std::max_element(pixels.begin(), pixels.end(), point_compare_x)->x,
+        min_y = std::min_element(pixels.begin(), pixels.end(), point_compare_y)->y,
+        max_y = std::max_element(pixels.begin(), pixels.end(), point_compare_y)->y;
+
+    // centroidを計算
+    double cx = 0., cy = 0., flux = 0.;
+    for (int x = min_x;  x <= max_x;  x++) {
+        for (int y = min_y;  y <= max_y;  y++) {
+            cx += x * data(x, y);
+            cy += y * data(x, y);
+            flux += data(x, y);
+        }
+    }
+    cx /= flux;
+    cy /= flux;
+
+    printf("% e % e % e\n", cx, cy, flux);
 }
 
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include <algorithm>
#include <stdio.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;

bool point_compare_x(const point_t &a, const point_t &b) { return a.x < b.x; }
bool point_compare_y(const point_t &a, const point_t &b) { return a.y < b.y; }


static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
    // http://en.cppreference.com/w/cpp/algorithm/max_element
    int min_x = std::min_element(pixels.begin(), pixels.end(), point_compare_x)->x,
        max_x = std::max_element(pixels.begin(), pixels.end(), point_compare_x)->x,
        min_y = std::min_element(pixels.begin(), pixels.end(), point_compare_y)->y,
        max_y = std::max_element(pixels.begin(), pixels.end(), point_compare_y)->y;

    // centroidを計算
    double cx = 0., cy = 0., flux = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            cx += x * data(x, y);
            cy += y * data(x, y);
            flux += data(x, y);
        }
    }
    cx /= flux;
    cy /= flux;

    printf("% e % e % e\n", cx, cy, flux);
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
標準ライブラリの話は C++マニアック,STL の使い方,how to use STL,標準テンプレートライブラリ,standard template library,コンテナ、container,イテレータ,iterator,アルゴリズム,algorithm,C++入門,C++言語講座 等がわかりやすいかと思います。
できたらds9上で画像に天体の重心をオーバープロットしてみましょう。
# コンパイル & 実行して結果をcentroid.txtに書き込み
s++ detection.cc -lsfitsio / data/corr.fits data/detected.fits > centroid.txt
./data/ds9mask data/detected.fits
# plot-centroidはコンパイルの必要はありません
./data/plot-centroid < centroid.txt
円の大きさは固定20pixelです。
ds9のマスクを消したい場合は次のようにします。
  1. メニューの「Analysis/Mask Parameters...」を選ぶ
  2. Clearボタンを押す

plot-centroidの実行結果

楕円形状計測

これが一応最後の節です。 下の図の$a, b, \theta$を測りましょう。

a, b, θ
http://www.kaggle.com/c/mdm/details/ellipticity より
$a, b$は天体が楕円gaussianだとしたときの$\sigma$です。 $a, b, \theta$は次の3つの2次モーメントから計算できます。 \begin{eqnarray} Q_{xx} &=& \frac 1 f \sum_{(x,y)\in S} (c_x - x)^2 I(x, y) \\ Q_{yy} &=& \frac 1 f \sum_{(x,y)\in S} (c_y - y)^2 I(x, y) \\ Q_{xy} &=& \frac 1 f \sum_{(x,y)\in S} (c_x - x)(c_y - y) I(x, y) \end{eqnarray} $f, c_x, c_y$はflux、重心のx座標、重心のy座標です。 これらを使って$a, b, \theta$は次のように書けます。 $$ \begin{eqnarray} \cos 2 \theta &=& Q_{xx} - Q_{yy} \\ \sin 2 \theta &=& 2 Q_{xy} \\ t &=& \sqrt{(Q_{xx} - Q_{yy})^2 + 4 Q_{xy}^2} \\ a &=& \sqrt{\frac 1 2 \left(Q_{xx} + Q_{yy} + t\right)} \\ b &=& \sqrt{\frac 1 2 \left(Q_{xx} + Q_{yy} - t\right)} \end{eqnarray} $$ 詳しくは http://www.kaggle.com/c/mdm/details/ellipticity などを見て下さい。
ちなみに半値全幅(FWHM)と$\sigma$は次の関係があります。 $$ \mathrm{FWHM} = 2 \sqrt{2\ln 2} \sigma \approx 2.354820 \sigma $$

課題7

measure()に2次モーメントを測り、それから$a, b, \theta$を計算する処理を実装して下さい。 また$a, b, \theta$を次のように書き出して下さい。
printf("% e % e % e %e % e % e\n", cx, cy, flux, a, b, theta);
thetaは$\theta$です。 $\theta$はラジアンで書き出して下さい。
--- code/detection-8.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-9.cc	2015-11-16 14:39:41.000000000 +0900
@@ -4,6 +4,7 @@
 #include <vector> // 可変長配列
 #include <stdio.h>
 #include <assert.h>
+#include <math.h>
 #include "mask.h"
 
 
@@ -84,7 +85,30 @@
     cx /= flux;
     cy /= flux;
 
-    printf("% e % e % e\n", cx, cy, flux);
+    // 2次モーメント
+    double xx = 0., yy = 0., xy = 0.;
+    for (int x = min_x;  x <= max_x;  x++) {
+        for (int y = min_y;  y <= max_y;  y++) {
+            xx += (cx - x)*(cx - x) * data(x, y);
+            yy += (cy - y)*(cy - y) * data(x, y);
+            xy += (cx - x)*(cy - y) * data(x, y);
+        }
+    }
+    xx /= flux;
+    yy /= flux;
+    xy /= flux;
+
+    // a, b, thetaを計算
+    double theta = atan2(2.0 * xy, xx - yy) / 2.0,
+           t = sqrt((xx - yy)*(xx - yy) + 4 * xy*xy),
+           a2 = (xx + yy + t) / 2.,
+           b2 = (xx + yy - t) / 2.;
+    if (b2 >= 0.) {
+        // dataが負になることがあるので2次モーメントも負になることがある
+        double a = sqrt(a2),
+               b = sqrt(b2);
+        printf("% e % e % e % e % e % e\n", cx, cy, flux, a, b, theta);
+    }
 }
 
 
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void containing_box(const std::vector<point_t> &pixels, int *min_x, int *max_x, int *min_y, int *max_y) {
    assert(pixels.size() > 0); // assertの括弧内の条件が満たされていなければその場で終了する
    *min_x = *max_x = pixels[0].x;
    *min_y = *max_y = pixels[0].y;
    for (unsigned i = 0;  i < pixels.size();  i++) {
        if (*min_x > pixels[i].x) *min_x = pixels[i].x;
        if (*max_x < pixels[i].x) *max_x = pixels[i].x;
        if (*min_y > pixels[i].y) *min_y = pixels[i].y;
        if (*max_y < pixels[i].y) *max_y = pixels[i].y;
    }
}


static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
    // pixelsを含む長方形領域を計算
    int min_x, max_x, min_y, max_y;
    containing_box(pixels, &min_x, &max_x, &min_y, &max_y);

    // centroidを計算
    double cx = 0., cy = 0., flux = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            cx += x * data(x, y);
            cy += y * data(x, y);
            flux += data(x, y);
        }
    }
    cx /= flux;
    cy /= flux;

    // 2次モーメント
    double xx = 0., yy = 0., xy = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            xx += (cx - x)*(cx - x) * data(x, y);
            yy += (cy - y)*(cy - y) * data(x, y);
            xy += (cx - x)*(cy - y) * data(x, y);
        }
    }
    xx /= flux;
    yy /= flux;
    xy /= flux;

    // a, b, thetaを計算
    double theta = atan2(2.0 * xy, xx - yy) / 2.0,
           t = sqrt((xx - yy)*(xx - yy) + 4 * xy*xy),
           a2 = (xx + yy + t) / 2.,
           b2 = (xx + yy - t) / 2.;
    if (b2 >= 0.) {
        // dataが負になることがあるので2次モーメントも負になることがある
        double a = sqrt(a2),
               b = sqrt(b2);
        printf("% e % e % e % e % e % e\n", cx, cy, flux, a, b, theta);
    }
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
できたらまたds9上でオーバープロットしてみましょう。
s++ detection.cc -lsfitsio / data/corr.fits data/detected.fits > ellip.txt
./data/ds9mask data/detected.fits
./data/plot-ellip < ellip.txt
plot-ellipは長軸、短軸、角度が$2a, 2b, \theta$の楕円を描いています。

plot-ellipの結果
これでようやく最初の画像が出てきました。
次の画像のようにまれに(と言うほどまれでもないですが)2つの天体が1つの天体として検出されてしまうことがあります。
これの対処が https://www.astromatic.net/pubsvn/software/sextractor/trunk/doc/sextractor.pdf のDeblendingの章にあります。
「接続の安全性を確認できません」と出ますが「危険性を理解したうえで接続するには」→「例外を追加」とクリックしていけば見られます。

2つの天体が1つの天体として測定されている
この講習はこれで終わりです。
もうちょっとなにか。挨拶的な

お疲れ様でした。

おまけ

最後に今日ここまでやってきたことを元に出来る少し発展的な話題を書いておきます。

ブルーミング潰し

下の画像のように明るい星の上下が白飛びしてしまうことをブルーミングと言います。 この領域の情報は残っていないので元に戻すことは出来ませんが左右のピクセルから補完することは出来ます。

ブルーミングの例
まずはdebais.ccを修正しバイアス補正後の生データが50000カウント以上の部分をSATURATEDでマークします。
--- code/debias-5.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/debias-6.cc	2015-11-16 14:39:41.000000000 +0900
@@ -1,6 +1,7 @@
 #include <sli/fitscc.h>
 #include <sli/mdarray_statistics.h>
 #include <stdlib.h>
+#include "mask.h"
 
 using namespace sli;
 
@@ -8,6 +9,21 @@
 static const int amp_n = 4; // アンプの数
 
 
+static void flag_saturated(fits_image &image_hdu, fits_image &mask_hdu) {
+    const int sat_level = 50000;
+    image_hdu.convert_type(FITS::FLOAT_T);
+    mdarray_float &image_data = image_hdu.float_array();
+    mdarray_uchar &mask_data = mask_hdu.uchar_array();
+
+    for (unsigned y = 0;  y < image_data.length(1);  y++) {
+        for (unsigned x = 0;  x < image_data.length(0);  x++) {
+            if (image_data(x, y) > sat_level)
+                mask_data(x, y) |= SATURATED;
+        }
+    }
+}
+
+
 static void debias(fits_image &hdu)
 {
     hdu.convert_type(FITS::FLOAT_T);
@@ -64,6 +80,9 @@
     fits.read_stream(argv[1]);
     debias(fits.image(0L));
     drop_overscan(fits.image(0L));
+    if (fits.length() < 2)
+        fits.append_image("Mask", 0, FITS::BYTE_T, fits.image(0L).length(0), fits.image(0L).length(1));
+    flag_saturated(fits.image(0L), fits.image(1L));
     fits.write_stream(argv[2]);
 
     return 0;
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include "mask.h"

using namespace sli;


static const int amp_n = 4; // アンプの数


static void flag_saturated(fits_image &image_hdu, fits_image &mask_hdu) {
    const int sat_level = 50000;
    image_hdu.convert_type(FITS::FLOAT_T);
    mdarray_float &image_data = image_hdu.float_array();
    mdarray_uchar &mask_data = mask_hdu.uchar_array();

    for (unsigned y = 0;  y < image_data.length(1);  y++) {
        for (unsigned x = 0;  x < image_data.length(0);  x++) {
            if (image_data(x, y) > sat_level)
                mask_data(x, y) |= SATURATED;
        }
    }
}


static void debias(fits_image &hdu)
{
    hdu.convert_type(FITS::FLOAT_T);
    mdarray_float &data = hdu.float_array();
    for (int amp = 1;  amp <= amp_n;  amp++) {
        const int os_min_x = hdu.headerf("S_OSMN%d1", amp).lvalue(),
                  os_max_x = hdu.headerf("S_OSMX%d1", amp).lvalue(),
                  ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
                  ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue();
        sli__eprintf(
            "amp = %d    os_min_x = % 5d    os_max_x = % 5d    ef_min_x = % 5d    ef_max_x = % 5d\n",
            amp, os_min_x, os_max_x, ef_min_x, ef_max_x);
        mdarray_float overscan_region, mean_x;
        overscan_region = data.section(os_min_x - 1, os_max_x - os_min_x + 1);
        mean_x = md_mean_x(overscan_region);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (int x = ef_min_x - 1;  x < ef_max_x;  x++)
                data(x, y) -= mean_x(0, y);
        }
    }
}


static void drop_overscan(fits_image &hdu) {
    mdarray_float &data = hdu.float_array();

    // y方向で切り詰め
    const int ef_min_y = hdu.headerf("S_EFMN12").lvalue(),
              ef_max_y = hdu.headerf("S_EFMX12").lvalue();
    data.crop(1, ef_min_y - 1, ef_max_y - ef_min_y + 1);

    // x方向で切り詰め
    int filled_width = 0;
    for (int amp = 1;  amp <= amp_n;  amp++) {
       const int ef_min_x = hdu.headerf("S_EFMN%d1", amp).lvalue(),
                 ef_max_x = hdu.headerf("S_EFMX%d1", amp).lvalue(),
                 width = ef_max_x - ef_min_x + 1;

        data.move(ef_min_x - 1, width, filled_width, false);
        filled_width += width;
    }
    data.crop(0, 0, filled_width);
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc fits;
    fits.read_stream(argv[1]);
    debias(fits.image(0L));
    drop_overscan(fits.image(0L));
    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, fits.image(0L).length(0), fits.image(0L).length(1));
    flag_saturated(fits.image(0L), fits.image(1L));
    fits.write_stream(argv[2]);

    return 0;
}
download
flat_saturated じゃなくて他と合わせるなら mark_saturated
マスクの様子を確認してみます。
s++ debias.cc -lsfitsio / data/object.fits data/debiased-object.fits
./data/ds9mask data/debiased-object.fits

緑の領域がSATURATEDのマークがついている所
ファイル名はrepair_saturatedではなくrepair-saturatedにする
次はSATURATEDにマークされている領域を左右のピクセルで補完するプログラムrepair_saturated.ccを次の内容で作成します。
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include "mask.h"

using namespace sli;


static void repair_saturated(fits_image &image_hdu, const fits_image &mask_hdu) {
    const int extend = 1;

    mdarray_float &data = image_hdu.float_array();
    const mdarray_uchar &mask = mask_hdu.uchar_array();
    const int w = data.length(0),
              h = data.length(1);

    for (int y = 0;  y < h;  y++) for (int x = 0;  x < w;  x++) {
        if (mask(x, y) & SATURATED) {
            int start = x, end;
            for (end = start;  end < w && mask(end, y) & SATURATED;)
                end++;
            //       SATURATED
            // +-----xxxxxxxx---------+
            // |     |       |        |
            // 0   start    end     w - 1
            end--;
            start -= extend;
            end   += extend;
            double val_left  = data(start - 1, y),
                   val_right = data(end + 1, y);
            for (x = start; x <= end;  x++) {
                double t = (double)(x - start) / (end - start);
                data(x, y) = val_right * t + val_left * (1. - t);
            }
            x = end;
        }
    }
}


int main(int argc, char *argv[]) {
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    fitscc fits;
    fits.read_stream(argv[1]);
    repair_saturated(fits.image(0L), fits.image(1L));
    fits.write_stream(argv[2]);

    return 0;
}
download
感度補正後にrepair_saturatedを実行し補完した画像を確認してみましょう。
# data/corr.fitsにSATURATEDマークを伝搬させるためにflat-fieldをやり直し
./flat-field data/debiased-object.fits data/master-flat.fits data/corr.fits
s++ repair_saturated.cc -lsfitsio / data/corr.fits data/repair-saturated.fits
./data/ds9mask data/repair-saturated.fits

補完前

補完後の同じ領域

cosmic-ray defect潰し

cosmic-ray defectとは宇宙線由来の下の画像のような鋭くカウントが上がっている部分です。 これもブルーミングと同じく元に戻すことは出来ませんが、周囲のピクセルから補完することは出来ます。

cosmic-ray defectの例
これはちょっと長いので簡単に・・・

median_spatial_filter spatial_median_filterどっちがいい?
まず補助関数としてmedian_spatial_filter(), max_spatial_filter(), min_spatial_filter()を作ります。 これらは周囲数ピクセル四方の中央値、最大値、最小値を返します。

元画像

max_spatial_filter適用後
--- code/detection-9.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-10.cc	2015-11-16 14:39:41.000000000 +0900
@@ -153,6 +153,51 @@
 }
 
 
+// OPTIMIZE : もっと省メモリに 
+static mdarray_float median_spatial_filter(const mdarray_float &data, int s) {
+    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
+    int z = 0;
+    for (int x = -s;  x <= s;  x++) {
+        for (int y = -s;  y <= s;  y++) {
+            stack.paste(data, x, y, z);
+            z++;
+        }
+    }
+    stack = md_median_small_z(stack);
+    return stack;
+}
+
+
+// OPTIMIZE : もっと省メモリに 
+static mdarray_float max_spatial_filter(const mdarray_float &data, int s) {
+    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
+    int z = 0;
+    for (int x = -s;  x <= s;  x++) {
+        for (int y = -s;  y <= s;  y++) {
+            stack.paste(data, x, y, z);
+            z++;
+        }
+    }
+    stack = md_max_small_z(stack);
+    return stack;
+}
+
+
+// OPTIMIZE : もっと省メモリに 
+static mdarray_float min_spatial_filter(const mdarray_float &data, int s) {
+    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
+    int z = 0;
+    for (int x = -s;  x <= s;  x++) {
+        for (int y = -s;  y <= s;  y++) {
+            stack.paste(data, x, y, z);
+            z++;
+        }
+    }
+    stack = md_min_small_z(stack);
+    return stack;
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void containing_box(const std::vector<point_t> &pixels, int *min_x, int *max_x, int *min_y, int *max_y) {
    assert(pixels.size() > 0); // assertの括弧内の条件が満たされていなければその場で終了する
    *min_x = *max_x = pixels[0].x;
    *min_y = *max_y = pixels[0].y;
    for (unsigned i = 0;  i < pixels.size();  i++) {
        if (*min_x > pixels[i].x) *min_x = pixels[i].x;
        if (*max_x < pixels[i].x) *max_x = pixels[i].x;
        if (*min_y > pixels[i].y) *min_y = pixels[i].y;
        if (*max_y < pixels[i].y) *max_y = pixels[i].y;
    }
}


static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
    // pixelsを含む長方形領域を計算
    int min_x, max_x, min_y, max_y;
    containing_box(pixels, &min_x, &max_x, &min_y, &max_y);

    // centroidを計算
    double cx = 0., cy = 0., flux = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            cx += x * data(x, y);
            cy += y * data(x, y);
            flux += data(x, y);
        }
    }
    cx /= flux;
    cy /= flux;

    // 2次モーメント
    double xx = 0., yy = 0., xy = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            xx += (cx - x)*(cx - x) * data(x, y);
            yy += (cy - y)*(cy - y) * data(x, y);
            xy += (cx - x)*(cy - y) * data(x, y);
        }
    }
    xx /= flux;
    yy /= flux;
    xy /= flux;

    // a, b, thetaを計算
    double theta = atan2(2.0 * xy, xx - yy) / 2.0,
           t = sqrt((xx - yy)*(xx - yy) + 4 * xy*xy),
           a2 = (xx + yy + t) / 2.,
           b2 = (xx + yy - t) / 2.;
    if (b2 >= 0.) {
        // dataが負になることがあるので2次モーメントも負になることがある
        double a = sqrt(a2),
               b = sqrt(b2);
        printf("% e % e % e % e % e % e\n", cx, cy, flux, a, b, theta);
    }
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float median_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_median_small_z(stack);
    return stack;
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float max_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_max_small_z(stack);
    return stack;
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float min_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_min_small_z(stack);
    return stack;
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
ここの空間フィルタの実装は極めメモリ効率が悪いものです。
気になる人は直して下さい。
天体に落ちたcosmic-rayを検出するのは難しいので、ここではskyに落ちたcosmic-rayの検出を目標にします。

cosmic-rayが次のような特徴を持っていることを利用してなんとかcosmic-rayにマークします。
  1. cosmic-rayは [元データ] - [median_spatial_filter] で際立つ
  2. cosmic-rayはmin_spatial_filterで消える
--- code/detection-10.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-11.cc	2015-11-16 14:39:41.000000000 +0900
@@ -198,6 +198,32 @@
 }
 
 
+static void flag_cosmicray(fits_image &image_hdu, fits_image &mask_hdu, double sky_stddev) {
+    const double threshold = 0.5;
+    mdarray_float &data = image_hdu.float_array();
+    mdarray_uchar &mask = mask_hdu.uchar_array();
+
+    mdarray_float sp_min, d_sp_median;
+    sp_min = min_spatial_filter(data, 1);
+
+    d_sp_median = data;
+    for (int i = 0;  i < 3;  i++)
+        d_sp_median -= median_spatial_filter(d_sp_median, 3);
+
+    d_sp_median = max_spatial_filter(d_sp_median, 1);
+
+    double stddev = md_stddev(d_sp_median),
+           mean   = md_mean(d_sp_median);
+
+    for (unsigned y = 0;  y < data.length(1);  y++) {
+        for (unsigned x = 0;  x < data.length(0);  x++) {
+            if ((d_sp_median(x, y) - mean) > threshold * stddev && sp_min(x, y) < sky_stddev)
+                mask(x, y) |= COSMICRAY;
+        }
+    }
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
@@ -211,6 +237,7 @@
     fits.read_stream(argv[1]);
     subtract_sky(fits.image(0L), &sky_stddev);
     sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
+    flag_cosmicray(fits.image(0L), fits.image(1L), sky_stddev);
     mark_detected(fits, sky_stddev);
     pickup_connecting_pixels(fits);
     fits.write_stream(argv[2]);
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void containing_box(const std::vector<point_t> &pixels, int *min_x, int *max_x, int *min_y, int *max_y) {
    assert(pixels.size() > 0); // assertの括弧内の条件が満たされていなければその場で終了する
    *min_x = *max_x = pixels[0].x;
    *min_y = *max_y = pixels[0].y;
    for (unsigned i = 0;  i < pixels.size();  i++) {
        if (*min_x > pixels[i].x) *min_x = pixels[i].x;
        if (*max_x < pixels[i].x) *max_x = pixels[i].x;
        if (*min_y > pixels[i].y) *min_y = pixels[i].y;
        if (*max_y < pixels[i].y) *max_y = pixels[i].y;
    }
}


static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
    // pixelsを含む長方形領域を計算
    int min_x, max_x, min_y, max_y;
    containing_box(pixels, &min_x, &max_x, &min_y, &max_y);

    // centroidを計算
    double cx = 0., cy = 0., flux = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            cx += x * data(x, y);
            cy += y * data(x, y);
            flux += data(x, y);
        }
    }
    cx /= flux;
    cy /= flux;

    // 2次モーメント
    double xx = 0., yy = 0., xy = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            xx += (cx - x)*(cx - x) * data(x, y);
            yy += (cy - y)*(cy - y) * data(x, y);
            xy += (cx - x)*(cy - y) * data(x, y);
        }
    }
    xx /= flux;
    yy /= flux;
    xy /= flux;

    // a, b, thetaを計算
    double theta = atan2(2.0 * xy, xx - yy) / 2.0,
           t = sqrt((xx - yy)*(xx - yy) + 4 * xy*xy),
           a2 = (xx + yy + t) / 2.,
           b2 = (xx + yy - t) / 2.;
    if (b2 >= 0.) {
        // dataが負になることがあるので2次モーメントも負になることがある
        double a = sqrt(a2),
               b = sqrt(b2);
        printf("% e % e % e % e % e % e\n", cx, cy, flux, a, b, theta);
    }
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float median_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_median_small_z(stack);
    return stack;
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float max_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_max_small_z(stack);
    return stack;
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float min_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_min_small_z(stack);
    return stack;
}


static void flag_cosmicray(fits_image &image_hdu, fits_image &mask_hdu, double sky_stddev) {
    const double threshold = 0.5;
    mdarray_float &data = image_hdu.float_array();
    mdarray_uchar &mask = mask_hdu.uchar_array();

    mdarray_float sp_min, d_sp_median;
    sp_min = min_spatial_filter(data, 1);

    d_sp_median = data;
    for (int i = 0;  i < 3;  i++)
        d_sp_median -= median_spatial_filter(d_sp_median, 3);

    d_sp_median = max_spatial_filter(d_sp_median, 1);

    double stddev = md_stddev(d_sp_median),
           mean   = md_mean(d_sp_median);

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0;  x < data.length(0);  x++) {
            if ((d_sp_median(x, y) - mean) > threshold * stddev && sp_min(x, y) < sky_stddev)
                mask(x, y) |= COSMICRAY;
        }
    }
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    flag_cosmicray(fits.image(0L), fits.image(1L), sky_stddev);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
関数名はflag_cosmicray → mark_cosmicray
ここまで出来たら、問題なくcosmic-rayがマークされているか確認して下さい。
s++ detection.cc -lsfitsio / data/repair-saturated.fits data/detected.fits
./data/ds9mask data/detected.fits

元画像

青緑の領域がCOSMICRAYにマークされている所
cosmic-rayの検出が出来たらその領域は周囲のピクセルの中央値で埋めてしまいましょう。
--- code/detection-11.cc	2015-11-16 14:39:41.000000000 +0900
+++ code/detection-12.cc	2015-11-16 14:39:41.000000000 +0900
@@ -224,6 +224,35 @@
 }
 
 
+static void repair_cosmicray(fitscc &fits) {
+    mdarray_float &data = fits.image(0L).float_array();
+    mdarray_uchar &mask = fits.image(1L).uchar_array();
+
+    for (unsigned y = 0;  y < mask.length(1);  y++) {
+        for (unsigned x = 0;  x < mask.length(0);  x++) {
+            if (mask(x, y) & COSMICRAY)
+                data(x, y) = NAN;
+        }
+    }
+
+    int nan_count;
+    do {
+        nan_count = 0;
+        mdarray_float median;
+        median = median_spatial_filter(data, 2);
+        for (unsigned y = 0;  y < data.length(1);  y++) {
+            for (unsigned x = 0;  x < data.length(0);  x++) {
+                if (isnan(data(x, y))) {
+                    nan_count++;
+                    data(x, y) = median(x, y);
+                }
+            }
+        }
+        sli__eprintf("repairing: %d NAN pixesl\n", nan_count);
+    } while(nan_count > 0);
+}
+
+
 int main(int argc, char *argv[])
 {
     if (argc != 3) {
@@ -238,6 +267,7 @@
     subtract_sky(fits.image(0L), &sky_stddev);
     sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
     flag_cosmicray(fits.image(0L), fits.image(1L), sky_stddev);
+    repair_cosmicray(fits);
     mark_detected(fits, sky_stddev);
     pickup_connecting_pixels(fits);
     fits.write_stream(argv[2]);
#include <sli/fitscc.h>
#include <sli/mdarray_statistics.h>
#include <stdlib.h>
#include <vector> // 可変長配列
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "mask.h"


using namespace sli;


static void subtract_sky(fits_image &hdu, double *sky_stddev) {
    mdarray_float data = hdu.float_array();
    const int iter_n = 5;
    const double sigma = 2.0;
    for (int i = 0;  i < iter_n;  i++) {
        double mean = md_mean(data),
               stddev = md_stddev(data);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if ((data(x, y) - mean) / stddev > sigma)
                    data(x, y) = NAN;
            }
        }
    }
    hdu.float_array() -= md_mean(data);
    *sky_stddev = md_stddev(data);
}


static void mark_detected(fitscc &fits, double sky_stddev) {
    const double threshold = 3.0;

    mdarray_float &data = fits.image(0L).float_array();

    if (fits.length() < 2)
        fits.append_image("Mask", 0, FITS::BYTE_T, data.length(0), data.length(1));

    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0 ;  x < data.length(0);  x++) {
            if (data(x, y) > sky_stddev * threshold)
                mask(x, y) |= DETECTED;
        }
    }
}


typedef struct {
    int x;
    int y;
} point_t;


static void containing_box(const std::vector<point_t> &pixels, int *min_x, int *max_x, int *min_y, int *max_y) {
    assert(pixels.size() > 0); // assertの括弧内の条件が満たされていなければその場で終了する
    *min_x = *max_x = pixels[0].x;
    *min_y = *max_y = pixels[0].y;
    for (unsigned i = 0;  i < pixels.size();  i++) {
        if (*min_x > pixels[i].x) *min_x = pixels[i].x;
        if (*max_x < pixels[i].x) *max_x = pixels[i].x;
        if (*min_y > pixels[i].y) *min_y = pixels[i].y;
        if (*max_y < pixels[i].y) *max_y = pixels[i].y;
    }
}


static void measure(const std::vector<point_t> &pixels, const mdarray_float &data) {
    // pixelsを含む長方形領域を計算
    int min_x, max_x, min_y, max_y;
    containing_box(pixels, &min_x, &max_x, &min_y, &max_y);

    // centroidを計算
    double cx = 0., cy = 0., flux = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            cx += x * data(x, y);
            cy += y * data(x, y);
            flux += data(x, y);
        }
    }
    cx /= flux;
    cy /= flux;

    // 2次モーメント
    double xx = 0., yy = 0., xy = 0.;
    for (int x = min_x;  x <= max_x;  x++) {
        for (int y = min_y;  y <= max_y;  y++) {
            xx += (cx - x)*(cx - x) * data(x, y);
            yy += (cy - y)*(cy - y) * data(x, y);
            xy += (cx - x)*(cy - y) * data(x, y);
        }
    }
    xx /= flux;
    yy /= flux;
    xy /= flux;

    // a, b, thetaを計算
    double theta = atan2(2.0 * xy, xx - yy) / 2.0,
           t = sqrt((xx - yy)*(xx - yy) + 4 * xy*xy),
           a2 = (xx + yy + t) / 2.,
           b2 = (xx + yy - t) / 2.;
    if (b2 >= 0.) {
        // dataが負になることがあるので2次モーメントも負になることがある
        double a = sqrt(a2),
               b = sqrt(b2);
        printf("% e % e % e % e % e % e\n", cx, cy, flux, a, b, theta);
    }
}


static void pickup_connecting_pixels(fitscc &fits) {
    const unsigned min_area = 20;
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask_ref = fits.image(1L).uchar_array();
    mdarray_uchar mask = mask_ref;

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & DETECTED) {
                std::vector<point_t> pixels;     // ピクセル座標を記録する可変長配列
                point_t p;
                p.x = x;
                p.y = y;
                pixels.push_back(p);             // 最初のピクセルをピックアップ
                mask(x, y) &= ~DETECTED;         // チェック済みのピクセルは DETECTED bit を下げる
                for (unsigned done = 0;  done < pixels.size();  done++) {
                    for (int xx = -1;  xx <= 1;  xx++) {            // この2行のループで
                        for (int yy = -1;  yy <= 1;  yy++) {        // 周囲9ピクセル(自分含む)の走査
                            int xxx = pixels[done].x + xx,
                                yyy = pixels[done].y + yy;
                            if (mask(xxx, yyy) & DETECTED) {        // 周囲のピクセルが DETECTED なら
                                point_t p;
                                p.x = xxx;
                                p.y = yyy;
                                pixels.push_back(p);                // ピックアップ
                                mask(xxx, yyy) &= ~DETECTED;        // チェック済みのピクセルは DETECTED bit を下げる
                            }
                        }
                    }
                }
                if (pixels.size() >= min_area) {
                    for (unsigned i = 0;  i < pixels.size();  i++)
                        mask_ref(pixels[i].x, pixels[i].y) |= SOURCE;
                    measure(pixels, data);
                }
            }
        }
    }
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float median_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_median_small_z(stack);
    return stack;
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float max_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_max_small_z(stack);
    return stack;
}


// OPTIMIZE : もっと省メモリに 
static mdarray_float min_spatial_filter(const mdarray_float &data, int s) {
    mdarray_float stack(false, data.length(0), data.length(1), (2*s + 1) * (2*s + 1));
    int z = 0;
    for (int x = -s;  x <= s;  x++) {
        for (int y = -s;  y <= s;  y++) {
            stack.paste(data, x, y, z);
            z++;
        }
    }
    stack = md_min_small_z(stack);
    return stack;
}


static void flag_cosmicray(fits_image &image_hdu, fits_image &mask_hdu, double sky_stddev) {
    const double threshold = 0.5;
    mdarray_float &data = image_hdu.float_array();
    mdarray_uchar &mask = mask_hdu.uchar_array();

    mdarray_float sp_min, d_sp_median;
    sp_min = min_spatial_filter(data, 1);

    d_sp_median = data;
    for (int i = 0;  i < 3;  i++)
        d_sp_median -= median_spatial_filter(d_sp_median, 3);

    d_sp_median = max_spatial_filter(d_sp_median, 1);

    double stddev = md_stddev(d_sp_median),
           mean   = md_mean(d_sp_median);

    for (unsigned y = 0;  y < data.length(1);  y++) {
        for (unsigned x = 0;  x < data.length(0);  x++) {
            if ((d_sp_median(x, y) - mean) > threshold * stddev && sp_min(x, y) < sky_stddev)
                mask(x, y) |= COSMICRAY;
        }
    }
}


static void repair_cosmicray(fitscc &fits) {
    mdarray_float &data = fits.image(0L).float_array();
    mdarray_uchar &mask = fits.image(1L).uchar_array();

    for (unsigned y = 0;  y < mask.length(1);  y++) {
        for (unsigned x = 0;  x < mask.length(0);  x++) {
            if (mask(x, y) & COSMICRAY)
                data(x, y) = NAN;
        }
    }

    int nan_count;
    do {
        nan_count = 0;
        mdarray_float median;
        median = median_spatial_filter(data, 2);
        for (unsigned y = 0;  y < data.length(1);  y++) {
            for (unsigned x = 0;  x < data.length(0);  x++) {
                if (isnan(data(x, y))) {
                    nan_count++;
                    data(x, y) = median(x, y);
                }
            }
        }
        sli__eprintf("repairing: %d NAN pixesl\n", nan_count);
    } while(nan_count > 0);
}


int main(int argc, char *argv[])
{
    if (argc != 3) {
        sli__eprintf("usage: %s INPUT OUTPUT\n", argv[0]);
        exit(1);
    }

    double sky_stddev = 0.0; // 警告抑止のため0.0を代入

    fitscc fits;
    fits.read_stream(argv[1]);
    subtract_sky(fits.image(0L), &sky_stddev);
    sli__eprintf("skylevel = 0.0 +/- %f\n", sky_stddev);
    flag_cosmicray(fits.image(0L), fits.image(1L), sky_stddev);
    repair_cosmicray(fits);
    mark_detected(fits, sky_stddev);
    pickup_connecting_pixels(fits);
    fits.write_stream(argv[2]);
    return 0;
}
download
これでcosmic-ray defect潰しは終わりです。 補完された画像がどうなっているか確認して下さい。
s++ detection.cc -lsfitsio / data/repair-saturated.fits data/detected.fits
./data/ds9mask data/detected.fits

元画像

きれいになりました
cosmic-ray defect潰しの詳しいことは http://www.astro.yale.edu/dokkum/lacosmic/ などを見て下さい。
※ この講習とは別のアプローチで実装されています。

星銀河分類

今回解析した画像には銀河と星、両方が写っています。 それらを選り分けるにはどうしたらよいでしょうか?

測定した天体の大きさ(測定の所で得た$\sqrt{a^2+b^2}$)とfluxの関係をプロットすると下の図のようになります。

青緑の縁で囲まれているのが星

$\sqrt{a^2+b^2}$ vs. flux
図の青い四角の領域にうっすらと縦のシーケンスが見えるような気がしませんか? これが星なのです。

星の光は点源なので、すべての星は大気のゆらぎの影響で共通の広がりを持ってCCD上に写ります。 したがって、楕円gaussianだと思って測った星の大きさは明るさによらないのです。
黄色の四角の中は星がsaturateしてしまいfluxが低く測定されたため大きめに計算されてしまったのです。
大気のせいで一定に広がるというのは言った方がよいかも。 ちなみに宇宙望遠鏡の場合も点にはならず、地上ほどではないが、 装置(光学系と検出器)起因で多少広がります。

saturateしている星のカウント

きれいに測定できた場合

課題8

  • 星のシーケンスがまっすぐ縦に現れない理由を考え、それがプログラムの修正で解決できそうなら実装を修正して下さい。
  • シーケンスがまっすぐ現れたらそのシーケンスを捕まえるプログラムを作成して下さい
  • 参考