Commit c24db7d3ff62e2dc843811a4239fc6cf05fc0bb9
Exists in
master
and in
55 other branches
Merge pull request #19 from tfmoraes/MIPs
MIPs
Showing
8 changed files
with
873 additions
and
23 deletions
Show diff stats
invesalius/constants.py
| ... | ... | @@ -292,7 +292,8 @@ WINDOW_LEVEL = {_("Abdomen"):(350,50), |
| 292 | 292 | _("Pelvis"): (450,50), |
| 293 | 293 | _("Sinus"):(4000, 400), |
| 294 | 294 | _("Vasculature - Hard"):(240,80), |
| 295 | - _("Vasculature - Soft"):(650,160)} | |
| 295 | + _("Vasculature - Soft"):(650,160), | |
| 296 | + _("Contour"): (255, 127)} | |
| 296 | 297 | |
| 297 | 298 | REDUCE_IMAGEDATA_QUALITY = 0 |
| 298 | 299 | |
| ... | ... | @@ -542,3 +543,18 @@ DICOM_ENCODING_TO_PYTHON = { |
| 542 | 543 | 'ISO_IR 138': 'iso_ir_138', |
| 543 | 544 | 'ISO_IR 144': 'iso_ir_144', |
| 544 | 545 | } |
| 546 | + | |
| 547 | +#-------------------- Projections type ---------------- | |
| 548 | +PROJECTION_NORMAL=0 | |
| 549 | +PROJECTION_MaxIP=1 | |
| 550 | +PROJECTION_MinIP=2 | |
| 551 | +PROJECTION_MeanIP=3 | |
| 552 | +PROJECTION_LMIP=4 | |
| 553 | +PROJECTION_MIDA=5 | |
| 554 | +PROJECTION_CONTOUR_MIP=6 | |
| 555 | +PROJECTION_CONTOUR_LMIP=7 | |
| 556 | +PROJECTION_CONTOUR_MIDA=8 | |
| 557 | + | |
| 558 | +#------------ Projections defaults ------------------ | |
| 559 | +PROJECTION_BORDER_SIZE=1.0 | |
| 560 | +PROJECTION_MIP_SIZE=2 | ... | ... |
| ... | ... | @@ -0,0 +1,379 @@ |
| 1 | +#http://en.wikipedia.org/wiki/Local_maximum_intensity_projection | |
| 2 | +import numpy as np | |
| 3 | +cimport numpy as np | |
| 4 | +cimport cython | |
| 5 | + | |
| 6 | +from libc.math cimport floor, ceil, sqrt, fabs | |
| 7 | +from cython.parallel import prange | |
| 8 | + | |
| 9 | +DTYPE = np.uint8 | |
| 10 | +ctypedef np.uint8_t DTYPE_t | |
| 11 | + | |
| 12 | +DTYPE16 = np.int16 | |
| 13 | +ctypedef np.int16_t DTYPE16_t | |
| 14 | + | |
| 15 | +DTYPEF32 = np.float32 | |
| 16 | +ctypedef np.float32_t DTYPEF32_t | |
| 17 | + | |
| 18 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 19 | +def lmip(np.ndarray[DTYPE16_t, ndim=3] image, int axis, DTYPE16_t tmin, | |
| 20 | + DTYPE16_t tmax, np.ndarray[DTYPE16_t, ndim=2] out): | |
| 21 | + cdef DTYPE16_t max | |
| 22 | + cdef int start | |
| 23 | + cdef int sz = image.shape[0] | |
| 24 | + cdef int sy = image.shape[1] | |
| 25 | + cdef int sx = image.shape[2] | |
| 26 | + | |
| 27 | + # AXIAL | |
| 28 | + if axis == 0: | |
| 29 | + for x in xrange(sx): | |
| 30 | + for y in xrange(sy): | |
| 31 | + max = image[0, y, x] | |
| 32 | + if max >= tmin and max <= tmax: | |
| 33 | + start = 1 | |
| 34 | + else: | |
| 35 | + start = 0 | |
| 36 | + for z in xrange(sz): | |
| 37 | + if image[z, y, x] > max: | |
| 38 | + max = image[z, y, x] | |
| 39 | + | |
| 40 | + elif image[z, y, x] < max and start: | |
| 41 | + break | |
| 42 | + | |
| 43 | + if image[z, y, x] >= tmin and image[z, y, x] <= tmax: | |
| 44 | + start = 1 | |
| 45 | + | |
| 46 | + out[y, x] = max | |
| 47 | + | |
| 48 | + #CORONAL | |
| 49 | + elif axis == 1: | |
| 50 | + for z in xrange(sz): | |
| 51 | + for x in xrange(sx): | |
| 52 | + max = image[z, 0, x] | |
| 53 | + if max >= tmin and max <= tmax: | |
| 54 | + start = 1 | |
| 55 | + else: | |
| 56 | + start = 0 | |
| 57 | + for y in xrange(sy): | |
| 58 | + if image[z, y, x] > max: | |
| 59 | + max = image[z, y, x] | |
| 60 | + | |
| 61 | + elif image[z, y, x] < max and start: | |
| 62 | + break | |
| 63 | + | |
| 64 | + if image[z, y, x] >= tmin and image[z, y, x] <= tmax: | |
| 65 | + start = 1 | |
| 66 | + | |
| 67 | + out[z, x] = max | |
| 68 | + | |
| 69 | + #CORONAL | |
| 70 | + elif axis == 2: | |
| 71 | + for z in xrange(sz): | |
| 72 | + for y in xrange(sy): | |
| 73 | + max = image[z, y, 0] | |
| 74 | + if max >= tmin and max <= tmax: | |
| 75 | + start = 1 | |
| 76 | + else: | |
| 77 | + start = 0 | |
| 78 | + for x in xrange(sx): | |
| 79 | + if image[z, y, x] > max: | |
| 80 | + max = image[z, y, x] | |
| 81 | + | |
| 82 | + elif image[z, y, x] < max and start: | |
| 83 | + break | |
| 84 | + | |
| 85 | + if image[z, y, x] >= tmin and image[z, y, x] <= tmax: | |
| 86 | + start = 1 | |
| 87 | + | |
| 88 | + out[z, y] = max | |
| 89 | + | |
| 90 | + | |
| 91 | +cdef DTYPE16_t get_colour(DTYPE16_t vl, DTYPE16_t wl, DTYPE16_t ww): | |
| 92 | + cdef DTYPE16_t out_colour | |
| 93 | + cdef DTYPE16_t min_value = wl - (ww / 2) | |
| 94 | + cdef DTYPE16_t max_value = wl + (ww / 2) | |
| 95 | + if vl < min_value: | |
| 96 | + out_colour = min_value | |
| 97 | + elif vl > max_value: | |
| 98 | + out_colour = max_value | |
| 99 | + else: | |
| 100 | + out_colour = vl | |
| 101 | + | |
| 102 | + return out_colour | |
| 103 | + | |
| 104 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 105 | +@cython.cdivision(True) | |
| 106 | +cdef float get_opacity(DTYPE16_t vl, DTYPE16_t wl, DTYPE16_t ww) nogil: | |
| 107 | + cdef float out_opacity | |
| 108 | + cdef DTYPE16_t min_value = wl - (ww / 2) | |
| 109 | + cdef DTYPE16_t max_value = wl + (ww / 2) | |
| 110 | + if vl < min_value: | |
| 111 | + out_opacity = 0.0 | |
| 112 | + elif vl > max_value: | |
| 113 | + out_opacity = 1.0 | |
| 114 | + else: | |
| 115 | + out_opacity = 1.0/(max_value - min_value) * (vl - min_value) | |
| 116 | + | |
| 117 | + return out_opacity | |
| 118 | + | |
| 119 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 120 | +@cython.cdivision(True) | |
| 121 | +cdef float get_opacity_f32(DTYPEF32_t vl, DTYPE16_t wl, DTYPE16_t ww) nogil: | |
| 122 | + cdef float out_opacity | |
| 123 | + cdef DTYPE16_t min_value = wl - (ww / 2) | |
| 124 | + cdef DTYPE16_t max_value = wl + (ww / 2) | |
| 125 | + if vl < min_value: | |
| 126 | + out_opacity = 0.0 | |
| 127 | + elif vl > max_value: | |
| 128 | + out_opacity = 1.0 | |
| 129 | + else: | |
| 130 | + out_opacity = 1.0/(max_value - min_value) * (vl - min_value) | |
| 131 | + | |
| 132 | + return out_opacity | |
| 133 | + | |
| 134 | + | |
| 135 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 136 | +@cython.cdivision(True) | |
| 137 | +def mida(np.ndarray[DTYPE16_t, ndim=3] image, int axis, DTYPE16_t wl, | |
| 138 | + DTYPE16_t ww, np.ndarray[DTYPE16_t, ndim=2] out): | |
| 139 | + cdef int sz = image.shape[0] | |
| 140 | + cdef int sy = image.shape[1] | |
| 141 | + cdef int sx = image.shape[2] | |
| 142 | + | |
| 143 | + cdef DTYPE16_t min = image.min() | |
| 144 | + cdef DTYPE16_t max = image.max() | |
| 145 | + cdef DTYPE16_t vl | |
| 146 | + | |
| 147 | + cdef DTYPE16_t min_value = wl - (ww / 2) | |
| 148 | + cdef DTYPE16_t max_value = wl + (ww / 2) | |
| 149 | + | |
| 150 | + cdef float fmax=0.0 | |
| 151 | + cdef float fpi | |
| 152 | + cdef float dl | |
| 153 | + cdef float bt | |
| 154 | + | |
| 155 | + cdef float alpha | |
| 156 | + cdef float alpha_p = 0.0 | |
| 157 | + cdef float colour | |
| 158 | + cdef float colour_p = 0 | |
| 159 | + | |
| 160 | + cdef int x, y, z | |
| 161 | + | |
| 162 | + # AXIAL | |
| 163 | + if axis == 0: | |
| 164 | + for x in prange(sx, nogil=True): | |
| 165 | + for y in xrange(sy): | |
| 166 | + fmax = 0.0 | |
| 167 | + alpha_p = 0.0 | |
| 168 | + colour_p = 0.0 | |
| 169 | + for z in xrange(sz): | |
| 170 | + vl = image[z, y, x] | |
| 171 | + fpi = 1.0/(max - min) * (vl - min) | |
| 172 | + if fpi > fmax: | |
| 173 | + dl = fpi - fmax | |
| 174 | + fmax = fpi | |
| 175 | + else: | |
| 176 | + dl = 0.0 | |
| 177 | + | |
| 178 | + bt = 1.0 - dl | |
| 179 | + | |
| 180 | + colour = fpi | |
| 181 | + alpha = get_opacity(vl, wl, ww) | |
| 182 | + colour = (bt * colour_p) + (1 - bt * alpha_p) * colour * alpha | |
| 183 | + alpha = (bt * alpha_p) + (1 - bt * alpha_p) * alpha | |
| 184 | + | |
| 185 | + colour_p = colour | |
| 186 | + alpha_p = alpha | |
| 187 | + | |
| 188 | + if alpha >= 1.0: | |
| 189 | + break | |
| 190 | + | |
| 191 | + | |
| 192 | + #out[y, x] = <DTYPE16_t>((max_value - min_value) * colour + min_value) | |
| 193 | + out[y, x] = <DTYPE16_t>((max - min) * colour + min) | |
| 194 | + | |
| 195 | + | |
| 196 | + #CORONAL | |
| 197 | + elif axis == 1: | |
| 198 | + for z in prange(sz, nogil=True): | |
| 199 | + for x in xrange(sx): | |
| 200 | + fmax = 0.0 | |
| 201 | + alpha_p = 0.0 | |
| 202 | + colour_p = 0.0 | |
| 203 | + for y in xrange(sy): | |
| 204 | + vl = image[z, y, x] | |
| 205 | + fpi = 1.0/(max - min) * (vl - min) | |
| 206 | + if fpi > fmax: | |
| 207 | + dl = fpi - fmax | |
| 208 | + fmax = fpi | |
| 209 | + else: | |
| 210 | + dl = 0.0 | |
| 211 | + | |
| 212 | + bt = 1.0 - dl | |
| 213 | + | |
| 214 | + colour = fpi | |
| 215 | + alpha = get_opacity(vl, wl, ww) | |
| 216 | + colour = (bt * colour_p) + (1 - bt * alpha_p) * colour * alpha | |
| 217 | + alpha = (bt * alpha_p) + (1 - bt * alpha_p) * alpha | |
| 218 | + | |
| 219 | + colour_p = colour | |
| 220 | + alpha_p = alpha | |
| 221 | + | |
| 222 | + if alpha >= 1.0: | |
| 223 | + break | |
| 224 | + | |
| 225 | + out[z, x] = <DTYPE16_t>((max - min) * colour + min) | |
| 226 | + | |
| 227 | + #AXIAL | |
| 228 | + elif axis == 2: | |
| 229 | + for z in prange(sz, nogil=True): | |
| 230 | + for y in xrange(sy): | |
| 231 | + fmax = 0.0 | |
| 232 | + alpha_p = 0.0 | |
| 233 | + colour_p = 0.0 | |
| 234 | + for x in xrange(sx): | |
| 235 | + vl = image[z, y, x] | |
| 236 | + fpi = 1.0/(max - min) * (vl - min) | |
| 237 | + if fpi > fmax: | |
| 238 | + dl = fpi - fmax | |
| 239 | + fmax = fpi | |
| 240 | + else: | |
| 241 | + dl = 0.0 | |
| 242 | + | |
| 243 | + bt = 1.0 - dl | |
| 244 | + | |
| 245 | + colour = fpi | |
| 246 | + alpha = get_opacity(vl, wl, ww) | |
| 247 | + colour = (bt * colour_p) + (1 - bt * alpha_p) * colour * alpha | |
| 248 | + alpha = (bt * alpha_p) + (1 - bt * alpha_p) * alpha | |
| 249 | + | |
| 250 | + colour_p = colour | |
| 251 | + alpha_p = alpha | |
| 252 | + | |
| 253 | + if alpha >= 1.0: | |
| 254 | + break | |
| 255 | + | |
| 256 | + out[z, y] = <DTYPE16_t>((max - min) * colour + min) | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 261 | +@cython.cdivision(True) | |
| 262 | +cdef inline void finite_difference(DTYPE16_t[:, :, :] image, | |
| 263 | + int x, int y, int z, float h, float *g) nogil: | |
| 264 | + cdef int px, py, pz, fx, fy, fz | |
| 265 | + | |
| 266 | + cdef int sz = image.shape[0] | |
| 267 | + cdef int sy = image.shape[1] | |
| 268 | + cdef int sx = image.shape[2] | |
| 269 | + | |
| 270 | + cdef float gx, gy, gz | |
| 271 | + | |
| 272 | + if x == 0: | |
| 273 | + px = 0 | |
| 274 | + fx = 1 | |
| 275 | + elif x == sx - 1: | |
| 276 | + px = x - 1 | |
| 277 | + fx = x | |
| 278 | + else: | |
| 279 | + px = x - 1 | |
| 280 | + fx = x + 1 | |
| 281 | + | |
| 282 | + if y == 0: | |
| 283 | + py = 0 | |
| 284 | + fy = 1 | |
| 285 | + elif y == sy - 1: | |
| 286 | + py = y - 1 | |
| 287 | + fy = y | |
| 288 | + else: | |
| 289 | + py = y - 1 | |
| 290 | + fy = y + 1 | |
| 291 | + | |
| 292 | + if z == 0: | |
| 293 | + pz = 0 | |
| 294 | + fz = 1 | |
| 295 | + elif z == sz - 1: | |
| 296 | + pz = z - 1 | |
| 297 | + fz = z | |
| 298 | + else: | |
| 299 | + pz = z - 1 | |
| 300 | + fz = z + 1 | |
| 301 | + | |
| 302 | + gx = (image[z, y, fx] - image[z, y, px]) / (2*h) | |
| 303 | + gy = (image[z, fy, x] - image[z, py, x]) / (2*h) | |
| 304 | + gz = (image[fz, y, x] - image[pz, y, x]) / (2*h) | |
| 305 | + | |
| 306 | + g[0] = gx | |
| 307 | + g[1] = gy | |
| 308 | + g[2] = gz | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 313 | +@cython.cdivision(True) | |
| 314 | +cdef inline float calc_fcm_itensity(DTYPE16_t[:, :, :] image, | |
| 315 | + int x, int y, int z, float n, float* dir) nogil: | |
| 316 | + cdef float g[3] | |
| 317 | + finite_difference(image, x, y, z, 1.0, g) | |
| 318 | + cdef float gm = sqrt(g[0]*g[0] + g[1]*g[1] + g[2]*g[2]) | |
| 319 | + cdef float d = g[0]*dir[0] + g[1]*dir[1] + g[2]*dir[2] | |
| 320 | + cdef float sf = (1.0 - fabs(d/gm))**n | |
| 321 | + #alpha = get_opacity_f32(gm, wl, ww) | |
| 322 | + cdef float vl = gm * sf | |
| 323 | + return vl | |
| 324 | + | |
| 325 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | |
| 326 | +@cython.cdivision(True) | |
| 327 | +def fast_countour_mip(np.ndarray[DTYPE16_t, ndim=3] image, | |
| 328 | + float n, | |
| 329 | + int axis, | |
| 330 | + DTYPE16_t wl, DTYPE16_t ww, | |
| 331 | + int tmip, | |
| 332 | + np.ndarray[DTYPE16_t, ndim=2] out): | |
| 333 | + cdef int sz = image.shape[0] | |
| 334 | + cdef int sy = image.shape[1] | |
| 335 | + cdef int sx = image.shape[2] | |
| 336 | + cdef float gm | |
| 337 | + cdef float alpha | |
| 338 | + cdef float sf | |
| 339 | + cdef float d | |
| 340 | + | |
| 341 | + cdef float* g | |
| 342 | + cdef float* dir = [ 0, 0, 0 ] | |
| 343 | + | |
| 344 | + cdef DTYPE16_t[:, :, :] vimage = image | |
| 345 | + cdef np.ndarray[DTYPE16_t, ndim=3] tmp = np.empty_like(image) | |
| 346 | + | |
| 347 | + cdef DTYPE16_t min = image.min() | |
| 348 | + cdef DTYPE16_t max = image.max() | |
| 349 | + cdef float fmin = <float>min | |
| 350 | + cdef float fmax = <float>max | |
| 351 | + cdef float vl | |
| 352 | + cdef DTYPE16_t V | |
| 353 | + | |
| 354 | + cdef int x, y, z | |
| 355 | + | |
| 356 | + if axis == 0: | |
| 357 | + dir[2] = 1.0 | |
| 358 | + elif axis == 1: | |
| 359 | + dir[1] = 1.0 | |
| 360 | + elif axis == 2: | |
| 361 | + dir[0] = 1.0 | |
| 362 | + | |
| 363 | + for z in prange(sz, nogil=True): | |
| 364 | + for y in range(sy): | |
| 365 | + for x in range(sx): | |
| 366 | + vl = calc_fcm_itensity(vimage, x, y, z, n, dir) | |
| 367 | + tmp[z, y, x] = <DTYPE16_t>vl | |
| 368 | + | |
| 369 | + cdef DTYPE16_t tmin = tmp.min() | |
| 370 | + cdef DTYPE16_t tmax = tmp.max() | |
| 371 | + | |
| 372 | + #tmp = ((max - min)/<float>(tmax - tmin)) * (tmp - tmin) + min | |
| 373 | + | |
| 374 | + if tmip == 0: | |
| 375 | + out[:] = tmp.max(axis) | |
| 376 | + elif tmip == 1: | |
| 377 | + lmip(tmp, axis, 700, 3033, out) | |
| 378 | + elif tmip == 2: | |
| 379 | + mida(tmp, axis, wl, ww, out) | ... | ... |
invesalius/data/slice_.py
| ... | ... | @@ -33,6 +33,7 @@ import utils |
| 33 | 33 | |
| 34 | 34 | from mask import Mask |
| 35 | 35 | from project import Project |
| 36 | +from data import mips | |
| 36 | 37 | |
| 37 | 38 | OTHER=0 |
| 38 | 39 | PLIST=1 |
| ... | ... | @@ -83,6 +84,10 @@ class Slice(object): |
| 83 | 84 | self.blend_filter = None |
| 84 | 85 | self.histogram = None |
| 85 | 86 | self._matrix = None |
| 87 | + | |
| 88 | + self._type_projection = const.PROJECTION_NORMAL | |
| 89 | + self.n_border = const.PROJECTION_BORDER_SIZE | |
| 90 | + | |
| 86 | 91 | self.spacing = (1.0, 1.0, 1.0) |
| 87 | 92 | |
| 88 | 93 | self.number_of_colours = 256 |
| ... | ... | @@ -111,7 +116,7 @@ class Slice(object): |
| 111 | 116 | def matrix(self, value): |
| 112 | 117 | self._matrix = value |
| 113 | 118 | i, e = value.min(), value.max() |
| 114 | - r = e - i | |
| 119 | + r = int(e) - int(i) | |
| 115 | 120 | self.histogram = numpy.histogram(self._matrix, r, (i, e))[0] |
| 116 | 121 | |
| 117 | 122 | def __bind_events(self): |
| ... | ... | @@ -133,6 +138,7 @@ class Slice(object): |
| 133 | 138 | 'Change mask colour') |
| 134 | 139 | Publisher.subscribe(self.__set_mask_name, 'Change mask name') |
| 135 | 140 | Publisher.subscribe(self.__show_mask, 'Show mask') |
| 141 | + Publisher.subscribe(self.__hide_current_mask, 'Hide current mask') | |
| 136 | 142 | |
| 137 | 143 | Publisher.subscribe(self.__set_current_mask_threshold_limits, |
| 138 | 144 | 'Update threshold limits') |
| ... | ... | @@ -148,6 +154,7 @@ class Slice(object): |
| 148 | 154 | |
| 149 | 155 | Publisher.subscribe(self.UpdateColourTableBackgroundWidget,\ |
| 150 | 156 | 'Change colour table from background image from widget') |
| 157 | + Publisher.subscribe(self._set_projection_type, 'Set projection type') | |
| 151 | 158 | |
| 152 | 159 | Publisher.subscribe(self.InputImageWidget, 'Input Image in the widget') |
| 153 | 160 | |
| ... | ... | @@ -346,6 +353,16 @@ class Slice(object): |
| 346 | 353 | if not value: |
| 347 | 354 | Publisher.sendMessage('Select mask name in combo', -1) |
| 348 | 355 | |
| 356 | + if self._type_projection != const.PROJECTION_NORMAL: | |
| 357 | + self.SetTypeProjection(const.PROJECTION_NORMAL) | |
| 358 | + Publisher.sendMessage('Reload actual slice') | |
| 359 | + | |
| 360 | + def __hide_current_mask(self, pubsub_evt): | |
| 361 | + if self.current_mask: | |
| 362 | + index = self.current_mask.index | |
| 363 | + value = False | |
| 364 | + Publisher.sendMessage('Show mask', (index, value)) | |
| 365 | + | |
| 349 | 366 | def edit_mask_pixel(self, operation, index, position, radius, orientation): |
| 350 | 367 | mask = self.buffer_slices[orientation].mask |
| 351 | 368 | image = self.buffer_slices[orientation].image |
| ... | ... | @@ -421,12 +438,16 @@ class Slice(object): |
| 421 | 438 | self.buffer_slices[orientation].discard_vtk_mask() |
| 422 | 439 | |
| 423 | 440 | |
| 424 | - def GetSlices(self, orientation, slice_number): | |
| 425 | - if self.buffer_slices[orientation].index == slice_number: | |
| 441 | + def GetSlices(self, orientation, slice_number, number_slices, | |
| 442 | + inverted=False, border_size=1.0): | |
| 443 | + if self.buffer_slices[orientation].index == slice_number and \ | |
| 444 | + self._type_projection == const.PROJECTION_NORMAL: | |
| 426 | 445 | if self.buffer_slices[orientation].vtk_image: |
| 427 | 446 | image = self.buffer_slices[orientation].vtk_image |
| 428 | 447 | else: |
| 429 | - n_image = self.get_image_slice(orientation, slice_number) | |
| 448 | + n_image = self.get_image_slice(orientation, slice_number, | |
| 449 | + number_slices, inverted, | |
| 450 | + border_size) | |
| 430 | 451 | image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) |
| 431 | 452 | ww_wl_image = self.do_ww_wl(image) |
| 432 | 453 | image = self.do_colour_image(ww_wl_image) |
| ... | ... | @@ -446,7 +467,8 @@ class Slice(object): |
| 446 | 467 | final_image = image |
| 447 | 468 | self.buffer_slices[orientation].vtk_image = image |
| 448 | 469 | else: |
| 449 | - n_image = self.get_image_slice(orientation, slice_number) | |
| 470 | + n_image = self.get_image_slice(orientation, slice_number, | |
| 471 | + number_slices, inverted, border_size) | |
| 450 | 472 | image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) |
| 451 | 473 | ww_wl_image = self.do_ww_wl(image) |
| 452 | 474 | image = self.do_colour_image(ww_wl_image) |
| ... | ... | @@ -469,17 +491,151 @@ class Slice(object): |
| 469 | 491 | |
| 470 | 492 | return final_image |
| 471 | 493 | |
| 472 | - def get_image_slice(self, orientation, slice_number): | |
| 494 | + def get_image_slice(self, orientation, slice_number, number_slices=1, | |
| 495 | + inverted=False, border_size=1.0): | |
| 473 | 496 | if self.buffer_slices[orientation].index == slice_number \ |
| 474 | 497 | and self.buffer_slices[orientation].image is not None: |
| 475 | 498 | n_image = self.buffer_slices[orientation].image |
| 476 | 499 | else: |
| 500 | + | |
| 477 | 501 | if orientation == 'AXIAL': |
| 478 | - n_image = numpy.array(self.matrix[slice_number]) | |
| 502 | + if self._type_projection == const.PROJECTION_NORMAL: | |
| 503 | + n_image = numpy.array(self.matrix[slice_number]) | |
| 504 | + else: | |
| 505 | + tmp_array = numpy.array(self.matrix[slice_number: | |
| 506 | + slice_number + number_slices]) | |
| 507 | + if inverted: | |
| 508 | + tmp_array = tmp_array[::-1] | |
| 509 | + | |
| 510 | + if self._type_projection == const.PROJECTION_MaxIP: | |
| 511 | + n_image = numpy.array(tmp_array).max(0) | |
| 512 | + elif self._type_projection == const.PROJECTION_MinIP: | |
| 513 | + n_image = numpy.array(tmp_array).min(0) | |
| 514 | + elif self._type_projection == const.PROJECTION_MeanIP: | |
| 515 | + n_image = numpy.array(tmp_array).mean(0) | |
| 516 | + elif self._type_projection == const.PROJECTION_LMIP: | |
| 517 | + n_image = numpy.empty(shape=(tmp_array.shape[1], | |
| 518 | + tmp_array.shape[2]), | |
| 519 | + dtype=tmp_array.dtype) | |
| 520 | + mips.lmip(tmp_array, 0, self.window_level, self.window_level, n_image) | |
| 521 | + elif self._type_projection == const.PROJECTION_MIDA: | |
| 522 | + n_image = numpy.empty(shape=(tmp_array.shape[1], | |
| 523 | + tmp_array.shape[2]), | |
| 524 | + dtype=tmp_array.dtype) | |
| 525 | + mips.mida(tmp_array, 0, self.window_level, self.window_level, n_image) | |
| 526 | + elif self._type_projection == const.PROJECTION_CONTOUR_MIP: | |
| 527 | + n_image = numpy.empty(shape=(tmp_array.shape[1], | |
| 528 | + tmp_array.shape[2]), | |
| 529 | + dtype=tmp_array.dtype) | |
| 530 | + mips.fast_countour_mip(tmp_array, border_size, 0, self.window_level, | |
| 531 | + self.window_level, 0, n_image) | |
| 532 | + elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: | |
| 533 | + n_image = numpy.empty(shape=(tmp_array.shape[1], | |
| 534 | + tmp_array.shape[2]), | |
| 535 | + dtype=tmp_array.dtype) | |
| 536 | + mips.fast_countour_mip(tmp_array, border_size, 0, self.window_level, | |
| 537 | + self.window_level, 1, n_image) | |
| 538 | + elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: | |
| 539 | + n_image = numpy.empty(shape=(tmp_array.shape[1], | |
| 540 | + tmp_array.shape[2]), | |
| 541 | + dtype=tmp_array.dtype) | |
| 542 | + mips.fast_countour_mip(tmp_array, border_size, 0, self.window_level, | |
| 543 | + self.window_level, 2, n_image) | |
| 544 | + else: | |
| 545 | + n_image = numpy.array(self.matrix[slice_number]) | |
| 546 | + | |
| 479 | 547 | elif orientation == 'CORONAL': |
| 480 | - n_image = numpy.array(self.matrix[..., slice_number, ...]) | |
| 548 | + if self._type_projection == const.PROJECTION_NORMAL: | |
| 549 | + n_image = numpy.array(self.matrix[..., slice_number, ...]) | |
| 550 | + else: | |
| 551 | + #if slice_number == 0: | |
| 552 | + #slice_number = 1 | |
| 553 | + #if slice_number - number_slices < 0: | |
| 554 | + #number_slices = slice_number | |
| 555 | + tmp_array = numpy.array(self.matrix[..., slice_number: slice_number + number_slices, ...]) | |
| 556 | + if inverted: | |
| 557 | + tmp_array = tmp_array[..., ::-1, ...] | |
| 558 | + if self._type_projection == const.PROJECTION_MaxIP: | |
| 559 | + n_image = numpy.array(tmp_array).max(1) | |
| 560 | + elif self._type_projection == const.PROJECTION_MinIP: | |
| 561 | + n_image = numpy.array(tmp_array).min(1) | |
| 562 | + elif self._type_projection == const.PROJECTION_MeanIP: | |
| 563 | + n_image = numpy.array(tmp_array).mean(1) | |
| 564 | + elif self._type_projection == const.PROJECTION_LMIP: | |
| 565 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 566 | + tmp_array.shape[2]), | |
| 567 | + dtype=tmp_array.dtype) | |
| 568 | + mips.lmip(tmp_array, 1, self.window_level, self.window_level, n_image) | |
| 569 | + elif self._type_projection == const.PROJECTION_MIDA: | |
| 570 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 571 | + tmp_array.shape[2]), | |
| 572 | + dtype=tmp_array.dtype) | |
| 573 | + mips.mida(tmp_array, 1, self.window_level, self.window_level, n_image) | |
| 574 | + elif self._type_projection == const.PROJECTION_CONTOUR_MIP: | |
| 575 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 576 | + tmp_array.shape[2]), | |
| 577 | + dtype=tmp_array.dtype) | |
| 578 | + mips.fast_countour_mip(tmp_array, border_size, 1, self.window_level, | |
| 579 | + self.window_level, 0, n_image) | |
| 580 | + elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: | |
| 581 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 582 | + tmp_array.shape[2]), | |
| 583 | + dtype=tmp_array.dtype) | |
| 584 | + mips.fast_countour_mip(tmp_array, border_size, 1, self.window_level, | |
| 585 | + self.window_level, 1, n_image) | |
| 586 | + elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: | |
| 587 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 588 | + tmp_array.shape[2]), | |
| 589 | + dtype=tmp_array.dtype) | |
| 590 | + mips.fast_countour_mip(tmp_array, border_size, 1, self.window_level, | |
| 591 | + self.window_level, 2, n_image) | |
| 592 | + else: | |
| 593 | + n_image = numpy.array(self.matrix[..., slice_number, ...]) | |
| 481 | 594 | elif orientation == 'SAGITAL': |
| 482 | - n_image = numpy.array(self.matrix[..., ..., slice_number]) | |
| 595 | + if self._type_projection == const.PROJECTION_NORMAL: | |
| 596 | + n_image = numpy.array(self.matrix[..., ..., slice_number]) | |
| 597 | + else: | |
| 598 | + tmp_array = numpy.array(self.matrix[..., ..., | |
| 599 | + slice_number: slice_number + number_slices]) | |
| 600 | + if inverted: | |
| 601 | + tmp_array = tmp_array[..., ..., ::-1] | |
| 602 | + if self._type_projection == const.PROJECTION_MaxIP: | |
| 603 | + n_image = numpy.array(tmp_array).max(2) | |
| 604 | + elif self._type_projection == const.PROJECTION_MinIP: | |
| 605 | + n_image = numpy.array(tmp_array).min(2) | |
| 606 | + elif self._type_projection == const.PROJECTION_MeanIP: | |
| 607 | + n_image = numpy.array(tmp_array).mean(2) | |
| 608 | + elif self._type_projection == const.PROJECTION_LMIP: | |
| 609 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 610 | + tmp_array.shape[1]), | |
| 611 | + dtype=tmp_array.dtype) | |
| 612 | + mips.lmip(tmp_array, 2, self.window_level, self.window_level, n_image) | |
| 613 | + elif self._type_projection == const.PROJECTION_MIDA: | |
| 614 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 615 | + tmp_array.shape[1]), | |
| 616 | + dtype=tmp_array.dtype) | |
| 617 | + mips.mida(tmp_array, 2, self.window_level, self.window_level, n_image) | |
| 618 | + | |
| 619 | + elif self._type_projection == const.PROJECTION_CONTOUR_MIP: | |
| 620 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 621 | + tmp_array.shape[1]), | |
| 622 | + dtype=tmp_array.dtype) | |
| 623 | + mips.fast_countour_mip(tmp_array, border_size, 2, self.window_level, | |
| 624 | + self.window_level, 0, n_image) | |
| 625 | + elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: | |
| 626 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 627 | + tmp_array.shape[1]), | |
| 628 | + dtype=tmp_array.dtype) | |
| 629 | + mips.fast_countour_mip(tmp_array, border_size, 2, self.window_level, | |
| 630 | + self.window_level, 1, n_image) | |
| 631 | + elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: | |
| 632 | + n_image = numpy.empty(shape=(tmp_array.shape[0], | |
| 633 | + tmp_array.shape[1]), | |
| 634 | + dtype=tmp_array.dtype) | |
| 635 | + mips.fast_countour_mip(tmp_array, border_size, 2, self.window_level, | |
| 636 | + self.window_level, 2, n_image) | |
| 637 | + else: | |
| 638 | + n_image = numpy.array(self.matrix[..., ..., slice_number]) | |
| 483 | 639 | return n_image |
| 484 | 640 | |
| 485 | 641 | def get_mask_slice(self, orientation, slice_number): |
| ... | ... | @@ -672,6 +828,26 @@ class Slice(object): |
| 672 | 828 | def GetOutput(self): |
| 673 | 829 | return self.blend_filter.GetOutput() |
| 674 | 830 | |
| 831 | + def _set_projection_type(self, pubsub_evt): | |
| 832 | + tprojection = pubsub_evt.data | |
| 833 | + self.SetTypeProjection(tprojection) | |
| 834 | + | |
| 835 | + def SetTypeProjection(self, tprojection): | |
| 836 | + if self._type_projection != tprojection: | |
| 837 | + if self._type_projection == const.PROJECTION_NORMAL: | |
| 838 | + Publisher.sendMessage('Hide current mask') | |
| 839 | + | |
| 840 | + if tprojection == const.PROJECTION_NORMAL: | |
| 841 | + Publisher.sendMessage('Show MIP interface', False) | |
| 842 | + else: | |
| 843 | + Publisher.sendMessage('Show MIP interface', True) | |
| 844 | + | |
| 845 | + self._type_projection = tprojection | |
| 846 | + for buffer_ in self.buffer_slices.values(): | |
| 847 | + buffer_.discard_buffer() | |
| 848 | + | |
| 849 | + Publisher.sendMessage('Check projection menu', tprojection) | |
| 850 | + | |
| 675 | 851 | def SetInput(self, imagedata, mask_dict): |
| 676 | 852 | print "SETINPUT!" |
| 677 | 853 | self.imagedata = imagedata |
| ... | ... | @@ -731,7 +907,14 @@ class Slice(object): |
| 731 | 907 | self.window_level = level |
| 732 | 908 | |
| 733 | 909 | for buffer_ in self.buffer_slices.values(): |
| 734 | - buffer_.discard_vtk_image() | |
| 910 | + if self._type_projection in (const.PROJECTION_NORMAL, | |
| 911 | + const.PROJECTION_MaxIP, | |
| 912 | + const.PROJECTION_MinIP, | |
| 913 | + const.PROJECTION_MeanIP, | |
| 914 | + const.PROJECTION_LMIP): | |
| 915 | + buffer_.discard_vtk_image() | |
| 916 | + else: | |
| 917 | + buffer_.discard_buffer() | |
| 735 | 918 | |
| 736 | 919 | Publisher.sendMessage('Reload actual slice') |
| 737 | 920 | |
| ... | ... | @@ -758,7 +941,14 @@ class Slice(object): |
| 758 | 941 | self.nodes = pubsub_evt.data |
| 759 | 942 | self.from_= WIDGET |
| 760 | 943 | for buffer_ in self.buffer_slices.values(): |
| 761 | - buffer_.discard_vtk_image() | |
| 944 | + if self._type_projection in (const.PROJECTION_NORMAL, | |
| 945 | + const.PROJECTION_MaxIP, | |
| 946 | + const.PROJECTION_MinIP, | |
| 947 | + const.PROJECTION_MeanIP, | |
| 948 | + const.PROJECTION_LMIP): | |
| 949 | + buffer_.discard_vtk_image() | |
| 950 | + else: | |
| 951 | + buffer_.discard_buffer() | |
| 762 | 952 | |
| 763 | 953 | knodes = sorted(self.nodes) |
| 764 | 954 | p0 = knodes[0].value | ... | ... |
invesalius/data/slice_data.py
| ... | ... | @@ -136,9 +136,13 @@ class SliceData(object): |
| 136 | 136 | self.overlay_renderer.AddActor(cursor.actor) |
| 137 | 137 | self.cursor = cursor |
| 138 | 138 | |
| 139 | - def SetNumber(self, number): | |
| 140 | - self.number = number | |
| 141 | - self.text.SetValue("%d" % self.number) | |
| 139 | + def SetNumber(self, init, end=None): | |
| 140 | + if end is None: | |
| 141 | + self.number = init | |
| 142 | + self.text.SetValue("%d" % self.number) | |
| 143 | + else: | |
| 144 | + self.number = init | |
| 145 | + self.text.SetValue("%d - %d" % (init, end)) | |
| 142 | 146 | self.text.SetPosition(const.TEXT_POS_LEFT_DOWN_ZERO) |
| 143 | 147 | |
| 144 | 148 | def SetOrientation(self, orientation): | ... | ... |
invesalius/data/viewer_slice.py
| ... | ... | @@ -32,6 +32,11 @@ import styles |
| 32 | 32 | import wx |
| 33 | 33 | from wx.lib.pubsub import pub as Publisher |
| 34 | 34 | |
| 35 | +try: | |
| 36 | + from agw import floatspin as FS | |
| 37 | +except ImportError: # if it's not there locally, try the wxPython lib. | |
| 38 | + import wx.lib.agw.floatspin as FS | |
| 39 | + | |
| 35 | 40 | import constants as const |
| 36 | 41 | import cursor_actors as ca |
| 37 | 42 | import data.slice_ as sl |
| ... | ... | @@ -51,6 +56,93 @@ ORIENTATIONS = { |
| 51 | 56 | "SAGITAL": const.SAGITAL, |
| 52 | 57 | } |
| 53 | 58 | |
| 59 | + | |
| 60 | +class ContourMIPConfig(wx.Panel): | |
| 61 | + def __init__(self, prnt, orientation): | |
| 62 | + wx.Panel.__init__(self, prnt) | |
| 63 | + self.mip_size_spin = wx.SpinCtrl(self, -1, min=1, max=240, | |
| 64 | + initial=const.PROJECTION_MIP_SIZE) | |
| 65 | + self.mip_size_spin.SetToolTip(wx.ToolTip(_("Number of slices used to compound the visualization"))) | |
| 66 | + w, h = self.mip_size_spin.GetTextExtent('M') | |
| 67 | + self.mip_size_spin.SetMinSize((5 * w + 10, -1)) | |
| 68 | + self.mip_size_spin.SetMaxSize((5 * w + 10, -1)) | |
| 69 | + | |
| 70 | + self.border_spin = FS.FloatSpin(self, -1, min_val=0, max_val=10, | |
| 71 | + increment=0.1, | |
| 72 | + value=const.PROJECTION_BORDER_SIZE, | |
| 73 | + digits=1, agwStyle=FS.FS_LEFT) | |
| 74 | + self.border_spin.SetToolTip(wx.ToolTip(_("Controls the sharpness of the" | |
| 75 | + " contour. The greater the" | |
| 76 | + " value, the sharper the" | |
| 77 | + " contour"))) | |
| 78 | + w, h = self.border_spin.GetTextExtent('M') | |
| 79 | + self.border_spin.SetMinSize((5 * w + 10, -1)) | |
| 80 | + self.border_spin.SetMaxSize((5 * w + 10, -1)) | |
| 81 | + | |
| 82 | + self.inverted = wx.CheckBox(self, -1, _("inverted")) | |
| 83 | + self.inverted.SetToolTip(wx.ToolTip(_("If checked, the slices are" | |
| 84 | + " traversed in descending" | |
| 85 | + " order to compound the" | |
| 86 | + " visualization instead of" | |
| 87 | + " ascending order"))) | |
| 88 | + | |
| 89 | + txt_mip_size = wx.StaticText(self, -1, _("Number of slices"), style=wx.ALIGN_CENTER_HORIZONTAL) | |
| 90 | + self.txt_mip_border = wx.StaticText(self, -1, _("Sharpness")) | |
| 91 | + | |
| 92 | + sizer = wx.BoxSizer(wx.HORIZONTAL) | |
| 93 | + sizer.Add(txt_mip_size, 0, wx.EXPAND | wx.ALL, 2) | |
| 94 | + sizer.Add(self.mip_size_spin, 0, wx.EXPAND) | |
| 95 | + sizer.AddSpacer((10, 0)) | |
| 96 | + sizer.Add(self.txt_mip_border, 0, wx.EXPAND | wx.ALL, 2) | |
| 97 | + sizer.Add(self.border_spin, 0, wx.EXPAND) | |
| 98 | + sizer.AddSpacer((10, 0)) | |
| 99 | + sizer.Add(self.inverted, 0, wx.EXPAND) | |
| 100 | + self.SetSizer(sizer) | |
| 101 | + sizer.Fit(self) | |
| 102 | + | |
| 103 | + self.Layout() | |
| 104 | + self.Update() | |
| 105 | + self.SetAutoLayout(1) | |
| 106 | + | |
| 107 | + self.orientation = orientation | |
| 108 | + | |
| 109 | + self.mip_size_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPSize) | |
| 110 | + self.border_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPBorder) | |
| 111 | + self.inverted.Bind(wx.EVT_CHECKBOX, self.OnCheckInverted) | |
| 112 | + | |
| 113 | + Publisher.subscribe(self._set_projection_type, 'Set projection type') | |
| 114 | + | |
| 115 | + def OnSetMIPSize(self, evt): | |
| 116 | + val = self.mip_size_spin.GetValue() | |
| 117 | + Publisher.sendMessage('Set MIP size %s' % self.orientation, val) | |
| 118 | + | |
| 119 | + def OnSetMIPBorder(self, evt): | |
| 120 | + val = self.border_spin.GetValue() | |
| 121 | + Publisher.sendMessage('Set MIP border %s' % self.orientation, val) | |
| 122 | + | |
| 123 | + def OnCheckInverted(self, evt): | |
| 124 | + val = self.inverted.GetValue() | |
| 125 | + Publisher.sendMessage('Set MIP Invert %s' % self.orientation, val) | |
| 126 | + | |
| 127 | + def _set_projection_type(self, pubsub_evt): | |
| 128 | + tprojection = pubsub_evt.data | |
| 129 | + | |
| 130 | + if tprojection in (const.PROJECTION_MIDA, | |
| 131 | + const.PROJECTION_CONTOUR_MIDA): | |
| 132 | + self.inverted.Enable() | |
| 133 | + else: | |
| 134 | + self.inverted.Disable() | |
| 135 | + | |
| 136 | + if tprojection in (const.PROJECTION_CONTOUR_MIP, | |
| 137 | + const.PROJECTION_CONTOUR_MIDA): | |
| 138 | + self.border_spin.Enable() | |
| 139 | + self.txt_mip_border.Enable() | |
| 140 | + else: | |
| 141 | + self.border_spin.Disable() | |
| 142 | + self.txt_mip_border.Disable() | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 54 | 146 | class Viewer(wx.Panel): |
| 55 | 147 | |
| 56 | 148 | def __init__(self, prnt, orientation='AXIAL'): |
| ... | ... | @@ -63,6 +155,9 @@ class Viewer(wx.Panel): |
| 63 | 155 | #self.modes = []#['DEFAULT'] |
| 64 | 156 | self.left_pressed = 0 |
| 65 | 157 | self.right_pressed = 0 |
| 158 | + | |
| 159 | + self._number_slices = const.PROJECTION_MIP_SIZE | |
| 160 | + self._mip_inverted = False | |
| 66 | 161 | |
| 67 | 162 | self.spined_image = False #Use to control to spin |
| 68 | 163 | self.paned_image = False |
| ... | ... | @@ -83,11 +178,11 @@ class Viewer(wx.Panel): |
| 83 | 178 | self.actors_by_slice_number = {} |
| 84 | 179 | self.renderers_by_slice_number = {} |
| 85 | 180 | |
| 86 | - self.__init_gui() | |
| 87 | - | |
| 88 | 181 | self.orientation = orientation |
| 89 | 182 | self.slice_number = 0 |
| 90 | 183 | |
| 184 | + self.__init_gui() | |
| 185 | + | |
| 91 | 186 | self._brush_cursor_op = const.DEFAULT_BRUSH_OP |
| 92 | 187 | self._brush_cursor_size = const.BRUSH_SIZE |
| 93 | 188 | self._brush_cursor_colour = const.BRUSH_COLOUR |
| ... | ... | @@ -111,12 +206,17 @@ class Viewer(wx.Panel): |
| 111 | 206 | |
| 112 | 207 | scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) |
| 113 | 208 | self.scroll = scroll |
| 209 | + | |
| 210 | + self.mip_ctrls = ContourMIPConfig(self, self.orientation) | |
| 211 | + self.mip_ctrls.Hide() | |
| 212 | + | |
| 114 | 213 | sizer = wx.BoxSizer(wx.HORIZONTAL) |
| 115 | 214 | sizer.Add(self.interactor, 1, wx.EXPAND|wx.GROW) |
| 215 | + sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) | |
| 116 | 216 | |
| 117 | - background_sizer = wx.BoxSizer(wx.HORIZONTAL) | |
| 217 | + background_sizer = wx.BoxSizer(wx.VERTICAL) | |
| 118 | 218 | background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) |
| 119 | - background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) | |
| 219 | + #background_sizer.Add(self.mip_ctrls, 0, wx.EXPAND|wx.GROW|wx.ALL, 2) | |
| 120 | 220 | self.SetSizer(background_sizer) |
| 121 | 221 | background_sizer.Fit(self) |
| 122 | 222 | |
| ... | ... | @@ -644,6 +744,13 @@ class Viewer(wx.Panel): |
| 644 | 744 | Publisher.subscribe(self.ReloadActualSlice, 'Reload actual slice') |
| 645 | 745 | Publisher.subscribe(self.OnUpdateScroll, 'Update scroll') |
| 646 | 746 | |
| 747 | + | |
| 748 | + # MIP | |
| 749 | + Publisher.subscribe(self.OnSetMIPSize, 'Set MIP size %s' % self.orientation) | |
| 750 | + Publisher.subscribe(self.OnSetMIPBorder, 'Set MIP border %s' % self.orientation) | |
| 751 | + Publisher.subscribe(self.OnSetMIPInvert, 'Set MIP Invert %s' % self.orientation) | |
| 752 | + Publisher.subscribe(self.OnShowMIPInterface, 'Show MIP interface') | |
| 753 | + | |
| 647 | 754 | def SetDefaultCursor(self, pusub_evt): |
| 648 | 755 | self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) |
| 649 | 756 | |
| ... | ... | @@ -982,6 +1089,17 @@ class Viewer(wx.Panel): |
| 982 | 1089 | max_slice_number) |
| 983 | 1090 | self.set_scroll_position(0) |
| 984 | 1091 | |
| 1092 | + @property | |
| 1093 | + def number_slices(self): | |
| 1094 | + return self._number_slices | |
| 1095 | + | |
| 1096 | + @number_slices.setter | |
| 1097 | + def number_slices(self, val): | |
| 1098 | + if val != self._number_slices: | |
| 1099 | + self._number_slices = val | |
| 1100 | + buffer_ = self.slice_.buffer_slices[self.orientation] | |
| 1101 | + buffer_.discard_buffer() | |
| 1102 | + | |
| 985 | 1103 | def set_scroll_position(self, position): |
| 986 | 1104 | self.scroll.SetThumbPosition(position) |
| 987 | 1105 | self.OnScrollBar() |
| ... | ... | @@ -1016,10 +1134,19 @@ class Viewer(wx.Panel): |
| 1016 | 1134 | |
| 1017 | 1135 | def OnKeyDown(self, evt=None, obj=None): |
| 1018 | 1136 | pos = self.scroll.GetThumbPosition() |
| 1137 | + skip = True | |
| 1019 | 1138 | |
| 1020 | 1139 | min = 0 |
| 1021 | 1140 | max = self.slice_.GetMaxSliceNumber(self.orientation) |
| 1022 | 1141 | |
| 1142 | + projections = {wx.WXK_NUMPAD0 : const.PROJECTION_NORMAL, | |
| 1143 | + wx.WXK_NUMPAD1 : const.PROJECTION_MaxIP, | |
| 1144 | + wx.WXK_NUMPAD2 : const.PROJECTION_MinIP, | |
| 1145 | + wx.WXK_NUMPAD3 : const.PROJECTION_MeanIP, | |
| 1146 | + wx.WXK_NUMPAD4 : const.PROJECTION_MIDA, | |
| 1147 | + wx.WXK_NUMPAD5 : const.PROJECTION_CONTOUR_MIP, | |
| 1148 | + wx.WXK_NUMPAD6 : const.PROJECTION_CONTOUR_MIDA,} | |
| 1149 | + | |
| 1023 | 1150 | if self._flush_buffer: |
| 1024 | 1151 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
| 1025 | 1152 | |
| ... | ... | @@ -1030,11 +1157,31 @@ class Viewer(wx.Panel): |
| 1030 | 1157 | elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max): |
| 1031 | 1158 | self.OnScrollBackward() |
| 1032 | 1159 | self.OnScrollBar() |
| 1160 | + | |
| 1161 | + elif (evt.GetKeyCode() == wx.WXK_NUMPAD_ADD): | |
| 1162 | + actual_value = self.mip_ctrls.mip_size_spin.GetValue() | |
| 1163 | + self.mip_ctrls.mip_size_spin.SetValue(actual_value + 1) | |
| 1164 | + if self.mip_ctrls.mip_size_spin.GetValue() != actual_value: | |
| 1165 | + self.number_slices = self.mip_ctrls.mip_size_spin.GetValue() | |
| 1166 | + self.ReloadActualSlice() | |
| 1167 | + | |
| 1168 | + elif (evt.GetKeyCode() == wx.WXK_NUMPAD_SUBTRACT): | |
| 1169 | + actual_value = self.mip_ctrls.mip_size_spin.GetValue() | |
| 1170 | + self.mip_ctrls.mip_size_spin.SetValue(actual_value - 1) | |
| 1171 | + if self.mip_ctrls.mip_size_spin.GetValue() != actual_value: | |
| 1172 | + self.number_slices = self.mip_ctrls.mip_size_spin.GetValue() | |
| 1173 | + self.ReloadActualSlice() | |
| 1174 | + | |
| 1175 | + elif evt.GetKeyCode() in projections: | |
| 1176 | + self.slice_.SetTypeProjection(projections[evt.GetKeyCode()]) | |
| 1177 | + Publisher.sendMessage('Set projection type', projections[evt.GetKeyCode()]) | |
| 1178 | + Publisher.sendMessage('Reload actual slice') | |
| 1179 | + skip = False | |
| 1033 | 1180 | |
| 1034 | 1181 | self.UpdateSlice3D(pos) |
| 1035 | 1182 | self.interactor.Render() |
| 1036 | 1183 | |
| 1037 | - if evt: | |
| 1184 | + if evt and skip: | |
| 1038 | 1185 | evt.Skip() |
| 1039 | 1186 | |
| 1040 | 1187 | def OnScrollForward(self, evt=None, obj=None): |
| ... | ... | @@ -1067,15 +1214,55 @@ class Viewer(wx.Panel): |
| 1067 | 1214 | self.slice_data.SetSize((w, h)) |
| 1068 | 1215 | evt.Skip() |
| 1069 | 1216 | |
| 1217 | + def OnSetMIPSize(self, pubsub_evt): | |
| 1218 | + val = pubsub_evt.data | |
| 1219 | + self.number_slices = val | |
| 1220 | + self.ReloadActualSlice() | |
| 1221 | + | |
| 1222 | + def OnSetMIPBorder(self, pubsub_evt): | |
| 1223 | + val = pubsub_evt.data | |
| 1224 | + self.slice_.n_border = val | |
| 1225 | + buffer_ = self.slice_.buffer_slices[self.orientation] | |
| 1226 | + buffer_.discard_buffer() | |
| 1227 | + self.ReloadActualSlice() | |
| 1228 | + | |
| 1229 | + def OnSetMIPInvert(self, pubsub_evt): | |
| 1230 | + val = pubsub_evt.data | |
| 1231 | + self._mip_inverted = val | |
| 1232 | + buffer_ = self.slice_.buffer_slices[self.orientation] | |
| 1233 | + buffer_.discard_buffer() | |
| 1234 | + self.ReloadActualSlice() | |
| 1235 | + | |
| 1236 | + def OnShowMIPInterface(self, pubsub_evt): | |
| 1237 | + value = pubsub_evt.data | |
| 1238 | + if value: | |
| 1239 | + if not self.mip_ctrls.Shown: | |
| 1240 | + self.mip_ctrls.Show() | |
| 1241 | + self.GetSizer().Add(self.mip_ctrls, 0, wx.EXPAND|wx.GROW|wx.ALL, 2) | |
| 1242 | + self.Layout() | |
| 1243 | + else: | |
| 1244 | + self.mip_ctrls.Hide() | |
| 1245 | + self.GetSizer().Remove(self.mip_ctrls) | |
| 1246 | + self.Layout() | |
| 1247 | + | |
| 1248 | + | |
| 1070 | 1249 | def set_slice_number(self, index): |
| 1071 | - image = self.slice_.GetSlices(self.orientation, index) | |
| 1250 | + inverted = self.mip_ctrls.inverted.GetValue() | |
| 1251 | + border_size = self.mip_ctrls.border_spin.GetValue() | |
| 1252 | + image = self.slice_.GetSlices(self.orientation, index, | |
| 1253 | + self.number_slices, inverted, border_size) | |
| 1072 | 1254 | self.slice_data.actor.SetInput(image) |
| 1073 | 1255 | for actor in self.actors_by_slice_number.get(self.slice_data.number, []): |
| 1074 | 1256 | self.slice_data.renderer.RemoveActor(actor) |
| 1075 | 1257 | for actor in self.actors_by_slice_number.get(index, []): |
| 1076 | 1258 | self.slice_data.renderer.AddActor(actor) |
| 1077 | 1259 | |
| 1078 | - self.slice_data.SetNumber(index) | |
| 1260 | + if self.slice_._type_projection == const.PROJECTION_NORMAL: | |
| 1261 | + self.slice_data.SetNumber(index) | |
| 1262 | + else: | |
| 1263 | + max_slices = self.slice_.GetMaxSliceNumber(self.orientation) | |
| 1264 | + end = min(max_slices, index + self.number_slices - 1) | |
| 1265 | + self.slice_data.SetNumber(index, end) | |
| 1079 | 1266 | self.__update_display_extent(image) |
| 1080 | 1267 | self.cross.SetModelBounds(self.slice_data.actor.GetBounds()) |
| 1081 | 1268 | |
| ... | ... | @@ -1111,7 +1298,7 @@ class Viewer(wx.Panel): |
| 1111 | 1298 | coord[index] = extent_min[index] |
| 1112 | 1299 | return coord |
| 1113 | 1300 | |
| 1114 | - def ReloadActualSlice(self, pubsub_evt): | |
| 1301 | + def ReloadActualSlice(self, pubsub_evt=None): | |
| 1115 | 1302 | pos = self.scroll.GetThumbPosition() |
| 1116 | 1303 | self.set_slice_number(pos) |
| 1117 | 1304 | self.interactor.Render() | ... | ... |
invesalius/gui/data_notebook.py
| ... | ... | @@ -365,6 +365,7 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 365 | 365 | 'Change mask colour in notebook') |
| 366 | 366 | |
| 367 | 367 | Publisher.subscribe(self.OnChangeCurrentMask, 'Change mask selected') |
| 368 | + Publisher.subscribe(self.__hide_current_mask, 'Hide current mask') | |
| 368 | 369 | Publisher.subscribe(self.OnCloseProject, 'Close project data') |
| 369 | 370 | |
| 370 | 371 | def OnKeyEvent(self, event): |
| ... | ... | @@ -432,6 +433,10 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 432 | 433 | if key != mask_index: |
| 433 | 434 | self.SetItemImage(key, 0) |
| 434 | 435 | |
| 436 | + def __hide_current_mask(self, pubsub_evt): | |
| 437 | + print self.mask_list_index.keys() | |
| 438 | + self.SetItemImage(self.current_index, 0) | |
| 439 | + | |
| 435 | 440 | def __init_columns(self): |
| 436 | 441 | |
| 437 | 442 | self.InsertColumn(0, "", wx.LIST_FORMAT_CENTER) | ... | ... |
invesalius/gui/widgets/slice_menu.py
| ... | ... | @@ -20,6 +20,7 @@ |
| 20 | 20 | # detalhes. |
| 21 | 21 | #-------------------------------------------------------------------------- |
| 22 | 22 | import sys |
| 23 | +from collections import OrderedDict | |
| 23 | 24 | |
| 24 | 25 | import wx |
| 25 | 26 | from wx.lib.pubsub import pub as Publisher |
| ... | ... | @@ -30,6 +31,15 @@ import presets |
| 30 | 31 | |
| 31 | 32 | from gui.dialogs import ClutImagedataDialog |
| 32 | 33 | |
| 34 | +PROJECTIONS_ID = OrderedDict(((_('Normal'), const.PROJECTION_NORMAL), | |
| 35 | + (_('MaxIP'), const.PROJECTION_MaxIP), | |
| 36 | + (_('MinIP'), const.PROJECTION_MinIP), | |
| 37 | + (_('MeanIP'), const.PROJECTION_MeanIP), | |
| 38 | + (_('MIDA'), const.PROJECTION_MIDA), | |
| 39 | + (_('Contour MaxIP'), const.PROJECTION_CONTOUR_MIP), | |
| 40 | + (_('Contour MIDA'), const.PROJECTION_CONTOUR_MIDA),) ) | |
| 41 | + | |
| 42 | + | |
| 33 | 43 | class SliceMenu(wx.Menu): |
| 34 | 44 | def __init__(self): |
| 35 | 45 | wx.Menu.__init__(self) |
| ... | ... | @@ -111,6 +121,17 @@ class SliceMenu(wx.Menu): |
| 111 | 121 | submenu_pseudo_colours.AppendItem(color_item) |
| 112 | 122 | self.ID_TO_TOOL_ITEM[new_id] = color_item |
| 113 | 123 | self.pseudo_color_items[new_id] = color_item |
| 124 | + | |
| 125 | + # --------------- Sub menu of the projection type --------------------- | |
| 126 | + self.projection_items = {} | |
| 127 | + submenu_projection = wx.Menu() | |
| 128 | + for name in PROJECTIONS_ID: | |
| 129 | + new_id = wx.NewId() | |
| 130 | + projection_item = wx.MenuItem(submenu_projection, new_id, name, | |
| 131 | + kind=wx.ITEM_RADIO) | |
| 132 | + submenu_projection.AppendItem(projection_item) | |
| 133 | + self.ID_TO_TOOL_ITEM[new_id] = projection_item | |
| 134 | + self.projection_items[PROJECTIONS_ID[name]] = projection_item | |
| 114 | 135 | |
| 115 | 136 | flag_tiling = False |
| 116 | 137 | #------------ Sub menu of the image tiling --------------- |
| ... | ... | @@ -130,6 +151,7 @@ class SliceMenu(wx.Menu): |
| 130 | 151 | # Add sub itens in the menu |
| 131 | 152 | self.AppendMenu(-1, _("Window width and level"), submenu_wl) |
| 132 | 153 | self.AppendMenu(-1, _("Pseudo color"), submenu_pseudo_colours) |
| 154 | + self.AppendMenu(-1, _("Projection type"), submenu_projection) | |
| 133 | 155 | ###self.AppendMenu(-1, _("Image Tiling"), submenu_image_tiling) |
| 134 | 156 | |
| 135 | 157 | # It doesn't work in Linux |
| ... | ... | @@ -139,6 +161,7 @@ class SliceMenu(wx.Menu): |
| 139 | 161 | submenu_wl.Bind(wx.EVT_MENU, self.OnPopup) |
| 140 | 162 | submenu_pseudo_colours.Bind(wx.EVT_MENU, self.OnPopup) |
| 141 | 163 | submenu_image_tiling.Bind(wx.EVT_MENU, self.OnPopup) |
| 164 | + submenu_projection.Bind(wx.EVT_MENU, self.OnPopup) | |
| 142 | 165 | |
| 143 | 166 | self.__bind_events() |
| 144 | 167 | |
| ... | ... | @@ -146,6 +169,8 @@ class SliceMenu(wx.Menu): |
| 146 | 169 | Publisher.subscribe(self.CheckWindowLevelOther, 'Check window and level other') |
| 147 | 170 | Publisher.subscribe(self.FirstItemSelect, 'Select first item from slice menu') |
| 148 | 171 | Publisher.subscribe(self._close, 'Close project data') |
| 172 | + | |
| 173 | + Publisher.subscribe(self._check_projection_menu, 'Check projection menu') | |
| 149 | 174 | |
| 150 | 175 | def FirstItemSelect(self, pusub_evt): |
| 151 | 176 | item = self.ID_TO_TOOL_ITEM[self.id_wl_first] |
| ... | ... | @@ -165,10 +190,16 @@ class SliceMenu(wx.Menu): |
| 165 | 190 | item = self.ID_TO_TOOL_ITEM[self.other_wl_id] |
| 166 | 191 | item.Check() |
| 167 | 192 | |
| 193 | + def _check_projection_menu(self, pubsub_evt): | |
| 194 | + p_id = pubsub_evt.data | |
| 195 | + item = self.projection_items[p_id] | |
| 196 | + item.Check() | |
| 197 | + | |
| 168 | 198 | def OnPopup(self, evt): |
| 169 | 199 | id = evt.GetId() |
| 170 | 200 | item = self.ID_TO_TOOL_ITEM[evt.GetId()] |
| 171 | 201 | key = item.GetLabel() |
| 202 | + print 'Key', key | |
| 172 | 203 | if(key in const.WINDOW_LEVEL.keys()): |
| 173 | 204 | window, level = const.WINDOW_LEVEL[key] |
| 174 | 205 | Publisher.sendMessage('Bright and contrast adjustment image', |
| ... | ... | @@ -216,6 +247,12 @@ class SliceMenu(wx.Menu): |
| 216 | 247 | Publisher.sendMessage('Set slice viewer layout', values) |
| 217 | 248 | Publisher.sendMessage('Update slice viewer') |
| 218 | 249 | |
| 250 | + elif key in PROJECTIONS_ID: | |
| 251 | + print 'Key', key | |
| 252 | + pid = PROJECTIONS_ID[key] | |
| 253 | + Publisher.sendMessage('Set projection type', pid) | |
| 254 | + Publisher.sendMessage('Reload actual slice') | |
| 255 | + | |
| 219 | 256 | elif key == _('Custom'): |
| 220 | 257 | if self.cdialog is None: |
| 221 | 258 | slc = sl.Slice() | ... | ... |
| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | +from distutils.core import setup | |
| 2 | +from distutils.extension import Extension | |
| 3 | +from Cython.Distutils import build_ext | |
| 4 | + | |
| 5 | +import sys | |
| 6 | + | |
| 7 | +import numpy | |
| 8 | + | |
| 9 | +if sys.platform == 'linux2': | |
| 10 | + setup( | |
| 11 | + cmdclass = {'build_ext': build_ext}, | |
| 12 | + ext_modules = [ Extension("invesalius.data.mips", ["invesalius/data/mips.pyx"], | |
| 13 | + include_dirs = [numpy.get_include()], | |
| 14 | + extra_compile_args=['-fopenmp'], | |
| 15 | + extra_link_args=['-fopenmp'],)] | |
| 16 | + ) | |
| 17 | + | |
| 18 | +elif sys.platform == 'win32': | |
| 19 | + setup( | |
| 20 | + cmdclass = {'build_ext': build_ext}, | |
| 21 | + ext_modules = [ Extension("invesalius.data.mips", ["invesalius/data/mips.pyx"], | |
| 22 | + include_dirs = [numpy.get_include()], | |
| 23 | + extra_compile_args=['/openmp'], | |
| 24 | + )] | |
| 25 | + ) | |
| 26 | + | |
| 27 | +else: | |
| 28 | + setup( | |
| 29 | + cmdclass = {'build_ext': build_ext}, | |
| 30 | + ext_modules = [ Extension("invesalius.data.mips", ["invesalius/data/mips.pyx"], | |
| 31 | + include_dirs = [numpy.get_include()],)] | |
| 32 | + ) | ... | ... |