Commit 86398ac2c899ed4f0b11af3a7787c66ffe5811c3
1 parent
e95a97e6
Exists in
master
and in
67 other branches
ENH: Threshold mask is working, but is necessary to create a mask because is not…
… created a mask automatically in the init.
Showing
4 changed files
with
110 additions
and
31 deletions
Show diff stats
invesalius/data/mask.py
| @@ -87,4 +87,5 @@ class Mask(): | @@ -87,4 +87,5 @@ class Mask(): | ||
| 87 | def create_mask(self, shape): | 87 | def create_mask(self, shape): |
| 88 | print "Creating a mask" | 88 | print "Creating a mask" |
| 89 | self.temp_file = tempfile.mktemp() | 89 | self.temp_file = tempfile.mktemp() |
| 90 | + shape = shape[0] + 1, shape[1] + 1, shape[2] + 1 | ||
| 90 | self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) | 91 | self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) |
invesalius/data/slice_.py
| @@ -41,6 +41,10 @@ class Slice(object): | @@ -41,6 +41,10 @@ class Slice(object): | ||
| 41 | self.blend_filter = None | 41 | self.blend_filter = None |
| 42 | self.matrix = None | 42 | self.matrix = None |
| 43 | 43 | ||
| 44 | + self.buffer_slices = {"AXIAL": [-1, None, None], | ||
| 45 | + "CORONAL": [-1,None, None], | ||
| 46 | + "SAGITAL": [-1, None, None]} | ||
| 47 | + | ||
| 44 | self.num_gradient = 0 | 48 | self.num_gradient = 0 |
| 45 | self.interaction_style = st.StyleStateManager() | 49 | self.interaction_style = st.StyleStateManager() |
| 46 | 50 | ||
| @@ -59,6 +63,8 @@ class Slice(object): | @@ -59,6 +63,8 @@ class Slice(object): | ||
| 59 | 'Set edition threshold values') | 63 | 'Set edition threshold values') |
| 60 | ps.Publisher().subscribe(self.__set_current_mask_threshold, | 64 | ps.Publisher().subscribe(self.__set_current_mask_threshold, |
| 61 | 'Set threshold values') | 65 | 'Set threshold values') |
| 66 | + ps.Publisher().subscribe(self.__set_current_mask_threshold_actual_slice, | ||
| 67 | + 'Changing threshold values') | ||
| 62 | ps.Publisher().subscribe(self.__set_current_mask_colour, | 68 | ps.Publisher().subscribe(self.__set_current_mask_colour, |
| 63 | 'Change mask colour') | 69 | 'Change mask colour') |
| 64 | ps.Publisher().subscribe(self.__set_mask_name, 'Change mask name') | 70 | ps.Publisher().subscribe(self.__set_mask_name, 'Change mask name') |
| @@ -192,11 +198,27 @@ class Slice(object): | @@ -192,11 +198,27 @@ class Slice(object): | ||
| 192 | def __set_current_mask_threshold(self, evt_pubsub): | 198 | def __set_current_mask_threshold(self, evt_pubsub): |
| 193 | threshold_range = evt_pubsub.data | 199 | threshold_range = evt_pubsub.data |
| 194 | index = self.current_mask.index | 200 | index = self.current_mask.index |
| 195 | - self.SetMaskThreshold(index, threshold_range) | 201 | + #self.SetMaskThreshold(index, threshold_range) |
| 202 | + #Clear edited points | ||
| 203 | + self.current_mask.edited_points = {} | ||
| 204 | + self.num_gradient += 1 | ||
| 205 | + self.current_mask.matrix[0, :, :] = 0 | ||
| 206 | + self.current_mask.matrix[:, 0, :] = 0 | ||
| 207 | + self.current_mask.matrix[:, :, 0] = 0 | ||
| 208 | + | ||
| 209 | + def __set_current_mask_threshold_actual_slice(self, evt_pubsub): | ||
| 210 | + threshold_range = evt_pubsub.data | ||
| 211 | + index = self.current_mask.index | ||
| 212 | + for orientation in self.buffer_slices: | ||
| 213 | + self.SetMaskThreshold(index, threshold_range, | ||
| 214 | + self.buffer_slices[orientation][0], | ||
| 215 | + orientation) | ||
| 196 | #Clear edited points | 216 | #Clear edited points |
| 197 | self.current_mask.edited_points = {} | 217 | self.current_mask.edited_points = {} |
| 198 | self.num_gradient += 1 | 218 | self.num_gradient += 1 |
| 199 | 219 | ||
| 220 | + ps.Publisher().sendMessage('Reload actual slice') | ||
| 221 | + | ||
| 200 | def __set_current_mask_colour(self, pubsub_evt): | 222 | def __set_current_mask_colour(self, pubsub_evt): |
| 201 | # "if" is necessary because wx events are calling this before any mask | 223 | # "if" is necessary because wx events are calling this before any mask |
| 202 | # has been created | 224 | # has been created |
| @@ -237,29 +259,66 @@ class Slice(object): | @@ -237,29 +259,66 @@ class Slice(object): | ||
| 237 | #--------------------------------------------------------------------------- | 259 | #--------------------------------------------------------------------------- |
| 238 | 260 | ||
| 239 | def GetSlices(self, orientation, slice_number): | 261 | def GetSlices(self, orientation, slice_number): |
| 262 | + if self.buffer_slices[orientation][0] == slice_number: | ||
| 263 | + print "From buffer" | ||
| 264 | + image = self.buffer_slices[orientation][1] | ||
| 265 | + n_mask = self.buffer_slices[orientation][2] | ||
| 266 | + mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation) | ||
| 267 | + final_image = self.do_blend(image, self.do_colour_mask(mask)) | ||
| 268 | + else: | ||
| 269 | + n_image = self.GetImageSlice(orientation, slice_number) | ||
| 270 | + image = iu.to_vtk(n_image, self.spacing, slice_number, orientation) | ||
| 271 | + image = self.do_ww_wl(image) | ||
| 272 | + | ||
| 273 | + if self.current_mask and self.current_mask.is_shown: | ||
| 274 | + print "Mask" | ||
| 275 | + n_mask = self.GetMaskSlice(orientation, slice_number) | ||
| 276 | + mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation) | ||
| 277 | + final_image = self.do_blend(image, self.do_colour_mask(mask)) | ||
| 278 | + else: | ||
| 279 | + n_mask = None | ||
| 280 | + final_image = image | ||
| 281 | + | ||
| 282 | + self.buffer_slices[orientation] = [slice_number, image, n_mask, | ||
| 283 | + n_image] | ||
| 284 | + self.slice_number = slice_number | ||
| 285 | + return final_image | ||
| 286 | + | ||
| 287 | + def GetImageSlice(self, orientation, slice_number): | ||
| 240 | if orientation == 'AXIAL': | 288 | if orientation == 'AXIAL': |
| 241 | - n_array = numpy.array(self.matrix[slice_number]) | 289 | + n_image = numpy.array(self.matrix[slice_number]) |
| 242 | elif orientation == 'CORONAL': | 290 | elif orientation == 'CORONAL': |
| 243 | - n_array = numpy.array(self.matrix[..., slice_number, ...]) | 291 | + n_image = numpy.array(self.matrix[..., slice_number, ...]) |
| 244 | elif orientation == 'SAGITAL': | 292 | elif orientation == 'SAGITAL': |
| 245 | - n_array = numpy.array(self.matrix[..., ..., slice_number]) | ||
| 246 | - image = iu.to_vtk(n_array, self.spacing, slice_number, orientation) | ||
| 247 | - if self.current_mask and self.current_mask.is_shown: | ||
| 248 | - mask = self.GetSlicesMask(orientation, slice_number) | ||
| 249 | - image = self.do_blend(self.do_ww_wl(image), mask) | ||
| 250 | - else: | ||
| 251 | - image = self.do_ww_wl(image) | ||
| 252 | - return image | 293 | + n_image = numpy.array(self.matrix[..., ..., slice_number]) |
| 294 | + return n_image | ||
| 253 | 295 | ||
| 254 | - def GetSlicesMask(self, orientation, slice_number): | 296 | + def GetMaskSlice(self, orientation, slice_number): |
| 297 | + slice_number += 1 | ||
| 255 | if orientation == 'AXIAL': | 298 | if orientation == 'AXIAL': |
| 299 | + if self.current_mask.matrix[slice_number, 0, 0] == 0: | ||
| 300 | + self.current_mask.matrix[slice_number, 1:, 1:] = \ | ||
| 301 | + self.do_threshold_to_a_slice(self.GetImageSlice(orientation, | ||
| 302 | + slice_number)) | ||
| 303 | + self.current_mask.matrix[slice_number, 0, 0] = 1 | ||
| 256 | n_mask = numpy.array(self.current_mask.matrix[slice_number]) | 304 | n_mask = numpy.array(self.current_mask.matrix[slice_number]) |
| 305 | + | ||
| 257 | elif orientation == 'CORONAL': | 306 | elif orientation == 'CORONAL': |
| 307 | + if self.current_mask.matrix[0, slice_number, 0] == 0: | ||
| 308 | + self.current_mask.matrix[1:, slice_number, 1:] = \ | ||
| 309 | + self.do_threshold_to_a_slice(self.GetImageSlice(orientation, | ||
| 310 | + slice_number)) | ||
| 311 | + self.current_mask.matrix[0, slice_number, 0] = 1 | ||
| 258 | n_mask = numpy.array(self.current_mask.matrix[..., slice_number, ...]) | 312 | n_mask = numpy.array(self.current_mask.matrix[..., slice_number, ...]) |
| 313 | + | ||
| 259 | elif orientation == 'SAGITAL': | 314 | elif orientation == 'SAGITAL': |
| 315 | + if self.current_mask.matrix[0, 0, slice_number] == 0: | ||
| 316 | + self.current_mask.matrix[1:, 1:, slice_number] = \ | ||
| 317 | + self.do_threshold_to_a_slice(self.GetImageSlice(orientation, | ||
| 318 | + slice_number)) | ||
| 319 | + self.current_mask.matrix[0, 0, slice_number] = 1 | ||
| 260 | n_mask = numpy.array(self.current_mask.matrix[..., ..., slice_number]) | 320 | n_mask = numpy.array(self.current_mask.matrix[..., ..., slice_number]) |
| 261 | - mask = iu.to_vtk(n_mask, self.spacing, slice_number, orientation) | ||
| 262 | - return self.do_colour_mask(mask) | 321 | + return n_mask |
| 263 | 322 | ||
| 264 | def GetNumberOfSlices(self, orientation): | 323 | def GetNumberOfSlices(self, orientation): |
| 265 | if orientation == 'AXIAL': | 324 | if orientation == 'AXIAL': |
| @@ -304,10 +363,14 @@ class Slice(object): | @@ -304,10 +363,14 @@ class Slice(object): | ||
| 304 | proj = Project() | 363 | proj = Project() |
| 305 | proj.mask_dict[index].edition_threshold_range = threshold_range | 364 | proj.mask_dict[index].edition_threshold_range = threshold_range |
| 306 | 365 | ||
| 307 | - def SetMaskThreshold(self, index, threshold_range): | 366 | + def SetMaskThreshold(self, index, threshold_range, slice_number=None, |
| 367 | + orientation=None): | ||
| 308 | """ | 368 | """ |
| 309 | Set a mask threshold range given its index and tuple of min and max | 369 | Set a mask threshold range given its index and tuple of min and max |
| 310 | threshold values. | 370 | threshold values. |
| 371 | + | ||
| 372 | + If slice_number is None then all the threshold is calculated for all | ||
| 373 | + slices, otherwise only to indicated slice. | ||
| 311 | """ | 374 | """ |
| 312 | thresh_min, thresh_max = threshold_range | 375 | thresh_min, thresh_max = threshold_range |
| 313 | 376 | ||
| @@ -327,12 +390,20 @@ class Slice(object): | @@ -327,12 +390,20 @@ class Slice(object): | ||
| 327 | #self.img_colours_mask.SetInput(self.current_mask.imagedata) | 390 | #self.img_colours_mask.SetInput(self.current_mask.imagedata) |
| 328 | 391 | ||
| 329 | # TODO: find out a better way to do threshold | 392 | # TODO: find out a better way to do threshold |
| 330 | - for n, slice_ in enumerate(self.matrix): | ||
| 331 | - m = numpy.ones(slice_.shape, self.current_mask.matrix.dtype) | ||
| 332 | - m[slice_ < thresh_min] = 0 | ||
| 333 | - m[slice_ > thresh_max] = 0 | ||
| 334 | - m[m == 1] = 255 | ||
| 335 | - self.current_mask.matrix[n] = m | 393 | + |
| 394 | + if slice_number is None: | ||
| 395 | + for n, slice_ in enumerate(self.matrix): | ||
| 396 | + m = numpy.ones(slice_.shape, self.current_mask.matrix.dtype) | ||
| 397 | + m[slice_ < thresh_min] = 0 | ||
| 398 | + m[slice_ > thresh_max] = 0 | ||
| 399 | + m[m == 1] = 255 | ||
| 400 | + self.current_mask.matrix[n] = m | ||
| 401 | + else: | ||
| 402 | + print "Only one slice" | ||
| 403 | + slice_ = self.buffer_slices[orientation][3] | ||
| 404 | + m = numpy.zeros(slice_.shape, self.current_mask.matrix.dtype) | ||
| 405 | + m[numpy.logical_and(slice_ >= thresh_min,slice_ <= thresh_max)] = 255 | ||
| 406 | + self.buffer_slices[orientation][2] = m | ||
| 336 | 407 | ||
| 337 | # Update viewer | 408 | # Update viewer |
| 338 | #ps.Publisher().sendMessage('Update slice viewer') | 409 | #ps.Publisher().sendMessage('Update slice viewer') |
| @@ -653,6 +724,16 @@ class Slice(object): | @@ -653,6 +724,16 @@ class Slice(object): | ||
| 653 | 724 | ||
| 654 | return colorer.GetOutput() | 725 | return colorer.GetOutput() |
| 655 | 726 | ||
| 727 | + def do_threshold_to_a_slice(self, slice_matrix): | ||
| 728 | + """ | ||
| 729 | + Based on the current threshold bounds generates a threshold mask to | ||
| 730 | + given slice_matrix. | ||
| 731 | + """ | ||
| 732 | + thresh_min, thresh_max = self.current_mask.threshold_range | ||
| 733 | + m = numpy.zeros(slice_matrix.shape, self.current_mask.matrix.dtype) | ||
| 734 | + m[numpy.logical_and(slice_matrix >= thresh_min, slice_matrix <= thresh_max)] = 255 | ||
| 735 | + return m | ||
| 736 | + | ||
| 656 | def do_colour_mask(self, imagedata): | 737 | def do_colour_mask(self, imagedata): |
| 657 | scalar_range = int(imagedata.GetScalarRange()[1]) | 738 | scalar_range = int(imagedata.GetScalarRange()[1]) |
| 658 | r, g, b = self.current_mask.colour | 739 | r, g, b = self.current_mask.colour |
| @@ -772,7 +853,3 @@ class Slice(object): | @@ -772,7 +853,3 @@ class Slice(object): | ||
| 772 | filename, filetype = pubsub_evt.data | 853 | filename, filetype = pubsub_evt.data |
| 773 | if (filetype == const.FILETYPE_IMAGEDATA): | 854 | if (filetype == const.FILETYPE_IMAGEDATA): |
| 774 | iu.Export(imagedata, filename) | 855 | iu.Export(imagedata, filename) |
| 775 | - | ||
| 776 | - | ||
| 777 | - | ||
| 778 | - |
invesalius/data/viewer_slice.py
| @@ -886,6 +886,8 @@ class Viewer(wx.Panel): | @@ -886,6 +886,8 @@ class Viewer(wx.Panel): | ||
| 886 | ps.Publisher().subscribe(self.AddActors, ('Add actors', ORIENTATIONS[self.orientation])) | 886 | ps.Publisher().subscribe(self.AddActors, ('Add actors', ORIENTATIONS[self.orientation])) |
| 887 | ps.Publisher().subscribe(self.RemoveActors, ('Remove actors', ORIENTATIONS[self.orientation])) | 887 | ps.Publisher().subscribe(self.RemoveActors, ('Remove actors', ORIENTATIONS[self.orientation])) |
| 888 | 888 | ||
| 889 | + ps.Publisher().subscribe(self.ReloadActualSlice, 'Reload actual slice') | ||
| 890 | + | ||
| 889 | def SetDefaultCursor(self, pusub_evt): | 891 | def SetDefaultCursor(self, pusub_evt): |
| 890 | self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) | 892 | self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) |
| 891 | 893 | ||
| @@ -1381,11 +1383,7 @@ class Viewer(wx.Panel): | @@ -1381,11 +1383,7 @@ class Viewer(wx.Panel): | ||
| 1381 | image = self.slice_.GetSlices(self.orientation, index) | 1383 | image = self.slice_.GetSlices(self.orientation, index) |
| 1382 | self.actor.SetInput(image) | 1384 | self.actor.SetInput(image) |
| 1383 | self.__update_display_extent(image) | 1385 | self.__update_display_extent(image) |
| 1384 | - print "slice", index | ||
| 1385 | - print "display extent", self.actor.GetDisplayExtent() | ||
| 1386 | - print "whole extent", image.GetWholeExtent() | ||
| 1387 | - print "boundsa", self.actor.GetBounds() | ||
| 1388 | - print "camera", self.cam.GetPosition(), self.cam.GetFocalPoint() | 1386 | + self.interactor.Render() |
| 1389 | 1387 | ||
| 1390 | def ChangeSliceNumber(self, pubsub_evt): | 1388 | def ChangeSliceNumber(self, pubsub_evt): |
| 1391 | index = pubsub_evt.data | 1389 | index = pubsub_evt.data |
| @@ -1445,6 +1443,9 @@ class Viewer(wx.Panel): | @@ -1445,6 +1443,9 @@ class Viewer(wx.Panel): | ||
| 1445 | slice_number)) | 1443 | slice_number)) |
| 1446 | self.interactor.Render() | 1444 | self.interactor.Render() |
| 1447 | 1445 | ||
| 1446 | + def ReloadActualSlice(self, pubsub_evt): | ||
| 1447 | + self.OnScrollBar() | ||
| 1448 | + | ||
| 1448 | def AddActors(self, pubsub_evt): | 1449 | def AddActors(self, pubsub_evt): |
| 1449 | "Inserting actors" | 1450 | "Inserting actors" |
| 1450 | actors, n = pubsub_evt.data | 1451 | actors, n = pubsub_evt.data |
invesalius/gui/dialogs.py
| @@ -521,7 +521,7 @@ class NewMask(wx.Dialog): | @@ -521,7 +521,7 @@ class NewMask(wx.Dialog): | ||
| 521 | self.SetSizer(sizer) | 521 | self.SetSizer(sizer) |
| 522 | sizer.Fit(self) | 522 | sizer.Fit(self) |
| 523 | 523 | ||
| 524 | - self.Bind(grad.EVT_THRESHOLD_CHANGE, self.OnSlideChanged, self.gradient) | 524 | + self.Bind(grad.EVT_THRESHOLD_CHANGED, self.OnSlideChanged, self.gradient) |
| 525 | self.combo_thresh.Bind(wx.EVT_COMBOBOX, self.OnComboThresh) | 525 | self.combo_thresh.Bind(wx.EVT_COMBOBOX, self.OnComboThresh) |
| 526 | 526 | ||
| 527 | 527 |