前記事からの続きです。Grand Challengeへの提出物の例を作ってみた際のメモです。
4. ローカルPCでのアルゴリズム・コンテナ(Algorithm container)の準備
4.1 参考とするアルゴリズムの準備
4.1.1 ファイルのコピー(クローン作成)
Anaconda PowerShell prompt で以下の通り、Githubからクローンを作成します。
(base) PS C:\Users\(ユーザー名)> git clone https://github.com/DIAGNijmegen/drive-vessels-unet.git
このファイルは参考にするだけなので、実はわざわざコピー(クローン)する必要はありませんでした。GithubのWebサイト上でソースコードを閲覧し適宜コピーすようにしても問題ありません。
今回は、C:\Users(ユーザー名)\git_work という作業用フォルダを作成して作業しました。以下、C:\Users\ (ユーザー名)\git_work を作業用フォルダとして説明します。
4.1.2 ファイル構成の理解
コピーしてきたファイルは以下のような構成になっていました。
drive-vessels-unet/
├ train.py # モデルの学習を行うPythonファイル
├ dataloader.py # train.pyから呼び出されるデータの読み出し機能のモジュール
├ best_metric_model_segmentation2d_dict.pth # train.pyで学習したモデルの重みファイル
├ inference.py # 学習済みの重みファイルを使って推論を行うPythonファイル
├ VesselSegmentation/ # 推論を行うプログラムをGrand Challengeに提出できる形態に変えた完成形
├ (省略)
├ (省略)
train.py と inference.py をざっと見ると、それぞれかなりシンプルなコードとなっています。
VesselSegmentation フォルダ以下がGrand Challengeに提出できる形態なので、そのまま提出すればよいわけですが、自分で作ったアルゴリズムをGrand Challengeに提出する形態に変える方法を習得するために以下のことを行います。
- train.py の学習プログラムをGooogle Colab上で実行できるようにする。
- interference.py の推論プログラムを Gooogle Colab上で実行できるようにする。
- interference.py の推論プログラムを ローカルPC上で Grand-challengeに提出できる形態に整える。
機械学習プログラムを作るときの順番は1→2→3ですが、この記事では先に3からやり方を習得します。
4.1.3 inference.pyの理解
移植元のinference.py
を眺めて理解しました。以下のコードの(1)モデルの読み込みと(2)推論部分を、後ほど提出用プログラムに移植します。
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import SimpleITK
import torch
import monai
import numpy as np
from skimage import transform
from scipy.special import expit
import matplotlib.pyplot as plt
# (1). モデル定義と重みファイルの読み込み ↓ここから
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = monai.networks.nets.UNet(
dimensions=2,
in_channels=3,
out_channels=1,
channels=(16, 32, 64, 128, 256),
strides=(2, 2, 2, 2),
num_res_units=2,
).to(device)
model.load_state_dict(torch.load("./best_metric_model_segmentation2d_dict.pth"))
# ↑(1).ここまで
fig, ax = plt.subplots(1, 2, figsize=(7, 7), constrained_layout=True)
# Read the image
image = SimpleITK.ReadImage("vesselSegmentor/test/1000.0.mha")
# (2) 推論の実行 ↓ここから
image = SimpleITK.GetArrayFromImage(image)
image = np.array(image)
shape = image.shape
# Plot the image
ax[0].imshow(image)
ax[0].axis("off")
ax[0].set_title("input")
# Pre-process the image
image = transform.resize(image, (512, 512), order=3)
image = image.astype(np.float32) / 255.
image = image.transpose((2, 0, 1))
image = torch.from_numpy(image).to(device).reshape(1, 3, 512, 512)
# Do the forward pass
out = model(image).squeeze().data.cpu().numpy()
# Post-process the image
out = transform.resize(out, shape[:-1], order=3)
out = (expit(out) > 0.99)
out = (out * 255).astype(np.uint8)
# ↑(2) ここまで
# Plot the prediction
ax[1].imshow(out, cmap="gray")
ax[1].axis("off")
ax[1].set_title("prediction")
plt.show()
以下2つのモジュールが私にとっては初見でした。いずれも医療画像特有のモジュールらしいです。
- SimpleITK : 画像処理モジュール。OpenCVみたいなもの。医療画像の標準ファイル形式であるDicomを読み書きできる。
- monai : PyTorch上で動く医用画像の処理に特化したAIフレームワーク
4.2 evalutilsによる雛形ファイルの生成
4.2.1 雛形ファイル生成
Anaconda PowerShell prompt で以下の通り、evalutils により雛形ファイルを生成します。
(base) PS C:\Users\(ユーザー名)\git_work> evalutils init algorithm VesselSegmentationContainer
"VesselSegmentationContainer" は生成する雛形につけるプロジェクト名であ、生成されるフォルダ名となります。
コマンドを実行すると以下の質問文が現れました。
What kind of algorithm is this? (Classification, Segmentation, Detection):
今回の例では Segmentation
と入力します。これで雛形ファイルが自動的に生成されました。
4.2.2 生成されたファイルの理解
生成された雛形ファイルは以下のような構成になっていました。
VesselSegmentationContainer/
├ Dockerfile
├ build.sh
├ export.sh
├ test.sh
├ requirements.txt
├ process.py
├ test/
├ 1.0.000.000000.0.00.0.0000000000.0000.0000000000.000.mhd
├ 1.0.000.000000.0.00.0.0000000000.0000.0000000000.000.zraw
└ expected_output.json
拡張子が.sh
はシェルスクリプトファイルです。
4.3. 推論プログラムの移植作業
evalutilsで生成した雛形ファイルに対して、以下の順番で推論プログラムの移植作業を行います。
4.3.1. process.py に推論を行うコードを移植する。
4.3.2. Dockerfileとrequirement.txt を修正し、依存関係にあるモジュール等を記載する。
4.3.3. build.sh でdockerコンテナがビルドできるか試してみる。
4.3.4. test.sh でdockerが構築した仮想環境上で推論が行えるかをチェックする(提出前に正しく動くかをチェックする)
4.3.5. export.sh でdockerコンテナの提出形態ファイル(.tar.gz)を作成する
4.3.1 process.py に推論を行うコードを移植
自動生成されたprocess.py
の中身を確認します。
import SimpleITK
import numpy as np
from evalutils import SegmentationAlgorithm
from evalutils.validators import (
UniquePathIndicesValidator,
UniqueImagesValidator,
)
class Vesselsegmentationcontainer(SegmentationAlgorithm):
def __init__(self):
super().__init__(
validators=dict(
input_image=(
UniqueImagesValidator(),
UniquePathIndicesValidator(),
)
),
)
# (1) ここに初期化処理を記載する
def predict(self, *, input_image: SimpleITK.Image) -> SimpleITK.Image:
# (2) ここに推論処理を記載する。 return 処理も書き換える。
# Segment all values greater than 2 in the input image
return SimpleITK.BinaryThreshold(
image1=input_image, lowerThreshold=2, insideValue=1, outsideValue=0
)
if __name__ == "__main__":
Vesselsegmentationcontainer().process()
def predict(self, *, input_image: SimpleITK.Image) -> SimpleITK.Image:
のコード表現が、私にとっては見慣れない書き方でした。
- 引数に
*
が指定されると、*
以降に定義されている引数、この場合だとinput_image
をキーワード引数(引数名=値)として受け取ることを強制します。*
自体は引数にはなりません。(参考Webサイト) -
: SimpleITK.Image
は関数アノテーションと呼び、引数input_image
がSimpleITK.Image
型であることを示しています。ただし、あくまで関数の使用者への説明用で、プログラム実行時に型チェックがされるわけではない。 -
-> SimpleITK.Image
も関数アノテーションであり、関数predict
の返り値がSimpleITK.Image
型であることを説明しています。
4.1.3項で確認したinference.pyの(1)モデル読み込み部 と (2)推論実施部 を、process.pyの(1), (2)に移植しました。
import SimpleITK
import numpy as np
import torch
import monai
from skimage import transform
from scipy.special import expit
from evalutils import SegmentationAlgorithm
from evalutils.validators import (
UniquePathIndicesValidator,
UniqueImagesValidator,
)
class Vesselsegmentationcontainer(SegmentationAlgorithm):
def __init__(self):
super().__init__(
validators=dict(
input_image=(
UniqueImagesValidator(),
UniquePathIndicesValidator(),
)
),
)
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = monai.networks.nets.UNet(
dimensions=2,
in_channels=3,
out_channels=1,
channels=(16, 32, 64, 128, 256),
strides=(2, 2, 2, 2),
num_res_units=2,
).to(self.device)
self.model.load_state_dict(torch.load("./best_metric_model_segmentation2d_dict.pth"))
def predict(self, *, input_image: SimpleITK.Image) -> SimpleITK.Image:
# Read the image
image = SimpleITK.GetArrayFromImage(input_image)
image = np.array(image)
shape = image.shape
# Pre-process the image
image = transform.resize(image, (512, 512), order=3)
image = image.astype(np.float32) / 255.
image = image.transpose((2, 0, 1))
image = torch.from_numpy(image).to(self.device).reshape(1, 3, 512, 512)
# Do the forward pass
out = self.model(image).squeeze().data.cpu().numpy()
# Post-process the image
out = transform.resize(out, shape[:-1], order=3)
out = (expit(out) > 0.99)
out = (out * 255).astype(np.uint8)
out = SimpleITK.GetImageFromArray(out)
return out
if __name__ == "__main__":
Vesselsegmentationcontainer().process()
(1)の移植について
- (1)移植部で
torch
、monai
に関するモジュールインポートが必要なため、import torch
、import monai
を追加します。 - 変数
device
、model
は、クラス内の別の関数でも読み出せるように、self.device
、self.model
にします。
(2)の移植について
-
interference.py
ではImage
となっていた変数は、process.py
はpredict
関数の引数input_image
として与えられるので書き換えます。 -
ax[0].
から始まる3行はグラフ表示用なので移植しません。 -
out = SimpleITK.GetImageFromArray(out)
を追加し、return out
とします。 - (2)移植部で
transform
、expit
を使うため、from skimage import transform
、from scipy.special import expit
を追加します。 - 変数
device
、model
は、self.device
、self.model
にする。
ローカルPCにGPU環境がない場合
以下のコードを書き換えます。
self.model.load_state_dict(torch.load("./best_metric_model_segmentation2d_dict.pth"))
self.model.load_state_dict(
torch.load(
"./best_metric_model_segmentation2d_dict.pth",
map_location=torch.device('cpu')
)
)
4.3.2 Dockerfileとrequirements.txt 修正
4.3.2.1 Dockerfileの確認
evalutilsに生成されたDockerfileの中身を確認します。
FROM python:3.9-slim
RUN groupadd -r algorithm && useradd -m --no-log-init -r -g algorithm algorithm
RUN mkdir -p /opt/algorithm /input /output \
&& chown algorithm:algorithm /opt/algorithm /input /output
USER algorithm
WORKDIR /opt/algorithm
ENV PATH="/home/algorithm/.local/bin:${PATH}"
RUN python -m pip install --user -U pip
COPY --chown=algorithm:algorithm requirements.txt /opt/algorithm/
RUN python -m pip install --user -rrequirements.txt
COPY --chown=algorithm:algorithm process.py /opt/algorithm/
ENTRYPOINT python -m process $0 $@
登場するコマンド(各行の冒頭部)の意味は以下の通り。(参考)
{命令} | 用途 |
---|---|
FROM | 元となるDockerイメージの指定 |
RUN | コマンドの実行 |
USER | 実行ユーザーの指定 |
WORKDIR | 作業ディレクトリの指定 |
ENV | 環境変数の指定 |
COPY | Dockerイメージのファイルシステム上で使いたいファイルをコピー |
ENTRYPOINT | コンテナーの実行コマンド |
このDokerfileは以下のようなことをしていると理解しました。
- 基となるDockerイメージとして、python:3.9-slimを指定
- グループユーザー名 algorithm、ユーザー名 algorithm を作成し、このユーザーで操作を行う
- 下記のフォルダを作成し、/opt/algorithm をワーキングディレクトリとする。
- requirements.txtを/opt/algorithmにコピーして実行し、必要な依存モジュールをインストールする
- process.pyを/opt/algorithmにコピーして実行する
./
├ opt/
│ └─ algorithm/
├ input/
└ output/
4.3.2.2 Dockerfileの修正
-
1行目の
FROM python:3.9-slim
を、FROM pytorch/pytorch
に書き換えます。-
From
はDockerイメージのベースを指定しています。もとはpythonが動作するイメージをベースにしていましたが、pytorchがプレインストールされたイメージに変更します。
-
-
学習済みの重みファイルをDockerが構築する仮想環境内で使えるようにします。
- drive-vessels-unetのファイル構成(4.1.2項で確認したもの)に含まれていた、best_metric_model_segmentation2d_dict.pth が学習済みの重みファイルです。これを、4.2.2項で確認したVesselSegmentationContainerフォルダ直下にコピーします。(普通にWindows エキスプローラでコピーしました)
- Dockerfileに以下のコマンドを追加します。場所は
COPY --chown=algorithm:algorithm process.py /opt/algorithm/
とENTRYPOINT python -m process $0 $@
の間です。
COPY --chown=algorithm:algorithm best_metric_model_segmentation2d_dict.pth /opt/algorithm/best_metric_model_segmentation2d_dict.pth
4.3.2.3 requirements.txt の確認
evalutilsに生成されたrequirements.txt の中身は以下のようになっていました。
evalutils==0.3.1
scikit-learn==0.24.2
scipy==1.6.3
4.3.2.4 requirements.txt の修正・追加
4.3.1項で移植したプログラムを動かすのに必要なモジュールをrequirements.txtに追加します。
- monaiとskimageの追加が必要です。skimageはscikit-imageです。
- torch(Pytorch)も必要ですが、Dockerのベースイメージ指定を
FROM pytorch/pytorch
としたためrequirements.txtでは指定不要になります。
evalutilsに生成されたrequirements.txt では、バージョンが完全指定(==)されていましたが、>=に書き換えました。
あとで、Google Colabの環境でもこのrequirement.txtを使って環境設定します。その際に、Google Colabではscikit-learn、scipy、scikit-imageなどは最新版を使っており、古いバージョンを指定すると毎回ランタイムの再起動が必要となり、やや面倒に感じたためです。
evalutils>=0.3.1
scikit-learn>=0.24.2
scipy>=1.6.3
monai>=0.4.0
scikit-image>=0.18.1
4.3.3 build.sh でdockerコンテナのビルド試行
4.3.3.1 build.sh の確認
evalutilsに生成されたbuild.sh の中身は以下のようになっていました。
変更の必要はありませんでしたが、理解のために中身を確認しておきます。
#!/usr/bin/env bash
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
docker build -t vesselsegmentationcontainer "$SCRIPTPATH"
主なコマンドは、docker build
だけです。
-
#!/usr/bin/env bash
はこのファイルがbashで実行されることを指定しています。 -
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
は、このファイルのディレクトリ名をSCRIPTPATH
に入れています。- スクリプトファイルで
$( コマンド )
と書くと、コマンドが実行された結果がそこに代入されます。 -
$0
はスクリプトファイル(今回はbuild.sh)のパスです。 -
cd
、dirname
、pwd
はそれぞれコマンドです。
- スクリプトファイルで
- 'docker build (パス)'で(パス)にあるDockerfileとコンテキストを使ってDockerイメージを構築します。
-t vesselsegmentationcontainer
のオプションにより、構築されたDockerイメージはvesselsegmentationcontainerという名前になります。
4.3.3.2 build.sh の実行
PowerShell上でWSLを起動させてから、build.sh を実行しました。
PS C:\Users\(ユーザー名)> wsl
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
(ユーザー名)@(PC名):/mnt/c/Users/(ユーザー名)$ ./git_work/VesselSegmentationContainer/build.sh
a
はじめ、PowerShell上でwslを立ち上げずにコマンドを実行してしまい、別のコマンド画面が立ち上がって一瞬だけエラーが映って消えてしまう現象にはまりました。一瞬すぎてエラーが読めないですし、挙動もおかしいです。
この問題は、PowerShell上でwslを立ち上げてからshファイルを実行することで解決できました。PowerShell上でwslを使わずに、build.shを実行すると、gitbashという別のプロンプトが立ち上がるようです。gitbashはdockerと連携していないためエラーとなり、さらにgitbashが終了するとウインドウも消えてしまうことが原因だったようです。
なお、一瞬だけ見えるエラーをスクリーンキャプチャで取ると以下のようなエラー文でした。
error during connect: This error may indicate that the docker daemon is not running.; Post"http://%2F%2Fpipe%2Fdocker_engine/v1.24/build?buildargs=%7B%7D&cashefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&shmsize=0&t=vesselsegmentationcontainer&target=&ulimits=null&version=1": open //./pipe/docker_engine: The system cannot find the file specified/
Ubuntuがインストールされていない状態で、wsl上でbuild.shを実行すると、dockerというコマンドが見つからない、というエラーが発生しました。この解決法は3.4項の「UbuntuのインストールとDockerの設定」の通りです。
うまくいけば、以下のような途中経過が出てから完了となります。
(ユーザー名)@(PC名):/mnt/c/Users/(ユーザー名)$ ./git_work/VesselSegmentationContainer/build.sh
[+] Building 126.5s (14/14) FINISHED
=> [internal] load build definition from Dockerfile 2.0s
=> => transferring dockerfile: 735B 0.3s
=> [internal] load .dockerignore 2.5s
=> => transferring context: 2B 0.1s
=> [internal] load metadata for docker.io/pytorch/pytorch:latest 6.3s
=> [internal] load build context 1.3s
=> => transferring context: 6.54MB 0.2s
=> [1/9] FROM docker.io/pytorch/pytorch@sha256:9904a7e081eaca29e3ee46afac87f2879676dd3bf7b5e9b8450454d84e074ef0 0.0s
=> CACHED [2/9] RUN groupadd -r algorithm && useradd -m --no-log-init -r -g algorithm algorithm 0.0s
=> CACHED [3/9] RUN mkdir -p /opt/algorithm /input /output && chown algorithm:algorithm /opt/algorithm /inpu 0.0s
=> CACHED [4/9] WORKDIR /opt/algorithm 0.0s
=> CACHED [5/9] RUN python -m pip install --user -U pip 0.0s
=> [6/9] COPY --chown=algorithm:algorithm requirements.txt /opt/algorithm/ 2.4s
=> [7/9] RUN python -m pip install --user -rrequirements.txt 95.4s
=> [8/9] COPY --chown=algorithm:algorithm process.py /opt/algorithm/ 2.7s
=> [9/9] COPY --chown=algorithm:algorithm best_metric_model_segmentation2d_dict.pth /opt/algorithm/best_metric_m 2.3s
=> exporting to image 12.3s
=> => exporting layers 11.3s
=> => writing image sha256:bf7813a3085923a6b3ec9b1980064825fc1c4f8b5324dc7ae22ef98d7881b5ec 0.1s
=> => naming to docker.io/library/vesselsegmentationcontainer 0.1s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
初回のbuild.shの実行は10分以上掛かりました。2回目以降はキャッシュを使うため数秒で終わります。
4.3.4. test.sh でdockerが構築した仮想環境上で推論が行えるかの確認
test.sh でdockerが構築した仮想環境上で推論が行えるかをチェックします。提出前に正しく動くかのチェックです。
4.3.4.1 test.shの確認・修正
evalutilsに生成されたtest.sh の中身は以下のようになっていました。
#!/usr/bin/env bash
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
./build.sh
VOLUME_SUFFIX=$(dd if=/dev/urandom bs=32 count=1 | md5sum | cut --delimiter=' ' --fields=1)
MEM_LIMIT="4g" # Maximum is currently 30g, configurable in your algorithm image settings on grand challenge
docker volume create vesselsegmentationcontainer-output-$VOLUME_SUFFIX
# Do not change any of the parameters to docker run, these are fixed
docker run --rm \
--memory="${MEM_LIMIT}" \
--memory-swap="${MEM_LIMIT}" \
--network="none" \
--cap-drop="ALL" \
--security-opt="no-new-privileges" \
--shm-size="128m" \
--pids-limit="256" \
-v $SCRIPTPATH/test/:/input/ \
-v vesselsegmentationcontainer-output-$VOLUME_SUFFIX:/output/ \
vesselsegmentationcontainer
docker run --rm \
-v vesselsegmentationcontainer-output-$VOLUME_SUFFIX:/output/ \
python:3.9-slim cat /output/results.json | python -m json.tool
docker run --rm \
-v vesselsegmentationcontainer-output-$VOLUME_SUFFIX:/output/ \
-v $SCRIPTPATH/test/:/input/ \
python:3.9-slim python -c "import json, sys; f1 = json.load(open('/output/results.json')); f2 = json.load(open('/input/expected_output.json')); sys.exit(f1 != f2);"
if [ $? -eq 0 ]; then
echo "Tests successfully passed..."
else
echo "Expected output was not found..."
fi
docker volume rm vesselsegmentationcontainer-output-$VOLUME_SUFFIX
- build.shを呼び出してDockerイメージを構築しています。
- build.sh内ではvesselsegmentationcontainerというDockerイメージ名で構築されます。
- 変数'VOLUME_SUFFIX'にランダムな文字列を生成し、
docker volume create
で vesselsegmentationcontainer-output-(ランダムな文字列) というDocker ボリューム(保存領域)を作成しています。- Dockerボリュームについて→ 参考サイト
- 構築したDockerイメージ vesselsegmentationcontainer をコンテナとして実行(run)しています。
-
-v ホスト側ソース:コンテナ側送信先
オプションでボリュームをマウントしています。 - Dockerイメージ vesselsegmentationcontainer は、Dockerfileに従って process.pyが実行されます。
- コンテナのinputフォルダ(ホストのtestフォルダがマウントされている)からファイルを読み込みます。
- コンテナのoutputフォルダ(Dockerボリュームのvesselsegmentationcontainer-output-(ランダムな文字列)がマウントされています)に結果ファイルを書き込みます。
-
- 2つめのDocker runでは、/output/results.jsonファイルに対して改行・インデントなど綺麗に整形しています(参考Webサイト)。
- python:3.9-slim は既存のDockerイメージ。pythonが動く最低限の環境
-
コマンド1 | コマンド2
バイプ「|」は、複数のコマンドをつなぎ、標準出力を次のコマンドに渡す。 -
cat (ファイル名)
はファイル名の中身を標準出力する。 - `python -m json.toolでjsonファイルを整形します。
- 3つめのDocker runでは、/output/results.jsonファイルと、/input/expected_output.jsonを比較しています。
- /input/expected_output.jsonはあらかじめ用意しておく正解ファイルです。
あとでエラーが出たため、1か所書き換えました。
inputフォルダ内のすべてのフォルダを読み込んでしまうため、読み込みたい画像だけが入ったフォルダを指定します。(参考)
docker run --rm \
(途中省略)
-v $SCRIPTPATH/test/:/input/ \
(省略)
docker run --rm \
(途中省略)
-v $SCRIPTPATH/test/input/:/input/ \
(省略)
4.3.4.2 推論対象のテスト画像の準備
evalutilsで生成したVesselSegmentationContainer/testフォルダにインプット画像などを入れます。
VesselSegmentationContainer/testフォルダには、もともと以下の3つのファイルが入っています。
VesselSegmentationContainer/
└ test/
├ 1.0.000.000000.0.00.0.0000000000.0000.0000000000.000.mhd
├ 1.0.000.000000.0.00.0.0000000000.0000.0000000000.000.zraw
└ expected_output.json
このうち、mhdとzrawは削除します。
4.1項でコピーしてきた元のアルゴリズムにテスト画像が入っています。
https://github.com/DIAGNijmegen/drive-vessels-unet/blob/master/VesselSegmentation/test/input/01_test.tif
この画像を以下の位置にコピーします。
VesselSegmentationContainer/
└ test/
├ input/
└01_test.tif
└ expected_output.json
expected_output.jsonは以下の通り書き換えます。
[
{
"outputs": [
{
"type": "metaio_image",
"filename": "01_test.tif"
}
],
"inputs": [
{
"type": "metaio_image",
"filename": "01_test.tif"
}
],
"error_messages": []
}
]
4.3.4.3 test.shの実行
PowerShell上でWSLを起動させてから、test.sh を実行します。
(ユーザー名)@(PC名):/mnt/c/Users/(ユーザー名)$ cd git_work/VesselSegmentationContainer/
(ユーザー名)@(PC名):/mnt/c/Users/(ユーザー名)/git_work/VesselSegmentationContainer$ ./test.sh
cdせずに``のようにtest.shを実行すると以下のようなエラーが出ました。
./git_work/VesselSegmentationContainer/test.sh: line 5: ./build.sh: No such file or directory
cdしてからtest.shを実行することで解決しました。
以下のエラーが発生しました。
RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False. If you are running on a CPU-only machine, please use torch.load with map_location=torch.device('cpu') to map your storages to the CPU.
解決方法は4.3.1項「ローカルPCにGPU環境がない場合」の通りです。
はじめ、Video tutorialに従って、以下のフォルダ構成で実行したところエラーが出ました。
VesselSegmentationContainer/
└ test/
├ 01_test.tif
└ expected_output.json
以下のエラーが発生しました(長いので抜粋)
(前略)
HDF5-DIAG: Error detected in HDF5 (1.10.6) thread 0:
#000: /tmp/SimpleITK-build/ITK/Modules/ThirdParty/HDF5/src/itkhdf5/src/H5F.c line 370 in itk_H5Fis_hdf5(): unable open file
major: File accessibilty
minor: Not an HDF5 file
#001: /tmp/SimpleITK-build/ITK/Modules/ThirdParty/HDF5/src/itkhdf5/src/H5Fint.c line 830 in itk_H5F__is_hdf5(): unable to locate file signature
major: File accessibilty
minor: Not an HDF5 file
#002: /tmp/SimpleITK-build/ITK/Modules/ThirdParty/HDF5/src/itkhdf5/src/H5FDint.c line 126 in itk_H5FD_locate_signature(): unable to read file signature
major: Low-level I/O
minor: Unable to initialize object
#003: /tmp/SimpleITK-build/ITK/Modules/ThirdParty/HDF5/src/itkhdf5/src/H5FDint.c line 205 in itk_H5FD_read(): driver read request failed
major: Virtual File Layer
minor: Read failed
#004: /tmp/SimpleITK-build/ITK/Modules/ThirdParty/HDF5/src/itkhdf5/src/H5FDsec2.c line 725 in H5FD_sec2_read(): file read failed: time = Mon Mar 21 02:13:13 2022
, filename = '/input/expected_output', file descriptor = 4, errno = 21, error message = 'Is a directory', buf = 0x7fff1d10c7a8, total read size = 8, bytes this sub-read = 8, bytes actually read = 18446744073709551615, offset = 0
major: Low-level I/O
minor: Read failed
Could not load expected_output using <evalutils.io.SimpleITKLoader object at 0x7fad55844c10>.
(中略)
この原因はinputフォルダ(testフォルダをマウント)内のすべてのファイルを読み込んで推論しようとするためでした。4.3.4.2項の通りinputフォルダを分けて、4.3.4.1項の通りtest.shを修正することで解消しました。
次のエラーが出ました。
./test.sh: line 27: python: command not found
test.shでは、Dockerイメージ「python:3.9-slim」を使ってPythonのコマンド・プログラムを動かしますが、「python:3.9-slim」のなかでPythonがうまく起動しなかったようです。以下の対応で動くようになりました。
- Dockerイメージを「python:3.9-slim」から「python:3」に変えてみた。
- コマンドを「python」から「python3」に変えてみた。
docker run --rm \
(省略)
python:3.9-slim cat /output/results.json | python -m json.tool
docker run --rm \
(省略)
python:3.9-slim python -c "import json, sys; f1 = json.load(open('/output/results.json')); f2 = json.load(open('/input/expected_output.json')); sys.exit(f1 != f2);"
docker run --rm \
(省略)
python:3 cat /output/results.json | python3 -m json.tool
docker run --rm \
(省略)
python:3 python3 -c "import json, sys; f1 = json.load(open('/output/results.json')); f2 = json.load(open('/input/expected_output.json')); sys.exit(f1 != f2);"
test.shでは、推論結果はDockerボリュームの「vesselsegmentationcontainer-output-(ランダムな文字列)」に保存されますが、test.shが完了するとdocker volume rm
によりDockerボリュームは削除されてしまいます。よって、本当に推論結果が出力されているのか、わかりづらいです。
そこで、test.shを以下のように変えると、出力結果はDockerボリュームではなく、ホストPCのフォルダに保存され、test.shの完了後に出力結果のファイルを確認できます。
#!/usr/bin/env bash
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
./build.sh
VOLUME_SUFFIX=$(dd if=/dev/urandom bs=32 count=1 | md5sum | cut --delimiter=' ' --fields=1)
MEM_LIMIT="4g" # Maximum is currently 30g, configurable in your algorithm image settings on grand challenge
# docker volume create vesselsegmentationcontainer-output-$VOLUME_SUFFIX
# Do not change any of the parameters to docker run, these are fixed
docker run --rm \
--memory="${MEM_LIMIT}" \
--memory-swap="${MEM_LIMIT}" \
--network="none" \
--cap-drop="ALL" \
--security-opt="no-new-privileges" \
--shm-size="128m" \
--pids-limit="256" \
-v $SCRIPTPATH/test/input/:/input/ \
-v $SCRIPTPATH/test/output/:/output/ \
vesselsegmentationcontainer
docker run --rm \
-v $SCRIPTPATH/test/output/:/output/ \
python:3 cat /output/results.json | python3 -m json.tool
echo CHECKPOINT
docker run --rm \
-v $SCRIPTPATH/test/output/:/output/ \
-v $SCRIPTPATH/test/:/input/ \
python:3 python3 -c "import json, sys; f1 = json.load(open('/output/results.json')); f2 = json.load(open('/input/expected_output.json')); sys.exit(f1 != f2);"
if [ $? -eq 0 ]; then
echo "Tests successfully passed..."
else
echo "Expected output was not found..."
fi
#docker volume rm vesselsegmentationcontainer-output-$VOLUME_SUFFIX
4.3.5. export.sh によるdockerコンテナの提出形態ファイル(.tar.gz)の作成
4.3.5.1 export.shの中身の確認
evalutilsに生成されたexport.sh の中身は以下のようになっていました。
#!/usr/bin/env bash
./build.sh
docker save vesselsegmentationcontainer | gzip -c > VesselSegmentationContainer.tar.gz
- build.shを実行し、Dockerイメージ「vesselsegmentationcontainer」を構築しています。
-
docker save
コマンドにより、Dockerイメージ「vesselsegmentationcontainer」をtar.gzファイルとして保存しています。
4.3.5.2 export.shの実行
PowerShell上でWSLを起動させてから、export.sh を実行します。
(ユーザー名)@(PC名):/mnt/c/Users/(ユーザー名)/git_work/VesselSegmentationContainer$ ./export.sh
完了すると、VesselSegmentationContainer.tar.gz ファイルがexport.shと同じフォルダに保存されています。
このファイルをGrand challengeに提出すればよいはずです。
私の環境では実行に3時間くらい掛かりました。
また出力ファイルも3GBくらいありました。
ここまでで、Grand Challengeへの提出物の例を作るのは一段落です。次の記事では、今回の推論プログラムの例に至る前の学習プログラムをGoogle Colab上で動かしてみます。