2005/09/23 三次元の差分シミュレーション
まず、立体データのI/O。ImageJでもAmiraでも、Tiffで扱うのが楽そう。libtiffというライブラリがあったのでチェック。サンプルコードも入ったページが
にあった。
まず使うためのヘッダファイルを探すと、/sw/include/の下にあった。どうやらfinkで入れたアプリのどれかがlibtiffを使っているらしい。したがって
</sw/include/tiffio.h>
とするととりあえず読んでくれた。
前述の解説ベージはカラー画像のものだったので、モノクロ画像のものに着いても読む。しかし、めんどくさそう。複数枚の画像が入っているものに着いてはサポートしているのか?
それともう一つ本質的な問題として、今のところmain.cppだけに全部記述してしまうスタイルを取っているが、これではそのうち破綻するのではないか?タスクに分けて小さなプログラムの集積にした方が結局はいいのではないだろうか?
<iostream>と<iostream.h>
まじめにエラーメッセージを見て、
/usr/include/gcc/darwin/4.0/c++/backward/backward_warning.h:32:2: warning: #warning This file includes at least one deprecated or antiquated header. Please consider using one of the 32 headers found in section 17.4.1.2 of the C++ standard. Examples include substituting the <X> header for the <X.h> header for C++ includes, or <iostream> instead of the deprecated header <iostream.h>. To disable this warning use -Wno-deprecated.
ということだったので、<iostream.h>を<iostream>に変えてみたらやたらとエラーが出てきた。何だこりゃ。これについては
<iostream>と<iostream.h>の違い by Gimite
2004年5月3日 19:35
その昔、C++標準に仕様変更が有りました。その時に、
・標準ライブラリのクラス/関数/変数には全て、頭に std:: を付けるようにする。
・C++標準のヘッダ<〜.h>は、.hを取って<〜>とする。(例:<iostream>)
・C標準のヘッダ<〜.h>は、<c〜>とする。(例:<cstdio>)
という3つの事が決まりました。つまり新しい仕様でのHello worldは、
#include <iostream>
int main(){
std::cout << "Hello world." << std::endl;
return 0;
}
となります。でも、いちいち std:: を付けるのは面倒なので、 using namespace std; と書けば、それ以降は std:: を省略できます。
#include <iostream>
using namespace std;
int main(){
cout << "Hello world." << endl;
return 0;
}
昔のC++の入門書に書かれていた
#include <iostream.h>
int main(){
cout << "Hello world." << endl;
return 0;
}
は、今や「正しいC++のコードではない」ので、注意してください。といっても、互換性のために、ほとんどのコンパイラは今でもこのコードを通します。
だそうな。
オブジェクト指向にしてみる?
せっかくだし、3次元ボリュームをひとつオブジェクトにして、C++っぽく書き換えてみよう!
Volume.height
Volume.width
Volume.Depth
Volume->read(“fileName”)
Volume->write(“fileName”)
Volume->DiffuseDt()
というようなメンバー関数を作ってやって、mainで動かすと。
とりあえずデータの読み込み部分( Volume->write(“fileName”))だけ書いてやってみる。コンパイラは通るが、実行すると
ZeroLink: unknown symbol '_TIFFOpen'
というエラーが出て先に進まない。_TIFFOpen 自体はヘッダには書かれているが、適切にプログラム本体にリンクされていない感じ。ライブラリもincludeしないといけないのか?
ということで、プロジェクトウインドウに、/sw/lib/libtiff.3.6.1.dylibを入れたらあっさり通るようになった。
次に、データ読み込みのところで、
Either undefined or unsupported number of bits per sample
というエラーが出て止まっているようだ。これもよく見ると、サンプルコードがバイナリ画像(0か1のみ)しか対応しないものであったためであった。別のサンプルコードを探してきて貼付ける。
しかし、こちらも読み込み部分がうまく行かない。結局もとのサンプルに戻って、ゆっくり読んで32ビット実数(float型)用に作り替える。
buffer: char 型ポインタ
v: float 型ポインタ
なので、bufferでさすデータを取り出すのにはいろいろ工夫が居る。
v[count] = *((float *)(buffer+count*4));
(buffer+count*4)は、char型ポインタだが、さしているアドレスはv[count]となるべきところの頭。
(float *) で、char型ポインタをfloat型ポインタにキャストして変換する
()で、ポインタの中身を指すもの(float型の数値)に変換する。
という流れ。一枚目の読み込みはまあうまくいった。次は書き出し。
今度は何も書き込んでくれないように見える。デバッガを出してみたら、vが2種類できていて、値がメンバ関数の中だけで定義されるようになっている...
なんでかな、と思っていたが、結局メンバー関数内での定義のときに
float *v = new float[bufferSize];
となっていたのを
v = new float[bufferSize];
にしたら直った。要は、変数の型宣言を二度していたから別の変数と思われた、ということらしい。
次に、書き出しの関数がおかしいらしく、変な数値になってしまう。
まず、浮動小数点の仕組みから調べて、値自体がどうなっているか見たが、こちらは別に異常がなさそう。
つぎにExifTagInfoというフリーウェアでタグ情報を比較したところ、SampleFormatというタグがなかった。
TIFFSetField(output, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP);
という一文を入れたら直った。これで二次元はできた。
ちなみに、tiffのtagについての詳しい情報は
にあった。
さらに複数枚のデータの取り込みをするには?
まず、画像の枚数を確認する方法をチェックしたが、実はあまりいい方法がないらしい。面倒だからいったん全部数えて閉じることにする。
"tiffio.h"
main(int argc, char* argv[])
{
TIFF* tif = TIFFOpen(argv[1], "r");
if (tif) {
int dircount = 0;
do {
dircount++;
} while (TIFFReadDirectory(tif));
printf("%d directories in %s\n", dircount, argv[1]);
TIFFClose(tif);
}
exit(0);
}
TIFFSetDirectory(image, 0);
を使ったら別に閉じなくてもよいことがわかった。
あとは枚数を使って複数枚読み込むだけ。枚数は実際の枚数(スタックじゃないと1)が入る。
...しかし、前述の例だと読み込み部分がごちゃごちゃして面倒。もう一つのサンプルから取る。これで三次元のデータセットの取り扱いがだいぶ楽になった。
あとは拡散の実装。こちらは学校でやることにする。
最終更新:2007年01月19日 05:40