Commit a2be4d98b0fd159de5f1a9726ab7e64d35bee9e3
1 parent
f532e845
Exists in
master
and in
6 other branches
ADD: New surface dialog (fix #127)
Showing
6 changed files
with
163 additions
and
68 deletions
Show diff stats
invesalius/constants.py
... | ... | @@ -202,6 +202,7 @@ SURFACE_QUALITY = { |
202 | 202 | _("High"): (0, 1, 0.3000, 0.1), |
203 | 203 | _("Optimal *"): (0, 2, 0.3000, 0.4)} |
204 | 204 | DEFAULT_SURFACE_QUALITY = _("Optimal *") |
205 | +SURFACE_QUALITY_LIST = [_("Low"),_("Medium"),_("High"),_("Optimal *")] | |
205 | 206 | |
206 | 207 | |
207 | 208 | # Surface properties | ... | ... |
invesalius/data/surface.py
... | ... | @@ -39,7 +39,7 @@ class Surface(): |
39 | 39 | Represent both vtkPolyData and associated properties. |
40 | 40 | """ |
41 | 41 | general_index = -1 |
42 | - def __init__(self, index=None): | |
42 | + def __init__(self, index=None, name=""): | |
43 | 43 | Surface.general_index += 1 |
44 | 44 | if index is None: |
45 | 45 | self.index = Surface.general_index |
... | ... | @@ -51,7 +51,10 @@ class Surface(): |
51 | 51 | self.transparency = const.SURFACE_TRANSPARENCY |
52 | 52 | self.volume = 0 |
53 | 53 | self.is_shown = 1 |
54 | - self.name = const.SURFACE_NAME_PATTERN %(self.index+1) | |
54 | + if not name: | |
55 | + self.name = const.SURFACE_NAME_PATTERN %(self.index+1) | |
56 | + else: | |
57 | + self.name = name | |
55 | 58 | |
56 | 59 | def SavePlist(self, filename): |
57 | 60 | surface = {} |
... | ... | @@ -186,17 +189,31 @@ class SurfaceManager(): |
186 | 189 | (surface.index, surface.name, |
187 | 190 | surface.colour, surface.volume, |
188 | 191 | surface.transparency)) |
192 | + | |
193 | + #### | |
194 | + #(mask_index, surface_name, quality, fill_holes, keep_largest) | |
195 | + | |
189 | 196 | def AddNewActor(self, pubsub_evt): |
190 | 197 | """ |
191 | 198 | Create surface actor, save into project and send it to viewer. |
192 | 199 | """ |
193 | - imagedata, colour, [min_value, max_value], \ | |
194 | - edited_points, overwrite = pubsub_evt.data | |
195 | - | |
200 | + surface_data = pubsub_evt.data | |
201 | + | |
202 | + if len(surface_data) == 5: | |
203 | + imagedata, colour, [min_value, max_value], \ | |
204 | + edited_points, overwrite = pubsub_evt.data | |
205 | + quality=_('Optimal *') | |
206 | + surface_name = "" | |
207 | + fill_holes = True | |
208 | + keep_largest = False | |
209 | + else: | |
210 | + imagedata, colour, [min_value, max_value],\ | |
211 | + edited_points, overwrite, surface_name,\ | |
212 | + quality, fill_holes, keep_largest =\ | |
213 | + pubsub_evt.data | |
196 | 214 | |
197 | - print "---------------- OVERWRITE:",overwrite | |
198 | - quality=_('Optimal *') | |
199 | 215 | mode = 'CONTOUR' # 'GRAYSCALE' |
216 | + | |
200 | 217 | ps.Publisher().sendMessage('Begin busy cursor') |
201 | 218 | imagedata_tmp = None |
202 | 219 | if (edited_points): |
... | ... | @@ -214,11 +231,15 @@ class SurfaceManager(): |
214 | 231 | if imagedata_resolution: |
215 | 232 | imagedata = iu.ResampleImage3D(imagedata, imagedata_resolution) |
216 | 233 | |
217 | - pipeline_size = 4 | |
234 | + pipeline_size = 3 | |
218 | 235 | if decimate_reduction: |
219 | 236 | pipeline_size += 1 |
220 | 237 | if (smooth_iterations and smooth_relaxation_factor): |
221 | 238 | pipeline_size += 1 |
239 | + if fill_holes: | |
240 | + pipeline_size += 1 | |
241 | + if keep_largest: | |
242 | + pipeline_size += 1 | |
222 | 243 | |
223 | 244 | # Update progress value in GUI |
224 | 245 | UpdateProgress = vu.ShowProgress(pipeline_size) |
... | ... | @@ -236,7 +257,7 @@ class SurfaceManager(): |
236 | 257 | pipe_in, pipe_out = multiprocessing.Pipe() |
237 | 258 | sp = surface_process.SurfaceProcess(pipe_in, filename_img, mode, min_value, max_value, |
238 | 259 | decimate_reduction, smooth_relaxation_factor, |
239 | - smooth_iterations, language) | |
260 | + smooth_iterations, language, fill_holes, keep_largest) | |
240 | 261 | sp.start() |
241 | 262 | |
242 | 263 | while 1: |
... | ... | @@ -282,28 +303,27 @@ class SurfaceManager(): |
282 | 303 | if overwrite: |
283 | 304 | surface = Surface(index = self.last_surface_index) |
284 | 305 | else: |
285 | - surface = Surface() | |
306 | + surface = Surface(name=surface_name) | |
286 | 307 | surface.colour = colour |
287 | 308 | surface.polydata = polydata |
288 | 309 | |
289 | - | |
290 | 310 | # Set actor colour and transparency |
291 | 311 | actor.GetProperty().SetColor(colour) |
292 | 312 | actor.GetProperty().SetOpacity(1-surface.transparency) |
293 | 313 | |
294 | 314 | # Remove temporary files |
295 | - if sys.platform == "win32": | |
296 | - try: | |
297 | - os.remove(filename_img) | |
298 | - os.remove(filename_polydata) | |
299 | - except (WindowsError): | |
300 | - print "Error while removing surface temporary file" | |
301 | - else: # sys.platform == "linux2" or sys.platform == "darwin" | |
302 | - try: | |
303 | - os.remove(filename_img) | |
304 | - os.remove(filename_polydata) | |
305 | - except (OSError): | |
306 | - print "Error while removing surface temporary file" | |
315 | + #if sys.platform == "win32": | |
316 | + # try: | |
317 | + # os.remove(filename_img) | |
318 | + # os.remove(filename_polydata) | |
319 | + # except (WindowsError): | |
320 | + # print "Error while removing surface temporary file" | |
321 | + #else: # sys.platform == "linux2" or sys.platform == "darwin" | |
322 | + # try: | |
323 | + # os.remove(filename_img) | |
324 | + # os.remove(filename_polydata) | |
325 | + # except (OSError): | |
326 | + # print "Error while removing surface temporary file" | |
307 | 327 | |
308 | 328 | # Append surface into Project.surface_dict |
309 | 329 | proj = prj.Project() | ... | ... |
invesalius/data/surface_process.py
... | ... | @@ -8,7 +8,7 @@ class SurfaceProcess(multiprocessing.Process): |
8 | 8 | |
9 | 9 | def __init__(self, pipe, filename, mode, min_value, max_value, |
10 | 10 | decimate_reduction, smooth_relaxation_factor, |
11 | - smooth_iterations, language): | |
11 | + smooth_iterations, language, fill_holes, keep_largest): | |
12 | 12 | |
13 | 13 | multiprocessing.Process.__init__(self) |
14 | 14 | self.pipe = pipe |
... | ... | @@ -20,6 +20,9 @@ class SurfaceProcess(multiprocessing.Process): |
20 | 20 | self.smooth_relaxation_factor = smooth_relaxation_factor |
21 | 21 | self.smooth_iterations = smooth_iterations |
22 | 22 | self.language = language |
23 | + self.fill_holes = fill_holes | |
24 | + self.keep_largest = keep_largest | |
25 | + | |
23 | 26 | |
24 | 27 | def run(self): |
25 | 28 | self.CreateSurface() |
... | ... | @@ -86,15 +89,26 @@ class SurfaceProcess(multiprocessing.Process): |
86 | 89 | self.SendProgress(obj, _("Generating 3D surface..."))) |
87 | 90 | polydata = smoother.GetOutput() |
88 | 91 | |
92 | + | |
93 | + if self.keep_largest: | |
94 | + conn = vtk.vtkPolyDataConnectivityFilter() | |
95 | + conn.SetInput(polydata) | |
96 | + conn.SetExtractionModeToLargestRegion() | |
97 | + conn.AddObserver("ProgressEvent", lambda obj,evt: | |
98 | + self.SendProgress(obj, _("Generating 3D surface..."))) | |
99 | + polydata = conn.GetOutput() | |
100 | + | |
89 | 101 | # Filter used to detect and fill holes. Only fill boundary edges holes. |
90 | 102 | #TODO: Hey! This piece of code is the same from |
91 | 103 | # polydata_utils.FillSurfaceHole, we need to review this. |
92 | - filled_polydata = vtk.vtkFillHolesFilter() | |
93 | - filled_polydata.SetInput(polydata) | |
94 | - filled_polydata.SetHoleSize(500) | |
95 | - filled_polydata.AddObserver("ProgressEvent", lambda obj,evt: | |
96 | - self.SendProgress(obj, _("Generating 3D surface..."))) | |
97 | - polydata = filled_polydata.GetOutput() | |
104 | + if self.fill_holes: | |
105 | + filled_polydata = vtk.vtkFillHolesFilter() | |
106 | + filled_polydata.SetInput(polydata) | |
107 | + filled_polydata.SetHoleSize(300) | |
108 | + filled_polydata.AddObserver("ProgressEvent", lambda obj,evt: | |
109 | + self.SendProgress(obj, _("Generating 3D surface..."))) | |
110 | + polydata = filled_polydata.GetOutput() | |
111 | + | |
98 | 112 | |
99 | 113 | |
100 | 114 | filename = tempfile.mktemp() |
... | ... | @@ -104,4 +118,4 @@ class SurfaceProcess(multiprocessing.Process): |
104 | 118 | writer.Write() |
105 | 119 | |
106 | 120 | self.pipe.send(None) |
107 | - self.pipe.send(filename) | |
108 | 121 | \ No newline at end of file |
122 | + self.pipe.send(filename) | ... | ... |
invesalius/gui/dialogs.py
... | ... | @@ -393,13 +393,13 @@ def ShowSavePresetDialog(default_filename="raycasting"): |
393 | 393 | |
394 | 394 | return filename |
395 | 395 | |
396 | -MASK_LIST = [] | |
397 | 396 | class NewSurfaceDialog(wx.Dialog): |
398 | 397 | def __init__(self, parent, ID, title, size=wx.DefaultSize, |
399 | 398 | pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE, |
400 | 399 | useMetal=False): |
401 | - import data.surface as surface | |
402 | 400 | import constants as const |
401 | + import data.surface as surface | |
402 | + import project as prj | |
403 | 403 | |
404 | 404 | # Instead of calling wx.Dialog.__init__ we precreate the dialog |
405 | 405 | # so we can set an extra style that must be set before |
... | ... | @@ -420,57 +420,98 @@ class NewSurfaceDialog(wx.Dialog): |
420 | 420 | |
421 | 421 | self.CenterOnScreen() |
422 | 422 | |
423 | - # Now continue with the normal construction of the dialog | |
424 | - # contents | |
423 | + # LINE 1: Surface name | |
424 | + | |
425 | + label_surface = wx.StaticText(self, -1, _("New surface name:")) | |
426 | + | |
427 | + default_name = const.SURFACE_NAME_PATTERN %(surface.Surface.general_index+2) | |
428 | + text = wx.TextCtrl(self, -1, "", size=(80,-1)) | |
429 | + text.SetHelpText(_("Name the surface to be created")) | |
430 | + text.SetValue(default_name) | |
431 | + self.text = text | |
425 | 432 | |
426 | - # Label related to mask name | |
427 | - label_mask = wx.StaticText(self, -1, _("Select mask to be used for creating 3D surface:")) | |
433 | + # LINE 2: Mask of reference | |
428 | 434 | |
429 | - # Combo related to mask name | |
430 | - combo_surface_name = wx.ComboBox(self, -1, "", choices= MASK_LIST, | |
435 | + # Informative label | |
436 | + label_mask = wx.StaticText(self, -1, _("Mask of reference:")) | |
437 | + | |
438 | + # Retrieve existing masks | |
439 | + project = prj.Project() | |
440 | + index_list = project.mask_dict.keys() | |
441 | + index_list.sort() | |
442 | + self.mask_list = [project.mask_dict[index].name for index in index_list] | |
443 | + | |
444 | + | |
445 | + # Mask selection combo | |
446 | + combo_mask = wx.ComboBox(self, -1, "", choices= self.mask_list, | |
431 | 447 | style=wx.CB_DROPDOWN|wx.CB_READONLY) |
432 | - combo_surface_name.SetSelection(0) | |
448 | + combo_mask.SetSelection(len(self.mask_list)-1) | |
433 | 449 | if sys.platform != 'win32': |
434 | - combo_surface_name.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
435 | - self.combo_surface_name = combo_surface_name | |
450 | + combo_mask.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
451 | + self.combo_mask = combo_mask | |
436 | 452 | |
453 | + # LINE 3: Surface quality | |
454 | + label_quality = wx.StaticText(self, -1, _("Surface quality:")) | |
437 | 455 | |
438 | - label_surface = wx.StaticText(self, -1, _("Set new surface name:")) | |
456 | + combo_quality = wx.ComboBox(self, -1, "", choices= const.SURFACE_QUALITY_LIST, | |
457 | + style=wx.CB_DROPDOWN|wx.CB_READONLY) | |
458 | + combo_quality.SetSelection(3) | |
459 | + if sys.platform != 'win32': | |
460 | + combo_quality.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
461 | + self.combo_quality = combo_quality | |
439 | 462 | |
440 | - text = wx.TextCtrl(self, -1, "", size=(80,-1)) | |
441 | - text.SetHelpText(_("Name of the new surface to be created")) | |
442 | 463 | |
443 | - default_name = const.SURFACE_NAME_PATTERN %(surface.Surface.general_index+2) | |
444 | - text.SetValue(default_name) | |
445 | - self.text = text | |
464 | + # OVERVIEW | |
465 | + # Sizer that joins content above | |
466 | + flag_link = wx.EXPAND|wx.GROW|wx.ALL | |
467 | + flag_button = wx.ALL | wx.EXPAND| wx.GROW | |
446 | 468 | |
447 | - sizer = wx.BoxSizer(wx.VERTICAL) | |
448 | - sizer.Add(label_mask, 0, wx.ALL|wx.GROW|wx.EXPAND, 5) | |
449 | - sizer.Add(combo_surface_name, 1, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 10) | |
450 | - sizer.Add(label_surface, 0, wx.ALL|wx.GROW|wx.EXPAND, 5) | |
451 | - sizer.Add(text, 0, wx.GROW|wx.EXPAND|wx.RIGHT|wx.LEFT, 10) | |
469 | + fixed_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=10, vgap=0) | |
470 | + fixed_sizer.AddGrowableCol(0, 1) | |
471 | + fixed_sizer.AddMany([ (label_surface, 1, flag_link, 5), | |
472 | + (text, 1, flag_button, 2), | |
473 | + (label_mask, 1, flag_link, 5), | |
474 | + (combo_mask, 0, flag_button, 1), | |
475 | + (label_quality, 1, flag_link, 5), | |
476 | + (combo_quality, 0, flag_button, 1)]) | |
452 | 477 | |
453 | - btnsizer = wx.StdDialogButtonSizer() | |
454 | 478 | |
455 | - #if wx.Platform != "__WXMSW__": | |
456 | - # btn = wx.ContextHelpButton(self) | |
457 | - # btnsizer.AddButton(btn) | |
479 | + # LINES 4 and 5: Checkboxes | |
480 | + check_box_holes = wx.CheckBox(self, -1, _("Fill holes")) | |
481 | + check_box_holes.SetValue(True) | |
482 | + self.check_box_holes = check_box_holes | |
483 | + check_box_largest = wx.CheckBox(self, -1, _("Keep largest region")) | |
484 | + self.check_box_largest = check_box_largest | |
458 | 485 | |
459 | - btn = wx.Button(self, wx.ID_OK) | |
460 | - btn.SetDefault() | |
461 | - btnsizer.AddButton(btn) | |
486 | + # LINE 6: Buttons | |
462 | 487 | |
463 | - btn = wx.Button(self, wx.ID_CANCEL) | |
464 | - btnsizer.AddButton(btn) | |
488 | + btn_ok = wx.Button(self, wx.ID_OK) | |
489 | + btn_ok.SetDefault() | |
490 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | |
491 | + | |
492 | + btnsizer = wx.StdDialogButtonSizer() | |
493 | + btnsizer.AddButton(btn_ok) | |
494 | + btnsizer.AddButton(btn_cancel) | |
465 | 495 | btnsizer.Realize() |
466 | 496 | |
467 | - sizer.Add(btnsizer, 0, wx.ALIGN_RIGHT|wx.ALL, 5) | |
497 | + # OVERVIEW | |
498 | + # Merge all sizers and checkboxes | |
499 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
500 | + sizer.Add(fixed_sizer, 0, wx.TOP|wx.RIGHT|wx.LEFT|wx.GROW|wx.EXPAND, 20) | |
501 | + sizer.Add(check_box_holes, 0, wx.RIGHT|wx.LEFT, 30) | |
502 | + sizer.Add(check_box_largest, 0, wx.RIGHT|wx.LEFT, 30) | |
503 | + sizer.Add(btnsizer, 0, wx.ALIGN_RIGHT|wx.ALL, 10) | |
468 | 504 | |
469 | 505 | self.SetSizer(sizer) |
470 | 506 | sizer.Fit(self) |
471 | 507 | |
472 | - #def GetValue(self): | |
473 | - # return self.text.GetValue() + _("| mask: ") + MASK_LIST[self.combo_surface_name.GetSelection()] | |
508 | + def GetValue(self): | |
509 | + mask_index = self.combo_mask.GetSelection() | |
510 | + surface_name = self.text.GetValue() | |
511 | + quality = const.SURFACE_QUALITY_LIST[self.combo_quality.GetSelection()] | |
512 | + fill_holes = self.check_box_holes.GetValue() | |
513 | + keep_largest = self.check_box_largest.GetValue() | |
514 | + return (mask_index, surface_name, quality, fill_holes, keep_largest) | |
474 | 515 | |
475 | 516 | INDEX_TO_EXTENSION = {0: "bmp", 1: "jpg", 2: "png", 3: "ps", 4:"povray", 5:"tiff"} |
476 | 517 | WILDCARD_SAVE_PICTURE = _("BMP image")+" (*.bmp)|*.bmp|"+\ | ... | ... |
invesalius/gui/task_surface.py
... | ... | @@ -26,6 +26,7 @@ import wx.lib.pubsub as ps |
26 | 26 | import gui.dialogs as dlg |
27 | 27 | import gui.widgets.foldpanelbar as fpb |
28 | 28 | import gui.widgets.colourselect as csel |
29 | +import project as prj | |
29 | 30 | import utils as utl |
30 | 31 | |
31 | 32 | #INTERPOLATION_MODE_LIST = ["Cubic", "Linear", "NearestNeighbor"] |
... | ... | @@ -130,7 +131,27 @@ class InnerTaskPanel(wx.Panel): |
130 | 131 | #import gui.dialogs as dlg |
131 | 132 | dialog = dlg.NewSurfaceDialog(self, -1, _('InVesalius 3 - New surface')) |
132 | 133 | if dialog.ShowModal() == wx.ID_OK: |
133 | - print "TODO: Send Signal - Create 3d surface %s \n" % dialog.GetValue() | |
134 | + # Retrieve information from dialog | |
135 | + (mask_index, surface_name, surface_quality, fill_holes,\ | |
136 | + keep_largest) = dialog.GetValue() | |
137 | + | |
138 | + # Retrieve information from mask | |
139 | + proj = prj.Project() | |
140 | + mask = proj.mask_dict[mask_index] | |
141 | + | |
142 | + # Send all information so surface can be created | |
143 | + surface_data = [proj.imagedata, | |
144 | + mask.colour, | |
145 | + mask.threshold_range, | |
146 | + mask.edited_points, | |
147 | + False, # overwrite | |
148 | + surface_name, | |
149 | + surface_quality, | |
150 | + fill_holes, | |
151 | + keep_largest] | |
152 | + | |
153 | + ps.Publisher().sendMessage('Create surface', surface_data) | |
154 | + print "TODO: Send Signal - Create 3d surface %s \n" % surface_data | |
134 | 155 | dialog.Destroy() |
135 | 156 | if evt: |
136 | 157 | evt.Skip() | ... | ... |
invesalius/project.py