この人頭いいなと思ったエピソード

医療画像(DICOM)のバイナリデータを読み込んで表示させるプログラムを作っているのですが、非常に遅い(約4秒)ので改善したいのです。

おそらく、ループ中での、エンディアンの変換とPixelへの張付けが原因と思うのですが、改善方法が判りません。宜しくお願いします。

BolandC++Builder6,Pentiam4,1Gメモリ,XPの環境です。
DICOMO画像のファイルサイズは約2053kB
画像データは1024*1024の16ビットです。

__________________________________
Byte bb[2097152];
int iImage[512][512];
word c,wData;

fp=fopen("filename","rb");
setvbuf(fp,NULL,_IOFBF,4096*1024*1024);

while(gData!=0xE07F) //グループタグの検索//
fread(& gData,2,1,fp);
while(eData!=0x1000) //エレメントタグの検索//
freadd(& eData,2,1,fp);

fread(&wData,2,4,fp); //空読み//
fread(bb,1,2097152,fp); //画像データ//

fclose(fp);

for(y=0;y<=512;y++){
for(x=0;x<=512;x++){
wData=256*bb[4*x+4096*y]+bb[4*x+4096*y+1];
c=wData*256/4096;
iImage[x][y]=c;
Image->Canvas->Pixels[x][y]=(TColor)((c<<16)|(c<<8)|c);
c=0;
}
}

A 回答 (18件中1~10件)

原因は幾つか推測できるだろうと思います。


が、ここでやるべきは闇雲にここが怪しいからいじってみよう
という場当たり的な対処ではなく、
きちんとプロファイリングをして、どこがホットスポットなのかを
見極めることだと思います。

もっともC++ Builderだとまともに使えるプロファイラがあるのかどうか
わかりません(無責任ですみません)。
少なくとも無料で使えるものはないかも知れません。

この回答への補足

遅くなりましたがご意見を参考にしてclockで処理時間を計測しました
default 3.00s
配列を[y][x] 2.47s
#16#17 2.49s
Canvas->Pixel[x][y]を外し、新たな配列に入れた場合 0.062s
となりました。
Canvas->Pixel[x][y]の処理が遅い原因のようですが、ループ内ではBitmapの画素値の計算だけにして、ループ外でBItmapの配列を直接imageに貼付ける処理を試してようと思いますが、方法が判りません。 

補足日時:2007/05/22 10:57
    • good
    • 0
この回答へのお礼

その後、ダブルバッファの様な処理をさせたところ、0.4sと約7倍速くなりました。貴重なご意見ありがとうございました。

お礼日時:2007/05/22 13:20

┌色々検索していたら,面白いものを見つけました.


│DICOM ファイルフォーマットのわかりやすい説明があります.

DR研究会誌
pp.189~222 よくわかるDICOM画像の取得方法 ‐自作プログラムによる方法‐
http://www.kit.hi-ho.ne.jp/dr-study-group/naiyou …


これを読んで,いくつか疑問点が出てきました.

(1) #5,#14 のお礼欄に「うまくいかない」と書かれていますが,
  どう「うまくいかない」んでしょうか?
  ・画像の形はそれらしいが,階調がおかしい.
  ・似ても似つかない画像が表示される.

(2) ご質問は高速化方法となっていますが,元のプログラムでは
  ちゃんと画像が表示されていたのでしょうか?
  #14 のお礼欄に「表示だけなら … OK」とありますが,
  これは画像ファイルを読んで表示させたということでしょうか?

(3) #14 のお礼欄に「リトルエンディアンのはず」とあるので,
  ファイルのフォーマットはおそらく "Implicit VR Little Endian"
  とやらになるのだと思いますが,その場合グループタグや
  エレメントタグもリトルエンディアンなのでしょうか?

  もしそうだとしたら,グループタグとエレメントタグの検索部分
  には間違いがあります.グループタグは PIXEL_GROUP=0x7FE0,
  エレメントタグは PIXEL_DATA_ELEMENT=0x0010 となっているので,
  これをそのまま (エンディアン反転させずに) 検索すべきです.
  つまり 0xE07F → 0x7FE0,0x1000 → 0x0010 に訂正が必要.
  (#9 のお礼欄の URL の Fig.8 参照.)

(4) 上記 URL (DR研究会誌の方) の204~205ページによると,
  Implicit VR のヘッダは,次の4つの繰り返しだそうです.
  (a) Group Tag (2バイト)
  (b) Element Tag (2バイト)
  (c) Value Length (4バイト)
  (d) Data (Value Length バイト)

  ところで,ご質問のプログラムでは,エレメントヘッダを読んだ
  直後に8バイトの空読みをしています.wData が2バイトなのに
  8バイト読み込んでいるのでアクセス違反になるという問題も
  ありますが,それは置いといて….

  Implicit VR の Value Length は4バイトですが,8バイト読んで
  いるのは単なる4バイトの間違い? それとも8バイトが正しいと
  すると,Explicit VR フォーマットの "OB,OW,SQ,US の場合"
  というのに相当しているんでしょうか?

(5) 画像データより前の Data 部にバイナリデータが入ることは
  ないんでしょうか? もしあるとすると,その中にたまたま
  グループタグやエレメントタグと同じ2バイトがあった場合に
  「偽物」をつかまされてしまいます.

(6) Value Length の値は奇数になることはないんでしょうか?
  グループタグとエレメントタグの検索は2バイト単位で読んで
  いるので,奇数バイトの Data があると検索に失敗する可能性
  があります.

画像ファイル内にある,画像データの本体の開始位置前後数十バイト
の16進ダンプを見せていただけると,色々わかるかもしれません.

この回答への補足

お礼の書き込み訂正します。Wordで持ってきて
E07F,1000,574F,0000,2000,0000,......................です。

補足日時:2007/05/21 11:03
    • good
    • 0
この回答へのお礼

お世話になっております。
私の勘違いが判りました。各装置のデータはリトルエンディアンなのですが、画像サーバがビックエンディアンに変換してました。
(1)(3)(4)上記理由によりお騒がせいたしました。
ちなみに画像データ前の16進並びは
E0 7F 10 00 57(W) 4F(O) 00 00 20 00 00 00
画像が22 00 20 00............です。
(2)正常に表示できました。
(5)(6)そういう場合もあるかもしれませんが、CT,MRI,透視,カラー画像のDICOM変換の4つでは2byte区切りです。これらの画像に対応できれば当面は問題ありません。

お礼日時:2007/05/21 09:07

★あぁ、済みませんでした。


・訂正前⇒Word (*lpBuff)[ 1024 ] = (Word*)bb; ←これを宣言
 訂正後⇒Word (*lpBuff)[ 1024 ] = (Word(*)[1024])bb; ←これを宣言
 でした。
    • good
    • 0

★回答者 No.1 です。


・補足要求、有り難うございます。
 ようやく質問ソースを把握できましたので、繰り返し部分を中心にサンプルを載せます。

サンプル:
int iImage[ 512 ][ 512 ];
Byte bb[ 2 * 1024 * 1024 ];
Word (*lpBuff)[ 1024 ] = (Word*)bb; ←これを宣言
Word wData;

if ( (fp = fopen("filename","rb")) != NULL ){
 /*
 画像データを読み込む
 */
 fclose( fp );
}
for ( y = 0 ; y < 512 ; y++ ){
 for ( x = 0 ; x < 512 ; x++ ){
  wData = lpBuff[ y * 2 ][ x * 2 ] >> 4; ←2ピクセル毎に12Bit→8Bit(4096→256階調)
  iImage[ x ][ y ] = wData;
  Image->Canvas->Pixels[ x ][ y ] = (TColor)(wData * 0x010101); ←グレイスケール変換
 }
}

最後に:
・本当は iImage[x][y] は iImage[y][x] にすべきです。
 一般に C 言語で2次元配列は横方向を次元の低い方で表し、縦方向を高い次元で表します。
 多分、もうすべてを [x][y] と操作するように設計していると思うので今度、プログラムを
 行うときには気をつけて下さい。本当は [y][x] 操作に直すべきだと思っていますが…。
・以上。一通り動作するか、確認をお願いします。
    • good
    • 0
この回答へのお礼

Word (*lpBuff)[ 1024 ] = (Word*)bb; ←これを宣言
の部分で以下のエラーがでました。
[C++ エラー] E2034 'unsigned short *' 型は 'unsigned short ( *)[1024]' 型に変換できない

お礼日時:2007/05/19 01:20

>もし「1画素16ビットの1024×1024を、間引きして、


>8ビット256階調の512×512にする」んで正しければ、
>以下のようになる。
間引処理
    wData=*p++;
     ↓
    wData=*p;
    p+=2;
    • good
    • 0

Intel系のCPUを使用していて、画像データが16bitの数値なら


下位8bit, 上位8bitの順に格納されるので、
wData=256*bb[n]+bb[n+1];ではなく
wData=bb[n]+256*bb[n+1];となるのですが?

また
bb[4*x+4096*y]、bb[4*x+4096*y+1]としている所からすると
4バイト毎に頭2byteを取得するという事でしょうか?
4×8bit=32bit ≠ 16bit(?)

Intel系のCPUで画像データが2byte(16bit)の場合であれば
下記の様なコーディングも可能です。
short *p, c, wData;

p=(short *)bb;
for(y=0;y<512;y++){
  for(x=0;x<512;x++){
    wData=*p++;
    c=wData/16; // 256/4096 => 1/16
    iImage[y][x]=c; //?? [x][y]
    Image->Canvas->Pixels[y][x]=(TColor)((c<<16)|(c<<8)|c);//??
    c=0;
  }
}
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
ご指摘の通り、リトルエンディアンのはずなのですが、それだとうまくいかないのです。どこか2重に処理しつじつまが合っているのでしょうか?表示だけならCT装置と透視装置のDICOMデータで確認してOKなのです。
bb[4*x+4096*y]以下は元画像サイズ1024*1024を512*512にリサイズしています。

お礼日時:2007/05/19 00:19

>(2)『c = wData * 256 / 4096;』の部分は下位バイトの上位4ビットを


>  取り出して c に4ビットを代入するのですか。→c = ((wData << 8) >> 12); と考えた。

No9の考え通りに下位バイトの上位4bitを出すなら良く考えたらこうかも
c= (bb[4*x+4096*y]>>4) & 0xf;


---------------以下ただのメモ
wData=256*bb[4*x+4096*y]+bb[4*x+4096*y+1];
c=wData*256/4096;

c = (256*bb[4*x+4096*y]+bb[4*x+4096*y+1]) *256/4096;
c = (256*bb[4*x+4096*y] + bb[4*x+4096*y+1]) /16;
c = 16*bb[4*x+4096*y] + bb[4*x+4096*y+1]/16
あれ?合わないぞ?(゜∀。)
    • good
    • 0
この回答へのお礼

私の質問に付き合っていただきましてありがとうございます。
*256/4096の部分はデータの数値0から4096をRGB256階調に変換している部分です。
手元の本でRGBは256階調でとあったので。

お礼日時:2007/05/18 22:57

#3 です.



可能ならば,iImage[x][y] のxとyを入れ替えて iImage[y][x] のように
した方が,いくらか速くなる可能性はあると思います.

なぜならば,ご質問のプログラムの二重ループ部分がアクセスしている順番
どおりに iImage[][] の要素がメモリ上に並ぶことになり,キャッシュの効きが
良くなると考えられるからです.(bb[] の方は既にそうなっています.)

現在のままでは iImage[][] のあるメモリ領域はランダムアクセスされているので,
キャッシュはあまり効いていない (むしろキャッシュライン単位で読み書きするので
逆効果になっている) のではないかと思われます.

もっとも,iImage[][] のxとyを入れ替える場合には,#10 のコードも改造が必要です.
(もっと簡単になります.)
    • good
    • 0

★回答者 No.1、No.9 です。


・繰り返し文の最適化は、他の回答者さんのを参考にして下さい。
 私はヘッダ部についてアドバイスします。
 そう思いましたが質問のソースから考えると gData=0xE07F、eData=0x1000 が常に
 入ってしまいませんか?
・検索となっていますが、fp で続けて fread() 関数で読み込んでいますよね。
 検索後にデータを読み飛ばしているようでもないので、ここの部分が良く理解できません。
 アドバイスするにあたり、医療画像(DICOM)のバイナリデータの詳細を教えてくれませんか?
 また補足要求です。何度もすみません。
    • good
    • 0

#3 です.



#5 さんのコードを元に,iImage[x][y] の2次元アクセスをしないようにしたバージョンです.
でも,どれほど効果があるかはわかりません.やはり実行時間を測定しないと….
(Image->Canvas->Pixels[x][y] の2次元アクセスは残したままだし.)


 const Byte *src = bb;
 int *destLine = &iImage[0][0]; // iImage の走査線の左端 iImage[0][y] を指す.
 int *dest;
 int x, y;

 for(y = 0; y < 512; y++) {
  dest = destLine; // == &iImage[0][y]
  for(x = 0; x < 512; x++) {
   wData = (src[0] << 8) | src[1];
#if 1
   // こっちでいいのかな?
   c = wData >> 4;
#else /* 0/1 */
   // ダメなら元のまま.
   c = wData * 256 / 4096;
#endif /* 0/1 */
   *dest = c;
   Image->Canvas->Pixels[x][y] = (TColor)(c * 0x010101);
   src += 4;     // 1画素読み飛ばす.
   dest += 512;   // == &iImage[x + 1][y]
  }
  src += 2048;    // 1行読み飛ばす.
  destLine++;     // == &iImage[0][y + 1]
 }



> (fread() についても.)はどういう意味でしょうか?

fread(),fopen() などはディスク I/O を行うので,途中でエラーが起きる
可能性もあります.それにファイルが壊れていたり,間違ったファイルを
指定したりすると,途中で読み込むべきデータがなくなることもあります.

例えばご質問のプログラムでは,ファイル内にグループタグかエレメントタグの
どちらかがない場合,無限ループします.
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報