Commit 8594ab8dc7129fe1411173e4568b48779e5f2bdd
1 parent
932566e7
Exists in
fill_holes_auto
Returning true when a hole were filled
Showing
3 changed files
with
49 additions
and
23 deletions
Show diff stats
invesalius/data/floodfill.pyx
| @@ -6,6 +6,7 @@ from collections import deque | @@ -6,6 +6,7 @@ from collections import deque | ||
| 6 | 6 | ||
| 7 | from cython.parallel import prange | 7 | from cython.parallel import prange |
| 8 | from libc.math cimport floor, ceil | 8 | from libc.math cimport floor, ceil |
| 9 | +from libcpp cimport bool | ||
| 9 | from libcpp.deque cimport deque as cdeque | 10 | from libcpp.deque cimport deque as cdeque |
| 10 | from libcpp.vector cimport vector | 11 | from libcpp.vector cimport vector |
| 11 | 12 | ||
| @@ -237,22 +238,37 @@ def floodfill_auto_threshold(np.ndarray[image_t, ndim=3] data, list seeds, float | @@ -237,22 +238,37 @@ def floodfill_auto_threshold(np.ndarray[image_t, ndim=3] data, list seeds, float | ||
| 237 | @cython.wraparound(False) | 238 | @cython.wraparound(False) |
| 238 | @cython.nonecheck(False) | 239 | @cython.nonecheck(False) |
| 239 | def fill_holes_automatically(np.ndarray[mask_t, ndim=3] mask, np.ndarray[np.uint16_t, ndim=3] labels, unsigned int nlabels, unsigned int max_size): | 240 | def fill_holes_automatically(np.ndarray[mask_t, ndim=3] mask, np.ndarray[np.uint16_t, ndim=3] labels, unsigned int nlabels, unsigned int max_size): |
| 240 | - cdef np.ndarray[np.uint32_t, ndim=1] sizes = np.zeros(shape=(nlabels + 1), dtype=np.uint32) | ||
| 241 | - cdef int x, y, z | ||
| 242 | - cdef int dx, dy, dz | 241 | + """ |
| 242 | + Fill mask holes automatically. The hole must <= max_size. Return True if any hole were filled. | ||
| 243 | + """ | ||
| 244 | + cdef np.ndarray[np.uint32_t, ndim=1] sizes = np.zeros(shape=(nlabels + 1), dtype=np.uint32) | ||
| 245 | + cdef int x, y, z | ||
| 246 | + cdef int dx, dy, dz | ||
| 247 | + cdef int i | ||
| 248 | + | ||
| 249 | + cdef bool modified = False | ||
| 250 | + | ||
| 251 | + dz = mask.shape[0] | ||
| 252 | + dy = mask.shape[1] | ||
| 253 | + dx = mask.shape[2] | ||
| 254 | + | ||
| 255 | + for z in xrange(dz): | ||
| 256 | + for y in xrange(dy): | ||
| 257 | + for x in xrange(dx): | ||
| 258 | + sizes[labels[z, y, x]] += 1 | ||
| 243 | 259 | ||
| 244 | - dz = mask.shape[0] | ||
| 245 | - dy = mask.shape[1] | ||
| 246 | - dx = mask.shape[2] | 260 | + #Checking if any hole will be filled |
| 261 | + for i in xrange(nlabels + 1): | ||
| 262 | + if sizes[i] <= max_size: | ||
| 263 | + modified = True | ||
| 247 | 264 | ||
| 248 | - for z in xrange(dz): | ||
| 249 | - for y in xrange(dy): | ||
| 250 | - for x in xrange(dx): | ||
| 251 | - sizes[labels[z, y, x]] += 1 | 265 | + if not modified: |
| 266 | + return 0 | ||
| 252 | 267 | ||
| 268 | + for z in prange(dz, nogil=True): | ||
| 269 | + for y in xrange(dy): | ||
| 270 | + for x in xrange(dx): | ||
| 271 | + if sizes[labels[z, y, x]] <= max_size: | ||
| 272 | + mask[z, y, x] = 254 | ||
| 253 | 273 | ||
| 254 | - for z in prange(dz, nogil=True): | ||
| 255 | - for y in xrange(dy): | ||
| 256 | - for x in xrange(dx): | ||
| 257 | - if sizes[labels[z, y, x]] <= max_size: | ||
| 258 | - mask[z, y, x] = 254 | 274 | + return modified |
invesalius/data/mask.py
| @@ -353,9 +353,13 @@ class Mask(): | @@ -353,9 +353,13 @@ class Mask(): | ||
| 353 | 353 | ||
| 354 | imask = (~(matrix > 127)) | 354 | imask = (~(matrix > 127)) |
| 355 | labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16) | 355 | labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16) |
| 356 | - print ">>>>", nlabels | ||
| 357 | - floodfill.fill_holes_automatically(matrix, labels, nlabels, size) | ||
| 358 | - self.save_history(index, orientation, self.matrix.copy(), cp_mask) | 356 | + |
| 357 | + if nlabels == 0: | ||
| 358 | + return | ||
| 359 | + | ||
| 360 | + ret = floodfill.fill_holes_automatically(matrix, labels, nlabels, size) | ||
| 361 | + if ret: | ||
| 362 | + self.save_history(index, orientation, self.matrix.copy(), cp_mask) | ||
| 359 | else: | 363 | else: |
| 360 | bstruct = ndimage.generate_binary_structure(2, CON2D[conn]) | 364 | bstruct = ndimage.generate_binary_structure(2, CON2D[conn]) |
| 361 | 365 | ||
| @@ -371,11 +375,15 @@ class Mask(): | @@ -371,11 +375,15 @@ class Mask(): | ||
| 371 | imask = (~(matrix > 127)) | 375 | imask = (~(matrix > 127)) |
| 372 | labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16) | 376 | labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16) |
| 373 | 377 | ||
| 378 | + if nlabels == 0: | ||
| 379 | + return | ||
| 380 | + | ||
| 374 | labels = labels.reshape(1, labels.shape[0], labels.shape[1]) | 381 | labels = labels.reshape(1, labels.shape[0], labels.shape[1]) |
| 375 | matrix = matrix.reshape(1, matrix.shape[0], matrix.shape[1]) | 382 | matrix = matrix.reshape(1, matrix.shape[0], matrix.shape[1]) |
| 376 | 383 | ||
| 377 | - floodfill.fill_holes_automatically(matrix, labels, nlabels, size) | ||
| 378 | - self.save_history(index, orientation, matrix.copy(), cp_mask) | 384 | + ret = floodfill.fill_holes_automatically(matrix, labels, nlabels, size) |
| 385 | + if ret: | ||
| 386 | + self.save_history(index, orientation, matrix.copy(), cp_mask) | ||
| 379 | 387 | ||
| 380 | def __del__(self): | 388 | def __del__(self): |
| 381 | if self.is_shown: | 389 | if self.is_shown: |
invesalius/gui/frame.py
| @@ -596,7 +596,6 @@ class Frame(wx.Frame): | @@ -596,7 +596,6 @@ class Frame(wx.Frame): | ||
| 596 | Publisher.sendMessage('Enable style', const.SLICE_STATE_MASK_FFILL) | 596 | Publisher.sendMessage('Enable style', const.SLICE_STATE_MASK_FFILL) |
| 597 | 597 | ||
| 598 | def OnFillHolesAutomatically(self): | 598 | def OnFillHolesAutomatically(self): |
| 599 | - # Publisher.sendMessage('Fill holes automatically') | ||
| 600 | fdlg = dlg.FillHolesAutoDialog(_(u"Fill holes automatically")) | 599 | fdlg = dlg.FillHolesAutoDialog(_(u"Fill holes automatically")) |
| 601 | fdlg.Show() | 600 | fdlg.Show() |
| 602 | 601 | ||
| @@ -752,20 +751,23 @@ class MenuBar(wx.MenuBar): | @@ -752,20 +751,23 @@ class MenuBar(wx.MenuBar): | ||
| 752 | self.clean_mask_menu.Enable(False) | 751 | self.clean_mask_menu.Enable(False) |
| 753 | 752 | ||
| 754 | mask_menu.AppendSeparator() | 753 | mask_menu.AppendSeparator() |
| 754 | + | ||
| 755 | self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill holes manually")) | 755 | self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill holes manually")) |
| 756 | self.fill_hole_mask_menu.Enable(False) | 756 | self.fill_hole_mask_menu.Enable(False) |
| 757 | 757 | ||
| 758 | self.fill_hole_auto_menu = mask_menu.Append(const.ID_FILL_HOLE_AUTO, _(u"Fill holes automatically")) | 758 | self.fill_hole_auto_menu = mask_menu.Append(const.ID_FILL_HOLE_AUTO, _(u"Fill holes automatically")) |
| 759 | self.fill_hole_mask_menu.Enable(False) | 759 | self.fill_hole_mask_menu.Enable(False) |
| 760 | 760 | ||
| 761 | + mask_menu.AppendSeparator() | ||
| 762 | + | ||
| 761 | self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts")) | 763 | self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts")) |
| 762 | self.remove_mask_part_menu.Enable(False) | 764 | self.remove_mask_part_menu.Enable(False) |
| 763 | 765 | ||
| 764 | self.select_mask_part_menu = mask_menu.Append(const.ID_SELECT_MASK_PART, _(u"Select parts")) | 766 | self.select_mask_part_menu = mask_menu.Append(const.ID_SELECT_MASK_PART, _(u"Select parts")) |
| 765 | self.select_mask_part_menu.Enable(False) | 767 | self.select_mask_part_menu.Enable(False) |
| 766 | - | 768 | + |
| 767 | mask_menu.AppendSeparator() | 769 | mask_menu.AppendSeparator() |
| 768 | - | 770 | + |
| 769 | self.crop_mask_menu = mask_menu.Append(const.ID_CROP_MASK, _("Crop")) | 771 | self.crop_mask_menu = mask_menu.Append(const.ID_CROP_MASK, _("Crop")) |
| 770 | self.crop_mask_menu.Enable(False) | 772 | self.crop_mask_menu.Enable(False) |
| 771 | 773 |