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 | |
| 7 | 7 | from cython.parallel import prange |
| 8 | 8 | from libc.math cimport floor, ceil |
| 9 | +from libcpp cimport bool | |
| 9 | 10 | from libcpp.deque cimport deque as cdeque |
| 10 | 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 | 238 | @cython.wraparound(False) |
| 238 | 239 | @cython.nonecheck(False) |
| 239 | 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 | 353 | |
| 354 | 354 | imask = (~(matrix > 127)) |
| 355 | 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 | 363 | else: |
| 360 | 364 | bstruct = ndimage.generate_binary_structure(2, CON2D[conn]) |
| 361 | 365 | |
| ... | ... | @@ -371,11 +375,15 @@ class Mask(): |
| 371 | 375 | imask = (~(matrix > 127)) |
| 372 | 376 | labels, nlabels = ndimage.label(imask, bstruct, output=np.uint16) |
| 373 | 377 | |
| 378 | + if nlabels == 0: | |
| 379 | + return | |
| 380 | + | |
| 374 | 381 | labels = labels.reshape(1, labels.shape[0], labels.shape[1]) |
| 375 | 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 | 388 | def __del__(self): |
| 381 | 389 | if self.is_shown: | ... | ... |
invesalius/gui/frame.py
| ... | ... | @@ -596,7 +596,6 @@ class Frame(wx.Frame): |
| 596 | 596 | Publisher.sendMessage('Enable style', const.SLICE_STATE_MASK_FFILL) |
| 597 | 597 | |
| 598 | 598 | def OnFillHolesAutomatically(self): |
| 599 | - # Publisher.sendMessage('Fill holes automatically') | |
| 600 | 599 | fdlg = dlg.FillHolesAutoDialog(_(u"Fill holes automatically")) |
| 601 | 600 | fdlg.Show() |
| 602 | 601 | |
| ... | ... | @@ -752,20 +751,23 @@ class MenuBar(wx.MenuBar): |
| 752 | 751 | self.clean_mask_menu.Enable(False) |
| 753 | 752 | |
| 754 | 753 | mask_menu.AppendSeparator() |
| 754 | + | |
| 755 | 755 | self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill holes manually")) |
| 756 | 756 | self.fill_hole_mask_menu.Enable(False) |
| 757 | 757 | |
| 758 | 758 | self.fill_hole_auto_menu = mask_menu.Append(const.ID_FILL_HOLE_AUTO, _(u"Fill holes automatically")) |
| 759 | 759 | self.fill_hole_mask_menu.Enable(False) |
| 760 | 760 | |
| 761 | + mask_menu.AppendSeparator() | |
| 762 | + | |
| 761 | 763 | self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts")) |
| 762 | 764 | self.remove_mask_part_menu.Enable(False) |
| 763 | 765 | |
| 764 | 766 | self.select_mask_part_menu = mask_menu.Append(const.ID_SELECT_MASK_PART, _(u"Select parts")) |
| 765 | 767 | self.select_mask_part_menu.Enable(False) |
| 766 | - | |
| 768 | + | |
| 767 | 769 | mask_menu.AppendSeparator() |
| 768 | - | |
| 770 | + | |
| 769 | 771 | self.crop_mask_menu = mask_menu.Append(const.ID_CROP_MASK, _("Crop")) |
| 770 | 772 | self.crop_mask_menu.Enable(False) |
| 771 | 773 | ... | ... |