├── pytorch_ssim ├── __pycache__ │ └── __init__.cpython-35.pyc └── __init__.py ├── LICENSE ├── ATLAS_dataset.py ├── ADNI_dataset.py ├── Model_WGAN.py ├── BRATS_dataset.py ├── README.md ├── Model_alphaWGAN.py ├── Model_alphaGAN.py ├── Model_VAEGAN.py ├── WGAN_ADNI_train.ipynb ├── VAEGAN_ADNI_train.ipynb ├── Alpha_GAN_ADNI_train.ipynb ├── Alpha_WGAN_ADNI_train.ipynb └── Test.ipynb /pytorch_ssim/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyclomon/3dbraingen/HEAD/pytorch_ssim/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 cyclomon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ATLAS_dataset.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import numpy as np 3 | import torch 4 | from torch.utils.data.dataset import Dataset 5 | import os 6 | from torchvision import transforms 7 | from skimage.transform import resize 8 | import nibabel as nib 9 | from skimage import exposure 10 | 11 | class ATLASdataset(Dataset): 12 | def __init__(self,augmentation=True): 13 | list_path = [] 14 | for i in range(9): 15 | root = '../ATLAS_R1.1/Site'+str(i+1) 16 | 17 | list_img = os.listdir(root) 18 | for s in range(len(list_img)): 19 | list_path.append(os.path.join(root,list_img[s])) 20 | 21 | list_path.sort() 22 | self.augmentation= augmentation 23 | self.imglist = list_path 24 | 25 | def __len__(self): 26 | return len(self.imglist) 27 | 28 | def __getitem__(self, index): 29 | path = os.path.join(self.imglist[index],'t01') 30 | tempimg = nib.load(os.path.join(path,'T1w_p.nii')) 31 | B = np.flip(tempimg.get_data(),1) 32 | sp_size = 64 33 | img = resize(B, (sp_size,sp_size,sp_size), mode='constant') 34 | img = 1.0*img 35 | img = (img-np.min(img))/(np.max(img)-np.min(img)) 36 | 37 | if self.augmentation: 38 | random_n = torch.rand(1) 39 | if random_n[0] > 0.5: 40 | img = np.flip(img,0) 41 | 42 | img = np.ascontiguousarray(img,dtype=np.float32) 43 | 44 | imageout = torch.from_numpy(img).float().view(1,sp_size,sp_size,sp_size) 45 | imageout = 2*imageout-1 46 | 47 | return imageout 48 | 49 | 50 | -------------------------------------------------------------------------------- /ADNI_dataset.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import numpy as np 3 | import torch 4 | from torch.utils.data.dataset import Dataset 5 | import os 6 | from torchvision import transforms 7 | from skimage.transform import resize 8 | from nilearn import surface 9 | import nibabel as nib 10 | 11 | class ADNIdataset(Dataset): 12 | def __init__(self, root='../ADNI', augmentation=False): 13 | self.root = root 14 | self.basis = 'FreeSurfer_Cross-Sectional_Processing_brainmask' 15 | self.augmentation = augmentation 16 | f = open('CN_list.csv','r') 17 | rdr = csv.reader(f) 18 | 19 | name = [] 20 | labels = [] 21 | date = [] 22 | for line in rdr: 23 | [month,day,year] = line[9].split('/') 24 | month = month.zfill(2) 25 | date.append(year+'-'+month+'-'+day) 26 | name.append(line[1]) 27 | 28 | name = np.asarray(name) 29 | date = np.asarray(date) 30 | 31 | self.name =name 32 | self.date =date 33 | def __len__(self): 34 | return len(self.name) 35 | 36 | def __getitem__(self, index): 37 | path = os.path.join(self.root,self.name[index],self.basis) 38 | files = os.listdir(path) 39 | for file in files: 40 | if file[:10] == self.date[index]: 41 | rname = file 42 | aname = os.listdir(os.path.join(path,rname))[0] 43 | path = os.path.join(path,rname,aname,'mri') 44 | img = nib.load(os.path.join(path,'image.nii')) 45 | 46 | img = np.swapaxes(img.get_data(),1,2) 47 | img = np.flip(img,1) 48 | img = np.flip(img,2) 49 | sp_size = 64 50 | img = resize(img, (sp_size,sp_size,sp_size), mode='constant') 51 | if self.augmentation: 52 | random_n = torch.rand(1) 53 | random_i = 0.3*torch.rand(1)[0]+0.7 54 | if random_n[0] > 0.5: 55 | img = np.flip(img,0) 56 | 57 | img = img*random_i.data.cpu().numpy() 58 | 59 | imageout = torch.from_numpy(img).float().view(1,sp_size,sp_size,sp_size) 60 | imageout = imageout*2-1 61 | 62 | return imageout 63 | 64 | -------------------------------------------------------------------------------- /Model_WGAN.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import os 4 | from torch import nn 5 | from torch import optim 6 | from torch.nn import functional as F 7 | 8 | class Discriminator(nn.Module): 9 | def __init__(self, channel=512): 10 | super(Discriminator, self).__init__() 11 | self.channel = channel 12 | n_class = 1 13 | 14 | self.conv1 = nn.Conv3d(1, channel//8, kernel_size=4, stride=2, padding=1) 15 | self.conv2 = nn.Conv3d(channel//8, channel//4, kernel_size=4, stride=2, padding=1) 16 | self.bn2 = nn.BatchNorm3d(channel//4) 17 | self.conv3 = nn.Conv3d(channel//4, channel//2, kernel_size=4, stride=2, padding=1) 18 | self.bn3 = nn.BatchNorm3d(channel//2) 19 | self.conv4 = nn.Conv3d(channel//2, channel, kernel_size=4, stride=2, padding=1) 20 | self.bn4 = nn.BatchNorm3d(channel) 21 | 22 | self.conv5 = nn.Conv3d(channel, n_class, kernel_size=4, stride=2, padding=1) 23 | 24 | def forward(self, x, _return_activations=False): 25 | h1 = F.leaky_relu(self.conv1(x), negative_slope=0.2) 26 | h2 = F.leaky_relu(self.bn2(self.conv2(h1)), negative_slope=0.2) 27 | h3 = F.leaky_relu(self.bn3(self.conv3(h2)), negative_slope=0.2) 28 | h4 = F.leaky_relu(self.bn4(self.conv4(h3)), negative_slope=0.2) 29 | h5 = self.conv5(h4) 30 | output = h5 31 | 32 | return output 33 | 34 | 35 | class Generator(nn.Module): 36 | def __init__(self, noise:int=1000, channel:int=64): 37 | super(Generator, self).__init__() 38 | _c = channel 39 | 40 | self.noise = noise 41 | self.fc = nn.Linear(1000,512*4*4*4) 42 | self.bn1 = nn.BatchNorm3d(_c*8) 43 | 44 | self.tp_conv2 = nn.Conv3d(_c*8, _c*4, kernel_size=3, stride=1, padding=1, bias=False) 45 | self.bn2 = nn.BatchNorm3d(_c*4) 46 | 47 | self.tp_conv3 = nn.Conv3d(_c*4, _c*2, kernel_size=3, stride=1, padding=1, bias=False) 48 | self.bn3 = nn.BatchNorm3d(_c*2) 49 | 50 | self.tp_conv4 = nn.Conv3d(_c*2, _c, kernel_size=3, stride=1, padding=1, bias=False) 51 | self.bn4 = nn.BatchNorm3d(_c) 52 | 53 | self.tp_conv5 = nn.Conv3d(_c, 1, kernel_size=3, stride=1, padding=1, bias=False) 54 | 55 | def forward(self, noise): 56 | noise = noise.view(-1, 1000) 57 | h = self.fc(noise) 58 | h = h.view(-1,512,4,4,4) 59 | h = F.relu(self.bn1(h)) 60 | 61 | h = F.upsample(h,scale_factor = 2) 62 | h = self.tp_conv2(h) 63 | h = F.relu(self.bn2(h)) 64 | 65 | h = F.upsample(h,scale_factor = 2) 66 | h = self.tp_conv3(h) 67 | h = F.relu(self.bn3(h)) 68 | 69 | h = F.upsample(h,scale_factor = 2) 70 | h = self.tp_conv4(h) 71 | h = F.relu(self.bn4(h)) 72 | 73 | h = F.upsample(h,scale_factor = 2) 74 | h = self.tp_conv5(h) 75 | 76 | h = F.tanh(h) 77 | 78 | return h -------------------------------------------------------------------------------- /BRATS_dataset.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import numpy as np 3 | import torch 4 | from torch.utils.data.dataset import Dataset 5 | import os 6 | from skimage.transform import resize 7 | from nilearn import surface 8 | import nibabel as nib 9 | from skimage import exposure 10 | 11 | class BRATSdataset(Dataset): 12 | def __init__(self, train=True, imgtype = 'flair', severity='HGG',is_flip=False,augmentation=True): 13 | self.augmentation = augmentation 14 | if train: 15 | self.root = '../Training_brats/' + severity 16 | else: 17 | self.root = '../Validation_brats' 18 | self.imgtype = imgtype 19 | list_img = os.listdir(self.root) 20 | list_img.sort() 21 | self.imglist = list_img 22 | self.is_flip = is_flip 23 | 24 | def __len__(self): 25 | return len(self.imglist) 26 | 27 | def __getitem__(self, index): 28 | 29 | path = os.path.join(self.root,self.imglist[index]) 30 | 31 | img = nib.load(os.path.join(path,self.imglist[index]+'_'+self.imgtype+'.nii.gz')) 32 | gt = nib.load(os.path.join(path,self.imglist[index])+'_'+'seg.nii.gz') 33 | 34 | A = np.zeros((240,240,166)) 35 | G = np.zeros((240,240,166)) 36 | A[:,:,11:] = img.get_data() 37 | G[:,:,11:] = gt.get_data() 38 | x=[] 39 | y=[] 40 | z=[] 41 | 42 | for i in range(240): 43 | if np.all(A[i,:,:] ==0): 44 | x.append(i) 45 | if np.all(A[:,i,:]==0): 46 | y.append(i) 47 | if i <155: 48 | if np.all(A[:,:,i]==0): 49 | z.append(i) 50 | 51 | xl,yl,zl = 0,0,0 52 | xh,yh,zh = 240,240,155 53 | for xn in x: 54 | if xn < 120: 55 | if xn> xl: 56 | xl = xn 57 | else: 58 | if xn yl: 63 | yl = yn 64 | else: 65 | if yn zl: 70 | zl = zn 71 | else: 72 | if zn 0.5: 89 | img = np.flip(img,0) 90 | 91 | img = 1.0*img 92 | img = exposure.rescale_intensity(img) 93 | img = (img-np.min(img))/(np.max(img)-np.min(img)) 94 | img = 2*img-1 95 | 96 | imageout = torch.from_numpy(img).float().view(1,sp_size,sp_size,sp_size) 97 | 98 | return imageout 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Official Pytorch Implementation of "Generation of 3D Brain MRI Using Auto-Encoding Generative Adversarial Networks" (accepted by MICCAI 2019) 2 | 3 | This repository provides a PyTorch implementation of 3D brain Generation. It can successfully generates plausible 3-dimensional brain MRI with Generative Adversarial Networks. Trained models are also provided in this page. 4 | 5 | ## Paper 6 | "Generation of 3D Brain MRI Using Auto-Encoding Generative Adversarial Networks" 7 | 8 | The 22nd International Conference on Medical Image Computing and Computer Assisted Intervention(MICCAI 2019) 9 | : (https://arxiv.org/abs/1908.02498) 10 | 11 | ## Cite 12 | ``` 13 | @inproceedings{kwon2019generation, 14 | title={Generation of 3D brain MRI using auto-encoding generative adversarial networks}, 15 | author={Kwon, Gihyun and Han, Chihye and Kim, Dae-shik}, 16 | booktitle={International Conference on Medical Image Computing and Computer-Assisted Intervention}, 17 | pages={118--126}, 18 | year={2019}, 19 | organization={Springer} 20 | } 21 | ``` 22 | 23 | ## Dependencies 24 | * [Python 3.5+](https://www.continuum.io/downloads) 25 | * [PyTorch 0.4.0+](http://pytorch.org/) 26 | * [Jupyter Notebook](https://jupyter.org/) 27 | * [Nilearn](https://nilearn.github.io/) 28 | * [Nibabel](https://nipy.org/nibabel/) 29 | 30 | We highly recommend you to use Jupyter Notebook for the better visualization! 31 | 32 | ## Dataset 33 | You can download the Normal MRI data in [Alzheimer's Disease Neuroimaging Initiative(ADNI)](http://adni.loni.usc.edu/) 34 | , Tumor MRI data in [BRATS2018](https://www.med.upenn.edu/sbia/brats2018/data.html) and Stroke MRI data in [Anatomical Tracings of Lesions After Stroke (ATLAS)](http://fcon_1000.projects.nitrc.org/indi/retro/atlas.html). 35 | 36 | We converted all the DICOM(.dcm) files of ADNI into Nifti(.nii) file format using [SPM12](https://www.fil.ion.ucl.ac.uk/spm/software/spm12/) I/O tools. 37 | 38 | ADNI : Download Post-processed(processed with 'recon-all' command of [Freesurfer](https://surfer.nmr.mgh.harvard.edu/)) Structural images labeled as 'Control Normal'. 39 | 40 | BRATS : Download dataset from BRATS2018 website. 41 | 42 | ATLAS : Download dataset from ATLAS website. 43 | Obtain probability maps(masks) from the original .nii images with SPM12 'Segmentation' function. 44 | Extract Brain areas with multiplying masks(c1,c2,c3 / GM,WM,CSF) with original images. 45 | 46 | ## Training Details 47 | For each training, run 12,000 iterations (100 epochs in VAE-GAN) 48 | 49 | Each run takes ~12 hour with one NVIDIA TITAN X GPU. 50 | 51 | Run the Jupyter Notebook code for training (~train.ipynb) 52 | 53 | ## Test Details 54 | You can download our Pre-trained models in our [Google Drive](https://drive.google.com/open?id=1Q5kkI_GxCY066c9owqzFFjzB_iEFCefJ) 55 | 56 | Download the models and save them in the directory './checkpoint' 57 | Then you can run the test code ('Test.ipynb') 58 | 59 | Quantitative calculation (MS-SSIM / MMD score) & Image sampling is availble in the code. 60 | 61 | For the PCA visualization, please follow the PCA tutorial that Nilearn provides. 62 | 63 | ## Model Details 64 | You can get the detailed settings of used models in our model codes 65 | 66 | (Model_alphaGAN.py , Model_alphaWGAN.py , Model_VAEGAN.py, Model_WGAN.py) 67 | 68 | 69 | ## Details for Dataset 70 | If you have any question about data, feel free to e-mail me! 71 | 72 | cyclomon@kaist.ac.kr 73 | -------------------------------------------------------------------------------- /Model_alphaWGAN.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import os 4 | from torch import nn 5 | from torch import optim 6 | from torch.nn import functional as F 7 | 8 | #*********************************************** 9 | #Encoder and Discriminator has same architecture 10 | #*********************************************** 11 | class Discriminator(nn.Module): 12 | def __init__(self, channel=512,out_class=1,is_dis =True): 13 | super(Discriminator, self).__init__() 14 | self.is_dis=is_dis 15 | self.channel = channel 16 | n_class = out_class 17 | 18 | self.conv1 = nn.Conv3d(1, channel//8, kernel_size=4, stride=2, padding=1) 19 | self.conv2 = nn.Conv3d(channel//8, channel//4, kernel_size=4, stride=2, padding=1) 20 | self.bn2 = nn.BatchNorm3d(channel//4) 21 | self.conv3 = nn.Conv3d(channel//4, channel//2, kernel_size=4, stride=2, padding=1) 22 | self.bn3 = nn.BatchNorm3d(channel//2) 23 | self.conv4 = nn.Conv3d(channel//2, channel, kernel_size=4, stride=2, padding=1) 24 | self.bn4 = nn.BatchNorm3d(channel) 25 | self.conv5 = nn.Conv3d(channel, n_class, kernel_size=4, stride=1, padding=0) 26 | 27 | def forward(self, x, _return_activations=False): 28 | h1 = F.leaky_relu(self.conv1(x), negative_slope=0.2) 29 | h2 = F.leaky_relu(self.bn2(self.conv2(h1)), negative_slope=0.2) 30 | h3 = F.leaky_relu(self.bn3(self.conv3(h2)), negative_slope=0.2) 31 | h4 = F.leaky_relu(self.bn4(self.conv4(h3)), negative_slope=0.2) 32 | h5 = self.conv5(h4) 33 | output = h5 34 | 35 | return output 36 | 37 | class Code_Discriminator(nn.Module): 38 | def __init__(self, code_size=100,num_units=750): 39 | super(Code_Discriminator, self).__init__() 40 | n_class = 1 41 | self.l1 = nn.Sequential(nn.Linear(code_size, num_units), 42 | nn.BatchNorm1d(num_units), 43 | nn.LeakyReLU(0.2,inplace=True)) 44 | self.l2 = nn.Sequential(nn.Linear(num_units, num_units), 45 | nn.BatchNorm1d(num_units), 46 | nn.LeakyReLU(0.2,inplace=True)) 47 | self.l3 = nn.Linear(num_units, 1) 48 | 49 | def forward(self, x): 50 | h1 = self.l1(x) 51 | h2 = self.l2(h1) 52 | h3 = self.l3(h2) 53 | output = h3 54 | 55 | return output 56 | 57 | class Generator(nn.Module): 58 | def __init__(self, noise:int=100, channel:int=64): 59 | super(Generator, self).__init__() 60 | _c = channel 61 | 62 | self.relu = nn.ReLU() 63 | self.noise = noise 64 | self.tp_conv1 = nn.ConvTranspose3d(noise, _c*8, kernel_size=4, stride=1, padding=0, bias=False) 65 | self.bn1 = nn.BatchNorm3d(_c*8) 66 | 67 | self.tp_conv2 = nn.Conv3d(_c*8, _c*4, kernel_size=3, stride=1, padding=1, bias=False) 68 | self.bn2 = nn.BatchNorm3d(_c*4) 69 | 70 | self.tp_conv3 = nn.Conv3d(_c*4, _c*2, kernel_size=3, stride=1, padding=1, bias=False) 71 | self.bn3 = nn.BatchNorm3d(_c*2) 72 | 73 | self.tp_conv4 = nn.Conv3d(_c*2, _c, kernel_size=3, stride=1, padding=1, bias=False) 74 | self.bn4 = nn.BatchNorm3d(_c) 75 | 76 | self.tp_conv5 = nn.Conv3d(_c, 1, kernel_size=3, stride=1, padding=1, bias=False) 77 | 78 | def forward(self, noise): 79 | 80 | noise = noise.view(-1,self.noise,1,1,1) 81 | h = self.tp_conv1(noise) 82 | h = self.relu(self.bn1(h)) 83 | 84 | h = F.upsample(h,scale_factor = 2) 85 | h = self.tp_conv2(h) 86 | h = self.relu(self.bn2(h)) 87 | 88 | h = F.upsample(h,scale_factor = 2) 89 | h = self.tp_conv3(h) 90 | h = self.relu(self.bn3(h)) 91 | 92 | h = F.upsample(h,scale_factor = 2) 93 | h = self.tp_conv4(h) 94 | h = self.relu(self.bn4(h)) 95 | 96 | h = F.upsample(h,scale_factor = 2) 97 | h = self.tp_conv5(h) 98 | 99 | h = F.tanh(h) 100 | 101 | return h -------------------------------------------------------------------------------- /Model_alphaGAN.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import os 4 | from torch import nn 5 | from torch import optim 6 | from torch.nn import functional as F 7 | 8 | #*********************************************** 9 | #Encoder and Discriminator has same architecture 10 | #*********************************************** 11 | class Discriminator(nn.Module): 12 | def __init__(self, channel=512,out_class=1,is_dis =True): 13 | super(Discriminator, self).__init__() 14 | self.is_dis=is_dis 15 | self.channel = channel 16 | n_class = out_class 17 | 18 | self.conv1 = nn.Conv3d(1, channel//8, kernel_size=4, stride=2, padding=1) 19 | self.conv2 = nn.Conv3d(channel//8, channel//4, kernel_size=4, stride=2, padding=1) 20 | self.bn2 = nn.BatchNorm3d(channel//4) 21 | self.conv3 = nn.Conv3d(channel//4, channel//2, kernel_size=4, stride=2, padding=1) 22 | self.bn3 = nn.BatchNorm3d(channel//2) 23 | self.conv4 = nn.Conv3d(channel//2, channel, kernel_size=4, stride=2, padding=1) 24 | self.bn4 = nn.BatchNorm3d(channel) 25 | 26 | self.conv5 = nn.Conv3d(channel, n_class, kernel_size=4, stride=1, padding=0) 27 | 28 | def forward(self, x, _return_activations=False): 29 | h1 = F.leaky_relu(self.conv1(x), negative_slope=0.2) 30 | h2 = F.leaky_relu(self.bn2(self.conv2(h1)), negative_slope=0.2) 31 | h3 = F.leaky_relu(self.bn3(self.conv3(h2)), negative_slope=0.2) 32 | h4 = F.leaky_relu(self.bn4(self.conv4(h3)), negative_slope=0.2) 33 | h5 = self.conv5(h4) 34 | 35 | if self.is_dis: 36 | output = F.sigmoid(h5.view(h5.size()[0],-1)) 37 | else: 38 | output = h5.view(h5.size()[0],-1) 39 | 40 | return output 41 | 42 | class Code_Discriminator(nn.Module): 43 | def __init__(self, code_size=100,num_units=750): 44 | super(Code_Discriminator, self).__init__() 45 | n_class = 1 46 | self.l1 = nn.Sequential(nn.Linear(code_size, num_units), 47 | nn.BatchNorm1d(num_units), 48 | nn.LeakyReLU(0.2,inplace=True)) 49 | self.l2 = nn.Sequential(nn.Linear(num_units, num_units), 50 | nn.BatchNorm1d(num_units), 51 | nn.LeakyReLU(0.2,inplace=True)) 52 | self.l3 = nn.Linear(num_units, 1) 53 | 54 | def forward(self, x): 55 | h1 = self.l1(x) 56 | h2 = self.l2(h1) 57 | h3 = self.l3(h2) 58 | output = F.sigmoid(h3) 59 | 60 | return output 61 | 62 | class Generator(nn.Module): 63 | def __init__(self, noise:int=100, channel:int=64): 64 | super(Generator, self).__init__() 65 | _c = channel 66 | 67 | self.relu = nn.ReLU() 68 | self.noise = noise 69 | self.tp_conv1 = nn.ConvTranspose3d(noise, _c*8, kernel_size=4, stride=1, padding=0, bias=False) 70 | self.bn1 = nn.BatchNorm3d(_c*8) 71 | 72 | self.tp_conv2 = nn.Conv3d(_c*8, _c*4, kernel_size=3, stride=1, padding=1, bias=False) 73 | self.bn2 = nn.BatchNorm3d(_c*4) 74 | 75 | self.tp_conv3 = nn.Conv3d(_c*4, _c*2, kernel_size=3, stride=1, padding=1, bias=False) 76 | self.bn3 = nn.BatchNorm3d(_c*2) 77 | 78 | self.tp_conv4 = nn.Conv3d(_c*2, _c, kernel_size=3, stride=1, padding=1, bias=False) 79 | self.bn4 = nn.BatchNorm3d(_c) 80 | 81 | self.tp_conv5 = nn.Conv3d(_c, 1, kernel_size=3, stride=1, padding=1, bias=False) 82 | 83 | def forward(self, noise): 84 | 85 | noise = noise.view(-1,self.noise,1,1,1) 86 | h = self.tp_conv1(noise) 87 | h = self.relu(self.bn1(h)) 88 | 89 | h = F.upsample(h,scale_factor = 2) 90 | h = self.tp_conv2(h) 91 | h = self.relu(self.bn2(h)) 92 | 93 | h = F.upsample(h,scale_factor = 2) 94 | h = self.tp_conv3(h) 95 | h = self.relu(self.bn3(h)) 96 | 97 | h = F.upsample(h,scale_factor = 2) 98 | h = self.tp_conv4(h) 99 | h = self.relu(self.bn4(h)) 100 | 101 | h = F.upsample(h,scale_factor = 2) 102 | h = self.tp_conv5(h) 103 | 104 | h = F.tanh(h) 105 | 106 | return h -------------------------------------------------------------------------------- /Model_VAEGAN.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import os 4 | from torch import nn 5 | from torch import optim 6 | from torch.nn import functional as F 7 | 8 | class Discriminator(nn.Module): 9 | def __init__(self, channel=512,out_class=1): 10 | super(Discriminator, self).__init__() 11 | 12 | self.channel = channel 13 | n_class = out_class 14 | 15 | self.conv1 = nn.Conv3d(1, channel//8, kernel_size=4, stride=2, padding=1) 16 | self.conv2 = nn.Conv3d(channel//8, channel//4, kernel_size=4, stride=2, padding=1) 17 | self.bn2 = nn.BatchNorm3d(channel//4) 18 | self.conv3 = nn.Conv3d(channel//4, channel//2, kernel_size=4, stride=2, padding=1) 19 | self.bn3 = nn.BatchNorm3d(channel//2) 20 | self.conv4 = nn.Conv3d(channel//2, channel, kernel_size=4, stride=2, padding=1) 21 | self.bn4 = nn.BatchNorm3d(channel) 22 | self.conv5 = nn.Conv3d(channel, n_class, kernel_size=4, stride=1, padding=0) 23 | 24 | 25 | def forward(self, x): 26 | batch_size = x.size()[0] 27 | h1 = F.leaky_relu(self.conv1(x), negative_slope=0.2) 28 | h2 = F.leaky_relu(self.bn2(self.conv2(h1)), negative_slope=0.2) 29 | h3 = F.leaky_relu(self.bn3(self.conv3(h2)), negative_slope=0.2) 30 | h4 = F.leaky_relu(self.bn4(self.conv4(h3)), negative_slope=0.2) 31 | h5 = self.conv5(h4) 32 | output = F.sigmoid(h5.view(h5.size()[0],-1)) 33 | return output 34 | 35 | class Encoder(nn.Module): 36 | def __init__(self, channel=512,out_class=1): 37 | super(Encoder, self).__init__() 38 | 39 | self.channel = channel 40 | n_class = out_class 41 | 42 | self.conv1 = nn.Conv3d(1, channel//8, kernel_size=4, stride=2, padding=1) 43 | self.conv2 = nn.Conv3d(channel//8, channel//4, kernel_size=4, stride=2, padding=1) 44 | self.bn2 = nn.BatchNorm3d(channel//4) 45 | self.conv3 = nn.Conv3d(channel//4, channel//2, kernel_size=4, stride=2, padding=1) 46 | self.bn3 = nn.BatchNorm3d(channel//2) 47 | self.conv4 = nn.Conv3d(channel//2, channel, kernel_size=4, stride=2, padding=1) 48 | self.bn4 = nn.BatchNorm3d(channel) 49 | 50 | self.mean = nn.Sequential( 51 | nn.Linear(32768, 2048), 52 | nn.BatchNorm1d(2048), 53 | nn.ReLU(), 54 | nn.Linear(2048, 1000)) 55 | self.logvar = nn.Sequential( 56 | nn.Linear(32768, 2048), 57 | nn.BatchNorm1d(2048), 58 | nn.ReLU(), 59 | nn.Linear(2048, 1000)) 60 | 61 | def forward(self, x, _return_activations=False): 62 | batch_size = x.size()[0] 63 | h1 = F.leaky_relu(self.conv1(x), negative_slope=0.2) 64 | h2 = F.leaky_relu(self.bn2(self.conv2(h1)), negative_slope=0.2) 65 | h3 = F.leaky_relu(self.bn3(self.conv3(h2)), negative_slope=0.2) 66 | h4 = F.leaky_relu(self.bn4(self.conv4(h3)), negative_slope=0.2) 67 | 68 | mean = self.mean(h4.view(batch_size,-1)) 69 | logvar = self.logvar(h4.view(batch_size,-1)) 70 | 71 | std = logvar.mul(0.5).exp_() 72 | reparametrized_noise = Variable(torch.randn((batch_size, 1000))).cuda() 73 | reparametrized_noise = mean + std * reparametrized_noise 74 | return mean,logvar ,reparametrized_noise 75 | 76 | class Generator(nn.Module): 77 | def __init__(self, noise:int=100, channel:int=64): 78 | super(Generator, self).__init__() 79 | _c = channel 80 | 81 | self.noise = noise 82 | self.fc = nn.Linear(1000,512*4*4*4) 83 | self.bn1 = nn.BatchNorm3d(_c*8) 84 | 85 | self.tp_conv2 = nn.Conv3d(_c*8, _c*4, kernel_size=3, stride=1, padding=1, bias=False) 86 | self.bn2 = nn.BatchNorm3d(_c*4) 87 | 88 | self.tp_conv3 = nn.Conv3d(_c*4, _c*2, kernel_size=3, stride=1, padding=1, bias=False) 89 | self.bn3 = nn.BatchNorm3d(_c*2) 90 | 91 | self.tp_conv4 = nn.Conv3d(_c*2, _c, kernel_size=3, stride=1, padding=1, bias=False) 92 | self.bn4 = nn.BatchNorm3d(_c) 93 | 94 | self.tp_conv5 = nn.Conv3d(_c, 1, kernel_size=3, stride=1, padding=1, bias=False) 95 | 96 | def forward(self, noise): 97 | noise = noise.view(-1, 1000) 98 | h = self.fc(noise) 99 | h = h.view(-1,512,4,4,4) 100 | h = F.relu(self.bn1(h)) 101 | 102 | h = F.upsample(h,scale_factor = 2) 103 | h = self.tp_conv2(h) 104 | h = F.relu(self.bn2(h)) 105 | 106 | h = F.upsample(h,scale_factor = 2) 107 | h = self.tp_conv3(h) 108 | h = F.relu(self.bn3(h)) 109 | 110 | h = F.upsample(h,scale_factor = 2) 111 | h = self.tp_conv4(h) 112 | h = F.relu(self.bn4(h)) 113 | 114 | h = F.upsample(h,scale_factor = 2) 115 | h = self.tp_conv5(h) 116 | 117 | h = F.tanh(h) 118 | 119 | return h 120 | -------------------------------------------------------------------------------- /WGAN_ADNI_train.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pylab inline\n", 10 | "import numpy as np\n", 11 | "import torch\n", 12 | "import os\n", 13 | "\n", 14 | "from torch import nn\n", 15 | "from torch import optim\n", 16 | "from torch.nn import functional as F\n", 17 | "from torch import autograd\n", 18 | "from torch.autograd import Variable\n", 19 | "import nibabel as nib\n", 20 | "from torch.utils.data.dataset import Dataset\n", 21 | "from torch.utils.data import dataloader\n", 22 | "from nilearn import plotting\n", 23 | "from ADNI_dataset import *\n", 24 | "from BRATS_dataset import *\n", 25 | "from ATLAS_dataset import *\n", 26 | "from Model_WGAN import *" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "# Configuration" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "BATCH_SIZE=4\n", 43 | "max_epoch = 100\n", 44 | "lr = 0.0001\n", 45 | "gpu = True\n", 46 | "workers = 4\n", 47 | "\n", 48 | "LAMBDA= 10\n", 49 | "#setting latent variable sizes\n", 50 | "latent_dim = 1000" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "trainset = ADNIdataset(augmentation=True)\n", 60 | "train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 61 | " shuffle=True,num_workers=workers)\n", 62 | "if Use_BRATS:\n", 63 | " #'flair' or 't2' or 't1ce'\n", 64 | " trainset = BRATSdataset(imgtype='flair')\n", 65 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size = BATCH_SIZE, shuffle=True,\n", 66 | " num_workers=workers)\n", 67 | "if Use_ATLAS:\n", 68 | " trainset = ATLASdataset(augmentation=True)\n", 69 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 70 | " shuffle=True,num_workers=workers)\n", 71 | "\n", 72 | "def inf_train_gen(data_loader):\n", 73 | " while True:\n", 74 | " for _,images in enumerate(data_loader):\n", 75 | " yield images\n" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "D = Discriminator()\n", 85 | "G = Generator(noise = latent_dim)\n", 86 | "\n", 87 | "g_optimizer = optim.Adam(G.parameters(), lr=0.0002)\n", 88 | "d_optimizer = optim.Adam(D.parameters(), lr=0.0002)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "def calc_gradient_penalty(netD, real_data, fake_data): \n", 98 | " alpha = torch.rand(real_data.size(0),1,1,1,1)\n", 99 | " alpha = alpha.expand(real_data.size())\n", 100 | " \n", 101 | " alpha = alpha.cuda()\n", 102 | "\n", 103 | " interpolates = alpha * real_data + ((1 - alpha) * fake_data)\n", 104 | "\n", 105 | " interpolates = interpolates.cuda()\n", 106 | " interpolates = Variable(interpolates, requires_grad=True)\n", 107 | "\n", 108 | " disc_interpolates = netD(interpolates)\n", 109 | "\n", 110 | " gradients = autograd.grad(outputs=disc_interpolates, inputs=interpolates,\n", 111 | " grad_outputs=torch.ones(disc_interpolates.size()).cuda(),\n", 112 | " create_graph=True, retain_graph=True, only_inputs=True)[0]\n", 113 | "\n", 114 | " gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean() * LAMBDA\n", 115 | " return gradient_penalty" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "# Training" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "real_y = Variable(torch.ones((BATCH_SIZE, 1)).cuda())\n", 132 | "fake_y = Variable(torch.zeros((BATCH_SIZE, 1)).cuda())\n", 133 | "loss_f = nn.BCELoss()\n", 134 | "\n", 135 | "d_real_losses = list()\n", 136 | "d_fake_losses = list()\n", 137 | "d_losses = list()\n", 138 | "g_losses = list()\n", 139 | "divergences = list()" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": { 146 | "scrolled": true 147 | }, 148 | "outputs": [], 149 | "source": [ 150 | "TOTAL_ITER = 200000\n", 151 | "gen_load = inf_train_gen(train_loader)\n", 152 | "for iteration in range(TOTAL_ITER):\n", 153 | " ###############################################\n", 154 | " # Train D \n", 155 | " ###############################################\n", 156 | " for p in D.parameters(): \n", 157 | " p.requires_grad = True \n", 158 | "\n", 159 | " real_images = gen_load.__next__()\n", 160 | " D.zero_grad()\n", 161 | " real_images = Variable(real_images).cuda()\n", 162 | "\n", 163 | " _batch_size = real_images.size(0)\n", 164 | "\n", 165 | "\n", 166 | " y_real_pred = D(real_images)\n", 167 | "\n", 168 | " d_real_loss = y_real_pred.mean()\n", 169 | " \n", 170 | " noise = Variable(torch.randn((_batch_size, latent_dim, 1, 1, 1)),volatile=True).cuda()\n", 171 | " fake_images = G(noise)\n", 172 | " y_fake_pred = D(fake_images.detach())\n", 173 | "\n", 174 | " d_fake_loss = y_fake_pred.mean()\n", 175 | "\n", 176 | " gradient_penalty = calc_gradient_penalty(D,real_images.data, fake_images.data)\n", 177 | " \n", 178 | " d_loss = - d_real_loss + d_fake_loss +gradient_penalty\n", 179 | " d_loss.backward()\n", 180 | " Wasserstein_D = d_real_loss - d_fake_loss\n", 181 | "\n", 182 | " d_optimizer.step()\n", 183 | "\n", 184 | " ###############################################\n", 185 | " # Train G \n", 186 | " ###############################################\n", 187 | " for p in D.parameters():\n", 188 | " p.requires_grad = False\n", 189 | " \n", 190 | " for iters in range(5):\n", 191 | " G.zero_grad()\n", 192 | " noise = Variable(torch.randn((_batch_size, latent_dim, 1, 1 ,1)).cuda())\n", 193 | " fake_image =G(noise)\n", 194 | " y_fake_g = D(fake_image)\n", 195 | "\n", 196 | " g_loss = -y_fake_g.mean()\n", 197 | "\n", 198 | " g_loss.backward()\n", 199 | " g_optimizer.step()\n", 200 | "\n", 201 | " ###############################################\n", 202 | " # Visualization\n", 203 | " ###############################################\n", 204 | " if iteration%10 == 0:\n", 205 | " d_real_losses.append(d_real_loss.data[0])\n", 206 | " d_fake_losses.append(d_fake_loss.data[0])\n", 207 | " d_losses.append(d_loss.data[0])\n", 208 | " g_losses.append(g_loss.data.cpu().numpy())\n", 209 | "\n", 210 | " print('[{}/{}]'.format(iteration,TOTAL_ITER),\n", 211 | " 'D: {:<8.3}'.format(d_loss.data[0].cpu().numpy()), \n", 212 | " 'D_real: {:<8.3}'.format(d_real_loss.data[0].cpu().numpy()),\n", 213 | " 'D_fake: {:<8.3}'.format(d_fake_loss.data[0].cpu().numpy()), \n", 214 | " 'G: {:<8.3}'.format(g_loss.data[0].cpu().numpy()))\n", 215 | "\n", 216 | " featmask = np.squeeze((0.5*fake_image+0.5)[0].data.cpu().numpy())\n", 217 | "\n", 218 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 219 | " plotting.plot_img(featmask,title=\"FAKE\")\n", 220 | " plotting.show()\n", 221 | " \n", 222 | " if (iteration+1)%500 ==0:\n", 223 | " torch.save(G.state_dict(),'./checkpoint/G_W_iter'+str(iteration+1)+'.pth')\n", 224 | " torch.save(D.state_dict(),'./checkpoint/D_W_iter'+str(iteration+1)+'.pth')" 225 | ] 226 | } 227 | ], 228 | "metadata": { 229 | "kernelspec": { 230 | "display_name": "Python 3", 231 | "language": "python", 232 | "name": "python3" 233 | }, 234 | "language_info": { 235 | "codemirror_mode": { 236 | "name": "ipython", 237 | "version": 3 238 | }, 239 | "file_extension": ".py", 240 | "mimetype": "text/x-python", 241 | "name": "python", 242 | "nbconvert_exporter": "python", 243 | "pygments_lexer": "ipython3", 244 | "version": "3.5.6" 245 | } 246 | }, 247 | "nbformat": 4, 248 | "nbformat_minor": 2 249 | } 250 | -------------------------------------------------------------------------------- /VAEGAN_ADNI_train.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pylab inline\n", 10 | "import numpy as np\n", 11 | "import torch\n", 12 | "import os\n", 13 | "\n", 14 | "from torch import nn\n", 15 | "from torch import optim\n", 16 | "from torch.nn import functional as F\n", 17 | "from torch import autograd\n", 18 | "from torch.autograd import Variable\n", 19 | "import nibabel as nib\n", 20 | "from torch.utils.data.dataset import Dataset\n", 21 | "from torch.utils.data import dataloader\n", 22 | "from skimage.transform import resize\n", 23 | "from nilearn import plotting\n", 24 | "from ADNI_dataset import *\n", 25 | "from BRATS_dataset import *\n", 26 | "from ATLAS_dataset import *\n", 27 | "from Model_VAEGAN import *" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "# Configuration" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "BATCH_SIZE=4\n", 44 | "max_epoch = 100\n", 45 | "gpu = True\n", 46 | "workers = 4\n", 47 | "\n", 48 | "reg = 5e-10\n", 49 | "\n", 50 | "gamma = 20\n", 51 | "beta = 10\n", 52 | "\n", 53 | "Use_BRATS=False\n", 54 | "Use_ATLAS = False\n", 55 | "\n", 56 | "#setting latent variable sizes\n", 57 | "latent_dim = 1000" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "trainset = ADNIdataset(augmentation=True)\n", 67 | "train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 68 | " shuffle=True,num_workers=workers)\n", 69 | "if Use_BRATS:\n", 70 | " #'flair' or 't2' or 't1ce'\n", 71 | " trainset = BRATSdataset(imgtype='flair')\n", 72 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size = BATCH_SIZE, shuffle=True,\n", 73 | " num_workers=workers)\n", 74 | "if Use_ATLAS:\n", 75 | " trainset = ATLASdataset(augmentation=True)\n", 76 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 77 | " shuffle=True,num_workers=workers)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "G = Generator(noise = latent_dim)\n", 87 | "D = Discriminator()\n", 88 | "E = Encoder()\n", 89 | "\n", 90 | "G.cuda()\n", 91 | "D.cuda()\n", 92 | "E.cuda()" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "g_optimizer = optim.Adam(G.parameters(), lr=0.0001)\n", 102 | "d_optimizer = optim.Adam(D.parameters(), lr=0.0001)\n", 103 | "e_optimizer = optim.Adam(E.parameters(), lr = 0.0001)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "# Training" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "N_EPOCH = 100\n", 120 | "\n", 121 | "real_y = Variable(torch.ones((BATCH_SIZE, 1)).cuda())\n", 122 | "fake_y = Variable(torch.zeros((BATCH_SIZE, 1)).cuda())\n", 123 | "criterion_bce = nn.BCELoss()\n", 124 | "criterion_l1 = nn.L1Loss()\n" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": { 131 | "scrolled": true 132 | }, 133 | "outputs": [], 134 | "source": [ 135 | "for epoch in range(N_EPOCH):\n", 136 | " for step, real_images in enumerate(train_loader):\n", 137 | " _batch_size = real_images.size(0)\n", 138 | " real_images = Variable(real_images,requires_grad=False).cuda()\n", 139 | " z_rand = Variable(torch.randn((_batch_size, latent_dim)),requires_grad=False).cuda()\n", 140 | " mean,logvar,code = E(real_images)\n", 141 | " x_rec = G(code)\n", 142 | " x_rand = G(z_rand)\n", 143 | " ###############################################\n", 144 | " # Train D \n", 145 | " ###############################################\n", 146 | " d_optimizer.zero_grad()\n", 147 | " \n", 148 | " d_real_loss = criterion_bce(D(real_images),real_y[:_batch_size])\n", 149 | " d_recon_loss = criterion_bce(D(x_rec), fake_y[:_batch_size])\n", 150 | " d_fake_loss = criterion_bce(D(x_rand), fake_y[:_batch_size])\n", 151 | " \n", 152 | " dis_loss = d_recon_loss+d_real_loss + d_fake_loss\n", 153 | " dis_loss.backward(retain_graph=True)\n", 154 | " \n", 155 | " d_optimizer.step()\n", 156 | " \n", 157 | " ###############################################\n", 158 | " # Train G\n", 159 | " ###############################################\n", 160 | " g_optimizer.zero_grad()\n", 161 | " output = D(real_images)\n", 162 | " d_real_loss = criterion_bce(output,real_y[:_batch_size])\n", 163 | " output = D(x_rec)\n", 164 | " d_recon_loss = criterion_bce(output,fake_y[:_batch_size])\n", 165 | " output = D(x_rand)\n", 166 | " d_fake_loss = criterion_bce(output,fake_y[:_batch_size])\n", 167 | " \n", 168 | " d_img_loss = d_real_loss + d_recon_loss+ d_fake_loss\n", 169 | " gen_img_loss = -d_img_loss\n", 170 | " \n", 171 | " rec_loss = ((x_rec - real_images)**2).mean()\n", 172 | " \n", 173 | " err_dec = gamma* rec_loss + gen_img_loss\n", 174 | " \n", 175 | " err_dec.backward(retain_graph=True)\n", 176 | " g_optimizer.step()\n", 177 | " ###############################################\n", 178 | " # Train E\n", 179 | " ###############################################\n", 180 | " prior_loss = 1+logvar-mean.pow(2) - logvar.exp()\n", 181 | " prior_loss = (-0.5*torch.sum(prior_loss))/torch.numel(mean.data)\n", 182 | " err_enc = prior_loss + beta*rec_loss\n", 183 | " \n", 184 | " e_optimizer.zero_grad()\n", 185 | " err_enc.backward()\n", 186 | " e_optimizer.step()\n", 187 | " ###############################################\n", 188 | " # Visualization\n", 189 | " ###############################################\n", 190 | " \n", 191 | " if step % 10 == 0:\n", 192 | " print('[{}/{}]'.format(epoch,N_EPOCH),\n", 193 | " 'D: {:<8.3}'.format(dis_loss.data[0].cpu().numpy()), \n", 194 | " 'En: {:<8.3}'.format(err_enc.data[0].cpu().numpy()),\n", 195 | " 'De: {:<8.3}'.format(err_dec.data[0].cpu().numpy()) \n", 196 | " )\n", 197 | " \n", 198 | " featmask = np.squeeze((0.5*real_images[0]+0.5).data.cpu().numpy())\n", 199 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 200 | " plotting.plot_img(featmask,title=\"X_Real\")\n", 201 | " plotting.show()\n", 202 | " \n", 203 | " featmask = np.squeeze((0.5*x_rec[0]+0.5).data.cpu().numpy())\n", 204 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 205 | " plotting.plot_img(featmask,title=\"X_DEC\")\n", 206 | " plotting.show()\n", 207 | " \n", 208 | " featmask = np.squeeze((0.5*x_rand[0]+0.5).data.cpu().numpy())\n", 209 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 210 | " plotting.plot_img(featmask,title=\"X_rand\")\n", 211 | " plotting.show()\n", 212 | "\n", 213 | " torch.save(G.state_dict(),'./chechpoint/G_VG_ep_'+str(epoch+1)+'.pth')\n", 214 | " torch.save(D.state_dict(),'./chechpoint/D_VG_ep_'+str(epoch+1)+'.pth')\n", 215 | " torch.save(E.state_dict(),'./chechpoint/E_VG_ep_'+str(epoch+1)+'.pth')" 216 | ] 217 | } 218 | ], 219 | "metadata": { 220 | "kernelspec": { 221 | "display_name": "Python 3", 222 | "language": "python", 223 | "name": "python3" 224 | }, 225 | "language_info": { 226 | "codemirror_mode": { 227 | "name": "ipython", 228 | "version": 3 229 | }, 230 | "file_extension": ".py", 231 | "mimetype": "text/x-python", 232 | "name": "python", 233 | "nbconvert_exporter": "python", 234 | "pygments_lexer": "ipython3", 235 | "version": "3.5.6" 236 | } 237 | }, 238 | "nbformat": 4, 239 | "nbformat_minor": 2 240 | } 241 | -------------------------------------------------------------------------------- /pytorch_ssim/__init__.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn.functional as F 3 | from math import exp 4 | import numpy as np 5 | import scipy.ndimage as ndimage 6 | 7 | 8 | def gaussian(window_size, sigma): 9 | gauss = torch.Tensor([exp(-(x - window_size//2)**2/float(2*sigma**2)) for x in range(window_size)]) 10 | return gauss/gauss.sum() 11 | 12 | 13 | def create_window(window_size, channel=1): 14 | _1D_window = gaussian(window_size, 1.5).unsqueeze(1) 15 | _2D_window = _1D_window.mm(_1D_window.t()).float().unsqueeze(0).unsqueeze(0) 16 | window = _2D_window.expand(channel, 1, window_size, window_size).contiguous() 17 | _3D_window = filters.gaussian_filter 18 | return window 19 | def ssim_exact(img1, img2, sd=1.5, C1=0.01**2, C2=0.03**2): 20 | 21 | mu1 = ndimage.gaussian_filter(img1, sd) 22 | mu2 = ndimage.gaussian_filter(img2, sd) 23 | mu1_sq = mu1 * mu1 24 | mu2_sq = mu2 * mu2 25 | mu1_mu2 = mu1 * mu2 26 | sigma1_sq = ndimage.gaussian_filter(img1 * img1, sd) - mu1_sq 27 | sigma2_sq = ndimage.gaussian_filter(img2 * img2, sd) - mu2_sq 28 | sigma12 = ndimage.gaussian_filter(img1 * img2, sd) - mu1_mu2 29 | 30 | ssim_num = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) 31 | 32 | ssim_den = ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2)) 33 | 34 | ssim_map = ssim_num / ssim_den 35 | 36 | v1 = 2.0 * sigma12 + C2 37 | v2 = sigma1_sq + sigma2_sq + C2 38 | cs = np.mean(v1 / v2) # contrast sensitivity 39 | 40 | # ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2) 41 | 42 | return np.mean(ssim_map),cs 43 | 44 | def ssim_3d(img1, img2, window_size=11, window=None, size_average=True, full=False, val_range=None): 45 | # Value range can be different from 255. Other common ranges are 1 (sigmoid) and 2 (tanh). 46 | if val_range is None: 47 | if torch.max(img1) > 128: 48 | max_val = 255 49 | else: 50 | max_val = 1 51 | 52 | if torch.min(img1) < -0.5: 53 | min_val = -1 54 | else: 55 | min_val = 0 56 | L = max_val - min_val 57 | else: 58 | L = val_range 59 | 60 | padd = 0 61 | (_, channel, height, width,width2) = img1.size() 62 | if window is None: 63 | real_size = min(window_size, height, width,width2) 64 | window = create_window(real_size, channel=channel).to(img1.device) 65 | 66 | mu1 = F.conv3d(img1, window, padding=padd, groups=channel) 67 | mu2 = F.conv3d(img2, window, padding=padd, groups=channel) 68 | 69 | mu1_sq = mu1.pow(2) 70 | mu2_sq = mu2.pow(2) 71 | mu1_mu2 = mu1 * mu2 72 | 73 | sigma1_sq = F.conv3d(img1 * img1, window, padding=padd, groups=channel) - mu1_sq 74 | sigma2_sq = F.conv3d(img2 * img2, window, padding=padd, groups=channel) - mu2_sq 75 | sigma12 = F.conv3d(img1 * img2, window, padding=padd, groups=channel) - mu1_mu2 76 | 77 | C1 = (0.01 * L) ** 2 78 | C2 = (0.03 * L) ** 2 79 | 80 | v1 = 2.0 * sigma12 + C2 81 | v2 = sigma1_sq + sigma2_sq + C2 82 | cs = torch.mean(v1 / v2) # contrast sensitivity 83 | 84 | ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2) 85 | 86 | if size_average: 87 | ret = ssim_map.mean() 88 | else: 89 | ret = ssim_map.mean(1).mean(1).mean(1) 90 | 91 | if full: 92 | return ret, cs 93 | return ret 94 | 95 | 96 | def msssim_3d(img1, img2, window_size=11, size_average=True, val_range=None, normalize=False): 97 | device = img1.device 98 | weights = torch.FloatTensor([0.0448, 0.2856, 0.3001, 0.2363, 0.1333]).to(device) 99 | levels = weights.size()[0] 100 | mssim = [] 101 | mcs = [] 102 | for _ in range(levels): 103 | sim, cs = ssim_exact(img1.data.cpu().numpy(), img2.data.cpu().numpy()) 104 | mssim.append(sim) 105 | mcs.append(cs) 106 | 107 | img1 = F.avg_pool3d(img1, (2, 2,2)) 108 | img2 = F.avg_pool3d(img2, (2, 2,2)) 109 | 110 | # mssim = torch.stack(torch.from_numpy(mssim)) 111 | # mcs = torch.stack(torch.from_numpy(mcs)) 112 | mssim = np.asarray(mssim) 113 | mcs = np.asarray(mcs) 114 | # Normalize (to avoid NaNs during training unstable models, not compliant with original definition) 115 | if normalize: 116 | mssim = (mssim + 1) / 2 117 | mcs = (mcs + 1) / 2 118 | 119 | pow1 = mcs ** weights 120 | pow2 = mssim ** weights 121 | # From Matlab implementation https://ece.uwaterloo.ca/~z70wang/research/iwssim/ 122 | output = torch.prod(pow1[:-1] * pow2[-1]) 123 | return output 124 | 125 | 126 | # Classes to re-use window 127 | class SSIM(torch.nn.Module): 128 | def __init__(self, window_size=11, size_average=True, val_range=None): 129 | super(SSIM, self).__init__() 130 | self.window_size = window_size 131 | self.size_average = size_average 132 | self.val_range = val_range 133 | 134 | # Assume 1 channel for SSIM 135 | self.channel = 1 136 | self.window = create_window(window_size) 137 | 138 | def forward(self, img1, img2): 139 | (_, channel, _, _) = img1.size() 140 | 141 | if channel == self.channel and self.window.dtype == img1.dtype: 142 | window = self.window 143 | else: 144 | window = create_window(self.window_size, channel).to(img1.device).type(img1.dtype) 145 | self.window = window 146 | self.channel = channel 147 | 148 | return ssim(img1, img2, window=window, window_size=self.window_size, size_average=self.size_average) 149 | 150 | class MSSSIM_3d(torch.nn.Module): 151 | def __init__(self, window_size=11, size_average=True, channel=3): 152 | super(MSSSIM_3d, self).__init__() 153 | self.window_size = window_size 154 | self.size_average = size_average 155 | self.channel = channel 156 | 157 | def forward(self, img1, img2): 158 | # TODO: store window between calls if possible 159 | return msssim_3d(img1, img2, window_size=self.window_size, size_average=self.size_average) 160 | 161 | def ssim(img1, img2, window_size=11, window=None, size_average=True, full=False, val_range=None): 162 | # Value range can be different from 255. Other common ranges are 1 (sigmoid) and 2 (tanh). 163 | if val_range is None: 164 | if torch.max(img1) > 128: 165 | max_val = 255 166 | else: 167 | max_val = 1 168 | 169 | if torch.min(img1) < -0.5: 170 | min_val = -1 171 | else: 172 | min_val = 0 173 | L = max_val - min_val 174 | else: 175 | L = val_range 176 | 177 | padd = 0 178 | (_, channel, height, width) = img1.size() 179 | if window is None: 180 | real_size = min(window_size, height, width) 181 | window = create_window(real_size, channel=channel).to(img1.device) 182 | 183 | mu1 = F.conv2d(img1, window, padding=padd, groups=channel) 184 | mu2 = F.conv2d(img2, window, padding=padd, groups=channel) 185 | 186 | mu1_sq = mu1.pow(2) 187 | mu2_sq = mu2.pow(2) 188 | mu1_mu2 = mu1 * mu2 189 | 190 | sigma1_sq = F.conv2d(img1 * img1, window, padding=padd, groups=channel) - mu1_sq 191 | sigma2_sq = F.conv2d(img2 * img2, window, padding=padd, groups=channel) - mu2_sq 192 | sigma12 = F.conv2d(img1 * img2, window, padding=padd, groups=channel) - mu1_mu2 193 | 194 | C1 = (0.01 * L) ** 2 195 | C2 = (0.03 * L) ** 2 196 | 197 | v1 = 2.0 * sigma12 + C2 198 | v2 = sigma1_sq + sigma2_sq + C2 199 | cs = torch.mean(v1 / v2) # contrast sensitivity 200 | 201 | ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2) 202 | 203 | if size_average: 204 | ret = ssim_map.mean() 205 | else: 206 | ret = ssim_map.mean(1).mean(1).mean(1) 207 | 208 | if full: 209 | return ret, cs 210 | return ret 211 | 212 | 213 | def msssim(img1, img2, window_size=11, size_average=True, val_range=None, normalize=False): 214 | device = img1.device 215 | weights = torch.FloatTensor([0.0448, 0.2856, 0.3001, 0.2363, 0.1333]).to(device) 216 | levels = weights.size()[0] 217 | mssim = [] 218 | mcs = [] 219 | for _ in range(levels): 220 | sim, cs = ssim(img1, img2, window_size=window_size, size_average=size_average, full=True, val_range=val_range) 221 | mssim.append(sim) 222 | mcs.append(cs) 223 | 224 | img1 = F.avg_pool2d(img1, (2, 2)) 225 | img2 = F.avg_pool2d(img2, (2, 2)) 226 | 227 | mssim = torch.stack(mssim) 228 | mcs = torch.stack(mcs) 229 | 230 | # Normalize (to avoid NaNs during training unstable models, not compliant with original definition) 231 | if normalize: 232 | mssim = (mssim + 1) / 2 233 | mcs = (mcs + 1) / 2 234 | 235 | pow1 = mcs ** weights 236 | pow2 = mssim ** weights 237 | # From Matlab implementation https://ece.uwaterloo.ca/~z70wang/research/iwssim/ 238 | output = torch.prod(pow1[:-1] * pow2[-1]) 239 | return output 240 | 241 | 242 | # Classes to re-use window 243 | class SSIM(torch.nn.Module): 244 | def __init__(self, window_size=11, size_average=True, val_range=None): 245 | super(SSIM, self).__init__() 246 | self.window_size = window_size 247 | self.size_average = size_average 248 | self.val_range = val_range 249 | 250 | # Assume 1 channel for SSIM 251 | self.channel = 1 252 | self.window = create_window(window_size) 253 | 254 | def forward(self, img1, img2): 255 | (_, channel, _, _) = img1.size() 256 | 257 | if channel == self.channel and self.window.dtype == img1.dtype: 258 | window = self.window 259 | else: 260 | window = create_window(self.window_size, channel).to(img1.device).type(img1.dtype) 261 | self.window = window 262 | self.channel = channel 263 | 264 | return ssim(img1, img2, window=window, window_size=self.window_size, size_average=self.size_average) 265 | 266 | class MSSSIM(torch.nn.Module): 267 | def __init__(self, window_size=11, size_average=True, channel=3): 268 | super(MSSSIM, self).__init__() 269 | self.window_size = window_size 270 | self.size_average = size_average 271 | self.channel = channel 272 | 273 | def forward(self, img1, img2): 274 | # TODO: store window between calls if possible 275 | return msssim(img1, img2, window_size=self.window_size, size_average=self.size_average) 276 | 277 | -------------------------------------------------------------------------------- /Alpha_GAN_ADNI_train.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pylab inline\n", 10 | "import numpy as np\n", 11 | "import torch\n", 12 | "import os\n", 13 | "from torch import nn\n", 14 | "from torch import optim\n", 15 | "from torch.nn import functional as F\n", 16 | "from torch import autograd\n", 17 | "from torch.autograd import Variable\n", 18 | "import nibabel as nib\n", 19 | "from torch.utils.data.dataset import Dataset\n", 20 | "from torch.utils.data import dataloader\n", 21 | "from nilearn import plotting\n", 22 | "from ADNI_dataset import *\n", 23 | "from BRATS_dataset import *\n", 24 | "from ATLAS_dataset import *\n", 25 | "from Model_alphaGAN import *" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "# Configuration" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "BATCH_SIZE=4\n", 42 | "gpu = True\n", 43 | "workers = 4\n", 44 | "LAMBDA= 10\n", 45 | "_eps = 1e-15\n", 46 | "\n", 47 | "Use_BRATS = False\n", 48 | "Use_ATLAS = False\n", 49 | "\n", 50 | "#setting latent variable sizes\n", 51 | "latent_dim = 1000" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": { 58 | "scrolled": true 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "trainset = ADNIdataset(augmentation=False)\n", 63 | "train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 64 | " shuffle=True,num_workers=workers)\n", 65 | "\n", 66 | "if Use_BRATS:\n", 67 | " #imgtype -> 'flair' or 't2' or 't1ce'\n", 68 | " trainset = BRATSdataset(train=True, imgtype = 'flair',augmentation=False)\n", 69 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 70 | " shuffle=True,num_workers=workers)\n", 71 | "if Use_ATLAS:\n", 72 | " trainset = ATLASdataset(augmentation=True)\n", 73 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 74 | " shuffle=True,num_workers=workers)" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "def inf_train_gen(data_loader):\n", 84 | " while True:\n", 85 | " for _,images in enumerate(data_loader):\n", 86 | " yield images" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "G = Generator(noise = latent_dim)\n", 96 | "CD = Code_Discriminator(code_size = latent_dim ,num_units = 4096)\n", 97 | "D = Discriminator(is_dis=True)\n", 98 | "E = Discriminator(out_class = latent_dim ,is_dis=False)\n", 99 | "\n", 100 | "G.cuda()\n", 101 | "D.cuda()\n", 102 | "CD.cuda()\n", 103 | "E.cuda()" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "g_optimizer = optim.Adam(G.parameters(), lr=0.0002)\n", 113 | "d_optimizer = optim.Adam(D.parameters(), lr=0.0002)\n", 114 | "e_optimizer = optim.Adam(E.parameters(), lr = 0.0002)\n", 115 | "cd_optimizer = optim.Adam(CD.parameters(), lr = 0.0002)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "def calc_gradient_penalty(model, x, x_gen, w=10):\n", 125 | " \"\"\"WGAN-GP gradient penalty\"\"\"\n", 126 | " assert x.size()==x_gen.size(), \"real and sampled sizes do not match\"\n", 127 | " alpha_size = tuple((len(x), *(1,)*(x.dim()-1)))\n", 128 | " alpha_t = torch.cuda.FloatTensor if x.is_cuda else torch.Tensor\n", 129 | " alpha = alpha_t(*alpha_size).uniform_()\n", 130 | " x_hat = x.data*alpha + x_gen.data*(1-alpha)\n", 131 | " x_hat = Variable(x_hat, requires_grad=True)\n", 132 | "\n", 133 | " def eps_norm(x):\n", 134 | " x = x.view(len(x), -1)\n", 135 | " return (x*x+_eps).sum(-1).sqrt()\n", 136 | " def bi_penalty(x):\n", 137 | " return (x-1)**2\n", 138 | "\n", 139 | " grad_xhat = torch.autograd.grad(model(x_hat).sum(), x_hat, create_graph=True, only_inputs=True)[0]\n", 140 | "\n", 141 | " penalty = w*bi_penalty(eps_norm(grad_xhat)).mean()\n", 142 | " return penalty" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "# Training" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "real_y = Variable(torch.ones((BATCH_SIZE, 1)).cuda(async=True))\n", 159 | "fake_y = Variable(torch.zeros((BATCH_SIZE, 1)).cuda(async=True))\n", 160 | "\n", 161 | "criterion_bce = nn.BCELoss()\n", 162 | "criterion_l1 = nn.L1Loss()\n", 163 | "criterion_mse = nn.MSELoss()" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "gen_load = inf_train_gen(train_loader)\n", 173 | "MAX_ITER = 200000\n", 174 | "for iteration in range(MAX_ITER):\n", 175 | " ###############################################\n", 176 | " # Train Encoder - Generator \n", 177 | " ###############################################\n", 178 | " for p in D.parameters(): # reset requires_grad\n", 179 | " p.requires_grad = False\n", 180 | " for p in CD.parameters(): # reset requires_grad\n", 181 | " p.requires_grad = False\n", 182 | " for p in E.parameters(): # reset requires_grad\n", 183 | " p.requires_grad = True\n", 184 | " for p in G.parameters(): # reset requires_grad\n", 185 | " p.requires_grad = True\n", 186 | "\n", 187 | " g_optimizer.zero_grad()\n", 188 | " e_optimizer.zero_grad()\n", 189 | "\n", 190 | "\n", 191 | " for iters in range(1):\n", 192 | " real_images = gen_load.__next__()\n", 193 | " real_images = Variable(real_images,volatile=True).cuda(async=True)\n", 194 | " _batch_size = real_images.size(0)\n", 195 | " z_hat = E(real_images).view(_batch_size,-1)\n", 196 | " z_rand = Variable(torch.randn((_batch_size,latent_dim)),requires_grad=False).cuda()\n", 197 | "\n", 198 | " x_hat = G(z_hat)\n", 199 | " x_rand = G(z_rand)\n", 200 | "\n", 201 | " l1_loss = 10 * criterion_l1(x_hat, real_images)\n", 202 | " c_loss = criterion_bce(CD(z_hat), real_y[:_batch_size])\n", 203 | " d_real_loss = criterion_bce(D(x_hat), real_y[:_batch_size]) \n", 204 | " d_fake_loss = criterion_bce(D(x_rand), real_y[:_batch_size])\n", 205 | "\n", 206 | " loss1 = l1_loss + c_loss + d_real_loss + d_fake_loss\n", 207 | "\n", 208 | " loss1.backward(retain_graph=True)\n", 209 | " e_optimizer.step()\n", 210 | "\n", 211 | " g_optimizer.step()\n", 212 | " g_optimizer.step()\n", 213 | "\n", 214 | " ###############################################\n", 215 | " # Train D\n", 216 | " ###############################################\n", 217 | " for p in D.parameters(): \n", 218 | " p.requires_grad = True\n", 219 | " for p in CD.parameters(): \n", 220 | " p.requires_grad = False\n", 221 | " for p in E.parameters(): \n", 222 | " p.requires_grad = False\n", 223 | " for p in G.parameters(): \n", 224 | " p.requires_grad = False\n", 225 | "\n", 226 | " for iters in range(1):\n", 227 | " d_optimizer.zero_grad()\n", 228 | "\n", 229 | " z_rand = Variable(torch.randn((_batch_size,latent_dim)),volatile=True).cuda()\n", 230 | " z_hat = E(real_images).view(_batch_size,-1)\n", 231 | " x_hat = G(z_hat)\n", 232 | " x_rand = G(z_rand)\n", 233 | "\n", 234 | " x_loss2 = 2.0 * criterion_bce(D(real_images), real_y[:_batch_size])+criterion_bce(D(x_hat), fake_y[:_batch_size])\n", 235 | " z_loss2 = criterion_bce(D(x_rand), fake_y[:_batch_size])\n", 236 | " loss2 = x_loss2 + z_loss2\n", 237 | "\n", 238 | " if iters<4:\n", 239 | " loss2.backward(retain_graph=True)\n", 240 | " else:\n", 241 | " loss2.backward(retain_graph=True)\n", 242 | " d_optimizer.step()\n", 243 | " ###############################################\n", 244 | " # Train CD\n", 245 | " ###############################################\n", 246 | " for p in D.parameters(): # reset requires_grad\n", 247 | " p.requires_grad = False\n", 248 | " for p in CD.parameters(): # reset requires_grad\n", 249 | " p.requires_grad = True\n", 250 | " for p in E.parameters(): # reset requires_grad\n", 251 | " p.requires_grad = False\n", 252 | " for p in G.parameters(): # reset requires_grad\n", 253 | " p.requires_grad = False\n", 254 | "\n", 255 | " for iters in range(1):\n", 256 | " cd_optimizer.zero_grad()\n", 257 | " z_hat = E(real_images).view(_batch_size,-1)\n", 258 | " x_loss3 = criterion_bce(CD(z_hat), fake_y[:_batch_size])\n", 259 | " z_rand = Variable(torch.randn((_batch_size,latent_dim)),volatile=True).cuda()\n", 260 | " z_loss3 = criterion_bce(CD(z_rand), real_y[:_batch_size])\n", 261 | " loss3 = x_loss3 + z_loss3\n", 262 | " loss3.backward(retain_graph=True)\n", 263 | " cd_optimizer.step()\n", 264 | " \n", 265 | " ###############################################\n", 266 | " # Visualization\n", 267 | " ###############################################\n", 268 | "\n", 269 | " if iteration % 50 == 0:\n", 270 | " print('[{}/{}]'.format(iteration,50000),\n", 271 | " 'D: {:<8.3}'.format(loss2.data[0].cpu().numpy()), \n", 272 | " 'En_Ge: {:<8.3}'.format(loss1.data[0].cpu().numpy()),\n", 273 | " 'Code: {:<8.3}'.format(loss3.data[0].cpu().numpy()),\n", 274 | " )\n", 275 | "\n", 276 | " featmask = np.squeeze((0.5*real_images[0]+0.5).data.cpu().numpy())\n", 277 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 278 | " plotting.plot_img(featmask,title=\"Real\")\n", 279 | " plotting.show()\n", 280 | "\n", 281 | " featmask = np.squeeze((0.5*x_hat[0]+0.5).data.cpu().numpy())\n", 282 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 283 | " plotting.plot_img(featmask,title=\"DEC\")\n", 284 | " plotting.show()\n", 285 | "\n", 286 | " featmask = np.squeeze((0.5*x_rand[0]+0.5).data.cpu().numpy())\n", 287 | " featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 288 | " plotting.plot_img(featmask,title=\"Rand\")\n", 289 | " plotting.show()\n", 290 | "\n", 291 | " if (iteration+1)%500 ==0: \n", 292 | " torch.save(G.state_dict(),'./checkpoint/G_noW_iter'+str(iteration+1)+'.pth')\n", 293 | " torch.save(D.state_dict(),'./checkpoint/D_noW_iter'+str(iteration+1)+'.pth')\n", 294 | " torch.save(E.state_dict(),'./checkpoint/E_noW_iter'+str(iteration+1)+'.pth')\n", 295 | " torch.save(CD.state_dict(),'./checkpoint/CD_noW_iter'+str(iteration+1)+'.pth')" 296 | ] 297 | } 298 | ], 299 | "metadata": { 300 | "kernelspec": { 301 | "display_name": "Python 3", 302 | "language": "python", 303 | "name": "python3" 304 | }, 305 | "language_info": { 306 | "codemirror_mode": { 307 | "name": "ipython", 308 | "version": 3 309 | }, 310 | "file_extension": ".py", 311 | "mimetype": "text/x-python", 312 | "name": "python", 313 | "nbconvert_exporter": "python", 314 | "pygments_lexer": "ipython3", 315 | "version": "3.5.6" 316 | } 317 | }, 318 | "nbformat": 4, 319 | "nbformat_minor": 2 320 | } 321 | -------------------------------------------------------------------------------- /Alpha_WGAN_ADNI_train.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pylab inline\n", 10 | "import numpy as np\n", 11 | "import torch\n", 12 | "import os\n", 13 | "from torch import nn\n", 14 | "from torch import optim\n", 15 | "from torch.nn import functional as F\n", 16 | "from torch import autograd\n", 17 | "from torch.autograd import Variable\n", 18 | "import nibabel as nib\n", 19 | "from torch.utils.data.dataset import Dataset\n", 20 | "from torch.utils.data import dataloader\n", 21 | "from nilearn import plotting\n", 22 | "from ADNI_dataset import *\n", 23 | "from BRATS_dataset import *\n", 24 | "from ATLAS_dataset import *\n", 25 | "from Model_alphaWGAN import *" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "# Configuration" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "BATCH_SIZE=4\n", 42 | "gpu = True\n", 43 | "workers = 4\n", 44 | "\n", 45 | "LAMBDA= 10\n", 46 | "_eps = 1e-15\n", 47 | "Use_BRATS=False\n", 48 | "Use_ATLAS = False\n", 49 | "\n", 50 | "#setting latent variable sizes\n", 51 | "latent_dim = 1000" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": { 58 | "scrolled": true 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "trainset = ADNIdataset(augmentation=True)\n", 63 | "train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 64 | " shuffle=True,num_workers=workers)\n", 65 | "if Use_BRATS:\n", 66 | " #'flair' or 't2' or 't1ce'\n", 67 | " trainset = BRATSdataset(imgtype='flair')\n", 68 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size = BATCH_SIZE, shuffle=True,\n", 69 | " num_workers=workers)\n", 70 | "if Use_ATLAS:\n", 71 | " trainset = ATLASdataset(augmentation=True)\n", 72 | " train_loader = torch.utils.data.DataLoader(trainset,batch_size=BATCH_SIZE,\n", 73 | " shuffle=True,num_workers=workers)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "def inf_train_gen(data_loader):\n", 83 | " while True:\n", 84 | " for _,images in enumerate(data_loader):\n", 85 | " yield images" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "G = Generator(noise = latent_dim)\n", 95 | "CD = Code_Discriminator(code_size = latent_dim ,num_units = 4096)\n", 96 | "D = Discriminator(is_dis=True)\n", 97 | "E = Discriminator(out_class = latent_dim,is_dis=False)\n", 98 | "\n", 99 | "G.cuda()\n", 100 | "D.cuda()\n", 101 | "CD.cuda()\n", 102 | "E.cuda()" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "g_optimizer = optim.Adam(G.parameters(), lr=0.0002)\n", 112 | "d_optimizer = optim.Adam(D.parameters(), lr=0.0002)\n", 113 | "e_optimizer = optim.Adam(E.parameters(), lr = 0.0002)\n", 114 | "cd_optimizer = optim.Adam(CD.parameters(), lr = 0.0002)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "def calc_gradient_penalty(model, x, x_gen, w=10):\n", 124 | " \"\"\"WGAN-GP gradient penalty\"\"\"\n", 125 | " assert x.size()==x_gen.size(), \"real and sampled sizes do not match\"\n", 126 | " alpha_size = tuple((len(x), *(1,)*(x.dim()-1)))\n", 127 | " alpha_t = torch.cuda.FloatTensor if x.is_cuda else torch.Tensor\n", 128 | " alpha = alpha_t(*alpha_size).uniform_()\n", 129 | " x_hat = x.data*alpha + x_gen.data*(1-alpha)\n", 130 | " x_hat = Variable(x_hat, requires_grad=True)\n", 131 | "\n", 132 | " def eps_norm(x):\n", 133 | " x = x.view(len(x), -1)\n", 134 | " return (x*x+_eps).sum(-1).sqrt()\n", 135 | " def bi_penalty(x):\n", 136 | " return (x-1)**2\n", 137 | "\n", 138 | " grad_xhat = torch.autograd.grad(model(x_hat).sum(), x_hat, create_graph=True, only_inputs=True)[0]\n", 139 | "\n", 140 | " penalty = w*bi_penalty(eps_norm(grad_xhat)).mean()\n", 141 | " return penalty" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "# Training" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "metadata": {}, 155 | "outputs": [], 156 | "source": [ 157 | "real_y = Variable(torch.ones((BATCH_SIZE, 1)).cuda(async=True))\n", 158 | "fake_y = Variable(torch.zeros((BATCH_SIZE, 1)).cuda(async=True))\n", 159 | "\n", 160 | "criterion_bce = nn.BCELoss()\n", 161 | "criterion_l1 = nn.L1Loss()\n", 162 | "criterion_mse = nn.MSELoss()" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "g_iter = 1\n", 172 | "d_iter = 1\n", 173 | "cd_iter =1\n", 174 | "TOTAL_ITER = 200000\n", 175 | "gen_load = inf_train_gen(train_loader)\n", 176 | "for iteration in range(TOTAL_ITER):\n", 177 | " for p in D.parameters(): \n", 178 | " p.requires_grad = False\n", 179 | " for p in CD.parameters(): \n", 180 | " p.requires_grad = False\n", 181 | " for p in E.parameters(): \n", 182 | " p.requires_grad = True\n", 183 | " for p in G.parameters(): \n", 184 | " p.requires_grad = True\n", 185 | "\n", 186 | " ###############################################\n", 187 | " # Train Encoder - Generator \n", 188 | " ###############################################\n", 189 | " for iters in range(g_iter):\n", 190 | " G.zero_grad()\n", 191 | " E.zero_grad()\n", 192 | " real_images = gen_load.__next__()\n", 193 | " _batch_size = real_images.size(0)\n", 194 | " real_images = Variable(real_images,volatile=True).cuda(async=True)\n", 195 | " z_rand = Variable(torch.randn((_batch_size,latent_dim)),volatile=True).cuda()\n", 196 | " z_hat = E(real_images).view(_batch_size,-1)\n", 197 | " x_hat = G(z_hat)\n", 198 | " x_rand = G(z_rand)\n", 199 | " c_loss = -CD(z_hat).mean()\n", 200 | "\n", 201 | " d_real_loss = D(x_hat).mean()\n", 202 | " d_fake_loss = D(x_rand).mean()\n", 203 | " d_loss = -d_fake_loss-d_real_loss\n", 204 | " l1_loss =10* criterion_l1(x_hat,real_images)\n", 205 | " loss1 = l1_loss + c_loss + d_loss\n", 206 | "\n", 207 | " if iters" 158 | ] 159 | }, 160 | "metadata": {}, 161 | "output_type": "display_data" 162 | }, 163 | { 164 | "data": { 165 | "image/png": "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m8Iar8937JkXWnX9mTXet3eznD+a+sXWtpw82vMyc5emcsh9seetd9DJ0WiH+W57zp1ZfZBzSK7d/kdgpiss5tlin72LbNHeYW79r0k+b+9++P60/d7V/KcpF5ItZT0ROflYmVNERERERERERERERERERERERERE5AjxnzlFRERERERERERERP5fe+fSbMlxle3FBxgsS5aEdW9162YJ2dhgQ8CIP86ICQRBEIQJgyxLLfW91ZJ1IUR4QJgB34S36tmn3zq71eruvc/p55mcity7qrIyV65cWWfnekVERERERERERA6IMusnYEpkpjqO9BblFZLWmKmXKROUlMuUv0j6b8qcNBkHSl3k85dffnkpi6QDv9dkFSnNHnmrSBrN7KYcbxKgeTZKFPC4SYFFSpEp/ynRkvObPD3bZZ+0Z+rWZEPZJ02apj1Dk+fjtSidmutQWoOSM/nusae7po2zrpGPYjtG6odlTdaH9p7PW1p9Qum1v/zLv5yZmStXrixl6RtKRzSZFvZbk6pt/cHnaXK7bTyQpExnmv8mU8QxEvugP+H4zXebfA2fp0luUG4qfov3ptxeI+O3pXqnbA99XZMKPjbYvpRHoYxySOr7Jp0209PcN0m0+ATaKuUw056UdMw425LJjQ02+RPOX002hvabvtqScs759HupJ8+hvbUx1ySHeNwki5sUcPPd++Qi0yccH+yLJiGbc/g91uPmzZsz02VqjwVK9LB/3njjjZnZta30M9uAdpLP9/nB2HqTfJ5Zx9zVq1eXssQfnEdJ7Ijtn3psySrlOZq0EW2sjQ/Ka0WCiXWjz2sxQMqaDc6sNsPrZA6gXacteW6T+OB4jQ9m3/I4bUm7zT3pDylrmXY5Zr++JWHVZM0zLthnbKP4e14n50eeeabL87Cv4tdpY4yBw5bvDWn/5gdJi2GaL+d3GRPkGVkfxrM5h9dscu70makH2z9239ZZW22RevAZcw7HDM/P8zZpafbTN5G0P2byHIztspaK1OHM7jxIGcOQNqSPjrwVz23ysvQrTT6O/ZfjNldsSTuf/B4/34rX25hKGa/d5CZJ7HVrLdHmnBZzZaxwnLAts3bifSKxx/s9++yzy3F8De+TOK2133klY33fOp59lPZufU6b2Gcf8WucO5vsFctSN9aXfRzfxjVibJdjl3N3ZEI5VttYYxyUa27NF61u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w1IWKdp2v5nVjprEFe2FdY+dsaz1eRu73ySFehufGUuUUsp92Cese+SZ2I+xYbY55UHyjGz/yCfwWSl9fNqzHUvqeNaDfikyrq0N941n2lb8G9syY4HX4TiLrVPqJ7ZMX8R5KPZKO8h3KYnepMybhFKTiuPnzz///F11o2Qcnyc2xbZM3bZk1uNHOcbzjJTszpigjB0l9lJ3zhk5v8WbrFukxHgdjuHMKSevddZIv1F+tMWZJM9LH5L5m7aevuAcSR/T5NGbNHv89sw6TunT0i+0IfZPk0Ns8QhtPf6cfrdJjTa5XD5jjmnrHAsZ57TB3JvSjlmr0C453n/xi1/c9Qx5bs6LrR5s/5Txe/ycY/uswj6/fv36zKw+f2a3f3LMWDV2TX+Q9tqSK4yd8N6xDdon65ExyT7N/EG/TnuLRCjPiQ0zrmH/Zrxv+ePAdolPbXE/4T1v3759V93iG3jvjHfGVIk3ZtYx0+TMInM502Mm9mPmUM615530x+XLl5ey9MvMaldsx/h59kfam36ax2lT9n9o8zrL6W+aTB19buyHdph78z48J+O3yf426eiZtQ0YX6RutENK+Lb54jTpVrYvx1raqK2JeG32Wa7JMZD54nGRHEsbcM766KOPluP0FSXg8m6Ddhc/TH/P4xaTNMl0XrNJybV1FueQJlmX90wXLlxYyvi8+ZwxWGyHY6JJ67b3Lm3M8FrNthgnNvlgknq2a3PubOtXjq1jWWMeErZHbLitzd54442lLPEDfTltPfba4sMXXnhhKeMc396vZY7nOrb5TtLWCxw/mdvb+0+uT7meyDuL1i5Nondmbdc23jmfNbukj8844/o019GWvz20g/QL7SDH++SoaYvpM77fZyyevmwSoIRxRI557/h42jrtJP6TsWuTvW4xG3142qW9q5/pctZNUj2f027pN9qYyfjY+t+DHAeMDWiDsRn2eXwmxwf9fmJUjo+MH16HYyZzEt9R5T6cM9r/MLney1jgmKA9Zizxmom5ttY58ngTP8x34q+++upynLFD24xP5XqtSaHTj+ccviviGPriiy/uug7fiwdes73/iO835pBvSvw35/3YE8ta/MFYor3T4XoxdtveHW3NB+1/TiKHJvZIv/24vJ87BGbmFBERERERERERERERERERERERERE5IGbmPAXupmo7UVvWQZJf0rfPubsr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e+c203147OhhZCds/xvimGI9YuNNba1lmTUekfuhZa99mPf5JtmbRUREziNm5hQREREREREREREREREREREREREROSD+mFNERERERERERERERERERERERERE5IAosy4LLe1+ZGaY0p+SGZHvpVRLjrekRiNv0aTvKEeTa8+sMhwtrTqlDnJNfq/JaDT5Ukp0UEa4yV/K8dPsmXIukU9pUnFb0iyRML1z585SFvuiJA3lUyMrQ5nE0yR6WU+OuyZrFlnULWmv2HSTBuH32C6RiVI+5rCwzyg1l2PKOMZOKFNHHxfb4TmxN9ogj09ee2a1k0hvzezafcYUZUtzTdanzQ28T2yTdkkbD20+4DhqMmg8JzJQlFCSR0f6hX1Lfx1o17ENfo/y0LGjFnNQco5y5JEkog3G1/M+lDEK9PUnn+vkMesUOBZOnsOYhOQ6vF78AuMqjrmvvvpqZnr7ysMnMeXHH3+8lNGvZwzsK2uy5ezn0+SdZ1aZVJ4Tn8g4nnYZu6cUb5NQbHXjdZrsWMpol01amJ832VbOQ5RAlkcD7enq1aszszvXUrb8mWeemZld/5b+5VzMa8beWqxLe6IMZOIHXjP2RtvhfdqY2hcr5ZjXzJjjdVIfxjX8vMUhqS+/x/GsPz8M7Kv/+I//mJld//jiiy8ux7H9Nl+zX+nDcn2+i0jZlhwvr3US2hzr8fTTT8/Mrj/P54xt6MdbXJIYrNl7W7/wc/qJjCXWV46HxJGMyRNTzMxcuHBhZvr7BcYHtOv0f9ZjM6s90V7u5x0cbSu+Mu84eE3KA7d3MSRljLUpqx04NhND3b59eyn74osvZmb3GeX4oA189NFHy3H6krae4/b+ema1Gfr6NhfcDxwfiZM4plK3rfcd8escp6kbr533O3xujqnYOt+T+j7x7ME+j80wvo6f5HsR2k7sub0PeRj20GSkGdewbolnWt34vcxD9PX019q1kIchqd6g3WV+aWtWHjdpdl7n284/Io8S/ycvIiKPO2bmFBERERERERERERERERERERERERE5IP6YU0RERERERERERERERERERERERETkgCizLnexL9V+5EdnZm7evDkzuxJ6lBoLTSqpyV9Quo4p1COTyrLIXrC+kfugxADljyIj2eT7KM1IeZgm7ytnB9oe5WBSHpm5mdU+KKlCSbtICTU5F8oMUWY6ci4c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EkVynZJ1kVynRC9tMD6/SZjST+d4S6I04+vXv/71Uhbfr4yj3C9bfj22Tvt/8cUXZ6ZLMvJa+6RzmxwvJU9Tj88++2wp++ijj2ZmV6K3yV6LbEG/TLtO/P0Hf7AuGV977bW7ypp0XaNJqrOMMqeJXa5evbqURV7d2F2+KfGdkZee2Y2z48PJ97///ZnZjWEaze9zHOVzxkc8J+Msc4vIg+LWrVvL8d/93d8tx5Fff/bZZ5eyxCeMORhrp5y22/x4vpd16szMjRs3luOvv/76fh5F5J6hjV2+fHlmZi5durSUPffcczOza+u04YwFriHzXZZFhpq+m3Yv8iiJDdNG8z7k+eefX8oow564hOvTxEscR3nXwvWwyDER++eatsms71uLioiIiIiIHBtm5hQREREREREREREREREREREREREROSC/NzNuQZOHAndBZvf7zJrR58knn7zru9wRyfOTgYrZJZKBkOckU4QZqeRYyG73J554YilL9h9mtWJmlO985zszs5vlIVlpv/vd7y5lyRDRMtbOrNki3GksjxLa9c9+9rOZmXnrrbeWspaZk5l/cswMEclwRd/OjLPJTMhsKGaOkIcNs6z96Ec/mpmZP//zP1/KaNehxTmMd1pmzvjymZkrV67MzMzNmzeXsi+++OKuc0QeBLFNZsz8yU9+MjMzb7/99lLWsreRlvmklTGG+cd//MeZmfn888+Xsn2ZbUW+CfThWZdyzRobTxbDk+eEZpcsiw9nGWP3f/3Xf52Z3WyeIg8axhpZl7700ktL2csvvzwzM08//fRSRn+e4xbHMD6Pbb///vtLGWN243N5lMRG835lZvXzFy9erOckazMzHH7ve9+bmd21Zt7VJGv4jLG4HBexf75DbD6cZYlzuP40PhERERERERE5DGbmFBERERERERERERERERERERERERE5IP6YU0RERERERERERERERERERERERETkgPzB/q+I3B+UaomU7kyXI4q0HeVffvvb3y7HkRON5NFMlyIVOTZipxwDkcSljB0lTCNPxzGSY5YFSiBR5k7kEFBG9OrVqzOzK73+1FNPLceR1I3c48xq4/T3GR9ffvnlUnbjxo27ruN8II8S+ttPP/10Znb9+quvvrocxzbp6+O7Ge9k/HDOoK0nhqJMqXYvD4vYFuOM69evz8yu9PSFCxeW43yXctSZAyhZGr9N+d1///d/X44TC2nf8rCgD0/swbIXXnhhZnZjFPr4yI42+VHGMF9//fXMzNy+fXsp47HypfIooC+NfSammFnj8z/+4z++q2xmjV/ox+PbP/vss6Xsww8/nJmZr776ainTxuVQxO5pty2W/v73v3/XuYzF48f5LibjyDhFjpXYJmMSxucnv7evTEREREREREQeLWbmFBERERERERERERERERERERERERE5IL83M263lIfO7/3e7y3H2QnMHcHZCc/MJ20nvFkd5Dzw+7//+zMz873vfW8pS3bamZn/+q//mpndbBE55g55d8vLsRPfT1t/8803l2Nm7Ayxf2ZQie9n5h/nAzlGaNPvvvvucvzDH/5wZmaeffbZpSyZB/N3Zs1SeOXKlaWM8ZDZl+XQJIZ55plnlrK/+Zu/uetzZnfLuEhm8pmZO3fuzMyawXlmN3OQyCHgmjX+mllof/rTny7HsfHELTNrbJJszTMzt27dmpnd7OLG8HJsRCGFa9LXXnttOX7llVdmZjebcmz/8uXLS5nZCuWskHhlZlchKOXJID6zZs43DhcRERERERERkUeFmTlFRERERERERERERERERERERERERA6IP+YUERERERERERERERERERERERERETkgyqzLURBJO0rbKSctjxP/7/+tv62PvWv3ch6hDHUkSinH+7vf/W5mdqXtIluqtJ2cJb7zne8sxz/4wQ9mZubixYtL2W9/+9uZ2ZVZ//rrr2dGuWk5fhizU5Y3x0888cRS9t///d8zsytHHRuPfxc5VijFS7v+wz/8w5lZ45aZmf/5n//Z+Ttj7CJnC/p2rk9TTnvWtkVEREREREREREQeDmbmFBERERERERERERERERERERERERE5IP6YU0RERERERERERERERERERERERETkgCizLiIiIiLyCKB8afjf/zUUFxERERERERERERERERERM3OKiIiIiIiIiIiIiIiIiIiIiIiIiBwUM3OKiIiIiIiIiIiIiIiIiIiIiIiIiBwQM3OKiIiIiIiIiIiIiIiIiIiIiIiIiBwQf8wpIiIiIiIiIiIiIiIiIiIiIiIiInJA/DGniIiIiIiIiIiIiIiIiIiIiIiIiMgB8cecIiIiIiIiIiIiIiIiIiIiIiIiIiIHxB9zioiIiIiIiIiIiIiIiIiIiIiIiIgcEH/MKSIiIiIiIiIiIiIiIiIiIiIiIiJyQPwxp4iIiIiIiIiIiIiIiIiIiIiIiIjIAfHHnCIiIiIiIiIiIiIiIiIiIiIiIiIiB8Qfc4qIiIiIiIiIiIiIiIiIiIiIiIiIHBB/zCkiIiIiIiIiIiIiIiIiIiIiIiIickD8MaeIiIiIiIiIiIiIiIiIiIiIiIiIyAHxx5wiIiIiIiIiIiIiIiIiIiIiIiIiIgfEH3OKiIiIiIiIiIiIiIiIiIiIiIiIiBwQf8wpIiIiIiIiIiIiIiIiIiIiIiIiInJA/DGniIiIiIiIiIiIiIiIiIiIiIiIiMgB8cecIiIiIiIiIiIiIiIiIiIiIiIiIiIHxB9zioiIiIiIiIiIiIiIiIiIiIiIiIgcEH/MKSIiIiIiIiIiIiIiIiIiIiIiIiJyQPwxp4iIiIiIiIiIiIiIiIiIiIiIiIjIAfHHnCIiIiIiIiIiIiIiIiIiIiIiIiIiB8Qfc4qIiIiIiIiIiIiIiIiIiIiIiIiIHBB/zCkiIiIiIiIiIiIiIiIiIiIiIiIickD8MaeIiIiIiIiIiIiIiIiIiIiIiIiIyAHxx5wiIiIiIiIiIiIiIiIiIiIiIiIiIgfEH3OKiIiIiIiIiIiIiIiIiIiIiIiIiBwQf8wpIiIiIiIiIiIiIiIiIiIiIiIiInJA/DGniIiIiIiIiIiIiIiIiIiIiIiIiMgB8cecIiIiIiIiIiIiIiIiIiIiIiIiIiIHxB9zioiIiIiIiIiIiIiIiIiIiIiIiIgcEH/MKSIiIiIiIiIiIiIiIiIiIiIiIiJyQPwxp4iIiIiIiIiIiIiIiIiIiIiIiIjIAfn/L8YBWNbfeYcAAAAASUVORK5CYII=\n", 166 | "text/plain": [ 167 | "
" 168 | ] 169 | }, 170 | "metadata": {}, 171 | "output_type": "display_data" 172 | } 173 | ], 174 | "source": [ 175 | "Show_color = False\n", 176 | "\n", 177 | "noise = Variable(torch.randn((1, 1000)).cuda())\n", 178 | "fake_image = G(noise)\n", 179 | "featmask = np.squeeze(fake_image[0].data.cpu().numpy())\n", 180 | "featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 181 | "\n", 182 | "arr1 = [4,6,8,10,12,14,16,18,20,22,24,26,28,30,32]\n", 183 | "arr2 = [34,36,38,40,42,44,46,48,50,52,54,56,58,60]\n", 184 | "if Show_color:\n", 185 | " disp = plotting.plot_img(featmask,cut_coords=arr1,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 186 | " # disp.annotate(size=25,left_right=False,positions=True)\n", 187 | " plotting.show()\n", 188 | " disp=plotting.plot_img(featmask,cut_coords=arr2,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 189 | " # disp.annotate(size=25,left_right=False)\n", 190 | " plotting.show()\n", 191 | "else:\n", 192 | " disp = plotting.plot_anat(featmask,cut_coords=arr1,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 193 | " plotting.show()\n", 194 | " # disp.annotate(size=25,left_right=False)\n", 195 | " disp=plotting.plot_anat(featmask,cut_coords=arr2,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 196 | " # disp.annotate(size=25,left_right=False)\n", 197 | " plotting.show()" 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "metadata": {}, 203 | "source": [ 204 | "Fake Image - Center cut slices Visualization" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": null, 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "noise = Variable(torch.randn((1, 1000)).cuda())\n", 214 | "fake_image = G(noise)\n", 215 | "featmask = np.squeeze(fake_image[0].data.cpu().numpy())\n", 216 | "featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 217 | "plotting.plot_img(featmask,cut_coords=(32,32,32),draw_cross=False,annotate=False,black_bg=True)\n", 218 | "plotting.plot_anat(featmask,cut_coords=(32,32,32),draw_cross=False,annotate=False,black_bg=True)\n", 219 | "plotting.show()" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 29, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "noise = Variable(torch.randn((1, 1000)).cuda())\n", 229 | "fake_image = G(noise)\n", 230 | "featmask = np.squeeze(fake_image[0].data.cpu().numpy())\n", 231 | "# featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 232 | "\n", 233 | "from PIL import Image\n", 234 | "for i in range(64):\n", 235 | " A = 0.5*featmask[:,:,i]+0.5\n", 236 | " im = Image.fromarray(np.uint8(255*A)).convert(\"L\")\n", 237 | " im.save('./SCANS/T2_num'+str(i)+'.png')\n", 238 | " \n", 239 | "\n", 240 | "# plotting.plot_anat(featmask,cut_coords=(32,32),draw_cross=False,annotate=False,black_bg=True,display_mode ='x')" 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": {}, 246 | "source": [ 247 | "Real Image - Slice series visualization" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": { 254 | "scrolled": true 255 | }, 256 | "outputs": [], 257 | "source": [ 258 | "Show_color = False\n", 259 | "\n", 260 | "image = gen_load.__next__()\n", 261 | "featmask = np.squeeze(image[0].data.cpu().numpy())\n", 262 | "featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 263 | "arr1 = [4,6,8,10,12,14,16,18,20,22,24,26,28,30,32]\n", 264 | "arr2 = [34,36,38,40,42,44,46,48,50,52,54,56,58,60]\n", 265 | "\n", 266 | "if Show_color:\n", 267 | " disp = plotting.plot_img(featmask,cut_coords=arr1,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 268 | " # disp.annotate(size=25,left_right=False,positions=True)\n", 269 | " plotting.show()\n", 270 | " disp=plotting.plot_img(featmask,cut_coords=arr2,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 271 | " # disp.annotate(size=25,left_right=False)\n", 272 | " plotting.show()\n", 273 | "else:\n", 274 | " disp = plotting.plot_anat(featmask,cut_coords=arr1,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 275 | " plotting.show()\n", 276 | " # disp.annotate(size=25,left_right=False)\n", 277 | " disp=plotting.plot_anat(featmask,cut_coords=arr2,draw_cross=False,annotate=False,black_bg=True,display_mode='x')\n", 278 | " # disp.annotate(size=25,left_right=False)\n", 279 | " plotting.show()" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 47, 285 | "metadata": {}, 286 | "outputs": [], 287 | "source": [ 288 | "gen_load = inf_train_gen(train_loader)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "code", 293 | "execution_count": 51, 294 | "metadata": {}, 295 | "outputs": [], 296 | "source": [ 297 | "image = gen_load.__next__()\n", 298 | "featmask = np.squeeze(image[0].data.cpu().numpy())\n", 299 | "# featmask = nib.Nifti1Image(featmask,affine = np.eye(4))\n", 300 | "\n", 301 | "from PIL import Image\n", 302 | "for i in range(64):\n", 303 | " A = 0.5*featmask[:,:,i]+0.5\n", 304 | " im = Image.fromarray(np.uint8(255*A)).convert(\"L\")\n", 305 | " im.save('./SCANS/Real_T2_num'+str(i)+'.png')\n", 306 | " " 307 | ] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "metadata": {}, 312 | "source": [ 313 | "# MS-SSIM Calculation" 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": 4, 319 | "metadata": {}, 320 | "outputs": [], 321 | "source": [ 322 | "sum_ssim = 0\n", 323 | "for k in range(20):\n", 324 | " for i,dat in enumerate(train_loader):\n", 325 | " if len(dat)!=2:\n", 326 | " break\n", 327 | " img1 = dat[0]\n", 328 | " img2 = dat[1]\n", 329 | "\n", 330 | " msssim = pytorch_ssim.msssim_3d(img1,img2)\n", 331 | " sum_ssim = sum_ssim+msssim\n", 332 | " print(sum_ssim/((k+1)*(i+1)))" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": null, 338 | "metadata": {}, 339 | "outputs": [], 340 | "source": [ 341 | "sum_ssim = 0\n", 342 | "for i in range(1000):\n", 343 | " noise = Variable(torch.randn((2, 1000)).cuda())\n", 344 | " fake_image = G(noise)\n", 345 | "\n", 346 | " img1 = fake_image[0]\n", 347 | " img2 = fake_image[1]\n", 348 | "\n", 349 | " msssim = pytorch_ssim.msssim_3d(img1,img2)\n", 350 | " sum_ssim = sum_ssim+msssim\n", 351 | "print(sum_ssim/1000)\n" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "# Maximum-Mean Discrepancy Score" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": null, 364 | "metadata": {}, 365 | "outputs": [], 366 | "source": [ 367 | "for p in G.parameters():\n", 368 | " p.requires_grad = False\n", 369 | "\n", 370 | "meanarr = []\n", 371 | "for s in range(100):\n", 372 | " distmean = 0.0\n", 373 | " for i,(y) in enumerate(train_loader):\n", 374 | " y = Variable(y).cuda()\n", 375 | " noise = Variable(torch.randn((y.size(0), 1000)).cuda())\n", 376 | " x = G(noise)\n", 377 | "\n", 378 | " B = y.size(0)\n", 379 | " x = x.view(x.size(0), x.size(2) * x.size(3)*x.size(4))\n", 380 | " y = y.view(y.size(0), y.size(2) * y.size(3)*y.size(4))\n", 381 | "\n", 382 | " xx, yy, zz = torch.mm(x,x.t()), torch.mm(y,y.t()), torch.mm(x,y.t())\n", 383 | "\n", 384 | " beta = (1./(B*B))\n", 385 | " gamma = (2./(B*B)) \n", 386 | "\n", 387 | " Dist = beta * (torch.sum(xx)+torch.sum(yy)) - gamma * torch.sum(zz)\n", 388 | " distmean += Dist\n", 389 | " print('Mean:'+str(distmean/(i+1)))\n", 390 | " meanarr.append(distmean/(i+1))\n", 391 | "meanarr = numpy.array(meanarr)\n", 392 | "print('Total_mean:'+str(np.mean(meanarr))+' STD:'+str(np.std(meanarr)))" 393 | ] 394 | } 395 | ], 396 | "metadata": { 397 | "kernelspec": { 398 | "display_name": "Python 3", 399 | "language": "python", 400 | "name": "python3" 401 | }, 402 | "language_info": { 403 | "codemirror_mode": { 404 | "name": "ipython", 405 | "version": 3 406 | }, 407 | "file_extension": ".py", 408 | "mimetype": "text/x-python", 409 | "name": "python", 410 | "nbconvert_exporter": "python", 411 | "pygments_lexer": "ipython3", 412 | "version": "3.5.6" 413 | } 414 | }, 415 | "nbformat": 4, 416 | "nbformat_minor": 2 417 | } 418 | --------------------------------------------------------------------------------