From 41fd036c28132aa41105954c1862db9fd234b569 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Tue, 21 Jan 2020 12:50:30 +0800 Subject: [PATCH 1/3] [DLMED] implement intensity normalization transform design according to our latest discussion: 1. input data is dict format with keys for fields. 2. only based on PyTorch and data shape is channel_last. --- monai/data/transforms/intensity_normalizer.py | 56 +++++++++++++++++++ monai/data/transforms/transform.py | 27 +++++++++ 2 files changed, 83 insertions(+) create mode 100644 monai/data/transforms/intensity_normalizer.py create mode 100644 monai/data/transforms/transform.py diff --git a/monai/data/transforms/intensity_normalizer.py b/monai/data/transforms/intensity_normalizer.py new file mode 100644 index 0000000000..a25a454798 --- /dev/null +++ b/monai/data/transforms/intensity_normalizer.py @@ -0,0 +1,56 @@ +# Copyright 2020 MONAI Consortium +# 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 numpy as np +from transform import Transform + + +class IntensityNormalizer(Transform): + """Normalize input based on provided args, using calculated mean and std if not provided + (shape of subtrahend and divisor must match. if 0, entire volume uses same subtrahend and + divisor, otherwise the shape can have dimension 1 for channels). + Current implementation can only support 'channel_last' format data. + + Args: + apply_keys (tuple or list): run transform on which field of the inout data + subtrahend (ndarray): the amount to subtract by (usually the mean) + divisor (ndarray): the amount to divide by (usually the standard deviation) + dtype: output data format + """ + + def __init__(self, apply_keys, subtrahend=None, divisor=None, dtype=np.float32): + Transform.__init__(self) + assert apply_keys is not None and (type(apply_keys) == tuple or type(apply_keys) == list), \ + 'must set apply_keys for this transform.' + self.apply_keys = apply_keys + if subtrahend is not None or divisor is not None: + assert isinstance(subtrahend, np.ndarray) and isinstance(divisor, np.ndarray), \ + 'subtrahend and divisor must be set in pair and in numpy array.' + self.subtrahend = subtrahend + self.divisor = divisor + self.dtype = dtype + + def __call__(self, data): + assert data is not None and type(data) == dict, 'data must be in dict format with keys.' + for key in self.apply_keys: + img = data[key] + assert key in data, 'can not find expected key={} in data.'.format(key) + if self.subtrahend is not None and self.divisor is not None: + img -= self.subtrahend + img /= self.divisor + else: + img -= np.mean(img) + img /= np.std(img) + + if self.dtype != img.dtype: + img = img.astype(self.dtype) + data[key] = img + return data diff --git a/monai/data/transforms/transform.py b/monai/data/transforms/transform.py new file mode 100644 index 0000000000..75cc90926b --- /dev/null +++ b/monai/data/transforms/transform.py @@ -0,0 +1,27 @@ +# Copyright 2020 MONAI Consortium +# 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. + +class Transform(object): + """An abstract class of a ``Transform`` + A transform is callable that maps data into output data. + """ + + def __call__(self, data): + """This method should return an updated version of ``data``. + One useful case is to create multiple instances of this class and + chain them together to form a more powerful transform: + for transform in transforms: + data = transform(data) + Args: + data (dict): an element which often comes from an iteration over an iterable, + such as``torch.utils.data.Dataset`` + """ + raise NotImplementedError From e31ec10664ee229877910028b6410a43a04dae95 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Tue, 21 Jan 2020 15:16:07 +0000 Subject: [PATCH 2/3] 9 part a adding test intensity normalisation transform (#33) * Adding script to run unit tests and example test cases (#29) Adding script to run unit tests and example test cases * initial unit tests for dice loss (#27) * initial unit tests for 2d/3d unet * unit tests update - triggering unit tests via github workflow - renamed testconvolutions.py to test_convolutions.py - test unet test cases as variables for readability * initial unit tests for 2d/3d unet (#26) * initial unit tests for 2d/3d unet * unit tests update - triggering unit tests via github workflow - renamed testconvolutions.py to test_convolutions.py - test unet test cases as variables for readability * 14 code examples of monai input data pipeline (#24) * fixes cardiac example * update example cardiac segmentation * Create .gitlab-ci.yml (#30) an initial step towards #19 * tests intensity normalizer - revised to support both `[key]` and `key` as an input for apply_keys - added `NumpyImageTestCase2D` and `TorchImageTestCase2D` * style updates and new test cases: - adding copyright notice - validate user input before setting class member - one line space after copyright - testing multiple keys input data Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: Isaac Yang --- .github/workflows/pythonapp.yml | 7 +- .gitignore | 1 + .gitlab-ci.yml | 12 ++ examples/cardiac_segmentation.ipynb | 101 +++++------------ monai/__init__.py | 2 +- monai/data/transforms/intensity_normalizer.py | 20 ++-- requirements.txt | 1 + runtests.sh | 103 ++++++++++++++++++ tests/__init__.py | 10 ++ tests/test_convolutions.py | 84 ++++++++++++++ tests/test_dice_loss.py | 53 +++++++++ tests/test_intensity_normalizer.py | 47 ++++++++ tests/test_unet.py | 68 ++++++++++++ tests/utils.py | 78 +++++++++++++ 14 files changed, 503 insertions(+), 84 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100755 runtests.sh create mode 100644 tests/__init__.py create mode 100644 tests/test_convolutions.py create mode 100644 tests/test_dice_loss.py create mode 100644 tests/test_intensity_normalizer.py create mode 100644 tests/test_unet.py create mode 100644 tests/utils.py diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 9251be0c36..4c8d04b8a9 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -25,7 +25,6 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. flake8 . --count --statistics -# - name: Test with pytest -# run: | -# pip install pytest -# pytest + - name: Test and coverage + run: | + ./runtests.sh --coverage diff --git a/.gitignore b/.gitignore index 9949bc981c..c30f242fd2 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,4 @@ venv.bak/ # mypy .mypy_cache/ examples/scd_lvsegs.npz +.idea/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..230eb1fe1b --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +stages: + - build + +.base_template : &BASE + script: + - cat README.md + +build-ci-test: + stage: build + tags: + - test + <<: *BASE diff --git a/examples/cardiac_segmentation.ipynb b/examples/cardiac_segmentation.ipynb index f96a14a5db..112a661fb4 100644 --- a/examples/cardiac_segmentation.ipynb +++ b/examples/cardiac_segmentation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -10,8 +10,8 @@ "output_type": "stream", "text": [ "MONAI version: 0.0.1\n", - "Python version: 3.7.3 (default, Mar 27 2019, 22:11:17) [GCC 7.3.0]\n", - "Numpy version: 1.16.4\n", + "Python version: 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 19:07:31) [GCC 7.3.0]\n", + "Numpy version: 1.18.0\n", "Pytorch version: 1.3.1\n", "Ignite version: 0.2.1\n" ] @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -64,11 +64,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "imSrc = data.readers.NPZReader(\"scd_lvsegs.npz\", [\"images\", \"segs\"], orderType=data.streams.OrderType.CHOICE)" + "imSrc = data.readers.NPZReader(\"scd_lvsegs.npz\", [\"images\", \"segs\"], other_values=data.streams.OrderType.CHOICE)" ] }, { @@ -80,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -93,16 +93,16 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXAAAADJCAYAAAA6q2k2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nO29aZAl13Ue+J18+6t96aqurt6xEgBXcYEsWaZIUYJIBuFwSDRljQeyGYH5MeORZxxhUqOYGCvCEyHFTFi2Zzwaw6IsyCGL5EiUyaFNURREWmNKXLAQxA50o9fqWrr2V/Xq7Xd+3Hvf+QqvGl3dqG5UNs4Xgeism5k37818yDz3O985R5xzMBgMBkP6kLzZAzAYDAbD9cFe4AaDwZBS2AvcYDAYUgp7gRsMBkNKYS9wg8FgSCnsBW4wGAwpxRt6gYvIAyLykoicEpHP7tWgDAaDwXB1yPXqwEUkA+BlAB8BcBHA9wH8gnPu+b0bnsFgMBiuhOwbOPf9AE45514FABH5PIAHAVzxBZ6Xgiui75ouIlkdomu3fVsmowfwByhuC3XQCW0ZXWy4dkf7Fwmn9vYjyRUWKPFYGhs67d5ritApvR9Kof3I5wAA7bL22SrQJfP+/CSrY88kfrvZpHE0tc/sVvxXx9bJ+jl1cnRt3gzdt/O9bYxslRrD+Y0hvV/5vkbPOY1N7dRl/HxyhZaek9Fx1lt+TgP5erctAT23OB8a/MZF/9tK6trnNjRDO/0W4rOKvy2DYT+igpVF59yB17a/kRf4NIAL9PdFAB94vROK6MMH5MPXdJHM+ER3u7O6BgBIBgf1gKa+KFyjCeA1L/2G3y/9+uHoVDa625LPbzsOAFz4Hz0p0huUPxpNf51kbFTPoT67fSX80QgviI6+yIX6l8MHAQCr7xzrtq3cqefXDvtrDkzodYZLNQDAxVkdR2ZOX5LjP/TXGn5urdtWP1AGAFQnczq2hF76dX9O5YheO1PrbkLC+A88tannhxfiuY+Wum1H77/Y3U7gzznz3SPdtuawvx/TJxf1nIGV7vap1XEAwE8eeqXbVk70GeXEn1/t6Hy//dn7AQCl00s6YPpwukvzfjxD+vtxW/4r117Ve2Qw7Df8mfvDczu1v5EX+K4gIg8DeBgAiijf6MsZDAbDWwZv5AU+A+AI/X04tG2Dc+4RAI8AwKCM7opwTwYGutud5VVtH+z3bevr3bbM6EjP+dESB4gu2VCLEbRcdjW/RBdaVkugM6IlDgAJ0yXHDvv9sws6zrou9bsgq767KsipJS9TurqoHRnyfdLqvjmgtysp+7FUlnUlkfzAz/3Ov6pon41qd3vmp/z+2t/Wft4+4S3a4dxWt+1gXu/nQDC3/+3p+7tt1R+ohT/1l34cCz/S321bu8vfz6GX1JLPP6R0R+1Ov7pwP6NzK034cS6s6LNer+mK5OTIMgDg6ZVpvXZZx/lfHfhLP8emPv9vHPD3uPysPgu3oSsW3H7U/7uk/UixGGcBgyFteCMqlO8DuENETohIHsCnAHxlb4ZlMBgMhqvhui1w51xLRP47AF8HkAHwO8655/ZiUOzcY342WsuZceWJXVUtSRS8Bec2qS2C+WjiobNHg3XIHGjcz46tHN2qrLeitzm+mNsO1jzz7nElwHOTiq4KOllv5a7cTR7FhCzwGW8pTv+lXrPvrOeMV+4b6rbVP6krlo8c/g4AYL6unO9qw9NYkwW12ptOVwWPLd4NADg+vKzX/knllDsf9OObmTnUbTvxb/y9ufTj2s/L/+Bod7s1GMYsOvb8E35MZTKQ19+n51f6/bO8ffCyXpuWJxea/jfw9oJy7eu3+bGNPamrA7em1nZr0N/DPFngrnUFh6fBkAK8IQ7cOfefAPynPRqLwWAwGK4BFolpMBgMKcUNV6FcF8i5l6zqchdhudteUOlZwjRFkPJFJySgmnF2Mia030VqZFwddc3pYQBAbo6cg+skE+zsIIxmBLqGnaBd7XqQCwKqyQaA2qjfX76k3TT7df/IK0Ha2FRa5fzHvANv7G/Mdtt+avJUd/vJFe9jrjZVave+ca9Gmq+r8/BbS3d0t0dL3rlYyqoj+KXL493tg0P+nrzjsPqrn/xbtwEAjv8Hvcdz96tDsl0KtBCZC1uHPZ2y1a/XyV3Qc87Ne+fl3Ekd50eOv9TdjtQJ0z+5d3lKSX6PHMpEWeVe8Te3c1ApONkMGslFkh4aDCmBWeAGg8GQUuxLC3z+gxpwNHRaHXSFOe/0S9jJWVepXqfqrcfMsJ7TDcopFbWtRjKzGNxBFvjGtLcEMwfUch36DjlGWyEilAN9OGAkygdFv4/JRJAJLqilJ+RAKxzzTr1WSS3K0Zdo/5If89rtqqVvvsOvCn504ky37dk1dS7OrPlrlgt6j36w4iWQMdIRAPJZvc5sxVu8fXROu63zWK/5+9ju0NxG/LG5DXVSFpf03uU2/ZwoDgcbx/z9apGssnFQrXHJ+VXO1iV1SP6/1bd3t79/wDtJv/2OL3Xb/ps7/wsA4D8m79ULFShYKgRtCTkxDYY0wyxwg8FgSCnsBW4wGAwpxb6kUNb+htIV6yc1v0Zx0S+HBy4Md9uGnqdIzdM+Ncs2imSnJFL5fM9+1guPfM1TE53bD3fbGifIsdr0VEGWddwU6SklP2YpcEYo2bbvtdfMr3h+oVlWqie7SUmo8p5q2JhW+ujAsB/neE6drY83VX/dCEmuqlWlEWp93mm3VVdHbi6n18mFhFJzc3qP0dFrNmr+vEpR6Y4knFM9pHMbfUGfQW08JOrKsb7f2w6bjqic1d4EW9Wjrdc2+fFd9vTQ2779d7ttBwb9/ejr03soRFl1an7unCtHhimvjsGQMpgFbjAYDCmFvcANBoMhpdiXFMr/+f4/6G7/ztEf724/ed7rmlt9pMQoazKjwQNeE148TeHXcyHhFIXSS1mX+gjL6m3pYmOI/FMvaNuPqgKiE5UTRJFso2VCilJOUStBLSNHVSVSe5tqwjcPepqhuEzh5kuamGrzuFeH1MdUgx7zZz9T0YRP031KKZ1f8MqaUlnpjMpmCCfPEzUhSjM1235ukrCqhr7zgXlpLFBmyQFPp+RXKbf3ZaWUcssh5zopTvIb/lk1hin1Lz2CTBwySe4dUTnj4542qlEu9JkFT/tk/rZSKMe+RpTUky/7fkj94yimwGBIG8wCNxgMhpRiX1rgv/Y//73u9tApteTuDNGQsqkWdvXeqe52btlb0x2yquREyHjL1W+2KFJvJDixZua1LSTQyhyc1KanT3e3W28/6a8zoFGg0q8WqcyF8VEkJoIO3VH0ZXWCnGnt4EylJyItNT+TRqgSRFaoc347JqgCgEpDHZbFUnCMkpVaDM7HHFW/GS6r0zj2ydrv2gbNczlEtvbp+ZlFv/rIkiO3Nq0RlJlQESizoSuSvtN+pVAfVP19bUyv2QmWflIjG6Ouq5ylZR9N2SnRPdoKEbC8mCJdfX4wjIkTm1EhDoMhbTAL3GAwGFIKe4EbDAZDSrEvKZTWL2ou6uKA5ulernmqYH5VkxENfl31zKWL3rG19jF1OJYX/LKd84rnZ6giz3mf4IidkNGJ6TbViQg6P/OUd4bhpGqu65Ma8t0OYfHFy1RMMtAhnbKOt7Cqy/+Nab/UH3pF6Yz6Qe2zfMrrmct3q+Pz4mHvtBsa0HFGJyQAbFY8bZOhQsjtVqAZaLrza0p31Lf8+DobOs6kQXOvhe2OXmfkWd+29A4db0dPB5z/Y+CiNvad8c91+EXVsM/+hKZAiKnDcxt6bSp/qWH56/oTbpc8zVRY1nPO/7TuH7ztBABg4nuqv0+MQjGkGGaBGwwGQ0phL3CDwWBIKa5KoYjI7wD4OIAF59x9oW0UwBcAHAdwFsAnnXMrezWomJMaALZauuyuhQx65aKqGaYfUvXImU94RUPzCZIhiFdl9J8nOoTQpU6oqHHnuNdq1ydUL752ksYx5pfoxSXVShdWdXvwTLgWq0himP89x7ttnO87CZHpmU2dW21Sr9++w9NGhx5TeulsUHAsT1HmvyHKex6okw4rV0IWweYWUSR5opTWfV+ZLT2neJnG2Yrj1f1bmmWgi8aw3o8YIt+Zl94DWZ9Pop1mYHUGzlJbH4X0D8exkYZ9IFyHftU89krIgLh6t1JGB564FwAw/Ht/1Ts2g2GfYzcW+O8CeOA1bZ8F8Jhz7g4Aj4W/DQaDwXATcVUL3Dn3FyJy/DXNDwL4YNh+FMC3AHxmrwb1iamnu9uzDU2qFIvzFhI11b7+ytu6262an05mUK2ytRP+G9XJqlY6c1gt28Ky77/4kla1SS55HXepovrn8rNq2cbEVqt3aj/9F3V/Zs07ImtH1ClXrPpz2JlandDv58QT3mrnaMX+p7Tqzfr7fGKt9oBGFk7/Z+8kPftxtcCbCUWHFoIzlizwbmIqirTsNHUc2YrfzlDu7iwtXqLGuqm3Bq2+0BflDes6OwHEWsTtQq8FLluaFGvorD7X9SP+WdKjRpsCaIdOd8I49aJLEyHvuHa5zRrvZsPK6Dlbfys4U3+vZ2gGw77H9XLgk865+MabAzD5egcbDAaDYe/xhp2Yzudj7c3ZGiAiD4vI4yLyeBP1Kx1mMBgMhmvE9erA50Vkyjk3KyJTABaudKBz7hEAjwDAoIxe8UXP+EFF9dX39iuNsNr0NMj3FnR/+ftKjYz8rNd0X8hrgqvNsRD6neMSX3otzVGtIfmFp171Y5/VaXFe8UwI1Z84rWHgro/W94ve0ZiZUF30+n3eCTn4oiabqo8oD5E77Rc0jkLya3ep5rv/jNdLL3xAaZnRFz2Fcte/Uvrn0kc1sVV9xDsqOQFW/NS6As1nQ7/j+TV/P7JUQS5T12MbwxL+1bbsZnAe5rStXSQHb9BlCw8jE9pqyndUpqlA8aY/nymQQ99SzXj2kr/Hbl3bBv/U/9u57Ui3rTWsqQUW3u3ppyrlVN+iXOkGQ9pwvRb4VwA8FLYfAvDlvRmOwWAwGHaL3cgI/wDeYTkuIhcB/C8Afh3AF0Xk0wDOAfjkXg5qqa5W6JMdtbafveyt5LE+9aqdeZ9ur5/zxZDfeed5PWfGn9McoChANerRKnprbGNaLfRW3x3+uJeoAPG6Rux1DnpruklRla1+3Z7/+WDNk89u+JQ3Pyt3qVO2PEspW3eoFrM1rn3mF/08C2vktLsnRFrepilqp/49pcCdCsWh22r6Rsfq1gHtmx2SManW6t3aVljRidRHQjHiEfUutsbCmNp6XLKp1nTlRByz2guDL4ftyyqLHDynK5pcJaSoPUfpXskB3DocnsGgrpxiSt6B80TV0Zrv8Nf982wNqSP4wkdo8gZDyrAbFcovXGHXh/d4LAaDwWC4BlgkpsFgMKQU+zKZ1TMX1RGHC+ocLN/lHYAnB3RZvbSpdMvKqqdBnr2olMKdh+ZDN0pdVNeIxpjza+ykRbrorF+qb51QZ2h+ha5zj3dOlpY0gnH1NirOG3IltWh1vnq7/1ZOPKlOu8k/JyfpUU8FdKgy0PAPlcKJucULq+oYHf4z72yVolIC7eM696Tur+VKSg9lQ27u0b9QxydyOvbzP+/15kViLjKUkyvyQq1xvV8SdNWZJc5gpXBh/9o7dO4bRz2Vc+Kr6kTse5bGlPX3oT2hTttkXT2rs38tPINFHUeu6qmi3KLSaovv02eYHPXXGn1aHckn/53XgZPc3GBIDcwCNxgMhpTCXuAGg8GQUuxLCuVjdz3b3f7y+nu629lQBmx2S5fVK/OD3e1jt3tKYvlPlEY4V/RL6HyW6I4jumDuu+SX6oV13V9Y8iqGVp9SAp2C3qqkGSiDuqo7RE9HccW3V3NUliwoNcrPKU3gBqlUWd2PSRLqiMrANQ77eZTO6fK/fft0zziLL83pmIdDIeRxpaHqo/7Y/IDSVDymoVf99euDOvaYJAoAXNyk8HtUQ9FiClHnZFfZakhnUCLVTdCMn/2o0j93/h+6/+ynpsO5epl8RZ91LHocn4Xv1P+zeZsel6sSxbLpn8vqPfr7Kc97aiz76lkYDGmDWeAGg8GQUuxLC/xU5YD+kVMrd+Wct0IH7yKdL1WbOXfGn5cc1bbR/+itserH1ukctcoqx0MB44Y6D4sLfn8skuwPoPSqK/62FS9qn31D6iyLSZvy63qdvuAsdVvqiJOmOvViBKYjh6Q0dKWQf+asbxtQJ2Z2w/eVpX7Yape6z0iVqZMjeN7fu9yMWvLVezTisxFS3LLV3VL/LTrB5ygtErnH/FhUuSdXkZ5zipf0HsfqOc1hCs9s6+ojF2T3HL0ZNfuArnLK8zp36QSHdFNPavbrTzzT6IT5qFO3MeT378v/EQyGq8AscIPBYEgp7AVuMBgMKcW+XDm++P3j3e3cEc081Wx4emFmSZ1Q01NaCGjm7DiA7c606kH/jdraUL1xpkxh4GV/CxpEGTSG/BK7sKh0RzKjmuy+2fDdI7pi8E/UEbj1130cOuf+7jvtxyn9JA6nEPeYAEvWdHnvprR4czLkqaDOJXVSSmhDhxx5LVI0d3z/+S2inILm25H2uzqpTtDGYBgzdZml5F/RqdghB22kQbiQcTdHOIDiou+zTXmjkkp0fGo/nBCsf9bTKZuTSruMvaCUVlLz+7NrXATaO22Tut6D4pqe4wq+r8El9YxWj6nD02BIG8wCNxgMhpTCXuAGg8GQUuxLCqUzoUv+wtOquhiajzmilYZY79f9yRGiJAJqE6H01qyu37NUsLcTFCmOPmW1Mc8FFGdIuVJSdQiqftlefafmnU5aquQof8+HuDOdsfozvvTb8Nee134mlCKRJCzlqcgv2hTeX/bXT8b1HOT9OF1FOQ6maNzaek+fLipWhpU6cCQo6dIcLDKh25qr+DFFWgQAWn29+b6zVd0f++RSZ7UQin/0T/VZt8e12HBh2d+7/rM6t2STqKBlHwKPIT2n+HKglzJ8D2lQod2V9VkWlql2nGHX+PqlH9z0a/7MoXfd9Gvud5gFbjAYDCnFvrTAhQrutil6b/NQqAYz0qH9qh1O6r3fIwlGcGtYj2v3qXVYnvGOLc6JPXAxHNvSc9yyOktxyJcAXTuhXju2Lidf9CYnV/EZPB2EzVMTOramWujRKuSixu1+8vqFrloj6uiTYF1mi+r4xJrmLZeyP9YNq5UqQTvOkaX14V5NN6NN3btwbEwC5uHve0Mvs80a74TzuVDy9H/2N6xwRrNmsWWcWfLzaBzWxGObd+hqa+Ow1/xzBGx0og6e612JAcDgKd9nZkavmQsOXktm1Ys3w8p+Pew0nre6VW4WuMFgMKQU9gI3GAyGlGJfUiiFkvIRhXeqzner7tfIOVrmU1Q8Mhm/dG7Uldpor3gaItnSbxUnWopL/aGzuhYvzYVrLhJtQpBNv3/yd5/acb/rC2HxDZ1HZjlQG0SbtA9q+L2E3N3NUY1b3zhMmvCgOR88r7rm2pifW4GcdjmibWQlODHJkVe/3VM4rRIVEKaET5sjoaixMjHb6ZBMvDY5KcMwC3S7OPw+E27n6PPKoZSfvuDnRU5IvjeLf83TVNUpvQ7nJY/95zUjABAe+/Lb9H5wceatUe+4nfwrKui8RiL3tyiuhSq5kZTF9VA2VzrnrUKtXNUCF5EjIvJNEXleRJ4TkV8O7aMi8g0ReSX8O3K1vgwGg8Gwd9iNBd4C8I+cc0+KyACAJ0TkGwB+CcBjzrlfF5HPAvgsgM/sxaAScmI22+TUa4VIugGNpGu0dAoibtu/AIB+b9kmc+oQ5ERLxcWYGpasslkfFdlpkNeNEi2hEORsObX0pUCePrK8uwiJpdhRhxYVG57yDrrqhPZZnaRIzll/7MK71YlZWGVHosfy28a72y7jt9nB2gwOXLa6W6Vez2VjmFLDcpKqYJlvc2yGR8RWN0ddRjOhsKDmcJQzbrxNCxkv303PMhjjfTM6Drb645yyNZpHuXcevHqoBwXmhQc0kvf4F966FvhuLd6bZc1e6TpvxDK/1S3xq1rgzrlZ59yTYbsC4AUA0wAeBPBoOOxRAH/zRg3SYDAYDL24Jg5cRI4DeDeA7wKYdM7FBCBzACavcM7DAB4GgCLKOx1iMBgMhuvArl/gItIP4I8A/EPn3LpQIifnnJNtvAV43yMAHgGAQRnd8ZjXIp9VZ1aG6JRM4tfDy1SFJ7vYq8XOtGnJH9iO4jLn5tb+cxVPjRRm1nTMG54nSEaJ1qfEVG7d75cs3T5yHrYrFT+OAXXQdXXeTLVkdQFUH+5NqpXbIPpg1B+b29S2wnqo/DOmNFMn3+tc5MDEmJu7Rrm1ExJBJ4E1ahV72wCNWOVKOVF/3SrRcTokFIKjMbmoRZxXf+pOAMDabeRw5Oo7a36eUfsPaBUeQKkczhEe6RJ2dm6jcqKWvkwU3ZTXmcurMLwG+4V+2Gkcu6VV+Lj9Mp+9xK5khCKSg395/75z7kuheV5EpsL+KQALVzrfYDAYDHuP3ahQBMDnALzgnPtntOsrAB4K2w8B+PLeD89gMBgMV8JuKJQfA/B3ATwjInE98j8B+HUAXxSRTwM4B+CTezUo+aoqE9Z+UtfVSdB5Zyh8XqAUSt+l8O+c7q9O+G8UKza4EG4sqZVboXzhJc8FdFaVVtlGGQVFimSUJ3ANvWYmUi8J8QixH1KztHN6zWY5lnYjKibP9EEv+xTpDFZfbKMMQnNTI9C7Ye3bQtC5zzDk0gIl/KI833Gbay83A1NUWNa2NtEpUf89+8k7dGghUVdeb/G2OcYEWTkSiTCltDEd7hcpbOLYt1FCRMtElQwnM1u93Q905NswpAiRDrkWhcqtqEy56gvcOfdfsGOGDADAh/d2OAaDwWDYLfZlJOaBJyvdbemoI7C0HCIt+5T5Ka6qKZjd9NuVI+ooHD7lrb/C02e7bXM/f2fPNYuX1czsHPeCmsxTL+kBee0zWt6OdOJC+6PlLaT5dtleazxWAwKAbLA+t8Z0bqxhjoWSu4m2ANQHQwFisrDZMu6a1vT5jVGRTJ6xwzIey1Y3I/bPln50KCYtcjhThGTlqO8sS9pzF6dOq6F2iXXevp016s1+cvAGy5pXEtHP3Kb58HZ39cFLjiuZJm9RpM065fG+FR2blgvFYDAYUgp7gRsMBkNKsS8plMySUiiTX1PPWDcMfV7zOceqNAAgBb+uL75C36Wgz25XNDtT+bKuoddOeGoj+ctn9Pp3nPAbrOOu6JhcqLSTjKmztbOk48yMh3ZyWErN0y1RdwwAnQIlodr0Yyou6zmdrK7vt8b9OGsjVEw4VsK5kro+HMr66UiNbNNxs5y96/gkp2+rN4Q9R8muor6anYfbqI0dMgt0Yu1kojDYSRk15ex83pa3PF6aHnV0gvJ463q7kV/f3jcAZLd2FZpgSAGuh05JO8wCNxgMhpTCXuAGg8GQUuxLCmXzbi07tjWuQyyFQrdZKrOVXVV+IDO3BABwfVR2LJTMyozQOZtKoQxcCHpiLgZ8ad6fW1QJQzJ5QPevxzhu5QwSPn8liJvH9JrS7i3zVVzQmO9Ozn9LawdU3rF2ovfxlBcog2HIzlda4JD73tBzR90koY2pBaZYGkMxOyPp3ukzH8PqmXaJNEdhTccmO1Q161Ai93q+txAyiM2IGnjOesi0TD4UV2a1TBwnh/EzrRPVNlwOrrDGsh3DrYLd6sTTrkgxC9xgMBhSin1pgV/4CEU4jql5ODLqLd/JfvWgLVbV8l08cwwAcPD/U0tv+AlvTXcm1eTMNNTsG346CJYpqlJc2D+iSbNYxx2t6c6Kip05KhMT4VoLS3r+tNeWZ1c1J3anoOajC5rwzUnKf05a6+h05CjSaDmz1d1iTXiwPmWHir0cAck68pgrvdVHTkyyxrs6cLKMo+XMlnpxWT2XWwf8wblN0rAP+PmyA5adi3Hs7gomRrSmHTl6o0OULXWO5IzRn2zpl1/yKXysqPGtiVvdsWkWuMFgMKQU9gI3GAyGlGJfUiiuoGvcu4/OdbfnN/xa/+yS6q/bL6hW++Bz/ryR78x02zpDnmK59BNKh7SUdcHIS77O1hAV1K3e6UuRFRbVyZjZUCrHFQN/wGXWisp3SCeMv0ycwIanTtpE5YASZK0fC5wAF2ymgrzddjqnsOKvs3GEcmLTkDaOeqqn/4J+p/PB4cgh5i5DNERgcKS1c4x5pCmisxNQ2qWgUnkkRFOVZ/29aw5Q6oBQCi2mCACAPOvAw/jYWcp0S9dhScOM9BL3yTrveM7Iy1TabWnnwtWGtx7SmOzKLHCDwWBIKfanBZ5Rq+nlWZUUDnzb5wMdm1Uzc/BJtbYjNu852N0+/1H/jXJFCgekKj/veMCXYpmvqiW//kf+thw8Q6VdLlNE6JZvF7K6QRa8W/ZWnaO25MBYzzhlS8cULUlOIcsSueiw3DhE1vS6nwdHRTZ0GijP+mNjdRtA09XmyLpvDJH0MLSzvI9XLHG7/wKNLVjTsboRACSUXjdZ951mVzjkcyD0R1WJBnQ7rgTYKm9S2twYpbotkjI6MaktS49w+EV/ozKvXNTG/BWydhluOVxPCtr9DrPADQaDIaWwF7jBYDCkFPuSQhl9SrXQY8+Qs+ySX/pyYeCNtytdsnifn87W23TdfHjSJ77abOhSeXpwvbs9FLiES52hblvMO90eUidkdot4hBhVycmq+srdbdcKFXs6tLzfAe1BpWBiPvA2FxAmjXPUXbMuujrZ62hk3XNMIrWtUHIsSkw0Q/Ey0RSDQQd+haLGHLUZESNCk/bOdERp1Q8qWVUvZ3nusr/OnYf1wEN60dqwH2jMee77p99CKO7M9yO25SjStv9lclLOhrKtQjnXi5wM3WBIF3ZTE7MoIt8TkadF5DkR+bXQfkJEvisip0TkCyKSv1pfBoPBYNg77IZCqQP4kHPunQDeBeABEbkfwG8A+E3n3O0AVgB8+sYN02AwGAyvxW5qYjoAUeeQC/85AB8C8HdC+6MA/gmA39qLQR38uipLXElphtpJn1Bq6V5K+HSvKj0OHfea8YGOfpcuzHj1R5JTuqPZVorm9KLf33lGKZSDz3jOIDury+/OgFIoshaogJIuv9vLemwyHMLfzx8AACAASURBVPpKSF8dEmxlFpW+6Uyrnj0u+5slHRuxOmgMhCRTDdJNh3D4JrE7XLasqx0nsfSOhZApyVSkS0qUNIspmKjF5tzcmUb8V68dk3MBSnltS+i14X9SufOa231oVn+OUZffPqC6+caY3u9Wv79PpTmly5Kg6pE5TWEgOUpXEBKSyYYmQ28fDAWoL/aqmQyG/Y5dOTFFJBMq0i8A+AaA0wBWnXPx7XkRwPQVzn1YRB4Xkceb2IFANRgMBsN1YVdOTOdcG8C7RGQYwB8DuHu3F3DOPQLgEQAYlNFdlT+JljYANAfVIr38Lj/c9l3qqSuRZV3OeQvs9Iyej7a3FF2VKPoBtdqqC958PfSc9lO66C3slfv1m7R5UL91018L3sHgiAOApECRmMHqc2y1L/nEV25UzWpp6jXzK96M3Zgixynpu2N0IadKjdGU2xNT9aaTLayq5bs17udRXKFiwiRnL6yEwtEDbLXrdiMEtLJjM27XyCovLejc2gP+AuzEjMnDNt6l97g0q881s+K35dR5HdspPb2Y9b8FV1ejQELka9TpA4BQGmHM++fVeOdJmo9/Vm9FV+ZOuui0p1fdDW6lBFfXJCN0zq0C+CaAHwUwLCLxA3AYgK1BDQaD4SZiNyqUA8HyhoiUAHwEwAvwL/KfC4c9BODLN2qQBoPBYOjFbiiUKQCPikgG/oX/RefcV0XkeQCfF5F/CuApAJ/bq0HVR3RYK3eSU+9tnlM4OFzpOQcALq369b1rE42w7s/vFJUyYPV0acZfq7CiS/HmiF+Ks6Nu87CeP/9BH94/+SXNB87VedxWiEevE88QHHmcAzxToYo8Ez5RV9QyA0D1YK9zcdvgw6EdYoc4/D7qwNlhGUPkWbIdQ/KBnQslNzQPGDJxyDSOmGebk2+J46LI4aIZ0l+X/D0un1WnbmNCKadO3j+XTD9VRdqguPh4bw+Oa1vIvy5l0uQX9eZ0DnjNuTSVUipd9FSNlTY2pBG7UaH8EMC7d2h/FcD7b8SgDAaDwXB1WCi9wWAwpBT7MpR+/ZjSJtVjpPMe8hTK3DKVOiON8+CA1/dW67oUz9TC/ildflfWVelRDt0XFnX93xjtpVCYEuhm7xsmbqGiCgq3EULHh0nIHc5PNigXdZmUK2FZX1yh8HySTVcnQ1ZFUqHsVMSXMxPGzIFMoURahdUqdS4MHPrKqlQahWXK0x37orHFYsW5ZW3kfODSCDeZy84FCiSh+5YME11S8+c4opywRnkCOr5/ofuJUFi6drtmsOzSNwByl4JcJ6u/j3af798sGUMaYb9bg8FgSCn2pQXOjjgpqwWeS7w1xVY3Y3nOW7xcTaZVCkmPWhQZWKXKMMGoY012uxCsXfq8JRSD1AxRkZ0RrQacUAFjJOH8iprDMujzX0ut0XMcAGSjlSrsHdTNfMXPfeMQFz0OiafUZ8dddq1oabuec/jTzZZ+rPLDVn0n12vB8/7SUowipejOgh6QCbehPUr3a807L9tH1VrmuUfLOFPX59I4qg7LxrDf33dKI2AbB/09Lp6hYtKbtJQYGgjX1rZMWAm8lYsa30q66LcazAI3GAyGlMJe4AaDwZBS7EsKZWtS1/SuTg7Npl82t7Z02FLT/TLg1/eFC8rBNEZCX6vEy/RRua9QCLdTVr1wYck7PLNbeo50dHnf7Pfn1MbV6VY+qw5Jt+KX9cJOzJg7nMuwkS46Ojk54ZMjSiETnIIDM0qHbI35+yCOKQ4uDBzydOP1KZCECiG3gg48R6XMMnXqM/h/ORQ/acW+yWFYUarIBV4n4RJyDb/NxaJrk+pcZtomYnNS79foi/68DunEW2U/qeyY1pXLkPPZZXrtlc5IOPZczy7DLYpbiSYyC9xgMBhSin1pgXfK5FXL6vbKWpB/Udicy9Ox7V4HXX41/EEGXZ0s1q0Q7bh+UqVlw0/5pEf9l7SNEzXVJvwANqf09vVts6y9JchJlbpVfPrUygRZh9G5SYYx+tZUIleb9pLFbJWs3KKfG6eY3eZ4DQZvtkZpXjP+2A49eY667PAA4v5Ob1thtdfpW1hRC5vTyWbqvt1laCUw5tO4ukvz3bb+dZUJLvz00Z5rjj1DksHQF0dnloIjWupUwLql44ySxGRdnZguvy//F3jTsJND81ZNanUrwCxwg8FgSCnsBW4wGAwpxb5cP+ZWdB3fPKhL4E6gSIRoE9Z0S9UvkWsHVdVbPuf3J7SqlrZ+txrDvq/KUW0bfNULq/tmdHne6FfqI1fxx9bGtM/OqDrOkkpItkXFc2OCK6mQM3VIz4kONqnqNR3RLcVZL6ZuDarTLr/m70eGnIdbB9QZGyvkZLe4uo6/t7ktdkLq9saU3z9wQe97bVSfR1+s1EO0S6bm2+ojOreB5yhJeS48ow5xMZHaODTZbXIX57rbE9+aBQCsvF+LVjcHeosm54gikSiCX9Zru/GR7nakTjoDel+7UaKGHuyULzzNdMrVnJdpnJtZ4AaDwZBS2AvcYDAYUop9SaEMnNHtlT4dYtQuJ8OqMe6QAkPCalq2kp5zWEhRWNbt3Lo/tkGVt84/4KmNiSeVdxl5QZUL+YqnMdZOKrWwTc3QjMJoGkfIFy5c7itPibwjdtCGA4AEhUZuS3XTSdA7s7ojv6qUQFSCdPK6vzzv7x3THZVDOvaRl33/WxOUt7zBsp9wnTV9Bs1BPw8uMIws3ZugZ09WNPd3/W2+lFpSUwok26JSeOEesgadUT4VwuUvUwHjkGO8M6Uh98mqpjOIBbKFEly1B96KxdSuDWmkFiJuJc33TjAL3GAwGFKK/WmBz5BjSXSI6yf8v506RT0OkM43JLEqXFbrrxacoKVLVNmHDN9Y6aa4qG1RI734drVCSws6jvHHfaTl0FOU4WqN8riWvFXHOvAkVonJ0YpiTS1SFyI1pV+151imij8jPqqTowkzs34p0RlWZ2ierMvmULxP5LQd9NdnvXffvN7DZti/TTueJf12cHiyQzE6WNmyZf21zHnnpDtIxarDyqq0TkWJ6ZzoaBx4ZkH7XFHnZCwcjX5NkOUG/b1LKO1se1xT/ibr3pHc6dPfT3bRO5zNlXn92C9OzuuxttO8ugCuwQIXkYyIPCUiXw1/nxCR74rIKRH5gojswAcYDAaD4UbhWiiUX4YvZhzxGwB+0zl3O4AVAJ/ey4EZDAaD4fWxKwpFRA4D+BiA/xXA/ygiAuBDAP5OOORRAP8EwG/txaD6Xrjc3c6vaEKo/kt+6Xv5nTrsOuc8Ctv1CV2KR015Y4hyYpPfKr8akjdRMeFYnLdwkfXT+q1bfK/XFo+8pKHdOQrf7lz2fEx0qjFiEqcrQYTD4mk70AdS1j5df6BlOLc35TUvXAoUTYfokP5AH3BFneLr/wwS0konMfnUDomhONc55+Hu3HWs59j8WkhmNa80UXQy+uvUes4B3894fZpbdHw6oqmSTaVoWgc81VQjrfzmj/i28X99tvd6hl1hJxpiPzsP006bMHZrgf9zAP8Y+r/9GIBV51z8P/sigOmdThSRh0XkcRF5vIn6TocYDAaD4Tpw1Re4iHwcwIJz7onruYBz7hHn3Hudc+/NoXD1EwwGg8GwK+yGQvkxAJ8QkY8CKAIYBPAvAAyLSDZY4YcBzOzVoNbereHVHAaeX/FL9ONf0mV39ZhSLBuH/XQ2p3VanWyvjpgrsjWDgKNbagxAJiwUWiWVajDFUlj3Y2qRRj1HYeISMhN2qkojJEGZInlVb3TWN3rPWSWlxQCF2k/5uH2hTHoIZdySAVVidMY5B3kYE40ts+IVGu0hVbtw4d9IObgspzXU7U4oxJy5TAqZoHHvrFd0vG3KuR7UODI+qv1MeeF9/Q591kJ0SP5c0Hc71qBTbu94H+l+dudJNFRrROvNNYb8sRX6fVxJZ254Y9iPtMqtRJ1EXNUCd879inPusHPuOIBPAfhz59wvAvgmgJ8Lhz0E4Ms3bJQGg8Fg6MEb0YF/BsDnReSfAngKwOf2ZkjAwnv1u1JaICu4EhNTqTOrtKyW3tCr3kIffbZX1cuOuDZV36mN+222wGMlmzYxPvUhdi6GCEeKgCwOkn57U3XIXQTr0jVIG865weP+nFqUrqIWrXT8PN2ohow2fuR2AED+h2e1T9KzuzCObc7UkKs8s0wa9LJ6dWU1XJMTT9Up+jOsCtjSj7rr9b9+vNvWKukzzG36vkoL6uTMLYTrkKXOOvBuYWhy5LKGXkJEquMqznHMFAWa1Om34EIh5DkqlDxgsWw3C7eiBfxm45pe4M65bwH4Vth+FcD7935IBoPBYNgNzPwwGAyGlGJfhtKXFnTZ3Kb4zkxgF+pU3qw5kKVj/bbLKvfRDudkSMHYN9dbIyyhlXYxlAvjEPJt+a9DcqfyeaU4OAe1DIXw7TuoLNhqoDMo+RJrmGOyq06N6Ipir2pHKJlV4WUfot45rI7AVp/eMGl5x2eWtNaRQtkGoi5aR3y4e6tf+2kMsVPY/5vboIRQJ9TZ2p0P5+QKVFP+vPI77VF/jzKUbIrH0f1lZog2YQ19cFRymyuGMVNh6KSilFXfnE+BwKkHJFA4dGWDITUwC9xgMBhSin1pgbfI5yZkGtVGvdWVpSC9Zl/vsSwTjFGV+Ypau/Uh/W7F9k5G9zf6k3AdbeOUqjFFLTtDs6Pq1HMxFeqiOgo7QbYn0DI+UlFnZ4xczFAyK9dQp58Ug6NRep16nMp2/SRVDqr6MecHdJytsh980iDZIyvpwqqgMcxWd6+Dl1PYRmu7uMSpbHV/dF46qjZUffeUP+42lUD2XdTI1sxpX5Gns673MDOlKw04P/6u1Q2g0+/vUSZY2q9FrHCUbOp12sP9Ox5rMKQBZoEbDAZDSmEvcIPBYEgp9iWFkiPfYG6jN1KuVdbleZYcgUmj59DuJ4r3cXRnrBbDeuNOcHw26ToZOn/oTKAC2MdJNEYSElt1SOedLAYnJ+me3YjmqpZ67+BZv+0qIef2oDrgYv/1UXV29l1Sp171oJ9IdpPu12Y7nKPj3V70OOQLp18Gzz3SVLURqgK0HnOEq4O0SPp8CRGUQgmwlu8K+cAXKNHWMY2aLA76BFj5ZeLLZrWUkqsGGoTuW9IJNBYnAWNHcGx3Fn1puDVgFrjBYDCkFPYCNxgMhpRiX1Io5ctMcWh7VENwAiLhvNZhNo1BogzCSrupq3NIh0qENYOme1E7Kp33y/J2QSmBrTG9VRvTflk+/LwqJLaVEwvL+oRpkUjVcIj6LJULy3k1hRTyPecAgNvyE3EblAArtHFZ3s3bqIRYEIXUh3t13OV5pVpYSx/17uUFVZRUDlN4f6SkSDdfH/aNmYV2z3EAkF0KpczGR7ptxWV/oeFXSHlS1/O7ShKiXbbuPaR9bvjxZ58/qxdaCudQcq9tofrhWTtKgBXzpxupYkgjzAI3GAyGlGJfWuDsIGO9cSlYyVmKqmRnW+Vo0tPWDn7AoVOUUrVJaUvXvQVWmVarrHrA27TsLC2s6Dl9c978TDi1K0cEhuRPwompQvQfR1KCHGwupJZ1TapqwxGFQR/uhEMc/X4hK7PvjK4K2gN+Ho1BSrnataBpPmfVqo9pYjnBVekV0ncvhGjK24502+Z+wqeJbZZ1bIUVPUfmffRp/R0amRqTh3XylKxsiZKABUdjhyJXC0uq7076/JLKZSlSM95vimbFAOnqc3HyOk6pWZERQ3phFrjBYDCkFPYCNxgMhpRiX1Iojpx3mS0KgQ/OyWY/OyH1vKgp7r+ky/fNg36KrZKeszWu363G3X4JnyU2JBuuOfSqUhP9Zyg396bXJjsu7Esh3V1Kg5b3cdneOKxVabLrqnF2Ez7Pd5PC3nMVolPOXPJ9F5Xa6Cx7SiGhYsKdPtWJZ1eqYTx6TtSM5585q+OlPiN1IhT27qjSTnLApwJws0ptHPxt31fjx+7V64zo3ItBf93Oc+5u/w9rx7mItKv56yeke3eD6px0UVvepDzvY/7YzAwlRV/Q7ZhkzHEe9sRsGEN6Yb9eg8FgSCnsBW4wGAwpxa4oFBE5C6ACnza55Zx7r4iMAvgCgOMAzgL4pHNu5zRw1zoook02jjGd4v8dfVGXzaxSaef99vJdVDg4CD24PBqrVCJ1Mnhe6ZJsKAGWqVOI+aguu/NB850wzUCZBRG03J0RWvKHUPvsKoWGU+FgzVyoOm6mU4qRSqiqbrqrLSd1hlCx4qjkkLbez9JMCMmnMP1thZBj26DulzEK+V/rLReXjHlaqPjcxW5b5YGTek5Qh+Q29Llla+F+bJJOm/N9R9UNlUcTyiLoQgk71tUn1UAlMXVFhZRjnnCh3O2unwIEDIaU4Vos8J90zr3LOffe8PdnATzmnLsDwGPhb4PBYDDcJLwRJ+aDAD4Yth+Fr5X5mTc4HgDA5iEqFkzBiqMves1uc1Ctso2Dut0MhiI7JGtj3vrMbrGmW/f3zXqrrLio1t/mIW/55it0zpLqhbuOM7L+OILSlby577j6TawgQ4mUOPovntPJkVVeU4u1OeWdnElNLeykGiY8M6+Xqeo464f9OfklvSESijvH/OMAIDVaSUxP+L639NqNCV1J5EPkYmdEnYvJBV8ZyFHkKedSj05QaY/rOaH74ozq1rfpt6MjO08rCo6qrPjxuwG1oLsFjguke6dCyC7kfOdnsE2XbzCkDLu1wB2APxWRJ0Tk4dA26ZybDdtzACZ3OlFEHhaRx0Xk8SbsfxaDwWDYK+zWAv9x59yMiEwA+IaIvMg7nXNORHZMJ+GcewTAIwAwKKOWcsJgMBj2CLt6gTvnZsK/CyLyxwDeD2BeRKacc7MiMgVg4XU7uQaU5/U9P3heqY3KEU9TbE2SDpzyRUVNeOWEUhsDZ0J5tCqFz1OO8VimrUVh7cOvhOU55ZWuHdD9/THke5uGmGq/LXqOJsP67ODQ7FDe8JhICVAnZ25Gc163Jof1/ILf33XUAXDnZnw/7LRbU812ri+kBBikdFeBPsiRI4/TAESNu9S1LZcjKigUHs6wIzCE90tZHZ+ccCwWbM5s6AqsuOLvZ6RCgO2pAxBzpZOD1o2oszXSWOzYlHB+h5yuyTIllw/jcFTUGNkdijwbDCnBVSkUEekTkYG4DeCnATwL4CsAHgqHPQTgyzdqkAaDwWDoxW4s8EkAfyzeqZQF8O+dc38iIt8H8EUR+TSAcwA+uWejIqJl9TZK/RkMVo6+bHGa2LB/9IdULWbDN3L05eZB3R55yXfGEreNo96SZLlh6TIldOoP1rioZZuhajEyFCw8cpYlG96y7QyQfK9fHZ+ZEJVZP3Gg25bdUGs7N++dfdukdMGJ6vg6ee0zRmgmG3pO60BIqkUrjs6GSgNlLSTVGlLHZbKqya6iBc5SPYkFobngMm/2hYLOK9pPdjw4J1uUl5Yg4Trtk9M6jnWae3QKcxKxEA3LjtwoNwQAROczWfqdYdpvMKQMV32BO+deBfDOHdqXAHz4RgzKYDAYDFeHRWIaDAZDSrEvk1lxHu7cptIDzT7fvjVBDsk1PXbgfIiQpFzX9aEktGn/pSXKDd7w2xtHlFKIEZ2FNT2usKDL907R37bMFnE5rDcO25xoqROTRDUoGrFClEBwmBZOqy+YnXFRrxyLGwNAMh4SS23R2JYpKrPf0zXNQ+r8S0LVG45AlDXVYruqdyomlKirQ8mstjlMY1vIzb35bs0RntnqTUwllEQqarFdkxyoZdJ0By13pJ4AQDbI4RmpkeYOFAxX3CHKqR206wk9g0jLkAvaYEgNzAI3GAyGlMJe4AaDwZBS7E8KhYQBOcqdVJ0OBYhniGIhTXe+4pftm1MUXh9yh/df1EVyeUHVHWsnPLXBtEsuJLOK9AsAyFEdVPliGBQXKCYFRkwE1RpUWqZwLqhUWDVB1Ec3Jzct/5Ol9Z5jI13hLxC00KQ8YUoharkzVQrJDwmysudItj+ienO3GMZJqQHkmCpBcNnv7xw72G1av8NTE1miTZIGSYkCTcKJqVplv12gAsRui+iSWjiW7jErY7r3kecbk3vxPeb7GdUppO83GNIMs8ANBoMhpdiXFriQX4oTWxWCcdg3R0mNyNDbDImtGoNUCPlyiDzcVEuuOqHWZWE9RO9lKW1tsB5bFMDYLui3LgkRheykdFSRJ1aESbbUeRitT0dFepMhdVJ2LWwQOErwwGg4n/TmVEmn2w/pquO1ErI4s5mgUaco0m3paGOfpJWWDdZfB6fwq5e07a67AABbozresR/o6kEGgvacnJCtou+HLeSo/fY7erXl7JDsjp+TiCW92nB0KCI0Jr7i/XS+wZA2mAVuMBgMKYW9wA0GgyGl2JcUSmOIlr00wqHTQTtMVXg69Amqjfn2qAcHgPpwLKirHXGuagnVf7JVCq8OOvDiqi7pcxWlJurTnhqJjjgAKM3r8j6J9APn/i57h2b7Pq1Uk8xSYvJQOaY5Svm+W0RjhMRXMqoOx/ZAcMBSiLmUqEBxyPkdrw1odZ7WEc3NnVlRx2jn1fO+zyFN+MRUTcx7zmklR57yVM3WMR3bNtPAhXmU6Do5f49jznIAKDz+il4n3A+mpjh3d7edE2DF+01tsUgzoGkCuAKRs6LGhhTDfr0Gg8GQUtgL3GAwGFKKfUmhtMu6QC8skhY7KArqQ0qhOBJqDJ7zS+dGP2k5QleFdQrJp1D92nCkS3TZnQsZDLkY8NK9uhQfOutVDO0i5wvX/X1LPvScswTKgqcZsmMjOs8DupRfv62vZ26stims+mtmWXselv+bdysd0vcqlRM764sMS13pn3ZfLPdGqptFVbZkYhFgDplnNUxUcDgaRyjpViSaauuwarbLrwQdOKUbSJr+3qydVIpk8ozem6jvdv1ECVEmxySkIWBtebwfXGZtG8USUxzUVIWShDJuO+dENBj2N8wCNxgMhpRiX1rg/ed2/q7EZFb1ESp6vNxrWbdKpB1ux38pSpDMrWiZsxNz5U5vFQ6e1wOnvqGFg9vD3sIrkL66kyVddWxfXtVxlIL1SFpnoWRYjZioa1z7rE7p4yks+e2hs2phVw9465MTfl1+/2h3+0BMGJXvfcydvFqukiOrPTopS+T4pOo83Wo2dd2PsLqQGY3uLFIVn+h4dXQ/XOJXDRkqk7r2Ho3uHHza95WsUwIrsrbjtlDVo+4+Hjs5PmNVJE6QZTCkGWaBGwwGQ0phL3CDwWBIKXZFoYjIMIDfBnAfvFvw7wN4CcAXABwHcBbAJ51zK1fo4prQN6d0RuWIfmOqBz29wMtuoejrrQN+f54clrGvxoD2U1zWk2ojfineIofk2HPBsdWnS3bWK8f24jwX1CWHZVjWd44oJSBrlJUroFNW6iITmAB2yma20IO1E72PrDGgY29TdH31uHeSlp84p40npwAAhXkqk0YOy0g/dMrqXGxOach/ZsvTSkmTxh7nS3nFkzMaao9I0VAIe0w8Vp3QCSdtncfyByYBAGPf1n44dUEMkXcNpVBiUq9tunemrKIzd5s+vzcdgcGQFuzWAv8XAP7EOXc3fHm1FwB8FsBjzrk7ADwW/jYYDAbDTcJVLXARGQLwEwB+CQCccw0ADRF5EMAHw2GPAvgWgM/sxaDYmm1SBtHynG+Pzkxgu+UcLXNOQVs53OvoYys3H5JcCSU9agyH28IZUQf0pJg2tTGq1ltjkPaP+/SrhUVdKkg9FNxd18Hx+e389jkCQHFFVyJbY0loo3GGpF2OVJMsbdw45OdROq+Ozeyyv77UdohqhFrem0epqDGl2u3ke7/5hZBwKqHKPVwoOYnRoSwjDJWQMhQV285R0q26b9+8d7Lb1vc0JdAKKWq3pdcNEkvZIscmWdtxv6NqQ5zsymBIG3ZjgZ8AcBnAvxWRp0Tkt0WkD8Ckc242HDMHX73eYDAYDDcJu3mBZwG8B8BvOefeDWATr6FLnI9Y2dGUEZGHReRxEXm8ifpOhxgMBoPhOrAbJ+ZFABedc98Nf/8h/At8XkSmnHOzIjIFYGGnk51zjwB4BAAGZXRX69Vtubn5ne9629oanIck+MjYqdd/KRTxpU8VOzQLIQIzaqoBILfVm0M8Rg767ZBDPEed0lI9U4/FlSkZVdRSc/5pOqcW9N88zkxD/4hz5lznncz2cwEgQxLneGz9oCbIKr3iH1NnmCgSqvzTnhoOcyPHaEG3Y5Rr37w6B6vT/iGU5JiO40VynEaaoqA/t9yaH+jAjM6xcpgollBpqVkmJ/Z9h3Qep3zOdbAOPFAjUqc22nYTPtIzYYcyadwNhrThqha4c24OwAURuSs0fRjA8wC+AuCh0PYQgC/fkBEaDAaDYUfsNhLzHwD4fRHJA3gVwN+Df/l/UUQ+DeAcgE/emCEaDAaDYScIJ1y60RiUUfcB+fBNu57BYDDcCvgz94dPOOfe+9p2i8Q0GAyGlMJe4AaDwZBS2AvcYDAYUgp7gRsMBkNKcVOdmCJyGT4QaPGmXfTGYxy31nyAW29ONp/9j1ttTns9n2POuQOvbbypL3AAEJHHd/KmphW32nyAW29ONp/9j1ttTjdrPkahGAwGQ0phL3CDwWBIKd6MF/gjb8I1byRutfkAt96cbD77H7fanG7KfG46B24wGAyGvYFRKAaDwZBS3NQXuIg8ICIvicgpEUldCTYROSIi3xSR50XkORH55dA+KiLfEJFXwr8jb/ZYrwUikgnFOr4a/j4hIt8Nz+kLIYlZaiAiwyLyhyLyooi8ICI/muZnJCL/Q/i9PSsifyAixTQ9IxH5HRFZEJFnqW3H5yEe/zLM64ci8p43b+RXxhXm9L+F39wPReSPQy3huO9XwpxeEpGf2atx3LQXuIhkAPwrAD8L4B4AvyAi99ys6+8RWgD+kXPuHgD3A/hvwxzSXh/0l+HrnEb8BoDfdM7dDmAFwKfflFFdP26ZGq4iMg3gvwfwXufcfQAyAD6FujULEwAAAypJREFUdD2j3wXwwGvarvQ8fhbAHeG/hwH81k0a47Xid9E7p28AuM859w4ALwP4FQAI74hPAbg3nPN/hffhG8bNtMDfD+CUc+7VUFfz8wAevInXf8Nwzs06554M2xX4F8M0/DweDYc9CuBvvjkjvHaIyGEAHwPw2+FvAfAh+MIdQPrmE2u4fg7wNVydc6tI8TOCT/tcEpEsgDKAWaToGTnn/gLA8muar/Q8HgTwe87jOwCGQ8GYfYWd5uSc+1PnXCv8+R0Ah8P2gwA+75yrO+fOADgF/z58w7iZL/BpABfo74uhLZUQkeMA3g3gu0h3fdB/DuAfA4jlg8YArNIPMW3P6Zaq4eqcmwHwvwM4D//iXgPwBNL9jIArP49b5T3x9wF8LWzfsDmZE/M6ICL9AP4IwD90zq3zvterD7rfICIfB7DgnHvizR7LHuIN1XDdbwjc8IPwH6ZDAPrQu3RPNdL0PHYDEflVeLr192/0tW7mC3wGwBH6+3BoSxVEJAf/8v5959yXQvN8XOa9Xn3QfYgfA/AJETkLT2l9CJ4/Hg7LdSB9z2mnGq7vQXqf0U8BOOOcu+ycawL4EvxzS/MzAq78PFL9nhCRXwLwcQC/6FSjfcPmdDNf4N8HcEfwnufhSf2v3MTrv2EEfvhzAF5wzv0z2pXK+qDOuV9xzh12zh2Hfx5/7pz7RQDfBPBz4bDUzAe4JWu4ngdwv4iUw+8vzie1zyjgSs/jKwD+66BGuR/AGlEt+xoi8gA8HfkJ51yVdn0FwKdEpCAiJ+AdtN/bk4s6527afwA+Cu+dPQ3gV2/mtfdo/D8Ov9T7IYAfhP8+Cs8bPwbgFQB/BmD0zR7rdcztgwC+GrZPhh/YKQD/D4DCmz2+a5zLuwA8Hp7TfwAwkuZnBODXALwI4FkA/w5AIU3PCMAfwPP3TfgV0qev9DwACLxa7TSAZ+DVN2/6HHY5p1PwXHd8N/zfdPyvhjm9BOBn92ocFolpMBgMKYU5MQ0GgyGlsBe4wWAwpBT2AjcYDIaUwl7gBoPBkFLYC9xgMBhSCnuBGwwGQ0phL3CDwWBIKewFbjAYDCnF/w9ANBhqMIYpTQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -115,7 +115,7 @@ ], "source": [ "def normalizeImg(im, seg):\n", - " im = utils.arrayutils.rescaleArray(im)\n", + " im = utils.arrayutils.rescale_array(im)\n", " im = im[None].astype(np.float32)\n", " seg = seg[None].astype(np.int32)\n", " return im, seg\n", @@ -126,7 +126,7 @@ " augments.rot90,\n", " augments.transpose,\n", " augments.flip,\n", - " partial(augments.shift, dimFract=5, order=0, nonzeroIndex=1),\n", + " partial(augments.shift, dim_fract=5, order=0, nonzero_index=1),\n", "]\n", "\n", "src = data.augments.augmentstream.ThreadAugmentStream(imSrc, 200, augments=augs)\n", @@ -146,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -154,11 +154,11 @@ "\n", "net = networks.nets.UNet(\n", " dimensions=2,\n", - " inChannels=1,\n", - " numClasses=1,\n", + " in_channels=1,\n", + " num_classes=1,\n", " channels=(16, 32, 64, 128, 256),\n", " strides=(2, 2, 2, 2),\n", - " numResUnits=2,\n", + " num_res_units=2,\n", ")\n", "\n", "loss = networks.losses.DiceLoss()\n", @@ -174,36 +174,9 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1 Loss: 0.8310171365737915\n", - "Epoch 2 Loss: 0.8060150742530823\n", - "Epoch 3 Loss: 0.7623872756958008\n", - "Epoch 4 Loss: 0.6729476451873779\n", - "Epoch 5 Loss: 0.6116510629653931\n", - "Epoch 6 Loss: 0.5286673903465271\n", - "Epoch 7 Loss: 0.4480087161064148\n", - "Epoch 8 Loss: 0.41203784942626953\n", - "Epoch 9 Loss: 0.3519987463951111\n", - "Epoch 10 Loss: 0.30135440826416016\n", - "Epoch 11 Loss: 0.274499773979187\n", - "Epoch 12 Loss: 0.2519426941871643\n", - "Epoch 13 Loss: 0.23030847311019897\n", - "Epoch 14 Loss: 0.22828155755996704\n", - "Epoch 15 Loss: 0.22576206922531128\n", - "Epoch 16 Loss: 0.23023653030395508\n", - "Epoch 17 Loss: 0.21913212537765503\n", - "Epoch 18 Loss: 0.22168612480163574\n", - "Epoch 19 Loss: 0.2222415804862976\n", - "Epoch 20 Loss: 0.20740610361099243\n" - ] - } - ], + "outputs": [], "source": [ "trainSteps = 100\n", "trainEpochs = 20\n", @@ -233,35 +206,12 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "im, seg = utils.mathutils.first(imSrc)\n", - "testim = utils.arrayutils.rescaleArray(im[None, None])\n", + "testim = utils.arrayutils.rescale_array(im[None, None])\n", "\n", "pred = net.cpu()(torch.from_numpy(testim))\n", "\n", @@ -269,6 +219,13 @@ "\n", "plt.imshow(np.hstack([testim[0, 0], pseg[0]]))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -287,7 +244,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.6.9" } }, "nbformat": 4, diff --git a/monai/__init__.py b/monai/__init__.py index 9dc1300c7b..e86508dd19 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -12,7 +12,7 @@ import os import sys -from .utils.moduleutils import load_submodules, loadSubmodules +from .utils.moduleutils import load_submodules __copyright__ = "(c) 2020 MONAI Consortium" __version__tuple__ = (0, 0, 1) diff --git a/monai/data/transforms/intensity_normalizer.py b/monai/data/transforms/intensity_normalizer.py index a25a454798..5b66994972 100644 --- a/monai/data/transforms/intensity_normalizer.py +++ b/monai/data/transforms/intensity_normalizer.py @@ -9,8 +9,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from collections.abc import Hashable + import numpy as np -from transform import Transform + +from .transform import Transform class IntensityNormalizer(Transform): @@ -20,17 +23,20 @@ class IntensityNormalizer(Transform): Current implementation can only support 'channel_last' format data. Args: - apply_keys (tuple or list): run transform on which field of the inout data + apply_keys (a hashable key or a tuple/list of hashable keys): run transform on which field of the input data subtrahend (ndarray): the amount to subtract by (usually the mean) divisor (ndarray): the amount to divide by (usually the standard deviation) dtype: output data format """ def __init__(self, apply_keys, subtrahend=None, divisor=None, dtype=np.float32): - Transform.__init__(self) - assert apply_keys is not None and (type(apply_keys) == tuple or type(apply_keys) == list), \ - 'must set apply_keys for this transform.' - self.apply_keys = apply_keys + _apply_keys = apply_keys if isinstance(apply_keys, (list, tuple)) else (apply_keys,) + if not _apply_keys: + raise ValueError('must set apply_keys for this transform.') + for key in _apply_keys: + if not isinstance(key, Hashable): + raise ValueError('apply_keys should be a hashable or a sequence of hashables used by data[key]') + self.apply_keys = _apply_keys if subtrahend is not None or divisor is not None: assert isinstance(subtrahend, np.ndarray) and isinstance(divisor, np.ndarray), \ 'subtrahend and divisor must be set in pair and in numpy array.' @@ -39,7 +45,7 @@ def __init__(self, apply_keys, subtrahend=None, divisor=None, dtype=np.float32): self.dtype = dtype def __call__(self, data): - assert data is not None and type(data) == dict, 'data must be in dict format with keys.' + assert data is not None and isinstance(data, dict), 'data must be in dict format with keys.' for key in self.apply_keys: img = data[key] assert key in data, 'can not find expected key={} in data.'.format(key) diff --git a/requirements.txt b/requirements.txt index 23493d8ecf..e45f176cda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ pillow pandas coverage nibabel +parameterized diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 0000000000..299b47dee6 --- /dev/null +++ b/runtests.sh @@ -0,0 +1,103 @@ +#! /bin/bash +set -e +# Test script for running all tests + + +homedir="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $homedir + +#export PYTHONPATH="$homedir:$PYTHONPATH" + +# configuration values +doCoverage=false +doQuickTests=false +doNetTests=false +doDryRun=false +doZooTests=false + +# testing command to run +cmd="python" +cmdprefix="" + + +# parse arguments +for i in "$@" +do + case $i in + --coverage) + doCoverage=true + ;; + --quick) + doQuickTests=true + doCoverage=true + export QUICKTEST=True + ;; + --net) + doNetTests=true + ;; + --dryrun) + doDryRun=true + ;; + --zoo) + doZooTests=true + ;; + *) + echo "runtests.sh [--coverage] [--quick] [--net] [--dryrun] [--zoo]" + exit 1 + ;; + esac +done + + +# commands are echoed instead of run in this case +if [ "$doDryRun" = 'true' ] +then + echo "Dry run commands:" + cmdprefix="dryrun " + + # create a dry run function which prints the command prepended with spaces for neatness + function dryrun { echo " " $* ; } +fi + + +# set command and clear previous coverage data +if [ "$doCoverage" = 'true' ] +then + cmd="coverage run -a --source ." + ${cmdprefix} coverage erase +fi + + +# # download test data if needed +# if [ ! -d testing_data ] && [ "$doDryRun" != 'true' ] +# then +# fi + + +# unit tests +${cmdprefix}${cmd} -m unittest + + +# network training/inference/eval tests +if [ "$doNetTests" = 'true' ] +then + for i in examples/*.py + do + echo $i + ${cmdprefix}${cmd} $i + done +fi + + +# # run model zoo tests +# if [ "$doZooTests" = 'true' ] +# then +# fi + + +# report on coverage +if [ "$doCoverage" = 'true' ] +then + ${cmdprefix}coverage report --omit='*/test/*' --skip-covered -m +fi + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..d0044e3563 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,10 @@ +# Copyright 2020 MONAI Consortium +# 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. diff --git a/tests/test_convolutions.py b/tests/test_convolutions.py new file mode 100644 index 0000000000..70644c8a9a --- /dev/null +++ b/tests/test_convolutions.py @@ -0,0 +1,84 @@ +# Copyright 2020 MONAI Consortium +# 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 .utils import TorchImageTestCase2D + +from monai.networks.layers.convolutions import Convolution, ResidualUnit + + +class TestConvolution2D(TorchImageTestCase2D): + def test_conv1(self): + conv = Convolution(2, self.input_channels, self.output_channels) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_conv_only1(self): + conv = Convolution(2, self.input_channels, self.output_channels, conv_only=True) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_stride1(self): + conv = Convolution(2, self.input_channels, self.output_channels, strides=2) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0] // 2, self.im_shape[1] // 2) + self.assertEqual(out.shape, expected_shape) + + def test_dilation1(self): + conv = Convolution(2, self.input_channels, self.output_channels, dilation=3) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_dropout1(self): + conv = Convolution(2, self.input_channels, self.output_channels, dropout=0.15) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_transpose1(self): + conv = Convolution(2, self.input_channels, self.output_channels, is_transposed=True) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_transpose2(self): + conv = Convolution(2, self.input_channels, self.output_channels, strides=2, is_transposed=True) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0] * 2, self.im_shape[1] * 2) + self.assertEqual(out.shape, expected_shape) + + +class TestResidualUnit2D(TorchImageTestCase2D): + def test_conv_only1(self): + conv = ResidualUnit(2, 1, self.output_channels) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_stride1(self): + conv = ResidualUnit(2, 1, self.output_channels, strides=2) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0] // 2, self.im_shape[1] // 2) + self.assertEqual(out.shape, expected_shape) + + def test_dilation1(self): + conv = ResidualUnit(2, 1, self.output_channels, dilation=3) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) + + def test_dropout1(self): + conv = ResidualUnit(2, 1, self.output_channels, dropout=0.15) + out = conv(self.imt) + expected_shape = (1, self.output_channels, self.im_shape[0], self.im_shape[1]) + self.assertEqual(out.shape, expected_shape) diff --git a/tests/test_dice_loss.py b/tests/test_dice_loss.py new file mode 100644 index 0000000000..0e1908b999 --- /dev/null +++ b/tests/test_dice_loss.py @@ -0,0 +1,53 @@ +# Copyright 2020 MONAI Consortium +# 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 unittest + +import torch +from parameterized import parameterized + +from monai.networks.losses.dice import DiceLoss + +TEST_CASE_1 = [ + { + 'include_background': False, + }, + { + 'pred': torch.tensor([[[[1., -1.], [-1., 1.]]]]), + 'ground': torch.tensor([[[[1., 0.], [1., 1.]]]]), + 'smooth': 1e-6, + }, + 0.307576, +] + +TEST_CASE_2 = [ + { + 'include_background': True, + }, + { + 'pred': torch.tensor([[[[1., -1.], [-1., 1.]]], [[[1., -1.], [-1., 1.]]]]), + 'ground': torch.tensor([[[[1., 1.], [1., 1.]]], [[[1., 0.], [1., 0.]]]]), + 'smooth': 1e-4, + }, + 0.416636, +] + + +class TestDiceLoss(unittest.TestCase): + + @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + def test_shape(self, input_param, input_data, expected_val): + result = DiceLoss(**input_param).forward(**input_data) + self.assertAlmostEqual(result.item(), expected_val, places=5) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_intensity_normalizer.py b/tests/test_intensity_normalizer.py new file mode 100644 index 0000000000..5ad2f95744 --- /dev/null +++ b/tests/test_intensity_normalizer.py @@ -0,0 +1,47 @@ +# Copyright 2020 MONAI Consortium +# 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 unittest + +import numpy as np + +from monai.data.transforms.intensity_normalizer import IntensityNormalizer +from tests.utils import NumpyImageTestCase2D + + +class IntensityNormTestCase(NumpyImageTestCase2D): + + def test_image_normalizer_default(self): + data_key = 'image' + normalizer = IntensityNormalizer(data_key) # test a single key + normalised = normalizer({data_key: self.imt}) + expected = (self.imt - np.mean(self.imt)) / np.std(self.imt) + self.assertTrue(np.allclose(normalised[data_key], expected)) + + def test_image_normalizer_default_1(self): + data_key = 'image' + normalizer = IntensityNormalizer([data_key]) # test list of keys + normalised = normalizer({data_key: self.imt}) + expected = (self.imt - np.mean(self.imt)) / np.std(self.imt) + self.assertTrue(np.allclose(normalised[data_key], expected)) + + def test_image_normalizer_default_2(self): + data_keys = ['image_1', 'image_2'] + normalizer = IntensityNormalizer(data_keys) # test list of keys + normalised = normalizer(dict(zip(data_keys, (self.imt, self.seg1)))) + expected_1 = (self.imt - np.mean(self.imt)) / np.std(self.imt) + expected_2 = (self.seg1 - np.mean(self.seg1)) / np.std(self.seg1) + self.assertTrue(np.allclose(normalised[data_keys[0]], expected_1)) + self.assertTrue(np.allclose(normalised[data_keys[1]], expected_2)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_unet.py b/tests/test_unet.py new file mode 100644 index 0000000000..95be1bf1a1 --- /dev/null +++ b/tests/test_unet.py @@ -0,0 +1,68 @@ +# Copyright 2020 MONAI Consortium +# 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 unittest + +import torch +from parameterized import parameterized + +from monai.networks.nets.unet import UNet + +TEST_CASE_1 = [ # single channel 2D, batch 16 + { + 'dimensions': 2, + 'in_channels': 1, + 'num_classes': 3, + 'channels': (16, 32, 64), + 'strides': (2, 2), + 'num_res_units': 1, + }, + torch.randn(16, 1, 32, 32), + (16, 32, 32), +] + +TEST_CASE_2 = [ # single channel 3D, batch 16 + { + 'dimensions': 3, + 'in_channels': 1, + 'num_classes': 3, + 'channels': (16, 32, 64), + 'strides': (2, 2), + 'num_res_units': 1, + }, + torch.randn(16, 1, 32, 24, 48), + (16, 32, 24, 48), +] + +TEST_CASE_3 = [ # 4-channel 3D, batch 16 + { + 'dimensions': 3, + 'in_channels': 4, + 'num_classes': 3, + 'channels': (16, 32, 64), + 'strides': (2, 2), + 'num_res_units': 1, + }, + torch.randn(16, 4, 32, 64, 48), + (16, 32, 64, 48), +] + + +class TestUNET(unittest.TestCase): + + @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3]) + def test_shape(self, input_param, input_data, expected_shape): + result = UNet(**input_param).forward(input_data)[1] + self.assertEqual(result.shape, expected_shape) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000000..cc66d261fd --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,78 @@ +# Copyright 2020 MONAI Consortium +# 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 os +import unittest + +import numpy as np +import torch + +from monai.utils.arrayutils import rescale_array + +quick_test_var = "QUICKTEST" + + +def skip_if_quick(obj): + is_quick = os.environ.get(quick_test_var, "").lower() == "true" + + return unittest.skipIf(is_quick, "Skipping slow tests")(obj) + + +def create_test_image(width, height, num_objs=12, rad_max=30, noise_max=0.0, num_seg_classes=5): + """ + Return a noisy 2D image with `numObj' circles and a 2D mask image. The maximum radius of the circles is given as + `radMax'. The mask will have `numSegClasses' number of classes for segmentations labeled sequentially from 1, plus a + background class represented as 0. If `noiseMax' is greater than 0 then noise will be added to the image taken from + the uniform distribution on range [0,noiseMax). + """ + image = np.zeros((width, height)) + + for i in range(num_objs): + x = np.random.randint(rad_max, width - rad_max) + y = np.random.randint(rad_max, height - rad_max) + rad = np.random.randint(5, rad_max) + spy, spx = np.ogrid[-x:width - x, -y:height - y] + circle = (spx * spx + spy * spy) <= rad * rad + + if num_seg_classes > 1: + image[circle] = np.ceil(np.random.random() * num_seg_classes) + else: + image[circle] = np.random.random() * 0.5 + 0.5 + + labels = np.ceil(image).astype(np.int32) + + norm = np.random.uniform(0, num_seg_classes * noise_max, size=image.shape) + noisyimage = rescale_array(np.maximum(image, norm)) + + return noisyimage, labels + + +class NumpyImageTestCase2D(unittest.TestCase): + im_shape = (128, 128) + input_channels = 1 + output_channels = 4 + num_classes = 3 + + def setUp(self): + im, msk = create_test_image(self.im_shape[0], self.im_shape[1], 4, 20, 0, self.num_classes) + + self.imt = im[None, None] + self.seg1 = (msk[None, None] > 0).astype(np.float32) + self.segn = msk[None, None] + + +class TorchImageTestCase2D(NumpyImageTestCase2D): + + def setUp(self): + NumpyImageTestCase2D.setUp(self) + self.imt = torch.tensor(self.imt) + self.seg1 = torch.tensor(self.seg1) + self.segn = torch.tensor(self.segn) From a2a4e23306887670d28efbf76251269865fccec8 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Wed, 22 Jan 2020 09:01:00 +0800 Subject: [PATCH 3/3] [DLMED] simplify intensity normalization transform for MVP --- monai/data/transforms/intensity_normalizer.py | 45 +++++++------------ monai/data/transforms/transform.py | 27 ----------- tests/test_intensity_normalizer.py | 23 ++-------- 3 files changed, 19 insertions(+), 76 deletions(-) delete mode 100644 monai/data/transforms/transform.py diff --git a/monai/data/transforms/intensity_normalizer.py b/monai/data/transforms/intensity_normalizer.py index 5b66994972..953498ab3d 100644 --- a/monai/data/transforms/intensity_normalizer.py +++ b/monai/data/transforms/intensity_normalizer.py @@ -9,34 +9,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections.abc import Hashable - import numpy as np +import monai -from .transform import Transform +export = monai.utils.export("monai.data.transforms") -class IntensityNormalizer(Transform): +@export +class IntensityNormalizer: """Normalize input based on provided args, using calculated mean and std if not provided (shape of subtrahend and divisor must match. if 0, entire volume uses same subtrahend and divisor, otherwise the shape can have dimension 1 for channels). Current implementation can only support 'channel_last' format data. Args: - apply_keys (a hashable key or a tuple/list of hashable keys): run transform on which field of the input data subtrahend (ndarray): the amount to subtract by (usually the mean) divisor (ndarray): the amount to divide by (usually the standard deviation) dtype: output data format """ - def __init__(self, apply_keys, subtrahend=None, divisor=None, dtype=np.float32): - _apply_keys = apply_keys if isinstance(apply_keys, (list, tuple)) else (apply_keys,) - if not _apply_keys: - raise ValueError('must set apply_keys for this transform.') - for key in _apply_keys: - if not isinstance(key, Hashable): - raise ValueError('apply_keys should be a hashable or a sequence of hashables used by data[key]') - self.apply_keys = _apply_keys + def __init__(self, subtrahend=None, divisor=None, dtype=np.float32): if subtrahend is not None or divisor is not None: assert isinstance(subtrahend, np.ndarray) and isinstance(divisor, np.ndarray), \ 'subtrahend and divisor must be set in pair and in numpy array.' @@ -44,19 +36,14 @@ def __init__(self, apply_keys, subtrahend=None, divisor=None, dtype=np.float32): self.divisor = divisor self.dtype = dtype - def __call__(self, data): - assert data is not None and isinstance(data, dict), 'data must be in dict format with keys.' - for key in self.apply_keys: - img = data[key] - assert key in data, 'can not find expected key={} in data.'.format(key) - if self.subtrahend is not None and self.divisor is not None: - img -= self.subtrahend - img /= self.divisor - else: - img -= np.mean(img) - img /= np.std(img) - - if self.dtype != img.dtype: - img = img.astype(self.dtype) - data[key] = img - return data + def __call__(self, img): + if self.subtrahend is not None and self.divisor is not None: + img -= self.subtrahend + img /= self.divisor + else: + img -= np.mean(img) + img /= np.std(img) + + if self.dtype != img.dtype: + img = img.astype(self.dtype) + return img diff --git a/monai/data/transforms/transform.py b/monai/data/transforms/transform.py deleted file mode 100644 index 75cc90926b..0000000000 --- a/monai/data/transforms/transform.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2020 MONAI Consortium -# 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. - -class Transform(object): - """An abstract class of a ``Transform`` - A transform is callable that maps data into output data. - """ - - def __call__(self, data): - """This method should return an updated version of ``data``. - One useful case is to create multiple instances of this class and - chain them together to form a more powerful transform: - for transform in transforms: - data = transform(data) - Args: - data (dict): an element which often comes from an iteration over an iterable, - such as``torch.utils.data.Dataset`` - """ - raise NotImplementedError diff --git a/tests/test_intensity_normalizer.py b/tests/test_intensity_normalizer.py index 5ad2f95744..f8c09b53bd 100644 --- a/tests/test_intensity_normalizer.py +++ b/tests/test_intensity_normalizer.py @@ -20,27 +20,10 @@ class IntensityNormTestCase(NumpyImageTestCase2D): def test_image_normalizer_default(self): - data_key = 'image' - normalizer = IntensityNormalizer(data_key) # test a single key - normalised = normalizer({data_key: self.imt}) + normalizer = IntensityNormalizer() + normalised = normalizer(self.imt) expected = (self.imt - np.mean(self.imt)) / np.std(self.imt) - self.assertTrue(np.allclose(normalised[data_key], expected)) - - def test_image_normalizer_default_1(self): - data_key = 'image' - normalizer = IntensityNormalizer([data_key]) # test list of keys - normalised = normalizer({data_key: self.imt}) - expected = (self.imt - np.mean(self.imt)) / np.std(self.imt) - self.assertTrue(np.allclose(normalised[data_key], expected)) - - def test_image_normalizer_default_2(self): - data_keys = ['image_1', 'image_2'] - normalizer = IntensityNormalizer(data_keys) # test list of keys - normalised = normalizer(dict(zip(data_keys, (self.imt, self.seg1)))) - expected_1 = (self.imt - np.mean(self.imt)) / np.std(self.imt) - expected_2 = (self.seg1 - np.mean(self.seg1)) / np.std(self.seg1) - self.assertTrue(np.allclose(normalised[data_keys[0]], expected_1)) - self.assertTrue(np.allclose(normalised[data_keys[1]], expected_2)) + self.assertTrue(np.allclose(normalised, expected)) if __name__ == '__main__':