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