From a1f9bc23dfb8b597e4bc5b313f5120a57f0a5cea Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 24 Jul 2017 23:07:04 +0200 Subject: [PATCH 001/100] Automatic hard segmentation. - First implementation. --- segmentator/arcweld.py | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 segmentator/arcweld.py diff --git a/segmentator/arcweld.py b/segmentator/arcweld.py new file mode 100644 index 00000000..d9607360 --- /dev/null +++ b/segmentator/arcweld.py @@ -0,0 +1,72 @@ +"""Full automation experiments for T1w-like data (MPRAGE & MP2RAGE).""" + +import os +import peakutils +import numpy as np +import matplotlib.pyplot as plt +from nibabel import load, Nifti1Image, save + +# load +nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/S02_T1wDivPD_bet_nosub_aniso.nii.gz') +ima = nii.get_data() +basename = nii.get_filename().split(os.extsep, 1)[0] + +# calculate gradient magnitude +gra = np.gradient(ima) +gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) + +ima_max = np.percentile(ima, 99) +gra_max = np.percentile(gra, 99) + +# reshape for histograms +ima, gra = ima.flatten(), gra.flatten() +msk = ima > 0 + +# select low gradient magnitude regime (lr) +msk_lr = (gra < np.percentile(gra[msk], 10)) & (msk) +n, bins, _ = plt.hist(ima[msk_lr], 100, range=(0, ima_max)) + +# detect 'pure tissue' peaks (TODO: Checks for always finding 3 peaks) +peaks = peakutils.indexes(n, thres=0.25/max(n), min_dist=20) +tissues = [] +for p in peaks: + tissues.append(bins[p]) +tissues = np.array(tissues) + +# insert extreme brightness (eg. vessel) anchor +# tissues = np.append(tissues, tissues[-1] + (tissues[-1] - tissues[-2]) / 2) + +# insert zero-max arc +zmax_center = (0 + ima_max) / 2 +zmax_radius = ima_max - zmax_center +tissues = np.append(tissues, zmax_center) + +# create smooth maps (distance to pure tissue) +voxels = np.vstack([ima, gra]) +soft = [] # to hold soft tissue membership maps +for i, t in enumerate(tissues): + tissue = np.array([t, 0]) + # euclidean distance + edist = np.sqrt(np.sum((voxels - tissue[:, None])**2, axis=0)) + soft.append(edist) + # save intermediate maps + out = Nifti1Image(edist.reshape(nii.shape), affine=nii.affine) + save(out, basename + '_t' + str(i) + '.nii.gz') +soft = np.array(soft) + +# interface translation +soft[-1, :] = np.abs(soft[-1, :] - zmax_radius) + +zmax_weight = (gra / zmax_radius)**-1 +# zmax_weight = (((gra / zmax_radius) + (ima / tissues[-1])) / 2)**-1 +soft[-1, :] = zmax_weight*soft[-1, :] +out = Nifti1Image(soft[-1, :].reshape(nii.shape), affine=nii.affine) +save(out, basename + '_zmaxarc' + '.nii.gz') + +# hard tissue membership maps +hard = np.argmin(soft, axis=0) + +# save intermediate maps +out = Nifti1Image(hard.reshape(nii.shape), affine=nii.affine) +save(out, basename + '_hard' + '.nii.gz') +print 'Finished.' From db478426dbcc9fd2a483da5035de590c223034cd Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 25 Jul 2017 15:33:41 +0200 Subject: [PATCH 002/100] In progress... - Weighting parameters are added to allow further tweaking. - Zero-max arc is definition is changed. --- segmentator/arcweld.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/segmentator/arcweld.py b/segmentator/arcweld.py index d9607360..63d3f6ad 100644 --- a/segmentator/arcweld.py +++ b/segmentator/arcweld.py @@ -7,7 +7,7 @@ from nibabel import load, Nifti1Image, save # load -nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/S02_T1wDivPD_bet_nosub_aniso.nii.gz') +nii = load('/home/faruk/gdrive/Segmentator/data/faruk/mp2rage_roy/S001_UNI_restore_aniso.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] @@ -23,11 +23,12 @@ msk = ima > 0 # select low gradient magnitude regime (lr) -msk_lr = (gra < np.percentile(gra[msk], 10)) & (msk) +msk_lr = (gra < np.percentile(gra[msk], 25)) & (msk) n, bins, _ = plt.hist(ima[msk_lr], 100, range=(0, ima_max)) # detect 'pure tissue' peaks (TODO: Checks for always finding 3 peaks) -peaks = peakutils.indexes(n, thres=0.25/max(n), min_dist=20) +peaks = peakutils.indexes(n, thres=0.25/max(n), min_dist=10) +# peaks = peaks[0:-1] tissues = [] for p in peaks: tissues.append(bins[p]) @@ -37,8 +38,9 @@ # tissues = np.append(tissues, tissues[-1] + (tissues[-1] - tissues[-2]) / 2) # insert zero-max arc -zmax_center = (0 + ima_max) / 2 -zmax_radius = ima_max - zmax_center +zmax_max = tissues[-1] + tissues[-1]-tissues[-2] +zmax_center = (0 + zmax_max) / 2. +zmax_radius = zmax_max - zmax_center tissues = np.append(tissues, zmax_center) # create smooth maps (distance to pure tissue) @@ -47,22 +49,32 @@ for i, t in enumerate(tissues): tissue = np.array([t, 0]) # euclidean distance - edist = np.sqrt(np.sum((voxels - tissue[:, None])**2, axis=0)) + edist = np.sqrt(np.sum((voxels - tissue[:, None])**2., axis=0)) soft.append(edist) # save intermediate maps out = Nifti1Image(edist.reshape(nii.shape), affine=nii.affine) save(out, basename + '_t' + str(i) + '.nii.gz') soft = np.array(soft) -# interface translation -soft[-1, :] = np.abs(soft[-1, :] - zmax_radius) +# interface translation (shift zero circle to another radius) +soft[-1, :] = soft[-1, :] - zmax_radius +# zmax_neg = soft[-1, :] < 0 # voxels fall inside zero-max arc +# weight zero-max arc to not coincide with pure class regions +# zmax_weight = (gra[zmax_neg] / zmax_radius)**-1 zmax_weight = (gra / zmax_radius)**-1 -# zmax_weight = (((gra / zmax_radius) + (ima / tissues[-1])) / 2)**-1 -soft[-1, :] = zmax_weight*soft[-1, :] +# soft[-1, :][zmax_neg] = zmax_weight*np.abs(soft[-1, :][zmax_neg]) +soft[-1, :] = zmax_weight*np.abs(soft[-1, :]) out = Nifti1Image(soft[-1, :].reshape(nii.shape), affine=nii.affine) save(out, basename + '_zmaxarc' + '.nii.gz') +# arbitrary weighting (TODO: Can be turned into config file of some sort) +# save these values for MPRAGE +# soft[0, :] = soft[0, :] * 0.66 # csf +# soft[1, :] = soft[1, :] * 1. # gm +# soft[2, :] = soft[2, :] * 1.25 # wm +# soft[3, :] = soft[3, :] * 0.5 # zero-max arc + # hard tissue membership maps hard = np.argmin(soft, axis=0) From 005f45b9e2e35d65613fb7c88f45e502ad2f665a Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 25 Jul 2017 21:55:48 +0200 Subject: [PATCH 003/100] Work in progress... - 1D smoothing added for histogram. - More tweaks. --- segmentator/arcweld.py | 53 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/segmentator/arcweld.py b/segmentator/arcweld.py index 63d3f6ad..3af43557 100644 --- a/segmentator/arcweld.py +++ b/segmentator/arcweld.py @@ -1,13 +1,21 @@ -"""Full automation experiments for T1w-like data (MPRAGE & MP2RAGE).""" +"""Full automation experiments for T1w-like data (MPRAGE & MP2RAGE). + +TODO: + - Put anisotropic diffusion based smoothing to segmentator utilities. + - MP2RAGE, 2 type of gray matter giving issues, barycentric weights might + be useful to deal with this issue. + +""" import os import peakutils import numpy as np import matplotlib.pyplot as plt from nibabel import load, Nifti1Image, save +from scipy.ndimage.filters import gaussian_filter1d # load -nii = load('/home/faruk/gdrive/Segmentator/data/faruk/mp2rage_roy/S001_UNI_restore_aniso.nii.gz') +nii = load('/home/faruk/gdrive/Segmentator/data/roy/mp2rage/S0013_uni_bet_nosub_restore_aniso.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] @@ -15,30 +23,40 @@ gra = np.gradient(ima) gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) -ima_max = np.percentile(ima, 99) -gra_max = np.percentile(gra, 99) +# save for debugging +# out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) +# save(out, basename + '_gra' + '.nii.gz') + +ima_max = np.percentile(ima, 99.9) +gra_max = np.percentile(gra, 99.9) # reshape for histograms ima, gra = ima.flatten(), gra.flatten() -msk = ima > 0 +msk = ima > 0 # TODO: Parametrize +gra_thr = np.percentile(gra[msk], 75) # select low gradient magnitude regime (lr) -msk_lr = (gra < np.percentile(gra[msk], 25)) & (msk) -n, bins, _ = plt.hist(ima[msk_lr], 100, range=(0, ima_max)) +msk_lr = (gra < gra_thr) & (msk) +n, bins, _ = plt.hist(ima[msk_lr], 200, range=(0, ima_max)) + +# smooth histogram (good for peak detection) +n = gaussian_filter1d(n, 1) # detect 'pure tissue' peaks (TODO: Checks for always finding 3 peaks) -peaks = peakutils.indexes(n, thres=0.25/max(n), min_dist=10) +peaks = peakutils.indexes(n, thres=0.01/max(n), min_dist=20) # peaks = peaks[0:-1] tissues = [] for p in peaks: tissues.append(bins[p]) tissues = np.array(tissues) +print peaks +print tissues # insert extreme brightness (eg. vessel) anchor # tissues = np.append(tissues, tissues[-1] + (tissues[-1] - tissues[-2]) / 2) # insert zero-max arc -zmax_max = tissues[-1] + tissues[-1]-tissues[-2] +zmax_max = tissues[-1] + tissues[-1] - tissues[1] # first gm and wm zmax_center = (0 + zmax_max) / 2. zmax_radius = zmax_max - zmax_center tissues = np.append(tissues, zmax_center) @@ -52,8 +70,8 @@ edist = np.sqrt(np.sum((voxels - tissue[:, None])**2., axis=0)) soft.append(edist) # save intermediate maps - out = Nifti1Image(edist.reshape(nii.shape), affine=nii.affine) - save(out, basename + '_t' + str(i) + '.nii.gz') + # out = Nifti1Image(edist.reshape(nii.shape), affine=nii.affine) + # save(out, basename + '_t' + str(i) + '.nii.gz') soft = np.array(soft) # interface translation (shift zero circle to another radius) @@ -65,16 +83,21 @@ zmax_weight = (gra / zmax_radius)**-1 # soft[-1, :][zmax_neg] = zmax_weight*np.abs(soft[-1, :][zmax_neg]) soft[-1, :] = zmax_weight*np.abs(soft[-1, :]) -out = Nifti1Image(soft[-1, :].reshape(nii.shape), affine=nii.affine) -save(out, basename + '_zmaxarc' + '.nii.gz') +# save for debugging +# out = Nifti1Image(soft[-1, :].reshape(nii.shape), affine=nii.affine) +# save(out, basename + '_zmaxarc' + '.nii.gz') # arbitrary weighting (TODO: Can be turned into config file of some sort) -# save these values for MPRAGE +# save these values for MPRAGE T1w/PDw # soft[0, :] = soft[0, :] * 0.66 # csf -# soft[1, :] = soft[1, :] * 1. # gm # soft[2, :] = soft[2, :] * 1.25 # wm # soft[3, :] = soft[3, :] * 0.5 # zero-max arc +# save these values for MP2RAGE UNI +soft[0, :] = soft[0, :] * 0.55 # csf +soft[-2, :] = soft[-2, :] * 2 # wm +soft[-1, :] = soft[-1, :] * 0.5 # zero-max arc + # hard tissue membership maps hard = np.argmin(soft, axis=0) From edc61145a4cc53cd1b5c5430ecfb75b82a5c5989 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 4 Aug 2017 20:01:04 +0200 Subject: [PATCH 004/100] work in progress... small tweaks, and mp2rage is separated. --- segmentator/arcweld.py | 48 ++++++++----- segmentator/arcweld_mp2rage.py | 120 +++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 segmentator/arcweld_mp2rage.py diff --git a/segmentator/arcweld.py b/segmentator/arcweld.py index 3af43557..73554394 100644 --- a/segmentator/arcweld.py +++ b/segmentator/arcweld.py @@ -13,12 +13,19 @@ import matplotlib.pyplot as plt from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d +from retinex_for_mri.filters import anisodiff3 # load -nii = load('/home/faruk/gdrive/Segmentator/data/roy/mp2rage/S0013_uni_bet_nosub_restore_aniso.nii.gz') +nii = load('/home/faruk/gdrive/temp_segmentator_paper_data/MPRAGE/S02/derived/01_division/spm_arcweld/mS02_T1wDivPD_nosub.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] +# non-zero mask +msk = ima > 0 # TODO: Parametrize + +# aniso. diff. filter +ima = anisodiff3(ima, niter=2, kappa=50, gamma=0.1, option=1) + # calculate gradient magnitude gra = np.gradient(ima) gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) @@ -27,13 +34,11 @@ # out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) # save(out, basename + '_gra' + '.nii.gz') -ima_max = np.percentile(ima, 99.9) -gra_max = np.percentile(gra, 99.9) +ima_max = np.percentile(ima, 99) # reshape for histograms -ima, gra = ima.flatten(), gra.flatten() -msk = ima > 0 # TODO: Parametrize -gra_thr = np.percentile(gra[msk], 75) +ima, gra, msk = ima.flatten(), gra.flatten(), msk.flatten() +gra_thr = np.percentile(gra[msk], 20) # select low gradient magnitude regime (lr) msk_lr = (gra < gra_thr) & (msk) @@ -43,7 +48,7 @@ n = gaussian_filter1d(n, 1) # detect 'pure tissue' peaks (TODO: Checks for always finding 3 peaks) -peaks = peakutils.indexes(n, thres=0.01/max(n), min_dist=20) +peaks = peakutils.indexes(n, thres=0.01/max(n), min_dist=40) # peaks = peaks[0:-1] tissues = [] for p in peaks: @@ -52,9 +57,6 @@ print peaks print tissues -# insert extreme brightness (eg. vessel) anchor -# tissues = np.append(tissues, tissues[-1] + (tissues[-1] - tissues[-2]) / 2) - # insert zero-max arc zmax_max = tissues[-1] + tissues[-1] - tissues[1] # first gm and wm zmax_center = (0 + zmax_max) / 2. @@ -89,19 +91,29 @@ # arbitrary weighting (TODO: Can be turned into config file of some sort) # save these values for MPRAGE T1w/PDw -# soft[0, :] = soft[0, :] * 0.66 # csf +soft[0, :] = soft[0, :] * 0.66 # csf # soft[2, :] = soft[2, :] * 1.25 # wm -# soft[3, :] = soft[3, :] * 0.5 # zero-max arc - -# save these values for MP2RAGE UNI -soft[0, :] = soft[0, :] * 0.55 # csf -soft[-2, :] = soft[-2, :] * 2 # wm -soft[-1, :] = soft[-1, :] * 0.5 # zero-max arc +soft[3, :] = soft[3, :] * 0.5 # zero-max arc # hard tissue membership maps hard = np.argmin(soft, axis=0) +# append masked out areas +hard = hard + 1 +hard[~msk] = 0 + # save intermediate maps out = Nifti1Image(hard.reshape(nii.shape), affine=nii.affine) -save(out, basename + '_hard' + '.nii.gz') +save(out, basename + '_arcweld' + '.nii.gz') + +# save segmentator polish mask (not GM and WM) +labels = np.unique(hard)[[0, 1, -1]] +polish = np.ones(hard.shape) +polish[hard == labels[0]] = 0 +polish[hard == labels[1]] = 0 +polish[hard == labels[2]] = 0 +out = Nifti1Image(polish.reshape(nii.shape), affine=nii.affine) +save(out, basename + '_arcweld_mask' + '.nii.gz') print 'Finished.' + +# plt.show() diff --git a/segmentator/arcweld_mp2rage.py b/segmentator/arcweld_mp2rage.py new file mode 100644 index 00000000..1b603f87 --- /dev/null +++ b/segmentator/arcweld_mp2rage.py @@ -0,0 +1,120 @@ +"""Full automation experiments for T1w-like data (MPRAGE & MP2RAGE). + +TODO: + - Put anisotropic diffusion based smoothing to segmentator utilities. + - MP2RAGE, 2 type of gray matter giving issues, barycentric weights might + be useful to deal with this issue. + +""" + +import os +import peakutils +import numpy as np +import matplotlib.pyplot as plt +from nibabel import load, Nifti1Image, save +from scipy.ndimage.filters import gaussian_filter1d +from retinex_for_mri.filters import anisodiff3 + +# load +nii = load('/home/faruk/gdrive/temp_segmentator_paper_data/MP2RAGE/S013/derived/01_uni/fast_restored_arcweld/S013_uni_bet_nosub_restore.nii.gz') +ima = nii.get_data() +basename = nii.get_filename().split(os.extsep, 1)[0] + +# non-zero mask +msk = (ima != 0) # TODO: Parametrize +# +ima[msk] = ima[msk] + np.min(ima) + +# aniso. diff. filter +ima = anisodiff3(ima, niter=2, kappa=500, gamma=0.1, option=1) + +# calculate gradient magnitude +gra = np.gradient(ima) +gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) + +# save for debugging +# out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) +# save(out, basename + '_gra' + '.nii.gz') + +ima_max = np.percentile(ima, 100) + +# reshape for histograms +ima, gra, msk = ima.flatten(), gra.flatten(), msk.flatten() +gra_thr = np.percentile(gra[msk], 20) + +# select low gradient magnitude regime (lr) +msk_lr = (gra < gra_thr) & (msk) +n, bins, _ = plt.hist(ima[msk_lr], 200, range=(0, ima_max)) + +# smooth histogram (good for peak detection) +n = gaussian_filter1d(n, 1) + +# detect 'pure tissue' peaks (TODO: Checks for always finding 3 peaks) +peaks = peakutils.indexes(n, thres=0.01/max(n), min_dist=40) +# peaks = peaks[0:-1] +tissues = [] +for p in peaks: + tissues.append(bins[p]) +tissues = np.array(tissues) +print peaks +print tissues + +# insert zero-max arc +zmax_max = ima_max +zmax_center = (0 + zmax_max) / 2. +zmax_radius = zmax_max - zmax_center +tissues = np.append(tissues, zmax_center) + +# create smooth maps (distance to pure tissue) +voxels = np.vstack([ima, gra]) +soft = [] # to hold soft tissue membership maps +for i, t in enumerate(tissues): + tissue = np.array([t, 0]) + # euclidean distance + edist = np.sqrt(np.sum((voxels - tissue[:, None])**2., axis=0)) + soft.append(edist) + # save intermediate maps + # out = Nifti1Image(edist.reshape(nii.shape), affine=nii.affine) + # save(out, basename + '_t' + str(i) + '.nii.gz') +soft = np.array(soft) + +# interface translation (shift zero circle to another radius) +soft[-1, :] = soft[-1, :] - zmax_radius +# zmax_neg = soft[-1, :] < 0 # voxels fall inside zero-max arc + +# weight zero-max arc to not coincide with pure class regions +# zmax_weight = (gra[zmax_neg] / zmax_radius)**-1 +zmax_weight = (gra / zmax_radius)**-1 +# soft[-1, :][zmax_neg] = zmax_weight*np.abs(soft[-1, :][zmax_neg]) +soft[-1, :] = zmax_weight*np.abs(soft[-1, :]) +# save for debugging +# out = Nifti1Image(soft[-1, :].reshape(nii.shape), affine=nii.affine) +# save(out, basename + '_zmaxarc' + '.nii.gz') + +# arbitrary weighting (TODO: Can be turned into config file of some sort) +# save these values for MP2RAGE UNI +soft[0, :] = soft[0, :] * 1 # csf +soft[-1, :] = soft[-1, :] * 0.5 # zero-max arc + +# hard tissue membership maps +hard = np.argmin(soft, axis=0) + +# append masked out areas +hard = hard + 1 +hard[~msk] = 0 + +# save hard classification +out = Nifti1Image(hard.reshape(nii.shape), affine=nii.affine) +save(out, basename + '_arcweld' + '.nii.gz') + +# save segmentator polish mask (not GM and WM) +labels = np.unique(hard)[[0, 1, -1]] +polish = np.ones(hard.shape) +polish[hard == labels[0]] = 0 +polish[hard == labels[1]] = 0 +polish[hard == labels[2]] = 0 +out = Nifti1Image(polish.reshape(nii.shape), affine=nii.affine) +save(out, basename + '_arcweld_mask' + '.nii.gz') +print 'Finished.' + +# plt.show() From 16b120f7aa7f945cab64979d045c08426218020e Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 7 Aug 2017 19:12:32 +0200 Subject: [PATCH 005/100] Theoretical borders after arcweld. --- segmentator/tests/test_arcweld.py | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 segmentator/tests/test_arcweld.py diff --git a/segmentator/tests/test_arcweld.py b/segmentator/tests/test_arcweld.py new file mode 100644 index 00000000..b0e1e872 --- /dev/null +++ b/segmentator/tests/test_arcweld.py @@ -0,0 +1,44 @@ +"""Test and demonstsrate arcweld classification. + +TODO: Turn this into unit tests for arcweld. + +""" + +import numpy as np +import matplotlib.pyplot as plt + +# create toy data +res = 100 # resolution +data = np.mgrid[0:res, 0:res].astype('float') +ima, gra = data[0, :, :].flatten(), data[1, :, :].flatten() +dims = data.shape + +# have 3 arbitrary classes (standing for classes csf, gm, wm) +classes = np.array([res/5, res/5*3, res/5*4]) + +# find arc anchor +arc_center = (data.max() + data.min()) / 2. +arc_radius = (data.max() - data.min()) / 2. +arc_weight = (gra / arc_radius)**-1 +classes = np.hstack([classes, arc_center]) + +# find euclidean distances to classes +soft = [] +data = data.reshape(dims[0], dims[1]*dims[2]) +for i, a in enumerate(classes): + tissue = np.array([a, 0]) + # euclidean distance + edist = np.sqrt(np.sum((data - tissue[:, None])**2., axis=0)) + soft.append(edist) +soft = np.asarray(soft) + +# arc translation +soft[-1, :] = soft[-1, :] - arc_radius +soft[-1, :] = arc_weight * np.abs(soft[-1, :]) + +# hard tissue membership maps +hard = np.argmin(soft, axis=0) +hard = hard.reshape(dims[1], dims[2]) + +plt.imshow(hard.T, origin="lower") +plt.show() From 490a4de8d497bef2ef7a2f5ce10c43b00e2c1c60 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 7 Aug 2017 20:56:38 +0200 Subject: [PATCH 006/100] work in progress... --- segmentator/tests/test_arcweld.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/segmentator/tests/test_arcweld.py b/segmentator/tests/test_arcweld.py index b0e1e872..d84b86b1 100644 --- a/segmentator/tests/test_arcweld.py +++ b/segmentator/tests/test_arcweld.py @@ -8,13 +8,13 @@ import matplotlib.pyplot as plt # create toy data -res = 100 # resolution +res = 500 # resolution data = np.mgrid[0:res, 0:res].astype('float') ima, gra = data[0, :, :].flatten(), data[1, :, :].flatten() dims = data.shape # have 3 arbitrary classes (standing for classes csf, gm, wm) -classes = np.array([res/5, res/5*3, res/5*4]) +classes = np.array([res/7, res/7*4, res/7*6]) # find arc anchor arc_center = (data.max() + data.min()) / 2. @@ -26,9 +26,9 @@ soft = [] data = data.reshape(dims[0], dims[1]*dims[2]) for i, a in enumerate(classes): - tissue = np.array([a, 0]) + c = np.array([a, 0]) # euclidean distance - edist = np.sqrt(np.sum((data - tissue[:, None])**2., axis=0)) + edist = np.sqrt(np.sum((data - c[:, None])**2., axis=0)) soft.append(edist) soft = np.asarray(soft) @@ -36,7 +36,11 @@ soft[-1, :] = soft[-1, :] - arc_radius soft[-1, :] = arc_weight * np.abs(soft[-1, :]) -# hard tissue membership maps +# arbitrary weights +soft[0, :] = soft[0, :] * 0.66 # csf +soft[-1, :] = soft[-1, :] * 0.5 # arc + +# hard class membership maps hard = np.argmin(soft, axis=0) hard = hard.reshape(dims[1], dims[2]) From 3e4c7aa143aeefa8eee3afe94df0be0d95b5adca Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 8 Aug 2017 18:43:37 +0200 Subject: [PATCH 007/100] New gradient magnitude calculations. - Default if now switched to sobel - prewitt is added as an option. - the old method is now referred to as 'numpy' --- segmentator/__main__.py | 5 +-- segmentator/arcweld.py | 4 +-- segmentator/arcweld_mp2rage.py | 4 +-- segmentator/config.py | 2 +- segmentator/hist2d_counts.py | 10 +++--- segmentator/segmentator_main.py | 9 ++---- segmentator/segmentator_ncut.py | 9 +++--- segmentator/tests/test_gradient_magnitude.py | 27 +++++++++++++++++ segmentator/utils.py | 32 +++++++++++++++++++- 9 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 segmentator/tests/test_gradient_magnitude.py diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 78b3ae43..03fe6697 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -26,8 +26,9 @@ def main(args=None): help="Path to input. Mostly a nifti file with image data." ) parser.add_argument( - "--gramag", metavar='path', required=False, - help="Path to gradient magnitude (useful for deriche)" + "--gramag", metavar='string', required=False, + default=config.gramag, + help="sobel, prewitt, numpy or path to a gradient magnitude nifti." ) parser.add_argument( "--ncut", metavar='path', required=False, diff --git a/segmentator/arcweld.py b/segmentator/arcweld.py index 73554394..0ff696ef 100644 --- a/segmentator/arcweld.py +++ b/segmentator/arcweld.py @@ -14,6 +14,7 @@ from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d from retinex_for_mri.filters import anisodiff3 +from utils import compute_gradient_magnitude # load nii = load('/home/faruk/gdrive/temp_segmentator_paper_data/MPRAGE/S02/derived/01_division/spm_arcweld/mS02_T1wDivPD_nosub.nii.gz') @@ -27,8 +28,7 @@ ima = anisodiff3(ima, niter=2, kappa=50, gamma=0.1, option=1) # calculate gradient magnitude -gra = np.gradient(ima) -gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) +gra = compute_gradient_magnitude(ima, method='sobel') # save for debugging # out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) diff --git a/segmentator/arcweld_mp2rage.py b/segmentator/arcweld_mp2rage.py index 1b603f87..d4490446 100644 --- a/segmentator/arcweld_mp2rage.py +++ b/segmentator/arcweld_mp2rage.py @@ -14,6 +14,7 @@ from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d from retinex_for_mri.filters import anisodiff3 +from utils import compute_gradient_magnitude # load nii = load('/home/faruk/gdrive/temp_segmentator_paper_data/MP2RAGE/S013/derived/01_uni/fast_restored_arcweld/S013_uni_bet_nosub_restore.nii.gz') @@ -29,8 +30,7 @@ ima = anisodiff3(ima, niter=2, kappa=500, gamma=0.1, option=1) # calculate gradient magnitude -gra = np.gradient(ima) -gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) +gra = compute_gradient_magnitude(ima, method='sobel') # save for debugging # out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) diff --git a/segmentator/config.py b/segmentator/config.py index 5343fa7b..637bb610 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -11,7 +11,7 @@ # segmentator main command line variables filename = 'sample_filename_here' -gramag = 'gradient_magnitude_sample_here' +gramag = 'sobel' perc_min = 0.25 perc_max = 99.75 scale = 400 diff --git a/segmentator/hist2d_counts.py b/segmentator/hist2d_counts.py index 6e9a97d0..b875393b 100644 --- a/segmentator/hist2d_counts.py +++ b/segmentator/hist2d_counts.py @@ -19,7 +19,7 @@ import os import numpy as np import config as cfg -from utils import TruncateRange, ScaleRange, Hist2D +from utils import TruncateRange, ScaleRange, Hist2D, compute_gradient_magnitude from nibabel import load # load data @@ -33,16 +33,14 @@ # copy intensity data so we can flatten the copy and leave original intact ima = orig.copy() -if cfg.gramag: +if cfg.gramag not in ['sobel', 'prewitt', 'numpy']: nii2 = load(cfg.gramag) gra = np.squeeze(nii2.get_data()) gra = TruncateRange(gra, percMin=cfg.perc_min, percMax=cfg.perc_max) gra = ScaleRange(gra, scaleFactor=cfg.scale, delta=0.0001) + else: - # calculate gradient magnitude (using L2 norm of the vector) - gra = np.gradient(ima) - gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + - np.power(gra[2], 2)) + gra = compute_gradient_magnitude(ima, method=cfg.gramag) # reshape ima (a bit more intuitive for voxel-wise operations) ima = np.ndarray.flatten(ima) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 78a382c4..718520f7 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -30,7 +30,7 @@ from nibabel import load from segmentator_functions import responsiveObj from sector_mask import sector_mask -from utils import Ima2VolHistMapping, Hist2D +from utils import Ima2VolHistMapping, Hist2D, compute_gradient_magnitude from utils import TruncateRange, ScaleRange import config as cfg @@ -48,17 +48,14 @@ # copy intensity data so we can flatten the copy and leave original intact ima = orig.copy() -if cfg.gramag: +if cfg.gramag not in ['sobel', 'prewitt', 'numpy']: nii2 = load(cfg.gramag) gra = np.squeeze(nii2.get_data()) gra = TruncateRange(gra, percMin=percMin, percMax=percMax) gra = ScaleRange(gra, scaleFactor=cfg.scale, delta=0.0001) else: - # calculate gradient magnitude (using L2 norm of the vector) - gra = np.gradient(ima) - gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + - np.power(gra[2], 2)) + gra = compute_gradient_magnitude(ima, method=cfg.gramag) # reshape ima (more intuitive for voxel-wise operations) ima = np.ndarray.flatten(ima) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 113b0686..401da3e7 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -28,6 +28,7 @@ from matplotlib.colors import LogNorm, ListedColormap, BoundaryNorm from matplotlib.widgets import Slider, Button, RadioButtons from utils import Ima2VolHistMapping, TruncateRange, ScaleRange, Hist2D +from utils import compute_gradient_magnitude from segmentator_functions import responsiveObj # %% @@ -74,16 +75,14 @@ # copy intensity data so we can flatten the copy and leave original intact ima = orig.copy() -if cfg.gramag: +if cfg.gramag not in ['sobel', 'prewitt', 'numpy']: nii2 = load(cfg.gramag) gra = np.squeeze(nii2.get_data()) gra = TruncateRange(gra, percMin=percMin, percMax=percMax) gra = ScaleRange(gra, scaleFactor=cfg.scale, delta=0.0001) + else: - # calculate gradient magnitude (using L2 norm of the vector) - gra = np.gradient(ima) - gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + - np.power(gra[2], 2)) + gra = compute_gradient_magnitude(ima, method=cfg.gramag) # reshape ima (more intuitive for voxel-wise operations) ima = np.ndarray.flatten(ima) diff --git a/segmentator/tests/test_gradient_magnitude.py b/segmentator/tests/test_gradient_magnitude.py new file mode 100644 index 00000000..361ab202 --- /dev/null +++ b/segmentator/tests/test_gradient_magnitude.py @@ -0,0 +1,27 @@ +"""Experiment with difference grdient magnitude calculations.""" + +import os +import numpy as np +from nibabel import load, Nifti1Image, save +from scipy.ndimage import generic_gradient_magnitude, sobel, prewitt, laplace + +# load +nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mprage_t1w_restore.nii.gz') +ima = nii.get_data() +basename = nii.get_filename().split(os.extsep, 1)[0] + +# calculate numpy gradient magnitude +gra = np.gradient(ima) +gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) +out = Nifti1Image(gra, affine=nii.affine) +save(out, basename + '_numpy_gradient.nii.gz') + +# calculate sobel +gra = generic_gradient_magnitude(ima, sobel)/32. +out = Nifti1Image(gra, affine=nii.affine) +save(out, basename + '_scipy_sobel.nii.gz') + +# calculate prewitt +gra = generic_gradient_magnitude(ima, prewitt)/32. +out = Nifti1Image(gra, affine=nii.affine) +save(out, basename + '_scipy_prewitt.nii.gz') diff --git a/segmentator/utils.py b/segmentator/utils.py index e98e2e43..b12583e5 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -19,7 +19,7 @@ from __future__ import division import numpy as np import matplotlib.pyplot as plt - +from scipy.ndimage import sobel, prewitt, laplace, generic_gradient_magnitude def sub2ind(array_shape, rows, cols): """Pixel to voxel mapping (similar to matlab's function). @@ -171,3 +171,33 @@ def Hist2D(ima, gra): binEdges = np.arange(dataMin, dataMax+1) counts, _, _, volHistH = plt.hist2d(ima, gra, bins=binEdges, cmap='Greys') return counts, volHistH, dataMin, dataMax, nrBins, binEdges + + +def compute_gradient_magnitude(ima, method='sobel'): + """Compute gradient magnitude of images. + + Parameters + ---------- + ima : np.ndarray + First image, which is often the intensity image (eg. T1w). + method : string + Gradient computation method. Available options are 'sobel', 'prewitt', + 'numpy'. + Returns + ------- + gra_mag : np.ndarray + Second image, which is often the gradient magnitude image + derived from the first image + """ + if method == 'sobel': + return generic_gradient_magnitude(ima, sobel)/32. + elif method == 'prewitt': + return generic_gradient_magnitude(ima, prewitt)/18. + elif method == 'numpy': + gra = np.gradient(ima) + gra_mag = np.zeros(ima.shape) + for d in range(len(gra)): + gra_mag = np.sum(gra_mag, np.power(gra[d], 2.)) + return np.sqrt(gra_mag) + else: + print 'Gradient magnitude method is invalid!' From a2f2309e4c157653d226f7a3072b76caae3fd666 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 12:29:46 +0200 Subject: [PATCH 008/100] Gradient magnitude option improvements. - It seems that having 3D kernels + convolution gives more plausible gradient magnitude scaling than default scipy.ndimage filter methods. - Scipy methods might be dropped in the future. --- segmentator/__main__.py | 5 +- segmentator/config.py | 6 +- segmentator/hist2d_counts.py | 2 +- segmentator/segmentator_main.py | 2 +- segmentator/segmentator_ncut.py | 2 +- segmentator/tests/test_gradient_magnitude.py | 36 +++++++---- segmentator/utils.py | 67 +++++++++++++++++--- 7 files changed, 92 insertions(+), 28 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 03fe6697..a32cba1a 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -26,9 +26,10 @@ def main(args=None): help="Path to input. Mostly a nifti file with image data." ) parser.add_argument( - "--gramag", metavar='string', required=False, + "--gramag", metavar='3D_sobel', required=False, default=config.gramag, - help="sobel, prewitt, numpy or path to a gradient magnitude nifti." + help="3D_sobel, '3D_prewitt', 'scipy_sobel', 'scipy_prewitt', 'numpy' \ + or path to a gradient magnitude nifti." ) parser.add_argument( "--ncut", metavar='path', required=False, diff --git a/segmentator/config.py b/segmentator/config.py index 637bb610..06591862 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -11,11 +11,15 @@ # segmentator main command line variables filename = 'sample_filename_here' -gramag = 'sobel' +gramag = '3D_sobel' perc_min = 0.25 perc_max = 99.75 scale = 400 +# possible gradient magnitude computation options +gramag_options = ['3D_sobel', '3D_prewitt', 'scipy_sobel', 'scipy_prewitt', + 'numpy'] + # used in Deriche filter deriche_alpha = 2 diff --git a/segmentator/hist2d_counts.py b/segmentator/hist2d_counts.py index b875393b..dad684f6 100644 --- a/segmentator/hist2d_counts.py +++ b/segmentator/hist2d_counts.py @@ -33,7 +33,7 @@ # copy intensity data so we can flatten the copy and leave original intact ima = orig.copy() -if cfg.gramag not in ['sobel', 'prewitt', 'numpy']: +if cfg.gramag not in cfg.gramag_options: nii2 = load(cfg.gramag) gra = np.squeeze(nii2.get_data()) gra = TruncateRange(gra, percMin=cfg.perc_min, percMax=cfg.perc_max) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 718520f7..47d311b3 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -48,7 +48,7 @@ # copy intensity data so we can flatten the copy and leave original intact ima = orig.copy() -if cfg.gramag not in ['sobel', 'prewitt', 'numpy']: +if cfg.gramag not in cfg.gramag_options: nii2 = load(cfg.gramag) gra = np.squeeze(nii2.get_data()) gra = TruncateRange(gra, percMin=percMin, percMax=percMax) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 401da3e7..00198d6d 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -75,7 +75,7 @@ # copy intensity data so we can flatten the copy and leave original intact ima = orig.copy() -if cfg.gramag not in ['sobel', 'prewitt', 'numpy']: +if cfg.gramag not in cfg.gramag_options: nii2 = load(cfg.gramag) gra = np.squeeze(nii2.get_data()) gra = TruncateRange(gra, percMin=percMin, percMax=percMax) diff --git a/segmentator/tests/test_gradient_magnitude.py b/segmentator/tests/test_gradient_magnitude.py index 361ab202..5263d353 100644 --- a/segmentator/tests/test_gradient_magnitude.py +++ b/segmentator/tests/test_gradient_magnitude.py @@ -1,27 +1,39 @@ -"""Experiment with difference grdient magnitude calculations.""" +"""Experiment with different gradient magnitude calculations. + +TODO: turn this into unit tests. + +""" import os -import numpy as np from nibabel import load, Nifti1Image, save -from scipy.ndimage import generic_gradient_magnitude, sobel, prewitt, laplace +from segmentator.utils import compute_gradient_magnitude # load nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mprage_t1w_restore.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] +# calculate 3D Sobel +gra_mag = compute_gradient_magnitude(ima, method='3D_sobel') +out = Nifti1Image(gra_mag, affine=nii.affine) +save(out, basename + '_3D_sobel.nii.gz') + +# calculate 3D Prewitt +gra_mag = compute_gradient_magnitude(ima, method='3D_prewitt') +out = Nifti1Image(gra_mag, affine=nii.affine) +save(out, basename + '_3D_prewitt.nii.gz') + # calculate numpy gradient magnitude -gra = np.gradient(ima) -gra = np.sqrt(np.power(gra[0], 2) + np.power(gra[1], 2) + np.power(gra[2], 2)) -out = Nifti1Image(gra, affine=nii.affine) +gra_mag = compute_gradient_magnitude(ima, method='numpy') +out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_numpy_gradient.nii.gz') -# calculate sobel -gra = generic_gradient_magnitude(ima, sobel)/32. -out = Nifti1Image(gra, affine=nii.affine) +# calculate scipy sobel +gra_mag = compute_gradient_magnitude(ima, method='scipy_sobel') +out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_scipy_sobel.nii.gz') -# calculate prewitt -gra = generic_gradient_magnitude(ima, prewitt)/32. -out = Nifti1Image(gra, affine=nii.affine) +# calculate scipy prewitt +gra_mag = compute_gradient_magnitude(ima, method='scipy_prewitt') +out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_scipy_prewitt.nii.gz') diff --git a/segmentator/utils.py b/segmentator/utils.py index b12583e5..da4bf2c7 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -19,7 +19,8 @@ from __future__ import division import numpy as np import matplotlib.pyplot as plt -from scipy.ndimage import sobel, prewitt, laplace, generic_gradient_magnitude +from scipy.ndimage import sobel, prewitt, convolve, generic_gradient_magnitude + def sub2ind(array_shape, rows, cols): """Pixel to voxel mapping (similar to matlab's function). @@ -173,6 +174,38 @@ def Hist2D(ima, gra): return counts, volHistH, dataMin, dataMax, nrBins, binEdges +def create_3D_kernel(operator='sobel'): + """Create various 3D kernels. + + Parameters + ---------- + operator : np.ndarray, shape=(n, n, 3) + Input can be 'sobel', 'prewitt' or any 3D numpy array. + + Returns + ------- + kernel : np.ndarray, shape(6, n, n, 3) + + """ + if operator == 'sobel': + operator = np.array([[[1, 2, 1], [2, 4, 2], [1, 2, 1]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[-1, -2, -1], [-2, -4, -2], [-1, -2, -1]]]) + elif operator == 'prewitt': + operator = np.array([[[1, 1, 1], [1, 1, 1], [1, 1, 1]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]]) + # create permutations operator that will be used in gradient computation + kernel = np.zeros([6, 3, 3, 3]) + kernel[0, ...] = operator + kernel[1, ...] = kernel[0, ::-1, ::-1, ::-1] + kernel[2, ...] = np.transpose(kernel[0, ...], [1, 2, 0]) + kernel[3, ...] = kernel[2, ::-1, ::-1, ::-1] + kernel[4, ...] = np.transpose(kernel[0, ...], [2, 0, 1]) + kernel[5, ...] = kernel[4, ::-1, ::-1, ::-1] + return kernel + + def compute_gradient_magnitude(ima, method='sobel'): """Compute gradient magnitude of images. @@ -181,23 +214,37 @@ def compute_gradient_magnitude(ima, method='sobel'): ima : np.ndarray First image, which is often the intensity image (eg. T1w). method : string - Gradient computation method. Available options are 'sobel', 'prewitt', - 'numpy'. + Gradient computation method. Available options are '3D_sobel', + 'scipy_sobel', 'scipy_prewitt', 'numpy'. Returns ------- gra_mag : np.ndarray Second image, which is often the gradient magnitude image derived from the first image """ - if method == 'sobel': + if method == '3D_sobel': # magnitude scale is similar to numpy method + kernel = create_3D_kernel(operator='sobel') + gra = np.zeros(ima.shape + (kernel.shape[0],)) + for d in range(kernel.shape[0]): + gra[..., d] = convolve(ima, kernel[d, ...]) + # compute generic gradient magnitude with normalization + gra_mag = np.sqrt(np.sum(np.power(gra, 2), axis=-1))/32. + return gra_mag + elif method == '3D_prewitt': + kernel = create_3D_kernel(operator='prewitt') + gra = np.zeros(ima.shape + (kernel.shape[0],)) + for d in range(kernel.shape[0]): + gra[..., d] = convolve(ima, kernel[d, ...]) + # compute generic gradient magnitude with normalization + gra_mag = np.sqrt(np.sum(np.power(gra, 2), axis=-1))/18. + return gra_mag + elif method == 'scipy_sobel': return generic_gradient_magnitude(ima, sobel)/32. - elif method == 'prewitt': + elif method == 'scipy_prewitt': return generic_gradient_magnitude(ima, prewitt)/18. elif method == 'numpy': - gra = np.gradient(ima) - gra_mag = np.zeros(ima.shape) - for d in range(len(gra)): - gra_mag = np.sum(gra_mag, np.power(gra[d], 2.)) - return np.sqrt(gra_mag) + gra = np.asarray(np.gradient(ima)) + gra_mag = np.sqrt(np.sum(np.power(gra, 2), axis=0)) + return gra_mag else: print 'Gradient magnitude method is invalid!' From 61cc8e6e803b2dbffd7abb8de34cd065797a3b50 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 12:51:34 +0200 Subject: [PATCH 009/100] Added scipy dependency. --- README.md | 18 +++++++++--------- setup.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0179a815..5379c17e 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ -Segmentator is a free and open-source package for multi-dimensional data exploration and segmentation for 3D images. This application is mainly developed and tested using ultra-high field magnetic resonance imaging (MRI) brain data. +Segmentator is a free and open-source package for multi-dimensional data exploration and segmentation for 3D images. This application is mainly developed and tested using ultra-high field magnetic resonance imaging (MRI) brain data. -The goal is to provide a complementary tool to the already available brain tissue segmentation methods (to the best of our knowledge) in other software packages (FSL, Freesurfer, SPM, Brainvoyager, itk-SNAP, etc.). +The goal is to provide a complementary tool to the already available brain tissue segmentation methods (to the best of our knowledge) in other software packages (FSL, Freesurfer, SPM, Brainvoyager, itk-SNAP, etc.). ## Core dependencies [**Python 2.7**](https://www.python.org/download/releases/2.7/) @@ -18,6 +18,7 @@ The goal is to provide a complementary tool to the already available brain tissu | [NumPy](http://www.numpy.org/) | 1.11.1 | | [matplotlib](http://matplotlib.org/) | 1.5.3 | | [NiBabel](http://nipy.org/nibabel/) | 2.1.0 | +| [SciPy](http://scipy.org/) | 0.19.0 | ## Installation & Quick Start Please visit [our wiki](https://github.com/ofgulban/segmentator/wiki/Installation) to see how to install and use Segmentator. @@ -31,10 +32,9 @@ The project is licensed under [GNU General Public License Version 3](http://www. ## References This application is based on the following work: -* Kindlmann, G., & Durkin, J. W. (1998). Semi-automatic generation of transfer functions for direct volume rendering. In Proceedings of the 1998 IEEE symposium on Volume visualization - VVS ’98 (pp. 79–86). New York, New York, USA: ACM Press. http://doi.org/10.1145/288126.288167 -* Kniss, J., Kindlmann, G., & Hansen, C. (2001). Interactive volume rendering using multi-dimensional transfer functions and direct manipulation widgets. In Proceedings Visualization, 2001. VIS ’01. (pp. 255–562). IEEE. http://doi.org/10.1109/VISUAL.2001.964519 -* Kniss, J., Kindlmann, G., & Hansen, C. (2002). Multidimensional transfer functions for interactive volume rendering. IEEE Transactions on Visualization and Computer Graphics, 8(3), 270–285. http://doi.org/10.1109/TVCG.2002.1021579 -* Kniss, J., Kindlmann, G., & Hansen, C. D. (2005). Multidimensional transfer functions for volume rendering. Visualization Handbook, 189–209. http://doi.org/10.1016/B978-012387582-2/50011-3 -* Jianbo Shi, & Malik, J. (2000). Normalized cuts and image segmentation. IEEE Transactions on Pattern Analysis and Machine Intelligence, 22(8), 888–905. http://doi.org/10.1109/34.868688 -* Ip, C. Y., Varshney, A., & Jaja, J. (2012). Hierarchical exploration of volumes using multilevel segmentation of the intensity-gradient histograms. IEEE Transactions on Visualization and Computer Graphics, 18(12), 2355–2363. http://doi.org/10.1109/TVCG.2012.231 - +* Kindlmann, G., & Durkin, J. W. (1998). Semi-automatic generation of transfer functions for direct volume rendering. In Proceedings of the 1998 IEEE symposium on Volume visualization - VVS ’98 (pp. 79–86). New York, New York, USA: ACM Press. http://doi.org/10.1145/288126.288167 +* Kniss, J., Kindlmann, G., & Hansen, C. (2001). Interactive volume rendering using multi-dimensional transfer functions and direct manipulation widgets. In Proceedings Visualization, 2001. VIS ’01. (pp. 255–562). IEEE. http://doi.org/10.1109/VISUAL.2001.964519 +* Kniss, J., Kindlmann, G., & Hansen, C. (2002). Multidimensional transfer functions for interactive volume rendering. IEEE Transactions on Visualization and Computer Graphics, 8(3), 270–285. http://doi.org/10.1109/TVCG.2002.1021579 +* Kniss, J., Kindlmann, G., & Hansen, C. D. (2005). Multidimensional transfer functions for volume rendering. Visualization Handbook, 189–209. http://doi.org/10.1016/B978-012387582-2/50011-3 +* Jianbo Shi, & Malik, J. (2000). Normalized cuts and image segmentation. IEEE Transactions on Pattern Analysis and Machine Intelligence, 22(8), 888–905. http://doi.org/10.1109/34.868688 +* Ip, C. Y., Varshney, A., & Jaja, J. (2012). Hierarchical exploration of volumes using multilevel segmentation of the intensity-gradient histograms. IEEE Transactions on Visualization and Computer Graphics, 18(12), 2355–2363. http://doi.org/10.1109/TVCG.2012.231 diff --git a/setup.py b/setup.py index 4553bbf8..f66c6052 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ author_email='faruk.gulban@maastrichtuniversity.nl', license='GNU General Public License Version 3', packages=['segmentator'], - install_requires=['numpy', 'matplotlib', 'cython'], + install_requires=['numpy', 'matplotlib', 'scipy', 'cython'], keywords=['mri', 'segmentation'], zip_safe=True, entry_points={ From 437d4056047b71826ba1abed493adcb36bf72da1 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 15:02:52 +0200 Subject: [PATCH 010/100] 3D sobel related changes. It seems that WM-GM classification needs to be tweaked further. --- segmentator/arcweld.py | 12 ++++++------ segmentator/arcweld_mp2rage.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/segmentator/arcweld.py b/segmentator/arcweld.py index 0ff696ef..bb901938 100644 --- a/segmentator/arcweld.py +++ b/segmentator/arcweld.py @@ -14,10 +14,10 @@ from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d from retinex_for_mri.filters import anisodiff3 -from utils import compute_gradient_magnitude +from segmentator.utils import compute_gradient_magnitude # load -nii = load('/home/faruk/gdrive/temp_segmentator_paper_data/MPRAGE/S02/derived/01_division/spm_arcweld/mS02_T1wDivPD_nosub.nii.gz') +nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mprage_S02_restore.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] @@ -28,7 +28,7 @@ ima = anisodiff3(ima, niter=2, kappa=50, gamma=0.1, option=1) # calculate gradient magnitude -gra = compute_gradient_magnitude(ima, method='sobel') +gra = compute_gradient_magnitude(ima, method='3D_sobel') # save for debugging # out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) @@ -91,9 +91,9 @@ # arbitrary weighting (TODO: Can be turned into config file of some sort) # save these values for MPRAGE T1w/PDw -soft[0, :] = soft[0, :] * 0.66 # csf -# soft[2, :] = soft[2, :] * 1.25 # wm -soft[3, :] = soft[3, :] * 0.5 # zero-max arc +# soft[0, :] = soft[0, :] * 0.66 # csf +# # soft[2, :] = soft[2, :] * 1.25 # wm +# soft[3, :] = soft[3, :] * 0.5 # zero-max arcs # hard tissue membership maps hard = np.argmin(soft, axis=0) diff --git a/segmentator/arcweld_mp2rage.py b/segmentator/arcweld_mp2rage.py index d4490446..3218a074 100644 --- a/segmentator/arcweld_mp2rage.py +++ b/segmentator/arcweld_mp2rage.py @@ -14,10 +14,10 @@ from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d from retinex_for_mri.filters import anisodiff3 -from utils import compute_gradient_magnitude +from segmentator.utils import compute_gradient_magnitude # load -nii = load('/home/faruk/gdrive/temp_segmentator_paper_data/MP2RAGE/S013/derived/01_uni/fast_restored_arcweld/S013_uni_bet_nosub_restore.nii.gz') +nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mp2rage_S001_restore.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] @@ -30,7 +30,7 @@ ima = anisodiff3(ima, niter=2, kappa=500, gamma=0.1, option=1) # calculate gradient magnitude -gra = compute_gradient_magnitude(ima, method='sobel') +gra = compute_gradient_magnitude(ima, method='3D_sobel') # save for debugging # out = Nifti1Image(gra.reshape(nii.shape), affine=nii.affine) @@ -93,8 +93,8 @@ # arbitrary weighting (TODO: Can be turned into config file of some sort) # save these values for MP2RAGE UNI -soft[0, :] = soft[0, :] * 1 # csf -soft[-1, :] = soft[-1, :] * 0.5 # zero-max arc +# soft[0, :] = soft[0, :] * 1 # csf +# soft[-1, :] = soft[-1, :] * 0.5 # zero-max arc # hard tissue membership maps hard = np.argmin(soft, axis=0) From 372808bbf882d6d0eeb57f0bae4629d03939b656 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 15:05:26 +0200 Subject: [PATCH 011/100] Consistency tweaks. --- segmentator/utils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index da4bf2c7..0cf0effe 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -190,11 +190,13 @@ def create_3D_kernel(operator='sobel'): if operator == 'sobel': operator = np.array([[[1, 2, 1], [2, 4, 2], [1, 2, 1]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[-1, -2, -1], [-2, -4, -2], [-1, -2, -1]]]) + [[-1, -2, -1], [-2, -4, -2], [-1, -2, -1]]], + dtype='float') elif operator == 'prewitt': operator = np.array([[[1, 1, 1], [1, 1, 1], [1, 1, 1]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]]) + [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]], + dtype='float') # create permutations operator that will be used in gradient computation kernel = np.zeros([6, 3, 3, 3]) kernel[0, ...] = operator @@ -228,7 +230,7 @@ def compute_gradient_magnitude(ima, method='sobel'): for d in range(kernel.shape[0]): gra[..., d] = convolve(ima, kernel[d, ...]) # compute generic gradient magnitude with normalization - gra_mag = np.sqrt(np.sum(np.power(gra, 2), axis=-1))/32. + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/32. return gra_mag elif method == '3D_prewitt': kernel = create_3D_kernel(operator='prewitt') @@ -236,7 +238,7 @@ def compute_gradient_magnitude(ima, method='sobel'): for d in range(kernel.shape[0]): gra[..., d] = convolve(ima, kernel[d, ...]) # compute generic gradient magnitude with normalization - gra_mag = np.sqrt(np.sum(np.power(gra, 2), axis=-1))/18. + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/18. return gra_mag elif method == 'scipy_sobel': return generic_gradient_magnitude(ima, sobel)/32. @@ -244,7 +246,7 @@ def compute_gradient_magnitude(ima, method='sobel'): return generic_gradient_magnitude(ima, prewitt)/18. elif method == 'numpy': gra = np.asarray(np.gradient(ima)) - gra_mag = np.sqrt(np.sum(np.power(gra, 2), axis=0)) + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=0)) return gra_mag else: print 'Gradient magnitude method is invalid!' From c3a222739c7225bfd49a5c48379222116f24c05e Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 15:52:18 +0200 Subject: [PATCH 012/100] Setting gradient magnitude is modularized. - and some little cosmetics. --- segmentator/config.py | 3 ++- segmentator/hist2d_counts.py | 16 +++---------- segmentator/segmentator_main.py | 41 +++++++++++--------------------- segmentator/segmentator_ncut.py | 42 +++++++++++---------------------- segmentator/utils.py | 30 +++++++++++++++++++++++ 5 files changed, 63 insertions(+), 69 deletions(-) diff --git a/segmentator/config.py b/segmentator/config.py index 06591862..7db935f1 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -16,10 +16,11 @@ perc_max = 99.75 scale = 400 -# possible gradient magnitude computation options +# possible gradient magnitude computation keyword options gramag_options = ['3D_sobel', '3D_prewitt', 'scipy_sobel', 'scipy_prewitt', 'numpy'] + # used in Deriche filter deriche_alpha = 2 diff --git a/segmentator/hist2d_counts.py b/segmentator/hist2d_counts.py index dad684f6..ed913c08 100644 --- a/segmentator/hist2d_counts.py +++ b/segmentator/hist2d_counts.py @@ -19,7 +19,7 @@ import os import numpy as np import config as cfg -from utils import TruncateRange, ScaleRange, Hist2D, compute_gradient_magnitude +from utils import TruncateRange, ScaleRange, Hist2D, set_gradient_magnitude from nibabel import load # load data @@ -30,20 +30,10 @@ orig = np.squeeze(nii.get_data()) orig = TruncateRange(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) orig = ScaleRange(orig, scaleFactor=cfg.scale, delta=0.0001) - -# copy intensity data so we can flatten the copy and leave original intact -ima = orig.copy() -if cfg.gramag not in cfg.gramag_options: - nii2 = load(cfg.gramag) - gra = np.squeeze(nii2.get_data()) - gra = TruncateRange(gra, percMin=cfg.perc_min, percMax=cfg.perc_max) - gra = ScaleRange(gra, scaleFactor=cfg.scale, delta=0.0001) - -else: - gra = compute_gradient_magnitude(ima, method=cfg.gramag) +gra = set_gradient_magnitude(orig, cfg.gramag) # reshape ima (a bit more intuitive for voxel-wise operations) -ima = np.ndarray.flatten(ima) +ima = np.ndarray.flatten(orig) gra = np.ndarray.flatten(gra) counts, _, _, _, _, _ = Hist2D(ima, gra) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 47d311b3..1c610f97 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -30,8 +30,8 @@ from nibabel import load from segmentator_functions import responsiveObj from sector_mask import sector_mask -from utils import Ima2VolHistMapping, Hist2D, compute_gradient_magnitude -from utils import TruncateRange, ScaleRange +from utils import Ima2VolHistMapping, Hist2D +from utils import TruncateRange, ScaleRange, set_gradient_magnitude import config as cfg """Load Data""" @@ -40,28 +40,15 @@ # """Data Processing""" orig = np.squeeze(nii.get_data()) - -percMin, percMax = cfg.perc_min, cfg.perc_max -orig = TruncateRange(orig, percMin=percMin, percMax=percMax) -scaleFactor = cfg.scale -orig = ScaleRange(orig, scaleFactor=scaleFactor, delta=0.0001) - -# copy intensity data so we can flatten the copy and leave original intact -ima = orig.copy() -if cfg.gramag not in cfg.gramag_options: - nii2 = load(cfg.gramag) - gra = np.squeeze(nii2.get_data()) - gra = TruncateRange(gra, percMin=percMin, percMax=percMax) - gra = ScaleRange(gra, scaleFactor=cfg.scale, delta=0.0001) - -else: - gra = compute_gradient_magnitude(ima, method=cfg.gramag) +dims = orig.shape +orig = TruncateRange(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) +orig = ScaleRange(orig, scaleFactor=cfg.scale, delta=0.0001) +gra = set_gradient_magnitude(orig, cfg.gramag) # reshape ima (more intuitive for voxel-wise operations) -ima = np.ndarray.flatten(ima) +ima = np.ndarray.flatten(orig) gra = np.ndarray.flatten(gra) - # """Plots""" # Set up a colormap: @@ -89,22 +76,22 @@ # plot 3D ima by default ax2 = fig.add_subplot(122) slcH = ax2.imshow( - orig[:, :, int(orig.shape[2]/2)], + orig[:, :, int(dims[2]/2)], cmap=plt.cm.gray, vmin=ima.min(), vmax=ima.max(), interpolation='none', - extent=[0, orig.shape[1], orig.shape[0], 0] + extent=[0, dims[1], dims[0], 0] ) -imaMask = np.ones(orig.shape[0:2]) +imaMask = np.ones(dims[0:2]) imaMaskH = ax2.imshow( imaMask, cmap=palette, vmin=0.1, interpolation='none', alpha=0.5, - extent=[0, orig.shape[1], orig.shape[0], 0] + extent=[0, dims[1], dims[0], 0] ) # adjust subplots on figure bottom = 0.30 @@ -145,7 +132,7 @@ nii=nii, sectorObj=sectorObj, nrBins=nrBins, - sliceNr=int(0.5*orig.shape[2]), + sliceNr=int(0.5*dims[2]), slcH=slcH, imaMask=imaMask, imaMaskH=imaMaskH, @@ -154,13 +141,13 @@ contains=volHistMaskH.contains, counts=counts, idxLasso=idxLasso, - initTpl=(percMin, percMax, scaleFactor), + initTpl=(cfg.perc_min, cfg.perc_max, cfg.scale), lassoSwitchCount=lassoSwitchCount) # make the figure responsive to clicks flexFig.connect() ima2volHistMap = Ima2VolHistMapping(xinput=ima, yinput=gra, binsArray=binEdges) -flexFig.invHistVolume = np.reshape(ima2volHistMap, orig.shape) +flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) # """Sliders and Buttons""" diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 00198d6d..ab3f5fcc 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -28,7 +28,7 @@ from matplotlib.colors import LogNorm, ListedColormap, BoundaryNorm from matplotlib.widgets import Slider, Button, RadioButtons from utils import Ima2VolHistMapping, TruncateRange, ScaleRange, Hist2D -from utils import compute_gradient_magnitude +from utils import set_gradient_magnitude from segmentator_functions import responsiveObj # %% @@ -63,29 +63,15 @@ # %% -"""Data Pre-Processing""" +"""Data Processing""" orig = np.squeeze(nii.get_data()) -percMin, percMax = cfg.perc_min, cfg.perc_max -orig = TruncateRange(orig, percMin=percMin, percMax=percMax) -scaleFactor = cfg.scale -orig = ScaleRange(orig, scaleFactor=scaleFactor, delta=0.0001) -# define dataMin and dataMax for later use -dataMin = np.round(orig.min()) -dataMax = np.round(orig.max()) - -# copy intensity data so we can flatten the copy and leave original intact -ima = orig.copy() -if cfg.gramag not in cfg.gramag_options: - nii2 = load(cfg.gramag) - gra = np.squeeze(nii2.get_data()) - gra = TruncateRange(gra, percMin=percMin, percMax=percMax) - gra = ScaleRange(gra, scaleFactor=cfg.scale, delta=0.0001) - -else: - gra = compute_gradient_magnitude(ima, method=cfg.gramag) +dims = orig.shape +orig = TruncateRange(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) +orig = ScaleRange(orig, scaleFactor=cfg.scale, delta=0.0001) +gra = set_gradient_magnitude(orig, cfg.gramag) # reshape ima (more intuitive for voxel-wise operations) -ima = np.ndarray.flatten(ima) +ima = np.ndarray.flatten(orig) gra = np.ndarray.flatten(gra) # %% @@ -130,14 +116,14 @@ # plot 3D ima by default ax2 = fig.add_subplot(122) -slcH = ax2.imshow(orig[:, :, int(orig.shape[2]/2)], cmap=plt.cm.gray, +slcH = ax2.imshow(orig[:, :, int(dims[2]/2)], cmap=plt.cm.gray, vmin=ima.min(), vmax=ima.max(), interpolation='none', - extent=[0, orig.shape[1], orig.shape[0], 0]) -imaMask = np.zeros(orig.shape[0:2])*total_labels[1] + extent=[0, dims[1], dims[0], 0]) +imaMask = np.zeros(dims[0:2])*total_labels[1] imaMaskH = ax2.imshow(imaMask, interpolation='none', alpha=0.5, cmap=ncut_palette, vmin=np.min(ncut_labels)+1, vmax=lMax, - extent=[0, orig.shape[1], orig.shape[0], 0]) + extent=[0, dims[1], dims[0], 0]) # adjust subplots on figure bottom = 0.30 @@ -158,7 +144,7 @@ nii=nii, ima=ima, nrBins=nrBins, - sliceNr=int(0.5*orig.shape[2]), + sliceNr=int(0.5*dims[2]), slcH=slcH, imaMask=imaMask, imaMaskH=imaMaskH, @@ -169,7 +155,7 @@ counterField=np.zeros((nrBins, nrBins)), orig_ncut_labels=orig_ncut_labels, ima_ncut_labels=ima_ncut_labels, - initTpl=(percMin, percMax, scaleFactor), + initTpl=(cfg.perc_min, cfg.perc_max, cfg.scale), lMax=lMax ) @@ -177,7 +163,7 @@ flexFig.connect() # get mapping from image slice to volume histogram ima2volHistMap = Ima2VolHistMapping(xinput=ima, yinput=gra, binsArray=binEdges) -flexFig.invHistVolume = np.reshape(ima2volHistMap, orig.shape) +flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) # %% """Sliders and Buttons""" diff --git a/segmentator/utils.py b/segmentator/utils.py index 0cf0effe..767a8727 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -19,6 +19,8 @@ from __future__ import division import numpy as np import matplotlib.pyplot as plt +import config as cfg +from nibabel import load from scipy.ndimage import sobel, prewitt, convolve, generic_gradient_magnitude @@ -250,3 +252,31 @@ def compute_gradient_magnitude(ima, method='sobel'): return gra_mag else: print 'Gradient magnitude method is invalid!' + + +def set_gradient_magnitude(image, gramag_option): + """Set gradient magnitude based on the command line flag. + + Parameters + ---------- + image : np.ndarray + First image, which is often the intensity image (eg. T1w). + gramag_option : string + A keyword string or a path to a nigti file. + + Returns + ------- + gra_mag : np.ndarray + Gradient magnitude image, which has the same shape as image. + + """ + if gramag_option not in cfg.gramag_options: + gra_mag_nii = load(gramag_option) + gra_mag = np.squeeze(gra_mag_nii.get_data()) + gra_mag = TruncateRange(gra_mag, percMin=cfg.perc_min, + percMax=cfg.perc_max) + gra_mag = ScaleRange(gra_mag, scaleFactor=cfg.scale, delta=0.0001) + + else: + gra_mag = compute_gradient_magnitude(image, method=gramag_option) + return gra_mag From cd9fe991ae7a8de334eb8f6bbf86695cdac1d0a9 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 17:17:48 +0200 Subject: [PATCH 013/100] Cosmetics changes. - and small quality improvements in utils. --- segmentator/config.py | 4 +-- segmentator/hist2d_counts.py | 9 ++--- segmentator/segmentator_functions.py | 6 ++-- segmentator/segmentator_main.py | 17 +++++----- segmentator/segmentator_ncut.py | 13 ++++---- segmentator/utils.py | 49 ++++++++++++++++++---------- 6 files changed, 58 insertions(+), 40 deletions(-) diff --git a/segmentator/config.py b/segmentator/config.py index 7db935f1..b993075c 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -12,8 +12,8 @@ # segmentator main command line variables filename = 'sample_filename_here' gramag = '3D_sobel' -perc_min = 0.25 -perc_max = 99.75 +perc_min = 2.5 +perc_max = 97.5 scale = 400 # possible gradient magnitude computation keyword options diff --git a/segmentator/hist2d_counts.py b/segmentator/hist2d_counts.py index ed913c08..302e0e82 100644 --- a/segmentator/hist2d_counts.py +++ b/segmentator/hist2d_counts.py @@ -19,7 +19,8 @@ import os import numpy as np import config as cfg -from utils import TruncateRange, ScaleRange, Hist2D, set_gradient_magnitude +from segmentator.utils import truncate_range, scale_range +from segmentator.utils import set_gradient_magnitude, prep_2D_hist from nibabel import load # load data @@ -28,15 +29,15 @@ # data processing orig = np.squeeze(nii.get_data()) -orig = TruncateRange(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) -orig = ScaleRange(orig, scaleFactor=cfg.scale, delta=0.0001) +orig = truncate_range(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) +orig = scale_range(orig, scale_factor=cfg.scale, delta=0.0001) gra = set_gradient_magnitude(orig, cfg.gramag) # reshape ima (a bit more intuitive for voxel-wise operations) ima = np.ndarray.flatten(orig) gra = np.ndarray.flatten(gra) -counts, _, _, _, _, _ = Hist2D(ima, gra) +counts, _, _, _, _, _ = prep_2D_hist(ima, gra) outName = (basename + '_volHist' + '_pMax' + str(cfg.perc_max) + '_pMin' + str(cfg.perc_min) + '_sc' + str(int(cfg.scale)) diff --git a/segmentator/segmentator_functions.py b/segmentator/segmentator_functions.py index cabaa04f..9cd5bd31 100755 --- a/segmentator/segmentator_functions.py +++ b/segmentator/segmentator_functions.py @@ -20,7 +20,7 @@ import os import numpy as np import matplotlib.pyplot as plt -from utils import VolHist2ImaMapping +from utils import map_2D_hist_to_ima from nibabel import save, Nifti1Image import config as cfg @@ -54,7 +54,7 @@ def updateMsks(self): self.volHistMaskH.set_data(self.volHistMask) self.volHistMaskH.set_extent((0, self.nrBins, self.nrBins, 0)) # update imaMask - self.imaMask = VolHist2ImaMapping( + self.imaMask = map_2D_hist_to_ima( self.invHistVolume[:, :, self.sliceNr], self.volHistMask) if self.borderSwitch == 1: self.imaMask = self.calcImaMaskBrd() @@ -298,7 +298,7 @@ def exportNifti(self, event): out_volHistMask[out_volHistMask == label] = intLabels[newLabel] # get 3D brain mask temp = np.transpose(self.invHistVolume, cycBackPerm) - outNii = VolHist2ImaMapping(temp, out_volHistMask) + outNii = map_2D_hist_to_ima(temp, out_volHistMask) outNii = outNii.reshape(temp.shape) # save mask image as nii new_image = Nifti1Image(outNii, header=self.nii.get_header(), diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 1c610f97..3af12b25 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -30,8 +30,9 @@ from nibabel import load from segmentator_functions import responsiveObj from sector_mask import sector_mask -from utils import Ima2VolHistMapping, Hist2D -from utils import TruncateRange, ScaleRange, set_gradient_magnitude +from segmentator.utils import map_ima_to_2D_hist, prep_2D_hist +from segmentator.utils import truncate_range, scale_range +from segmentator.utils import set_gradient_magnitude import config as cfg """Load Data""" @@ -41,13 +42,13 @@ """Data Processing""" orig = np.squeeze(nii.get_data()) dims = orig.shape -orig = TruncateRange(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) -orig = ScaleRange(orig, scaleFactor=cfg.scale, delta=0.0001) +orig = truncate_range(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) +orig = scale_range(orig, scale_factor=cfg.scale, delta=0.0001) gra = set_gradient_magnitude(orig, cfg.gramag) # reshape ima (more intuitive for voxel-wise operations) -ima = np.ndarray.flatten(orig) -gra = np.ndarray.flatten(gra) +ima = np.copy(orig.flatten()) +gra = gra.flatten() # """Plots""" @@ -61,7 +62,7 @@ fig = plt.figure() ax = fig.add_subplot(121) -counts, volHistH, dataMin, dataMax, nrBins, binEdges = Hist2D(ima, gra) +counts, volHistH, dataMin, dataMax, nrBins, binEdges = prep_2D_hist(ima, gra) ax.set_xlim(dataMin, dataMax) ax.set_ylim(dataMin, dataMax) @@ -146,7 +147,7 @@ # make the figure responsive to clicks flexFig.connect() -ima2volHistMap = Ima2VolHistMapping(xinput=ima, yinput=gra, binsArray=binEdges) +ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=binEdges) flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) # diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index ab3f5fcc..a00c8283 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -27,8 +27,9 @@ from nibabel import load from matplotlib.colors import LogNorm, ListedColormap, BoundaryNorm from matplotlib.widgets import Slider, Button, RadioButtons -from utils import Ima2VolHistMapping, TruncateRange, ScaleRange, Hist2D -from utils import set_gradient_magnitude +from segmentator.utils import map_ima_to_2D_hist, prep_2D_hist +from segmentator.utils import truncate_range, scale_range +from segmentator.utils import set_gradient_magnitude from segmentator_functions import responsiveObj # %% @@ -66,8 +67,8 @@ """Data Processing""" orig = np.squeeze(nii.get_data()) dims = orig.shape -orig = TruncateRange(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) -orig = ScaleRange(orig, scaleFactor=cfg.scale, delta=0.0001) +orig = truncate_range(orig, percMin=cfg.perc_min, percMax=cfg.perc_max) +orig = scale_range(orig, scale_factor=cfg.scale, delta=0.0001) gra = set_gradient_magnitude(orig, cfg.gramag) # reshape ima (more intuitive for voxel-wise operations) @@ -80,7 +81,7 @@ fig = plt.figure() ax = fig.add_subplot(121) -counts, volHistH, dataMin, dataMax, nrBins, binEdges = Hist2D(ima, gra) +counts, volHistH, dataMin, dataMax, nrBins, binEdges = prep_2D_hist(ima, gra) ax.set_xlim(dataMin, dataMax) ax.set_ylim(dataMin, dataMax) @@ -162,7 +163,7 @@ # make the figure responsive to clicks flexFig.connect() # get mapping from image slice to volume histogram -ima2volHistMap = Ima2VolHistMapping(xinput=ima, yinput=gra, binsArray=binEdges) +ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=binEdges) flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) # %% diff --git a/segmentator/utils.py b/segmentator/utils.py index 767a8727..d5307306 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -42,7 +42,7 @@ def sub2ind(array_shape, rows, cols): return (cols*array_shape + rows) -def Ima2VolHistMapping(xinput, yinput, binsArray): +def map_ima_to_2D_hist(xinput, yinput, bins_arr): """Image to volume histogram mapping (kind of inverse histogram). Parameters @@ -52,7 +52,8 @@ def Ima2VolHistMapping(xinput, yinput, binsArray): yinput : TODO Second image, which is often the gradient magnitude image derived from the first image. - binsArray : TODO + bins_arr : TODO + Array of bins. Returns ------- @@ -60,14 +61,14 @@ def Ima2VolHistMapping(xinput, yinput, binsArray): Voxel to pixel mapping. """ - dgtzData = np.digitize(xinput, binsArray)-1 - dgtzGra = np.digitize(yinput, binsArray)-1 - nrBins = len(binsArray)-1 # subtract 1 (more borders than containers) + dgtzData = np.digitize(xinput, bins_arr)-1 + dgtzGra = np.digitize(yinput, bins_arr)-1 + nrBins = len(bins_arr)-1 # subtract 1 (more borders than containers) vox2pixMap = sub2ind(nrBins, dgtzData, dgtzGra) # 1D return vox2pixMap -def VolHist2ImaMapping(imaSlc2volHistMap, volHistMask): +def map_2D_hist_to_ima(imaSlc2volHistMap, volHistMask): """Volume histogram to image mapping for slices (uses np.ind1). Parameters @@ -92,7 +93,7 @@ def VolHist2ImaMapping(imaSlc2volHistMap, volHistMask): return imaSlcMask -def TruncateRange(data, percMin=0.25, percMax=99.75): +def truncate_range(data, percMin=0.25, percMax=99.75, discard_zeros=True): """Truncate too low and too high values. Parameters @@ -103,31 +104,40 @@ def TruncateRange(data, percMin=0.25, percMax=99.75): Percentile minimum. percMax : float Percentile maximum. + discard_zeros : bool + Discard voxels with value 0 from truncation. Returns ------- data : np.ndarray """ - percDataMin, percDataMax = np.percentile(data, [percMin, percMax]) + if discard_zeros: + msk = data != 0 + else: + msk = np.ones(data.shape) + percDataMin, percDataMax = np.percentile(data[msk], [percMin, percMax]) data[data < percDataMin] = percDataMin # adjust minimum data[data > percDataMax] = percDataMax # adjust maximum + data[~msk] = 0 # put back masked out voxels return data -def ScaleRange(data, scaleFactor=500, delta=0): +def scale_range(data, scale_factor=500, delta=0, discard_zeros=True): """Scale values as a preprocessing step. Parameters ---------- data : np.ndarray Image to be scaled. - scaleFactor : float + scale_factor : float Lower scaleFactors provides faster interface due to loweing the resolution of 2D histogram ( 500 seems fast enough). delta : float Delta ensures that the max data points fall inside the last bin when this function is used with histograms. + discard_zeros : bool + Discard voxels with value 0 from truncation. Returns ------- @@ -135,12 +145,17 @@ def ScaleRange(data, scaleFactor=500, delta=0): Scaled image. """ - scaleFactor = scaleFactor - delta - data = data - data.min() - return scaleFactor / data.max() * data + if discard_zeros: + msk = data != 0 + else: + msk = np.ones(data.shape) + scale_factor = scale_factor - delta + data[msk] = data[msk] - data[msk].min() + data[msk] = scale_factor / data[msk].max() * data[msk] + return data -def Hist2D(ima, gra): +def prep_2D_hist(ima, gra): """Prepare 2D histogram related variables. Parameters @@ -273,9 +288,9 @@ def set_gradient_magnitude(image, gramag_option): if gramag_option not in cfg.gramag_options: gra_mag_nii = load(gramag_option) gra_mag = np.squeeze(gra_mag_nii.get_data()) - gra_mag = TruncateRange(gra_mag, percMin=cfg.perc_min, - percMax=cfg.perc_max) - gra_mag = ScaleRange(gra_mag, scaleFactor=cfg.scale, delta=0.0001) + gra_mag = truncate_range(gra_mag, percMin=cfg.perc_min, + percMax=cfg.perc_max) + gra_mag = scale_range(gra_mag, scale_factor=cfg.scale, delta=0.0001) else: gra_mag = compute_gradient_magnitude(image, method=gramag_option) From 76bf2e50127f5b202bf675d6ded14987a5641e92 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 17:26:50 +0200 Subject: [PATCH 014/100] Reorganization. - arcweld scripts are moved to future folder. - anisotropic diffusion based smoothing filter is included in segmentator.utils. Deprency for retinex_for_mri package is not needed anymore. --- segmentator/{ => future}/arcweld.py | 5 +- segmentator/{ => future}/arcweld_mp2rage.py | 5 +- segmentator/utils.py | 152 ++++++++++++++++++++ 3 files changed, 156 insertions(+), 6 deletions(-) rename segmentator/{ => future}/arcweld.py (95%) rename segmentator/{ => future}/arcweld_mp2rage.py (95%) diff --git a/segmentator/arcweld.py b/segmentator/future/arcweld.py similarity index 95% rename from segmentator/arcweld.py rename to segmentator/future/arcweld.py index bb901938..c32c3d3f 100644 --- a/segmentator/arcweld.py +++ b/segmentator/future/arcweld.py @@ -13,8 +13,7 @@ import matplotlib.pyplot as plt from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d -from retinex_for_mri.filters import anisodiff3 -from segmentator.utils import compute_gradient_magnitude +from segmentator.utils import compute_gradient_magnitude, aniso_diff_3D # load nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mprage_S02_restore.nii.gz') @@ -25,7 +24,7 @@ msk = ima > 0 # TODO: Parametrize # aniso. diff. filter -ima = anisodiff3(ima, niter=2, kappa=50, gamma=0.1, option=1) +ima = aniso_diff_3D(ima, niter=2, kappa=50, gamma=0.1, option=1) # calculate gradient magnitude gra = compute_gradient_magnitude(ima, method='3D_sobel') diff --git a/segmentator/arcweld_mp2rage.py b/segmentator/future/arcweld_mp2rage.py similarity index 95% rename from segmentator/arcweld_mp2rage.py rename to segmentator/future/arcweld_mp2rage.py index 3218a074..2029fc1c 100644 --- a/segmentator/arcweld_mp2rage.py +++ b/segmentator/future/arcweld_mp2rage.py @@ -13,8 +13,7 @@ import matplotlib.pyplot as plt from nibabel import load, Nifti1Image, save from scipy.ndimage.filters import gaussian_filter1d -from retinex_for_mri.filters import anisodiff3 -from segmentator.utils import compute_gradient_magnitude +from segmentator.utils import compute_gradient_magnitude, aniso_diff_3D # load nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mp2rage_S001_restore.nii.gz') @@ -27,7 +26,7 @@ ima[msk] = ima[msk] + np.min(ima) # aniso. diff. filter -ima = anisodiff3(ima, niter=2, kappa=500, gamma=0.1, option=1) +ima = aniso_diff_3D(ima, niter=2, kappa=500, gamma=0.1, option=1) # calculate gradient magnitude gra = compute_gradient_magnitude(ima, method='3D_sobel') diff --git a/segmentator/utils.py b/segmentator/utils.py index d5307306..9ea8a4c7 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -18,6 +18,7 @@ from __future__ import division import numpy as np +import warnings import matplotlib.pyplot as plt import config as cfg from nibabel import load @@ -295,3 +296,154 @@ def set_gradient_magnitude(image, gramag_option): else: gra_mag = compute_gradient_magnitude(image, method=gramag_option) return gra_mag + + +def aniso_diff_3D(stack, niter=1, kappa=50, gamma=0.1, step=(1., 1., 1.), + option=1, ploton=False): + """3D anisotropic diffusion based smoothing. + + Disclosure + ---------- + This script is adapted from a stackoverflow post by user ali_m: + [1] http://stackoverflow.com/questions/10802611/anisotropic-diffusion-2d-images + [2] http://pastebin.com/sBsPX4Y7 + + Parameters + ---------- + stack : 3d numpy array + Input stack/image/volume/data. + niter : int + Number of iterations + kappa : float + Conduction coefficient (20-100?). Controls conduction as a + function of gradient. If kappa is low small intensity + gradients are able to block conduction and hence diffusion + across step edges. A large value reduces the influence of + intensity gradients on conduction. + gamma : float + Controls speed of diffusion (you usually want it at a + maximum of 0.25 for stability). + step : tuple + The distance between adjacent pixels in (z,y,x). Step is + used to scale the gradients in case the spacing between + adjacent pixels differs in the x,y and/or z axes. + option : int, 1 or 2 + 1 favours high contrast edges over low contrast ones + (Perona & Malik [1] diffusion equation No 1). + 2 favours wide regions over smaller ones + (Perona & Malik [1] diffusion equation No 2). + ploton : bool + if True, the middle z-plane will be plotted on every + iteration/ + + Returns + ------- + stackout : 3d numpy array + Diffused stack/image/volume/data. + + Reference + --------- + [1] P. Perona and J. Malik. + Scale-space and edge detection using ansotropic diffusion. + IEEE Transactions on Pattern Analysis and Machine + Intelligence, 12(7):629-639, July 1990. + + Notes + ----- + Original MATLAB code by Peter Kovesi + School of Computer Science & Software Engineering, + The University of Western Australia + + + Translated to Python and optimised by Alistair Muldal + Department of Pharmacology, University of Oxford + + + June 2000 original version. + March 2002 corrected diffusion equation No 2. + July 2012 translated to Python + January 2017 docstring reorganization. + + """ + # ...you could always diffuse each color channel independently if you + # really want + if stack.ndim == 4: + warnings.warn("Only grayscale stacks allowed, converting to 3D \ + matrix") + stack = stack.mean(3) + + # initialize output array + stack = stack.astype('float32') + stackout = stack.copy() + + # initialize some internal variables + deltaS = np.zeros_like(stackout) + deltaE = deltaS.copy() + deltaD = deltaS.copy() + NS = deltaS.copy() + EW = deltaS.copy() + UD = deltaS.copy() + gS = np.ones_like(stackout) + gE = gS.copy() + gD = gS.copy() + + # create the plot figure, if requested + if ploton: + import pylab as pl + + showplane = stack.shape[0]//2 + + fig = pl.figure(figsize=(20, 5.5), num="Anisotropic diffusion") + ax1, ax2 = fig.add_subplot(1, 2, 1), fig.add_subplot(1, 2, 2) + + ax1.imshow(stack[showplane, ...].squeeze(), + interpolation='nearest') + ih = ax2.imshow(stackout[showplane, ...].squeeze(), + interpolation='nearest', animated=True) + ax1.set_title("Original stack (Z = %i)" % showplane) + ax2.set_title("Iteration 0") + + fig.canvas.draw() + + for ii in xrange(niter): + + # calculate the diffs + deltaD[:-1, :, :] = np.diff(stackout, axis=0) + deltaS[:, :-1, :] = np.diff(stackout, axis=1) + deltaE[:, :, :-1] = np.diff(stackout, axis=2) + + # conduction gradients (only need to compute one per dim!) + if option == 1: + gD = np.exp(-(deltaD/kappa)**2.)/step[0] + gS = np.exp(-(deltaS/kappa)**2.)/step[1] + gE = np.exp(-(deltaE/kappa)**2.)/step[2] + elif option == 2: + gD = 1./(1.+(deltaD/kappa)**2.)/step[0] + gS = 1./(1.+(deltaS/kappa)**2.)/step[1] + gE = 1./(1.+(deltaE/kappa)**2.)/step[2] + + # update matrices + D = gD*deltaD + E = gE*deltaE + S = gS*deltaS + + # subtract a copy that has been shifted 'Up/North/West' by one + # pixel. don't as questions. just do it. trust me. + UD[:] = D + NS[:] = S + EW[:] = E + UD[1:, :, :] -= D[:-1, :, :] + NS[:, 1:, :] -= S[:, :-1, :] + EW[:, :, 1:] -= E[:, :, :-1] + + # update the image + stackout += gamma*(UD+NS+EW) + + if ploton: + iterstring = "Iteration %i" % (ii+1) + ih.set_data(stackout[showplane, ...].squeeze()) + ax2.set_title(iterstring) + fig.canvas.draw() + # sleep(0.01) + + return stackout From 6832c09a32c1dc08026ee34bae463c9c5b4118c8 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 17:27:14 +0200 Subject: [PATCH 015/100] Name change. --- segmentator/future/{arcweld.py => arcweld_mprage.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename segmentator/future/{arcweld.py => arcweld_mprage.py} (100%) diff --git a/segmentator/future/arcweld.py b/segmentator/future/arcweld_mprage.py similarity index 100% rename from segmentator/future/arcweld.py rename to segmentator/future/arcweld_mprage.py From 1f0c102c75af9295a8805eb5a3c020e6d8f42762 Mon Sep 17 00:00:00 2001 From: Omer Faruk Gulban Date: Wed, 9 Aug 2017 21:11:50 +0200 Subject: [PATCH 016/100] Create readme.md --- segmentator/future/readme.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 segmentator/future/readme.md diff --git a/segmentator/future/readme.md b/segmentator/future/readme.md new file mode 100644 index 00000000..b2eb5861 --- /dev/null +++ b/segmentator/future/readme.md @@ -0,0 +1 @@ +This is a folder for work in progress components. From 41ad002a54babaa91a288909c99376f7314e892d Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 21:23:19 +0200 Subject: [PATCH 017/100] Copyright statements added. --- segmentator/future/arcweld_mp2rage.py | 16 ++++++++++++++++ segmentator/future/arcweld_mprage.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/segmentator/future/arcweld_mp2rage.py b/segmentator/future/arcweld_mp2rage.py index 2029fc1c..b0e847ec 100644 --- a/segmentator/future/arcweld_mp2rage.py +++ b/segmentator/future/arcweld_mp2rage.py @@ -7,6 +7,22 @@ """ +# Part of the Segmentator library +# Copyright (C) 2017 Omer Faruk Gulban and Marian Schneider +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + import os import peakutils import numpy as np diff --git a/segmentator/future/arcweld_mprage.py b/segmentator/future/arcweld_mprage.py index c32c3d3f..9ebc7b33 100644 --- a/segmentator/future/arcweld_mprage.py +++ b/segmentator/future/arcweld_mprage.py @@ -7,6 +7,22 @@ """ +# Part of the Segmentator library +# Copyright (C) 2017 Omer Faruk Gulban and Marian Schneider +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + import os import peakutils import numpy as np From 738f687401f262ad792a4cbc1479c79bb2229042 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 9 Aug 2017 21:27:59 +0200 Subject: [PATCH 018/100] Readme update. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5379c17e..81204ce0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ Please visit [our wiki](https://github.com/ofgulban/segmentator/wiki/Installatio Please use [GitHub issues](https://github.com/ofgulban/segmentator/issues) for questions, bug reports or feature requests. ## License -The project is licensed under [GNU General Public License Version 3](http://www.gnu.org/licenses/gpl.html). +Copyright © 2016, [Omer Faruk Gulban](https://github.com/ofgulban) and [Marian Schneider](https://github.com/MSchnei). +Released under [GNU General Public License Version 3](http://www.gnu.org/licenses/gpl.html). ## References This application is based on the following work: From 120cba7673f527d4bdf3451caec34ae8594c2392 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 10 Aug 2017 13:53:47 +0200 Subject: [PATCH 019/100] work in progress... - 3D Scharr operator --- segmentator/tests/test_gradient_magnitude.py | 7 ++++++- segmentator/utils.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/segmentator/tests/test_gradient_magnitude.py b/segmentator/tests/test_gradient_magnitude.py index 5263d353..bdda79c6 100644 --- a/segmentator/tests/test_gradient_magnitude.py +++ b/segmentator/tests/test_gradient_magnitude.py @@ -9,7 +9,7 @@ from segmentator.utils import compute_gradient_magnitude # load -nii = load('/home/faruk/gdrive/Segmentator/data/faruk/arcweld/mprage_t1w_restore.nii.gz') +nii = load('/home/faruk/gdrive/Segmentator/data/faruk/gramag_test/mprage_S02_restore.nii.gz') ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] @@ -37,3 +37,8 @@ gra_mag = compute_gradient_magnitude(ima, method='scipy_prewitt') out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_scipy_prewitt.nii.gz') + +# calculate 3D Prewitt +gra_mag = compute_gradient_magnitude(ima, method='3D_scharr') +out = Nifti1Image(gra_mag, affine=nii.affine) +save(out, basename + '_3D_scharr.nii.gz') diff --git a/segmentator/utils.py b/segmentator/utils.py index 9ea8a4c7..44b6688d 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -215,6 +215,11 @@ def create_3D_kernel(operator='sobel'): [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]], dtype='float') + elif operator == 'scharr': + operator = np.array([[[9, 30, 9], [30, 100, 30], [9, 30, 9]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[-9, -30, -9], [-30, -100, -30], [-9, -30, -9]]], + dtype='float') # create permutations operator that will be used in gradient computation kernel = np.zeros([6, 3, 3, 3]) kernel[0, ...] = operator @@ -258,6 +263,14 @@ def compute_gradient_magnitude(ima, method='sobel'): # compute generic gradient magnitude with normalization gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/18. return gra_mag + elif method == '3D_scharr': + kernel = create_3D_kernel(operator='prewitt') + gra = np.zeros(ima.shape + (kernel.shape[0],)) + for d in range(kernel.shape[0]): + gra[..., d] = convolve(ima, kernel[d, ...]) + # compute generic gradient magnitude with normalization + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/256. + return gra_mag elif method == 'scipy_sobel': return generic_gradient_magnitude(ima, sobel)/32. elif method == 'scipy_prewitt': From 2cadaaf77ab32bbf49b862cfdd16d983432ca0a3 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 10 Aug 2017 17:32:08 +0200 Subject: [PATCH 020/100] 3D Scharr filter is added. - Default gradient magnitude computation is set to 3D_scharr. - Small improvements in gradient magnitude functions. - scipy filter options are removed. --- segmentator/__main__.py | 4 +-- segmentator/config.py | 6 ++-- segmentator/tests/test_gradient_magnitude.py | 26 ++++++------------ segmentator/utils.py | 29 ++++++++++---------- 4 files changed, 26 insertions(+), 39 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index a32cba1a..c18a8b1f 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -26,9 +26,9 @@ def main(args=None): help="Path to input. Mostly a nifti file with image data." ) parser.add_argument( - "--gramag", metavar='3D_sobel', required=False, + "--gramag", metavar='3D_scharr', required=False, default=config.gramag, - help="3D_sobel, '3D_prewitt', 'scipy_sobel', 'scipy_prewitt', 'numpy' \ + help="'3D_scharr', '3D_sobel', '3D_prewitt', 'numpy' \ or path to a gradient magnitude nifti." ) parser.add_argument( diff --git a/segmentator/config.py b/segmentator/config.py index b993075c..496c1ca4 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -11,15 +11,13 @@ # segmentator main command line variables filename = 'sample_filename_here' -gramag = '3D_sobel' +gramag = '3D_scharr' perc_min = 2.5 perc_max = 97.5 scale = 400 # possible gradient magnitude computation keyword options -gramag_options = ['3D_sobel', '3D_prewitt', 'scipy_sobel', 'scipy_prewitt', - 'numpy'] - +gramag_options = ['3D_scharr', '3D_sobel', '3D_prewitt', 'numpy'] # used in Deriche filter deriche_alpha = 2 diff --git a/segmentator/tests/test_gradient_magnitude.py b/segmentator/tests/test_gradient_magnitude.py index bdda79c6..60e19914 100644 --- a/segmentator/tests/test_gradient_magnitude.py +++ b/segmentator/tests/test_gradient_magnitude.py @@ -13,32 +13,22 @@ ima = nii.get_data() basename = nii.get_filename().split(os.extsep, 1)[0] -# calculate 3D Sobel +# 3D Scharr gradient magnitude +gra_mag = compute_gradient_magnitude(ima, method='3D_scharr') +out = Nifti1Image(gra_mag, affine=nii.affine) +save(out, basename + '_3D_scharr.nii.gz') + +# 3D Sobel gradient magnitude gra_mag = compute_gradient_magnitude(ima, method='3D_sobel') out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_3D_sobel.nii.gz') -# calculate 3D Prewitt +# 3D Prewitt gradient magnitude gra_mag = compute_gradient_magnitude(ima, method='3D_prewitt') out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_3D_prewitt.nii.gz') -# calculate numpy gradient magnitude +# numpy gradient magnitude gra_mag = compute_gradient_magnitude(ima, method='numpy') out = Nifti1Image(gra_mag, affine=nii.affine) save(out, basename + '_numpy_gradient.nii.gz') - -# calculate scipy sobel -gra_mag = compute_gradient_magnitude(ima, method='scipy_sobel') -out = Nifti1Image(gra_mag, affine=nii.affine) -save(out, basename + '_scipy_sobel.nii.gz') - -# calculate scipy prewitt -gra_mag = compute_gradient_magnitude(ima, method='scipy_prewitt') -out = Nifti1Image(gra_mag, affine=nii.affine) -save(out, basename + '_scipy_prewitt.nii.gz') - -# calculate 3D Prewitt -gra_mag = compute_gradient_magnitude(ima, method='3D_scharr') -out = Nifti1Image(gra_mag, affine=nii.affine) -save(out, basename + '_3D_scharr.nii.gz') diff --git a/segmentator/utils.py b/segmentator/utils.py index 44b6688d..98940473 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -192,7 +192,7 @@ def prep_2D_hist(ima, gra): return counts, volHistH, dataMin, dataMax, nrBins, binEdges -def create_3D_kernel(operator='sobel'): +def create_3D_kernel(operator='3D_sobel'): """Create various 3D kernels. Parameters @@ -205,21 +205,24 @@ def create_3D_kernel(operator='sobel'): kernel : np.ndarray, shape(6, n, n, 3) """ - if operator == 'sobel': + if operator == '3D_sobel': operator = np.array([[[1, 2, 1], [2, 4, 2], [1, 2, 1]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-1, -2, -1], [-2, -4, -2], [-1, -2, -1]]], dtype='float') - elif operator == 'prewitt': + elif operator == '3D_prewitt': operator = np.array([[[1, 1, 1], [1, 1, 1], [1, 1, 1]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]], dtype='float') - elif operator == 'scharr': + elif operator == '3D_scharr': operator = np.array([[[9, 30, 9], [30, 100, 30], [9, 30, 9]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-9, -30, -9], [-30, -100, -30], [-9, -30, -9]]], dtype='float') + scale_normalization_factor = np.sum(np.abs(operator)) + operator = np.divide(operator, scale_normalization_factor) + # create permutations operator that will be used in gradient computation kernel = np.zeros([6, 3, 3, 3]) kernel[0, ...] = operator @@ -231,7 +234,7 @@ def create_3D_kernel(operator='sobel'): return kernel -def compute_gradient_magnitude(ima, method='sobel'): +def compute_gradient_magnitude(ima, method='3D_scharr'): """Compute gradient magnitude of images. Parameters @@ -248,33 +251,29 @@ def compute_gradient_magnitude(ima, method='sobel'): derived from the first image """ if method == '3D_sobel': # magnitude scale is similar to numpy method - kernel = create_3D_kernel(operator='sobel') + kernel = create_3D_kernel(operator=method) gra = np.zeros(ima.shape + (kernel.shape[0],)) for d in range(kernel.shape[0]): gra[..., d] = convolve(ima, kernel[d, ...]) # compute generic gradient magnitude with normalization - gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/32. + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1)) return gra_mag elif method == '3D_prewitt': - kernel = create_3D_kernel(operator='prewitt') + kernel = create_3D_kernel(operator=method) gra = np.zeros(ima.shape + (kernel.shape[0],)) for d in range(kernel.shape[0]): gra[..., d] = convolve(ima, kernel[d, ...]) # compute generic gradient magnitude with normalization - gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/18. + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1)) return gra_mag elif method == '3D_scharr': - kernel = create_3D_kernel(operator='prewitt') + kernel = create_3D_kernel(operator=method) gra = np.zeros(ima.shape + (kernel.shape[0],)) for d in range(kernel.shape[0]): gra[..., d] = convolve(ima, kernel[d, ...]) # compute generic gradient magnitude with normalization - gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1))/256. + gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1)) return gra_mag - elif method == 'scipy_sobel': - return generic_gradient_magnitude(ima, sobel)/32. - elif method == 'scipy_prewitt': - return generic_gradient_magnitude(ima, prewitt)/18. elif method == 'numpy': gra = np.asarray(np.gradient(ima)) gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=0)) From 69c6d96e3d631a0f03e01615ca738daf9447f3e7 Mon Sep 17 00:00:00 2001 From: Ingo Marquardt Date: Wed, 30 Aug 2017 11:56:38 +0200 Subject: [PATCH 021/100] Indentation level adjusted. Docstring changes. --- segmentator/utils.py | 292 +++++++++++++++++++++---------------------- 1 file changed, 145 insertions(+), 147 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index 98940473..6a5593c4 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -312,150 +312,148 @@ def set_gradient_magnitude(image, gramag_option): def aniso_diff_3D(stack, niter=1, kappa=50, gamma=0.1, step=(1., 1., 1.), option=1, ploton=False): - """3D anisotropic diffusion based smoothing. - - Disclosure - ---------- - This script is adapted from a stackoverflow post by user ali_m: - [1] http://stackoverflow.com/questions/10802611/anisotropic-diffusion-2d-images - [2] http://pastebin.com/sBsPX4Y7 - - Parameters - ---------- - stack : 3d numpy array - Input stack/image/volume/data. - niter : int - Number of iterations - kappa : float - Conduction coefficient (20-100?). Controls conduction as a - function of gradient. If kappa is low small intensity - gradients are able to block conduction and hence diffusion - across step edges. A large value reduces the influence of - intensity gradients on conduction. - gamma : float - Controls speed of diffusion (you usually want it at a - maximum of 0.25 for stability). - step : tuple - The distance between adjacent pixels in (z,y,x). Step is - used to scale the gradients in case the spacing between - adjacent pixels differs in the x,y and/or z axes. - option : int, 1 or 2 - 1 favours high contrast edges over low contrast ones - (Perona & Malik [1] diffusion equation No 1). - 2 favours wide regions over smaller ones - (Perona & Malik [1] diffusion equation No 2). - ploton : bool - if True, the middle z-plane will be plotted on every - iteration/ - - Returns - ------- - stackout : 3d numpy array - Diffused stack/image/volume/data. - - Reference - --------- - [1] P. Perona and J. Malik. - Scale-space and edge detection using ansotropic diffusion. - IEEE Transactions on Pattern Analysis and Machine - Intelligence, 12(7):629-639, July 1990. - - Notes - ----- - Original MATLAB code by Peter Kovesi - School of Computer Science & Software Engineering, - The University of Western Australia - - - Translated to Python and optimised by Alistair Muldal - Department of Pharmacology, University of Oxford - - - June 2000 original version. - March 2002 corrected diffusion equation No 2. - July 2012 translated to Python - January 2017 docstring reorganization. - - """ - # ...you could always diffuse each color channel independently if you - # really want - if stack.ndim == 4: - warnings.warn("Only grayscale stacks allowed, converting to 3D \ - matrix") - stack = stack.mean(3) - - # initialize output array - stack = stack.astype('float32') - stackout = stack.copy() - - # initialize some internal variables - deltaS = np.zeros_like(stackout) - deltaE = deltaS.copy() - deltaD = deltaS.copy() - NS = deltaS.copy() - EW = deltaS.copy() - UD = deltaS.copy() - gS = np.ones_like(stackout) - gE = gS.copy() - gD = gS.copy() - - # create the plot figure, if requested - if ploton: - import pylab as pl - - showplane = stack.shape[0]//2 - - fig = pl.figure(figsize=(20, 5.5), num="Anisotropic diffusion") - ax1, ax2 = fig.add_subplot(1, 2, 1), fig.add_subplot(1, 2, 2) - - ax1.imshow(stack[showplane, ...].squeeze(), - interpolation='nearest') - ih = ax2.imshow(stackout[showplane, ...].squeeze(), - interpolation='nearest', animated=True) - ax1.set_title("Original stack (Z = %i)" % showplane) - ax2.set_title("Iteration 0") - - fig.canvas.draw() - - for ii in xrange(niter): - - # calculate the diffs - deltaD[:-1, :, :] = np.diff(stackout, axis=0) - deltaS[:, :-1, :] = np.diff(stackout, axis=1) - deltaE[:, :, :-1] = np.diff(stackout, axis=2) - - # conduction gradients (only need to compute one per dim!) - if option == 1: - gD = np.exp(-(deltaD/kappa)**2.)/step[0] - gS = np.exp(-(deltaS/kappa)**2.)/step[1] - gE = np.exp(-(deltaE/kappa)**2.)/step[2] - elif option == 2: - gD = 1./(1.+(deltaD/kappa)**2.)/step[0] - gS = 1./(1.+(deltaS/kappa)**2.)/step[1] - gE = 1./(1.+(deltaE/kappa)**2.)/step[2] - - # update matrices - D = gD*deltaD - E = gE*deltaE - S = gS*deltaS - - # subtract a copy that has been shifted 'Up/North/West' by one - # pixel. don't as questions. just do it. trust me. - UD[:] = D - NS[:] = S - EW[:] = E - UD[1:, :, :] -= D[:-1, :, :] - NS[:, 1:, :] -= S[:, :-1, :] - EW[:, :, 1:] -= E[:, :, :-1] - - # update the image - stackout += gamma*(UD+NS+EW) - - if ploton: - iterstring = "Iteration %i" % (ii+1) - ih.set_data(stackout[showplane, ...].squeeze()) - ax2.set_title(iterstring) - fig.canvas.draw() - # sleep(0.01) - - return stackout + """3D anisotropic diffusion based smoothing. + + Disclosure + ---------- + This script is adapted from a stackoverflow post by user ali_m: + [1] http://stackoverflow.com/questions/10802611/anisotropic-diffusion-2d-images #noqa + [2] http://pastebin.com/sBsPX4Y7 + + Parameters + ---------- + stack : 3d numpy array + Input stack/image/volume/data. + niter : int + Number of iterations + kappa : float + Conduction coefficient (20-100?). Controls conduction as a function of + gradient. If kappa is low small intensity gradients are able to block + conduction and hence diffusion across step edges. A large value reduces + the influence of intensity gradients on conduction. + gamma : float + Controls speed of diffusion (you usually want it at a maximum of 0.25 + for stability). + step : tuple + The distance between adjacent pixels in (z,y,x). Step is used to scale + the gradients in case the spacing between adjacent pixels differs in + the x,y and/or z axes. + option : int, 1 or 2 + 1 favours high contrast edges over low contrast ones (Perona & Malik + [1] diffusion equation No 1). + 2 favours wide regions over smaller ones (Perona & Malik [1] diffusion + equation No 2). + ploton : bool + If True, the middle z-plane will be plotted on every iteration. + + Returns + ------- + stackout : 3d numpy array + Diffused stack/image/volume/data. + + Reference + --------- + [1] P. Perona and J. Malik. + Scale-space and edge detection using ansotropic diffusion. + IEEE Transactions on Pattern Analysis and Machine + Intelligence, 12(7):629-639, July 1990. + + Notes + ----- + Original MATLAB code by Peter Kovesi + School of Computer Science & Software Engineering, + The University of Western Australia + + + Translated to Python and optimised by Alistair Muldal + Department of Pharmacology, University of Oxford + + + June 2000 original version. + March 2002 corrected diffusion equation No 2. + July 2012 translated to Python + January 2017 docstring reorganization. + + """ + # ...you could always diffuse each color channel independently if you + # really want + if stack.ndim == 4: + warnings.warn("Only grayscale stacks allowed, converting to 3D \ + matrix") + stack = stack.mean(3) + + # initialize output array + stack = stack.astype('float32') + stackout = stack.copy() + + # initialize some internal variables + deltaS = np.zeros_like(stackout) + deltaE = deltaS.copy() + deltaD = deltaS.copy() + NS = deltaS.copy() + EW = deltaS.copy() + UD = deltaS.copy() + gS = np.ones_like(stackout) + gE = gS.copy() + gD = gS.copy() + + # create the plot figure, if requested + if ploton: + import pylab as pl + + showplane = stack.shape[0]//2 + + fig = pl.figure(figsize=(20, 5.5), num="Anisotropic diffusion") + ax1, ax2 = fig.add_subplot(1, 2, 1), fig.add_subplot(1, 2, 2) + + ax1.imshow(stack[showplane, ...].squeeze(), + interpolation='nearest') + ih = ax2.imshow(stackout[showplane, ...].squeeze(), + interpolation='nearest', animated=True) + ax1.set_title("Original stack (Z = %i)" % showplane) + ax2.set_title("Iteration 0") + + fig.canvas.draw() + + for ii in xrange(niter): + + # calculate the diffs + deltaD[:-1, :, :] = np.diff(stackout, axis=0) + deltaS[:, :-1, :] = np.diff(stackout, axis=1) + deltaE[:, :, :-1] = np.diff(stackout, axis=2) + + # conduction gradients (only need to compute one per dim!) + if option == 1: + gD = np.exp(-(deltaD/kappa)**2.)/step[0] + gS = np.exp(-(deltaS/kappa)**2.)/step[1] + gE = np.exp(-(deltaE/kappa)**2.)/step[2] + elif option == 2: + gD = 1./(1.+(deltaD/kappa)**2.)/step[0] + gS = 1./(1.+(deltaS/kappa)**2.)/step[1] + gE = 1./(1.+(deltaE/kappa)**2.)/step[2] + + # update matrices + D = gD*deltaD + E = gE*deltaE + S = gS*deltaS + + # subtract a copy that has been shifted 'Up/North/West' by one + # pixel. don't as questions. just do it. trust me. + UD[:] = D + NS[:] = S + EW[:] = E + UD[1:, :, :] -= D[:-1, :, :] + NS[:, 1:, :] -= S[:, :-1, :] + EW[:, :, 1:] -= E[:, :, :-1] + + # update the image + stackout += gamma*(UD+NS+EW) + + if ploton: + iterstring = "Iteration %i" % (ii+1) + ih.set_data(stackout[showplane, ...].squeeze()) + ax2.set_title(iterstring) + fig.canvas.draw() + # sleep(0.01) + + return stackout From 28c6cf7da86462da01e8d7acf583d11a328544bb Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 8 Sep 2017 11:01:56 +0200 Subject: [PATCH 022/100] Archived Deriche for better future integration. It created a setup error for a user with windows pc. I have commented out the related line in setup file, we are not using this feature for now anyway. --- segmentator/{ => future}/deriche.py | 0 segmentator/{ => future}/deriche_3D.pyx | 0 setup.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename segmentator/{ => future}/deriche.py (100%) rename segmentator/{ => future}/deriche_3D.pyx (100%) diff --git a/segmentator/deriche.py b/segmentator/future/deriche.py similarity index 100% rename from segmentator/deriche.py rename to segmentator/future/deriche.py diff --git a/segmentator/deriche_3D.pyx b/segmentator/future/deriche_3D.pyx similarity index 100% rename from segmentator/deriche_3D.pyx rename to segmentator/future/deriche_3D.pyx diff --git a/setup.py b/setup.py index f66c6052..195977e3 100644 --- a/setup.py +++ b/setup.py @@ -24,5 +24,5 @@ 'console_scripts': [ 'segmentator = segmentator.__main__:main', ]}, - ext_modules=cythonize("segmentator/deriche_3D.pyx") + #ext_modules=cythonize("segmentator/future/deriche_3D.pyx") # not used for now ) From 10a2d0a00da8d7eb8730e43d4682535c7de67a19 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 19 Sep 2017 19:20:07 +0200 Subject: [PATCH 023/100] Handling NaNs in niftis in truncation. --- segmentator/utils.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index 6a5593c4..0192b6ca 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -22,7 +22,7 @@ import matplotlib.pyplot as plt import config as cfg from nibabel import load -from scipy.ndimage import sobel, prewitt, convolve, generic_gradient_magnitude +from scipy.ndimage import convolve def sub2ind(array_shape, rows, cols): @@ -115,12 +115,14 @@ def truncate_range(data, percMin=0.25, percMax=99.75, discard_zeros=True): """ if discard_zeros: msk = data != 0 + pMin, pMax = np.nanpercentile(data[msk], [percMin, percMax]) else: - msk = np.ones(data.shape) - percDataMin, percDataMax = np.percentile(data[msk], [percMin, percMax]) - data[data < percDataMin] = percDataMin # adjust minimum - data[data > percDataMax] = percDataMax # adjust maximum - data[~msk] = 0 # put back masked out voxels + pMin, pMax = np.nanpercentile(data, [percMin, percMax]) + temp = data[~np.isnan(data)] + temp[temp < pMin], temp[temp > pMax] = pMin, pMax # truncate min and max + data[~np.isnan(data)] = temp + if discard_zeros: + data[~msk] = 0 # put back masked out voxels return data From 322d508c208a28e3e423a5f13109120ba8b204e3 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 16:55:16 +0200 Subject: [PATCH 024/100] Truncation and scaling updates. NaNs and discarding zeroes handling improved. --- segmentator/utils.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index 6a5593c4..312e8134 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -115,12 +115,14 @@ def truncate_range(data, percMin=0.25, percMax=99.75, discard_zeros=True): """ if discard_zeros: msk = data != 0 + pMin, pMax = np.nanpercentile(data[msk], [percMin, percMax]) else: - msk = np.ones(data.shape) - percDataMin, percDataMax = np.percentile(data[msk], [percMin, percMax]) - data[data < percDataMin] = percDataMin # adjust minimum - data[data > percDataMax] = percDataMax # adjust maximum - data[~msk] = 0 # put back masked out voxels + pMin, pMax = np.nanpercentile(data, [percMin, percMax]) + temp = data[~np.isnan(data)] + temp[temp < pMin], temp[temp > pMax] = pMin, pMax # truncate min and max + data[~np.isnan(data)] = temp + if discard_zeros: + data[~msk] = 0 # put back masked out voxels return data @@ -149,10 +151,12 @@ def scale_range(data, scale_factor=500, delta=0, discard_zeros=True): if discard_zeros: msk = data != 0 else: - msk = np.ones(data.shape) + msk = np.ones(data.shape, dtype=bool) scale_factor = scale_factor - delta - data[msk] = data[msk] - data[msk].min() - data[msk] = scale_factor / data[msk].max() * data[msk] + data[msk] = data[msk] - np.nanmin(data[msk]) + data[msk] = scale_factor / np.nanmax(data[msk]) * data[msk] + if discard_zeros: + data[~msk] = 0 # put back masked out voxels return data From ea3ddd5594f2b056904473672f33ca6b59ff0463 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:01:46 +0200 Subject: [PATCH 025/100] First tests are added. The old sctipts with test_ prefix are renamed with wip_ prefix. --- segmentator/tests/test_utils.py | 34 +++++++++++++++++++ .../{test_arcweld.py => wip_test_arcweld.py} | 0 ...tude.py => wip_test_gradient_magnitude.py} | 0 3 files changed, 34 insertions(+) create mode 100644 segmentator/tests/test_utils.py rename segmentator/tests/{test_arcweld.py => wip_test_arcweld.py} (100%) rename segmentator/tests/{test_gradient_magnitude.py => wip_test_gradient_magnitude.py} (100%) diff --git a/segmentator/tests/test_utils.py b/segmentator/tests/test_utils.py new file mode 100644 index 00000000..aa73c71b --- /dev/null +++ b/segmentator/tests/test_utils.py @@ -0,0 +1,34 @@ +"""Test utility functions.""" + +import numpy as np +from tetrahydra.utils import truncate_range, scale_range + + +def test_truncate_range(): + """Test range truncation.""" + # Given + data = np.random.random(100) + data.ravel()[np.random.choice(data.size, 10, replace=False)] = 0 + data.ravel()[np.random.choice(data.size, 5, replace=False)] = np.nan + p_min, p_max = 2.5, 97.5 + expected = np.nanpercentile(data, [p_min, p_max]) + # When + output = truncate_range(data, percMin=p_min, percMax=p_max, + discard_zeros=False) + # Then + assert all(np.nanpercentile(output, [0, 100]) == expected) + + +def test_scale_range(): + """Test range scaling.""" + # Given + data = np.random.random(100) - 0.5 + data.ravel()[np.random.choice(data.size, 10, replace=False)] = 0. + data.ravel()[np.random.choice(data.size, 5, replace=False)] = np.nan + s = 42. # scaling factor + expected = [0., s] # min and max + # When + output = scale_range(data, scale_factor=s, delta=0.01, discard_zeros=False) + # Then + assert all([np.nanmin(output) >= expected[0], + np.nanmax(output) < expected[1]]) diff --git a/segmentator/tests/test_arcweld.py b/segmentator/tests/wip_test_arcweld.py similarity index 100% rename from segmentator/tests/test_arcweld.py rename to segmentator/tests/wip_test_arcweld.py diff --git a/segmentator/tests/test_gradient_magnitude.py b/segmentator/tests/wip_test_gradient_magnitude.py similarity index 100% rename from segmentator/tests/test_gradient_magnitude.py rename to segmentator/tests/wip_test_gradient_magnitude.py From b13689b56970bd44c7a6ecd8a655a6e04eeb9f93 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:02:24 +0200 Subject: [PATCH 026/100] Travis and Codecov integration added. --- .coveragerc | 7 +++++++ .travis.yml | 11 +++++++++++ requirements.txt | 5 +++++ 3 files changed, 23 insertions(+) create mode 100644 .coveragerc create mode 100644 .travis.yml create mode 100644 requirements.txt diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..e92e49d2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +source = segmentator + +omit = + *__init__* + *tests/* + segmentator/future/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..afa3871d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +sudo: false +language: python +python: + - 2.7 +install: # command to install dependencies + - pip install -r requirements.txt + - python setup.py develop +script: # command to run tests + - py.test --cov=./segmentator +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..b6263eae --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +numpy==1.11.1 +scipy==0.18.1 +matplotlib=1.5.1 +nibabel=2.0.2 +pytest-cov==2.5.1 From acbb11ca0bab797f42ae3acb781aef96d1aee0f1 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:05:36 +0200 Subject: [PATCH 027/100] Deriche filter related lines are removed. Curently there is no plan to use Deriche filter so it is better if related lines are not cluttering. This feature might be revisited in the future. --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 195977e3..264bc88e 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,6 @@ except ImportError: from distutils.core import setup -from Cython.Build import cythonize # required for deriche filter - setup(name='segmentator', version='1.2.0', description=('Multi-dimensional data exploration and segmentation for 3D \ @@ -24,5 +22,4 @@ 'console_scripts': [ 'segmentator = segmentator.__main__:main', ]}, - #ext_modules=cythonize("segmentator/future/deriche_3D.pyx") # not used for now ) From 16a5e01105f932cbbf7e0a85562edfffe27e9e6e Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:06:09 +0200 Subject: [PATCH 028/100] Version bump. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 264bc88e..b84c0b74 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from distutils.core import setup setup(name='segmentator', - version='1.2.0', + version='1.3.0', description=('Multi-dimensional data exploration and segmentation for 3D \ images.'), url='https://github.com/ofgulban/segmentator', From e2596fcb948125430096f665b302ba6adfe8a7b1 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:07:04 +0200 Subject: [PATCH 029/100] cython requirement dropped. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b84c0b74..5a02db24 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ author_email='faruk.gulban@maastrichtuniversity.nl', license='GNU General Public License Version 3', packages=['segmentator'], - install_requires=['numpy', 'matplotlib', 'scipy', 'cython'], + install_requires=['numpy', 'matplotlib', 'scipy'], keywords=['mri', 'segmentation'], zip_safe=True, entry_points={ From 37e4fdbcda324d16bf09cf34383fc7632b0b1b78 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:10:38 +0200 Subject: [PATCH 030/100] Small fix. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b6263eae..35cc2f58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.11.1 scipy==0.18.1 matplotlib=1.5.1 -nibabel=2.0.2 +nibabel==2.0.2 pytest-cov==2.5.1 From afb4c6f6d4f7559da5e888dc62a4b0744272ac9c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:21:03 +0200 Subject: [PATCH 031/100] I always forget == in requirements :) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 35cc2f58..0a2c2dfb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.11.1 scipy==0.18.1 -matplotlib=1.5.1 +matplotlib==1.5.1 nibabel==2.0.2 pytest-cov==2.5.1 From ba99fc65ae4a671ff70c53919f546273f67f11a7 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:21:37 +0200 Subject: [PATCH 032/100] osx added. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index afa3871d..40f410f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ sudo: false +os: + - linux + - osx language: python python: - 2.7 From dda112ba20d8fff6ace58cb919af2868399e5f0c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:21:59 +0200 Subject: [PATCH 033/100] appveyor added to cover Windows. --- .appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..893bbb0c --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,3 @@ +version: 1.0.{build} +build: + verbosity: minimal \ No newline at end of file From 571b46c61459c2251fedc5575cd2f20dc0e5d006 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:29:21 +0200 Subject: [PATCH 034/100] Trying to figure out appveyor stuff. --- .appveyor.yml | 85 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 893bbb0c..3175fd19 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,3 +1,82 @@ -version: 1.0.{build} -build: - verbosity: minimal \ No newline at end of file +environment: + global: + # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the + # /E:ON and /V:ON options are not enabled in the batch script intepreter + # See: http://stackoverflow.com/a/13751649/163740 + CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd" + + matrix: + + # Pre-installed Python versions, which Appveyor may upgrade to + # a later point release. + # See: http://www.appveyor.com/docs/installed-software#python + + - PYTHON: "C:\\Python27" + PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_ARCH: "32" + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_ARCH: "64" + +install: + # If there is a newer build queued for the same PR, cancel this one. + # The AppVeyor 'rollout builds' option is supposed to serve the same + # purpose but it is problematic because it tends to cancel builds pushed + # directly to master instead of just PR builds (or the converse). + # credits: JuliaLang developers. + - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + throw "There are newer queued builds for this pull request, failing early." } + - ECHO "Filesystem root:" + - ps: "ls \"C:/\"" + + - ECHO "Installed SDKs:" + - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" + + # Install Python (from the official .msi of http://python.org) and pip when + # not already installed. + - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 } + + # Prepend newly installed Python to the PATH of this build (this cannot be + # done from inside the powershell script as it would require to restart + # the parent CMD process). + - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + + # Check that we have the expected version and architecture for Python + - "python --version" + - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + + # Upgrade to the latest version of pip to avoid it displaying warnings + # about it being out of date. + - "pip install --disable-pip-version-check --user --upgrade pip" + + # Install the build dependencies of the project. If some dependencies contain + # compiled extensions and are not provided as pre-built wheel packages, + # pip will build them from source using the MSVC compiler matching the + # target Python version and architecture + - "%CMD_IN_ENV% pip install -r dev-requirements.txt" + +build_script: + # Build the compiled extension + - "%CMD_IN_ENV% python setup.py build" + +test_script: + # Run the project tests + - "%CMD_IN_ENV% python setup.py nosetests" + +after_test: + # If tests are successful, create binary packages for the project. + - "%CMD_IN_ENV% python setup.py bdist_wheel" + - "%CMD_IN_ENV% python setup.py bdist_wininst" + - "%CMD_IN_ENV% python setup.py bdist_msi" + - ps: "ls dist" + +artifacts: + # Archive the generated packages in the ci.appveyor.com build report. + - path: dist\* + +#on_success: +# - TODO: upload the content of dist/*.whl to a public wheelhouse +# From ad045a5f70109e0a1da604c5cb8d60a419ef1eb3 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:45:23 +0200 Subject: [PATCH 035/100] Small fix. --- segmentator/tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segmentator/tests/test_utils.py b/segmentator/tests/test_utils.py index aa73c71b..16731cb0 100644 --- a/segmentator/tests/test_utils.py +++ b/segmentator/tests/test_utils.py @@ -1,7 +1,7 @@ """Test utility functions.""" import numpy as np -from tetrahydra.utils import truncate_range, scale_range +from segmentator.utils import truncate_range, scale_range def test_truncate_range(): From f586d2ec545d55eb8c3be48d82973dfbc4aabe0a Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:55:13 +0200 Subject: [PATCH 036/100] Appveyor stuff. --- .appveyor.yml | 93 +++++++++++---------------------------------------- 1 file changed, 20 insertions(+), 73 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3175fd19..4dc09f2a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,82 +1,29 @@ -environment: - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script intepreter - # See: http://stackoverflow.com/a/13751649/163740 - CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd" +build: false - matrix: +matrix: + fast_finish: true - # Pre-installed Python versions, which Appveyor may upgrade to - # a later point release. - # See: http://www.appveyor.com/docs/installed-software#python +platform: + - x64 - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.x" # currently 2.7.9 - PYTHON_ARCH: "32" +environment: + matrix: + - PYTHON_VERSION: 2.7 + MINICONDA_DIRNAME: C:\Miniconda2-x64 - - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.x" # currently 2.7.9 - PYTHON_ARCH: "64" +init: + - ECHO %PYTHON_VERSION% %MINICONDA_DIRNAME% install: - # If there is a newer build queued for the same PR, cancel this one. - # The AppVeyor 'rollout builds' option is supposed to serve the same - # purpose but it is problematic because it tends to cancel builds pushed - # directly to master instead of just PR builds (or the converse). - # credits: JuliaLang developers. - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } - - ECHO "Filesystem root:" - - ps: "ls \"C:/\"" - - - ECHO "Installed SDKs:" - - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" - - # Install Python (from the official .msi of http://python.org) and pip when - # not already installed. - - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 } - - # Prepend newly installed Python to the PATH of this build (this cannot be - # done from inside the powershell script as it would require to restart - # the parent CMD process). - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + - cmd: set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%" - # Check that we have the expected version and architecture for Python - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - # Upgrade to the latest version of pip to avoid it displaying warnings - # about it being out of date. - - "pip install --disable-pip-version-check --user --upgrade pip" - - # Install the build dependencies of the project. If some dependencies contain - # compiled extensions and are not provided as pre-built wheel packages, - # pip will build them from source using the MSVC compiler matching the - # target Python version and architecture - - "%CMD_IN_ENV% pip install -r dev-requirements.txt" - -build_script: - # Build the compiled extension - - "%CMD_IN_ENV% python setup.py build" + - cmd: conda config --set always_yes true + - cmd: conda update --quiet conda + - cmd: conda info --all + - cmd: conda create --quiet --name conda-env-%PYTHON_VERSION% python=%PYTHON_VERSION% --file requirements-conda.txt + - cmd: activate conda-env-%PYTHON_VERSION% + - cmd: python setup.py develop test_script: - # Run the project tests - - "%CMD_IN_ENV% python setup.py nosetests" - -after_test: - # If tests are successful, create binary packages for the project. - - "%CMD_IN_ENV% python setup.py bdist_wheel" - - "%CMD_IN_ENV% python setup.py bdist_wininst" - - "%CMD_IN_ENV% python setup.py bdist_msi" - - ps: "ls dist" - -artifacts: - # Archive the generated packages in the ci.appveyor.com build report. - - path: dist\* - -#on_success: -# - TODO: upload the content of dist/*.whl to a public wheelhouse -# + - ps: | + py.test --cov=./segmentator From 80b0c93e93131dcf41ab0b2ce0600dc5fbfc2387 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 17:59:36 +0200 Subject: [PATCH 037/100] Appveyor stuff... --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4dc09f2a..a2f2866a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,7 +9,7 @@ platform: environment: matrix: - PYTHON_VERSION: 2.7 - MINICONDA_DIRNAME: C:\Miniconda2-x64 + MINICONDA_DIRNAME: C:\Miniconda27-x64 init: - ECHO %PYTHON_VERSION% %MINICONDA_DIRNAME% From 7a56bcec4e64b7d9b0d061af6b170470461590b8 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:16:35 +0200 Subject: [PATCH 038/100] Appveyor stuff... --- .appveyor.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index a2f2866a..3f88c001 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,20 +9,25 @@ platform: environment: matrix: - PYTHON_VERSION: 2.7 - MINICONDA_DIRNAME: C:\Miniconda27-x64 + MINICONDA_DIRNAME: C:\\Miniconda-x64 init: - ECHO %PYTHON_VERSION% %MINICONDA_DIRNAME% install: - - cmd: set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%" - - - cmd: conda config --set always_yes true - - cmd: conda update --quiet conda - - cmd: conda info --all - - cmd: conda create --quiet --name conda-env-%PYTHON_VERSION% python=%PYTHON_VERSION% --file requirements-conda.txt - - cmd: activate conda-env-%PYTHON_VERSION% - - cmd: python setup.py develop + # Prepend chosen Python to the PATH of this build + - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + # Check that we have the expected version and architecture for Python + - "python --version" + - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + # Install the conda supplied packages + - "conda update -y conda" + - "conda install -y pip" + # install ase into the current python + - "echo %cd%" + - "where pip" + - "pip install -r requirements.txt" + - "python setup.py develop" test_script: - ps: | From 1ddd2a72cdd8e3bf4532b10d18f919b544d3753c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:17:25 +0200 Subject: [PATCH 039/100] Omittins some files. --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index e92e49d2..9d4b8830 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,5 +3,7 @@ source = segmentator omit = *__init__* + *__main__* + *config* *tests/* segmentator/future/* From 40c6cc520ff2197b54f3d0b3c06af22f1b553e42 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:19:18 +0200 Subject: [PATCH 040/100] requirement versions updated. --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0a2c2dfb..9118e155 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -numpy==1.11.1 -scipy==0.18.1 -matplotlib==1.5.1 +numpy==1.13.1 +scipy==0.19.1 +matplotlib==2.0.2 nibabel==2.0.2 pytest-cov==2.5.1 From b8e56b2987056a2200a0bf662cda6d231cdec204 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:33:42 +0200 Subject: [PATCH 041/100] Appveyor stuff... --- .appveyor.yml | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3f88c001..e3843754 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,27 +8,18 @@ platform: environment: matrix: - - PYTHON_VERSION: 2.7 - MINICONDA_DIRNAME: C:\\Miniconda-x64 + - PYTHON: "C:\\Python27" + PYTHON_VERSION: 2.7 + PYTHON_ARCH: 64 init: - - ECHO %PYTHON_VERSION% %MINICONDA_DIRNAME% + - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% install: - # Prepend chosen Python to the PATH of this build - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - # Check that we have the expected version and architecture for Python - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - # Install the conda supplied packages - - "conda update -y conda" - - "conda install -y pip" - # install ase into the current python - - "echo %cd%" - - "where pip" + - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% + - "python -m pip install -U pip" - "pip install -r requirements.txt" - "python setup.py develop" test_script: - - ps: | - py.test --cov=./segmentator + - py.test --cov=./segmentator From 5bcef860d4cee59f50ec1009c2ac7fc2e775704a Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:36:04 +0200 Subject: [PATCH 042/100] Failures allowed for osx. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 40f410f8..f21c97d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ sudo: false os: - linux - osx +matrix: + allow_failures: + - os: osx language: python python: - 2.7 From 3e37d3d0ece4cf918e825c983b71cbc2202e0432 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:38:59 +0200 Subject: [PATCH 043/100] Appveyor stuff... --- .appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index e3843754..666bd13f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,9 +8,9 @@ platform: environment: matrix: - - PYTHON: "C:\\Python27" - PYTHON_VERSION: 2.7 - PYTHON_ARCH: 64 + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_ARCH: "64" init: - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% From 04b4a4086371901deb0a8122a92e750b7e6e0061 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:50:00 +0200 Subject: [PATCH 044/100] new badges --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 81204ce0..6bf560cd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Segmentator +[![Build Status](https://travis-ci.org/ofgulban/segmentator.svg?branch=master)](https://travis-ci.org/ofgulban/segmentator) [![Build status](https://ci.appveyor.com/api/projects/status/lkxp4y5ahssqv6ng?svg=true)](https://ci.appveyor.com/project/ofgulban/segmentator) [![codecov](https://codecov.io/gh/ofgulban/segmentator/branch/master/graph/badge.svg)](https://codecov.io/gh/ofgulban/segmentator) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/segmentator/Lobby) + [![DOI](https://zenodo.org/badge/59303623.svg)](https://zenodo.org/badge/latestdoi/59303623) -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/segmentator/Lobby) From 1634dce55765c78b9f628a933b61132f1c3fa5c0 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:54:27 +0200 Subject: [PATCH 045/100] Appveyor stuff... --- .appveyor.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 666bd13f..a1446c08 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,18 +8,19 @@ platform: environment: matrix: - - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.x" # currently 2.7.9 - PYTHON_ARCH: "64" + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" # currently 2.7.9 + PYTHON_ARCH: "64" + +init: + - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% -init: - - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% - -install: +install: - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - - "python -m pip install -U pip" - - "pip install -r requirements.txt" + - "python -m pip install -U pip" + - "pip install msvc_runtime" + - "pip install -r requirements.txt" - "python setup.py develop" test_script: - - py.test --cov=./segmentator + - py.test --cov=./segmentator From b3f803e996f7f45bc96501c4803b192285f86de9 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 18:57:30 +0200 Subject: [PATCH 046/100] Appveyor stuff... --- .appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index a1446c08..34a09a7f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,3 +1,5 @@ +image: Visual Studio 2017 + build: false matrix: @@ -18,7 +20,6 @@ init: install: - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - "python -m pip install -U pip" - - "pip install msvc_runtime" - "pip install -r requirements.txt" - "python setup.py develop" From b799148f2a76f315097d29a745cac7c1816d566c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:00:34 +0200 Subject: [PATCH 047/100] Appveyor stuff... --- .appveyor.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 34a09a7f..93b8aef8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,9 +1,4 @@ -image: Visual Studio 2017 - -build: false - -matrix: - fast_finish: true +image: Visual Studio 2015 platform: - x64 From 8c1ace51f558f82f74215651756c95eee6a70256 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:07:09 +0200 Subject: [PATCH 048/100] Appveyor stuff... --- .appveyor.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 93b8aef8..1c55bb6c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,17 +5,15 @@ platform: environment: matrix: - - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.x" # currently 2.7.9 - PYTHON_ARCH: "64" + - PYTHON: "C:\\Python27" + PYTHON_VERSION: "2.7.8" + PYTHON_ARCH: "32" init: - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% install: - - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - - "python -m pip install -U pip" - - "pip install -r requirements.txt" + - "%PYTHON%/Scripts/pip.exe install -r requirements.txt" - "python setup.py develop" test_script: From b89aa6defcda022a45dd63144590d0fad515b673 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:14:03 +0200 Subject: [PATCH 049/100] Appveyor stuff. --- .appveyor.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1c55bb6c..5d1bd9b9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,20 +1,20 @@ -image: Visual Studio 2015 - -platform: - - x64 +build: false environment: matrix: - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.8" - PYTHON_ARCH: "32" - -init: - - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% + - PYTHON_VERSION: 2.7 + MINICONDA: C:\Miniconda + +init: + - "ECHO %PYTHON_VERSION% %MINICONDA%" -install: - - "%PYTHON%/Scripts/pip.exe install -r requirements.txt" - - "python setup.py develop" +install: + - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%" + - conda config --set always_yes yes --set changeps1 no + - conda update -q conda + - conda info -a + - "conda create -q -n test-environment python=%PYTHON_VERSION% --file requirements.txt" + - activate test-environment test_script: - - py.test --cov=./segmentator + - py.test --cov=./segmentator From 43531bc42ec25b62b68c2bcccff0d12ee258bb99 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:28:28 +0200 Subject: [PATCH 050/100] Nibabel version update. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9118e155..243ff523 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.13.1 scipy==0.19.1 matplotlib==2.0.2 -nibabel==2.0.2 +nibabel==2.1.0 pytest-cov==2.5.1 From ac9d958e46306314cffb58ee937b62656057ee13 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:31:04 +0200 Subject: [PATCH 051/100] conda-forge added to appveyor installation. --- .appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor.yml b/.appveyor.yml index 5d1bd9b9..8d03bafe 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -12,6 +12,7 @@ install: - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%" - conda config --set always_yes yes --set changeps1 no - conda update -q conda + - conda config --add channels conda-forge - conda info -a - "conda create -q -n test-environment python=%PYTHON_VERSION% --file requirements.txt" - activate test-environment From 6a28794e4550a54b4c9c9e68db49cfe147e1c651 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:36:12 +0200 Subject: [PATCH 052/100] Appveyor stuff... --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 8d03bafe..ac9428af 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -18,4 +18,4 @@ install: - activate test-environment test_script: - - py.test --cov=./segmentator + - "py.test --cov=C:\\projects\\segmentator" From 1df7b926715bc69c6e64d8638fad12b7426303a0 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:39:42 +0200 Subject: [PATCH 053/100] Appveyor stuff... --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index ac9428af..aa1cd6be 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -18,4 +18,4 @@ install: - activate test-environment test_script: - - "py.test --cov=C:\\projects\\segmentator" + - py.test From 84573ec9fb0d93194f51870735c1443cf1a60a07 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 19:45:45 +0200 Subject: [PATCH 054/100] Appveyor stuff... --- .appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor.yml b/.appveyor.yml index aa1cd6be..74891dd7 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,6 +16,7 @@ install: - conda info -a - "conda create -q -n test-environment python=%PYTHON_VERSION% --file requirements.txt" - activate test-environment + - python setup.py develop test_script: - py.test From 6c255a1246d3566a02dff44795cdf114870cb4cd Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 21:09:47 +0200 Subject: [PATCH 055/100] Readme update. --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6bf560cd..5f946ab7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# Segmentator - -[![Build Status](https://travis-ci.org/ofgulban/segmentator.svg?branch=master)](https://travis-ci.org/ofgulban/segmentator) [![Build status](https://ci.appveyor.com/api/projects/status/lkxp4y5ahssqv6ng?svg=true)](https://ci.appveyor.com/project/ofgulban/segmentator) [![codecov](https://codecov.io/gh/ofgulban/segmentator/branch/master/graph/badge.svg)](https://codecov.io/gh/ofgulban/segmentator) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/segmentator/Lobby) +[![DOI](https://zenodo.org/badge/59303623.svg)](https://zenodo.org/badge/latestdoi/59303623) [![Build Status](https://travis-ci.org/ofgulban/segmentator.svg?branch=master)](https://travis-ci.org/ofgulban/segmentator) [![Build status](https://ci.appveyor.com/api/projects/status/lkxp4y5ahssqv6ng?svg=true)](https://ci.appveyor.com/project/ofgulban/segmentator) [![codecov](https://codecov.io/gh/ofgulban/segmentator/branch/master/graph/badge.svg)](https://codecov.io/gh/ofgulban/segmentator) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/segmentator/Lobby) -[![DOI](https://zenodo.org/badge/59303623.svg)](https://zenodo.org/badge/latestdoi/59303623) +# Segmentator @@ -16,10 +14,10 @@ The goal is to provide a complementary tool to the already available brain tissu | Package | Tested version | |--------------------------------------|----------------| -| [NumPy](http://www.numpy.org/) | 1.11.1 | +| [NumPy](http://www.numpy.org/) | 1.13.1 | | [matplotlib](http://matplotlib.org/) | 1.5.3 | | [NiBabel](http://nipy.org/nibabel/) | 2.1.0 | -| [SciPy](http://scipy.org/) | 0.19.0 | +| [SciPy](http://scipy.org/) | 0.19.1 | ## Installation & Quick Start Please visit [our wiki](https://github.com/ofgulban/segmentator/wiki/Installation) to see how to install and use Segmentator. From c2545ef3888896824863b85eb4e093e9bcce5e98 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Wed, 20 Sep 2017 21:16:30 +0200 Subject: [PATCH 056/100] Landscape code health badge added. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f946ab7..60e61de6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![DOI](https://zenodo.org/badge/59303623.svg)](https://zenodo.org/badge/latestdoi/59303623) [![Build Status](https://travis-ci.org/ofgulban/segmentator.svg?branch=master)](https://travis-ci.org/ofgulban/segmentator) [![Build status](https://ci.appveyor.com/api/projects/status/lkxp4y5ahssqv6ng?svg=true)](https://ci.appveyor.com/project/ofgulban/segmentator) [![codecov](https://codecov.io/gh/ofgulban/segmentator/branch/master/graph/badge.svg)](https://codecov.io/gh/ofgulban/segmentator) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/segmentator/Lobby) +[![DOI](https://zenodo.org/badge/59303623.svg)](https://zenodo.org/badge/latestdoi/59303623) [![Build Status](https://travis-ci.org/ofgulban/segmentator.svg?branch=master)](https://travis-ci.org/ofgulban/segmentator) [![Build status](https://ci.appveyor.com/api/projects/status/lkxp4y5ahssqv6ng?svg=true)](https://ci.appveyor.com/project/ofgulban/segmentator) [![codecov](https://codecov.io/gh/ofgulban/segmentator/branch/master/graph/badge.svg)](https://codecov.io/gh/ofgulban/segmentator) [![Code Health](https://landscape.io/github/ofgulban/segmentator/master/landscape.svg?style=flat)](https://landscape.io/github/ofgulban/segmentator/master) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/segmentator/Lobby) # Segmentator From f02cd0d87624c3ccedb01df4ee7d3ceb1cd30b99 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 16:21:05 +0200 Subject: [PATCH 057/100] environment.yml added. appveyor configuration is changed to work with this file. --- .appveyor.yml | 6 ++---- environment.yml | 11 +++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 environment.yml diff --git a/.appveyor.yml b/.appveyor.yml index 74891dd7..b8fed9c1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -12,10 +12,8 @@ install: - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%" - conda config --set always_yes yes --set changeps1 no - conda update -q conda - - conda config --add channels conda-forge - - conda info -a - - "conda create -q -n test-environment python=%PYTHON_VERSION% --file requirements.txt" - - activate test-environment + - "conda env create --force -f environment.yml python=2.7" + - activate segmentator - python setup.py develop test_script: diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..c8b06c24 --- /dev/null +++ b/environment.yml @@ -0,0 +1,11 @@ +name: segmentator +channels: +- conda-forge +dependencies: +- setuptools>=19.6* +- pytest>=3* +- pytest-cov>=2.5* +- numpy>=1.13* +- scipy>=0.19* +- matplotlib>=2.0* +- nibabel>=2.1* From 111678a7e269bc2f2510f659ee9bd4aaec7df7bb Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 17:36:23 +0200 Subject: [PATCH 058/100] Right click bug in ncut interface is fixed. --- segmentator/segmentator_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/segmentator/segmentator_functions.py b/segmentator/segmentator_functions.py index 9cd5bd31..eb0d8630 100755 --- a/segmentator/segmentator_functions.py +++ b/segmentator/segmentator_functions.py @@ -209,8 +209,8 @@ def on_press(self, event): return elif event.button == 3: # right button if event.inaxes == self.axes: # cursor in left plot (hist) - xbin = np.floor(event.xdata) - ybin = np.floor(event.ydata) + xbin = int(np.floor(event.xdata)) + ybin = int(np.floor(event.ydata)) val = self.volHistMask[ybin][xbin] # fetch the slider value to get label nr self.volHistMask[self.volHistMask == val] = \ From 6971d54ebb56996fcf5ed7310adb4998fda06e85 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 17:56:24 +0200 Subject: [PATCH 059/100] gitignore update --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f939ecc5..93f805de 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ target/ #Ipython Notebook .ipynb_checkpoints + +bin/ From 044e6cb4042bcc3da6dd938f7fc3b5095952dbb4 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 17:56:35 +0200 Subject: [PATCH 060/100] small note added --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5a02db24..096d8c3c 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,9 @@ -"""Segmentator setup.""" +"""Segmentator setup. + +To install for development, using the commandline do: + pip install -e /path/to/segmentator + +""" try: from setuptools import setup From b866808e1fef5b82b1222cb8761134209f43ef83 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 17:57:37 +0200 Subject: [PATCH 061/100] matplotlib>2.0 giving issues, sticking with 1.5.3. --- environment.yml | 3 ++- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index c8b06c24..8a56c738 100644 --- a/environment.yml +++ b/environment.yml @@ -1,11 +1,12 @@ name: segmentator channels: - conda-forge +- clinicalgraphics dependencies: - setuptools>=19.6* - pytest>=3* - pytest-cov>=2.5* - numpy>=1.13* - scipy>=0.19* -- matplotlib>=2.0* +- matplotlib=1.5* - nibabel>=2.1* diff --git a/requirements.txt b/requirements.txt index 243ff523..22ac27e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.13.1 scipy==0.19.1 -matplotlib==2.0.2 +matplotlib==1.5.3 nibabel==2.1.0 pytest-cov==2.5.1 From d4e457cd7fb8ce5b3937fd23337350593f09aafd Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 18:05:08 +0200 Subject: [PATCH 062/100] Deriche filter related lines are removed. --- segmentator/__main__.py | 16 ---------------- segmentator/config.py | 3 --- 2 files changed, 19 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index c18a8b1f..2a5e065f 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -55,18 +55,6 @@ def main(args=None): help="Only save 2D histogram image without showing GUI." ) - # used in Deriche filter gradient magnitude computation - parser.add_argument( - "--deriche_prepare", action='store_true', - help=("------------------(utility feature)------------------ \ - Use this flag with the following arguments:") - ) - parser.add_argument( - "--der_alpha", required=False, type=float, - default=2, metavar='2', - help="Alpha controls smoothing, lower -> smoother" - ) - # used in ncut preparation (TODO: not yet tested after restructuring.) parser.add_argument( "--ncut_prepare", action='store_true', @@ -107,8 +95,6 @@ def main(args=None): config.scale = args.scale config.perc_min = args.percmin config.perc_max = args.percmax - # used in deriche filter - config.deriche_alpha = args.der_alpha # used in ncut preparation config.ncut_figs = args.ncut_figs config.max_rec = args.ncut_maxRec @@ -123,8 +109,6 @@ def main(args=None): if args.nogui: print '--No GUI option is selected. Saving 2D histogram image...' import hist2d_counts - elif args.deriche_prepare: - import deriche elif args.ncut_prepare: print '--Preparing n-cut related files...' import ncut_prepare diff --git a/segmentator/config.py b/segmentator/config.py index 496c1ca4..9621210f 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -19,9 +19,6 @@ # possible gradient magnitude computation keyword options gramag_options = ['3D_scharr', '3D_sobel', '3D_prewitt', 'numpy'] -# used in Deriche filter -deriche_alpha = 2 - # used in segmentator ncut ncut = False max_rec = 8 From 0c08ab5d1cc442bbcfdac07e4cece7e0872322cb Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 18:24:19 +0200 Subject: [PATCH 063/100] Cosmetics. --- segmentator/segmentator_main.py | 18 +++++++++--------- segmentator/segmentator_ncut.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 3af12b25..0558cdf8 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -62,10 +62,10 @@ fig = plt.figure() ax = fig.add_subplot(121) -counts, volHistH, dataMin, dataMax, nrBins, binEdges = prep_2D_hist(ima, gra) +counts, volHistH, d_min, d_max, nr_bins, bin_edges = prep_2D_hist(ima, gra) -ax.set_xlim(dataMin, dataMax) -ax.set_ylim(dataMin, dataMax) +ax.set_xlim(d_min, d_max) +ax.set_ylim(d_min, d_max) ax.set_xlabel("Intensity f(x)") ax.set_ylabel("Gradient Magnitude f'(x)") ax.set_title("2D Histogram") @@ -104,7 +104,7 @@ """Initialisation""" # create first instance of sector mask sectorObj = sector_mask( - (nrBins, nrBins), + (nr_bins, nr_bins), cfg.init_centre, cfg.init_radius, cfg.init_theta @@ -118,12 +118,12 @@ vmin=0.1, interpolation='nearest', origin='lower', - extent=[0, nrBins, 0, nrBins] + extent=[0, nr_bins, 0, nr_bins] ) # initiate a flexible figure object, pass to it usefull properties segmType = 'main' -idxLasso = np.zeros(nrBins*nrBins, dtype=bool) +idxLasso = np.zeros(nr_bins*nr_bins, dtype=bool) lassoSwitchCount = 0 flexFig = responsiveObj(figure=ax.figure, axes=ax.axes, @@ -132,7 +132,7 @@ orig=orig, nii=nii, sectorObj=sectorObj, - nrBins=nrBins, + nr_bins=nr_bins, sliceNr=int(0.5*dims[2]), slcH=slcH, imaMask=imaMask, @@ -147,7 +147,7 @@ # make the figure responsive to clicks flexFig.connect() -ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=binEdges) +ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=bin_edges) flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) # @@ -224,7 +224,7 @@ def lassoSwitch(event): # Pixel coordinates -pix = np.arange(nrBins) +pix = np.arange(nr_bins) xv, yv = np.meshgrid(pix, pix) pix = np.vstack((xv.flatten(), yv.flatten())).T diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index a00c8283..17a04bf6 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -81,23 +81,23 @@ fig = plt.figure() ax = fig.add_subplot(121) -counts, volHistH, dataMin, dataMax, nrBins, binEdges = prep_2D_hist(ima, gra) +counts, volHistH, d_min, d_max, nr_bins, bin_edges = prep_2D_hist(ima, gra) -ax.set_xlim(dataMin, dataMax) -ax.set_ylim(dataMin, dataMax) +ax.set_xlim(d_min, d_max) +ax.set_ylim(d_min, d_max) ax.set_xlabel("Intensity f(x)") ax.set_ylabel("Gradient Magnitude f'(x)") ax.set_title("2D Histogram") # plot map for poltical borders -pltMap = np.zeros((nrBins, nrBins, 1)).repeat(4, 2) +pltMap = np.zeros((nr_bins, nr_bins, 1)).repeat(4, 2) cmapPltMap = ListedColormap(['w', 'black', 'red', 'blue']) boundsPltMap = [0, 1, 2, 3, 4] cmapPltMap.set_under('w', 0) normPltMap = BoundaryNorm(boundsPltMap, cmapPltMap.N) pltMapH = ax.imshow(pltMap, alpha=1, cmap=cmapPltMap, norm=normPltMap, vmin=boundsPltMap[1], vmax=boundsPltMap[-1], - extent=[0, nrBins, nrBins, 0], interpolation='none') + extent=[0, nr_bins, nr_bins, 0], interpolation='none') # plot colorbar for 2d hist volHistH.set_norm(LogNorm(vmax=1000)) @@ -113,7 +113,7 @@ alpha=0.2, cmap=ncut_palette, vmin=np.min(ncut_labels)+1, # to make 0 transparent vmax=lMax, - extent=[0, nrBins, nrBins, 0]) + extent=[0, nr_bins, nr_bins, 0]) # plot 3D ima by default ax2 = fig.add_subplot(122) @@ -144,7 +144,7 @@ orig=orig, nii=nii, ima=ima, - nrBins=nrBins, + nr_bins=nr_bins, sliceNr=int(0.5*dims[2]), slcH=slcH, imaMask=imaMask, @@ -153,7 +153,7 @@ volHistMaskH=volHistMaskH, pltMap=pltMap, pltMapH=pltMapH, - counterField=np.zeros((nrBins, nrBins)), + counterField=np.zeros((nr_bins, nr_bins)), orig_ncut_labels=orig_ncut_labels, ima_ncut_labels=ima_ncut_labels, initTpl=(cfg.perc_min, cfg.perc_max, cfg.scale), @@ -163,7 +163,7 @@ # make the figure responsive to clicks flexFig.connect() # get mapping from image slice to volume histogram -ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=binEdges) +ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=bin_edges) flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) # %% From 7ad25064404ace7c6f916d17738c63ea6088a743 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 18:41:10 +0200 Subject: [PATCH 064/100] Discard 0s by default and some cosmetics. Related to the 2D histogram call. --- segmentator/segmentator_main.py | 2 +- segmentator/segmentator_ncut.py | 2 +- segmentator/utils.py | 27 ++++++++++++++------------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 0558cdf8..bc788bdb 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -132,7 +132,7 @@ orig=orig, nii=nii, sectorObj=sectorObj, - nr_bins=nr_bins, + nrBins=nr_bins, sliceNr=int(0.5*dims[2]), slcH=slcH, imaMask=imaMask, diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 17a04bf6..e47b3290 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -144,7 +144,7 @@ orig=orig, nii=nii, ima=ima, - nr_bins=nr_bins, + nrBins=nr_bins, sliceNr=int(0.5*dims[2]), slcH=slcH, imaMask=imaMask, diff --git a/segmentator/utils.py b/segmentator/utils.py index 18fdea02..3c6fd92e 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -64,8 +64,8 @@ def map_ima_to_2D_hist(xinput, yinput, bins_arr): """ dgtzData = np.digitize(xinput, bins_arr)-1 dgtzGra = np.digitize(yinput, bins_arr)-1 - nrBins = len(bins_arr)-1 # subtract 1 (more borders than containers) - vox2pixMap = sub2ind(nrBins, dgtzData, dgtzGra) # 1D + nr_bins = len(bins_arr)-1 # subtract 1 (more borders than containers) + vox2pixMap = sub2ind(nr_bins, dgtzData, dgtzGra) # 1D return vox2pixMap @@ -160,7 +160,7 @@ def scale_range(data, scale_factor=500, delta=0, discard_zeros=True): return data -def prep_2D_hist(ima, gra): +def prep_2D_hist(ima, gra, discard_zeros=True): """Prepare 2D histogram related variables. Parameters @@ -175,25 +175,26 @@ def prep_2D_hist(ima, gra): ------- counts : integer volHistH : TODO - dataMin : float + d_min : float Minimum of the first image. - dataMax : float + d_max : float Maximum of the first image. - nrBins : integer + nr_bins : integer Number of one dimensional bins (not the pixels). - binEdges : TODO + bin_edges : TODO Notes ----- This function is modularized to be called from the terminal. """ - dataMin = np.round(ima.min()) - dataMax = np.round(ima.max()) - nrBins = int(dataMax - dataMin) - binEdges = np.arange(dataMin, dataMax+1) - counts, _, _, volHistH = plt.hist2d(ima, gra, bins=binEdges, cmap='Greys') - return counts, volHistH, dataMin, dataMax, nrBins, binEdges + if discard_zeros: + ima, gra = ima[ima != 0], gra[ima != 0] + d_min, d_max = np.round(np.nanpercentile(ima, [0, 100])) + nr_bins = int(d_max - d_min) + bin_edges = np.arange(d_min, d_max+1) + counts, _, _, volHistH = plt.hist2d(ima, gra, bins=bin_edges, cmap='Greys') + return counts, volHistH, d_min, d_max, nr_bins, bin_edges def create_3D_kernel(operator='3D_sobel'): From ec2efb6721189887c4832b91c0375a0e178c56cb Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 18:58:20 +0200 Subject: [PATCH 065/100] Interface update for discarding image zeros. --- segmentator/segmentator_ncut.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index e47b3290..ef9e71b5 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -81,7 +81,8 @@ fig = plt.figure() ax = fig.add_subplot(121) -counts, volHistH, d_min, d_max, nr_bins, bin_edges = prep_2D_hist(ima, gra) +counts, volHistH, d_min, d_max, nr_bins, bin_edges \ + = prep_2D_hist(ima, gra, discard_zeros=cfg.discard_zeros) ax.set_xlim(d_min, d_max) ax.set_ylim(d_min, d_max) From bb9bdc72ba1fa96e308063762a01bae3cf60de84 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 18:59:05 +0200 Subject: [PATCH 066/100] Interdace update for discarding image zeros. --- segmentator/__main__.py | 6 ++++++ segmentator/config.py | 1 + segmentator/segmentator_main.py | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 2a5e065f..4c42cbdf 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -54,6 +54,10 @@ def main(args=None): "--nogui", action='store_true', help="Only save 2D histogram image without showing GUI." ) + parser.add_argument( + "--include_zeros", action='store_true', + help="Include image zeros in histograms. Not used by default." + ) # used in ncut preparation (TODO: not yet tested after restructuring.) parser.add_argument( @@ -95,6 +99,8 @@ def main(args=None): config.scale = args.scale config.perc_min = args.percmin config.perc_max = args.percmax + if args.include_zeros: + config.discard_zeros = False # used in ncut preparation config.ncut_figs = args.ncut_figs config.max_rec = args.ncut_maxRec diff --git a/segmentator/config.py b/segmentator/config.py index 9621210f..abaf2b85 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -15,6 +15,7 @@ perc_min = 2.5 perc_max = 97.5 scale = 400 +discard_zeros = True # possible gradient magnitude computation keyword options gramag_options = ['3D_scharr', '3D_sobel', '3D_prewitt', 'numpy'] diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index bc788bdb..7fa27ed9 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -62,7 +62,8 @@ fig = plt.figure() ax = fig.add_subplot(121) -counts, volHistH, d_min, d_max, nr_bins, bin_edges = prep_2D_hist(ima, gra) +counts, volHistH, d_min, d_max, nr_bins, bin_edges \ + = prep_2D_hist(ima, gra, discard_zeros=cfg.discard_zeros) ax.set_xlim(d_min, d_max) ax.set_ylim(d_min, d_max) From 795d3b124444c623da603cff7b661eeb1af06deb Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 19:13:39 +0200 Subject: [PATCH 067/100] Update readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60e61de6..0632f755 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ The goal is to provide a complementary tool to the already available brain tissu | Package | Tested version | |--------------------------------------|----------------| +| [matplotlib](http://matplotlib.org/) | **1.5.3** | | [NumPy](http://www.numpy.org/) | 1.13.1 | -| [matplotlib](http://matplotlib.org/) | 1.5.3 | | [NiBabel](http://nipy.org/nibabel/) | 2.1.0 | | [SciPy](http://scipy.org/) | 0.19.1 | From 79b34ee6fbc13c0e9034e129c1b79e97558aa4d2 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 19:18:53 +0200 Subject: [PATCH 068/100] Forgot to include discard zeros option in noGUI. --- segmentator/hist2d_counts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segmentator/hist2d_counts.py b/segmentator/hist2d_counts.py index 302e0e82..f6d5f035 100644 --- a/segmentator/hist2d_counts.py +++ b/segmentator/hist2d_counts.py @@ -37,7 +37,7 @@ ima = np.ndarray.flatten(orig) gra = np.ndarray.flatten(gra) -counts, _, _, _, _, _ = prep_2D_hist(ima, gra) +counts, _, _, _, _, _ = prep_2D_hist(ima, gra, discard_zeros=cfg.discard_zeros) outName = (basename + '_volHist' + '_pMax' + str(cfg.perc_max) + '_pMin' + str(cfg.perc_min) + '_sc' + str(int(cfg.scale)) From a4e24f78ba43160072e3fa0be4843a9316f22678 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 19:34:34 +0200 Subject: [PATCH 069/100] Default written in the interface are updated. --- segmentator/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 4c42cbdf..4aa2b0cc 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -36,17 +36,17 @@ def main(args=None): help="Path to nyp file with ncut labels" ) parser.add_argument( - "--scale", metavar='500', required=False, type=float, + "--scale", metavar='400', required=False, type=float, default=config.scale, help="Data is scaled from 0 to this number." ) parser.add_argument( - "--percmin", metavar='0.25', required=False, type=float, + "--percmin", metavar='2.5', required=False, type=float, default=config.perc_min, help="Minimum percentile used in truncation." ) parser.add_argument( - "--percmax", metavar='99.75', required=False, type=float, + "--percmax", metavar='97.5', required=False, type=float, default=config.perc_max, help="Maximum percentile used in truncation." ) From ee5d2d69864622d5c862298b3dd9a2b2225abead Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 20:16:53 +0200 Subject: [PATCH 070/100] nonzero tolerances added to cover a special case. values close to 0 by 0.5 are considered as zeros. This issue accured with some niftis processed by fsl. --- segmentator/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index 3c6fd92e..a10a34a4 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -114,7 +114,7 @@ def truncate_range(data, percMin=0.25, percMax=99.75, discard_zeros=True): """ if discard_zeros: - msk = data != 0 + msk = ~np.isclose(data, 0, rtol=-5.e-1, atol=1.) pMin, pMax = np.nanpercentile(data[msk], [percMin, percMax]) else: pMin, pMax = np.nanpercentile(data, [percMin, percMax]) @@ -149,7 +149,7 @@ def scale_range(data, scale_factor=500, delta=0, discard_zeros=True): """ if discard_zeros: - msk = data != 0 + msk = ~np.isclose(data, 0, rtol=-5.e-1, atol=1.) else: msk = np.ones(data.shape, dtype=bool) scale_factor = scale_factor - delta @@ -189,7 +189,8 @@ def prep_2D_hist(ima, gra, discard_zeros=True): """ if discard_zeros: - ima, gra = ima[ima != 0], gra[ima != 0] + gra = gra[~np.isclose(ima, 0, rtol=-5.e-1, atol=1.)] + ima = ima[~np.isclose(ima, 0, rtol=-5.e-1, atol=1.)] d_min, d_max = np.round(np.nanpercentile(ima, [0, 100])) nr_bins = int(d_max - d_min) bin_edges = np.arange(d_min, d_max+1) From 80b85ebcb2a1b7ad4b5cac3a69c0f55c5e825080 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 20:35:18 +0200 Subject: [PATCH 071/100] Reverting the last commit. It seemed that tinkering with np.isclose defaults will bring more harm than good in the future. --- segmentator/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index a10a34a4..5ece3f3c 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -114,7 +114,7 @@ def truncate_range(data, percMin=0.25, percMax=99.75, discard_zeros=True): """ if discard_zeros: - msk = ~np.isclose(data, 0, rtol=-5.e-1, atol=1.) + msk = ~np.isclose(data, 0) pMin, pMax = np.nanpercentile(data[msk], [percMin, percMax]) else: pMin, pMax = np.nanpercentile(data, [percMin, percMax]) @@ -149,7 +149,7 @@ def scale_range(data, scale_factor=500, delta=0, discard_zeros=True): """ if discard_zeros: - msk = ~np.isclose(data, 0, rtol=-5.e-1, atol=1.) + msk = ~np.isclose(data, 0) else: msk = np.ones(data.shape, dtype=bool) scale_factor = scale_factor - delta @@ -189,8 +189,8 @@ def prep_2D_hist(ima, gra, discard_zeros=True): """ if discard_zeros: - gra = gra[~np.isclose(ima, 0, rtol=-5.e-1, atol=1.)] - ima = ima[~np.isclose(ima, 0, rtol=-5.e-1, atol=1.)] + gra = gra[~np.isclose(ima, 0] + ima = ima[~np.isclose(ima, 0] d_min, d_max = np.round(np.nanpercentile(ima, [0, 100])) nr_bins = int(d_max - d_min) bin_edges = np.arange(d_min, d_max+1) From 25058dac9019b1d91d30d2af982396d16cd09abc Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 20:40:40 +0200 Subject: [PATCH 072/100] Small fix. --- segmentator/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/segmentator/utils.py b/segmentator/utils.py index 5ece3f3c..30c3370b 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -189,8 +189,8 @@ def prep_2D_hist(ima, gra, discard_zeros=True): """ if discard_zeros: - gra = gra[~np.isclose(ima, 0] - ima = ima[~np.isclose(ima, 0] + gra = gra[~np.isclose(ima, 0)] + ima = ima[~np.isclose(ima, 0)] d_min, d_max = np.round(np.nanpercentile(ima, [0, 100])) nr_bins = int(d_max - d_min) bin_edges = np.arange(d_min, d_max+1) From b2ed3638cd6037f97b1decaca60b0fe02cf32d67 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 20:55:09 +0200 Subject: [PATCH 073/100] Colorbar initial and maximum value arguments added. For the command line interface --- segmentator/__main__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 4aa2b0cc..d36f3d55 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -50,6 +50,16 @@ def main(args=None): default=config.perc_max, help="Maximum percentile used in truncation." ) + parser.add_argument( + "--cbar_init", metavar='2.0', required=False, type=float, + default=config.cbar_init, + help="Initial value (power of 10) of the colorbar slider. Useful when scripting." + ) + parser.add_argument( + "--cbar_max", metavar='5.0', required=False, type=float, + default=config.cbar_max, + help="Maximum value (power of 10) of the colorbar slider." + ) parser.add_argument( "--nogui", action='store_true', help="Only save 2D histogram image without showing GUI." @@ -99,6 +109,8 @@ def main(args=None): config.scale = args.scale config.perc_min = args.percmin config.perc_max = args.percmax + config.cbar_init = args.cbar_init + config.cbar_max = args.cbar_max if args.include_zeros: config.discard_zeros = False # used in ncut preparation From 43a28d20245b980fefbb984fa51c4a910f3c5eaa Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 20:56:10 +0200 Subject: [PATCH 074/100] Colorbar inital, max value arguments added to ui. --- segmentator/config.py | 2 ++ segmentator/segmentator_main.py | 5 +++-- segmentator/segmentator_ncut.py | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/segmentator/config.py b/segmentator/config.py index abaf2b85..5957bc33 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -15,6 +15,8 @@ perc_min = 2.5 perc_max = 97.5 scale = 400 +cbar_init = 2.0 +cbar_max = 5.0 discard_zeros = True # possible gradient magnitude computation keyword options diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 7fa27ed9..295a963b 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -72,7 +72,7 @@ ax.set_title("2D Histogram") # plot colorbar for 2d hist -volHistH.set_norm(LogNorm(vmax=1000)) +volHistH.set_norm(LogNorm(vmax=np.power(10, cfg.cbar_init))) plt.colorbar(volHistH) # plot 3D ima by default @@ -156,7 +156,8 @@ # colorbar slider axcolor = 'lightgoldenrodyellow' axHistC = plt.axes([0.15, bottom-0.20, 0.25, 0.025], axisbg=axcolor) -flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, 5, valinit=3, valfmt='%0.1f') +flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, cfg.cbar_max, + valinit=cfg.cbar_init, valfmt='%0.1f') # ima browser slider axSliceNr = plt.axes([0.6, bottom-0.15, 0.25, 0.025], axisbg=axcolor) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index ef9e71b5..ae130294 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -101,7 +101,7 @@ extent=[0, nr_bins, nr_bins, 0], interpolation='none') # plot colorbar for 2d hist -volHistH.set_norm(LogNorm(vmax=1000)) +volHistH.set_norm(LogNorm(vmax=np.power(10, cfg.cbar_init))) plt.colorbar(volHistH) # Set up a colormap for ncut labels @@ -178,7 +178,8 @@ # colorbar slider axHistC = plt.axes([0.15, bottom-0.230, 0.25, 0.025], axisbg=axcolor) -flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, 5, valinit=3, valfmt='%0.1f') +flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, cfg.cbar_max, + valinit=cfg.cbar_init, valfmt='%0.1f') # label slider axLabels = plt.axes([0.15, bottom-0.270, 0.25, 0.025], axisbg=axcolor) From ce5e64b02c0ca8f73b3d2c9618a524f135deadd8 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 21:24:53 +0200 Subject: [PATCH 075/100] Cosmetics. --- segmentator/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index d36f3d55..12359949 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -53,7 +53,7 @@ def main(args=None): parser.add_argument( "--cbar_init", metavar='2.0', required=False, type=float, default=config.cbar_init, - help="Initial value (power of 10) of the colorbar slider. Useful when scripting." + help="Initial value (power of 10) of the colorbar slider." ) parser.add_argument( "--cbar_max", metavar='5.0', required=False, type=float, From 9b0906b4c55bc49677040891e918dd2b55bbcd9e Mon Sep 17 00:00:00 2001 From: ofgulban Date: Thu, 21 Sep 2017 21:33:25 +0200 Subject: [PATCH 076/100] Truncation control for ncut prepare. + some cosmetics. --- segmentator/__main__.py | 15 ++++++++------- segmentator/config.py | 2 +- segmentator/ncut_prepare.py | 11 +++++++---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 12359949..1982abf7 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -50,16 +50,17 @@ def main(args=None): default=config.perc_max, help="Maximum percentile used in truncation." ) - parser.add_argument( - "--cbar_init", metavar='2.0', required=False, type=float, - default=config.cbar_init, - help="Initial value (power of 10) of the colorbar slider." - ) parser.add_argument( "--cbar_max", metavar='5.0', required=False, type=float, default=config.cbar_max, help="Maximum value (power of 10) of the colorbar slider." ) + parser.add_argument( + "--cbar_init", metavar='2.0', required=False, type=float, + default=config.cbar_init, + help="Initial value (power of 10) of the colorbar slider. \ + Also used with --ncut_prepare flag." + ) parser.add_argument( "--nogui", action='store_true', help="Only save 2D histogram image without showing GUI." @@ -109,8 +110,8 @@ def main(args=None): config.scale = args.scale config.perc_min = args.percmin config.perc_max = args.percmax - config.cbar_init = args.cbar_init config.cbar_max = args.cbar_max + config.cbar_init = args.cbar_init if args.include_zeros: config.discard_zeros = False # used in ncut preparation @@ -128,7 +129,7 @@ def main(args=None): print '--No GUI option is selected. Saving 2D histogram image...' import hist2d_counts elif args.ncut_prepare: - print '--Preparing n-cut related files...' + print '--Preparing N-cut related files...' import ncut_prepare elif args.ncut: print '--Experimental N-cut feature is selected.' diff --git a/segmentator/config.py b/segmentator/config.py index 5957bc33..3e6746d8 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -15,8 +15,8 @@ perc_min = 2.5 perc_max = 97.5 scale = 400 -cbar_init = 2.0 cbar_max = 5.0 +cbar_init = 2.0 discard_zeros = True # possible gradient magnitude computation keyword options diff --git a/segmentator/ncut_prepare.py b/segmentator/ncut_prepare.py index 8e9604b5..6ec32600 100644 --- a/segmentator/ncut_prepare.py +++ b/segmentator/ncut_prepare.py @@ -42,9 +42,6 @@ def norm_grap_cut(image, max_edge=10000000, max_rec=4, compactness=2, identifier. """ - # truncate very high values - perc = np.percentile(image, 99.99) - image[image > perc] = perc # scale for uint8 conversion image = np.round(255 / image.max() * image) @@ -66,8 +63,14 @@ def norm_grap_cut(image, max_edge=10000000, max_rec=4, compactness=2, path = cfg.filename basename = path.split(os.extsep, 1)[0] +# load data img = np.load(path) -img = np.log10(img+1) +# take logarithm of every count to make it similar to what is seen in gui +img = np.log10(img+1.) + +# truncate very high values +img_max = cfg.cbar_init +img[img > img_max] = img_max max_recursion = cfg.max_rec ncut = np.zeros((img.shape[0], img.shape[1], max_recursion + 1)) From 539c18e54ec4263acce8bc678275241c3d0bfe0c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 22 Sep 2017 12:24:58 +0200 Subject: [PATCH 077/100] Defaults in ui are collected from config file now --- segmentator/__main__.py | 60 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 1982abf7..878c1c8e 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -12,7 +12,7 @@ import sys import argparse -import config +import config as cfg def main(args=None): @@ -26,8 +26,8 @@ def main(args=None): help="Path to input. Mostly a nifti file with image data." ) parser.add_argument( - "--gramag", metavar='3D_scharr', required=False, - default=config.gramag, + "--gramag", metavar=str(cfg.gramag), required=False, + default=cfg.gramag, help="'3D_scharr', '3D_sobel', '3D_prewitt', 'numpy' \ or path to a gradient magnitude nifti." ) @@ -36,28 +36,28 @@ def main(args=None): help="Path to nyp file with ncut labels" ) parser.add_argument( - "--scale", metavar='400', required=False, type=float, - default=config.scale, + "--scale", metavar=str(cfg.scale), required=False, type=float, + default=cfg.scale, help="Data is scaled from 0 to this number." ) parser.add_argument( - "--percmin", metavar='2.5', required=False, type=float, - default=config.perc_min, + "--percmin", metavar=str(cfg.perc_min), required=False, type=float, + default=cfg.perc_min, help="Minimum percentile used in truncation." ) parser.add_argument( - "--percmax", metavar='97.5', required=False, type=float, - default=config.perc_max, + "--percmax", metavar=str(cfg.perc_max), required=False, type=float, + default=cfg.perc_max, help="Maximum percentile used in truncation." ) parser.add_argument( - "--cbar_max", metavar='5.0', required=False, type=float, - default=config.cbar_max, + "--cbar_max", metavar=str(cfg.cbar_max), required=False, type=float, + default=cfg.cbar_max, help="Maximum value (power of 10) of the colorbar slider." ) parser.add_argument( - "--cbar_init", metavar='2.0', required=False, type=float, - default=config.cbar_init, + "--cbar_init", metavar=str(cfg.cbar_init), required=False, + type=float, default=cfg.cbar_init, help="Initial value (power of 10) of the colorbar slider. \ Also used with --ncut_prepare flag." ) @@ -82,17 +82,17 @@ def main(args=None): ) parser.add_argument( "--ncut_maxRec", required=False, type=int, - default=config.max_rec, metavar=config.max_rec, + default=cfg.max_rec, metavar=cfg.max_rec, help="Maximum number of recursions." ) parser.add_argument( "--ncut_nrSupPix", required=False, type=int, - default=config.nr_sup_pix, metavar=config.nr_sup_pix, + default=cfg.nr_sup_pix, metavar=cfg.nr_sup_pix, help="Number of regions/superpixels." ) parser.add_argument( "--ncut_compactness", required=False, type=float, - default=config.compactness, metavar=config.compactness, + default=cfg.compactness, metavar=cfg.compactness, help="Compactness balances intensity proximity and space \ proximity of the superpixels. \ Higher values give more weight to space proximity, making \ @@ -101,26 +101,26 @@ def main(args=None): objects in the image." ) - # set config file variables to be accessed from other scripts + # set cfg file variables to be accessed from other scripts args = parser.parse_args() # used in all - config.filename = args.filename + cfg.filename = args.filename # used in segmentator GUI (main and ncut) - config.gramag = args.gramag - config.scale = args.scale - config.perc_min = args.percmin - config.perc_max = args.percmax - config.cbar_max = args.cbar_max - config.cbar_init = args.cbar_init + cfg.gramag = args.gramag + cfg.scale = args.scale + cfg.perc_min = args.percmin + cfg.perc_max = args.percmax + cfg.cbar_max = args.cbar_max + cfg.cbar_init = args.cbar_init if args.include_zeros: - config.discard_zeros = False + cfg.discard_zeros = False # used in ncut preparation - config.ncut_figs = args.ncut_figs - config.max_rec = args.ncut_maxRec - config.nr_sup_pix = args.ncut_nrSupPix - config.compactness = args.ncut_compactness + cfg.ncut_figs = args.ncut_figs + cfg.max_rec = args.ncut_maxRec + cfg.nr_sup_pix = args.ncut_nrSupPix + cfg.compactness = args.ncut_compactness # used in ncut - config.ncut = args.ncut + cfg.ncut = args.ncut print("===========\nSegmentator\n===========") From c4379eae070629f3df150e310842e3b77886ba18 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 22 Sep 2017 14:17:29 +0200 Subject: [PATCH 078/100] Travis emails turned off. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f21c97d8..e23237e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,3 +15,5 @@ script: # command to run tests - py.test --cov=./segmentator after_success: - bash <(curl -s https://codecov.io/bash) +notifications: + email: false From b5b409cebd47c9acf221264cedecd7711e124122 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 22 Sep 2017 18:10:14 +0200 Subject: [PATCH 079/100] Cosmetics --- segmentator/VolHist2ImaMapping4Vols.py | 47 ------ ...{segmentator_functions.py => gui_utils.py} | 120 ++++++++++++++++ segmentator/ncut_prepare.py | 3 +- segmentator/sector_mask.py | 135 ------------------ segmentator/segmentator_main.py | 5 +- segmentator/segmentator_ncut.py | 6 +- segmentator/utils.py | 4 +- 7 files changed, 128 insertions(+), 192 deletions(-) delete mode 100644 segmentator/VolHist2ImaMapping4Vols.py rename segmentator/{segmentator_functions.py => gui_utils.py} (81%) delete mode 100755 segmentator/sector_mask.py diff --git a/segmentator/VolHist2ImaMapping4Vols.py b/segmentator/VolHist2ImaMapping4Vols.py deleted file mode 100644 index c22b6c59..00000000 --- a/segmentator/VolHist2ImaMapping4Vols.py +++ /dev/null @@ -1,47 +0,0 @@ -"""This function serves to make a mapping from the volume histogram to every -voxel in the data, not just the currently displayed slice. - -This function is slow and might benefit from cython. -""" - -# Part of the Segmentator library -# Copyright (C) 2016 Omer Faruk Gulban and Marian Schneider -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import numpy as np - - -def VolHist2ImaOffline(vox2pixMap, nrBins): - """Map from volume histogram to 3D image (uses logical indexing).""" - # get bincount (to know how many voxels are in pixel 0,1,etc.. of volHist) - counts = np.bincount(vox2pixMap, minlength=nrBins**2) - # get sorting indices (used to sort list acc. to pixels for vox2pixMap) - sortIdx = np.argsort(vox2pixMap) - # transform array to list, so we can hold a varying number of elem - pix2VoxMap = sortIdx.tolist() - # get position of zero elements - posZeros = np.where(counts == 0)[0] - # prepare to split the list into list of lists - upperBound = np.delete(counts, posZeros) - upperBound = np.cumsum(upperBound).astype(int) - lowerBound = np.hstack(([0], upperBound[0:-1])).astype(int) - # split the list into list of lists - pix2VoxMap = [pix2VoxMap[i:j] for i, j in zip(lowerBound, upperBound)] - # insert empty lists at zero positions - for idx in posZeros: - pix2VoxMap.insert(idx, []) - # convert back to numpy array - pix2VoxMap = np.array(pix2VoxMap) - return pix2VoxMap diff --git a/segmentator/segmentator_functions.py b/segmentator/gui_utils.py similarity index 81% rename from segmentator/segmentator_functions.py rename to segmentator/gui_utils.py index eb0d8630..3990f5eb 100755 --- a/segmentator/segmentator_functions.py +++ b/segmentator/gui_utils.py @@ -444,3 +444,123 @@ def calcImaMaskBrd(self): grad = np.gradient(self.imaMask) return np.greater(np.sqrt(np.power(grad[0], 2) + np.power(grad[1], 2)), 0) + + +class sector_mask: + """A pacman-like shape with useful parameters. + + Disclaimer + ---------- + This script is adapted from a stackoverflow post by user ali_m: + [1] http://stackoverflow.com/questions/18352973/mask-a-circular-sector-in-a-numpy-array + + """ + + def __init__(self, shape, centre, radius, angle_range): + self.radius = radius + self.shape = shape + self.x, self.y = np.ogrid[:shape[0], :shape[1]] + self.cx, self.cy = centre + self.tmin, self.tmax = np.deg2rad(angle_range) + # ensure stop angle > start angle + if self.tmax < self.tmin: + self.tmax += 2*np.pi + # convert cartesian --> polar coordinates + self.r2 = (self.x-self.cx)*(self.x-self.cx) + ( + self.y-self.cy)*(self.y-self.cy) + self.theta = np.arctan2(self.x-self.cx, self.y-self.cy) - self.tmin + # wrap angles between 0 and 2*pi + self.theta %= (2*np.pi) + + def set_polCrd(self): + """Convert cartesian to polar coordinates.""" + self.r2 = (self.x-self.cx)*(self.x-self.cx) + ( + self.y-self.cy)*(self.y-self.cy) + self.theta = np.arctan2(self.x-self.cx, self.y-self.cy) - self.tmin + # wrap angles between 0 and 2*pi + self.theta %= (2*np.pi) + + def set_x(self, x): + """Set x axis value.""" + self.cx = x + self.set_polCrd() # update polar coordinates + + def set_y(self, y): + """Set y axis value.""" + self.cy = y + self.set_polCrd() # update polar coordinates + + def set_r(self, radius): + """Set radius of the circle.""" + self.radius = radius + + def scale_r(self, scale): + """Scale (multiply) the radius.""" + self.radius = self.radius * scale + + def rotate(self, degree): + """Rotate shape.""" + rad = np.deg2rad(degree) + self.tmin += rad + self.tmax += rad + self.set_polCrd() # update polar coordinates + + def theta_min(self, degree): + """Angle to determine one the cut out piece in circular mask.""" + rad = np.deg2rad(degree) + self.tmin = rad + # ensure stop angle > start angle + if self.tmax <= self.tmin: + self.tmax += 2*np.pi + # ensure stop angle- 2*np.pi NOT > start angle + if self.tmax - 2*np.pi >= self.tmin: + self.tmax -= 2*np.pi + # update polar coordinates + self.set_polCrd() + + def theta_max(self, degree): + """Angle to determine one the cut out piece in circular mask.""" + rad = np.deg2rad(degree) + self.tmax = rad + # ensure stop angle > start angle + if self.tmax <= self.tmin: + self.tmax += 2*np.pi + # ensure stop angle- 2*np.pi NOT > start angle + if self.tmax - 2*np.pi >= self.tmin: + self.tmax -= 2*np.pi + # update polar coordinates + self.set_polCrd() + + def binaryMask(self): + """Return a boolean mask for a circular sector.""" + # circular mask + self.circmask = self.r2 <= self.radius*self.radius + # angular mask + self.anglemask = self.theta <= (self.tmax-self.tmin) + # return binary mask + return self.circmask*self.anglemask + + def contains(self, event): + """Check if a cursor pointer is inside the sector mask.""" + xbin = np.floor(event.xdata) + ybin = np.floor(event.ydata) + Mask = self.binaryMask() + # the next line doesn't follow pep 8 (otherwise it fails) + if Mask[ybin][xbin] is True: # switch x and ybin, volHistMask not Cart + return True + else: + return False + + def draw(self, ax, cmap='Reds', alpha=0.2, vmin=0.1, + interpolation='nearest', origin='lower', extent=[0, 100, 0, 100]): + """Draw stuff.""" + BinMask = self.binaryMask() + FigObj = ax.imshow( + BinMask, + cmap=cmap, + alpha=alpha, + vmin=vmin, + interpolation=interpolation, + origin=origin, + extent=extent) + return (FigObj, BinMask) diff --git a/segmentator/ncut_prepare.py b/segmentator/ncut_prepare.py index 6ec32600..c72dd771 100644 --- a/segmentator/ncut_prepare.py +++ b/segmentator/ncut_prepare.py @@ -37,12 +37,11 @@ def norm_grap_cut(image, max_edge=10000000, max_rec=4, compactness=2, Returns ------- - sector_mask: np.ndarray (2D) + labels2, labels1: np.ndarray (2D) Segmented volume histogram mask image. Each label has a unique identifier. """ - # scale for uint8 conversion image = np.round(255 / image.max() * image) image = image.astype('uint8') diff --git a/segmentator/sector_mask.py b/segmentator/sector_mask.py deleted file mode 100755 index ba446cbe..00000000 --- a/segmentator/sector_mask.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Sector mask to mask stuff in volume histogram.""" - -# Part of the Segmentator library -# Copyright (C) 2016 Omer Faruk Gulban and Marian Schneider -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# This code is taken from user 'ali_m' from StackOverflow: -# - -import numpy as np - - -class sector_mask: - """A shape with useful parameters to detect some tissues quickly.""" - - def __init__(self, shape, centre, radius, angle_range): - self.radius = radius - self.shape = shape - self.x, self.y = np.ogrid[:shape[0], :shape[1]] - self.cx, self.cy = centre - self.tmin, self.tmax = np.deg2rad(angle_range) - # ensure stop angle > start angle - if self.tmax < self.tmin: - self.tmax += 2*np.pi - # convert cartesian --> polar coordinates - self.r2 = (self.x-self.cx)*(self.x-self.cx) + ( - self.y-self.cy)*(self.y-self.cy) - self.theta = np.arctan2(self.x-self.cx, self.y-self.cy) - self.tmin - # wrap angles between 0 and 2*pi - self.theta %= (2*np.pi) - - def set_polCrd(self): - """Convert cartesian to polar coordinates.""" - self.r2 = (self.x-self.cx)*(self.x-self.cx) + ( - self.y-self.cy)*(self.y-self.cy) - self.theta = np.arctan2(self.x-self.cx, self.y-self.cy) - self.tmin - # wrap angles between 0 and 2*pi - self.theta %= (2*np.pi) - - def set_x(self, x): - """Set x axis value.""" - self.cx = x - self.set_polCrd() # update polar coordinates - - def set_y(self, y): - """Set y axis value.""" - self.cy = y - self.set_polCrd() # update polar coordinates - - def set_r(self, radius): - """Set radius of the circle.""" - self.radius = radius - - def scale_r(self, scale): - """Scale (multiply) the radius.""" - self.radius = self.radius * scale - - def rotate(self, degree): - """Rotate shape.""" - rad = np.deg2rad(degree) - self.tmin += rad - self.tmax += rad - self.set_polCrd() # update polar coordinates - - def theta_min(self, degree): - """Angle to determine one the cut out piece in circular mask.""" - rad = np.deg2rad(degree) - self.tmin = rad - # ensure stop angle > start angle - if self.tmax <= self.tmin: - self.tmax += 2*np.pi - # ensure stop angle- 2*np.pi NOT > start angle - if self.tmax - 2*np.pi >= self.tmin: - self.tmax -= 2*np.pi - # update polar coordinates - self.set_polCrd() - - def theta_max(self, degree): - """Angle to determine one the cut out piece in circular mask.""" - rad = np.deg2rad(degree) - self.tmax = rad - # ensure stop angle > start angle - if self.tmax <= self.tmin: - self.tmax += 2*np.pi - # ensure stop angle- 2*np.pi NOT > start angle - if self.tmax - 2*np.pi >= self.tmin: - self.tmax -= 2*np.pi - # update polar coordinates - self.set_polCrd() - - def binaryMask(self): - """Return a boolean mask for a circular sector.""" - # circular mask - self.circmask = self.r2 <= self.radius*self.radius - # angular mask - self.anglemask = self.theta <= (self.tmax-self.tmin) - # return binary mask - return self.circmask*self.anglemask - - def contains(self, event): - """Check if a cursor pointer is inside the sector mask.""" - xbin = np.floor(event.xdata) - ybin = np.floor(event.ydata) - Mask = self.binaryMask() - # the next line doesn't follow pep 8 (otherwise it fails) - if Mask[ybin][xbin] is True: # switch x and ybin, volHistMask not Cart - return True - else: - return False - - def draw(self, ax, cmap='Reds', alpha=0.2, vmin=0.1, - interpolation='nearest', origin='lower', extent=[0, 100, 0, 100]): - """Draw stuff.""" - BinMask = self.binaryMask() - FigObj = ax.imshow( - BinMask, - cmap=cmap, - alpha=alpha, - vmin=vmin, - interpolation=interpolation, - origin=origin, - extent=extent) - return (FigObj, BinMask) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 295a963b..d5a3cee6 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -23,17 +23,16 @@ from __future__ import division import numpy as np +import config as cfg import matplotlib.pyplot as plt from matplotlib.colors import LogNorm from matplotlib.widgets import Slider, Button, LassoSelector from matplotlib import path from nibabel import load -from segmentator_functions import responsiveObj -from sector_mask import sector_mask from segmentator.utils import map_ima_to_2D_hist, prep_2D_hist from segmentator.utils import truncate_range, scale_range from segmentator.utils import set_gradient_magnitude -import config as cfg +from gui_utils import sector_mask, responsiveObj """Load Data""" nii = load(cfg.filename) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index ae130294..2721454a 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -22,15 +22,15 @@ from __future__ import division import numpy as np -import matplotlib.pyplot as plt import config as cfg -from nibabel import load +import matplotlib.pyplot as plt from matplotlib.colors import LogNorm, ListedColormap, BoundaryNorm from matplotlib.widgets import Slider, Button, RadioButtons +from nibabel import load from segmentator.utils import map_ima_to_2D_hist, prep_2D_hist from segmentator.utils import truncate_range, scale_range from segmentator.utils import set_gradient_magnitude -from segmentator_functions import responsiveObj +from gui_utils import responsiveObj # %% """Load Data""" diff --git a/segmentator/utils.py b/segmentator/utils.py index 30c3370b..f8adc09c 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -296,7 +296,7 @@ def set_gradient_magnitude(image, gramag_option): image : np.ndarray First image, which is often the intensity image (eg. T1w). gramag_option : string - A keyword string or a path to a nigti file. + A keyword string or a path to a nifti file. Returns ------- @@ -323,7 +323,7 @@ def aniso_diff_3D(stack, niter=1, kappa=50, gamma=0.1, step=(1., 1., 1.), Disclosure ---------- This script is adapted from a stackoverflow post by user ali_m: - [1] http://stackoverflow.com/questions/10802611/anisotropic-diffusion-2d-images #noqa + [1] http://stackoverflow.com/questions/10802611/anisotropic-diffusion-2d-images [2] http://pastebin.com/sBsPX4Y7 Parameters From a011129e67efa0768d9ad00f8b8f5ff349bcbea8 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 22 Sep 2017 18:18:03 +0200 Subject: [PATCH 080/100] Image slice is not reset anymore. Resetting image slice was expecially annoying when used with ncuts. --- segmentator/gui_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 3990f5eb..855ce27b 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -317,12 +317,8 @@ def resetGlobal(self, event): """Reset stuff.""" # reset color bar self.sHistC.reset() - # reset ima browser slider - self.sSliceNr.reset() # reset transparency self.TranspVal = 0.5 - # reset slice number - self.sliceNr = int(self.sSliceNr.val*self.orig.shape[2]) # update brain slice self.updateSlc() if self.segmType == 'main': From a42bcba4ced514e9aebe254a2da07a8ff08dd36b Mon Sep 17 00:00:00 2001 From: ofgulban Date: Fri, 22 Sep 2017 20:10:25 +0200 Subject: [PATCH 081/100] Rotation button (work in progress). It is working but a bit wonky for now. Remaining ssues are: - Not working immediately upon initialization. - Aspect ratio sometimes not right after usage. - Rotation is forgotten upon slice updates. --- segmentator/gui_utils.py | 81 +++++++++++++++++++----------- segmentator/segmentator_main.py | 89 +++++++++++++-------------------- segmentator/segmentator_ncut.py | 57 +++++++++++---------- 3 files changed, 115 insertions(+), 112 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 855ce27b..5cd29d04 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -36,7 +36,7 @@ def __init__(self, **kwargs): self.press = None self.ctrlHeld = False self.labelNr = 0 - self.imaMaskSwitchCount = 0 + self.imaSlcMaskSwitchCount = 0 self.TranspVal = 0.5 self.nrExports = 0 self.entropWin = 0 @@ -53,21 +53,27 @@ def updateMsks(self): self.labelContours() self.volHistMaskH.set_data(self.volHistMask) self.volHistMaskH.set_extent((0, self.nrBins, self.nrBins, 0)) - # update imaMask - self.imaMask = map_2D_hist_to_ima( + self.updateImaSlcMsk() + self.figure.canvas.draw() # draw to canvas + + def updateImaSlcMsk(self): + """Update image slice mask.""" + self.imaSlcMask = map_2D_hist_to_ima( self.invHistVolume[:, :, self.sliceNr], self.volHistMask) if self.borderSwitch == 1: - self.imaMask = self.calcImaMaskBrd() - self.imaMaskH.set_data(self.imaMask) - self.imaMaskH.set_extent((0, self.imaMask.shape[1], - self.imaMask.shape[0], 0)) - # draw to canvas - self.figure.canvas.draw() + self.imaSlcMask = self.calcImaMaskBrd() + self.updateSlcMsk(self.imaSlcMask) + + def updateSlc(self, array_2d): + """Update image browser slice.""" + self.imaSlcH.set_data(array_2d) + self.imaSlcH.set_extent((0, self.orig.shape[1], self.orig.shape[0], 0)) - def updateSlc(self): - """Update image browser slices.""" - self.slcH.set_data(self.orig[:, :, self.sliceNr]) - self.slcH.set_extent((0, self.orig.shape[1], self.orig.shape[0], 0)) + def updateSlcMsk(self, array_2d): + """Update image browser slice mask.""" + self.imaSlcMaskH.set_data(array_2d) + self.imaSlcMaskH.set_extent((0, self.orig.shape[1], self.orig.shape[0], + 0)) def connect(self): """Make the object responsive.""" @@ -87,11 +93,11 @@ def on_key_press(self, event): if event.key == 'control': self.ctrlHeld = True elif event.key == 'q': - self.imaMaskIncr(-0.1) + self.imaSlcMaskIncr(-0.1) elif event.key == 'w': - self.imaMaskTransSwitch() + self.imaSlcMaskTransSwitch() elif event.key == 'e': - self.imaMaskIncr(0.1) + self.imaSlcMaskIncr(0.1) elif event.key == '1': self.borderSwitch = (self.borderSwitch + 1) % 2 self.updateMsks() @@ -264,11 +270,16 @@ def updateColorBar(self, val): histVMax = np.power(10, self.sHistC.val) plt.clim(vmax=histVMax) + def updateSliceNr(self): + """Update slice number and the selected slice.""" + self.sliceNr = int(self.sSliceNr.val*self.orig.shape[2]) + self.imaSlc = self.orig[:, :, self.sliceNr] # selected slice + def updateImaBrowser(self, val): """Update image browse.""" # scale slider value [0,1) to dimension index - self.sliceNr = int(self.sSliceNr.val*self.orig.shape[2]) - self.updateSlc() # update brain slice + self.updateSliceNr() + self.updateSlc(self.imaSlc) # update brain slice self.updateMsks() def cycleView(self, event): @@ -279,11 +290,21 @@ def cycleView(self, event): # transpose ima2volHistMap self.invHistVolume = np.transpose(self.invHistVolume, (2, 0, 1)) # update slice number - self.sliceNr = int(self.sSliceNr.val*self.orig.shape[2]) + self.updateSliceNr() # update brain slice - self.updateSlc() + self.updateSlc(self.imaSlc) self.updateMsks() + def rotateView(self, event): + """Rotate image slice 90 degrees.""" + # rotate data + self.imaSlc = np.rot90(self.imaSlc, axes=(0, 1)) + self.imaSlcMask = np.rot90(self.imaSlcMask, axes=(0, 1)) + # update brain slice + self.updateSlc(self.imaSlc) + self.updateSlcMsk(self.imaSlcMask) + self.figure.canvas.draw() # draw to canvas + def exportNifti(self, event): """Export labels in the image browser as a nifti file.""" print "start exporting labels..." @@ -320,7 +341,7 @@ def resetGlobal(self, event): # reset transparency self.TranspVal = 0.5 # update brain slice - self.updateSlc() + self.updateSlc(self.imaSlc) if self.segmType == 'main': if self.lassoSwitchCount == 1: # reset only lasso drawing self.idxLasso = np.zeros(self.nrBins*self.nrBins, dtype=bool) @@ -395,20 +416,20 @@ def updateLabels(self, val): else: return - def imaMaskIncr(self, incr): + def imaSlcMaskIncr(self, incr): """Update transparency of image mask by increment.""" if (self.TranspVal + incr >= 0) & (self.TranspVal + incr <= 1): self.TranspVal += incr - self.imaMaskH.set_alpha(self.TranspVal) + self.imaSlcMaskH.set_alpha(self.TranspVal) self.updateMsks() - def imaMaskTransSwitch(self): + def imaSlcMaskTransSwitch(self): """Update transparency of image mask to toggle transparency of it.""" - self.imaMaskSwitchCount = (self.imaMaskSwitchCount+1) % 2 - if self.imaMaskSwitchCount == 1: # set imaMask transp - self.imaMaskH.set_alpha(0) - else: # set imaMask opaque - self.imaMaskH.set_alpha(self.TranspVal) + self.imaSlcMaskSwitchCount = (self.imaSlcMaskSwitchCount+1) % 2 + if self.imaSlcMaskSwitchCount == 1: # set imaSlcMask transp + self.imaSlcMaskH.set_alpha(0) + else: # set imaSlcMask opaque + self.imaSlcMaskH.set_alpha(self.TranspVal) self.updateMsks() def updateLabelsRadio(self, val): @@ -437,7 +458,7 @@ def lassoArr(self, array, indices): def calcImaMaskBrd(self): """Calculate borders of image mask slice.""" - grad = np.gradient(self.imaMask) + grad = np.gradient(self.imaSlcMask) return np.greater(np.sqrt(np.power(grad[0], 2) + np.power(grad[1], 2)), 0) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index d5a3cee6..f7d90ec7 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -76,24 +76,16 @@ # plot 3D ima by default ax2 = fig.add_subplot(122) -slcH = ax2.imshow( - orig[:, :, int(dims[2]/2)], - cmap=plt.cm.gray, - vmin=ima.min(), - vmax=ima.max(), - interpolation='none', - extent=[0, dims[1], dims[0], 0] - ) - -imaMask = np.ones(dims[0:2]) -imaMaskH = ax2.imshow( - imaMask, - cmap=palette, - vmin=0.1, - interpolation='none', - alpha=0.5, - extent=[0, dims[1], dims[0], 0] - ) +sliceNr = int(0.5*dims[2]) +imaSlcH = ax2.imshow(orig[:, :, sliceNr], cmap=plt.cm.gray, vmin=ima.min(), + vmax=ima.max(), interpolation='none', + extent=[0, dims[1], dims[0], 0]) + +imaSlcMask = np.ones(dims[0:2]) +imaSlcMaskH = ax2.imshow(imaSlcMask, cmap=palette, vmin=0.1, + interpolation='none', alpha=0.5, + extent=[0, dims[1], dims[0], 0]) + # adjust subplots on figure bottom = 0.30 fig.subplots_adjust(bottom=bottom) @@ -103,42 +95,26 @@ # """Initialisation""" # create first instance of sector mask -sectorObj = sector_mask( - (nr_bins, nr_bins), - cfg.init_centre, - cfg.init_radius, - cfg.init_theta - ) +sectorObj = sector_mask((nr_bins, nr_bins), cfg.init_centre, cfg.init_radius, + cfg.init_theta) # draw sector mask for the first time -volHistMaskH, volHistMask = sectorObj.draw( - ax, - cmap='Reds', - alpha=0.2, - vmin=0.1, - interpolation='nearest', - origin='lower', - extent=[0, nr_bins, 0, nr_bins] - ) +volHistMaskH, volHistMask = sectorObj.draw(ax, cmap='Reds', alpha=0.2, + vmin=0.1, interpolation='nearest', + origin='lower', + extent=[0, nr_bins, 0, nr_bins]) # initiate a flexible figure object, pass to it usefull properties -segmType = 'main' idxLasso = np.zeros(nr_bins*nr_bins, dtype=bool) lassoSwitchCount = 0 -flexFig = responsiveObj(figure=ax.figure, - axes=ax.axes, - axes2=ax2.axes, - segmType=segmType, - orig=orig, - nii=nii, +flexFig = responsiveObj(figure=ax.figure, axes=ax.axes, axes2=ax2.axes, + segmType='main', orig=orig, nii=nii, sectorObj=sectorObj, nrBins=nr_bins, - sliceNr=int(0.5*dims[2]), - slcH=slcH, - imaMask=imaMask, - imaMaskH=imaMaskH, - volHistMask=volHistMask, - volHistMaskH=volHistMaskH, + sliceNr=sliceNr, + imaSlcH=imaSlcH, + imaSlcMask=imaSlcMask, imaSlcMaskH=imaSlcMaskH, + volHistMask=volHistMask, volHistMaskH=volHistMaskH, contains=volHistMaskH.contains, counts=counts, idxLasso=idxLasso, @@ -153,7 +129,7 @@ # """Sliders and Buttons""" # colorbar slider -axcolor = 'lightgoldenrodyellow' +axcolor, hovcolor = '0.875', '0.975' axHistC = plt.axes([0.15, bottom-0.20, 0.25, 0.025], axisbg=axcolor) flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, cfg.cbar_max, valinit=cfg.cbar_init, valfmt='%0.1f') @@ -172,24 +148,30 @@ valinit=cfg.init_theta[1]-0.1, valfmt='%0.1f') # cycle button -cycleax = plt.axes([0.55, bottom-0.285, 0.075, 0.075]) -flexFig.bCycle = Button(cycleax, 'Cycle\nView', - color=axcolor, hovercolor='0.975') +cycleax = plt.axes([0.55, bottom-0.2475, 0.075, 0.0375]) +flexFig.bCycle = Button(cycleax, 'Cycle', + color=axcolor, hovercolor=hovcolor) flexFig.cycleCount = 0 +# rotate button +rotateax = plt.axes([0.55, bottom-0.285, 0.075, 0.0375]) +flexFig.bRotate = Button(rotateax, 'Rotate', + color=axcolor, hovercolor=hovcolor) +flexFig.rotationCount = 0 + # export nii button exportax = plt.axes([0.75, bottom-0.285, 0.075, 0.075]) flexFig.bExport = Button(exportax, 'Export\nNifti', - color=axcolor, hovercolor='0.975') + color=axcolor, hovercolor=hovcolor) # export nyp button exportax = plt.axes([0.85, bottom-0.285, 0.075, 0.075]) flexFig.bExportNyp = Button(exportax, 'Export\nHist', - color=axcolor, hovercolor='0.975') + color=axcolor, hovercolor=hovcolor) # reset button resetax = plt.axes([0.65, bottom-0.285, 0.075, 0.075]) -flexFig.bReset = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975') +flexFig.bReset = Button(resetax, 'Reset', color=axcolor, hovercolor=hovcolor) # @@ -199,6 +181,7 @@ flexFig.sThetaMin.on_changed(flexFig.updateThetaMin) flexFig.sThetaMax.on_changed(flexFig.updateThetaMax) flexFig.bCycle.on_clicked(flexFig.cycleView) +flexFig.bRotate.on_clicked(flexFig.rotateView) flexFig.bExport.on_clicked(flexFig.exportNifti) flexFig.bExportNyp.on_clicked(flexFig.exportNyp) flexFig.bReset.on_clicked(flexFig.resetGlobal) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 2721454a..9c2ea0f2 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -118,14 +118,15 @@ # plot 3D ima by default ax2 = fig.add_subplot(122) -slcH = ax2.imshow(orig[:, :, int(dims[2]/2)], cmap=plt.cm.gray, - vmin=ima.min(), vmax=ima.max(), interpolation='none', - extent=[0, dims[1], dims[0], 0]) -imaMask = np.zeros(dims[0:2])*total_labels[1] -imaMaskH = ax2.imshow(imaMask, interpolation='none', alpha=0.5, - cmap=ncut_palette, vmin=np.min(ncut_labels)+1, - vmax=lMax, - extent=[0, dims[1], dims[0], 0]) +sliceNr = int(0.5*dims[2]) +imaSlcH = ax2.imshow(orig[:, :, sliceNr], cmap=plt.cm.gray, + vmin=ima.min(), vmax=ima.max(), interpolation='none', + extent=[0, dims[1], dims[0], 0]) +imaSlcMask = np.zeros(dims[0:2])*total_labels[1] +imaSlcMaskH = ax2.imshow(imaSlcMask, interpolation='none', alpha=0.5, + cmap=ncut_palette, vmin=np.min(ncut_labels)+1, + vmax=lMax, + extent=[0, dims[1], dims[0], 0]) # adjust subplots on figure bottom = 0.30 @@ -136,30 +137,21 @@ # %% """Initialisation""" -segmType = 'ncut' # initiate a flexible figure object, pass to it usefull properties -flexFig = responsiveObj(figure=ax.figure, - axes=ax.axes, - axes2=ax2.axes, - segmType=segmType, - orig=orig, - nii=nii, - ima=ima, +flexFig = responsiveObj(figure=ax.figure, axes=ax.axes, axes2=ax2.axes, + segmType='ncut', orig=orig, nii=nii, ima=ima, nrBins=nr_bins, - sliceNr=int(0.5*dims[2]), - slcH=slcH, - imaMask=imaMask, - imaMaskH=imaMaskH, + sliceNr=sliceNr, + imaSlcH=imaSlcH, + imaSlcMask=imaSlcMask, imaSlcMaskH=imaSlcMaskH, volHistMask=volHistMask, volHistMaskH=volHistMaskH, - pltMap=pltMap, - pltMapH=pltMapH, + pltMap=pltMap, pltMapH=pltMapH, counterField=np.zeros((nr_bins, nr_bins)), orig_ncut_labels=orig_ncut_labels, ima_ncut_labels=ima_ncut_labels, initTpl=(cfg.perc_min, cfg.perc_max, cfg.scale), - lMax=lMax - ) + lMax=lMax) # make the figure responsive to clicks flexFig.connect() @@ -169,7 +161,7 @@ # %% """Sliders and Buttons""" -axcolor = 'lightgoldenrodyellow' +axcolor, hovcolor = '0.875', '0.975' # radio buttons (ugly but good enough for now) rax = plt.axes([0.91, 0.35, 0.08, 0.5], axisbg=(0.75, 0.75, 0.75)) @@ -194,22 +186,28 @@ # cycle button cycleax = plt.axes([0.55, bottom-0.285, 0.075, 0.075]) flexFig.bCycle = Button(cycleax, 'Cycle\nView', - color=axcolor, hovercolor='0.975') + color=axcolor, hovercolor=hovcolor) flexFig.cycleCount = 0 +# rotate button +rotateax = plt.axes([0.55, bottom-0.285, 0.075, 0.0375]) +flexFig.bRotate = Button(rotateax, 'Rotate', + color=axcolor, hovercolor=hovcolor) +flexFig.rotationCount = 0 + # export nii button exportax = plt.axes([0.75, bottom-0.285, 0.075, 0.075]) flexFig.bExport = Button(exportax, 'Export\nNifti', - color=axcolor, hovercolor='0.975') + color=axcolor, hovercolor=hovcolor) # export nyp button exportax = plt.axes([0.85, bottom-0.285, 0.075, 0.075]) flexFig.bExportNyp = Button(exportax, 'Export\nHist', - color=axcolor, hovercolor='0.975') + color=axcolor, hovercolor=hovcolor) # reset button resetax = plt.axes([0.65, bottom-0.285, 0.075, 0.075]) -flexFig.bReset = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975') +flexFig.bReset = Button(resetax, 'Reset', color=axcolor, hovercolor=hovcolor) # %% @@ -218,6 +216,7 @@ flexFig.sSliceNr.on_changed(flexFig.updateImaBrowser) flexFig.sLabelNr.on_changed(flexFig.updateLabels) flexFig.bCycle.on_clicked(flexFig.cycleView) +flexFig.bRotate.on_clicked(flexFig.rotateView) flexFig.bExport.on_clicked(flexFig.exportNifti) flexFig.bExportNyp.on_clicked(flexFig.exportNyp) flexFig.bReset.on_clicked(flexFig.resetGlobal) From cd213224c335587dc9693ca5dc8694e0ef707538 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 25 Sep 2017 12:10:29 +0200 Subject: [PATCH 082/100] change cbar_init default back to 3 --- segmentator/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segmentator/config.py b/segmentator/config.py index 3e6746d8..515ab8ac 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -16,7 +16,7 @@ perc_max = 97.5 scale = 400 cbar_max = 5.0 -cbar_init = 2.0 +cbar_init = 3.0 discard_zeros = True # possible gradient magnitude computation keyword options From 383ad869b82fc6f0daa0054da8c80d0ee85f05d0 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 25 Sep 2017 18:51:34 +0200 Subject: [PATCH 083/100] Rotate is working, lasso on/off indication added + cosmetics + under the hood changes --- segmentator/gui_utils.py | 201 +++++++++++++++++++++----------- segmentator/segmentator_main.py | 30 +++-- segmentator/segmentator_ncut.py | 14 +-- 3 files changed, 156 insertions(+), 89 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 5cd29d04..35413944 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -36,14 +36,24 @@ def __init__(self, **kwargs): self.press = None self.ctrlHeld = False self.labelNr = 0 - self.imaSlcMaskSwitchCount = 0 + self.imaSlcMskSwitchCount = 0 self.TranspVal = 0.5 self.nrExports = 0 self.entropWin = 0 self.borderSwitch = 0 + self.imaSlc = self.orig[:, :, self.sliceNr] # selected slice + self.cycleCount = 0 + self.rotationCount = 0 + + def remapMsks(self, remap_slice=True): + """Update volume histogram to image mapping. - def updateMsks(self): - """Update volume histogram mask.""" + Parameters + ---------- + remap_slice : bool + Do histogram to image mapping. Used to map displayed slice mask. + + """ if self.segmType == 'main': self.volHistMask = self.sectorObj.binaryMask() self.volHistMask = self.lassoArr(self.volHistMask, @@ -53,27 +63,24 @@ def updateMsks(self): self.labelContours() self.volHistMaskH.set_data(self.volHistMask) self.volHistMaskH.set_extent((0, self.nrBins, self.nrBins, 0)) - self.updateImaSlcMsk() - self.figure.canvas.draw() # draw to canvas - - def updateImaSlcMsk(self): - """Update image slice mask.""" - self.imaSlcMask = map_2D_hist_to_ima( - self.invHistVolume[:, :, self.sliceNr], self.volHistMask) - if self.borderSwitch == 1: - self.imaSlcMask = self.calcImaMaskBrd() - self.updateSlcMsk(self.imaSlcMask) - - def updateSlc(self, array_2d): - """Update image browser slice.""" - self.imaSlcH.set_data(array_2d) - self.imaSlcH.set_extent((0, self.orig.shape[1], self.orig.shape[0], 0)) - - def updateSlcMsk(self, array_2d): - """Update image browser slice mask.""" - self.imaSlcMaskH.set_data(array_2d) - self.imaSlcMaskH.set_extent((0, self.orig.shape[1], self.orig.shape[0], - 0)) + # histogram to image mapping + if remap_slice: + self.imaSlcMsk = map_2D_hist_to_ima( + self.invHistVolume[:, :, self.sliceNr], self.volHistMask) + if self.borderSwitch == 1: + self.imaSlcMsk = self.calcImaMaskBrd() + + def updatePanels(self, update_slice=True, update_rotation=False, + update_extent=False): + """Update histogram and image panels.""" + if update_rotation: + self.checkRotation() + if update_extent: + self.updateImaExtent() + if update_slice: + self.imaSlcH.set_data(self.imaSlc) + self.imaSlcMskH.set_data(self.imaSlcMsk) + self.figure.canvas.draw() def connect(self): """Make the object responsive.""" @@ -93,27 +100,38 @@ def on_key_press(self, event): if event.key == 'control': self.ctrlHeld = True elif event.key == 'q': - self.imaSlcMaskIncr(-0.1) + self.imaSlcMskIncr(-0.1) elif event.key == 'w': - self.imaSlcMaskTransSwitch() + self.imaSlcMskTransSwitch() elif event.key == 'e': - self.imaSlcMaskIncr(0.1) + self.imaSlcMskIncr(0.1) elif event.key == '1': self.borderSwitch = (self.borderSwitch + 1) % 2 - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=False, + update_extent=False) + if self.segmType == 'main': if event.key == 'up': self.sectorObj.scale_r(1.05) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) elif event.key == 'down': self.sectorObj.scale_r(0.95) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) elif event.key == 'right': self.sectorObj.rotate(-10.0) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) elif event.key == 'left': self.sectorObj.rotate(10.0) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) else: return @@ -167,10 +185,14 @@ def on_press(self, event): # increase/decrease radius of the sector mask if self.ctrlHeld is False: # ctrl no self.sectorObj.scale_r(1.05) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) elif self.ctrlHeld is True: # ctrl yes self.sectorObj.rotate(10.0) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) else: return elif event.button == 3: # right button @@ -179,10 +201,14 @@ def on_press(self, event): # rotate the sector mask if self.ctrlHeld is False: # ctrl no self.sectorObj.scale_r(0.95) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) elif self.ctrlHeld is True: # ctrl yes self.sectorObj.rotate(-10.0) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) else: return elif self.segmType == 'ncut': @@ -207,7 +233,9 @@ def on_press(self, event): # replace old values with new values (in clicked subfield) self.volHistMask[oLabels == val] = np.copy( nLabels[oLabels == val]) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) elif event.inaxes == self.axes2: # cursor in right plot (brow) self.findVoxInHist(event) @@ -221,7 +249,9 @@ def on_press(self, event): # fetch the slider value to get label nr self.volHistMask[self.volHistMask == val] = \ np.copy(self.labelNr) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) def on_motion(self, event): """Determine what happens if mouse button moves.""" @@ -242,10 +272,12 @@ def on_motion(self, event): dx = event.ydata - ypress # update x and y position of sector, # based on past motion of cursor - self.sectorObj.set_x(x0+dx) - self.sectorObj.set_y(y0+dy) + self.sectorObj.set_x(x0 + dx) + self.sectorObj.set_y(y0 + dy) # update masks - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) else: return @@ -273,37 +305,59 @@ def updateColorBar(self, val): def updateSliceNr(self): """Update slice number and the selected slice.""" self.sliceNr = int(self.sSliceNr.val*self.orig.shape[2]) - self.imaSlc = self.orig[:, :, self.sliceNr] # selected slice + self.imaSlc = self.orig[:, :, self.sliceNr] def updateImaBrowser(self, val): """Update image browse.""" # scale slider value [0,1) to dimension index self.updateSliceNr() - self.updateSlc(self.imaSlc) # update brain slice - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=True, update_rotation=True, + update_extent=True) + + def updateImaExtent(self): + """Update both image and mask extent in image browser.""" + self.imaSlcH.set_extent((0, self.imaSlc.shape[1], + self.imaSlc.shape[0], 0)) + self.imaSlcMskH.set_extent((0, self.imaSlc.shape[1], + self.imaSlc.shape[0], 0)) def cycleView(self, event): """Cycle through views.""" - self.cycleCount = (self.cycleCount+1) % 3 + self.cycleCount = (self.cycleCount + 1) % 3 # transpose data self.orig = np.transpose(self.orig, (2, 0, 1)) # transpose ima2volHistMap self.invHistVolume = np.transpose(self.invHistVolume, (2, 0, 1)) - # update slice number + # updates self.updateSliceNr() - # update brain slice - self.updateSlc(self.imaSlc) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=True, update_rotation=True, + update_extent=True) - def rotateView(self, event): + def rotateIma90(self, axes=(0, 1)): """Rotate image slice 90 degrees.""" # rotate data - self.imaSlc = np.rot90(self.imaSlc, axes=(0, 1)) - self.imaSlcMask = np.rot90(self.imaSlcMask, axes=(0, 1)) + self.imaSlc = np.rot90(self.imaSlc, axes=axes) + self.imaSlcMsk = np.rot90(self.imaSlcMsk, axes=axes) + + def changeRotation(self, event): + """Change rotation of image after clicking the button.""" + self.rotationCount = (self.rotationCount + 1) % 4 + self.rotateIma90() # update brain slice - self.updateSlc(self.imaSlc) - self.updateSlcMsk(self.imaSlcMask) - self.figure.canvas.draw() # draw to canvas + self.updatePanels(update_slice=True, update_rotation=False, + update_extent=True) + + def checkRotation(self): + """Check rotation update if changed.""" + if self.rotationCount == 1: # 90 + self.rotateIma90(axes=(0, 1)) + elif self.rotationCount == 2: # 180 + self.imaSlc = self.imaSlc[::-1, ::-1] + self.imaSlcMsk = self.imaSlcMsk[::-1, ::-1] + elif self.rotationCount == 3: # 270 + self.rotateIma90(axes=(1, 0)) def exportNifti(self, event): """Export labels in the image browser as a nifti file.""" @@ -340,8 +394,6 @@ def resetGlobal(self, event): self.sHistC.reset() # reset transparency self.TranspVal = 0.5 - # update brain slice - self.updateSlc(self.imaSlc) if self.segmType == 'main': if self.lassoSwitchCount == 1: # reset only lasso drawing self.idxLasso = np.zeros(self.nrBins*self.nrBins, dtype=bool) @@ -368,14 +420,19 @@ def resetGlobal(self, event): # reset political borders self.pltMap = np.zeros((self.nrBins, self.nrBins)) self.pltMapH.set_data(self.pltMap) - self.updateMsks() + self.updateSliceNr() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) def updateThetaMin(self, val): """Update theta (min) in volume histogram mask.""" if self.segmType == 'main': theta_val = self.sThetaMin.val # get theta value from slider self.sectorObj.theta_min(theta_val) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) else: return @@ -384,7 +441,9 @@ def updateThetaMax(self, val): if self.segmType == 'main': theta_val = self.sThetaMax.val # get theta value from slider self.sectorObj.theta_max(theta_val) - self.updateMsks() + self.remapMsks() + self.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) else: return @@ -416,21 +475,21 @@ def updateLabels(self, val): else: return - def imaSlcMaskIncr(self, incr): + def imaSlcMskIncr(self, incr): """Update transparency of image mask by increment.""" if (self.TranspVal + incr >= 0) & (self.TranspVal + incr <= 1): self.TranspVal += incr - self.imaSlcMaskH.set_alpha(self.TranspVal) - self.updateMsks() + self.imaSlcMskH.set_alpha(self.TranspVal) + self.figure.canvas.draw() - def imaSlcMaskTransSwitch(self): + def imaSlcMskTransSwitch(self): """Update transparency of image mask to toggle transparency of it.""" - self.imaSlcMaskSwitchCount = (self.imaSlcMaskSwitchCount+1) % 2 - if self.imaSlcMaskSwitchCount == 1: # set imaSlcMask transp - self.imaSlcMaskH.set_alpha(0) - else: # set imaSlcMask opaque - self.imaSlcMaskH.set_alpha(self.TranspVal) - self.updateMsks() + self.imaSlcMskSwitchCount = (self.imaSlcMskSwitchCount+1) % 2 + if self.imaSlcMskSwitchCount == 1: # set imaSlcMsk transp + self.imaSlcMskH.set_alpha(0) + else: # set imaSlcMsk opaque + self.imaSlcMskH.set_alpha(self.TranspVal) + self.figure.canvas.draw() def updateLabelsRadio(self, val): """Update labels with radio buttons.""" @@ -458,7 +517,7 @@ def lassoArr(self, array, indices): def calcImaMaskBrd(self): """Calculate borders of image mask slice.""" - grad = np.gradient(self.imaSlcMask) + grad = np.gradient(self.imaSlcMsk) return np.greater(np.sqrt(np.power(grad[0], 2) + np.power(grad[1], 2)), 0) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index f7d90ec7..643cfaf5 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -81,10 +81,10 @@ vmax=ima.max(), interpolation='none', extent=[0, dims[1], dims[0], 0]) -imaSlcMask = np.ones(dims[0:2]) -imaSlcMaskH = ax2.imshow(imaSlcMask, cmap=palette, vmin=0.1, - interpolation='none', alpha=0.5, - extent=[0, dims[1], dims[0], 0]) +imaSlcMsk = np.ones(dims[0:2]) +imaSlcMskH = ax2.imshow(imaSlcMsk, cmap=palette, vmin=0.1, + interpolation='none', alpha=0.5, + extent=[0, dims[1], dims[0], 0]) # adjust subplots on figure bottom = 0.30 @@ -113,7 +113,7 @@ nrBins=nr_bins, sliceNr=sliceNr, imaSlcH=imaSlcH, - imaSlcMask=imaSlcMask, imaSlcMaskH=imaSlcMaskH, + imaSlcMsk=imaSlcMsk, imaSlcMskH=imaSlcMskH, volHistMask=volHistMask, volHistMaskH=volHistMaskH, contains=volHistMaskH.contains, counts=counts, @@ -125,6 +125,7 @@ flexFig.connect() ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=bin_edges) flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) +flexFig.updatePanels() # """Sliders and Buttons""" @@ -151,13 +152,11 @@ cycleax = plt.axes([0.55, bottom-0.2475, 0.075, 0.0375]) flexFig.bCycle = Button(cycleax, 'Cycle', color=axcolor, hovercolor=hovcolor) -flexFig.cycleCount = 0 # rotate button rotateax = plt.axes([0.55, bottom-0.285, 0.075, 0.0375]) flexFig.bRotate = Button(rotateax, 'Rotate', color=axcolor, hovercolor=hovcolor) -flexFig.rotationCount = 0 # export nii button exportax = plt.axes([0.75, bottom-0.285, 0.075, 0.075]) @@ -181,7 +180,7 @@ flexFig.sThetaMin.on_changed(flexFig.updateThetaMin) flexFig.sThetaMax.on_changed(flexFig.updateThetaMax) flexFig.bCycle.on_clicked(flexFig.cycleView) -flexFig.bRotate.on_clicked(flexFig.rotateView) +flexFig.bRotate.on_clicked(flexFig.changeRotation) flexFig.bExport.on_clicked(flexFig.exportNifti) flexFig.bExportNyp.on_clicked(flexFig.exportNyp) flexFig.bReset.on_clicked(flexFig.resetGlobal) @@ -190,8 +189,11 @@ # """New stuff: Lasso (Experimental)""" # Lasso button +lasso_inactive_colors = ['0.875', '1'] +lasso_active_colors = ['1', '0.875'] lassoax = plt.axes([0.15, bottom-0.285, 0.075, 0.075]) -bLasso = Button(lassoax, 'Lasso\nON OFF', color=axcolor, hovercolor='0.975') +bLasso = Button(lassoax, 'Lasso', color=lasso_inactive_colors[0], + hovercolor=lasso_inactive_colors[1]) def lassoSwitch(event): @@ -202,9 +204,13 @@ def lassoSwitch(event): if flexFig.lassoSwitchCount == 1: # enable lasso flexFig.disconnect() # disable drag function of sector mask lasso = LassoSelector(ax, onselect) + bLasso.color = lasso_active_colors[0] + bLasso.hovercolor = lasso_active_colors[1] else: # disable lasso - lasso = [] # I am not sure we want to reset lasso with this button + # lasso = [] # I am not sure we want to reset lasso with this button flexFig.connect() # enable drag function of sector mask + bLasso.color = lasso_inactive_colors[0] + bLasso.hovercolor = lasso_inactive_colors[1] # Pixel coordinates @@ -220,7 +226,9 @@ def onselect(verts): newLasIdx = p.contains_points(pix, radius=1.5) # new lasso indices flexFig.idxLasso[newLasIdx] = True # updated old lasso indices # update volume histogram mask - flexFig.updateMsks() + flexFig.remapMsks() + flexFig.updatePanels(update_slice=False, update_rotation=True, + update_extent=False) bLasso.on_clicked(lassoSwitch) diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 9c2ea0f2..0d9c605c 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -122,11 +122,11 @@ imaSlcH = ax2.imshow(orig[:, :, sliceNr], cmap=plt.cm.gray, vmin=ima.min(), vmax=ima.max(), interpolation='none', extent=[0, dims[1], dims[0], 0]) -imaSlcMask = np.zeros(dims[0:2])*total_labels[1] -imaSlcMaskH = ax2.imshow(imaSlcMask, interpolation='none', alpha=0.5, - cmap=ncut_palette, vmin=np.min(ncut_labels)+1, - vmax=lMax, - extent=[0, dims[1], dims[0], 0]) +imaSlcMsk = np.zeros(dims[0:2])*total_labels[1] +imaSlcMskH = ax2.imshow(imaSlcMsk, interpolation='none', alpha=0.5, + cmap=ncut_palette, vmin=np.min(ncut_labels)+1, + vmax=lMax, + extent=[0, dims[1], dims[0], 0]) # adjust subplots on figure bottom = 0.30 @@ -143,7 +143,7 @@ nrBins=nr_bins, sliceNr=sliceNr, imaSlcH=imaSlcH, - imaSlcMask=imaSlcMask, imaSlcMaskH=imaSlcMaskH, + imaSlcMsk=imaSlcMsk, imaSlcMskH=imaSlcMskH, volHistMask=volHistMask, volHistMaskH=volHistMaskH, pltMap=pltMap, pltMapH=pltMapH, @@ -216,7 +216,7 @@ flexFig.sSliceNr.on_changed(flexFig.updateImaBrowser) flexFig.sLabelNr.on_changed(flexFig.updateLabels) flexFig.bCycle.on_clicked(flexFig.cycleView) -flexFig.bRotate.on_clicked(flexFig.rotateView) +flexFig.bRotate.on_clicked(flexFig.changeRotation) flexFig.bExport.on_clicked(flexFig.exportNifti) flexFig.bExportNyp.on_clicked(flexFig.exportNyp) flexFig.bReset.on_clicked(flexFig.resetGlobal) From 83e457cd6fe5572efe12711cdd58509c4b716c5d Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 25 Sep 2017 19:05:36 +0200 Subject: [PATCH 084/100] Gui remembers cycle rotation history. - and initial update work correctly now. --- segmentator/gui_utils.py | 12 +++++++----- segmentator/segmentator_main.py | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 35413944..f69874c2 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -43,7 +43,7 @@ def __init__(self, **kwargs): self.borderSwitch = 0 self.imaSlc = self.orig[:, :, self.sliceNr] # selected slice self.cycleCount = 0 - self.rotationCount = 0 + self.cycRotHistory = [[0, 0], [0, 0], [0, 0]] def remapMsks(self, remap_slice=True): """Update volume histogram to image mapping. @@ -343,7 +343,8 @@ def rotateIma90(self, axes=(0, 1)): def changeRotation(self, event): """Change rotation of image after clicking the button.""" - self.rotationCount = (self.rotationCount + 1) % 4 + self.cycRotHistory[self.cycleCount][1] += 1 + self.cycRotHistory[self.cycleCount][1] %= 4 self.rotateIma90() # update brain slice self.updatePanels(update_slice=True, update_rotation=False, @@ -351,12 +352,13 @@ def changeRotation(self, event): def checkRotation(self): """Check rotation update if changed.""" - if self.rotationCount == 1: # 90 + cyc_rot = self.cycRotHistory[self.cycleCount][1] + if cyc_rot == 1: # 90 self.rotateIma90(axes=(0, 1)) - elif self.rotationCount == 2: # 180 + elif cyc_rot == 2: # 180 self.imaSlc = self.imaSlc[::-1, ::-1] self.imaSlcMsk = self.imaSlcMsk[::-1, ::-1] - elif self.rotationCount == 3: # 270 + elif cyc_rot == 3: # 270 self.rotateIma90(axes=(1, 0)) def exportNifti(self, event): diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 643cfaf5..b40c3856 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -125,7 +125,6 @@ flexFig.connect() ima2volHistMap = map_ima_to_2D_hist(xinput=ima, yinput=gra, bins_arr=bin_edges) flexFig.invHistVolume = np.reshape(ima2volHistMap, dims) -flexFig.updatePanels() # """Sliders and Buttons""" @@ -232,5 +231,7 @@ def onselect(verts): bLasso.on_clicked(lassoSwitch) - +flexFig.remapMsks() +flexFig.updatePanels(update_slice=True, update_rotation=False, + update_extent=False) plt.show() From 44c8470fc1f13c7e60d986b638abafaa9cd58c57 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 25 Sep 2017 19:33:22 +0200 Subject: [PATCH 085/100] Print version in the welcome string. --- segmentator/__init__.py | 18 ++++++++++++++++++ segmentator/__main__.py | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/segmentator/__init__.py b/segmentator/__init__.py index e69de29b..a2f31754 100644 --- a/segmentator/__init__.py +++ b/segmentator/__init__.py @@ -0,0 +1,18 @@ +"""All this stuff is to get the version from setup.py.""" + +from pkg_resources import get_distribution, DistributionNotFound +import os.path + +try: + _dist = get_distribution('segmentator') + # Normalize case for Windows systems + dist_loc = os.path.normcase(_dist.location) + here = os.path.normcase(__file__) + if not here.startswith(os.path.join(dist_loc, 'segmentator')): + # not installed, but there is another version that *is* + raise DistributionNotFound +except DistributionNotFound: + __version__ = 'Version information not found. Please install this project \ + with setup.py)' +else: + __version__ = _dist.version diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 878c1c8e..4a0aa9ee 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -13,6 +13,7 @@ import sys import argparse import config as cfg +from segmentator import __version__ def main(args=None): @@ -122,7 +123,9 @@ def main(args=None): # used in ncut cfg.ncut = args.ncut - print("===========\nSegmentator\n===========") + welcome_str = 'Segmentator ' + __version__ + welcome_decoration = '=' * len(welcome_str) + print(welcome_decoration + '\n' + welcome_str + '\n' + welcome_decoration) # Call other scripts with import method (couldn't find a better way). if args.nogui: From caea1e6f18718f11b5d800a6c9946af3512c36e5 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 25 Sep 2017 20:30:50 +0200 Subject: [PATCH 086/100] Lasso rotation bug fixed. --- segmentator/segmentator_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index b40c3856..8637fbcc 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -227,7 +227,7 @@ def onselect(verts): # update volume histogram mask flexFig.remapMsks() flexFig.updatePanels(update_slice=False, update_rotation=True, - update_extent=False) + update_extent=True) bLasso.on_clicked(lassoSwitch) From ada8a32e1aef42a7e5caabca6eadb999df30d970 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Mon, 25 Sep 2017 20:31:00 +0200 Subject: [PATCH 087/100] Cosmetics. --- segmentator/gui_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index f69874c2..8ab9e379 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -337,7 +337,6 @@ def cycleView(self, event): def rotateIma90(self, axes=(0, 1)): """Rotate image slice 90 degrees.""" - # rotate data self.imaSlc = np.rot90(self.imaSlc, axes=axes) self.imaSlcMsk = np.rot90(self.imaSlcMsk, axes=axes) @@ -346,7 +345,6 @@ def changeRotation(self, event): self.cycRotHistory[self.cycleCount][1] += 1 self.cycRotHistory[self.cycleCount][1] %= 4 self.rotateIma90() - # update brain slice self.updatePanels(update_slice=True, update_rotation=False, update_extent=True) From 509628e34b2119fd1a93f5f9c2fa3c0f91f61616 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 11:27:53 +0200 Subject: [PATCH 088/100] Lasso button label update added. --- segmentator/segmentator_main.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 8637fbcc..5b8941f9 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -188,11 +188,8 @@ # """New stuff: Lasso (Experimental)""" # Lasso button -lasso_inactive_colors = ['0.875', '1'] -lasso_active_colors = ['1', '0.875'] lassoax = plt.axes([0.15, bottom-0.285, 0.075, 0.075]) -bLasso = Button(lassoax, 'Lasso', color=lasso_inactive_colors[0], - hovercolor=lasso_inactive_colors[1]) +bLasso = Button(lassoax, 'Lasso\nOff', color=axcolor, hovercolor=hovcolor) def lassoSwitch(event): @@ -203,13 +200,11 @@ def lassoSwitch(event): if flexFig.lassoSwitchCount == 1: # enable lasso flexFig.disconnect() # disable drag function of sector mask lasso = LassoSelector(ax, onselect) - bLasso.color = lasso_active_colors[0] - bLasso.hovercolor = lasso_active_colors[1] + bLasso.label.set_text("Lasso\nOn") else: # disable lasso # lasso = [] # I am not sure we want to reset lasso with this button flexFig.connect() # enable drag function of sector mask - bLasso.color = lasso_inactive_colors[0] - bLasso.hovercolor = lasso_inactive_colors[1] + bLasso.label.set_text("Lasso\nOff") # Pixel coordinates From fbff17e6eeeea535f48804a620bf4b65f0367041 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 11:37:28 +0200 Subject: [PATCH 089/100] Readme update. Installation instructions for v1.3.0 added. --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0632f755..69fbfb6e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,21 @@ The goal is to provide a complementary tool to the already available brain tissu | [SciPy](http://scipy.org/) | 0.19.1 | ## Installation & Quick Start -Please visit [our wiki](https://github.com/ofgulban/segmentator/wiki/Installation) to see how to install and use Segmentator. +- Make sure you have [**Python 2.7**](https://www.python.org/download/releases/2.7/) and [**pip**](https://en.wikipedia.org/wiki/Pip_(package_manager)) installed. +- Change directory in your command line: +``` +cd \path\to\segmentator +``` +- Install the requirements by running the following command: +``` +pip install -r requirements.txt +``` +- Install Segmentator: +``` +python setup.py install +``` + +Visit [our wiki](https://github.com/ofgulban/segmentator/wiki/Installation) to see alternative installation methods. ## Support Please use [GitHub issues](https://github.com/ofgulban/segmentator/issues) for questions, bug reports or feature requests. From c3848c62d73d117e136b5dc6b544718d50906e72 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 11:40:23 +0200 Subject: [PATCH 090/100] Update readme. Missing quick start added. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 69fbfb6e..51b72799 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The goal is to provide a complementary tool to the already available brain tissu - Make sure you have [**Python 2.7**](https://www.python.org/download/releases/2.7/) and [**pip**](https://en.wikipedia.org/wiki/Pip_(package_manager)) installed. - Change directory in your command line: ``` -cd \path\to\segmentator +cd /path/to/segmentator ``` - Install the requirements by running the following command: ``` @@ -33,6 +33,14 @@ pip install -r requirements.txt ``` python setup.py install ``` +- Simply call segmentator with a nifti file: +``` +segmentator /path/to/file.nii.gz +``` +- See the help for further options: +``` +segmentator --help +``` Visit [our wiki](https://github.com/ofgulban/segmentator/wiki/Installation) to see alternative installation methods. From 0ad2f6a07189eda93b8d871c8450ed73411cad0c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 11:44:11 +0200 Subject: [PATCH 091/100] Update readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51b72799..490c6a89 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ python setup.py install ``` segmentator /path/to/file.nii.gz ``` -- See the help for further options: +- Or see the help for available options: ``` segmentator --help ``` From 9620aeb1d36e79267039cba8acb80e9c2b3845cb Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 12:04:03 +0200 Subject: [PATCH 092/100] Gradient magnitude names simplified. 3D_ prefix removed. --- segmentator/__main__.py | 2 +- segmentator/config.py | 4 ++-- .../tests/wip_test_gradient_magnitude.py | 12 +++++------ segmentator/utils.py | 21 ++++++++++--------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/segmentator/__main__.py b/segmentator/__main__.py index 4a0aa9ee..ac446041 100644 --- a/segmentator/__main__.py +++ b/segmentator/__main__.py @@ -29,7 +29,7 @@ def main(args=None): parser.add_argument( "--gramag", metavar=str(cfg.gramag), required=False, default=cfg.gramag, - help="'3D_scharr', '3D_sobel', '3D_prewitt', 'numpy' \ + help="'scharr', 'sobel', 'prewitt', 'numpy' \ or path to a gradient magnitude nifti." ) parser.add_argument( diff --git a/segmentator/config.py b/segmentator/config.py index 515ab8ac..13d0af44 100755 --- a/segmentator/config.py +++ b/segmentator/config.py @@ -11,7 +11,7 @@ # segmentator main command line variables filename = 'sample_filename_here' -gramag = '3D_scharr' +gramag = 'scharr' perc_min = 2.5 perc_max = 97.5 scale = 400 @@ -20,7 +20,7 @@ discard_zeros = True # possible gradient magnitude computation keyword options -gramag_options = ['3D_scharr', '3D_sobel', '3D_prewitt', 'numpy'] +gramag_options = ['scharr', 'sobel', 'prewitt', 'numpy'] # used in segmentator ncut ncut = False diff --git a/segmentator/tests/wip_test_gradient_magnitude.py b/segmentator/tests/wip_test_gradient_magnitude.py index 60e19914..d2041b09 100644 --- a/segmentator/tests/wip_test_gradient_magnitude.py +++ b/segmentator/tests/wip_test_gradient_magnitude.py @@ -14,19 +14,19 @@ basename = nii.get_filename().split(os.extsep, 1)[0] # 3D Scharr gradient magnitude -gra_mag = compute_gradient_magnitude(ima, method='3D_scharr') +gra_mag = compute_gradient_magnitude(ima, method='scharr') out = Nifti1Image(gra_mag, affine=nii.affine) -save(out, basename + '_3D_scharr.nii.gz') +save(out, basename + '_scharr.nii.gz') # 3D Sobel gradient magnitude -gra_mag = compute_gradient_magnitude(ima, method='3D_sobel') +gra_mag = compute_gradient_magnitude(ima, method='sobel') out = Nifti1Image(gra_mag, affine=nii.affine) -save(out, basename + '_3D_sobel.nii.gz') +save(out, basename + '_sobel.nii.gz') # 3D Prewitt gradient magnitude -gra_mag = compute_gradient_magnitude(ima, method='3D_prewitt') +gra_mag = compute_gradient_magnitude(ima, method='prewitt') out = Nifti1Image(gra_mag, affine=nii.affine) -save(out, basename + '_3D_prewitt.nii.gz') +save(out, basename + '_prewitt.nii.gz') # numpy gradient magnitude gra_mag = compute_gradient_magnitude(ima, method='numpy') diff --git a/segmentator/utils.py b/segmentator/utils.py index f8adc09c..c1bf7b31 100755 --- a/segmentator/utils.py +++ b/segmentator/utils.py @@ -198,7 +198,7 @@ def prep_2D_hist(ima, gra, discard_zeros=True): return counts, volHistH, d_min, d_max, nr_bins, bin_edges -def create_3D_kernel(operator='3D_sobel'): +def create_3D_kernel(operator='sobel'): """Create various 3D kernels. Parameters @@ -211,17 +211,17 @@ def create_3D_kernel(operator='3D_sobel'): kernel : np.ndarray, shape(6, n, n, 3) """ - if operator == '3D_sobel': + if operator == 'sobel': operator = np.array([[[1, 2, 1], [2, 4, 2], [1, 2, 1]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-1, -2, -1], [-2, -4, -2], [-1, -2, -1]]], dtype='float') - elif operator == '3D_prewitt': + elif operator == 'prewitt': operator = np.array([[[1, 1, 1], [1, 1, 1], [1, 1, 1]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]], dtype='float') - elif operator == '3D_scharr': + elif operator == 'scharr': operator = np.array([[[9, 30, 9], [30, 100, 30], [9, 30, 9]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[-9, -30, -9], [-30, -100, -30], [-9, -30, -9]]], @@ -240,7 +240,7 @@ def create_3D_kernel(operator='3D_sobel'): return kernel -def compute_gradient_magnitude(ima, method='3D_scharr'): +def compute_gradient_magnitude(ima, method='scharr'): """Compute gradient magnitude of images. Parameters @@ -248,15 +248,16 @@ def compute_gradient_magnitude(ima, method='3D_scharr'): ima : np.ndarray First image, which is often the intensity image (eg. T1w). method : string - Gradient computation method. Available options are '3D_sobel', - 'scipy_sobel', 'scipy_prewitt', 'numpy'. + Gradient computation method. Available options are 'scharr', + 'sobel', 'prewitt', 'numpy'. Returns ------- gra_mag : np.ndarray Second image, which is often the gradient magnitude image derived from the first image + """ - if method == '3D_sobel': # magnitude scale is similar to numpy method + if method == 'sobel': # magnitude scale is similar to numpy method kernel = create_3D_kernel(operator=method) gra = np.zeros(ima.shape + (kernel.shape[0],)) for d in range(kernel.shape[0]): @@ -264,7 +265,7 @@ def compute_gradient_magnitude(ima, method='3D_scharr'): # compute generic gradient magnitude with normalization gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1)) return gra_mag - elif method == '3D_prewitt': + elif method == 'prewitt': kernel = create_3D_kernel(operator=method) gra = np.zeros(ima.shape + (kernel.shape[0],)) for d in range(kernel.shape[0]): @@ -272,7 +273,7 @@ def compute_gradient_magnitude(ima, method='3D_scharr'): # compute generic gradient magnitude with normalization gra_mag = np.sqrt(np.sum(np.power(gra, 2.), axis=-1)) return gra_mag - elif method == '3D_scharr': + elif method == 'scharr': kernel = create_3D_kernel(operator=method) gra = np.zeros(ima.shape + (kernel.shape[0],)) for d in range(kernel.shape[0]): From c04baf80968dd26cd7ec7ff87eba6e13ff58b13c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 13:01:35 +0200 Subject: [PATCH 093/100] Matplotlib 2.0 compatibility added. matplotlib 1.5.3 will not work anymore. --- segmentator/gui_utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 8ab9e379..8e027e78 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -497,11 +497,7 @@ def updateLabelsRadio(self, val): self.labelNr = int(float(val) * labelScale) def labelContours(self): - """ - Calculate and plot political borders. - - Used in ncut version. - """ + """Plot political borders used in ncut version.""" grad = np.gradient(self.volHistMask) self.pltMap = np.greater(np.sqrt(np.power(grad[0], 2) + np.power(grad[1], 2)), 0) From 48af89bc758396314cadc2a94f06441f66bce7d0 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 13:02:22 +0200 Subject: [PATCH 094/100] Matplotlib 2.0.2 compatibility added. Forgot to add rest of changes in the prev. commit :) --- environment.yml | 2 +- requirements.txt | 2 +- segmentator/segmentator_main.py | 10 +++++----- segmentator/segmentator_ncut.py | 18 ++++++++++-------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/environment.yml b/environment.yml index 8a56c738..3a5aebc6 100644 --- a/environment.yml +++ b/environment.yml @@ -8,5 +8,5 @@ dependencies: - pytest-cov>=2.5* - numpy>=1.13* - scipy>=0.19* -- matplotlib=1.5* +- matplotlib=2.0* - nibabel>=2.1* diff --git a/requirements.txt b/requirements.txt index 22ac27e5..243ff523 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==1.13.1 scipy==0.19.1 -matplotlib==1.5.3 +matplotlib==2.0.2 nibabel==2.1.0 pytest-cov==2.5.1 diff --git a/segmentator/segmentator_main.py b/segmentator/segmentator_main.py index 5b8941f9..027a7400 100755 --- a/segmentator/segmentator_main.py +++ b/segmentator/segmentator_main.py @@ -58,7 +58,7 @@ palette.set_bad('m', 1.0) # Plot 2D histogram -fig = plt.figure() +fig = plt.figure(facecolor='0.775') ax = fig.add_subplot(121) counts, volHistH, d_min, d_max, nr_bins, bin_edges \ @@ -130,20 +130,20 @@ """Sliders and Buttons""" # colorbar slider axcolor, hovcolor = '0.875', '0.975' -axHistC = plt.axes([0.15, bottom-0.20, 0.25, 0.025], axisbg=axcolor) +axHistC = plt.axes([0.15, bottom-0.20, 0.25, 0.025], facecolor=axcolor) flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, cfg.cbar_max, valinit=cfg.cbar_init, valfmt='%0.1f') # ima browser slider -axSliceNr = plt.axes([0.6, bottom-0.15, 0.25, 0.025], axisbg=axcolor) +axSliceNr = plt.axes([0.6, bottom-0.15, 0.25, 0.025], facecolor=axcolor) flexFig.sSliceNr = Slider(axSliceNr, 'Slice', 0, 0.999, valinit=0.5, valfmt='%0.2f') # theta sliders -aThetaMin = plt.axes([0.15, bottom-0.10, 0.25, 0.025], axisbg=axcolor) +aThetaMin = plt.axes([0.15, bottom-0.10, 0.25, 0.025], facecolor=axcolor) flexFig.sThetaMin = Slider(aThetaMin, 'ThetaMin', 0, 359.9, valinit=cfg.init_theta[0], valfmt='%0.1f') -aThetaMax = plt.axes([0.15, bottom-0.15, 0.25, 0.025], axisbg=axcolor) +aThetaMax = plt.axes([0.15, bottom-0.15, 0.25, 0.025], facecolor=axcolor) flexFig.sThetaMax = Slider(aThetaMax, 'ThetaMax', 0, 359.9, valinit=cfg.init_theta[1]-0.1, valfmt='%0.1f') diff --git a/segmentator/segmentator_ncut.py b/segmentator/segmentator_ncut.py index 0d9c605c..2af035b2 100755 --- a/segmentator/segmentator_ncut.py +++ b/segmentator/segmentator_ncut.py @@ -78,7 +78,7 @@ # %% """Plots""" # Plot 2D histogram -fig = plt.figure() +fig = plt.figure(facecolor='0.775') ax = fig.add_subplot(121) counts, volHistH, d_min, d_max, nr_bins, bin_edges \ @@ -92,11 +92,13 @@ # plot map for poltical borders pltMap = np.zeros((nr_bins, nr_bins, 1)).repeat(4, 2) -cmapPltMap = ListedColormap(['w', 'black', 'red', 'blue']) +cmapPltMap = ListedColormap([[1, 1, 1, 0], # transparent zeros + [0, 0, 0, 0.75], # political borders + [1, 0, 0, 0.5], # other colors for future use + [0, 0, 1, 0.5]]) boundsPltMap = [0, 1, 2, 3, 4] -cmapPltMap.set_under('w', 0) normPltMap = BoundaryNorm(boundsPltMap, cmapPltMap.N) -pltMapH = ax.imshow(pltMap, alpha=1, cmap=cmapPltMap, norm=normPltMap, +pltMapH = ax.imshow(pltMap, cmap=cmapPltMap, norm=normPltMap, vmin=boundsPltMap[1], vmax=boundsPltMap[-1], extent=[0, nr_bins, nr_bins, 0], interpolation='none') @@ -164,22 +166,22 @@ axcolor, hovcolor = '0.875', '0.975' # radio buttons (ugly but good enough for now) -rax = plt.axes([0.91, 0.35, 0.08, 0.5], axisbg=(0.75, 0.75, 0.75)) +rax = plt.axes([0.91, 0.35, 0.08, 0.5], facecolor=(0.75, 0.75, 0.75)) flexFig.radio = RadioButtons(rax, [str(i) for i in range(7)], activecolor=(0.25, 0.25, 0.25)) # colorbar slider -axHistC = plt.axes([0.15, bottom-0.230, 0.25, 0.025], axisbg=axcolor) +axHistC = plt.axes([0.15, bottom-0.230, 0.25, 0.025], facecolor=axcolor) flexFig.sHistC = Slider(axHistC, 'Colorbar', 1, cfg.cbar_max, valinit=cfg.cbar_init, valfmt='%0.1f') # label slider -axLabels = plt.axes([0.15, bottom-0.270, 0.25, 0.025], axisbg=axcolor) +axLabels = plt.axes([0.15, bottom-0.270, 0.25, 0.025], facecolor=axcolor) flexFig.sLabelNr = Slider(axLabels, 'Labels', 0, lMax, valinit=lMax, valfmt='%i') # ima browser slider -axSliceNr = plt.axes([0.6, bottom-0.15, 0.25, 0.025], axisbg=axcolor) +axSliceNr = plt.axes([0.6, bottom-0.15, 0.25, 0.025], facecolor=axcolor) flexFig.sSliceNr = Slider(axSliceNr, 'Slice', 0, 0.999, valinit=0.5, valfmt='%0.3f') From 5af29ed0b2f955e578b0b2035a1e504398ed1360 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 13:03:52 +0200 Subject: [PATCH 095/100] Channel required for matplotlib 1.5.3 removed --- environment.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index 3a5aebc6..c8b06c24 100644 --- a/environment.yml +++ b/environment.yml @@ -1,12 +1,11 @@ name: segmentator channels: - conda-forge -- clinicalgraphics dependencies: - setuptools>=19.6* - pytest>=3* - pytest-cov>=2.5* - numpy>=1.13* - scipy>=0.19* -- matplotlib=2.0* +- matplotlib>=2.0* - nibabel>=2.1* From 592a0e46c91f6f1d752b43c43a6170fcfe03a0d2 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 13:04:49 +0200 Subject: [PATCH 096/100] Update readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 490c6a89..a548d4aa 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The goal is to provide a complementary tool to the already available brain tissu | Package | Tested version | |--------------------------------------|----------------| -| [matplotlib](http://matplotlib.org/) | **1.5.3** | +| [matplotlib](http://matplotlib.org/) | 2.0.2 | | [NumPy](http://www.numpy.org/) | 1.13.1 | | [NiBabel](http://nipy.org/nibabel/) | 2.1.0 | | [SciPy](http://scipy.org/) | 0.19.1 | From 4d56ddd81c9e3131dfefc5fdb0fa688a535114c8 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 14:24:46 +0200 Subject: [PATCH 097/100] Cosmetics --- segmentator/gui_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 8e027e78..b853b35a 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -514,8 +514,7 @@ def lassoArr(self, array, indices): def calcImaMaskBrd(self): """Calculate borders of image mask slice.""" grad = np.gradient(self.imaSlcMsk) - return np.greater(np.sqrt(np.power(grad[0], 2) + - np.power(grad[1], 2)), 0) + return np.greater(np.abs(grad[0], 2) + np.abs(grad[1], 2), 0) class sector_mask: From 34730792ca98616fb4e15d78869f844217873e47 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 15:06:24 +0200 Subject: [PATCH 098/100] Highlight changes (wip). - Still need to remove remaining circles. --- segmentator/gui_utils.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index b853b35a..59526f5c 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -143,20 +143,29 @@ def on_key_release(self, event): def findVoxInHist(self, event): """Find voxel's location in histogram.""" self.press = event.xdata, event.ydata - xvoxel = int(np.floor(event.xdata)) - yvoxel = int(np.floor(event.ydata)) - # SWITCH x and y voxel to get linear index - # since NOT Cartes.!!! - pixelLin = self.invHistVolume[ - yvoxel, xvoxel, self.sliceNr] + pixel_x = int(np.floor(event.xdata)) + pixel_y = int(np.floor(event.ydata)) + aoi = self.invHistVolume[:, :, self.sliceNr] # array of interest + # Check rotation (TODO: code repetition!) + cyc_rot = self.cycRotHistory[self.cycleCount][1] + if cyc_rot == 1: # 90 + aoi = np.rot90(aoi, axes=(0, 1)) + elif cyc_rot == 2: # 180 + aoi = aoi[::-1, ::-1] + elif cyc_rot == 3: # 270 + aoi = np.rot90(aoi, axes=(1, 0)) + # Switch x and y voxel to get linear index since not Cartesian!!! + pixelLin = aoi[pixel_y, pixel_x] # ind2sub xpix = (pixelLin / self.nrBins) ypix = (pixelLin % self.nrBins) - # SWITCH x and y for circle centre - # since back TO Cartesian!!! - self.circle1 = plt.Circle( - (ypix, xpix), radius=5, color='b') + # Switch x and y for circle centre since back to Cartesian. + self.circle1 = plt.Circle((ypix, xpix), radius=5, edgecolor=None, + color=np.array([33, 113, 181, 255])/255) + self.circle2 = plt.Circle((ypix, xpix), radius=1, edgecolor=None, + color=np.array([8, 48, 107, 255])/255) self.axes.add_artist(self.circle1) + self.axes.add_artist(self.circle2) self.figure.canvas.draw() def on_press(self, event): @@ -284,8 +293,7 @@ def on_motion(self, event): def on_release(self, event): """Determine what happens if mouse button is released.""" self.press = None - # try to remove the blue circle - try: + try: # remove highlight circle self.circle1.remove() except: return From b73425db2fc9aea09a8682c10335c724f7d7eb20 Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 18:39:04 +0200 Subject: [PATCH 099/100] Highlights can be toggles ('h') or resetted. --- segmentator/gui_utils.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 59526f5c..69082a13 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -36,7 +36,7 @@ def __init__(self, **kwargs): self.press = None self.ctrlHeld = False self.labelNr = 0 - self.imaSlcMskSwitchCount = 0 + self.imaSlcMskSwitch, self.volHistHighlightSwitch = 0, 0 self.TranspVal = 0.5 self.nrExports = 0 self.entropWin = 0 @@ -44,6 +44,7 @@ def __init__(self, **kwargs): self.imaSlc = self.orig[:, :, self.sliceNr] # selected slice self.cycleCount = 0 self.cycRotHistory = [[0, 0], [0, 0], [0, 0]] + self.highlights = [] # to hold image to histogram circles def remapMsks(self, remap_slice=True): """Update volume histogram to image mapping. @@ -103,6 +104,8 @@ def on_key_press(self, event): self.imaSlcMskIncr(-0.1) elif event.key == 'w': self.imaSlcMskTransSwitch() + elif event.key == 'h': + self.volHistHighlightTransSwitch() elif event.key == 'e': self.imaSlcMskIncr(0.1) elif event.key == '1': @@ -162,10 +165,11 @@ def findVoxInHist(self, event): # Switch x and y for circle centre since back to Cartesian. self.circle1 = plt.Circle((ypix, xpix), radius=5, edgecolor=None, color=np.array([33, 113, 181, 255])/255) - self.circle2 = plt.Circle((ypix, xpix), radius=1, edgecolor=None, - color=np.array([8, 48, 107, 255])/255) + self.highlights.append( + plt.Circle((ypix, xpix), radius=1, edgecolor=None, + color=np.array([8, 48, 107, 255])/255)) self.axes.add_artist(self.circle1) - self.axes.add_artist(self.circle2) + self.axes.add_artist(self.highlights[-1]) self.figure.canvas.draw() def on_press(self, event): @@ -396,8 +400,16 @@ def exportNifti(self, event): print "successfully exported image labels as: \n" + \ self.basename + self.flexfilename + def clearOverlays(self): + """Clear overlaid items such as circle highlights.""" + if self.highlights: + {h.remove() for h in self.highlights} + self.highlights = [] + def resetGlobal(self, event): """Reset stuff.""" + # reset highlights + self.clearOverlays() # reset color bar self.sHistC.reset() # reset transparency @@ -491,14 +503,24 @@ def imaSlcMskIncr(self, incr): self.figure.canvas.draw() def imaSlcMskTransSwitch(self): - """Update transparency of image mask to toggle transparency of it.""" - self.imaSlcMskSwitchCount = (self.imaSlcMskSwitchCount+1) % 2 - if self.imaSlcMskSwitchCount == 1: # set imaSlcMsk transp + """Update transparency of image mask to toggle transparency.""" + self.imaSlcMskSwitch = (self.imaSlcMskSwitch+1) % 2 + if self.imaSlcMskSwitch == 1: # set imaSlcMsk transp self.imaSlcMskH.set_alpha(0) else: # set imaSlcMsk opaque self.imaSlcMskH.set_alpha(self.TranspVal) self.figure.canvas.draw() + def volHistHighlightTransSwitch(self): + """Update transparency of highlights to toggle transparency.""" + self.volHistHighlightSwitch = (self.volHistHighlightSwitch+1) % 2 + if self.volHistHighlightSwitch == 1 and self.highlights: + if self.highlights: + {h.set_visible(False) for h in self.highlights} + elif self.volHistHighlightSwitch == 0 and self.highlights: + {h.set_visible(True) for h in self.highlights} + self.figure.canvas.draw() + def updateLabelsRadio(self, val): """Update labels with radio buttons.""" labelScale = self.lMax / 6. # nr of non-zero radio buttons From b5ed056f4a7965d6a86199233dae6fea5200080c Mon Sep 17 00:00:00 2001 From: ofgulban Date: Tue, 26 Sep 2017 19:10:21 +0200 Subject: [PATCH 100/100] Cosmetics - try except removed (it was giving pep8 error anyway) - highlights mechanism generalized to include large circle. --- segmentator/gui_utils.py | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/segmentator/gui_utils.py b/segmentator/gui_utils.py index 69082a13..faba1bf9 100755 --- a/segmentator/gui_utils.py +++ b/segmentator/gui_utils.py @@ -44,7 +44,7 @@ def __init__(self, **kwargs): self.imaSlc = self.orig[:, :, self.sliceNr] # selected slice self.cycleCount = 0 self.cycRotHistory = [[0, 0], [0, 0], [0, 0]] - self.highlights = [] # to hold image to histogram circles + self.highlights = [[], []] # to hold image to histogram circles def remapMsks(self, remap_slice=True): """Update volume histogram to image mapping. @@ -163,13 +163,14 @@ def findVoxInHist(self, event): xpix = (pixelLin / self.nrBins) ypix = (pixelLin % self.nrBins) # Switch x and y for circle centre since back to Cartesian. - self.circle1 = plt.Circle((ypix, xpix), radius=5, edgecolor=None, - color=np.array([33, 113, 181, 255])/255) - self.highlights.append( - plt.Circle((ypix, xpix), radius=1, edgecolor=None, - color=np.array([8, 48, 107, 255])/255)) - self.axes.add_artist(self.circle1) - self.axes.add_artist(self.highlights[-1]) + circle_colors = [np.array([8, 48, 107, 255])/255, + np.array([33, 113, 181, 255])/255] + self.highlights[0].append(plt.Circle((ypix, xpix), radius=1, + edgecolor=None, color=circle_colors[0])) + self.highlights[1].append(plt.Circle((ypix, xpix), radius=5, + edgecolor=None, color=circle_colors[1])) + self.axes.add_artist(self.highlights[0][-1]) # small circle + self.axes.add_artist(self.highlights[1][-1]) # large circle self.figure.canvas.draw() def on_press(self, event): @@ -297,10 +298,9 @@ def on_motion(self, event): def on_release(self, event): """Determine what happens if mouse button is released.""" self.press = None - try: # remove highlight circle - self.circle1.remove() - except: - return + # Remove highlight circle + if self.highlights[1]: + self.highlights[1][-1].set_visible(False) self.figure.canvas.draw() def disconnect(self): @@ -402,9 +402,10 @@ def exportNifti(self, event): def clearOverlays(self): """Clear overlaid items such as circle highlights.""" - if self.highlights: - {h.remove() for h in self.highlights} - self.highlights = [] + if self.highlights[0]: + {h.remove() for h in self.highlights[0]} + {h.remove() for h in self.highlights[1]} + self.highlights[0] = [] def resetGlobal(self, event): """Reset stuff.""" @@ -514,11 +515,11 @@ def imaSlcMskTransSwitch(self): def volHistHighlightTransSwitch(self): """Update transparency of highlights to toggle transparency.""" self.volHistHighlightSwitch = (self.volHistHighlightSwitch+1) % 2 - if self.volHistHighlightSwitch == 1 and self.highlights: - if self.highlights: - {h.set_visible(False) for h in self.highlights} - elif self.volHistHighlightSwitch == 0 and self.highlights: - {h.set_visible(True) for h in self.highlights} + if self.volHistHighlightSwitch == 1 and self.highlights[0]: + if self.highlights[0]: + {h.set_visible(False) for h in self.highlights[0]} + elif self.volHistHighlightSwitch == 0 and self.highlights[0]: + {h.set_visible(True) for h in self.highlights[0]} self.figure.canvas.draw() def updateLabelsRadio(self, val):