Commit ba729de68a727015d028099a1946248d6abdef66
1 parent
f0faf9e1
Exists in
ff_mask
Fill holes in mask
Showing
5 changed files
with
326 additions
and
1 deletions
Show diff stats
invesalius/constants.py
@@ -481,6 +481,7 @@ ID_BOOLEAN_MASK = wx.NewId() | @@ -481,6 +481,7 @@ ID_BOOLEAN_MASK = wx.NewId() | ||
481 | ID_CLEAN_MASK = wx.NewId() | 481 | ID_CLEAN_MASK = wx.NewId() |
482 | 482 | ||
483 | ID_REORIENT_IMG = wx.NewId() | 483 | ID_REORIENT_IMG = wx.NewId() |
484 | +ID_FLOODFILL_MASK = wx.NewId() | ||
484 | 485 | ||
485 | #--------------------------------------------------------- | 486 | #--------------------------------------------------------- |
486 | STATE_DEFAULT = 1000 | 487 | STATE_DEFAULT = 1000 |
@@ -498,6 +499,7 @@ SLICE_STATE_SCROLL = 3007 | @@ -498,6 +499,7 @@ SLICE_STATE_SCROLL = 3007 | ||
498 | SLICE_STATE_EDITOR = 3008 | 499 | SLICE_STATE_EDITOR = 3008 |
499 | SLICE_STATE_WATERSHED = 3009 | 500 | SLICE_STATE_WATERSHED = 3009 |
500 | SLICE_STATE_REORIENT = 3010 | 501 | SLICE_STATE_REORIENT = 3010 |
502 | +SLICE_STATE_MASK_FFILL = 3011 | ||
501 | 503 | ||
502 | VOLUME_STATE_SEED = 2001 | 504 | VOLUME_STATE_SEED = 2001 |
503 | # STATE_LINEAR_MEASURE = 3001 | 505 | # STATE_LINEAR_MEASURE = 3001 |
@@ -515,6 +517,7 @@ SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES | @@ -515,6 +517,7 @@ SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES | ||
515 | SLICE_STYLES.append(STATE_DEFAULT) | 517 | SLICE_STYLES.append(STATE_DEFAULT) |
516 | SLICE_STYLES.append(SLICE_STATE_EDITOR) | 518 | SLICE_STYLES.append(SLICE_STATE_EDITOR) |
517 | SLICE_STYLES.append(SLICE_STATE_WATERSHED) | 519 | SLICE_STYLES.append(SLICE_STATE_WATERSHED) |
520 | +SLICE_STYLES.append(SLICE_STATE_MASK_FFILL) | ||
518 | 521 | ||
519 | VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, | 522 | VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, |
520 | STATE_MEASURE_ANGLE] | 523 | STATE_MEASURE_ANGLE] |
@@ -523,6 +526,7 @@ VOLUME_STYLES.append(STATE_DEFAULT) | @@ -523,6 +526,7 @@ VOLUME_STYLES.append(STATE_DEFAULT) | ||
523 | 526 | ||
524 | STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, | 527 | STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, |
525 | SLICE_STATE_WATERSHED: 1, | 528 | SLICE_STATE_WATERSHED: 1, |
529 | + SLICE_STATE_MASK_FFILL: 1, | ||
526 | SLICE_STATE_CROSS: 2, | 530 | SLICE_STATE_CROSS: 2, |
527 | SLICE_STATE_SCROLL: 2, | 531 | SLICE_STATE_SCROLL: 2, |
528 | SLICE_STATE_REORIENT: 2, | 532 | SLICE_STATE_REORIENT: 2, |
@@ -0,0 +1,214 @@ | @@ -0,0 +1,214 @@ | ||
1 | +import numpy as np | ||
2 | +cimport numpy as np | ||
3 | +cimport cython | ||
4 | + | ||
5 | +from collections import deque | ||
6 | + | ||
7 | +from libc.math cimport floor, ceil | ||
8 | +from libcpp.deque cimport deque as cdeque | ||
9 | + | ||
10 | +from cy_my_types cimport image_t, mask_t | ||
11 | + | ||
12 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | ||
13 | +@cython.wraparound(False) | ||
14 | +@cython.nonecheck(False) | ||
15 | +@cython.cdivision(True) | ||
16 | +cdef inline void append_queue(cdeque[int]& stack, int x, int y, int z, int d, int h, int w) nogil: | ||
17 | + stack.push_back(z*h*w + y*w + x) | ||
18 | + | ||
19 | + | ||
20 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | ||
21 | +@cython.wraparound(False) | ||
22 | +@cython.nonecheck(False) | ||
23 | +@cython.cdivision(True) | ||
24 | +cdef inline void pop_queue(cdeque[int]& stack, int* x, int* y, int* z, int d, int h, int w) nogil: | ||
25 | + cdef int i = stack.front() | ||
26 | + stack.pop_front() | ||
27 | + x[0] = i % w | ||
28 | + y[0] = (i / w) % h | ||
29 | + z[0] = i / (h * w) | ||
30 | + | ||
31 | + | ||
32 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | ||
33 | +def floodfill(np.ndarray[image_t, ndim=3] data, int i, int j, int k, int v, int fill, np.ndarray[mask_t, ndim=3] out): | ||
34 | + | ||
35 | + cdef int to_return = 0 | ||
36 | + if out is None: | ||
37 | + out = np.zeros_like(data) | ||
38 | + to_return = 1 | ||
39 | + | ||
40 | + cdef int x, y, z | ||
41 | + cdef int w, h, d | ||
42 | + | ||
43 | + d = data.shape[0] | ||
44 | + h = data.shape[1] | ||
45 | + w = data.shape[2] | ||
46 | + | ||
47 | + stack = [(i, j, k), ] | ||
48 | + out[k, j, i] = fill | ||
49 | + | ||
50 | + while stack: | ||
51 | + x, y, z = stack.pop() | ||
52 | + | ||
53 | + if z + 1 < d and data[z + 1, y, x] == v and out[z + 1, y, x] != fill: | ||
54 | + out[z + 1, y, x] = fill | ||
55 | + stack.append((x, y, z + 1)) | ||
56 | + | ||
57 | + if z - 1 >= 0 and data[z - 1, y, x] == v and out[z - 1, y, x] != fill: | ||
58 | + out[z - 1, y, x] = fill | ||
59 | + stack.append((x, y, z - 1)) | ||
60 | + | ||
61 | + if y + 1 < h and data[z, y + 1, x] == v and out[z, y + 1, x] != fill: | ||
62 | + out[z, y + 1, x] = fill | ||
63 | + stack.append((x, y + 1, z)) | ||
64 | + | ||
65 | + if y - 1 >= 0 and data[z, y - 1, x] == v and out[z, y - 1, x] != fill: | ||
66 | + out[z, y - 1, x] = fill | ||
67 | + stack.append((x, y - 1, z)) | ||
68 | + | ||
69 | + if x + 1 < w and data[z, y, x + 1] == v and out[z, y, x + 1] != fill: | ||
70 | + out[z, y, x + 1] = fill | ||
71 | + stack.append((x + 1, y, z)) | ||
72 | + | ||
73 | + if x - 1 >= 0 and data[z, y, x - 1] == v and out[z, y, x - 1] != fill: | ||
74 | + out[z, y, x - 1] = fill | ||
75 | + stack.append((x - 1, y, z)) | ||
76 | + | ||
77 | + if to_return: | ||
78 | + return out | ||
79 | + | ||
80 | + | ||
81 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | ||
82 | +@cython.wraparound(False) | ||
83 | +@cython.nonecheck(False) | ||
84 | +def floodfill_threshold(np.ndarray[image_t, ndim=3] data, list seeds, int t0, int t1, int fill, np.ndarray[mask_t, ndim=3] out): | ||
85 | + | ||
86 | + cdef int to_return = 0 | ||
87 | + if out is None: | ||
88 | + out = np.zeros_like(data) | ||
89 | + to_return = 1 | ||
90 | + | ||
91 | + cdef int x, y, z | ||
92 | + cdef int w, h, d | ||
93 | + cdef int xo, yo, zo | ||
94 | + | ||
95 | + d = data.shape[0] | ||
96 | + h = data.shape[1] | ||
97 | + w = data.shape[2] | ||
98 | + | ||
99 | + stack = deque() | ||
100 | + | ||
101 | + for i, j, k in seeds: | ||
102 | + if data[k, j, i] >= t0 and data[k, j, i] <= t1: | ||
103 | + stack.append((i, j, k)) | ||
104 | + out[k, j, i] = fill | ||
105 | + | ||
106 | + while stack: | ||
107 | + x, y, z = stack.pop() | ||
108 | + | ||
109 | + xo = x | ||
110 | + yo = y | ||
111 | + zo = z | ||
112 | + | ||
113 | + if z + 1 < d and data[z + 1, y, x] >= t0 and data[z + 1, y, x] <= t1 and out[zo + 1, yo, xo] != fill: | ||
114 | + out[zo + 1, yo, xo] = fill | ||
115 | + stack.append((x, y, z + 1)) | ||
116 | + | ||
117 | + if z - 1 >= 0 and data[z - 1, y, x] >= t0 and data[z - 1, y, x] <= t1 and out[zo - 1, yo, xo] != fill: | ||
118 | + out[zo - 1, yo, xo] = fill | ||
119 | + stack.append((x, y, z - 1)) | ||
120 | + | ||
121 | + if y + 1 < h and data[z, y + 1, x] >= t0 and data[z, y + 1, x] <= t1 and out[zo, yo + 1, xo] != fill: | ||
122 | + out[zo, yo + 1, xo] = fill | ||
123 | + stack.append((x, y + 1, z)) | ||
124 | + | ||
125 | + if y - 1 >= 0 and data[z, y - 1, x] >= t0 and data[z, y - 1, x] <= t1 and out[zo, yo - 1, xo] != fill: | ||
126 | + out[zo, yo - 1, xo] = fill | ||
127 | + stack.append((x, y - 1, z)) | ||
128 | + | ||
129 | + if x + 1 < w and data[z, y, x + 1] >= t0 and data[z, y, x + 1] <= t1 and out[zo, yo, xo + 1] != fill: | ||
130 | + out[zo, yo, xo + 1] = fill | ||
131 | + stack.append((x + 1, y, z)) | ||
132 | + | ||
133 | + if x - 1 >= 0 and data[z, y, x - 1] >= t0 and data[z, y, x - 1] <= t1 and out[zo, yo, xo - 1] != fill: | ||
134 | + out[zo, yo, xo - 1] = fill | ||
135 | + stack.append((x - 1, y, z)) | ||
136 | + | ||
137 | + if to_return: | ||
138 | + return out | ||
139 | + | ||
140 | + | ||
141 | +@cython.boundscheck(False) # turn of bounds-checking for entire function | ||
142 | +@cython.wraparound(False) | ||
143 | +@cython.nonecheck(False) | ||
144 | +def floodfill_auto_threshold(np.ndarray[image_t, ndim=3] data, list seeds, float p, int fill, np.ndarray[mask_t, ndim=3] out): | ||
145 | + | ||
146 | + cdef int to_return = 0 | ||
147 | + if out is None: | ||
148 | + out = np.zeros_like(data) | ||
149 | + to_return = 1 | ||
150 | + | ||
151 | + cdef cdeque[int] stack | ||
152 | + cdef int x, y, z | ||
153 | + cdef int w, h, d | ||
154 | + cdef int xo, yo, zo | ||
155 | + cdef int t0, t1 | ||
156 | + | ||
157 | + cdef int i, j, k | ||
158 | + | ||
159 | + d = data.shape[0] | ||
160 | + h = data.shape[1] | ||
161 | + w = data.shape[2] | ||
162 | + | ||
163 | + | ||
164 | + # stack = deque() | ||
165 | + | ||
166 | + x = 0 | ||
167 | + y = 0 | ||
168 | + z = 0 | ||
169 | + | ||
170 | + | ||
171 | + for i, j, k in seeds: | ||
172 | + append_queue(stack, i, j, k, d, h, w) | ||
173 | + out[k, j, i] = fill | ||
174 | + print i, j, k, d, h, w | ||
175 | + | ||
176 | + with nogil: | ||
177 | + while stack.size(): | ||
178 | + pop_queue(stack, &x, &y, &z, d, h, w) | ||
179 | + | ||
180 | + # print x, y, z, d, h, w | ||
181 | + | ||
182 | + xo = x | ||
183 | + yo = y | ||
184 | + zo = z | ||
185 | + | ||
186 | + t0 = <int>ceil(data[z, y, x] * (1 - p)) | ||
187 | + t1 = <int>floor(data[z, y, x] * (1 + p)) | ||
188 | + | ||
189 | + if z + 1 < d and data[z + 1, y, x] >= t0 and data[z + 1, y, x] <= t1 and out[zo + 1, yo, xo] != fill: | ||
190 | + out[zo + 1, yo, xo] = fill | ||
191 | + append_queue(stack, x, y, z+1, d, h, w) | ||
192 | + | ||
193 | + if z - 1 >= 0 and data[z - 1, y, x] >= t0 and data[z - 1, y, x] <= t1 and out[zo - 1, yo, xo] != fill: | ||
194 | + out[zo - 1, yo, xo] = fill | ||
195 | + append_queue(stack, x, y, z-1, d, h, w) | ||
196 | + | ||
197 | + if y + 1 < h and data[z, y + 1, x] >= t0 and data[z, y + 1, x] <= t1 and out[zo, yo + 1, xo] != fill: | ||
198 | + out[zo, yo + 1, xo] = fill | ||
199 | + append_queue(stack, x, y+1, z, d, h, w) | ||
200 | + | ||
201 | + if y - 1 >= 0 and data[z, y - 1, x] >= t0 and data[z, y - 1, x] <= t1 and out[zo, yo - 1, xo] != fill: | ||
202 | + out[zo, yo - 1, xo] = fill | ||
203 | + append_queue(stack, x, y-1, z, d, h, w) | ||
204 | + | ||
205 | + if x + 1 < w and data[z, y, x + 1] >= t0 and data[z, y, x + 1] <= t1 and out[zo, yo, xo + 1] != fill: | ||
206 | + out[zo, yo, xo + 1] = fill | ||
207 | + append_queue(stack, x+1, y, z, d, h, w) | ||
208 | + | ||
209 | + if x - 1 >= 0 and data[z, y, x - 1] >= t0 and data[z, y, x - 1] <= t1 and out[zo, yo, xo - 1] != fill: | ||
210 | + out[zo, yo, xo - 1] = fill | ||
211 | + append_queue(stack, x-1, y, z, d, h, w) | ||
212 | + | ||
213 | + if to_return: | ||
214 | + return out |
invesalius/data/styles.py
@@ -42,6 +42,8 @@ from skimage import filter | @@ -42,6 +42,8 @@ from skimage import filter | ||
42 | 42 | ||
43 | from .measures import MeasureData | 43 | from .measures import MeasureData |
44 | 44 | ||
45 | +from . import floodfill | ||
46 | + | ||
45 | import watershed_process | 47 | import watershed_process |
46 | 48 | ||
47 | import utils | 49 | import utils |
@@ -1747,6 +1749,92 @@ class ReorientImageInteractorStyle(DefaultInteractorStyle): | @@ -1747,6 +1749,92 @@ class ReorientImageInteractorStyle(DefaultInteractorStyle): | ||
1747 | buffer_.discard_vtk_image() | 1749 | buffer_.discard_vtk_image() |
1748 | buffer_.discard_image() | 1750 | buffer_.discard_image() |
1749 | 1751 | ||
1752 | + | ||
1753 | +class FlooFillMaskInteractorStyle(DefaultInteractorStyle): | ||
1754 | + def __init__(self, viewer): | ||
1755 | + DefaultInteractorStyle.__init__(self, viewer) | ||
1756 | + | ||
1757 | + self.viewer = viewer | ||
1758 | + self.orientation = self.viewer.orientation | ||
1759 | + | ||
1760 | + self.picker = vtk.vtkWorldPointPicker() | ||
1761 | + self.slice_actor = viewer.slice_data.actor | ||
1762 | + self.slice_data = viewer.slice_data | ||
1763 | + | ||
1764 | + self.viewer.slice_.do_threshold_to_all_slices() | ||
1765 | + | ||
1766 | + self.AddObserver("LeftButtonPressEvent", self.OnFFClick) | ||
1767 | + | ||
1768 | + def OnFFClick(self, obj, evt): | ||
1769 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
1770 | + return | ||
1771 | + | ||
1772 | + | ||
1773 | + viewer = self.viewer | ||
1774 | + iren = viewer.interactor | ||
1775 | + | ||
1776 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
1777 | + render = iren.FindPokedRenderer(mouse_x, mouse_y) | ||
1778 | + slice_data = viewer.get_slice_data(render) | ||
1779 | + | ||
1780 | + self.picker.Pick(mouse_x, mouse_y, 0, render) | ||
1781 | + | ||
1782 | + coord = self.get_coordinate_cursor() | ||
1783 | + position = slice_data.actor.GetInput().FindPoint(coord) | ||
1784 | + | ||
1785 | + if position != -1: | ||
1786 | + coord = slice_data.actor.GetInput().GetPoint(position) | ||
1787 | + | ||
1788 | + if position < 0: | ||
1789 | + position = viewer.calculate_matrix_position(coord) | ||
1790 | + | ||
1791 | + x, y, z = self.calcultate_scroll_position(position) | ||
1792 | + mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] | ||
1793 | + | ||
1794 | + cp_mask = mask.copy() | ||
1795 | + | ||
1796 | + floodfill.floodfill_threshold(cp_mask, [[x, y, z]], 0, 1, 254, mask) | ||
1797 | + | ||
1798 | + viewer.OnScrollBar() | ||
1799 | + | ||
1800 | + def get_coordinate_cursor(self): | ||
1801 | + # Find position | ||
1802 | + x, y, z = self.picker.GetPickPosition() | ||
1803 | + bounds = self.viewer.slice_data.actor.GetBounds() | ||
1804 | + if bounds[0] == bounds[1]: | ||
1805 | + x = bounds[0] | ||
1806 | + elif bounds[2] == bounds[3]: | ||
1807 | + y = bounds[2] | ||
1808 | + elif bounds[4] == bounds[5]: | ||
1809 | + z = bounds[4] | ||
1810 | + return x, y, z | ||
1811 | + | ||
1812 | + def calcultate_scroll_position(self, position): | ||
1813 | + # Based in the given coord (x, y, z), returns a list with the scroll positions for each | ||
1814 | + # orientation, being the first position the sagital, second the coronal | ||
1815 | + # and the last, axial. | ||
1816 | + | ||
1817 | + if self.orientation == 'AXIAL': | ||
1818 | + image_width = self.slice_actor.GetInput().GetDimensions()[0] | ||
1819 | + axial = self.slice_data.number | ||
1820 | + coronal = position / image_width | ||
1821 | + sagital = position % image_width | ||
1822 | + | ||
1823 | + elif self.orientation == 'CORONAL': | ||
1824 | + image_width = self.slice_actor.GetInput().GetDimensions()[0] | ||
1825 | + axial = position / image_width | ||
1826 | + coronal = self.slice_data.number | ||
1827 | + sagital = position % image_width | ||
1828 | + | ||
1829 | + elif self.orientation == 'SAGITAL': | ||
1830 | + image_width = self.slice_actor.GetInput().GetDimensions()[1] | ||
1831 | + axial = position / image_width | ||
1832 | + coronal = position % image_width | ||
1833 | + sagital = self.slice_data.number | ||
1834 | + | ||
1835 | + return sagital, coronal, axial | ||
1836 | + | ||
1837 | + | ||
1750 | def get_style(style): | 1838 | def get_style(style): |
1751 | STYLES = { | 1839 | STYLES = { |
1752 | const.STATE_DEFAULT: DefaultInteractorStyle, | 1840 | const.STATE_DEFAULT: DefaultInteractorStyle, |
@@ -1762,5 +1850,8 @@ def get_style(style): | @@ -1762,5 +1850,8 @@ def get_style(style): | ||
1762 | const.SLICE_STATE_EDITOR: EditorInteractorStyle, | 1850 | const.SLICE_STATE_EDITOR: EditorInteractorStyle, |
1763 | const.SLICE_STATE_WATERSHED: WaterShedInteractorStyle, | 1851 | const.SLICE_STATE_WATERSHED: WaterShedInteractorStyle, |
1764 | const.SLICE_STATE_REORIENT: ReorientImageInteractorStyle, | 1852 | const.SLICE_STATE_REORIENT: ReorientImageInteractorStyle, |
1853 | + const.SLICE_STATE_MASK_FFILL: FlooFillMaskInteractorStyle, | ||
1765 | } | 1854 | } |
1766 | return STYLES[style] | 1855 | return STYLES[style] |
1856 | + | ||
1857 | + |
invesalius/gui/frame.py
@@ -439,6 +439,9 @@ class Frame(wx.Frame): | @@ -439,6 +439,9 @@ class Frame(wx.Frame): | ||
439 | elif id == const.ID_REORIENT_IMG: | 439 | elif id == const.ID_REORIENT_IMG: |
440 | self.OnReorientImg() | 440 | self.OnReorientImg() |
441 | 441 | ||
442 | + elif id == const.ID_FLOODFILL_MASK: | ||
443 | + self.OnFillHolesManually() | ||
444 | + | ||
442 | def OnSize(self, evt): | 445 | def OnSize(self, evt): |
443 | """ | 446 | """ |
444 | Refresh GUI when frame is resized. | 447 | Refresh GUI when frame is resized. |
@@ -559,6 +562,9 @@ class Frame(wx.Frame): | @@ -559,6 +562,9 @@ class Frame(wx.Frame): | ||
559 | rdlg = dlg.ReorientImageDialog() | 562 | rdlg = dlg.ReorientImageDialog() |
560 | rdlg.Show() | 563 | rdlg.Show() |
561 | 564 | ||
565 | + def OnFillHolesManually(self): | ||
566 | + Publisher.sendMessage('Enable style', const.SLICE_STATE_MASK_FFILL) | ||
567 | + | ||
562 | # ------------------------------------------------------------------ | 568 | # ------------------------------------------------------------------ |
563 | # ------------------------------------------------------------------ | 569 | # ------------------------------------------------------------------ |
564 | # ------------------------------------------------------------------ | 570 | # ------------------------------------------------------------------ |
@@ -578,7 +584,8 @@ class MenuBar(wx.MenuBar): | @@ -578,7 +584,8 @@ class MenuBar(wx.MenuBar): | ||
578 | self.enable_items = [const.ID_PROJECT_SAVE, | 584 | self.enable_items = [const.ID_PROJECT_SAVE, |
579 | const.ID_PROJECT_SAVE_AS, | 585 | const.ID_PROJECT_SAVE_AS, |
580 | const.ID_PROJECT_CLOSE, | 586 | const.ID_PROJECT_CLOSE, |
581 | - const.ID_REORIENT_IMG] | 587 | + const.ID_REORIENT_IMG, |
588 | + const.ID_FLOODFILL_MASK] | ||
582 | self.__init_items() | 589 | self.__init_items() |
583 | self.__bind_events() | 590 | self.__bind_events() |
584 | 591 | ||
@@ -689,6 +696,9 @@ class MenuBar(wx.MenuBar): | @@ -689,6 +696,9 @@ class MenuBar(wx.MenuBar): | ||
689 | self.clean_mask_menu = mask_menu.Append(const.ID_CLEAN_MASK, _(u"Clean Mask\tCtrl+Shift+A")) | 696 | self.clean_mask_menu = mask_menu.Append(const.ID_CLEAN_MASK, _(u"Clean Mask\tCtrl+Shift+A")) |
690 | self.clean_mask_menu.Enable(False) | 697 | self.clean_mask_menu.Enable(False) |
691 | 698 | ||
699 | + self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill mask holes manually")) | ||
700 | + self.fill_hole_mask_menu.Enable(False) | ||
701 | + | ||
692 | tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) | 702 | tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) |
693 | 703 | ||
694 | # Image menu | 704 | # Image menu |
setup.py
@@ -25,6 +25,12 @@ if sys.platform == 'linux2': | @@ -25,6 +25,12 @@ if sys.platform == 'linux2': | ||
25 | include_dirs=[numpy.get_include()], | 25 | include_dirs=[numpy.get_include()], |
26 | extra_compile_args=['-fopenmp',], | 26 | extra_compile_args=['-fopenmp',], |
27 | extra_link_args=['-fopenmp',]), | 27 | extra_link_args=['-fopenmp',]), |
28 | + | ||
29 | + Extension("invesalius.data.floodfill", ["invesalius/data/floodfill.pyx"], | ||
30 | + include_dirs=[numpy.get_include()], | ||
31 | + extra_compile_args=['-fopenmp',], | ||
32 | + extra_link_args=['-fopenmp',], | ||
33 | + language='c++',), | ||
28 | ]) | 34 | ]) |
29 | ) | 35 | ) |
30 | 36 |