🐈

[Groovy][QuPath] 章8 QuPathスクリプト入門

2023/04/28に公開約18,600字

既にWorkflowからQuPathスクリプトの雰囲気は分かったと思うが、ここからは自身でスクリプトを改変、新規作成していく。

"プログラミング言語としての変数/オブジェクト"と"QuPathのオブジェクト (アノテーションなどのROIの総称)"で表現が重複しているので、前者を"オブジェクト", 後者を"Object"と記載することにする。また、"Java用語のクラス"と"QuPathのObject分類のために使うクラス"でも表現が重複しているので、前者を"クラス"、後者を"class"と記載することにする。

QuPathスクリプトではGroovy言語で記載するが、QuPathのソースコード自体はJava言語で書かれている。Javaのクラスは、プログラムを実行するためのオブジェクトやそれに関連したメソッドをまとめた大枠と考えてもらうとよい。文字列や数字は、java.lang.String, java.lang.IntegerのようなJavaクラスとして扱われている。

QuPathコマンドの出力の多くは、qupath.lib.images.ImageData, qupath.lib.objects.classes.PathClassのようなQuPath用に定義された特定のクラスのオブジェクトである。それらのクラスにどのような専用のメソッドが用意されているのかは調べながら進めていく。

Script Editor

まずは上部メニューのAutomate > Show script editorからScript Editorを立ち上げる。

QuPath Javadocs

Script Editor Help > Show Javadocsを押すと、QuPathのjavadocを開くことができる。

QuPath Javadocs

ここからQuPathコマンドの解説やQuPathで定義したJavaクラスの組み込みメソッドを確認することができる。

試しに、「自動化、バッチ処理」の章で登場したcreateAnnotationsFromPixelClassifierを見てみる。
コマンド例では引数にはどのような型の値を指定すれば良いかが分かる。Parametersでは引数の簡単な解説、Returnsではコマンドの返り値が示されている。

PixelClassifierTools.CreateObjectOptionsの箇所がリンクになっていたので飛んでみると、次のような4つのオプションが使えるのが確認できた。実際には"DELETE_EXISTING"のようにクオーテーションをつけて使用する。

コマンド例

それでは実際にQuPathコマンドを書いてみる。
QuPathスクリプトはコマンドを1行ずつ進めるのではなく、全実行内容を記載したスクリプトを1度処理することになる。Script Editor右下のRunCtr+Rでスクリプトを実行する。
Run > Run selected codeかCtr+Shift+Rで選択した行だけの処理を進めることもできるが、選択領域だけで完結する内容でないとエラーが出る。

QuPath公式ページのCustom scriptsの項を基に幾つか紹介する。ただ実用的でないものも多いのでここでは画像データやProjectの情報を得るコマンドを紹介する程度に留めて、各章で関連するものを紹介することにする。

まずはQuPathスクリプトとしての実行例を示したうえで、各コマンドの解説を記す。深い理解は必要無いので解説は読み飛ばしても構わない。ただ各コマンドはどの型のオブジェクトに対して使えるのか、コマンドの出力はどんな型のオブジェクトなのか調べながら進める習慣をつければ、自身で自由なスクリプトを書くことができるようになる。

画像の情報を取得 ImageDataクラス

画像を開いた状態で以下を行う。

def imageData = getCurrentImageData()
print imageData

こちらでも同じ

def viewer = getCurrentViewer()
def imageData = viewer.getImageData()
print imageData

返り値はImageDataクラスのオブジェクト。画像の主な情報を含んでいる。

ImageDataクラスに対して行えるメソッドを確認してみる。

describe(getCurrentImageData())
ImageDataクラスの組み込みメソッド
INFO: qupath.lib.images.ImageData
  Methods:
    void addPropertyChangeListener(PropertyChangeListener)
    ColorDeconvolutionStains getColorDeconvolutionStains()
    PathObjectHierarchy getHierarchy()
    Workflow getHistoryWorkflow()
    ImageType getImageType()
    String getLastSavedPath()
    Map getProperties()
    Object getProperty(String)
    ImageServer getServer()
    String getServerPath()
    void hierarchyChanged(PathObjectHierarchyEvent)
    boolean isBrightfield()
    boolean isChanged()
    boolean isFluorescence()
    Object removeProperty(String)
    void removePropertyChangeListener(PropertyChangeListener)
    void setChanged(boolean)
    void setColorDeconvolutionStains(ColorDeconvolutionStains)
    void setImageType(ImageType)
    void setLastSavedPath(String, boolean)
    Object setProperty(String, Object)
    String toString()
    void updateServerMetadata(ImageServerMetadata)
    void workflowUpdated(Workflow)

ImageDataクラスのオブジェクトに対して、get〇〇()シリーズを使えば様々な情報が取り出せるのが分かる。

またset〇〇()とあるように、画像の情報をスクリプトから登録することができる。

ImageTypeを変更
def imageData = getCurrentImageData()
imageData.setImageType(BRIGHTFIELD_H_DAB) \\ 他には次のものが選べる。 BRIGHTFIELD_H_E, BRIGHTFIELD_OTHER, FLUORESCENCE, OTHER, UNSET

画像のメタデータを取得

server情報

QuPathスクリプトではserverという情報をよく扱う。serverと言われてもイメージしにくいがjavadocを見ると、ピクセルやメタ情報のkey interfaceとのことである。


実際にデモデータでserver情報を見てみると、OpenslideImageServerを使っていることが分かる。

開いている画像のserverを取得する
def imageData = getCurrentImageData()
def server = imageData.getServer()
print server

このようにしてもserver情報が得られる。

開いている画像のserverを取得する
def server = getCurrentServer()
print server


serverオブジェクトに対して行えるメソッドを確認してみる。

describe(getCurrentServer())
OpenslideImageServerの組み込みメソッド
INFO: Result: qupath.lib.images.servers.openslide.OpenslideImageServer
  Methods:
    void close()
    String dumpMetadata()
    BufferedImage getAssociatedImage(String)
    Object getAssociatedImage(String)
    List getAssociatedImageList()
    ServerBuilder getBuilder()
    Object getCachedTile(TileRequest)
    ImageChannel getChannel(int)
    Object getDefaultThumbnail(int, int) throws IOException
    double getDownsampleForResolution(int)
    int getHeight()
    Class getImageClass()
    ImageServerMetadata getMetadata()
    ImageServerMetadata getOriginalMetadata()
    String getPath()
    PixelCalibration getPixelCalibration()
    PixelType getPixelType()
    double[] getPreferredDownsamples()
    String getServerType()
    TileRequestManager getTileRequestManager()
    Collection getURIs()
    int getWidth()
    boolean isEmptyRegion(RegionRequest)
    boolean isRGB()
    int nChannels()
    int nResolutions()
    int nTimepoints()
    int nZSlices()
    BufferedImage readRegion(RegionRequest) throws IOException
    Object readRegion(RegionRequest) throws IOException
    Object readRegion(double, int, int, int, int, int, int) throws IOException
    Object readRegion(double, int, int, int, int) throws IOException
    BufferedImage readTile(TileRequest) throws IOException
    void setMetadata(ImageServerMetadata)
    String toString()

getWidth()getHeight()は画像のピクセルサイズを調べるメソッドである。その他、解像度やデータ型、ファイルパスなど、画像を読み込む際に関連した情報がserverオブジェクトには含まれている。

serverオブジェクトからgetMetadata()で得られる情報は代表的な情報をまとめたものが得られる。

後々登場するが、readRegion()はserverオブジェクトから一部の領域を読み込む際に使用する。

projectに対する操作

QuPath上で現在開いている画像に対する処理だけでなく、projectに登録されている他の画像にアクセスする。
見た目には画像を開いていなくても、処理の中で画像を開いてその情報を取得することができる。

Project内の画像名を表示

\\ Projectの情報を変数projectに保存
def project = getProject()

\\ project変数から画像の一覧を作成し、1要素ずつの画像名を表示する。
for (entry in project.getImageList()) {
    print entry.getImageName()
}

現在のprojectに登録されている画像名が表示される。

解説
  1. getProject()でQuPath project情報を取得

    getProject機能の引数説明



出力はqupath.lib.projects.DefaultProject

getProject()の出力に対して行える組み込みmethod
describe(getProject())
INFO: Result: qupath.lib.projects.DefaultProject
  Fields:
    public static final java.lang.String qupath.lib.projects.DefaultProject.IMAGE_ID
  Methods:
    ProjectImageEntry addDuplicate(ProjectImageEntry, boolean) throws IOException
    ProjectImageEntry addImage(ServerBuilder) throws IOException
    Project createSubProject(String, Collection)
    long getCreationTimestamp()
    ProjectImageEntry getEntry(ImageData)
    List getImageList()
    boolean getMaskImageNames()
    long getModificationTimestamp()
    String getName()
    Manager getObjectClassifiers()
    Path getPath()
    List getPathClasses()
    Manager getPixelClassifiers()
    URI getPreviousURI()
    Manager getResources(String, Class, String)
    Manager getScripts()
    URI getURI()
    String getVersion()
    boolean isEmpty()
    void removeAllImages(Collection, boolean)
    void removeImage(ProjectImageEntry, boolean)
    void setMaskImageNames(boolean)
    boolean setPathClasses(Collection)
    int size()
    void syncChanges() throws IOException
    String toString()


組み込みメソッドのうち幾つか試してみる。

qupath.lib.projects.DefaultProjectクラスに対する組み込みメソッドの例
// projectの名前
print getProject().getName()
// projectが空かどうか
print getProject().isEmpty()
// projectの登録数
print getProject().size()
// projectの画像リスト取得
print getProject().getImageList()    


  1. 画像リストを作成し、for文で1要素ずつ取り出す
    getProject().getImageList()の出力は上記のようにリスト型。for文で1要素を取り出し、entryという変数に入れる。


  1. リストから取り出した要素から画像名を取得
    getImageList()の出力はリストだが、リストの要素は単純な文字列ではなく、qupath.lib.projects.DefaultProject$DefaultProjectImageEntryというクラス。
DefaultProject$DefaultProjectImageEntryの組み込みメソッド
describe(qupath.lib.projects.DefaultProject$DefaultProjectImageEntry)
INFO: Result: qupath.lib.projects.DefaultProject$DefaultProjectImageEntry
  Methods:
    void clearMetadata()
    boolean containsMetadata(String)
    String getDescription()
    Path getEntryPath()
    String getID()
    String getImageName()
    Manager getImages()
    Collection getMetadataKeys()
    Map getMetadataMap()
    String getMetadataSummaryString()
    String getMetadataValue(String)
    String getOriginalImageName()
    ServerBuilder getServerBuilder()
    String getSummary()
    Object getThumbnail() throws IOException
    BufferedImage getThumbnail() throws IOException
    Collection getURIs() throws IOException
    Collection getUris() throws IOException
    boolean hasImageData()
    String putMetadataValue(String, String)
    PathObjectHierarchy readHierarchy() throws IOException
    ImageData readImageData() throws IOException
    String removeMetadataValue(String)
    void saveImageData(ImageData) throws IOException
    void setDescription(String)
    void setImageName(String)
    void setThumbnail(Object) throws IOException
    void setThumbnail(BufferedImage) throws IOException
    String toString()
    boolean updateURIs(Map) throws IOException
    boolean updateUris(Map) throws IOException


print getProject().getImageList()[0].getID()
print getProject().getImageList()[0].getImageName()
print getProject().getImageList()[0].getSummary()


最終的なgetImageName()の出力は文字列

Project内の画像名とその画像データに含まれるアノテーションの数を表示

\\ Projectの情報を変数projectに保存
def project = getProject()

\\ project変数から画像の一覧を作成し、1要素ずつのentry変数へ渡す。
for (entry in project.getImageList()) {
    \\ 画像データを読み込む
    def imageData = entry.readImageData()
    \\ ヒエラルキー情報を取得
    def hierarchy = imageData.getHierarchy()
    \\ ヒエラルキー情報からアノテーションの情報を取得 リスト型の戻り値
    def annotations = hierarchy.getAnnotationObjects()
    \\ 画像名とアノテーションリストのサイズを表示
    print entry.getImageName() + '\t' + annotations.size()
}

解説
  1. QuPath project情報。getProject()
    --> qupath.lib.projects.DefaultProject


  1. 画像リストを作成し、for文で1要素ずつ取り出す
    --> qupath.lib.projects.DefaultProject$DefaultProjectImageEntry


  1. 画像データを取得する readImageData()


readImageData()の戻り値のclassと組み込みメソッド
INFO: Result: qupath.lib.images.ImageData
  Methods:
    void addPropertyChangeListener(PropertyChangeListener)
    ColorDeconvolutionStains getColorDeconvolutionStains()
    PathObjectHierarchy getHierarchy()
    Workflow getHistoryWorkflow()
    ImageType getImageType()
    String getLastSavedPath()
    Map getProperties()
    Object getProperty(String)
    ImageServer getServer()
    String getServerPath()
    void hierarchyChanged(PathObjectHierarchyEvent)
    boolean isBrightfield()
    boolean isChanged()
    boolean isFluorescence()
    Object removeProperty(String)
    void removePropertyChangeListener(PropertyChangeListener)
    void setChanged(boolean)
    void setColorDeconvolutionStains(ColorDeconvolutionStains)
    void setImageType(ImageType)
    void setLastSavedPath(String, boolean)
    Object setProperty(String, Object)
    String toString()
    void updateServerMetadata(ImageServerMetadata)
    void workflowUpdated(Workflow)


  1. 画像データからヒエラルキー情報を取得 getHierarchy()


ヒエラルキーはオブジェクト (アノテーションやCell, Detectionも含む)の階層情報などを含んだPathObjectHierarchyクラスを指す。階層だけでなく画像データのオブジェクトに関する情報はPathObjectHierarchyクラスのデータにまとめられていると想像するとよい。
またここにオブジェクトを追加して、画像データに反映させることもできる。


getHierarchy()の戻り値のclassと組み込みメソッド

Fields:
public static final java.util.Comparator qupath.lib.objects.hierarchy.PathObjectHierarchy.HIERARCHY_COMPARATOR
Methods:
void addListener(PathObjectHierarchyListener)
boolean addObject(PathObject)
boolean addObject(PathObject, boolean)
boolean addObjectBelowParent(PathObject, PathObject, boolean)
boolean addObjects(Collection)
void clearAll()
void fireHierarchyChangedEvent(Object)
void fireHierarchyChangedEvent(Object, PathObject)
void fireObjectClassificationsChangedEvent(Object, Collection)
void fireObjectMeasurementsChangedEvent(Object, Collection)
void fireObjectMeasurementsChangedEvent(Object, Collection, boolean)
void fireObjectsChangedEvent(Object, Collection)
void fireObjectsChangedEvent(Object, Collection, boolean)
Collection getAllObjects(boolean)
Collection getAnnotationObjects()
Collection getCellObjects()
Collection getDetectionObjects()
List getFlattenedObjectList(List)
Collection getObjects(Collection, Class)
Collection getObjectsForROI(Class, ROI)
Collection getObjectsForRegion(Class, ImageRegion, Collection)
Collection getPointObjects(Class)
PathObject getRootObject()
PathObjectSelectionModel getSelectionModel()
TMAGrid getTMAGrid()
Collection getTileObjects()
boolean hasObjectsForRegion(Class, ImageRegion)
boolean insertPathObject(PathObject, boolean)
boolean insertPathObjects(Collection)
boolean isEmpty()
int nObjects()
void removeListener(PathObjectHierarchyListener)
boolean removeObject(PathObject, boolean)
boolean removeObjectWithoutUpdate(PathObject, boolean)
void removeObjects(Collection, boolean)
void resolveHierarchy()
void setHierarchy(PathObjectHierarchy)
void setTMAGrid(TMAGrid)
String toString()
void updateObject(PathObject, boolean)


  1. ヒエラルキー情報からアノテーションの情報を取得 getAnnotationObjects()

オブジェクトのうちアノテーションオブジェクトを取得する。戻り値はアノテーションが1つであってもリスト型。
その要素はPathAnnotationObjectというクラス。今後もこのクラスの操作はよく登場する。

getAnnotationObjects()の戻り値の1要素のclassと組み込みメソッド

INFO: Result: qupath.lib.objects.PathAnnotationObject
Methods:
void addChildObject(PathObject)
void addChildObjects(Collection)
void clearChildObjects()
Collection getChildObjects(Collection)
Collection getChildObjects()
PathObject[] getChildObjectsAsArray()
double getClassProbability()
Set getClassifications()
Integer getColor()
Collection getDescendantObjects(Collection)
String getDescription()
String getDisplayedName()
UUID getID()
int getLevel()
MeasurementList getMeasurementList()
Map getMeasurements()
String getName()
PathObject getParent()
PathClass getPathClass()
ROI getROI()
boolean hasChildObjects()
boolean hasMeasurements()
boolean hasROI()
boolean isAnnotation()
boolean isCell()
boolean isDetection()
boolean isLocked()
boolean isRootObject()
boolean isTMACore()
boolean isTile()
int nChildObjects()
int nDescendants()
void readExternal(ObjectInput) throws IOException, ClassNotFoundException
void refreshID()
void removeChildObject(PathObject)
void removeChildObjects(Collection)
boolean resetPathClass()
void setClassifications(Collection)
void setColor(int, int, int)
void setColor(Integer)
void setDescription(String)
void setID(UUID) throws IllegalArgumentException
void setLocked(boolean)
void setName(String)
void setPathClass(PathClass, double)
void setPathClass(PathClass)
void setROI(ROI)
String toString()
void writeExternal(ObjectOutput) throws IOException


  1. 画像名とアノテーションリストのサイズを表示
    上記の出力はリスト型である。size()はリストの要素数を調べるメソッド。つまりアノテーションが幾つあったかを調べている。


readImageData()は画像データを開いている分処理時間がかかるらしい。以下のようにすればヒエラルキー情報を画像データを開かずに取得でき高速化が図れる。

readImageData()を使わずにヒエラルキー情報を得る。
def project = getProject()
for (entry in project.getImageList()) {
    def hierarchy = entry.readHierarchy()
    def annotations = hierarchy.getAnnotationObjects()
    print entry.getImageName() + '\t' + annotations.size()
}
readImageDataあり -> 2.38秒 readImageDataなし -> 0.03秒

このように最終的に欲しい情報に辿り着くのに複数通りのやり方がある。それに伴って、似たようなメソッドが沢山存在する。どのメソッドを使ってもよいが、高速化を目指すなら中間に重たい処理、重たい変数の保持を減らすことを心掛けるとよい。


Package

コマンドを検索する際に、コマンド名の前にそのコマンドが含まれているパッケージ名が記されている。

setImageType()コマンドはqupath.lib.imagesパッケージに含まれている。

パッケージはimportコマンドで読み込んでから使用する。

QuPathではメインのパッケージはデフォルトで読み込まれているので、パッケージを意識せずに使用しているが、importしないと使用できないものもある。例えばImageJやOpenCVのコマンドをQuPathから使うには明示的にimportする必要がある。

ちなみにScript EditorのRunからInclude default importsのチェックを外すと、setImageTypeはそんなコマンド見つかりませんとエラーが出る。

import qupath.lib.images.ImageData.*のようにsetImageType()が含まれるパッケージを明示的にimportしてやると、setImageType()が使用できるようになる。

*はqupath.lib.images内のコマンド全てをimportしている。setImageType()はqupath.lib.imagesパッケージのImageDataクラスの組み込みmethodなので、import qupath.lib.images.ImageDataのようにしても使用できるようになる。

Discussion

ログインするとコメントできます