提交 56055e7f 编写于 作者: G Guanghua Yu 提交者: wangguanzhong

AGE baseline (#3031)

* AGE challenge baseline
上级 6db35e7a
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Angle closure Glaucoma Evaluation Challenge](https://age.grand-challenge.org/Details/)\n",
"## Angle closure classification Baseline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## requirement install"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install xlrd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zip File Extract\n",
"\n",
"Assume `Training100.zip` and `Validation_ASOCT_Image.zip` are stored @ `./AGE_challenge Baseline/datasets/`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!unzip -q ../datasets/Training100.zip -d ../datasets/\n",
"!unzip -q ../datasets/Validation_ASOCT_Image.zip -d ../datasets/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Explore Data & Train/Val split"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import csv\n",
"import matplotlib.pyplot as plt\n",
"import cv2\n",
"import os, shutil\n",
"import pprint\n",
"import pandas as pd\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"data_root_path = \"../datasets/Training100/\"\n",
"xlsx_file_path = os.path.join(data_root_path, \"Training100_Location.xlsx\")\n",
"\n",
"# Load\n",
"image_path = os.path.join(data_root_path, \"ASOCT_Image\")\n",
"label_file_path = os.path.join(data_root_path, \"train_cls.csv\")\n",
"\n",
"# Save\n",
"train_file_path = os.path.join(data_root_path, \"cls_train_split.csv\")\n",
"val_file_path = os.path.join(data_root_path, \"cls_val_split.csv\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>ASOCT_Name</th>\n",
" <th>Left_Label</th>\n",
" <th>X1</th>\n",
" <th>Y1</th>\n",
" <th>Right_Label</th>\n",
" <th>X2</th>\n",
" <th>Y2</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>T0056-10.jpg</td>\n",
" <td>1</td>\n",
" <td>228.833656</td>\n",
" <td>466.959601</td>\n",
" <td>1</td>\n",
" <td>1870.803864</td>\n",
" <td>451.592300</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>T0047-06.jpg</td>\n",
" <td>1</td>\n",
" <td>207.935545</td>\n",
" <td>525.938764</td>\n",
" <td>1</td>\n",
" <td>1792.231404</td>\n",
" <td>432.521881</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>T0066-15.jpg</td>\n",
" <td>0</td>\n",
" <td>239.372633</td>\n",
" <td>476.273925</td>\n",
" <td>0</td>\n",
" <td>1899.775568</td>\n",
" <td>501.007410</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>T0025-15.jpg</td>\n",
" <td>0</td>\n",
" <td>177.708404</td>\n",
" <td>545.655935</td>\n",
" <td>0</td>\n",
" <td>1862.380363</td>\n",
" <td>439.228928</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>T0088-06.jpg</td>\n",
" <td>0</td>\n",
" <td>285.256170</td>\n",
" <td>735.076014</td>\n",
" <td>0</td>\n",
" <td>1884.122651</td>\n",
" <td>767.858589</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" ASOCT_Name Left_Label X1 Y1 Right_Label X2 \\\n",
"0 T0056-10.jpg 1 228.833656 466.959601 1 1870.803864 \n",
"1 T0047-06.jpg 1 207.935545 525.938764 1 1792.231404 \n",
"2 T0066-15.jpg 0 239.372633 476.273925 0 1899.775568 \n",
"3 T0025-15.jpg 0 177.708404 545.655935 0 1862.380363 \n",
"4 T0088-06.jpg 0 285.256170 735.076014 0 1884.122651 \n",
"\n",
" Y2 \n",
"0 451.592300 \n",
"1 432.521881 \n",
"2 501.007410 \n",
"3 439.228928 \n",
"4 767.858589 "
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"xlsx_file = pd.read_excel(xlsx_file_path)\n",
"xlsx_file.to_csv(label_file_path, \n",
" index=False, columns=['ASOCT_Name', 'Left_Label', 'Right_Label'])\n",
"xlsx_file.head()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[['ASOCT_Name', 'Left_Label', 'Right_Label'],\n",
" ['T0056-10.jpg', '1', '1'],\n",
" ['T0047-06.jpg', '1', '1'],\n",
" ['T0066-15.jpg', '0', '0'],\n",
" ['T0025-15.jpg', '0', '0']]\n"
]
}
],
"source": [
"data_list = []\n",
"\n",
"with open(label_file_path,'r') as f: \n",
" lines=csv.reader(f) \n",
" for key, line in enumerate(lines): \n",
" data_list.append(line)\n",
" \n",
"pprint.pprint(data_list[:5])"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1280, 320]\n",
"[1280, 320]\n"
]
}
],
"source": [
"# left, right\n",
"# negative sample (label==0): 1280, 1280\n",
"# positive sample (label==1): 320, 320\n",
"left_label_counter = [0, 0]\n",
"right_label_counter = [0, 0]\n",
"\n",
"for line in data_list[1:]:\n",
" file_name, l_label, r_label = line\n",
" left_label_counter[int(l_label)] += 1\n",
" right_label_counter[int(r_label)] += 1\n",
" \n",
"print(left_label_counter)\n",
"print(right_label_counter)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Left label == Right label, in **TRAINING SET**\n",
"for line in data_list[1:]:\n",
" file_name, l_label, r_label = line\n",
" if int(l_label) != int(r_label):\n",
" print(line)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Train/Val Split"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def train_val_split(data_list, train_ratio=0.8, shuffle_seed=42):\n",
" testee_list = list(set( [line[0].split(\"-\")[0] for line in data_list[1:]] ))\n",
" \n",
" # Split by patient id, prevent data leakage\n",
" val_testee_idx = np.random.choice(testee_list, int(len(testee_list) * (1-train_ratio)), replace=False)\n",
"\n",
" train_list = []\n",
" val_list = []\n",
" \n",
" for line in data_list[1:]:\n",
" file_name, _, _ = line\n",
" if file_name.split(\"-\")[0] in val_testee_idx:\n",
" val_list.append(line)\n",
" else:\n",
" train_list.append(line)\n",
" \n",
" return train_list, val_list"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1296\n",
"304\n"
]
}
],
"source": [
"train_data_list, val_data_list = train_val_split(data_list)\n",
"print(len(train_data_list))\n",
"print(len(val_data_list))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# Write to files\n",
"with open(train_file_path, \"w+\") as f:\n",
" for line in train_data_list:\n",
"# file_name, l_label, r_label = line\n",
" f.write(\"{},{},{}\\n\".format(*line))\n",
" \n",
"with open(val_file_path, \"w+\") as f:\n",
" for line in val_data_list:\n",
" f.write(\"{},{},{}\\n\".format(*line))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Angle closure Glaucoma Evaluation Challenge](https://age.grand-challenge.org/Details/)\n",
"## Angle closure classification Baseline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Training\n",
"\n",
"- Assume `Training100.zip` and `Validation_ASOCT_Image.zip` are stored @ `./AGE_challenge Baseline/datasets/`\n",
"- Assume `weights` are stored @ `./AGE_challenge Baseline/weights/`\n",
"- In training phase, we use standard ResNet34 with `sigmoid(fc(1))` output\n",
"- We split a single image into two parts"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Download ImageNet weight"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--2019-08-06 13:36:07-- https://paddle-imagenet-models-name.bj.bcebos.com/ResNet34_pretrained.tar\n",
"Resolving paddle-imagenet-models-name.bj.bcebos.com (paddle-imagenet-models-name.bj.bcebos.com)... 220.181.33.44, 220.181.33.43\n",
"Connecting to paddle-imagenet-models-name.bj.bcebos.com (paddle-imagenet-models-name.bj.bcebos.com)|220.181.33.44|:443... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 87470080 (83M) [application/x-tar]\n",
"Saving to: ‘../weights/ResNet34_pretrained.tar’\n",
"\n",
"ResNet34_pretrained 100%[===================>] 83.42M 1.80MB/s in 66s \n",
"\n",
"2019-08-06 13:37:13 (1.27 MB/s) - ‘../weights/ResNet34_pretrained.tar’ saved [87470080/87470080]\n",
"\n"
]
}
],
"source": [
"# https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification\n",
"!rm ../weights/ResNet34_pretrained.tar \n",
"!rm -rf ../weights/ResNet34_pretrained\n",
"\n",
"!wget -P ../weights/ https://paddle-imagenet-models-name.bj.bcebos.com/ResNet34_pretrained.tar \n",
"!tar xvf ../weights/ResNet34_pretrained.tar -C ../weights/ > /dev/null # silent\n",
"!rm ../weights/ResNet34_pretrained/fc*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Main Code"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import os, random, functools, math\n",
"import cv2\n",
"import numpy as np\n",
"import time\n",
"from sklearn.metrics import roc_auc_score, confusion_matrix, roc_curve"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running Verify Fluid Program ... \n",
"Your Paddle Fluid works well on SINGLE GPU or CPU.\n",
"Your Paddle Fluid works well on MUTIPLE GPU or CPU.\n",
"Your Paddle Fluid is installed successfully! Let's start deep Learning with Paddle Fluid now\n"
]
}
],
"source": [
"import paddle\n",
"import paddle.fluid as fluid\n",
"import paddle.fluid.layers as FL\n",
"import paddle.fluid.optimizer as FO\n",
"fluid.install_check.run_check()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from resnet import *"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"data_root_path = \"../datasets/Training100/\"\n",
"image_path = os.path.join(data_root_path, \"ASOCT_Image\")\n",
"\n",
"train_file_path = os.path.join(data_root_path, \"cls_train_split.csv\")\n",
"val_file_path = os.path.join(data_root_path, \"cls_val_split.csv\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"BATCH_SIZE = 32 // 2 # image split * 2\n",
"THREAD = 8\n",
"BUF_SIZE = 32"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define Data Loader"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Real time data augmentation in training\n",
"\n",
"def rotate_image(image, angle=90, scale=1.0):\n",
" '''\n",
" Rotate the image\n",
" :param image: image to be processed\n",
" :param angle: Rotation angle in degrees. Positive values mean counter-clockwise rotation (the coordinate origin is assumed to be the top-left corner).\n",
" :param scale: Isotropic scale factor.\n",
" '''\n",
" w = image.shape[1]\n",
" h = image.shape[0]\n",
" #rotate matrix\n",
" M = cv2.getRotationMatrix2D((w/2,h/2), angle, scale)\n",
" #rotate\n",
" image = cv2.warpAffine(image,M,(w,h))\n",
" return image\n",
"\n",
"def vflip_image(image):\n",
" return cv2.flip(image, flipCode=1)\n",
"\n",
"def crop_image(img, target_size, center):\n",
" \"\"\" crop_image \"\"\"\n",
" height, width = img.shape[:2]\n",
" size = target_size\n",
" if center == True:\n",
" w_start = (width - size) // 2\n",
" h_start = (height - size) // 2\n",
" else:\n",
" w_start = np.random.randint(0, width - size + 1)\n",
" h_start = np.random.randint(0, height - size + 1)\n",
" w_end = w_start + size\n",
" h_end = h_start + size\n",
" img = img[h_start:h_end, w_start:w_end, :]\n",
" return img\n",
"\n",
"def split_image(img):\n",
" rows,_,_ = img.shape\n",
" # left, right split\n",
" return [img[:, :rows, :], img[:, -rows:, :]]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# data reader and xmap wrapper to enable multiprocessing data load\n",
"\n",
"def reader(img_path, file_list, batch_size=32, shuffle=True, shuffle_seed=42):\n",
" def read_file_list():\n",
" batch_data = []\n",
" np.random.shuffle(file_list)\n",
" for line in file_list:\n",
" single_img_path, l_label, r_label = line.split(\",\")\n",
" batch_data.append([single_img_path, int(l_label), int(r_label)])\n",
" if len(batch_data) == batch_size:\n",
" yield batch_data\n",
" batch_data = []\n",
" if len(batch_data) != 0:\n",
" yield batch_data\n",
" return read_file_list\n",
"\n",
"def process_batch_data(input_data, mode, rotate=True, flip=True):\n",
" batch_data = []\n",
" for sample in input_data:\n",
" file, l_label, r_label = sample\n",
"\n",
" img = cv2.imread( file )\n",
" img = img[:, :, ::-1].astype('float32') / 255\n",
" \n",
" img = np.concatenate(split_image(img), axis=-1) # concat at channel dim\n",
" img = cv2.resize(img, (256, 256))\n",
" \n",
" if mode == 'train':\n",
" img = crop_image(img, target_size=224, center=False)\n",
"# img = img + np.random.randn(*img.shape) * 0.3 / 255 \n",
" if rotate:\n",
" angle = np.random.randint(1, 30, size=1)\n",
" img = rotate_image(img, angle)\n",
" if flip and np.random.randint(0,2):\n",
" img = vflip_image(img)\n",
" else:\n",
" img = crop_image(img, target_size=224, center=True)\n",
" \n",
" img = img.transpose((2, 0, 1))\n",
"\n",
" batch_data.append((img[:3,:,:], l_label))\n",
" batch_data.append((img[3:,:,:], r_label))\n",
"\n",
" return batch_data"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def data_loader(img_list, img_path, batch_size, order=False, mode='train'):\n",
" data_reader = reader(img_path, img_list, batch_size)\n",
" mapper = functools.partial(process_batch_data, mode=mode)\n",
" \n",
" data_reader = paddle.reader.shuffle(data_reader, 32)\n",
" \n",
" return paddle.reader.xmap_readers(\n",
" mapper, data_reader, THREAD, BUF_SIZE, order=order)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"with open(train_file_path) as flist:\n",
" train_file_list = [os.path.join(image_path,line.strip()) for line in flist]\n",
"\n",
"with open(val_file_path) as flist:\n",
" val_file_list = [os.path.join(image_path,line.strip()) for line in flist]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1296\n",
"304\n",
"../datasets/Training100/ASOCT_Image/T0047-06.jpg,1,1\n"
]
}
],
"source": [
"print(len(train_file_list))\n",
"print(len(val_file_list))\n",
"\n",
"print(train_file_list[0])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"np.random.shuffle(train_file_list)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1072, 224]\n"
]
}
],
"source": [
"# Class imbalance\n",
"classes_collaction = [0] * 2\n",
"for line in train_file_list:\n",
" file, c_l, c_r = line.split(\",\")\n",
" classes_collaction[int(c_l)] +=1\n",
" \n",
"print(classes_collaction)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"train_dataloader = data_loader(train_file_list, image_path, BATCH_SIZE, False, mode='train')\n",
"val_dataloader = data_loader(val_file_list, image_path, BATCH_SIZE, True, mode='val')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define model (compute graph)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"def network():\n",
" data_shape = [3, 224, 224]\n",
" \n",
" model = ResNet34()\n",
" \n",
" input_feature = FL.data(name='pixel', shape=data_shape, dtype='float32')\n",
" label = FL.data(name='label', shape=[1], dtype='int64')\n",
" \n",
" logit = model.net(input_feature, class_dim=1)\n",
" predict = FL.sigmoid(logit)\n",
"\n",
" reader = fluid.io.PyReader(feed_list=[input_feature, label], \n",
" capacity=64, iterable=True, use_double_buffer=True)\n",
"\n",
" cost = FL.log_loss(predict, FL.cast(label, \"float32\"), epsilon=1e-7)\n",
" loss = FL.mean(cost)\n",
"\n",
" accuracy = FL.mean(FL.cast(FL.equal(FL.cast(FL.round(predict),\"int64\"), label), \"float32\") )\n",
" \n",
" return [loss, accuracy, predict, reader]"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def calc_auc_numpy(y_pred, y_true):\n",
" auc = roc_auc_score(y_true, y_pred)\n",
"\n",
" fpr, tpr, thresh = roc_curve(y_true, y_pred)\n",
" optimal_idx = np.argmax(tpr - fpr)\n",
" \n",
" print(\"Best Sensi: %1.4f\" % (tpr[optimal_idx]))\n",
" print(\"Best Speci: %1.4f\" % (1-fpr[optimal_idx]))\n",
" print(\"Best Thresh: %1.4f\" % (thresh[optimal_idx]))\n",
" \n",
" y_pred = (y_pred > 0.5).astype(np.int_)\n",
" print(confusion_matrix(y_true, y_pred))\n",
" \n",
" return auc"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"def train(use_cuda, params_dirname_prefix, pretrained_model=False, EPOCH_NUM=10):\n",
" place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()\n",
" \n",
" startup_prog = fluid.Program()\n",
" train_prog = fluid.Program()\n",
" val_prog = fluid.Program()\n",
"\n",
" with fluid.program_guard(train_prog, startup_prog):\n",
" # fluid.unique_name.guard() to share parameters with test network\n",
" with fluid.unique_name.guard():\n",
" train_loss, train_acc, train_output, train_reader = network()\n",
" \n",
" optimizer = fluid.optimizer.Adam(learning_rate=1e-4)\n",
" optimizer.minimize(train_loss)\n",
" \n",
" # 定义预测网络\n",
" with fluid.program_guard(val_prog, startup_prog):\n",
" # Use fluid.unique_name.guard() to share parameters with train network\n",
" with fluid.unique_name.guard():\n",
" val_loss, val_acc, val_output, val_reader = network()\n",
"\n",
" val_prog = val_prog.clone(for_test=True)\n",
"\n",
" train_loss.persistable = True\n",
" train_acc.persistable = True\n",
" val_loss.persistable = True\n",
" val_acc.persistable = True\n",
" val_output.persistable = True\n",
" \n",
" exe = fluid.Executor(place)\n",
" exe.run(startup_prog)\n",
"\n",
" if pretrained_model:\n",
" def if_exist(var):\n",
" return os.path.exists(os.path.join(pretrained_model, var.name))\n",
"\n",
" fluid.io.load_vars(\n",
" exe, pretrained_model, main_program=train_prog, predicate=if_exist)\n",
"\n",
" train_reader.decorate_sample_list_generator( train_dataloader, places=place )\n",
" val_reader.decorate_sample_list_generator( val_dataloader, places=place )\n",
"\n",
" # For training test cost\n",
" def train_test(val_prog, val_reader):\n",
" count = 0\n",
" accumulated = [0,0]\n",
" \n",
" prediction = []\n",
" label_values = []\n",
" \n",
" for tid, val_data in enumerate(val_reader()):\n",
" avg_cost_np = exe.run(\n",
" program=val_prog,\n",
" feed=val_data,\n",
" fetch_list=[val_loss, val_acc, val_output],\n",
" use_program_cache=True)\n",
" accumulated = [\n",
" x[0] + x[1][0] for x in zip(accumulated, avg_cost_np)\n",
" ]\n",
" prediction.append(avg_cost_np[2])\n",
" label_values.append( np.array(val_data[0]['label']) )\n",
" count += 1\n",
"\n",
" prediction = np.concatenate(prediction, 0)\n",
" label_values = np.concatenate(label_values, 0)\n",
" \n",
" auc = calc_auc_numpy(prediction, label_values)\n",
" \n",
" return [x / count for x in accumulated], auc\n",
"\n",
" # main train loop.\n",
" def train_loop():\n",
" step = 0\n",
" best_auc = 0.\n",
"\n",
" for pass_id in range(EPOCH_NUM):\n",
" data_load_time = time.time()\n",
" for step_id, data_train in enumerate(train_reader()):\n",
" data_load_costtime = time.time() - data_load_time\n",
" start_time = time.time()\n",
" avg_loss_value = exe.run(\n",
" train_prog,\n",
" feed=data_train,\n",
" fetch_list=[train_loss, train_acc], \n",
" use_program_cache=True)\n",
" cost_time = time.time() - start_time\n",
" if step_id % 50 == 0:\n",
" print(\"Pass %d, Epoch %d, Cost %f, Acc %f, Time %f, LoadTime %f\" % (\n",
" step_id, pass_id, avg_loss_value[0], avg_loss_value[1], cost_time, data_load_costtime))\n",
" else:\n",
" pass\n",
" step += 1\n",
" data_load_time = time.time()\n",
"\n",
" metrics, auc = train_test(val_prog, val_reader)\n",
" avg_cost_test, accuracy_test = metrics\n",
" \n",
" print('Test with Epoch {0}, Loss {1:2.4}, Acc {2:2.4}, Auc {3:2.4}'.format(\n",
" pass_id, avg_cost_test, accuracy_test, auc))\n",
" \n",
" if auc >= best_auc:\n",
" best_data = [pass_id, avg_cost_test, accuracy_test, auc]\n",
" best_auc = auc\n",
" print(\"\\nBest AUC, Checkpoint Saved!\\n\")\n",
" if not os.path.isdir(params_dirname_prefix+\"_best/\"):\n",
" os.makedirs(params_dirname_prefix+\"_best/\")\n",
" fluid.io.save_persistables(exe, params_dirname_prefix+\"_best/\", main_program=train_prog)\n",
"\n",
" if not os.path.isdir(params_dirname_prefix+\"_checkpoint/\"):\n",
" os.makedirs(params_dirname_prefix+\"_checkpoint/\")\n",
" fluid.io.save_persistables(exe, params_dirname_prefix+\"_checkpoint/\", main_program=train_prog)\n",
" train_loop()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# download imagenet pretrain weight from:\n",
"# https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification\n",
"# remove ResNet34_pretrained/fc*\n",
"train(use_cuda=True, params_dirname_prefix=\"../weights/classify_weights\", \n",
" pretrained_model=\"../weights/ResNet34_pretrained\", EPOCH_NUM=20)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Angle closure Glaucoma Evaluation Challenge](https://age.grand-challenge.org/Details/)\n",
"## Angle closure classification Baseline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Inference\n",
"\n",
"- Assume `Training100.zip` and `Validation_ASOCT_Image.zip` are stored @ `./AGE_challenge Baseline/datasets/`\n",
"- Assume `weights` are stored @ `./AGE_challenge Baseline/weights/`\n",
"- In training phase, we use standard ResNet34 with `sigmoid(fc(1))` output\n",
"- We split a single image into two parts"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os, random, functools, math\n",
"import cv2\n",
"import numpy as np\n",
"import time\n",
"from sklearn.metrics import roc_auc_score, confusion_matrix, roc_curve"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running Verify Fluid Program ... \n",
"Your Paddle Fluid works well on SINGLE GPU or CPU.\n",
"Your Paddle Fluid works well on MUTIPLE GPU or CPU.\n",
"Your Paddle Fluid is installed successfully! Let's start deep Learning with Paddle Fluid now\n"
]
}
],
"source": [
"import paddle\n",
"import paddle.fluid as fluid\n",
"import paddle.fluid.layers as FL\n",
"import paddle.fluid.optimizer as FO\n",
"fluid.install_check.run_check()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from resnet import *"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"data_root_path = \"../datasets/Training100/\"\n",
"image_path = os.path.join(data_root_path, \"ASOCT_Image\")\n",
"\n",
"val_file_path = os.path.join(data_root_path, \"cls_val_split.csv\")\n",
"\n",
"output_file = \"./Classification_Results.csv\""
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"BATCH_SIZE = 32 // 2 # image split * 2\n",
"THREAD = 8\n",
"BUF_SIZE = 32"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define Inference Data Loader"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Real time data augmentation\n",
"def crop_image(img, target_size, center):\n",
" \"\"\" crop_image \"\"\"\n",
" height, width = img.shape[:2]\n",
" size = target_size\n",
" if center == True:\n",
" w_start = (width - size) // 2\n",
" h_start = (height - size) // 2\n",
" else:\n",
" w_start = np.random.randint(0, width - size + 1)\n",
" h_start = np.random.randint(0, height - size + 1)\n",
" w_end = w_start + size\n",
" h_end = h_start + size\n",
" img = img[h_start:h_end, w_start:w_end, :]\n",
" return img\n",
"\n",
"def split_image(img):\n",
" rows,_,_ = img.shape\n",
" # left, right split\n",
" return [img[:, :rows, :], img[:, -rows:, :]]\n",
" \n",
"# data reader and xmap wrapper to enable multiprocessing data load\n",
"\n",
"def reader(img_path, file_list, batch_size=32, shuffle=True, shuffle_seed=42):\n",
" def read_file_list():\n",
" batch_data = []\n",
" np.random.shuffle(file_list)\n",
" for line in file_list:\n",
" single_img_path, _, _ = line.split(\",\")\n",
" batch_data.append(single_img_path)\n",
" if len(batch_data) == batch_size:\n",
" yield batch_data\n",
" batch_data = []\n",
" if len(batch_data) != 0:\n",
" yield batch_data\n",
" return read_file_list\n",
"\n",
"def process_batch_data(input_data):\n",
" batch_data = []\n",
" for sample in input_data:\n",
" file = sample\n",
"\n",
" img = cv2.imread( file )\n",
" img = img[:, :, ::-1].astype('float32') / 255\n",
" \n",
" img = np.concatenate(split_image(img), axis=-1) # concat at channel dim\n",
" img = cv2.resize(img, (256, 256))\n",
" \n",
" img = crop_image(img, target_size=224, center=True)\n",
" \n",
" img = img.transpose((2, 0, 1))\n",
"\n",
" batch_data.append((file, 0, img[:3,:,:]))\n",
" batch_data.append((file, 1, img[3:,:,:]))\n",
"\n",
" return batch_data"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def data_loader(img_list, img_path, batch_size, order=False):\n",
" data_reader = reader(img_path, img_list, batch_size)\n",
" mapper = functools.partial(process_batch_data)\n",
" \n",
" data_reader = paddle.reader.shuffle(data_reader, 32)\n",
" \n",
" return paddle.reader.xmap_readers(\n",
" mapper, data_reader, THREAD, BUF_SIZE, order=order)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"with open(val_file_path) as flist:\n",
" val_file_list = [os.path.join(image_path,line.strip()) for line in flist]\n",
" \n",
"val_dataloader = data_loader(val_file_list, image_path, BATCH_SIZE, True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define model (compute graph)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def network():\n",
" data_shape = [3, 224, 224]\n",
" \n",
" model = ResNet34()\n",
" \n",
" input_feature = FL.data(name='pixel', shape=data_shape, dtype='float32')\n",
" \n",
" logit = model.net(input_feature, class_dim=1)\n",
" predict = FL.sigmoid(logit)\n",
"\n",
" return predict"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def inference(use_cuda, pretrained_model, threshold=0.5):\n",
" place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()\n",
" \n",
" startup_prog = fluid.Program()\n",
" val_prog = fluid.Program()\n",
"\n",
" # 定义预测网络\n",
" with fluid.program_guard(val_prog, startup_prog):\n",
" # Use fluid.unique_name.guard() to share parameters with train network\n",
" with fluid.unique_name.guard():\n",
" val_output = network()\n",
"\n",
" val_prog = val_prog.clone(for_test=True)\n",
" val_output.persistable = True\n",
" \n",
" exe = fluid.Executor(place)\n",
" exe.run(startup_prog)\n",
"\n",
" if pretrained_model:\n",
" def if_exist(var):\n",
" return os.path.exists(os.path.join(pretrained_model, var.name))\n",
"\n",
" fluid.io.load_vars(\n",
" exe, pretrained_model, main_program=val_prog, predicate=if_exist)\n",
" \n",
" positive_ratio = 1. / (1. - threshold)\n",
" negative_ratio = 1. / threshold\n",
" \n",
" result = {}\n",
" for tid, data in enumerate(val_dataloader()):\n",
" file_names, part_splits, val_datas = [],[],[]\n",
" for item in data:\n",
" file_names.append(item[0])\n",
" part_splits.append(item[1])\n",
" val_datas.append(item[2])\n",
" \n",
" batch_preds, = exe.run(\n",
" program=val_prog,\n",
" feed={\"pixel\":np.array(val_datas)},\n",
" fetch_list=[val_output],\n",
" use_program_cache=True)\n",
"\n",
" for file, part, pred in zip(file_names, part_splits, batch_preds[:,0]):\n",
" if pred >= threshold:\n",
" threshold_pred = (pred-threshold) * positive_ratio\n",
" else:\n",
" threshold_pred = (pred-threshold) * negative_ratio\n",
" if file not in result.keys():\n",
" result[file] = [0, 0]\n",
" result[file][part] = threshold_pred\n",
" return result\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"result = inference(True, \"../weights/classify_weights_best/\")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"with open(output_file, \"w+\") as f:\n",
" f.write(\"{},{},{}\\n\".format(\"ASOCT_NAME\", \"LEFT_ANGLE_RESULTS\", \"RIGHT_ANGLE_RESULTS\"))\n",
" for file, pred_labels in result.items():\n",
" f.write(\"{},{},{}\\n\".format(file.split(\"/\")[-1], *pred_labels))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
# Angle closure Glaucoma Evaluation Challenge
The goal of the challenge is to evaluate and compare automated algorithms for angle closure classification and localization of scleral spur (SS) points on a common dataset of AS-OCT images. We invite the medical image analysis community to participate by developing and testing existing and novel automated classification and segmentation methods.
More detail [AGE challenge](https://age.grand-challenge.org/Details/).
## Angle closure classification task
1. Prepare data
* We assume that you have downloaded data(two zip files), and stored @ `../datasets/`.
* (Updated on August 5) Replace update files.
* We provide a demo about `zip file extract`, `xlsx reader`, `data structure explore` and `Train/Val split`.
2. Train
* We assume that you have downloaded data, extracted compressed files, and stored @ `../datasets/`.
* Based on PaddlePaddle and [ResNet34](https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/models/resnet.py), we provide a baseline about `pretrain weight download and load`, `datareader`, `computation graph of ResNet34 model`, `training` and `evaluation metrics`.
3. Inference
* We assume that you have downloaded data, extracted compressed files, and stored @ `../datasets/`.
* We assume that you store checkpoint files @ `../weights/`
* Based on PaddlePaddle and [ResNet34](https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/models/resnet.py), we provide a baseline about `inference` and `dump result to csv file`.
#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import math
import paddle
import paddle.fluid as fluid
from paddle.fluid.param_attr import ParamAttr
__all__ = ["ResNet", "ResNet18", "ResNet34", "ResNet50", "ResNet101", "ResNet152"]
train_parameters = {
"input_size": [3, 224, 224],
"input_mean": [0.485, 0.456, 0.406],
"input_std": [0.229, 0.224, 0.225],
"learning_strategy": {
"name": "piecewise_decay",
"batch_size": 256,
"epochs": [30, 60, 90],
"steps": [0.1, 0.01, 0.001, 0.0001]
}
}
class ResNet():
def __init__(self, layers=50):
self.params = train_parameters
self.layers = layers
def net(self, input, class_dim=1000):
layers = self.layers
supported_layers = [18, 34, 50, 101, 152]
assert layers in supported_layers, \
"supported layers are {} but input layer is {}".format(supported_layers, layers)
if layers == 18:
depth = [2, 2, 2, 2]
elif layers == 34 or layers == 50:
depth = [3, 4, 6, 3]
elif layers == 101:
depth = [3, 4, 23, 3]
elif layers == 152:
depth = [3, 8, 36, 3]
num_filters = [64, 128, 256, 512]
conv = self.conv_bn_layer(
input=input, num_filters=64, filter_size=7, stride=2, act='relu',name="conv1")
conv = fluid.layers.pool2d(
input=conv,
pool_size=3,
pool_stride=2,
pool_padding=1,
pool_type='max')
if layers >= 50:
for block in range(len(depth)):
for i in range(depth[block]):
if layers in [101, 152] and block == 2:
if i == 0:
conv_name="res"+str(block+2)+"a"
else:
conv_name="res"+str(block+2)+"b"+str(i)
else:
conv_name="res"+str(block+2)+chr(97+i)
conv = self.bottleneck_block(
input=conv,
num_filters=num_filters[block],
stride=2 if i == 0 and block != 0 else 1, name=conv_name)
pool = fluid.layers.pool2d(
input=conv, pool_size=7, pool_type='avg', global_pooling=True)
stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0)
out = fluid.layers.fc(input=pool,
size=class_dim,
param_attr=fluid.param_attr.ParamAttr(
initializer=fluid.initializer.Uniform(-stdv, stdv)))
else:
for block in range(len(depth)):
for i in range(depth[block]):
conv_name="res"+str(block+2)+chr(97+i)
conv = self.basic_block(
input=conv,
num_filters=num_filters[block],
stride=2 if i == 0 and block != 0 else 1,
is_first=block==i==0,
name=conv_name)
pool = fluid.layers.pool2d(
input=conv, pool_size=7, pool_type='avg', global_pooling=True)
stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0)
out = fluid.layers.fc(input=pool,
size=class_dim,
param_attr=fluid.param_attr.ParamAttr(
initializer=fluid.initializer.Uniform(-stdv, stdv)))
return out
def conv_bn_layer(self,
input,
num_filters,
filter_size,
stride=1,
groups=1,
act=None,
name=None):
conv = fluid.layers.conv2d(
input=input,
num_filters=num_filters,
filter_size=filter_size,
stride=stride,
padding=(filter_size - 1) // 2,
groups=groups,
act=None,
param_attr=ParamAttr(name=name + "_weights"),
bias_attr=False,
name=name + '.conv2d.output.1')
if name == "conv1":
bn_name = "bn_" + name
else:
bn_name = "bn" + name[3:]
return fluid.layers.batch_norm(input=conv,
act=act,
name=bn_name+'.output.1',
param_attr=ParamAttr(name=bn_name + '_scale'),
bias_attr=ParamAttr(bn_name + '_offset'),
moving_mean_name=bn_name + '_mean',
moving_variance_name=bn_name + '_variance',)
def shortcut(self, input, ch_out, stride, is_first, name):
ch_in = input.shape[1]
if ch_in != ch_out or stride != 1 or is_first == True:
return self.conv_bn_layer(input, ch_out, 1, stride, name=name)
else:
return input
def bottleneck_block(self, input, num_filters, stride, name):
conv0 = self.conv_bn_layer(
input=input, num_filters=num_filters, filter_size=1, act='relu',name=name+"_branch2a")
conv1 = self.conv_bn_layer(
input=conv0,
num_filters=num_filters,
filter_size=3,
stride=stride,
act='relu',
name=name+"_branch2b")
conv2 = self.conv_bn_layer(
input=conv1, num_filters=num_filters * 4, filter_size=1, act=None, name=name+"_branch2c")
short = self.shortcut(input, num_filters * 4, stride, is_first=False, name=name + "_branch1")
return fluid.layers.elementwise_add(x=short, y=conv2, act='relu',name=name+".add.output.5")
def basic_block(self, input, num_filters, stride, is_first, name):
conv0 = self.conv_bn_layer(input=input, num_filters=num_filters, filter_size=3, act='relu', stride=stride,
name=name+"_branch2a")
conv1 = self.conv_bn_layer(input=conv0, num_filters=num_filters, filter_size=3, act=None,
name=name+"_branch2b")
short = self.shortcut(input, num_filters, stride, is_first, name=name + "_branch1")
return fluid.layers.elementwise_add(x=short, y=conv1, act='relu')
def ResNet18():
model = ResNet(layers=18)
return model
def ResNet34():
model = ResNet(layers=34)
return model
def ResNet50():
model = ResNet(layers=50)
return model
def ResNet101():
model = ResNet(layers=101)
return model
def ResNet152():
model = ResNet(layers=152)
return model
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Angle closure Glaucoma Evaluation Challenge](https://age.grand-challenge.org/Details/)\n",
"## Scleral spur localization Baseline (FCN)\n",
"\n",
"- To keep model training stable, images with coordinate == -1, were removed.\n",
"\n",
"- For real inference, you MIGHT keep all images in val_file_path file."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Training\n",
"\n",
"- Assume `Training100.zip` and `Validation_ASOCT_Image.zip` are stored @ `./AGE_challenge Baseline/datasets/`\n",
"- Assume `weights` are stored @ `./AGE_challenge Baseline/weights/`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Download ImageNet weight"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--2019-08-06 14:16:30-- https://paddle-imagenet-models-name.bj.bcebos.com/ResNet34_pretrained.tar\n",
"Resolving paddle-imagenet-models-name.bj.bcebos.com (paddle-imagenet-models-name.bj.bcebos.com)... 111.206.47.194, 202.106.5.21\n",
"Connecting to paddle-imagenet-models-name.bj.bcebos.com (paddle-imagenet-models-name.bj.bcebos.com)|111.206.47.194|:443... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 87470080 (83M) [application/x-tar]\n",
"Saving to: ‘../weights/ResNet34_pretrained.tar’\n",
"\n",
"ResNet34_pretrained 100%[===================>] 83.42M 2.08MB/s in 43s \n",
"\n",
"2019-08-06 14:17:14 (1.93 MB/s) - ‘../weights/ResNet34_pretrained.tar’ saved [87470080/87470080]\n",
"\n"
]
}
],
"source": [
"# https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification\n",
"!rm ../weights/ResNet34_pretrained.tar \n",
"!rm -rf ../weights/ResNet34_pretrained\n",
"\n",
"!wget -P ../weights/ https://paddle-imagenet-models-name.bj.bcebos.com/ResNet34_pretrained.tar \n",
"!tar xvf ../weights/ResNet34_pretrained.tar -C ../weights/ > /dev/null # silent\n",
"!rm ../weights/ResNet34_pretrained/fc*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Main Code"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import os, random, functools, math\n",
"import cv2\n",
"import numpy as np\n",
"import time"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running Verify Fluid Program ... \n",
"Your Paddle Fluid works well on SINGLE GPU or CPU.\n",
"Your Paddle Fluid works well on MUTIPLE GPU or CPU.\n",
"Your Paddle Fluid is installed successfully! Let's start deep Learning with Paddle Fluid now\n"
]
}
],
"source": [
"import paddle\n",
"import paddle.fluid as fluid\n",
"import paddle.fluid.layers as FL\n",
"import paddle.fluid.optimizer as FO\n",
"fluid.install_check.run_check()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# preprocess: extract left/right label col in Training100_Location.xlsx\n",
"# save to train_csv file\n",
"data_root_path = \"../datasets/Training100/\"\n",
"image_path = os.path.join(data_root_path, \"ASOCT_Image_loc\")\n",
"\n",
"train_file_path = os.path.join(data_root_path, \"loc_train_split.csv\")\n",
"val_file_path = os.path.join(data_root_path, \"loc_val_split.csv\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"BATCH_SIZE = 32\n",
"THREAD = 8\n",
"BUF_SIZE = 32"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Remove last global pooling and fullyconnect layer to enable FCN arch.\n",
"# Standard ResNet Implement: \n",
"# https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/models/resnet.py\n",
"from resnet_modified import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define Data Loader"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def vflip_image(image):\n",
" return cv2.flip(image, flipCode=1)\n",
"\n",
"def gaussian_k(x0,y0, sigma, width, height):\n",
" \"\"\" Make a square gaussian kernel centered at (x0, y0) with sigma as SD.\n",
" \"\"\"\n",
" x = np.arange(0, width, 1, float) ## (width,)\n",
" y = np.arange(0, height, 1, float)[:, np.newaxis] ## (height,1)\n",
" return np.exp(-((x-x0)**2 + (y-y0)**2) / (2*sigma**2))\n",
"\n",
"def generate_hm(height, width, point, s=10):\n",
" \"\"\" Generate a full Heap Map for every landmarks in an array\n",
" Args:\n",
" height : The height of Heat Map (the height of target output)\n",
" width : The width of Heat Map (the width of target output)\n",
" point : (x,y)\n",
" \"\"\"\n",
" hm = gaussian_k(point[0], point[1], s, height, width)\n",
" return hm"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def reader(img_path, file_list, batch_size=32, shuffle=True, shuffle_seed=42):\n",
" def read_file_list():\n",
" batch_data = []\n",
" np.random.shuffle(file_list)\n",
" for line in file_list:\n",
" file_name, p_x, p_y = line.split(\",\")\n",
" batch_data.append([file_name, float(p_x), float(p_y)])\n",
" if len(batch_data) == batch_size:\n",
" yield batch_data\n",
" batch_data = []\n",
" if len(batch_data) != 0:\n",
" yield batch_data\n",
" return read_file_list\n",
"\n",
"def process_batch_data(input_data, mode, rotate=True, flip=True):\n",
" batch_data = []\n",
" for sample in input_data:\n",
" file, p_x, p_y = sample\n",
" \n",
" img = cv2.imread( file )\n",
" img = img[:, :, ::-1].astype('float32') / 255.0\n",
" \n",
" ratio = 256.0 / img.shape[0]\n",
" p_x, p_y = p_x * ratio, p_y * ratio\n",
" img = cv2.resize(img, (256, 256))\n",
"\n",
" if mode == 'train':\n",
" img = img + np.random.randn(*img.shape) * 0.3 / 255 \n",
" if flip and np.random.randint(0,2):\n",
" img = vflip_image(img)\n",
" p_x = 256 - p_x\n",
" else:\n",
" pass\n",
" \n",
" hm = generate_hm(256, 256, (p_x, p_y))\n",
" img = img.transpose((2, 0, 1))\n",
" batch_data.append((img, hm))\n",
"\n",
" return batch_data"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def data_loader(img_list, img_path, batch_size, order=False, mode='train'):\n",
" data_reader = reader(img_path, img_list, batch_size)\n",
" mapper = functools.partial(process_batch_data, mode=mode)\n",
" \n",
" data_reader = paddle.reader.shuffle(data_reader, 32)\n",
" \n",
" return paddle.reader.xmap_readers(\n",
" mapper, data_reader, THREAD, BUF_SIZE, order=order)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"with open(train_file_path) as flist:\n",
" train_file_list = [os.path.join(image_path,line.strip()) for line in flist]\n",
"\n",
"with open(val_file_path) as flist:\n",
" val_file_list = [os.path.join(image_path,line.strip()) for line in flist] "
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2586\n",
"607\n",
"../datasets/Training100/ASOCT_Image_loc/T0056-10_left.jpg,228.83365553922314,466.95960107867666\n"
]
}
],
"source": [
"print(len(train_file_list))\n",
"print(len(val_file_list))\n",
"print(train_file_list[0])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"np.random.shuffle(train_file_list)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"train_dataloader = data_loader(train_file_list, image_path, BATCH_SIZE, False, mode='train')\n",
"val_dataloader = data_loader(val_file_list, image_path, BATCH_SIZE, True, mode='val')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define model (compute graph)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"def network():\n",
" data_shape = [3, 256, 256]\n",
" \n",
" model = ResNet34()\n",
" \n",
" input_feature = FL.data(name='pixel', shape=data_shape, dtype='float32')\n",
" hm = FL.data(name='label', shape=data_shape[1:], dtype='float32')\n",
" \n",
" logit = model.net(input_feature, class_dim=1)\n",
" pred_hm = FL.squeeze(\n",
" FL.conv2d_transpose(logit, num_filters=1, output_size=256), axes=[1]) # Bs, 256,256\n",
" \n",
" reader = fluid.io.PyReader(feed_list=[input_feature, hm], \n",
" capacity=64, iterable=True, use_double_buffer=True)\n",
"\n",
" cost = FL.square_error_cost(pred_hm, hm)\n",
" loss = FL.mean(cost)\n",
" \n",
" return [loss, pred_hm, reader]"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"def calc_dist(pred_hm, hm):\n",
" hm = np.array(hm)\n",
" \n",
" mean_dis = 0.\n",
" for single_hm, single_pred_hm in zip(hm, pred_hm):\n",
" # Find argmax_x, argmax_y from 2D tensor\n",
" label_x, label_y = np.unravel_index(single_hm.argmax(), single_hm.shape)\n",
" pred_x, pred_y = np.unravel_index(single_pred_hm.argmax(), single_pred_hm.shape)\n",
" mean_dis += np.sqrt((pred_x - label_x) ** 2 + (pred_y - label_y) ** 2)\n",
" \n",
" return mean_dis / hm.shape[0]"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def train(use_cuda, params_dirname_prefix, pretrained_model=False, EPOCH_NUM=10):\n",
" place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()\n",
" \n",
" startup_prog = fluid.Program()\n",
" train_prog = fluid.Program()\n",
" val_prog = fluid.Program()\n",
"\n",
" with fluid.program_guard(train_prog, startup_prog):\n",
" # fluid.unique_name.guard() to share parameters with test network\n",
" with fluid.unique_name.guard():\n",
" train_loss, train_output, train_reader = network()\n",
" \n",
" optimizer = fluid.optimizer.Adam(learning_rate=1e-4)\n",
" optimizer.minimize(train_loss)\n",
" \n",
" # 定义预测网络\n",
" with fluid.program_guard(val_prog, startup_prog):\n",
" # Use fluid.unique_name.guard() to share parameters with train network\n",
" with fluid.unique_name.guard():\n",
" val_loss, val_output, val_reader = network()\n",
"\n",
" val_prog = val_prog.clone(for_test=True)\n",
"\n",
" train_loss.persistable = True\n",
" val_loss.persistable = True\n",
" val_output.persistable = True\n",
" \n",
" exe = fluid.Executor(place)\n",
" exe.run(startup_prog)\n",
"\n",
" if pretrained_model:\n",
" def if_exist(var):\n",
" return os.path.exists(os.path.join(pretrained_model, var.name))\n",
"\n",
" fluid.io.load_vars(\n",
" exe, pretrained_model, main_program=train_prog, predicate=if_exist)\n",
"\n",
" train_reader.decorate_sample_list_generator( train_dataloader, places=place )\n",
" val_reader.decorate_sample_list_generator( val_dataloader, places=place )\n",
"\n",
" # For training test cost\n",
" def train_test(val_prog, val_reader):\n",
" count = 0\n",
" accumulated = [0,0]\n",
" \n",
" prediction = []\n",
" label_values = []\n",
" \n",
" for tid, val_data in enumerate(val_reader()):\n",
" avg_cost_np = exe.run(\n",
" program=val_prog,\n",
" feed=val_data,\n",
" fetch_list=[val_loss, val_output],\n",
" use_program_cache=True)\n",
" accumulated = [\n",
" x[0] + x[1][0] for x in zip(accumulated, avg_cost_np)\n",
" ]\n",
" prediction.append(avg_cost_np[1])\n",
" label_values.append( np.array(val_data[0]['label']) )\n",
" count += 1\n",
"\n",
" prediction = np.concatenate(prediction, 0)\n",
" label_values = np.concatenate(label_values, 0)\n",
"\n",
" mean_dis = calc_dist(prediction, label_values)\n",
" \n",
" return [x / count for x in accumulated], mean_dis\n",
"\n",
" # main train loop.\n",
" def train_loop():\n",
" step = 0\n",
" best_dist = 65536.\n",
"\n",
" for pass_id in range(EPOCH_NUM):\n",
" data_load_time = time.time()\n",
" for step_id, data_train in enumerate(train_reader()):\n",
" data_load_costtime = time.time() - data_load_time\n",
" start_time = time.time()\n",
" avg_loss_value = exe.run(\n",
" train_prog,\n",
" feed=data_train,\n",
" fetch_list=[train_loss, train_output], \n",
" use_program_cache=True)\n",
" cost_time = time.time() - start_time\n",
" if step_id % 50 == 0:\n",
" mean_dis = calc_dist(avg_loss_value[1], data_train[0]['label'])\n",
" print(\"Pass %d, Epoch %d, Cost %f, EuDis %f, Time %f, LoadTime %f\" % (\n",
" step_id, pass_id, avg_loss_value[0], mean_dis, cost_time, data_load_costtime))\n",
" else:\n",
" pass\n",
" step += 1\n",
" data_load_time = time.time()\n",
"\n",
" avg_cost_test, avg_dist_test = train_test(val_prog, val_reader)\n",
"\n",
" print('Test with Epoch {0}, Loss {1:2.4}, EuDis {2:2.4}'.format(\n",
" pass_id, avg_cost_test[0], avg_dist_test))\n",
"\n",
" if avg_dist_test < best_dist:\n",
" best_dist = avg_dist_test\n",
" print(\"\\nBest Dis, Checkpoint Saved!\\n\")\n",
" if not os.path.isdir(params_dirname_prefix+\"_best/\"):\n",
" os.makedirs(params_dirname_prefix+\"_best/\")\n",
" fluid.io.save_persistables(exe, params_dirname_prefix+\"_best/\", main_program=train_prog)\n",
"\n",
" if not os.path.isdir(params_dirname_prefix+\"_checkpoint/\"):\n",
" os.makedirs(params_dirname_prefix+\"_checkpoint/\")\n",
" fluid.io.save_persistables(exe, params_dirname_prefix+\"_checkpoint/\", main_program=train_prog)\n",
" train_loop()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# download imagenet pretrain weight from:\n",
"# https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification\n",
"# remove ResNet34_pretrained/fc*\n",
"train(use_cuda=True, params_dirname_prefix=\"../weights/loc_fcn\", \n",
" pretrained_model=\"../weights/ResNet34_pretrained\", EPOCH_NUM=40)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
# Angle closure Glaucoma Evaluation Challenge
The goal of the challenge is to evaluate and compare automated algorithms for angle closure classification and localization of scleral spur (SS) points on a common dataset of AS-OCT images. We invite the medical image analysis community to participate by developing and testing existing and novel automated classification and segmentation methods.
More detail [AGE challenge](https://age.grand-challenge.org/Details/).
## Scleral spur localization task (FCN model)
1. Method
* Inspired by Fully Convolutional Networks (FCN), a keypoint is equivalent to 2D gaussian heatmap.
<img src="assets/1.png">
<img src="assets/2.png">
* Then, a localization task could be transformed to a heatmap regression task.
2. Prepare data
* We assume that you have downloaded data(two zip files), and store @ `../datasets/`.
* (Updated on August 5) Replace update files.
* We provide a demo about `zip file extract`, `data structure explore`, and `Train/Val split`.
3. Train
* We assume that you have download data, extract compressed files, and store @ `../datasets/`.
* Based on PaddlePaddle and [ResNet34](https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/models/resnet.py), we modify the model structure to enable FCN model, which global pooling layer and final fc layer were removed.
4. Inference
* We assume that you have download data, extract compressed files, and store @ `../datasets/`.
* We assume that you stored checkpoint files @ `../weights/loc_fcn`
* We provide a baseline about `inference` and `visualization`.
<img src="assets/3.png">
<img src="assets/4.png">
\ No newline at end of file
#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import math
import paddle
import paddle.fluid as fluid
from paddle.fluid.param_attr import ParamAttr
__all__ = ["ResNet", "ResNet18", "ResNet34", "ResNet50", "ResNet101", "ResNet152"]
train_parameters = {
"input_size": [3, 224, 224],
"input_mean": [0.485, 0.456, 0.406],
"input_std": [0.229, 0.224, 0.225],
"learning_strategy": {
"name": "piecewise_decay",
"batch_size": 256,
"epochs": [30, 60, 90],
"steps": [0.1, 0.01, 0.001, 0.0001]
}
}
class ResNet():
def __init__(self, layers=50):
self.params = train_parameters
self.layers = layers
def net(self, input, class_dim=1000):
layers = self.layers
supported_layers = [18, 34, 50, 101, 152]
assert layers in supported_layers, \
"supported layers are {} but input layer is {}".format(supported_layers, layers)
if layers == 18:
depth = [2, 2, 2, 2]
elif layers == 34 or layers == 50:
depth = [3, 4, 6, 3]
elif layers == 101:
depth = [3, 4, 23, 3]
elif layers == 152:
depth = [3, 8, 36, 3]
num_filters = [64, 128, 256, 512]
conv = self.conv_bn_layer(
input=input, num_filters=64, filter_size=7, stride=2, act='relu',name="conv1")
conv = fluid.layers.pool2d(
input=conv,
pool_size=3,
pool_stride=2,
pool_padding=1,
pool_type='max')
if layers >= 50:
for block in range(len(depth)):
for i in range(depth[block]):
if layers in [101, 152] and block == 2:
if i == 0:
conv_name="res"+str(block+2)+"a"
else:
conv_name="res"+str(block+2)+"b"+str(i)
else:
conv_name="res"+str(block+2)+chr(97+i)
conv = self.bottleneck_block(
input=conv,
num_filters=num_filters[block],
stride=2 if i == 0 and block != 0 else 1, name=conv_name)
out = conv
# pool = fluid.layers.pool2d(
# input=conv, pool_size=7, pool_type='avg', global_pooling=True)
# stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0)
# out = fluid.layers.fc(input=pool,
# size=class_dim,
# param_attr=fluid.param_attr.ParamAttr(
# initializer=fluid.initializer.Uniform(-stdv, stdv)))
else:
for block in range(len(depth)):
for i in range(depth[block]):
conv_name="res"+str(block+2)+chr(97+i)
conv = self.basic_block(
input=conv,
num_filters=num_filters[block],
stride=2 if i == 0 and block != 0 else 1,
is_first=block==i==0,
name=conv_name)
# pool = fluid.layers.pool2d(
# input=conv, pool_size=7, pool_type='avg', global_pooling=True)
# stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0)
# out = fluid.layers.fc(input=pool,
# size=class_dim,
# param_attr=fluid.param_attr.ParamAttr(
# initializer=fluid.initializer.Uniform(-stdv, stdv)))
out = conv
return out
def conv_bn_layer(self,
input,
num_filters,
filter_size,
stride=1,
groups=1,
act=None,
name=None):
conv = fluid.layers.conv2d(
input=input,
num_filters=num_filters,
filter_size=filter_size,
stride=stride,
padding=(filter_size - 1) // 2,
groups=groups,
act=None,
param_attr=ParamAttr(name=name + "_weights"),
bias_attr=False,
name=name + '.conv2d.output.1')
if name == "conv1":
bn_name = "bn_" + name
else:
bn_name = "bn" + name[3:]
return fluid.layers.batch_norm(input=conv,
act=act,
name=bn_name+'.output.1',
param_attr=ParamAttr(name=bn_name + '_scale'),
bias_attr=ParamAttr(bn_name + '_offset'),
moving_mean_name=bn_name + '_mean',
moving_variance_name=bn_name + '_variance',)
def shortcut(self, input, ch_out, stride, is_first, name):
ch_in = input.shape[1]
if ch_in != ch_out or stride != 1 or is_first == True:
return self.conv_bn_layer(input, ch_out, 1, stride, name=name)
else:
return input
def bottleneck_block(self, input, num_filters, stride, name):
conv0 = self.conv_bn_layer(
input=input, num_filters=num_filters, filter_size=1, act='relu',name=name+"_branch2a")
conv1 = self.conv_bn_layer(
input=conv0,
num_filters=num_filters,
filter_size=3,
stride=stride,
act='relu',
name=name+"_branch2b")
conv2 = self.conv_bn_layer(
input=conv1, num_filters=num_filters * 4, filter_size=1, act=None, name=name+"_branch2c")
short = self.shortcut(input, num_filters * 4, stride, is_first=False, name=name + "_branch1")
return fluid.layers.elementwise_add(x=short, y=conv2, act='relu',name=name+".add.output.5")
def basic_block(self, input, num_filters, stride, is_first, name):
conv0 = self.conv_bn_layer(input=input, num_filters=num_filters, filter_size=3, act='relu', stride=stride,
name=name+"_branch2a")
conv1 = self.conv_bn_layer(input=conv0, num_filters=num_filters, filter_size=3, act=None,
name=name+"_branch2b")
short = self.shortcut(input, num_filters, stride, is_first, name=name + "_branch1")
return fluid.layers.elementwise_add(x=short, y=conv1, act='relu')
def ResNet18():
model = ResNet(layers=18)
return model
def ResNet34():
model = ResNet(layers=34)
return model
def ResNet50():
model = ResNet(layers=50)
return model
def ResNet101():
model = ResNet(layers=101)
return model
def ResNet152():
model = ResNet(layers=152)
return model
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Angle closure Glaucoma Evaluation Challenge](https://age.grand-challenge.org/Details/)\n",
"## Scleral spur localization Baseline (RCNN)\n",
"\n",
"- To keep model training stable, images with coordinate == -1, were removed.\n",
"- For real inference, you MIGHT keep all images in val_file_path file."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Training\n",
"\n",
"- Assume `Training100.zip` and `Validation_ASOCT_Image.zip` are stored @ `./AGE_challenge Baseline/datasets/`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Download PaddleDetection\n",
" https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/PaddleDetection\n",
" \n",
"- To use Origin PaddleDetection for AGE loc task :\n",
"- Replace `PaddleDetection/configs/cascade_rcnn_r50_fpn_1x.yml` with `./cascade_rcnn_r50_fpn_1x.yml`\n",
"- Or, you might edit configs/cascade_rcnn_r50_fpn_1x.yml\n",
"\n",
"```\n",
"max_iters: 12960\n",
"snapshot_iter: 2000\n",
"LearningRate:\n",
" milestones: [6000, 8000]\n",
"```\n",
"\n",
"for more details, see [PaddleDetection Docs](https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/PaddleDetection/docs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Custom dataset (coco type)\n",
"\n",
"- coco type json files and folder architecture was constructed in pervious cell.\n",
"- Under data10461/Training100/, you need these three folders:\n",
"\n",
"```\n",
"annotations\n",
"\tinstances_train2017.json\n",
"\tinstances_val2017.json\n",
"train2017\n",
"\t***.jpg\n",
"val2017\n",
"\t***.jpg\n",
"```\n",
"\n",
"for more details, see [Data.md](https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/PaddleDetection/docs/DATA.md), [Data.md中文版](https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/PaddleDetection/docs/DATA_cn.md)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/home/aiib-mia/Desktop/shangfangxin/AGE_challenge Baseline/LocalizationRCNN/PaddleDetection\n"
]
}
],
"source": [
"!rm -rf ./PaddleDetection/dataset/coco\n",
"# you might replace this path to absolute path\n",
"!ln -sf ../../../datasets/Training100/ ./PaddleDetection/dataset/coco\n",
"%cd ./PaddleDetection"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"env: PYTHONPATH=./\n",
"./\r\n"
]
}
],
"source": [
"%set_env PYTHONPATH=./\n",
"!echo $PYTHONPATH"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# too many lines of training log, set print frequence to per 1000 steps. 12960 steps in total\n",
"!python tools/train.py -c configs/cascade_rcnn_r50_fpn_1x.yml -o log_iter=1000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
# Angle closure Glaucoma Evaluation Challenge
The goal of the challenge is to evaluate and compare automated algorithms for angle closure classification and localization of scleral spur (SS) points on a common dataset of AS-OCT images. We invite the medical image analysis community to participate by developing and testing existing and novel automated classification and segmentation methods.
More detail [AGE challenge](https://age.grand-challenge.org/Details/).
## Scleral spur localization task (RCNN model)
1. Method
* A localization task could be transformed to a object detection task.
<img src="assets/1.png">
* Then, a image could be splited into 2 parts, the right part:
<img src="assets/2.png">
2. Prepare data
* We assume that you have downloaded data(two zip files), and stored @ `../datasets/`.
* (Updated on August 5) Replace update files.
* We provide a demo about `zip file extract`, `data structure explore`, `format data to coco type` and `Train/Val split`.
3. Train
* We assume that you have downloaded data, extracted compressed files, and stored @ `../datasets/`.
* You should download or clone [PaddleModels](https://github.com/PaddlePaddle/models) manually.
* After you download whole repo, use soft link command:
```
$ git clone https://github.com/PaddlePaddle/models
$ cd models/PaddleCV/
$ ln -sf ./PaddleDetection Research/AGEchallenge/LocalizationRCNN/PaddleDetection
```
* Based on [PaddleDetection](https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/PaddleDetection), we provide a guideline to modify config file.
* You could also simply replace `PaddleDetection/configs/cascade_rcnn_r50_fpn_1x.yml` with `./cascade_rcnn_r50_fpn_1x.yml`
* `PaddleDetection/tools/train.py` will automatically download pretrain weights, default save to `~/.cache/paddle/weights/`.
* We provide a demo to call `PaddleDetection/tools/train.py`, which starts training, save checkpoint to `PaddleDetection/output/cascade_rcnn_r50_fpn_1x/`
4. Inference
* We assume that you have downloaded data, extracted compressed files, and stored @ `../datasets/`.
* We assume that you stored checkpoint files @ `PaddleDetection/output/cascade_rcnn_r50_fpn_1x/`
* We provide a demo about `inference` and `visualization`.
<img src="assets/3.png">
<img src="assets/4.png">
\ No newline at end of file
architecture: CascadeRCNN
train_feed: FasterRCNNTrainFeed
eval_feed: FasterRCNNEvalFeed
test_feed: FasterRCNNTestFeed
max_iters: 12960
snapshot_iter: 2000
use_gpu: true
log_smooth_window: 20
save_dir: output
pretrain_weights: https://paddle-imagenet-models-name.bj.bcebos.com/ResNet50_cos_pretrained.tar
weights: output/cascade_rcnn_r50_fpn_1x/model_final
metric: COCO
num_classes: 2
CascadeRCNN:
backbone: ResNet
fpn: FPN
rpn_head: FPNRPNHead
roi_extractor: FPNRoIAlign
bbox_head: CascadeBBoxHead
bbox_assigner: CascadeBBoxAssigner
ResNet:
norm_type: affine_channel
depth: 50
feature_maps: [2, 3, 4, 5]
freeze_at: 2
variant: b
FPN:
min_level: 2
max_level: 6
num_chan: 256
spatial_scale: [0.03125, 0.0625, 0.125, 0.25]
FPNRPNHead:
anchor_generator:
anchor_sizes: [32, 64, 128, 256, 512]
aspect_ratios: [0.5, 1.0, 2.0]
stride: [16.0, 16.0]
variance: [1.0, 1.0, 1.0, 1.0]
anchor_start_size: 32
min_level: 2
max_level: 6
num_chan: 256
rpn_target_assign:
rpn_batch_size_per_im: 256
rpn_fg_fraction: 0.5
rpn_positive_overlap: 0.7
rpn_negative_overlap: 0.3
rpn_straddle_thresh: 0.0
train_proposal:
min_size: 0.0
nms_thresh: 0.7
pre_nms_top_n: 2000
post_nms_top_n: 2000
test_proposal:
min_size: 0.0
nms_thresh: 0.7
pre_nms_top_n: 1000
post_nms_top_n: 1000
FPNRoIAlign:
canconical_level: 4
canonical_size: 224
min_level: 2
max_level: 5
box_resolution: 7
sampling_ratio: 2
CascadeBBoxAssigner:
batch_size_per_im: 512
bbox_reg_weights: [10, 20, 30]
bg_thresh_lo: [0.0, 0.0, 0.0]
bg_thresh_hi: [0.5, 0.6, 0.7]
fg_thresh: [0.5, 0.6, 0.7]
fg_fraction: 0.25
CascadeBBoxHead:
head: FC6FC7Head
nms:
keep_top_k: 100
nms_threshold: 0.5
score_threshold: 0.05
FC6FC7Head:
num_chan: 1024
LearningRate:
base_lr: 0.02
schedulers:
- !PiecewiseDecay
gamma: 0.1
milestones: [6000, 8000]
- !LinearWarmup
start_factor: 0.3333333333333333
steps: 500
OptimizerBuilder:
optimizer:
momentum: 0.9
type: Momentum
regularizer:
factor: 0.0001
type: L2
FasterRCNNTrainFeed:
batch_size: 2
dataset:
dataset_dir: dataset/coco
annotation: annotations/instances_train2017.json
image_dir: train2017
batch_transforms:
- !PadBatch
pad_to_stride: 32
drop_last: false
num_workers: 2
FasterRCNNEvalFeed:
batch_size: 1
dataset:
dataset_dir: dataset/coco
annotation: annotations/instances_val2017.json
image_dir: val2017
batch_transforms:
- !PadBatch
pad_to_stride: 32
FasterRCNNTestFeed:
batch_size: 1
dataset:
annotation: dataset/coco/annotations/instances_val2017.json
batch_transforms:
- !PadBatch
pad_to_stride: 32
drop_last: false
num_workers: 2
# Ref: https://github.com/waspinator/pycococreator/blob/master/examples/shapes/shapes_to_coco.py
import datetime
import os
import re
import fnmatch
import cv2
import numpy as np
INFO = {
"description": "AGE Challenge Location",
"url": "https://age.grand-challenge.org/PaddlePaddle/",
"version": "0.1.0",
"year": 2019,
"contributor": "shangfangxin@baidu.com",
"date_created": datetime.datetime.utcnow().isoformat(' ')
}
LICENSES = [
{
"id": 1,
"name": "",
"url": ""
}
]
CATEGORIES = [
{
'id': 1,
'name': 'point',
'supercategory': 'shape',
},
]
def create_image_info(image_id, file_name, image_size,
date_captured=datetime.datetime.utcnow().isoformat(' '),
license_id=1, coco_url="", flickr_url=""):
image_info = {
"id": image_id,
"file_name": file_name,
"width": image_size[1],
"height": image_size[0],
"date_captured": date_captured,
"license": license_id,
"coco_url": coco_url,
"flickr_url": flickr_url
}
return image_info
def create_annotation_info(image, annotation_id, image_id, category_info, bounding_box=None):
annotation_info = {
"id": annotation_id,
"image_id": image_id,
"category_id": category_info["id"],
"iscrowd": category_info["is_crowd"],
"area": bounding_box[2] * bounding_box[3],
"bbox": bounding_box,
"segmentation": [[]],
"width": image.shape[1],
"height": image.shape[0],
}
return annotation_info
def create_anno_info(image, point_x, point_y, image_id, category_info, segmentation_id, box_range):
bounding_box = [point_x - box_range, point_y - box_range, box_range*2, box_range*2]
return create_annotation_info(image,
segmentation_id, image_id, category_info, bounding_box)
def get_coco_dict(img_path, data_list, box_range=20):
coco_output = {
"info": INFO,
"licenses": LICENSES,
"categories": CATEGORIES,
"images": [],
"annotations": []
}
image_id = 1
segmentation_id = 1
for item in data_list:
image_filename, p_x, p_y = item
p_x, p_y = int(float(p_x)), int(float(p_y))
image_filename = os.path.join(img_path, image_filename)
image = cv2.imread(image_filename)
image_info = create_image_info(
image_id, os.path.basename(image_filename), image.shape)
coco_output["images"].append(image_info)
# filter for associated png annotations
class_id = 1
category_info = {'id': class_id, 'is_crowd': 0}
if p_x != -1 and p_y != -1:
coco_output["annotations"].append(
create_anno_info(image, p_x, p_y, image_id, category_info, segmentation_id, box_range))
segmentation_id = segmentation_id + 1
image_id = image_id + 1
return coco_output
\ No newline at end of file
# Angle closure Glaucoma Evaluation Challenge
The goal of the challenge is to evaluate and compare automated algorithms for angle closure classification and localization of scleral spur (SS) points on a common dataset of AS-OCT images. We invite the medical image analysis community to participate by developing and testing existing and novel automated classification and segmentation methods.
More detail [AGE challenge](https://age.grand-challenge.org/Details/).
## 1.Download data
After you sign up `Grand Challenge` and join the [AGE challenge](https://age.grand-challenge.org/Details/).
Dataset can be downloaded from the [Download page](https://age.grand-challenge.org/Download/)
We assume `Training100.zip` and `Validation_ASOCT_Image.zip` are stored @ `./AGE_challenge Baseline/datasets/`
## 2.Environment installation
* Python >= 3.5
* cuDNN >= 7.3
* CUDA 9
* paddlepaddle-gpu >= 1.5.0
* xlrd == 1.2.0
* tqdm == 4.32.2
* pycocotools == 2.0.0
More detail [PaddlePaddle Installation Manuals](https://www.paddlepaddle.org.cn/documentation/docs/en/1.5/beginners_guide/install/index_en.html)
## 3. Angle closure classification task
See `Classification/`.
## 4. Scleral spur localization task
We provide two baseline models for localization task.
See `LocalizationFCN/` and `LocalizationRCNN/`.
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册