diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/README.md b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9daebf60a6dd921ddd79d416b8cc3d96b3a0a587 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/README.md @@ -0,0 +1,79 @@ +# voxelmorph +voxelmorph推理部分实现,模型概述详情请看[TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow · Ascend/ModelZoo-TensorFlow - 码云 - 开源中国 (gitee.com)](https://gitee.com/ascend/ModelZoo-TensorFlow/tree/master/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow) + +## 训练环境 + +* TensorFlow 1.15.0 +* Python 3.7.5 + +## 代码及路径解释 + +``` +voxelmorph_ID2120_for_ACL +├─ ext 外部依赖库文件夹 +├─ models 存放ckpt,pb,om模型的文件夹 +├─ output om推理输出文件夹 +| +├─ ckpt2pb.py ckpt模型固化为pb +├─ pb2om.sh pb转om脚本 +├─ data2bin.py 数据转换为bin形式批量程序 +├─ omrun.sh om推理脚本 +├─ omtest.py om推理结果测试程序 +├─ datagenerators.py 测试代码依赖:导入数据程序 +├─ networks.py 测试代码依赖:voxelmorph网络py程序 +└─ README.md +``` + + +## 数据集 +* Dataset-ABIDE + +下载地址:`obs://voxelmorph-zyh/Dataset-ABIDE/` + +## 模型文件 +包括初始ckpt文件,固化pb文件,以及推理om文件 +链接: + +## pb模型 + +模型固化 +```shell +python3 ckpt2pb.py +``` +## 生成om模型 + +使用ATC模型转换工具进行模型转换时可参考脚本 `pb2om.sh`, 指令可参考: +```shell +${atc_path}/atc --input_shape="input_src:1,160,192,224,1;input_tgt:1,160,192,224,1" \ +--out_nodes="spatial_transformer/map/TensorArrayStack/TensorArrayGatherV3:0;flow/BiasAdd:0" \ +--output="./models/vm" \ +--soc_version=Ascend310 --framework=3 --model="./models/frozen_model.pb" +``` +具体参数使用方法请查看官方文档。 + +## 将测试集图片转为bin文件 + +```shell +python3 data2bin.py +``` +## 使用msame工具推理 + +使用msame工具进行推理时可参考脚本`omrun.sh`,指令可参考: +```shell +${msame_path}/msame --model ./models/vm.om \ +--input ${input_src},${input_tgt} --output ./output \ +--outputSize "1000000000, 1000000000" --outfmt BIN --loop 1 --debug true +``` +参考 https://gitee.com/ascend/tools/tree/master/msame, 获取msame推理工具及使用方法。 + +## 使用推理得到的bin文件进行推理 +```shell +python3 omtest.py +``` + +## 推理精度 +Ascend910模型精度,推理与NPU结果相同: + +| | 论文精度 | GPU精度 | NPU精度 | 推理精度 | +| :--------------------------------------: | ------------ | ------------ | ------------ | ------------ | +| DICE系数([0, 1], 1 最优)/ 均值(标准差) | 0.752(0.140) | 0.708(0.133) | 0.703(0.134) | 0.703(0.134) | diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ckpt2pb.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ckpt2pb.py new file mode 100644 index 0000000000000000000000000000000000000000..ca6d49a481286af11f563f0c4b5113ca5da7ed8e --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ckpt2pb.py @@ -0,0 +1,43 @@ +import tensorflow as tf +from networks import cvpr2018_net + +def freeze_graph(input_checkpoint,output_graph): + ''' + :param input_checkpoint: + :param output_graph: PB模型保存路径 + :return: + ''' + + tf.reset_default_graph() + # 定义网络的输入节点 + src = tf.placeholder(dtype=tf.float32, shape=[None, 160, 192, 224, 1], name="input_src") + tgt = tf.placeholder(dtype=tf.float32, shape=[None, 160, 192, 224, 1], name="input_tgt") + vol_size = tgt.shape[1:-1] + nf_enc = [16, 32, 32, 32] + nf_dec = [32, 32, 32, 32, 32, 16, 16] + # 调用网络模型生成推理图和输出 + y, flow = cvpr2018_net(vol_size, nf_enc, nf_dec, src, tgt) + + # 指定输出的节点名称,该节点名称必须是原模型中存在的节点 + output_node_names = "spatial_transformer/map/TensorArrayStack/TensorArrayGatherV3,flow/BiasAdd" + + with tf.Session() as sess: + saver = tf.train.Saver() + saver.restore(sess, input_checkpoint) #恢复图并得到数据 + print('normal') + output_graph_def = tf.graph_util.convert_variables_to_constants( # 模型持久化,将变量值固定 + sess=sess, + input_graph_def=sess.graph_def,# 等于:sess.graph_def + output_node_names=output_node_names.split(","))# 如果有多个输出节点,以逗号隔开 + + with tf.gfile.GFile(output_graph, "wb") as f: #保存模型 + f.write(output_graph_def.SerializeToString()) #序列化输出 + print("%d ops in the final graph." % len(output_graph_def.node)) #得到当前图有几个操作节点 + + +# 输入ckpt模型路径 +input_checkpoint='./models/model' +# 输出pb模型的路径 +out_pb_path="./models/frozen_model.pb" +# 调用freeze_graph将ckpt转为pb +freeze_graph(input_checkpoint,out_pb_path) \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/data2bin.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/data2bin.py new file mode 100644 index 0000000000000000000000000000000000000000..ed347497734c0bbb647cd05129512604a3ccd838 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/data2bin.py @@ -0,0 +1,78 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- coding: UTF-8 -*- +# python imports +import os +from argparse import ArgumentParser +import datagenerators +import numpy as np +import nibabel as nib +# npu imports +from npu_bridge.npu_init import * + + +def data2bin(data_path): + # load and transfer atlas from provided files. The atlas we used is 160x192x224. + atlas_vol = nib.load(os.path.join(data_path, 'atlas_abide_brain_crop.nii.gz')).dataobj[ + np.newaxis, ..., np.newaxis].astype('float32') + atlas_vol.tofile(data_path + '/tgt.bin') + + # load and transfer the test data + test_path = os.path.join(data_path, 'test/') + seg_path = os.path.join(data_path, 'seg_affined/') + + # 获取当前路径下的文件名,返回List + file_names = os.listdir(test_path) + n_batches = len(file_names) + + for k in range(n_batches): + vol_name = test_path + file_names[k] + seg_name = seg_path + file_names[k].replace('brain', 'seg') + # load subject test + X_vol, X_seg = datagenerators.load_example_by_name(vol_name, seg_name) + X_vol = X_vol.astype('float32') + + # prepare model folder + bin_dir = data_path+'test_bin' + if not os.path.isdir(bin_dir): + os.mkdir(bin_dir) + X_vol.tofile(bin_dir+'/%03d.bin' % k) + print(k) + + + +if __name__ == "__main__": + parser = ArgumentParser() + + parser.add_argument("--data_path", type=str, + dest="data_path", default='../Dataset-ABIDE/') + + args = parser.parse_args() + print(args.data_path) + data2bin(args.data_path) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/datagenerators.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/datagenerators.py new file mode 100644 index 0000000000000000000000000000000000000000..1f12d773aada847d26dbadc8a7f9e7eded25569a --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/datagenerators.py @@ -0,0 +1,187 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +data generators for VoxelMorph + +for the CVPR and MICCAI papers, we have data arranged in train/validate/test folders +inside each folder is a /vols/ and a /asegs/ folder with the volumes +and segmentations. All of our papers use npz formated data. +""" + +import os, sys +import numpy as np +import nibabel as nib +import tensorflow as tf + + +def zyh_data_in(vol_name, np_var='vol_data'): + X = load_volfile(vol_name, np_var=np_var) + X = X[np.newaxis, ..., np.newaxis].astype('float32') + return X + + +def cvpr2018_gen(gen, atlas_vol_bs, batch_size=1): + """ generator used for cvpr 2018 model """ + + volshape = atlas_vol_bs.shape[1:-1] + zeros = np.zeros((batch_size, *volshape, len(volshape))) + while True: + X = next(gen)[0] + yield ([X, atlas_vol_bs], [atlas_vol_bs, zeros]) + + +def cvpr2018_gen_s2s(gen, batch_size=1): + """ generator used for cvpr 2018 model for subject 2 subject registration """ + zeros = None + while True: + X1 = next(gen)[0] + X2 = next(gen)[0] + + if zeros is None: + volshape = X1.shape[1:-1] + zeros = np.zeros((batch_size, *volshape, len(volshape))) + yield ([X1, X2], [X2, zeros]) + + +def miccai2018_gen(gen, atlas_vol_bs, batch_size=1, bidir=False): + """ generator used for miccai 2018 model """ + volshape = atlas_vol_bs.shape[1:-1] + zeros = np.zeros((batch_size, *volshape, len(volshape))) + while True: + X = next(gen)[0] + if bidir: + yield ([X, atlas_vol_bs], [atlas_vol_bs, X, zeros]) + else: + yield ([X, atlas_vol_bs], [atlas_vol_bs, zeros]) + + +def miccai2018_gen_s2s(gen, batch_size=1, bidir=False): + """ generator used for miccai 2018 model """ + zeros = None + while True: + X = next(gen)[0] + Y = next(gen)[0] + if zeros is None: + volshape = X.shape[1:-1] + zeros = np.zeros((batch_size, *volshape, len(volshape))) + if bidir: + yield ([X, Y], [Y, X, zeros]) + else: + yield ([X, Y], [Y, zeros]) + + +def example_gen(vol_names, batch_size=1, return_segs=False, seg_dir=None, np_var='vol_data'): + """ + generate examples + + Parameters: + vol_names: a list or tuple of filenames + batch_size: the size of the batch (default: 1) + + The following are fairly specific to our data structure, please change to your own + return_segs: logical on whether to return segmentations + seg_dir: the segmentations directory. + np_var: specify the name of the variable in numpy files, if your data is stored in + npz files. default to 'vol_data' + """ + + while True: + idxes = np.random.randint(len(vol_names), size=batch_size) + + X_data = [] + for idx in idxes: + X = load_volfile(vol_names[idx], np_var=np_var) + X = X[np.newaxis, ..., np.newaxis] + X_data.append(X) + + if batch_size > 1: + return_vals = [np.concatenate(X_data, 0)] + else: + return_vals = [X_data[0]] + + # also return segmentations + if return_segs: + X_data = [] + for idx in idxes: + X_seg = load_volfile(vol_names[idx].replace('norm', 'aseg'), np_var=np_var) + X_seg = X_seg[np.newaxis, ..., np.newaxis] + X_data.append(X_seg) + + if batch_size > 1: + return_vals.append(np.concatenate(X_data, 0)) + else: + return_vals.append(X_data[0]) + + yield tuple(return_vals) + + +def load_example_by_name(vol_name, seg_name, np_var='vol_data'): + """ + load a specific volume and segmentation + + np_var: specify the name of the variable in numpy files, if your data is stored in + npz files. default to 'vol_data' + """ + X = load_volfile(vol_name, np_var) + X = X[np.newaxis, ..., np.newaxis].astype('float32') + + return_vals = [X] + + X_seg = load_volfile(seg_name, np_var) + X_seg = X_seg[np.newaxis, ..., np.newaxis].astype('float32') + + return_vals.append(X_seg) + + return tuple(return_vals) + + +def load_volfile(datafile, np_var='vol_data'): + """ + load volume file + formats: nii, nii.gz, mgz, npz + if it's a npz (compressed numpy), variable names innp_var (default: 'vol_data') + """ + assert datafile.endswith(('.nii', '.nii.gz', '.mgz', '.npz')), 'Unknown data file' + + if datafile.endswith(('.nii', '.nii.gz', '.mgz')): + # import nibabel + # if 'nibabel' not in sys.modules: + # try : + # import nibabel as nib + # except: + # print('Failed to import nibabel. need nibabel library for these data file types.') + + X = nib.load(datafile).get_data() + + else: # npz + if np_var is None: + np_var = 'vol_data' + X = np.load(datafile)[np_var] + + return X diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__init__.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bfd54ba430f540dca22651428e75ead9d96c1ea9 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import metrics \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__init__.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5755220db0481d948c82c9bb9803bf7e1d577731 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__init__.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__pycache__/__init__.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..097c7f8f3f010d94d8b9f869b789b16c52d6ebad Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__pycache__/__init__.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__pycache__/metrics.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__pycache__/metrics.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d5fdb892633124fbd128e55e7c16fbb3f9e0e86 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/__pycache__/metrics.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/metrics.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..76f24a81f90950609509dec1d09427ddb2a63d58 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/metrics.py @@ -0,0 +1,80 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +metrics + +Contact: adalca@csail.mit.edu +''' + +# imports +import numpy as np + + +def dice(vol1, vol2, labels=None, nargout=1): + ''' + Dice [1] volume overlap metric + + The default is to *not* return a measure for the background layer (label = 0) + + [1] Dice, Lee R. "Measures of the amount of ecologic association between species." + Ecology 26.3 (1945): 297-302. + + Parameters + ---------- + vol1 : nd array. The first volume (e.g. predicted volume) + vol2 : nd array. The second volume (e.g. "true" volume) + labels : optional vector of labels on which to compute Dice. + If this is not provided, Dice is computed on all non-background (non-0) labels + nargout : optional control of output arguments. if 1, output Dice measure(s). + if 2, output tuple of (Dice, labels) + + Output + ------ + if nargout == 1 : dice : vector of dice measures for each labels + if nargout == 2 : (dice, labels) : where labels is a vector of the labels on which + dice was computed + ''' + if labels is None: + labels = np.unique(np.concatenate((vol1, vol2))) + labels = np.delete(labels, np.where(labels == 0)) # remove background + + dicem = np.zeros(len(labels)) + for idx, lab in enumerate(labels): + vol1l = vol1 == lab + vol2l = vol2 == lab + top = 2 * np.sum(np.logical_and(vol1l, vol2l)) + bottom = np.sum(vol1l) + np.sum(vol2l) + bottom = np.maximum(bottom, np.finfo(float).eps) # add epsilon. + dicem[idx] = top / bottom + + if nargout == 1: + return dicem + else: + return (dicem, labels) + diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/metrics.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/metrics.pyc new file mode 100644 index 0000000000000000000000000000000000000000..027b682db53473d26ff4b4e02af9a2ff37813f77 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/medipy/metrics.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/readme.md b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..1b384b87d645fe0420e33f474a77880fcb535972 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/medipy-lib/readme.md @@ -0,0 +1,2 @@ +# MedIPy +Medical Image Analysis library for Python diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/README.md b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e3f794b44a386bef84558e431c8c0ac3e283fd6f --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/README.md @@ -0,0 +1,35 @@ +# neuron +A Neural networks toolbox for anatomical image analysis + +This toolbox is **currently in development**, with the goal providing a set of tools with infrastructure for medical image analysis with neural network. While the tools are somewhat general, `neuron` will generally run with `keras` on top of `tensorflow`. + +### Main tools +`callbacks`: a set of callbacks during keras training to help with understanding your fit, such as Dice measurements and volume-segmentation overlaps +`generators`: generators for medical image volumes and various combinations of volumes, segmentation, categorical and other output +`dataproc`: a set of tools for processing medical imaging data for preparation for training/testing +`metrics`: metrics (most of which can be used as loss functions), such as dice or weighted categorical crossentropy. +`models`: a set of flexible models (many parameters to play with...) particularly useful in medical image analysis, such as a U-net/hourglass model and a standard classifier. +`layers`: a few simple layers +`plot`: plotting tools, mostly for debugging models +`utils`: various utilities useful in debugging. + +Other utilities and a few `jupyter` notebooks are also provided. + +### Requirements: +- tensorflow +- keras and all of its requirements (e.g. hyp5) +- numpy, scipy +- tqdm +- [python libraries](https://github.com/search?q=user%3Aadalca+topic%3Apython) from @adalca github account + +### Development: +Please contact Adrian Dalca, adalca@csail.mit.edu for question related to `neuron` + +### Papers: +**Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation** +AV Dalca, J Guttag, MR Sabuncu +*Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018.* + +**Spatial Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation** +A.V. Dalca, J. Guttag, and M. R. Sabuncu +*NIPS ML4H: Machine Learning for Health. 2017.* diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__init__.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..50778a84dc5aa65fe37163c36655fe92e816f4cf --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__init__.py @@ -0,0 +1,38 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# import various +from . import dataproc +from . import generators +from . import callbacks +from . import plot +from . import metrics +from . import inits +from . import models +from . import utils +from . import layers diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/__init__.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b380a8ec73814a453a11024748bb9bf9f00454a0 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/__init__.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/callbacks.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/callbacks.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21fdca86bcb661602c799871426d4cadaae3af2e Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/callbacks.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/dataproc.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/dataproc.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d358d0512c847c2ef4a0f87093d17202a443299 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/dataproc.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/generators.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/generators.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..489b59513dd39e5fb74e47502a274efaea917b31 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/generators.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/inits.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/inits.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38e2c1b1a2718db561c843f368ebc9692ced58c3 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/inits.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/layers.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/layers.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..414431169804ec70b9884a00692b483f33c40bd9 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/layers.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/metrics.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/metrics.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0eba2346f1b05af10f3ae38b0da1445a51beded4 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/metrics.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/models.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/models.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44f5db6f57199b28b0143b5af6681654bc65d570 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/models.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/plot.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/plot.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62dd12b8d33b2a15a5daf915ad5364b8d7ed029d Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/plot.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/utils.cpython-37.pyc b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..083f1f1dd3b986f3f4d3fa0c200175112b6d2de6 Binary files /dev/null and b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/__pycache__/utils.cpython-37.pyc differ diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/callbacks.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..9fecd7a0ff4a34847c8732aa569b46f27ed289c2 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/callbacks.py @@ -0,0 +1,654 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' callbacks for the neuron project ''' + +''' +We'd like the following callback actions for neuron: + +- print metrics on the test and validation, especially surface-specific dice +--- Perhaps doable with CSVLogger? +- output graph up to current iteration for each metric +--- Perhaps call CSVLogger or some metric computing callback? +- save dice plots on validation +--- again, expand CSVLogger or similar +- save screenshots of a single test subject [Perhaps just do this as a separate callback?] +--- new callback, PlotSlices + +''' +import sys + +import tensorflow.python.keras as keras +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import warnings +from imp import reload +import pytools.timer as timer + +import pynd.ndutils as nd +import pynd.segutils as su + +# the neuron folder should be on the path +import neuron.plot as nrn_plt +import neuron.utils as nrn_utils + +class ModelWeightCheck(keras.callbacks.Callback): + """ + check model weights for nan and infinite entries + """ + + def __init__(self, + weight_diff=False, + at_batch_end=False, + at_epoch_end=True): + """ + Params: + at_batch_end: None or number indicate when to execute + (i.e. at_batch_end = 10 means execute every 10 batches) + at_epoch_end: logical, whether to execute at epoch end + """ + super(ModelWeightCheck, self).__init__() + self.at_batch_end = at_batch_end + self.at_epoch_end = at_epoch_end + self.current_epoch = 0 + self.weight_diff = weight_diff + self.wts = None + + def on_batch_end(self, batch, logs=None): + if self.at_batch_end is not None and np.mod(batch + 1, self.at_batch_end) == 0: + self.on_model_check(self.current_epoch, batch + 1, logs=logs) + + def on_epoch_end(self, epoch, logs=None): + if self.at_epoch_end: + self.on_model_check(epoch, 0, logs=logs) + self.current_epoch = epoch + + def on_model_check(self, epoch, iter, logs=None): + for layer in self.model.layers: + for wt in layer.get_weights(): + assert ~np.any(np.isnan(wt)), 'Found nan weights in model layer %s' % layer.name + assert np.all(np.isfinite(wt)), 'Found infinite weights in model layer %s' % layer.name + + # compute max change + if self.weight_diff: + wts = self.model.get_weights() + diff = -np.inf + + if self.wts is not None: + for wi, w in enumerate(wts): + if len(w) > 0: + for si, sw in enumerate(w): + diff = np.maximum(diff, np.max(np.abs(sw - self.wts[wi][si]))) + + self.wts = wts + logs['max_diff'] = diff + # print("max diff", diff) + +class CheckLossTrend(keras.callbacks.Callback): + """ + check model weights for nan and infinite entries + """ + + def __init__(self, + at_batch_end=True, + at_epoch_end=False, + nb_std_err=2, + loss_window=10): + """ + Params: + at_batch_end: None or number indicate when to execute + (i.e. at_batch_end = 10 means execute every 10 batches) + at_epoch_end: logical, whether to execute at epoch end + """ + super(CheckLossTrend, self).__init__() + self.at_batch_end = at_batch_end + self.at_epoch_end = at_epoch_end + self.current_epoch = 0 + self.loss_window = loss_window + self.nb_std_err = nb_std_err + self.losses = [] + + def on_batch_end(self, batch, logs=None): + if self.at_batch_end is not None and np.mod(batch + 1, self.at_batch_end) == 0: + self.on_model_check(self.current_epoch, batch + 1, logs=logs) + + def on_epoch_end(self, epoch, logs=None): + if self.at_epoch_end: + self.on_model_check(epoch, 0, logs=logs) + self.current_epoch = epoch + + def on_model_check(self, epoch, iter, logs=None): + if len(self.losses) < self.loss_window: + self.losses = [*self.losses, logs['loss']] + else: + losses_mean = np.mean(self.losses) + losses_std = np.std(self.losses) + this_loss = logs['loss'] + + if (this_loss) > (losses_mean + self.nb_std_err * losses_std): + print(logs) + err = "Found loss %f, which is much higher than %f + %f " % (this_loss, losses_mean, losses_std) + # raise ValueError(err) + print(err, file=sys.stderr) + + if (this_loss - losses_mean) > (losses_mean * 100): + err = "Found loss %f, which is much higher than %f * 100 " % (this_loss, losses_mean) + raise ValueError(err) + + # cut the first loss and stack athe latest loss. + self.losses = [*self.losses[1:], logs['loss']] + + + + +class PlotTestSlices(keras.callbacks.Callback): + ''' + plot slices of a test subject from several directions + ''' + + def __init__(self, + savefilepath, + generator, + vol_size, + run, # object with fields: patch_size, patch_stride, grid_size + data, # object with fields: + at_batch_end=None, # None or number indicate when to execute (i.e. at_batch_end = 10 means execute every 10 batches) + at_epoch_end=True, # logical, whether to execute at epoch end + verbose=False, + period=1, + prior=None): + """ + Parameteres: + savefilepath, + generator, + vol_size, + run: object with fields: patch_size, patch_stride, grid_size + data: object with fields: + at_batch_end=None: None or number indicate when to execute (i.e. at_batch_end = 10 means execute every 10 batches) + at_epoch_end=True: logical, whether to execute at epoch end + verbose=False: + period=1 + prior=None + """ + + super().__init__() + + # save some parameters + self.savefilepath = savefilepath + self.generator = generator + self.vol_size = vol_size + + self.run = run + self.data = data + + self.at_batch_end = at_batch_end + self.at_epoch_end = at_epoch_end + self.current_epoch = 0 + self.period = period + + self.verbose = verbose + + # prepare prior + self.prior = None + if prior is not None: + data = np.load(prior) + loc_vol = data['prior'] + self.prior = np.expand_dims(loc_vol, axis=0) # reshape for model + + def on_batch_end(self, batch, logs={}): + if self.at_batch_end is not None and np.mod(batch + 1, self.at_batch_end) == 0: + self.on_plot_save(self.current_epoch, batch + 1, logs=logs) + + def on_epoch_end(self, epoch, logs={}): + if self.at_epoch_end and np.mod(epoch + 1, self.period) == 0: + self.on_plot_save(epoch, 0, logs=logs) + self.current_epoch = epoch + + def on_plot_save(self, epoch, iter, logs={}): + # import neuron sandbox + # has to be here, can't be at the top, due to cyclical imports (??) + # TODO: should just pass the function to compute the figures given the model and generator + import neuron.sandbox as nrn_sandbox + reload(nrn_sandbox) + + with timer.Timer('plot callback', self.verbose): + if len(self.run.grid_size) == 3: + collapse_2d = [0, 1, 2] + else: + collapse_2d = [2] + + exampl = nrn_sandbox.show_example_prediction_result(self.model, + self.generator, + self.run, + self.data, + test_batch_size=1, + test_model_names=None, + test_grid_size=self.run.grid_size, + ccmap=None, + collapse_2d=collapse_2d, + slice_nr=None, + plt_width=17, + verbose=self.verbose) + + # save, then close + figs = exampl[1:] + for idx, fig in enumerate(figs): + dirn = "dirn_%d" % idx + slice_nr = 0 + filename = self.savefilepath.format(epoch=epoch, iter=iter, axis=dirn, slice_nr=slice_nr) + fig.savefig(filename) + plt.close() + + +class PredictMetrics(keras.callbacks.Callback): + ''' + Compute metrics, like Dice, and save to CSV/log + + ''' + + def __init__(self, + filepath, + metrics, + data_generator, + nb_samples, + nb_labels, + batch_size, + label_ids=None, + vol_params=None, + at_batch_end=None, + at_epoch_end=True, + period=1, + verbose=False): + """ + Parameters: + filepath: filepath with epoch and metric + metrics: list of metrics (functions) + data_generator: validation generator + nb_samples: number of validation samples - volumes or batches + depending on whether vol_params is passed or not + nb_labels: number of labels + batch_size: + label_ids=None: + vol_params=None: + at_batch_end=None: None or number indicate when to execute + (i.e. at_batch_end = 10 means execute every 10 batches) + at_epoch_end=True: logical, whether to execute at epoch end + verbose=False + """ + + # pass in the parameters to object variables + self.metrics = metrics + self.data_generator = data_generator + self.nb_samples = nb_samples + self.filepath = filepath + self.nb_labels = nb_labels + if label_ids is None: + self.label_ids = list(range(nb_labels)) + else: + self.label_ids = label_ids + self.vol_params = vol_params + + self.current_epoch = 1 + self.at_batch_end = at_batch_end + self.at_epoch_end = at_epoch_end + self.batch_size = batch_size + self.period = period + + self.verbose = verbose + + def on_batch_end(self, batch, logs={}): + if self.at_batch_end is not None and np.mod(batch + 1, self.at_batch_end) == 0: + self.on_metric_call(self.current_epoch, batch + 1, logs=logs) + + def on_epoch_end(self, epoch, logs={}): + if self.at_epoch_end and np.mod(epoch + 1, self.period) == 0: + self.on_metric_call(epoch, 0, logs=logs) + self.current_epoch = epoch + + def on_metric_call(self, epoch, iter, logs={}): + """ compute metrics on several predictions """ + with timer.Timer('predict metrics callback', self.verbose): + + # prepare metric + met = np.zeros((self.nb_samples, self.nb_labels, len(self.metrics))) + + # generate predictions + # the idea is to predict either a full volume or just a slice, + # depending on what we need + gen = _generate_predictions(self.model, + self.data_generator, + self.batch_size, + self.nb_samples, + self.vol_params) + batch_idx = 0 + for (vol_true, vol_pred) in gen: + for idx, metric in enumerate(self.metrics): + met[batch_idx, :, idx] = metric(vol_true, vol_pred) + batch_idx += 1 + + # write metric to csv file + if self.filepath is not None: + for idx, metric in enumerate(self.metrics): + filen = self.filepath.format(epoch=epoch, iter=iter, metric=metric.__name__) + np.savetxt(filen, met[:, :, idx], fmt='%f', delimiter=',') + else: + meanmet = np.nanmean(met, axis=0) + for midx, metric in enumerate(self.metrics): + for idx in range(self.nb_labels): + varname = '%s_label_%d' % (metric.__name__, self.label_ids[idx]) + logs[varname] = meanmet[idx, midx] + +class ModelCheckpoint(keras.callbacks.Callback): + """ + A modification of keras' ModelCheckpoint, but allow for saving on_batch_end + changes include: + - optional at_batch_end, at_epoch_end arguments, + - filename now must includes 'iter' + + Save the model after every epoch. + `filepath` can contain named formatting options, + which will be filled the value of `epoch` and + keys in `logs` (passed in `on_epoch_end`). + For example: if `filepath` is `weights.{epoch:02d}-{val_loss:.2f}.hdf5`, + then the model checkpoints will be saved with the epoch number and + the validation loss in the filename. + # Arguments + filepath: string, path to save the model file. + monitor: quantity to monitor. + verbose: verbosity mode, 0 or 1. + save_best_only: if `save_best_only=True`, + the latest best model according to + the quantity monitored will not be overwritten. + mode: one of {auto, min, max}. + If `save_best_only=True`, the decision + to overwrite the current save file is made + based on either the maximization or the + minimization of the monitored quantity. For `val_acc`, + this should be `max`, for `val_loss` this should + be `min`, etc. In `auto` mode, the direction is + automatically inferred from the name of the monitored quantity. + save_weights_only: if True, then only the model's weights will be + saved (`model.save_weights(filepath)`), else the full model + is saved (`model.save(filepath)`). + period: Interval (number of epochs) between checkpoints. + """ + + def __init__(self, filepath, + monitor='val_loss', + save_best_only=False, + save_weights_only=False, + at_batch_end=None, + at_epoch_end=True, + mode='auto', period=1, + verbose=False): + """ + Parameters: + ... + at_batch_end=None: None or number indicate when to execute + (i.e. at_batch_end = 10 means execute every 10 batches) + at_epoch_end=True: logical, whether to execute at epoch end + """ + super(ModelCheckpoint, self).__init__() + self.monitor = monitor + self.verbose = verbose + self.filepath = filepath + self.save_best_only = save_best_only + self.save_weights_only = save_weights_only + self.period = period + self.steps_since_last_save = 0 + + if mode not in ['auto', 'min', 'max']: + warnings.warn('ModelCheckpoint mode %s is unknown, ' + 'fallback to auto mode.' % (mode), + RuntimeWarning) + mode = 'auto' + + if mode == 'min': + self.monitor_op = np.less + self.best = np.Inf + elif mode == 'max': + self.monitor_op = np.greater + self.best = -np.Inf + else: + if 'acc' in self.monitor or self.monitor.startswith('fmeasure'): + self.monitor_op = np.greater + self.best = -np.Inf + else: + self.monitor_op = np.less + self.best = np.Inf + + self.at_batch_end = at_batch_end + self.at_epoch_end = at_epoch_end + self.current_epoch = 0 + + def on_epoch_begin(self, epoch, logs=None): + self.current_epoch = epoch + + def on_batch_end(self, batch, logs=None): + if self.at_batch_end is not None and np.mod(batch + 1, self.at_batch_end) == 0: + print("Saving model at batch end!") + self.on_model_save(self.current_epoch, batch + 1, logs=logs) + + def on_epoch_end(self, epoch, logs=None): + if self.at_epoch_end: + self.on_model_save(epoch, 0, logs=logs) + self.current_epoch = epoch + 1 + + def on_model_save(self, epoch, iter, logs=None): + """ save the model to hdf5. Code mostly from keras core """ + + with timer.Timer('model save callback', self.verbose): + logs = logs or {} + self.steps_since_last_save += 1 + if self.steps_since_last_save >= self.period: + self.steps_since_last_save = 0 + filepath = self.filepath.format(epoch=epoch, iter=iter, **logs) + if self.save_best_only: + current = logs.get(self.monitor) + if current is None: + warnings.warn('Can save best model only with %s available, ' + 'skipping.' % (self.monitor), RuntimeWarning) + else: + if self.monitor_op(current, self.best): + if self.verbose > 0: + print('Epoch %05d Iter%05d: %s improved from %0.5f to %0.5f,' + ' saving model to %s' + % (epoch, iter, self.monitor, self.best, + current, filepath)) + self.best = current + if self.save_weights_only: + self.model.save_weights(filepath, overwrite=True) + else: + self.model.save(filepath, overwrite=True) + else: + if self.verbose > 0: + print('Epoch %05d Iter%05d: %s did not improve' % + (epoch, iter, self.monitor)) + else: + if self.verbose > 0: + print('Epoch %05d: saving model to %s' % (epoch, filepath)) + if self.save_weights_only: + self.model.save_weights(filepath, overwrite=True) + else: + self.model.save(filepath, overwrite=True) + +class ModelCheckpointParallel(keras.callbacks.Callback): + """ + + borrow from: https://github.com/rmkemker/main/blob/master/machine_learning/model_checkpoint_parallel.py + + Save the model after every epoch. + `filepath` can contain named formatting options, + which will be filled the value of `epoch` and + keys in `logs` (passed in `on_epoch_end`). + For example: if `filepath` is `weights.{epoch:02d}-{val_loss:.2f}.hdf5`, + then the model checkpoints will be saved with the epoch number and + the validation loss in the filename. + # Arguments + filepath: string, path to save the model file. + monitor: quantity to monitor. + verbose: verbosity mode, 0 or 1. + save_best_only: if `save_best_only=True`, + the latest best model according to + the quantity monitored will not be overwritten. + mode: one of {auto, min, max}. + If `save_best_only=True`, the decision + to overwrite the current save file is made + based on either the maximization or the + minimization of the monitored quantity. For `val_acc`, + this should be `max`, for `val_loss` this should + be `min`, etc. In `auto` mode, the direction is + automatically inferred from the name of the monitored quantity. + save_weights_only: if True, then only the model's weights will be + saved (`model.save_weights(filepath)`), else the full model + is saved (`model.save(filepath)`). + period: Interval (number of epochs) between checkpoints. + """ + + def __init__(self, filepath, monitor='val_loss', verbose=0, + save_best_only=False, save_weights_only=False, + at_batch_end=None, + at_epoch_end=True, + mode='auto', period=1): + super(ModelCheckpointParallel, self).__init__() + self.monitor = monitor + self.verbose = verbose + self.filepath = filepath + self.save_best_only = save_best_only + self.save_weights_only = save_weights_only + self.period = period + self.epochs_since_last_save = 0 + + if mode not in ['auto', 'min', 'max']: + warnings.warn('ModelCheckpointParallel mode %s is unknown, ' + 'fallback to auto mode.' % (mode), + RuntimeWarning) + mode = 'auto' + + if mode == 'min': + self.monitor_op = np.less + self.best = np.Inf + elif mode == 'max': + self.monitor_op = np.greater + self.best = -np.Inf + else: + if 'acc' in self.monitor or self.monitor.startswith('fmeasure'): + self.monitor_op = np.greater + self.best = -np.Inf + else: + self.monitor_op = np.less + self.best = np.Inf + + self.at_batch_end = at_batch_end + self.at_epoch_end = at_epoch_end + self.current_epoch = 0 + + def on_epoch_begin(self, epoch, logs=None): + self.current_epoch = epoch + + def on_batch_end(self, batch, logs=None): + if self.at_batch_end is not None and np.mod(batch + 1, self.at_batch_end) == 0: + print("Saving model at batch end!") + self.on_model_save(self.current_epoch, batch + 1, logs=logs) + + def on_epoch_end(self, epoch, logs=None): + if self.at_epoch_end: + self.on_model_save(epoch, 0, logs=logs) + self.current_epoch = epoch + 1 + + def on_model_save(self, epoch, iter, logs=None): + """ save the model to hdf5. Code mostly from keras core """ + + with timer.Timer('model save callback', self.verbose): + logs = logs or {} + num_outputs = len(self.model.outputs) + self.epochs_since_last_save += 1 + if self.epochs_since_last_save >= self.period: + self.epochs_since_last_save = 0 + filepath = self.filepath.format(epoch=epoch, iter=iter, **logs) + if self.save_best_only: + current = logs.get(self.monitor) + if current is None: + warnings.warn('Can save best model only with %s available, ' + 'skipping.' % (self.monitor), RuntimeWarning) + else: + if self.monitor_op(current, self.best): + if self.verbose > 0: + print('Epoch %05d: Iter%05d: %s improved from %0.5f to %0.5f,' + ' saving model to %s' + % (epoch, iter, self.monitor, self.best, + current, filepath)) + self.best = current + if self.save_weights_only: + self.model.layers[-(num_outputs+1)].save_weights(filepath, overwrite=True) + else: + self.model.layers[-(num_outputs+1)].save(filepath, overwrite=True) + else: + if self.verbose > 0: + print('Epoch %05d Iter%05d: %s did not improve' % + (epoch, iter, self.monitor)) + else: + if self.verbose > 0: + print('Epoch %05d: saving model to %s' % (epoch, filepath)) + if self.save_weights_only: + self.model.layers[-(num_outputs+1)].save_weights(filepath, overwrite=True) + else: + self.model.layers[-(num_outputs+1)].save(filepath, overwrite=True) + +################################################################################################## +# helper functions +################################################################################################## + +def _generate_predictions(model, data_generator, batch_size, nb_samples, vol_params): + # whole volumes + if vol_params is not None: + for _ in range(nb_samples): # assumes nr volume + vols = nrn_utils.predict_volumes(model, + data_generator, + batch_size, + vol_params["patch_size"], + vol_params["patch_stride"], + vol_params["grid_size"]) + vol_true, vol_pred = vols[0], vols[1] + yield (vol_true, vol_pred) + + # just one batch + else: + for _ in range(nb_samples): # assumes nr batches + vol_pred, vol_true = nrn_utils.next_label(model, data_generator) + yield (vol_true, vol_pred) + +import collections +def _flatten(l): + # https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists + for el in l: + if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)): + yield from _flatten(el) + else: + yield el diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/dataproc.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/dataproc.py new file mode 100644 index 0000000000000000000000000000000000000000..2d90a7671f0af9b5dc3f3398229392dff1189167 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/dataproc.py @@ -0,0 +1,448 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' data processing for neuron project ''' + +# built-in +import sys +import os +import shutil +import six + +# third party +import nibabel as nib +import numpy as np +import scipy.ndimage.interpolation +from tqdm import tqdm_notebook as tqdm # for verbosity for forloops +from PIL import Image +import matplotlib.pyplot as plt + + + +# note sure if tqdm_notebook reverts back to +try: + get_ipython + from tqdm import tqdm_notebook as tqdm +except: + from tqdm import tqdm as tqdm + +from subprocess import call + + +# import local ndutils +import pynd.ndutils as nd +import re + +from imp import reload +reload(nd) + +# from imp import reload # for re-loading modules, since some of the modules are still in development +# reload(nd) + + +def proc_mgh_vols(inpath, + outpath, + ext='.mgz', + label_idx=None, + **kwargs): + ''' process mgh data from mgz format and save to numpy format + + 1. load file + 2. normalize intensity + 3. resize + 4. save as python block + + TODO: check header info and such.? + ''' + + # get files in input directory + files = [f for f in os.listdir(inpath) if f.endswith(ext)] + + # go through each file + list_skipped_files = () + for fileidx in tqdm(range(len(files)), ncols=80): + + # load nifti volume + volnii = nib.load(os.path.join(inpath, files[fileidx])) + + # get the data out + vol_data = volnii.get_data().astype(float) + + if ('dim' in volnii.header) and volnii.header['dim'][4] > 1: + vol_data = vol_data[:, :, :, -1] + + # process volume + try: + vol_data = vol_proc(vol_data, **kwargs) + except Exception as e: + list_skipped_files += (files[fileidx], ) + print("Skipping %s\nError: %s" % (files[fileidx], str(e)), file=sys.stderr) + continue + + if label_idx is not None: + vol_data = (vol_data == label_idx).astype(int) + + # save numpy file + outname = os.path.splitext(os.path.join(outpath, files[fileidx]))[0] + '.npz' + np.savez_compressed(outname, vol_data=vol_data) + + for file in list_skipped_files: + print("Skipped: %s" % file, file=sys.stderr) + + +def scans_to_slices(inpath, outpath, slice_nrs, + ext='.mgz', + label_idx=None, + dim_idx=2, + out_ext='.png', + slice_pad=0, + vol_inner_pad_for_slice_nrs=0, + **kwargs): # vol_proc args + + # get files in input directory + files = [f for f in os.listdir(inpath) if f.endswith(ext)] + + # go through each file + list_skipped_files = () + for fileidx in tqdm(range(len(files)), ncols=80): + + # load nifti volume + volnii = nib.load(os.path.join(inpath, files[fileidx])) + + # get the data out + vol_data = volnii.get_data().astype(float) + + if ('dim' in volnii.header) and volnii.header['dim'][4] > 1: + vol_data = vol_data[:, :, :, -1] + + if slice_pad > 0: + assert (out_ext != '.png'), "slice pad can only be used with volumes" + + # process volume + try: + vol_data = vol_proc(vol_data, **kwargs) + except Exception as e: + list_skipped_files += (files[fileidx], ) + print("Skipping %s\nError: %s" % (files[fileidx], str(e)), file=sys.stderr) + continue + + mult_fact = 255 + if label_idx is not None: + vol_data = (vol_data == label_idx).astype(int) + mult_fact = 1 + + # extract slice + if slice_nrs is None: + slice_nrs_sel = range(vol_inner_pad_for_slice_nrs+slice_pad, vol_data.shape[dim_idx]-slice_pad-vol_inner_pad_for_slice_nrs) + else: + slice_nrs_sel = slice_nrs + + for slice_nr in slice_nrs_sel: + slice_nr_out = range(slice_nr - slice_pad, slice_nr + slice_pad + 1) + if dim_idx == 2: # TODO: fix in one line + vol_img = np.squeeze(vol_data[:, :, slice_nr_out]) + elif dim_idx == 1: + vol_img = np.squeeze(vol_data[:, slice_nr_out, :]) + else: + vol_img = np.squeeze(vol_data[slice_nr_out, :, :]) + + # save file + if out_ext == '.png': + # save png file + img = (vol_img*mult_fact).astype('uint8') + outname = os.path.splitext(os.path.join(outpath, files[fileidx]))[0] + '_slice%d.png' % slice_nr + Image.fromarray(img).convert('RGB').save(outname) + else: + if slice_pad == 0: # dimenion has collapsed + assert vol_img.ndim == 2 + vol_img = np.expand_dims(vol_img, dim_idx) + # assuming nibabel saving image + nii = nib.Nifti1Image(vol_img, np.diag([1,1,1,1])) + outname = os.path.splitext(os.path.join(outpath, files[fileidx]))[0] + '_slice%d.nii.gz' % slice_nr + nib.save(nii, outname) + + +def vol_proc(vol_data, + crop=None, + resize_shape=None, # None (to not resize), or vector. If vector, third entry can be None + interp_order=None, + rescale=None, + rescale_prctle=None, + resize_slices=None, + resize_slices_dim=None, + offset=None, + clip=None, + extract_nd=None, # extracts a particular section + force_binary=None, # forces anything > 0 to be 1 + permute=None): + ''' process a volume with a series of intensity rescale, resize and crop rescale''' + + if offset is not None: + vol_data = vol_data + offset + + # intensity normalize data .* rescale + if rescale is not None: + vol_data = np.multiply(vol_data, rescale) + + if rescale_prctle is not None: + # print("max:", np.max(vol_data.flat)) + # print("test") + rescale = np.percentile(vol_data.flat, rescale_prctle) + # print("rescaling by 1/%f" % (rescale)) + vol_data = np.multiply(vol_data.astype(float), 1/rescale) + + if resize_slices is not None: + resize_slices = [*resize_slices] + assert resize_shape is None, "if resize_slices is given, resize_shape has to be None" + resize_shape = resize_slices + if resize_slices_dim is None: + resize_slices_dim = np.where([f is None for f in resize_slices])[0] + assert len(resize_slices_dim) == 1, "Could not find dimension or slice resize" + resize_slices_dim = resize_slices_dim[0] + resize_shape[resize_slices_dim] = vol_data.shape[resize_slices_dim] + + # resize (downsample) matrices + if resize_shape is not None and resize_shape != vol_data.shape: + resize_shape = [*resize_shape] + # allow for the last entry to be None + if resize_shape[-1] is None: + resize_ratio = np.divide(resize_shape[0], vol_data.shape[0]) + resize_shape[-1] = np.round(resize_ratio * vol_data.shape[-1]).astype('int') + resize_ratio = np.divide(resize_shape, vol_data.shape) + vol_data = scipy.ndimage.interpolation.zoom(vol_data, resize_ratio, order=interp_order) + + # crop data if necessary + if crop is not None: + vol_data = nd.volcrop(vol_data, crop=crop) + + # needs to be last to guarantee clip limits. + # For e.g., resize might screw this up due to bicubic interpolation if it was done after. + if clip is not None: + vol_data = np.clip(vol_data, clip[0], clip[1]) + + if extract_nd is not None: + vol_data = vol_data[np.ix_(*extract_nd)] + + if force_binary: + vol_data = (vol_data > 0).astype(float) + + # return with checks. this check should be right at the end before rturn + if clip is not None: + assert np.max(vol_data) <= clip[1], "clip failed" + assert np.min(vol_data) >= clip[0], "clip failed" + return vol_data + + +def prior_to_weights(prior_filename, nargout=1, min_freq=0, force_binary=False, verbose=False): + + ''' transform a 4D prior (3D + nb_labels) into a class weight vector ''' + + # load prior + if isinstance(prior_filename, six.string_types): + prior = np.load(prior_filename)['prior'] + else: + prior = prior_filename + + # assumes prior is 4D. + assert np.ndim(prior) == 4 or np.ndim(prior) == 3, "prior is the wrong number of dimensions" + prior_flat = np.reshape(prior, (np.prod(prior.shape[0:(np.ndim(prior)-1)]), prior.shape[-1])) + + if force_binary: + nb_labels = prior_flat.shape[-1] + prior_flat[:, 1] = np.sum(prior_flat[:, 1:nb_labels], 1) + prior_flat = np.delete(prior_flat, range(2, nb_labels), 1) + + # sum total class votes + class_count = np.sum(prior_flat, 0) + class_prior = class_count / np.sum(class_count) + + # adding minimum frequency + class_prior[class_prior < min_freq] = min_freq + class_prior = class_prior / np.sum(class_prior) + + if np.any(class_prior == 0): + print("Warning, found a label with 0 support. Setting its weight to 0!", file=sys.stderr) + class_prior[class_prior == 0] = np.inf + + # compute weights from class frequencies + weights = 1/class_prior + weights = weights / np.sum(weights) + # weights[0] = 0 # explicitly don't care about bg + + # a bit of verbosity + if verbose: + f, (ax1, ax2, ax3) = plt.subplots(1, 3) + ax1.bar(range(prior.size), np.log(prior)) + ax1.set_title('log class freq') + ax2.bar(range(weights.size), weights) + ax2.set_title('weights') + ax3.bar(range(weights.size), np.log((weights))-np.min(np.log((weights)))) + ax3.set_title('log(weights)-minlog') + f.set_size_inches(12, 3) + plt.show() + np.set_printoptions(precision=3) + + # return + if nargout == 1: + return weights + else: + return (weights, prior) + + + + +def filestruct_change(in_path, out_path, re_map, + mode='subj_to_type', + use_symlinks=False, name=""): + """ + change from independent subjects in a folder to breakdown structure + + example: filestruct_change('/../in_path', '/../out_path', {'asegs.nii.gz':'asegs', 'norm.nii.gz':'vols'}) + + + input structure: + /.../in_path/subj_1 --> with files that match regular repressions defined in re_map.keys() + /.../in_path/subj_2 --> with files that match regular repressions defined in re_map.keys() + ... + output structure: + /.../out_path/asegs/subj_1.nii.gz, subj_2.nii.gz + /.../out_path/vols/subj_1.nii.gz, subj_2.nii.gz + + Parameters: + in_path (string): input path + out_path (string): output path + re_map (dictionary): keys are reg-exs that match files in the input folders. + values are the folders to put those files in the new structure. + values can also be tuples, in which case values[0] is the dst folder, + and values[1] is the extension of the output file + mode (optional) + use_symlinks (bool): whether to just use symlinks rather than copy files + default:True + """ + + + if not os.path.isdir(out_path): + os.mkdir(out_path) + + # go through folders + for subj in tqdm(os.listdir(in_path), desc=name): + + # go through files in a folder + files = os.listdir(os.path.join(in_path, subj)) + for file in files: + + # see which key matches. Make sure only one does. + matches = [re.match(k, file) for k in re_map.keys()] + nb_matches = sum([f is not None for f in matches]) + assert nb_matches == 1, "Found %d matches for file %s/%s" %(nb_matches, file, subj) + + # get the matches key + match_idx = [i for i,f in enumerate(matches) if f is not None][0] + matched_dst = re_map[list(re_map.keys())[match_idx]] + _, ext = os.path.splitext(file) + if isinstance(matched_dst, tuple): + ext = matched_dst[1] + matched_dst = matched_dst[0] + + # prepare source and destination file + src_file = os.path.join(in_path, subj, file) + dst_path = os.path.join(out_path, matched_dst) + if not os.path.isdir(dst_path): + os.mkdir(dst_path) + dst_file = os.path.join(dst_path, subj + ext) + + if use_symlinks: + # on windows there are permission problems. + # Can try : call(['mklink', 'LINK', 'TARGET'], shell=True) + # or note https://stackoverflow.com/questions/6260149/os-symlink-support-in-windows + os.symlink(src_file, dst_file) + + else: + shutil.copyfile(src_file, dst_file) + + +def ml_split(in_path, out_path, + cat_titles=['train', 'validate', 'test'], + cat_prop=[0.5, 0.3, 0.2], + use_symlinks=False, + seed=None, + tqdm=tqdm): + """ + split dataset + """ + + if seed is not None: + np.random.seed(seed) + + if not os.path.isdir(out_path): + os.makedirs(out_path) + + # get subjects and randomize their order + subjs = sorted(os.listdir(in_path)) + nb_subj = len(subjs) + subj_order = np.random.permutation(nb_subj) + + # prepare split + cat_tot = np.cumsum(cat_prop) + if not cat_tot[-1] == 1: + print("split_prop sums to %f, re-normalizing" % cat_tot) + cat_tot = np.array(cat_tot) / cat_tot[-1] + nb_cat_subj = np.round(cat_tot * nb_subj).astype(int) + cat_subj_start = [0, *nb_cat_subj[:-1]] + + # go through each category + for cat_idx, cat in enumerate(cat_titles): + if not os.path.isdir(os.path.join(out_path, cat)): + os.mkdir(os.path.join(out_path, cat)) + + cat_subj_idx = subj_order[cat_subj_start[cat_idx]:nb_cat_subj[cat_idx]] + for subj_idx in tqdm(cat_subj_idx, desc=cat): + src_folder = os.path.join(in_path, subjs[subj_idx]) + dst_folder = os.path.join(out_path, cat, subjs[subj_idx]) + + if use_symlinks: + # on windows there are permission problems. + # Can try : call(['mklink', 'LINK', 'TARGET'], shell=True) + # or note https://stackoverflow.com/questions/6260149/os-symlink-support-in-windows + os.symlink(src_folder, dst_folder) + + else: + if os.path.isdir(src_folder): + shutil.copytree(src_folder, dst_folder) + else: + shutil.copyfile(src_folder, dst_folder) + + + + + + + + + + diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/generators.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/generators.py new file mode 100644 index 0000000000000000000000000000000000000000..5eb9f0daa117e089651fcd0f72e2e044d6a1f9ff --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/generators.py @@ -0,0 +1,1443 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" generators for the neuron project """ + +# general imports +import sys +import os + +# third party imports +import numpy as np +import nibabel as nib +import scipy +from tensorflow.python.keras.utils import np_utils +import tensorflow.python.keras as keras +import tensorflow.python.keras.preprocessing +import tensorflow.python.keras.preprocessing.image +from tensorflow.python.keras.models import Model + +# local packages +import pynd.ndutils as nd +import pytools.patchlib as pl +import pytools.timer as timer + +# reload patchlib (it's often updated right now...) +from imp import reload +reload(pl) + +# other neuron (this project) packages +from . import dataproc as nrn_proc +from . import models as nrn_models + + +class Vol(object): + + def __init__(self, + volpath, + ext='.npz', + nb_restart_cycle=None, # number of files to restart after + name='single_vol', # name + fixed_vol_size=True, # assumes each volume is fixed size + ): + + # get filenames at given paths + volfiles = _get_file_list(volpath, ext, vol_rand_seed) + nb_files = len(volfiles) + assert nb_files > 0, "Could not find any files at %s with extension %s" % (volpath, ext) + + # set up restart cycle for volume files -- + # i.e. after how many volumes do we restart + if nb_restart_cycle is None: + nb_restart_cycle = nb_files + + # compute subvolume split + vol_data = _load_medical_volume(os.path.join(volpath, volfiles[0]), ext) + # process volume + if data_proc_fn is not None: + vol_data = data_proc_fn(vol_data) + [f for f in _npz_headers(npz, namelist=['vol_data.npy'])][0][1] + + nb_patches_per_vol = 1 + if fixed_vol_size and (patch_size is not None) and all(f is not None for f in patch_size): + nb_patches_per_vol = np.prod(pl.gridsize(vol_data.shape, patch_size, patch_stride)) + + assert nb_restart_cycle <= (nb_files * nb_patches_per_vol), \ + '%s restart cycle (%s) too big (%s) in %s' % \ + (name, nb_restart_cycle, nb_files * nb_patches_per_vol, volpath) + + +def vol(volpath, + ext='.npz', + batch_size=1, + expected_nb_files=-1, + expected_files=None, + data_proc_fn=None, # processing function that takes in one arg (the volume) + relabel=None, # relabeling array + nb_labels_reshape=0, # reshape to categorial format for keras, need # labels + keep_vol_size=False, # whether to keep the volume size on categorical resizing + name='single_vol', # name, optional + nb_restart_cycle=None, # number of files to restart after + patch_size=None, # split the volume in patches? if so, get patch_size + patch_stride=1, # split the volume in patches? if so, get patch_stride + collapse_2d=None, + extract_slice=None, + force_binary=False, + nb_feats=1, + patch_rand=False, + patch_rand_seed=None, + vol_rand_seed=None, + binary=False, + yield_incomplete_final_batch=True, + verbose=False): + """ + generator for single volume (or volume patches) from a list of files + + simple volume generator that loads a volume (via npy/mgz/nii/niigz), processes it, + and prepares it for keras model formats + + if a patch size is passed, breaks the volume into patches and generates those + """ + + # get filenames at given paths + volfiles = _get_file_list(volpath, ext, vol_rand_seed) + nb_files = len(volfiles) + assert nb_files > 0, "Could not find any files at %s with extension %s" % (volpath, ext) + + # compute subvolume split + vol_data = _load_medical_volume(os.path.join(volpath, volfiles[0]), ext) + + # process volume + if data_proc_fn is not None: + vol_data = data_proc_fn(vol_data) + + nb_patches_per_vol = 1 + if patch_size is not None and all(f is not None for f in patch_size): + if relabel is None and len(patch_size) == (len(vol_data.shape) - 1): + tmp_patch_size = [f for f in patch_size] + patch_size = [*patch_size, vol_data.shape[-1]] + patch_stride = [f for f in patch_stride] + patch_stride = [*patch_stride, vol_data.shape[-1]] + assert len(vol_data.shape) == len(patch_size), "Vol dims %d are not equal to patch dims %d" % (len(vol_data.shape), len(patch_size)) + nb_patches_per_vol = np.prod(pl.gridsize(vol_data.shape, patch_size, patch_stride)) + if nb_restart_cycle is None: + print("setting restart cycle to", nb_files) + nb_restart_cycle = nb_files + + assert nb_restart_cycle <= (nb_files * nb_patches_per_vol), \ + '%s restart cycle (%s) too big (%s) in %s' % \ + (name, nb_restart_cycle, nb_files * nb_patches_per_vol, volpath) + + # check the number of files matches expected (if passed) + if expected_nb_files >= 0: + assert nb_files == expected_nb_files, \ + "number of files do not match: %d, %d" % (nb_files, expected_nb_files) + if expected_files is not None: + if not (volfiles == expected_files): + print('file lists did not match. You should probably stop execution.', file=sys.stderr) + print(len(volfiles), len(expected_files)) + + if verbose: + print('nb_restart_cycle:', nb_restart_cycle) + + # iterate through files + fileidx = -1 + batch_idx = -1 + feat_idx = 0 + batch_shape = None + while 1: + fileidx = np.mod(fileidx + 1, nb_restart_cycle) + if verbose and fileidx == 0: + print('starting %s cycle' % name) + + # read next file (circular) + + try: + if verbose: + print('opening %s' % os.path.join(volpath, volfiles[fileidx])) + file_name = os.path.join(volpath, volfiles[fileidx]) + vol_data = _load_medical_volume(file_name, ext, verbose) + # print(file_name, " was loaded", vol_data.shape) + except: + debug_error_msg = "#files: %d, fileidx: %d, nb_restart_cycle: %d. error: %s" + print(debug_error_msg % (len(volfiles), fileidx, nb_restart_cycle, sys.exc_info()[0])) + raise + + # process volume + if data_proc_fn is not None: + vol_data = data_proc_fn(vol_data) + + # the original segmentation files have non-sequential relabel (i.e. some relabel are + # missing to avoid exploding our model, we only care about the relabel that exist. + if relabel is not None: + vol_data = _relabel(vol_data, relabel) + + # split volume into patches if necessary and yield + if patch_size is None: + this_patch_size = vol_data.shape + patch_stride = [1 for f in this_patch_size] + + else: + this_patch_size = [f for f in patch_size] + for pi, p in enumerate(this_patch_size): + if p is None: + this_patch_size[pi] = vol_data.shape[pi] + patch_stride[pi] = 1 + + assert ~np.any(np.isnan(vol_data)), "Found a nan for %s" % volfiles[fileidx] + assert np.all(np.isfinite(vol_data)), "Found a inf for %s" % volfiles[fileidx] + + patch_gen = patch(vol_data, this_patch_size, + patch_stride=patch_stride, + nb_labels_reshape=nb_labels_reshape, + batch_size=1, + infinite=False, + collapse_2d=collapse_2d, + patch_rand=patch_rand, + patch_rand_seed=patch_rand_seed, + keep_vol_size=keep_vol_size) + + empty_gen = True + patch_idx = -1 + for lpatch in patch_gen: + empty_gen = False + patch_idx += 1 + + # add to feature + if np.mod(feat_idx, nb_feats) == 0: + vol_data_feats = lpatch + + else: + vol_data_feats = np.concatenate([vol_data_feats, lpatch], np.ndim(lpatch)-1) + feat_idx += 1 + + if binary: + vol_data_feats = vol_data_feats.astype(bool) + + if np.mod(feat_idx, nb_feats) == 0: + feats_shape = vol_data_feats[1:] + + # yield previous batch if the new volume has different patch sizes + if batch_shape is not None and (feats_shape != batch_shape): + batch_idx = -1 + batch_shape = None + print('switching patch sizes') + yield np.vstack(vol_data_batch) + + # add to batch of volume data, unless the batch is currently empty + if batch_idx == -1: + vol_data_batch = [vol_data_feats] + batch_shape = vol_data_feats[1:] + else: + vol_data_batch = [*vol_data_batch, vol_data_feats] + + # yield patch + batch_idx += 1 + batch_done = batch_idx == batch_size - 1 + files_done = np.mod(fileidx + 1, nb_restart_cycle) == 0 + final_batch = yield_incomplete_final_batch and files_done and patch_idx == (nb_patches_per_vol-1) + if final_batch: # verbose and + print('last batch in %s cycle %d. nb_batch:%d' % (name, fileidx, len(vol_data_batch))) + + if batch_done or final_batch: + batch_idx = -1 + q = np.vstack(vol_data_batch) + yield q + + if empty_gen: + raise ValueError('Patch generator was empty for file %s', volfiles[fileidx]) + + +def patch(vol_data, # the volume + patch_size, # patch size + patch_stride=1, # patch stride (spacing) + nb_labels_reshape=1, # number of labels for categorical resizing. 0 if no resizing + keep_vol_size=False, # whether to keep the volume size on categorical resizing + batch_size=1, # batch size + collapse_2d=None, + patch_rand=False, + patch_rand_seed=None, + variable_batch_size=False, + infinite=False): # whether the generator should continue (re)-generating patches + """ + generate patches from volume for keras package + + Yields: + patch: nd array of shape [batch_size, *patch_size], unless resized via nb_labels_reshape + """ + + # some parameter setup + assert batch_size >= 1, "batch_size should be at least 1" + if patch_size is None: + patch_size = vol_data.shape + for pi,p in enumerate(patch_size): + if p is None: + patch_size[pi] = vol_data.shape[pi] + batch_idx = -1 + if variable_batch_size: + batch_size = yield + + + # do while. if not infinite, will break at the end + while True: + # create patch generator + gen = pl.patch_gen(vol_data, patch_size, + stride=patch_stride, + rand=patch_rand, + rand_seed=patch_rand_seed) + + # go through the patch generator + empty_gen = True + for lpatch in gen: + + empty_gen = False + # reshape output layer as categorical and prep proper size + # print(lpatch.shape, nb_labels_reshape, keep_vol_size, patch_size) + lpatch = _categorical_prep(lpatch, nb_labels_reshape, keep_vol_size, patch_size) + + if collapse_2d is not None: + lpatch = np.squeeze(lpatch, collapse_2d + 1) # +1 due to batch in first dim + + # add this patch to the stack + if batch_idx == -1: + if batch_size == 1: + patch_data_batch = lpatch + else: + patch_data_batch = np.zeros([batch_size, *lpatch.shape[1:]]) + patch_data_batch[0, :] = lpatch + + else: + patch_data_batch[batch_idx+1, :] = lpatch + + # yield patch + batch_idx += 1 + if batch_idx == batch_size - 1: + batch_idx = -1 + batch_size_y = yield patch_data_batch + if variable_batch_size: + batch_size = batch_size_y + + assert not empty_gen, 'generator was empty. vol size was %s' % ''.join(['%d '%d for d in vol_data.shape]) + + # if not infinite generation, yield the last batch and break the while + if not infinite: + if batch_idx >= 0: + patch_data_batch = patch_data_batch[:(batch_idx+1), :] + yield patch_data_batch + break + + +def vol_seg(volpath, + segpath, + proc_vol_fn=None, + proc_seg_fn=None, + verbose=False, + name='vol_seg', # name, optional + ext='.npz', + nb_restart_cycle=None, # number of files to restart after + nb_labels_reshape=-1, + collapse_2d=None, + force_binary=False, + nb_input_feats=1, + relabel=None, + vol_rand_seed=None, + seg_binary=False, + vol_subname='norm', # subname of volume + seg_subname='aseg', # subname of segmentation + **kwargs): + """ + generator with (volume, segmentation) + + verbose is passed down to the base generators.py primitive generator (e.g. vol, here) + + ** kwargs are any named arguments for vol(...), + except verbose, data_proc_fn, ext, nb_labels_reshape and name + (which this function will control when calling vol()) + """ + + # get vol generator + vol_gen = vol(volpath, **kwargs, ext=ext, + nb_restart_cycle=nb_restart_cycle, collapse_2d=collapse_2d, force_binary=False, + relabel=None, data_proc_fn=proc_vol_fn, nb_labels_reshape=1, name=name+' vol', + verbose=verbose, nb_feats=nb_input_feats, vol_rand_seed=vol_rand_seed) + + # get seg generator, matching nb_files + # vol_files = [f.replace('norm', 'aseg') for f in _get_file_list(volpath, ext)] + # vol_files = [f.replace('orig', 'aseg') for f in vol_files] + vol_files = [f.replace(vol_subname, seg_subname) for f in _get_file_list(volpath, ext, vol_rand_seed)] + seg_gen = vol(segpath, **kwargs, ext=ext, nb_restart_cycle=nb_restart_cycle, collapse_2d=collapse_2d, + force_binary=force_binary, relabel=relabel, vol_rand_seed=vol_rand_seed, + data_proc_fn=proc_seg_fn, nb_labels_reshape=nb_labels_reshape, keep_vol_size=True, + expected_files=vol_files, name=name+' seg', binary=seg_binary, verbose=False) + + # on next (while): + while 1: + # get input and output (seg) vols + input_vol = next(vol_gen).astype('float16') + output_vol = next(seg_gen).astype('float16') # was int8. Why? need float possibility... + + # output input and output + yield (input_vol, output_vol) + + +# def seg_seg(volpath, +# segpath, +# crop=None, resize_shape=None, rescale=None, # processing parameters +# verbose=False, +# name='seg_seg', # name, optional +# ext='.npz', +# nb_restart_cycle=None, # number of files to restart after +# nb_labels_reshape=-1, +# collapse_2d=None, +# force_binary=False, +# nb_input_feats=1, +# **kwargs): +# """ +# generator with (volume, segmentation) + +# verbose is passed down to the base generators.py primitive generator (e.g. vol, here) + +# ** kwargs are any named arguments for vol(...), +# except verbose, data_proc_fn, ext, nb_labels_reshape and name +# (which this function will control when calling vol()) +# """ + +# # compute processing function +# proc_seg_fn = lambda x: nrn_proc.vol_proc(x, crop=crop, resize_shape=resize_shape, +# interp_order=0, rescale=rescale) + +# # get seg generator, matching nb_files +# seg_gen = vol(segpath, **kwargs, ext=ext, nb_restart_cycle=nb_restart_cycle, +# force_binary=force_binary, collapse_2d = collapse_2d, +# data_proc_fn=proc_seg_fn, nb_labels_reshape=nb_labels_reshape, keep_vol_size=True, +# name=name+' seg', verbose=False) + +# # on next (while): +# while 1: +# # get input and output (seg) vols +# input_vol = next(seg_gen).astype('float16') +# vol_size = np.prod(input_vol.shape[1:-1]) # get the volume size +# output_vol = np.reshape(input_vol, (input_vol.shape[0], vol_size, input_vol.shape[-1])) + +# # output input and output +# yield (input_vol, output_vol) + + + +def vol_cat(volpaths, # expect two folders in here + crop=None, resize_shape=None, rescale=None, # processing parameters + verbose=False, + name='vol_cat', # name, optional + ext='.npz', + nb_labels_reshape=-1, + vol_rand_seed=None, + **kwargs): # named arguments for vol(...), except verbose, data_proc_fn, ext, nb_labels_reshape and name (which this function will control when calling vol()) + """ + generator with (volume, binary_bit) (random order) + ONLY works with abtch size of 1 for now + + verbose is passed down to the base generators.py primitive generator (e.g. vol, here) + """ + + folders = [f for f in sorted(os.listdir(volpaths))] + + # compute processing function + proc_vol_fn = lambda x: nrn_proc.vol_proc(x, crop=crop, resize_shape=resize_shape, + interp_order=2, rescale=rescale) + + # get vol generators + generators = () + generators_len = () + for folder in folders: + vol_gen = vol(os.path.join(volpaths, folder), **kwargs, ext=ext, vol_rand_seed=vol_rand_seed, + data_proc_fn=proc_vol_fn, nb_labels_reshape=1, name=folder, verbose=False) + generators_len += (len(_get_file_list(os.path.join(volpaths, folder), '.npz')), ) + generators += (vol_gen, ) + + bake_data_test = False + if bake_data_test: + print('fake_data_test', file=sys.stderr) + + # on next (while): + while 1: + # build the random order stack + order = np.hstack((np.zeros(generators_len[0]), np.ones(generators_len[1]))).astype('int') + np.random.shuffle(order) # shuffle + for idx in order: + gen = generators[idx] + + # for idx, gen in enumerate(generators): + z = np.zeros([1, 2]) #1,1,2 for categorical binary style + z[0,idx] = 1 # + # z[0,0,0] = idx + + data = next(gen).astype('float32') + if bake_data_test and idx == 0: + # data = data*idx + data = -data + + yield (data, z) + + + +def add_prior(gen, + proc_vol_fn=None, + proc_seg_fn=None, + prior_type='location', # file-static, file-gen, location + prior_file=None, # prior filename + prior_feed='input', # input or output + patch_stride=1, + patch_size=None, + batch_size=1, + collapse_2d=None, + extract_slice=None, + force_binary=False, + verbose=False, + patch_rand=False, + patch_rand_seed=None): + """ + # + # add a prior generator to a given generator + # with the number of patches in batch matching output of gen + """ + + # get prior + if prior_type == 'location': + prior_vol = nd.volsize2ndgrid(vol_size) + prior_vol = np.transpose(prior_vol, [1, 2, 3, 0]) + prior_vol = np.expand_dims(prior_vol, axis=0) # reshape for model + + elif prior_type == 'file': # assumes a npz filename passed in prior_file + with timer.Timer('loading prior', True): + data = np.load(prior_file) + prior_vol = data['prior'].astype('float16') + + else: # assumes a volume + with timer.Timer('loading prior', True): + prior_vol = prior_file.astype('float16') + + if force_binary: + nb_labels = prior_vol.shape[-1] + prior_vol[:, :, :, 1] = np.sum(prior_vol[:, :, :, 1:nb_labels], 3) + prior_vol = np.delete(prior_vol, range(2, nb_labels), 3) + + nb_channels = prior_vol.shape[-1] + + if extract_slice is not None: + if isinstance(extract_slice, int): + prior_vol = prior_vol[:, :, extract_slice, np.newaxis, :] + else: # assume slices + prior_vol = prior_vol[:, :, extract_slice, :] + + # get the prior to have the right volume [x, y, z, nb_channels] + assert np.ndim(prior_vol) == 4 or np.ndim(prior_vol) == 3, "prior is the wrong size" + + # prior generator + if patch_size is None: + patch_size = prior_vol.shape[0:3] + assert len(patch_size) == len(patch_stride) + prior_gen = patch(prior_vol, [*patch_size, nb_channels], + patch_stride=[*patch_stride, nb_channels], + batch_size=batch_size, + collapse_2d=collapse_2d, + keep_vol_size=True, + infinite=True, + patch_rand=patch_rand, + patch_rand_seed=patch_rand_seed, + variable_batch_size=True, + nb_labels_reshape=0) + assert next(prior_gen) is None, "bad prior gen setup" + + # generator loop + while 1: + + # generate input and output volumes + gen_sample = next(gen) + + # generate prior batch + gs_sample = _get_shape(gen_sample) + prior_batch = prior_gen.send(gs_sample) + + yield (gen_sample, prior_batch) + + +def vol_prior(*args, + proc_vol_fn=None, + proc_seg_fn=None, + prior_type='location', # file-static, file-gen, location + prior_file=None, # prior filename + prior_feed='input', # input or output + patch_stride=1, + patch_size=None, + batch_size=1, + collapse_2d=None, + extract_slice=None, + force_binary=False, + nb_input_feats=1, + verbose=False, + vol_rand_seed=None, + patch_rand=False, + **kwargs): # anything else you'd like to pass to vol() + """ + generator that appends prior to (volume, segmentation) depending on input + e.g. could be ((volume, prior), segmentation) + """ + + patch_rand_seed = None + if patch_rand: + patch_rand_seed = np.random.random() + + + # prepare the vol_seg + vol_gen = vol(*args, + **kwargs, + collapse_2d=collapse_2d, + force_binary=False, + verbose=verbose, + vol_rand_seed=vol_rand_seed) + gen = vol(*args, **kwargs, + proc_vol_fn=None, + proc_seg_fn=None, + collapse_2d=collapse_2d, + extract_slice=extract_slice, + force_binary=force_binary, + verbose=verbose, + patch_size=patch_size, + patch_stride=patch_stride, + batch_size=batch_size, + vol_rand_seed=vol_rand_seed, + patch_rand=patch_rand, + patch_rand_seed=patch_rand_seed, + nb_input_feats=nb_input_feats) + + # add prior to output + pgen = add_prior(gen, + proc_vol_fn=proc_vol_fn, + proc_seg_fn=proc_seg_fn, + prior_type=prior_type, + prior_file=prior_file, + prior_feed=prior_feed, + patch_stride=patch_stride, + patch_size=patch_size, + batch_size=batch_size, + collapse_2d=collapse_2d, + extract_slice=extract_slice, + force_binary=force_binary, + verbose=verbose, + patch_rand=patch_rand, + patch_rand_seed=patch_rand_seed, + vol_rand_seed=vol_rand_seed) + + # generator loop + while 1: + + gen_sample, prior_batch = next(pgen) + input_vol, output_vol = gen_sample + + if prior_feed == 'input': + yield ([input_vol, prior_batch], output_vol) + else: + assert prior_feed == 'output' + yield (input_vol, [output_vol, prior_batch]) + + +def vol_seg_prior(*args, + proc_vol_fn=None, + proc_seg_fn=None, + prior_type='location', # file-static, file-gen, location + prior_file=None, # prior filename + prior_feed='input', # input or output + patch_stride=1, + patch_size=None, + batch_size=1, + collapse_2d=None, + extract_slice=None, + force_binary=False, + nb_input_feats=1, + verbose=False, + vol_rand_seed=None, + patch_rand=None, + **kwargs): + """ + generator that appends prior to (volume, segmentation) depending on input + e.g. could be ((volume, prior), segmentation) + """ + + + patch_rand_seed = None + if patch_rand: + patch_rand_seed = np.random.random() + + # prepare the vol_seg + gen = vol_seg(*args, **kwargs, + proc_vol_fn=None, + proc_seg_fn=None, + collapse_2d=collapse_2d, + extract_slice=extract_slice, + force_binary=force_binary, + verbose=verbose, + patch_size=patch_size, + patch_stride=patch_stride, + batch_size=batch_size, + vol_rand_seed=vol_rand_seed, + patch_rand=patch_rand, + patch_rand_seed=patch_rand_seed, + nb_input_feats=nb_input_feats) + + # add prior to output + pgen = add_prior(gen, + proc_vol_fn=proc_vol_fn, + proc_seg_fn=proc_seg_fn, + prior_type=prior_type, + prior_file=prior_file, + prior_feed=prior_feed, + patch_stride=patch_stride, + patch_size=patch_size, + batch_size=batch_size, + collapse_2d=collapse_2d, + extract_slice=extract_slice, + force_binary=force_binary, + verbose=verbose, + patch_rand=patch_rand, + patch_rand_seed=patch_rand_seed) + + # generator loop + while 1: + + gen_sample, prior_batch = next(pgen) + input_vol, output_vol = gen_sample + + if prior_feed == 'input': + yield ([input_vol, prior_batch], output_vol) + else: + assert prior_feed == 'output' + yield (input_vol, [output_vol, prior_batch]) + + + +def vol_prior_hack(*args, + proc_vol_fn=None, + proc_seg_fn=None, + prior_type='location', # file-static, file-gen, location + prior_file=None, # prior filename + prior_feed='input', # input or output + patch_stride=1, + patch_size=None, + batch_size=1, + collapse_2d=None, + extract_slice=None, + force_binary=False, + nb_input_feats=1, + verbose=False, + vol_rand_seed=None, + **kwargs): + """ + + """ + # prepare the vol_seg + gen = vol_seg_hack(*args, **kwargs, + proc_vol_fn=None, + proc_seg_fn=None, + collapse_2d=collapse_2d, + extract_slice=extract_slice, + force_binary=force_binary, + verbose=verbose, + patch_size=patch_size, + patch_stride=patch_stride, + batch_size=batch_size, + vol_rand_seed=vol_rand_seed, + nb_input_feats=nb_input_feats) + + # get prior + if prior_type == 'location': + prior_vol = nd.volsize2ndgrid(vol_size) + prior_vol = np.transpose(prior_vol, [1, 2, 3, 0]) + prior_vol = np.expand_dims(prior_vol, axis=0) # reshape for model + + elif prior_type == 'file': # assumes a npz filename passed in prior_file + with timer.Timer('loading prior', True): + data = np.load(prior_file) + prior_vol = data['prior'].astype('float16') + else : # assumes a volume + with timer.Timer('astyping prior', verbose): + prior_vol = prior_file + if not (prior_vol.dtype == 'float16'): + prior_vol = prior_vol.astype('float16') + + if force_binary: + nb_labels = prior_vol.shape[-1] + prior_vol[:, :, :, 1] = np.sum(prior_vol[:, :, :, 1:nb_labels], 3) + prior_vol = np.delete(prior_vol, range(2, nb_labels), 3) + + nb_channels = prior_vol.shape[-1] + + if extract_slice is not None: + if isinstance(extract_slice, int): + prior_vol = prior_vol[:, :, extract_slice, np.newaxis, :] + else: # assume slices + prior_vol = prior_vol[:, :, extract_slice, :] + + # get the prior to have the right volume [x, y, z, nb_channels] + assert np.ndim(prior_vol) == 4 or np.ndim(prior_vol) == 3, "prior is the wrong size" + + # prior generator + if patch_size is None: + patch_size = prior_vol.shape[0:3] + assert len(patch_size) == len(patch_stride) + prior_gen = patch(prior_vol, [*patch_size, nb_channels], + patch_stride=[*patch_stride, nb_channels], + batch_size=batch_size, + collapse_2d=collapse_2d, + keep_vol_size=True, + infinite=True, + #variable_batch_size=True, # this + nb_labels_reshape=0) + # assert next(prior_gen) is None, "bad prior gen setup" + + # generator loop + while 1: + + # generate input and output volumes + input_vol = next(gen) + + if verbose and np.all(input_vol.flat == 0): + print("all entries are 0") + + # generate prior batch + # with timer.Timer("with send?"): + # prior_batch = prior_gen.send(input_vol.shape[0]) + prior_batch = next(prior_gen) + + if prior_feed == 'input': + yield ([input_vol, prior_batch], input_vol) + else: + assert prior_feed == 'output' + yield (input_vol, [input_vol, prior_batch]) + + +def vol_seg_hack(volpath, + segpath, + proc_vol_fn=None, + proc_seg_fn=None, + verbose=False, + name='vol_seg', # name, optional + ext='.npz', + nb_restart_cycle=None, # number of files to restart after + nb_labels_reshape=-1, + collapse_2d=None, + force_binary=False, + nb_input_feats=1, + relabel=None, + vol_rand_seed=None, + seg_binary=False, + vol_subname='norm', # subname of volume + seg_subname='aseg', # subname of segmentation + **kwargs): + """ + generator with (volume, segmentation) + + verbose is passed down to the base generators.py primitive generator (e.g. vol, here) + + ** kwargs are any named arguments for vol(...), + except verbose, data_proc_fn, ext, nb_labels_reshape and name + (which this function will control when calling vol()) + """ + + # get vol generator + vol_gen = vol(volpath, **kwargs, ext=ext, + nb_restart_cycle=nb_restart_cycle, collapse_2d=collapse_2d, force_binary=False, + relabel=None, data_proc_fn=proc_vol_fn, nb_labels_reshape=1, name=name+' vol', + verbose=verbose, nb_feats=nb_input_feats, vol_rand_seed=vol_rand_seed) + + + # on next (while): + while 1: + # get input and output (seg) vols + input_vol = next(vol_gen).astype('float16') + + # output input and output + yield input_vol + +def vol_sr_slices(volpath, + nb_input_slices, + nb_slice_spacing, + batch_size=1, + ext='.npz', + vol_rand_seed=None, + nb_restart_cycle=None, + name='vol_sr_slices', + rand_slices=True, # randomize init slice order (i.e. across entries per batch) given a volume + simulate_whole_sparse_vol=False, + verbose=False + ): + """ + default generator for slice-wise super resolution + """ + + def indices_to_batch(vol_data, start_indices, nb_slices_in_subvol, nb_slice_spacing): + idx = start_indices[0] + output_batch = np.expand_dims(vol_data[:,:,idx:idx+nb_slices_in_subvol], 0) + input_batch = np.expand_dims(vol_data[:,:,idx:(idx+nb_slices_in_subvol):(nb_slice_spacing+1)], 0) + + for idx in start_indices[1:]: + out_sel = np.expand_dims(vol_data[:,:,idx:idx+nb_slices_in_subvol], 0) + output_batch = np.vstack([output_batch, out_sel]) + input_batch = np.vstack([input_batch, np.expand_dims(vol_data[:,:,idx:(idx+nb_slices_in_subvol):(nb_slice_spacing+1)], 0)]) + output_batch = np.reshape(output_batch, [batch_size, -1, output_batch.shape[-1]]) + + return (input_batch, output_batch) + + + print('vol_sr_slices: SHOULD PROPERLY RANDOMIZE accross different subjects', file=sys.stderr) + + volfiles = _get_file_list(volpath, ext, vol_rand_seed) + nb_files = len(volfiles) + + if nb_restart_cycle is None: + nb_restart_cycle = nb_files + + # compute the number of slices we'll need in a subvolume + nb_slices_in_subvol = (nb_input_slices - 1) * (nb_slice_spacing + 1) + 1 + + # iterate through files + fileidx = -1 + while 1: + fileidx = np.mod(fileidx + 1, nb_restart_cycle) + if verbose and fileidx == 0: + print('starting %s cycle' % name) + + + try: + vol_data = _load_medical_volume(os.path.join(volpath, volfiles[fileidx]), ext, verbose) + except: + debug_error_msg = "#files: %d, fileidx: %d, nb_restart_cycle: %d. error: %s" + print(debug_error_msg % (len(volfiles), fileidx, nb_restart_cycle, sys.exc_info()[0])) + raise + + # compute some random slice + nb_slices = vol_data.shape[2] + nb_start_slices = nb_slices - nb_slices_in_subvol + 1 + + # prepare batches + if simulate_whole_sparse_vol: # if essentially simulate a whole sparse volume for consistent inputs, and yield slices like that: + init_slice = 0 + if rand_slices: + init_slice = np.random.randint(0, high=nb_start_slices-1) + + all_start_indices = list(range(init_slice, nb_start_slices, nb_slice_spacing+1)) + + for batch_start in range(0, len(all_start_indices), batch_size*(nb_input_slices-1)): + start_indices = [all_start_indices[s] for s in range(batch_start, batch_start + batch_size)] + input_batch, output_batch = indices_to_batch(vol_data, start_indices, nb_slices_in_subvol, nb_slice_spacing) + yield (input_batch, output_batch) + + # if just random slices, get a batch of random starts from this volume and that's it. + elif rand_slices: + assert not simulate_whole_sparse_vol + start_indices = np.random.choice(range(nb_start_slices), size=batch_size, replace=False) + input_batch, output_batch = indices_to_batch(vol_data, start_indices, nb_slices_in_subvol, nb_slice_spacing) + yield (input_batch, output_batch) + + # go slice by slice (overlapping regions) + else: + for batch_start in range(0, nb_start_slices, batch_size): + start_indices = list(range(batch_start, batch_start + batch_size)) + input_batch, output_batch = indices_to_batch(vol_data, start_indices, nb_slices_in_subvol, nb_slice_spacing) + yield (input_batch, output_batch) + + + + + + + + + + + + + +def ext_data(segpath, + volpath, + batch_size, + expected_files=None, + ext='.npy', + nb_restart_cycle=None, + data_proc_fn=None, # processing function that takes in one arg (the volume) + vol_rand_seed=None, + name='ext_data', + verbose=False, + yield_incomplete_final_batch=True, + patch_stride=1, + extract_slice=None, + patch_size=None, + ext_data_fields=None, + expected_nb_files = -1): + assert ext_data_fields is not None, "Need some external data fields" + + # get filenames at given paths + volfiles = _get_file_list(segpath, ext, vol_rand_seed) + nb_files = len(volfiles) + assert nb_files > 0, "Could not find any files at %s with extension %s" % (segpath, ext) + + # compute subvolume split + vol_data = _load_medical_volume(os.path.join(volpath, expected_files[0]), '.npz') + # process volume + if data_proc_fn is not None: + vol_data = data_proc_fn(vol_data) + + # nb_patches_per_vol = 1 + # if patch_size is not None and all(f is not None for f in patch_size): + # nb_patches_per_vol = np.prod(pl.gridsize(vol_data.shape, patch_size, patch_stride)) + if nb_restart_cycle is None: + nb_restart_cycle = nb_files + + # assert nb_restart_cycle <= (nb_files * nb_patches_per_vol), \ + # '%s restart cycle (%s) too big (%s) in %s' % \ + # (name, nb_restart_cycle, nb_files * nb_patches_per_vol, volpath) + + # check the number of files matches expected (if passed) + if expected_nb_files >= 0: + assert nb_files == expected_nb_files, \ + "number of files do not match: %d, %d" % (nb_files, expected_nb_files) + if expected_files is not None: + if not (volfiles == expected_files): + print('file lists did not match !!!', file=sys.stderr) + + # iterate through files + fileidx = -1 + batch_idx = -1 + feat_idx = 0 + while 1: + fileidx = np.mod(fileidx + 1, nb_restart_cycle) + if verbose and fileidx == 0: + print('starting %s cycle' % name) + + this_ext_data = np.load(os.path.join(segpath, volfiles[fileidx])) + # print(os.path.join(segpath, volfiles[fileidx]), " was loaded") + + for _ in range(nb_patches_per_vol): + if batch_idx == -1: + ext_data_batch = [this_ext_data[f] for f in ext_data_fields] + else: + tmp_data = [this_ext_data[f] for f in ext_data_fields] + ext_data_batch = [[*ext_data_batch[f], this_ext_data[f]] for f in range(len(tmp_data))] + + # yield patch + batch_idx += 1 + batch_done = batch_idx == batch_size - 1 + files_done = np.mod(fileidx + 1, nb_restart_cycle) == 0 + final_batch = (yield_incomplete_final_batch and files_done) + if verbose and final_batch: + print('last batch in %s cycle %d' % (name, fileidx)) + + if batch_done or final_batch: + for fi,f in enumerate(ext_data_batch): + ext_data_batch[fi] = np.array(f) + + batch_idx = -1 + yield ext_data_batch + + + +def vol_ext_data(volpath, + segpath, + proc_vol_fn=None, + proc_seg_fn=None, + verbose=False, + name='vol_seg', # name, optional + ext='.npz', + nb_restart_cycle=None, # number of files to restart after + nb_labels_reshape=-1, + collapse_2d=None, + force_binary=False, + nb_input_feats=1, + relabel=None, + vol_rand_seed=None, + vol_subname='norm', # subname of volume + seg_subname='norm', # subname of segmentation + **kwargs): + """ + generator with (volume, segmentation) + + verbose is passed down to the base generators.py primitive generator (e.g. vol, here) + + ** kwargs are any named arguments for vol(...), + except verbose, data_proc_fn, ext, nb_labels_reshape and name + (which this function will control when calling vol()) + """ + + # get vol generator + vol_gen = vol(volpath, **kwargs, ext=ext, + nb_restart_cycle=nb_restart_cycle, collapse_2d=collapse_2d, force_binary=False, + relabel=None, data_proc_fn=proc_vol_fn, nb_labels_reshape=1, name=name+' vol', + verbose=verbose, nb_feats=nb_input_feats, vol_rand_seed=vol_rand_seed) + + # get seg generator, matching nb_files + # vol_files = [f.replace('norm', 'aseg') for f in _get_file_list(volpath, ext)] + # vol_files = [f.replace('orig', 'aseg') for f in vol_files] + vol_files = [f.replace(vol_subname, seg_subname) for f in _get_file_list(volpath, ext, vol_rand_seed)] + seg_gen = ext_data(segpath, + volpath, + **kwargs, + data_proc_fn=proc_seg_fn, + ext='.npy', + nb_restart_cycle=nb_restart_cycle, + vol_rand_seed=vol_rand_seed, + expected_files=vol_files, + name=name+' ext_data', + verbose=False) + + # on next (while): + while 1: + # get input and output (seg) vols + input_vol = next(vol_gen).astype('float16') + output_vol = next(seg_gen) #.astype('float16') + + # output input and output + yield (input_vol, output_vol) + + + + +def vol_ext_data_prior(*args, + proc_vol_fn=None, + proc_seg_fn=None, + prior_type='location', # file-static, file-gen, location + prior_file=None, # prior filename + prior_feed='input', # input or output + patch_stride=1, + patch_size=None, + batch_size=1, + collapse_2d=None, + extract_slice=None, + force_binary=False, + nb_input_feats=1, + verbose=False, + vol_rand_seed=None, + **kwargs): + """ + generator that appends prior to (volume, segmentation) depending on input + e.g. could be ((volume, prior), segmentation) + """ + + if verbose: + print('starting vol_seg_prior') + + # prepare the vol_seg + gen = vol_ext_data(*args, **kwargs, + proc_vol_fn=None, + proc_seg_fn=None, + collapse_2d=collapse_2d, + extract_slice=extract_slice, + force_binary=force_binary, + verbose=verbose, + patch_size=patch_size, + patch_stride=patch_stride, + batch_size=batch_size, + vol_rand_seed=vol_rand_seed, + nb_input_feats=nb_input_feats) + + # get prior + if prior_type == 'location': + prior_vol = nd.volsize2ndgrid(vol_size) + prior_vol = np.transpose(prior_vol, [1, 2, 3, 0]) + prior_vol = np.expand_dims(prior_vol, axis=0) # reshape for model + + else: # assumes a npz filename passed in prior_file + with timer.Timer('loading prior', verbose): + data = np.load(prior_file) + prior_vol = data['prior'].astype('float16') + + if force_binary: + nb_labels = prior_vol.shape[-1] + prior_vol[:, :, :, 1] = np.sum(prior_vol[:, :, :, 1:nb_labels], 3) + prior_vol = np.delete(prior_vol, range(2, nb_labels), 3) + + nb_channels = prior_vol.shape[-1] + + if extract_slice is not None: + if isinstance(extract_slice, int): + prior_vol = prior_vol[:, :, extract_slice, np.newaxis, :] + else: # assume slices + prior_vol = prior_vol[:, :, extract_slice, :] + + # get the prior to have the right volume [x, y, z, nb_channels] + assert np.ndim(prior_vol) == 4, "prior is the wrong size" + + # prior generator + if patch_size is None: + patch_size = prior_vol.shape[0:3] + assert len(patch_size) == len(patch_stride) + prior_gen = patch(prior_vol, [*patch_size, nb_channels], + patch_stride=[*patch_stride, nb_channels], + batch_size=batch_size, + collapse_2d=collapse_2d, + infinite=True, + variable_batch_size=True, + nb_labels_reshape=0) + assert next(prior_gen) is None, "bad prior gen setup" + + # generator loop + while 1: + + # generate input and output volumes + input_vol, output_vol = next(gen) + if verbose and np.all(input_vol.flat == 0): + print("all entries are 0") + + # generate prior batch + prior_batch = prior_gen.send(input_vol.shape[0]) + + if prior_feed == 'input': + yield ([input_vol, prior_batch], output_vol) + else: + assert prior_feed == 'output' + yield (input_vol, [output_vol, prior_batch]) + + +def max_patch_in_vol_cat(volpaths, + patch_size, + patch_stride, + model, + tst_model, + out_layer, + name='max_patch_in_vol_cat', # name, optional + **kwargs): # named arguments for vol_cat(...) + """ + given a model by reference + goes to the next volume and, given a patch_size, finds + the highest prediction of out_layer_name, and yields that patch index to the model + + TODO: this doesn't work if called while training, something about running through the graph + perhaps need to do a different tf session? + """ + + # strategy: call vol_cat on full volume + vol_cat_gen = vol_cat(volpaths, **kwargs) + + # need to make a deep copy: + #model_tmp = Model(inputs=model.inputs, outputs=model.get_layer(out_layer).output) + # nrn_models.copy_weights(model_tmp, tst_model) + #nrn_models.copy_weights(tst_model, tst_model) + #asdasd + tst_model = model + + while 1: + # get next volume + try: + sample = next(vol_cat_gen) + except: + print("Failed loading file. Skipping", file=sys.stderr) + continue + sample_vol = np.squeeze(sample[0]) + patch_gen = patch(sample_vol, patch_size, patch_stride=patch_stride) + + # go through its patches + max_resp = -np.infty + max_idx = np.nan + max_out = None + for idx, ptc in enumerate(patch_gen): + # predict + res = np.squeeze(tst_model.predict(ptc))[1] + if res > max_resp: + max_ptc = ptc + max_out = sample[1] + max_idx = idx + + # yield the right patch + yield (max_ptc, max_out) + + +def img_seg(volpath, + segpath, + batch_size=1, + verbose=False, + nb_restart_cycle=None, + name='img_seg', # name, optional + ext='.png', + vol_rand_seed=None, + **kwargs): + """ + generator for (image, segmentation) + """ + + def imggen(path, ext, nb_restart_cycle=None): + """ + TODO: should really use the volume generators for this + """ + files = _get_file_list(path, ext, vol_rand_seed) + if nb_restart_cycle is None: + nb_restart_cycle = len(files) + + idx = -1 + while 1: + idx = np.mod(idx+1, nb_restart_cycle) + im = scipy.misc.imread(os.path.join(path, files[idx]))[:, :, 0] + yield im.reshape((1,) + im.shape) + + img_gen = imggen(volpath, ext, nb_restart_cycle) + seg_gen = imggen(segpath, ext) + + # on next (while): + while 1: + input_vol = np.vstack([next(img_gen).astype('float16')/255 for i in range(batch_size)]) + input_vol = np.expand_dims(input_vol, axis=-1) + + output_vols = [np_utils.to_categorical(next(seg_gen).astype('int8'), num_classes=2) for i in range(batch_size)] + output_vol = np.vstack([np.expand_dims(f, axis=0) for f in output_vols]) + + # output input and output + yield (input_vol, output_vol) + + +# Some internal use functions + +def _get_file_list(volpath, ext=None, vol_rand_seed=None): + """ + get a list of files at the given path with the given extension + """ + files = [f for f in sorted(os.listdir(volpath)) if ext is None or f.endswith(ext)] + if vol_rand_seed is not None: + np.random.seed(vol_rand_seed) + files = np.random.permutation(files).tolist() + return files + + +def _load_medical_volume(filename, ext, verbose=False): + """ + load a medical volume from one of a number of file types + """ + with timer.Timer('load_vol', verbose >= 2): + if ext == '.npz': + vol_file = np.load(filename) + vol_data = vol_file['vol_data'] + elif ext == 'npy': + vol_data = np.load(filename) + elif ext == '.mgz' or ext == '.nii' or ext == '.nii.gz': + vol_med = nib.load(filename) + vol_data = vol_med.get_data() + else: + raise ValueError("Unexpected extension %s" % ext) + + return vol_data + + +def _categorical_prep(vol_data, nb_labels_reshape, keep_vol_size, patch_size): + + if nb_labels_reshape > 1: + + lpatch = _to_categorical(vol_data, nb_labels_reshape, keep_vol_size) + # if keep_vol_size: + # lpatch = np.reshape(lpatch, [*patch_size, nb_labels_reshape]) + elif nb_labels_reshape == 1: + lpatch = np.expand_dims(vol_data, axis=-1) + else: + assert nb_labels_reshape == 0 + lpatch = vol_data + lpatch = np.expand_dims(lpatch, axis=0) + + return lpatch + + + +def _to_categorical(y, num_classes=None, reshape=True): + """ + # copy of keras.utils.np_utils.to_categorical, but with a boolean matrix instead of float + + Converts a class vector (integers) to binary class matrix. + + E.g. for use with categorical_crossentropy. + + # Arguments + y: class vector to be converted into a matrix + (integers from 0 to num_classes). + num_classes: total number of classes. + + # Returns + A binary matrix representation of the input. + """ + oshape = y.shape + y = np.array(y, dtype='int').ravel() + if not num_classes: + num_classes = np.max(y) + 1 + n = y.shape[0] + categorical = np.zeros((n, num_classes), bool) + categorical[np.arange(n), y] = 1 + + if reshape: + categorical = np.reshape(categorical, [*oshape, num_classes]) + + return categorical + +def _relabel(vol_data, labels, forcecheck=False): + + if forcecheck: + vd = np.unique(vol_data.flat) + assert len(vd) == len(labels), "number of given labels does not match number of actual labels" + + # by doing zeros, any label not in labels gets left to 0 + new_vol_data = np.zeros(vol_data.shape, vol_data.dtype) + for idx, val in np.ndenumerate(labels): + new_vol_data[vol_data == val] = idx + + return new_vol_data + + + + +import zipfile + +def _npz_headers(npz, namelist=None): + """ + taken from https://stackoverflow.com/a/43223420 + + Takes a path to an .npz file, which is a Zip archive of .npy files. + Generates a sequence of (name, shape, np.dtype). + + namelist is a list with variable names, ending in '.npy'. + e.g. if variable 'var' is in the file, namelist could be ['var.npy'] + """ + with zipfile.ZipFile(npz) as archive: + if namelist is None: + namelist = archive.namelist() + + for name in namelist: + if not name.endswith('.npy'): + continue + + npy = archive.open(name) + version = np.lib.format.read_magic(npy) + shape, fortran, dtype = np.lib.format._read_array_header(npy, version) + yield name[:-4], shape, dtype + +def _get_shape(x): + if isinstance(x, (list, tuple)): + return _get_shape(x[0]) + else: + return x.shape[0] diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/inits.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/inits.py new file mode 100644 index 0000000000000000000000000000000000000000..fa7d4b4f9ea34632d7fe8d61d966901319540aaa --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/inits.py @@ -0,0 +1,47 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' initializations for the neuron project ''' + +# general imports +import os +import numpy as np +import tensorflow.python.keras.backend as K + + +def output_init(shape, name=None, dim_ordering=None): + ''' initialization for output weights''' + size = (shape[0], shape[1], shape[2] - shape[3], shape[3]) + + # initialize output weights with random and identity + rpart = np.random.random(size) +# idpart_ = np.eye(size[3]) + idpart_ = np.ones((size[3], size[3])) + idpart = np.expand_dims(np.expand_dims(idpart_, 0), 0) + value = np.concatenate((rpart, idpart), axis=2) + return K.variable(value, name=name) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/layers.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/layers.py new file mode 100644 index 0000000000000000000000000000000000000000..eacecbbfe68c39ba8d8504b75729dc4ff50182db --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/layers.py @@ -0,0 +1,887 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +tensorflow/keras utilities for the neuron project + +If you use this code, please cite +Dalca AV, Guttag J, Sabuncu MR +Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation, +CVPR 2018 + +or for the transformation/integration functions: + +Unsupervised Learning for Fast Probabilistic Diffeomorphic Registration +Adrian V. Dalca, Guha Balakrishnan, John Guttag, Mert R. Sabuncu +MICCAI 2018. + +Contact: adalca [at] csail [dot] mit [dot] edu +License: GPLv3 +""" + +# third party +import numpy as np +from tensorflow.python.keras import backend as K +# from keras.legacy import interfaces +import tensorflow.python.keras as keras +from tensorflow.python.keras.layers import Layer, InputLayer, Input +import tensorflow as tf +# from keras.engine.topology import Node +from tensorflow.python.keras.engine import node + +# local +from .utils import transform, resize, integrate_vec, affine_to_shift + + +class SpatialTransformer(Layer): + """ + N-D Spatial Transformer Tensorflow / Keras Layer + + The Layer can handle both affine and dense transforms. + Both transforms are meant to give a 'shift' from the current position. + Therefore, a dense transform gives displacements (not absolute locations) at each voxel, + and an affine transform gives the *difference* of the affine matrix from + the identity matrix. + + If you find this function useful, please cite: + Unsupervised Learning for Fast Probabilistic Diffeomorphic Registration + Adrian V. Dalca, Guha Balakrishnan, John Guttag, Mert R. Sabuncu + MICCAI 2018. + + Originally, this code was based on voxelmorph code, which + was in turn transformed to be dense with the help of (affine) STN code + via https://github.com/kevinzakka/spatial-transformer-network + + Since then, we've re-written the code to be generalized to any + dimensions, and along the way wrote grid and interpolation functions + """ + + def __init__(self, + interp_method='linear', + indexing='ij', + single_transform=False, + **kwargs): + """ + Parameters: + interp_method: 'linear' or 'nearest' + single_transform: whether a single transform supplied for the whole batch + indexing (default: 'ij'): 'ij' (matrix) or 'xy' (cartesian) + 'xy' indexing will have the first two entries of the flow + (along last axis) flipped compared to 'ij' indexing + """ + self.interp_method = interp_method + self.ndims = None + self.inshape = None + self.single_transform = single_transform + + assert indexing in ['ij', 'xy'], "indexing has to be 'ij' (matrix) or 'xy' (cartesian)" + self.indexing = indexing + + super(self.__class__, self).__init__(**kwargs) + + + def build(self, input_shape): + """ + input_shape should be a list for two inputs: + input1: image. + input2: transform Tensor + if affine: + should be a N x N+1 matrix + *or* a N*N+1 tensor (which will be reshape to N x (N+1) and an identity row added) + if not affine: + should be a *vol_shape x N + """ + + if len(input_shape) > 2: + raise Exception('Spatial Transformer must be called on a list of length 2.' + 'First argument is the image, second is the transform.') + + # set up number of dimensions + self.ndims = len(input_shape[0]) - 2 + self.inshape = input_shape + vol_shape = input_shape[0][1:-1] + trf_shape = input_shape[1][1:] + + # the transform is an affine iff: + # it's a 1D Tensor [dense transforms need to be at least ndims + 1] + # it's a 2D Tensor and shape == [N+1, N+1]. + # [dense with N=1, which is the only one that could have a transform shape of 2, would be of size Mx1] + self.is_affine = len(trf_shape) == 1 or \ + (len(trf_shape) == 2 and all([trf_shape[0] == self.ndims, trf_shape[1] == self.ndims+1])) + + # check sizes + if self.is_affine and len(trf_shape) == 1: + ex = self.ndims * (self.ndims + 1) + if trf_shape[0] != ex: + raise Exception('Expected flattened affine of len %d but got %d' + % (ex, trf_shape[0])) + + if not self.is_affine: + if trf_shape[-1] != self.ndims: + raise Exception('Offset flow field size expected: %d, found: %d' + % (self.ndims, trf_shape[-1])) + + # confirm built + self.built = True + + def call(self, inputs): + """ + Parameters + inputs: list with two entries + """ + + # check shapes + assert len(inputs) == 2, "inputs has to be len 2, found: %d" % len(inputs) + vol = inputs[0] + trf = inputs[1] + + # necessary for multi_gpu models... + vol = K.reshape(vol, [-1, *self.inshape[0][1:]]) + trf = K.reshape(trf, [-1, *self.inshape[1][1:]]) + + # go from affine + if self.is_affine: + trf = tf.map_fn(lambda x: self._single_aff_to_shift(x, vol.shape[1:-1]), trf, dtype=tf.float32) + + # prepare location shift + if self.indexing == 'xy': # shift the first two dimensions + trf_split = tf.split(trf, trf.shape[-1], axis=-1) + trf_lst = [trf_split[1], trf_split[0], *trf_split[2:]] + trf = tf.concat(trf_lst, -1) + + # map transform across batch + if self.single_transform: + fn = lambda x: self._single_transform([x, trf[0,:]]) + return tf.map_fn(fn, vol, dtype=tf.float32) + else: + return tf.map_fn(self._single_transform, [vol, trf], dtype=tf.float32) + + def _single_aff_to_shift(self, trf, volshape): + if len(trf.shape) == 1: # go from vector to matrix + trf = tf.reshape(trf, [self.ndims, self.ndims + 1]) + + # note this is unnecessarily extra graph since at every batch entry we have a tf.eye graph + trf += tf.eye(self.ndims+1)[:self.ndims,:] # add identity, hence affine is a shift from identitiy + return affine_to_shift(trf, volshape, shift_center=True) + + def _single_transform(self, inputs): + return transform(inputs[0], inputs[1], interp_method=self.interp_method) + + def get_config(self): + config = {"interp_method": self.interp_method, "indexing": self.indexing, "single_transform": self.single_transform} + base_config = super(SpatialTransformer, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + + +class Resize(Layer): + """ + N-D Resize Tensorflow / Keras Layer + Note: this is not re-shaping an existing volume, but resizing, like scipy's "Zoom" + + If you find this function useful, please cite: + Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation,Dalca AV, Guttag J, Sabuncu MR + CVPR 2018 + + Since then, we've re-written the code to be generalized to any + dimensions, and along the way wrote grid and interpolation functions + """ + + def __init__(self, + zoom_factor, + interp_method='linear', + **kwargs): + """ + Parameters: + interp_method: 'linear' or 'nearest' + 'xy' indexing will have the first two entries of the flow + (along last axis) flipped compared to 'ij' indexing + """ + self.zoom_factor = zoom_factor + self.interp_method = interp_method + self.ndims = None + self.inshape = None + super(Resize, self).__init__(**kwargs) + + def build(self, input_shape): + """ + input_shape should be an element of list of one inputs: + input1: volume + should be a *vol_shape x N + """ + + if isinstance(input_shape[0], (list, tuple)) and len(input_shape) > 1: + raise Exception('Resize must be called on a list of length 1.' + 'First argument is the image, second is the transform.') + + if isinstance(input_shape[0], (list, tuple)): + input_shape = input_shape[0] + + # set up number of dimensions + self.ndims = len(input_shape) - 2 + self.inshape = input_shape + + # confirm built + self.built = True + + def call(self, inputs): + """ + Parameters + inputs: volume of list with one volume + """ + + # check shapes + if isinstance(inputs, (list, tuple)): + assert len(inputs) == 1, "inputs has to be len 1. found: %d" % len(inputs) + vol = inputs[0] + else: + vol = inputs + + # necessary for multi_gpu models... + vol = K.reshape(vol, [-1, *self.inshape[1:]]) + + # map transform across batch + return tf.map_fn(self._single_resize, vol, dtype=tf.float32) + + def compute_output_shape(self, input_shape): + output_shape = [input_shape[0]] + output_shape += [int(f * self.zoom_factor) for f in input_shape[1:-1]] + output_shape += [input_shape[-1]] + return tuple(output_shape) + + def _single_resize(self, inputs): + return resize(inputs, self.zoom_factor, interp_method=self.interp_method) + +# Zoom naming of resize, to match scipy's naming +Zoom = Resize + + +class VecInt(Layer): + """ + Vector Integration Layer + + Enables vector integration via several methods + (ode or quadrature for time-dependent vector fields, + scaling and squaring for stationary fields) + + If you find this function useful, please cite: + Unsupervised Learning for Fast Probabilistic Diffeomorphic Registration + Adrian V. Dalca, Guha Balakrishnan, John Guttag, Mert R. Sabuncu + MICCAI 2018. + """ + + def __init__(self, indexing='ij', method='ss', int_steps=7, **kwargs): + """ + Parameters: + method can be any of the methods in neuron.utils.integrate_vec + """ + + assert indexing in ['ij', 'xy'], "indexing has to be 'ij' (matrix) or 'xy' (cartesian)" + self.indexing = indexing + self.method = method + self.int_steps = int_steps + self.inshape = None + super(self.__class__, self).__init__(**kwargs) + + def build(self, input_shape): + # confirm built + self.built = True + self.inshape = input_shape + + if input_shape[-1] != len(input_shape) - 2: + raise Exception('transform ndims %d does not match expected ndims %d' \ + % (input_shape[-1], len(input_shape) - 2)) + + def call(self, inputs): + loc_shift = inputs + + # necessary for multi_gpu models... + loc_shift = K.reshape(loc_shift, [-1, *self.inshape[1:]]) + loc_shift._keras_shape = inputs._keras_shape + + # prepare location shift + if self.indexing == 'xy': # shift the first two dimensions + loc_shift_split = tf.split(loc_shift, loc_shift.shape[-1], axis=-1) + loc_shift_lst = [loc_shift_split[1], loc_shift_split[0], *loc_shift_split[2:]] + loc_shift = tf.concat(loc_shift_lst, -1) + + # map transform across batch + out = tf.map_fn(self._single_int, loc_shift, dtype=tf.float32) + out._keras_shape = inputs._keras_shape + return out + + def _single_int(self, inputs): + + vel = inputs + return integrate_vec(vel, method=self.method, + nb_steps=self.int_steps, + ode_args={'rtol':1e-6, 'atol':1e-12}, + time_pt=1) + + +class LocalBias(Layer): + """ + Local bias layer: each pixel/voxel has its own bias operation (one parameter) + out[v] = in[v] + b + """ + + def __init__(self, my_initializer='RandomNormal', biasmult=1.0, **kwargs): + self.initializer = my_initializer + self.biasmult = biasmult + super(LocalBias, self).__init__(**kwargs) + + def build(self, input_shape): + # Create a trainable weight variable for this layer. + self.kernel = self.add_weight(name='kernel', + shape=input_shape[1:], + initializer=self.initializer, + trainable=True) + super(LocalBias, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + return x + self.kernel * self.biasmult # weights are difference from input + + def compute_output_shape(self, input_shape): + return input_shape + + +# class LocalParam(InputLayer): + +# def __init__(self, shape, mult=1, my_initializer='RandomNormal', **kwargs): +# super(LocalParam, self).__init__(input_shape=shape, **kwargs) + +# # Create a trainable weight variable for this layer. +# self.kernel = self.add_weight(name='kernel', +# shape=tuple(shape), +# initializer=my_initializer, +# trainable=True) + +# outputs = self._inbound_nodes[0].output_tensors +# z = Input(tensor=K.expand_dims(self.kernel, 0)*mult) +# if len(outputs) == 1: +# self._inbound_nodes[0].output_tensors[0] = z +# else: +# self._inbound_nodes[0].output_tensors = z + +# def get_output(self): # call() would force inputs +# outputs = self._inbound_nodes[0].output_tensors +# if len(outputs) == 1: +# return outputs[0] +# else: +# return outputs + + + +class LocalParam_new(Layer): + + def __init__(self, + shape, + my_initializer='RandomNormal', + name=None, + mult=1.0, + **kwargs): + + self.shape = tuple([1, *shape]) + self.my_initializer = my_initializer + self.mult = mult + + super(LocalParam_new, self).__init__(**kwargs) + + def build(self, input_shape): + + # Create a trainable weight variable for this layer. + self.kernel = self.add_weight(name='kernel', + shape=tuple(self.shape[1:]), + initializer='uniform', + trainable=True) + super(LocalParam_new, self).build(input_shape) # Be sure to call this at the end + + def call(self, _): + # make sure it has a shape + if self.shape is not None: + self.kernel = tf.reshape(self.kernel, self.shape) + return self.kernel + + def compute_output_shape(self, input_shape): + if self.shape is None: + return input_shape + else: + return self.shape + + +class LocalParam(Layer): + """ + Local Parameter layer: each pixel/voxel has its own parameter (one parameter) + out[v] = b + + using code from + https://github.com/YerevaNN/R-NET-in-Keras/blob/master/layers/SharedWeight.py + and + https://github.com/keras-team/keras/blob/ee02d256611b17d11e37b86bd4f618d7f2a37d84/keras/engine/input_layer.py + """ + + def __init__(self, + shape, + my_initializer='RandomNormal', + name=None, + mult=1.0, + **kwargs): + self.shape = [1, *shape] + self.my_initializer = my_initializer + self.mult = mult + + if not name: + prefix = 'param' + name = '%s_%d' % (prefix, K.get_uid(prefix)) + Layer.__init__(self, name=name, **kwargs) + + # Create a trainable weight variable for this layer. + with K.name_scope(self.name): + self.kernel = self.add_weight(name='kernel', + shape=self.shape, + initializer=self.my_initializer, + trainable=True) + + # prepare output tensor, which is essentially the kernel. + output_tensor = self.kernel * self.mult + output_tensor._keras_shape = self.shape + output_tensor._uses_learning_phase = False + output_tensor._keras_history = (self, 0, 0) + output_tensor._batch_input_shape = self.shape + + self.trainable = True + self.built = True + self.is_placeholder = False + + # create new node + Node(self, + inbound_layers=[], + node_indices=[], + tensor_indices=[], + input_tensors=[], + output_tensors=[output_tensor], + input_masks=[], + output_masks=[None], + input_shapes=[], + output_shapes=[self.shape]) + + def get_config(self): + config = { + '_batch_input_shape': self.shape, + '_keras_shape': self.shape, + 'name': self.name + } + return config + + def call(self, _): + z = self.get_output() + return tf.reshape(z, self.shape) + + def compute_output_shape(self, input_shape): + return tuple(self.shape) + + def get_output(self): # call() would force inputs + outputs = self._inbound_nodes[0].output_tensors + if len(outputs) == 1: + return outputs[0] + else: + return outputs + + +class MeanStream(Layer): + """ + Maintain stream of data mean. + + cap refers to mainting an approximation of up to that number of subjects -- that is, + any incoming datapoint will have at least 1/cap weight. + """ + + def __init__(self, cap=100, **kwargs): + self.cap = K.variable(cap, dtype='float32') + super(MeanStream, self).__init__(**kwargs) + + def build(self, input_shape): + # Create mean and count + # These are weights because just maintaining variables don't get saved with the model, and we'd like + # to have these numbers saved when we save the model. + # But we need to make sure that the weights are untrainable. + self.mean = self.add_weight(name='mean', + shape=input_shape[1:], + initializer='zeros', + trainable=False) + self.count = self.add_weight(name='count', + shape=[1], + initializer='zeros', + trainable=False) + + # self.mean = K.zeros(input_shape[1:], name='mean') + # self.count = K.variable(0.0, name='count') + super(MeanStream, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + # previous mean + pre_mean = self.mean + + # compute this batch stats + this_sum = tf.reduce_sum(x, 0) + this_bs = tf.cast(K.shape(x)[0], 'float32') # this batch size + + # increase count and compute weights + new_count = self.count + this_bs + alpha = this_bs/K.minimum(new_count, self.cap) + + # compute new mean. Note that once we reach self.cap (e.g. 1000), the 'previous mean' matters less + new_mean = pre_mean * (1-alpha) + (this_sum/this_bs) * alpha + + updates = [(self.count, new_count), (self.mean, new_mean)] + self.add_update(updates, x) + + # the first few 1000 should not matter that much towards this cost + return K.minimum(1., new_count/self.cap) * K.expand_dims(new_mean, 0) + + def compute_output_shape(self, input_shape): + return input_shape + + +class LocalLinear(Layer): + """ + Local linear layer: each pixel/voxel has its own linear operation (two parameters) + out[v] = a * in[v] + b + """ + + def __init__(self, my_initializer='RandomNormal', **kwargs): + self.initializer = my_initializer + super(LocalLinear, self).__init__(**kwargs) + + def build(self, input_shape): + # Create a trainable weight variable for this layer. + self.mult = self.add_weight(name='mult-kernel', + shape=input_shape[1:], + initializer=self.initializer, + trainable=True) + self.bias = self.add_weight(name='bias-kernel', + shape=input_shape[1:], + initializer=self.initializer, + trainable=True) + super(LocalLinear, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + return x * self.mult + self.bias + + def compute_output_shape(self, input_shape): + return input_shape + + +class LocallyConnected3D(Layer): + """ + code based on LocallyConnected3D from keras layers: + https://github.com/keras-team/keras/blob/master/keras/layers/local.py + + Locally-connected layer for 3D inputs. + The `LocallyConnected3D` layer works similarly + to the `Conv3D` layer, except that weights are unshared, + that is, a different set of filters is applied at each + different patch of the input. + # Examples + ```python + # apply a 3x3x3 unshared weights convolution with 64 output filters on a 32x32x32 image + # with `data_format="channels_last"`: + model = Sequential() + model.add(LocallyConnected3D(64, (3, 3, 3), input_shape=(32, 32, 32, 1))) + # now model.output_shape == (None, 30, 30, 30, 64) + # notice that this layer will consume (30*30*30)*(3*3*3*1*64) + (30*30*30)*64 parameters + # add a 3x3x3 unshared weights convolution on top, with 32 output filters: + model.add(LocallyConnected3D(32, (3, 3, 3))) + # now model.output_shape == (None, 28, 28, 28, 32) + ``` + # Arguments + filters: Integer, the dimensionality of the output space + (i.e. the number of output filters in the convolution). + kernel_size: An integer or tuple/list of 2 integers, specifying the + width and height of the 3D convolution window. + Can be a single integer to specify the same value for + all spatial dimensions. + strides: An integer or tuple/list of 2 integers, + specifying the strides of the convolution along the width and height. + Can be a single integer to specify the same value for + all spatial dimensions. + padding: Currently only support `"valid"` (case-insensitive). + `"same"` will be supported in future. + data_format: A string, + one of `channels_last` (default) or `channels_first`. + The ordering of the dimensions in the inputs. + `channels_last` corresponds to inputs with shape + `(batch, height, width, channels)` while `channels_first` + corresponds to inputs with shape + `(batch, channels, height, width)`. + It defaults to the `image_data_format` value found in your + Keras config file at `~/.keras/keras.json`. + If you never set it, then it will be "channels_last". + activation: Activation function to use + (see [activations](../activations.md)). + If you don't specify anything, no activation is applied + (ie. "linear" activation: `a(x) = x`). + use_bias: Boolean, whether the layer uses a bias vector. + kernel_initializer: Initializer for the `kernel` weights matrix + (see [initializers](../initializers.md)). + bias_initializer: Initializer for the bias vector + (see [initializers](../initializers.md)). + kernel_regularizer: Regularizer function applied to + the `kernel` weights matrix + (see [regularizer](../regularizers.md)). + bias_regularizer: Regularizer function applied to the bias vector + (see [regularizer](../regularizers.md)). + activity_regularizer: Regularizer function applied to + the output of the layer (its "activation"). + (see [regularizer](../regularizers.md)). + kernel_constraint: Constraint function applied to the kernel matrix + (see [constraints](../constraints.md)). + bias_constraint: Constraint function applied to the bias vector + (see [constraints](../constraints.md)). + # Input shape + 4D tensor with shape: + `(samples, channels, rows, cols)` if data_format='channels_first' + or 4D tensor with shape: + `(samples, rows, cols, channels)` if data_format='channels_last'. + # Output shape + 4D tensor with shape: + `(samples, filters, new_rows, new_cols)` if data_format='channels_first' + or 4D tensor with shape: + `(samples, new_rows, new_cols, filters)` if data_format='channels_last'. + `rows` and `cols` values might have changed due to padding. + """ + + # @interfaces.legacy_conv3d_support + def __init__(self, filters, + kernel_size, + strides=(1, 1, 1), + padding='valid', + data_format=None, + activation=None, + use_bias=True, + kernel_initializer='glorot_uniform', + bias_initializer='zeros', + kernel_regularizer=None, + bias_regularizer=None, + activity_regularizer=None, + kernel_constraint=None, + bias_constraint=None, + **kwargs): + + super(LocallyConnected3D, self).__init__(**kwargs) + self.filters = filters + self.kernel_size = conv_utils.normalize_tuple( + kernel_size, 3, 'kernel_size') + self.strides = conv_utils.normalize_tuple(strides, 3, 'strides') + self.padding = conv_utils.normalize_padding(padding) + if self.padding != 'valid': + raise ValueError('Invalid border mode for LocallyConnected3D ' + '(only "valid" is supported): ' + padding) + self.data_format = conv_utils.normalize_data_format(data_format) + self.activation = activations.get(activation) + self.use_bias = use_bias + self.kernel_initializer = initializers.get(kernel_initializer) + self.bias_initializer = initializers.get(bias_initializer) + self.kernel_regularizer = regularizers.get(kernel_regularizer) + self.bias_regularizer = regularizers.get(bias_regularizer) + self.activity_regularizer = regularizers.get(activity_regularizer) + self.kernel_constraint = constraints.get(kernel_constraint) + self.bias_constraint = constraints.get(bias_constraint) + self.input_spec = InputSpec(ndim=5) + + def build(self, input_shape): + + if self.data_format == 'channels_last': + input_row, input_col, input_z = input_shape[1:-1] + input_filter = input_shape[4] + else: + input_row, input_col, input_z = input_shape[2:] + input_filter = input_shape[1] + if input_row is None or input_col is None: + raise ValueError('The spatial dimensions of the inputs to ' + ' a LocallyConnected3D layer ' + 'should be fully-defined, but layer received ' + 'the inputs shape ' + str(input_shape)) + output_row = conv_utils.conv_output_length(input_row, self.kernel_size[0], + self.padding, self.strides[0]) + output_col = conv_utils.conv_output_length(input_col, self.kernel_size[1], + self.padding, self.strides[1]) + output_z = conv_utils.conv_output_length(input_z, self.kernel_size[2], + self.padding, self.strides[2]) + self.output_row = output_row + self.output_col = output_col + self.output_z = output_z + self.kernel_shape = (output_row * output_col * output_z, + self.kernel_size[0] * + self.kernel_size[1] * + self.kernel_size[2] * input_filter, + self.filters) + self.kernel = self.add_weight(shape=self.kernel_shape, + initializer=self.kernel_initializer, + name='kernel', + regularizer=self.kernel_regularizer, + constraint=self.kernel_constraint) + if self.use_bias: + self.bias = self.add_weight(shape=(output_row, output_col, output_z, self.filters), + initializer=self.bias_initializer, + name='bias', + regularizer=self.bias_regularizer, + constraint=self.bias_constraint) + else: + self.bias = None + if self.data_format == 'channels_first': + self.input_spec = InputSpec(ndim=5, axes={1: input_filter}) + else: + self.input_spec = InputSpec(ndim=5, axes={-1: input_filter}) + self.built = True + + def compute_output_shape(self, input_shape): + if self.data_format == 'channels_first': + rows = input_shape[2] + cols = input_shape[3] + z = input_shape[4] + elif self.data_format == 'channels_last': + rows = input_shape[1] + cols = input_shape[2] + z = input_shape[3] + + rows = conv_utils.conv_output_length(rows, self.kernel_size[0], + self.padding, self.strides[0]) + cols = conv_utils.conv_output_length(cols, self.kernel_size[1], + self.padding, self.strides[1]) + z = conv_utils.conv_output_length(z, self.kernel_size[2], + self.padding, self.strides[2]) + + if self.data_format == 'channels_first': + return (input_shape[0], self.filters, rows, cols, z) + elif self.data_format == 'channels_last': + return (input_shape[0], rows, cols, z, self.filters) + + def call(self, inputs): + + output = self.local_conv3d(inputs, + self.kernel, + self.kernel_size, + self.strides, + (self.output_row, self.output_col, self.output_z), + self.data_format) + + if self.use_bias: + output = K.bias_add(output, self.bias, + data_format=self.data_format) + + output = self.activation(output) + return output + + def get_config(self): + config = { + 'filters': self.filters, + 'kernel_size': self.kernel_size, + 'strides': self.strides, + 'padding': self.padding, + 'data_format': self.data_format, + 'activation': activations.serialize(self.activation), + 'use_bias': self.use_bias, + 'kernel_initializer': initializers.serialize(self.kernel_initializer), + 'bias_initializer': initializers.serialize(self.bias_initializer), + 'kernel_regularizer': regularizers.serialize(self.kernel_regularizer), + 'bias_regularizer': regularizers.serialize(self.bias_regularizer), + 'activity_regularizer': regularizers.serialize(self.activity_regularizer), + 'kernel_constraint': constraints.serialize(self.kernel_constraint), + 'bias_constraint': constraints.serialize(self.bias_constraint) + } + base_config = super( + LocallyConnected3D, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + def local_conv3d(self, inputs, kernel, kernel_size, strides, output_shape, data_format=None): + """Apply 3D conv with un-shared weights. + # Arguments + inputs: 4D tensor with shape: + (batch_size, filters, new_rows, new_cols) + if data_format='channels_first' + or 4D tensor with shape: + (batch_size, new_rows, new_cols, filters) + if data_format='channels_last'. + kernel: the unshared weight for convolution, + with shape (output_items, feature_dim, filters) + kernel_size: a tuple of 2 integers, specifying the + width and height of the 3D convolution window. + strides: a tuple of 2 integers, specifying the strides + of the convolution along the width and height. + output_shape: a tuple with (output_row, output_col) + data_format: the data format, channels_first or channels_last + # Returns + A 4d tensor with shape: + (batch_size, filters, new_rows, new_cols) + if data_format='channels_first' + or 4D tensor with shape: + (batch_size, new_rows, new_cols, filters) + if data_format='channels_last'. + # Raises + ValueError: if `data_format` is neither + `channels_last` or `channels_first`. + """ + if data_format is None: + data_format = K.image_data_format() + if data_format not in {'channels_first', 'channels_last'}: + raise ValueError('Unknown data_format: ' + str(data_format)) + + stride_row, stride_col, stride_z = strides + output_row, output_col, output_z = output_shape + kernel_shape = K.int_shape(kernel) + _, feature_dim, filters = kernel_shape + + xs = [] + for i in range(output_row): + for j in range(output_col): + for k in range(output_z): + slice_row = slice(i * stride_row, + i * stride_row + kernel_size[0]) + slice_col = slice(j * stride_col, + j * stride_col + kernel_size[1]) + slice_z = slice(k * stride_z, + k * stride_z + kernel_size[2]) + if data_format == 'channels_first': + xs.append(K.reshape(inputs[:, :, slice_row, slice_col, slice_z], + (1, -1, feature_dim))) + else: + xs.append(K.reshape(inputs[:, slice_row, slice_col, slice_z, :], + (1, -1, feature_dim))) + + x_aggregate = K.concatenate(xs, axis=0) + output = K.batch_dot(x_aggregate, kernel) + output = K.reshape(output, + (output_row, output_col, output_z, -1, filters)) + + if data_format == 'channels_first': + output = K.permute_dimensions(output, (3, 4, 0, 1, 2)) + else: + output = K.permute_dimensions(output, (3, 0, 1, 2, 4)) + return output + diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/metrics.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..be27a1ba362ed5e942cb83795e24ce0d4a041ed2 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/metrics.py @@ -0,0 +1,463 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +tensorflow/keras utilities for the neuron project + +If you use this code, please cite +Dalca AV, Guttag J, Sabuncu MR +Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation, +CVPR 2018 + +Contact: adalca [at] csail [dot] mit [dot] edu +License: GPLv3 +""" + +import sys + +# third party +import numpy as np +import tensorflow.python.keras.backend as K +from tensorflow.python.keras import losses +import tensorflow as tf + +# local +from . import utils + +class CategoricalCrossentropy(object): + """ + Categorical crossentropy with optional categorical weights and spatial prior + + Adapted from weighted categorical crossentropy via wassname: + https://gist.github.com/wassname/ce364fddfc8a025bfab4348cf5de852d + + Variables: + weights: numpy array of shape (C,) where C is the number of classes + + Usage: + loss = CategoricalCrossentropy().loss # or + loss = CategoricalCrossentropy(weights=weights).loss # or + loss = CategoricalCrossentropy(..., prior=prior).loss + model.compile(loss=loss, optimizer='adam') + """ + + def __init__(self, weights=None, use_float16=False, vox_weights=None, crop_indices=None): + """ + Parameters: + vox_weights is either a numpy array the same size as y_true, + or a string: 'y_true' or 'expy_true' + crop_indices: indices to crop each element of the batch + if each element is N-D (so y_true is N+1 dimensional) + then crop_indices is a Tensor of crop ranges (indices) + of size <= N-D. If it's < N-D, then it acts as a slice + for the last few dimensions. + See Also: tf.gather_nd + """ + + self.weights = weights if (weights is not None) else None + self.use_float16 = use_float16 + self.vox_weights = vox_weights + self.crop_indices = crop_indices + + if self.crop_indices is not None and vox_weights is not None: + self.vox_weights = utils.batch_gather(self.vox_weights, self.crop_indices) + + def loss(self, y_true, y_pred): + """ categorical crossentropy loss """ + + if self.crop_indices is not None: + y_true = utils.batch_gather(y_true, self.crop_indices) + y_pred = utils.batch_gather(y_pred, self.crop_indices) + + if self.use_float16: + y_true = K.cast(y_true, 'float16') + y_pred = K.cast(y_pred, 'float16') + + # scale and clip probabilities + # this should not be necessary for softmax output. + y_pred /= K.sum(y_pred, axis=-1, keepdims=True) + y_pred = K.clip(y_pred, K.epsilon(), 1) + + # compute log probability + log_post = K.log(y_pred) # likelihood + + # loss + loss = - y_true * log_post + + # weighted loss + if self.weights is not None: + loss *= self.weights + + if self.vox_weights is not None: + loss *= self.vox_weights + + # take the total loss + # loss = K.batch_flatten(loss) + mloss = K.mean(K.sum(K.cast(loss, 'float32'), -1)) + tf.verify_tensor_all_finite(mloss, 'Loss not finite') + return mloss + + +class Dice(object): + """ + Dice of two Tensors. + + Tensors should either be: + - probabilitic for each label + i.e. [batch_size, *vol_size, nb_labels], where vol_size is the size of the volume (n-dims) + e.g. for a 2D vol, y has 4 dimensions, where each entry is a prob for that voxel + - max_label + i.e. [batch_size, *vol_size], where vol_size is the size of the volume (n-dims). + e.g. for a 2D vol, y has 3 dimensions, where each entry is the max label of that voxel + + Variables: + nb_labels: optional numpy array of shape (L,) where L is the number of labels + if not provided, all non-background (0) labels are computed and averaged + weights: optional numpy array of shape (L,) giving relative weights of each label + input_type is 'prob', or 'max_label' + dice_type is hard or soft + + Usage: + diceloss = metrics.dice(weights=[1, 2, 3]) + model.compile(diceloss, ...) + + Test: + import keras.utils as nd_utils + reload(nrn_metrics) + weights = [0.1, 0.2, 0.3, 0.4, 0.5] + nb_labels = len(weights) + vol_size = [10, 20] + batch_size = 7 + + dice_loss = metrics.Dice(nb_labels=nb_labels).loss + dice = metrics.Dice(nb_labels=nb_labels).dice + dice_wloss = metrics.Dice(nb_labels=nb_labels, weights=weights).loss + + # vectors + lab_size = [batch_size, *vol_size] + r = nd_utils.to_categorical(np.random.randint(0, nb_labels, lab_size), nb_labels) + vec_1 = np.reshape(r, [*lab_size, nb_labels]) + r = nd_utils.to_categorical(np.random.randint(0, nb_labels, lab_size), nb_labels) + vec_2 = np.reshape(r, [*lab_size, nb_labels]) + + # get some standard vectors + tf_vec_1 = tf.constant(vec_1, dtype=tf.float32) + tf_vec_2 = tf.constant(vec_2, dtype=tf.float32) + + # compute some metrics + res = [f(tf_vec_1, tf_vec_2) for f in [dice, dice_loss, dice_wloss]] + res_same = [f(tf_vec_1, tf_vec_1) for f in [dice, dice_loss, dice_wloss]] + + # tf run + init_op = tf.global_variables_initializer() + with tf.Session() as sess: + sess.run(init_op) + sess.run(res) + sess.run(res_same) + print(res[2].eval()) + print(res_same[2].eval()) + """ + + def __init__(self, nb_labels, + weights=None, + input_type='prob', + dice_type='soft', + approx_hard_max=True, + vox_weights=None, + crop_indices=None, + area_reg=0.1): # regularization for bottom of Dice coeff + """ + input_type is 'prob', or 'max_label' + dice_type is hard or soft + approx_hard_max - see note below + + Note: for hard dice, we grab the most likely label and then compute a + one-hot encoding for each voxel with respect to possible labels. To grab the most + likely labels, argmax() can be used, but only when Dice is used as a metric + For a Dice *loss*, argmax is not differentiable, and so we can't use it + Instead, we approximate the prob->one_hot translation when approx_hard_max is True. + """ + + self.nb_labels = nb_labels + self.weights = None if weights is None else K.variable(weights) + self.vox_weights = None if vox_weights is None else K.variable(vox_weights) + self.input_type = input_type + self.dice_type = dice_type + self.approx_hard_max = approx_hard_max + self.area_reg = area_reg + self.crop_indices = crop_indices + + if self.crop_indices is not None and vox_weights is not None: + self.vox_weights = utils.batch_gather(self.vox_weights, self.crop_indices) + + def dice(self, y_true, y_pred): + """ + compute dice for given Tensors + + """ + if self.crop_indices is not None: + y_true = utils.batch_gather(y_true, self.crop_indices) + y_pred = utils.batch_gather(y_pred, self.crop_indices) + + if self.input_type == 'prob': + # We assume that y_true is probabilistic, but just in case: + y_true /= K.sum(y_true, axis=-1, keepdims=True) + y_true = K.clip(y_true, K.epsilon(), 1) + + # make sure pred is a probability + y_pred /= K.sum(y_pred, axis=-1, keepdims=True) + y_pred = K.clip(y_pred, K.epsilon(), 1) + + # Prepare the volumes to operate on + # If we're doing 'hard' Dice, then we will prepare one-hot-based matrices of size + # [batch_size, nb_voxels, nb_labels], where for each voxel in each batch entry, + # the entries are either 0 or 1 + if self.dice_type == 'hard': + + # if given predicted probability, transform to "hard max"" + if self.input_type == 'prob': + if self.approx_hard_max: + y_pred_op = _hard_max(y_pred, axis=-1) + y_true_op = _hard_max(y_true, axis=-1) + else: + y_pred_op = _label_to_one_hot(K.argmax(y_pred, axis=-1), self.nb_labels) + y_true_op = _label_to_one_hot(K.argmax(y_true, axis=-1), self.nb_labels) + + # if given predicted label, transform to one hot notation + else: + assert self.input_type == 'max_label' + y_pred_op = _label_to_one_hot(y_pred, self.nb_labels) + y_true_op = _label_to_one_hot(y_true, self.nb_labels) + + # If we're doing soft Dice, require prob output, and the data already is as we need it + # [batch_size, nb_voxels, nb_labels] + else: + assert self.input_type == 'prob', "cannot do soft dice with max_label input" + y_pred_op = y_pred + y_true_op = y_true + + # compute dice for each entry in batch. + # dice will now be [batch_size, nb_labels] + sum_dim = 1 + top = 2 * K.sum(y_true_op * y_pred_op, sum_dim) + bottom = K.sum(K.square(y_true_op), sum_dim) + K.sum(K.square(y_pred_op), sum_dim) + # make sure we have no 0s on the bottom. K.epsilon() + bottom = K.maximum(bottom, self.area_reg) + return top / bottom + + def mean_dice(self, y_true, y_pred): + """ weighted mean dice across all patches and labels """ + + # compute dice, which will now be [batch_size, nb_labels] + dice_metric = self.dice(y_true, y_pred) + + # weigh the entries in the dice matrix: + if self.weights is not None: + dice_metric *= self.weights + if self.vox_weights is not None: + dice_metric *= self.vox_weights + + # return one minus mean dice as loss + mean_dice_metric = K.mean(dice_metric) + tf.verify_tensor_all_finite(mean_dice_metric, 'metric not finite') + return mean_dice_metric + + + def loss(self, y_true, y_pred): + """ the loss. Assumes y_pred is prob (in [0,1] and sum_row = 1) """ + + # compute dice, which will now be [batch_size, nb_labels] + dice_metric = self.dice(y_true, y_pred) + + # loss + dice_loss = 1 - dice_metric + + # weigh the entries in the dice matrix: + if self.weights is not None: + dice_loss *= self.weights + + # return one minus mean dice as loss + mean_dice_loss = K.mean(dice_loss) + tf.verify_tensor_all_finite(mean_dice_loss, 'Loss not finite') + return mean_dice_loss + + +class MeanSquaredError(): + """ + MSE with several weighting options + """ + + + def __init__(self, weights=None, vox_weights=None, crop_indices=None): + """ + Parameters: + vox_weights is either a numpy array the same size as y_true, + or a string: 'y_true' or 'expy_true' + crop_indices: indices to crop each element of the batch + if each element is N-D (so y_true is N+1 dimensional) + then crop_indices is a Tensor of crop ranges (indices) + of size <= N-D. If it's < N-D, then it acts as a slice + for the last few dimensions. + See Also: tf.gather_nd + """ + self.weights = weights + self.vox_weights = vox_weights + self.crop_indices = crop_indices + + if self.crop_indices is not None and vox_weights is not None: + self.vox_weights = utils.batch_gather(self.vox_weights, self.crop_indices) + + def loss(self, y_true, y_pred): + + if self.crop_indices is not None: + y_true = utils.batch_gather(y_true, self.crop_indices) + y_pred = utils.batch_gather(y_pred, self.crop_indices) + + ksq = K.square(y_pred - y_true) + + if self.vox_weights is not None: + if self.vox_weights == 'y_true': + ksq *= y_true + elif self.vox_weights == 'expy_true': + ksq *= tf.exp(y_true) + else: + ksq *= self.vox_weights + + if self.weights is not None: + ksq *= self.weights + + return K.mean(ksq) + + +class Mix(): + """ a mix of several losses """ + + def __init__(self, losses, loss_wts=None): + self.losses = losses + self.loss_wts = loss_wts + if loss_wts is None: + self.loss_wts = np.ones(len(loss_wts)) + + def loss(self, y_true, y_pred): + total_loss = K.variable(0) + for idx, loss in enumerate(self.losses): + total_loss += self.loss_wts[idx] * loss(y_true, y_pred) + return total_loss + + +class WGAN_GP(object): + """ + based on https://github.com/rarilurelo/keras_improved_wgan/blob/master/wgan_gp.py + """ + + def __init__(self, disc, batch_size=1, lambda_gp=10): + self.disc = disc + self.lambda_gp = lambda_gp + self.batch_size = batch_size + + def loss(self, y_true, y_pred): + + # get the value for the true and fake images + disc_true = self.disc(y_true) + disc_pred = self.disc(y_pred) + + # sample a x_hat by sampling along the line between true and pred + # z = tf.placeholder(tf.float32, shape=[None, 1]) + # shp = y_true.get_shape()[0] + # WARNING: SHOULD REALLY BE shape=[batch_size, 1] !!! + # self.batch_size does not work, since it's not None!!! + alpha = K.random_uniform(shape=[K.shape(y_pred)[0], 1, 1, 1]) + diff = y_pred - y_true + interp = y_true + alpha * diff + + # take gradient of D(x_hat) + gradients = K.gradients(self.disc(interp), [interp])[0] + grad_pen = K.mean(K.square(K.sqrt(K.sum(K.square(gradients), axis=1))-1)) + + # compute loss + return (K.mean(disc_pred) - K.mean(disc_true)) + self.lambda_gp * grad_pen + + +class Nonbg(object): + """ UNTESTED + class to modify output on operating only on the non-bg class + + All data is aggregated and the (passed) metric is called on flattened true and + predicted outputs in all (true) non-bg regions + + Usage: + loss = metrics.dice + nonbgloss = nonbg(loss).loss + """ + + def __init__(self, metric): + self.metric = metric + + def loss(self, y_true, y_pred): + """ prepare a loss of the given metric/loss operating on non-bg data """ + yt = y_true #.eval() + ytbg = np.where(yt == 0) + y_true_fix = K.variable(yt.flat(ytbg)) + y_pred_fix = K.variable(y_pred.flat(ytbg)) + return self.metric(y_true_fix, y_pred_fix) + + +def l1(y_true, y_pred): + """ L1 metric (MAE) """ + return losses.mean_absolute_error(y_true, y_pred) + + +def l2(y_true, y_pred): + """ L2 metric (MSE) """ + return losses.mean_squared_error(y_true, y_pred) + + +############################################################################### +# Helper Functions +############################################################################### + +def _label_to_one_hot(tens, nb_labels): + """ + Transform a label nD Tensor to a one-hot 3D Tensor. The input tensor is first + batch-flattened, and then each batch and each voxel gets a one-hot representation + """ + y = K.batch_flatten(tens) + return K.one_hot(y, nb_labels) + + +def _hard_max(tens, axis): + """ + we can't use the argmax function in a loss, as it's not differentiable + We can use it in a metric, but not in a loss function + therefore, we replace the 'hard max' operation (i.e. argmax + onehot) + with this approximation + """ + tensmax = K.max(tens, axis=axis, keepdims=True) + eps_hot = K.maximum(tens - tensmax + K.epsilon(), 0) + one_hot = eps_hot / K.epsilon() + return one_hot diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/models.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e91a75672eeeff5e56f76cd5f68ec3c9f5137462 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/models.py @@ -0,0 +1,1037 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +tensorflow/keras utilities for the neuron project + +If you use this code, please cite +Dalca AV, Guttag J, Sabuncu MR +Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation, +CVPR 2018 + +Contact: adalca [at] csail [dot] mit [dot] edu +License: GPLv3 +""" + +import sys + +from . import layers + +# third party +import numpy as np +import tensorflow as tf +import tensorflow.python.keras as keras +import tensorflow.python.keras.layers as KL +from tensorflow.python.keras.models import Model +import tensorflow.python.keras.backend as K +from tensorflow.python.keras.constraints import maxnorm + + +def dilation_net(nb_features, + input_shape, # input layer shape, vector of size ndims + 1(nb_channels) + nb_levels, + conv_size, + nb_labels, + name='dilation_net', + prefix=None, + feat_mult=1, + pool_size=2, + use_logp=True, + padding='same', + dilation_rate_mult=1, + activation='elu', + use_residuals=False, + final_pred_activation='softmax', + nb_conv_per_level=1, + add_prior_layer=False, + add_prior_layer_reg=0, + layer_nb_feats=None, + batch_norm=None): + + return unet(nb_features, + input_shape, # input layer shape, vector of size ndims + 1(nb_channels) + nb_levels, + conv_size, + nb_labels, + name='unet', + prefix=None, + feat_mult=1, + pool_size=2, + use_logp=True, + padding='same', + activation='elu', + use_residuals=False, + dilation_rate_mult=dilation_rate_mult, + final_pred_activation='softmax', + nb_conv_per_level=1, + add_prior_layer=False, + add_prior_layer_reg=0, + layer_nb_feats=None, + batch_norm=None) + + + +def unet(nb_features, + input_shape, # input layer shape, vector of size ndims + 1(nb_channels) + nb_levels, + conv_size, + nb_labels, + name='unet', + prefix=None, + feat_mult=1, + pool_size=2, + use_logp=True, + padding='same', + dilation_rate_mult=1, + activation='elu', + use_residuals=False, + final_pred_activation='softmax', + nb_conv_per_level=1, + add_prior_layer=False, + add_prior_layer_reg=0, + layer_nb_feats=None, + conv_dropout=0, + batch_norm=None): + """ + unet-style model with an overdose of parametrization + + for U-net like architecture, we need to use Deconvolution3D. + However, this is not yet available (maybe soon, it's on a dev branch in github I believe) + Until then, we'll upsample and convolve. + TODO: Need to check that UpSampling3D actually does NN-upsampling! + """ + + # naming + model_name = name + if prefix is None: + prefix = model_name + + # volume size data + ndims = len(input_shape) - 1 + if isinstance(pool_size, int): + pool_size = (pool_size,) * ndims + + # get encoding model + enc_model = conv_enc(nb_features, + input_shape, + nb_levels, + conv_size, + name=model_name, + prefix=prefix, + feat_mult=feat_mult, + pool_size=pool_size, + padding=padding, + dilation_rate_mult=dilation_rate_mult, + activation=activation, + use_residuals=use_residuals, + nb_conv_per_level=nb_conv_per_level, + layer_nb_feats=layer_nb_feats, + conv_dropout=conv_dropout, + batch_norm=batch_norm) + + # get decoder + # use_skip_connections=1 makes it a u-net + lnf = layer_nb_feats[(nb_levels * nb_conv_per_level):] if layer_nb_feats is not None else None + dec_model = conv_dec(nb_features, + None, + nb_levels, + conv_size, + nb_labels, + name=model_name, + prefix=prefix, + feat_mult=feat_mult, + pool_size=pool_size, + use_skip_connections=1, + padding=padding, + dilation_rate_mult=dilation_rate_mult, + activation=activation, + use_residuals=use_residuals, + final_pred_activation='linear' if add_prior_layer else final_pred_activation, + nb_conv_per_level=nb_conv_per_level, + batch_norm=batch_norm, + layer_nb_feats=lnf, + conv_dropout=conv_dropout, + input_model=enc_model) + + final_model = dec_model + if add_prior_layer: + final_model = add_prior(dec_model, + [*input_shape[:-1], nb_labels], + name=model_name + '_prior', + use_logp=use_logp, + final_pred_activation=final_pred_activation, + add_prior_layer_reg=add_prior_layer_reg) + + return final_model + + +def ae(nb_features, + input_shape, + nb_levels, + conv_size, + nb_labels, + enc_size, + name='ae', + prefix=None, + feat_mult=1, + pool_size=2, + padding='same', + activation='elu', + use_residuals=False, + nb_conv_per_level=1, + batch_norm=None, + enc_batch_norm=None, + ae_type='conv', # 'dense', or 'conv' + enc_lambda_layers=None, + add_prior_layer=False, + add_prior_layer_reg=0, + use_logp=True, + conv_dropout=0, + include_mu_shift_layer=False, + single_model=False, # whether to return a single model, or a tuple of models that can be stacked. + final_pred_activation='softmax', + do_vae=False): + """ + Convolutional Auto-Encoder. + Optionally Variational. + Optionally Dense middle layer + + "Mostly" in that the inner encoding can be (optionally) constructed via dense features. + + Parameters: + do_vae (bool): whether to do a variational auto-encoder or not. + + enc_lambda_layers functions to try: + K.softsign + + a = 1 + longtanh = lambda x: K.tanh(x) * K.log(2 + a * abs(x)) + """ + + # naming + model_name = name + + # volume size data + ndims = len(input_shape) - 1 + if isinstance(pool_size, int): + pool_size = (pool_size,) * ndims + + # get encoding model + enc_model = conv_enc(nb_features, + input_shape, + nb_levels, + conv_size, + name=model_name, + feat_mult=feat_mult, + pool_size=pool_size, + padding=padding, + activation=activation, + use_residuals=use_residuals, + nb_conv_per_level=nb_conv_per_level, + conv_dropout=conv_dropout, + batch_norm=batch_norm) + + # middle AE structure + if single_model: + in_input_shape = None + in_model = enc_model + else: + in_input_shape = enc_model.output.shape.as_list()[1:] + in_model = None + mid_ae_model = single_ae(enc_size, + in_input_shape, + conv_size=conv_size, + name=model_name, + ae_type=ae_type, + input_model=in_model, + batch_norm=enc_batch_norm, + enc_lambda_layers=enc_lambda_layers, + include_mu_shift_layer=include_mu_shift_layer, + do_vae=do_vae) + + # decoder + if single_model: + in_input_shape = None + in_model = mid_ae_model + else: + in_input_shape = mid_ae_model.output.shape.as_list()[1:] + in_model = None + dec_model = conv_dec(nb_features, + in_input_shape, + nb_levels, + conv_size, + nb_labels, + name=model_name, + feat_mult=feat_mult, + pool_size=pool_size, + use_skip_connections=False, + padding=padding, + activation=activation, + use_residuals=use_residuals, + final_pred_activation='linear', + nb_conv_per_level=nb_conv_per_level, + batch_norm=batch_norm, + conv_dropout=conv_dropout, + input_model=in_model) + + if add_prior_layer: + dec_model = add_prior(dec_model, + [*input_shape[:-1],nb_labels], + name=model_name, + prefix=model_name + '_prior', + use_logp=use_logp, + final_pred_activation=final_pred_activation, + add_prior_layer_reg=add_prior_layer_reg) + + if single_model: + return dec_model + else: + return (dec_model, mid_ae_model, enc_model) + + +def conv_enc(nb_features, + input_shape, + nb_levels, + conv_size, + name=None, + prefix=None, + feat_mult=1, + pool_size=2, + dilation_rate_mult=1, + padding='same', + activation='elu', + layer_nb_feats=None, + use_residuals=False, + nb_conv_per_level=2, + conv_dropout=0, + batch_norm=None): + """ + Fully Convolutional Encoder + """ + + # naming + model_name = name + if prefix is None: + prefix = model_name + + # volume size data + ndims = len(input_shape) - 1 + input_shape = tuple(input_shape) + if isinstance(pool_size, int): + pool_size = (pool_size,) * ndims + + # prepare layers + convL = getattr(KL, 'Conv%dD' % ndims) + conv_kwargs = {'padding': padding, 'activation': activation} + maxpool = getattr(KL, 'MaxPooling%dD' % ndims) + + # first layer: input + name = '%s_input' % prefix + last_tensor = KL.Input(shape=input_shape, name=name) + input_tensor = last_tensor + + # down arm: + # add nb_levels of conv + ReLu + conv + ReLu. Pool after each of first nb_levels - 1 layers + lfidx = 0 + for level in range(nb_levels): + lvl_first_tensor = last_tensor + nb_lvl_feats = np.round(nb_features*feat_mult**level).astype(int) + conv_kwargs['dilation_rate'] = dilation_rate_mult**level + + for conv in range(nb_conv_per_level): + if layer_nb_feats is not None: + nb_lvl_feats = layer_nb_feats[lfidx] + lfidx += 1 + + name = '%s_conv_downarm_%d_%d' % (prefix, level, conv) + if conv < (nb_conv_per_level-1) or (not use_residuals): + last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor) + else: # no activation + last_tensor = convL(nb_lvl_feats, conv_size, padding=padding, name=name)(last_tensor) + + if conv_dropout > 0: + # conv dropout along feature space only + name = '%s_dropout_downarm_%d_%d' % (prefix, level, conv) + noise_shape = [None, *[1]*ndims, nb_lvl_feats] + last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor) + + if use_residuals: + convarm_layer = last_tensor + + # the "add" layer is the original input + # However, it may not have the right number of features to be added + nb_feats_in = lvl_first_tensor.get_shape()[-1] + nb_feats_out = convarm_layer.get_shape()[-1] + add_layer = lvl_first_tensor + if nb_feats_in > 1 and nb_feats_out > 1 and (nb_feats_in != nb_feats_out): + name = '%s_expand_down_merge_%d' % (prefix, level) + last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(lvl_first_tensor) + add_layer = last_tensor + + if conv_dropout > 0: + name = '%s_dropout_down_merge_%d_%d' % (prefix, level, conv) + noise_shape = [None, *[1]*ndims, nb_lvl_feats] + last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor) + + name = '%s_res_down_merge_%d' % (prefix, level) + last_tensor = KL.add([add_layer, convarm_layer], name=name) + + name = '%s_res_down_merge_act_%d' % (prefix, level) + last_tensor = KL.Activation(activation, name=name)(last_tensor) + + if batch_norm is not None: + name = '%s_bn_down_%d' % (prefix, level) + last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + + # max pool if we're not at the last level + if level < (nb_levels - 1): + name = '%s_maxpool_%d' % (prefix, level) + last_tensor = maxpool(pool_size=pool_size, name=name, padding=padding)(last_tensor) + + # create the model and return + model = Model(inputs=input_tensor, outputs=[last_tensor], name=model_name) + return model + + +def conv_dec(nb_features, + input_shape, + nb_levels, + conv_size, + nb_labels, + name=None, + prefix=None, + feat_mult=1, + pool_size=2, + use_skip_connections=False, + padding='same', + dilation_rate_mult=1, + activation='elu', + use_residuals=False, + final_pred_activation='softmax', + nb_conv_per_level=2, + layer_nb_feats=None, + batch_norm=None, + conv_dropout=0, + input_model=None): + """ + Fully Convolutional Decoder + + Parameters: + ... + use_skip_connections (bool): if true, turns an Enc-Dec to a U-Net. + If true, input_tensor and tensors are required. + It assumes a particular naming of layers. conv_enc... + """ + + # naming + model_name = name + if prefix is None: + prefix = model_name + + # if using skip connections, make sure need to use them. + if use_skip_connections: + assert input_model is not None, "is using skip connections, tensors dictionary is required" + + # first layer: input + input_name = '%s_input' % prefix + if input_model is None: + input_tensor = KL.Input(shape=input_shape, name=input_name) + last_tensor = input_tensor + else: + input_tensor = input_model.input + last_tensor = input_model.output + input_shape = last_tensor.shape.as_list()[1:] + + # vol size info + ndims = len(input_shape) - 1 + input_shape = tuple(input_shape) + if isinstance(pool_size, int): + pool_size = (pool_size,) * ndims + + # prepare layers + convL = getattr(KL, 'Conv%dD' % ndims) + conv_kwargs = {'padding': padding, 'activation': activation} + upsample = getattr(KL, 'UpSampling%dD' % ndims) + + # up arm: + # nb_levels - 1 layers of Deconvolution3D + # (approx via up + conv + ReLu) + merge + conv + ReLu + conv + ReLu + lfidx = 0 + for level in range(nb_levels - 1): + nb_lvl_feats = np.round(nb_features*feat_mult**(nb_levels-2-level)).astype(int) + conv_kwargs['dilation_rate'] = dilation_rate_mult**(nb_levels-2-level) + + # upsample matching the max pooling layers size + name = '%s_up_%d' % (prefix, nb_levels + level) + last_tensor = upsample(size=pool_size, name=name)(last_tensor) + up_tensor = last_tensor + + # merge layers combining previous layer + # TODO: add Cropping3D or Cropping2D if 'valid' padding + if use_skip_connections: + conv_name = '%s_conv_downarm_%d_%d' % (prefix, nb_levels - 2 - level, nb_conv_per_level - 1) + cat_tensor = input_model.get_layer(conv_name).output + name = '%s_merge_%d' % (prefix, nb_levels + level) + last_tensor = KL.concatenate([cat_tensor, last_tensor], axis=ndims+1, name=name) + + # convolution layers + for conv in range(nb_conv_per_level): + if layer_nb_feats is not None: + nb_lvl_feats = layer_nb_feats[lfidx] + lfidx += 1 + + name = '%s_conv_uparm_%d_%d' % (prefix, nb_levels + level, conv) + if conv < (nb_conv_per_level-1) or (not use_residuals): + last_tensor = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor) + else: + last_tensor = convL(nb_lvl_feats, conv_size, padding=padding, name=name)(last_tensor) + + if conv_dropout > 0: + name = '%s_dropout_uparm_%d_%d' % (prefix, level, conv) + noise_shape = [None, *[1]*ndims, nb_lvl_feats] + last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor) + + # residual block + if use_residuals: + + # the "add" layer is the original input + # However, it may not have the right number of features to be added + add_layer = up_tensor + nb_feats_in = add_layer.get_shape()[-1] + nb_feats_out = last_tensor.get_shape()[-1] + if nb_feats_in > 1 and nb_feats_out > 1 and (nb_feats_in != nb_feats_out): + name = '%s_expand_up_merge_%d' % (prefix, level) + add_layer = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(add_layer) + + if conv_dropout > 0: + name = '%s_dropout_up_merge_%d_%d' % (prefix, level, conv) + noise_shape = [None, *[1]*ndims, nb_lvl_feats] + last_tensor = KL.Dropout(conv_dropout, noise_shape=noise_shape)(last_tensor) + + name = '%s_res_up_merge_%d' % (prefix, level) + last_tensor = KL.add([last_tensor, add_layer], name=name) + + name = '%s_res_up_merge_act_%d' % (prefix, level) + last_tensor = KL.Activation(activation, name=name)(last_tensor) + + if batch_norm is not None: + name = '%s_bn_up_%d' % (prefix, level) + last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + + # Compute likelyhood prediction (no activation yet) + name = '%s_likelihood' % prefix + last_tensor = convL(nb_labels, 1, activation=None, name=name)(last_tensor) + like_tensor = last_tensor + + # output prediction layer + # we use a softmax to compute P(L_x|I) where x is each location + if final_pred_activation == 'softmax': + print("using final_pred_activation %s for %s" % (final_pred_activation, model_name)) + name = '%s_prediction' % prefix + softmax_lambda_fcn = lambda x: keras.activations.softmax(x, axis=ndims + 1) + pred_tensor = KL.Lambda(softmax_lambda_fcn, name=name)(last_tensor) + + # otherwise create a layer that does nothing. + else: + name = '%s_prediction' % prefix + pred_tensor = KL.Activation('linear', name=name)(like_tensor) + + # create the model and retun + model = Model(inputs=input_tensor, outputs=pred_tensor, name=model_name) + return model + + + + + +def add_prior(input_model, + prior_shape, + name='prior_model', + prefix=None, + use_logp=True, + final_pred_activation='softmax', + add_prior_layer_reg=0): + """ + Append post-prior layer to a given model + """ + + # naming + model_name = name + if prefix is None: + prefix = model_name + + # prior input layer + prior_input_name = '%s-input' % prefix + prior_tensor = KL.Input(shape=prior_shape, name=prior_input_name) + prior_tensor_input = prior_tensor + like_tensor = input_model.output + + # operation varies depending on whether we log() prior or not. + if use_logp: + # name = '%s-log' % prefix + # prior_tensor = KL.Lambda(_log_layer_wrap(add_prior_layer_reg), name=name)(prior_tensor) + print("Breaking change: use_logp option now requires log input!", file=sys.stderr) + merge_op = KL.add + + else: + # using sigmoid to get the likelihood values between 0 and 1 + # note: they won't add up to 1. + name = '%s_likelihood_sigmoid' % prefix + like_tensor = KL.Activation('sigmoid', name=name)(like_tensor) + merge_op = KL.multiply + + # merge the likelihood and prior layers into posterior layer + name = '%s_posterior' % prefix + post_tensor = merge_op([prior_tensor, like_tensor], name=name) + + # output prediction layer + # we use a softmax to compute P(L_x|I) where x is each location + pred_name = '%s_prediction' % prefix + if final_pred_activation == 'softmax': + assert use_logp, 'cannot do softmax when adding prior via P()' + print("using final_pred_activation %s for %s" % (final_pred_activation, model_name)) + softmax_lambda_fcn = lambda x: keras.activations.softmax(x, axis=-1) + pred_tensor = KL.Lambda(softmax_lambda_fcn, name=pred_name)(post_tensor) + + else: + pred_tensor = KL.Activation('linear', name=pred_name)(post_tensor) + + # create the model + model_inputs = [*input_model.inputs, prior_tensor_input] + model = Model(inputs=model_inputs, outputs=[pred_tensor], name=model_name) + + # compile + return model + + +def single_ae(enc_size, + input_shape, + name='single_ae', + prefix=None, + ae_type='dense', # 'dense', or 'conv' + conv_size=None, + input_model=None, + enc_lambda_layers=None, + batch_norm=True, + padding='same', + activation=None, + include_mu_shift_layer=False, + do_vae=False): + """single-layer Autoencoder (i.e. input - encoding - output""" + + # naming + model_name = name + if prefix is None: + prefix = model_name + + if enc_lambda_layers is None: + enc_lambda_layers = [] + + # prepare input + input_name = '%s_input' % prefix + if input_model is None: + assert input_shape is not None, 'input_shape of input_model is necessary' + input_tensor = KL.Input(shape=input_shape, name=input_name) + last_tensor = input_tensor + else: + input_tensor = input_model.input + last_tensor = input_model.output + input_shape = last_tensor.shape.as_list()[1:] + input_nb_feats = last_tensor.shape.as_list()[-1] + + # prepare conv type based on input + if ae_type == 'conv': + ndims = len(input_shape) - 1 + convL = getattr(KL, 'Conv%dD' % ndims) + assert conv_size is not None, 'with conv ae, need conv_size' + conv_kwargs = {'padding': padding, 'activation': activation} + + + + # if want to go through a dense layer in the middle of the U, need to: + # - flatten last layer if not flat + # - do dense encoding and decoding + # - unflatten (rehsape spatially) at end + if ae_type == 'dense' and len(input_shape) > 1: + name = '%s_ae_%s_down_flat' % (prefix, ae_type) + last_tensor = KL.Flatten(name=name)(last_tensor) + + # recall this layer + pre_enc_layer = last_tensor + + # encoding layer + if ae_type == 'dense': + assert len(enc_size) == 1, "enc_size should be of length 1 for dense layer" + + enc_size_str = ''.join(['%d_' % d for d in enc_size])[:-1] + name = '%s_ae_mu_enc_dense_%s' % (prefix, enc_size_str) + last_tensor = KL.Dense(enc_size[0], name=name)(pre_enc_layer) + + else: # convolution + # convolve then resize. enc_size should be [nb_dim1, nb_dim2, ..., nb_feats] + assert len(enc_size) == len(input_shape), \ + "encoding size does not match input shape %d %d" % (len(enc_size), len(input_shape)) + + if list(enc_size)[:-1] != list(input_shape)[:-1] and \ + all([f is not None for f in input_shape[:-1]]) and \ + all([f is not None for f in enc_size[:-1]]): + + assert len(enc_size) - 1 == 2, "Sorry, I have not yet implemented non-2D resizing -- need to check out interpn!" + name = '%s_ae_mu_enc_conv' % (prefix) + last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer) + + name = '%s_ae_mu_enc' % (prefix) + resize_fn = lambda x: tf.image.resize_bilinear(x, enc_size[:-1]) + last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor) + + elif enc_size[-1] is None: # convolutional, but won't tell us bottleneck + name = '%s_ae_mu_enc' % (prefix) + last_tensor = KL.Lambda(lambda x: x, name=name)(pre_enc_layer) + + else: + name = '%s_ae_mu_enc' % (prefix) + last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer) + + if include_mu_shift_layer: + # shift + name = '%s_ae_mu_shift' % (prefix) + last_tensor = layers.LocalBias(name=name)(last_tensor) + + # encoding clean-up layers + for layer_fcn in enc_lambda_layers: + lambda_name = layer_fcn.__name__ + name = '%s_ae_mu_%s' % (prefix, lambda_name) + last_tensor = KL.Lambda(layer_fcn, name=name)(last_tensor) + + if batch_norm is not None: + name = '%s_ae_mu_bn' % (prefix) + last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + + # have a simple layer that does nothing to have a clear name before sampling + name = '%s_ae_mu' % (prefix) + last_tensor = KL.Lambda(lambda x: x, name=name)(last_tensor) + + + # if doing variational AE, will need the sigma layer as well. + if do_vae: + mu_tensor = last_tensor + + # encoding layer + if ae_type == 'dense': + name = '%s_ae_sigma_enc_dense_%s' % (prefix, enc_size_str) + last_tensor = KL.Dense(enc_size[0], name=name)(pre_enc_layer) + + else: + if list(enc_size)[:-1] != list(input_shape)[:-1] and \ + all([f is not None for f in input_shape[:-1]]) and \ + all([f is not None for f in enc_size[:-1]]): + + assert len(enc_size) - 1 == 2, "Sorry, I have not yet implemented non-2D resizing..." + name = '%s_ae_sigma_enc_conv' % (prefix) + last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer) + + name = '%s_ae_sigma_enc' % (prefix) + resize_fn = lambda x: tf.image.resize_bilinear(x, enc_size[:-1]) + last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor) + + elif enc_size[-1] is None: # convolutional, but won't tell us bottleneck + name = '%s_ae_sigma_enc' % (prefix) + last_tensor = convL(pre_enc_layer.shape.as_list()[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer) + # cannot use lambda, then mu and sigma will be same layer. + # last_tensor = KL.Lambda(lambda x: x, name=name)(pre_enc_layer) + + else: + name = '%s_ae_sigma_enc' % (prefix) + last_tensor = convL(enc_size[-1], conv_size, name=name, **conv_kwargs)(pre_enc_layer) + + # encoding clean-up layers + for layer_fcn in enc_lambda_layers: + lambda_name = layer_fcn.__name__ + name = '%s_ae_sigma_%s' % (prefix, lambda_name) + last_tensor = KL.Lambda(layer_fcn, name=name)(last_tensor) + + if batch_norm is not None: + name = '%s_ae_sigma_bn' % (prefix) + last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + + # have a simple layer that does nothing to have a clear name before sampling + name = '%s_ae_sigma' % (prefix) + last_tensor = KL.Lambda(lambda x: x, name=name)(last_tensor) + + logvar_tensor = last_tensor + + # VAE sampling + sampler = _VAESample().sample_z + + name = '%s_ae_sample' % (prefix) + last_tensor = KL.Lambda(sampler, name=name)([mu_tensor, logvar_tensor]) + + if include_mu_shift_layer: + # shift + name = '%s_ae_sample_shift' % (prefix) + last_tensor = layers.LocalBias(name=name)(last_tensor) + + # decoding layer + if ae_type == 'dense': + name = '%s_ae_%s_dec_flat_%s' % (prefix, ae_type, enc_size_str) + last_tensor = KL.Dense(np.prod(input_shape), name=name)(last_tensor) + + # unflatten if dense method + if len(input_shape) > 1: + name = '%s_ae_%s_dec' % (prefix, ae_type) + last_tensor = KL.Reshape(input_shape, name=name)(last_tensor) + + else: + + if list(enc_size)[:-1] != list(input_shape)[:-1] and \ + all([f is not None for f in input_shape[:-1]]) and \ + all([f is not None for f in enc_size[:-1]]): + + name = '%s_ae_mu_dec' % (prefix) + resize_fn = lambda x: tf.image.resize_bilinear(x, input_shape[:-1]) + last_tensor = KL.Lambda(resize_fn, name=name)(last_tensor) + + name = '%s_ae_%s_dec' % (prefix, ae_type) + last_tensor = convL(input_nb_feats, conv_size, name=name, **conv_kwargs)(last_tensor) + + + if batch_norm is not None: + name = '%s_bn_ae_%s_dec' % (prefix, ae_type) + last_tensor = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + + # create the model and retun + model = Model(inputs=input_tensor, outputs=[last_tensor], name=model_name) + return model + + + +def design_dnn(nb_features, input_shape, nb_levels, conv_size, nb_labels, + feat_mult=1, + pool_size=2, + padding='same', + activation='elu', + final_layer='dense-sigmoid', + conv_dropout=0, + conv_maxnorm=0, + nb_input_features=1, + batch_norm=False, + name=None, + prefix=None, + use_strided_convolution_maxpool=True, + nb_conv_per_level=2): + """ + "deep" cnn with dense or global max pooling layer @ end... + + Could use sequential... + """ + + model_name = name + if model_name is None: + model_name = 'model_1' + if prefix is None: + prefix = model_name + + ndims = len(input_shape) + input_shape = tuple(input_shape) + + convL = getattr(KL, 'Conv%dD' % ndims) + maxpool = KL.MaxPooling3D if len(input_shape) == 3 else KL.MaxPooling2D + if isinstance(pool_size, int): + pool_size = (pool_size,) * ndims + + # kwargs for the convolution layer + conv_kwargs = {'padding': padding, 'activation': activation} + if conv_maxnorm > 0: + conv_kwargs['kernel_constraint'] = maxnorm(conv_maxnorm) + + # initialize a dictionary + enc_tensors = {} + + # first layer: input + name = '%s_input' % prefix + enc_tensors[name] = KL.Input(shape=input_shape + (nb_input_features,), name=name) + last_tensor = enc_tensors[name] + + # down arm: + # add nb_levels of conv + ReLu + conv + ReLu. Pool after each of first nb_levels - 1 layers + for level in range(nb_levels): + for conv in range(nb_conv_per_level): + if conv_dropout > 0: + name = '%s_dropout_%d_%d' % (prefix, level, conv) + enc_tensors[name] = KL.Dropout(conv_dropout)(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_conv_%d_%d' % (prefix, level, conv) + nb_lvl_feats = np.round(nb_features*feat_mult**level).astype(int) + enc_tensors[name] = convL(nb_lvl_feats, conv_size, **conv_kwargs, name=name)(last_tensor) + last_tensor = enc_tensors[name] + + # max pool + if use_strided_convolution_maxpool: + name = '%s_strided_conv_%d' % (prefix, level) + enc_tensors[name] = convL(nb_lvl_feats, pool_size, **conv_kwargs, name=name)(last_tensor) + last_tensor = enc_tensors[name] + else: + name = '%s_maxpool_%d' % (prefix, level) + enc_tensors[name] = maxpool(pool_size=pool_size, name=name, padding=padding)(last_tensor) + last_tensor = enc_tensors[name] + + # dense layer + if final_layer == 'dense-sigmoid': + + name = "%s_flatten" % prefix + enc_tensors[name] = KL.Flatten(name=name)(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_dense' % prefix + enc_tensors[name] = KL.Dense(1, name=name, activation="sigmoid")(last_tensor) + + elif final_layer == 'dense-tanh': + + name = "%s_flatten" % prefix + enc_tensors[name] = KL.Flatten(name=name)(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_dense' % prefix + enc_tensors[name] = KL.Dense(1, name=name)(last_tensor) + last_tensor = enc_tensors[name] + + # Omittting BatchNorm for now, it seems to have a cpu vs gpu problem + # https://github.com/tensorflow/tensorflow/pull/8906 + # https://github.com/fchollet/keras/issues/5802 + # name = '%s_%s_bn' % prefix + # enc_tensors[name] = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + # last_tensor = enc_tensors[name] + + name = '%s_%s_tanh' % prefix + enc_tensors[name] = KL.Activation(activation="tanh", name=name)(last_tensor) + + elif final_layer == 'dense-softmax': + + name = "%s_flatten" % prefix + enc_tensors[name] = KL.Flatten(name=name)(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_dense' % prefix + enc_tensors[name] = KL.Dense(nb_labels, name=name, activation="softmax")(last_tensor) + + # global max pooling layer + elif final_layer == 'myglobalmaxpooling': + + name = '%s_batch_norm' % prefix + enc_tensors[name] = KL.BatchNormalization(axis=batch_norm, name=name)(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_global_max_pool' % prefix + enc_tensors[name] = KL.Lambda(_global_max_nd, name=name)(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_global_max_pool_reshape' % prefix + enc_tensors[name] = KL.Reshape((1, 1), name=name)(last_tensor) + last_tensor = enc_tensors[name] + + # cannot do activation in lambda layer. Could code inside, but will do extra lyaer + name = '%s_global_max_pool_sigmoid' % prefix + enc_tensors[name] = KL.Conv1D(1, 1, name=name, activation="sigmoid", use_bias=True)(last_tensor) + + elif final_layer == 'globalmaxpooling': + + name = '%s_conv_to_featmaps' % prefix + enc_tensors[name] = KL.Conv3D(2, 1, name=name, activation="relu")(last_tensor) + last_tensor = enc_tensors[name] + + name = '%s_global_max_pool' % prefix + enc_tensors[name] = KL.GlobalMaxPooling3D(name=name)(last_tensor) + last_tensor = enc_tensors[name] + + # cannot do activation in lambda layer. Could code inside, but will do extra lyaer + name = '%s_global_max_pool_softmax' % prefix + enc_tensors[name] = KL.Activation('softmax', name=name)(last_tensor) + + last_tensor = enc_tensors[name] + + # create the model + model = Model(inputs=[enc_tensors['%s_input' % prefix]], outputs=[last_tensor], name=model_name) + return model + + + +############################################################################### +# Helper function +############################################################################### + +def _global_max_nd(xtens): + ytens = K.batch_flatten(xtens) + return K.max(ytens, 1, keepdims=True) + +def _log_layer_wrap(reg=K.epsilon()): + def _log_layer(tens): + return K.log(tens + reg) + return _log_layer + +# def _global_max_nd(x): + # return K.exp(x) + +class _VAESample(): + def __init__(self): #, nb_z): + # self.nb_z = nb_z + pass + + def sample_z(self, args): + mu, log_var = args + # shape = (K.shape(mu)[0], self.nb_z) + shape = K.shape(mu) + eps = K.random_normal(shape=shape, mean=0., stddev=1.) + return mu + K.exp(log_var / 2) * eps + + + +def _softmax(x, axis=-1, alpha=1): + """ + building on keras implementation, allow alpha parameter + + Softmax activation function. + # Arguments + x : Tensor. + axis: Integer, axis along which the softmax normalization is applied. + alpha: a value to multiply all x + # Returns + Tensor, output of softmax transformation. + # Raises + ValueError: In case `dim(x) == 1`. + """ + x = alpha * x + ndim = K.ndim(x) + if ndim == 2: + return K.softmax(x) + elif ndim > 2: + e = K.exp(x - K.max(x, axis=axis, keepdims=True)) + s = K.sum(e, axis=axis, keepdims=True) + return e / s + else: + raise ValueError('Cannot apply softmax to a tensor that is 1D') diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/plot.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/plot.py new file mode 100644 index 0000000000000000000000000000000000000000..cd27f00f450c65ac61f09d132fae2cf15f4f508e --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/plot.py @@ -0,0 +1,282 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +tensorflow/keras plot utilities for the neuron project + +If you use this code, please cite +Dalca AV, Guttag J, Sabuncu MR +Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation, +CVPR 2018 + +Contact: adalca [at] csail [dot] mit [dot] edu +License: GPLv3 +""" + +# third party +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.cm as cm +from matplotlib.colors import Normalize +from mpl_toolkits.axes_grid1 import make_axes_locatable # plotting + +def slices(slices_in, # the 2D slices + titles=None, # list of titles + cmaps=None, # list of colormaps + norms=None, # list of normalizations + do_colorbars=False, # option to show colorbars on each slice + grid=False, # option to plot the images in a grid or a single row + width=15, # width in in + show=True, # option to actually show the plot (plt.show()) + imshow_args=None): + ''' + plot a grid of slices (2d images) + ''' + + # input processing + nb_plots = len(slices_in) + for si, slice_in in enumerate(slices_in): + assert len(slice_in.shape) == 2, 'each slice has to be 2d: 2d channels' + slices_in[si] = slice_in.astype('float') + + + def input_check(inputs, nb_plots, name): + ''' change input from None/single-link ''' + assert (inputs is None) or (len(inputs) == nb_plots) or (len(inputs) == 1), \ + 'number of %s is incorrect' % name + if inputs is None: + inputs = [None] + if len(inputs) == 1: + inputs = [inputs[0] for i in range(nb_plots)] + return inputs + + titles = input_check(titles, nb_plots, 'titles') + cmaps = input_check(cmaps, nb_plots, 'cmaps') + norms = input_check(norms, nb_plots, 'norms') + imshow_args = input_check(imshow_args, nb_plots, 'imshow_args') + for idx, ia in enumerate(imshow_args): + imshow_args[idx] = {} if ia is None else ia + + # figure out the number of rows and columns + if grid: + if isinstance(grid, bool): + rows = np.floor(np.sqrt(nb_plots)).astype(int) + cols = np.ceil(nb_plots/rows).astype(int) + else: + assert isinstance(grid, (list, tuple)), \ + "grid should either be bool or [rows,cols]" + rows, cols = grid + else: + rows = 1 + cols = nb_plots + + # prepare the subplot + fig, axs = plt.subplots(rows, cols) + if rows == 1 and cols == 1: + axs = [axs] + + for i in range(nb_plots): + col = np.remainder(i, cols) + row = np.floor(i/cols).astype(int) + + # get row and column axes + row_axs = axs if rows == 1 else axs[row] + ax = row_axs[col] + + # turn off axis + ax.axis('off') + + # add titles + if titles is not None and titles[i] is not None: + ax.title.set_text(titles[i]) + + # show figure + im_ax = ax.imshow(slices_in[i], cmap=cmaps[i], interpolation="nearest", norm=norms[i], **imshow_args[i]) + + # colorbars + # http://stackoverflow.com/questions/18195758/set-matplotlib-colorbar-size-to-match-graph + if do_colorbars and cmaps[i] is not None: + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size="5%", pad=0.05) + fig.colorbar(im_ax, cax=cax) + + # clear axes that are unnecessary + for i in range(nb_plots, col*row): + col = np.remainder(i, cols) + row = np.floor(i/cols).astype(int) + + # get row and column axes + row_axs = axs if rows == 1 else axs[row] + ax = row_axs[col] + + ax.axis('off') + + # show the plots + fig.set_size_inches(width, rows/cols*width) + plt.tight_layout() + + if show: + plt.show() + + return (fig, axs) + + +def flow_legend(): + """ + show quiver plot to indicate how arrows are colored in the flow() method. + https://stackoverflow.com/questions/40026718/different-colours-for-arrows-in-quiver-plot + """ + ph = np.linspace(0, 2*np.pi, 13) + x = np.cos(ph) + y = np.sin(ph) + u = np.cos(ph) + v = np.sin(ph) + colors = np.arctan2(u, v) + + norm = Normalize() + norm.autoscale(colors) + # we need to normalize our colors array to match it colormap domain + # which is [0, 1] + + colormap = cm.winter + + plt.figure(figsize=(6, 6)) + plt.xlim(-2, 2) + plt.ylim(-2, 2) + plt.quiver(x, y, u, v, color=colormap(norm(colors)), angles='xy', scale_units='xy', scale=1) + plt.show() + + +def flow(slices_in, # the 2D slices + titles=None, # list of titles + cmaps=None, # list of colormaps + width=15, # width in in + img_indexing=True, # whether to match the image view, i.e. flip y axis + grid=False, # option to plot the images in a grid or a single row + show=True, # option to actually show the plot (plt.show()) + scale=1): # note quiver essentially draws quiver length = 1/scale + ''' + plot a grid of flows (2d+2 images) + ''' + + # input processing + nb_plots = len(slices_in) + for slice_in in slices_in: + assert len(slice_in.shape) == 3, 'each slice has to be 3d: 2d+2 channels' + assert slice_in.shape[-1] == 2, 'each slice has to be 3d: 2d+2 channels' + + def input_check(inputs, nb_plots, name): + ''' change input from None/single-link ''' + if not isinstance(inputs, (list, tuple)): + inputs = [inputs] + assert (inputs is None) or (len(inputs) == nb_plots) or (len(inputs) == 1), \ + 'number of %s is incorrect' % name + if inputs is None: + inputs = [None] + if len(inputs) == 1: + inputs = [inputs[0] for i in range(nb_plots)] + return inputs + + if img_indexing: + for si, slc in enumerate(slices_in): + slices_in[si] = np.flipud(slc) + + titles = input_check(titles, nb_plots, 'titles') + cmaps = input_check(cmaps, nb_plots, 'cmaps') + scale = input_check(scale, nb_plots, 'scale') + + # figure out the number of rows and columns + if grid: + if isinstance(grid, bool): + rows = np.floor(np.sqrt(nb_plots)).astype(int) + cols = np.ceil(nb_plots/rows).astype(int) + else: + assert isinstance(grid, (list, tuple)), \ + "grid should either be bool or [rows,cols]" + rows, cols = grid + else: + rows = 1 + cols = nb_plots + + # prepare the subplot + fig, axs = plt.subplots(rows, cols) + if rows == 1 and cols == 1: + axs = [axs] + + for i in range(nb_plots): + col = np.remainder(i, cols) + row = np.floor(i/cols).astype(int) + + # get row and column axes + row_axs = axs if rows == 1 else axs[row] + ax = row_axs[col] + + # turn off axis + ax.axis('off') + + # add titles + if titles is not None and titles[i] is not None: + ax.title.set_text(titles[i]) + + u, v = slices_in[i][...,0], slices_in[i][...,1] + colors = np.arctan2(u, v) + colors[np.isnan(colors)] = 0 + norm = Normalize() + norm.autoscale(colors) + if cmaps[i] is None: + colormap = cm.winter + else: + raise Exception("custom cmaps not currently implemented for plt.flow()") + + # show figure + ax.quiver(u, v, + color=colormap(norm(colors).flatten()), + angles='xy', + units='xy', + scale=scale[i]) + ax.axis('equal') + + # clear axes that are unnecessary + for i in range(nb_plots, col*row): + col = np.remainder(i, cols) + row = np.floor(i/cols).astype(int) + + # get row and column axes + row_axs = axs if rows == 1 else axs[row] + ax = row_axs[col] + + ax.axis('off') + + # show the plots + fig.set_size_inches(width, rows/cols*width) + plt.tight_layout() + + if show: + plt.show() + + return (fig, axs) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/utils.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ef03858788ce14b6d2cb72163d9a5aa239c7071d --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/neuron/neuron/utils.py @@ -0,0 +1,1315 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +tensorflow/keras utilities for the neuron project + +If you use this code, please cite +Dalca AV, Guttag J, Sabuncu MR +Anatomical Priors in Convolutional Networks for Unsupervised Biomedical Segmentation, +CVPR 2018 + +or for the transformation/interpolation related functions: + +Unsupervised Learning for Fast Probabilistic Diffeomorphic Registration +Adrian V. Dalca, Guha Balakrishnan, John Guttag, Mert R. Sabuncu +MICCAI 2018. + +Contact: adalca [at] csail [dot] mit [dot] edu +License: GPLv3 +""" + +# python imports +import itertools + +# third party imports +import numpy as np +from tqdm import tqdm_notebook as tqdm +from pprint import pformat + +import pytools.patchlib as pl +import pytools.timer as timer + +# local imports +import pynd.ndutils as nd + +# often changed file +from imp import reload +import tensorflow.python.keras as keras +import tensorflow.python.keras.backend as K +import tensorflow as tf +reload(pl) + +def interpn(vol, loc, interp_method='linear'): + """ + N-D gridded interpolation in tensorflow + + vol can have more dimensions than loc[i], in which case loc[i] acts as a slice + for the first dimensions + + Parameters: + vol: volume with size vol_shape or [*vol_shape, nb_features] + loc: a N-long list of N-D Tensors (the interpolation locations) for the new grid + each tensor has to have the same size (but not nec. same size as vol) + or a tensor of size [*new_vol_shape, D] + interp_method: interpolation type 'linear' (default) or 'nearest' + + Returns: + new interpolated volume of the same size as the entries in loc + + TODO: + enable optional orig_grid - the original grid points. + check out tf.contrib.resampler, only seems to work for 2D data + """ + + if isinstance(loc, (list, tuple)): + loc = tf.stack(loc, -1) + + # since loc can be a list, nb_dims has to be based on vol. + nb_dims = loc.shape[-1] + + if nb_dims != len(vol.shape[:-1]): + raise Exception("Number of loc Tensors %d does not match volume dimension %d" + % (nb_dims, len(vol.shape[:-1]))) + + if nb_dims > len(vol.shape): + raise Exception("Loc dimension %d does not match volume dimension %d" + % (nb_dims, len(vol.shape))) + + if len(vol.shape) == nb_dims: + vol = K.expand_dims(vol, -1) + + # flatten and float location Tensors + loc = tf.cast(loc, 'float32') + + if isinstance(vol.shape, (tf.Dimension, tf.TensorShape)): + volshape = vol.shape.as_list() + else: + volshape = vol.shape + + # interpolate + if interp_method == 'linear': + loc0 = tf.floor(loc) + + # clip values + max_loc = [d - 1 for d in vol.get_shape().as_list()] + clipped_loc = [tf.clip_by_value(loc[...,d], 0, max_loc[d]) for d in range(nb_dims)] + loc0lst = [tf.clip_by_value(loc0[...,d], 0, max_loc[d]) for d in range(nb_dims)] + + # get other end of point cube + loc1 = [tf.clip_by_value(loc0lst[d] + 1, 0, max_loc[d]) for d in range(nb_dims)] + locs = [[tf.cast(f, 'int32') for f in loc0lst], [tf.cast(f, 'int32') for f in loc1]] + + # compute the difference between the upper value and the original value + # differences are basically 1 - (pt - floor(pt)) + # because: floor(pt) + 1 - pt = 1 + (floor(pt) - pt) = 1 - (pt - floor(pt)) + diff_loc1 = [loc1[d] - clipped_loc[d] for d in range(nb_dims)] + diff_loc0 = [1 - d for d in diff_loc1] + weights_loc = [diff_loc1, diff_loc0] # note reverse ordering since weights are inverse of diff. + + # go through all the cube corners, indexed by a ND binary vector + # e.g. [0, 0] means this "first" corner in a 2-D "cube" + cube_pts = list(itertools.product([0, 1], repeat=nb_dims)) + interp_vol = 0 + + for c in cube_pts: + + # get nd values + # note re: indices above volumes via https://github.com/tensorflow/tensorflow/issues/15091 + # It works on GPU because we do not perform index validation checking on GPU -- it's too + # expensive. Instead we fill the output with zero for the corresponding value. The CPU + # version caught the bad index and returned the appropriate error. + subs = [locs[c[d]][d] for d in range(nb_dims)] + + # tf stacking is slow for large volumes, so we will use sub2ind and use single indexing. + # indices = tf.stack(subs, axis=-1) + # vol_val = tf.gather_nd(vol, indices) + # faster way to gather than gather_nd, because the latter needs tf.stack which is slow :( + idx = sub2ind(vol.shape[:-1], subs) + vol_val = tf.gather(tf.reshape(vol, [-1, volshape[-1]]), idx) + + # get the weight of this cube_pt based on the distance + # if c[d] is 0 --> want weight = 1 - (pt - floor[pt]) = diff_loc1 + # if c[d] is 1 --> want weight = pt - floor[pt] = diff_loc0 + wts_lst = [weights_loc[c[d]][d] for d in range(nb_dims)] + # tf stacking is slow, we we will use prod_n() + # wlm = tf.stack(wts_lst, axis=0) + # wt = tf.reduce_prod(wlm, axis=0) + wt = prod_n(wts_lst) + wt = K.expand_dims(wt, -1) + + # compute final weighted value for each cube corner + interp_vol += wt * vol_val + + else: + assert interp_method == 'nearest' + roundloc = tf.cast(tf.round(loc), 'int32') + + # clip values + max_loc = [tf.cast(d - 1, 'int32') for d in vol.shape] + roundloc = [tf.clip_by_value(roundloc[...,d], 0, max_loc[d]) for d in range(nb_dims)] + + # get values + # tf stacking is slow. replace with gather + # roundloc = tf.stack(roundloc, axis=-1) + # interp_vol = tf.gather_nd(vol, roundloc) + idx = sub2ind(vol.shape[:-1], roundloc) + interp_vol = tf.gather(tf.reshape(vol, [-1, vol.shape[-1]]), idx) + + return interp_vol + + +def resize(vol, zoom_factor, interp_method='linear'): + """ + if zoom_factor is a list, it will determine the ndims, in which case vol has to be of length ndims of ndims + 1 + + if zoom_factor is an integer, then vol must be of length ndims + 1 + + """ + + if isinstance(zoom_factor, (list, tuple)): + ndims = len(zoom_factor) + vol_shape = vol.shape[:ndims] + + assert len(vol_shape) in (ndims, ndims+1), \ + "zoom_factor length %d does not match ndims %d" % (len(vol_shape), ndims) + + else: + vol_shape = vol.shape[:-1] + ndims = len(vol_shape) + zoom_factor = [zoom_factor] * ndims + if not isinstance(vol_shape[0], int): + vol_shape = vol_shape.as_list() + + new_shape = [vol_shape[f] * zoom_factor[f] for f in range(ndims)] + new_shape = [int(f) for f in new_shape] + + # get grid for new shape + grid = volshape_to_ndgrid(new_shape) + grid = [tf.cast(f, 'float32') for f in grid] + offset = [grid[f] / zoom_factor[f] - grid[f] for f in range(ndims)] + offset = tf.stack(offset, ndims) + + # transform + return transform(vol, offset, interp_method) + + +def zoom(*args, **kwargs): + return resize(*args, **kwargs) + + +def affine_to_shift(affine_matrix, volshape, shift_center=True, indexing='ij'): + """ + transform an affine matrix to a dense location shift tensor in tensorflow + + Algorithm: + - get grid and shift grid to be centered at the center of the image (optionally) + - apply affine matrix to each index. + - subtract grid + + Parameters: + affine_matrix: ND+1 x ND+1 or ND x ND+1 matrix (Tensor) + volshape: 1xN Nd Tensor of the size of the volume. + shift_center (optional) + + Returns: + shift field (Tensor) of size *volshape x N + + TODO: + allow affine_matrix to be a vector of size nb_dims * (nb_dims + 1) + """ + + if isinstance(volshape, (tf.Dimension, tf.TensorShape)): + volshape = volshape.as_list() + + if affine_matrix.dtype != 'float32': + affine_matrix = tf.cast(affine_matrix, 'float32') + + nb_dims = len(volshape) + + if len(affine_matrix.shape) == 1: + if len(affine_matrix) != (nb_dims * (nb_dims + 1)) : + raise ValueError('transform is supposed a vector of len ndims * (ndims + 1).' + 'Got len %d' % len(affine_matrix)) + + affine_matrix = tf.reshape(affine_matrix, [nb_dims, nb_dims + 1]) + + if not (affine_matrix.shape[0] in [nb_dims, nb_dims + 1] and affine_matrix.shape[1] == (nb_dims + 1)): + raise Exception('Affine matrix shape should match' + '%d+1 x %d+1 or ' % (nb_dims, nb_dims) + \ + '%d x %d+1.' % (nb_dims, nb_dims) + \ + 'Got: ' + str(volshape)) + + # list of volume ndgrid + # N-long list, each entry of shape volshape + mesh = volshape_to_meshgrid(volshape, indexing=indexing) + mesh = [tf.cast(f, 'float32') for f in mesh] + + if shift_center: + mesh = [mesh[f] - (volshape[f]-1)/2 for f in range(len(volshape))] + + # add an all-ones entry and transform into a large matrix + flat_mesh = [flatten(f) for f in mesh] + flat_mesh.append(tf.ones(flat_mesh[0].shape, dtype='float32')) + mesh_matrix = tf.transpose(tf.stack(flat_mesh, axis=1)) # 4 x nb_voxels + + # compute locations + loc_matrix = tf.matmul(affine_matrix, mesh_matrix) # N+1 x nb_voxels + loc_matrix = tf.transpose(loc_matrix[:nb_dims, :]) # nb_voxels x N + loc = tf.reshape(loc_matrix, list(volshape) + [nb_dims]) # *volshape x N + # loc = [loc[..., f] for f in range(nb_dims)] # N-long list, each entry of shape volshape + + # get shifts and return + return loc - tf.stack(mesh, axis=nb_dims) + + +def transform(vol, loc_shift, interp_method='linear', indexing='ij'): + """ + transform (interpolation N-D volumes (features) given shifts at each location in tensorflow + + Essentially interpolates volume vol at locations determined by loc_shift. + This is a spatial transform in the sense that at location [x] we now have the data from, + [x + shift] so we've moved data. + + Parameters: + vol: volume with size vol_shape or [*vol_shape, nb_features] + loc_shift: shift volume [*new_vol_shape, N] + interp_method (default:'linear'): 'linear', 'nearest' + indexing (default: 'ij'): 'ij' (matrix) or 'xy' (cartesian). + In general, prefer to leave this 'ij' + + Return: + new interpolated volumes in the same size as loc_shift[0] + + Keyworks: + interpolation, sampler, resampler, linear, bilinear + """ + + # parse shapes + if isinstance(loc_shift.shape, (tf.Dimension, tf.TensorShape)): + volshape = loc_shift.shape[:-1].as_list() + else: + volshape = loc_shift.shape[:-1] + nb_dims = len(volshape) + + # location should be mesh and delta + mesh = volshape_to_meshgrid(volshape, indexing=indexing) # volume mesh + loc = [tf.cast(mesh[d], 'float32') + loc_shift[..., d] for d in range(nb_dims)] + + # test single + return interpn(vol, loc, interp_method=interp_method) + + +def integrate_vec(vec, time_dep=False, method='ss', **kwargs): + """ + Integrate (stationary of time-dependent) vector field (N-D Tensor) in tensorflow + + Aside from directly using tensorflow's numerical integration odeint(), also implements + "scaling and squaring", and quadrature. Note that the diff. equation given to odeint + is the one used in quadrature. + + Parameters: + vec: the Tensor field to integrate. + If vol_size is the size of the intrinsic volume, and vol_ndim = len(vol_size), + then vector shape (vec_shape) should be + [vol_size, vol_ndim] (if stationary) + [vol_size, vol_ndim, nb_time_steps] (if time dependent) + time_dep: bool whether vector is time dependent + method: 'scaling_and_squaring' or 'ss' or 'ode' or 'quadrature' + + if using 'scaling_and_squaring': currently only supports integrating to time point 1. + nb_steps: int number of steps. Note that this means the vec field gets broken + down to 2**nb_steps. so nb_steps of 0 means integral = vec. + + if using 'ode': + out_time_pt (optional): a time point or list of time points at which to evaluate + Default: 1 + init (optional): if using 'ode', the initialization method. + Currently only supporting 'zero'. Default: 'zero' + ode_args (optional): dictionary of all other parameters for + tf.contrib.integrate.odeint() + + Returns: + int_vec: integral of vector field. + Same shape as the input if method is 'scaling_and_squaring', 'ss', 'quadrature', + or 'ode' with out_time_pt not a list. Will have shape [*vec_shape, len(out_time_pt)] + if method is 'ode' with out_time_pt being a list. + + Todo: + quadrature for more than just intrinsically out_time_pt = 1 + """ + + if method not in ['ss', 'scaling_and_squaring', 'ode', 'quadrature']: + raise ValueError("method has to be 'scaling_and_squaring' or 'ode'. found: %s" % method) + + if method in ['ss', 'scaling_and_squaring']: + nb_steps = kwargs['nb_steps'] + assert nb_steps >= 0, 'nb_steps should be >= 0, found: %d' % nb_steps + + if time_dep: + svec = K.permute_dimensions(vec, [-1, *range(0, vec.shape[-1] - 1)]) + assert 2**nb_steps == svec.shape[0], "2**nb_steps and vector shape don't match" + + svec = svec/(2**nb_steps) + for _ in range(nb_steps): + svec = svec[0::2] + tf.map_fn(transform, svec[1::2,:], svec[0::2,:]) + + disp = svec[0, :] + + else: + vec = vec/(2**nb_steps) + for _ in range(nb_steps): + vec += transform(vec, vec) + disp = vec + + elif method == 'quadrature': + # TODO: could output more than a single timepoint! + nb_steps = kwargs['nb_steps'] + assert nb_steps >= 1, 'nb_steps should be >= 1, found: %d' % nb_steps + + vec = vec/nb_steps + + if time_dep: + disp = vec[...,0] + for si in range(nb_steps-1): + disp += transform(vec[...,si+1], disp) + else: + disp = vec + for _ in range(nb_steps-1): + disp += transform(vec, disp) + + else: + assert not time_dep, "odeint not implemented with time-dependent vector field" + fn = lambda disp, _: transform(vec, disp) + + # process time point. + out_time_pt = kwargs['out_time_pt'] if 'out_time_pt' in kwargs.keys() else 1 + single_out_time_pt = not isinstance(out_time_pt, (list, tuple)) + if single_out_time_pt: out_time_pt = [out_time_pt] + K_out_time_pt = K.variable([0, *out_time_pt]) + + # process initialization + if 'init' not in kwargs.keys() or kwargs['init'] == 'zero': + disp0 = vec*0 + else: + raise ValueError('non-zero init for ode method not implemented') + + # compute integration with tf.contrib.integrate.odeint + if 'ode_args' not in kwargs.keys(): kwargs['ode_args'] = {} + disp = tf.contrib.integrate.odeint(fn, disp0, K_out_time_pt, **kwargs['ode_args']) + disp = K.permute_dimensions(disp[1:len(out_time_pt)+1, :], [*range(1,len(disp.shape)), 0]) + + # return + if single_out_time_pt: + disp = disp[...,0] + + return disp + + +def volshape_to_ndgrid(volshape, **kwargs): + """ + compute Tensor ndgrid from a volume size + + Parameters: + volshape: the volume size + **args: "name" (optional) + + Returns: + A list of Tensors + + See Also: + ndgrid + """ + + isint = [float(d).is_integer() for d in volshape] + if not all(isint): + raise ValueError("volshape needs to be a list of integers") + + linvec = [tf.range(0, d) for d in volshape] + return ndgrid(*linvec, **kwargs) + + +def volshape_to_meshgrid(volshape, **kwargs): + """ + compute Tensor meshgrid from a volume size + + Parameters: + volshape: the volume size + **args: "name" (optional) + + Returns: + A list of Tensors + + See Also: + tf.meshgrid, meshgrid, ndgrid, volshape_to_ndgrid + """ + + isint = [float(d).is_integer() for d in volshape] + if not all(isint): + raise ValueError("volshape needs to be a list of integers") + + linvec = [tf.range(0, d) for d in volshape] + return meshgrid(*linvec, **kwargs) + + +def ndgrid(*args, **kwargs): + """ + broadcast Tensors on an N-D grid with ij indexing + uses meshgrid with ij indexing + + Parameters: + *args: Tensors with rank 1 + **args: "name" (optional) + + Returns: + A list of Tensors + + """ + return meshgrid(*args, indexing='ij', **kwargs) + + +def flatten(v): + """ + flatten Tensor v + + Parameters: + v: Tensor to be flattened + + Returns: + flat Tensor + """ + + return tf.reshape(v, [-1]) + + +def meshgrid(*args, **kwargs): + """ + + meshgrid code that builds on (copies) tensorflow's meshgrid but dramatically + improves runtime by changing the last step to tiling instead of multiplication. + https://github.com/tensorflow/tensorflow/blob/c19e29306ce1777456b2dbb3a14f511edf7883a8/tensorflow/python/ops/array_ops.py#L1921 + + Broadcasts parameters for evaluation on an N-D grid. + Given N one-dimensional coordinate arrays `*args`, returns a list `outputs` + of N-D coordinate arrays for evaluating expressions on an N-D grid. + Notes: + `meshgrid` supports cartesian ('xy') and matrix ('ij') indexing conventions. + When the `indexing` argument is set to 'xy' (the default), the broadcasting + instructions for the first two dimensions are swapped. + Examples: + Calling `X, Y = meshgrid(x, y)` with the tensors + ```python + x = [1, 2, 3] + y = [4, 5, 6] + X, Y = meshgrid(x, y) + # X = [[1, 2, 3], + # [1, 2, 3], + # [1, 2, 3]] + # Y = [[4, 4, 4], + # [5, 5, 5], + # [6, 6, 6]] + ``` + Args: + *args: `Tensor`s with rank 1. + **kwargs: + - indexing: Either 'xy' or 'ij' (optional, default: 'xy'). + - name: A name for the operation (optional). + Returns: + outputs: A list of N `Tensor`s with rank N. + Raises: + TypeError: When no keyword arguments (kwargs) are passed. + ValueError: When indexing keyword argument is not one of `xy` or `ij`. + """ + + indexing = kwargs.pop("indexing", "xy") + name = kwargs.pop("name", "meshgrid") + if kwargs: + key = list(kwargs.keys())[0] + raise TypeError("'{}' is an invalid keyword argument " + "for this function".format(key)) + + if indexing not in ("xy", "ij"): + raise ValueError("indexing parameter must be either 'xy' or 'ij'") + + # with ops.name_scope(name, "meshgrid", args) as name: + ndim = len(args) + s0 = (1,) * ndim + + # Prepare reshape by inserting dimensions with size 1 where needed + output = [] + for i, x in enumerate(args): + output.append(tf.reshape(tf.stack(x), (s0[:i] + (-1,) + s0[i + 1::]))) + # Create parameters for broadcasting each tensor to the full size + shapes = [tf.size(x) for x in args] + sz = [x.get_shape().as_list()[0] for x in args] + + # output_dtype = tf.convert_to_tensor(args[0]).dtype.base_dtype + + if indexing == "xy" and ndim > 1: + output[0] = tf.reshape(output[0], (1, -1) + (1,) * (ndim - 2)) + output[1] = tf.reshape(output[1], (-1, 1) + (1,) * (ndim - 2)) + shapes[0], shapes[1] = shapes[1], shapes[0] + sz[0], sz[1] = sz[1], sz[0] + + # This is the part of the implementation from tf that is slow. + # We replace it below to get a ~6x speedup (essentially using tile instead of * tf.ones()) + # TODO(nolivia): improve performance with a broadcast + # mult_fact = tf.ones(shapes, output_dtype) + # return [x * mult_fact for x in output] + for i in range(len(output)): + output[i] = tf.tile(output[i], tf.stack([*sz[:i], 1, *sz[(i+1):]])) + return output + + + +def prod_n(lst): + prod = lst[0] + for p in lst[1:]: + prod *= p + return prod + + +def sub2ind(siz, subs, **kwargs): + """ + assumes column-order major + """ + # subs is a list + assert len(siz) == len(subs), 'found inconsistent siz and subs: %d %d' % (len(siz), len(subs)) + + k = np.cumprod(siz[::-1]) + + ndx = subs[-1] + for i, v in enumerate(subs[:-1][::-1]): + ndx = ndx + v * k[i] + + return ndx + + + +def gaussian_kernel(sigma, windowsize=None, indexing='ij'): + """ + sigma will be a number of a list of numbers. + + # some guidance from my MATLAB file + https://github.com/adalca/mivt/blob/master/src/gaussFilt.m + + Parameters: + sigma: scalar or list of scalars + windowsize (optional): scalar or list of scalars indicating the shape of the kernel + + Returns: + ND kernel the same dimensiosn as the number of sigmas. + + Todo: could use MultivariateNormalDiag + """ + + if not isinstance(sigma, (list, tuple)): + sigma = [sigma] + sigma = [np.maximum(f, np.finfo(float).eps) for f in sigma] + + nb_dims = len(sigma) + + # compute windowsize + if windowsize is None: + windowsize = [np.round(f * 3) * 2 + 1 for f in sigma] + + if len(sigma) != len(windowsize): + raise ValueError('sigma and windowsize should have the same length.' + 'Got vectors: ' + str(sigma) + 'and' + str(windowsize)) + + # ok, let's get to work. + mid = [(w - 1)/2 for w in windowsize] + + # list of volume ndgrid + # N-long list, each entry of shape volshape + mesh = volshape_to_meshgrid(windowsize, indexing=indexing) + mesh = [tf.cast(f, 'float32') for f in mesh] + + # compute independent gaussians + diff = [mesh[f] - mid[f] for f in range(len(windowsize))] + exp_term = [- K.square(diff[f])/(2 * (sigma[f]**2)) for f in range(nb_dims)] + norms = [exp_term[f] - np.log(sigma[f] * np.sqrt(2 * np.pi)) for f in range(nb_dims)] + + # add an all-ones entry and transform into a large matrix + norms_matrix = tf.stack(norms, axis=-1) # *volshape x N + g = K.sum(norms_matrix, -1) # volshape + g = tf.exp(g) + g /= tf.reduce_sum(g) + + return g + + + + + + + + +def stack_models(models, connecting_node_ids=None): + """ + stacks keras models sequentially without nesting the models into layers + (the nominal behaviour in keras as of 1/13/2018 is to nest models) + This preserves the layers (i.e. does not copy layers). This means that if you modify the + original layer weights, you are automatically affecting the new stacked model. + + Parameters: + models: a list of models, in order of: [input_model, second_model, ..., final_output_model] + connecting_node_ids (optional): a list of connecting node pointers from Nth model to N+1th model + + Returns: + new stacked model pointer + """ + + output_tensors = models[0].outputs + stacked_inputs = [*models[0].inputs] + + # go through models 1 onwards and stack with current graph + for mi in range(1, len(models)): + + # prepare input nodes - a combination of + new_input_nodes = list(models[mi].inputs) + stacked_inputs_contrib = list(models[mi].inputs) + + if connecting_node_ids is None: + conn_id = list(range(len(new_input_nodes))) + assert len(new_input_nodes) == len(models[mi-1].outputs), \ + 'argument count does not match' + else: + conn_id = connecting_node_ids[mi-1] + + for out_idx, ii in enumerate(conn_id): + new_input_nodes[ii] = output_tensors[out_idx] + stacked_inputs_contrib[ii] = None + + output_tensors = mod_submodel(models[mi], new_input_nodes=new_input_nodes) + stacked_inputs = stacked_inputs + stacked_inputs_contrib + + stacked_inputs_ = [i for i in stacked_inputs if i is not None] + # check for unique, but keep order: + stacked_inputs = [] + for inp in stacked_inputs_: + if inp not in stacked_inputs: + stacked_inputs.append(inp) + new_model = keras.models.Model(stacked_inputs, output_tensors) + return new_model + + +def mod_submodel(orig_model, + new_input_nodes=None, + input_layers=None): + """ + modify (cut and/or stitch) keras submodel + + layer objects themselved will be untouched - the new model, even if it includes, + say, a subset of the previous layers, those layer objects will be shared with + the original model + + given an original model: + model stitching: given new input node(s), get output tensors of having pushed these + nodes through the model + + model cutting: given input layer (pointers) inside the model, the new input nodes + will match the new input layers, hence allowing cutting the model + + Parameters: + orig_model: original keras model pointer + new_input_nodes: a pointer to a new input node replacement + input_layers: the name of the layer in the original model to replace input nodes + + Returns: + pointer to modified model + """ + + def _layer_dependency_dict(orig_model): + """ + output: a dictionary of all layers in the orig_model + for each layer: + dct[layer] is a list of lists of layers. + """ + + out_layers = orig_model.output_layers + out_node_idx = orig_model.output_layers_node_indices + + node_list = [ol._inbound_nodes[out_node_idx[i]] for i, ol in enumerate(out_layers)] + + dct = {} + dct_node_idx = {} + while len(node_list) > 0: + node = node_list.pop(0) + + add = True + # if not empty. we need to check that we're not adding the same layers through the same node. + if len(dct.setdefault(node.outbound_layer, [])) > 0: + for li, layers in enumerate(dct[node.outbound_layer]): + if layers == node.inbound_layers and \ + dct_node_idx[node.outbound_layer][li] == node.node_indices: + add = False + break + if add: + dct[node.outbound_layer].append(node.inbound_layers) + dct_node_idx.setdefault(node.outbound_layer, []).append(node.node_indices) + # append is in place + + # add new node + for li, layer in enumerate(node.inbound_layers): + if hasattr(layer, '_inbound_nodes'): + node_list.append(layer._inbound_nodes[node.node_indices[li]]) + + return dct + + def _get_new_layer_output(layer, new_layer_outputs, inp_layers): + """ + (recursive) given a layer, get new outbound_nodes based on new inbound_nodes + + new_layer_outputs is a (reference) dictionary that we will be adding + to within the recursion stack. + """ + + if layer not in new_layer_outputs: + + if layer not in inp_layers: + raise Exception('layer %s is not in inp_layers' % layer.name) + + # for all input layers to this layer, gather their output (our input) + for group in inp_layers[layer]: + input_nodes = [None] * len(group) + for li, inp_layer in enumerate(group): + if inp_layer in new_layer_outputs: + input_nodes[li] = new_layer_outputs[inp_layer] + else: # recursive call + input_nodes[li] = _get_new_layer_output(inp_layer, new_layer_outputs, inp_layers) + + # layer call + if len(input_nodes) == 1: + new_layer_outputs[layer] = layer(*input_nodes) + else: + new_layer_outputs[layer] = layer(input_nodes) + + return new_layer_outputs[layer] + + + + # for each layer create list of input layers + inp_layers = _layer_dependency_dict(orig_model) + + # get input layers + # These layers will be 'ignored' in that they will not be called! + # instead, the outbound nodes of the layers will be the input nodes + # computed below or passed in + if input_layers is None: # if none provided, search for them + InputLayerClass = keras.engine.topology.InputLayer + input_layers = [l for l in orig_model.layers if isinstance(l, InputLayerClass)] + + else: + if not isinstance(input_layers, (tuple, list)): + input_layers = [input_layers] + for idx, input_layer in enumerate(input_layers): + # if it's a string, assume it's layer name, and get the layer pointer + if isinstance(input_layer, str): + input_layers[idx] = orig_model.get_layer(input_layer) + + # process new input nodes + if new_input_nodes is None: + input_nodes = list(orig_model.inputs) + else: + input_nodes = new_input_nodes + assert len(input_nodes) == len(input_layers) + + # initialize dictionary of layer:new_output_node + # note: the input layers are not called, instead their outbound nodes + # are assumed to be the given input nodes. If we call the nodes, we can run + # into multiple-inbound-nodes problems, or if we completely skip the layers altogether + # we have problems with multiple inbound input layers into subsequent layers + new_layer_outputs = {} + for i, input_layer in enumerate(input_layers): + new_layer_outputs[input_layer] = input_nodes[i] + + # recursively go back from output layers and request new input nodes + output_layers = [] + for layer in orig_model.layers: + if hasattr(layer, '_inbound_nodes'): + for i in range(len(layer._inbound_nodes)): + if layer.get_output_at(i) in orig_model.outputs: + output_layers.append(layer) + break + assert len(output_layers) == len(orig_model.outputs), "Number of output layers don't match" + + outputs = [None] * len(output_layers) + for li, output_layer in enumerate(output_layers): + outputs[li] = _get_new_layer_output(output_layer, new_layer_outputs, inp_layers) + + return outputs + + +def reset_weights(model, session=None): + """ + reset weights of model with the appropriate initializer. + Note: only uses "kernel_initializer" and "bias_initializer" + does not close session. + + Reference: + https://www.codementor.io/nitinsurya/how-to-re-initialize-keras-model-weights-et41zre2g + + Parameters: + model: keras model to reset + session (optional): the current session + """ + + if session is None: + session = K.get_session() + + for layer in model.layers: + reset = False + if hasattr(layer, 'kernel_initializer'): + layer.kernel.initializer.run(session=session) + reset = True + + if hasattr(layer, 'bias_initializer'): + layer.bias.initializer.run(session=session) + reset = True + + if not reset: + print('Could not find initializer for layer %s. skipping', layer.name) + + +def copy_model_weights(src_model, dst_model): + """ + copy weights from the src keras model to the dst keras model via layer names + + Parameters: + src_model: source keras model to copy from + dst_model: destination keras model to copy to + """ + + for layer in tqdm(dst_model.layers): + try: + wts = src_model.get_layer(layer.name).get_weights() + layer.set_weights(wts) + except: + print('Could not copy weights of %s' % layer.name) + continue + + +def robust_multi_gpu_model(model, gpus, verbose=True): + """ + re-work keras model for multi-gpus if number of gpus is > 1 + + Parameters: + model: keras Model + gpus: list of gpus to split to (e.g. [1, 4, 6]), or count of gpus available (e.g. 3) + Note: if given int, assume that is the count of gpus, + so if you want a single specific gpu, this function will not do that. + verbose: whether to display what happened (default: True) + + Returns: + keras model + """ + + islist = isinstance(gpus, (list, tuple)) + if (islist and len(gpus) > 1) or (not islist and gpus > 1): + count = gpus if not islist else len(gpus) + print("Returning multi-gpu (%d) model" % count) + return keras.utils.multi_gpu_model(model, count) + + else: + print("Returning keras model back (single gpu found)") + return model + + + + +def logtanh(x, a=1): + """ + log * tanh + + See Also: arcsinh + """ + return K.tanh(x) * K.log(2 + a * abs(x)) + + +def arcsinh(x, alpha=1): + """ + asignh + + See Also: logtanh + """ + return tf.asinh(x * alpha) / alpha + + + + + + + +def predict_volumes(models, + data_generator, + batch_size, + patch_size, + patch_stride, + grid_size, + nan_func=np.nanmedian, + do_extra_vol=False, # should compute vols beyond label + do_prob_of_true=False, # should compute prob_of_true vols + verbose=False): + """ + Note: we allow models to be a list or a single model. + Normally, if you'd like to run a function over a list for some param, + you can simply loop outside of the function. here, however, we are dealing with a generator, + and want the output of that generator to be consistent for each model. + + Returns: + if models isa list of more than one model: + a tuple of model entried, each entry is a tuple of: + true_label, pred_label, , , , + if models is just one model: + a tuple of + (true_label, pred_label, , , , ) + + TODO: could add prior + """ + + if not isinstance(models, (list, tuple)): + models = (models,) + + # get the input and prediction stack + with timer.Timer('predict_volume_stack', verbose): + vol_stack = predict_volume_stack(models, + data_generator, + batch_size, + grid_size, + verbose) + if len(models) == 1: + do_prior = len(vol_stack) == 4 + else: + do_prior = len(vol_stack[0]) == 4 + + # go through models and volumes + ret = () + for midx, _ in enumerate(models): + + stack = vol_stack if len(models) == 1 else vol_stack[midx] + + if do_prior: + all_true, all_pred, all_vol, all_prior = stack + else: + all_true, all_pred, all_vol = stack + + # get max labels + all_true_label, all_pred_label = pred_to_label(all_true, all_pred) + + # quilt volumes and aggregate overlapping patches, if any + args = [patch_size, grid_size, patch_stride] + label_kwargs = {'nan_func_layers':nan_func, 'nan_func_K':nan_func, 'verbose':verbose} + vol_true_label = _quilt(all_true_label, *args, **label_kwargs).astype('int') + vol_pred_label = _quilt(all_pred_label, *args, **label_kwargs).astype('int') + + ret_set = (vol_true_label, vol_pred_label) + + if do_extra_vol: + vol_input = _quilt(all_vol, *args) + ret_set += (vol_input, ) + + if do_prior: + all_prior_label, = pred_to_label(all_prior) + vol_prior_label = _quilt(all_prior_label, *args, **label_kwargs).astype('int') + ret_set += (vol_prior_label, ) + + # compute the probability of prediction and prior + # instead of quilting the probabilistic volumes and then computing the probability + # of true label, which takes a long time, we'll first compute the probability of label, + # and then quilt. This is faster, but we'll need to take median votes + if do_extra_vol and do_prob_of_true: + all_pp = prob_of_label(all_pred, all_true_label) + pred_prob_of_true = _quilt(all_pp, *args, **label_kwargs) + ret_set += (pred_prob_of_true, ) + + if do_prior: + all_pp = prob_of_label(all_prior, all_true_label) + prior_prob_of_true = _quilt(all_pp, *args, **label_kwargs) + + ret_set += (prior_prob_of_true, ) + + ret += (ret_set, ) + + if len(models) == 1: + ret = ret[0] + + # return + return ret + + +def predict_volume_stack(models, + data_generator, + batch_size, + grid_size, + verbose=False): + """ + predict all the patches in a volume + + requires batch_size to be a divisor of the number of patches (prod(grid_size)) + + Note: we allow models to be a list or a single model. + Normally, if you'd like to run a function over a list for some param, + you can simply loop outside of the function. here, however, we are dealing with a generator, + and want the output of that generator to be consistent for each model. + + Returns: + if models isa list of more than one model: + a tuple of model entried, each entry is a tuple of: + all_true, all_pred, all_vol, + if models is just one model: + a tuple of + all_true, all_pred, all_vol, + """ + + if not isinstance(models, (list, tuple)): + models = (models,) + + # compute the number of batches we need for one volume + # we need the batch_size to be a divisor of nb_patches, + # in order to loop through batches and form full volumes + nb_patches = np.prod(grid_size) + # assert np.mod(nb_patches, batch_size) == 0, \ + # "batch_size %d should be a divisor of nb_patches %d" %(batch_size, nb_patches) + nb_batches = ((nb_patches - 1) // batch_size) + 1 + + # go through the patches + batch_gen = tqdm(range(nb_batches)) if verbose else range(nb_batches) + for batch_idx in batch_gen: + sample = next(data_generator) + nb_vox = np.prod(sample[1].shape[1:-1]) + do_prior = isinstance(sample[0], (list, tuple)) + + # pre-allocate all the data + if batch_idx == 0: + nb_labels = sample[1].shape[-1] + all_vol = [np.zeros((nb_patches, nb_vox)) for f in models] + all_true = [np.zeros((nb_patches, nb_vox * nb_labels)) for f in models] + all_pred = [np.zeros((nb_patches, nb_vox * nb_labels)) for f in models] + all_prior = [np.zeros((nb_patches, nb_vox * nb_labels)) for f in models] + + # get in_vol, y_true, y_pred + for idx, model in enumerate(models): + # with timer.Timer('prediction', verbose): + pred = model.predict(sample[0]) + assert pred.shape[0] == batch_size, \ + "batch size mismatch. sample has batch size %d, given batch size is %d" %(pred.shape[0], batch_size) + input_batch = sample[0] if not do_prior else sample[0][0] + + # compute batch range + batch_start = batch_idx * batch_size + batch_end = np.minimum(batch_start + batch_size, nb_patches) + batch_range = np.arange(batch_start, batch_end) + batch_vox_idx = batch_end-batch_start + + # update stacks + all_vol[idx][batch_range, :] = K.batch_flatten(input_batch)[0:batch_vox_idx, :] + all_true[idx][batch_range, :] = K.batch_flatten(sample[1])[0:batch_vox_idx, :] + all_pred[idx][batch_range, :] = K._batch_flatten(pred)[0:batch_vox_idx, :] + if do_prior: + all_prior[idx][batch_range, :] = K.batch_flatten(sample[0][1])[0:batch_vox_idx, :] + + # reshape probabilistic answers + for idx, _ in enumerate(models): + all_true[idx] = np.reshape(all_true[idx], [nb_patches, nb_vox, nb_labels]) + all_pred[idx] = np.reshape(all_pred[idx], [nb_patches, nb_vox, nb_labels]) + if do_prior: + all_prior[idx] = np.reshape(all_prior[idx], [nb_patches, nb_vox, nb_labels]) + + # prepare output tuple + ret = () + for midx, _ in enumerate(models): + if do_prior: + ret += ((all_true[midx], all_pred[midx], all_vol[midx], all_prior[midx]), ) + else: + ret += ((all_true[midx], all_pred[midx], all_vol[midx]), ) + + if len(models) == 1: + ret = ret[0] + return ret + + +def prob_of_label(vol, labelvol): + """ + compute the probability of the labels in labelvol in each of the volumes in vols + + Parameters: + vol (float numpy array of dim (nd + 1): volume with a prob dist at each voxel in a nd vols + labelvol (int numpy array of dim nd): nd volume of labels + + Returns: + nd volume of probabilities + """ + + # check dimensions + nb_dims = np.ndim(labelvol) + assert np.ndim(vol) == nb_dims + 1, "vol dimensions do not match [%d] vs [%d]" % (np.ndim(vol)-1, nb_dims) + shp = vol.shape + nb_voxels = np.prod(shp[0:nb_dims]) + nb_labels = shp[-1] + + # reshape volume to be [nb_voxels, nb_labels] + flat_vol = np.reshape(vol, (nb_voxels, nb_labels)) + + # normalize accross second dimension + rows_sums = flat_vol.sum(axis=1) + flat_vol_norm = flat_vol / rows_sums[:, np.newaxis] + + # index into the flattened volume + idx = list(range(nb_voxels)) + v = flat_vol_norm[idx, labelvol.flat] + return np.reshape(v, labelvol.shape) + + +def next_pred_label(model, data_generator, verbose=False): + """ + predict the next sample batch from the generator, and compute max labels + return sample, prediction, max_labels + """ + sample = next(data_generator) + with timer.Timer('prediction', verbose): + pred = model.predict(sample[0]) + sample_input = sample[0] if not isinstance(sample[0], (list, tuple)) else sample[0][0] + max_labels = pred_to_label(sample_input, pred) + return (sample, pred) + max_labels + +def next_label(model, data_generator): + """ + predict the next sample batch from the generator, and compute max labels + return max_labels + """ + batch_proc = next_pred_label(model, data_generator) + return (batch_proc[2], batch_proc[3]) + +def sample_to_label(model, sample): + """ + redict a sample batch and compute max labels + return max_labels + """ + # predict output for a new sample + res = model.predict(sample[0]) + # return + return pred_to_label(sample[1], res) + +def pred_to_label(*y): + """ + return the true and predicted labels given true and predicted nD+1 volumes + """ + # compute resulting volume(s) + return tuple(np.argmax(f, -1).astype(int) for f in y) + +def next_vol_pred(model, data_generator, verbose=False): + """ + get the next batch, predict model output + + returns (input_vol, y_true, y_pred, ) + """ + + # batch to input, output and prediction + sample = next(data_generator) + with timer.Timer('prediction', verbose): + pred = model.predict(sample[0]) + data = (sample[0], sample[1], pred) + if isinstance(sample[0], (list, tuple)): # if given prior, might be a list + data = (sample[0][0], sample[1], pred, sample[0][1]) + + return data + + + + + +############################################################################### +# functions from some external source +############################################################################### + +def batch_gather(reference, indices): + """ + C+P From Keras pull request https://github.com/keras-team/keras/pull/6377/files + + Batchwise gathering of row indices. + + The numpy equivalent is `reference[np.arange(batch_size), indices]`, where + `batch_size` is the first dimension of the reference tensor. + + # Arguments + reference: A tensor with ndim >= 2 of shape. + (batch_size, dim1, dim2, ..., dimN) + indices: A 1d integer tensor of shape (batch_size) satisfying + 0 <= i < dim2 for each element i. + + # Returns + The selected tensor with shape (batch_size, dim2, ..., dimN). + + # Examples + 1. If reference is `[[3, 5, 7], [11, 13, 17]]` and indices is `[2, 1]` + then the result is `[7, 13]`. + + 2. If reference is + ``` + [[[2, 3], [4, 5], [6, 7]], + [[10, 11], [12, 13], [16, 17]]] + ``` + and indices is `[2, 1]` then the result is `[[6, 7], [12, 13]]`. + """ + batch_size = K.shape(reference)[0] + indices = tf.stack([tf.range(batch_size), indices], axis=1) + return tf.gather_nd(reference, indices) + + +############################################################################### +# helper functions +############################################################################### + +def _concat(lists, dim): + if lists[0].size == 0: + lists = lists[1:] + + return np.concatenate(lists, dim) + +def _quilt(patches, patch_size, grid_size, patch_stride, verbose=False, **kwargs): + assert len(patches.shape) >= 2, "patches has bad shape %s" % pformat(patches.shape) + + # reshape to be [nb_patches x nb_vox] + patches = np.reshape(patches, (patches.shape[0], -1, 1)) + + # quilt + quilted_vol = pl.quilt(patches, patch_size, grid_size, patch_stride=patch_stride, **kwargs) + assert quilted_vol.ndim == len(patch_size), "problem with dimensions after quilt" + + # return + return quilted_vol + + +# TO MOVE (numpy softmax) +def softmax(x, axis): + """ + softmax of a numpy array along a given dimension + """ + + return np.exp(x) / np.sum(np.exp(x), axis=axis, keepdims=True) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/.gitignore b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..341e31ecbc6b4128147af1fbeea6491b6b52ad04 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.idea \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/__init__.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0c598b6247e22148fc8e37bc34d4795d45a86286 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import ndutils +from . import segutils \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/imutils.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/imutils.py new file mode 100644 index 0000000000000000000000000000000000000000..be7c1ec1b49a184c4f43d6a4af2734e500b7c438 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/imutils.py @@ -0,0 +1,39 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' image utilities ''' + +from numpy import np + +def gray2color(gray, color): + ''' + transform a gray image (2d array) to a color image given the color (1x3 vector) + untested + ''' + + return np.concatenate((gray * c for c in color), 2) \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/ndutils.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/ndutils.py new file mode 100644 index 0000000000000000000000000000000000000000..50c5e85cbdc611e4635261ce2d86be814f1a74e9 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/ndutils.py @@ -0,0 +1,464 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Utilities for nd (n-dimensional) arrays +Tested on Python 3.5 + +Contact: adalca@csail.mit.edu +""" + +import builtins +import numpy as np +import scipy as sp +import scipy.ndimage +from scipy.spatial import ConvexHull + + +def boundingbox(bwvol): + """ + bounding box coordinates of a nd volume + + Parameters + ---------- + vol : nd array + the binary (black/white) array for which to compute the boundingbox + + Returns + ------- + boundingbox : 1-by-(nd*2) array + [xstart ystart ... xend yend ...] + """ + + # find indices where bwvol is True + idx = np.where(bwvol) + + # get the starts + starts = [np.min(x) for x in idx] + + # get the ends + ends = [np.max(x) for x in idx] + + # concatinate [starts, ends] + return np.concatenate((starts, ends), 0) + + + +def bwdist(bwvol): + """ + positive distance transform from positive entries in logical image + + Parameters + ---------- + bwvol : nd array + The logical volume + + Returns + ------- + possdtrf : nd array + the positive distance transform + + See Also + -------- + bw2sdtrf + """ + + # reverse volume to run scipy function + revbwvol = np.logical_not(bwvol) + + # get distance + return scipy.ndimage.morphology.distance_transform_edt(revbwvol) + + + +def bw2sdtrf(bwvol): + """ + computes the signed distance transform from the surface between the + binary True/False elements of logical bwvol + + Note: the distance transform on either side of the surface will be +1/-1 + - i.e. there are no voxels for which the dst should be 0. + + Runtime: currently the function uses bwdist twice. If there is a quick way to + compute the surface, bwdist could be used only once. + + Parameters + ---------- + bwvol : nd array + The logical volume + + Returns + ------- + sdtrf : nd array + the signed distance transform + + See Also + -------- + bwdist + """ + + # get the positive transform (outside the positive island) + posdst = bwdist(bwvol) + + # get the negative transform (distance inside the island) + notbwvol = np.logical_not(bwvol) + negdst = bwdist(notbwvol) + + # combine the positive and negative map + return posdst * notbwvol - negdst * bwvol + + +def bw_convex_hull(bwvol): + # transform bw to mesh. + grid = volsize2ndgrid(bwvol.shape) + # get the 1 points + q = np.concatenate([grid[d].flat for d in bwvol.ndims], 1) + return q + +def bw2contour(bwvol, type='both', thr=1.01): + """ + computes the contour of island(s) on a nd logical volume + + Parameters + ---------- + bwvol : nd array + The logical volume + type : optional string + since the contour is drawn on voxels, it can be drawn on the inside + of the island ('inner'), outside of the island ('outer'), or both + ('both' - default) + + Returns + ------- + contour : nd array + the contour map of the same size of the input + + See Also + -------- + bwdist, bw2dstrf + """ + + # obtain a signed distance transform for the bw volume + sdtrf = bw2sdtrf(bwvol) + + if type == 'inner': + return np.logical_and(sdtrf <= 0, sdtrf > -thr) + elif type == 'outer': + return np.logical_and(sdtrf >= 0, sdtrf < thr) + else: + assert type == 'both', 'type should only be inner, outer or both' + return np.abs(sdtrf) < thr + + +def ndgrid(*args, **kwargs): + """ + Disclaimer: This code is taken directly from the scitools package [1] + Since at the time of writing scitools predominantly requires python 2.7 while we work with 3.5+ + To avoid issues, we copy the quick code here. + + Same as calling ``meshgrid`` with *indexing* = ``'ij'`` (see + ``meshgrid`` for documentation). + """ + kwargs['indexing'] = 'ij' + return np.meshgrid(*args, **kwargs) + + +def volsize2ndgrid(volsize): + """ + return the dense nd-grid for the volume with size volsize + essentially return the ndgrid fpr + """ + ranges = [np.arange(e) for e in volsize] + return ndgrid(*ranges) + + +def bw_sphere(volshape, rad, loc=None): + """ + compute a logical (black/white) image of a sphere + """ + + # if the location is not given, use the center of the volume. + if loc is None: + loc = 1.0 * (np.array(volshape)-1) / 2 + assert len(loc) == len(volshape), \ + 'Location (%d) and volume dimensions (%d) do not match' % (len(loc), len(volshape)) + + + # compute distances between each location in the volume and ``loc`` + volgrid = volsize2ndgrid(volshape) + dst = [np.square(loc[d] - volgrid[d]) for d in range(len(volshape))] + dst = np.sqrt(np.sum(dst, 0)) + + # draw the sphere + return dst <= rad + + +def volcrop(vol, new_vol_size=None, start=None, end=None, crop=None): + """ + crop a nd volume. + + Parameters + ---------- + vol : nd array + the nd-dimentional volume to crop. If only specified parameters, is returned intact + new_vol_size : nd vector, optional + the new size of the cropped volume + crop : nd tuple, optional + either tuple of integers or tuple of tuples. + If tuple of integers, will crop that amount from both sides. + if tuple of tuples, expect each inner tuple to specify (crop from start, crop from end) + start : int, optional + start of cropped volume + end : int, optional + end of cropped volume + + Returns + ------ + cropped_vol : nd array + """ + + vol_size = np.asarray(vol.shape) + + # check which parameters are passed + passed_new_vol_size = new_vol_size is not None + passed_start = start is not None + passed_end = end is not None + passed_crop = crop is not None + + # from whatever is passed, we want to obtain start and end. + if passed_start and passed_end: + assert not (passed_new_vol_size or passed_crop), \ + "If passing start and end, don't pass anything else" + + elif passed_new_vol_size: + # compute new volume size and crop_size + assert not passed_crop, "Cannot use both new volume size and crop info" + + # compute start and end + if passed_start: + assert not passed_end, \ + "When giving passed_new_vol_size, cannot pass both start and end" + end = start + new_vol_size + + elif passed_end: + assert not passed_start, \ + "When giving passed_new_vol_size, cannot pass both start and end" + start = end - new_vol_size + + else: # none of crop_size, crop, start or end are passed + mid = np.asarray(vol_size) // 2 + start = mid - (new_vol_size // 2) + end = start + new_vol_size + + elif passed_crop: + assert not (passed_start or passed_end or new_vol_size), \ + "Cannot pass both passed_crop and start or end or new_vol_size" + + if isinstance(crop[0], (list, tuple)): + end = vol_size - [val[1] for val in crop] + start = [val[0] for val in crop] + else: + end = vol_size - crop + start = crop + + elif passed_start: # nothing else is passed + end = vol_size + + else: + assert passed_end + start = vol_size * 0 + + # get indices. Since we want this to be an nd-volume crop function, we + # idx = [] + # for i in range(len(end)): + # idx.append(slice(start[i], end[i])) + idx = range(start, end) + + return vol[np.ix_(*idx)] + + +def slice(*args): + """ + slice([start], end [,step]) + nd version of slice, where each arg can be a vector of the same length + + Parameters: + [start] (vector): the start + + """ + + # if passed in scalars call the built-in range + if not isinstance(args[0], (list, tuple, np.ndarray)): + return builtins.slice(*args) + + start, end, step = _prep_range(*args) + + # prepare + idx = [slice(start[i], end[i], step[i]) for i in range(len(end))] + return idx + +def range(*args): + """ + range([start], end [,step]) + nd version of range, where each arg can be a vector of the same length + + Parameters: + [start] (vector): the start + + """ + + # if passed in scalars call the built-in range + if not isinstance(args[0], (list, tuple, np.ndarray)): + return np.arange(*args) + + start, end, step = _prep_range(*args) + + # prepare + idx = [range(start[i], end[i], step[i]) for i in range(len(end))] + return idx + + +def arange(*args): + """ + aange([start], end [,step]) + nd version of arange, where each arg can be a vector of the same length + + Parameters: + [start] (vector): the start + + """ + + # if passed in scalars call the built-in range + if not isinstance(args[0], (list, tuple, np.ndarray)): + return builtins.range(*args) + + start, end, step = _prep_range(*args) + + # prepare + idx = [np.arange(start[i], end[i], step[i]) for i in range(len(end))] + return idx + + + +def axissplit(arr, axis): + """ + Split a nd volume along an exis into n volumes, where n is the size of the axis dim. + + Parameters + ---------- + arr : nd array + array to split + axis : integer + indicating axis to split + + Output + ------ + outarr : 1-by-n array + where n is the size of the axis dim in original volume. + each entry is a sub-volume of the original volume + + See also numpy.split() + """ + nba = arr.shape[axis] + return np.split(arr, nba, axis=axis) + + + + +def sub2ind(arr, size, **kwargs): + """ + similar to MATLAB's sub2ind + + Note default order is C-style, not F-style (Fortran/MATLAB) + """ + return np.ravel_multi_index(arr, size, **kwargs) + + +def ind2sub(indices, size, **kwargs): + """ + similar to MATLAB's ind2sub + + Note default order is C-style, not F-style (Fortran/MATLAB) + """ + return np.unravel_index(indices, size, **kwargs) + + +def centroid(im): + """ + compute centroid of a probability ndimage in 0/1 + """ + volgrid = volsize2ndgrid(im.shape) + prob = [np.array(im) * np.array(volgrid[d]) for d in range(len(im.shape))] + return [np.sum(p.flat) / np.sum(im.shape) for p in prob] + + + +def ind2sub_entries(indices, size, **kwargs): + """ + returns a nb_entries -by- nb_dims (essentially the transpose of ind2sub) + + somewhat similar to MATLAB's ind2subvec + https://github.com/adalca/mgt/blob/master/src/ind2subvec.m + + Note default order is C-style, not F-style (Fortran/MATLAB) + """ + sub = ind2sub(np.array(indices).flatten(), size, **kwargs) + subvec = np.vstack(sub).transpose() + # Warning this might be F-style-like stacking... it's a bit confusing + return subvec + +############################################################################### +# internal +############################################################################### + +def _prep_range(*args): + """ + _prep_range([start], end [,step]) + prepare the start, end and step for range and arange + + Parameters: + [start] (vector): the start + + """ + + # prepare the start, step and end + step = np.ones(len(args[0]), 'int') + if len(args) == 1: + end = args[0] + start = np.zeros(len(end), 'int') + elif len(args) == 2: + assert len(args[0]) == len(args[1]), "argument vectors do not match" + start, end = args + elif len(args) == 3: + assert len(args[0]) == len(args[1]), "argument vectors do not match" + assert len(args[0]) == len(args[2]), "argument vectors do not match" + start, end, step = args + else: + raise ValueError('unknown arguments') + + return (start, end, step) \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/segutils.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/segutils.py new file mode 100644 index 0000000000000000000000000000000000000000..67ab9220f51bf8509e0b4cd4ee2cbe314b033108 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/pynd/segutils.py @@ -0,0 +1,150 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +nd segmentation (label map) utilities + +Contact: adalca@csail.mit.edu +''' + +import numpy as np +from . import ndutils as nd + +def seg2contour(seg, exclude_zero=True, contour_type='inner', thickness=1): + ''' + transform nd segmentation (label maps) to contour maps + + Parameters + ---------- + seg : nd array + volume of labels/segmentations + exclude_zero : optional logical + whether to exclude the zero label. + default True + contour_type : string + where to draw contour voxels relative to label 'inner','outer', or 'both' + + Output + ------ + con : nd array + nd array (volume) of contour maps + + See Also + -------- + seg_overlap + ''' + + # extract unique labels + labels = np.unique(seg) + if exclude_zero: + labels = np.delete(labels, np.where(labels == 0)) + + # get the contour of each label + contour_map = seg * 0 + for lab in labels: + + # extract binary label map for this label + label_map = seg == lab + + # extract contour map for this label + thickness = thickness + 0.01 + label_contour_map = nd.bw2contour(label_map, type=contour_type, thr=thickness) + + # assign contour to this label + contour_map[label_contour_map] = lab + + return contour_map + + + +def seg_overlap(vol, seg, do_contour=True, do_rgb=True, cmap=None, thickness=1.0): + ''' + overlap a nd volume and nd segmentation (label map) + + do_contour should be None, boolean, or contour_type from seg2contour + + not well tested yet. + ''' + + # compute contours for each label if necessary + if do_contour is not None and do_contour is not False: + if not isinstance(do_contour, str): + do_contour = 'inner' + seg = seg2contour(seg, contour_type=do_contour, thickness=thickness) + + # compute a rgb-contour map + if do_rgb: + if cmap is None: + nb_labels = np.max(seg).astype(int) + 1 + colors = np.random.random((nb_labels, 3)) * 0.5 + 0.5 + colors[0, :] = [0, 0, 0] + else: + colors = cmap[:, 0:3] + + olap = colors[seg.flat, :] + sf = seg.flat == 0 + for d in range(3): + olap[sf, d] = vol.flat[sf] + olap = np.reshape(olap, vol.shape + (3, )) + + else: + olap = seg + olap[seg == 0] = vol[seg == 0] + + return olap + + +def seg_overlay(vol, seg, do_rgb=True, seg_wt=0.5, cmap=None): + ''' + overlap a nd volume and nd segmentation (label map) + + not well tested yet. + ''' + + # compute contours for each label if necessary + + # compute a rgb-contour map + if do_rgb: + if cmap is None: + nb_labels = np.max(seg) + 1 + colors = np.random.random((nb_labels, 3)) * 0.5 + 0.5 + colors[0, :] = [0, 0, 0] + else: + colors = cmap[:, 0:3] + + seg_flat = colors[seg.flat, :] + seg_rgb = np.reshape(seg_flat, vol.shape + (3, )) + + # get the overlap image + olap = seg_rgb * seg_wt + np.expand_dims(vol, -1) * (1-seg_wt) + + else: + olap = seg * seg_wt + vol * (1-seg_wt) + + return olap + diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/readme.md b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..a2faa8a635d5025e3f09d491f1e65ab7a4f8e18a --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pynd-lib/readme.md @@ -0,0 +1,2 @@ +# ND utilities +Python Library for ND (n-dimensional) array operations \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/.gitignore b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5877f8ca2af300f760e0b99843a7045d8dd8af57 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/.gitignore @@ -0,0 +1,50 @@ +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# python +__pycache__ \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/__init__.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f0ba57f2e7eb818d7d396526f60ea1969dc1fe0d --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import iniparse +from . import patchlib +from . import timer +from . import plotting diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/iniparse.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/iniparse.py new file mode 100644 index 0000000000000000000000000000000000000000..20de59cefaf8d380cbbddfcef8a50555bf692678 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/iniparse.py @@ -0,0 +1,239 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +very simple ini parser and tools + +tested on python 3.6 + +contact: adalca at csail.mit.edu + +TODO: see + from collections import namedtuple + instead of Struct +""" + +# built-in modules +# we'll need python's ini parser: 'configparser' +import configparser + +def ini_to_struct(file): + """ + very simple ini parser that expands on configparser + tries to cast values from string whereever possible + parsed data ini can be accessed with + + data = ini_to_struct(file) + value = data.section.key + + does not support hierarchical sections + + Parameters: + file: string full filename of the ini file. + + Returns: + stuct: a Struct that allows ini data to be access in the manner of data.section.key + """ + + # read the file via config. + conf = configparser.ConfigParser() + confout = conf.read(file) + assert len(confout) > 0, 'Cannot read file %s ' % file + + # prepare the Struct + strct = Struct() + + # go through the sections in the ini file + for sec in conf.sections(): + + # each section is its own struct + secstrct = Struct() + + # go through the keys + for key in conf[sec]: + val = conf[sec][key] + + # try to cast the data + ret, done = str_convert_single(val) + + # if couldn't cast, try a comma/whitespace separated list + if not done: + lst = str_to_list(val) + + # if the size of the list is 1, we didn't achieve anything + if len(lst) == 1: + ret = lst[0] # still not done + + # if we actually get a list, only keep it if we can cast its elements to something + # otherwise keep the entry as an entire string + else: + # make sure all elements in the list convert to something + done = all([str_convert_single(v)[1] for v in lst]) + if done: + ret = [str_convert_single(v)[0] for v in lst] + + # defeated, accept the entry as just a simple string... + if not done: + ret = val # accept string + + # assign secstrct.key = ret + setattr(secstrct, key, ret) + + # assign strct.sec = secstrct + setattr(strct, sec, secstrct) + + return strct + + +class Struct(): + """ + a simple struct class to allow for the following syntax: + data = Struct() + data.foo = 'bar' + """ + + def __str__(self): + return self.__dict__.__str__() + + +def str_to_none(val): + """ + cast a string to a None + + Parameters: + val: the string to cast + + Returns: + (casted_val, success) + casted val: the casted value if successful, or None + success: None if casting was successful + """ + if val == 'None': + return (None, True) + else: + return (None, False) + + +def str_to_type(val, ctype): + """ + cast a string to a type (e.g. int('8')), with try/except + do *not* use for bool casting, instead see str_to_bull + + Parameters: + val: the string to cast + + Returns: + (casted_val, success) + casted val: the casted value if successful, or None + success: bool if casting was successful + """ + assert ctype is not bool, 'use str_to_bull() for casting to bool' + + ret = None + success = True + try: + ret = ctype(val) + except ValueError: + success = False + return (ret, success) + + +def str_to_bool(val): + """ + cast a string to a bool + + Parameters: + val: the string to cast + + Returns: + (casted_val, success) + casted val: the casted value if successful, or None + success: bool if casting was successful + """ + if val == 'True': + return (True, True) + elif val == 'False': + return (False, True) + else: + return (None, False) + + +def str_to_list(val): + """ + Split a string to a list of elements, where elements are separated by whitespace or commas + Leading/ending parantheses are stripped. + + Returns: + val: the string to split + + Returns: + casted_dst: the casted list + """ + val = val.replace('[', '') + val = val.replace('(', '') + val = val.replace(']', '') + val = val.replace(')', '') + + if ',' in val: + lst = val.split(',') + else: + lst = val.split() + + return lst + + +def str_convert_single(val): + """ + try to cast a string to an int, float or bool (in that order) + + Parameters: + val: the string to cast + + Returns: + (casted_val, success) + casted val: the casted value if successful, or None + success: bool if casting was successful + """ + val = val.strip() + # try int + ret, done = str_to_type(val, int) + + # try float + if not done: + ret, done = str_to_type(val, float) + + # try bool + if not done: + ret, done = str_to_bool(val) + + # try None + if not done: + ret, done = str_to_none(val) + + return (ret, done) + diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/patchlib.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/patchlib.py new file mode 100644 index 0000000000000000000000000000000000000000..9688f4988ea8115ae30a20b71363d1d72bac073e --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/patchlib.py @@ -0,0 +1,479 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +patchlib (python version) + +Library for working with N-D patches. +Modelled after the MATLAB patchlib (https://github.com/adalca/patchlib) +""" + +# built-in +import sys +from pprint import pformat +from random import shuffle +import random + + +# third party +import numpy as np + +# local +import pynd.ndutils as nd +from imp import reload +reload(nd) + + + + +def quilt(patches, + patch_size, + grid_size, + patch_stride=1, + nan_func_layers=np.nanmean, + nan_func_K=np.nanmean): + """ + quilt (merge) or reconstruct volume from patch indexes in library + + TODO: allow patches to be generator + + Parameters: + patches: matrix [N x V x K], with patches(i, :, 1:K) + indicates K patch candidates at location i (e.g. the result of a 3-nearest + neightbours search). V = prod(patch_size); N = prod(grid_size) + patch_size: vector indicating the patch size + grid_size or target_size: vector indicating the grid size in each dimension + OR + specification of the target image size instead of the grid_size + patch_stride (optional, default:1): patch stride (spacing), default is 1 (sliding window) + nan_func_layers (optional): function to compute accross stack layers. default: np.nanmean + nan_func_K (optional): function to compute accross K (nd+1th dim). default: np.nanmean + + Returns: + quilt_img: the quilted nd volume + """ + + # input checks + assert patches.ndim == 2 or patches.ndim == 3, 'patches should be [NxV] or [NxVxK]' + assert patches.shape[1] == np.prod(patch_size), \ + "patches V (%d) does not match patch size V (%d)" % (patches.shape[1], np.prod(patch_size)) + nb_dims = len(patch_size) + + # stack patches + patch_stack = stack(patches, patch_size, grid_size, patch_stride) + + # quilt via nan_funs + quilted_vol_k = nan_func_layers(patch_stack, 0) + quilted_vol = nan_func_K(quilted_vol_k, nb_dims) + assert quilted_vol.ndim == len(patch_size), "patchlib: problem with dimensions after quilt" + + # done, yey! time to celebrate - maybe visualize the quilted volume? + return quilted_vol + + +def stack(patches, patch_size, grid_size, patch_stride=1, nargout=1): + """ + Stack (gridded) patches in layer structure. + + Together, patch_size, grid_size and the patch overlap (see below), indicate + how the patches will be layed out and what the target layer size will be. For more + information about the interplay between patch_size, grid_size and patchOverlap, see + patchlib.grid. + + TODO: allow patches to be generator + + Parameters: + patches: matrix [N x V x K], with patches(i, :, 1:K) + indicates K patch candidates at location i (e.g. the result of a 3-nearest + neightbours search). V = prod(patch_size); N = prod(grid_size) + patch_size: vector indicating the patch size + grid_size or target_size: vector indicating the grid size in each dimension + OR + specification of the target image size instead of the grid_size + patch_stride (optional, default:1): patch stride (spacing), default is 1 (sliding window) + nargout (optional, default:1): the number of arguments to output + + Returns: + layers: a [nb_layers x target_size x K] array, with nb_layers that are the size of + the desired target (i.e. once the patches are positioned to fit the grid). The + first layer, essentially stacks the first patch, then the next non-overlapping patch, + and so on. The second layer takes the first non-stacked patch, and then the next + non-overlapping patch, and so on until we run out of patches. + idxmat (if nargout >= 2): also returns a matrix the same size as + 'layers' containing linear indexes into the inputted patches matrix. This is useful, + for example, to create a layer structure of patch weights to match the patches + layer structure. idxmat is [2 x N x targetSize x K], with idxmat[1, :] giving patch + ids, and idxmat[2, :] giving voxel ids + p_layer_idx (if nargout == 3): a [V x 1] vector indicating the layer index of each input + patch + + See Also: + grid(), quilt() + + See example in patchlib.quilt code. + + Contact: {adalca,klbouman}@csail.mit.edu + """ + +# assert np.all(np.mod(patch_size, 2) == 1), "patch size is not odd" + K = patches.shape[2] if len(patches.shape) > 2 else 1 + + # compute the input target_size and target + if np.prod(grid_size) == patches.shape[0]: # given the number of patches in the grid + target_size = grid2volsize(grid_size, patch_size, patch_stride=patch_stride) + else: + target_size = grid_size + + # compute the grid indexes (and check that the target size matches) + [grid_idx, target_size_chk] = grid(target_size, patch_size, patch_stride, nargout=2) + assert np.all(target_size == target_size_chk), 'Target does not match the provided target size' + + # prepare subscript and index vectors + grid_sub = nd.ind2sub_entries(grid_idx, target_size) + all_idx = list(range(grid_idx.size)) + + # get index of layer location so that patches don't overlap + # we do this by computing the modulo of the patch start location + # with respect to the patch size. This won't be optimal yet, but we'll + # eliminate any layers with no patches after + mod_sub = np.array([_mod_base(g, patch_size) for g in grid_sub]).transpose() + patch_payer_idx = nd.sub2ind(mod_sub, patch_size) + + # initiate the votes layer structure + layer_ids = np.unique(patch_payer_idx) + nb_layers = len(layer_ids) + layers = np.empty([nb_layers, *target_size, K]) + layers[:] = np.NAN + + # prepare input matching matrix + if nargout >= 2: + idxmat = np.empty([2, nb_layers, *target_size, K]) + idxmat[:] = np.NAN + + # go over each layer index + for layer_idx in range(nb_layers): + # get patches attributed to this layer + patch_id_in_layer = np.where(patch_payer_idx == layer_ids[layer_idx]) + + # prepare the layers + layer_stack = np.empty([*target_size, K]) + layer_stack[:] = np.NAN + if nargout >= 2: + layer_idxmat = np.nan([2, *target_size, K]) + + # go thorugh each patch location for patches in this layer + for pidx in patch_id_in_layer[0]: + + # extract the patches + localpatches = np.squeeze(patches[pidx, :]) + patch = np.reshape(localpatches, [*patch_size, K]) + + # put the patches in the layers + sub = [*grid_sub[pidx, :], 0] + endsub = np.array(sub) + np.array([*patch_size, K]) + rge = nd.slice(sub, endsub) + layer_stack[rge] = patch + + # update input matching matrix + if nargout >= 2: + # the linear index of the patch in the grid + locidx = np.ones([2, *patch_size, K]) * all_idx[pidx] + locidx[1, :] = np.matlib.repmat(list(range(np.prod(patch_size))), 1, K) + layer_idxmat[rge] = locidx + + # update layer + layers[layer_idx, :] = np.reshape(layer_stack.flatten(), [*target_size, K]) + + # update the complete idxmat + if nargout >= 2: + idxmat[0, layer_idx, :] = layer_idxmat[0, :] + idxmat[1, layer_idx, :] = layer_idxmat[1, :] + + # setup outputs + if nargout == 1: + return layers + elif nargout == 2: + print("idxmat UNTESTED", file=sys.stderr) + return (layers, idxmat) + elif nargout == 3: + print("p_layer_idx UNTESTED", file=sys.stderr) + p = np.zeros(1, np.max(patch_payer_idx.flatten())) + p[layer_ids] = list(range(len(layer_ids))) + return (layers, idxmat, p[patch_payer_idx]) + + +def grid2volsize(grid_size, patch_size, patch_stride=1): + """ + Compute the volume size from the grid size and patch information + + Parameters: + grid_size (vector): the size of the grid in each dimension + patch_size (vector): the size of the patch_gen + patch_stride (vector/scalar, optional): the size of the stride + + Returns: + Volume size filled up by the patches + + See Also: + gridsize(), grid() + + Contact: + {adalca,klbouman}@csail.mit.edu + """ + + # parameter checking + if not isinstance(grid_size, np.ndarray): + grid_size = np.array(grid_size, 'int') + if not isinstance(patch_size, np.ndarray): + patch_size = np.array(patch_size, 'int') + nb_dims = len(patch_size) # number of dimensions + if isinstance(patch_stride, int): + patch_stride = np.repeat(patch_stride, nb_dims).astype('int') + + patch_overlap = patch_size - patch_stride + vol_size = grid_size * patch_stride + patch_overlap + return vol_size + + +def gridsize(vol_size, patch_size, patch_stride=1, start_sub=0, nargout=1): + """ + Number of patches that fit into volSize given a particular patch_size. patch_strideb + cropped to the maximum size that fits the patch grid. For example, a [6x6] volume with + patch_size opatch_stridee + + Parameters: + vol_size (numpy vector): the size of the input volume + patch_size (numpy vector): the size of the patches + patch_stride (int or numpy vector, optional): stride (separation) in each dimension. + default: 1 + start_sub (int or numpy vector, optional): the volume location where patches start + This essentially means that the volume will be cropped starting at that location. + e.g. if startSub is [2, 2], then only vol(2:end, 2:end) will be included. + default: 0 + nargout (int, 1 or 2): optionally output new (cropped) volume size. + return the grid_size only if nargout is 1, or (grid_size, new_vol_size) + if narout is 2. + Returns: + grid_size only, if nargout is 1, or + (grid_size, new_vol_size) if narout is 2 + + See Also: + grid() + + Contact: + {adalca,klbouman}@csail.mit.edu + """ + + # parameter checking + if not isinstance(vol_size, np.ndarray): + vol_size = np.array(vol_size, 'int') + if not isinstance(patch_size, np.ndarray): + patch_size = np.array(patch_size, 'int') + nb_dims = len(patch_size) # number of dimensions + if isinstance(patch_stride, int): + patch_stride = np.repeat(patch_stride, nb_dims).astype('int') + if isinstance(start_sub, int): + start_sub = np.repeat(start_sub, nb_dims).astype('int') + + # adjacent patch overlap + patch_overlap = patch_size - patch_stride + + # modified volume size if starting late + mod_vol_size = vol_size - start_sub + assert np.all(np.array(mod_vol_size) > 0), "New volume size is non-positive" + + # compute the number of patches + # the final volume size will be + # >> grid_size * patch_stride + patch_overlap + # thus the part that is a multiplier of patch_stride is vol_size - patch_overlap + patch_stride_multiples = mod_vol_size - patch_overlap # not sure? + grid_size = np.floor(patch_stride_multiples / patch_stride).astype('int') + assert np.all(np.array(grid_size) > 0), "Grid size is non-positive" + + if nargout == 1: + return grid_size + else: + # new volume size based on how far the patches can reach + new_vol_size = grid2volsize(grid_size, patch_size, patch_stride=patch_stride) + return (grid_size, new_vol_size) + + +def grid(vol_size, patch_size, patch_stride=1, start_sub=0, nargout=1, grid_type='idx'): + """ + grid of patch starting points for nd volume that fit into given volume size + + The index is in the given volume. If the volume gets cropped as part of the function and you + want a linear indexing into the new volume size, use + >> newidx = ind2ind(new_vol_size, vol_size, idx) + new_vol_size can be passed by the current function, see below. + + Parameters: + vol_size (numpy vector): the size of the input volume + patch_size (numpy vector): the size of the patches + patch_stride (int or numpy vector, optional): stride (separation) in each dimension. + default: 1 + start_sub (int or numpy vector, optional): the volume location where patches start + This essentially means that the volume will be cropped starting at that location. + e.g. if startSub is [2, 2], then only vol(2:end, 2:end) will be included. + default: 0 + nargout (int, 1,2 or 3): optionally output new (cropped) volume size and the grid size + return the idx array only if nargout is 1, or (idx, new_vol_size) if nargout is 2, + or (idx, new_vol_size, grid_size) if nargout is 3 + grid_type ('idx' or 'sub', optional): how to describe the grid, in linear index (idx) + or nd subscripts ('sub'). sub will be a nb_patches x nb_dims ndarray. This is + equivalent to sub = ind2sub(vol_size, idx), but is done faster inside this function. + [TODO: or it was faster in MATLAB, this might not be true in python anymore] + + Returns: + idx nd array only if nargout is 1, or (idx, new_vol_size) if nargout is 2, + or (idx, new_vol_size, grid_size) if nargout is 3 + + See also: + gridsize() + + Contact: + {adalca,klbouman}@csail.mit.edu + """ + + # parameter checking + assert grid_type in ('idx', 'sub') + if not isinstance(vol_size, np.ndarray): + vol_size = np.array(vol_size, 'int') + if not isinstance(patch_size, np.ndarray): + patch_size = np.array(patch_size, 'int') + nb_dims = len(patch_size) # number of dimensions + if isinstance(patch_stride, int): + patch_stride = np.repeat(patch_stride, nb_dims).astype('int') + if isinstance(start_sub, int): + start_sub = np.repeat(start_sub, nb_dims).astype('int') + + # get the grid data + [grid_size, new_vol_size] = gridsize(vol_size, patch_size, + patch_stride=patch_stride, + start_sub=start_sub, + nargout=2) + + # compute grid linear index + # prepare the sample grid in each dimension + xvec = () + for idx in range(nb_dims): + volend = new_vol_size[idx] + start_sub[idx] - patch_size[idx] + 1 + locs = list(range(start_sub[idx], volend, patch_stride[idx])) + xvec += (locs, ) + assert any((locs[-1] + patch_size - 1) == (new_vol_size + start_sub - 1)) + + # get the nd grid + # if want subs, this is the faster way to compute in MATLAB (rather than ind -> ind2sub) + # TODO: need to investigate for python, maybe use np.ix_ ? + idx = nd.ndgrid(*xvec) + if grid_type == 'idx': + # if want index, this is the faster way to compute (rather than sub -> sub2ind + all_idx = np.array(list(range(0, np.prod(vol_size)))) + all_idx = np.reshape(all_idx, vol_size) + idx = all_idx[idx] + + if nargout == 1: + return idx + elif nargout == 2: + return (idx, new_vol_size) + else: + return (idx, new_vol_size, grid_size) + + +def patch_gen(vol, patch_size, stride=1, nargout=1, rand=False, rand_seed=None): + """ + NOT VERY WELL TESTED + generator of patches from volume + + TODO: use .grid() to get sub + + """ + + # some parameter checking + if isinstance(stride, int): + stride = [stride for f in patch_size] + assert len(vol.shape) == len(patch_size), \ + "vol shape %s and patch size %s do not match dimensions" \ + % (pformat(vol.shape), pformat(patch_size)) + assert len(vol.shape) == len(stride), \ + "vol shape %s and patch stride %s do not match dimensions" \ + % (pformat(vol.shape), pformat(stride)) + + cropped_vol_size = np.array(vol.shape) - np.array(patch_size) + 1 + assert np.all(cropped_vol_size >= 0), \ + "patch size needs to be smaller than volume size" + + # get range subs + sub = () + for idx, cvs in enumerate(cropped_vol_size): + sub += (list(range(0, cvs, stride[idx])), ) + + # check the size + gs = gridsize(vol.shape, patch_size, patch_stride=stride) + assert [len(f) for f in sub] == list(gs), 'Patch gen side failure' + + # get ndgrid of subs + ndg = nd.ndgrid(*sub) + ndg = [f.flat for f in ndg] + + # generator + rng = list(range(len(ndg[0]))) + if rand: + if rand_seed is not None: + random.seed(rand_seed) + shuffle(rng) + + + for idx in rng: + slicer = lambda f, g: slice(f[idx], f[idx] + g) + patch_sub = [slicer(f, g) for f, g in zip(ndg, patch_size)] + # print(patch_sub) + if nargout == 1: + yield vol[patch_sub] + else: + yield (vol[patch_sub], patch_sub) + + +# local helper functions + +def _mod_base(num, div, base=0): + """ + modulo with respect to a specific base numbering system + i.e. returns base + ((num - base) % div) + modBase(num, div) behaves like num % div + + Parameters: + num (array_like): divident + div (array_like): divisor + base (optional, default 0): the base + + Returns: + the modulo + """ + + return base + np.mod(num - base, div) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/plotting.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/plotting.py new file mode 100644 index 0000000000000000000000000000000000000000..5e93c771066e79908efe81cafbb67ce441201e89 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/plotting.py @@ -0,0 +1,91 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +function to help in plotting +""" + +import numpy as np +import six +import matplotlib +import matplotlib.pylab as plt + + +def jitter(n=256, colmap="hsv", nargout=1): + """ + jitter colormap of size [n x 3]. The jitter colormap will (likely) have distinct colors, with + neighburing colors being quite different + + Parameters: + n (optional): the size of the colormap. default:256 + colmap: the colormap to scramble. Either a string passable to plt.get_cmap, + or a n-by-3 or n-by-4 array + + Algorithm: given a (preferably smooth) colormap as a starting point (default "hsv"), jitter + reorders the colors by skipping roughly a quarter of the colors. So given jitter(9, "hsv"), + jitter would take color numbers, in order, 1, 3, 5, 7, 9, 2, 4, 6, 8. + + Contact: adalca@csail.mit.edu + """ + + # get a 1:n vector + idx = range(n) + + # roughly compute the quarter mark. in hsv, a quarter is enough to see a significant col change + m = np.maximum(np.round(0.25 * n), 1).astype(int) + + # compute a new order, by reshaping this index array as a [m x ?] matrix, then vectorizing in + # the opposite direction + + # pad with -1 to make it transformable to a square + nb_elems = np.ceil(n / m) * m + idx = np.pad(idx, [0, (nb_elems - n).astype(int)], 'constant', constant_values=-1) + + # permute elements by resizing to a matrix, transposing, and re-flatteneing + idxnew = np.array(np.reshape(idx, [m, (nb_elems // m).astype(int)]).transpose().flatten()) + + # throw away the extra elements + idxnew = idxnew[np.where(idxnew >= 0)] + assert len(idxnew) == n, "jitter: something went wrong with some inner logic :(" + + # get colormap and scramble it + if isinstance(colmap, six.string_types): + cmap = plt.get_cmap(colmap, nb_elems) + scrambled_cmap = cmap(idxnew) + else: + # assumes colmap is a nx3 or nx4 + assert colmap.shape[0] == n + assert colmap.shape[1] == 3 or colmap.shape[1] == 4 + scrambled_cmap = colmap[idxnew, :] + + new_cmap = matplotlib.colors.ListedColormap(scrambled_cmap) + if nargout == 1: + return new_cmap + else: + assert nargout == 2 + return (new_cmap, scrambled_cmap) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/timer.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/timer.py new file mode 100644 index 0000000000000000000000000000000000000000..fa237559422523cdf9839fdc2e0fdef22814c238 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/pytools/timer.py @@ -0,0 +1,59 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' A collection of general python utilities ''' + +import time + +class Timer(object): + """ + modified from: + http://stackoverflow.com/questions/5849800/tic-toc-functions-analog-in-python + a helper class for timing + use: + with Timer('foo_stuff'): + # do some foo + # do some stuff + as an alternative to + t = time.time() + # do stuff + elapsed = time.time() - t + """ + + def __init__(self, name=None, verbose=True): + self.name = name + self.verbose = verbose + + def __enter__(self): + self.tstart = time.time() + + def __exit__(self, type, value, traceback): + if self.verbose: + if self.name: + print('[%s]' % self.name, end="") + print('Elapsed: %6.4s' % (time.time() - self.tstart)) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/readme.md b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..886d6f22d17192f42da2b2bf46f29eb841c78f48 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/ext/pytools-lib/readme.md @@ -0,0 +1,2 @@ +# pytools +General python tools diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/networks.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/networks.py new file mode 100644 index 0000000000000000000000000000000000000000..ba56cf52e0d58c500c1dfc37374e294ac0301c06 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/networks.py @@ -0,0 +1,711 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Networks for voxelmorph model + +In general, these are fairly specific architectures that were designed for the presented papers. +However, the VoxelMorph concepts are not tied to a very particular architecture, and we +encourage you to explore architectures that fit your needs. +see e.g. more powerful unet function in https://github.com/adalca/neuron/blob/master/neuron/models.py +""" +# main imports +import sys +import os + +# third party +import numpy as np +import tensorflow.python.keras.backend as K +from tensorflow.python.keras.models import Model +import tensorflow.python.keras.layers as KL +from tensorflow.python.keras.layers import Layer +from tensorflow.python.keras.layers import Conv3D, Activation, Input, UpSampling3D, concatenate +from tensorflow.python.keras.layers import LeakyReLU, Reshape, Lambda +from tensorflow.python.keras.initializers import RandomNormal +import tensorflow.python.keras as keras +import tensorflow.python.keras.initializers +import tensorflow as tf + +# import neuron layers, which will be useful for Transforming. +project_path = os.path.dirname(__file__) +sys.path.append(os.path.join(project_path, 'ext/neuron')) +sys.path.append(os.path.join(project_path, 'ext/pynd-lib')) +sys.path.append(os.path.join(project_path, 'ext/pytools-lib')) +import neuron.layers as nrn_layers +import neuron.models as nrn_models +import neuron.utils as nrn_utils + + +def unet_core(vol_size, enc_nf, dec_nf, x_in, full_size=True): + + ndims = len(vol_size) + assert ndims in [1, 2, 3], "ndims should be one of 1, 2, or 3. found: %d" % ndims + upsample_layer = getattr(KL, 'UpSampling%dD' % ndims) + + # down-sample path (encoder) + x_enc = [x_in] + for i in range(len(enc_nf)): + x_enc.append(conv_block(x_enc[-1], enc_nf[i], 2)) + + # up-sample path (decoder) + x = conv_block(x_enc[-1], dec_nf[0]) + x = upsample_layer()(x) + x = concatenate([x, x_enc[-2]]) + x = conv_block(x, dec_nf[1]) + x = upsample_layer()(x) + x = concatenate([x, x_enc[-3]]) + x = conv_block(x, dec_nf[2]) + x = upsample_layer()(x) + x = concatenate([x, x_enc[-4]]) + x = conv_block(x, dec_nf[3]) + x = conv_block(x, dec_nf[4]) + + # only upsampleto full dim if full_size + # here we explore architectures where we essentially work with flow fields + # that are 1/2 size + if full_size: + x = upsample_layer()(x) + x = concatenate([x, x_enc[0]]) + x = conv_block(x, dec_nf[5]) + + # optional convolution at output resolution (used in voxelmorph-2) + if len(dec_nf) == 7: + x = conv_block(x, dec_nf[6]) + + return x + + +def cvpr2018_net(vol_size, enc_nf, dec_nf, src, tgt, full_size=True, indexing='ij'): + """ + unet architecture for voxelmorph models presented in the CVPR 2018 paper. + You may need to modify this code (e.g., number of layers) to suit your project needs. + + :param vol_size: volume size. e.g. (256, 256, 256) + :param enc_nf: list of encoder filters. right now it needs to be 1x4. + e.g. [16,32,32,32] + :param dec_nf: list of decoder filters. right now it must be 1x6 (like voxelmorph-1) or 1x7 (voxelmorph-2) + :return: the keras model + """ + ndims = len(vol_size) + assert ndims in [1, 2, 3], "ndims should be one of 1, 2, or 3. found: %d" % ndims + + # inputs + x_in = concatenate([src, tgt]) + + # get the core model + x = unet_core(vol_size, enc_nf, dec_nf, x_in, full_size=full_size) + + # transform the results into a flow field. + Conv = getattr(KL, 'Conv%dD' % ndims) + flow = Conv(ndims, kernel_size=3, padding='same', name='flow', + kernel_initializer=RandomNormal(mean=0.0, stddev=1e-5))(x) + + # warp the source with the flow + y = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing)([src, flow]) + + return y, flow + + +def miccai2018_net(vol_size, enc_nf, dec_nf, int_steps=7, use_miccai_int=False, indexing='ij', bidir=False, vel_resize=1/2): + """ + architecture for probabilistic diffeomoprhic VoxelMorph presented in the MICCAI 2018 paper. + You may need to modify this code (e.g., number of layers) to suit your project needs. + + The stationary velocity field operates in a space (0.5)^3 of vol_size for computational reasons. + + :param vol_size: volume size. e.g. (256, 256, 256) + :param enc_nf: list of encoder filters. right now it needs to be 1x4. + e.g. [16,32,32,32] + :param dec_nf: list of decoder filters. right now it must be 1x6, see unet function. + :param use_miccai_int: whether to use the manual miccai implementation of scaling and squaring integration + note that the 'velocity' field outputted in that case was + since then we've updated the code to be part of a flexible layer. see neuron.layers.VecInt + **This param will be phased out (set to False behavior)** + :param int_steps: the number of integration steps + :param indexing: xy or ij indexing. we recommend ij indexing if training from scratch. + miccai 2018 runs were done with xy indexing. + **This param will be phased out (set to 'ij' behavior)** + :return: the keras model + """ + ndims = len(vol_size) + assert ndims in [1, 2, 3], "ndims should be one of 1, 2, or 3. found: %d" % ndims + + # get unet + unet_model = unet_core(vol_size, enc_nf, dec_nf, full_size=False) + [src, tgt] = unet_model.inputs + x_out = unet_model.outputs[-1] + + # velocity mean and logsigma layers + Conv = getattr(KL, 'Conv%dD' % ndims) + flow_mean = Conv(ndims, kernel_size=3, padding='same', + kernel_initializer=RandomNormal(mean=0.0, stddev=1e-5), name='flow')(x_out) + # we're going to initialize the velocity variance very low, to start stable. + flow_log_sigma = Conv(ndims, kernel_size=3, padding='same', + kernel_initializer=RandomNormal(mean=0.0, stddev=1e-10), + bias_initializer=keras.initializers.Constant(value=-10), + name='log_sigma')(x_out) + flow_params = concatenate([flow_mean, flow_log_sigma]) + + # velocity sample + flow = Sample(name="z_sample")([flow_mean, flow_log_sigma]) + + # integrate if diffeomorphic (i.e. treating 'flow' above as stationary velocity field) + if use_miccai_int: + # for the miccai2018 submission, the squaring layer + # scaling was essentially built in by the network + # was manually composed of a Transform and and Add Layer. + v = flow + for _ in range(int_steps): + v1 = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing)([v, v]) + v = keras.layers.add([v, v1]) + flow = v + + else: + # new implementation in neuron is cleaner. + z_sample = flow + flow = nrn_layers.VecInt(method='ss', name='flow-int', int_steps=int_steps)(z_sample) + if bidir: + rev_z_sample = Negate()(z_sample) + neg_flow = nrn_layers.VecInt(method='ss', name='neg_flow-int', int_steps=int_steps)(rev_z_sample) + + # get up to final resolution + flow = trf_resize(flow, vel_resize, name='diffflow') + + if bidir: + neg_flow = trf_resize(neg_flow, vel_resize, name='neg_diffflow') + + # transform + y = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing)([src, flow]) + if bidir: + y_tgt = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing)([tgt, neg_flow]) + + # prepare outputs and losses + outputs = [y, flow_params] + if bidir: + outputs = [y, y_tgt, flow_params] + + # build the model + return Model(inputs=[src, tgt], outputs=outputs) + + +def nn_trf(vol_size, indexing='xy'): + """ + Simple transform model for nearest-neighbor based transformation + Note: this is essentially a wrapper for the neuron.utils.transform(..., interp_method='nearest') + """ + ndims = len(vol_size) + + # nn warp model + subj_input = Input((*vol_size, 1), name='subj_input') + print(subj_input.shape) + trf_input = Input((*vol_size, ndims), name='trf_input') + + # note the nearest neighbour interpolation method + # note xy indexing because Guha's original code switched x and y dimensions + nn_output = nrn_layers.SpatialTransformer(interp_method='nearest', indexing=indexing) + nn_spatial_output = nn_output([subj_input, trf_input]) + return keras.models.Model([subj_input, trf_input], nn_spatial_output) + + +def cvpr2018_net_probatlas(vol_size, enc_nf, dec_nf, nb_labels, + diffeomorphic=True, + full_size=True, + indexing='ij', + init_mu=None, + init_sigma=None, + stat_post_warp=False, # compute statistics post warp? + network_stat_weight=0.001, + warp_method='WARP', + stat_nb_feats=16): + """ + Network to do unsupervised segmentation with probabilistic atlas + (Dalca et al., submitted to MICCAI 2019) + """ + # print(warp_method) + ndims = len(vol_size) + assert ndims in [1, 2, 3], "ndims should be one of 1, 2, or 3. found: %d" % ndims + weaknorm = RandomNormal(mean=0.0, stddev=1e-5) + + # get the core model + unet_model = unet_core(vol_size, enc_nf, dec_nf, full_size=full_size, tgt_feats=nb_labels) + [src_img, src_atl] = unet_model.inputs + x = unet_model.output + + # transform the results into a flow field. + Conv = getattr(KL, 'Conv%dD' % ndims) + flow1 = Conv(ndims, kernel_size=3, padding='same', name='flow', kernel_initializer=weaknorm)(x) + if diffeomorphic: + flow2 = nrn_layers.VecInt(method='ss', name='flow-int', int_steps=8)(flow1) + else: + flow2 = flow1 + if full_size: + flow = flow2 + else: + flow = trf_resize(flow2, 1/2, name='diffflow') + + # warp atlas + if warp_method == 'WARP': + warped_atlas = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing, name='warped_atlas')([src_atl, flow]) + else: + warped_atlas = src_atl + + if stat_post_warp: + assert warp_method == 'WARP', "if computing stat post warp, must do warp... :) set warp_method to 'WARP' or stat_post_warp to False?" + + # combine warped atlas and warpedimage and output mu and log_sigma_squared + combined = concatenate([warped_atlas, src_img]) + else: + combined = unet_model.layers[-2].output + + conv1 = conv_block(combined, stat_nb_feats) + conv2 = conv_block(conv1, nb_labels) + stat_mu_vol = Conv(nb_labels, kernel_size=3, name='mu_vol', + kernel_initializer=weaknorm, bias_initializer=weaknorm)(conv2) + stat_mu = keras.layers.GlobalMaxPooling3D()(stat_mu_vol) + stat_logssq_vol = Conv(nb_labels, kernel_size=3, name='logsigmasq_vol', + kernel_initializer=weaknorm, bias_initializer=weaknorm)(conv2) + stat_logssq = keras.layers.GlobalMaxPooling3D()(stat_logssq_vol) + + # combine mu with initializtion + if init_mu is not None: + init_mu = np.array(init_mu) + stat_mu = Lambda(lambda x: network_stat_weight * x + init_mu, name='comb_mu')(stat_mu) + + # combine sigma with initializtion + if init_sigma is not None: + init_logsigmasq = np.array([2*np.log(f) for f in init_sigma]) + stat_logssq = Lambda(lambda x: network_stat_weight * x + init_logsigmasq, name='comb_sigma')(stat_logssq) + + # unnorm log-lik + def unnorm_loglike(I, mu, logsigmasq, uselog=True): + P = tf.distributions.Normal(mu, K.exp(logsigmasq/2)) + if uselog: + return P.log_prob(I) + else: + return P.prob(I) + + uloglhood = KL.Lambda(lambda x:unnorm_loglike(*x), name='unsup_likelihood')([src_img, stat_mu, stat_logssq]) + + # compute data loss as a layer, because it's a bit easier than outputting a ton of things, etc. + # def logsum(ll, atl): + # pdf = ll * atl + # return tf.log(tf.reduce_sum(pdf, -1, keepdims=True) + K.epsilon()) + + def logsum_safe(prob_ll, atl): + """ + safe computation using the log sum exp trick + e.g. https://www.xarg.org/2016/06/the-log-sum-exp-trick-in-machine-learning/ + where x = logpdf + + note does not normalize p + """ + logpdf = prob_ll + K.log(atl + K.epsilon()) + alpha = tf.reduce_max(logpdf, -1, keepdims=True) + return alpha + tf.log(tf.reduce_sum(K.exp(logpdf-alpha), -1, keepdims=True) + K.epsilon()) + + loss_vol = Lambda(lambda x: logsum_safe(*x))([uloglhood, warped_atlas]) + + return Model(inputs=[src_img, src_atl], outputs=[loss_vol, flow]) + + + +######################################################## +# Atlas creation functions +######################################################## + + + +def diff_net(vol_size, enc_nf, dec_nf, int_steps=7, src_feats=1, + indexing='ij', bidir=False, ret_flows=False, full_size=False, + vel_resize=1/2, src=None, tgt=None): + """ + diffeomorphic net, similar to miccai2018, but no sampling. + + architecture for probabilistic diffeomoprhic VoxelMorph presented in the MICCAI 2018 paper. + You may need to modify this code (e.g., number of layers) to suit your project needs. + + The stationary velocity field operates in a space (0.5)^3 of vol_size for computational reasons. + + :param vol_size: volume size. e.g. (256, 256, 256) + :param enc_nf: list of encoder filters. right now it needs to be 1x4. + e.g. [16,32,32,32] + :param dec_nf: list of decoder filters. right now it must be 1x6, see unet function. + :param use_miccai_int: whether to use the manual miccai implementation of scaling and squaring integration + note that the 'velocity' field outputted in that case was + since then we've updated the code to be part of a flexible layer. see neuron.layers.VecInt + **This param will be phased out (set to False behavior)** + :param int_steps: the number of integration steps + :param indexing: xy or ij indexing. we recommend ij indexing if training from scratch. + miccai 2018 runs were done with xy indexing. + **This param will be phased out (set to 'ij' behavior)** + :return: the keras model + """ + ndims = len(vol_size) + assert ndims in [1, 2, 3], "ndims should be one of 1, 2, or 3. found: %d" % ndims + + # get unet + unet_model = unet_core(vol_size, enc_nf, dec_nf, full_size=full_size, src=src, tgt=tgt, src_feats=src_feats) + [src, tgt] = unet_model.inputs + + # velocity sample + # unet_model.layers[-1].name = 'vel' + # vel = unet_model.output + x_out = unet_model.outputs[-1] + + # velocity mean and logsigma layers + Conv = getattr(KL, 'Conv%dD' % ndims) + vel = Conv(ndims, kernel_size=3, padding='same', + kernel_initializer=RandomNormal(mean=0.0, stddev=1e-5), name='flow')(x_out) + + if full_size and vel_resize != 1: + vel = trf_resize(vel, 1.0/vel_resize, name='flow-resize') + + # new implementation in neuron is cleaner. + flow = nrn_layers.VecInt(method='ss', name='flow-int', int_steps=int_steps)(vel) + if bidir: + # rev_z_sample = Lambda(lambda x: -x)(z_sample) + neg_vel = Negate()(vel) + neg_flow = nrn_layers.VecInt(method='ss', name='neg_flow-int', int_steps=int_steps)(neg_vel) + + # get up to final resolution + flow = trf_resize(flow, vel_resize, name='diffflow') + if bidir: + neg_flow = trf_resize(neg_flow, vel_resize, name='neg_diffflow') + + # transform + y = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing, name='warped_src')([src, flow]) + if bidir: + y_tgt = nrn_layers.SpatialTransformer(interp_method='linear', indexing=indexing, name='warped_tgt')([tgt, neg_flow]) + + # prepare outputs and losses + outputs = [y, vel] + if bidir: + outputs = [y, y_tgt, vel] + + model = Model(inputs=[src, tgt], outputs=outputs) + + if ret_flows: + outputs += [model.get_layer('diffflow').output, model.get_layer('neg_diffflow').output] + return Model(inputs=[src, tgt], outputs=outputs) + else: + return model + + +def atl_img_model(vol_shape, mult=1.0, src=None, atl_layer_name='img_params'): + """ + atlas model with flow representation + idea: starting with some (probably rough) atlas (like a ball or average shape), + the output atlas is this input ball plus a + """ + + # get a new layer (std) + if src is None: + src = Input(shape=[*vol_shape, 1], name='input_atlas') + + # get the velocity field + v_layer = LocalParamWithInput(shape=[*vol_shape, 1], + mult=mult, + name=atl_layer_name, + my_initializer=RandomNormal(mean=0.0, stddev=1e-7)) + v = v_layer(src) # this is so memory-wasteful... + + return keras.models.Model(src, v) + + + + + +def cond_img_atlas_diff_model(vol_shape, nf_enc, nf_dec, + atl_mult=1.0, + bidir=True, + smooth_pen_layer='diffflow', + vel_resize=1/2, + int_steps=5, + nb_conv_features=32, + cond_im_input_shape=[10,12,14,1], + cond_nb_levels=5, + cond_conv_size=[3,3,3], + use_stack=True, + do_mean_layer=True, + pheno_input_shape=[1], + atlas_feats=1, + name='cond_model', + mean_cap=100, + templcondsi=False, + templcondsi_init=None, + full_size=False, + ret_vm=False, + extra_conv_layers=0, + **kwargs): + + # conv layer class + Conv = getattr(KL, 'Conv%dD' % len(vol_shape)) + + # vm model. inputs: "atlas" (we will replace this) and + mn = diff_net(vol_shape, nf_enc, nf_dec, int_steps=int_steps, bidir=bidir, src_feats=atlas_feats, + full_size=full_size, vel_resize=vel_resize, ret_flows=(not use_stack), **kwargs) + + # pre-warp model (atlas model) + pheno_input = KL.Input(pheno_input_shape, name='pheno_input') + dense_tensor = KL.Dense(np.prod(cond_im_input_shape), activation='elu')(pheno_input) + reshape_tensor = KL.Reshape(cond_im_input_shape)(dense_tensor) + pheno_init_model = keras.models.Model(pheno_input, reshape_tensor) + pheno_tmp_model = nrn_models.conv_dec(nb_conv_features, cond_im_input_shape, cond_nb_levels, cond_conv_size, + nb_labels=nb_conv_features, final_pred_activation='linear', + input_model=pheno_init_model, name='atlasmodel') + last_tensor = pheno_tmp_model.output + for i in range(extra_conv_layers): + last_tensor = Conv(nb_conv_features, kernel_size=cond_conv_size, padding='same', name='atlas_ec_%d' % i)(last_tensor) + pout = Conv(atlas_feats, kernel_size=3, padding='same', name='atlasmodel_c', + kernel_initializer=RandomNormal(mean=0.0, stddev=1e-7), + bias_initializer=RandomNormal(mean=0.0, stddev=1e-7))(last_tensor) + atlas_input = KL.Input([*vol_shape, atlas_feats], name='atlas_input') + if not templcondsi: + atlas_tensor = KL.Add(name='atlas')([atlas_input, pout]) + else: + atlas_tensor = KL.Add(name='atlas_tmp')([atlas_input, pout]) + + # change first channel to be result from seg with another add layer + tmp_layer = KL.Lambda(lambda x: K.softmax(x[...,1:]))(atlas_tensor) # this is just tmp. Do not use me. + cl = Conv(1, kernel_size=1, padding='same', use_bias=False, name='atlas_gen', kernel_initializer=RandomNormal(mean=0, stddev=1e-5)) + ximg = cl(tmp_layer) + if templcondsi_init is not None: + w = cl.get_weights() + w[0] = templcondsi_init.reshape(w[0].shape) + cl.set_weights(w) + atlas_tensor = KL.Lambda(lambda x: K.concatenate([x[0], x[1][...,1:]]), name='atlas')([ximg, atlas_tensor]) + + pheno_model = keras.models.Model([pheno_tmp_model.input, atlas_input], atlas_tensor) + + # stack models + inputs = pheno_model.inputs + [mn.inputs[1]] + + if use_stack: + sm = nrn_utils.stack_models([pheno_model, mn], [[0]]) + neg_diffflow_out = sm.get_layer('neg_diffflow').get_output_at(-1) + diffflow_out = mn.get_layer(smooth_pen_layer).get_output_at(-1) + warped_src = sm.get_layer('warped_src').get_output_at(-1) + warped_tgt = sm.get_layer('warped_tgt').get_output_at(-1) + + else: + assert bidir + assert smooth_pen_layer == 'diffflow' + warped_src, warped_tgt, _, diffflow_out, neg_diffflow_out = mn(pheno_model.outputs + [mn.inputs[1]]) + sm = keras.models.Model(inputs, [warped_src, warped_tgt]) + + if do_mean_layer: + mean_layer = nrn_layers.MeanStream(name='mean_stream', cap=mean_cap)(neg_diffflow_out) + outputs = [warped_src, warped_tgt, mean_layer, diffflow_out] + else: + outputs = [warped_src, warped_tgt, diffflow_out] + + + model = keras.models.Model(inputs, outputs, name=name) + if ret_vm: + return model, mn + else: + return model + + + + + +def img_atlas_diff_model(vol_shape, nf_enc, nf_dec, + atl_mult=1.0, + bidir=True, + smooth_pen_layer='diffflow', + atl_int_steps=3, + vel_resize=1/2, + int_steps=3, + mean_cap=100, + atl_layer_name='atlas', + **kwargs): + + + + # vm model + mn = diff_net(vol_shape, nf_enc, nf_dec, int_steps=int_steps, bidir=bidir, + vel_resize=vel_resize, **kwargs) + + # pre-warp model (atlas model) + pw = atl_img_model(vol_shape, mult=atl_mult, src=mn.inputs[0], atl_layer_name=atl_layer_name) # Wait I'm confused.... + + # stack models + sm = nrn_utils.stack_models([pw, mn], [[0]]) + # note: sm.outputs might be out of order now + + # TODO: I'm not sure the mean layer is the right direction + mean_layer = nrn_layers.MeanStream(name='mean_stream', cap=mean_cap)(sm.get_layer('neg_diffflow').get_output_at(-1)) + + outputs = [sm.get_layer('warped_src').get_output_at(-1), + sm.get_layer('warped_tgt').get_output_at(-1), + mean_layer, + mn.get_layer(smooth_pen_layer).get_output_at(-1)] + + model = keras.models.Model(mn.inputs, outputs) + return model + + + + +######################################################## +# Helper functions +######################################################## + +def conv_block(x_in, nf, strides=1): + """ + specific convolution module including convolution followed by leakyrelu + """ + ndims = len(x_in.get_shape()) - 2 + assert ndims in [1, 2, 3], "ndims should be one of 1, 2, or 3. found: %d" % ndims + + Conv = getattr(KL, 'Conv%dD' % ndims) + x_out = Conv(nf, kernel_size=3, padding='same', + kernel_initializer='he_normal', strides=strides)(x_in) + x_out = LeakyReLU(0.2)(x_out) + return x_out + + +def sample(args): + """ + sample from a normal distribution + """ + mu = args[0] + log_sigma = args[1] + noise = tf.random_normal(tf.shape(mu), 0, 1, dtype=tf.float32) + z = mu + tf.exp(log_sigma/2.0) * noise + return z + + +def trf_resize(trf, vel_resize, name='flow'): + if vel_resize > 1: + trf = nrn_layers.Resize(1/vel_resize, name=name+'_tmp')(trf) + return Rescale(1 / vel_resize, name=name)(trf) + + else: # multiply first to save memory (multiply in smaller space) + trf = Rescale(1 / vel_resize, name=name+'_tmp')(trf) + return nrn_layers.Resize(1/vel_resize, name=name)(trf) + + +class Sample(Layer): + """ + Keras Layer: Gaussian sample from [mu, sigma] + """ + + def __init__(self, **kwargs): + super(Sample, self).__init__(**kwargs) + + def build(self, input_shape): + super(Sample, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + return sample(x) + + def compute_output_shape(self, input_shape): + return input_shape[0] + +class Negate(Layer): + """ + Keras Layer: negative of the input + """ + + def __init__(self, **kwargs): + super(Negate, self).__init__(**kwargs) + + def build(self, input_shape): + super(Negate, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + return -x + + def compute_output_shape(self, input_shape): + return input_shape + +class Rescale(Layer): + """ + Keras layer: rescale data by fixed factor + """ + + def __init__(self, resize, **kwargs): + self.resize = resize + super(Rescale, self).__init__(**kwargs) + + def build(self, input_shape): + super(Rescale, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + return x * self.resize + + def compute_output_shape(self, input_shape): + return input_shape + +class RescaleDouble(Rescale): + def __init__(self, **kwargs): + self.resize = 2 + super(RescaleDouble, self).__init__(self.resize, **kwargs) + +class ResizeDouble(nrn_layers.Resize): + def __init__(self, **kwargs): + self.zoom_factor = 2 + super(ResizeDouble, self).__init__(self.zoom_factor, **kwargs) + + +class LocalParamWithInput(Layer): + """ + The neuron.layers.LocalParam has an issue where _keras_shape gets lost upon calling get_output :( + tried using call() but this requires an input (or i don't know how to fix it) + the fix was that after the return, for every time that tensor would be used i would need to do something like + new_vec._keras_shape = old_vec._keras_shape + + which messed up the code. Instead, we'll do this quick version where we need an input, but we'll ignore it. + + this doesn't have the _keras_shape issue since we built on the input and use call() + """ + + def __init__(self, shape, my_initializer='RandomNormal', mult=1.0, **kwargs): + self.shape=shape + self.initializer = my_initializer + self.biasmult = mult + super(LocalParamWithInput, self).__init__(**kwargs) + + def build(self, input_shape): + self.kernel = self.add_weight(name='kernel', + shape=self.shape, # input_shape[1:] + initializer=self.initializer, + trainable=True) + super(LocalParamWithInput, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + # want the x variable for it's keras properties and the batch. + b = 0*K.batch_flatten(x)[:,0:1] + 1 + params = K.expand_dims(K.flatten(self.kernel * self.biasmult), 0) + z = K.reshape(K.dot(b, params), [-1, *self.shape]) + return z + + def compute_output_shape(self, input_shape): + return (input_shape[0], *self.shape) diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/omrun.sh b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/omrun.sh new file mode 100644 index 0000000000000000000000000000000000000000..71ff09123e7b2b5eb51dc75db42269314673f258 --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/omrun.sh @@ -0,0 +1,9 @@ +export DDK_PATH=/home/TestUser02/Ascend/ascend-toolkit/latest +export NPU_HOST_LIB=/home/TestUser02/Ascend/ascend-toolkit/latest/acllib/lib64/stub + +input_src=../Dataset-ABIDE/test_bin/002.bin +input_tgt=../Dataset-ABIDE/tgt.bin +ulimit -c 0 +/home/TestUser02/tools/msame/out/msame --model ./models/vm.om \ +--input ${input_src},${input_tgt} --output ./output \ +--outputSize "1000000000, 1000000000" --outfmt BIN --loop 1 --debug true \ No newline at end of file diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/omtest.py b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/omtest.py new file mode 100644 index 0000000000000000000000000000000000000000..52e52d38f6e444391776e077714cdc952d42f7fc --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/omtest.py @@ -0,0 +1,113 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# -*- coding: UTF-8 -*- +# python imports +import os +import sys +from argparse import ArgumentParser +# third-party imports +import tensorflow as tf +import numpy as np +import nibabel as nib +# project imports +import datagenerators +import networks +sys.path.append('./ext/neuron') +sys.path.append('./ext/medipy-lib') +from medipy.metrics import dice +# npu import +from npu_bridge.npu_init import * + +def omtest(data_path, test_num, om_outbin_dir): + # some parameters need to be make manully + good_labels = np.array([0, 2, 3, 4, 7, 8, 10, 11, 13, 14, 15, 16, 17, 24, 28, 41, 42, 43, 46, + 47, 49, 50, 53, 54, 60, 251, 252, 253, 254, 255]) + + # load tgt image from provided files. 160x192x224. + atlas_vol = nib.load(os.path.join(data_path, 'atlas_abide_brain_crop.nii.gz')).dataobj[ + np.newaxis, ..., np.newaxis].astype('float32') + atlas_seg = np.array(nib.load(os.path.join(data_path, 'atlas_abide_seg_crop.nii.gz')).dataobj).astype('float32') + vol_size = atlas_vol.shape[1:-1] + + # vector of dice values for all the good labels + dice_vals = np.zeros((len(good_labels), 1)) + + # find all the test files + test_path = os.path.join(data_path, 'test/') + seg_path = os.path.join(data_path, 'seg_affined/') + file_names = os.listdir(test_path) + + # NN transfer model + nn_trf_model = networks.nn_trf(vol_size, indexing='ij') + + # npu config + config = tf.ConfigProto() + config.graph_options.rewrite_options.remapping = RewriterConfig.OFF # 必须显式关闭 + config.graph_options.rewrite_options.memory_optimization = RewriterConfig.OFF # 必须显式关闭 + + with tf.Session(config=config) as sess: + # init + init_op = tf.group(tf.local_variables_initializer(), tf.global_variables_initializer()) + sess.run(init_op) + + # load the src image + vol_name = test_path + file_names[test_num] + seg_name = seg_path + file_names[test_num].replace('brain', 'seg') + _, X_seg = datagenerators.load_example_by_name(vol_name, seg_name) + + # load the om output flow tensor + pred_flow = np.fromfile(om_outbin_dir, dtype=np.float32)[:20643840].reshape((1, 160, 192, 224, 3)) + + # register and transfer the src to tgt + warp_seg = nn_trf_model.predict([X_seg, pred_flow])[0, ..., 0] + + # calculate the dice coefficients + dice_vals = dice(warp_seg, atlas_seg, labels=good_labels) + + # print + print('%5.3f' % np.mean(dice_vals)) + + +if __name__ == "__main__": + parser = ArgumentParser() + + parser.add_argument("--data_path", type=str, + dest="data_path", default='../Dataset-ABIDE/') + parser.add_argument("--test_num", type=int, + dest="test_num", default=2) + parser.add_argument("--om_outbin_dir", type=str, + dest="om_outbin_dir", default='./output/2022921_13_56_7_916021/vm_output_1.bin') + + args = parser.parse_args() + + omtest(args.data_path, args.test_num, args.om_outbin_dir) + + + + diff --git a/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/pb2om.sh b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/pb2om.sh new file mode 100644 index 0000000000000000000000000000000000000000..42b7c552ec0f5d3ca4d59204f21595fa4f13d9cf --- /dev/null +++ b/ACL_TensorFlow/contrib/cv/voxelmorph_ID2120_for_ACL/pb2om.sh @@ -0,0 +1,7 @@ +export PYTHONPATH=/home/TestUser02/Ascend/ascend-toolkit/latest/atc/python/site-packages/te:$PYTHONPATH +export LD_LIBRARY_PATH=/home/TestUser02/Ascend/ascend-toolkit/latest/atc/lib64:${LD_LIBRARY_PATH} + +/home/TestUser02/Ascend/ascend-toolkit/latest/atc/bin/atc --input_shape="input_src:1,160,192,224,1;input_tgt:1,160,192,224,1" \ +--out_nodes="spatial_transformer/map/TensorArrayStack/TensorArrayGatherV3:0;flow/BiasAdd:0" \ +--output="./models/vm" \ +--soc_version=Ascend910 --framework=3 --model="./models/frozen_model.pb" \ No newline at end of file diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/checkpoint b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/checkpoint deleted file mode 100644 index a6e034f303e6fd8795171c44ad5816e660476552..0000000000000000000000000000000000000000 --- a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/checkpoint +++ /dev/null @@ -1,2 +0,0 @@ -model_checkpoint_path: "model" -all_model_checkpoint_paths: "model" diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.data-00000-of-00001 b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.data-00000-of-00001 deleted file mode 100644 index ac1b40da4f869cf9f221feda5a15fc5b0cc9a76c..0000000000000000000000000000000000000000 Binary files a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.data-00000-of-00001 and /dev/null differ diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.index b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.index deleted file mode 100644 index 0e3ce1f119f1301a92696eeaba7efc9f2dd4c774..0000000000000000000000000000000000000000 Binary files a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.index and /dev/null differ diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.meta b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.meta deleted file mode 100644 index b99d16e86fa22cc20ecd31251c16132579416659..0000000000000000000000000000000000000000 Binary files a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/models/model.meta and /dev/null differ diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/datagenerators.py b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/datagenerators.py index da9d94d21521803558042cdab9b898c6ae90976c..edd33ebc3a6d81d4bc2dac76fc6a610c50921ce8 100644 --- a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/datagenerators.py +++ b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/datagenerators.py @@ -42,7 +42,7 @@ import tensorflow as tf def zyh_data_in(vol_name, np_var='vol_data'): X = load_volfile(vol_name, np_var=np_var) - X = X[np.newaxis, ..., np.newaxis] + X = X[np.newaxis, ..., np.newaxis].astype('float32') return X @@ -149,12 +149,12 @@ def load_example_by_name(vol_name, seg_name, np_var='vol_data'): npz files. default to 'vol_data' """ X = load_volfile(vol_name, np_var) - X = X[np.newaxis, ..., np.newaxis] + X = X[np.newaxis, ..., np.newaxis].astype('float32') return_vals = [X] X_seg = load_volfile(seg_name, np_var) - X_seg = X_seg[np.newaxis, ..., np.newaxis] + X_seg = X_seg[np.newaxis, ..., np.newaxis].astype('float32') return_vals.append(X_seg) diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/fusion_switch.cfg b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/fusion_switch.cfg index 2579293c0229eff668c1e6f4899e5e70d59a4e63..5086be539fe748266f4a620bca967812f07b353f 100644 --- a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/fusion_switch.cfg +++ b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/fusion_switch.cfg @@ -2,7 +2,7 @@ "Switch": { "GraphFusion": {}, "UBFusion": { - "ALL":"off" + "TbeMultiOutputFusionPass":"off" } } } \ No newline at end of file diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/test_zyh.py b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/test_zyh.py index 62cab91a68dbd54c7547c9e9e9eb14a38310f034..c4664a50eeacbced048e768722ebea887fc17479 100644 --- a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/test_zyh.py +++ b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/test_zyh.py @@ -34,6 +34,7 @@ train atlas-based alignment with CVPR2018 version of VoxelMorph # python imports import os import sys +import time from argparse import ArgumentParser # third-party imports @@ -77,8 +78,8 @@ def test_zyh(data_path, # load atlas from provided files. The atlas we used is 160x192x224. atlas_vol = nib.load(os.path.join(data_path, 'atlas_abide_brain_crop.nii.gz')).dataobj[ - np.newaxis, ..., np.newaxis] - atlas_seg = nib.load(os.path.join(data_path, 'atlas_abide_seg_crop.nii.gz')).dataobj + np.newaxis, ..., np.newaxis].astype('float32') + atlas_seg = np.array(nib.load(os.path.join(data_path, 'atlas_abide_seg_crop.nii.gz')).dataobj).astype('float32') vol_size = atlas_vol.shape[1:-1] test_path = os.path.join(data_path, 'test/') seg_path = os.path.join(data_path, 'seg_affined/') @@ -129,13 +130,15 @@ def test_zyh(data_path, # load subject test X_vol, X_seg = datagenerators.load_example_by_name(vol_name, seg_name) + t0 = time.time() pred_flow = sess.run(flow, feed_dict={src: X_vol, tgt: atlas_vol}) + t = time.time()-t0 warp_seg = nn_trf_model.predict([X_seg, pred_flow])[0, ..., 0] # OrthoSlicer3D(warp_seg).show() dice_vals[:, k] = dice(warp_seg, atlas_seg, labels=good_labels) - print('%3d %5.3f %5.3f' % (k, np.mean(dice_vals[:, k]), np.mean(np.mean(dice_vals[:, :k + 1])))) + print('%3d %5.3f %5.3f, %.3f' % (k, np.mean(dice_vals[:, k]), np.mean(np.mean(dice_vals[:, :k + 1])), t)) return np.mean(dice_vals[:]), np.std(dice_vals[:]) diff --git a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/train_all.py b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/train_all.py index 262fdb26e0c5132fcefd559ce105eb102d282a80..9c9851b67702c82fdce3c253049d08cb929fa9bf 100644 --- a/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/train_all.py +++ b/TensorFlow/contrib/cv/voxelmorph_ID2120_for_TensorFlow/src/train_all.py @@ -90,7 +90,7 @@ def train(train_data_dir, :param data_loss: data_loss: 'mse' or 'ncc """ # load atlas from provided files. The atlas we used is 160x192x224. - atlas_vol = nib.load(atlas_file).dataobj[np.newaxis, ..., np.newaxis] + atlas_vol = nib.load(atlas_file).dataobj[np.newaxis, ..., np.newaxis].astype('float32') vol_size = atlas_vol.shape[1:-1] # prepare data files