Commit 7b7b807a6b317cdece2e24c82330f9e83947e44b

Authored by tfmoraes
1 parent fe6a94e1

Merging improvement in editor branch to master

invesalius/data/cursor_actors.py
... ... @@ -19,6 +19,7 @@
19 19  
20 20 from math import *
21 21  
  22 +import numpy
22 23 import vtk
23 24 import wx.lib.pubsub as ps
24 25 from project import Project
... ... @@ -89,50 +90,24 @@ class CursorCircle:
89 90 def __calculate_area_pixels(self):
90 91 """
91 92 Return the cursor's pixels.
92   - This method scans the circle line by line.
93   - Extracted equation.
94   - http://www.mathopenref.com/chord.html
95 93 """
96   - xc = 0.0
97   - yc = 0.0
98   - z = 0.0
99   - xs, ys, zs = self.spacing
100   -
101   - proj = Project()
102   - orig_orien = proj.original_orientation
103   -
104   - xy = (xs, ys)
105   - yz = (ys, zs)
106   - xz = (xs, zs)
107   -
108   - if (orig_orien == const.SAGITAL):
109   - orientation_based_spacing = {"SAGITAL" : xy,
110   - "AXIAL" : yz,
111   - "CORONAL" : xz}
112   - elif(orig_orien == const.CORONAL):
113   - orientation_based_spacing = {"CORONAL" : xy,
114   - "AXIAL" : xz,
115   - "SAGITAL" : yz}
116   - else:
117   - orientation_based_spacing = {"AXIAL" : xy,
118   - "SAGITAL" : yz,
119   - "CORONAL" : xz}
120   -
121   - xs, ys = orientation_based_spacing[self.orientation]
122   - self.pixel_list = []
123 94 radius = self.radius
124   - for i in utils.frange(yc - radius, yc + radius, ys):
125   - # distance from the line to the circle's center
126   - d = yc - i
127   - # line size
128   - line = sqrt(radius**2 - d**2) * 2
129   - # line initial x
130   - xi = xc - line/2.0
131   - # line final
132   - xf = line/2.0 + xc
133   - yi = i
134   - for k in utils.frange(xi,xf,xs):
135   - self.pixel_list.append((k, yi))
  95 + if self.orientation == 'AXIAL':
  96 + sx = self.spacing[0]
  97 + sy = self.spacing[1]
  98 + elif self.orientation == 'CORONAL':
  99 + sx = self.spacing[0]
  100 + sy = self.spacing[2]
  101 + elif self.orientation == 'SAGITAL':
  102 + sx = self.spacing[1]
  103 + sy = self.spacing[2]
  104 +
  105 + y,x = numpy.ogrid[-radius/sy:+radius/sy,
  106 + -radius/sx:+radius/sx]
  107 +
  108 + index = (y*sy)**2 + (x*sx)**2 <= radius**2
  109 + self.points = index
  110 +
136 111  
137 112 def SetSize(self, diameter):
138 113 radius = self.radius = diameter/2.0
... ... @@ -185,38 +160,7 @@ class CursorCircle:
185 160 self.actor.VisibilityOff()
186 161  
187 162 def GetPixels(self):
188   - px, py, pz = self.edition_position
189   - orient = self.orientation
190   - xs, ys, zs = self.spacing
191   - proj = Project()
192   - orig_orien = proj.original_orientation
193   - xy1 = lambda x,y: (px + x / xs, py+(y/ys), pz)
194   - xy2 = lambda x,y: (px+(x/xs), py, pz+(y/zs))
195   - xy3 = lambda x,y: (px, py+(x/ys), pz+(y/zs))
196   -
197   - if (orig_orien == const.SAGITAL):
198   - abs_pixel = {"SAGITAL": xy1,
199   - "AXIAL": xy2,
200   - "CORONAL": xy3}
201   - elif(orig_orien == const.CORONAL):
202   - abs_pixel = {"CORONAL": xy1,
203   - "SAGITAL": xy3,
204   - "AXIAL": xy2}
205   - else:
206   - abs_pixel = {"AXIAL": xy1,
207   - "CORONAL": xy2,
208   - "SAGITAL": xy3}
209   -
210   - function_orientation = abs_pixel[orient]
211   -
212   - for pixel_0,pixel_1 in self.pixel_list:
213   - # The position of the pixels in this list is relative (based only on
214   - # the area, and not the cursor position).
215   - # Let's calculate the absolute position
216   - # TODO: Optimize this!!!!
217   - yield function_orientation(pixel_0, pixel_1)
218   -
219   -
  163 + return self.points
220 164  
221 165  
222 166 class CursorRectangle:
... ... @@ -228,6 +172,7 @@ class CursorRectangle:
228 172  
229 173 self.x_length = 30
230 174 self.y_length = 30
  175 + self.radius = 15
231 176  
232 177 self.dimension = (self.x_length, self.y_length)
233 178 self.position = (0 ,0)
... ... @@ -240,6 +185,7 @@ class CursorRectangle:
240 185 def SetSize(self, size):
241 186 self.x_length = size
242 187 self.y_length = size
  188 + self.radius = size / 2
243 189 retangle = self.retangle
244 190 retangle.SetXLength(size)
245 191 retangle.SetYLength(size)
... ... @@ -311,67 +257,21 @@ class CursorRectangle:
311 257 actor.SetVisibility(0)
312 258  
313 259 def __calculate_area_pixels(self):
314   - xc = 0
315   - yc = 0
316   - z = 0
317   - xs, ys, zs = self.spacing
318   -
319   - proj = Project()
320   - orig_orien = proj.original_orientation
321   -
322   - xy = (xs, ys)
323   - yz = (ys, zs)
324   - xz = (xs, zs)
325   -
326   - if (orig_orien == const.SAGITAL):
327   - orientation_based_spacing = {"SAGITAL" : xy,
328   - "AXIAL" : yz,
329   - "CORONAL" : xz}
330   - elif(orig_orien == const.CORONAL):
331   - orientation_based_spacing = {"CORONAL" : xy,
332   - "AXIAL" : xz,
333   - "SAGITAL" : yz}
334   - else:
335   - orientation_based_spacing = {"AXIAL" : xy,
336   - "SAGITAL" : yz,
337   - "CORONAL" : xz}
338   -
339   - xs, ys = orientation_based_spacing[self.orientation]
340   - self.pixel_list = []
341   - for i in utils.frange(yc - self.y_length/2, yc + self.y_length/2, ys):
342   - for k in utils.frange(xc - self.x_length/2, xc + self.x_length/2, xs):
343   - self.pixel_list.append((k, i))
344   -
  260 + if self.orientation == 'AXIAL':
  261 + sx = self.spacing[0]
  262 + sy = self.spacing[1]
  263 + elif self.orientation == 'CORONAL':
  264 + sx = self.spacing[0]
  265 + sy = self.spacing[2]
  266 + elif self.orientation == 'SAGITAL':
  267 + sx = self.spacing[1]
  268 + sy = self.spacing[2]
  269 + shape = (self.y_length/sy, self.x_length/sx)
  270 + self.points = numpy.empty(shape, dtype='bool')
  271 + self.points.fill(True)
345 272  
346 273 def GetPixels(self):
347 274 """
348 275 Return the points of the rectangle
349 276 """
350   - px, py, pz = self.edition_position
351   - orient = self.orientation
352   - xs, ys, zs = self.spacing
353   - proj = Project()
354   - orig_orien = proj.original_orientation
355   - xy1 = lambda x,y: (px + x / xs, py+(y/ys), pz)
356   - xy2 = lambda x,y: (px+(x/xs), py, pz+(y/zs))
357   - xy3 = lambda x,y: (px, py+(x/ys), pz+(y/zs))
358   -
359   - if (orig_orien == const.SAGITAL):
360   - abs_pixel = {"SAGITAL": xy1,
361   - "AXIAL": xy2,
362   - "CORONAL": xy3}
363   - elif(orig_orien == const.CORONAL):
364   - abs_pixel = {"CORONAL": xy1,
365   - "SAGITAL": xy3,
366   - "AXIAL": xy2}
367   - else:
368   - abs_pixel = {"AXIAL": xy1,
369   - "CORONAL": xy2,
370   - "SAGITAL": xy3}
371   - function_orientation = abs_pixel[orient]
372   - for pixel_0,pixel_1 in self.pixel_list:
373   - # The position of the pixels in this list is relative (based only on
374   - # the area, and not the cursor position).
375   - # Let's calculate the absolute position
376   - # TODO: Optimize this!!!!
377   - yield function_orientation(pixel_0, pixel_1)
  277 + return self.points
... ...
invesalius/data/slice_.py
... ... @@ -16,6 +16,8 @@
16 16 # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
17 17 # detalhes.
18 18 #--------------------------------------------------------------------------
  19 +import math
  20 +
19 21 import numpy
20 22 import vtk
21 23 import wx.lib.pubsub as ps
... ... @@ -101,11 +103,6 @@ class Slice(object):
101 103 ps.Publisher().subscribe(self.__set_mask_name, 'Change mask name')
102 104 ps.Publisher().subscribe(self.__show_mask, 'Show mask')
103 105  
104   - # Operations related to slice editor
105   - ps.Publisher().subscribe(self.__erase_mask_pixel, 'Erase mask pixel')
106   - ps.Publisher().subscribe(self.__edit_mask_pixel, 'Edit mask pixel')
107   - ps.Publisher().subscribe(self.__add_mask_pixel, 'Add mask pixel')
108   -
109 106 ps.Publisher().subscribe(self.__set_current_mask_threshold_limits,
110 107 'Update threshold limits')
111 108  
... ... @@ -249,9 +246,7 @@ class Slice(object):
249 246 #Clear edited points
250 247 self.current_mask.edited_points = {}
251 248 self.num_gradient += 1
252   - self.current_mask.matrix[0, :, :] = 0
253   - self.current_mask.matrix[:, 0, :] = 0
254   - self.current_mask.matrix[:, :, 0] = 0
  249 + self.current_mask.matrix[:] = 0
255 250  
256 251 def __set_current_mask_threshold_actual_slice(self, evt_pubsub):
257 252 threshold_range = evt_pubsub.data
... ... @@ -288,20 +283,222 @@ class Slice(object):
288 283 index, value = pubsub_evt.data
289 284 self.ShowMask(index, value)
290 285 #---------------------------------------------------------------------------
291   - def __erase_mask_pixel(self, pubsub_evt):
292   - positions = pubsub_evt.data
293   - for position in positions:
294   - self.ErasePixel(position)
  286 + def erase_mask_pixel(self, index, position, radius, orientation):
  287 + mask = self.buffer_slices[orientation].mask
  288 + image = self.buffer_slices[orientation].image
  289 +
  290 + if hasattr(position, '__iter__'):
  291 + py, px = position
  292 + if orientation == 'AXIAL':
  293 + sx = self.spacing[0]
  294 + sy = self.spacing[1]
  295 + elif orientation == 'CORONAL':
  296 + sx = self.spacing[0]
  297 + sy = self.spacing[2]
  298 + elif orientation == 'SAGITAL':
  299 + sx = self.spacing[1]
  300 + sy = self.spacing[2]
  301 +
  302 + else:
  303 + if orientation == 'AXIAL':
  304 + sx = self.spacing[0]
  305 + sy = self.spacing[1]
  306 + py = position / mask.shape[1]
  307 + px = position % mask.shape[1]
  308 + elif orientation == 'CORONAL':
  309 + sx = self.spacing[0]
  310 + sy = self.spacing[2]
  311 + py = position / mask.shape[1]
  312 + px = position % mask.shape[1]
  313 + elif orientation == 'SAGITAL':
  314 + sx = self.spacing[1]
  315 + sy = self.spacing[2]
  316 + py = position / mask.shape[1]
  317 + px = position % mask.shape[1]
  318 +
  319 + xi = px - math.ceil(radius/sx)
  320 + xf = px + math.ceil(radius/sx)
  321 + yi = py - math.ceil(radius/sy)
  322 + yf = py + math.ceil(radius/sy)
  323 +
  324 + if yi < 0:
  325 + index = index[abs(yi):,:]
  326 + yi = 0
  327 + if yf > image.shape[0]:
  328 + index = index[:index.shape[0]-(yf-image.shape[0]), :]
  329 + yf = image.shape[0]
  330 +
  331 + if xi < 0:
  332 + index = index[:,abs(xi):]
  333 + xi = 0
  334 + if xf > image.shape[1]:
  335 + index = index[:,:index.shape[1]-(xf-image.shape[1])]
  336 + xf = image.shape[1]
  337 +
  338 + # Verifying if the points is over the image array.
  339 + if (not 0 < xi < image.shape[1] and not 0 < xf < image.shape[1]) or \
  340 + (not 0 < yi < image.shape[0] and not 0 < yf < image.shape[0]):
  341 + return
  342 +
  343 + roi_m = mask[yi:yf,xi:xf]
  344 + roi_i = image[yi:yf, xi:xf]
  345 +
  346 + roi_m[index] = 1
  347 + self.buffer_slices[orientation].discard_vtk_mask()
  348 +
  349 + def edit_mask_pixel(self, index, position, radius, orientation):
  350 + mask = self.buffer_slices[orientation].mask
  351 + image = self.buffer_slices[orientation].image
  352 + thresh_min, thresh_max = self.current_mask.edition_threshold_range
295 353  
296   - def __edit_mask_pixel(self, pubsub_evt):
297   - positions = pubsub_evt.data
298   - for position in positions:
299   - self.EditPixelBasedOnThreshold(position)
  354 + if hasattr(position, '__iter__'):
  355 + py, px = position
  356 + if orientation == 'AXIAL':
  357 + sx = self.spacing[0]
  358 + sy = self.spacing[1]
  359 + elif orientation == 'CORONAL':
  360 + sx = self.spacing[0]
  361 + sy = self.spacing[2]
  362 + elif orientation == 'SAGITAL':
  363 + sx = self.spacing[1]
  364 + sy = self.spacing[2]
  365 +
  366 + else:
  367 + if orientation == 'AXIAL':
  368 + sx = self.spacing[0]
  369 + sy = self.spacing[1]
  370 + py = position / mask.shape[1]
  371 + px = position % mask.shape[1]
  372 + elif orientation == 'CORONAL':
  373 + sx = self.spacing[0]
  374 + sy = self.spacing[2]
  375 + py = position / mask.shape[1]
  376 + px = position % mask.shape[1]
  377 + elif orientation == 'SAGITAL':
  378 + sx = self.spacing[1]
  379 + sy = self.spacing[2]
  380 + py = position / mask.shape[1]
  381 + px = position % mask.shape[1]
  382 +
  383 + xi = px - math.ceil(radius/sx)
  384 + xf = px + math.ceil(radius/sx)
  385 + yi = py - math.ceil(radius/sy)
  386 + yf = py + math.ceil(radius/sy)
  387 +
  388 + if yi < 0:
  389 + index = index[abs(yi):,:]
  390 + yi = 0
  391 + if yf > image.shape[0]:
  392 + index = index[:index.shape[0]-(yf-image.shape[0]), :]
  393 + yf = image.shape[0]
  394 +
  395 + if xi < 0:
  396 + index = index[:,abs(xi):]
  397 + xi = 0
  398 + if xf > image.shape[1]:
  399 + index = index[:,:index.shape[1]-(xf-image.shape[1])]
  400 + xf = image.shape[1]
  401 +
  402 + # Verifying if the points is over the image array.
  403 + if (not 0 < xi < image.shape[1] and not 0 < xf < image.shape[1]) or \
  404 + (not 0 < yi < image.shape[0] and not 0 < yf < image.shape[0]):
  405 + return
  406 +
  407 + roi_m = mask[yi:yf,xi:xf]
  408 + roi_i = image[yi:yf, xi:xf]
  409 +
  410 + # It's a trick to make points between threshold gets value 254
  411 + # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1).
  412 + roi_m[index] = (((roi_i[index] >= thresh_min)
  413 + & (roi_i[index] <= thresh_max)) * 253) + 1
  414 + self.buffer_slices[orientation].discard_vtk_mask()
  415 +
  416 + def add_mask_pixel(self, index, position, radius, orientation):
  417 + #mask = self.buffer_slices[orientation].mask
  418 + #if orientation == 'AXIAL':
  419 + #sx = self.spacing[0]
  420 + #sy = self.spacing[1]
  421 + #py = position / mask.shape[1]
  422 + #px = position % mask.shape[1]
  423 + #elif orientation == 'CORONAL':
  424 + #sx = self.spacing[0]
  425 + #sy = self.spacing[2]
  426 + #py = position / mask.shape[1]
  427 + #px = position % mask.shape[1]
  428 + #elif orientation == 'SAGITAL':
  429 + #sx = self.spacing[1]
  430 + #sy = self.spacing[2]
  431 + #py = position / mask.shape[1]
  432 + #px = position % mask.shape[1]
  433 +
  434 + #print "->px, py", px, py
  435 + #print "->position", position
  436 + #print '->shape', mask.shape
  437 +
  438 + #mask[py - radius / sy: py + radius / sy,
  439 + #px - radius / sx: px + radius / sx] = 255
  440 + #self.buffer_slices[orientation].discard_vtk_mask()
  441 + mask = self.buffer_slices[orientation].mask
  442 + image = self.buffer_slices[orientation].image
  443 +
  444 + if hasattr(position, '__iter__'):
  445 + py, px = position
  446 + if orientation == 'AXIAL':
  447 + sx = self.spacing[0]
  448 + sy = self.spacing[1]
  449 + elif orientation == 'CORONAL':
  450 + sx = self.spacing[0]
  451 + sy = self.spacing[2]
  452 + elif orientation == 'SAGITAL':
  453 + sx = self.spacing[1]
  454 + sy = self.spacing[2]
300 455  
301   - def __add_mask_pixel(self, pubsub_evt):
302   - positions = pubsub_evt.data
303   - for position in positions:
304   - self.DrawPixel(position)
  456 + else:
  457 + if orientation == 'AXIAL':
  458 + sx = self.spacing[0]
  459 + sy = self.spacing[1]
  460 + py = position / mask.shape[1]
  461 + px = position % mask.shape[1]
  462 + elif orientation == 'CORONAL':
  463 + sx = self.spacing[0]
  464 + sy = self.spacing[2]
  465 + py = position / mask.shape[1]
  466 + px = position % mask.shape[1]
  467 + elif orientation == 'SAGITAL':
  468 + sx = self.spacing[1]
  469 + sy = self.spacing[2]
  470 + py = position / mask.shape[1]
  471 + px = position % mask.shape[1]
  472 +
  473 + xi = px - math.ceil(radius/sx)
  474 + xf = px + math.ceil(radius/sx)
  475 + yi = py - math.ceil(radius/sy)
  476 + yf = py + math.ceil(radius/sy)
  477 +
  478 + if yi < 0:
  479 + index = index[abs(yi):,:]
  480 + yi = 0
  481 + if yf > image.shape[0]:
  482 + index = index[:index.shape[0]-(yf-image.shape[0]), :]
  483 + yf = image.shape[0]
  484 +
  485 + if xi < 0:
  486 + index = index[:,abs(xi):]
  487 + xi = 0
  488 + if xf > image.shape[1]:
  489 + index = index[:,:index.shape[1]-(xf-image.shape[1])]
  490 + xf = image.shape[1]
  491 +
  492 + # Verifying if the points is over the image array.
  493 + if (not 0 < xi < image.shape[1] and not 0 < xf < image.shape[1]) or \
  494 + (not 0 < yi < image.shape[0] and not 0 < yf < image.shape[0]):
  495 + return
  496 +
  497 + roi_m = mask[yi:yf,xi:xf]
  498 + roi_i = image[yi:yf, xi:xf]
  499 +
  500 + roi_m[index] = 254
  501 + self.buffer_slices[orientation].discard_vtk_mask()
305 502 #---------------------------------------------------------------------------
306 503 # END PUBSUB_EVT METHODS
307 504 #---------------------------------------------------------------------------
... ... @@ -311,7 +508,7 @@ class Slice(object):
311 508 if self.buffer_slices[orientation].vtk_image:
312 509 image = self.buffer_slices[orientation].vtk_image
313 510 else:
314   - n_image = self.GetImageSlice(orientation, slice_number)
  511 + n_image = self.get_image_slice(orientation, slice_number)
315 512 image = iu.to_vtk(n_image, self.spacing, slice_number, orientation)
316 513 image = self.do_ww_wl(image)
317 514 if self.current_mask and self.current_mask.is_shown:
... ... @@ -320,19 +517,22 @@ class Slice(object):
320 517 mask = self.buffer_slices[orientation].vtk_mask
321 518 else:
322 519 print "Do not getting from buffer"
323   - n_mask = self.GetMaskSlice(orientation, slice_number)
  520 + n_mask = self.get_mask_slice(orientation, slice_number)
324 521 mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation)
325 522 mask = self.do_colour_mask(mask)
  523 + self.buffer_slices[orientation].mask = n_mask
326 524 final_image = self.do_blend(image, mask)
  525 + self.buffer_slices[orientation].vtk_mask = mask
327 526 else:
328 527 final_image = image
  528 + self.buffer_slices[orientation].vtk_image = image
329 529 else:
330   - n_image = self.GetImageSlice(orientation, slice_number)
  530 + n_image = self.get_image_slice(orientation, slice_number)
331 531 image = iu.to_vtk(n_image, self.spacing, slice_number, orientation)
332 532 image = self.do_ww_wl(image)
333 533  
334 534 if self.current_mask and self.current_mask.is_shown:
335   - n_mask = self.GetMaskSlice(orientation, slice_number)
  535 + n_mask = self.get_mask_slice(orientation, slice_number)
336 536 mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation)
337 537 mask = self.do_colour_mask(mask)
338 538 final_image = self.do_blend(image, mask)
... ... @@ -349,7 +549,7 @@ class Slice(object):
349 549  
350 550 return final_image
351 551  
352   - def GetImageSlice(self, orientation, slice_number):
  552 + def get_image_slice(self, orientation, slice_number):
353 553 if self.buffer_slices[orientation].index == slice_number \
354 554 and self.buffer_slices[orientation].image is not None:
355 555 n_image = self.buffer_slices[orientation].image
... ... @@ -362,7 +562,7 @@ class Slice(object):
362 562 n_image = numpy.array(self.matrix[..., ..., slice_number])
363 563 return n_image
364 564  
365   - def GetMaskSlice(self, orientation, slice_number):
  565 + def get_mask_slice(self, orientation, slice_number):
366 566 """
367 567 It gets the from actual mask the given slice from given orientation
368 568 """
... ... @@ -376,25 +576,28 @@ class Slice(object):
376 576 n = slice_number + 1
377 577 if orientation == 'AXIAL':
378 578 if self.current_mask.matrix[n, 0, 0] == 0:
379   - self.current_mask.matrix[n, 1:, 1:] = \
380   - self.do_threshold_to_a_slice(self.GetImageSlice(orientation,
381   - slice_number))
  579 + mask = self.current_mask.matrix[n, 1:, 1:]
  580 + mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation,
  581 + slice_number),
  582 + mask)
382 583 self.current_mask.matrix[n, 0, 0] = 1
383 584 n_mask = numpy.array(self.current_mask.matrix[n, 1:, 1:])
384 585  
385 586 elif orientation == 'CORONAL':
386 587 if self.current_mask.matrix[0, n, 0] == 0:
387   - self.current_mask.matrix[1:, n, 1:] = \
388   - self.do_threshold_to_a_slice(self.GetImageSlice(orientation,
389   - slice_number))
  588 + mask = self.current_mask.matrix[1:, n, 1:]
  589 + mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation,
  590 + slice_number),
  591 + mask)
390 592 self.current_mask.matrix[0, n, 0] = 1
391 593 n_mask = numpy.array(self.current_mask.matrix[1:, n, 1:])
392 594  
393 595 elif orientation == 'SAGITAL':
394 596 if self.current_mask.matrix[0, 0, n] == 0:
395   - self.current_mask.matrix[1:, 1:, n] = \
396   - self.do_threshold_to_a_slice(self.GetImageSlice(orientation,
397   - slice_number))
  597 + mask = self.current_mask.matrix[1:, 1:, n]
  598 + mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation,
  599 + slice_number),
  600 + mask)
398 601 self.current_mask.matrix[0, 0, n] = 1
399 602 n_mask = numpy.array(self.current_mask.matrix[1:, 1:, n])
400 603 return n_mask
... ... @@ -804,13 +1007,15 @@ class Slice(object):
804 1007  
805 1008 return colorer.GetOutput()
806 1009  
807   - def do_threshold_to_a_slice(self, slice_matrix):
  1010 + def do_threshold_to_a_slice(self, slice_matrix, mask):
808 1011 """
809 1012 Based on the current threshold bounds generates a threshold mask to
810 1013 given slice_matrix.
811 1014 """
812 1015 thresh_min, thresh_max = self.current_mask.threshold_range
813   - m= numpy.logical_and(slice_matrix >= thresh_min, slice_matrix <= thresh_max) * 255
  1016 + m = (((slice_matrix >= thresh_min) & (slice_matrix <= thresh_max)) * 255)
  1017 + m[mask == 1] = 1
  1018 + m[mask == 254] = 254
814 1019 return m
815 1020  
816 1021 def do_colour_mask(self, imagedata):
... ... @@ -819,14 +1024,15 @@ class Slice(object):
819 1024  
820 1025 # map scalar values into colors
821 1026 lut_mask = vtk.vtkLookupTable()
822   - lut_mask.SetNumberOfColors(255)
  1027 + lut_mask.SetNumberOfColors(256)
823 1028 lut_mask.SetHueRange(const.THRESHOLD_HUE_RANGE)
824 1029 lut_mask.SetSaturationRange(1, 1)
825   - lut_mask.SetValueRange(0, 1)
  1030 + lut_mask.SetValueRange(0, 255)
  1031 + lut_mask.SetRange(0, 255)
826 1032 lut_mask.SetNumberOfTableValues(256)
827 1033 lut_mask.SetTableValue(0, 0, 0, 0, 0.0)
828 1034 lut_mask.SetTableValue(1, 0, 0, 0, 0.0)
829   - lut_mask.SetTableValue(2, 0, 0, 0, 0.0)
  1035 + lut_mask.SetTableValue(254, r, g, b, 1.0)
830 1036 lut_mask.SetTableValue(255, r, g, b, 1.0)
831 1037 lut_mask.SetRampToLinear()
832 1038 lut_mask.Build()
... ... @@ -858,6 +1064,25 @@ class Slice(object):
858 1064  
859 1065 return blend_imagedata.GetOutput()
860 1066  
  1067 + def apply_slice_buffer_to_mask(self, orientation):
  1068 + """
  1069 + Apply the modifications (edition) in mask buffer to mask.
  1070 + """
  1071 + b_mask = self.buffer_slices[orientation].mask
  1072 + index = self.buffer_slices[orientation].index
  1073 + print "-> ORIENTATION", orientation, index, b_mask
  1074 + if orientation == 'AXIAL':
  1075 + self.current_mask.matrix[index+1,1:,1:] = b_mask
  1076 + elif orientation == 'CORONAL':
  1077 + self.current_mask.matrix[1:, index+1, 1:] = b_mask
  1078 + elif orientation == 'SAGITAL':
  1079 + self.current_mask.matrix[1:, 1:, index+1] = b_mask
  1080 +
  1081 + for o in self.buffer_slices:
  1082 + if o != orientation:
  1083 + self.buffer_slices[o].discard_mask()
  1084 + self.buffer_slices[o].discard_vtk_mask()
  1085 + ps.Publisher().sendMessage('Reload actual slice')
861 1086  
862 1087 def __build_mask(self, imagedata, create=True):
863 1088 # create new mask instance and insert it into project
... ...
invesalius/data/viewer_slice.py
... ... @@ -90,7 +90,7 @@ class Viewer(wx.Panel):
90 90 self.on_text = False
91 91 # VTK pipeline and actors
92 92 self.__config_interactor()
93   - self.pick = vtk.vtkPropPicker()
  93 + self.pick = vtk.vtkWorldPointPicker()
94 94 self.cross_actor = vtk.vtkActor()
95 95  
96 96  
... ... @@ -180,6 +180,7 @@ class Viewer(wx.Panel):
180 180 {
181 181 "MouseMoveEvent": self.OnBrushMove,
182 182 "LeftButtonPressEvent": self.OnBrushClick,
  183 + "LeftButtonReleaseEvent": self.OnBrushRelease,
183 184 "EnterEvent": self.OnEnterInteractor,
184 185 "LeaveEvent": self.OnLeaveInteractor
185 186 },
... ... @@ -566,9 +567,9 @@ class Viewer(wx.Panel):
566 567  
567 568 def ChangeBrushSize(self, pubsub_evt):
568 569 size = pubsub_evt.data
569   - self._brush_cursor_size = size
570   - for slice_data in self.slice_data_list:
571   - slice_data.cursor.SetSize(size)
  570 + #self._brush_cursor_size = size
  571 + #for slice_data in self.slice_data_list:
  572 + self.slice_data.cursor.SetSize(size)
572 573  
573 574 def ChangeBrushColour(self, pubsub_evt):
574 575 vtk_colour = pubsub_evt.data[3]
... ... @@ -587,60 +588,71 @@ class Viewer(wx.Panel):
587 588  
588 589 def ChangeBrushActor(self, pubsub_evt):
589 590 brush_type = pubsub_evt.data
590   - for slice_data in self.slice_data_list:
591   - self._brush_cursor_type = brush_type
592   - #self.ren.RemoveActor(self.cursor.actor)
593   -
594   - if brush_type == const.BRUSH_SQUARE:
595   - cursor = ca.CursorRectangle()
596   - elif brush_type == const.BRUSH_CIRCLE:
597   - cursor = ca.CursorCircle()
598   - #self.cursor = cursor
599   -
600   - cursor.SetOrientation(self.orientation)
601   - coordinates = {"SAGITAL": [slice_data.number, 0, 0],
602   - "CORONAL": [0, slice_data.number, 0],
603   - "AXIAL": [0, 0, slice_data.number]}
604   - cursor.SetPosition(coordinates[self.orientation])
605   - cursor.SetSpacing(self.imagedata.GetSpacing())
606   - cursor.SetColour(self._brush_cursor_colour)
607   - cursor.SetSize(self._brush_cursor_size)
608   - slice_data.SetCursor(cursor)
609   - #self.ren.AddActor(cursor.actor)
610   - #self.ren.Render()
  591 + slice_data = self.slice_data
  592 + self._brush_cursor_type = brush_type
  593 +
  594 + if brush_type == const.BRUSH_SQUARE:
  595 + cursor = ca.CursorRectangle()
  596 + elif brush_type == const.BRUSH_CIRCLE:
  597 + cursor = ca.CursorCircle()
  598 +
  599 + cursor.SetOrientation(self.orientation)
  600 + coordinates = {"SAGITAL": [slice_data.number, 0, 0],
  601 + "CORONAL": [0, slice_data.number, 0],
  602 + "AXIAL": [0, 0, slice_data.number]}
  603 + cursor.SetPosition(coordinates[self.orientation])
  604 + cursor.SetSpacing(self.slice_.spacing)
  605 + cursor.SetColour(self._brush_cursor_colour)
  606 + cursor.SetSize(self._brush_cursor_size)
  607 + slice_data.SetCursor(cursor)
611 608 self.interactor.Render()
612   - #self.cursor = cursor
613 609  
614 610  
615 611 def OnBrushClick(self, evt, obj):
616   -
  612 + self.__set_editor_cursor_visibility(1)
  613 +
617 614 mouse_x, mouse_y = self.interactor.GetEventPosition()
618 615 render = self.interactor.FindPokedRenderer(mouse_x, mouse_y)
619 616 slice_data = self.get_slice_data(render)
620   - self.pick.Pick(mouse_x, mouse_y, 0, render)
621 617  
  618 + # TODO: Improve!
  619 + #for i in self.slice_data_list:
  620 + #i.cursor.Show(0)
  621 + self.slice_data.cursor.Show()
  622 +
  623 + self.pick.Pick(mouse_x, mouse_y, 0, render)
  624 +
622 625 coord = self.get_coordinate_cursor()
623 626 slice_data.cursor.SetPosition(coord)
624 627 slice_data.cursor.SetEditionPosition(
625 628 self.get_coordinate_cursor_edition(slice_data))
626 629 self.__update_cursor_position(slice_data, coord)
627   - #render.Render()
628 630  
629   - evt_msg = {const.BRUSH_ERASE: 'Erase mask pixel',
630   - const.BRUSH_DRAW: 'Add mask pixel',
631   - const.BRUSH_THRESH: 'Edit mask pixel'}
632   - msg = evt_msg[self._brush_cursor_op]
  631 + cursor = self.slice_data.cursor
  632 + position = self.slice_data.actor.GetInput().FindPoint(coord)
  633 + radius = cursor.radius
633 634  
634   - pixels = itertools.ifilter(self.test_operation_position,
635   - slice_data.cursor.GetPixels())
636   - ps.Publisher().sendMessage(msg, pixels)
  635 + if position < 0:
  636 + position = self.calculate_matrix_position(coord)
  637 +
  638 +
  639 + # TODO: Call slice_ functions instead of to use pubsub message,
  640 + # maybe we can get some performances improvements here.
  641 + if self._brush_cursor_op == const.BRUSH_ERASE:
  642 + self.slice_.erase_mask_pixel(cursor.GetPixels(), position, radius,
  643 + self.orientation)
  644 + elif self._brush_cursor_op == const.BRUSH_DRAW:
  645 + self.slice_.add_mask_pixel(cursor.GetPixels(), position, radius,
  646 + self.orientation)
  647 + elif self._brush_cursor_op == const.BRUSH_THRESH:
  648 + self.slice_.edit_mask_pixel(cursor.GetPixels(), position, radius,
  649 + self.orientation)
  650 +
  651 + # TODO: To create a new function to reload images to viewer.
  652 + self.OnScrollBar()
637 653  
638   - # FIXME: This is idiot, but is the only way that brush operations are
639   - # working when cross is disabled
640   - ps.Publisher().sendMessage('Update slice viewer')
641 654  
642 655 def OnBrushMove(self, evt, obj):
643   -
644 656 self.__set_editor_cursor_visibility(1)
645 657  
646 658 mouse_x, mouse_y = self.interactor.GetEventPosition()
... ... @@ -650,35 +662,50 @@ class Viewer(wx.Panel):
650 662 # TODO: Improve!
651 663 #for i in self.slice_data_list:
652 664 #i.cursor.Show(0)
653   - #slice_data.cursor.Show()
  665 + self.slice_data.cursor.Show()
654 666  
655 667 self.pick.Pick(mouse_x, mouse_y, 0, render)
656 668  
657   - if (self.pick.GetProp()):
658   - self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_BLANK))
659   - else:
660   - self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
  669 + #if (self.pick.GetViewProp()):
  670 + #self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_BLANK))
  671 + #else:
  672 + #self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
661 673  
662 674 coord = self.get_coordinate_cursor()
663 675 slice_data.cursor.SetPosition(coord)
664 676 slice_data.cursor.SetEditionPosition(
665 677 self.get_coordinate_cursor_edition(slice_data))
666 678 self.__update_cursor_position(slice_data, coord)
667   -
668   - if self._brush_cursor_op == const.BRUSH_ERASE:
669   - evt_msg = 'Erase mask pixel'
670   - elif self._brush_cursor_op == const.BRUSH_DRAW:
671   - evt_msg = 'Add mask pixel'
672   - elif self._brush_cursor_op == const.BRUSH_THRESH:
673   - evt_msg = 'Edit mask pixel'
674   -
  679 +
675 680 if (self.left_pressed):
676   - pixels = itertools.ifilter(self.test_operation_position,
677   - slice_data.cursor.GetPixels())
678   - ps.Publisher().sendMessage(evt_msg, pixels)
679   - ps.Publisher().sendMessage('Update slice viewer')
  681 + cursor = self.slice_data.cursor
  682 + position = self.slice_data.actor.GetInput().FindPoint(coord)
  683 + radius = cursor.radius
680 684  
681   - self.interactor.Render()
  685 + if position < 0:
  686 + position = self.calculate_matrix_position(coord)
  687 +
  688 +
  689 + # TODO: Call slice_ functions instead of to use pubsub message,
  690 + # maybe we can get some performances improvements here.
  691 + if self._brush_cursor_op == const.BRUSH_ERASE:
  692 + self.slice_.erase_mask_pixel(cursor.GetPixels(), position, radius,
  693 + self.orientation)
  694 + elif self._brush_cursor_op == const.BRUSH_DRAW:
  695 + self.slice_.add_mask_pixel(cursor.GetPixels(), position, radius,
  696 + self.orientation)
  697 + elif self._brush_cursor_op == const.BRUSH_THRESH:
  698 + self.slice_.edit_mask_pixel(cursor.GetPixels(), position, radius,
  699 + self.orientation)
  700 +
  701 + # TODO: To create a new function to reload images to viewer.
  702 + self.OnScrollBar()
  703 +
  704 + else:
  705 + self.interactor.Render()
  706 +
  707 + def OnBrushRelease(self, evt, obj):
  708 + self.slice_.apply_slice_buffer_to_mask(self.orientation)
682 709  
683 710 def OnCrossMouseClick(self, evt, obj):
684 711 self.ChangeCrossPosition()
... ... @@ -787,10 +814,24 @@ class Viewer(wx.Panel):
787 814 # vtkImageData extent
788 815 return coord
789 816  
  817 + def calculate_matrix_position(self, coord):
  818 + x, y, z = coord
  819 + xi, xf, yi, yf, zi, zf = self.slice_data.actor.GetBounds()
  820 + if self.orientation == 'AXIAL':
  821 + mx = round((x - xi)/self.slice_.spacing[0], 0)
  822 + my = round((y - yi)/self.slice_.spacing[1], 0)
  823 + elif self.orientation == 'CORONAL':
  824 + mx = round((x - xi)/self.slice_.spacing[0], 0)
  825 + my = round((z - zi)/self.slice_.spacing[2], 0)
  826 + elif self.orientation == 'SAGITAL':
  827 + mx = round((y - yi)/self.slice_.spacing[1], 0)
  828 + my = round((z - zi)/self.slice_.spacing[2], 0)
  829 + return my, mx
  830 +
790 831 def get_coordinate_cursor(self):
791 832 # Find position
792 833 x, y, z = self.pick.GetPickPosition()
793   - bounds = self.actor.GetBounds()
  834 + bounds = self.slice_data.actor.GetBounds()
794 835 if bounds[0] == bounds[1]:
795 836 x = bounds[0]
796 837 elif bounds[2] == bounds[3]:
... ... @@ -816,10 +857,10 @@ class Viewer(wx.Panel):
816 857 dy = bound_yf - bound_yi
817 858 dz = bound_zf - bound_zi
818 859  
819   - dimensions = self.imagedata.GetDimensions()
  860 + dimensions = self.slice_.matrix.shape
820 861  
821 862 try:
822   - x = (x * dimensions[0]) / dx
  863 + x = (x * dimensions[2]) / dx
823 864 except ZeroDivisionError:
824 865 x = slice_number
825 866 try:
... ... @@ -827,7 +868,7 @@ class Viewer(wx.Panel):
827 868 except ZeroDivisionError:
828 869 y = slice_number
829 870 try:
830   - z = (z * dimensions[2]) / dz
  871 + z = (z * dimensions[0]) / dz
831 872 except ZeroDivisionError:
832 873 z = slice_number
833 874  
... ... @@ -959,7 +1000,7 @@ class Viewer(wx.Panel):
959 1000 self.slice_number = 0
960 1001 self.cursor = None
961 1002 self.wl_text = None
962   - self.pick = vtk.vtkPropPicker()
  1003 + self.pick = vtk.vtkWorldPointPicker()
963 1004  
964 1005  
965 1006 def OnSetInteractorStyle(self, pubsub_evt):
... ... @@ -1046,7 +1087,7 @@ class Viewer(wx.Panel):
1046 1087 cursor.SetOrientation(self.orientation)
1047 1088 #self.__update_cursor_position([i for i in actor_bound[1::2]])
1048 1089 cursor.SetColour(self._brush_cursor_colour)
1049   - cursor.SetSpacing(self.imagedata.GetSpacing())
  1090 + cursor.SetSpacing(self.slice_.spacing)
1050 1091 cursor.Show(0)
1051 1092 self.cursor_ = cursor
1052 1093 return cursor
... ... @@ -1060,6 +1101,7 @@ class Viewer(wx.Panel):
1060 1101  
1061 1102 self.slice_data = self.create_slice_window()
1062 1103 self.slice_data.actor.SetInput(imagedata)
  1104 + self.slice_data.SetCursor(self.__create_cursor())
1063 1105 self.cam = self.slice_data.renderer.GetActiveCamera()
1064 1106 self.set_slice_number(0)
1065 1107 self.__update_camera()
... ...