Commit c24db7d3ff62e2dc843811a4239fc6cf05fc0bb9

Authored by Thiago Franco de Moraes
2 parents d0d68211 fcc3fa2e

Merge pull request #19 from tfmoraes/MIPs

MIPs
invesalius/constants.py
@@ -292,7 +292,8 @@ WINDOW_LEVEL = {_("Abdomen"):(350,50), @@ -292,7 +292,8 @@ WINDOW_LEVEL = {_("Abdomen"):(350,50),
292 _("Pelvis"): (450,50), 292 _("Pelvis"): (450,50),
293 _("Sinus"):(4000, 400), 293 _("Sinus"):(4000, 400),
294 _("Vasculature - Hard"):(240,80), 294 _("Vasculature - Hard"):(240,80),
295 - _("Vasculature - Soft"):(650,160)} 295 + _("Vasculature - Soft"):(650,160),
  296 + _("Contour"): (255, 127)}
296 297
297 REDUCE_IMAGEDATA_QUALITY = 0 298 REDUCE_IMAGEDATA_QUALITY = 0
298 299
@@ -542,3 +543,18 @@ DICOM_ENCODING_TO_PYTHON = { @@ -542,3 +543,18 @@ DICOM_ENCODING_TO_PYTHON = {
542 'ISO_IR 138': 'iso_ir_138', 543 'ISO_IR 138': 'iso_ir_138',
543 'ISO_IR 144': 'iso_ir_144', 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
invesalius/data/mips.pyx 0 → 100644
@@ -0,0 +1,379 @@ @@ -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,6 +33,7 @@ import utils
33 33
34 from mask import Mask 34 from mask import Mask
35 from project import Project 35 from project import Project
  36 +from data import mips
36 37
37 OTHER=0 38 OTHER=0
38 PLIST=1 39 PLIST=1
@@ -83,6 +84,10 @@ class Slice(object): @@ -83,6 +84,10 @@ class Slice(object):
83 self.blend_filter = None 84 self.blend_filter = None
84 self.histogram = None 85 self.histogram = None
85 self._matrix = None 86 self._matrix = None
  87 +
  88 + self._type_projection = const.PROJECTION_NORMAL
  89 + self.n_border = const.PROJECTION_BORDER_SIZE
  90 +
86 self.spacing = (1.0, 1.0, 1.0) 91 self.spacing = (1.0, 1.0, 1.0)
87 92
88 self.number_of_colours = 256 93 self.number_of_colours = 256
@@ -111,7 +116,7 @@ class Slice(object): @@ -111,7 +116,7 @@ class Slice(object):
111 def matrix(self, value): 116 def matrix(self, value):
112 self._matrix = value 117 self._matrix = value
113 i, e = value.min(), value.max() 118 i, e = value.min(), value.max()
114 - r = e - i 119 + r = int(e) - int(i)
115 self.histogram = numpy.histogram(self._matrix, r, (i, e))[0] 120 self.histogram = numpy.histogram(self._matrix, r, (i, e))[0]
116 121
117 def __bind_events(self): 122 def __bind_events(self):
@@ -133,6 +138,7 @@ class Slice(object): @@ -133,6 +138,7 @@ class Slice(object):
133 'Change mask colour') 138 'Change mask colour')
134 Publisher.subscribe(self.__set_mask_name, 'Change mask name') 139 Publisher.subscribe(self.__set_mask_name, 'Change mask name')
135 Publisher.subscribe(self.__show_mask, 'Show mask') 140 Publisher.subscribe(self.__show_mask, 'Show mask')
  141 + Publisher.subscribe(self.__hide_current_mask, 'Hide current mask')
136 142
137 Publisher.subscribe(self.__set_current_mask_threshold_limits, 143 Publisher.subscribe(self.__set_current_mask_threshold_limits,
138 'Update threshold limits') 144 'Update threshold limits')
@@ -148,6 +154,7 @@ class Slice(object): @@ -148,6 +154,7 @@ class Slice(object):
148 154
149 Publisher.subscribe(self.UpdateColourTableBackgroundWidget,\ 155 Publisher.subscribe(self.UpdateColourTableBackgroundWidget,\
150 'Change colour table from background image from widget') 156 'Change colour table from background image from widget')
  157 + Publisher.subscribe(self._set_projection_type, 'Set projection type')
151 158
152 Publisher.subscribe(self.InputImageWidget, 'Input Image in the widget') 159 Publisher.subscribe(self.InputImageWidget, 'Input Image in the widget')
153 160
@@ -346,6 +353,16 @@ class Slice(object): @@ -346,6 +353,16 @@ class Slice(object):
346 if not value: 353 if not value:
347 Publisher.sendMessage('Select mask name in combo', -1) 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 def edit_mask_pixel(self, operation, index, position, radius, orientation): 366 def edit_mask_pixel(self, operation, index, position, radius, orientation):
350 mask = self.buffer_slices[orientation].mask 367 mask = self.buffer_slices[orientation].mask
351 image = self.buffer_slices[orientation].image 368 image = self.buffer_slices[orientation].image
@@ -421,12 +438,16 @@ class Slice(object): @@ -421,12 +438,16 @@ class Slice(object):
421 self.buffer_slices[orientation].discard_vtk_mask() 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 if self.buffer_slices[orientation].vtk_image: 445 if self.buffer_slices[orientation].vtk_image:
427 image = self.buffer_slices[orientation].vtk_image 446 image = self.buffer_slices[orientation].vtk_image
428 else: 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 image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) 451 image = converters.to_vtk(n_image, self.spacing, slice_number, orientation)
431 ww_wl_image = self.do_ww_wl(image) 452 ww_wl_image = self.do_ww_wl(image)
432 image = self.do_colour_image(ww_wl_image) 453 image = self.do_colour_image(ww_wl_image)
@@ -446,7 +467,8 @@ class Slice(object): @@ -446,7 +467,8 @@ class Slice(object):
446 final_image = image 467 final_image = image
447 self.buffer_slices[orientation].vtk_image = image 468 self.buffer_slices[orientation].vtk_image = image
448 else: 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 image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) 472 image = converters.to_vtk(n_image, self.spacing, slice_number, orientation)
451 ww_wl_image = self.do_ww_wl(image) 473 ww_wl_image = self.do_ww_wl(image)
452 image = self.do_colour_image(ww_wl_image) 474 image = self.do_colour_image(ww_wl_image)
@@ -469,17 +491,151 @@ class Slice(object): @@ -469,17 +491,151 @@ class Slice(object):
469 491
470 return final_image 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 if self.buffer_slices[orientation].index == slice_number \ 496 if self.buffer_slices[orientation].index == slice_number \
474 and self.buffer_slices[orientation].image is not None: 497 and self.buffer_slices[orientation].image is not None:
475 n_image = self.buffer_slices[orientation].image 498 n_image = self.buffer_slices[orientation].image
476 else: 499 else:
  500 +
477 if orientation == 'AXIAL': 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 elif orientation == 'CORONAL': 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 elif orientation == 'SAGITAL': 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 return n_image 639 return n_image
484 640
485 def get_mask_slice(self, orientation, slice_number): 641 def get_mask_slice(self, orientation, slice_number):
@@ -672,6 +828,26 @@ class Slice(object): @@ -672,6 +828,26 @@ class Slice(object):
672 def GetOutput(self): 828 def GetOutput(self):
673 return self.blend_filter.GetOutput() 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 def SetInput(self, imagedata, mask_dict): 851 def SetInput(self, imagedata, mask_dict):
676 print "SETINPUT!" 852 print "SETINPUT!"
677 self.imagedata = imagedata 853 self.imagedata = imagedata
@@ -731,7 +907,14 @@ class Slice(object): @@ -731,7 +907,14 @@ class Slice(object):
731 self.window_level = level 907 self.window_level = level
732 908
733 for buffer_ in self.buffer_slices.values(): 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 Publisher.sendMessage('Reload actual slice') 919 Publisher.sendMessage('Reload actual slice')
737 920
@@ -758,7 +941,14 @@ class Slice(object): @@ -758,7 +941,14 @@ class Slice(object):
758 self.nodes = pubsub_evt.data 941 self.nodes = pubsub_evt.data
759 self.from_= WIDGET 942 self.from_= WIDGET
760 for buffer_ in self.buffer_slices.values(): 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 knodes = sorted(self.nodes) 953 knodes = sorted(self.nodes)
764 p0 = knodes[0].value 954 p0 = knodes[0].value
invesalius/data/slice_data.py
@@ -136,9 +136,13 @@ class SliceData(object): @@ -136,9 +136,13 @@ class SliceData(object):
136 self.overlay_renderer.AddActor(cursor.actor) 136 self.overlay_renderer.AddActor(cursor.actor)
137 self.cursor = cursor 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 self.text.SetPosition(const.TEXT_POS_LEFT_DOWN_ZERO) 146 self.text.SetPosition(const.TEXT_POS_LEFT_DOWN_ZERO)
143 147
144 def SetOrientation(self, orientation): 148 def SetOrientation(self, orientation):
invesalius/data/viewer_slice.py
@@ -32,6 +32,11 @@ import styles @@ -32,6 +32,11 @@ import styles
32 import wx 32 import wx
33 from wx.lib.pubsub import pub as Publisher 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 import constants as const 40 import constants as const
36 import cursor_actors as ca 41 import cursor_actors as ca
37 import data.slice_ as sl 42 import data.slice_ as sl
@@ -51,6 +56,93 @@ ORIENTATIONS = { @@ -51,6 +56,93 @@ ORIENTATIONS = {
51 "SAGITAL": const.SAGITAL, 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 class Viewer(wx.Panel): 146 class Viewer(wx.Panel):
55 147
56 def __init__(self, prnt, orientation='AXIAL'): 148 def __init__(self, prnt, orientation='AXIAL'):
@@ -63,6 +155,9 @@ class Viewer(wx.Panel): @@ -63,6 +155,9 @@ class Viewer(wx.Panel):
63 #self.modes = []#['DEFAULT'] 155 #self.modes = []#['DEFAULT']
64 self.left_pressed = 0 156 self.left_pressed = 0
65 self.right_pressed = 0 157 self.right_pressed = 0
  158 +
  159 + self._number_slices = const.PROJECTION_MIP_SIZE
  160 + self._mip_inverted = False
66 161
67 self.spined_image = False #Use to control to spin 162 self.spined_image = False #Use to control to spin
68 self.paned_image = False 163 self.paned_image = False
@@ -83,11 +178,11 @@ class Viewer(wx.Panel): @@ -83,11 +178,11 @@ class Viewer(wx.Panel):
83 self.actors_by_slice_number = {} 178 self.actors_by_slice_number = {}
84 self.renderers_by_slice_number = {} 179 self.renderers_by_slice_number = {}
85 180
86 - self.__init_gui()  
87 -  
88 self.orientation = orientation 181 self.orientation = orientation
89 self.slice_number = 0 182 self.slice_number = 0
90 183
  184 + self.__init_gui()
  185 +
91 self._brush_cursor_op = const.DEFAULT_BRUSH_OP 186 self._brush_cursor_op = const.DEFAULT_BRUSH_OP
92 self._brush_cursor_size = const.BRUSH_SIZE 187 self._brush_cursor_size = const.BRUSH_SIZE
93 self._brush_cursor_colour = const.BRUSH_COLOUR 188 self._brush_cursor_colour = const.BRUSH_COLOUR
@@ -111,12 +206,17 @@ class Viewer(wx.Panel): @@ -111,12 +206,17 @@ class Viewer(wx.Panel):
111 206
112 scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) 207 scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL)
113 self.scroll = scroll 208 self.scroll = scroll
  209 +
  210 + self.mip_ctrls = ContourMIPConfig(self, self.orientation)
  211 + self.mip_ctrls.Hide()
  212 +
114 sizer = wx.BoxSizer(wx.HORIZONTAL) 213 sizer = wx.BoxSizer(wx.HORIZONTAL)
115 sizer.Add(self.interactor, 1, wx.EXPAND|wx.GROW) 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 background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) 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 self.SetSizer(background_sizer) 220 self.SetSizer(background_sizer)
121 background_sizer.Fit(self) 221 background_sizer.Fit(self)
122 222
@@ -644,6 +744,13 @@ class Viewer(wx.Panel): @@ -644,6 +744,13 @@ class Viewer(wx.Panel):
644 Publisher.subscribe(self.ReloadActualSlice, 'Reload actual slice') 744 Publisher.subscribe(self.ReloadActualSlice, 'Reload actual slice')
645 Publisher.subscribe(self.OnUpdateScroll, 'Update scroll') 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 def SetDefaultCursor(self, pusub_evt): 754 def SetDefaultCursor(self, pusub_evt):
648 self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) 755 self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
649 756
@@ -982,6 +1089,17 @@ class Viewer(wx.Panel): @@ -982,6 +1089,17 @@ class Viewer(wx.Panel):
982 max_slice_number) 1089 max_slice_number)
983 self.set_scroll_position(0) 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 def set_scroll_position(self, position): 1103 def set_scroll_position(self, position):
986 self.scroll.SetThumbPosition(position) 1104 self.scroll.SetThumbPosition(position)
987 self.OnScrollBar() 1105 self.OnScrollBar()
@@ -1016,10 +1134,19 @@ class Viewer(wx.Panel): @@ -1016,10 +1134,19 @@ class Viewer(wx.Panel):
1016 1134
1017 def OnKeyDown(self, evt=None, obj=None): 1135 def OnKeyDown(self, evt=None, obj=None):
1018 pos = self.scroll.GetThumbPosition() 1136 pos = self.scroll.GetThumbPosition()
  1137 + skip = True
1019 1138
1020 min = 0 1139 min = 0
1021 max = self.slice_.GetMaxSliceNumber(self.orientation) 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 if self._flush_buffer: 1150 if self._flush_buffer:
1024 self.slice_.apply_slice_buffer_to_mask(self.orientation) 1151 self.slice_.apply_slice_buffer_to_mask(self.orientation)
1025 1152
@@ -1030,11 +1157,31 @@ class Viewer(wx.Panel): @@ -1030,11 +1157,31 @@ class Viewer(wx.Panel):
1030 elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max): 1157 elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max):
1031 self.OnScrollBackward() 1158 self.OnScrollBackward()
1032 self.OnScrollBar() 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 self.UpdateSlice3D(pos) 1181 self.UpdateSlice3D(pos)
1035 self.interactor.Render() 1182 self.interactor.Render()
1036 1183
1037 - if evt: 1184 + if evt and skip:
1038 evt.Skip() 1185 evt.Skip()
1039 1186
1040 def OnScrollForward(self, evt=None, obj=None): 1187 def OnScrollForward(self, evt=None, obj=None):
@@ -1067,15 +1214,55 @@ class Viewer(wx.Panel): @@ -1067,15 +1214,55 @@ class Viewer(wx.Panel):
1067 self.slice_data.SetSize((w, h)) 1214 self.slice_data.SetSize((w, h))
1068 evt.Skip() 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 def set_slice_number(self, index): 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 self.slice_data.actor.SetInput(image) 1254 self.slice_data.actor.SetInput(image)
1073 for actor in self.actors_by_slice_number.get(self.slice_data.number, []): 1255 for actor in self.actors_by_slice_number.get(self.slice_data.number, []):
1074 self.slice_data.renderer.RemoveActor(actor) 1256 self.slice_data.renderer.RemoveActor(actor)
1075 for actor in self.actors_by_slice_number.get(index, []): 1257 for actor in self.actors_by_slice_number.get(index, []):
1076 self.slice_data.renderer.AddActor(actor) 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 self.__update_display_extent(image) 1266 self.__update_display_extent(image)
1080 self.cross.SetModelBounds(self.slice_data.actor.GetBounds()) 1267 self.cross.SetModelBounds(self.slice_data.actor.GetBounds())
1081 1268
@@ -1111,7 +1298,7 @@ class Viewer(wx.Panel): @@ -1111,7 +1298,7 @@ class Viewer(wx.Panel):
1111 coord[index] = extent_min[index] 1298 coord[index] = extent_min[index]
1112 return coord 1299 return coord
1113 1300
1114 - def ReloadActualSlice(self, pubsub_evt): 1301 + def ReloadActualSlice(self, pubsub_evt=None):
1115 pos = self.scroll.GetThumbPosition() 1302 pos = self.scroll.GetThumbPosition()
1116 self.set_slice_number(pos) 1303 self.set_slice_number(pos)
1117 self.interactor.Render() 1304 self.interactor.Render()
invesalius/gui/data_notebook.py
@@ -365,6 +365,7 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): @@ -365,6 +365,7 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin):
365 'Change mask colour in notebook') 365 'Change mask colour in notebook')
366 366
367 Publisher.subscribe(self.OnChangeCurrentMask, 'Change mask selected') 367 Publisher.subscribe(self.OnChangeCurrentMask, 'Change mask selected')
  368 + Publisher.subscribe(self.__hide_current_mask, 'Hide current mask')
368 Publisher.subscribe(self.OnCloseProject, 'Close project data') 369 Publisher.subscribe(self.OnCloseProject, 'Close project data')
369 370
370 def OnKeyEvent(self, event): 371 def OnKeyEvent(self, event):
@@ -432,6 +433,10 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): @@ -432,6 +433,10 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin):
432 if key != mask_index: 433 if key != mask_index:
433 self.SetItemImage(key, 0) 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 def __init_columns(self): 440 def __init_columns(self):
436 441
437 self.InsertColumn(0, "", wx.LIST_FORMAT_CENTER) 442 self.InsertColumn(0, "", wx.LIST_FORMAT_CENTER)
invesalius/gui/widgets/slice_menu.py
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 # detalhes. 20 # detalhes.
21 #-------------------------------------------------------------------------- 21 #--------------------------------------------------------------------------
22 import sys 22 import sys
  23 +from collections import OrderedDict
23 24
24 import wx 25 import wx
25 from wx.lib.pubsub import pub as Publisher 26 from wx.lib.pubsub import pub as Publisher
@@ -30,6 +31,15 @@ import presets @@ -30,6 +31,15 @@ import presets
30 31
31 from gui.dialogs import ClutImagedataDialog 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 class SliceMenu(wx.Menu): 43 class SliceMenu(wx.Menu):
34 def __init__(self): 44 def __init__(self):
35 wx.Menu.__init__(self) 45 wx.Menu.__init__(self)
@@ -111,6 +121,17 @@ class SliceMenu(wx.Menu): @@ -111,6 +121,17 @@ class SliceMenu(wx.Menu):
111 submenu_pseudo_colours.AppendItem(color_item) 121 submenu_pseudo_colours.AppendItem(color_item)
112 self.ID_TO_TOOL_ITEM[new_id] = color_item 122 self.ID_TO_TOOL_ITEM[new_id] = color_item
113 self.pseudo_color_items[new_id] = color_item 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 flag_tiling = False 136 flag_tiling = False
116 #------------ Sub menu of the image tiling --------------- 137 #------------ Sub menu of the image tiling ---------------
@@ -130,6 +151,7 @@ class SliceMenu(wx.Menu): @@ -130,6 +151,7 @@ class SliceMenu(wx.Menu):
130 # Add sub itens in the menu 151 # Add sub itens in the menu
131 self.AppendMenu(-1, _("Window width and level"), submenu_wl) 152 self.AppendMenu(-1, _("Window width and level"), submenu_wl)
132 self.AppendMenu(-1, _("Pseudo color"), submenu_pseudo_colours) 153 self.AppendMenu(-1, _("Pseudo color"), submenu_pseudo_colours)
  154 + self.AppendMenu(-1, _("Projection type"), submenu_projection)
133 ###self.AppendMenu(-1, _("Image Tiling"), submenu_image_tiling) 155 ###self.AppendMenu(-1, _("Image Tiling"), submenu_image_tiling)
134 156
135 # It doesn't work in Linux 157 # It doesn't work in Linux
@@ -139,6 +161,7 @@ class SliceMenu(wx.Menu): @@ -139,6 +161,7 @@ class SliceMenu(wx.Menu):
139 submenu_wl.Bind(wx.EVT_MENU, self.OnPopup) 161 submenu_wl.Bind(wx.EVT_MENU, self.OnPopup)
140 submenu_pseudo_colours.Bind(wx.EVT_MENU, self.OnPopup) 162 submenu_pseudo_colours.Bind(wx.EVT_MENU, self.OnPopup)
141 submenu_image_tiling.Bind(wx.EVT_MENU, self.OnPopup) 163 submenu_image_tiling.Bind(wx.EVT_MENU, self.OnPopup)
  164 + submenu_projection.Bind(wx.EVT_MENU, self.OnPopup)
142 165
143 self.__bind_events() 166 self.__bind_events()
144 167
@@ -146,6 +169,8 @@ class SliceMenu(wx.Menu): @@ -146,6 +169,8 @@ class SliceMenu(wx.Menu):
146 Publisher.subscribe(self.CheckWindowLevelOther, 'Check window and level other') 169 Publisher.subscribe(self.CheckWindowLevelOther, 'Check window and level other')
147 Publisher.subscribe(self.FirstItemSelect, 'Select first item from slice menu') 170 Publisher.subscribe(self.FirstItemSelect, 'Select first item from slice menu')
148 Publisher.subscribe(self._close, 'Close project data') 171 Publisher.subscribe(self._close, 'Close project data')
  172 +
  173 + Publisher.subscribe(self._check_projection_menu, 'Check projection menu')
149 174
150 def FirstItemSelect(self, pusub_evt): 175 def FirstItemSelect(self, pusub_evt):
151 item = self.ID_TO_TOOL_ITEM[self.id_wl_first] 176 item = self.ID_TO_TOOL_ITEM[self.id_wl_first]
@@ -165,10 +190,16 @@ class SliceMenu(wx.Menu): @@ -165,10 +190,16 @@ class SliceMenu(wx.Menu):
165 item = self.ID_TO_TOOL_ITEM[self.other_wl_id] 190 item = self.ID_TO_TOOL_ITEM[self.other_wl_id]
166 item.Check() 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 def OnPopup(self, evt): 198 def OnPopup(self, evt):
169 id = evt.GetId() 199 id = evt.GetId()
170 item = self.ID_TO_TOOL_ITEM[evt.GetId()] 200 item = self.ID_TO_TOOL_ITEM[evt.GetId()]
171 key = item.GetLabel() 201 key = item.GetLabel()
  202 + print 'Key', key
172 if(key in const.WINDOW_LEVEL.keys()): 203 if(key in const.WINDOW_LEVEL.keys()):
173 window, level = const.WINDOW_LEVEL[key] 204 window, level = const.WINDOW_LEVEL[key]
174 Publisher.sendMessage('Bright and contrast adjustment image', 205 Publisher.sendMessage('Bright and contrast adjustment image',
@@ -216,6 +247,12 @@ class SliceMenu(wx.Menu): @@ -216,6 +247,12 @@ class SliceMenu(wx.Menu):
216 Publisher.sendMessage('Set slice viewer layout', values) 247 Publisher.sendMessage('Set slice viewer layout', values)
217 Publisher.sendMessage('Update slice viewer') 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 elif key == _('Custom'): 256 elif key == _('Custom'):
220 if self.cdialog is None: 257 if self.cdialog is None:
221 slc = sl.Slice() 258 slc = sl.Slice()
setup.py 0 → 100644
@@ -0,0 +1,32 @@ @@ -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 + )