diff --git a/invesalius/data/cursor_actors.py b/invesalius/data/cursor_actors.py index 189f1f2..ef806cd 100644 --- a/invesalius/data/cursor_actors.py +++ b/invesalius/data/cursor_actors.py @@ -19,6 +19,7 @@ from math import * +import numpy import vtk import wx.lib.pubsub as ps from project import Project @@ -89,50 +90,24 @@ class CursorCircle: def __calculate_area_pixels(self): """ Return the cursor's pixels. - This method scans the circle line by line. - Extracted equation. - http://www.mathopenref.com/chord.html """ - xc = 0.0 - yc = 0.0 - z = 0.0 - xs, ys, zs = self.spacing - - proj = Project() - orig_orien = proj.original_orientation - - xy = (xs, ys) - yz = (ys, zs) - xz = (xs, zs) - - if (orig_orien == const.SAGITAL): - orientation_based_spacing = {"SAGITAL" : xy, - "AXIAL" : yz, - "CORONAL" : xz} - elif(orig_orien == const.CORONAL): - orientation_based_spacing = {"CORONAL" : xy, - "AXIAL" : xz, - "SAGITAL" : yz} - else: - orientation_based_spacing = {"AXIAL" : xy, - "SAGITAL" : yz, - "CORONAL" : xz} - - xs, ys = orientation_based_spacing[self.orientation] - self.pixel_list = [] radius = self.radius - for i in utils.frange(yc - radius, yc + radius, ys): - # distance from the line to the circle's center - d = yc - i - # line size - line = sqrt(radius**2 - d**2) * 2 - # line initial x - xi = xc - line/2.0 - # line final - xf = line/2.0 + xc - yi = i - for k in utils.frange(xi,xf,xs): - self.pixel_list.append((k, yi)) + if self.orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + elif self.orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + elif self.orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + + y,x = numpy.ogrid[-radius/sy:+radius/sy, + -radius/sx:+radius/sx] + + index = (y*sy)**2 + (x*sx)**2 <= radius**2 + self.points = index + def SetSize(self, diameter): radius = self.radius = diameter/2.0 @@ -185,38 +160,7 @@ class CursorCircle: self.actor.VisibilityOff() def GetPixels(self): - px, py, pz = self.edition_position - orient = self.orientation - xs, ys, zs = self.spacing - proj = Project() - orig_orien = proj.original_orientation - xy1 = lambda x,y: (px + x / xs, py+(y/ys), pz) - xy2 = lambda x,y: (px+(x/xs), py, pz+(y/zs)) - xy3 = lambda x,y: (px, py+(x/ys), pz+(y/zs)) - - if (orig_orien == const.SAGITAL): - abs_pixel = {"SAGITAL": xy1, - "AXIAL": xy2, - "CORONAL": xy3} - elif(orig_orien == const.CORONAL): - abs_pixel = {"CORONAL": xy1, - "SAGITAL": xy3, - "AXIAL": xy2} - else: - abs_pixel = {"AXIAL": xy1, - "CORONAL": xy2, - "SAGITAL": xy3} - - function_orientation = abs_pixel[orient] - - for pixel_0,pixel_1 in self.pixel_list: - # The position of the pixels in this list is relative (based only on - # the area, and not the cursor position). - # Let's calculate the absolute position - # TODO: Optimize this!!!! - yield function_orientation(pixel_0, pixel_1) - - + return self.points class CursorRectangle: @@ -228,6 +172,7 @@ class CursorRectangle: self.x_length = 30 self.y_length = 30 + self.radius = 15 self.dimension = (self.x_length, self.y_length) self.position = (0 ,0) @@ -240,6 +185,7 @@ class CursorRectangle: def SetSize(self, size): self.x_length = size self.y_length = size + self.radius = size / 2 retangle = self.retangle retangle.SetXLength(size) retangle.SetYLength(size) @@ -311,67 +257,21 @@ class CursorRectangle: actor.SetVisibility(0) def __calculate_area_pixels(self): - xc = 0 - yc = 0 - z = 0 - xs, ys, zs = self.spacing - - proj = Project() - orig_orien = proj.original_orientation - - xy = (xs, ys) - yz = (ys, zs) - xz = (xs, zs) - - if (orig_orien == const.SAGITAL): - orientation_based_spacing = {"SAGITAL" : xy, - "AXIAL" : yz, - "CORONAL" : xz} - elif(orig_orien == const.CORONAL): - orientation_based_spacing = {"CORONAL" : xy, - "AXIAL" : xz, - "SAGITAL" : yz} - else: - orientation_based_spacing = {"AXIAL" : xy, - "SAGITAL" : yz, - "CORONAL" : xz} - - xs, ys = orientation_based_spacing[self.orientation] - self.pixel_list = [] - for i in utils.frange(yc - self.y_length/2, yc + self.y_length/2, ys): - for k in utils.frange(xc - self.x_length/2, xc + self.x_length/2, xs): - self.pixel_list.append((k, i)) - + if self.orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + elif self.orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + elif self.orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + shape = (self.y_length/sy, self.x_length/sx) + self.points = numpy.empty(shape, dtype='bool') + self.points.fill(True) def GetPixels(self): """ Return the points of the rectangle """ - px, py, pz = self.edition_position - orient = self.orientation - xs, ys, zs = self.spacing - proj = Project() - orig_orien = proj.original_orientation - xy1 = lambda x,y: (px + x / xs, py+(y/ys), pz) - xy2 = lambda x,y: (px+(x/xs), py, pz+(y/zs)) - xy3 = lambda x,y: (px, py+(x/ys), pz+(y/zs)) - - if (orig_orien == const.SAGITAL): - abs_pixel = {"SAGITAL": xy1, - "AXIAL": xy2, - "CORONAL": xy3} - elif(orig_orien == const.CORONAL): - abs_pixel = {"CORONAL": xy1, - "SAGITAL": xy3, - "AXIAL": xy2} - else: - abs_pixel = {"AXIAL": xy1, - "CORONAL": xy2, - "SAGITAL": xy3} - function_orientation = abs_pixel[orient] - for pixel_0,pixel_1 in self.pixel_list: - # The position of the pixels in this list is relative (based only on - # the area, and not the cursor position). - # Let's calculate the absolute position - # TODO: Optimize this!!!! - yield function_orientation(pixel_0, pixel_1) + return self.points diff --git a/invesalius/data/slice_.py b/invesalius/data/slice_.py index a3abefa..3bfed40 100644 --- a/invesalius/data/slice_.py +++ b/invesalius/data/slice_.py @@ -16,6 +16,8 @@ # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais # detalhes. #-------------------------------------------------------------------------- +import math + import numpy import vtk import wx.lib.pubsub as ps @@ -101,11 +103,6 @@ class Slice(object): ps.Publisher().subscribe(self.__set_mask_name, 'Change mask name') ps.Publisher().subscribe(self.__show_mask, 'Show mask') - # Operations related to slice editor - ps.Publisher().subscribe(self.__erase_mask_pixel, 'Erase mask pixel') - ps.Publisher().subscribe(self.__edit_mask_pixel, 'Edit mask pixel') - ps.Publisher().subscribe(self.__add_mask_pixel, 'Add mask pixel') - ps.Publisher().subscribe(self.__set_current_mask_threshold_limits, 'Update threshold limits') @@ -249,9 +246,7 @@ class Slice(object): #Clear edited points self.current_mask.edited_points = {} self.num_gradient += 1 - self.current_mask.matrix[0, :, :] = 0 - self.current_mask.matrix[:, 0, :] = 0 - self.current_mask.matrix[:, :, 0] = 0 + self.current_mask.matrix[:] = 0 def __set_current_mask_threshold_actual_slice(self, evt_pubsub): threshold_range = evt_pubsub.data @@ -288,20 +283,222 @@ class Slice(object): index, value = pubsub_evt.data self.ShowMask(index, value) #--------------------------------------------------------------------------- - def __erase_mask_pixel(self, pubsub_evt): - positions = pubsub_evt.data - for position in positions: - self.ErasePixel(position) + def erase_mask_pixel(self, index, position, radius, orientation): + mask = self.buffer_slices[orientation].mask + image = self.buffer_slices[orientation].image + + if hasattr(position, '__iter__'): + py, px = position + if orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + elif orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + elif orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + + else: + if orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + py = position / mask.shape[1] + px = position % mask.shape[1] + elif orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + py = position / mask.shape[1] + px = position % mask.shape[1] + elif orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + py = position / mask.shape[1] + px = position % mask.shape[1] + + xi = px - math.ceil(radius/sx) + xf = px + math.ceil(radius/sx) + yi = py - math.ceil(radius/sy) + yf = py + math.ceil(radius/sy) + + if yi < 0: + index = index[abs(yi):,:] + yi = 0 + if yf > image.shape[0]: + index = index[:index.shape[0]-(yf-image.shape[0]), :] + yf = image.shape[0] + + if xi < 0: + index = index[:,abs(xi):] + xi = 0 + if xf > image.shape[1]: + index = index[:,:index.shape[1]-(xf-image.shape[1])] + xf = image.shape[1] + + # Verifying if the points is over the image array. + if (not 0 < xi < image.shape[1] and not 0 < xf < image.shape[1]) or \ + (not 0 < yi < image.shape[0] and not 0 < yf < image.shape[0]): + return + + roi_m = mask[yi:yf,xi:xf] + roi_i = image[yi:yf, xi:xf] + + roi_m[index] = 1 + self.buffer_slices[orientation].discard_vtk_mask() + + def edit_mask_pixel(self, index, position, radius, orientation): + mask = self.buffer_slices[orientation].mask + image = self.buffer_slices[orientation].image + thresh_min, thresh_max = self.current_mask.edition_threshold_range - def __edit_mask_pixel(self, pubsub_evt): - positions = pubsub_evt.data - for position in positions: - self.EditPixelBasedOnThreshold(position) + if hasattr(position, '__iter__'): + py, px = position + if orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + elif orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + elif orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + + else: + if orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + py = position / mask.shape[1] + px = position % mask.shape[1] + elif orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + py = position / mask.shape[1] + px = position % mask.shape[1] + elif orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + py = position / mask.shape[1] + px = position % mask.shape[1] + + xi = px - math.ceil(radius/sx) + xf = px + math.ceil(radius/sx) + yi = py - math.ceil(radius/sy) + yf = py + math.ceil(radius/sy) + + if yi < 0: + index = index[abs(yi):,:] + yi = 0 + if yf > image.shape[0]: + index = index[:index.shape[0]-(yf-image.shape[0]), :] + yf = image.shape[0] + + if xi < 0: + index = index[:,abs(xi):] + xi = 0 + if xf > image.shape[1]: + index = index[:,:index.shape[1]-(xf-image.shape[1])] + xf = image.shape[1] + + # Verifying if the points is over the image array. + if (not 0 < xi < image.shape[1] and not 0 < xf < image.shape[1]) or \ + (not 0 < yi < image.shape[0] and not 0 < yf < image.shape[0]): + return + + roi_m = mask[yi:yf,xi:xf] + roi_i = image[yi:yf, xi:xf] + + # It's a trick to make points between threshold gets value 254 + # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1). + roi_m[index] = (((roi_i[index] >= thresh_min) + & (roi_i[index] <= thresh_max)) * 253) + 1 + self.buffer_slices[orientation].discard_vtk_mask() + + def add_mask_pixel(self, index, position, radius, orientation): + #mask = self.buffer_slices[orientation].mask + #if orientation == 'AXIAL': + #sx = self.spacing[0] + #sy = self.spacing[1] + #py = position / mask.shape[1] + #px = position % mask.shape[1] + #elif orientation == 'CORONAL': + #sx = self.spacing[0] + #sy = self.spacing[2] + #py = position / mask.shape[1] + #px = position % mask.shape[1] + #elif orientation == 'SAGITAL': + #sx = self.spacing[1] + #sy = self.spacing[2] + #py = position / mask.shape[1] + #px = position % mask.shape[1] + + #print "->px, py", px, py + #print "->position", position + #print '->shape', mask.shape + + #mask[py - radius / sy: py + radius / sy, + #px - radius / sx: px + radius / sx] = 255 + #self.buffer_slices[orientation].discard_vtk_mask() + mask = self.buffer_slices[orientation].mask + image = self.buffer_slices[orientation].image + + if hasattr(position, '__iter__'): + py, px = position + if orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + elif orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + elif orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] - def __add_mask_pixel(self, pubsub_evt): - positions = pubsub_evt.data - for position in positions: - self.DrawPixel(position) + else: + if orientation == 'AXIAL': + sx = self.spacing[0] + sy = self.spacing[1] + py = position / mask.shape[1] + px = position % mask.shape[1] + elif orientation == 'CORONAL': + sx = self.spacing[0] + sy = self.spacing[2] + py = position / mask.shape[1] + px = position % mask.shape[1] + elif orientation == 'SAGITAL': + sx = self.spacing[1] + sy = self.spacing[2] + py = position / mask.shape[1] + px = position % mask.shape[1] + + xi = px - math.ceil(radius/sx) + xf = px + math.ceil(radius/sx) + yi = py - math.ceil(radius/sy) + yf = py + math.ceil(radius/sy) + + if yi < 0: + index = index[abs(yi):,:] + yi = 0 + if yf > image.shape[0]: + index = index[:index.shape[0]-(yf-image.shape[0]), :] + yf = image.shape[0] + + if xi < 0: + index = index[:,abs(xi):] + xi = 0 + if xf > image.shape[1]: + index = index[:,:index.shape[1]-(xf-image.shape[1])] + xf = image.shape[1] + + # Verifying if the points is over the image array. + if (not 0 < xi < image.shape[1] and not 0 < xf < image.shape[1]) or \ + (not 0 < yi < image.shape[0] and not 0 < yf < image.shape[0]): + return + + roi_m = mask[yi:yf,xi:xf] + roi_i = image[yi:yf, xi:xf] + + roi_m[index] = 254 + self.buffer_slices[orientation].discard_vtk_mask() #--------------------------------------------------------------------------- # END PUBSUB_EVT METHODS #--------------------------------------------------------------------------- @@ -311,7 +508,7 @@ class Slice(object): if self.buffer_slices[orientation].vtk_image: image = self.buffer_slices[orientation].vtk_image else: - n_image = self.GetImageSlice(orientation, slice_number) + n_image = self.get_image_slice(orientation, slice_number) image = iu.to_vtk(n_image, self.spacing, slice_number, orientation) image = self.do_ww_wl(image) if self.current_mask and self.current_mask.is_shown: @@ -320,19 +517,22 @@ class Slice(object): mask = self.buffer_slices[orientation].vtk_mask else: print "Do not getting from buffer" - n_mask = self.GetMaskSlice(orientation, slice_number) + n_mask = self.get_mask_slice(orientation, slice_number) mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation) mask = self.do_colour_mask(mask) + self.buffer_slices[orientation].mask = n_mask final_image = self.do_blend(image, mask) + self.buffer_slices[orientation].vtk_mask = mask else: final_image = image + self.buffer_slices[orientation].vtk_image = image else: - n_image = self.GetImageSlice(orientation, slice_number) + n_image = self.get_image_slice(orientation, slice_number) image = iu.to_vtk(n_image, self.spacing, slice_number, orientation) image = self.do_ww_wl(image) if self.current_mask and self.current_mask.is_shown: - n_mask = self.GetMaskSlice(orientation, slice_number) + n_mask = self.get_mask_slice(orientation, slice_number) mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation) mask = self.do_colour_mask(mask) final_image = self.do_blend(image, mask) @@ -349,7 +549,7 @@ class Slice(object): return final_image - def GetImageSlice(self, orientation, slice_number): + def get_image_slice(self, orientation, slice_number): if self.buffer_slices[orientation].index == slice_number \ and self.buffer_slices[orientation].image is not None: n_image = self.buffer_slices[orientation].image @@ -362,7 +562,7 @@ class Slice(object): n_image = numpy.array(self.matrix[..., ..., slice_number]) return n_image - def GetMaskSlice(self, orientation, slice_number): + def get_mask_slice(self, orientation, slice_number): """ It gets the from actual mask the given slice from given orientation """ @@ -376,25 +576,28 @@ class Slice(object): n = slice_number + 1 if orientation == 'AXIAL': if self.current_mask.matrix[n, 0, 0] == 0: - self.current_mask.matrix[n, 1:, 1:] = \ - self.do_threshold_to_a_slice(self.GetImageSlice(orientation, - slice_number)) + mask = self.current_mask.matrix[n, 1:, 1:] + mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation, + slice_number), + mask) self.current_mask.matrix[n, 0, 0] = 1 n_mask = numpy.array(self.current_mask.matrix[n, 1:, 1:]) elif orientation == 'CORONAL': if self.current_mask.matrix[0, n, 0] == 0: - self.current_mask.matrix[1:, n, 1:] = \ - self.do_threshold_to_a_slice(self.GetImageSlice(orientation, - slice_number)) + mask = self.current_mask.matrix[1:, n, 1:] + mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation, + slice_number), + mask) self.current_mask.matrix[0, n, 0] = 1 n_mask = numpy.array(self.current_mask.matrix[1:, n, 1:]) elif orientation == 'SAGITAL': if self.current_mask.matrix[0, 0, n] == 0: - self.current_mask.matrix[1:, 1:, n] = \ - self.do_threshold_to_a_slice(self.GetImageSlice(orientation, - slice_number)) + mask = self.current_mask.matrix[1:, 1:, n] + mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation, + slice_number), + mask) self.current_mask.matrix[0, 0, n] = 1 n_mask = numpy.array(self.current_mask.matrix[1:, 1:, n]) return n_mask @@ -804,13 +1007,15 @@ class Slice(object): return colorer.GetOutput() - def do_threshold_to_a_slice(self, slice_matrix): + def do_threshold_to_a_slice(self, slice_matrix, mask): """ Based on the current threshold bounds generates a threshold mask to given slice_matrix. """ thresh_min, thresh_max = self.current_mask.threshold_range - m= numpy.logical_and(slice_matrix >= thresh_min, slice_matrix <= thresh_max) * 255 + m = (((slice_matrix >= thresh_min) & (slice_matrix <= thresh_max)) * 255) + m[mask == 1] = 1 + m[mask == 254] = 254 return m def do_colour_mask(self, imagedata): @@ -819,14 +1024,15 @@ class Slice(object): # map scalar values into colors lut_mask = vtk.vtkLookupTable() - lut_mask.SetNumberOfColors(255) + lut_mask.SetNumberOfColors(256) lut_mask.SetHueRange(const.THRESHOLD_HUE_RANGE) lut_mask.SetSaturationRange(1, 1) - lut_mask.SetValueRange(0, 1) + lut_mask.SetValueRange(0, 255) + lut_mask.SetRange(0, 255) lut_mask.SetNumberOfTableValues(256) lut_mask.SetTableValue(0, 0, 0, 0, 0.0) lut_mask.SetTableValue(1, 0, 0, 0, 0.0) - lut_mask.SetTableValue(2, 0, 0, 0, 0.0) + lut_mask.SetTableValue(254, r, g, b, 1.0) lut_mask.SetTableValue(255, r, g, b, 1.0) lut_mask.SetRampToLinear() lut_mask.Build() @@ -858,6 +1064,25 @@ class Slice(object): return blend_imagedata.GetOutput() + def apply_slice_buffer_to_mask(self, orientation): + """ + Apply the modifications (edition) in mask buffer to mask. + """ + b_mask = self.buffer_slices[orientation].mask + index = self.buffer_slices[orientation].index + print "-> ORIENTATION", orientation, index, b_mask + if orientation == 'AXIAL': + self.current_mask.matrix[index+1,1:,1:] = b_mask + elif orientation == 'CORONAL': + self.current_mask.matrix[1:, index+1, 1:] = b_mask + elif orientation == 'SAGITAL': + self.current_mask.matrix[1:, 1:, index+1] = b_mask + + for o in self.buffer_slices: + if o != orientation: + self.buffer_slices[o].discard_mask() + self.buffer_slices[o].discard_vtk_mask() + ps.Publisher().sendMessage('Reload actual slice') def __build_mask(self, imagedata, create=True): # create new mask instance and insert it into project diff --git a/invesalius/data/viewer_slice.py b/invesalius/data/viewer_slice.py index 442bcbf..f5afdb9 100755 --- a/invesalius/data/viewer_slice.py +++ b/invesalius/data/viewer_slice.py @@ -90,7 +90,7 @@ class Viewer(wx.Panel): self.on_text = False # VTK pipeline and actors self.__config_interactor() - self.pick = vtk.vtkPropPicker() + self.pick = vtk.vtkWorldPointPicker() self.cross_actor = vtk.vtkActor() @@ -180,6 +180,7 @@ class Viewer(wx.Panel): { "MouseMoveEvent": self.OnBrushMove, "LeftButtonPressEvent": self.OnBrushClick, + "LeftButtonReleaseEvent": self.OnBrushRelease, "EnterEvent": self.OnEnterInteractor, "LeaveEvent": self.OnLeaveInteractor }, @@ -566,9 +567,9 @@ class Viewer(wx.Panel): def ChangeBrushSize(self, pubsub_evt): size = pubsub_evt.data - self._brush_cursor_size = size - for slice_data in self.slice_data_list: - slice_data.cursor.SetSize(size) + #self._brush_cursor_size = size + #for slice_data in self.slice_data_list: + self.slice_data.cursor.SetSize(size) def ChangeBrushColour(self, pubsub_evt): vtk_colour = pubsub_evt.data[3] @@ -587,60 +588,71 @@ class Viewer(wx.Panel): def ChangeBrushActor(self, pubsub_evt): brush_type = pubsub_evt.data - for slice_data in self.slice_data_list: - self._brush_cursor_type = brush_type - #self.ren.RemoveActor(self.cursor.actor) - - if brush_type == const.BRUSH_SQUARE: - cursor = ca.CursorRectangle() - elif brush_type == const.BRUSH_CIRCLE: - cursor = ca.CursorCircle() - #self.cursor = cursor - - cursor.SetOrientation(self.orientation) - coordinates = {"SAGITAL": [slice_data.number, 0, 0], - "CORONAL": [0, slice_data.number, 0], - "AXIAL": [0, 0, slice_data.number]} - cursor.SetPosition(coordinates[self.orientation]) - cursor.SetSpacing(self.imagedata.GetSpacing()) - cursor.SetColour(self._brush_cursor_colour) - cursor.SetSize(self._brush_cursor_size) - slice_data.SetCursor(cursor) - #self.ren.AddActor(cursor.actor) - #self.ren.Render() + slice_data = self.slice_data + self._brush_cursor_type = brush_type + + if brush_type == const.BRUSH_SQUARE: + cursor = ca.CursorRectangle() + elif brush_type == const.BRUSH_CIRCLE: + cursor = ca.CursorCircle() + + cursor.SetOrientation(self.orientation) + coordinates = {"SAGITAL": [slice_data.number, 0, 0], + "CORONAL": [0, slice_data.number, 0], + "AXIAL": [0, 0, slice_data.number]} + cursor.SetPosition(coordinates[self.orientation]) + cursor.SetSpacing(self.slice_.spacing) + cursor.SetColour(self._brush_cursor_colour) + cursor.SetSize(self._brush_cursor_size) + slice_data.SetCursor(cursor) self.interactor.Render() - #self.cursor = cursor def OnBrushClick(self, evt, obj): - + self.__set_editor_cursor_visibility(1) + mouse_x, mouse_y = self.interactor.GetEventPosition() render = self.interactor.FindPokedRenderer(mouse_x, mouse_y) slice_data = self.get_slice_data(render) - self.pick.Pick(mouse_x, mouse_y, 0, render) + # TODO: Improve! + #for i in self.slice_data_list: + #i.cursor.Show(0) + self.slice_data.cursor.Show() + + self.pick.Pick(mouse_x, mouse_y, 0, render) + coord = self.get_coordinate_cursor() slice_data.cursor.SetPosition(coord) slice_data.cursor.SetEditionPosition( self.get_coordinate_cursor_edition(slice_data)) self.__update_cursor_position(slice_data, coord) - #render.Render() - evt_msg = {const.BRUSH_ERASE: 'Erase mask pixel', - const.BRUSH_DRAW: 'Add mask pixel', - const.BRUSH_THRESH: 'Edit mask pixel'} - msg = evt_msg[self._brush_cursor_op] + cursor = self.slice_data.cursor + position = self.slice_data.actor.GetInput().FindPoint(coord) + radius = cursor.radius - pixels = itertools.ifilter(self.test_operation_position, - slice_data.cursor.GetPixels()) - ps.Publisher().sendMessage(msg, pixels) + if position < 0: + position = self.calculate_matrix_position(coord) + + + # TODO: Call slice_ functions instead of to use pubsub message, + # maybe we can get some performances improvements here. + if self._brush_cursor_op == const.BRUSH_ERASE: + self.slice_.erase_mask_pixel(cursor.GetPixels(), position, radius, + self.orientation) + elif self._brush_cursor_op == const.BRUSH_DRAW: + self.slice_.add_mask_pixel(cursor.GetPixels(), position, radius, + self.orientation) + elif self._brush_cursor_op == const.BRUSH_THRESH: + self.slice_.edit_mask_pixel(cursor.GetPixels(), position, radius, + self.orientation) + + # TODO: To create a new function to reload images to viewer. + self.OnScrollBar() - # FIXME: This is idiot, but is the only way that brush operations are - # working when cross is disabled - ps.Publisher().sendMessage('Update slice viewer') def OnBrushMove(self, evt, obj): - self.__set_editor_cursor_visibility(1) mouse_x, mouse_y = self.interactor.GetEventPosition() @@ -650,35 +662,50 @@ class Viewer(wx.Panel): # TODO: Improve! #for i in self.slice_data_list: #i.cursor.Show(0) - #slice_data.cursor.Show() + self.slice_data.cursor.Show() self.pick.Pick(mouse_x, mouse_y, 0, render) - if (self.pick.GetProp()): - self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_BLANK)) - else: - self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + #if (self.pick.GetViewProp()): + #self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_BLANK)) + #else: + #self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) coord = self.get_coordinate_cursor() slice_data.cursor.SetPosition(coord) slice_data.cursor.SetEditionPosition( self.get_coordinate_cursor_edition(slice_data)) self.__update_cursor_position(slice_data, coord) - - if self._brush_cursor_op == const.BRUSH_ERASE: - evt_msg = 'Erase mask pixel' - elif self._brush_cursor_op == const.BRUSH_DRAW: - evt_msg = 'Add mask pixel' - elif self._brush_cursor_op == const.BRUSH_THRESH: - evt_msg = 'Edit mask pixel' - + if (self.left_pressed): - pixels = itertools.ifilter(self.test_operation_position, - slice_data.cursor.GetPixels()) - ps.Publisher().sendMessage(evt_msg, pixels) - ps.Publisher().sendMessage('Update slice viewer') + cursor = self.slice_data.cursor + position = self.slice_data.actor.GetInput().FindPoint(coord) + radius = cursor.radius - self.interactor.Render() + if position < 0: + position = self.calculate_matrix_position(coord) + + + # TODO: Call slice_ functions instead of to use pubsub message, + # maybe we can get some performances improvements here. + if self._brush_cursor_op == const.BRUSH_ERASE: + self.slice_.erase_mask_pixel(cursor.GetPixels(), position, radius, + self.orientation) + elif self._brush_cursor_op == const.BRUSH_DRAW: + self.slice_.add_mask_pixel(cursor.GetPixels(), position, radius, + self.orientation) + elif self._brush_cursor_op == const.BRUSH_THRESH: + self.slice_.edit_mask_pixel(cursor.GetPixels(), position, radius, + self.orientation) + + # TODO: To create a new function to reload images to viewer. + self.OnScrollBar() + + else: + self.interactor.Render() + + def OnBrushRelease(self, evt, obj): + self.slice_.apply_slice_buffer_to_mask(self.orientation) def OnCrossMouseClick(self, evt, obj): self.ChangeCrossPosition() @@ -787,10 +814,24 @@ class Viewer(wx.Panel): # vtkImageData extent return coord + def calculate_matrix_position(self, coord): + x, y, z = coord + xi, xf, yi, yf, zi, zf = self.slice_data.actor.GetBounds() + if self.orientation == 'AXIAL': + mx = round((x - xi)/self.slice_.spacing[0], 0) + my = round((y - yi)/self.slice_.spacing[1], 0) + elif self.orientation == 'CORONAL': + mx = round((x - xi)/self.slice_.spacing[0], 0) + my = round((z - zi)/self.slice_.spacing[2], 0) + elif self.orientation == 'SAGITAL': + mx = round((y - yi)/self.slice_.spacing[1], 0) + my = round((z - zi)/self.slice_.spacing[2], 0) + return my, mx + def get_coordinate_cursor(self): # Find position x, y, z = self.pick.GetPickPosition() - bounds = self.actor.GetBounds() + bounds = self.slice_data.actor.GetBounds() if bounds[0] == bounds[1]: x = bounds[0] elif bounds[2] == bounds[3]: @@ -816,10 +857,10 @@ class Viewer(wx.Panel): dy = bound_yf - bound_yi dz = bound_zf - bound_zi - dimensions = self.imagedata.GetDimensions() + dimensions = self.slice_.matrix.shape try: - x = (x * dimensions[0]) / dx + x = (x * dimensions[2]) / dx except ZeroDivisionError: x = slice_number try: @@ -827,7 +868,7 @@ class Viewer(wx.Panel): except ZeroDivisionError: y = slice_number try: - z = (z * dimensions[2]) / dz + z = (z * dimensions[0]) / dz except ZeroDivisionError: z = slice_number @@ -959,7 +1000,7 @@ class Viewer(wx.Panel): self.slice_number = 0 self.cursor = None self.wl_text = None - self.pick = vtk.vtkPropPicker() + self.pick = vtk.vtkWorldPointPicker() def OnSetInteractorStyle(self, pubsub_evt): @@ -1046,7 +1087,7 @@ class Viewer(wx.Panel): cursor.SetOrientation(self.orientation) #self.__update_cursor_position([i for i in actor_bound[1::2]]) cursor.SetColour(self._brush_cursor_colour) - cursor.SetSpacing(self.imagedata.GetSpacing()) + cursor.SetSpacing(self.slice_.spacing) cursor.Show(0) self.cursor_ = cursor return cursor @@ -1060,6 +1101,7 @@ class Viewer(wx.Panel): self.slice_data = self.create_slice_window() self.slice_data.actor.SetInput(imagedata) + self.slice_data.SetCursor(self.__create_cursor()) self.cam = self.slice_data.renderer.GetActiveCamera() self.set_slice_number(0) self.__update_camera() -- libgit2 0.21.2