Commit 7853c0732d9ebe9aad87f26bc096b0791fd04359

Authored by tatiana
1 parent 81b99bfa

ENC: Aesthetic commit, related to methods naming and organization inside Slice class

Showing 2 changed files with 226 additions and 179 deletions   Show diff stats
invesalius/constants.py
1 1 from project import Project
2 2  
  3 +# Slice orientation
3 4 AXIAL = 0
4 5 CORONAL = 1
5 6 SAGITAL = 2
6 7  
  8 +# Colour representing each orientation
  9 +ORIENTATION_COLOUR = {'AXIAL': (1,0,0), # Red
  10 + 'CORONAL': (0,1,0), # Green
  11 + 'SAGITAL': (0,0,1)} # Blue
7 12  
8   -proj = Project()
  13 +# Camera according to slice's orientation
  14 +CAM_POSITION = {"AXIAL":(0, 0, 1), "CORONAL":(0, -1, 0), "SAGITAL":(1, 0, 0)}
  15 +CAM_VIEW_UP = {"AXIAL":(0, 1, 0), "CORONAL":(0, 0, 1), "SAGITAL":(0, 0, 1)}
9 16  
  17 +# Mask threshold options
  18 +proj = Project()
10 19 THRESHOLD_RANGE = proj.threshold_modes["Bone"]
11   -
12 20 THRESHOLD_PRESETS_INDEX = 0 #Bone
13   -
14 21 THRESHOLD_HUE_RANGE = (0, 0.6667)
15 22 THRESHOLD_INVALUE = 5000
16 23 THRESHOLD_OUTVALUE = 0
17 24  
  25 +# Mask properties
18 26 MASK_NAME_PATTERN = "Mask %d"
19   -
  27 +MASK_OPACITY = 0.3
20 28 MASK_COLOUR = [(0.33, 1, 0.33),
21 29 (1, 1, 0.33),
22 30 (0.33, 0.91, 1),
... ... @@ -34,33 +42,32 @@ MASK_COLOUR = [(0.33, 1, 0.33),
34 42 #(0.792156862745098, 1.0, 0.66666666666666663), # too "light"
35 43 #(0.66666666666666663, 0.792156862745098, 1.0)]
36 44  
37   -MASK_OPACITY = 0.3
  45 +# Related to slice editor brush
  46 +BRUSH_FORMAT = 0 # 0: circle, 1: square
  47 +BRUSH_SIZE = 30
  48 +BRUSH_OP = 0 # 0: erase, 1: add, 2: threshold
  49 +BRUSH_COLOUR = (0,0,1.0)
38 50  
39   -OP_ADD = 0
40   -OP_DEL = 1
41   -OP_THRESH =2
42 51  
43   -# Surface creation default values. List contains:
  52 +# Surface creation values. Each element's list contains:
44 53 # 0: imagedata reformat ratio
45 54 # 1: smooth_iterations
46 55 # 2: smooth_relaxation_factor
47 56 # 3: decimate_reduction
48   -
49 57 SURFACE_QUALITY = {
50 58 "Low": (3, 2, 0.3000, 0.4),
51 59 "Medium": (2, 2, 0.3000, 0.4),
52 60 "High": (0, 1, 0.3000, 0.1),
53 61 "Optimal": (0, 2, 0.3000, 0.4),
54   - "Custom": (None, None, None, None)
55   -}
56   -
  62 + "Custom": (None, None, None, None)}
57 63 DEFAULT_SURFACE_QUALITY = "Optimal"
58 64  
59   -
  65 +# Surface properties
60 66 SURFACE_TRANSPARENCY = 0.0
61 67 SURFACE_NAME_PATTERN = "Surface %d"
62 68  
63   -WINDOW_LEVEL = ({"Abdomen":(350,50),
  69 +# Imagedata - window and level presets
  70 +WINDOW_LEVEL = {"Abdomen":(350,50),
64 71 "Bone":(2000, 300),
65 72 "Brain Posterior Fossa":(120,40),
66 73 "Brain":(80,40),
... ... @@ -76,12 +83,6 @@ WINDOW_LEVEL = ({"Abdomen":(350,50),
76 83 "Pelvis": (450,50),
77 84 "Sinus":(4000, 400),
78 85 "Vasculature - Hard":(240,80),
79   - "Vasculature - Soft":(650,160)
80   - })
  86 + "Vasculature - Soft":(650,160)}
81 87  
82   -ORIENTATION_COLOUR = {'AXIAL': (1,0,0), # Red
83   - 'CORONAL': (0,1,0), # Green
84   - 'SAGITAL': (0,0,1)} # Blue
85   -CAM_POSITION = {"AXIAL":(0, 0, 1), "CORONAL":(0, -1, 0), "SAGITAL":(1, 0, 0)}
86   -CAM_VIEW_UP = {"AXIAL":(0, 1, 0), "CORONAL":(0, 0, 1), "SAGITAL":(0, 0, 1)}
87 88  
... ...
invesalius/data/slice_.py
... ... @@ -11,109 +11,220 @@ from utils import Singleton
11 11  
12 12 class Slice(object):
13 13 __metaclass__= Singleton
14   - # Only one project will be initialized per time. Therefore, we use
15   - # Singleton design pattern for implementing it
  14 + # Only one slice will be initialized per time (despite several viewers
  15 + # show it from distinct perspectives).
  16 + # Therefore, we use Singleton design pattern for implementing it.
16 17  
17 18 def __init__(self):
18 19 self.imagedata = None
19   - self.__bind_events()
20 20 self.current_mask = None
21   -
  21 + self.__bind_events()
  22 +
22 23 def __bind_events(self):
23   - ps.Publisher().subscribe(self.SetThresholdRange, 'Set threshold values')
24   - ps.Publisher().subscribe(self.SetEditionThresholdRange,
25   - 'Set edition threshold values')
26   - ps.Publisher().subscribe(self.OnChangeCurrentMaskColour,
27   - 'Change mask colour')
28   - ps.Publisher().subscribe(self.AddMask, 'Create new mask')
29   - ps.Publisher().subscribe(self.OnChangeCurrentMask, 'Change mask selected')
30   - ps.Publisher().subscribe(self.CreateSurfaceFromIndex,
31   - 'Create surface from index')
  24 + # Slice properties
32 25 ps.Publisher().subscribe(self.UpdateCursorPosition,
33 26 'Update cursor position in slice')
34   - ps.Publisher().subscribe(self.ShowMask, 'Show mask')
35   - ps.Publisher().subscribe(self.ChangeMaskName, 'Change mask name')
36   - ps.Publisher().subscribe(self.EraseMaskPixel, 'Erase mask pixel')
37   - ps.Publisher().subscribe(self.EditMaskPixel, 'Edit mask pixel')
38   - ps.Publisher().subscribe(self.AddMaskPixel, 'Add mask pixel')
  27 + # General slice control
  28 + ps.Publisher().subscribe(self.CreateSurfaceFromIndex,
  29 + 'Create surface from index')
  30 + # Mask control
  31 + ps.Publisher().subscribe(self.__add_mask, 'Create new mask')
  32 + ps.Publisher().subscribe(self.__select_current_mask,
  33 + 'Change mask selected')
  34 + # Mask properties
  35 + ps.Publisher().subscribe(self.__set_current_mask_edition_threshold,
  36 + 'Set edition threshold values')
  37 + ps.Publisher().subscribe(self.__set_current_mask_threshold,
  38 + 'Set threshold values')
  39 + ps.Publisher().subscribe(self.__set_current_mask_colour,
  40 + 'Change mask colour')
  41 + ps.Publisher().subscribe(self.__set_mask_name, 'Change mask name')
  42 + ps.Publisher().subscribe(self.__show_mask, 'Show mask')
  43 +
  44 + # Operations related to slice editor
  45 + ps.Publisher().subscribe(self.__erase_mask_pixel, 'Erase mask pixel')
  46 + ps.Publisher().subscribe(self.__edit_mask_pixel, 'Edit mask pixel')
  47 + ps.Publisher().subscribe(self.__add_mask_pixel, 'Add mask pixel')
  48 +
  49 + #---------------------------------------------------------------------------
  50 + # BEGIN PUBSUB_EVT METHODS
  51 + #---------------------------------------------------------------------------
  52 + def __get_mask_data_for_surface_creation(self, pubsub_evt):
  53 + mask_index = pubsub_evt.data
  54 + CreateSurfaceFromIndex
39 55  
40   - def EraseMaskPixel(self, pubsub_evt):
  56 +
  57 +
  58 +
  59 + def __add_mask(self, pubsub_evt):
  60 + mask_name = pubsub_evt.data
  61 + self.CreateMask(name=mask_name)
  62 + self.ChangeCurrentMaskColour(self.current_mask.colour)
  63 +
  64 + def __select_current_mask(self, pubsub_evt):
  65 + mask_index = pubsub_evt.data
  66 + self.SelectCurrentMask(mask_index)
  67 + #---------------------------------------------------------------------------
  68 + def __set_current_mask_edition_threshold(self, evt_pubsub):
  69 + if self.current_mask:
  70 + threshold_range = evt_pubsub.data
  71 + index = self.current_mask.index
  72 + self.SetMaskEditionThreshold(index, threshold_range)
  73 +
  74 + def __set_current_mask_threshold(self, evt_pubsub):
  75 + threshold_range = evt_pubsub.data
  76 + index = self.current_mask.index
  77 + self.SetMaskThreshold(index, threshold_range)
  78 +
  79 + def __set_current_mask_colour(self, pubsub_evt):
  80 + # "if" is necessary because wx events are calling this before any mask
  81 + # has been created
  82 + if self.current_mask:
  83 + colour_wx = pubsub_evt.data
  84 + colour_vtk = [c/255.0 for c in colour_wx]
  85 + self.SetMaskColour(self.current_mask.index, colour_vtk)
  86 +
  87 + def __set_mask_name(self, pubsub_evt):
  88 + index, name = pubsub_evt.data
  89 + self.SetMaskName(index, name)
  90 +
  91 + def __show_mask(self, pubsub_evt):
  92 + # "if" is necessary because wx events are calling this before any mask
  93 + # has been created
  94 + if self.current_mask:
  95 + index, value = pubsub_evt.data
  96 + self.ShowMask(index, value)
  97 + #---------------------------------------------------------------------------
  98 + def __erase_mask_pixel(self, pubsub_evt):
41 99 position = pubsub_evt.data
42 100 self.ErasePixel(position)
43 101  
44   - def EditMaskPixel(self, pubsub_evt):
  102 + def __edit_mask_pixel(self, pubsub_evt):
45 103 position = pubsub_evt.data
46 104 self.EditPixelBasedOnThreshold(position)
47 105  
48   - def AddMaskPixel(self, pubsub_evt):
  106 + def __add_mask_pixel(self, pubsub_evt):
49 107 position = pubsub_evt.data
50 108 self.DrawPixel(position)
51   -
52   - def ChangeMaskName(self, pubsub_evt):
53   - index, name = pubsub_evt.data
54   -
  109 + #---------------------------------------------------------------------------
  110 + # END PUBSUB_EVT METHODS
  111 + #---------------------------------------------------------------------------
  112 +
  113 +
  114 + def SetMaskColour(self, index, colour, update=True):
  115 + "Set a mask colour given its index and colour (RGB 0-1 values)"
  116 + proj = Project()
  117 + proj.mask_dict[index].colour = colour
  118 +
  119 + (r,g,b) = colour
  120 + scalar_range = int(self.imagedata.GetScalarRange()[1])
  121 + self.lut_mask.SetTableValue(1, r, g, b, 1.0)
  122 + self.lut_mask.SetTableValue(scalar_range - 1, r, g, b, 1.0)
  123 +
  124 + colour_wx = [r*255, g*255, b*255]
  125 + ps.Publisher().sendMessage('Change mask colour in notebook',
  126 + (self.current_mask.index, (r,g,b)))
  127 + ps.Publisher().sendMessage('Set GUI items colour', colour_wx)
  128 + if update:
  129 + ps.Publisher().sendMessage('Update slice viewer')
  130 +
  131 + def SetMaskName(self, index, name):
  132 + "Rename a mask given its index and the new name"
55 133 proj = Project()
56 134 proj.mask_dict[index].name = name
57 135  
  136 + def SetMaskEditionThreshold(self, index, threshold_range):
  137 + "Set threshold bounds to be used while editing slice"
  138 + proj = Project()
  139 + proj.mask_dict[index].edition_threshold_range = threshold_range
58 140  
59   - def ShowMask(self, pubsub_evt):
60   -
61   - # This is necessary because wx events are calling this before it was created
62   - if self.current_mask:
63   - mask_index, value = pubsub_evt.data
  141 + def SetMaskThreshold(self, index, threshold_range):
  142 + """
  143 + Set a mask threshold range given its index and tuple of min and max
  144 + threshold values.
  145 + """
  146 + thresh_min, thresh_max = threshold_range
  147 +
  148 + if self.current_mask.index == index:
  149 + # Update pipeline (this must be here, so pipeline is not broken)
  150 + self.img_thresh_mask.SetInput(self.imagedata)
  151 + self.img_thresh_mask.ThresholdBetween(float(thresh_min),
  152 + float(thresh_max))
  153 + self.img_thresh_mask.Update()
64 154  
65   - proj = Project()
66   - proj.mask_dict[mask_index].is_shown = value
  155 + # Create imagedata copy so the pipeline is not broken
  156 + imagedata = self.img_thresh_mask.GetOutput()
  157 + self.current_mask.imagedata.DeepCopy(imagedata)
  158 + self.current_mask.threshold_range = threshold_range
67 159  
68   -
69   - if (mask_index == self.current_mask.index):
70   - if value:
71   - self.blend_filter.SetOpacity(1, self.current_mask.opacity)
72   - else:
73   -
74   - self.blend_filter.SetOpacity(1, 0)
75   - self.blend_filter.Update()
76   - ps.Publisher().sendMessage('Update slice viewer')
77   -
  160 + # Update pipeline (this must be here, so pipeline is not broken)
  161 + self.img_colours_mask.SetInput(self.current_mask.imagedata)
78 162  
79   -
80   - def CreateSurfaceFromIndex(self, pubsub_evt):
81   - mask_index = pubsub_evt.data
  163 + # Update viewer
  164 + ps.Publisher().sendMessage('Update slice viewer')
  165 +
  166 + # Update data notebook (GUI)
  167 + ps.Publisher().sendMessage('Set mask threshold in notebook',
  168 + (self.current_mask.index,
  169 + self.current_mask.threshold_range))
  170 + else:
  171 + proj = Project()
  172 + proj.mask_dict[index].threshold_range = threshold_range
82 173  
83   -
  174 + def ShowMask(self, index, value):
  175 + "Show a mask given its index and 'show' value (0: hide, other: show)"
84 176 proj = Project()
85   - mask = proj.mask_dict[mask_index]
86   -
87   - # This is very important. Do not use masks' imagedata. It would mess up
88   - # surface quality event when using contour
89   - imagedata = self.imagedata
90   -
91   - colour = mask.colour
92   - threshold = mask.threshold_range
93   -
94   - ps.Publisher().sendMessage('Create surface',
95   - (imagedata,colour,threshold))
  177 + proj.mask_dict[index].is_shown = value
  178 + if (mask_index == self.current_mask.index):
  179 + if value:
  180 + self.blend_filter.SetOpacity(1, self.current_mask.opacity)
  181 + else:
  182 + self.blend_filter.SetOpacity(1, 0)
  183 + self.blend_filter.Update()
  184 + ps.Publisher().sendMessage('Update slice viewer')
  185 + #---------------------------------------------------------------------------
  186 + def ErasePixel(self, position):
  187 + "Delete pixel, based on x, y and z position coordinates."
  188 + x, y, z = position
  189 + colour = self.imagedata.GetScalarRange()[0]# - 1 # Important to effect erase
  190 + imagedata = self.current_mask.imagedata
  191 + imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
  192 + imagedata.Update()
96 193  
97   - def OnChangeCurrentMaskColour(self, pubsub_evt):
98   - colour_wx = pubsub_evt.data
99   - colour_vtk = [c/255.0 for c in colour_wx]
100   - self.ChangeCurrentMaskColour(colour_vtk)
  194 + def DrawPixel(self, position, colour=None):
  195 + "Draw pixel, based on x, y and z position coordinates."
  196 + x, y, z = position
  197 + if not colour:
  198 + colour = self.imagedata.GetScalarRange()[1]
  199 + imagedata = self.current_mask.imagedata
  200 + imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
  201 + imagedata.Update()
101 202  
102   - def OnChangeCurrentMask(self, pubsub_evt):
  203 + def EditPixelBasedOnThreshold(self, position):
  204 + "Erase or draw pixel based on edition threshold range."
  205 + x, y, z = position
  206 + colour = self.imagedata.GetScalarComponentAsDouble(x, y, z, 0)
  207 + thresh_min, thresh_max = self.current_mask.edition_threshold_range
103 208  
104   - mask_index = pubsub_evt.data
  209 + if (colour >= thresh_min) and (colour <= thresh_max):
  210 + self.DrawPixel(position, colour)
  211 + else:
  212 + self.ErasePixel(position)
  213 + #---------------------------------------------------------------------------
  214 + def SelectCurrentMask(self, index):
  215 + "Insert mask data, based on given index, into pipeline."
105 216  
106 217 # This condition is not necessary in Linux, only under mac and windows
107   - # in these platforms, because the combobox event is binded when the
108   - # same item is selected again.
109   - if mask_index != self.current_mask.index:
  218 + # because combobox event is binded when the same item is selected again.
  219 + if index != self.current_mask.index:
110 220 proj = Project()
111   - future_mask = proj.GetMask(mask_index)
  221 + future_mask = proj.GetMask(index)
112 222  
113 223 self.current_mask = future_mask
114 224  
115 225 colour = future_mask.colour
116   - self.ChangeCurrentMaskColour(colour, update=False)
  226 + index = future_mask.index
  227 + self.SetMaskColour(index, colour, update=False)
117 228  
118 229 imagedata = future_mask.imagedata
119 230 self.img_colours_mask.SetInput(imagedata)
... ... @@ -132,25 +243,35 @@ class Slice(object):
132 243 self.current_mask.threshold_range)
133 244 ps.Publisher().sendMessage('Select mask name in combo', mask_index)
134 245 ps.Publisher().sendMessage('Update slice viewer')
  246 + #---------------------------------------------------------------------------
135 247  
136 248  
137 249  
138   - def ChangeCurrentMaskColour(self, colour, update=True):
139   - # This is necessary because wx events are calling this before it was created
140   - if self.current_mask:
141   - (r,g,b) = colour
142   - scalar_range = int(self.imagedata.GetScalarRange()[1])
143   - self.current_mask.colour = colour
144 250  
145   - self.lut_mask.SetTableValue(1, r, g, b, 1.0)
146   - self.lut_mask.SetTableValue(scalar_range - 1, r, g, b, 1.0)
147 251  
148   - colour_wx = [r*255, g*255, b*255]
149   - ps.Publisher().sendMessage('Change mask colour in notebook',
150   - (self.current_mask.index, (r,g,b)))
151   - ps.Publisher().sendMessage('Set GUI items colour', colour_wx)
152   - if update:
153   - ps.Publisher().sendMessage('Update slice viewer')
  252 +
  253 + def CreateSurfaceFromIndex(self, pubsub_evt):
  254 + mask_index = pubsub_evt.data
  255 +
  256 +
  257 + proj = Project()
  258 + mask = proj.mask_dict[mask_index]
  259 +
  260 + # This is very important. Do not use masks' imagedata. It would mess up
  261 + # surface quality event when using contour
  262 + imagedata = self.imagedata
  263 +
  264 + colour = mask.colour
  265 + threshold = mask.threshold_range
  266 +
  267 + ps.Publisher().sendMessage('Create surface',
  268 + (imagedata,colour,threshold))
  269 +
  270 +
  271 +
  272 +
  273 +
  274 +
154 275  
155 276  
156 277 def GetOutput(self):
... ... @@ -235,11 +356,6 @@ class Slice(object):
235 356  
236 357 return img_colours_bg.GetOutput()
237 358  
238   - def AddMask(self, pubsub_evt):
239   - mask_name = pubsub_evt.data
240   - self.CreateMask(name=mask_name)
241   - self.ChangeCurrentMaskColour(self.current_mask.colour)
242   -
243 359 def CreateMask(self, imagedata=None, name=None):
244 360  
245 361 future_mask = Mask()
... ... @@ -311,15 +427,6 @@ class Slice(object):
311 427 mask_thresh_imagedata = self.__create_mask_threshold(imagedata)
312 428 current_mask.imagedata.DeepCopy(mask_thresh_imagedata)
313 429  
314   - #import time
315   - #viewer = vtk.vtkImageViewer()
316   - #viewer.SetInput(mask_thresh_imagedata)
317   - #viewer.SetColorWindow(400)
318   - #viewer.SetColorLevel(200)
319   - #viewer.Render()
320   - #time.sleep(5)
321   -
322   -
323 430 # map the input image through a lookup table
324 431 img_colours_mask = vtk.vtkImageMapToColors()
325 432 img_colours_mask.SetOutputFormatToRGBA()
... ... @@ -348,64 +455,3 @@ class Slice(object):
348 455 imagedata_mask.Update()
349 456  
350 457 return imagedata_mask
351   -
352   - def SetThresholdRange(self, evt_pubsub):
353   - thresh_min, thresh_max = evt_pubsub.data
354   -
355   - self.img_thresh_mask.SetInput(self.imagedata)
356   - self.img_thresh_mask.ThresholdBetween(float(thresh_min),
357   - float(thresh_max))
358   - self.img_thresh_mask.Update()
359   -
360   - imagedata = self.img_thresh_mask.GetOutput()
361   -
362   - self.current_mask.imagedata.DeepCopy(imagedata)
363   -
364   - self.img_colours_mask.SetInput(self.current_mask.imagedata)
365   -
366   - # save new data inside current mask
367   - self.current_mask.threshold_range = (thresh_min, thresh_max)
368   -
369   - ps.Publisher().sendMessage('Set mask threshold in notebook',
370   - (self.current_mask.index,
371   - self.current_mask.threshold_range))
372   - ps.Publisher().sendMessage('Update slice viewer')
373   -
374   - def SetEditionThresholdRange(self, evt_pubsub):
375   - if self.current_mask:
376   - thresh_min, thresh_max = evt_pubsub.data
377   - self.current_mask.edition_threshold_range = thresh_min, thresh_max
378   -
379   - def ErasePixel(self, position):
380   - """
381   - Delete pixel, based on x, y and z position coordinates.
382   - """
383   - x, y, z = position
384   - colour = self.imagedata.GetScalarRange()[0]# - 1 # Important to effect erase
385   - imagedata = self.current_mask.imagedata
386   - imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
387   - imagedata.Update()
388   -
389   - def DrawPixel(self, position, colour=None):
390   - """
391   - Draw pixel, based on x, y and z position coordinates.
392   - """
393   - x, y, z = position
394   - if not colour:
395   - colour = self.imagedata.GetScalarRange()[1]
396   - imagedata = self.current_mask.imagedata
397   - imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
398   - imagedata.Update()
399   -
400   - def EditPixelBasedOnThreshold(self, position):
401   - """
402   - Erase or draw pixel based on edition threshold range.
403   - """
404   - x, y, z = position
405   - colour = self.imagedata.GetScalarComponentAsDouble(x, y, z, 0)
406   - thresh_min, thresh_max = self.current_mask.edition_threshold_range
407   -
408   - if (colour >= thresh_min) and (colour <= thresh_max):
409   - self.DrawPixel(position, colour)
410   - else:
411   - self.ErasePixel(position)
... ...