Skip to content

Commit

Permalink
added scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
ms authored and ms committed Dec 16, 2019
1 parent 7f39f3d commit a63eeac
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## DeepBleed

ICH volumetric estimation is a task routinely performed in clinical practice. This is the first publicly available deep neural network model to perform the task of ICH segmentation and volume estimation. The model was originally developed in testing the hypothesis that an ICH segmentation deep neural network could be trained in an earlier clinical trial phase (MISTIE Phase II) and validated by a later phase (MISTIE Phase III), with results on par or better than customized architectures and models trained on significantly larger, curated single center datasets. The implication being that such a model could be used to derive metrics for a multicenter clinical trial.

We provide the 3D model from our paper "High-Throughput 3D Segmentation of Intracerebral Hemorrhage: Development and Validation within a Clinical Trial Series". The model version 1.0 will perform binary segmentation of ICH and will include areas of IVH if present. The original model expects non-contrast CT with prior preprocessing described in our paper, including a validated brain extraction method and spatial normalization via registration to a 1.5mm x 1.5mm x 1.5mm template with a shape of (128, 128, 128). Support for deployment on a local machine or on the cloud via docker image is provided.

Please read the software license, this is not intended for any clinical or commercial use.


To install move to the directory of DeepBleed that was just cloned and type:
```
$ python setup.py install
```
If it happens to be missing some dependencies listed above, you may install them with pip: <br/>
```
$ pip install tensorflow-gpu==2.0.0
$ pip install nibabel
$ ...
```
Alternatively, you can run the program in docker. You can directly pull the docker image from dockerhub and avoid installing dependencies. (For tutorials on docker, see [docker](https://docs.docker.com/install/) and [nvidia-docker](https://github.com/NVIDIA/nvidia-docker))

```
$ docker pull msharrock/neuroimage:tf-2.0
$ docker run -it --rm msharrock/neuroimage:tf-2.0
```
The underlying deep neural network architecture is based on the VNET by Milletari et al. at https://github.com/faustomilletari/VNet



## Authors

* **Matthew Sharrock** - *Study Design, Neural Network Dev/Training*
* **John Muschelli** - *Study Design, Preprocessing, Statistical Validation*

95 changes: 95 additions & 0 deletions blocks/vnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @author: msharrock
# version: 0.0.1

"""
VNet Blocks for DeepBleed
tensorflow version 2.0
"""

import tensorflow as tf
from tensorflow.keras import layers

class VNetInBlock(layers.Layer):
def __init__(self, shape):
super(VNetInBlock, self).__init__()
self.shape = shape
self.add = layers.Add()
self.inlayer = layers.Input(shape = self.shape)
self.concatenate = layers.Concatenate()
self.convolution = layers.Conv3D(filters=16, kernel_size=(5,5,5), strides=1,
padding='same', kernel_initializer='he_normal', activation='relu')

def call(self, inputs):
#x_in = self.inlayer(inputs)
x = self.convolution(inputs)
d = self.concatenate(16 * [inputs])
x = self.add([x, d])
return x

class VNetDownBlock(layers.Layer):
def __init__(self, channels, n_convs):
super(VNetDownBlock, self).__init__()
self.channels = channels
self.n_convs = n_convs
self.add = layers.Add()
self.downsample = layers.Conv3D(filters=self.channels, kernel_size=(2,2,2), strides=2,
padding='valid', kernel_initializer='he_normal', activation='relu')
self.convolution = layers.Conv3D(filters=self.channels, kernel_size=(5,5,5), strides=1,
padding='same', kernel_initializer='he_normal', activation='relu')

def call(self, inputs):
d = self.downsample(inputs)

for _ in range(self.n_convs):
x = self.convolution(d)

x = self.add([x, d])

return x

class VNetUpBlock(layers.Layer):
def __init__(self, channels, n_convs):
super(VNetUpBlock, self).__init__()
self.channels = channels
self.n_convs = n_convs
self.add = layers.Add()
self.concatenate = layers.Concatenate()
self.upsample = layers.Conv3DTranspose(filters=self.channels//2, kernel_size=(2,2,2), strides=2,
padding='valid', kernel_initializer='he_normal', activation='relu')
self.convolution = layers.Conv3D(filters=self.channels, kernel_size=(5,5,5), strides=1,
padding='same', kernel_initializer='he_normal', activation='relu')

def call(self, inputs, skip):

x = self.upsample(inputs)
cat = self.concatenate([x, skip])

for _ in range(self.n_convs):
x = self.convolution(cat)

x = self.add([x, cat])

return x

class VNetOutBlock(layers.Layer):

def __init__(self, in_chns):
super(VNetOutBlock, self).__init__()
self.in_chns = in_chns
self.final = layers.Conv3D(filters=2, kernel_size=(1,1,1), strides=1,
padding='valid', kernel_initializer='he_normal', activation='relu')

self.binary = layers.Conv3D(filters=1, kernel_size=(1,1,1), strides=1,
padding='valid', kernel_initializer='he_normal', activation='sigmoid')
#self.softmax = layers.Softmax()
self.argmax =

def call(self, inputs):
x = self.final(inputs)
x = self.binary(x)
return x

12 changes: 12 additions & 0 deletions license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
By using this software, you are agreeing to the following terms and conditions.

- Permission is granted to use this software without charge for non-commercial research purposes only.
- Other than the rights granted herein, the authors retain all rights, title, and interest in the software and Technology, and You retain all rights, title, and interest in Your Modifications and associated specifications, subject to the terms of this License.
- You may make verbatim copies of this software for personal use, or for use within your organization, provided that you duplicate all of the original copyright notices and associated disclaimers. If you provide the use of the software to other users within your organization, they also must comply with all the terms of this Software Distribution Agreement.
- You must not remove or alter any copyright or other proprietary notices in the software.
- Software has not been reviewed or approved by the Food and Drug Administration, and is for non-clinical, IRB-approved Research Use Only. In no event shall data or images generated through the use of the Software be used in the provision of patient care.
- THE SOFTWARE IS PROVIDED “AS IS,” AND THE AUTHORS AND COLLABORATORS DO NOT MAKE ANY WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
- This software is for research purposes only and has not been approved for clinical use.
- You may publish papers and books using results produced using software provided by this site provided that you contact the authors and reference the appropriate citations. A list of citations is available here.
- You agree to comply Trademark Usage Requirements, as modified from time to time, described in the “Use of Materials Limitations” section of the LONI Terms of Use Agreement.
- All Technology and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you.
56 changes: 56 additions & 0 deletions models/vnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @author: msharrock
# version: 0.0.1

"""
Neural Net Models for DeepBleed
tensorflow version 2.0
"""

import tensorflow as tf
from tensorflow.keras import layers
from blocks import vnet

"""
Model below is the VNet architecture for volumetric anatomic segmentation,
originally by Milletari et al.
'V-Net: Fully Convolutional Neural Networks for Volumetric Medical Image Segmentation'
https://arxiv.org/abs/1606.04797
"""


class VNet(keras.Model):
def __init__(self):
super(VNet, self).__init__()
input_layer = VNetInBlock(16)
down_1 = VNetDownBlock(32, 2)
down_2 = VNetDownBlock(64, 3)
down_3 = VNetDownBlock(128, 3)
down_4 = VNetDownBlock(256, 3)
up_4 = VNetUpBlock(256, 3)
up_3 = VNetUpBlock(128, 3)
up_2 = VNetUpBlock(64, 2)
up_1 = VNetUpBlock(32, 1)
outblock = VNetOutBlock(32)

def call(self, inputs, shape):
inputs = layers.Input(shape = shape)
x_16 = input_layer(inputs)
x_32 = down_1(x_16)
x_64 = down_2(x_32)
x_128 = down_3(x_64)
x_256 = down_4(x_128)

x = up_4(x_256, skip=x_128)
x = up_3(x, skip=x_64)
x = up_2(x, skip=x_32)
x = up_1(x, skip=x_16)
outputs = outblock(x)
82 changes: 82 additions & 0 deletions predict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @author: msharrock
# version: 0.0.1

"""
Prediction Script for DeepBleed
Command Line Arguments:
--indir: string, location to perform prediction
--outdir: string, location to save predictions
--weights: string, optional, location of model weights
--cpus: int, optional, number of cpu cores to utilize
--gpus: int, optional, number of gpus to utilize
"""

import os
import shutil

import fsl
import ants
import nibabel as nib
import tensorflow as tf

from tools import parse
from preprocess import extract, register, convert
from models import vnet

# load command line arguments
setup = parse.args('predict')
if setup.CPUS == None:
setup.CPUS = '1'
# environmental variable setup
os.environ["ANTS_RANDOM_SEED"] = '1'
os.environ['FSLOUTPUTTYPE'] = 'NIFTI_GZ'

if setup.CPUS == None:
pass
else:
os.environ["ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS"] = setup.CPUS

if setup.GPUS == None:
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
else:
os.environ['CUDA_VISIBLE_DEVICES'] = ','.join(map(str,range(setup.GPUS)))

# set paths to scripts, templates, weights etc.
TEMPLATE_PATH = os.path.join('templates', 'scct_unsmooth_SS_0.01_128x128x128.nii.gz')
WEIGHT_PATH = setup.weights

# setup directory trees
IN_DIR = setup.IN_DIR
OUT_DIR = setup.OUT_DIR

# load the model and weights
model = VNet()
model.load_weights()

# load input data
files = sorted(next(os.walk(IN_DIR)))[2]
template = ants.image_read(TEMPLATE_PATH, pixeltype = 'float')

for filename in files:

# preprocessing
image = nib.load(filename)
image = extract.brain(image)
image = convert.nii2ants(image)
image, transforms = register.rigid(template, image)
image = convert.ants2tf(image)

# neural net prediction
prediction = model.predict(image)

# invert registration
image = ants.img_read(filename)
prediction = convert.tf2ants(prediction)
prediction = register.invert(image, prediction, transforms)
prediction = convert.ants2nii(prediction)
nib.save(prediction, os.path.join(OUT_DIR, filename))

33 changes: 33 additions & 0 deletions preprocess/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @author: msharrock
# version: 0.0.1

"""
Image Format Conversion for DeepBleed
"""
import.tf
import ants
import nibabel as nib

def ants2nii(image):
array_data = image.numpy()
affine = np.hstack([image.direction*np.diag(image.spacing),np.array(image.origin).reshape(3,1)])
affine = np.vstack([affine, np.array([0,0,0,1.])])
nii_image = nib.Nifti1Image(array_data, affine)
return nii_image

def nii2ants(image):
from tempfile import mktemp
tmpfile = mktemp(suffix='.nii.gz')
image.to_filename(tmpfile)
image = ants.image_read(tmpfile, pixeltype = 'float')
os.remove(tmpfile)
return ants_image

def ants2tf(image):
image = image.numpy()
tf_image = tf.convert_to_tensor(image, dtype=tf.float32)
tf_image = tf.expand_dims(tf_image, -1)
return tf_image
37 changes: 37 additions & 0 deletions preprocess/extract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @author: msharrock
# version: 0.0.1

'''
Extraction methods for DeepBleed
'''

import os

from fsl.wrappers import fslmaths, bet


def brain(image):

'''
Brain Extraction with FSL
Params:
- image: nifti object, scan to brain extract
Output:
- brain_image: nifti object, extracted brain
'''
tmpfile = 'tmpfile.nii.gz'
image.to_filename(tmpfile)
mask = fslmaths(image).thr('0.000000').uthr('100.000000').bin().fillh().run()
fslmaths(image).mas(mask).run(tmpfile)
bet(tmpfile, tmpfile, fracintensity = 0.01)
mask = fslmaths(tmpfile).bin().fillh().run()
brain_image = fslmaths(image).mas(mask).run()
os.remove(tmpfile)

return brain_image

Loading

0 comments on commit a63eeac

Please sign in to comment.