Commit 620a3f2d7666f1d2bc09d7605ebc334ccecc29aa
1 parent
6a51200e
Exists in
master
and in
68 other branches
ADD: Create 3D Surface from Edited Mask
Showing
3 changed files
with
53 additions
and
47 deletions
Show diff stats
invesalius/data/mask.py
invesalius/data/slice_.py
... | ... | @@ -20,14 +20,14 @@ class Slice(object): |
20 | 20 | self.current_mask = None |
21 | 21 | self.blend_filter = None |
22 | 22 | self.__bind_events() |
23 | - | |
23 | + | |
24 | 24 | def __bind_events(self): |
25 | 25 | # Slice properties |
26 | 26 | ps.Publisher().subscribe(self.UpdateCursorPosition, |
27 | 27 | 'Update cursor position in slice') |
28 | 28 | ps.Publisher().subscribe(self.UpdateCursorPositionSingleAxis, |
29 | 29 | 'Update cursor single position in slice') |
30 | - | |
30 | + | |
31 | 31 | # General slice control |
32 | 32 | ps.Publisher().subscribe(self.CreateSurfaceFromIndex, |
33 | 33 | 'Create surface from index') |
... | ... | @@ -35,7 +35,7 @@ class Slice(object): |
35 | 35 | ps.Publisher().subscribe(self.__add_mask, 'Create new mask') |
36 | 36 | ps.Publisher().subscribe(self.__select_current_mask, |
37 | 37 | 'Change mask selected') |
38 | - # Mask properties | |
38 | + # Mask properties | |
39 | 39 | ps.Publisher().subscribe(self.__set_current_mask_edition_threshold, |
40 | 40 | 'Set edition threshold values') |
41 | 41 | ps.Publisher().subscribe(self.__set_current_mask_threshold, |
... | ... | @@ -44,34 +44,34 @@ class Slice(object): |
44 | 44 | 'Change mask colour') |
45 | 45 | ps.Publisher().subscribe(self.__set_mask_name, 'Change mask name') |
46 | 46 | ps.Publisher().subscribe(self.__show_mask, 'Show mask') |
47 | - | |
47 | + | |
48 | 48 | # Operations related to slice editor |
49 | 49 | ps.Publisher().subscribe(self.__erase_mask_pixel, 'Erase mask pixel') |
50 | 50 | ps.Publisher().subscribe(self.__edit_mask_pixel, 'Edit mask pixel') |
51 | - ps.Publisher().subscribe(self.__add_mask_pixel, 'Add mask pixel') | |
52 | - | |
51 | + ps.Publisher().subscribe(self.__add_mask_pixel, 'Add mask pixel') | |
52 | + | |
53 | 53 | #--------------------------------------------------------------------------- |
54 | 54 | # BEGIN PUBSUB_EVT METHODS |
55 | 55 | #--------------------------------------------------------------------------- |
56 | 56 | def __get_mask_data_for_surface_creation(self, pubsub_evt): |
57 | 57 | mask_index = pubsub_evt.data |
58 | 58 | CreateSurfaceFromIndex |
59 | - | |
59 | + | |
60 | 60 | def __add_mask(self, pubsub_evt): |
61 | 61 | mask_name = pubsub_evt.data |
62 | 62 | self.CreateMask(name=mask_name) |
63 | 63 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) |
64 | - | |
64 | + | |
65 | 65 | def __select_current_mask(self, pubsub_evt): |
66 | 66 | mask_index = pubsub_evt.data |
67 | 67 | self.SelectCurrentMask(mask_index) |
68 | - #--------------------------------------------------------------------------- | |
68 | + #--------------------------------------------------------------------------- | |
69 | 69 | def __set_current_mask_edition_threshold(self, evt_pubsub): |
70 | 70 | if self.current_mask: |
71 | 71 | threshold_range = evt_pubsub.data |
72 | 72 | index = self.current_mask.index |
73 | 73 | self.SetMaskEditionThreshold(index, threshold_range) |
74 | - | |
74 | + | |
75 | 75 | def __set_current_mask_threshold(self, evt_pubsub): |
76 | 76 | threshold_range = evt_pubsub.data |
77 | 77 | index = self.current_mask.index |
... | ... | @@ -99,24 +99,24 @@ class Slice(object): |
99 | 99 | def __erase_mask_pixel(self, pubsub_evt): |
100 | 100 | position = pubsub_evt.data |
101 | 101 | self.ErasePixel(position) |
102 | - | |
102 | + | |
103 | 103 | def __edit_mask_pixel(self, pubsub_evt): |
104 | 104 | position = pubsub_evt.data |
105 | 105 | self.EditPixelBasedOnThreshold(position) |
106 | 106 | |
107 | 107 | def __add_mask_pixel(self, pubsub_evt): |
108 | 108 | position = pubsub_evt.data |
109 | - self.DrawPixel(position) | |
109 | + self.DrawPixel(position) | |
110 | 110 | #--------------------------------------------------------------------------- |
111 | 111 | # END PUBSUB_EVT METHODS |
112 | 112 | #--------------------------------------------------------------------------- |
113 | - | |
114 | - | |
113 | + | |
114 | + | |
115 | 115 | def SetMaskColour(self, index, colour, update=True): |
116 | 116 | "Set a mask colour given its index and colour (RGB 0-1 values)" |
117 | 117 | proj = Project() |
118 | 118 | proj.mask_dict[index].colour = colour |
119 | - | |
119 | + | |
120 | 120 | (r,g,b) = colour |
121 | 121 | scalar_range = int(self.imagedata.GetScalarRange()[1]) |
122 | 122 | self.lut_mask.SetTableValue(1, r, g, b, 1.0) |
... | ... | @@ -128,7 +128,7 @@ class Slice(object): |
128 | 128 | ps.Publisher().sendMessage('Set GUI items colour', colour_wx) |
129 | 129 | if update: |
130 | 130 | ps.Publisher().sendMessage('Update slice viewer') |
131 | - | |
131 | + | |
132 | 132 | def SetMaskName(self, index, name): |
133 | 133 | "Rename a mask given its index and the new name" |
134 | 134 | proj = Project() |
... | ... | @@ -145,25 +145,25 @@ class Slice(object): |
145 | 145 | threshold values. |
146 | 146 | """ |
147 | 147 | thresh_min, thresh_max = threshold_range |
148 | - | |
148 | + | |
149 | 149 | if self.current_mask.index == index: |
150 | 150 | # Update pipeline (this must be here, so pipeline is not broken) |
151 | 151 | self.img_thresh_mask.SetInput(self.imagedata) |
152 | 152 | self.img_thresh_mask.ThresholdBetween(float(thresh_min), |
153 | 153 | float(thresh_max)) |
154 | 154 | self.img_thresh_mask.Update() |
155 | - | |
155 | + | |
156 | 156 | # Create imagedata copy so the pipeline is not broken |
157 | 157 | imagedata = self.img_thresh_mask.GetOutput() |
158 | 158 | self.current_mask.imagedata.DeepCopy(imagedata) |
159 | 159 | self.current_mask.threshold_range = threshold_range |
160 | - | |
160 | + | |
161 | 161 | # Update pipeline (this must be here, so pipeline is not broken) |
162 | 162 | self.img_colours_mask.SetInput(self.current_mask.imagedata) |
163 | - | |
163 | + | |
164 | 164 | # Update viewer |
165 | 165 | ps.Publisher().sendMessage('Update slice viewer') |
166 | - | |
166 | + | |
167 | 167 | # Update data notebook (GUI) |
168 | 168 | ps.Publisher().sendMessage('Set mask threshold in notebook', |
169 | 169 | (self.current_mask.index, |
... | ... | @@ -171,7 +171,7 @@ class Slice(object): |
171 | 171 | else: |
172 | 172 | proj = Project() |
173 | 173 | proj.mask_dict[index].threshold_range = threshold_range |
174 | - | |
174 | + | |
175 | 175 | def ShowMask(self, index, value): |
176 | 176 | "Show a mask given its index and 'show' value (0: hide, other: show)" |
177 | 177 | proj = Project() |
... | ... | @@ -180,7 +180,7 @@ class Slice(object): |
180 | 180 | if value: |
181 | 181 | self.blend_filter.SetOpacity(1, self.current_mask.opacity) |
182 | 182 | else: |
183 | - self.blend_filter.SetOpacity(1, 0) | |
183 | + self.blend_filter.SetOpacity(1, 0) | |
184 | 184 | self.blend_filter.Update() |
185 | 185 | ps.Publisher().sendMessage('Update slice viewer') |
186 | 186 | #--------------------------------------------------------------------------- |
... | ... | @@ -191,22 +191,23 @@ class Slice(object): |
191 | 191 | imagedata = self.current_mask.imagedata |
192 | 192 | imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour) |
193 | 193 | imagedata.Update() |
194 | + self.current_mask.edited_points[(x, y, z)] = colour | |
194 | 195 | |
195 | 196 | def DrawPixel(self, position, colour=None): |
196 | 197 | "Draw pixel, based on x, y and z position coordinates." |
197 | 198 | x, y, z = position |
198 | - if not colour: | |
199 | - colour = self.imagedata.GetScalarRange()[1] | |
199 | + #if not colour: | |
200 | + colour = self.imagedata.GetScalarRange()[1] | |
200 | 201 | imagedata = self.current_mask.imagedata |
201 | 202 | imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour) |
202 | 203 | imagedata.Update() |
204 | + self.current_mask.edited_points[(x, y, z)] = colour | |
203 | 205 | |
204 | 206 | def EditPixelBasedOnThreshold(self, position): |
205 | 207 | "Erase or draw pixel based on edition threshold range." |
206 | 208 | x, y, z = position |
207 | 209 | colour = self.imagedata.GetScalarComponentAsDouble(x, y, z, 0) |
208 | 210 | thresh_min, thresh_max = self.current_mask.edition_threshold_range |
209 | - | |
210 | 211 | if (colour >= thresh_min) and (colour <= thresh_max): |
211 | 212 | self.DrawPixel(position, colour) |
212 | 213 | else: |
... | ... | @@ -234,8 +235,8 @@ class Slice(object): |
234 | 235 | if self.current_mask.is_shown: |
235 | 236 | self.blend_filter.SetOpacity(1, self.current_mask.opacity) |
236 | 237 | else: |
237 | - | |
238 | - self.blend_filter.SetOpacity(1, 0) | |
238 | + | |
239 | + self.blend_filter.SetOpacity(1, 0) | |
239 | 240 | self.blend_filter.Update() |
240 | 241 | |
241 | 242 | ps.Publisher().sendMessage('Set mask threshold in notebook', |
... | ... | @@ -254,31 +255,32 @@ class Slice(object): |
254 | 255 | |
255 | 256 | def CreateSurfaceFromIndex(self, pubsub_evt): |
256 | 257 | mask_index = pubsub_evt.data |
257 | - | |
258 | + | |
258 | 259 | |
259 | 260 | proj = Project() |
260 | 261 | mask = proj.mask_dict[mask_index] |
261 | - | |
262 | + | |
262 | 263 | # This is very important. Do not use masks' imagedata. It would mess up |
263 | 264 | # surface quality event when using contour |
264 | 265 | imagedata = self.imagedata |
265 | - | |
266 | + | |
266 | 267 | colour = mask.colour |
267 | 268 | threshold = mask.threshold_range |
269 | + edited_points = mask.edited_points | |
268 | 270 | |
269 | 271 | ps.Publisher().sendMessage('Create surface', |
270 | - (imagedata,colour,threshold)) | |
272 | + (imagedata,colour,threshold, edited_points)) | |
273 | + | |
271 | 274 | |
272 | 275 | |
273 | 276 | |
274 | 277 | |
275 | 278 | |
276 | - | |
277 | 279 | |
278 | 280 | |
279 | 281 | def GetOutput(self): |
280 | 282 | return self.cast_filter.GetOutput() |
281 | - | |
283 | + | |
282 | 284 | |
283 | 285 | |
284 | 286 | def SetInput(self, imagedata): |
... | ... | @@ -314,19 +316,19 @@ class Slice(object): |
314 | 316 | |
315 | 317 | cross = vtk.vtkImageCursor3D() |
316 | 318 | cross.GetOutput().ReleaseDataFlagOn() |
317 | - cross.SetInput(blend_filter.GetOutput()) | |
319 | + cross.SetInput(blend_filter.GetOutput()) | |
318 | 320 | cross.SetCursorPosition(CURSOR_X, CURSOR_Y, CURSOR_Z) |
319 | 321 | cross.SetCursorValue(CURSOR_VALUE) |
320 | - cross.SetCursorRadius(CURSOR_RADIUS) | |
322 | + cross.SetCursorRadius(CURSOR_RADIUS) | |
321 | 323 | cross.Modified() |
322 | 324 | self.cross = cross |
323 | - | |
324 | - cast = vtk.vtkImageCast() | |
325 | + | |
326 | + cast = vtk.vtkImageCast() | |
325 | 327 | cast.SetInput(cross.GetOutput()) |
326 | 328 | cast.GetOutput().SetUpdateExtentToWholeExtent() |
327 | - cast.SetOutputScalarTypeToUnsignedChar() | |
329 | + cast.SetOutputScalarTypeToUnsignedChar() | |
328 | 330 | cast.Update() |
329 | - | |
331 | + | |
330 | 332 | self.cast_filter = cast |
331 | 333 | |
332 | 334 | |
... | ... | @@ -337,7 +339,7 @@ class Slice(object): |
337 | 339 | self.cross.Modified() |
338 | 340 | self.cast_filter.Update() |
339 | 341 | ps.Publisher().sendMessage('Update slice viewer') |
340 | - | |
342 | + | |
341 | 343 | def UpdateCursorPositionSingleAxis(self, pubsub_evt): |
342 | 344 | axis_pos = pubsub_evt.data |
343 | 345 | x, y, z = self.cross.GetCursorPosition() |
... | ... | @@ -348,8 +350,8 @@ class Slice(object): |
348 | 350 | self.cross.Modified() |
349 | 351 | self.cast_filter.Update() |
350 | 352 | ps.Publisher().sendMessage('Update slice viewer') |
351 | - | |
352 | - | |
353 | + | |
354 | + | |
353 | 355 | def __create_background(self, imagedata): |
354 | 356 | |
355 | 357 | thresh_min, thresh_max = imagedata.GetScalarRange() |
... | ... | @@ -414,11 +416,11 @@ class Slice(object): |
414 | 416 | future_mask.colour)) |
415 | 417 | |
416 | 418 | self.current_mask = future_mask |
417 | - | |
419 | + | |
418 | 420 | ps.Publisher().sendMessage('Change mask selected', future_mask.index) |
419 | 421 | #ps.Publisher().sendMessage('Show mask', (future_mask.index, 1)) |
420 | 422 | ps.Publisher().sendMessage('Update slice viewer') |
421 | - | |
423 | + | |
422 | 424 | |
423 | 425 | |
424 | 426 | def __create_mask(self, imagedata): | ... | ... |
invesalius/data/surface.py
... | ... | @@ -6,6 +6,7 @@ import imagedata_utils as iu |
6 | 6 | from project import Project |
7 | 7 | import vtk_utils as vu |
8 | 8 | import polydata_utils as pu |
9 | +from imagedata_utils import BuildEditedImage | |
9 | 10 | |
10 | 11 | class Surface(): |
11 | 12 | """ |
... | ... | @@ -53,10 +54,13 @@ class SurfaceManager(): |
53 | 54 | """ |
54 | 55 | Create surface actor, save into project and send it to viewer. |
55 | 56 | """ |
56 | - imagedata, colour, [min_value, max_value] = pubsub_evt.data | |
57 | + imagedata, colour, [min_value, max_value], edited_points = pubsub_evt.data | |
57 | 58 | quality='Optimal' |
58 | 59 | mode = 'CONTOUR' # 'GRAYSCALE' |
59 | 60 | |
61 | + if (edited_points): | |
62 | + imagedata = BuildEditedImage(imagedata, edited_points) | |
63 | + | |
60 | 64 | if quality in const.SURFACE_QUALITY.keys(): |
61 | 65 | imagedata_resolution = const.SURFACE_QUALITY[quality][0] |
62 | 66 | smooth_iterations = const.SURFACE_QUALITY[quality][1] | ... | ... |