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 | ... | ... |