省型旧形国電の残影を求めて

戦前型旧形国電および鉄道と変褪色フィルム写真を中心とした写真補正編集の話題を扱います。他のサイトでは得られない、筆者独自開発の写真補正ツールや補正技法についても情報提供しています。写真補正技法への質問はコメント欄へどうぞ

Imagej / Python Tips: ImageProcessorのbit深度を変更する際の注意

 ImageProcessorには以下の記事に書いたように、8bit、256種類の区分(0-255の整数)しかない ByteProcessor, 16bit、65536種類の区分(0-65535の整数)のある ShortProcessor、32bitフルに使う FloatProcessor、RGBを1チャンネル当たり8bit x 3で表し、24bit使う、ColorProcessorの4種類です。

yasuo-ssi.hatenablog.com

 ところで、ImageJの画像データのbit深度を変更する必要がある場合、ImageConverterというAPIを使います。これを使ったときのImageJの挙動について注意点です。

 ImageConverterを使ってbit深度を変更すると、たしかにImageProcessorのタイプは変更してくれます。しかし、値の扱いは要注意です。例えば、今、ShortProcessorの画像、imp があるとします。これをByteProcessorに変換するには、

ImageConverter(imp).convertToGray8()

という命令を使うと変換してくれます。この時、元々0-65535整数の値を取っていた画像は当然、0-255整数の値にダウンスケーリングして変換してくれます。しかし、bit深度をアップスケールする際は要注意で、ByteProcessorを、convertToGray16()、convertToGray32()メソッドを使い、ShortやFloatProcessorに変換したり、ShortProcessorをFloatProcessorに変換しても、自動的に値までアップスケーリングしてくれず、変換前の値そのままです。自分で256倍してやらなければなりません。

 なお、ImageConverterには、setDoScalingメソッドがあり、setDoScaling(True)を実行することで変更してくれるようですが...

 ところで、今まで全くFloatProcessorを使っておらず、今までその挙動を確認してきませんでした。ちょっと今FloatProcessorを使う必要を感じていろいろ確認中ですが、どうも、よく言えば非常に融通が利き、悪く言えば非常にぬえ的な挙動をします。取る値の範囲も決まっていないようで、0~1.0で運用することもできれば、0~16777215 で使う、という運用も可能なようです。さらに+-符号付での運用も可能なようです。そのためか、FloatProcessorに対してsetDoScalingメソッドを実行しても何も値が変わりません。

 さらに、ImageJ自体が、16bit 最大値65535までの表示にしか対応しておらず、FloatProcessorを0~16777215で運用すると表示はクリップして真っ白になってしまいます。しかしgetStatisticsなどで、値を調べてみると値はクリップしておらず、ちゃんと保持されているようです。

クリップしてほぼ真っ白になったFloatProcessor画像

 この0~16777215で作成した画像をTIFファイルに落とし、IrfanViewで読み込むと正常に表示されます。Informationを見てもオリジナルが32bit画像であると出ます。

上の画像を保存してIrfanViewで読み込んだところ

 さらにFloatProcessorを0.0~1.0で使ってみると、今度は画面が真っ黒です。小数点以下のピクセルの表示にも対応していません。

画面が真っ黒になったfloatProcessor画像

 しかし、これも一旦TIFFに保存してIrfanViewで開くと、正常に表示されます。やはりデータ自体はクリップしていません。

 なお、GIMPの場合は0.0~1.0では正しく読めますが、0~16777215では正しく読み込むことができませんでいた。

GIMPで 32bit 0~16777215画像を読み込んだところ

GIMPで32bit 0.0~1.0画像を読み込んだところ

 因みに、ImageProcessorに、setMinAndMax​(最小値, 最大値) という画像の明度の最大値、最小値を設定するメソッドがあるようです。ImageJでは、表示用に8bitのプレビュー画像を使っているので、このメソッドで値を指定すると、適切な表示用プレビュー画像を作るということです。未確認ですが、これを使うと32bit画像でも正しく表示できるのではないかと思います。

imagej.nih.gov

 なお、ImageJにおいて32bit画像を扱おうとすると、bio-formatプラグインが32bit カラー画像のインポートが現時点では不可能なので、32bit画像を扱える場面は限定されます。ただ画像計算処理のための中間保存のためのオブジェクトとしては十分に使えますし、おそらくfloatProcessorが設けられている主目的も、そのためにあるものと思われます。

 なお、ImageProcessorの挙動を検証するための簡単なPython (Jython) スクリプトを書いてみました。一応16bit画像を読むことを想定しています。bit深度を変更し、プリント文で、変換した画像の平均値、最大値、最小値を出力させています。もしよろしければ、ImageJのスクリプトエディタにコピペして、適宜変更してお使いください。

-------------------------------------

from ij import IJ, ImagePlus, CompositeImage
from ij import WindowManager as WM
from ij.io import DirectoryChooser, OpenDialog
from ij.plugin import Duplicator
from ij.process import ImageConverter
from ij import IJ
from loci.plugins import BF
from ij import plugin
 
# 1. Open an image  
Mode = "Go"
window = WM.getActiveWindow()
if not window:
    od = OpenDialog("Choose a file")
    folder = od.getDirectory()
    filename = od.getFileName()
    if filename:
        path = folder + filename
        IJ.log(path)
        IJ.log(folder)
        IJ.log(filename)
# Always Open file with Bio-format Plugin
        imps = BF.openImagePlus(folder + filename) # Open file with Bio-format Plugin
        imp0 = imps[0] #imp0: Original Image 
        imp0.show()
    else:
        Mode ="Stop"
else:
    imp0 = IJ.getImage()
    filename = imp0.getTitle()
    folder = imp0.getOriginalFileInfo().directory  # get image's original directory
    path = folder + filename
window = WM.getActiveWindow()
if not window: # if file opening is canceled 
    Mode = "Stop"
# 2. Convert images 
if Mode != "Stop":
    imp0_32 = Duplicator().run(imp0)
    ImageConverter(imp0_32).convertToGray32()
    imp0_8 = Duplicator().run(imp0)
    ImageConverter(imp0_8).convertToGray8()
    stat_imp0 = imp0_32.getStatistics()
    print "statistics org"
    print stat_imp0.mean
    print stat_imp0.max
    print stat_imp0.min
    
    # 以下適当にコマンドを選んでください
    IJ.run(imp0_32, "Divide...", "value=65535")
    #IJ.run(imp0_32, "Multiply...", "value=256")
    #ImageConverter(imp0_32).setDoScaling(True)
    #
    
# 3. Output statistics & show the images
    
    stat_imp0_32 = imp0_32.getStatistics()
    print "statistics32"
    print stat_imp0_32.mean
    print stat_imp0_32.max
    print stat_imp0_32.min
    imp0_32.show()
    stat_imp0_8 = imp0_8.getStatistics()
    print "statistics8"
    print stat_imp0_8.mean
    print stat_imp0_8.max
    print stat_imp0_8.min
    imp0_8.show()

----------------------------------