SLLIB + SFITSIO を使ったデータ解析講習会 [part3]
    
    目的、ゴール
    昨日までの講習
    でざっとSLLIB/SFITSIOの機能の紹介があったと思います。
    
    今日の講習では、実際にSLLIB/SFITSIOを使いデータ解析プログラムを作りSLLIB/SFITSIOの使い方を身につけていきましょう。
    ゴールは下の図左端の銀河の形状測定です。
    
      
        - C++では意外に短いコードでそこそこな物が作れるということを実感してもらう
- この講習ではおまけも含めてコードは全部で450行程度
- おまけ含めないと350行程度
- 
          sextractorなど他のパッケージの話題
          
            - 既に使えるパッケージがあるのにどうして自分で実装するの?
- ちょっとしたことがぱぱっと出来るようになっていると便利だよ
 
- 下に並んでいる図を生データから作るよ
- 入門的な位置づけなので測定の精度など改良の余地がある
      など文章にする
    
 
    
    
       
      
      天体の形状測定
    
 
    
       
      
      大きさ vs. 明るさ
      
      
        これはイマイチだけどここに載せる?
      
     
    
       
      
      cosmic-ray潰し前
    
 
    
       
      
      cosmic-ray潰し後
    
 
    
    本講習の内容
    
      - 
        
          - 一次処理概要(Suprime-Cam)
- CCDの生データを天体測定できる状態にする処理の説明です。
 
- 
        
          - 一次処理実習(Suprime-Cam)
- 実際にSFITSIOを使って一次処理するプログラムを作成します。
 
- 
        
          - 天体検出概要
- 一次処理されたデータから天体を検出するアルゴリズムを解説します。
 
- 
        
          - 天体検出実習
- 実際にSFITSIOを使って一次処理されたデータから天体を検出するプログラムを作成します。
 
- 
        
          - おまけ
- 時間が余ったらやります。cosmic-ray潰し、ブルーミング潰しなど
 
このチュートリアルに必要な物
    
      - sllib / sfitsio
- ds9
- xpa
- python >= 2.4
一次処理概要(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
    全体を適当なスケールで見るには次のようにして下さい。
    
      - メニューから「Zoom/Zoom to Fit Frame」を選ぶ
- メニューから「Scale/ZScale」を選ぶ (Scale/ZScale...ではない)
まず黒い帯で領域が4つに分割されていることに気付くと思います。
    この4つの領域は別々のアンプで読み出されていて、それぞれのアンプでバイアスやゲインが違うため領域間で段差が見えてます。
    また左上が少し暗くなっていますがこれは周辺減光によるものです。
    ヘッダーも確認してみましょう。
      - メニューから「File/Display Header...」を選ぶ
BITPIX=16, BZERO=32768.0, BSCALE=1.0からこのファイルのデータが16bit整数、レンジが0...65535ということがわかります。
    各ピクセルの値はそのピクセルにカーソルを合わせると、Value欄に表示されます。
      「物理値 = BZERO + BSCALE × 配列値」となります。(FITSの手引き 第5.3版 p.48)
    
    
    
       
      
      object.fits
    
 
    
    
       
      
      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で一次処理を行うプログラムを作成していきますが、まずは何を作るか列挙しておきます。
    
      - 
        単一のFITSからバイアスを引き算して書き出すプログラム
        
          - 
            次の様に実行します。
            ./debias INPUT OUTPUT 
- 
            $
            s'(x, y) = s(x, y) - B(x, y)
            $
          
- 
            $
            g'(x, y) = g(x, y) - B(x, y)
            $
          
- 
            
              
                | $s(x, y)$ | INPUT |  
                | $s'(x, y)$ | OUTPUT |  
 または
              
                | $g_i(x, y)$ | INPUT |  
                | $g_i'(x, y)$ | OUTPUT |  
 
 
- 
        複数枚のバイアス補正済みフラットの中央値のstackを作るプログラム
        
      
- 
        バイアス補正済みの解析対象データとマスターフラットを引数に感度補正をするプログラム
        
      
それでは、いよいよコーディングです。バイアス補正
    好きなエディタで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
    
      - メニューから「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
    
      - メニューから「Frame/Lock/Frame/Image」を選ぶ
- メニューから「Frame/Lock/Scale」を選ぶ
- メニューから「Frame/Lock/Colorbar」を選ぶ
       
      
      
        
          | debiased-flat-1.fits | debiased-flat-2.fits | 
        
          | master-flat.fits |  | 
      
      単一フラットでは写っていたcosmic-rayが
      master-flatでは消えている
    
 
    
    
    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$を測ります。
    
    
       
      
      検出結果
    
 
    
    
    大まかに手順を書くと次のようになります。
    
      - 
        
          - マーク
- 画像からある閾値以上の値をもったピクセルをマークする
 
- 
        
          - ピックアップ
- マークされたピクセルがある閾値以上連続している場所を探し、そこを天体だとする
 
- 
        
          - 計測
- 天体だとされた領域のピクセルの値からflux、重心などの量を測る
 
詳しくは実習で見ていきます。天体検出実習
    sky引き
    一次処理済みの画像をds9で開いて空の領域のカウントを見て下さい。
    ds9 data/corr.fits
    
      - マウスカーソルを星でない空の領域に合わせ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の標準偏差は次のようにして求めます。
    
      - 画像の平均値を出す(画像のほとんどがskyなのでこれが大体のskylevel)
- 画像の平均値から2σ以上離れているピクセルを除外(天体を除外)
- 1, 2を5回繰り返す
- 残りの平均値をsky levelとする
- 残りの標準偏差を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
    
      - メニューから「View/Horizontal Graph」を選ぶ
- マウスカーソルを星でない空の領域に合わせ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だったときのマスク値の意味は次のように考えます。
    
      - 6(10)は2進数で00000110(2)
- 1になっているビットは1番目と2番目
- SOURCEとSATURATEDのマークがついている
次のように定数を定義してあれば
      #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()を完成させて下さい。
    
      - mask(x, y)にDETECTEDのマークをつけるにはmask(x, y) |= 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色ですが他のマスク値があれば別の色でオーバーレイされます。
    
    
      ※ マスクの透明度は次のようにして変更できます。
    
    
      - メニューの「Analysis/Mask Parameters」を選ぶ
- 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領域
    
 
    
       
      
      複雑な形をした領域の例
    
 
    
    マス目の中の数字は配列に追加された順番です。
    
      - まずマークのついたあるピクセルをピックアップ
- 周囲の8ピクセルがマークされているかチェックし、チェックされていればそれらをピックアップ
- 配列の2番目の要素(4,8)の周囲8ピクセルをチェックし、チェックされていればそれらをピックアップ
- 配列の3番目の要素(5,8)の周囲8ピクセルをチェックし、チェックされていればそれらをピックアップ
- 配列の4番目の要素(3,7)の周囲8ピクセルを...これを配列の終わりまで繰り返す
- おわり
       
      
      流れ 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
     
    
      - 
        新しくpoint_tという構造体を定義しました。この構造体はピクセルの位置を表します。
      
- 
        std::vector<point_t>という型が出てきました。これはpoint_tの可変長配列です。
      
- 
        可変長配列とは長さが変わる配列です。std::vectorの簡単な使い方の例をみてみましょう。
        
          #include <vector>
int main(int argc, char *argv[]) {
    std::vector<int> a;
    printf("%d\n",  a.size());   // => 0
    a.push_back(10);             // 10をaの最後(といってもaは空)に追加
    printf("%d\n",  a.size());   // => 1
    printf("%d\n",  a[0]);       // => 10
    a.push_back(20);             // 20をaの最後に追加
    a.push_back(30);             // 30をaの最後に追加
    printf("%d\n",  a.size());   // => 3
    printf("%d\n",  a[0]);       // => 10
    printf("%d\n",  a[1]);       // => 20
    printf("%d\n",  a[2]);       // => 30
    return 0;
}download
 
コンパイル & 実行し連続したピクセルの数が出力されるのを確認して下さい。s++ detection.cc -lsfitsio / data/corr.fits data/detected.fits
    課題5
    pickup_connecting_pixels()を変更して、連続したDETECTED領域が20ピクセル以上だったらその領域にSOURCEのマークをして下さい。
    
      - 今のpickup_connecting_pixels()はmaskをfitsに紐づいた参照でなくコピーで持っていることに注意して下さい。
      
        --- 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以上の領域は
DETECTEDと
SOURCEのマークが
      
      それより小さい領域は
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引数のpixelsの最小最大のx, yを計算し次の変数に保存する
        
      
- min_x, max_x, min_y, max_yで決まる長方形領域を$S_j$としflux, 重心を計算する
- 
        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++言語講座
        等がわかりやすいかと思います。
      
# コンパイル & 実行して結果を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のマスクを消したい場合は次のようにします。
    
      - メニューの「Analysis/Mask Parameters...」を選ぶ
- Clearボタンを押す
       
      
      plot-centroidの実行結果
    
 
    
    楕円形状計測
    これが一応最後の節です。
    下の図の$a, b, \theta$を測りましょう。
    
    
    
    $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$はラジアンで書き出して下さい。
    
    
      - $\cos \theta = x, \sin \theta = y$のときの$\theta$はatan2(y, x)で求めることが出来ます。
      
        --- 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の結果
    
 
    
    これでようやく最初の画像が出てきました。
    
    この講習はこれで終わりです。
    もうちょっとなにか。挨拶的な
    
    お疲れ様でした。
    おまけ
    最後に今日ここまでやってきたことを元に出来る少し発展的な話題を書いておきます。
    
      - ブルーミング潰し
- cosmic-ray defect潰し
- 星銀河分類
ブルーミング潰し
    下の画像のように明るい星の上下が白飛びしてしまうことをブルーミングと言います。
    この領域の情報は残っていないので元に戻すことは出来ませんが左右のピクセルから補完することは出来ます。
    
    
       
      
      ブルーミングの例
    
 
    
    まずは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にマークします。
    
      - cosmic-rayは [元データ] - [median_spatial_filter] で際立つ
- 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
    
      
        星のシーケンスがまっすぐ縦に現れない理由を考え、それがプログラムの修正で解決できそうなら実装を修正して下さい。
      
      
        シーケンスがまっすぐ現れたらそのシーケンスを捕まえるプログラムを作成して下さい
      
    
    参考