ImageJとpythonで粒子解析を自動化する
追記(2022/06/26)
自動化の難易度が高い画像を手動で分析する方法も投稿しました.
やったこと
ImageJを用いて粒子形状を自動で解析する初歩的な内容はこちらの方がまとめられている. ただ個人的には改善したい点もたくさんあって,
- もっとオブジェクト指向にしてコードの見通しを良くしたい
RoiManager
が表示されてると実行速度が絶望的に遅い- 同じく
IJ.run()
は裏でGUIを操作しているため速くない - 長さとか面積とかに単位が欲しい
ParticleAnalyzer
のオプションを色々弄りたい- 元画像のどこが粒子として認識されたか確認したい
とか色々ある.
このため前述の巨人の肩に乗りながら(コードを流用しながら),上記の要望を叶える方法を調べてコードを改変した.
なお少なくともImagePlus
とImageProcessor
の関係と使い方は理解していないと本コードを理解するのは苦労するため,
拙著を流し読みした方が良いかもしれない.
改変したpythonコード
import os from ij import IJ from ij.process import ImageProcessor from ij.plugin import Colors from ij.plugin.frame import RoiManager from ij.plugin.filter import ParticleAnalyzer, BackgroundSubtracter, EDM from ij.measure import ResultsTable, Measurements def main(imagefilepath): # 画像を開いてImagePlusを取得する imp = IJ.openImage(imagefilepath) # 1ピクセルが相当する実世界の長さを設定 # ImagePlusから該当するmetaデータを取り出して編集する cal = imp.getCalibration() cal.pixelHeight = 0.1 cal.pixelWidth = 0.1 cal.setUnit('um') # 忘れずにImagePlusに反映する cal.setImage(imp) # 背景を均一にする bs = BackgroundSubtracter() ip = imp.getProcessor() # 後でimp.setProcessor()すると戻せなくなるため # 無編集のデータも先に分けておく ip0 = imp.getProcessor().duplicate() # 背景の差引 # bs.rollingBallBackground(ColorProcessor, radius, createBackground, lightBackground, useParaboloid, doPresmooth, correctCorners) bs.rollingBallBackground(ip, 40.0, False, True, False, True, True) # 粒界を定める閾値を設定する # 第一引数で2値化のアルゴリズムを選択できる # ip.setAutoThreshold(method, darkBackground, lutUpdate) ip.setAutoThreshold('Default', False, ImageProcessor.NO_LUT_UPDATE) # 2値化した画像を取得 mask = ip.createMask() # 今回は粒界が離れているため省略 #EDM().toWatershed(mask) # 2値化していてもParticleAnalyzerのために閾値の設定は必要 mask.setAutoThreshold('Default', True, ImageProcessor.NO_LUT_UPDATE) # 粒子解析 # 粒子の輪郭をROIで保存する # 引数にTrueかFalseを与えるとウィンドウが表示されなくなって高速化できる # どちらを与えても同じで,どちらも与えないと表示される rm = RoiManager(False) # 解析結果の保存先 # こちらは何もしなくてもウィンドウは表示されない rt = ResultsTable() # 粒子解析する本体 # インスタンス化せずにクラスメソッドとして実行すると次のインスタンス化時に反映される ParticleAnalyzer.setRoiManager(rm) # インスタンス化 # 第一引数は解析方法の指定を行なっており2進数の和で表現されている # ただそれぞれ覚えていられないため次のようにクラス変数を参照する # 第二引数も同様に数値化する項目を指定できるが全部解析して後で省けばよし # 第4,5,6,7引数は解析対象とするそれぞれ最小面積,最大面積,最小円形度,最大円形度 # 工夫してノイズを除外すること pa = ParticleAnalyzer( ParticleAnalyzer.EXCLUDE_EDGE_PARTICLES + ParticleAnalyzer.INCLUDE_HOLES + ParticleAnalyzer.ADD_TO_MANAGER, Measurements.ALL_STATS, rt, 0., 1.e9, 0., 1, ) # 長さの情報をもつImagePlusと2値化画像のImageProcessorを渡す pa.analyze(imp, mask) # 保存 savefilepath = imagefilepath[:-4]+"_" rt.saveAs(savefilepath + "Results.csv") imp.setProcessor(mask) IJ.saveAs(imp, "bmp", savefilepath + "masks.bmp") rm.runCommand("Save", savefilepath + "Roi.zip") # 抽出した粒子の輪郭を描画する # 輪郭を黄色にする # 使いたい色の16進数表記を変換してColorsオブジェクトにする c = Colors().decode('#ffff00') # そのままだとグレースケールの画像になっている # そこに色を渡してもグレースケールに変換されてしまうため # 元の画像をRGBカラーに変換しておく ip = ip0.convertToColorProcessor() # やっと色を指定できる ip.setColor(c) # ROIそれぞれを描画するとImageProcessorに渡した色が使われる for roi in rm.getRoisAsArray(): ip.draw(roi) # ImagePlusに画像を反映して保存 imp.setProcessor(ip) IJ.saveAs(imp, "jpg", savefilepath + "AnalyzedResult.jpg") # close roi manager, results table and images rm.close() rt.reset() imp.close()
フォルダ内の画像に一括適用したいなら
この関数をos.walk
する.
ip.setAutoThreshold()
の第一引数は文字列で
他のアルゴリズムを与えられる.
解析した粒子を明示するのは重要で,自動化していると想定外な輪郭を取得される可能性もあり,それを簡単に確認できる必要がある.
初めて扱う種類の画像であればip.setAutoThreshold()
の第一引数を全て試して最適なアルゴリズムを選択するべきだろう.
なおRoiManager
を使っているとheadless
モードでエラーが出てRoiManager
のインスタンス化に失敗する.このため本コードはImageJのエディタから実行する必要がある.