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 |