またまた、プログラムをいじっていてハマった点です。
ImageJのスクリプトで画像データをもとに白と黒の二値画像のマスクを作るプロセスがあります。具体的には次のような画像です。
上の画像から下の二値化画像を得るために、当初記録したマクロを元にPython化したコードを書いて実現していましたが、コードが数行にわたってしまいました。
それを getProcessor().threshold() を使うと1~2行で済みそうなので、それで次のようなコードを書いて書き換えました。
from ij import IJ, ImagePlus
imp.getProcessor().threshold(int(convValue(X, Mode)))
imp.updateAndDraw()
上のコードで impは対象となる画像 ImagePlus オブジェクトです。 convValue(X, Mode) というのは私が定義した関数で、Xの値を画像のモード (8bitか16bitか)に応じて適切な値に変換する関数です。具体的には8bitモードであれば、Xの値はそのまま、16bitモードならXの値に256を掛けるという動作をします。
getProcessor().threshold( ) の( )の中には、二値化する閾値を入れるのですが、画像が8bitなら0~255の値を代入し、16bitなら、それに256を掛けた値に変換して代入するというわけです。
ところが... 8bit画像ならうまく二値化された画像が出力されるのですが、16bit画像だと、真っ暗な画像になってしまいます。しかも妙なことに、IrfanVIewで見た時だけは正しく二値化されたデータが表示されますが、GIMPやPhotoshopでその画像を読み込むと真っ黒になってしまいます。
半日ほど首をひねって、はたと気づきました。閾値を指定する際、16bitモードの時はその値を256倍することは注意を払っていたのですが、getProcessor().threshold()が吐き出す結果はどうなのか、と。 getProcessor().threshold()は0と255に二値化しているわけですが、それが16bitモードであっても、相変わらず 0/255で二値化しているのではないかと... ビンゴでした!
結局、変換元画像が8bitだろうが16bitだろうが、0と255に二値化しているので、16bitで255だと、0とほぼ変わらず、ほぼ真っ黒という話だったのです。ただIrfanViewに読み込ませると、画像の最高値が低いので、その画像が8bitであると誤解して正しく結果が表示されていた (結構IrfanViewは仕様が緩いので... )、という訳でした。よくよく考えてみれば当然のことです。
という訳で次のようなコードを挿入することで解決できました。
imp.getProcessor().threshold(int(convValue(X, Mode)))
IJ.run(imp, "Multiply...", "value=" + str(convValue(1, Mode)))
imp.updateAndDraw()
つまり、オリジナル画像が16bitなら、一旦getProcessor().threshold(int(convValue(X, Mode))) で加工した画像をさらに256倍する、というコードを中間に挟んだのです。
これで落着です。
ちなみに ImageJのthreshold コマンドは、画像が8bitであることのみを前提とし、16bitその他は前提としていないようです。
ちなみにこれを試みる前は、impに、一旦イメージコンバータで8bitgクレースケールに変えて処理し ImageConverter(imp).convertToGray8() それから二値化するということも試みたのですが、この impがチャンネルスプリッタで分割した R 画像にイメージコンバータで16bitグレースケール変換した画像だったためか、それをさらに8bitグレースケールに変えるとなぜか画像が赤くなってしまいます。レッドのLookup Table (LUT) の色が保存されてしまっているのです。それをGIMPで読み込むと本来白い部分がうっすら灰色に... それでうまくいかないので上の方法に切り替えたのですが...
結局、論理的推論でこうなるはずだ、というのと、実際のプログラムの実装が、うまくその通りになってくれるとは限らないので試行錯誤することになります。
--------
※お断り
当記事は、ImageJ - Fijiディストリビューションの記事執筆時点での最新バージョンを前提とした記事です。ImageJのオリジナルバージョンだと、環境設定をご自分で整備しない限り、同様に動作しない可能性があります。