├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── DATA2NPY ├── dicom2npy.py └── nii2npy.py ├── LICENSE ├── OrganSegRSTN ├── Data.py ├── _fast_functions.so ├── coarse2fine_testing.py ├── coarse_fusion.py ├── coarse_testing.py ├── fast_functions.py ├── init.py ├── model.py ├── oracle_fusion.py ├── oracle_testing.py ├── run.sh ├── training.py ├── training_parallel.py └── utils.py ├── README.md ├── SWIG_fast_functions ├── 1.npz ├── fast_functions.cc ├── fast_functions.h ├── fast_functions.i ├── numpy.i ├── setup.py └── test.py ├── icon.png └── logs ├── FD0_X3_1_20180822_204202.txt ├── FD0_Y3_1_20180822_204202.txt ├── FD0_Z3_1_20180822_204202.txt ├── FD0_coarse2fine_results.txt └── FD0_coarse_fusion_results.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | *.i linguist-language=python 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __pycache__/ 3 | build/ 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGE LOG 2 | 3 | **v1.2:** 4 | 5 | - add standard deviation of DSC in `coarse2fine_testing.py` 6 | - Our codebase is also compatible with PyTorch 0.4.1. 7 | 8 | **v1.1:** 9 | 10 | - Thank *Qihang Yu* for finding the bug which affects performance when `batch > 1` in `model.py` and having fixed it elegantly. 11 | - remove the redundant `clone()` in `model.py` 12 | 13 | **v1.0:** 14 | 15 | - make `get_parameters` in `model.py` more robust 16 | 17 | **v0.5:** 18 | 19 | - add **`logs/`** which contains training logs and testing results in `FOLD #0`. please see section 5 20 | - add **RSTN pre-trained models** in section 5 21 | - add **`oracle_testing.py` & `oracle_fusion.py`** to evaluate fine models. please see 4.6 & 4.7 22 | 23 | **v0.4:** 24 | 25 | - we introduce **`epoch` hyperparameter** to replace `max_iterations` because the size of datasets varies. 26 | - Epoch dict {2, 6, 8} for (S, I, J) is intended for NIH dataset. You may modify it according to your dataset. 27 | - **Add `training_parallel.py` to support multi-GPU training:** 28 | - please see 4.3.4 section for details. 29 | - Simplify the bilinear weight initialization in ConvTranspose layer (issue [#1](https://github.com/twni2016/OrganSegRSTN_PyTorch/issues/1)) 30 | - **Add `coarse_fusion.py`** 31 | - `training.py` & `training_parallel.py`: print **coarse/fine/average** loss, giving more information of training loss 32 | - Thank Angtian Wang and Yingwei Li for finding bugs on multi-GPU training. 33 | 34 | **v0.3:** no big improvements. 35 | 36 | **v0.2:** 37 | 38 | - `utils.py`: two faster functions `post_processing` and `DSC_computation` are re-implemented in C for python3.6 39 | - give instructions in section 4.8.3 on how to compile ` fast_functions.i` to get `_fast_functions.so` for different version of python like 3.5. 40 | - `training.py` : now trains by *iterations* instead of *epoches*, and learning rate will decay in `J` mode every 10k iterations. 41 | - performance of current version is **84.3%** in NIH dataset, which is *slightly lower* than **84.4-84.6%** in CAFFE implementation. 42 | 43 | **v0.1:** init version. 44 | 45 | ## Differences from [OrganSegRSTN](https://github.com/198808xc/OrganSegRSTN) 46 | 47 | Improvements: 48 | 49 | - We merge `indiv_training.py`, `joint_training.py` into `training.py` 50 | - We merge all `*.prototxt` to `model.py` 51 | - Our code runs almost **twice faster** than original one in CAFFE. 52 | - The *minimum* of DSC in test cases is **a little higher** (63.4%) than original minimum (62.8%). 53 | 54 | Performance: in NIH Pancreas Dataset, average DSC is **a little poorer** (84.25% - 84.45%) than original one in CAFFE (84.4% - 84.6%). 55 | -------------------------------------------------------------------------------- /DATA2NPY/dicom2npy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import dicom 4 | 5 | 6 | N = 82 7 | W = 512 8 | H = 512 9 | path1 = 'DOI' 10 | path2 = 'images' 11 | if not os.path.exists(path2): 12 | os.makedirs(path2) 13 | 14 | for n in range(N): 15 | volumeID = '{:0>4}'.format(n + 1) 16 | print 'Processing File ' + volumeID 17 | filename1 = 'PANCREAS_' + volumeID 18 | directory1 = os.path.join(path1, filename1) 19 | filename2 = volumeID + '.npy' 20 | for path_, _, file_ in os.walk(directory1): 21 | L = len(file_) 22 | if L > 0: 23 | print ' ' + str(L) + ' slices along the axial view.' 24 | data = np.zeros((W, H, L), dtype = np.int16) 25 | for f in sorted(file_): 26 | file1 = os.path.abspath(os.path.join(path_, f)) 27 | image = dicom.read_file(file1) 28 | sliceID = image.data_element("InstanceNumber").value - 1 29 | if image.pixel_array.shape[0] <> 512 or image.pixel_array.shape[1] <> 512: 30 | exit(' Error: DICOM image does not fit ' + str(W) + 'x' + str(H) + ' size!') 31 | data[:, :, sliceID] = image.pixel_array 32 | file2 = os.path.join(path2, filename2) 33 | np.save(file2, data) 34 | print 'File ' + volumeID + ' is saved in ' + file2 + ' .' 35 | 36 | -------------------------------------------------------------------------------- /DATA2NPY/nii2npy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import nibabel 4 | 5 | 6 | N = 82 7 | W = 512 8 | H = 512 9 | path1 = 'TCIA_pancreas_labels-02-05-2017' 10 | path2 = 'labels' 11 | if not os.path.exists(path2): 12 | os.makedirs(path2) 13 | 14 | for n in range(N): 15 | volumeID = '{:0>4}'.format(n + 1) 16 | print 'Processing File ' + volumeID 17 | filename1 = 'label' + volumeID + '.nii.gz' 18 | directory1 = os.path.join(path1, filename1) 19 | filename2 = volumeID + '.npy' 20 | file1 = os.path.join(path1, filename1) 21 | data = nibabel.load(file1).get_data().transpose(1, 0, 2) 22 | print ' Data shape is ' + str(data.shape) + ' .' 23 | file2 = os.path.join(path2, filename2) 24 | np.save(file2, data) 25 | print 'File ' + volumeID + ' is saved in ' + file2 + ' .' 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tianwei Ni 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OrganSegRSTN/Data.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import numpy as np 4 | import torch 5 | import torch.utils.data as data 6 | from utils import * 7 | 8 | class DataLayer(data.Dataset): 9 | def __init__(self, data_path, current_fold, organ_number, low_range, high_range, \ 10 | slice_threshold, slice_thickness, organ_ID, plane): 11 | self.low_range = low_range 12 | self.high_range = high_range 13 | self.slice_thickness = slice_thickness 14 | self.organ_ID = organ_ID 15 | 16 | image_list = open(training_set_filename(current_fold), 'r').read().splitlines() 17 | self.training_image_set = np.zeros((len(image_list)), dtype = np.int) 18 | for i in range(len(image_list)): 19 | s = image_list[i].split(' ') 20 | self.training_image_set[i] = int(s[0]) 21 | slice_list = open(list_training[plane], 'r').read().splitlines() 22 | self.slices = len(slice_list) 23 | self.image_ID = np.zeros((self.slices), dtype = np.int) 24 | self.slice_ID = np.zeros((self.slices), dtype = np.int) 25 | self.image_filename = ['' for l in range(self.slices)] 26 | self.label_filename = ['' for l in range(self.slices)] 27 | self.average = np.zeros((self.slices)) 28 | self.pixels = np.zeros((self.slices), dtype = np.int) 29 | 30 | for l in range(self.slices): 31 | s = slice_list[l].split(' ') 32 | self.image_ID[l] = s[0] 33 | self.slice_ID[l] = s[1] 34 | self.image_filename[l] = s[2] # important 35 | self.label_filename[l] = s[3] # important 36 | self.average[l] = float(s[4]) # pixel value avg 37 | self.pixels[l] = int(s[organ_ID * 5]) # sum of label 38 | if slice_threshold <= 1: # 0.98 39 | pixels_index = sorted(range(self.slices), key = lambda l: self.pixels[l]) 40 | last_index = int(math.floor((self.pixels > 0).sum() * slice_threshold)) 41 | min_pixels = self.pixels[pixels_index[-last_index]] 42 | else: # or set up directly 43 | min_pixels = slice_threshold 44 | self.active_index = [l for l, p in enumerate(self.pixels) 45 | if p >= min_pixels and self.image_ID[l] in self.training_image_set] # true active 46 | 47 | def __getitem__(self, index): 48 | self.index1 = self.active_index[index] 49 | self.index0 = self.index1 - 1 50 | if self.index1 == 0 or self.slice_ID[self.index0] != self.slice_ID[self.index1] - 1: 51 | self.index0 = self.index1 52 | self.index2 = self.index1 + 1 53 | if self.index1 == self.slices - 1 or self.slice_ID[self.index2] != self.slice_ID[self.index1] + 1: 54 | self.index2 = self.index1 55 | 56 | self.data, self.label = self.load_data() 57 | return torch.from_numpy(self.data), torch.from_numpy(self.label) 58 | 59 | def __len__(self): 60 | return len(self.active_index) 61 | 62 | def load_data(self): 63 | if self.slice_thickness == 1: 64 | image1 = np.load(self.image_filename[self.index1]).astype(np.float32) 65 | label1 = np.load(self.label_filename[self.index1]) 66 | width = label1.shape[0] 67 | height = label1.shape[1] 68 | image = np.repeat(image1.reshape(1, width, height), 3, axis = 0) 69 | label = label1.reshape(1, width, height) 70 | elif self.slice_thickness == 3: 71 | image0 = np.load(self.image_filename[self.index0]) 72 | width = image0.shape[0] 73 | height = image0.shape[1] 74 | image = np.zeros((3, width, height), dtype = np.float32) 75 | image[0, ...] = image0 76 | image[1, ...] = np.load(self.image_filename[self.index1]) 77 | image[2, ...] = np.load(self.image_filename[self.index2]) 78 | label = np.zeros((3, width, height), dtype = np.uint8) 79 | label[0, ...] = np.load(self.label_filename[self.index0]) 80 | label[1, ...] = np.load(self.label_filename[self.index1]) 81 | label[2, ...] = np.load(self.label_filename[self.index2]) 82 | np.minimum(np.maximum(image, self.low_range, image), self.high_range, image) 83 | image -= self.low_range 84 | image /= (self.high_range - self.low_range) 85 | label = is_organ(label, self.organ_ID).astype(np.uint8) 86 | return image, label 87 | -------------------------------------------------------------------------------- /OrganSegRSTN/_fast_functions.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twni2016/OrganSegRSTN_PyTorch/fb51b919448353b52883b550891503fbd0ad0e58/OrganSegRSTN/_fast_functions.so -------------------------------------------------------------------------------- /OrganSegRSTN/coarse2fine_testing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import time 5 | from utils import * 6 | from model import * 7 | 8 | 9 | data_path = sys.argv[1] 10 | current_fold = int(sys.argv[2]) 11 | organ_number = int(sys.argv[3]) 12 | low_range = int(sys.argv[4]) 13 | high_range = int(sys.argv[5]) 14 | slice_threshold = float(sys.argv[6]) 15 | slice_thickness = int(sys.argv[7]) 16 | organ_ID = int(sys.argv[8]) 17 | GPU_ID = int(sys.argv[9]) 18 | learning_rate1 = float(sys.argv[10]) 19 | learning_rate_m1 = int(sys.argv[11]) 20 | learning_rate2 = float(sys.argv[12]) 21 | learning_rate_m2 = int(sys.argv[13]) 22 | crop_margin = int(sys.argv[14]) 23 | 24 | fine_snapshot_path = os.path.join(snapshot_path, 'SIJ_training_' + \ 25 | sys.argv[10] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 26 | coarse_result_path = os.path.join(result_path, 'coarse_testing_' + \ 27 | sys.argv[10] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 28 | coarse2fine_result_path = os.path.join(result_path, 'coarse2fine_testing_' + \ 29 | sys.argv[10] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 30 | 31 | epoch = 'e' + sys.argv[15] + sys.argv[16] + sys.argv[17] + sys.argv[18] 32 | epoch_list = [epoch] 33 | coarse_threshold = float(sys.argv[19]) 34 | fine_threshold = float(sys.argv[20]) 35 | max_rounds = int(sys.argv[21]) 36 | timestamp = {} 37 | timestamp['X'] = sys.argv[22] 38 | timestamp['Y'] = sys.argv[23] 39 | timestamp['Z'] = sys.argv[24] 40 | 41 | volume_list = open(testing_set_filename(current_fold), 'r').read().splitlines() 42 | while volume_list[len(volume_list) - 1] == '': 43 | volume_list.pop() 44 | 45 | print('Looking for snapshots:') 46 | fine_snapshot_ = {} 47 | fine_snapshot_name_ = {} 48 | for plane in ['X', 'Y', 'Z']: 49 | fine_snapshot_name = snapshot_name_from_timestamp(fine_snapshot_path, \ 50 | current_fold, plane, 'J', slice_thickness, organ_ID, timestamp[plane]) 51 | if fine_snapshot_name == '': 52 | exit(' Error: no valid snapshot directories are detected!') 53 | fine_snapshot_directory = os.path.join(fine_snapshot_path, fine_snapshot_name) 54 | print(' Snapshot directory 1 for plane ' + plane + ': ' + fine_snapshot_directory + ' .') 55 | fine_snapshot = [fine_snapshot_directory] 56 | print(' ' + str(len(fine_snapshot)) + ' snapshots are to be evaluated.') 57 | for t in range(len(fine_snapshot)): 58 | print(' Snapshot #' + str(t + 1) + ': ' + fine_snapshot[t] + ' .') 59 | fine_snapshot_[plane] = fine_snapshot 60 | fine_snapshot_name = fine_snapshot_name.split(':')[1] 61 | fine_snapshot_name_[plane] = fine_snapshot_name.split('.')[0] 62 | 63 | print('In the coarse stage:') 64 | coarse_result_name_ = {} 65 | coarse_result_directory_ = {} 66 | for plane in ['X', 'Y', 'Z']: 67 | coarse_result_name__ = result_name_from_timestamp(coarse_result_path, current_fold, \ 68 | plane, 'J', slice_thickness, organ_ID, volume_list, timestamp[plane]) 69 | if coarse_result_name__ == '': 70 | exit(' Error: no valid result directories are detected!') 71 | coarse_result_directory__ = os.path.join(coarse_result_path, coarse_result_name__, 'volumes') 72 | print(' Result directory for plane ' + plane + ': ' + coarse_result_directory__ + ' .') 73 | if coarse_result_name__.startswith('FD'): 74 | index_ = coarse_result_name__.find(':') 75 | coarse_result_name__ = coarse_result_name__[index_ + 1: ] 76 | coarse_result_name_[plane] = coarse_result_name__ 77 | coarse_result_directory_[plane] = coarse_result_directory__ 78 | 79 | coarse2fine_result_name = 'FD' + str(current_fold) + ':' + \ 80 | fine_snapshot_name_['X'] + ',' + \ 81 | fine_snapshot_name_['Y'] + ',' + \ 82 | fine_snapshot_name_['Z'] + ':' + \ 83 | epoch + '_' + str(coarse_threshold) + '_' + \ 84 | str(fine_threshold) + ',' + str(max_rounds) 85 | coarse2fine_result_directory = os.path.join( \ 86 | coarse2fine_result_path, coarse2fine_result_name, 'volumes') 87 | 88 | finished = np.ones((len(volume_list)), dtype = np.int) 89 | for i in range(len(volume_list)): 90 | for r in range(max_rounds + 1): 91 | volume_file = volume_filename_coarse2fine(coarse2fine_result_directory, r, i) 92 | if not os.path.isfile(volume_file): 93 | finished[i] = 0 94 | break 95 | finished_all = (finished.sum() == len(volume_list)) 96 | if finished_all: 97 | exit() 98 | 99 | os.environ["CUDA_VISIBLE_DEVICES"]= str(GPU_ID) 100 | net_ = {} 101 | for plane in ['X', 'Y', 'Z']: 102 | net_[plane] = [] 103 | for t in range(len(epoch_list)): 104 | net = RSTN(crop_margin=crop_margin, TEST='F').cuda() 105 | net.load_state_dict(torch.load(fine_snapshot_[plane][t])) 106 | net.eval() 107 | net_[plane].append(net) 108 | 109 | DSC = np.zeros((max_rounds + 1, len(volume_list))) 110 | DSC_90 = np.zeros((len(volume_list))) 111 | DSC_95 = np.zeros((len(volume_list))) 112 | DSC_98 = np.zeros((len(volume_list))) 113 | DSC_99 = np.zeros((len(volume_list))) 114 | coarse2fine_result_directory = os.path.join(coarse2fine_result_path, \ 115 | coarse2fine_result_name, 'volumes') 116 | if not os.path.exists(coarse2fine_result_directory): 117 | os.makedirs(coarse2fine_result_directory) 118 | coarse2fine_result_file = os.path.join(coarse2fine_result_path, \ 119 | coarse2fine_result_name, 'results.txt') 120 | output = open(coarse2fine_result_file, 'w') 121 | output.close() 122 | output = open(coarse2fine_result_file, 'a+') 123 | output.write('Fusing results of ' + str(len(epoch_list)) + ' snapshots:\n') 124 | output.close() 125 | 126 | for i in range(len(volume_list)): 127 | start_time = time.time() 128 | print('Testing ' + str(i + 1) + ' out of ' + str(len(volume_list)) + ' testcases.') 129 | output = open(coarse2fine_result_file, 'a+') 130 | output.write(' Testcase ' + str(i + 1) + ':\n') 131 | output.close() 132 | s = volume_list[i].split(' ') 133 | label = np.load(s[2]) 134 | label = is_organ(label, organ_ID).astype(np.uint8) 135 | finished = True 136 | for r in range(max_rounds + 1): 137 | volume_file = volume_filename_coarse2fine(coarse2fine_result_directory, r, i) 138 | if not os.path.isfile(volume_file): 139 | finished = False 140 | break 141 | if not finished: 142 | image = np.load(s[1]).astype(np.float32) 143 | np.minimum(np.maximum(image, low_range, image), high_range, image) 144 | image -= low_range 145 | image /= (high_range - low_range) 146 | imageX = image 147 | imageY = image.transpose(1, 0, 2).copy() 148 | imageZ = image.transpose(2, 0, 1).copy() 149 | print(' Data loading is finished: ' + str(time.time() - start_time) + ' second(s) elapsed.') 150 | for r in range(max_rounds + 1): 151 | print(' Iteration round ' + str(r) + ':') 152 | volume_file = volume_filename_coarse2fine(coarse2fine_result_directory, r, i) 153 | if not finished: 154 | if r == 0: # coarse majority voting 155 | pred_ = np.zeros(label.shape, dtype = np.float32) 156 | for plane in ['X', 'Y', 'Z']: 157 | for t in range(len(epoch_list)): 158 | volume_file_ = volume_filename_testing( \ 159 | coarse_result_directory_[plane], epoch_list[t], i) 160 | volume_data = np.load(volume_file_) 161 | pred_ += volume_data['volume'] 162 | pred_ /= (255 * len(epoch_list) * 3) 163 | print(' Fusion is finished: ' + \ 164 | str(time.time() - start_time) + ' second(s) elapsed.') 165 | else: 166 | mask_sumX = np.sum(mask, axis = (1, 2)) 167 | if mask_sumX.sum() == 0: 168 | continue 169 | mask_sumY = np.sum(mask, axis = (0, 2)) 170 | mask_sumZ = np.sum(mask, axis = (0, 1)) 171 | scoreX = score 172 | scoreY = score.transpose(1, 0, 2).copy() 173 | scoreZ = score.transpose(2, 0, 1).copy() 174 | maskX = mask 175 | maskY = mask.transpose(1, 0, 2).copy() 176 | maskZ = mask.transpose(2, 0, 1).copy() 177 | pred_ = np.zeros(label.shape, dtype = np.float32) 178 | for plane in ['X', 'Y', 'Z']: 179 | for t in range(len(epoch_list)): 180 | net = net_[plane][t] 181 | minR = 0 182 | if plane == 'X': 183 | maxR = label.shape[0] 184 | shape_ = (1, 3, image.shape[1], image.shape[2]) 185 | pred__ = np.zeros((image.shape[0], image.shape[1], image.shape[2]), \ 186 | dtype = np.float32) 187 | elif plane == 'Y': 188 | maxR = label.shape[1] 189 | shape_ = (1, 3, image.shape[0], image.shape[2]) 190 | pred__ = np.zeros((image.shape[1], image.shape[0], image.shape[2]), \ 191 | dtype = np.float32) 192 | elif plane == 'Z': 193 | maxR = label.shape[2] 194 | shape_ = (1, 3, image.shape[0], image.shape[1]) 195 | pred__ = np.zeros((image.shape[2], image.shape[0], image.shape[1]), \ 196 | dtype = np.float32) 197 | for j in range(minR, maxR): 198 | if slice_thickness == 1: 199 | sID = [j, j, j] 200 | elif slice_thickness == 3: 201 | sID = [max(minR, j - 1), j, min(maxR - 1, j + 1)] 202 | if (plane == 'X' and mask_sumX[sID].sum() == 0) or \ 203 | (plane == 'Y' and mask_sumY[sID].sum() == 0) or \ 204 | (plane == 'Z' and mask_sumZ[sID].sum() == 0): 205 | continue 206 | if plane == 'X': 207 | image_ = imageX[sID, :, :] 208 | score_ = scoreX[sID, :, :] 209 | mask_ = maskX[sID, :, :] 210 | elif plane == 'Y': 211 | image_ = imageY[sID, :, :] 212 | score_ = scoreY[sID, :, :] 213 | mask_ = maskY[sID, :, :] 214 | elif plane == 'Z': 215 | image_ = imageZ[sID, :, :] 216 | score_ = scoreZ[sID, :, :] 217 | mask_ = maskZ[sID, :, :] 218 | 219 | image_ = image_.reshape(1, 3, image_.shape[1], image_.shape[2]) 220 | score_ = score_.reshape(1, 3, score_.shape[1], score_.shape[2]) 221 | mask_ = mask_.reshape(1, 3, mask_.shape[1], mask_.shape[2]) 222 | image_ = torch.from_numpy(image_).cuda().float() 223 | score_ = torch.from_numpy(score_).cuda().float() 224 | mask_ = torch.from_numpy(mask_).cuda().float() 225 | out = net(image_, score=score_, mask=mask_).data.cpu().numpy()[0, :, :, :] 226 | 227 | if slice_thickness == 1: 228 | pred__[j, :, :] = out 229 | elif slice_thickness == 3: 230 | if j == minR: 231 | pred__[minR: minR + 2, :, :] += out[1: 3, :, :] 232 | elif j == maxR - 1: 233 | pred__[maxR - 2: maxR, :, :] += out[0: 2, :, :] 234 | else: 235 | pred__[j - 1: j + 2, :, :] += out 236 | if slice_thickness == 3: 237 | pred__[minR, :, :] /= 2 238 | pred__[minR + 1: maxR - 1, :, :] /= 3 239 | pred__[maxR - 1, :, :] /= 2 240 | print(' Testing on plane ' + plane + ' and snapshot ' + str(t + 1) + \ 241 | ' is finished: ' + str(time.time() - start_time) + \ 242 | ' second(s) elapsed.') 243 | if plane == 'X': 244 | pred_ += pred__ 245 | elif plane == 'Y': 246 | pred_ += pred__.transpose(1, 0, 2) 247 | elif plane == 'Z': 248 | pred_ += pred__.transpose(1, 2, 0) 249 | pred_ /= (len(epoch_list) * 3) 250 | print(' Testing is finished: ' + \ 251 | str(time.time() - start_time) + ' second(s) elapsed.') 252 | 253 | pred = (pred_ >= fine_threshold).astype(np.uint8) 254 | if r > 0: 255 | pred = post_processing(pred, pred, 0.5, organ_ID) 256 | np.savez_compressed(volume_file, volume = pred) 257 | else: 258 | volume_data = np.load(volume_file) 259 | pred = volume_data['volume'].astype(np.uint8) 260 | print(' Testing result is loaded: ' + \ 261 | str(time.time() - start_time) + ' second(s) elapsed.') 262 | 263 | DSC[r, i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred) 264 | print(' DSC = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 265 | str(label_sum) + ') = ' + str(DSC[r, i]) + ' .') 266 | output = open(coarse2fine_result_file, 'a+') 267 | output.write(' Round ' + str(r) + ', ' + 'DSC = 2 * ' + str(inter_sum) + ' / (' + \ 268 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC[r, i]) + ' .\n') 269 | output.close() 270 | 271 | if pred_sum == 0 and label_sum == 0: 272 | DSC[r, i] = 0 273 | if r > 0: 274 | inter_DSC, inter_sum, pred_sum, label_sum = DSC_computation(mask, pred) 275 | if pred_sum == 0 and label_sum == 0: 276 | inter_DSC = 1 277 | print(' Inter-iteration DSC = 2 * ' + str(inter_sum) + ' / (' + \ 278 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(inter_DSC) + ' .') 279 | output = open(coarse2fine_result_file, 'a+') 280 | output.write(' Inter-iteration DSC = 2 * ' + str(inter_sum) + ' / (' + \ 281 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(inter_DSC) + ' .\n') 282 | output.close() 283 | if DSC_90[i] == 0 and (r == max_rounds or inter_DSC >= 0.90): 284 | DSC_90[i] = DSC[r, i] 285 | if DSC_95[i] == 0 and (r == max_rounds or inter_DSC >= 0.95): 286 | DSC_95[i] = DSC[r, i] 287 | if DSC_98[i] == 0 and (r == max_rounds or inter_DSC >= 0.98): 288 | DSC_98[i] = DSC[r, i] 289 | if DSC_99[i] == 0 and (r == max_rounds or inter_DSC >= 0.99): 290 | DSC_99[i] = DSC[r, i] 291 | if r <= max_rounds: 292 | if not finished: 293 | score = pred_ # [0,1] 294 | mask = pred # {0,1} after postprocessing 295 | 296 | for r in range(max_rounds + 1): 297 | print('Round ' + str(r) + ', ' + 'Average DSC = ' + str(np.mean(DSC[r, :])) + ' .') 298 | output = open(coarse2fine_result_file, 'a+') 299 | output.write('Round ' + str(r) + ', ' + 'Average DSC = ' + str(np.mean(DSC[r, :])) + ' .\n') 300 | output.close() 301 | 302 | print('DSC threshold = 0.90, ' + 'Average DSC = ' + str(np.mean(DSC_90)) + ' std = ' + str(np.std(DSC_90)) + ' .') 303 | print('DSC threshold = 0.95, ' + 'Average DSC = ' + str(np.mean(DSC_95)) + ' std = ' + str(np.std(DSC_95)) + ' .') 304 | print('DSC threshold = 0.98, ' + 'Average DSC = ' + str(np.mean(DSC_98)) + ' std = ' + str(np.std(DSC_98)) + ' .') 305 | print('DSC threshold = 0.99, ' + 'Average DSC = ' + str(np.mean(DSC_99)) + ' std = ' + str(np.std(DSC_99)) + ' .') 306 | output = open(coarse2fine_result_file, 'a+') 307 | output.write('DSC threshold = 0.90, ' + 'Average DSC = ' + str(np.mean(DSC_90)) + ' std = ' + str(np.std(DSC_90)) + ' .\n') 308 | output.write('DSC threshold = 0.95, ' + 'Average DSC = ' + str(np.mean(DSC_95)) + ' std = ' + str(np.std(DSC_95)) + ' .\n') 309 | output.write('DSC threshold = 0.98, ' + 'Average DSC = ' + str(np.mean(DSC_98)) + ' std = ' + str(np.std(DSC_98)) + ' .\n') 310 | output.write('DSC threshold = 0.99, ' + 'Average DSC = ' + str(np.mean(DSC_99)) + ' std = ' + str(np.std(DSC_99)) + ' .\n') 311 | output.close() 312 | print('The coarse-to-fine testing process is finished.') 313 | -------------------------------------------------------------------------------- /OrganSegRSTN/coarse_fusion.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import time 5 | from utils import * 6 | 7 | 8 | data_path = sys.argv[1] 9 | current_fold = int(sys.argv[2]) 10 | organ_number = int(sys.argv[3]) 11 | low_range = int(sys.argv[4]) 12 | high_range = int(sys.argv[5]) 13 | slice_threshold = float(sys.argv[6]) 14 | slice_thickness = int(sys.argv[7]) 15 | organ_ID = int(sys.argv[8]) 16 | GPU_ID = int(sys.argv[9]) 17 | learning_rate1 = float(sys.argv[10]) 18 | learning_rate_m1 = int(sys.argv[11]) 19 | learning_rate2 = float(sys.argv[12]) 20 | learning_rate_m2 = int(sys.argv[13]) 21 | crop_margin = int(sys.argv[14]) 22 | result_path = os.path.join(result_path, 'coarse_testing_' + \ 23 | sys.argv[10] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 24 | epoch = 'e' + sys.argv[15] + sys.argv[16] + sys.argv[17] + sys.argv[18] 25 | epoch_list = [epoch] 26 | threshold = float(sys.argv[19]) 27 | timestamp = {} 28 | timestamp['X'] = sys.argv[20] 29 | timestamp['Y'] = sys.argv[21] 30 | timestamp['Z'] = sys.argv[22] 31 | 32 | volume_list = open(testing_set_filename(current_fold), 'r').read().splitlines() 33 | while volume_list[len(volume_list) - 1] == '': 34 | volume_list.pop() 35 | 36 | result_name_ = {} 37 | result_directory_ = {} 38 | for plane in ['X', 'Y', 'Z']: 39 | result_name__ = result_name_from_timestamp(result_path, current_fold, \ 40 | plane, 'J', slice_thickness, organ_ID, volume_list, timestamp[plane]) 41 | if result_name__ == '': 42 | exit(' Error: no valid result directories are detected!') 43 | result_directory__ = os.path.join(result_path, result_name__, 'volumes') 44 | print(' Result directory for plane ' + plane + ': ' + result_directory__ + ' .') 45 | if result_name__.startswith('FD'): 46 | index_ = result_name__.find(':') 47 | result_name__ = result_name__[index_ + 1: ] 48 | result_name_[plane] = result_name__ 49 | result_directory_[plane] = result_directory__ 50 | 51 | DSC_X = np.zeros((len(volume_list))) 52 | DSC_Y = np.zeros((len(volume_list))) 53 | DSC_Z = np.zeros((len(volume_list))) 54 | DSC_F1 = np.zeros((len(volume_list))) 55 | DSC_F2 = np.zeros((len(volume_list))) 56 | DSC_F3 = np.zeros((len(volume_list))) 57 | DSC_F1P = np.zeros((len(volume_list))) 58 | DSC_F2P = np.zeros((len(volume_list))) 59 | DSC_F3P = np.zeros((len(volume_list))) 60 | 61 | result_name = 'FD' + str(current_fold) + ':' + 'fusion:' + result_name_['X'] + ',' + \ 62 | result_name_['Y'] + ',' + result_name_['Z'] + '_' + epoch + ',' + str(threshold) 63 | result_directory = os.path.join(result_path, result_name, 'volumes') 64 | if not os.path.exists(result_directory): 65 | os.makedirs(result_directory) 66 | 67 | result_file = os.path.join(result_path, result_name, 'results.txt') 68 | output = open(result_file, 'w') 69 | output.close() 70 | output = open(result_file, 'a+') 71 | output.write('Fusing results of ' + str(len(epoch_list)) + ' snapshots:\n') 72 | output.close() 73 | 74 | for i in range(len(volume_list)): 75 | start_time = time.time() 76 | print('Testing ' + str(i + 1) + ' out of ' + str(len(volume_list)) + ' testcases.') 77 | output = open(result_file, 'a+') 78 | output.write(' Testcase ' + str(i + 1) + ':\n') 79 | output.close() 80 | s = volume_list[i].split(' ') 81 | label = np.load(s[2]) 82 | label = is_organ(label, organ_ID).astype(np.uint8) 83 | 84 | for plane in ['X', 'Y', 'Z']: 85 | volume_file = volume_filename_fusion(result_directory, plane, i) 86 | pred = np.zeros(label.shape, dtype = np.float32) 87 | for t in range(len(epoch_list)): 88 | volume_file_ = volume_filename_testing(result_directory_[plane], epoch_list[t], i) 89 | pred += np.load(volume_file_)['volume'] 90 | pred_ = (pred >= threshold * 255 * len(epoch_list)) 91 | if not os.path.isfile(volume_file): 92 | np.savez_compressed(volume_file, volume = pred_) 93 | DSC_, inter_sum, pred_sum, label_sum = DSC_computation(label, pred_) 94 | print(' DSC_' + plane + ' = 2 * ' + str(inter_sum) + ' / (' + \ 95 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_) + ' .') 96 | output = open(result_file, 'a+') 97 | output.write(' DSC_' + plane + ' = 2 * ' + str(inter_sum) + ' / (' + \ 98 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_) + ' .\n') 99 | output.close() 100 | if pred_sum == 0 and label_sum == 0: 101 | DSC_ = 0 102 | pred /= (255 * len(epoch_list)) 103 | if plane == 'X': 104 | pred_X = pred 105 | DSC_X[i] = DSC_ 106 | elif plane == 'Y': 107 | pred_Y = pred 108 | DSC_Y[i] = DSC_ 109 | elif plane == 'Z': 110 | pred_Z = pred 111 | DSC_Z[i] = DSC_ 112 | 113 | volume_file_F1 = volume_filename_fusion(result_directory, 'F1', i) 114 | volume_file_F2 = volume_filename_fusion(result_directory, 'F2', i) 115 | volume_file_F3 = volume_filename_fusion(result_directory, 'F3', i) 116 | 117 | if not os.path.isfile(volume_file_F1) or not os.path.isfile(volume_file_F2) or \ 118 | not os.path.isfile(volume_file_F3): 119 | pred_total = pred_X + pred_Y + pred_Z 120 | if os.path.isfile(volume_file_F1): 121 | pred_F1 = np.load(volume_file_F1)['volume'].astype(np.uint8) 122 | else: 123 | pred_F1 = (pred_total >= 0.5).astype(np.uint8) 124 | np.savez_compressed(volume_file_F1, volume = pred_F1) 125 | DSC_F1[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F1) 126 | print(' DSC_F1 = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' \ 127 | + str(label_sum) + ') = ' + str(DSC_F1[i]) + ' .') 128 | output = open(result_file, 'a+') 129 | output.write(' DSC_F1 = 2 * ' + str(inter_sum) + ' / (' + \ 130 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F1[i]) + ' .\n') 131 | output.close() 132 | if pred_sum == 0 and label_sum == 0: 133 | DSC_F1[i] = 0 134 | 135 | if os.path.isfile(volume_file_F2): 136 | pred_F2 = np.load(volume_file_F2)['volume'].astype(np.uint8) 137 | else: 138 | pred_F2 = (pred_total >= 1.5).astype(np.uint8) 139 | np.savez_compressed(volume_file_F2, volume = pred_F2) 140 | DSC_F2[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F2) 141 | print(' DSC_F2 = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 142 | str(label_sum) + ') = ' + str(DSC_F2[i]) + ' .') 143 | output = open(result_file, 'a+') 144 | output.write(' DSC_F2 = 2 * ' + str(inter_sum) + ' / (' + \ 145 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F2[i]) + ' .\n') 146 | output.close() 147 | if pred_sum == 0 and label_sum == 0: 148 | DSC_F2[i] = 0 149 | 150 | if os.path.isfile(volume_file_F3): 151 | pred_F3 = np.load(volume_file_F3)['volume'].astype(np.uint8) 152 | else: 153 | pred_F3 = (pred_total >= 2.5).astype(np.uint8) 154 | np.savez_compressed(volume_file_F3, volume = pred_F3) 155 | DSC_F3[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F3) 156 | print(' DSC_F3 = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 157 | str(label_sum) + ') = ' + str(DSC_F3[i]) + ' .') 158 | output = open(result_file, 'a+') 159 | output.write(' DSC_F3 = 2 * ' + str(inter_sum) + ' / (' + \ 160 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F3[i]) + ' .\n') 161 | output.close() 162 | if pred_sum == 0 and label_sum == 0: 163 | DSC_F3[i] = 0 164 | 165 | volume_file_F1P = volume_filename_fusion(result_directory, 'F1P', i) 166 | volume_file_F2P = volume_filename_fusion(result_directory, 'F2P', i) 167 | volume_file_F3P = volume_filename_fusion(result_directory, 'F3P', i) 168 | S = pred_F3 169 | if (S.sum() == 0): 170 | S = pred_F2 171 | if (S.sum() == 0): 172 | S = pred_F1 173 | 174 | if os.path.isfile(volume_file_F1P): 175 | pred_F1P = np.load(volume_file_F1P)['volume'].astype(np.uint8) 176 | else: 177 | pred_F1P = post_processing(pred_F1, S, 0.5, organ_ID) 178 | np.savez_compressed(volume_file_F1P, volume = pred_F1P) 179 | DSC_F1P[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F1P) 180 | print(' DSC_F1P = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 181 | str(label_sum) + ') = ' + str(DSC_F1P[i]) + ' .') 182 | output = open(result_file, 'a+') 183 | output.write(' DSC_F1P = 2 * ' + str(inter_sum) + ' / (' + \ 184 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F1P[i]) + ' .\n') 185 | output.close() 186 | if pred_sum == 0 and label_sum == 0: 187 | DSC_F1P[i] = 0 188 | 189 | if os.path.isfile(volume_file_F2P): 190 | pred_F2P = np.load(volume_file_F2P)['volume'].astype(np.uint8) 191 | else: 192 | pred_F2P = post_processing(pred_F2, S, 0.5, organ_ID) 193 | np.savez_compressed(volume_file_F2P, volume = pred_F2P) 194 | DSC_F2P[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F2P) 195 | print(' DSC_F2P = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 196 | str(label_sum) + ') = ' + str(DSC_F2P[i]) + ' .') 197 | output = open(result_file, 'a+') 198 | output.write(' DSC_F2P = 2 * ' + str(inter_sum) + ' / (' + \ 199 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F2P[i]) + ' .\n') 200 | output.close() 201 | if pred_sum == 0 and label_sum == 0: 202 | DSC_F2P[i] = 0 203 | 204 | if os.path.isfile(volume_file_F3P): 205 | pred_F3P = np.load(volume_file_F3P)['volume'].astype(np.uint8) 206 | else: 207 | pred_F3P = post_processing(pred_F3, S, 0.5, organ_ID) 208 | np.savez_compressed(volume_file_F3P, volume = pred_F3P) 209 | DSC_F3P[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F3P) 210 | print(' DSC_F3P = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 211 | str(label_sum) + ') = ' + str(DSC_F3P[i]) + ' .') 212 | output = open(result_file, 'a+') 213 | output.write(' DSC_F3P = 2 * ' + str(inter_sum) + ' / (' + \ 214 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F3P[i]) + ' .\n') 215 | output.close() 216 | if pred_sum == 0 and label_sum == 0: 217 | DSC_F3P[i] = 0 218 | 219 | pred_X = None 220 | pred_Y = None 221 | pred_Z = None 222 | pred_F1 = None 223 | pred_F2 = None 224 | pred_F3 = None 225 | pred_F1P = None 226 | pred_F2P = None 227 | pred_F3P = None 228 | 229 | output = open(result_file, 'a+') 230 | print('Average DSC_X = ' + str(np.mean(DSC_X)) + ' .') 231 | output.write('Average DSC_X = ' + str(np.mean(DSC_X)) + ' .\n') 232 | print('Average DSC_Y = ' + str(np.mean(DSC_Y)) + ' .') 233 | output.write('Average DSC_Y = ' + str(np.mean(DSC_Y)) + ' .\n') 234 | print('Average DSC_Z = ' + str(np.mean(DSC_Z)) + ' .') 235 | output.write('Average DSC_Z = ' + str(np.mean(DSC_Z)) + ' .\n') 236 | print('Average DSC_F1 = ' + str(np.mean(DSC_F1)) + ' .') 237 | output.write('Average DSC_F1 = ' + str(np.mean(DSC_F1)) + ' .\n') 238 | print('Average DSC_F2 = ' + str(np.mean(DSC_F2)) + ' .') 239 | output.write('Average DSC_F2 = ' + str(np.mean(DSC_F2)) + ' .\n') 240 | print('Average DSC_F3 = ' + str(np.mean(DSC_F3)) + ' .') 241 | output.write('Average DSC_F3 = ' + str(np.mean(DSC_F3)) + ' .\n') 242 | print('Average DSC_F1P = ' + str(np.mean(DSC_F1P)) + ' .') 243 | output.write('Average DSC_F1P = ' + str(np.mean(DSC_F1P)) + ' .\n') 244 | print('Average DSC_F2P = ' + str(np.mean(DSC_F2P)) + ' .') 245 | output.write('Average DSC_F2P = ' + str(np.mean(DSC_F2P)) + ' .\n') 246 | print('Average DSC_F3P = ' + str(np.mean(DSC_F3P)) + ' .') 247 | output.write('Average DSC_F3P = ' + str(np.mean(DSC_F3P)) + ' .\n') 248 | output.close() 249 | print('The fusion process is finished.') -------------------------------------------------------------------------------- /OrganSegRSTN/coarse_testing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import time 5 | from utils import * 6 | from model import * 7 | 8 | data_path = sys.argv[1] 9 | current_fold = int(sys.argv[2]) 10 | organ_number = int(sys.argv[3]) 11 | low_range = int(sys.argv[4]) 12 | high_range = int(sys.argv[5]) 13 | slice_threshold = float(sys.argv[6]) 14 | slice_thickness = int(sys.argv[7]) 15 | organ_ID = int(sys.argv[8]) 16 | plane = sys.argv[9] 17 | GPU_ID = int(sys.argv[10]) 18 | learning_rate1 = float(sys.argv[11]) 19 | learning_rate_m1 = int(sys.argv[12]) 20 | learning_rate2 = float(sys.argv[13]) 21 | learning_rate_m2 = int(sys.argv[14]) 22 | crop_margin = int(sys.argv[15]) 23 | crop_prob = float(sys.argv[16]) 24 | crop_sample_batch = int(sys.argv[17]) 25 | snapshot_path = os.path.join(snapshot_path, 'SIJ_training_' + \ 26 | sys.argv[11] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 27 | result_path = os.path.join(result_path, 'coarse_testing_' + \ 28 | sys.argv[11] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 29 | epoch = 'e' + sys.argv[18] + sys.argv[19] + sys.argv[20] + sys.argv[21] 30 | epoch_list = [epoch] 31 | timestamp = sys.argv[22] 32 | 33 | snapshot_name = snapshot_name_from_timestamp(snapshot_path, \ 34 | current_fold, plane, 'J', slice_thickness, organ_ID, timestamp) 35 | if snapshot_name == '': 36 | exit('Error: no valid snapshot directories are detected!') 37 | snapshot_directory = os.path.join(snapshot_path, snapshot_name) 38 | print('Snapshot directory: ' + snapshot_directory + ' .') 39 | snapshot = [snapshot_directory] 40 | print(str(len(snapshot)) + ' snapshots are to be evaluated.') 41 | for t in range(len(snapshot)): 42 | print(' Snapshot #' + str(t + 1) + ': ' + snapshot[t] + ' .') 43 | result_name = snapshot_name 44 | 45 | os.environ["CUDA_VISIBLE_DEVICES"]= str(GPU_ID) 46 | 47 | volume_list = open(testing_set_filename(current_fold), 'r').read().splitlines() 48 | while volume_list[len(volume_list) - 1] == '': 49 | volume_list.pop() 50 | DSC = np.zeros((len(snapshot), len(volume_list))) 51 | result_directory = os.path.join(result_path, result_name, 'volumes') 52 | if not os.path.exists(result_directory): 53 | os.makedirs(result_directory) 54 | result_file = os.path.join(result_path, result_name, 'results.txt') 55 | output = open(result_file, 'w') 56 | output.close() 57 | 58 | for t in range(len(snapshot)): 59 | output = open(result_file, 'a+') 60 | output.write('Evaluating snapshot ' + str(epoch_list[t]) + ':\n') 61 | output.close() 62 | finished = True 63 | for i in range(len(volume_list)): 64 | volume_file = volume_filename_testing(result_directory, epoch_list[t], i) 65 | if not os.path.isfile(volume_file): 66 | finished = False 67 | break 68 | if not finished: 69 | net = RSTN(crop_margin=crop_margin, crop_prob=crop_prob, \ 70 | crop_sample_batch=crop_sample_batch, TEST='C').cuda() 71 | net.load_state_dict(torch.load(snapshot[t])) 72 | net.eval() 73 | 74 | for i in range(len(volume_list)): 75 | start_time = time.time() 76 | print('Testing ' + str(i + 1) + ' out of ' + str(len(volume_list)) + ' testcases, ' + \ 77 | str(t + 1) + ' out of ' + str(len(snapshot)) + ' snapshots.') 78 | volume_file = volume_filename_testing(result_directory, epoch_list[t], i) 79 | s = volume_list[i].split(' ') 80 | label = np.load(s[2]) 81 | label = is_organ(label, organ_ID).astype(np.uint8) 82 | if not os.path.isfile(volume_file): 83 | image = np.load(s[1]).astype(np.float32) 84 | np.minimum(np.maximum(image, low_range, image), high_range, image) 85 | image -= low_range 86 | image /= (high_range - low_range) 87 | print(' Data loading is finished: ' + \ 88 | str(time.time() - start_time) + ' second(s) elapsed.') 89 | pred = np.zeros(image.shape, dtype = np.float32) 90 | minR = 0 91 | if plane == 'X': 92 | maxR = image.shape[0] 93 | shape_ = (1, 3, image.shape[1], image.shape[2]) 94 | elif plane == 'Y': 95 | maxR = image.shape[1] 96 | shape_ = (1, 3, image.shape[0], image.shape[2]) 97 | elif plane == 'Z': 98 | maxR = image.shape[2] 99 | shape_ = (1, 3, image.shape[0], image.shape[1]) 100 | for j in range(minR, maxR): 101 | if slice_thickness == 1: 102 | sID = [j, j, j] 103 | elif slice_thickness == 3: 104 | sID = [max(minR, j - 1), j, min(maxR - 1, j + 1)] 105 | if plane == 'X': 106 | image_ = image[sID, :, :].astype(np.float32) 107 | elif plane == 'Y': 108 | image_ = image[:, sID, :].transpose(1, 0, 2).astype(np.float32) 109 | elif plane == 'Z': 110 | image_ = image[:, :, sID].transpose(2, 0, 1).astype(np.float32) 111 | 112 | image_ = image_.reshape((1, 3, image_.shape[1], image_.shape[2])) 113 | image_ = torch.from_numpy(image_).cuda().float() 114 | out = net(image_).data.cpu().numpy()[0, :, :, :] 115 | 116 | if slice_thickness == 1: 117 | if plane == 'X': 118 | pred[j, :, :] = out 119 | elif plane == 'Y': 120 | pred[:, j, :] = out 121 | elif plane == 'Z': 122 | pred[:, :, j] = out 123 | elif slice_thickness == 3: 124 | if plane == 'X': 125 | if j == minR: 126 | pred[j: j + 2, :, :] += out[1: 3, :, :] 127 | elif j == maxR - 1: 128 | pred[j - 1: j + 1, :, :] += out[0: 2, :, :] 129 | else: 130 | pred[j - 1: j + 2, :, :] += out[...] 131 | elif plane == 'Y': 132 | if j == minR: 133 | pred[:, j: j + 2, :] += out[1: 3, :, :].transpose(1, 0, 2) 134 | elif j == maxR - 1: 135 | pred[:, j - 1: j + 1, :] += out[0: 2, :, :].transpose(1, 0, 2) 136 | else: 137 | pred[:, j - 1: j + 2, :] += out[...].transpose(1, 0, 2) 138 | elif plane == 'Z': 139 | if j == minR: 140 | pred[:, :, j: j + 2] += out[1: 3, :, :].transpose(1, 2, 0) 141 | elif j == maxR - 1: 142 | pred[:, :, j - 1: j + 1] += out[0: 2, :, :].transpose(1, 2, 0) 143 | else: 144 | pred[:, :, j - 1: j + 2] += out[...].transpose(1, 2, 0) 145 | if slice_thickness == 3: 146 | if plane == 'X': 147 | pred[minR, :, :] /= 2 148 | pred[minR + 1: maxR - 1, :, :] /= 3 149 | pred[maxR - 1, :, :] /= 2 150 | elif plane == 'Y': 151 | pred[:, minR, :] /= 2 152 | pred[:, minR + 1: maxR - 1, :] /= 3 153 | pred[:, maxR - 1, :] /= 2 154 | elif plane == 'Z': 155 | pred[:, :, minR] /= 2 156 | pred[:, :, minR + 1: maxR - 1] /= 3 157 | pred[:, :, maxR - 1] /= 2 158 | print(' Testing is finished: ' + str(time.time() - start_time) + ' second(s) elapsed.') 159 | pred = np.around(pred * 255).astype(np.uint8) 160 | np.savez_compressed(volume_file, volume = pred) 161 | print(' Data saving is finished: ' + \ 162 | str(time.time() - start_time) + ' second(s) elapsed.') 163 | pred_temp = (pred >= 128) 164 | else: 165 | volume_data = np.load(volume_file) 166 | pred = volume_data['volume'].astype(np.uint8) 167 | print(' Testing result is loaded: ' + \ 168 | str(time.time() - start_time) + ' second(s) elapsed.') 169 | pred_temp = (pred >= 128) 170 | 171 | DSC[t, i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_temp) 172 | print(' DSC = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + \ 173 | ' + ' + str(label_sum) + ') = ' + str(DSC[t, i]) + ' .') 174 | output = open(result_file, 'a+') 175 | output.write(' Testcase ' + str(i + 1) + ': DSC = 2 * ' + str(inter_sum) + ' / (' + \ 176 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC[t, i]) + ' .\n') 177 | output.close() 178 | if pred_sum == 0 and label_sum == 0: 179 | DSC[t, i] = 0 180 | print(' DSC computation is finished: ' + \ 181 | str(time.time() - start_time) + ' second(s) elapsed.') 182 | 183 | print('Snapshot ' + str(epoch_list[t]) + ': average DSC = ' + str(np.mean(DSC[t, :])) + ' .') 184 | output = open(result_file, 'a+') 185 | output.write('Snapshot ' + str(epoch_list[t]) + \ 186 | ': average DSC = ' + str(np.mean(DSC[t, :])) + ' .\n') 187 | output.close() 188 | 189 | print('The testing process is finished.') 190 | for t in range(len(snapshot)): 191 | print(' Snapshot ' + str(epoch_list[t]) + ': average DSC = ' + str(np.mean(DSC[t, :])) + ' .') 192 | -------------------------------------------------------------------------------- /OrganSegRSTN/fast_functions.py: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by SWIG (http://www.swig.org). 2 | # Version 3.0.8 3 | # 4 | # Do not make changes to this file unless you know what you are doing--modify 5 | # the SWIG interface file instead. 6 | 7 | 8 | 9 | 10 | 11 | from sys import version_info 12 | if version_info >= (2, 6, 0): 13 | def swig_import_helper(): 14 | from os.path import dirname 15 | import imp 16 | fp = None 17 | try: 18 | fp, pathname, description = imp.find_module('_fast_functions', [dirname(__file__)]) 19 | except ImportError: 20 | import _fast_functions 21 | return _fast_functions 22 | if fp is not None: 23 | try: 24 | _mod = imp.load_module('_fast_functions', fp, pathname, description) 25 | finally: 26 | fp.close() 27 | return _mod 28 | _fast_functions = swig_import_helper() 29 | del swig_import_helper 30 | else: 31 | import _fast_functions 32 | del version_info 33 | try: 34 | _swig_property = property 35 | except NameError: 36 | pass # Python < 2.2 doesn't have 'property'. 37 | 38 | 39 | def _swig_setattr_nondynamic(self, class_type, name, value, static=1): 40 | if (name == "thisown"): 41 | return self.this.own(value) 42 | if (name == "this"): 43 | if type(value).__name__ == 'SwigPyObject': 44 | self.__dict__[name] = value 45 | return 46 | method = class_type.__swig_setmethods__.get(name, None) 47 | if method: 48 | return method(self, value) 49 | if (not static): 50 | if _newclass: 51 | object.__setattr__(self, name, value) 52 | else: 53 | self.__dict__[name] = value 54 | else: 55 | raise AttributeError("You cannot add attributes to %s" % self) 56 | 57 | 58 | def _swig_setattr(self, class_type, name, value): 59 | return _swig_setattr_nondynamic(self, class_type, name, value, 0) 60 | 61 | 62 | def _swig_getattr_nondynamic(self, class_type, name, static=1): 63 | if (name == "thisown"): 64 | return self.this.own() 65 | method = class_type.__swig_getmethods__.get(name, None) 66 | if method: 67 | return method(self) 68 | if (not static): 69 | return object.__getattr__(self, name) 70 | else: 71 | raise AttributeError(name) 72 | 73 | def _swig_getattr(self, class_type, name): 74 | return _swig_getattr_nondynamic(self, class_type, name, 0) 75 | 76 | 77 | def _swig_repr(self): 78 | try: 79 | strthis = "proxy of " + self.this.__repr__() 80 | except Exception: 81 | strthis = "" 82 | return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,) 83 | 84 | try: 85 | _object = object 86 | _newclass = 1 87 | except AttributeError: 88 | class _object: 89 | pass 90 | _newclass = 0 91 | 92 | 93 | 94 | def post_processing(F, S, threshold, top2): 95 | return _fast_functions.post_processing(F, S, threshold, top2) 96 | post_processing = _fast_functions.post_processing 97 | 98 | def DSC_computation(A, G, P): 99 | return _fast_functions.DSC_computation(A, G, P) 100 | DSC_computation = _fast_functions.DSC_computation 101 | # This file is compatible with both classic and new-style classes. 102 | 103 | 104 | -------------------------------------------------------------------------------- /OrganSegRSTN/init.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import time 5 | from utils import * 6 | 7 | data_path = sys.argv[1] 8 | organ_number = int(sys.argv[2]) 9 | folds = int(sys.argv[3]) 10 | low_range = int(sys.argv[4]) 11 | high_range = int(sys.argv[5]) 12 | 13 | image_list = [] 14 | image_filename = [] 15 | keyword = '' 16 | for directory, _, file_ in os.walk(image_path): 17 | for filename in sorted(file_): 18 | if keyword in filename: 19 | image_list.append(os.path.join(directory, filename)) 20 | image_filename.append(os.path.splitext(filename)[0]) 21 | label_list = [] 22 | label_filename = [] 23 | for directory, _, file_ in os.walk(label_path): 24 | for filename in sorted(file_): 25 | if keyword in filename: 26 | label_list.append(os.path.join(directory, filename)) 27 | label_filename.append(os.path.splitext(filename)[0]) 28 | if len(image_list) != len(label_list): 29 | exit('Error: the number of labels and the number of images are not equal!') 30 | total_samples = len(image_list) 31 | 32 | for plane in ['X', 'Y', 'Z']: 33 | output = open(list_training[plane], 'w') # create txt 34 | output.close() 35 | print('Initialization starts.') 36 | 37 | for i in range(total_samples): 38 | start_time = time.time() 39 | print('Processing ' + str(i + 1) + ' out of ' + str(total_samples) + ' files.') 40 | image = np.load(image_list[i]) 41 | label = np.load(label_list[i]) 42 | print(' 3D volume is loaded: ' + str(time.time() - start_time) + ' second(s) elapsed.') 43 | for plane in ['X', 'Y', 'Z']: 44 | if plane == 'X': 45 | slice_number = label.shape[0] 46 | elif plane == 'Y': 47 | slice_number = label.shape[1] 48 | elif plane == 'Z': 49 | slice_number = label.shape[2] 50 | print(' Processing data on ' + plane + ' plane (' + str(slice_number) + ' slices): ' + \ 51 | str(time.time() - start_time) + ' second(s) elapsed.') 52 | image_directory_ = os.path.join(image_path_[plane], image_filename[i]) 53 | if not os.path.exists(image_directory_): 54 | os.makedirs(image_directory_) 55 | label_directory_ = os.path.join(label_path_[plane], label_filename[i]) 56 | if not os.path.exists(label_directory_): 57 | os.makedirs(label_directory_) 58 | print(' Slicing data: ' + str(time.time() - start_time) + ' second(s) elapsed.') 59 | sum_ = np.zeros((slice_number, organ_number + 1), dtype = np.int) 60 | minA = np.zeros((slice_number, organ_number + 1), dtype = np.int) 61 | maxA = np.zeros((slice_number, organ_number + 1), dtype = np.int) 62 | minB = np.zeros((slice_number, organ_number + 1), dtype = np.int) 63 | maxB = np.zeros((slice_number, organ_number + 1), dtype = np.int) 64 | average = np.zeros((slice_number), dtype = np.float) 65 | for j in range(0, slice_number): 66 | image_filename_ = os.path.join( \ 67 | image_path_[plane], image_filename[i], '{:0>4}'.format(j) + '.npy') 68 | label_filename_ = os.path.join( \ 69 | label_path_[plane], label_filename[i], '{:0>4}'.format(j) + '.npy') 70 | if plane == 'X': 71 | image_ = image[j, :, :] 72 | label_ = label[j, :, :] 73 | elif plane == 'Y': 74 | image_ = image[:, j, :] 75 | label_ = label[:, j, :] 76 | elif plane == 'Z': 77 | image_ = image[:, :, j] 78 | label_ = label[:, :, j] 79 | if not os.path.isfile(image_filename_) or not os.path.isfile(label_filename_): 80 | np.save(image_filename_, image_) # main function, no truncate 81 | np.save(label_filename_, label_) 82 | np.minimum(np.maximum(image_, low_range, image_), high_range, image_) 83 | 84 | average[j] = float(image_.sum()) / (image_.shape[0] * image_.shape[1]) 85 | for o in range(1, organ_number + 1): 86 | sum_[j, o] = (is_organ(label_, o)).sum() 87 | arr = np.nonzero(is_organ(label_, o)) 88 | minA[j, o] = 0 if not len(arr[0]) else min(arr[0]) # [A*B] min/max nonzero 89 | maxA[j, o] = 0 if not len(arr[0]) else max(arr[0]) 90 | minB[j, o] = 0 if not len(arr[1]) else min(arr[1]) 91 | maxB[j, o] = 0 if not len(arr[1]) else max(arr[1]) 92 | print(' Writing training lists: ' + str(time.time() - start_time) + ' second(s) elapsed.') 93 | output = open(list_training[plane], 'a+') 94 | for j in range(0, slice_number): 95 | image_filename_ = os.path.join( \ 96 | image_path_[plane], image_filename[i], '{:0>4}'.format(j) + '.npy') 97 | label_filename_ = os.path.join( \ 98 | label_path_[plane], label_filename[i], '{:0>4}'.format(j) + '.npy') 99 | output.write(str(i) + ' ' + str(j)) 100 | output.write(' ' + image_filename_ + ' ' + label_filename_) 101 | output.write(' ' + str(average[j])) 102 | for o in range(1, organ_number + 1): 103 | output.write(' ' + str(sum_[j, o]) + ' ' + str(minA[j, o]) + \ 104 | ' ' + str(maxA[j, o]) + ' ' + str(minB[j, o]) + ' ' + str(maxB[j, o])) 105 | output.write('\n') 106 | output.close() 107 | print(' ' + plane + ' plane is done: ' + \ 108 | str(time.time() - start_time) + ' second(s) elapsed.') 109 | print('Processed ' + str(i + 1) + ' out of ' + str(total_samples) + ' files: ' + \ 110 | str(time.time() - start_time) + ' second(s) elapsed.') 111 | 112 | print('Writing training image list.') 113 | for f in range(folds): 114 | list_training_ = training_set_filename(f) 115 | output = open(list_training_, 'w') 116 | for i in range(total_samples): 117 | if in_training_set(total_samples, i, folds, f): 118 | output.write(str(i) + ' ' + image_list[i] + ' ' + label_list[i] + '\n') 119 | output.close() 120 | print('Writing testing image list.') 121 | for f in range(folds): 122 | list_testing_ = testing_set_filename(f) 123 | output = open(list_testing_, 'w') 124 | for i in range(total_samples): 125 | if not in_training_set(total_samples, i, folds, f): 126 | output.write(str(i) + ' ' + image_list[i] + ' ' + label_list[i] + '\n') 127 | output.close() 128 | print('Initialization is done.') 129 | 130 | -------------------------------------------------------------------------------- /OrganSegRSTN/model.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | import torch 4 | import torch.nn as nn 5 | 6 | class FCN8s(nn.Module): 7 | def __init__(self, n_class=3): 8 | super(FCN8s, self).__init__() 9 | # conv1 10 | self.conv1_1 = nn.Conv2d(3, 64, 3, padding=100) 11 | self.relu1_1 = nn.ReLU(inplace=True) 12 | self.conv1_2 = nn.Conv2d(64, 64, 3, padding=1) 13 | self.relu1_2 = nn.ReLU(inplace=True) 14 | self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/2 15 | 16 | # conv2 17 | self.conv2_1 = nn.Conv2d(64, 128, 3, padding=1) 18 | self.relu2_1 = nn.ReLU(inplace=True) 19 | self.conv2_2 = nn.Conv2d(128, 128, 3, padding=1) 20 | self.relu2_2 = nn.ReLU(inplace=True) 21 | self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/4 22 | 23 | # conv3 24 | self.conv3_1 = nn.Conv2d(128, 256, 3, padding=1) 25 | self.relu3_1 = nn.ReLU(inplace=True) 26 | self.conv3_2 = nn.Conv2d(256, 256, 3, padding=1) 27 | self.relu3_2 = nn.ReLU(inplace=True) 28 | self.conv3_3 = nn.Conv2d(256, 256, 3, padding=1) 29 | self.relu3_3 = nn.ReLU(inplace=True) 30 | self.pool3 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/8 31 | 32 | # conv4 33 | self.conv4_1 = nn.Conv2d(256, 512, 3, padding=1) 34 | self.relu4_1 = nn.ReLU(inplace=True) 35 | self.conv4_2 = nn.Conv2d(512, 512, 3, padding=1) 36 | self.relu4_2 = nn.ReLU(inplace=True) 37 | self.conv4_3 = nn.Conv2d(512, 512, 3, padding=1) 38 | self.relu4_3 = nn.ReLU(inplace=True) 39 | self.pool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/16 40 | 41 | # conv5 42 | self.conv5_1 = nn.Conv2d(512, 512, 3, padding=1) 43 | self.relu5_1 = nn.ReLU(inplace=True) 44 | self.conv5_2 = nn.Conv2d(512, 512, 3, padding=1) 45 | self.relu5_2 = nn.ReLU(inplace=True) 46 | self.conv5_3 = nn.Conv2d(512, 512, 3, padding=1) 47 | self.relu5_3 = nn.ReLU(inplace=True) 48 | self.pool5 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 1/32 49 | 50 | # fc6 51 | self.fc6 = nn.Conv2d(512, 4096, 7) 52 | self.relu6 = nn.ReLU(inplace=True) 53 | self.drop6 = nn.Dropout2d() 54 | 55 | # fc7 56 | self.fc7 = nn.Conv2d(4096, 4096, 1) 57 | self.relu7 = nn.ReLU(inplace=True) 58 | self.drop7 = nn.Dropout2d() 59 | 60 | self.score_fr = nn.Conv2d(4096, n_class, 1) 61 | self.score_pool3 = nn.Conv2d(256, n_class, 1) 62 | self.score_pool4 = nn.Conv2d(512, n_class, 1) 63 | 64 | self.upscore2 = nn.ConvTranspose2d( 65 | n_class, n_class, 4, stride=2, bias=False) 66 | self.upscore8 = nn.ConvTranspose2d( 67 | n_class, n_class, 16, stride=8, bias=False) 68 | self.upscore_pool4 = nn.ConvTranspose2d( 69 | n_class, n_class, 4, stride=2, bias=False) 70 | 71 | self._initialize_weights() 72 | 73 | def _initialize_weights(self): 74 | for mod in self.modules(): 75 | if isinstance(mod, nn.Conv2d): 76 | mod.weight.data.zero_() 77 | if mod.bias is not None: 78 | mod.bias.data.zero_() 79 | if isinstance(mod, nn.ConvTranspose2d): 80 | m, k, h, w = mod.weight.data.shape 81 | if m != k and k != 1: 82 | raise RuntimeError('input + output channels need to be the same or |output| == 1') 83 | if h != w: 84 | raise RuntimeError('filters need to be square') 85 | filt = torch.from_numpy(self.upsample_filt(h)).float() 86 | mod.weight.data[range(m), range(k), :, :] = filt 87 | 88 | def upsample_filt(self, size): 89 | factor = (size + 1) // 2 90 | if size % 2 == 1: 91 | center = factor - 1 92 | else: 93 | center = factor - 0.5 94 | og = np.ogrid[: size, : size] 95 | return (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor) 96 | 97 | def forward(self, x): 98 | h = x 99 | h = self.relu1_1(self.conv1_1(h)) 100 | h = self.relu1_2(self.conv1_2(h)) 101 | h = self.pool1(h) 102 | 103 | h = self.relu2_1(self.conv2_1(h)) 104 | h = self.relu2_2(self.conv2_2(h)) 105 | h = self.pool2(h) 106 | 107 | h = self.relu3_1(self.conv3_1(h)) 108 | h = self.relu3_2(self.conv3_2(h)) 109 | h = self.relu3_3(self.conv3_3(h)) 110 | h = self.pool3(h) 111 | pool3 = h # 1/8 112 | 113 | h = self.relu4_1(self.conv4_1(h)) 114 | h = self.relu4_2(self.conv4_2(h)) 115 | h = self.relu4_3(self.conv4_3(h)) 116 | h = self.pool4(h) 117 | pool4 = h # 1/16 118 | 119 | h = self.relu5_1(self.conv5_1(h)) 120 | h = self.relu5_2(self.conv5_2(h)) 121 | h = self.relu5_3(self.conv5_3(h)) 122 | h = self.pool5(h) 123 | 124 | h = self.relu6(self.fc6(h)) 125 | h = self.drop6(h) 126 | 127 | h = self.relu7(self.fc7(h)) 128 | h = self.drop7(h) 129 | 130 | h = self.score_fr(h) 131 | h = self.upscore2(h) 132 | upscore2 = h # 1/16 133 | 134 | h = self.score_pool4(pool4) 135 | h = h[:, :, 136 | 5:5 + upscore2.size()[2], 137 | 5:5 + upscore2.size()[3]] 138 | score_pool4c = h # 1/16 139 | 140 | h = upscore2 + score_pool4c # 1/16 141 | h = self.upscore_pool4(h) 142 | upscore_pool4 = h # 1/8 143 | 144 | h = self.score_pool3(pool3) 145 | h = h[:, :, 146 | 9:9 + upscore_pool4.size()[2], 147 | 9:9 + upscore_pool4.size()[3]] 148 | score_pool3c = h # 1/8 149 | 150 | h = upscore_pool4 + score_pool3c # 1/8 151 | 152 | h = self.upscore8(h) 153 | h = h[:, :, 31:31 + x.size()[2], 154 | 31:31 + x.size()[3]] # 1/1 155 | 156 | return h 157 | 158 | class RSTN(nn.Module): 159 | def __init__(self, crop_margin=20, crop_prob=0.5, \ 160 | crop_sample_batch=1, n_class=3, TEST=None): 161 | super(RSTN, self).__init__() 162 | self.TEST = TEST 163 | self.margin = crop_margin 164 | self.prob = crop_prob 165 | self.batch = crop_sample_batch 166 | 167 | # Coarse-scaled Network 168 | self.coarse_model = FCN8s(n_class) 169 | # Saliency Transformation Module 170 | self.saliency1 = nn.Conv2d(n_class, n_class, kernel_size=3, stride=1, padding=1) 171 | self.relu_saliency1 = nn.ReLU(inplace=True) 172 | self.saliency2 = nn.Conv2d(n_class, n_class, kernel_size=5, stride=1, padding=2) 173 | # Fine-scaled Network 174 | self.fine_model = FCN8s(n_class) 175 | 176 | self._initialize_weights() 177 | 178 | def _initialize_weights(self): 179 | for name, mod in self.named_children(): 180 | if name == 'saliency1': 181 | nn.init.xavier_normal_(mod.weight.data) 182 | mod.bias.data.fill_(1) 183 | elif name == 'saliency2': 184 | mod.weight.data.zero_() 185 | mod.bias.data = torch.tensor([1.0, 1.5, 2.0]) 186 | 187 | def forward(self, image, label=None, mode=None, score=None, mask=None): 188 | if self.TEST is None: 189 | assert label is not None and mode is not None \ 190 | and score is None and mask is None 191 | # Coarse-scaled Network 192 | h = image 193 | h = self.coarse_model(h) 194 | h = torch.sigmoid(h) 195 | coarse_prob = h 196 | # Saliency Transformation Module 197 | h = self.relu_saliency1(self.saliency1(h)) 198 | h = self.saliency2(h) 199 | saliency = h 200 | 201 | if mode == 'S': 202 | cropped_image, crop_info = self.crop(label, image) 203 | elif mode == 'I': 204 | cropped_image, crop_info = self.crop(label, image * saliency) 205 | elif mode == 'J': 206 | cropped_image, crop_info = self.crop(coarse_prob, image * saliency, label) 207 | else: 208 | raise ValueError("wrong value of mode, should be in ['S', 'I', 'J']") 209 | 210 | # Fine-scaled Network 211 | h = cropped_image 212 | h = self.fine_model(h) 213 | h = self.uncrop(crop_info, h, image) 214 | h = torch.sigmoid(h) 215 | fine_prob = h 216 | return coarse_prob, fine_prob 217 | 218 | elif self.TEST == 'C': # Coarse testing 219 | assert label is None and mode is None and \ 220 | score is None and mask is None 221 | # Coarse-scaled Network 222 | h = image 223 | h = self.coarse_model(h) 224 | h = torch.sigmoid(h) 225 | coarse_prob = h 226 | return coarse_prob 227 | 228 | elif self.TEST == 'O': # Oracle testing 229 | assert label is not None and mode is None and \ 230 | score is None and mask is None 231 | # Coarse-scaled Network 232 | h = image 233 | h = self.coarse_model(h) 234 | h = torch.sigmoid(h) 235 | # Saliency Transformation Module 236 | h = self.relu_saliency1(self.saliency1(h)) 237 | h = self.saliency2(h) 238 | saliency = h 239 | cropped_image, crop_info = self.crop(label, image * saliency) 240 | # Fine-scaled Network 241 | h = cropped_image 242 | h = self.fine_model(h) 243 | h = self.uncrop(crop_info, h, image) 244 | h = torch.sigmoid(h) 245 | fine_prob = h 246 | return fine_prob 247 | 248 | elif self.TEST == 'F': # Fine testing 249 | assert label is None and mode is None \ 250 | and score is not None and mask is not None 251 | # Saliency Transformation Module 252 | h = score 253 | h = self.relu_saliency1(self.saliency1(h)) 254 | h = self.saliency2(h) 255 | saliency = h 256 | cropped_image, crop_info = self.crop(mask, image * saliency) 257 | # Fine-scaled Network 258 | h = cropped_image 259 | h = self.fine_model(h) 260 | h = self.uncrop(crop_info, h, image) 261 | h = torch.sigmoid(h) 262 | fine_prob = h 263 | return fine_prob 264 | 265 | else: 266 | raise ValueError("wrong value of TEST, should be in [None, 'C', 'F', 'O']") 267 | 268 | def crop(self, prob_map, saliency_data, label=None): 269 | (N, C, W, H) = prob_map.shape 270 | 271 | binary_mask = (prob_map >= 0.5) # torch.uint8 272 | if label is not None and binary_mask.sum().item() == 0: 273 | binary_mask = (label >= 0.5) 274 | 275 | if self.TEST is not None: 276 | self.left = self.margin 277 | self.right = self.margin 278 | self.top = self.margin 279 | self.bottom = self.margin 280 | else: 281 | self.update_margin() 282 | 283 | if binary_mask.sum().item() == 0: # avoid this by pre-condition in TEST 'F' 284 | minA = 0 285 | maxA = W 286 | minB = 0 287 | maxB = H 288 | self.no_forward = True 289 | else: 290 | if N > 1: 291 | mask = torch.zeros(size = (N, C, W, H)) 292 | for n in range(N): 293 | cur_mask = binary_mask[n, :, :, :] 294 | arr = torch.nonzero(cur_mask) 295 | minA = arr[:, 1].min().item() 296 | maxA = arr[:, 1].max().item() 297 | minB = arr[:, 2].min().item() 298 | maxB = arr[:, 2].max().item() 299 | bbox = [int(max(minA - self.left, 0)), int(min(maxA + self.right + 1, W)), \ 300 | int(max(minB - self.top, 0)), int(min(maxB + self.bottom + 1, H))] 301 | mask[n, :, bbox[0]: bbox[1], bbox[2]: bbox[3]] = 1 302 | saliency_data = saliency_data * mask.cuda() 303 | 304 | arr = torch.nonzero(binary_mask) 305 | minA = arr[:, 2].min().item() 306 | maxA = arr[:, 2].max().item() 307 | minB = arr[:, 3].min().item() 308 | maxB = arr[:, 3].max().item() 309 | self.no_forward = False 310 | 311 | bbox = [int(max(minA - self.left, 0)), int(min(maxA + self.right + 1, W)), \ 312 | int(max(minB - self.top, 0)), int(min(maxB + self.bottom + 1, H))] 313 | cropped_image = saliency_data[:, :, bbox[0]: bbox[1], \ 314 | bbox[2]: bbox[3]] 315 | 316 | if self.no_forward == True and self.TEST == 'F': 317 | cropped_image = torch.zeros_like(cropped_image).cuda() 318 | 319 | crop_info = np.zeros((1, 4), dtype = np.int16) 320 | crop_info[0] = bbox 321 | crop_info = torch.from_numpy(crop_info).cuda() 322 | 323 | return cropped_image, crop_info 324 | 325 | def update_margin(self): 326 | MAX_INT = 256 327 | if random.randint(0, MAX_INT - 1) >= MAX_INT * self.prob: 328 | self.left = self.margin 329 | self.right = self.margin 330 | self.top = self.margin 331 | self.bottom = self.margin 332 | else: 333 | a = np.zeros(self.batch * 4, dtype = np.uint8) 334 | for i in range(self.batch * 4): 335 | a[i] = random.randint(0, self.margin * 2) 336 | self.left = int(a[0: self.batch].sum() / self.batch) 337 | self.right = int(a[self.batch: self.batch * 2].sum() / self.batch) 338 | self.top = int(a[self.batch * 2: self.batch * 3].sum() / self.batch) 339 | self.bottom = int(a[self.batch * 3: self.batch * 4].sum() / self.batch) 340 | 341 | def uncrop(self, crop_info, cropped_image, image): 342 | uncropped_image = torch.ones_like(image).cuda() 343 | uncropped_image *= (-9999999) 344 | bbox = crop_info[0] 345 | uncropped_image[:, :, bbox[0].item(): bbox[1].item(), bbox[2].item(): bbox[3].item()] = cropped_image 346 | return uncropped_image 347 | 348 | def get_parameters(model, coarse=True, bias=False, parallel=False): 349 | print('coarse, bias', coarse, bias) 350 | if parallel: 351 | for name, mod in model.named_children(): 352 | print('parallel', name) 353 | model = mod 354 | break 355 | for name, mod in model.named_children(): 356 | if name == 'coarse_model' and coarse \ 357 | or name in ['saliency1', 'saliency2', 'fine_model'] and not coarse: 358 | print(name) 359 | for n, m in mod.named_modules(): 360 | if isinstance(m, nn.Conv2d): 361 | print(n, m) 362 | if bias and m.bias is not None: 363 | yield m.bias 364 | elif not bias: 365 | yield m.weight 366 | elif isinstance(m, nn.ConvTranspose2d): 367 | # weight is frozen because it is just a bilinear upsampling 368 | if bias: 369 | assert m.bias is None 370 | 371 | class DSC_loss(nn.Module): 372 | def __init__(self): 373 | super(DSC_loss, self).__init__() 374 | self.epsilon = 0.000001 375 | return 376 | def forward(self, pred, target): # soft mode. per item. 377 | batch_num = pred.shape[0] 378 | pred = pred.contiguous().view(batch_num, -1) 379 | target = target.contiguous().view(batch_num, -1) 380 | DSC = (2 * (pred * target).sum(1) + self.epsilon) / \ 381 | ((pred + target).sum(1) + self.epsilon) 382 | return 1 - DSC.sum() / float(batch_num) 383 | -------------------------------------------------------------------------------- /OrganSegRSTN/oracle_fusion.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import time 5 | from utils import * 6 | 7 | 8 | data_path = sys.argv[1] 9 | current_fold = int(sys.argv[2]) 10 | organ_number = int(sys.argv[3]) 11 | low_range = int(sys.argv[4]) 12 | high_range = int(sys.argv[5]) 13 | slice_threshold = float(sys.argv[6]) 14 | slice_thickness = int(sys.argv[7]) 15 | organ_ID = int(sys.argv[8]) 16 | GPU_ID = int(sys.argv[9]) 17 | learning_rate1 = float(sys.argv[10]) 18 | learning_rate_m1 = int(sys.argv[11]) 19 | learning_rate2 = float(sys.argv[12]) 20 | learning_rate_m2 = int(sys.argv[13]) 21 | crop_margin = int(sys.argv[14]) 22 | result_path = os.path.join(result_path, 'oracle_testing_' + \ 23 | sys.argv[10] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 24 | epoch = 'e' + sys.argv[15] + sys.argv[16] + sys.argv[17] + sys.argv[18] 25 | epoch_list = [epoch] 26 | threshold = float(sys.argv[19]) 27 | timestamp = {} 28 | timestamp['X'] = sys.argv[20] 29 | timestamp['Y'] = sys.argv[21] 30 | timestamp['Z'] = sys.argv[22] 31 | 32 | volume_list = open(testing_set_filename(current_fold), 'r').read().splitlines() 33 | while volume_list[len(volume_list) - 1] == '': 34 | volume_list.pop() 35 | 36 | result_name_ = {} 37 | result_directory_ = {} 38 | for plane in ['X', 'Y', 'Z']: 39 | result_name__ = result_name_from_timestamp(result_path, current_fold, \ 40 | plane, 'J', slice_thickness, organ_ID, volume_list, timestamp[plane]) 41 | if result_name__ == '': 42 | exit(' Error: no valid result directories are detected!') 43 | result_directory__ = os.path.join(result_path, result_name__, 'volumes') 44 | print(' Result directory for plane ' + plane + ': ' + result_directory__ + ' .') 45 | if result_name__.startswith('FD'): 46 | index_ = result_name__.find(':') 47 | result_name__ = result_name__[index_ + 1: ] 48 | result_name_[plane] = result_name__ 49 | result_directory_[plane] = result_directory__ 50 | 51 | DSC_X = np.zeros((len(volume_list))) 52 | DSC_Y = np.zeros((len(volume_list))) 53 | DSC_Z = np.zeros((len(volume_list))) 54 | DSC_F1 = np.zeros((len(volume_list))) 55 | DSC_F2 = np.zeros((len(volume_list))) 56 | DSC_F3 = np.zeros((len(volume_list))) 57 | DSC_F1P = np.zeros((len(volume_list))) 58 | DSC_F2P = np.zeros((len(volume_list))) 59 | DSC_F3P = np.zeros((len(volume_list))) 60 | 61 | result_name = 'FD' + str(current_fold) + ':' + 'fusion:' + result_name_['X'] + ',' + \ 62 | result_name_['Y'] + ',' + result_name_['Z'] + '_' + epoch + ',' + str(threshold) 63 | result_directory = os.path.join(result_path, result_name, 'volumes') 64 | if not os.path.exists(result_directory): 65 | os.makedirs(result_directory) 66 | 67 | result_file = os.path.join(result_path, result_name, 'results.txt') 68 | output = open(result_file, 'w') 69 | output.close() 70 | output = open(result_file, 'a+') 71 | output.write('Fusing results of ' + str(len(epoch_list)) + ' snapshots:\n') 72 | output.close() 73 | 74 | for i in range(len(volume_list)): 75 | start_time = time.time() 76 | print('Testing ' + str(i + 1) + ' out of ' + str(len(volume_list)) + ' testcases.') 77 | output = open(result_file, 'a+') 78 | output.write(' Testcase ' + str(i + 1) + ':\n') 79 | output.close() 80 | s = volume_list[i].split(' ') 81 | label = np.load(s[2]) 82 | label = is_organ(label, organ_ID).astype(np.uint8) 83 | 84 | for plane in ['X', 'Y', 'Z']: 85 | volume_file = volume_filename_fusion(result_directory, plane, i) 86 | pred = np.zeros(label.shape, dtype = np.float32) 87 | for t in range(len(epoch_list)): 88 | volume_file_ = volume_filename_testing(result_directory_[plane], epoch_list[t], i) 89 | pred += np.load(volume_file_)['volume'] 90 | pred_ = (pred >= threshold * 255 * len(epoch_list)) 91 | if not os.path.isfile(volume_file): 92 | np.savez_compressed(volume_file, volume = pred_) 93 | DSC_, inter_sum, pred_sum, label_sum = DSC_computation(label, pred_) 94 | print(' DSC_' + plane + ' = 2 * ' + str(inter_sum) + ' / (' + \ 95 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_) + ' .') 96 | output = open(result_file, 'a+') 97 | output.write(' DSC_' + plane + ' = 2 * ' + str(inter_sum) + ' / (' + \ 98 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_) + ' .\n') 99 | output.close() 100 | if pred_sum == 0 and label_sum == 0: 101 | DSC_ = 0 102 | pred /= (255 * len(epoch_list)) 103 | if plane == 'X': 104 | pred_X = pred 105 | DSC_X[i] = DSC_ 106 | elif plane == 'Y': 107 | pred_Y = pred 108 | DSC_Y[i] = DSC_ 109 | elif plane == 'Z': 110 | pred_Z = pred 111 | DSC_Z[i] = DSC_ 112 | 113 | volume_file_F1 = volume_filename_fusion(result_directory, 'F1', i) 114 | volume_file_F2 = volume_filename_fusion(result_directory, 'F2', i) 115 | volume_file_F3 = volume_filename_fusion(result_directory, 'F3', i) 116 | 117 | if not os.path.isfile(volume_file_F1) or not os.path.isfile(volume_file_F2) or \ 118 | not os.path.isfile(volume_file_F3): 119 | pred_total = pred_X + pred_Y + pred_Z 120 | if os.path.isfile(volume_file_F1): 121 | pred_F1 = np.load(volume_file_F1)['volume'].astype(np.uint8) 122 | else: 123 | pred_F1 = (pred_total >= 0.5).astype(np.uint8) 124 | np.savez_compressed(volume_file_F1, volume = pred_F1) 125 | DSC_F1[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F1) 126 | print(' DSC_F1 = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' \ 127 | + str(label_sum) + ') = ' + str(DSC_F1[i]) + ' .') 128 | output = open(result_file, 'a+') 129 | output.write(' DSC_F1 = 2 * ' + str(inter_sum) + ' / (' + \ 130 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F1[i]) + ' .\n') 131 | output.close() 132 | if pred_sum == 0 and label_sum == 0: 133 | DSC_F1[i] = 0 134 | 135 | if os.path.isfile(volume_file_F2): 136 | pred_F2 = np.load(volume_file_F2)['volume'].astype(np.uint8) 137 | else: 138 | pred_F2 = (pred_total >= 1.5).astype(np.uint8) 139 | np.savez_compressed(volume_file_F2, volume = pred_F2) 140 | DSC_F2[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F2) 141 | print(' DSC_F2 = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 142 | str(label_sum) + ') = ' + str(DSC_F2[i]) + ' .') 143 | output = open(result_file, 'a+') 144 | output.write(' DSC_F2 = 2 * ' + str(inter_sum) + ' / (' + \ 145 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F2[i]) + ' .\n') 146 | output.close() 147 | if pred_sum == 0 and label_sum == 0: 148 | DSC_F2[i] = 0 149 | 150 | if os.path.isfile(volume_file_F3): 151 | pred_F3 = np.load(volume_file_F3)['volume'].astype(np.uint8) 152 | else: 153 | pred_F3 = (pred_total >= 2.5).astype(np.uint8) 154 | np.savez_compressed(volume_file_F3, volume = pred_F3) 155 | DSC_F3[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F3) 156 | print(' DSC_F3 = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 157 | str(label_sum) + ') = ' + str(DSC_F3[i]) + ' .') 158 | output = open(result_file, 'a+') 159 | output.write(' DSC_F3 = 2 * ' + str(inter_sum) + ' / (' + \ 160 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F3[i]) + ' .\n') 161 | output.close() 162 | if pred_sum == 0 and label_sum == 0: 163 | DSC_F3[i] = 0 164 | 165 | volume_file_F1P = volume_filename_fusion(result_directory, 'F1P', i) 166 | volume_file_F2P = volume_filename_fusion(result_directory, 'F2P', i) 167 | volume_file_F3P = volume_filename_fusion(result_directory, 'F3P', i) 168 | S = pred_F3 169 | if (S.sum() == 0): 170 | S = pred_F2 171 | if (S.sum() == 0): 172 | S = pred_F1 173 | 174 | if os.path.isfile(volume_file_F1P): 175 | pred_F1P = np.load(volume_file_F1P)['volume'].astype(np.uint8) 176 | else: 177 | pred_F1P = post_processing(pred_F1, S, 0.5, organ_ID) 178 | np.savez_compressed(volume_file_F1P, volume = pred_F1P) 179 | DSC_F1P[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F1P) 180 | print(' DSC_F1P = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 181 | str(label_sum) + ') = ' + str(DSC_F1P[i]) + ' .') 182 | output = open(result_file, 'a+') 183 | output.write(' DSC_F1P = 2 * ' + str(inter_sum) + ' / (' + \ 184 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F1P[i]) + ' .\n') 185 | output.close() 186 | if pred_sum == 0 and label_sum == 0: 187 | DSC_F1P[i] = 0 188 | 189 | if os.path.isfile(volume_file_F2P): 190 | pred_F2P = np.load(volume_file_F2P)['volume'].astype(np.uint8) 191 | else: 192 | pred_F2P = post_processing(pred_F2, S, 0.5, organ_ID) 193 | np.savez_compressed(volume_file_F2P, volume = pred_F2P) 194 | DSC_F2P[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F2P) 195 | print(' DSC_F2P = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 196 | str(label_sum) + ') = ' + str(DSC_F2P[i]) + ' .') 197 | output = open(result_file, 'a+') 198 | output.write(' DSC_F2P = 2 * ' + str(inter_sum) + ' / (' + \ 199 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F2P[i]) + ' .\n') 200 | output.close() 201 | if pred_sum == 0 and label_sum == 0: 202 | DSC_F2P[i] = 0 203 | 204 | if os.path.isfile(volume_file_F3P): 205 | pred_F3P = np.load(volume_file_F3P)['volume'].astype(np.uint8) 206 | else: 207 | pred_F3P = post_processing(pred_F3, S, 0.5, organ_ID) 208 | np.savez_compressed(volume_file_F3P, volume = pred_F3P) 209 | DSC_F3P[i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_F3P) 210 | print(' DSC_F3P = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + ' + ' + \ 211 | str(label_sum) + ') = ' + str(DSC_F3P[i]) + ' .') 212 | output = open(result_file, 'a+') 213 | output.write(' DSC_F3P = 2 * ' + str(inter_sum) + ' / (' + \ 214 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC_F3P[i]) + ' .\n') 215 | output.close() 216 | if pred_sum == 0 and label_sum == 0: 217 | DSC_F3P[i] = 0 218 | 219 | pred_X = None 220 | pred_Y = None 221 | pred_Z = None 222 | pred_F1 = None 223 | pred_F2 = None 224 | pred_F3 = None 225 | pred_F1P = None 226 | pred_F2P = None 227 | pred_F3P = None 228 | 229 | output = open(result_file, 'a+') 230 | print('Average DSC_X = ' + str(np.mean(DSC_X)) + ' .') 231 | output.write('Average DSC_X = ' + str(np.mean(DSC_X)) + ' .\n') 232 | print('Average DSC_Y = ' + str(np.mean(DSC_Y)) + ' .') 233 | output.write('Average DSC_Y = ' + str(np.mean(DSC_Y)) + ' .\n') 234 | print('Average DSC_Z = ' + str(np.mean(DSC_Z)) + ' .') 235 | output.write('Average DSC_Z = ' + str(np.mean(DSC_Z)) + ' .\n') 236 | print('Average DSC_F1 = ' + str(np.mean(DSC_F1)) + ' .') 237 | output.write('Average DSC_F1 = ' + str(np.mean(DSC_F1)) + ' .\n') 238 | print('Average DSC_F2 = ' + str(np.mean(DSC_F2)) + ' .') 239 | output.write('Average DSC_F2 = ' + str(np.mean(DSC_F2)) + ' .\n') 240 | print('Average DSC_F3 = ' + str(np.mean(DSC_F3)) + ' .') 241 | output.write('Average DSC_F3 = ' + str(np.mean(DSC_F3)) + ' .\n') 242 | print('Average DSC_F1P = ' + str(np.mean(DSC_F1P)) + ' .') 243 | output.write('Average DSC_F1P = ' + str(np.mean(DSC_F1P)) + ' .\n') 244 | print('Average DSC_F2P = ' + str(np.mean(DSC_F2P)) + ' .') 245 | output.write('Average DSC_F2P = ' + str(np.mean(DSC_F2P)) + ' .\n') 246 | print('Average DSC_F3P = ' + str(np.mean(DSC_F3P)) + ' .') 247 | output.write('Average DSC_F3P = ' + str(np.mean(DSC_F3P)) + ' .\n') 248 | output.close() 249 | print('The fusion process is finished.') -------------------------------------------------------------------------------- /OrganSegRSTN/oracle_testing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import time 5 | from utils import * 6 | from model import * 7 | 8 | data_path = sys.argv[1] 9 | current_fold = int(sys.argv[2]) 10 | organ_number = int(sys.argv[3]) 11 | low_range = int(sys.argv[4]) 12 | high_range = int(sys.argv[5]) 13 | slice_threshold = float(sys.argv[6]) 14 | slice_thickness = int(sys.argv[7]) 15 | organ_ID = int(sys.argv[8]) 16 | plane = sys.argv[9] 17 | GPU_ID = int(sys.argv[10]) 18 | learning_rate1 = float(sys.argv[11]) 19 | learning_rate_m1 = int(sys.argv[12]) 20 | learning_rate2 = float(sys.argv[13]) 21 | learning_rate_m2 = int(sys.argv[14]) 22 | crop_margin = int(sys.argv[15]) 23 | crop_prob = float(sys.argv[16]) 24 | crop_sample_batch = int(sys.argv[17]) 25 | snapshot_path = os.path.join(snapshot_path, 'SIJ_training_' + \ 26 | sys.argv[11] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 27 | result_path = os.path.join(result_path, 'oracle_testing_' + \ 28 | sys.argv[11] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 29 | epoch = 'e' + sys.argv[18] + sys.argv[19] + sys.argv[20] + sys.argv[21] 30 | epoch_list = [epoch] 31 | timestamp = sys.argv[22] 32 | 33 | snapshot_name = snapshot_name_from_timestamp(snapshot_path, \ 34 | current_fold, plane, 'J', slice_thickness, organ_ID, timestamp) 35 | if snapshot_name == '': 36 | exit('Error: no valid snapshot directories are detected!') 37 | snapshot_directory = os.path.join(snapshot_path, snapshot_name) 38 | print('Snapshot directory: ' + snapshot_directory + ' .') 39 | snapshot = [snapshot_directory] 40 | print(str(len(snapshot)) + ' snapshots are to be evaluated.') 41 | for t in range(len(snapshot)): 42 | print(' Snapshot #' + str(t + 1) + ': ' + snapshot[t] + ' .') 43 | result_name = snapshot_name 44 | 45 | os.environ["CUDA_VISIBLE_DEVICES"]= str(GPU_ID) 46 | 47 | volume_list = open(testing_set_filename(current_fold), 'r').read().splitlines() 48 | while volume_list[len(volume_list) - 1] == '': 49 | volume_list.pop() 50 | DSC = np.zeros((len(snapshot), len(volume_list))) 51 | result_directory = os.path.join(result_path, result_name, 'volumes') 52 | if not os.path.exists(result_directory): 53 | os.makedirs(result_directory) 54 | result_file = os.path.join(result_path, result_name, 'results.txt') 55 | output = open(result_file, 'w') 56 | output.close() 57 | 58 | for t in range(len(snapshot)): 59 | output = open(result_file, 'a+') 60 | output.write('Evaluating snapshot ' + str(epoch_list[t]) + ':\n') 61 | output.close() 62 | finished = True 63 | for i in range(len(volume_list)): 64 | volume_file = volume_filename_testing(result_directory, epoch_list[t], i) 65 | if not os.path.isfile(volume_file): 66 | finished = False 67 | break 68 | if not finished: 69 | net = RSTN(crop_margin=crop_margin, crop_prob=crop_prob, \ 70 | crop_sample_batch=crop_sample_batch, TEST='O').cuda() 71 | net.load_state_dict(torch.load(snapshot[t])) 72 | net.eval() 73 | 74 | for i in range(len(volume_list)): 75 | start_time = time.time() 76 | print('Testing ' + str(i + 1) + ' out of ' + str(len(volume_list)) + ' testcases, ' + \ 77 | str(t + 1) + ' out of ' + str(len(snapshot)) + ' snapshots.') 78 | volume_file = volume_filename_testing(result_directory, epoch_list[t], i) 79 | s = volume_list[i].split(' ') 80 | label = np.load(s[2]) 81 | label = is_organ(label, organ_ID).astype(np.uint8) 82 | if not os.path.isfile(volume_file): 83 | image = np.load(s[1]).astype(np.float32) 84 | np.minimum(np.maximum(image, low_range, image), high_range, image) 85 | image -= low_range 86 | image /= (high_range - low_range) 87 | print(' Data loading is finished: ' + \ 88 | str(time.time() - start_time) + ' second(s) elapsed.') 89 | pred = np.zeros(image.shape, dtype = np.float32) 90 | label_sumX = np.sum(label, axis = (1, 2)) 91 | label_sumY = np.sum(label, axis = (0, 2)) 92 | label_sumZ = np.sum(label, axis = (0, 1)) 93 | if label_sumX.sum() == 0: 94 | continue 95 | minR = 0 96 | if plane == 'X': 97 | maxR = image.shape[0] 98 | shape_ = (1, 3, image.shape[1], image.shape[2]) 99 | elif plane == 'Y': 100 | maxR = image.shape[1] 101 | shape_ = (1, 3, image.shape[0], image.shape[2]) 102 | elif plane == 'Z': 103 | maxR = image.shape[2] 104 | shape_ = (1, 3, image.shape[0], image.shape[1]) 105 | for j in range(minR, maxR): 106 | if slice_thickness == 1: 107 | sID = [j, j, j] 108 | elif slice_thickness == 3: 109 | sID = [max(minR, j - 1), j, min(maxR - 1, j + 1)] 110 | if plane == 'X': 111 | if label_sumX[sID].sum() == 0: 112 | continue 113 | image_ = image[sID, :, :].astype(np.float32) 114 | label_ = label[sID, :, :].astype(np.float32) 115 | elif plane == 'Y': 116 | if label_sumY[sID].sum() == 0: 117 | continue 118 | image_ = image[:, sID, :].transpose(1, 0, 2).astype(np.float32) 119 | label_ = label[:, sID, :].transpose(1, 0, 2).astype(np.float32) 120 | elif plane == 'Z': 121 | if label_sumZ[sID].sum() == 0: 122 | continue 123 | image_ = image[:, :, sID].transpose(2, 0, 1).astype(np.float32) 124 | label_ = label[:, :, sID].transpose(2, 0, 1).astype(np.float32) 125 | 126 | image_ = image_.reshape((1, 3, image_.shape[1], image_.shape[2])) 127 | image_ = torch.from_numpy(image_).cuda().float() 128 | label_ = label_.reshape((1, 3, label_.shape[1], label_.shape[2])) 129 | label_ = torch.from_numpy(label_).cuda().float() 130 | out = net(image_, label_).data.cpu().numpy()[0, :, :, :] 131 | 132 | if slice_thickness == 1: 133 | if plane == 'X': 134 | pred[j, :, :] = out 135 | elif plane == 'Y': 136 | pred[:, j, :] = out 137 | elif plane == 'Z': 138 | pred[:, :, j] = out 139 | elif slice_thickness == 3: 140 | if plane == 'X': 141 | if j == minR: 142 | pred[j: j + 2, :, :] += out[1: 3, :, :] 143 | elif j == maxR - 1: 144 | pred[j - 1: j + 1, :, :] += out[0: 2, :, :] 145 | else: 146 | pred[j - 1: j + 2, :, :] += out[...] 147 | elif plane == 'Y': 148 | if j == minR: 149 | pred[:, j: j + 2, :] += out[1: 3, :, :].transpose(1, 0, 2) 150 | elif j == maxR - 1: 151 | pred[:, j - 1: j + 1, :] += out[0: 2, :, :].transpose(1, 0, 2) 152 | else: 153 | pred[:, j - 1: j + 2, :] += out[...].transpose(1, 0, 2) 154 | elif plane == 'Z': 155 | if j == minR: 156 | pred[:, :, j: j + 2] += out[1: 3, :, :].transpose(1, 2, 0) 157 | elif j == maxR - 1: 158 | pred[:, :, j - 1: j + 1] += out[0: 2, :, :].transpose(1, 2, 0) 159 | else: 160 | pred[:, :, j - 1: j + 2] += out[...].transpose(1, 2, 0) 161 | if slice_thickness == 3: 162 | if plane == 'X': 163 | pred[minR, :, :] /= 2 164 | pred[minR + 1: maxR - 1, :, :] /= 3 165 | pred[maxR - 1, :, :] /= 2 166 | elif plane == 'Y': 167 | pred[:, minR, :] /= 2 168 | pred[:, minR + 1: maxR - 1, :] /= 3 169 | pred[:, maxR - 1, :] /= 2 170 | elif plane == 'Z': 171 | pred[:, :, minR] /= 2 172 | pred[:, :, minR + 1: maxR - 1] /= 3 173 | pred[:, :, maxR - 1] /= 2 174 | print(' Testing is finished: ' + str(time.time() - start_time) + ' second(s) elapsed.') 175 | pred = np.around(pred * 255).astype(np.uint8) 176 | np.savez_compressed(volume_file, volume = pred) 177 | print(' Data saving is finished: ' + \ 178 | str(time.time() - start_time) + ' second(s) elapsed.') 179 | pred_temp = (pred >= 128) 180 | else: 181 | volume_data = np.load(volume_file) 182 | pred = volume_data['volume'].astype(np.uint8) 183 | print(' Testing result is loaded: ' + \ 184 | str(time.time() - start_time) + ' second(s) elapsed.') 185 | pred_temp = (pred >= 128) 186 | 187 | DSC[t, i], inter_sum, pred_sum, label_sum = DSC_computation(label, pred_temp) 188 | print(' DSC = 2 * ' + str(inter_sum) + ' / (' + str(pred_sum) + \ 189 | ' + ' + str(label_sum) + ') = ' + str(DSC[t, i]) + ' .') 190 | output = open(result_file, 'a+') 191 | output.write(' Testcase ' + str(i + 1) + ': DSC = 2 * ' + str(inter_sum) + ' / (' + \ 192 | str(pred_sum) + ' + ' + str(label_sum) + ') = ' + str(DSC[t, i]) + ' .\n') 193 | output.close() 194 | if pred_sum == 0 and label_sum == 0: 195 | DSC[t, i] = 0 196 | print(' DSC computation is finished: ' + \ 197 | str(time.time() - start_time) + ' second(s) elapsed.') 198 | 199 | print('Snapshot ' + str(epoch_list[t]) + ': average DSC = ' + str(np.mean(DSC[t, :])) + ' .') 200 | output = open(result_file, 'a+') 201 | output.write('Snapshot ' + str(epoch_list[t]) + \ 202 | ': average DSC = ' + str(np.mean(DSC[t, :])) + ' .\n') 203 | output.close() 204 | 205 | print('The testing process is finished.') 206 | for t in range(len(snapshot)): 207 | print(' Snapshot ' + str(epoch_list[t]) + ': average DSC = ' + str(np.mean(DSC[t, :])) + ' .') 208 | -------------------------------------------------------------------------------- /OrganSegRSTN/run.sh: -------------------------------------------------------------------------------- 1 | #################################################################################################### 2 | # RSTN: Recurrent Saliency Transformation Network for organ segmentation framework # 3 | # This is PyTorch 0.4.0 Python 3.6 verison of OrganSegRSTN in CAFFE Python 2.7 . # 4 | # Author: Tianwei Ni, Huangjie Zheng, Lingxi Xie. # 5 | # # 6 | # If you use our codes, please cite our paper accordingly: # 7 | # Qihang Yu, Lingxi Xie, Yan Wang, Yuyin Zhou, Elliot K. Fishman, Alan L. Yuille, # 8 | # "Recurrent Saliency Transformation Network: # 9 | # Incorporating Multi-Stage Visual Cues for Small Organ Segmentation", # 10 | # in IEEE Conference on Computer Vision and Pattern Recognition, 2018. # 11 | # # 12 | # NOTE: this program can be used for multi-organ segmentation. # 13 | # Please also refer to its previous version, OrganSegC2F. # 14 | #################################################################################################### 15 | 16 | #################################################################################################### 17 | # variables for convenience 18 | CURRENT_ORGAN_ID=1 19 | CURRENT_PLANE=A 20 | CURRENT_FOLD=0 21 | CURRENT_GPU=$CURRENT_FOLD 22 | 23 | #################################################################################################### 24 | # turn on these switches to execute each module 25 | ENABLE_INITIALIZATION=0 26 | ENABLE_TRAINING=0 27 | ENABLE_COARSE_TESTING=0 28 | ENABLE_COARSE_FUSION=0 29 | ENABLE_ORACLE_TESTING=0 30 | ENABLE_ORACLE_FUSION=0 31 | ENABLE_COARSE2FINE_TESTING=0 32 | # training settings: X|Y|Z 33 | TRAINING_ORGAN_ID=$CURRENT_ORGAN_ID 34 | TRAINING_PLANE=$CURRENT_PLANE 35 | TRAINING_GPU=$CURRENT_GPU 36 | # coarse_testing settings: X|Y|Z, before this, coarse-scaled models shall be ready 37 | COARSE_TESTING_ORGAN_ID=$CURRENT_ORGAN_ID 38 | COARSE_TESTING_PLANE=$CURRENT_PLANE 39 | COARSE_TESTING_GPU=$CURRENT_GPU 40 | # coarse_fusion settings: before this, coarse-scaled results on 3 views shall be ready 41 | COARSE_FUSION_ORGAN_ID=$CURRENT_ORGAN_ID 42 | # oracle_testing settings: X|Y|Z, before this, fine-scaled models shall be ready 43 | ORACLE_TESTING_ORGAN_ID=$CURRENT_ORGAN_ID 44 | ORACLE_TESTING_PLANE=$CURRENT_PLANE 45 | ORACLE_TESTING_GPU=$CURRENT_GPU 46 | # oracle_fusion settings: before this, fine-scaled results on 3 views shall be ready 47 | ORACLE_FUSION_ORGAN_ID=$CURRENT_ORGAN_ID 48 | # fine_testing settings: before this, both coarse-scaled and fine-scaled models shall be ready 49 | COARSE2FINE_TESTING_ORGAN_ID=$CURRENT_ORGAN_ID 50 | COARSE2FINE_TESTING_GPU=$CURRENT_GPU 51 | 52 | #################################################################################################### 53 | # defining the root path which stores image and label data 54 | DATA_PATH='/mnt/data0/tianwei/NIH/' 55 | 56 | #################################################################################################### 57 | # data initialization: only needs to be run once 58 | # variables 59 | ORGAN_NUMBER=1 60 | FOLDS=4 61 | LOW_RANGE=-100 62 | HIGH_RANGE=240 63 | # init.py : data_path, organ_number, folds, low_range, high_range 64 | if [ "$ENABLE_INITIALIZATION" = "1" ] 65 | then 66 | python init.py \ 67 | $DATA_PATH $ORGAN_NUMBER $FOLDS $LOW_RANGE $HIGH_RANGE 68 | fi 69 | 70 | #################################################################################################### 71 | # the individual and joint training processes 72 | # variables 73 | SLICE_THRESHOLD=0.98 74 | SLICE_THICKNESS=3 75 | LEARNING_RATE1=1e-5 76 | LEARNING_RATE2=1e-5 77 | LEARNING_RATE_M1=10 78 | LEARNING_RATE_M2=10 79 | TRAINING_MARGIN=20 80 | TRAINING_PROB=0.5 81 | TRAINING_SAMPLE_BATCH=1 82 | TRAINING_EPOCH_S=2 83 | TRAINING_EPOCH_I=6 84 | TRAINING_EPOCH_J=8 85 | LR_DECAY_EPOCH_J_STEP=2 86 | if [ "$ENABLE_TRAINING" = "1" ] 87 | then 88 | TRAINING_TIMESTAMP=$(date +'%Y%m%d_%H%M%S') 89 | else 90 | TRAINING_TIMESTAMP=_ 91 | fi 92 | # training.py : data_path, current_fold, organ_number, low_range, high_range, 93 | # slice_threshold, slice_thickness, organ_ID, plane, GPU_ID, 94 | # learning_rate1, learning_rate2 (not used), margin, prob, sample_batch, 95 | # step, max_iterations1, max_iterations2 (not used), fraction, timestamp 96 | if [ "$ENABLE_TRAINING" = "1" ] 97 | then 98 | if [ "$TRAINING_PLANE" = "X" ] || [ "$TRAINING_PLANE" = "A" ] 99 | then 100 | TRAINING_MODELNAME=X${SLICE_THICKNESS}_${TRAINING_ORGAN_ID} 101 | TRAINING_LOG=${DATA_PATH}logs/FD${CURRENT_FOLD}:${TRAINING_MODELNAME}_${TRAINING_TIMESTAMP}.txt 102 | python training.py \ 103 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 104 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 105 | $TRAINING_ORGAN_ID X $TRAINING_GPU \ 106 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 107 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 108 | $TRAINING_EPOCH_S $TRAINING_EPOCH_I $TRAINING_EPOCH_J \ 109 | $LR_DECAY_EPOCH_J_STEP $TRAINING_TIMESTAMP 1 2>&1 | tee $TRAINING_LOG 110 | fi 111 | if [ "$TRAINING_PLANE" = "Y" ] || [ "$TRAINING_PLANE" = "A" ] 112 | then 113 | TRAINING_MODELNAME=Y${SLICE_THICKNESS}_${TRAINING_ORGAN_ID} 114 | TRAINING_LOG=${DATA_PATH}logs/FD${CURRENT_FOLD}:${TRAINING_MODELNAME}_${TRAINING_TIMESTAMP}.txt 115 | python training.py \ 116 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 117 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 118 | $TRAINING_ORGAN_ID Y $TRAINING_GPU \ 119 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 120 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 121 | $TRAINING_EPOCH_S $TRAINING_EPOCH_I $TRAINING_EPOCH_J \ 122 | $LR_DECAY_EPOCH_J_STEP $TRAINING_TIMESTAMP 1 2>&1 | tee $TRAINING_LOG 123 | fi 124 | if [ "$TRAINING_PLANE" = "Z" ] || [ "$TRAINING_PLANE" = "A" ] 125 | then 126 | TRAINING_MODELNAME=Z${SLICE_THICKNESS}_${TRAINING_ORGAN_ID} 127 | TRAINING_LOG=${DATA_PATH}logs/FD${CURRENT_FOLD}:${TRAINING_MODELNAME}_${TRAINING_TIMESTAMP}.txt 128 | python training.py \ 129 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 130 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 131 | $TRAINING_ORGAN_ID Z $TRAINING_GPU \ 132 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 133 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 134 | $TRAINING_EPOCH_S $TRAINING_EPOCH_I $TRAINING_EPOCH_J \ 135 | $LR_DECAY_EPOCH_J_STEP $TRAINING_TIMESTAMP 1 2>&1 | tee $TRAINING_LOG 136 | fi 137 | fi 138 | 139 | #################################################################################################### 140 | # the coarse-scaled testing processes 141 | # variables 142 | COARSE_TESTING_EPOCH_S=$TRAINING_EPOCH_S 143 | COARSE_TESTING_EPOCH_I=$TRAINING_EPOCH_I 144 | COARSE_TESTING_EPOCH_J=$TRAINING_EPOCH_J 145 | COARSE_TESTING_EPOCH_STEP=$LR_DECAY_EPOCH_J_STEP 146 | COARSE_TIMESTAMP1=$TRAINING_TIMESTAMP 147 | COARSE_TIMESTAMP2=$TRAINING_TIMESTAMP 148 | # coarse_testing.py : data_path, current_fold, organ_number, low_range, high_range, 149 | # slice_threshold, slice_thickness, organ_ID, plane, GPU_ID, 150 | # learning_rate1, learning_rate2, margin, prob, sample_batch, 151 | # EPOCH_S, EPOCH_I, EPOCH_J, EPOCH_STEP, 152 | # timestamp1, timestamp2 (optional) 153 | if [ "$ENABLE_COARSE_TESTING" = "1" ] 154 | then 155 | if [ "$COARSE_TESTING_PLANE" = "X" ] || [ "$COARSE_TESTING_PLANE" = "A" ] 156 | then 157 | python coarse_testing.py \ 158 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 159 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 160 | $COARSE_TESTING_ORGAN_ID X $COARSE_TESTING_GPU \ 161 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 162 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 163 | $COARSE_TESTING_EPOCH_S $COARSE_TESTING_EPOCH_I \ 164 | $COARSE_TESTING_EPOCH_J $COARSE_TESTING_EPOCH_STEP \ 165 | $COARSE_TIMESTAMP1 $COARSE_TIMESTAMP2 166 | fi 167 | if [ "$COARSE_TESTING_PLANE" = "Y" ] || [ "$COARSE_TESTING_PLANE" = "A" ] 168 | then 169 | python coarse_testing.py \ 170 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 171 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 172 | $COARSE_TESTING_ORGAN_ID Y $COARSE_TESTING_GPU \ 173 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 174 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 175 | $COARSE_TESTING_EPOCH_S $COARSE_TESTING_EPOCH_I \ 176 | $COARSE_TESTING_EPOCH_J $COARSE_TESTING_EPOCH_STEP \ 177 | $COARSE_TIMESTAMP1 $COARSE_TIMESTAMP2 178 | fi 179 | if [ "$COARSE_TESTING_PLANE" = "Z" ] || [ "$COARSE_TESTING_PLANE" = "A" ] 180 | then 181 | python coarse_testing.py \ 182 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 183 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 184 | $COARSE_TESTING_ORGAN_ID Z $COARSE_TESTING_GPU \ 185 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 186 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 187 | $COARSE_TESTING_EPOCH_S $COARSE_TESTING_EPOCH_I \ 188 | $COARSE_TESTING_EPOCH_J $COARSE_TESTING_EPOCH_STEP \ 189 | $COARSE_TIMESTAMP1 $COARSE_TIMESTAMP2 190 | fi 191 | fi 192 | 193 | #################################################################################################### 194 | # the coarse-scaled fusion process 195 | # variables 196 | COARSE_FUSION_EPOCH_S=$TRAINING_EPOCH_S 197 | COARSE_FUSION_EPOCH_I=$TRAINING_EPOCH_I 198 | COARSE_FUSION_EPOCH_J=$TRAINING_EPOCH_J 199 | COARSE_FUSION_EPOCH_STEP=$LR_DECAY_EPOCH_J_STEP 200 | COARSE_FUSION_THRESHOLD=0.5 201 | COARSE_TIMESTAMP1_X=$TRAINING_TIMESTAMP 202 | COARSE_TIMESTAMP1_Y=$TRAINING_TIMESTAMP 203 | COARSE_TIMESTAMP1_Z=$TRAINING_TIMESTAMP 204 | COARSE_TIMESTAMP2_X=$TRAINING_TIMESTAMP 205 | COARSE_TIMESTAMP2_Y=$TRAINING_TIMESTAMP 206 | COARSE_TIMESTAMP2_Z=$TRAINING_TIMESTAMP 207 | # coarse_fusion.py : data_path, current_fold, organ_number, low_range, high_range, 208 | # slice_threshold, slice_thickness, organ_ID, plane, GPU_ID, 209 | # learning_rate1, learning_rate_m1, learning_rate2, learning_rate_m2, margin, 210 | # EPOCH_S, EPOCH_I, EPOCH_J, EPOCH_STEP, threshold, 211 | # timestamp1_X, timestamp1_Y, timestamp1_Z, 212 | # timestamp2_X (optional), timestamp2_Y (optional), timestamp2_Z (optional) 213 | if [ "$ENABLE_COARSE_FUSION" = "1" ] 214 | then 215 | python coarse_fusion.py \ 216 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 217 | $SLICE_THRESHOLD $SLICE_THICKNESS $COARSE_TESTING_ORGAN_ID $COARSE_TESTING_GPU \ 218 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 $TRAINING_MARGIN \ 219 | $COARSE_FUSION_EPOCH_S $COARSE_FUSION_EPOCH_I $COARSE_FUSION_EPOCH_J \ 220 | $COARSE_FUSION_EPOCH_STEP $COARSE_FUSION_THRESHOLD \ 221 | $COARSE_TIMESTAMP1_X $COARSE_TIMESTAMP1_Y $COARSE_TIMESTAMP1_Z \ 222 | $COARSE_TIMESTAMP2_X $COARSE_TIMESTAMP2_Y $COARSE_TIMESTAMP2_Z 223 | fi 224 | 225 | #################################################################################################### 226 | # the oracle testing processes 227 | # variables 228 | ORACLE_TESTING_EPOCH_S=$TRAINING_EPOCH_S 229 | ORACLE_TESTING_EPOCH_I=$TRAINING_EPOCH_I 230 | ORACLE_TESTING_EPOCH_J=$TRAINING_EPOCH_J 231 | ORACLE_TESTING_EPOCH_STEP=$LR_DECAY_EPOCH_J_STEP 232 | ORACLE_TIMESTAMP1=$TRAINING_TIMESTAMP 233 | ORACLE_TIMESTAMP2=$TRAINING_TIMESTAMP 234 | # oracle_testing.py : data_path, current_fold, organ_number, low_range, high_range, 235 | # slice_threshold, slice_thickness, organ_ID, plane, GPU_ID, 236 | # learning_rate1, learning_rate_m1, learning_rate2, learning_rate_m2, 237 | # margin, prob, sample_batch, 238 | # EPOCH_S, EPOCH_I, EPOCH_J, EPOCH_STEP, 239 | # timestamp1, timestamp2 (optional) 240 | if [ "$ENABLE_ORACLE_TESTING" = "1" ] 241 | then 242 | if [ "$ORACLE_TESTING_PLANE" = "X" ] || [ "$ORACLE_TESTING_PLANE" = "A" ] 243 | then 244 | python oracle_testing.py \ 245 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 246 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 247 | $ORACLE_TESTING_ORGAN_ID X $ORACLE_TESTING_GPU \ 248 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 249 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 250 | $ORACLE_TESTING_EPOCH_S $ORACLE_TESTING_EPOCH_I \ 251 | $ORACLE_TESTING_EPOCH_J $ORACLE_TESTING_EPOCH_STEP \ 252 | $ORACLE_TIMESTAMP1 $ORACLE_TIMESTAMP2 253 | fi 254 | if [ "$ORACLE_TESTING_PLANE" = "Y" ] || [ "$ORACLE_TESTING_PLANE" = "A" ] 255 | then 256 | python oracle_testing.py \ 257 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 258 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 259 | $ORACLE_TESTING_ORGAN_ID Y $ORACLE_TESTING_GPU \ 260 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 261 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 262 | $ORACLE_TESTING_EPOCH_S $ORACLE_TESTING_EPOCH_I \ 263 | $ORACLE_TESTING_EPOCH_J $ORACLE_TESTING_EPOCH_STEP \ 264 | $ORACLE_TIMESTAMP1 $ORACLE_TIMESTAMP2 265 | fi 266 | if [ "$ORACLE_TESTING_PLANE" = "Z" ] || [ "$ORACLE_TESTING_PLANE" = "A" ] 267 | then 268 | python oracle_testing.py \ 269 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 270 | $SLICE_THRESHOLD $SLICE_THICKNESS \ 271 | $ORACLE_TESTING_ORGAN_ID Z $ORACLE_TESTING_GPU \ 272 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 \ 273 | $TRAINING_MARGIN $TRAINING_PROB $TRAINING_SAMPLE_BATCH \ 274 | $ORACLE_TESTING_EPOCH_S $ORACLE_TESTING_EPOCH_I \ 275 | $ORACLE_TESTING_EPOCH_J $ORACLE_TESTING_EPOCH_STEP \ 276 | $ORACLE_TIMESTAMP1 $ORACLE_TIMESTAMP2 277 | fi 278 | fi 279 | 280 | #################################################################################################### 281 | # the oracle-scaled fusion process 282 | # variables 283 | ORACLE_FUSION_EPOCH_S=$TRAINING_EPOCH_S 284 | ORACLE_FUSION_EPOCH_I=$TRAINING_EPOCH_I 285 | ORACLE_FUSION_EPOCH_J=$TRAINING_EPOCH_J 286 | ORACLE_FUSION_EPOCH_STEP=$LR_DECAY_EPOCH_J_STEP 287 | ORACLE_FUSION_THRESHOLD=0.5 288 | ORACLE_TIMESTAMP1_X=$TRAINING_TIMESTAMP 289 | ORACLE_TIMESTAMP1_Y=$TRAINING_TIMESTAMP 290 | ORACLE_TIMESTAMP1_Z=$TRAINING_TIMESTAMP 291 | ORACLE_TIMESTAMP2_X=$TRAINING_TIMESTAMP 292 | ORACLE_TIMESTAMP2_Y=$TRAINING_TIMESTAMP 293 | ORACLE_TIMESTAMP2_Z=$TRAINING_TIMESTAMP 294 | # oracle_fusion.py : data_path, current_fold, organ_number, low_range, high_range, 295 | # slice_threshold, slice_thickness, organ_ID, plane, GPU_ID, 296 | # learning_rate1, learning_rate_m1, learning_rate2, learning_rate_m2, margin, 297 | # EPOCH_S, EPOCH_I, EPOCH_J, EPOCH_STEP, threshold, 298 | # timestamp1_X, timestamp1_Y, timestamp1_Z, 299 | # timestamp2_X (optional), timestamp2_Y (optional), timestamp2_Z (optional) 300 | if [ "$ENABLE_ORACLE_FUSION" = "1" ] 301 | then 302 | python oracle_fusion.py \ 303 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 304 | $SLICE_THRESHOLD $SLICE_THICKNESS $ORACLE_TESTING_ORGAN_ID $ORACLE_TESTING_GPU \ 305 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 $TRAINING_MARGIN \ 306 | $ORACLE_FUSION_EPOCH_S $ORACLE_FUSION_EPOCH_I $ORACLE_FUSION_EPOCH_J \ 307 | $ORACLE_FUSION_EPOCH_STEP $ORACLE_FUSION_THRESHOLD \ 308 | $ORACLE_TIMESTAMP1_X $ORACLE_TIMESTAMP1_Y $ORACLE_TIMESTAMP1_Z \ 309 | $ORACLE_TIMESTAMP2_X $ORACLE_TIMESTAMP2_Y $ORACLE_TIMESTAMP2_Z 310 | fi 311 | 312 | #################################################################################################### 313 | # the coarse-to-fine testing process 314 | # variables 315 | FINE_TESTING_EPOCH_S=$TRAINING_EPOCH_S 316 | FINE_TESTING_EPOCH_I=$TRAINING_EPOCH_I 317 | FINE_TESTING_EPOCH_J=$TRAINING_EPOCH_J 318 | FINE_TESTING_EPOCH_STEP=$LR_DECAY_EPOCH_J_STEP 319 | FINE_FUSION_THRESHOLD=0.5 320 | COARSE2FINE_TIMESTAMP1_X=$TRAINING_TIMESTAMP 321 | COARSE2FINE_TIMESTAMP1_Y=$TRAINING_TIMESTAMP 322 | COARSE2FINE_TIMESTAMP1_Z=$TRAINING_TIMESTAMP 323 | COARSE2FINE_TIMESTAMP2_X=$TRAINING_TIMESTAMP 324 | COARSE2FINE_TIMESTAMP2_Y=$TRAINING_TIMESTAMP 325 | COARSE2FINE_TIMESTAMP2_Z=$TRAINING_TIMESTAMP 326 | MAX_ROUNDS=10 327 | # coarse2fine_testing.py : data_path, current_fold, organ_number, low_range, high_range, 328 | # slice_threshold, slice_thickness, organ_ID, GPU_ID, 329 | # learning_rate1, learning_rate_m1, learning_rate2, learning_rate_m2, margin, 330 | # coarse_fusion_starting_iterations, coarse_fusion_step, coarse_fusion_max_iterations, 331 | # coarse_fusion_threshold, coarse_fusion_code, 332 | # EPOCH_S, EPOCH_I, EPOCH_J, EPOCH_STEP, 333 | # fine_fusion_threshold, max_rounds, 334 | # timestamp1_X, timestamp1_Y, timestamp1_Z, 335 | # timestamp2_X (optional), timestamp2_Y (optional), timestamp2_Z (optional) 336 | if [ "$ENABLE_COARSE2FINE_TESTING" = "1" ] 337 | then 338 | python coarse2fine_testing.py \ 339 | $DATA_PATH $CURRENT_FOLD $ORGAN_NUMBER $LOW_RANGE $HIGH_RANGE \ 340 | $SLICE_THRESHOLD $SLICE_THICKNESS $COARSE2FINE_TESTING_ORGAN_ID $COARSE2FINE_TESTING_GPU \ 341 | $LEARNING_RATE1 $LEARNING_RATE_M1 $LEARNING_RATE2 $LEARNING_RATE_M2 $TRAINING_MARGIN \ 342 | $FINE_TESTING_EPOCH_S $FINE_TESTING_EPOCH_I $FINE_TESTING_EPOCH_J $FINE_TESTING_EPOCH_STEP \ 343 | $COARSE_FUSION_THRESHOLD $FINE_FUSION_THRESHOLD $MAX_ROUNDS \ 344 | $COARSE2FINE_TIMESTAMP1_X $COARSE2FINE_TIMESTAMP1_Y $COARSE2FINE_TIMESTAMP1_Z \ 345 | $COARSE2FINE_TIMESTAMP2_X $COARSE2FINE_TIMESTAMP2_Y $COARSE2FINE_TIMESTAMP2_Z 346 | fi 347 | 348 | #################################################################################################### 349 | -------------------------------------------------------------------------------- /OrganSegRSTN/training.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | from utils import * 5 | from model import * 6 | 7 | if __name__ == '__main__': 8 | data_path = sys.argv[1] 9 | current_fold = sys.argv[2] 10 | organ_number = int(sys.argv[3]) 11 | low_range = int(sys.argv[4]) 12 | high_range = int(sys.argv[5]) 13 | slice_threshold = float(sys.argv[6]) 14 | slice_thickness = int(sys.argv[7]) 15 | organ_ID = int(sys.argv[8]) 16 | plane = sys.argv[9] 17 | GPU_ID = int(sys.argv[10]) 18 | learning_rate1 = float(sys.argv[11]) 19 | learning_rate_m1 = int(sys.argv[12]) 20 | learning_rate2 = float(sys.argv[13]) 21 | learning_rate_m2 = int(sys.argv[14]) 22 | crop_margin = int(sys.argv[15]) 23 | crop_prob = float(sys.argv[16]) 24 | crop_sample_batch = int(sys.argv[17]) 25 | snapshot_path = os.path.join(snapshot_path, 'SIJ_training_' + \ 26 | sys.argv[11] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 27 | epoch = {} 28 | epoch['S'] = int(sys.argv[18]) 29 | epoch['I'] = int(sys.argv[19]) 30 | epoch['J'] = int(sys.argv[20]) 31 | epoch['lr_decay'] = int(sys.argv[21]) 32 | timestamp = sys.argv[22] 33 | 34 | if not os.path.exists(snapshot_path): 35 | os.makedirs(snapshot_path) 36 | 37 | FCN_weights = os.path.join(pretrained_model_path, 'fcn8s_from_caffe.pth') 38 | if not os.path.isfile(FCN_weights): 39 | raise RuntimeError('Please Download from the Internet ...') 40 | 41 | from Data import DataLayer 42 | training_set = DataLayer(data_path=data_path, current_fold=int(current_fold), organ_number=organ_number, \ 43 | low_range=low_range, high_range=high_range, slice_threshold=slice_threshold, slice_thickness=slice_thickness, \ 44 | organ_ID=organ_ID, plane=plane) 45 | 46 | batch_size = 1 47 | os.environ["CUDA_VISIBLE_DEVICES"]= str(GPU_ID) 48 | trainloader = torch.utils.data.DataLoader(training_set, batch_size=batch_size, shuffle=True, num_workers=16, drop_last=True) 49 | print(current_fold + plane, len(trainloader)) 50 | print(epoch) 51 | 52 | RSTN_model = RSTN(crop_margin=crop_margin, \ 53 | crop_prob=crop_prob, crop_sample_batch=crop_sample_batch) 54 | RSTN_snapshot = {} 55 | 56 | model_parameters = filter(lambda p: p.requires_grad, RSTN_model.parameters()) 57 | params = sum([np.prod(p.size()) for p in model_parameters]) 58 | print('model parameters:', params) 59 | 60 | optimizer = torch.optim.SGD( 61 | [ 62 | {'params': get_parameters(RSTN_model, coarse=True, bias=False, parallel=False)}, 63 | {'params': get_parameters(RSTN_model, coarse=True, bias=True, parallel=False), 64 | 'lr': learning_rate1 * 2, 'weight_decay': 0}, 65 | {'params': get_parameters(RSTN_model, coarse=False, bias=False, parallel=False), 66 | 'lr': learning_rate1 * 10}, 67 | {'params': get_parameters(RSTN_model, coarse=False, bias=True, parallel=False), 68 | 'lr': learning_rate1 * 20, 'weight_decay': 0} 69 | ], 70 | lr=learning_rate1, 71 | momentum=0.99, 72 | weight_decay=0.0005) 73 | 74 | criterion = DSC_loss() 75 | COARSE_WEIGHT = 1 / 3 76 | 77 | RSTN_model = RSTN_model.cuda() 78 | RSTN_model.train() 79 | 80 | for mode in ['S', 'I', 'J']: 81 | if mode == 'S': 82 | RSTN_dict = RSTN_model.state_dict() 83 | pretrained_dict = torch.load(FCN_weights) # FCN8s(n_class=21) 84 | # 1. filter out unnecessary keys 85 | pretrained_dict_coarse = {'coarse_model.' + k : v 86 | for k, v in pretrained_dict.items() 87 | if 'coarse_model.' + k in RSTN_dict and 'score' not in k} 88 | pretrained_dict_fine = {'fine_model.' + k : v 89 | for k, v in pretrained_dict.items() 90 | if 'fine_model.' + k in RSTN_dict and 'score' not in k} 91 | # 2. overwrite entries in the existing state dict 92 | RSTN_dict.update(pretrained_dict_coarse) 93 | RSTN_dict.update(pretrained_dict_fine) 94 | # 3. load the new state dict 95 | RSTN_model.load_state_dict(RSTN_dict) 96 | print(plane + mode, 'load pre-trained FCN8s model successfully!') 97 | 98 | elif mode == 'I': 99 | print(plane + mode, 'load S model successfully!') 100 | elif mode == 'J': 101 | print(plane + mode, 'load I model successfully!') 102 | else: 103 | raise ValueError("wrong value of mode, should be in ['S', 'I', 'J']") 104 | 105 | try: 106 | for e in range(epoch[mode]): 107 | total_loss = 0.0 108 | total_coarse_loss = 0.0 109 | total_fine_loss = 0.0 110 | start = time.time() 111 | for index, (image, label) in enumerate(trainloader): 112 | start_it = time.time() 113 | optimizer.zero_grad() 114 | image, label = image.cuda().float(), label.cuda().float() 115 | coarse_prob, fine_prob = RSTN_model(image, label, mode=mode) 116 | coarse_loss = criterion(coarse_prob, label) 117 | fine_loss = criterion(fine_prob, label) 118 | loss = COARSE_WEIGHT * coarse_loss \ 119 | + (1 - COARSE_WEIGHT) * fine_loss 120 | total_loss += loss.item() 121 | total_coarse_loss += coarse_loss.item() 122 | total_fine_loss += fine_loss.item() 123 | loss.backward() 124 | optimizer.step() 125 | print(current_fold + plane + mode, "Epoch[%d/%d], Iter[%05d], Coarse/Fine/Avg Loss %.4f/%.4f/%.4f, Time Elapsed %.2fs" \ 126 | %(e+1, epoch[mode], index, coarse_loss.item(), fine_loss.item(), loss.item(), time.time()-start_it)) 127 | del image, label, coarse_prob, fine_prob, loss, coarse_loss, fine_loss 128 | 129 | if mode == 'J' and (e+1) % epoch['lr_decay'] == 0: 130 | print('lr decay') 131 | for param_group in optimizer.param_groups: 132 | param_group['lr'] *= 0.5 133 | 134 | print(current_fold + plane + mode, "Epoch[%d], Total Coarse/Fine/Avg Loss %.4f/%.4f/%.4f, Time elapsed %.2fs" \ 135 | %(e+1, total_coarse_loss / len(trainloader), total_fine_loss / len(trainloader), total_loss / len(trainloader), time.time()-start)) 136 | 137 | except KeyboardInterrupt: 138 | print('!' * 10 , 'save before quitting ...') 139 | finally: 140 | snapshot_name = 'FD' + current_fold + ':' + \ 141 | plane + mode + str(slice_thickness) + '_' + str(organ_ID) + '_' + timestamp 142 | RSTN_snapshot[mode] = os.path.join(snapshot_path, snapshot_name) + '.pkl' 143 | torch.save(RSTN_model.state_dict(), RSTN_snapshot[mode]) 144 | print('#' * 10 , 'end of ' + current_fold + plane + mode + ' training stage!') 145 | 146 | -------------------------------------------------------------------------------- /OrganSegRSTN/training_parallel.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | from utils import * 5 | from model import * 6 | 7 | if __name__ == '__main__': 8 | data_path = sys.argv[1] 9 | current_fold = sys.argv[2] 10 | organ_number = int(sys.argv[3]) 11 | low_range = int(sys.argv[4]) 12 | high_range = int(sys.argv[5]) 13 | slice_threshold = float(sys.argv[6]) 14 | slice_thickness = int(sys.argv[7]) 15 | organ_ID = int(sys.argv[8]) 16 | plane = sys.argv[9] 17 | GPU_ID = int(sys.argv[10]) 18 | learning_rate1 = float(sys.argv[11]) 19 | learning_rate_m1 = int(sys.argv[12]) 20 | learning_rate2 = float(sys.argv[13]) 21 | learning_rate_m2 = int(sys.argv[14]) 22 | crop_margin = int(sys.argv[15]) 23 | crop_prob = float(sys.argv[16]) 24 | crop_sample_batch = int(sys.argv[17]) 25 | snapshot_path = os.path.join(snapshot_path, 'SIJ_training_' + \ 26 | sys.argv[11] + 'x' + str(learning_rate_m1) + ',' + str(crop_margin)) 27 | epoch = {} 28 | epoch['S'] = int(sys.argv[18]) 29 | epoch['I'] = int(sys.argv[19]) 30 | epoch['J'] = int(sys.argv[20]) 31 | epoch['lr_decay'] = int(sys.argv[21]) 32 | timestamp = sys.argv[22] 33 | 34 | if not os.path.exists(snapshot_path): 35 | os.makedirs(snapshot_path) 36 | 37 | FCN_weights = os.path.join(pretrained_model_path, 'fcn8s_from_caffe.pth') 38 | if not os.path.isfile(FCN_weights): 39 | raise RuntimeError('Please Download from the Internet ...') 40 | 41 | from Data import DataLayer 42 | training_set = DataLayer(data_path=data_path, current_fold=int(current_fold), organ_number=organ_number, \ 43 | low_range=low_range, high_range=high_range, slice_threshold=slice_threshold, slice_thickness=slice_thickness, \ 44 | organ_ID=organ_ID, plane=plane) 45 | 46 | batch_size = 4 47 | os.environ["CUDA_VISIBLE_DEVICES"]= '0,1,2,3' 48 | trainloader = torch.utils.data.DataLoader(training_set, batch_size=batch_size, shuffle=True, num_workers=16, drop_last=True) 49 | print(current_fold + plane, len(trainloader)) 50 | print(epoch) 51 | 52 | RSTN_model = nn.DataParallel(RSTN(crop_margin=crop_margin, \ 53 | crop_prob=crop_prob, crop_sample_batch=crop_sample_batch), device_ids=[0,1,2,3]) 54 | RSTN_snapshot = {} 55 | 56 | model_parameters = filter(lambda p: p.requires_grad, RSTN_model.parameters()) 57 | params = sum([np.prod(p.size()) for p in model_parameters]) 58 | print('model parameters:', params) 59 | 60 | optimizer = torch.optim.SGD( 61 | [ 62 | {'params': get_parameters(RSTN_model, coarse=True, bias=False, parallel=True)}, 63 | {'params': get_parameters(RSTN_model, coarse=True, bias=True, parallel=True), 64 | 'lr': learning_rate1 * 2, 'weight_decay': 0}, 65 | {'params': get_parameters(RSTN_model, coarse=False, bias=False, parallel=True), 66 | 'lr': learning_rate1 * 10}, 67 | {'params': get_parameters(RSTN_model, coarse=False, bias=True, parallel=True), 68 | 'lr': learning_rate1 * 20, 'weight_decay': 0} 69 | ], 70 | lr=learning_rate1, 71 | momentum=0.99, 72 | weight_decay=0.0005) 73 | 74 | criterion = DSC_loss() 75 | COARSE_WEIGHT = 1 / 3 76 | 77 | RSTN_model = RSTN_model.cuda() 78 | RSTN_model.train() 79 | 80 | for mode in ['S', 'I', 'J']: 81 | if mode == 'S': 82 | RSTN_dict = RSTN_model.state_dict() 83 | pretrained_dict = torch.load(FCN_weights) # FCN8s(n_class=21) 84 | # 1. filter out unnecessary keys 85 | pretrained_dict_coarse = {'module.coarse_model.' + k : v 86 | for k, v in pretrained_dict.items() 87 | if 'module.coarse_model.' + k in RSTN_dict and 'score' not in k} 88 | pretrained_dict_fine = {'module.fine_model.' + k : v 89 | for k, v in pretrained_dict.items() 90 | if 'module.fine_model.' + k in RSTN_dict and 'score' not in k} 91 | # 2. overwrite entries in the existing state dict 92 | RSTN_dict.update(pretrained_dict_coarse) 93 | RSTN_dict.update(pretrained_dict_fine) 94 | # 3. load the new state dict 95 | RSTN_model.load_state_dict(RSTN_dict) 96 | print(plane + mode, 'load pre-trained FCN8s model successfully!') 97 | 98 | elif mode == 'I': 99 | print(plane + mode, 'load S model successfully!') 100 | elif mode == 'J': 101 | print(plane + mode, 'load I model successfully!') 102 | else: 103 | raise ValueError("wrong value of mode, should be in ['S', 'I', 'J']") 104 | 105 | try: 106 | for e in range(epoch[mode]): 107 | total_loss = 0.0 108 | total_coarse_loss = 0.0 109 | total_fine_loss = 0.0 110 | start = time.time() 111 | for index, (image, label) in enumerate(trainloader): 112 | start_it = time.time() 113 | optimizer.zero_grad() 114 | image, label = image.cuda().float(), label.cuda().float() 115 | coarse_prob, fine_prob = RSTN_model(image, label, mode=mode) 116 | coarse_loss = criterion(coarse_prob, label) 117 | fine_loss = criterion(fine_prob, label) 118 | loss = COARSE_WEIGHT * coarse_loss \ 119 | + (1 - COARSE_WEIGHT) * fine_loss 120 | total_loss += loss.item() 121 | total_coarse_loss += coarse_loss.item() 122 | total_fine_loss += fine_loss.item() 123 | loss.backward() 124 | optimizer.step() 125 | print(current_fold + plane + mode, "Epoch[%d/%d], Iter[%05d], Coarse/Fine/Avg Loss %.4f/%.4f/%.4f, Time Elapsed %.2fs" \ 126 | %(e+1, epoch[mode], index, coarse_loss.item(), fine_loss.item(), loss.item(), time.time()-start_it)) 127 | del image, label, coarse_prob, fine_prob, loss, coarse_loss, fine_loss 128 | 129 | if mode == 'J' and (e+1) % epoch['lr_decay'] == 0: 130 | print('lr decay') 131 | for param_group in optimizer.param_groups: 132 | param_group['lr'] *= 0.5 133 | 134 | print(current_fold + plane + mode, "Epoch[%d], Total Coarse/Fine/Avg Loss %.4f/%.4f/%.4f, Time elapsed %.2fs" \ 135 | %(e+1, total_coarse_loss / len(trainloader), total_fine_loss / len(trainloader), total_loss / len(trainloader), time.time()-start)) 136 | 137 | except KeyboardInterrupt: 138 | print('!' * 10 , 'save before quitting ...') 139 | finally: 140 | snapshot_name = 'FD' + current_fold + ':' + \ 141 | plane + mode + str(slice_thickness) + '_' + str(organ_ID) + '_' + timestamp 142 | RSTN_snapshot[mode] = os.path.join(snapshot_path, snapshot_name) + '.pkl' 143 | torch.save(RSTN_model.state_dict(), RSTN_snapshot[mode]) 144 | print('#' * 10 , 'end of ' + current_fold + plane + mode + ' training stage!') 145 | 146 | -------------------------------------------------------------------------------- /OrganSegRSTN/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | import math 5 | import torch.nn as nn 6 | import fast_functions as ff 7 | 8 | #################################################################################################### 9 | # returning the binary label map by the organ ID (especially useful under overlapping cases) 10 | # label: the label matrix 11 | # organ_ID: the organ ID 12 | def is_organ(label, organ_ID): 13 | return label == organ_ID 14 | 15 | #################################################################################################### 16 | # determining if a sample belongs to the training set by the fold number 17 | # total_samples: the total number of samples 18 | # i: sample ID, an integer in [0, total_samples - 1] 19 | # folds: the total number of folds 20 | # current_fold: the current fold ID, an integer in [0, folds - 1] 21 | def in_training_set(total_samples, i, folds, current_fold): 22 | fold_remainder = folds - total_samples % folds 23 | fold_size = (total_samples - total_samples % folds) / folds 24 | start_index = fold_size * current_fold + max(0, current_fold - fold_remainder) 25 | end_index = fold_size * (current_fold + 1) + max(0, current_fold + 1 - fold_remainder) 26 | return not (i >= start_index and i < end_index) 27 | 28 | #################################################################################################### 29 | # returning the filename of the training set according to the current fold ID 30 | def training_set_filename(current_fold): 31 | return os.path.join(list_path, 'training_' + 'FD' + str(current_fold) + '.txt') 32 | 33 | #################################################################################################### 34 | # returning the filename of the testing set according to the current fold ID 35 | def testing_set_filename(current_fold): 36 | return os.path.join(list_path, 'testing_' + 'FD' + str(current_fold) + '.txt') 37 | 38 | #################################################################################################### 39 | # returning the filename of the log file 40 | def log_filename(snapshot_directory): 41 | count = 0 42 | while True: 43 | count += 1 44 | if count == 1: 45 | log_file_ = os.path.join(snapshot_directory, 'log.txt') 46 | else: 47 | log_file_ = os.path.join(snapshot_directory, 'log' + str(count) + '.txt') 48 | if not os.path.isfile(log_file_): 49 | return log_file_ 50 | 51 | #################################################################################################### 52 | # returning the snapshot name 53 | def snapshot_name_from_timestamp(snapshot_path, \ 54 | current_fold, plane, stage_code, slice_thickness, organ_ID, timestamp): 55 | snapshot_prefix = 'FD' + str(current_fold) + ':' + plane + \ 56 | stage_code + str(slice_thickness) + '_' + str(organ_ID) 57 | if len(timestamp) == 15: 58 | snapshot_prefix = snapshot_prefix + '_' + timestamp 59 | snapshot_name = snapshot_prefix + '.pkl' 60 | if os.path.isfile(os.path.join(snapshot_path, snapshot_name)): 61 | return snapshot_name 62 | else: 63 | return '' 64 | 65 | #################################################################################################### 66 | # returning the result name 67 | def result_name_from_timestamp(result_path, current_fold, \ 68 | plane, stage_code, slice_thickness, organ_ID, volume_list, timestamp): 69 | result_prefix = 'FD' + str(current_fold) + ':' + plane + \ 70 | stage_code + str(slice_thickness) + '_' + str(organ_ID) 71 | if len(timestamp) == 15: 72 | result_prefix = result_prefix + '_' + timestamp 73 | result_name = result_prefix + '.pkl' 74 | if os.path.exists(os.path.join(result_path, result_name, 'volumes')): 75 | return result_name 76 | else: 77 | return '' 78 | 79 | #################################################################################################### 80 | # returning the volume filename as in the testing stage 81 | def volume_filename_testing(result_directory, t, i): 82 | return os.path.join(result_directory, str(t) + '_' + str(i + 1) + '.npz') 83 | 84 | #################################################################################################### 85 | # returning the volume filename as in the fusion stage 86 | def volume_filename_fusion(result_directory, code, i): 87 | return os.path.join(result_directory, code + '_' + str(i + 1) + '.npz') 88 | 89 | #################################################################################################### 90 | # returning the volume filename as in the coarse-to-fine testing stage 91 | def volume_filename_coarse2fine(result_directory, r, i): 92 | return os.path.join(result_directory, 'R' + str(r) + '_' + str(i + 1) + '.npz') 93 | 94 | #################################################################################################### 95 | # computing the DSC together with other values based on the label and prediction volumes 96 | # def DSC_computation(label, pred): 97 | # pred_sum = pred.sum() 98 | # label_sum = label.sum() 99 | # inter_sum = np.logical_and(pred, label).sum() 100 | # return 2 * float(inter_sum) / (pred_sum + label_sum), inter_sum, pred_sum, label_sum 101 | 102 | def DSC_computation(label, pred): 103 | P = np.zeros(3, dtype = np.uint32) 104 | ff.DSC_computation(label, pred, P) 105 | return 2 * float(P[2]) / (P[0] + P[1]), P[2], P[1], P[0] 106 | 107 | #################################################################################################### 108 | # post-processing: preserving the largest connecting component(s) and discarding other voxels 109 | # The floodfill algorithm is used to detect the connecting components. 110 | # In the future version, this function is to be replaced by a C module for speedup! 111 | # F: a binary volume, the volume to be post-processed 112 | # S: a binary volume, the seed voxels (currently defined as those predicted as FG by all 3 views) 113 | # NOTE: a connected component will not be considered if it does not contain any seed voxels 114 | # threshold: a floating point number in [0, 1] determining if a connected component is accepted 115 | # NOTE: accepted if it is not smaller larger than the largest volume times this number 116 | # NOTE: 1 means to only keep the largest one(s), 0 means to keep all 117 | # organ_ID: passed in case that each organ needs to be dealt with differently 118 | # def post_processing(F, S, threshold, organ_ID): 119 | # if F.sum() == 0: 120 | # return F 121 | # if F.sum() >= np.product(F.shape) / 2: 122 | # return F 123 | # height = F.shape[0] 124 | # width = F.shape[1] 125 | # depth = F.shape[2] 126 | # ll = np.array(np.nonzero(S)) 127 | # marked = np.zeros_like(F, dtype = np.bool) 128 | # queue = np.zeros((F.sum(), 3), dtype = np.int) 129 | # volume = np.zeros(F.sum(), dtype = np.int) 130 | # head = 0 131 | # tail = 0 132 | # bestHead = 0 133 | # bestTail = 0 134 | # bestHead2 = 0 135 | # bestTail2 = 0 136 | # for l in range(ll.shape[1]): 137 | # if not marked[ll[0, l], ll[1, l], ll[2, l]]: 138 | # temp = head 139 | # marked[ll[0, l], ll[1, l], ll[2, l]] = True 140 | # queue[tail, :] = [ll[0, l], ll[1, l], ll[2, l]] 141 | # tail = tail + 1 142 | # while (head < tail): 143 | # t1 = queue[head, 0] 144 | # t2 = queue[head, 1] 145 | # t3 = queue[head, 2] 146 | # if t1 > 0 and F[t1 - 1, t2, t3] and not marked[t1 - 1, t2, t3]: 147 | # marked[t1 - 1, t2, t3] = True 148 | # queue[tail, :] = [t1 - 1, t2, t3] 149 | # tail = tail + 1 150 | # if t1 < height - 1 and F[t1 + 1, t2, t3] and not marked[t1 + 1, t2, t3]: 151 | # marked[t1 + 1, t2, t3] = True 152 | # queue[tail, :] = [t1 + 1, t2, t3] 153 | # tail = tail + 1 154 | # if t2 > 0 and F[t1, t2 - 1, t3] and not marked[t1, t2 - 1, t3]: 155 | # marked[t1, t2 - 1, t3] = True 156 | # queue[tail, :] = [t1, t2 - 1, t3] 157 | # tail = tail + 1 158 | # if t2 < width - 1 and F[t1, t2 + 1, t3] and not marked[t1, t2 + 1, t3]: 159 | # marked[t1, t2 + 1, t3] = True 160 | # queue[tail, :] = [t1, t2 + 1, t3] 161 | # tail = tail + 1 162 | # if t3 > 0 and F[t1, t2, t3 - 1] and not marked[t1, t2, t3 - 1]: 163 | # marked[t1, t2, t3 - 1] = True 164 | # queue[tail, :] = [t1, t2, t3 - 1] 165 | # tail = tail + 1 166 | # if t3 < depth - 1 and F[t1, t2, t3 + 1] and not marked[t1, t2, t3 + 1]: 167 | # marked[t1, t2, t3 + 1] = True 168 | # queue[tail, :] = [t1, t2, t3 + 1] 169 | # tail = tail + 1 170 | # head = head + 1 171 | # if tail - temp > bestTail - bestHead: 172 | # bestHead2 = bestHead 173 | # bestTail2 = bestTail 174 | # bestHead = temp 175 | # bestTail = tail 176 | # elif tail - temp > bestTail2 - bestHead2: 177 | # bestHead2 = temp 178 | # bestTail2 = tail 179 | # volume[temp: tail] = tail - temp 180 | # volume = volume[0: tail] 181 | # target_voxel = np.where(volume >= (bestTail - bestHead) * threshold) 182 | # F0 = np.zeros_like(F, dtype = np.bool) 183 | # F0[tuple(map(tuple, np.transpose(queue[target_voxel, :])))] = True 184 | # return F0.astype(np.uint8) 185 | 186 | def post_processing(F, S, threshold, organ_ID): 187 | ff.post_processing(F, S, threshold, False) 188 | return F 189 | 190 | #################################################################################################### 191 | # defining the common variables used throughout the entire flowchart 192 | data_path = sys.argv[1] 193 | image_path = os.path.join(data_path, 'images') 194 | image_path_ = {} 195 | for plane in ['X', 'Y', 'Z']: 196 | image_path_[plane] = os.path.join(data_path, 'images_' + plane) 197 | if not os.path.exists(image_path_[plane]): 198 | os.makedirs(image_path_[plane]) 199 | label_path = os.path.join(data_path, 'labels') 200 | label_path_ = {} 201 | for plane in ['X', 'Y', 'Z']: 202 | label_path_[plane] = os.path.join(data_path, 'labels_' + plane) 203 | if not os.path.exists(label_path_[plane]): 204 | os.makedirs(label_path_[plane]) 205 | list_path = os.path.join(data_path, 'lists') 206 | if not os.path.exists(list_path): 207 | os.makedirs(list_path) 208 | list_training = {} 209 | for plane in ['X', 'Y', 'Z']: 210 | list_training[plane] = os.path.join(list_path, 'training_' + plane + '.txt') 211 | model_path = os.path.join(data_path, 'models') 212 | if not os.path.exists(model_path): 213 | os.makedirs(model_path) 214 | pretrained_model_path = os.path.join(data_path, 'models', 'pretrained') 215 | if not os.path.exists(pretrained_model_path): 216 | os.makedirs(pretrained_model_path) 217 | snapshot_path = os.path.join(data_path, 'models', 'snapshots') 218 | if not os.path.exists(snapshot_path): 219 | os.makedirs(snapshot_path) 220 | log_path = os.path.join(data_path, 'logs') 221 | if not os.path.exists(log_path): 222 | os.makedirs(log_path) 223 | result_path = os.path.join(data_path, 'results') 224 | if not os.path.exists(result_path): 225 | os.makedirs(result_path) 226 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OrganSegRSTN_PyTorch: an end-to-end coarse-to-fine organ segmentation framework 2 | 3 | **This is a re-implementation of [OrganSegRSTN](https://github.com/198808xc/OrganSegRSTN) in PyTorch 0.4, Python 3.6** 4 | 5 | v1.2 - Nov 9 2018 - by Tianwei Ni and Lingxi Xie 6 | 7 | Credit: Original version of [OrganSegRSTN](https://github.com/198808xc/OrganSegRSTN) is implemented in CAFFE by Qihang Yu, Yuyin Zhou and Lingxi Xie. 8 | 9 | **Before you start, please note that there is a LAZY MODE, 10 | which allows you to run the entire framework with ONE click. 11 | Check the contents before Section 4.3 for details.** 12 | 13 | ## 1. Introduction 14 | 15 | OrganSegRSTN is a code package for our paper: 16 | 17 | Qihang Yu, Lingxi Xie, Yan Wang, Yuyin Zhou, Elliot K. Fishman, Alan L. Yuille, 18 | "Recurrent Saliency Transformation Network: Incorporating Multi-Stage Visual Cues for Small Organ Segmentation", 19 | in IEEE Conference on CVPR, Salt Lake City, Utah, USA, 2018. 20 | 21 | OrganSegRSTN is a segmentation framework designed for 3D volumes. 22 | It was originally designed for segmenting abdominal organs in CT scans, 23 | but we believe that it can also be used for other purposes, 24 | such as brain tissue segmentation in fMRI-scanned images. 25 | 26 | OrganSegRSTN is based on the state-of-the-art deep learning techniques. 27 | This code package is to be used with *PyTorch*, a deep learning library. 28 | 29 | It is highly recommended to use one or more modern GPUs for computation. 30 | Using CPUs will take at least 50x more time in computation. 31 | 32 | **We provide an easy implementation in which the training stages has only 1 fine-scaled iteration. 33 | If you hope to add more, please modify the `model.py` accordingly. 34 | As we said in the paper, our strategy of using 1 stage in training and multiple iterations in testing works very well.** 35 | 36 | ## 2. File List 37 | 38 | | Folder/File | Description | 39 | | :-------------------------- | :-------------------------------------------------- | 40 | | `README.md` | the README file | 41 | | | | 42 | | **DATA2NPY/** | codes to transfer the NIH dataset into NPY format | 43 | | `dicom2npy.py` | transferring image data (DICOM) into NPY format | 44 | | `nii2npy.py` | transferring label data (NII) into NPY format | 45 | | | | 46 | | **OrganSegRSTN/** | primary codes of OrganSegRSTN | 47 | | `coarse2fine_testing.py` | the coarse-to-fine testing process | 48 | | `coarse_fusion.py` | the coarse-scaled fusion process | 49 | | `coarse_testing.py` | the coarse-scaled testing process | 50 | | `Data.py` | the data layer | 51 | | `model.py` | the models of RSTN | 52 | | `init.py` | the initialization functions | 53 | | `oracle_fusion.py` | the fusion process with oracle information | 54 | | `oracle_testing.py` | the testing process with oracle information | 55 | | `training.py` | training the coarse and fine stages jointly | 56 | | `training_parallel.py` | training the coarse and fine stages jointly with multi-GPU | 57 | | `run.sh` | the main program to be called in bash shell | 58 | | `utils.py` | the common functions | 59 | | | | 60 | | **SWIG_fast_functions/** | C codes for acceleration in testing process | 61 | | **logs/** | training log files on the NIH dataset | 62 | 63 | 64 | ## 3. Installation 65 | 66 | - Python 3.6+ 67 | - PyTorch 0.4 68 | 69 | ## 4. Usage 70 | 71 | Please follow these steps to reproduce our results on the NIH pancreas segmentation dataset. 72 | 73 | **NOTE**: Here we only provide basic steps to run our codes on the NIH dataset. 74 | For more detailed analysis and empirical guidelines for parameter setting 75 | (this is very important especially when you are using our codes on other datasets), 76 | please refer to our technical report (check our webpage for updates). 77 | 78 | 79 | #### 4.1 Data preparation 80 | 81 | ###### 4.1.1 Download NIH data from https://wiki.cancerimagingarchive.net/display/Public/Pancreas-CT . 82 | You should be able to download image and label data individually. 83 | Suppose your data directory is $RAW_PATH: 84 | The image data are organized as $RAW_PATH/DOI/PANCREAS_00XX/A_LONG_CODE/A_LONG_CODE/ . 85 | The label data are organized as $RAW_PATH/TCIA_pancreas_labels-TIMESTAMP/label00XX.nii.gz . 86 | 87 | ###### 4.1.2 Use our codes to transfer these data into NPY format. 88 | Put dicom2npy.py under $RAW_PATH, and run: python dicom2npy.py . 89 | The transferred data should be put under $RAW_PATH/images/ 90 | Put nii2npy.py under $RAW_PATH, and run: python nii2npy.py . 91 | The transferred data should be put under $RAW_PATH/labels/ 92 | 93 | ###### 4.1.3 Suppose your directory to store experimental data is `$DATA_PATH`: 94 | 95 | Put images/ under $DATA_PATH/ 96 | Put labels/ under $DATA_PATH/ 97 | Download the FCN8s pretrained model below and put it under $DATA_PATH/models/pretrained/ 98 | 99 | 20210414: [The FCN8s pretrained model in PyTorch](https://github.com/wkentaro/pytorch-fcn) the link went error, so you have to pre-train the model using this repo by yourself. 100 | 101 | NOTE: If you use other path(s), please modify the variable(s) in run.sh accordingly. 102 | 103 | #### 4.2 Initialization (requires: 4.1) 104 | 105 | ###### 4.2.1 Check `run.sh` and set $DATA_PATH accordingly. 106 | 107 | ###### 4.2.2 Set `$ENABLE_INITIALIZATION=1` and run this script. 108 | Several folders will be created under $DATA_PATH: 109 | $DATA_PATH/images_X|Y|Z/: the sliced image data (data are sliced for faster I/O). 110 | $DATA_PATH/labels_X|Y|Z/: the sliced label data (data are sliced for faster I/O). 111 | $DATA_PATH/lists/: used for storing training, testing and slice lists. 112 | $DATA_PATH/logs/: used for storing log files during the training process. 113 | $DATA_PATH/models/: used for storing models (snapshots) during the training process. 114 | $DATA_PATH/results/: used for storing testing results (volumes and text results). 115 | According to the I/O speed of your hard drive, the time cost may vary. 116 | For a typical HDD, around 20 seconds are required for a 512x512x300 volume. 117 | This process needs to be executed only once. 118 | 119 | NOTE: if you are using another dataset which contains multiple targets, 120 | you can modify the variables "ORGAN_NUMBER" and "ORGAN_ID" in run.sh, 121 | as well as the "is_organ" function in utils.py to define your mapping function flexibly. 122 | 123 | 124 | ![](icon.png) 125 | **LAZY MODE!** 126 | ![](icon.png) 127 | 128 | You can run all the following modules with **one** execution! 129 | * a) Enable everything (except initialization) in the beginning part. 130 | * b) Set all the "PLANE" variables as "A" (4 in total) in the following part. 131 | * c) Run this manuscript! 132 | 133 | 134 | #### 4.3 Training (requires: 4.2) 135 | 136 | ###### 4.3.1 Check `run.sh` and set `$TRAINING_PLANE` , `$TRAINING_GPU` , `$CURRENT_FOLD`. 137 | 138 | You need to run X|Y|Z planes individually, so you can use 3 GPUs in parallel. 139 | You can also set TRAINING_PLANE=A, so that three planes are trained orderly in one GPU. 140 | You need to set CURRENT_FOLD in {0, 1, ..., FOLDS-1}, which means the testing fold is $CURRENT_FOLD, and training folds are the rest. 141 | 142 | ###### 4.3.2 Set `$ENABLE_TRAINING=1` and run this script. 143 | 144 | The following folders/files will be created: 145 | Under $DATA_PATH/models/snapshots/, a folder named by training information. 146 | Snapshots will be stored in this folder. 147 | On the axial view (training image size is 512x512, small input images make training faster), 148 | each 20 iterations cost ~10s on a Titan-X Pascal GPU, or ~8s on a Titan-Xp GPU. 149 | As described in the code, we need ~80K iterations, which take less than 5 GPU-hours. 150 | 151 | ###### 4.3.3 Important notes on initialization, model mode and model convergence. 152 | 153 | ![](icon.png) 154 | ![](icon.png) 155 | ![](icon.png) 156 | ![](icon.png) 157 | ![](icon.png) 158 | ![](icon.png) 159 | ![](icon.png) 160 | ![](icon.png) 161 | 162 | It is very important to provide a reasonable initialization for our model. 163 | In the previous step of data preparation, we provide a scratch model for the NIH dataset, 164 | in which both the coarse and fine stages are initialized using the weights of an FCN-8s model 165 | (please refer to the [FCN project](https://github.com/shelhamer/fcn.berkeleyvision.org)). 166 | This model was pre-trained on PASCALVOC. 167 | 168 | ###### What does `mode` in RSTN stand for? 169 | 170 | We train RSTN model in **three sequential modes `S,I,J`** in order to make model converge well. 171 | 172 | - `S` stands for Separate. The input pair (image, label) of fine FCN is *irrelevant* to the outputs of coarse FCN. 173 | - `I` stands for Individual. The input (image) of fine FCN is *relevant* to the outputs of coarse FCN, but label is *irrelevant*. 174 | - `J` stands for Joint. The input pair (image, label) of fine FCN is *relevant* to the outputs of coarse FCN. 175 | 176 | ###### How to determine if a model converges and works well? 177 | 178 | The coarse loss in the beginning of training is almost 1.0. 179 | If a model converges, you should observe the loss function values to decrease gradually. 180 | **In order to make it work well, in the end of each training stage, 181 | you need to confirm the average loss to be sufficiently low (e.g. 0.3 in `S`, 0.2 in `I`, 0.15 in `J`).** 182 | 183 | ###### Training RSTN on other CT datasets? 184 | 185 | If you are experimenting on other **CT datasets**, we strongly recommend you to use a pre-trained model, 186 | such as those pre-trained model attached in the last part of this file. 187 | We also provide [a mixed model](http://nothing) (to be provided soon), 188 | which was tuned using all X|Y|Z images of 82 training samples for pancreas segmentation on NIH. 189 | Of course, do not use it to evaluate any NIH data, as all cases have been used for training. 190 | 191 | ###### 4.3.4 Multi-GPU training 192 | 193 | **For your convenience, we provide `training_parallel.py` to support multi-GPU training.** Thus, you just run it instead of `training.py` in the training stage. But you should *pay attention that:* 194 | 195 | - image size must be uniform (but normally, the shape of each medical image case is not identical; in NIH dataset, only Z plane could be trained in parallel without padding into same size) 196 | - `batch_size` should be no less than the number of GPUs (set by `os.environ["CUDA_VISIBLE_DEVICES"]`) 197 | - `parallel` in `get_parameters()` must be set `True`. 198 | - prefix `module.` should be added to the keys of `pretrained_dict` 199 | - last incomplete batch is dropped in `trainloader` 200 | - in `coarse_testing.py` and `coarse2fine_testing.py`: you should wrap the model into `nn.DataParallel` 201 | 202 | #### 4.4 Coarse-scaled testing (requires: 4.3) 203 | 204 | ###### 4.4.1 Check `run.sh` and set `$COARSE_TESTING_PLANE` and `$COARSE_TESTING_GPU`. 205 | 206 | You need to run X|Y|Z planes individually, so you can use 3 GPUs in parallel. 207 | You can also set COARSE_TESTING_PLANE=A, so that three planes are tested orderly in one GPU. 208 | 209 | ###### 4.4.2 Set `$ENABLE_COARSE_TESTING=1` and run this script. 210 | 211 | The following folder will be created: 212 | Under $DATA_PATH/results/, a folder named by training information. 213 | Testing each volume costs ~30 seconds on a Titan-X Pascal GPU, or ~25s on a Titan-Xp GPU. 214 | 215 | #### 4.5 Coarse-scaled fusion (optional) (requires: 4.4) 216 | 217 | ###### 4.5.1 Fusion is performed on CPU and all X|Y|Z planes are combined and executed once. 218 | 219 | ###### 4.5.2 Set `$ENABLE_COARSE_FUSION=1` and run this script. 220 | 221 | The following folder will be created: 222 | Under $DATA_PATH/results/coarse_testing_*, a folder named by fusion information. 223 | The main cost in fusion includes I/O and post-processing (removing non-maximum components). 224 | We have implemented post-processing in C for acceleration (see 4.8.3). 225 | 226 | #### 4.6 Oracle testing (optional) (requires: 4.3) 227 | 228 | **NOTE**: Without this step, you can also run the coarse-to-fine testing process. 229 | This stage is still recommended, so that you can check the quality of the fine-scaled models. 230 | 231 | ###### 4.6.1 Check `run.sh` and set `$ORACLE_TESTING_PLANE` and `$ORACLE_TESTING_GPU`. 232 | You need to run X|Y|Z planes individually, so you can use 3 GPUs in parallel. 233 | You can also set ORACLE_TESTING_PLANE=A, so that three planes are tested orderly in one GPU. 234 | 235 | ###### 4.6.2 Set `$ENABLE_ORACLE_TESTING=1` and run this script. 236 | The following folder will be created: 237 | Under $DATA_PATH/results/, a folder named by training information. 238 | Testing each volume costs ~10 seconds on a Titan-X Pascal GPU, or ~8s on a Titan-Xp GPU. 239 | 240 | 241 | #### 4.7 Oracle fusion (optional) (requires: 4.6) 242 | 243 | **NOTE**: Without this step, you can also run the coarse-to-fine testing process. 244 | This stage is still recommended, so that you can check the quality of the fine-scaled models. 245 | 246 | ###### 4.7.1 Fusion is perfomed on CPU and all X|Y|Z planes are combined and executed once. 247 | 248 | ###### 4.7.2 Set `$ENABLE_ORACLE_FUSION=1` and run this script. 249 | The following folder will be created: 250 | Under $DATA_PATH/results/, a folder named by fusion information. 251 | The main cost in fusion includes I/O and post-processing (removing non-maximum components). 252 | 253 | #### 4.8 Coarse-to-fine testing (requires: 4.4) 254 | 255 | ###### 4.8.1 Check run.sh and set `$COARSE2FINE_TESTING_GPU`. 256 | 257 | Fusion is performed on CPU and all X|Y|Z planes are combined. 258 | Currently X|Y|Z testing processes are executed with one GPU, but it is not time-comsuming. 259 | 260 | ###### 4.8.2 Set `$ENABLE_COARSE2FINE_TESTING=1` and run this script. 261 | 262 | The following folder will be created: 263 | Under $DATA_PATH/results/, a folder named by coarse-to-fine information (very long). 264 | This function calls both fine-scaled testing and fusion codes, so both GPU and CPU are used. 265 | In our future release, we will implement post-processing in C for acceleration. 266 | 267 | ###### 4.8.3 how to compile `fast_functions` for other python version? 268 | 269 | We provide `_fast_functions.so` for python3.6 for acceleration in `coarse2fine_testing.py`, which can be only run in python 3.6 environment. But we also support other python version 3+, here is the **instructions**: 270 | 271 | - First, check your default `python3` version by `ls -l /usr/bin/python*`,ensure that ` /usr/bin/python3` is linked to the `python3.*` version you want. Otherwise, please download `python3.*` you want and `ln -s /usr/bin/python3.* /usr/bin/python3`. 272 | 273 | - Then go to `SWIG_fast_functions` directory, run 274 | 275 | ````bash 276 | $ swig -python -py3 fast_functions.i 277 | $ python3 setup.py build_ext --inplace 278 | $ mv _fast_functions.cpython-3*m-x86_64-linux-gnu.so _fast_functions.so # * depends on py3.x 279 | $ python test.py # test 280 | $ cp {_fast_functions.so,fast_functions.py} ../OrganSegRSTN/ # no space in {} 281 | ```` 282 | 283 | - Finally, you can run `coarse2fine_testing.py` successfully. 284 | 285 | - **If fails,** you can comment `post_processing` and `DSC_computation` in *ff*, and **uncomment those in *vanilla python*.** These functions in vanilla python can be run successfully, but slower than C re-implementation. 286 | 287 | **NOTE**: currently we set the maximal rounds of iteration to be 10 in order to observe the convergence. 288 | Most often, it reaches an inter-DSC of >99% after 3-5 iterations. 289 | If you hope to save time, you can slight modify the codes in coarse2fine_testing.py. 290 | Testing each volume costs ~40 seconds on a Titan-X Pascal GPU, or ~32s on a Titan-Xp GPU. 291 | If you set the threshold to be 99%, this stage will be done within 2 minutes (in average). 292 | 293 | 294 | Congratulations! You have finished the entire process. Check your results now! 295 | 296 | ## 5. Pre-trained Models on the NIH Dataset 297 | 298 | **NOTE**: all these models were trained following our default settings. 299 | 300 | The 82 cases in the NIH dataset are split into 4 folds: 301 | * **Fold #0**: testing on Cases 01, 02, ..., 20; 302 | * **Fold #1**: testing on Cases 21, 22, ..., 40; 303 | * **Fold #2**: testing on Cases 41, 42, ..., 61; 304 | * **Fold #3**: testing on Cases 62, 63, ..., 82. 305 | 306 | We provide the trained models on each plane of `Fold #0`, in total 3 files in the [google drive](https://drive.google.com/drive/folders/1hTV-7aFlU9jaGQl32J8nC13vFS4RVoKt?usp=sharing). They have **84.62% accuracy** of in the coarse-to-fine testing. Each of these models is around 1.07GB, approximately the size of two (coarse+fine) FCN models. 307 | 308 | We also attach the log files and testing results for your reference here. Please refer to the `logs/` folder. 309 | 310 | 311 | ## 6. Contact Information 312 | 313 | If you encounter any problems in using these codes, please open an issue in this repository. 314 | You may also contact Tianwei Ni (twni2016@gmail.com) or Lingxi Xie (198808xc@gmail.com). 315 | 316 | Thanks for your interest! Have fun! 317 | 318 | ## 7. Citation 319 | 320 | If you use our codes, please cite our [paper](https://arxiv.org/abs/1709.04518) accordingly: 321 | 322 | ``` 323 | @inproceedings{yu2018recurrent, 324 | title={Recurrent saliency transformation network: Incorporating multi-stage visual cues for small organ segmentation}, 325 | author={Yu, Qihang and Xie, Lingxi and Wang, Yan and Zhou, Yuyin and Fishman, Elliot K and Yuille, Alan L}, 326 | booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition}, 327 | pages={8280--8289}, 328 | year={2018} 329 | } 330 | ``` 331 | 332 | and possibly, our [previous work](https://arxiv.org/abs/1612.08230) (the basis of this work): 333 | 334 | ``` 335 | @inproceedings{zhou2017fixed, 336 | title={A fixed-point model for pancreas segmentation in abdominal CT scans}, 337 | author={Zhou, Yuyin and Xie, Lingxi and Shen, Wei and Wang, Yan and Fishman, Elliot K and Yuille, Alan L}, 338 | booktitle={International conference on medical image computing and computer-assisted intervention}, 339 | pages={693--701}, 340 | year={2017}, 341 | organization={Springer} 342 | } 343 | ``` 344 | 345 | All the materials released in this library can **ONLY** be used for **RESEARCH** purposes. 346 | 347 | The authors and their institution (JHU/JHMI) preserve the copyright and all legal rights of these codes. 348 | -------------------------------------------------------------------------------- /SWIG_fast_functions/1.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twni2016/OrganSegRSTN_PyTorch/fb51b919448353b52883b550891503fbd0ad0e58/SWIG_fast_functions/1.npz -------------------------------------------------------------------------------- /SWIG_fast_functions/fast_functions.cc: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | #include "stdio.h" 3 | 4 | void post_processing(uint8_t* F, int width, int height, int length, 5 | const uint8_t* S, int width_, int height_, int length_, float threshold, bool top2) { 6 | int F_sum = 0; 7 | for (int i = 0; i < width * height * length; i++) { 8 | F_sum += F[i]; 9 | } 10 | if (F_sum == 0 or F_sum >= width * height * length / 2) { 11 | return; 12 | } 13 | bool* marked = new bool[width * height * length]; 14 | for (int i = 0; i < width * height * length; i++) { 15 | marked[i] = false; 16 | } 17 | int* queue = new int[F_sum]; 18 | int* volume = new int[F_sum]; 19 | int head = 0; 20 | int tail = 0; 21 | int bestHead = 0; 22 | int bestTail = 0; 23 | int bestHead2 = 0; 24 | int bestTail2 = 0; 25 | for (int i = 0; i < width * height * length; i++) { 26 | if (F[i] && !marked[i]) { 27 | int temp = head; 28 | marked[i] = true; 29 | queue[tail] = i; 30 | tail++; 31 | while (head < tail) { 32 | int x = queue[head] / (height * length); 33 | int y = queue[head] % (height * length) / length; 34 | int z = queue[head] % length; 35 | if (x > 0) { 36 | int t = queue[head] - height * length; 37 | if (F[t] && !marked[t]) { 38 | marked[t] = true; 39 | queue[tail] = t; 40 | tail++; 41 | } 42 | } 43 | if (x < width - 1) { 44 | int t = queue[head] + height * length; 45 | if (F[t] && !marked[t]) { 46 | marked[t] = true; 47 | queue[tail] = t; 48 | tail++; 49 | } 50 | } 51 | if (y > 0) { 52 | int t = queue[head] - length; 53 | if (F[t] && !marked[t]) { 54 | marked[t] = true; 55 | queue[tail] = t; 56 | tail++; 57 | } 58 | } 59 | if (y < height - 1) { 60 | int t = queue[head] + length; 61 | if (F[t] && !marked[t]) { 62 | marked[t] = true; 63 | queue[tail] = t; 64 | tail++; 65 | } 66 | } 67 | if (z > 0) { 68 | int t = queue[head] - 1; 69 | if (F[t] && !marked[t]) { 70 | marked[t] = true; 71 | queue[tail] = t; 72 | tail++; 73 | } 74 | } 75 | if (z < length - 1) { 76 | int t = queue[head] + 1; 77 | if (F[t] && !marked[t]) { 78 | marked[t] = true; 79 | queue[tail] = t; 80 | tail++; 81 | } 82 | } 83 | head++; 84 | } 85 | int volume_ = tail - temp; 86 | for (int l = temp; l < tail; l++) { 87 | volume[l] = volume_; 88 | } 89 | if (volume_ > bestTail - bestHead) { 90 | bestHead = temp; 91 | bestTail = tail; 92 | } else if (volume_ > bestTail2 - bestHead2) { 93 | bestHead2 = temp; 94 | bestTail2 = tail; 95 | } 96 | } 97 | } 98 | int min_volume = bestTail - bestHead; 99 | if (top2) { 100 | min_volume = bestTail2 - bestHead2; 101 | } 102 | for (int i = 0; i < width * height * length; i++) { 103 | F[i] = 0; 104 | } 105 | for (int l = 0; l < tail; l++) { 106 | if (volume[l] >= threshold * min_volume) { 107 | F[queue[l]] = 1; 108 | } 109 | } 110 | delete marked; 111 | delete queue; 112 | delete volume; 113 | } 114 | 115 | void DSC_computation(const uint8_t* A, int width1, int height1, int length1, 116 | const uint8_t* G, int width2, int height2, int length2, uint32_t* P, int count) { 117 | P[0] = 0; 118 | P[1] = 0; 119 | P[2] = 0; 120 | for (int i = 0; i < width1 * height1 * length1; i++) { 121 | if (A[i]) { 122 | P[0]++; 123 | if (G[i]) { 124 | P[1]++; 125 | P[2]++; 126 | } 127 | } else if (G[i]) { 128 | P[1]++; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /SWIG_fast_functions/fast_functions.h: -------------------------------------------------------------------------------- 1 | void post_processing(uint8_t* F, int width, int height, int length, 2 | const uint8_t* S, int width_, int height_, int length_, float threshold, bool top2); 3 | 4 | void DSC_computation(const uint8_t* A, int width1, int height1, int length1, 5 | const uint8_t* G, int width2, int height2, int length2, uint32_t* P, int count); 6 | -------------------------------------------------------------------------------- /SWIG_fast_functions/fast_functions.i: -------------------------------------------------------------------------------- 1 | %module fast_functions 2 | %{ 3 | #define SWIG_FILE_WITH_INIT 4 | #include "fast_functions.h" 5 | %} 6 | 7 | %include "numpy.i" 8 | %include "stdint.i" 9 | 10 | %init %{ 11 | import_array(); 12 | %} 13 | 14 | %apply (uint8_t* INPLACE_ARRAY3, int DIM1, int DIM2, int DIM3) 15 | {(uint8_t* F, int width, int height, int length)}; 16 | 17 | %apply (uint8_t* IN_ARRAY3, int DIM1, int DIM2, int DIM3) 18 | {(const uint8_t* S, int width_, int height_, int length_)}; 19 | 20 | %apply (uint8_t* IN_ARRAY3, int DIM1, int DIM2, int DIM3) 21 | {(const uint8_t* A, int width1, int height1, int length1)}; 22 | 23 | %apply (uint8_t* IN_ARRAY3, int DIM1, int DIM2, int DIM3) 24 | {(const uint8_t* G, int width2, int height2, int length2)}; 25 | 26 | %apply (uint32_t* INPLACE_ARRAY1, int DIM1) 27 | {(uint32_t* P, int count)}; 28 | 29 | %include "fast_functions.h" 30 | -------------------------------------------------------------------------------- /SWIG_fast_functions/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | import numpy as np 3 | import os 4 | import shutil 5 | 6 | 7 | os.environ['CC'] = 'g++'; 8 | 9 | setup(name = 'fast cpp functions', version = '1.0', \ 10 | ext_modules = [Extension('_fast_functions', \ 11 | ['fast_functions.cc', 'fast_functions.i'], include_dirs = [np.get_include(), '.'])]) 12 | 13 | #shutil.copyfile('build/lib.linux-x86_64-2.7/_fast_functions.so', '_fast_functions.so') 14 | -------------------------------------------------------------------------------- /SWIG_fast_functions/test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import fast_functions as ff 3 | import time 4 | 5 | 6 | def DSC_computation(label, pred): 7 | pred_sum = pred.sum() 8 | label_sum = label.sum() 9 | inter_sum = (pred & label).sum() 10 | return 2 * float(inter_sum) / (pred_sum + label_sum), inter_sum, pred_sum, label_sum 11 | 12 | 13 | def post_processing(F, S, threshold, top2): 14 | F_sum = F.sum() 15 | if F_sum == 0: 16 | return F 17 | if F_sum >= np.product(F.shape) / 2: 18 | return F 19 | height = F.shape[0] 20 | width = F.shape[1] 21 | depth = F.shape[2] 22 | ll = np.array(np.nonzero(S)) 23 | marked = np.zeros(F.shape, dtype = np.bool) 24 | queue = np.zeros((F_sum, 3), dtype = np.int) 25 | volume = np.zeros(F_sum, dtype = np.int) 26 | head = 0 27 | tail = 0 28 | bestHead = 0 29 | bestTail = 0 30 | bestHead2 = 0 31 | bestTail2 = 0 32 | for l in range(ll.shape[1]): 33 | if not marked[ll[0, l], ll[1, l], ll[2, l]]: 34 | temp = head 35 | marked[ll[0, l], ll[1, l], ll[2, l]] = True 36 | queue[tail, :] = [ll[0, l], ll[1, l], ll[2, l]] 37 | tail = tail + 1 38 | while (head < tail): 39 | t1 = queue[head, 0] 40 | t2 = queue[head, 1] 41 | t3 = queue[head, 2] 42 | if t1 > 0 and F[t1 - 1, t2, t3] and not marked[t1 - 1, t2, t3]: 43 | marked[t1 - 1, t2, t3] = True 44 | queue[tail, :] = [t1 - 1, t2, t3] 45 | tail = tail + 1 46 | if t1 < height - 1 and F[t1 + 1, t2, t3] and not marked[t1 + 1, t2, t3]: 47 | marked[t1 + 1, t2, t3] = True 48 | queue[tail, :] = [t1 + 1, t2, t3] 49 | tail = tail + 1 50 | if t2 > 0 and F[t1, t2 - 1, t3] and not marked[t1, t2 - 1, t3]: 51 | marked[t1, t2 - 1, t3] = True 52 | queue[tail, :] = [t1, t2 - 1, t3] 53 | tail = tail + 1 54 | if t2 < width - 1 and F[t1, t2 + 1, t3] and not marked[t1, t2 + 1, t3]: 55 | marked[t1, t2 + 1, t3] = True 56 | queue[tail, :] = [t1, t2 + 1, t3] 57 | tail = tail + 1 58 | if t3 > 0 and F[t1, t2, t3 - 1] and not marked[t1, t2, t3 - 1]: 59 | marked[t1, t2, t3 - 1] = True 60 | queue[tail, :] = [t1, t2, t3 - 1] 61 | tail = tail + 1 62 | if t3 < depth - 1 and F[t1, t2, t3 + 1] and not marked[t1, t2, t3 + 1]: 63 | marked[t1, t2, t3 + 1] = True 64 | queue[tail, :] = [t1, t2, t3 + 1] 65 | tail = tail + 1 66 | head = head + 1 67 | if tail - temp > bestTail - bestHead: 68 | bestHead2 = bestHead 69 | bestTail2 = bestTail 70 | bestHead = temp 71 | bestTail = tail 72 | elif tail - temp > bestTail2 - bestHead2: 73 | bestHead2 = temp 74 | bestTail2 = tail 75 | volume[temp: tail] = tail - temp 76 | volume = volume[0: tail] 77 | if top2: 78 | target_voxel = np.where(volume >= (bestTail2 - bestHead2) * threshold) 79 | else: 80 | target_voxel = np.where(volume >= (bestTail - bestHead) * threshold) 81 | F0 = np.zeros(F.shape, dtype = np.bool) 82 | F0[tuple(map(tuple, np.transpose(queue[target_voxel, :])))] = True 83 | return F0 84 | 85 | print('python') 86 | G = np.zeros((512,512,240),dtype=np.uint8) 87 | G[128:384,128:384,60:180]=1 88 | volume_data = np.load('1.npz') 89 | F = volume_data['volume'].astype(np.uint8) 90 | start_time = time.time() 91 | F = post_processing(F, F, 1.0, False) 92 | print(time.time() - start_time) 93 | start_time = time.time() 94 | for l in range(10): 95 | DSC = DSC_computation(F,G) 96 | print(DSC) 97 | print(time.time() - start_time) 98 | 99 | 100 | print('SWIG') 101 | volume_data = np.load('1.npz') 102 | G = np.zeros((512,512,240),dtype=np.uint8) 103 | G[128:384,128:384,60:180]=1 104 | F = volume_data['volume'].astype(np.uint8) 105 | start_time = time.time() 106 | ff.post_processing(F, F, 1.0, False) 107 | print(time.time() - start_time) 108 | start_time = time.time() 109 | for l in range(10): 110 | P = np.zeros(3, dtype = np.uint32) 111 | ff.DSC_computation(F,G,P) 112 | print(P, float(P[2]) * 2 / (P[0] + P[1])) 113 | print(time.time() - start_time) 114 | 115 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twni2016/OrganSegRSTN_PyTorch/fb51b919448353b52883b550891503fbd0ad0e58/icon.png -------------------------------------------------------------------------------- /logs/FD0_coarse2fine_results.txt: -------------------------------------------------------------------------------- 1 | Fusing results of 1 snapshots: 2 | Testcase 1: 3 | Round 0, DSC = 2 * 48622 / (53401 + 78329) = 0.738206938434677 . 4 | Round 1, DSC = 2 * 59839 / (67649 + 78329) = 0.8198358656783898 . 5 | Inter-iteration DSC = 2 * 50987 / (67649 + 53401) = 0.8424122263527468 . 6 | Round 2, DSC = 2 * 63246 / (72464 + 78329) = 0.8388453044902615 . 7 | Inter-iteration DSC = 2 * 66093 / (72464 + 67649) = 0.9434242361522486 . 8 | Round 3, DSC = 2 * 65065 / (75222 + 78329) = 0.8474708728696003 . 9 | Inter-iteration DSC = 2 * 71189 / (75222 + 72464) = 0.9640588816814052 . 10 | Round 4, DSC = 2 * 65946 / (76688 + 78329) = 0.850822812981802 . 11 | Inter-iteration DSC = 2 * 74134 / (76688 + 75222) = 0.9760252781252057 . 12 | Round 5, DSC = 2 * 66299 / (77449 + 78329) = 0.8511985004300993 . 13 | Inter-iteration DSC = 2 * 75728 / (77449 + 76688) = 0.9826063826336311 . 14 | Round 6, DSC = 2 * 66482 / (77952 + 78329) = 0.8508008011210576 . 15 | Inter-iteration DSC = 2 * 76639 / (77952 + 77449) = 0.9863385692498762 . 16 | Round 7, DSC = 2 * 66595 / (78309 + 78329) = 0.8503045238064837 . 17 | Inter-iteration DSC = 2 * 77199 / (78309 + 77952) = 0.9880776393341909 . 18 | Round 8, DSC = 2 * 66601 / (78528 + 78329) = 0.8491938517248194 . 19 | Inter-iteration DSC = 2 * 77646 / (78528 + 78309) = 0.9901490082059718 . 20 | Round 9, DSC = 2 * 66640 / (78708 + 78329) = 0.848717181301222 . 21 | Inter-iteration DSC = 2 * 77946 / (78708 + 78528) = 0.9914523391589712 . 22 | Round 10, DSC = 2 * 66605 / (78808 + 78329) = 0.8477315972686255 . 23 | Inter-iteration DSC = 2 * 78093 / (78808 + 78708) = 0.9915564133167424 . 24 | Testcase 2: 25 | Round 0, DSC = 2 * 86183 / (128667 + 110797) = 0.7197992182541009 . 26 | Round 1, DSC = 2 * 87628 / (127831 + 110797) = 0.7344318353252761 . 27 | Inter-iteration DSC = 2 * 120467 / (127831 + 128667) = 0.9393211642975774 . 28 | Round 2, DSC = 2 * 86826 / (126114 + 110797) = 0.7329841163981411 . 29 | Inter-iteration DSC = 2 * 123836 / (126114 + 127831) = 0.975297800704877 . 30 | Round 3, DSC = 2 * 86489 / (125433 + 110797) = 0.7322439994920205 . 31 | Inter-iteration DSC = 2 * 124007 / (125433 + 126114) = 0.985954911010666 . 32 | Round 4, DSC = 2 * 86223 / (124909 + 110797) = 0.7316148082781092 . 33 | Inter-iteration DSC = 2 * 123952 / (124909 + 125433) = 0.9902613225108052 . 34 | Round 5, DSC = 2 * 86058 / (124702 + 110797) = 0.7308566066097946 . 35 | Inter-iteration DSC = 2 * 123936 / (124702 + 124909) = 0.9930331595963319 . 36 | Round 6, DSC = 2 * 86057 / (124648 + 110797) = 0.7310157361591879 . 37 | Inter-iteration DSC = 2 * 123968 / (124648 + 124702) = 0.994329256065771 . 38 | Round 7, DSC = 2 * 86036 / (124632 + 110797) = 0.7308870190163489 . 39 | Inter-iteration DSC = 2 * 124063 / (124632 + 124648) = 0.9953706675224647 . 40 | Round 8, DSC = 2 * 86012 / (124602 + 110797) = 0.7307762564836724 . 41 | Inter-iteration DSC = 2 * 124166 / (124602 + 124632) = 0.9963809111116461 . 42 | Round 9, DSC = 2 * 86032 / (124618 + 110797) = 0.7308965019221375 . 43 | Inter-iteration DSC = 2 * 124162 / (124618 + 124602) = 0.9964047829227188 . 44 | Round 10, DSC = 2 * 86044 / (124660 + 110797) = 0.730868056587827 . 45 | Inter-iteration DSC = 2 * 124295 / (124660 + 124618) = 0.9972400292043422 . 46 | Testcase 3: 47 | Round 0, DSC = 2 * 79562 / (91187 + 125067) = 0.7358199154697717 . 48 | Round 1, DSC = 2 * 103071 / (123781 + 125067) = 0.828385198996978 . 49 | Inter-iteration DSC = 2 * 89145 / (123781 + 91187) = 0.8293792564474712 . 50 | Round 2, DSC = 2 * 107719 / (131132 + 125067) = 0.8409010183490178 . 51 | Inter-iteration DSC = 2 * 120915 / (131132 + 123781) = 0.9486766073130833 . 52 | Round 3, DSC = 2 * 109367 / (134439 + 125067) = 0.8428860989726634 . 53 | Inter-iteration DSC = 2 * 129316 / (134439 + 131132) = 0.9738713940904692 . 54 | Round 4, DSC = 2 * 110188 / (136270 + 125067) = 0.8432636786983856 . 55 | Inter-iteration DSC = 2 * 132878 / (136270 + 134439) = 0.9817036005452349 . 56 | Round 5, DSC = 2 * 110657 / (137230 + 125067) = 0.8437534550528599 . 57 | Inter-iteration DSC = 2 * 134993 / (137230 + 136270) = 0.9871517367458866 . 58 | Round 6, DSC = 2 * 110966 / (137971 + 125067) = 0.8437260015663136 . 59 | Inter-iteration DSC = 2 * 136088 / (137971 + 137230) = 0.9890080341277829 . 60 | Round 7, DSC = 2 * 111126 / (138448 + 125067) = 0.8434130884389883 . 61 | Inter-iteration DSC = 2 * 136952 / (138448 + 137971) = 0.9909014937468119 . 62 | Round 8, DSC = 2 * 111254 / (138823 + 125067) = 0.8431846602751146 . 63 | Inter-iteration DSC = 2 * 137583 / (138823 + 138448) = 0.9924081494278161 . 64 | Round 9, DSC = 2 * 111281 / (138976 + 125067) = 0.8429005881617767 . 65 | Inter-iteration DSC = 2 * 137993 / (138976 + 138823) = 0.9934736986094262 . 66 | Round 10, DSC = 2 * 111335 / (139094 + 125067) = 0.8429329083399896 . 67 | Inter-iteration DSC = 2 * 138155 / (139094 + 138976) = 0.9936706584672924 . 68 | Testcase 4: 69 | Round 0, DSC = 2 * 44088 / (61324 + 63116) = 0.7085824493731919 . 70 | Round 1, DSC = 2 * 52200 / (74249 + 63116) = 0.760018927674444 . 71 | Inter-iteration DSC = 2 * 57198 / (74249 + 61324) = 0.8437963311278794 . 72 | Round 2, DSC = 2 * 54281 / (79460 + 63116) = 0.7614324991583437 . 73 | Inter-iteration DSC = 2 * 72809 / (79460 + 74249) = 0.9473615728421888 . 74 | Round 3, DSC = 2 * 55055 / (81733 + 63116) = 0.7601709366305601 . 75 | Inter-iteration DSC = 2 * 78250 / (81733 + 79460) = 0.9708858325113373 . 76 | Round 4, DSC = 2 * 55466 / (82925 + 63116) = 0.7595949082791819 . 77 | Inter-iteration DSC = 2 * 80686 / (82925 + 81733) = 0.9800434840700117 . 78 | Round 5, DSC = 2 * 55598 / (83411 + 63116) = 0.7588772035188054 . 79 | Inter-iteration DSC = 2 * 82011 / (83411 + 82925) = 0.9860883993843786 . 80 | Round 6, DSC = 2 * 55666 / (83674 + 63116) = 0.7584440356972546 . 81 | Inter-iteration DSC = 2 * 82545 / (83674 + 83411) = 0.9880599694766137 . 82 | Round 7, DSC = 2 * 55672 / (83807 + 63116) = 0.7578391402299164 . 83 | Inter-iteration DSC = 2 * 82958 / (83807 + 83674) = 0.9906556564625241 . 84 | Round 8, DSC = 2 * 55666 / (83869 + 63116) = 0.7574378337925639 . 85 | Inter-iteration DSC = 2 * 83131 / (83869 + 83807) = 0.991567069825139 . 86 | Round 9, DSC = 2 * 55571 / (83825 + 63116) = 0.7563716049298698 . 87 | Inter-iteration DSC = 2 * 83211 / (83825 + 83869) = 0.9924147554474221 . 88 | Round 10, DSC = 2 * 55510 / (83792 + 63116) = 0.7557110572603262 . 89 | Inter-iteration DSC = 2 * 83044 / (83792 + 83825) = 0.990878013566643 . 90 | Testcase 5: 91 | Round 0, DSC = 2 * 62192 / (71581 + 79155) = 0.8251777942893536 . 92 | Round 1, DSC = 2 * 69730 / (80188 + 79155) = 0.8752188674745675 . 93 | Inter-iteration DSC = 2 * 67943 / (80188 + 71581) = 0.8953475347402962 . 94 | Round 2, DSC = 2 * 70883 / (81706 + 79155) = 0.8812950311138188 . 95 | Inter-iteration DSC = 2 * 78533 / (81706 + 80188) = 0.9701780177152952 . 96 | Round 3, DSC = 2 * 71159 / (81902 + 79155) = 0.8836498879278765 . 97 | Inter-iteration DSC = 2 * 80431 / (81902 + 81706) = 0.9832159796586964 . 98 | Round 4, DSC = 2 * 71275 / (81949 + 79155) = 0.8848321581090476 . 99 | Inter-iteration DSC = 2 * 81053 / (81949 + 81902) = 0.9893500802558421 . 100 | Round 5, DSC = 2 * 71404 / (82013 + 79155) = 0.8860816042886925 . 101 | Inter-iteration DSC = 2 * 81331 / (82013 + 81949) = 0.9920713336016882 . 102 | Round 6, DSC = 2 * 71513 / (82098 + 79155) = 0.8869664440351497 . 103 | Inter-iteration DSC = 2 * 81502 / (82098 + 82013) = 0.9932545655074919 . 104 | Round 7, DSC = 2 * 71552 / (82143 + 79155) = 0.8872025691577081 . 105 | Inter-iteration DSC = 2 * 81665 / (82143 + 82098) = 0.9944532729342855 . 106 | Round 8, DSC = 2 * 71576 / (82152 + 79155) = 0.8874506376040717 . 107 | Inter-iteration DSC = 2 * 81813 / (82152 + 82143) = 0.9959280562402995 . 108 | Round 9, DSC = 2 * 71589 / (82158 + 79155) = 0.8875788064198173 . 109 | Inter-iteration DSC = 2 * 81803 / (82158 + 82152) = 0.9957154159819853 . 110 | Round 10, DSC = 2 * 71596 / (82165 + 79155) = 0.8876270766179023 . 111 | Inter-iteration DSC = 2 * 81833 / (82165 + 82158) = 0.9960017769880053 . 112 | Testcase 6: 113 | Round 0, DSC = 2 * 53267 / (58685 + 69120) = 0.8335667618637769 . 114 | Round 1, DSC = 2 * 59003 / (64086 + 69120) = 0.8858910259297629 . 115 | Inter-iteration DSC = 2 * 54904 / (64086 + 58685) = 0.8944131757499735 . 116 | Round 2, DSC = 2 * 59837 / (65586 + 69120) = 0.8884088310839903 . 117 | Inter-iteration DSC = 2 * 62968 / (65586 + 64086) = 0.9711888457030045 . 118 | Round 3, DSC = 2 * 60189 / (66248 + 69120) = 0.8892648188641333 . 119 | Inter-iteration DSC = 2 * 64876 / (66248 + 65586) = 0.9842074123518971 . 120 | Round 4, DSC = 2 * 60411 / (66575 + 69120) = 0.8903938980802535 . 121 | Inter-iteration DSC = 2 * 65785 / (66575 + 66248) = 0.990566392868705 . 122 | Round 5, DSC = 2 * 60570 / (66810 + 69120) = 0.8911939969101743 . 123 | Inter-iteration DSC = 2 * 66246 / (66810 + 66575) = 0.993305094275968 . 124 | Round 6, DSC = 2 * 60694 / (66999 + 69120) = 0.8917785173267508 . 125 | Inter-iteration DSC = 2 * 66633 / (66999 + 66810) = 0.995941976997063 . 126 | Round 7, DSC = 2 * 60784 / (67120 + 69120) = 0.8923076923076924 . 127 | Inter-iteration DSC = 2 * 66880 / (67120 + 66999) = 0.9973232726161095 . 128 | Round 8, DSC = 2 * 60835 / (67186 + 69120) = 0.8926239490558009 . 129 | Inter-iteration DSC = 2 * 67043 / (67186 + 67120) = 0.9983619495778298 . 130 | Round 9, DSC = 2 * 60862 / (67206 + 69120) = 0.892889104059387 . 131 | Inter-iteration DSC = 2 * 67116 / (67206 + 67186) = 0.9988094529436276 . 132 | Round 10, DSC = 2 * 60885 / (67234 + 69120) = 0.8930431083796588 . 133 | Inter-iteration DSC = 2 * 67174 / (67234 + 67206) = 0.9993156798571854 . 134 | Testcase 7: 135 | Round 0, DSC = 2 * 58732 / (62814 + 90326) = 0.7670366984458665 . 136 | Round 1, DSC = 2 * 75922 / (84152 + 90326) = 0.8702759087105537 . 137 | Inter-iteration DSC = 2 * 61862 / (84152 + 62814) = 0.8418545786100187 . 138 | Round 2, DSC = 2 * 80720 / (91040 + 90326) = 0.8901337626677547 . 139 | Inter-iteration DSC = 2 * 83396 / (91040 + 84152) = 0.9520526051417872 . 140 | Round 3, DSC = 2 * 82286 / (93648 + 90326) = 0.8945394457912531 . 141 | Inter-iteration DSC = 2 * 90354 / (93648 + 91040) = 0.9784501429437754 . 142 | Round 4, DSC = 2 * 82813 / (94759 + 90326) = 0.8948645217062431 . 143 | Inter-iteration DSC = 2 * 93111 / (94759 + 93648) = 0.9884027663515687 . 144 | Round 5, DSC = 2 * 83099 / (95330 + 90326) = 0.895193260654113 . 145 | Inter-iteration DSC = 2 * 94276 / (95330 + 94759) = 0.9919143138214205 . 146 | Round 6, DSC = 2 * 83206 / (95618 + 90326) = 0.8949576216495289 . 147 | Inter-iteration DSC = 2 * 94930 / (95618 + 95330) = 0.9943021136644532 . 148 | Round 7, DSC = 2 * 83224 / (95709 + 90326) = 0.894713360389174 . 149 | Inter-iteration DSC = 2 * 95213 / (95709 + 95618) = 0.9952907848866077 . 150 | Round 8, DSC = 2 * 83258 / (95808 + 90326) = 0.8946028130271739 . 151 | Inter-iteration DSC = 2 * 95352 / (95808 + 95709) = 0.9957549460361221 . 152 | Round 9, DSC = 2 * 83281 / (95856 + 90326) = 0.894619243535895 . 153 | Inter-iteration DSC = 2 * 95549 / (95856 + 95808) = 0.9970469154353452 . 154 | Round 10, DSC = 2 * 83274 / (95880 + 90326) = 0.8944287509532454 . 155 | Inter-iteration DSC = 2 * 95587 / (95880 + 95856) = 0.9970688863854467 . 156 | Testcase 8: 157 | Round 0, DSC = 2 * 127899 / (141875 + 156716) = 0.8566835571065438 . 158 | Round 1, DSC = 2 * 138950 / (154262 + 156716) = 0.8936323469827447 . 159 | Inter-iteration DSC = 2 * 136628 / (154262 + 141875) = 0.9227350854503152 . 160 | Round 2, DSC = 2 * 141881 / (158771 + 156716) = 0.8994411814115955 . 161 | Inter-iteration DSC = 2 * 152368 / (158771 + 154262) = 0.9734948072567429 . 162 | Round 3, DSC = 2 * 143155 / (160820 + 156716) = 0.9016615438879371 . 163 | Inter-iteration DSC = 2 * 157674 / (160820 + 158771) = 0.9867236561730461 . 164 | Round 4, DSC = 2 * 143793 / (161746 + 156716) = 0.903046517323888 . 165 | Inter-iteration DSC = 2 * 159829 / (161746 + 160820) = 0.9909847907094982 . 166 | Round 5, DSC = 2 * 144171 / (162267 + 156716) = 0.9039415893636965 . 167 | Inter-iteration DSC = 2 * 160917 / (162267 + 161746) = 0.9932749611898288 . 168 | Round 6, DSC = 2 * 144492 / (162618 + 156716) = 0.9049584447631633 . 169 | Inter-iteration DSC = 2 * 161572 / (162618 + 162267) = 0.9946411807254875 . 170 | Round 7, DSC = 2 * 144745 / (163006 + 156716) = 0.9054428534789598 . 171 | Inter-iteration DSC = 2 * 162019 / (163006 + 162618) = 0.995129351644842 . 172 | Round 8, DSC = 2 * 144888 / (163254 + 156716) = 0.9056349032721818 . 173 | Inter-iteration DSC = 2 * 162456 / (163254 + 163006) = 0.995868325875069 . 174 | Round 9, DSC = 2 * 145036 / (163476 + 156716) = 0.9059314411353188 . 175 | Inter-iteration DSC = 2 * 162646 / (163476 + 163254) = 0.9955988124751324 . 176 | Round 10, DSC = 2 * 145174 / (163704 + 156716) = 0.9061481805130766 . 177 | Inter-iteration DSC = 2 * 162945 / (163704 + 163476) = 0.9960572162112599 . 178 | Testcase 9: 179 | Round 0, DSC = 2 * 22076 / (23883 + 81152) = 0.4203551197219974 . 180 | Round 1, DSC = 2 * 28157 / (30276 + 81152) = 0.5053846429981692 . 181 | Inter-iteration DSC = 2 * 21115 / (30276 + 23883) = 0.7797411325910745 . 182 | Round 2, DSC = 2 * 32623 / (35701 + 81152) = 0.5583596484471943 . 183 | Inter-iteration DSC = 2 * 29957 / (35701 + 30276) = 0.9081043393909999 . 184 | Round 3, DSC = 2 * 35028 / (38867 + 81152) = 0.5837075796332247 . 185 | Inter-iteration DSC = 2 * 35401 / (38867 + 35701) = 0.9494957622572685 . 186 | Round 4, DSC = 2 * 36357 / (40792 + 81152) = 0.5962901003739421 . 187 | Inter-iteration DSC = 2 * 38484 / (40792 + 38867) = 0.9662185063834595 . 188 | Round 5, DSC = 2 * 37342 / (42211 + 81152) = 0.6054003226250982 . 189 | Inter-iteration DSC = 2 * 40364 / (42211 + 40792) = 0.9725913521198029 . 190 | Round 6, DSC = 2 * 38077 / (43108 + 81152) = 0.6128601319813295 . 191 | Inter-iteration DSC = 2 * 41665 / (43108 + 42211) = 0.976687490476916 . 192 | Round 7, DSC = 2 * 38793 / (43929 + 81152) = 0.6202860546365955 . 193 | Inter-iteration DSC = 2 * 42617 / (43929 + 43108) = 0.9792846720360306 . 194 | Round 8, DSC = 2 * 39454 / (44771 + 81152) = 0.6266369130341558 . 195 | Inter-iteration DSC = 2 * 43546 / (44771 + 43929) = 0.9818714768883878 . 196 | Round 9, DSC = 2 * 39980 / (45440 + 81152) = 0.6316354903943378 . 197 | Inter-iteration DSC = 2 * 44415 / (45440 + 44771) = 0.9846914456108457 . 198 | Round 10, DSC = 2 * 40459 / (45954 + 81152) = 0.63661825562916 . 199 | Inter-iteration DSC = 2 * 45030 / (45954 + 45440) = 0.9854038558329868 . 200 | Testcase 10: 201 | Round 0, DSC = 2 * 89125 / (104275 + 100044) = 0.8724102995805578 . 202 | Round 1, DSC = 2 * 89676 / (100576 + 100044) = 0.8939886352307845 . 203 | Inter-iteration DSC = 2 * 95276 / (100576 + 104275) = 0.9301980463849334 . 204 | Round 2, DSC = 2 * 89383 / (99942 + 100044) = 0.8938925724800736 . 205 | Inter-iteration DSC = 2 * 97985 / (99942 + 100576) = 0.9773187444518696 . 206 | Round 3, DSC = 2 * 89284 / (99913 + 100044) = 0.8930320018804043 . 207 | Inter-iteration DSC = 2 * 98778 / (99913 + 99942) = 0.988496660078557 . 208 | Round 4, DSC = 2 * 89218 / (99949 + 100044) = 0.8922112273929588 . 209 | Inter-iteration DSC = 2 * 99075 / (99949 + 99913) = 0.99143408952177 . 210 | Round 5, DSC = 2 * 89212 / (100013 + 100044) = 0.8918658182418011 . 211 | Inter-iteration DSC = 2 * 99283 / (100013 + 99949) = 0.9930186735479741 . 212 | Round 6, DSC = 2 * 89216 / (100054 + 100044) = 0.8917230557027057 . 213 | Inter-iteration DSC = 2 * 99502 / (100054 + 100013) = 0.9946867799287239 . 214 | Round 7, DSC = 2 * 89250 / (100137 + 100044) = 0.8916930178188739 . 215 | Inter-iteration DSC = 2 * 99650 / (100137 + 100054) = 0.9955492504658051 . 216 | Round 8, DSC = 2 * 89231 / (100181 + 100044) = 0.8913072793107754 . 217 | Inter-iteration DSC = 2 * 99713 / (100181 + 100137) = 0.9955470801425733 . 218 | Round 9, DSC = 2 * 89141 / (100038 + 100044) = 0.8910446716846093 . 219 | Inter-iteration DSC = 2 * 99635 / (100038 + 100181) = 0.9952601900918494 . 220 | Round 10, DSC = 2 * 89130 / (100071 + 100044) = 0.8907877970167154 . 221 | Inter-iteration DSC = 2 * 99642 / (100071 + 100038) = 0.9958772469004392 . 222 | Testcase 11: 223 | Round 0, DSC = 2 * 48214 / (50565 + 85700) = 0.7076505338861777 . 224 | Round 1, DSC = 2 * 65303 / (70095 + 85700) = 0.8383195866362848 . 225 | Inter-iteration DSC = 2 * 49396 / (70095 + 50565) = 0.8187634675948947 . 226 | Round 2, DSC = 2 * 70743 / (78024 + 85700) = 0.8641738535584276 . 227 | Inter-iteration DSC = 2 * 69464 / (78024 + 70095) = 0.9379485413755156 . 228 | Round 3, DSC = 2 * 72333 / (80772 + 85700) = 0.8690110048536691 . 229 | Inter-iteration DSC = 2 * 77331 / (80772 + 78024) = 0.973966598654878 . 230 | Round 4, DSC = 2 * 72887 / (81767 + 85700) = 0.8704640317196821 . 231 | Inter-iteration DSC = 2 * 80118 / (81767 + 80772) = 0.9858310928454094 . 232 | Round 5, DSC = 2 * 73133 / (82180 + 85700) = 0.8712532761496307 . 233 | Inter-iteration DSC = 2 * 81248 / (82180 + 81767) = 0.9911495788273039 . 234 | Round 6, DSC = 2 * 73413 / (82619 + 85700) = 0.8723079390918435 . 235 | Inter-iteration DSC = 2 * 81865 / (82619 + 82180) = 0.993513310153581 . 236 | Round 7, DSC = 2 * 73548 / (82822 + 85700) = 0.8728593299391177 . 237 | Inter-iteration DSC = 2 * 82245 / (82822 + 82619) = 0.9942517272018423 . 238 | Round 8, DSC = 2 * 73646 / (82961 + 85700) = 0.8733020674607644 . 239 | Inter-iteration DSC = 2 * 82526 / (82961 + 82822) = 0.9955906214750607 . 240 | Round 9, DSC = 2 * 73705 / (83037 + 85700) = 0.8736080409157446 . 241 | Inter-iteration DSC = 2 * 82656 / (83037 + 82961) = 0.9958674200893987 . 242 | Round 10, DSC = 2 * 73800 / (83148 + 85700) = 0.8741590069174642 . 243 | Inter-iteration DSC = 2 * 82722 / (83148 + 83037) = 0.9955411138189367 . 244 | Testcase 12: 245 | Round 0, DSC = 2 * 46057 / (73187 + 50402) = 0.7453252312098974 . 246 | Round 1, DSC = 2 * 46386 / (66377 + 50402) = 0.7944236549379597 . 247 | Inter-iteration DSC = 2 * 62419 / (66377 + 73187) = 0.8944856839872747 . 248 | Round 2, DSC = 2 * 46317 / (64155 + 50402) = 0.8086280192393306 . 249 | Inter-iteration DSC = 2 * 62674 / (64155 + 66377) = 0.9602856004657861 . 250 | Round 3, DSC = 2 * 46209 / (63050 + 50402) = 0.8146000070514403 . 251 | Inter-iteration DSC = 2 * 62138 / (63050 + 64155) = 0.9769741755434141 . 252 | Round 4, DSC = 2 * 46130 / (62469 + 50402) = 0.8173933074040276 . 253 | Inter-iteration DSC = 2 * 61864 / (62469 + 63050) = 0.9857312438754292 . 254 | Round 5, DSC = 2 * 46077 / (62145 + 50402) = 0.8188045883053302 . 255 | Inter-iteration DSC = 2 * 61705 / (62145 + 62469) = 0.9903381642512077 . 256 | Round 6, DSC = 2 * 46067 / (62052 + 50402) = 0.8193038931474202 . 257 | Inter-iteration DSC = 2 * 61596 / (62052 + 62145) = 0.9919080171018624 . 258 | Round 7, DSC = 2 * 46029 / (62037 + 50402) = 0.8187372708757638 . 259 | Inter-iteration DSC = 2 * 61674 / (62037 + 62052) = 0.9940284795590262 . 260 | Round 8, DSC = 2 * 46023 / (61973 + 50402) = 0.8190967741935484 . 261 | Inter-iteration DSC = 2 * 61693 / (61973 + 62037) = 0.9949681477300217 . 262 | Round 9, DSC = 2 * 46017 / (61968 + 50402) = 0.8190264305419596 . 263 | Inter-iteration DSC = 2 * 61758 / (61968 + 61973) = 0.996570949080611 . 264 | Round 10, DSC = 2 * 46006 / (61962 + 50402) = 0.8188743725748461 . 265 | Inter-iteration DSC = 2 * 61749 / (61962 + 61968) = 0.9965141612200435 . 266 | Testcase 13: 267 | Round 0, DSC = 2 * 85369 / (106398 + 101061) = 0.8229963510862387 . 268 | Round 1, DSC = 2 * 91809 / (110052 + 101061) = 0.8697616916059172 . 269 | Inter-iteration DSC = 2 * 97893 / (110052 + 106398) = 0.9045322245322245 . 270 | Round 2, DSC = 2 * 92843 / (110021 + 101061) = 0.8796865673055969 . 271 | Inter-iteration DSC = 2 * 105932 / (110021 + 110052) = 0.9626987408723469 . 272 | Round 3, DSC = 2 * 93069 / (109533 + 101061) = 0.88387133536568 . 273 | Inter-iteration DSC = 2 * 107303 / (109533 + 110021) = 0.977463403080791 . 274 | Round 4, DSC = 2 * 93052 / (108895 + 101061) = 0.886395244717941 . 275 | Inter-iteration DSC = 2 * 107553 / (108895 + 109533) = 0.9847913271192338 . 276 | Round 5, DSC = 2 * 92964 / (108522 + 101061) = 0.8871330212851233 . 277 | Inter-iteration DSC = 2 * 107495 / (108522 + 108895) = 0.9888371194524808 . 278 | Round 6, DSC = 2 * 92895 / (108294 + 101061) = 0.8874399942681092 . 279 | Inter-iteration DSC = 2 * 107577 / (108294 + 108522) = 0.9923345140580031 . 280 | Round 7, DSC = 2 * 92848 / (108135 + 101061) = 0.887665156121532 . 281 | Inter-iteration DSC = 2 * 107543 / (108135 + 108294) = 0.9937947317596071 . 282 | Round 8, DSC = 2 * 92863 / (108046 + 101061) = 0.88818643087032 . 283 | Inter-iteration DSC = 2 * 107469 / (108046 + 108135) = 0.9942501884994519 . 284 | Round 9, DSC = 2 * 92822 / (108034 + 101061) = 0.8878452378105646 . 285 | Inter-iteration DSC = 2 * 107488 / (108034 + 108046) = 0.994890781192151 . 286 | Round 10, DSC = 2 * 92782 / (107982 + 101061) = 0.8876833952823103 . 287 | Inter-iteration DSC = 2 * 107457 / (107982 + 108034) = 0.9948985260351085 . 288 | Testcase 14: 289 | Round 0, DSC = 2 * 21697 / (27070 + 68026) = 0.4563178261966854 . 290 | Round 1, DSC = 2 * 38852 / (45724 + 68026) = 0.6831120879120879 . 291 | Inter-iteration DSC = 2 * 24987 / (45724 + 27070) = 0.6865126246668681 . 292 | Round 2, DSC = 2 * 46437 / (55484 + 68026) = 0.7519553072625699 . 293 | Inter-iteration DSC = 2 * 44962 / (55484 + 45724) = 0.8885068374041578 . 294 | Round 3, DSC = 2 * 50689 / (61218 + 68026) = 0.7843923122156541 . 295 | Inter-iteration DSC = 2 * 54759 / (61218 + 55484) = 0.9384415005741118 . 296 | Round 4, DSC = 2 * 53171 / (64394 + 68026) = 0.8030660021144842 . 297 | Inter-iteration DSC = 2 * 60330 / (64394 + 61218) = 0.9605770149348788 . 298 | Round 5, DSC = 2 * 54650 / (66232 + 68026) = 0.8141041874599652 . 299 | Inter-iteration DSC = 2 * 63564 / (66232 + 64394) = 0.9732212576363052 . 300 | Round 6, DSC = 2 * 55706 / (67536 + 68026) = 0.8218527315914489 . 301 | Inter-iteration DSC = 2 * 65442 / (67536 + 66232) = 0.9784402846719694 . 302 | Round 7, DSC = 2 * 56233 / (68086 + 68026) = 0.8262754202421535 . 303 | Inter-iteration DSC = 2 * 66643 / (68086 + 67536) = 0.9827756558670422 . 304 | Round 8, DSC = 2 * 56583 / (68401 + 68026) = 0.8294985596692737 . 305 | Inter-iteration DSC = 2 * 67294 / (68401 + 68086) = 0.9860865870009597 . 306 | Round 9, DSC = 2 * 56713 / (68499 + 68026) = 0.8308075444057865 . 307 | Inter-iteration DSC = 2 * 67647 / (68499 + 68401) = 0.9882688093498905 . 308 | Round 10, DSC = 2 * 56841 / (68612 + 68026) = 0.8319940280156325 . 309 | Inter-iteration DSC = 2 * 67806 / (68612 + 68499) = 0.9890672520804312 . 310 | Testcase 15: 311 | Round 0, DSC = 2 * 83661 / (91860 + 125060) = 0.7713534943758068 . 312 | Round 1, DSC = 2 * 101933 / (110389 + 125060) = 0.8658605472947433 . 313 | Inter-iteration DSC = 2 * 87796 / (110389 + 91860) = 0.8681971233479523 . 314 | Round 2, DSC = 2 * 108289 / (117945 + 125060) = 0.8912491512520319 . 315 | Inter-iteration DSC = 2 * 108690 / (117945 + 110389) = 0.9520264174411169 . 316 | Round 3, DSC = 2 * 111137 / (121476 + 125060) = 0.9015884089950352 . 317 | Inter-iteration DSC = 2 * 116464 / (121476 + 117945) = 0.9728804073159831 . 318 | Round 4, DSC = 2 * 112141 / (122905 + 125060) = 0.9044905531022523 . 319 | Inter-iteration DSC = 2 * 120164 / (122905 + 121476) = 0.9834152409557207 . 320 | Round 5, DSC = 2 * 112507 / (123538 + 125060) = 0.9051319801446512 . 321 | Inter-iteration DSC = 2 * 121888 / (123538 + 122905) = 0.9891780249388297 . 322 | Round 6, DSC = 2 * 112672 / (123774 + 125060) = 0.9055997170804633 . 323 | Inter-iteration DSC = 2 * 122735 / (123774 + 123538) = 0.9925519182247525 . 324 | Round 7, DSC = 2 * 112768 / (123887 + 125060) = 0.9059599031119073 . 325 | Inter-iteration DSC = 2 * 123211 / (123887 + 123774) = 0.9949971937446752 . 326 | Round 8, DSC = 2 * 112841 / (123990 + 125060) = 0.9061714515157598 . 327 | Inter-iteration DSC = 2 * 123368 / (123990 + 123887) = 0.9953969105645138 . 328 | Round 9, DSC = 2 * 112840 / (124006 + 125060) = 0.9061052090610521 . 329 | Inter-iteration DSC = 2 * 123488 / (124006 + 123990) = 0.9958870304359748 . 330 | Round 10, DSC = 2 * 112876 / (124037 + 125060) = 0.9062814887373192 . 331 | Inter-iteration DSC = 2 * 123559 / (124037 + 124006) = 0.9962708078841168 . 332 | Testcase 16: 333 | Round 0, DSC = 2 * 95992 / (101798 + 147099) = 0.7713391483223985 . 334 | Round 1, DSC = 2 * 115478 / (123260 + 147099) = 0.8542567475097925 . 335 | Inter-iteration DSC = 2 * 99042 / (123260 + 101798) = 0.8801464511370403 . 336 | Round 2, DSC = 2 * 120166 / (129243 + 147099) = 0.8696904560291233 . 337 | Inter-iteration DSC = 2 * 121354 / (129243 + 123260) = 0.9612083816825938 . 338 | Round 3, DSC = 2 * 121784 / (131719 + 147099) = 0.8735734421737478 . 339 | Inter-iteration DSC = 2 * 127819 / (131719 + 129243) = 0.9795985622427786 . 340 | Round 4, DSC = 2 * 122723 / (133224 + 147099) = 0.8755828098300888 . 341 | Inter-iteration DSC = 2 * 130642 / (133224 + 131719) = 0.9861894822659968 . 342 | Round 5, DSC = 2 * 123150 / (134057 + 147099) = 0.8760261207301284 . 343 | Inter-iteration DSC = 2 * 132298 / (134057 + 133224) = 0.9899543925681212 . 344 | Round 6, DSC = 2 * 123644 / (134855 + 147099) = 0.8770508664533931 . 345 | Inter-iteration DSC = 2 * 133399 / (134855 + 134057) = 0.9921386922115785 . 346 | Round 7, DSC = 2 * 124114 / (135619 + 147099) = 0.8780056452012253 . 347 | Inter-iteration DSC = 2 * 134087 / (135619 + 134855) = 0.9914964100061374 . 348 | Round 8, DSC = 2 * 124653 / (136448 + 147099) = 0.8792404786508057 . 349 | Inter-iteration DSC = 2 * 134977 / (136448 + 135619) = 0.9922335307111851 . 350 | Round 9, DSC = 2 * 125064 / (137164 + 147099) = 0.8799175411502728 . 351 | Inter-iteration DSC = 2 * 135809 / (137164 + 136448) = 0.9927123079397102 . 352 | Round 10, DSC = 2 * 125363 / (137723 + 147099) = 0.8802901461263526 . 353 | Inter-iteration DSC = 2 * 136527 / (137723 + 137164) = 0.9933318054327779 . 354 | Testcase 17: 355 | Round 0, DSC = 2 * 98423 / (119657 + 108464) = 0.8629017056737434 . 356 | Round 1, DSC = 2 * 100299 / (118081 + 108464) = 0.8854664636165 . 357 | Inter-iteration DSC = 2 * 110577 / (118081 + 119657) = 0.9302425359008657 . 358 | Round 2, DSC = 2 * 100343 / (117590 + 108464) = 0.887779026250365 . 359 | Inter-iteration DSC = 2 * 115202 / (117590 + 118081) = 0.9776510474347714 . 360 | Round 3, DSC = 2 * 100364 / (117211 + 108464) = 0.889456076215797 . 361 | Inter-iteration DSC = 2 * 115984 / (117211 + 117590) = 0.9879344636521991 . 362 | Round 4, DSC = 2 * 100331 / (116895 + 108464) = 0.8904104118317884 . 363 | Inter-iteration DSC = 2 * 116034 / (116895 + 117211) = 0.9912945417887623 . 364 | Round 5, DSC = 2 * 100262 / (116703 + 108464) = 0.8905567867405082 . 365 | Inter-iteration DSC = 2 * 116124 / (116703 + 116895) = 0.9942208409318573 . 366 | Round 6, DSC = 2 * 100231 / (116616 + 108464) = 0.8906255553580948 . 367 | Inter-iteration DSC = 2 * 116022 / (116616 + 116703) = 0.9945353786018284 . 368 | Round 7, DSC = 2 * 100230 / (116590 + 108464) = 0.89071956063878 . 369 | Inter-iteration DSC = 2 * 116005 / (116590 + 116616) = 0.9948714870114834 . 370 | Round 8, DSC = 2 * 100195 / (116458 + 108464) = 0.8909310783293765 . 371 | Inter-iteration DSC = 2 * 115959 / (116458 + 116590) = 0.9951512134839174 . 372 | Round 9, DSC = 2 * 100173 / (116389 + 108464) = 0.8910087924110419 . 373 | Inter-iteration DSC = 2 * 115912 / (116389 + 116458) = 0.9956065570954318 . 374 | Round 10, DSC = 2 * 100151 / (116351 + 108464) = 0.890963681249027 . 375 | Inter-iteration DSC = 2 * 115922 / (116351 + 116389) = 0.9961502105353613 . 376 | Testcase 18: 377 | Round 0, DSC = 2 * 60084 / (62701 + 87441) = 0.8003623236669286 . 378 | Round 1, DSC = 2 * 61648 / (63316 + 87441) = 0.8178459375020729 . 379 | Inter-iteration DSC = 2 * 57983 / (63316 + 62701) = 0.9202409198758897 . 380 | Round 2, DSC = 2 * 61742 / (63324 + 87441) = 0.8190495141445295 . 381 | Inter-iteration DSC = 2 * 61576 / (63324 + 63316) = 0.9724573594440935 . 382 | Round 3, DSC = 2 * 61929 / (63484 + 87441) = 0.820659267848269 . 383 | Inter-iteration DSC = 2 * 62355 / (63484 + 63324) = 0.9834553025045738 . 384 | Round 4, DSC = 2 * 62092 / (63615 + 87441) = 0.8221057091409808 . 385 | Inter-iteration DSC = 2 * 62827 / (63615 + 63484) = 0.9886309097632554 . 386 | Round 5, DSC = 2 * 62110 / (63663 + 87441) = 0.822082803896654 . 387 | Inter-iteration DSC = 2 * 63038 / (63663 + 63615) = 0.9905561055327707 . 388 | Round 6, DSC = 2 * 62216 / (63771 + 87441) = 0.8228976536253736 . 389 | Inter-iteration DSC = 2 * 63238 / (63771 + 63663) = 0.9924823830374939 . 390 | Round 7, DSC = 2 * 62257 / (63838 + 87441) = 0.8230752450769769 . 391 | Inter-iteration DSC = 2 * 63449 / (63838 + 63771) = 0.9944282926752815 . 392 | Round 8, DSC = 2 * 62267 / (63872 + 87441) = 0.8230224765882641 . 393 | Inter-iteration DSC = 2 * 63495 / (63872 + 63838) = 0.9943622269203665 . 394 | Round 9, DSC = 2 * 62226 / (63808 + 87441) = 0.822828580684831 . 395 | Inter-iteration DSC = 2 * 63501 / (63808 + 63872) = 0.9946898496240602 . 396 | Round 10, DSC = 2 * 62225 / (63808 + 87441) = 0.8228153574569088 . 397 | Inter-iteration DSC = 2 * 63517 / (63808 + 63808) = 0.99543944332999 . 398 | Testcase 19: 399 | Round 0, DSC = 2 * 70561 / (82078 + 94641) = 0.7985672168810372 . 400 | Round 1, DSC = 2 * 77132 / (86986 + 94641) = 0.8493450863583057 . 401 | Inter-iteration DSC = 2 * 76641 / (86986 + 82078) = 0.9066507358160223 . 402 | Round 2, DSC = 2 * 80073 / (90474 + 94641) = 0.8651162790697674 . 403 | Inter-iteration DSC = 2 * 85418 / (90474 + 86986) = 0.9626732784852925 . 404 | Round 3, DSC = 2 * 81603 / (92542 + 94641) = 0.8719061025841022 . 405 | Inter-iteration DSC = 2 * 89549 / (92542 + 90474) = 0.9785920356690125 . 406 | Round 4, DSC = 2 * 82572 / (93843 + 94641) = 0.8761698605717196 . 407 | Inter-iteration DSC = 2 * 91874 / (93843 + 92542) = 0.9858518657617298 . 408 | Round 5, DSC = 2 * 83152 / (94615 + 94641) = 0.8787251130743543 . 409 | Inter-iteration DSC = 2 * 93215 / (94615 + 93843) = 0.9892389816298591 . 410 | Round 6, DSC = 2 * 83567 / (95224 + 94641) = 0.8802780923287599 . 411 | Inter-iteration DSC = 2 * 94151 / (95224 + 94615) = 0.9919036657378094 . 412 | Round 7, DSC = 2 * 83800 / (95591 + 94641) = 0.8810294797930948 . 413 | Inter-iteration DSC = 2 * 94791 / (95591 + 95224) = 0.993538243848754 . 414 | Round 8, DSC = 2 * 83975 / (95825 + 94641) = 0.8817846754801382 . 415 | Inter-iteration DSC = 2 * 95163 / (95825 + 95591) = 0.9943055961884064 . 416 | Round 9, DSC = 2 * 84069 / (96016 + 94641) = 0.8818873684155315 . 417 | Inter-iteration DSC = 2 * 95418 / (96016 + 95825) = 0.9947612866905406 . 418 | Round 10, DSC = 2 * 84140 / (96146 + 94641) = 0.8820307463296766 . 419 | Inter-iteration DSC = 2 * 95676 / (96146 + 96016) = 0.995784806569457 . 420 | Testcase 20: 421 | Round 0, DSC = 2 * 90781 / (113724 + 103581) = 0.8355169002093831 . 422 | Round 1, DSC = 2 * 95902 / (120652 + 103581) = 0.8553781111611582 . 423 | Inter-iteration DSC = 2 * 107747 / (120652 + 113724) = 0.9194371437348534 . 424 | Round 2, DSC = 2 * 96462 / (122084 + 103581) = 0.854913256375601 . 425 | Inter-iteration DSC = 2 * 118174 / (122084 + 120652) = 0.9736833432206183 . 426 | Round 3, DSC = 2 * 96795 / (122919 + 103581) = 0.8547019867549669 . 427 | Inter-iteration DSC = 2 * 120783 / (122919 + 122084) = 0.9859716003477509 . 428 | Round 4, DSC = 2 * 96867 / (123139 + 103581) = 0.8545077628793225 . 429 | Inter-iteration DSC = 2 * 121768 / (123139 + 122919) = 0.9897503840557917 . 430 | Round 5, DSC = 2 * 96903 / (123342 + 103581) = 0.8540606284951283 . 431 | Inter-iteration DSC = 2 * 122245 / (123342 + 123139) = 0.991922298270455 . 432 | Round 6, DSC = 2 * 96899 / (123177 + 103581) = 0.8546468040818846 . 433 | Inter-iteration DSC = 2 * 122526 / (123177 + 123342) = 0.9940491402285422 . 434 | Round 7, DSC = 2 * 96904 / (123170 + 103581) = 0.8547172890086483 . 435 | Inter-iteration DSC = 2 * 122541 / (123170 + 123177) = 0.9948649668962886 . 436 | Round 8, DSC = 2 * 96928 / (123155 + 103581) = 0.8549855338367087 . 437 | Inter-iteration DSC = 2 * 122564 / (123155 + 123170) = 0.9951405663249772 . 438 | Round 9, DSC = 2 * 96904 / (123136 + 103581) = 0.8548454681386928 . 439 | Inter-iteration DSC = 2 * 122590 / (123136 + 123155) = 0.995489075930505 . 440 | Round 10, DSC = 2 * 96894 / (123113 + 103581) = 0.8548439746971689 . 441 | Inter-iteration DSC = 2 * 122514 / (123113 + 123136) = 0.9950416042298649 . 442 | Round 0, Average DSC = 0.7524984742024067 . 443 | Round 1, Average DSC = 0.8190416584768245 . 444 | Round 2, Average DSC = 0.8338967698043767 . 445 | Round 3, Average DSC = 0.8396193565004018 . 446 | Round 4, Average DSC = 0.8423760162268049 . 447 | Round 5, Average DSC = 0.8438120431988303 . 448 | Round 6, Average DSC = 0.8449617018514616 . 449 | Round 7, Average DSC = 0.845656680964497 . 450 | Round 8, Average DSC = 0.8462534312087644 . 451 | Round 9, Average DSC = 0.8465232423539923 . 452 | Round 10, Average DSC = 0.8467916492976617 . 453 | DSC threshold = 0.90, Average DSC = 0.8338679997693854 . 454 | DSC threshold = 0.95, Average DSC = 0.8390581390318319 . 455 | DSC threshold = 0.98, Average DSC = 0.8448499666303668 . 456 | DSC threshold = 0.99, Average DSC = 0.8462290029432895 . 457 | -------------------------------------------------------------------------------- /logs/FD0_coarse_fusion_results.txt: -------------------------------------------------------------------------------- 1 | Fusing results of 1 snapshots: 2 | Testcase 1: 3 | DSC_X = 2 * 50117 / (61141 + 78329) = 0.7186778518677852 . 4 | DSC_Y = 2 * 49394 / (62656 + 78329) = 0.7006986558853778 . 5 | DSC_Z = 2 * 44255 / (61013 + 78329) = 0.6351997244190553 . 6 | DSC_F1 = 2 * 63331 / (101072 + 78329) = 0.706027279669567 . 7 | DSC_F2 = 2 * 48622 / (53401 + 78329) = 0.738206938434677 . 8 | DSC_F3 = 2 * 31414 / (32053 + 78329) = 0.5691870051276476 . 9 | DSC_F1P = 2 * 63320 / (93201 + 78329) = 0.7382965078994927 . 10 | DSC_F2P = 2 * 48621 / (53203 + 78329) = 0.739302983304443 . 11 | DSC_F3P = 2 * 30682 / (31308 + 78329) = 0.5597015606045405 . 12 | Testcase 2: 13 | DSC_X = 2 * 87686 / (138102 + 110797) = 0.7045910188470021 . 14 | DSC_Y = 2 * 86130 / (131661 + 110797) = 0.7104735665558571 . 15 | DSC_Z = 2 * 81504 / (121608 + 110797) = 0.7013962694434285 . 16 | DSC_F1 = 2 * 98764 / (169367 + 110797) = 0.7050441884039348 . 17 | DSC_F2 = 2 * 86183 / (128667 + 110797) = 0.7197992182541009 . 18 | DSC_F3 = 2 * 69438 / (95477 + 110797) = 0.6732598388551151 . 19 | DSC_F1P = 2 * 98762 / (168695 + 110797) = 0.706725058320095 . 20 | DSC_F2P = 2 * 86183 / (128667 + 110797) = 0.7197992182541009 . 21 | DSC_F3P = 2 * 69438 / (95477 + 110797) = 0.6732598388551151 . 22 | Testcase 3: 23 | DSC_X = 2 * 87959 / (127467 + 125067) = 0.6966111493897852 . 24 | DSC_Y = 2 * 53210 / (73007 + 125067) = 0.5372739481203994 . 25 | DSC_Z = 2 * 80205 / (100383 + 125067) = 0.7115103127079175 . 26 | DSC_F1 = 2 * 112336 / (182462 + 125067) = 0.7305717509568204 . 27 | DSC_F2 = 2 * 79562 / (91187 + 125067) = 0.7358199154697717 . 28 | DSC_F3 = 2 * 27955 / (29690 + 125067) = 0.361276065056831 . 29 | DSC_F1P = 2 * 112335 / (174202 + 125067) = 0.7507292770049687 . 30 | DSC_F2P = 2 * 79530 / (91151 + 125067) = 0.7356464309169449 . 31 | DSC_F3P = 2 * 18663 / (19928 + 125067) = 0.2574295665367771 . 32 | Testcase 4: 33 | DSC_X = 2 * 48013 / (95196 + 63116) = 0.6065617262115317 . 34 | DSC_Y = 2 * 46084 / (77928 + 63116) = 0.6534698391991152 . 35 | DSC_Z = 2 * 29267 / (35578 + 63116) = 0.5930856992319695 . 36 | DSC_F1 = 2 * 56773 / (124987 + 63116) = 0.6036373688883219 . 37 | DSC_F2 = 2 * 44088 / (61324 + 63116) = 0.7085824493731919 . 38 | DSC_F3 = 2 * 21731 / (24382 + 63116) = 0.49671992502685774 . 39 | DSC_F1P = 2 * 56772 / (110007 + 63116) = 0.6558573961865264 . 40 | DSC_F2P = 2 * 44080 / (61005 + 63116) = 0.7102746513482812 . 41 | DSC_F3P = 2 * 19959 / (22471 + 63116) = 0.46640260787269094 . 42 | Testcase 5: 43 | DSC_X = 2 * 61999 / (80590 + 79155) = 0.7762246079689505 . 44 | DSC_Y = 2 * 59089 / (78103 + 79155) = 0.7514911800989457 . 45 | DSC_Z = 2 * 57391 / (83752 + 79155) = 0.704586052164732 . 46 | DSC_F1 = 2 * 74447 / (128804 + 79155) = 0.7159776686750754 . 47 | DSC_F2 = 2 * 62192 / (71581 + 79155) = 0.8251777942893536 . 48 | DSC_F3 = 2 * 41144 / (43176 + 79155) = 0.6726667811102665 . 49 | DSC_F1P = 2 * 74386 / (112988 + 79155) = 0.774277491243501 . 50 | DSC_F2P = 2 * 62189 / (71496 + 79155) = 0.8256035472715083 . 51 | DSC_F3P = 2 * 40184 / (42208 + 79155) = 0.6622117119715234 . 52 | Testcase 6: 53 | DSC_X = 2 * 54656 / (67679 + 69120) = 0.7990701686415836 . 54 | DSC_Y = 2 * 53120 / (70560 + 69120) = 0.7605956471935853 . 55 | DSC_Z = 2 * 49468 / (54050 + 69120) = 0.8032475440448161 . 56 | DSC_F1 = 2 * 63876 / (94369 + 69120) = 0.7814103701166439 . 57 | DSC_F2 = 2 * 53267 / (58685 + 69120) = 0.8335667618637769 . 58 | DSC_F3 = 2 * 39099 / (39792 + 69120) = 0.7179925077126488 . 59 | DSC_F1P = 2 * 63876 / (90368 + 69120) = 0.8010132423756019 . 60 | DSC_F2P = 2 * 53267 / (57641 + 69120) = 0.840431994067576 . 61 | DSC_F3P = 2 * 39071 / (39764 + 69120) = 0.7176628338415194 . 62 | Testcase 7: 63 | DSC_X = 2 * 53461 / (60060 + 90326) = 0.7109837351881159 . 64 | DSC_Y = 2 * 56415 / (64796 + 90326) = 0.7273629788166733 . 65 | DSC_Z = 2 * 64278 / (73953 + 90326) = 0.7825467649547416 . 66 | DSC_F1 = 2 * 77805 / (99165 + 90326) = 0.8211999514488815 . 67 | DSC_F2 = 2 * 58732 / (62814 + 90326) = 0.7670366984458665 . 68 | DSC_F3 = 2 * 36981 / (37718 + 90326) = 0.5776295648370873 . 69 | DSC_F1P = 2 * 77804 / (96242 + 90326) = 0.8340551434329574 . 70 | DSC_F2P = 2 * 58680 / (62665 + 90326) = 0.7671039472910172 . 71 | DSC_F3P = 2 * 36934 / (37671 + 90326) = 0.5771072759517801 . 72 | Testcase 8: 73 | DSC_X = 2 * 120949 / (146544 + 156716) = 0.7976587746488162 . 74 | DSC_Y = 2 * 127409 / (148805 + 156716) = 0.8340441409919449 . 75 | DSC_Z = 2 * 127266 / (150846 + 156716) = 0.8275794799097418 . 76 | DSC_F1 = 2 * 144840 / (200493 + 156716) = 0.8109538113541372 . 77 | DSC_F2 = 2 * 127899 / (141875 + 156716) = 0.8566835571065438 . 78 | DSC_F3 = 2 * 101260 / (105343 + 156716) = 0.7728030710641497 . 79 | DSC_F1P = 2 * 144838 / (190173 + 156716) = 0.8350682783253432 . 80 | DSC_F2P = 2 * 127899 / (141743 + 156716) = 0.8570624440877976 . 81 | DSC_F3P = 2 * 101260 / (105343 + 156716) = 0.7728030710641497 . 82 | Testcase 9: 83 | DSC_X = 2 * 30533 / (38241 + 81152) = 0.5114705217223791 . 84 | DSC_Y = 2 * 26746 / (34029 + 81152) = 0.46441687431086726 . 85 | DSC_Z = 2 * 17181 / (19976 + 81152) = 0.33978720037971677 . 86 | DSC_F1 = 2 * 43646 / (60465 + 81152) = 0.6163949243381798 . 87 | DSC_F2 = 2 * 22076 / (23883 + 81152) = 0.4203551197219974 . 88 | DSC_F3 = 2 * 9454 / (9625 + 81152) = 0.2082906463090871 . 89 | DSC_F1P = 2 * 32290 / (37173 + 81152) = 0.5457849144305937 . 90 | DSC_F2P = 2 * 20489 / (21270 + 81152) = 0.40008982445177793 . 91 | DSC_F3P = 2 * 9382 / (9446 + 81152) = 0.20711273979558048 . 92 | Testcase 10: 93 | DSC_X = 2 * 85543 / (105445 + 100044) = 0.8325798461231502 . 94 | DSC_Y = 2 * 88109 / (106826 + 100044) = 0.8518296514719389 . 95 | DSC_Z = 2 * 88514 / (108049 + 100044) = 0.8507157857304186 . 96 | DSC_F1 = 2 * 96304 / (135074 + 100044) = 0.8191971690810572 . 97 | DSC_F2 = 2 * 89125 / (104275 + 100044) = 0.8724102995805578 . 98 | DSC_F3 = 2 * 75736 / (81877 + 100044) = 0.832625150477405 . 99 | DSC_F1P = 2 * 96304 / (131323 + 100044) = 0.8324782704534355 . 100 | DSC_F2P = 2 * 89125 / (104275 + 100044) = 0.8724102995805578 . 101 | DSC_F3P = 2 * 75734 / (81875 + 100044) = 0.8326123164705171 . 102 | Testcase 11: 103 | DSC_X = 2 * 28794 / (32335 + 85700) = 0.4878891854111069 . 104 | DSC_Y = 2 * 50962 / (57791 + 85700) = 0.7103163264594992 . 105 | DSC_Z = 2 * 54033 / (61479 + 85700) = 0.7342487719036004 . 106 | DSC_F1 = 2 * 66867 / (83427 + 85700) = 0.790731225646999 . 107 | DSC_F2 = 2 * 48214 / (50565 + 85700) = 0.7076505338861777 . 108 | DSC_F3 = 2 * 18256 / (18470 + 85700) = 0.3505039838725161 . 109 | DSC_F1P = 2 * 66867 / (82414 + 85700) = 0.7954959134872764 . 110 | DSC_F2P = 2 * 48214 / (50510 + 85700) = 0.7079362748696865 . 111 | DSC_F3P = 2 * 18072 / (18286 + 85700) = 0.3475852518608274 . 112 | Testcase 12: 113 | DSC_X = 2 * 44532 / (79980 + 50402) = 0.6831004279731865 . 114 | DSC_Y = 2 * 45990 / (82281 + 50402) = 0.6932312353504216 . 115 | DSC_Z = 2 * 44540 / (73227 + 50402) = 0.7205429146883013 . 116 | DSC_F1 = 2 * 49005 / (113438 + 50402) = 0.59820556640625 . 117 | DSC_F2 = 2 * 46057 / (73187 + 50402) = 0.7453252312098974 . 118 | DSC_F3 = 2 * 39318 / (51034 + 50402) = 0.7752277298000709 . 119 | DSC_F1P = 2 * 49005 / (106962 + 50402) = 0.6228235174499885 . 120 | DSC_F2P = 2 * 46056 / (73171 + 50402) = 0.7454055497560147 . 121 | DSC_F3P = 2 * 39316 / (50973 + 50402) = 0.7756547472256473 . 122 | Testcase 13: 123 | DSC_X = 2 * 83894 / (116495 + 101061) = 0.7712405081909945 . 124 | DSC_Y = 2 * 81315 / (115783 + 101061) = 0.7499861651694305 . 125 | DSC_Z = 2 * 82236 / (119495 + 101061) = 0.745715373873302 . 126 | DSC_F1 = 2 * 95835 / (178233 + 101061) = 0.6862660852005413 . 127 | DSC_F2 = 2 * 85369 / (106398 + 101061) = 0.8229963510862387 . 128 | DSC_F3 = 2 * 64994 / (70233 + 101061) = 0.7588590376779105 . 129 | DSC_F1P = 2 * 95835 / (168817 + 101061) = 0.7102097985015452 . 130 | DSC_F2P = 2 * 85368 / (105752 + 101061) = 0.8255573875916891 . 131 | DSC_F3P = 2 * 64970 / (70206 + 101061) = 0.7586984065815364 . 132 | Testcase 14: 133 | DSC_X = 2 * 40982 / (70664 + 68026) = 0.5909870935179177 . 134 | DSC_Y = 2 * 19813 / (41728 + 68026) = 0.36104378883685334 . 135 | DSC_Z = 2 * 15087 / (21869 + 68026) = 0.3356582679793092 . 136 | DSC_F1 = 2 * 47384 / (102669 + 68026) = 0.5551890799379009 . 137 | DSC_F2 = 2 * 21697 / (27070 + 68026) = 0.4563178261966854 . 138 | DSC_F3 = 2 * 6668 / (7544 + 68026) = 0.17647214503109698 . 139 | DSC_F1P = 2 * 47369 / (81777 + 68026) = 0.6324172413102541 . 140 | DSC_F2P = 2 * 15067 / (19385 + 68026) = 0.34473922046424366 . 141 | DSC_F3P = 2 * 5860 / (6718 + 68026) = 0.15680188376324522 . 142 | Testcase 15: 143 | DSC_X = 2 * 82087 / (98929 + 125060) = 0.7329556362142784 . 144 | DSC_Y = 2 * 70522 / (83776 + 125060) = 0.6753816391809842 . 145 | DSC_Z = 2 * 92606 / (123209 + 125060) = 0.7460133967591605 . 146 | DSC_F1 = 2 * 109768 / (163485 + 125060) = 0.7608379975393786 . 147 | DSC_F2 = 2 * 83661 / (91860 + 125060) = 0.7713534943758068 . 148 | DSC_F3 = 2 * 50856 / (52223 + 125060) = 0.5737267532701951 . 149 | DSC_F1P = 2 * 109768 / (145214 + 125060) = 0.8122719906465291 . 150 | DSC_F2P = 2 * 83640 / (91532 + 125060) = 0.7723276944670163 . 151 | DSC_F3P = 2 * 36484 / (37517 + 125060) = 0.44882117396679727 . 152 | Testcase 16: 153 | DSC_X = 2 * 82363 / (92423 + 147099) = 0.687728058382946 . 154 | DSC_Y = 2 * 103361 / (118389 + 147099) = 0.7786491291508467 . 155 | DSC_Z = 2 * 93694 / (107826 + 147099) = 0.735071099342944 . 156 | DSC_F1 = 2 * 123547 / (157747 + 147099) = 0.8105535253865886 . 157 | DSC_F2 = 2 * 95992 / (101798 + 147099) = 0.7713391483223985 . 158 | DSC_F3 = 2 * 58987 / (59928 + 147099) = 0.5698483772648012 . 159 | DSC_F1P = 2 * 123546 / (155492 + 147099) = 0.8165874067635852 . 160 | DSC_F2P = 2 * 95992 / (101793 + 147099) = 0.7713546437812384 . 161 | DSC_F3P = 2 * 58892 / (59833 + 147099) = 0.5691918118029111 . 162 | Testcase 17: 163 | DSC_X = 2 * 98243 / (141342 + 108464) = 0.786554366188162 . 164 | DSC_Y = 2 * 94973 / (125067 + 108464) = 0.8133652491532174 . 165 | DSC_Z = 2 * 92498 / (117243 + 108464) = 0.819628988024297 . 166 | DSC_F1 = 2 * 105558 / (179048 + 108464) = 0.7342858732852889 . 167 | DSC_F2 = 2 * 98423 / (119657 + 108464) = 0.8629017056737434 . 168 | DSC_F3 = 2 * 80195 / (85797 + 108464) = 0.8256417911984392 . 169 | DSC_F1P = 2 * 105558 / (169264 + 108464) = 0.7601538195644659 . 170 | DSC_F2P = 2 * 98423 / (119538 + 108464) = 0.8633520758589837 . 171 | DSC_F3P = 2 * 80192 / (85792 + 108464) = 0.8256321555061362 . 172 | Testcase 18: 173 | DSC_X = 2 * 56603 / (75473 + 87441) = 0.6948819622622979 . 174 | DSC_Y = 2 * 63637 / (69267 + 87441) = 0.8121729586236822 . 175 | DSC_Z = 2 * 59333 / (71771 + 87441) = 0.7453332663367083 . 176 | DSC_F1 = 2 * 74592 / (109515 + 87441) = 0.7574483641016267 . 177 | DSC_F2 = 2 * 60084 / (62701 + 87441) = 0.8003623236669286 . 178 | DSC_F3 = 2 * 44461 / (44908 + 87441) = 0.6718751180590711 . 179 | DSC_F1P = 2 * 74592 / (87221 + 87441) = 0.8541296904879138 . 180 | DSC_F2P = 2 * 60084 / (62627 + 87441) = 0.8007569901644588 . 181 | DSC_F3P = 2 * 44461 / (44908 + 87441) = 0.6718751180590711 . 182 | Testcase 19: 183 | DSC_X = 2 * 67791 / (82840 + 94641) = 0.7639240256703534 . 184 | DSC_Y = 2 * 71967 / (88522 + 94641) = 0.7858246479911336 . 185 | DSC_Z = 2 * 67492 / (87575 + 94641) = 0.7407911489660622 . 186 | DSC_F1 = 2 * 83413 / (121759 + 94641) = 0.7709149722735674 . 187 | DSC_F2 = 2 * 70561 / (82078 + 94641) = 0.7985672168810372 . 188 | DSC_F3 = 2 * 52663 / (56460 + 94641) = 0.697056935427297 . 189 | DSC_F1P = 2 * 83410 / (112405 + 94641) = 0.8057146721018518 . 190 | DSC_F2P = 2 * 70549 / (81930 + 94641) = 0.7991006450662906 . 191 | DSC_F3P = 2 * 52657 / (56449 + 94641) = 0.6970282613012112 . 192 | Testcase 20: 193 | DSC_X = 2 * 84939 / (117831 + 103581) = 0.7672483876212671 . 194 | DSC_Y = 2 * 86100 / (119125 + 103581) = 0.7732167072283639 . 195 | DSC_Z = 2 * 88911 / (115086 + 103581) = 0.8132091262056003 . 196 | DSC_F1 = 2 * 99747 / (163667 + 103581) = 0.7464751840986649 . 197 | DSC_F2 = 2 * 90781 / (113724 + 103581) = 0.8355169002093831 . 198 | DSC_F3 = 2 * 68486 / (75872 + 103581) = 0.7632750636656952 . 199 | DSC_F1P = 2 * 99747 / (160174 + 103581) = 0.7563610168527611 . 200 | DSC_F2P = 2 * 90781 / (113697 + 103581) = 0.8356207255221422 . 201 | DSC_F3P = 2 * 68485 / (75804 + 103581) = 0.7635532513866823 . 202 | Average DSC_X = 0.7060469526020805 . 203 | Average DSC_Y = 0.707242216489457 . 204 | Average DSC_Z = 0.7042933593532912 . 205 | Average DSC_F1 = 0.7260661178404714 . 206 | Average DSC_F2 = 0.7524984742024067 . 207 | Average DSC_F3 = 0.6022468745422095 . 208 | Average DSC_F1P = 0.7520225323419343 . 209 | Average DSC_F2P = 0.7466938274057885 . 210 | Average DSC_F3P = 0.587057279220913 . 211 | --------------------------------------------------------------------------------