Commit 7c2fdb4bbe8bb56d5fbc8e15f04f26f0168f8712
1 parent
4191a837
Exists in
master
and in
5 other branches
Merge branch 'surface_creation'
Showing
8 changed files
with
380 additions
and
97 deletions
Show diff stats
invesalius/data/slice_.py
| ... | ... | @@ -596,10 +596,11 @@ class Slice(object): |
| 596 | 596 | #--------------------------------------------------------------------------- |
| 597 | 597 | |
| 598 | 598 | def CreateSurfaceFromIndex(self, pubsub_evt): |
| 599 | - mask_index, overwrite_surface, algorithm, options = pubsub_evt.data | |
| 599 | + print pubsub_evt.data | |
| 600 | + surface_parameters = pubsub_evt.data | |
| 600 | 601 | |
| 601 | 602 | proj = Project() |
| 602 | - mask = proj.mask_dict[mask_index] | |
| 603 | + mask = proj.mask_dict[surface_parameters['options']['index']] | |
| 603 | 604 | |
| 604 | 605 | # This is very important. Do not use masks' imagedata. It would mess up |
| 605 | 606 | # surface quality event when using contour |
| ... | ... | @@ -611,12 +612,8 @@ class Slice(object): |
| 611 | 612 | |
| 612 | 613 | mask.matrix.flush() |
| 613 | 614 | |
| 614 | - Publisher.sendMessage('Create surface', (algorithm, options, | |
| 615 | - self.matrix, | |
| 616 | - self.matrix_filename, | |
| 617 | - mask, self.spacing, | |
| 618 | - overwrite_surface)) | |
| 619 | - | |
| 615 | + Publisher.sendMessage('Create surface', (self, mask, | |
| 616 | + surface_parameters)) | |
| 620 | 617 | def GetOutput(self): |
| 621 | 618 | return self.blend_filter.GetOutput() |
| 622 | 619 | ... | ... |
invesalius/data/surface.py
| ... | ... | @@ -369,31 +369,29 @@ class SurfaceManager(): |
| 369 | 369 | """ |
| 370 | 370 | Create surface actor, save into project and send it to viewer. |
| 371 | 371 | """ |
| 372 | - algorithm, options, matrix, filename_img, mask, spacing, overwrite = pubsub_evt.data | |
| 373 | - min_value, max_value = mask.threshold_range | |
| 374 | - fill_holes = False | |
| 375 | - | |
| 376 | - mask.matrix.flush() | |
| 377 | - | |
| 378 | - #if len(surface_data) == 5: | |
| 379 | - #imagedata, colour, [min_value, max_value], \ | |
| 380 | - #edited_points, overwrite = pubsub_evt.data | |
| 381 | - #quality=_('Optimal *') | |
| 382 | - #surface_name = "" | |
| 383 | - #fill_holes = True | |
| 384 | - #keep_largest = False | |
| 385 | - #else: | |
| 386 | - #imagedata, colour, [min_value, max_value],\ | |
| 387 | - #edited_points, overwrite, surface_name,\ | |
| 388 | - #quality, fill_holes, keep_largest =\ | |
| 389 | - #pubsub_evt.data | |
| 372 | + slice_, mask, surface_parameters = pubsub_evt.data | |
| 373 | + matrix = slice_.matrix | |
| 374 | + filename_img = slice_.matrix_filename | |
| 375 | + spacing = slice_.spacing | |
| 376 | + | |
| 377 | + algorithm = surface_parameters['method']['algorithm'] | |
| 378 | + options = surface_parameters['method']['options'] | |
| 379 | + | |
| 380 | + surface_name = surface_parameters['options']['name'] | |
| 381 | + quality = surface_parameters['options']['quality'] | |
| 382 | + fill_holes = surface_parameters['options']['fill'] | |
| 383 | + keep_largest = surface_parameters['options']['keep_largest'] | |
| 390 | 384 | |
| 391 | 385 | mode = 'CONTOUR' # 'GRAYSCALE' |
| 392 | - quality=_('Optimal *') | |
| 393 | - keep_largest = False | |
| 394 | - surface_name = "" | |
| 386 | + min_value, max_value = mask.threshold_range | |
| 395 | 387 | colour = mask.colour |
| 396 | 388 | |
| 389 | + try: | |
| 390 | + overwrite = surface_parameters['options']['overwrite'] | |
| 391 | + except KeyError: | |
| 392 | + overwrite = False | |
| 393 | + mask.matrix.flush() | |
| 394 | + | |
| 397 | 395 | if quality in const.SURFACE_QUALITY.keys(): |
| 398 | 396 | imagedata_resolution = const.SURFACE_QUALITY[quality][0] |
| 399 | 397 | smooth_iterations = const.SURFACE_QUALITY[quality][1] |
| ... | ... | @@ -448,8 +446,9 @@ class SurfaceManager(): |
| 448 | 446 | smooth_relaxation_factor, |
| 449 | 447 | smooth_iterations, language, |
| 450 | 448 | flip_image, q_in, q_out, |
| 451 | - mask.was_edited and algorithm != u'InVesalius 3.b2', | |
| 452 | - algorithm) | |
| 449 | + algorithm != 'Default', | |
| 450 | + algorithm, | |
| 451 | + imagedata_resolution) | |
| 453 | 452 | p.append(sp) |
| 454 | 453 | sp.start() |
| 455 | 454 | |
| ... | ... | @@ -501,7 +500,7 @@ class SurfaceManager(): |
| 501 | 500 | polydata.SetSource(None) |
| 502 | 501 | del polydata_append |
| 503 | 502 | |
| 504 | - if algorithm == u'Context aware smoothing': | |
| 503 | + if algorithm == 'ca_smoothing': | |
| 505 | 504 | normals = vtk.vtkPolyDataNormals() |
| 506 | 505 | normals_ref = weakref.ref(normals) |
| 507 | 506 | normals_ref().AddObserver("ProgressEvent", lambda obj,evt: |
| ... | ... | @@ -542,21 +541,23 @@ class SurfaceManager(): |
| 542 | 541 | polydata.DebugOn() |
| 543 | 542 | |
| 544 | 543 | else: |
| 545 | - smoother = vtk.vtkWindowedSincPolyDataFilter() | |
| 544 | + #smoother = vtk.vtkWindowedSincPolyDataFilter() | |
| 545 | + smoother = vtk.vtkSmoothPolyDataFilter() | |
| 546 | 546 | smoother_ref = weakref.ref(smoother) |
| 547 | 547 | smoother_ref().AddObserver("ProgressEvent", lambda obj,evt: |
| 548 | 548 | UpdateProgress(smoother_ref(), _("Generating 3D surface..."))) |
| 549 | 549 | smoother.SetInput(polydata) |
| 550 | 550 | smoother.SetNumberOfIterations(smooth_iterations) |
| 551 | - smoother.SetFeatureAngle(120) | |
| 552 | - smoother.SetEdgeAngle(90.0) | |
| 551 | + smoother.SetRelaxationFactor(smooth_relaxation_factor) | |
| 552 | + smoother.SetFeatureAngle(80) | |
| 553 | + #smoother.SetEdgeAngle(90.0) | |
| 554 | + #smoother.SetPassBand(0.1) | |
| 553 | 555 | smoother.BoundarySmoothingOn() |
| 554 | - smoother.SetPassBand(0.1) | |
| 556 | + smoother.FeatureEdgeSmoothingOn() | |
| 557 | + #smoother.NormalizeCoordinatesOn() | |
| 558 | + #smoother.NonManifoldSmoothingOn() | |
| 555 | 559 | smoother.ReleaseDataFlagOn() |
| 556 | 560 | smoother.GetOutput().ReleaseDataFlagOn() |
| 557 | - #smoother.FeatureEdgeSmoothingOn() | |
| 558 | - #smoother.NonManifoldSmoothingOn() | |
| 559 | - #smoother.NormalizeCoordinatesOn() | |
| 560 | 561 | smoother.Update() |
| 561 | 562 | del polydata |
| 562 | 563 | polydata = smoother.GetOutput() | ... | ... |
invesalius/data/surface_process.py
| ... | ... | @@ -7,6 +7,8 @@ import vtk |
| 7 | 7 | |
| 8 | 8 | import i18n |
| 9 | 9 | import converters |
| 10 | +import imagedata_utils as iu | |
| 11 | + | |
| 10 | 12 | from scipy import ndimage |
| 11 | 13 | |
| 12 | 14 | class SurfaceProcess(multiprocessing.Process): |
| ... | ... | @@ -15,7 +17,7 @@ class SurfaceProcess(multiprocessing.Process): |
| 15 | 17 | mask_shape, mask_dtype, spacing, mode, min_value, max_value, |
| 16 | 18 | decimate_reduction, smooth_relaxation_factor, |
| 17 | 19 | smooth_iterations, language, flip_image, q_in, q_out, |
| 18 | - from_binary, algorithm): | |
| 20 | + from_binary, algorithm, imagedata_resolution): | |
| 19 | 21 | |
| 20 | 22 | multiprocessing.Process.__init__(self) |
| 21 | 23 | self.pipe = pipe |
| ... | ... | @@ -35,6 +37,7 @@ class SurfaceProcess(multiprocessing.Process): |
| 35 | 37 | self.shape = shape |
| 36 | 38 | self.from_binary = from_binary |
| 37 | 39 | self.algorithm = algorithm |
| 40 | + self.imagedata_resolution = imagedata_resolution | |
| 38 | 41 | |
| 39 | 42 | self.mask_filename = mask_filename |
| 40 | 43 | self.mask_shape = mask_shape |
| ... | ... | @@ -96,6 +99,9 @@ class SurfaceProcess(multiprocessing.Process): |
| 96 | 99 | "AXIAL") |
| 97 | 100 | del a_image |
| 98 | 101 | |
| 102 | + if self.imagedata_resolution: | |
| 103 | + image = iu.ResampleImage3D(image, self.imagedata_resolution) | |
| 104 | + | |
| 99 | 105 | flip = vtk.vtkImageFlip() |
| 100 | 106 | flip.SetInput(image) |
| 101 | 107 | flip.SetFilteredAxis(1) | ... | ... |
invesalius/gui/data_notebook.py
| ... | ... | @@ -29,6 +29,7 @@ import wx.lib.platebtn as pbtn |
| 29 | 29 | from wx.lib.pubsub import pub as Publisher |
| 30 | 30 | |
| 31 | 31 | import constants as const |
| 32 | +import data.slice_ as slice_ | |
| 32 | 33 | import gui.dialogs as dlg |
| 33 | 34 | import gui.widgets.listctrl as listmix |
| 34 | 35 | import utils as ul |
| ... | ... | @@ -615,9 +616,10 @@ class SurfaceButtonControlPanel(wx.Panel): |
| 615 | 616 | self.OnDuplicate() |
| 616 | 617 | |
| 617 | 618 | def OnNew(self): |
| 618 | - import project as prj | |
| 619 | - | |
| 620 | - dialog = dlg.NewSurfaceDialog() | |
| 619 | + sl = slice_.Slice() | |
| 620 | + dialog = dlg.SurfaceCreationDialog(self, -1, | |
| 621 | + _('InVesalius 3 - New surface'), | |
| 622 | + mask_edited=sl.current_mask.was_edited) | |
| 621 | 623 | try: |
| 622 | 624 | if dialog.ShowModal() == wx.ID_OK: |
| 623 | 625 | ok = 1 |
| ... | ... | @@ -627,25 +629,10 @@ class SurfaceButtonControlPanel(wx.Panel): |
| 627 | 629 | ok = 1 |
| 628 | 630 | |
| 629 | 631 | if ok: |
| 630 | - (mask_index, surface_name, surface_quality, fill_holes,\ | |
| 631 | - keep_largest) = dialog.GetValue() | |
| 632 | - | |
| 633 | - # Retrieve information from mask | |
| 634 | - proj = prj.Project() | |
| 635 | - mask = proj.mask_dict[mask_index] | |
| 636 | - | |
| 637 | - # Send all information so surface can be created | |
| 638 | - surface_data = [proj.imagedata, | |
| 639 | - mask.colour, | |
| 640 | - mask.threshold_range, | |
| 641 | - mask.edited_points, | |
| 642 | - False, # overwrite | |
| 643 | - surface_name, | |
| 644 | - surface_quality, | |
| 645 | - fill_holes, | |
| 646 | - keep_largest] | |
| 647 | - | |
| 648 | - Publisher.sendMessage('Create surface', surface_data) | |
| 632 | + surface_options = dialog.GetValue() | |
| 633 | + | |
| 634 | + Publisher.sendMessage('Create surface from index', surface_options) | |
| 635 | + dialog.Destroy() | |
| 649 | 636 | |
| 650 | 637 | def OnRemove(self): |
| 651 | 638 | self.parent.listctrl.RemoveSurfaces() | ... | ... |
invesalius/gui/default_tasks.py
| ... | ... | @@ -260,7 +260,7 @@ class UpperTaskPanel(wx.Panel): |
| 260 | 260 | Publisher.subscribe(self.OnFoldExport, 'Fold export task') |
| 261 | 261 | |
| 262 | 262 | def OnOverwrite(self, pubsub_evt): |
| 263 | - self.overwrite = pubsub_evt.data[1] | |
| 263 | + self.overwrite = pubsub_evt.data['options']['overwrite'] | |
| 264 | 264 | |
| 265 | 265 | def OnFoldSurface(self, pubsub_evt): |
| 266 | 266 | if not self.overwrite: | ... | ... |
invesalius/gui/dialogs.py
| ... | ... | @@ -34,6 +34,14 @@ import project as proj |
| 34 | 34 | import session as ses |
| 35 | 35 | import utils |
| 36 | 36 | |
| 37 | +class MaskEvent(wx.PyCommandEvent): | |
| 38 | + def __init__(self , evtType, id, mask_index): | |
| 39 | + wx.PyCommandEvent.__init__(self, evtType, id,) | |
| 40 | + self.mask_index = mask_index | |
| 41 | + | |
| 42 | +myEVT_MASK_SET = wx.NewEventType() | |
| 43 | +EVT_MASK_SET = wx.PyEventBinder(myEVT_MASK_SET, 1) | |
| 44 | + | |
| 37 | 45 | class NumberDialog(wx.Dialog): |
| 38 | 46 | def __init__(self, message, value=0): |
| 39 | 47 | pre = wx.PreDialog() |
| ... | ... | @@ -964,7 +972,6 @@ class SurfaceDialog(wx.Dialog): |
| 964 | 972 | def __init__(self): |
| 965 | 973 | wx.Dialog.__init__(self, None, -1, u'Surface generation options') |
| 966 | 974 | self._build_widgets() |
| 967 | - self._bind_wx() | |
| 968 | 975 | self.CenterOnScreen() |
| 969 | 976 | |
| 970 | 977 | def _build_widgets(self): |
| ... | ... | @@ -975,12 +982,266 @@ class SurfaceDialog(wx.Dialog): |
| 975 | 982 | btn_sizer.AddButton(btn_cancel) |
| 976 | 983 | btn_sizer.Realize() |
| 977 | 984 | |
| 978 | - self.alg_types = {0: u'Context aware smoothing', 1: u'Binary'}#,2: u'InVesalius 3.b2'} | |
| 979 | - self.cb_types = wx.ComboBox(self, -1, self.alg_types[0], | |
| 980 | - choices=[self.alg_types[i] for i in sorted(self.alg_types)], | |
| 981 | - style=wx.CB_READONLY) | |
| 985 | + self.ca = SurfaceMethodPanel(self, -1, True) | |
| 986 | + | |
| 987 | + self.main_sizer = wx.BoxSizer(wx.VERTICAL) | |
| 988 | + self.main_sizer.Add(self.ca, 0, wx.EXPAND|wx.ALL, 5) | |
| 989 | + self.main_sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 5) | |
| 990 | + | |
| 991 | + self.SetSizer(self.main_sizer) | |
| 992 | + self.Fit() | |
| 993 | + | |
| 994 | + def GetOptions(self): | |
| 995 | + return self.ca.GetOptions() | |
| 996 | + | |
| 997 | + def GetAlgorithmSelected(self): | |
| 998 | + return self.ca.GetAlgorithmSelected() | |
| 999 | + | |
| 1000 | + | |
| 1001 | +####################### New surface creation dialog ########################### | |
| 1002 | +class SurfaceCreationDialog(wx.Dialog): | |
| 1003 | + def __init__(self, parent=None, ID=-1, title=_(u"Surface creation"), | |
| 1004 | + size=wx.DefaultSize, pos=wx.DefaultPosition, | |
| 1005 | + style=wx.DEFAULT_DIALOG_STYLE, useMetal=False, | |
| 1006 | + mask_edited=False): | |
| 1007 | + | |
| 1008 | + # Instead of calling wx.Dialog.__init__ we precreate the dialog | |
| 1009 | + # so we can set an extra style that must be set before | |
| 1010 | + # creation, and then we create the GUI object using the Create | |
| 1011 | + # method. | |
| 1012 | + pre = wx.PreDialog() | |
| 1013 | + pre.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP) | |
| 1014 | + pre.Create(parent, ID, title, pos, (50,30), style) | |
| 1015 | + | |
| 1016 | + # This extra style can be set after the UI object has been created. | |
| 1017 | + if 'wxMac' in wx.PlatformInfo and useMetal: | |
| 1018 | + self.SetExtraStyle(wx.DIALOG_EX_METAL) | |
| 1019 | + | |
| 1020 | + # This next step is the most important, it turns this Python | |
| 1021 | + # object into the real wrapper of the dialog (instead of pre) | |
| 1022 | + # as far as the wxPython extension is concerned. | |
| 1023 | + self.PostCreate(pre) | |
| 1024 | + | |
| 1025 | + self.CenterOnScreen() | |
| 1026 | + | |
| 1027 | + self.nsd = SurfaceCreationOptionsPanel(self, -1) | |
| 1028 | + self.nsd.Bind(EVT_MASK_SET, self.OnSetMask) | |
| 1029 | + surface_options_sizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, | |
| 1030 | + _('Surface creation options')), wx.VERTICAL) | |
| 1031 | + surface_options_sizer.Add(self.nsd, 0, wx.EXPAND|wx.ALL, 5) | |
| 1032 | + | |
| 1033 | + self.ca = SurfaceMethodPanel(self, -1, mask_edited) | |
| 1034 | + surface_method_sizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, | |
| 1035 | + _('Surface creation method')), wx.VERTICAL) | |
| 1036 | + surface_method_sizer.Add(self.ca, 0, wx.EXPAND|wx.ALL, 5) | |
| 1037 | + | |
| 1038 | + btn_ok = wx.Button(self, wx.ID_OK) | |
| 1039 | + btn_ok.SetDefault() | |
| 1040 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | |
| 1041 | + | |
| 1042 | + btnsizer = wx.StdDialogButtonSizer() | |
| 1043 | + btnsizer.AddButton(btn_ok) | |
| 1044 | + btnsizer.AddButton(btn_cancel) | |
| 1045 | + btnsizer.Realize() | |
| 1046 | + | |
| 1047 | + sizer_panels = wx.BoxSizer(wx.HORIZONTAL) | |
| 1048 | + sizer_panels.Add(surface_method_sizer, 0, wx.EXPAND|wx.ALL, 5) | |
| 1049 | + sizer_panels.Add(surface_options_sizer, 0, wx.EXPAND|wx.ALL, 5) | |
| 1050 | + | |
| 1051 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
| 1052 | + sizer.Add(sizer_panels, 0, wx.ALIGN_RIGHT|wx.ALL, 5) | |
| 1053 | + sizer.Add(btnsizer, 0, wx.ALIGN_RIGHT|wx.ALL, 5) | |
| 1054 | + | |
| 1055 | + self.SetSizer(sizer) | |
| 1056 | + sizer.Fit(self) | |
| 1057 | + | |
| 1058 | + self.Show() | |
| 1059 | + | |
| 1060 | + def OnSetMask(self, evt): | |
| 1061 | + mask = proj.Project().mask_dict[evt.mask_index] | |
| 1062 | + self.ca.mask_edited = mask.was_edited | |
| 1063 | + self.ca.ReloadMethodsOptions() | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + def GetValue(self): | |
| 1068 | + return {"method": self.ca.GetValue(), | |
| 1069 | + "options": self.nsd.GetValue()} | |
| 1070 | + | |
| 1071 | + | |
| 1072 | +class SurfaceCreationOptionsPanel(wx.Panel): | |
| 1073 | + def __init__(self, parent, ID=-1): | |
| 1074 | + import constants as const | |
| 1075 | + import data.surface as surface | |
| 1076 | + import project as prj | |
| 1077 | + | |
| 1078 | + wx.Panel.__init__(self, parent, ID) | |
| 1079 | + | |
| 1080 | + # LINE 1: Surface name | |
| 1081 | + label_surface = wx.StaticText(self, -1, _("New surface name:")) | |
| 1082 | + | |
| 1083 | + default_name = const.SURFACE_NAME_PATTERN %(surface.Surface.general_index+2) | |
| 1084 | + text = wx.TextCtrl(self, -1, "", size=(80,-1)) | |
| 1085 | + text.SetHelpText(_("Name the surface to be created")) | |
| 1086 | + text.SetValue(default_name) | |
| 1087 | + self.text = text | |
| 1088 | + | |
| 1089 | + # LINE 2: Mask of reference | |
| 1090 | + | |
| 1091 | + # Informative label | |
| 1092 | + label_mask = wx.StaticText(self, -1, _("Mask of reference:")) | |
| 1093 | + | |
| 1094 | + #Retrieve existing masks | |
| 1095 | + project = prj.Project() | |
| 1096 | + index_list = project.mask_dict.keys() | |
| 1097 | + index_list.sort() | |
| 1098 | + self.mask_list = [project.mask_dict[index].name for index in index_list] | |
| 1099 | + | |
| 1100 | + # Mask selection combo | |
| 1101 | + combo_mask = wx.ComboBox(self, -1, "", choices= self.mask_list, | |
| 1102 | + style=wx.CB_DROPDOWN|wx.CB_READONLY) | |
| 1103 | + combo_mask.SetSelection(len(self.mask_list)-1) | |
| 1104 | + combo_mask.Bind(wx.EVT_COMBOBOX, self.OnSetMask) | |
| 1105 | + if sys.platform != 'win32': | |
| 1106 | + combo_mask.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
| 1107 | + self.combo_mask = combo_mask | |
| 1108 | + | |
| 1109 | + # LINE 3: Surface quality | |
| 1110 | + label_quality = wx.StaticText(self, -1, _("Surface quality:")) | |
| 1111 | + | |
| 1112 | + choices = const.SURFACE_QUALITY_LIST | |
| 1113 | + style = wx.CB_DROPDOWN|wx.CB_READONLY | |
| 1114 | + combo_quality = wx.ComboBox(self, -1, "", | |
| 1115 | + choices= choices, | |
| 1116 | + style=style) | |
| 1117 | + combo_quality.SetSelection(3) | |
| 1118 | + if sys.platform != 'win32': | |
| 1119 | + combo_quality.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
| 1120 | + self.combo_quality = combo_quality | |
| 1121 | + | |
| 1122 | + # OVERVIEW | |
| 1123 | + # Sizer that joins content above | |
| 1124 | + flag_link = wx.EXPAND|wx.GROW|wx.ALL | |
| 1125 | + flag_button = wx.ALL | wx.EXPAND| wx.GROW | |
| 1126 | + | |
| 1127 | + fixed_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=10, vgap=5) | |
| 1128 | + fixed_sizer.AddGrowableCol(0, 1) | |
| 1129 | + fixed_sizer.AddMany([ (label_surface, 1, flag_link, 0), | |
| 1130 | + (text, 1, flag_button, 0), | |
| 1131 | + (label_mask, 1, flag_link, 0), | |
| 1132 | + (combo_mask, 0, flag_button, 0), | |
| 1133 | + (label_quality, 1, flag_link, 0), | |
| 1134 | + (combo_quality, 0, flag_button, 0)]) | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + # LINES 4 and 5: Checkboxes | |
| 1138 | + check_box_holes = wx.CheckBox(self, -1, _("Fill holes")) | |
| 1139 | + check_box_holes.SetValue(True) | |
| 1140 | + self.check_box_holes = check_box_holes | |
| 1141 | + check_box_largest = wx.CheckBox(self, -1, _("Keep largest region")) | |
| 1142 | + self.check_box_largest = check_box_largest | |
| 1143 | + | |
| 1144 | + # OVERVIEW | |
| 1145 | + # Merge all sizers and checkboxes | |
| 1146 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
| 1147 | + sizer.Add(fixed_sizer, 0, wx.TOP|wx.RIGHT|wx.LEFT|wx.GROW|wx.EXPAND, 5) | |
| 1148 | + sizer.Add(check_box_holes, 0, wx.RIGHT|wx.LEFT, 5) | |
| 1149 | + sizer.Add(check_box_largest, 0, wx.RIGHT|wx.LEFT, 5) | |
| 1150 | + | |
| 1151 | + self.SetSizer(sizer) | |
| 1152 | + sizer.Fit(self) | |
| 1153 | + | |
| 1154 | + def OnSetMask(self, evt): | |
| 1155 | + new_evt = MaskEvent(myEVT_MASK_SET, -1, self.combo_mask.GetSelection()) | |
| 1156 | + self.GetEventHandler().ProcessEvent(new_evt) | |
| 1157 | + | |
| 1158 | + def GetValue(self): | |
| 1159 | + mask_index = self.combo_mask.GetSelection() | |
| 1160 | + surface_name = self.text.GetValue() | |
| 1161 | + quality = const.SURFACE_QUALITY_LIST[self.combo_quality.GetSelection()] | |
| 1162 | + fill_holes = self.check_box_holes.GetValue() | |
| 1163 | + keep_largest = self.check_box_largest.GetValue() | |
| 1164 | + return {"index": mask_index, | |
| 1165 | + "name": surface_name, | |
| 1166 | + "quality": quality, | |
| 1167 | + "fill": fill_holes, | |
| 1168 | + "keep_largest": keep_largest, | |
| 1169 | + "overwrite": False} | |
| 1170 | + | |
| 1171 | + | |
| 1172 | +class CAOptions(wx.Panel): | |
| 1173 | + ''' | |
| 1174 | + Options related to Context aware algorithm: | |
| 1175 | + Angle: The min angle to a vertex to be considered a staircase vertex; | |
| 1176 | + Max distance: The max distance a normal vertex must be to calculate its | |
| 1177 | + weighting; | |
| 1178 | + Min Weighting: The min weight a vertex must have; | |
| 1179 | + Steps: The number of iterations the smoothing algorithm have to do. | |
| 1180 | + ''' | |
| 1181 | + def __init__(self, parent): | |
| 1182 | + wx.Panel.__init__(self, parent, -1) | |
| 1183 | + self._build_widgets() | |
| 1184 | + | |
| 1185 | + def _build_widgets(self): | |
| 1186 | + self.angle = floatspin.FloatSpin(self, -1, value=0.7, min_val=0.0, | |
| 1187 | + max_val=1.0, increment=0.1, | |
| 1188 | + digits=1) | |
| 1189 | + | |
| 1190 | + self.max_distance = floatspin.FloatSpin(self, -1, value=3.0, min_val=0.0, | |
| 1191 | + max_val=100.0, increment=0.1, | |
| 1192 | + digits=2) | |
| 1193 | + | |
| 1194 | + self.min_weight = floatspin.FloatSpin(self, -1, value=0.2, min_val=0.0, | |
| 1195 | + max_val=1.0, increment=0.1, | |
| 1196 | + digits=1) | |
| 1197 | + | |
| 1198 | + self.steps = wx.SpinCtrl(self, -1, value='10', min=1, max=100) | |
| 1199 | + | |
| 1200 | + layout_sizer = wx.FlexGridSizer(rows=4, cols=2, hgap=5, vgap=5) | |
| 1201 | + layout_sizer.Add(wx.StaticText(self, -1, _(u'Angle:')), 0, wx.EXPAND) | |
| 1202 | + layout_sizer.Add(self.angle, 0, wx.EXPAND) | |
| 1203 | + layout_sizer.Add(wx.StaticText(self, -1, _(u'Max. distance:')), 0, wx.EXPAND) | |
| 1204 | + layout_sizer.Add(self.max_distance, 0, wx.EXPAND) | |
| 1205 | + layout_sizer.Add(wx.StaticText(self, -1, _(u'Min. weight:')), 0, wx.EXPAND) | |
| 1206 | + layout_sizer.Add(self.min_weight, 0, wx.EXPAND) | |
| 1207 | + layout_sizer.Add(wx.StaticText(self, -1, _(u'N. steps:')), 0, wx.EXPAND) | |
| 1208 | + layout_sizer.Add(self.steps, 0, wx.EXPAND) | |
| 1209 | + | |
| 1210 | + self.main_sizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('Context aware options')), wx.VERTICAL) | |
| 1211 | + self.main_sizer.Add(layout_sizer, 0, wx.EXPAND | wx.ALL, 5) | |
| 1212 | + self.SetSizer(self.main_sizer) | |
| 1213 | + | |
| 1214 | +class SurfaceMethodPanel(wx.Panel): | |
| 1215 | + ''' | |
| 1216 | + This dialog is only shown when the mask whose surface will be generate was | |
| 1217 | + edited. So far, the only options available are the choice of method to | |
| 1218 | + generate the surface, Binary or `Context aware smoothing', and options from | |
| 1219 | + `Context aware smoothing' | |
| 1220 | + ''' | |
| 1221 | + def __init__(self, parent, id, mask_edited=False): | |
| 1222 | + wx.Panel.__init__(self, parent, id) | |
| 1223 | + | |
| 1224 | + self.mask_edited = mask_edited | |
| 1225 | + self.alg_types = {_(u'Default'): 'Default', | |
| 1226 | + _(u'Context aware smoothing'): 'ca_smoothing', | |
| 1227 | + _(u'Binary'): 'Binary'} | |
| 1228 | + self.edited_imp = [_(u'Default'), ] | |
| 1229 | + | |
| 1230 | + self._build_widgets() | |
| 1231 | + self._bind_wx() | |
| 982 | 1232 | |
| 1233 | + def _build_widgets(self): | |
| 983 | 1234 | self.ca_options = CAOptions(self) |
| 1235 | + self.cb_types = wx.ComboBox(self, -1, _(u'Default'), | |
| 1236 | + choices=[i for i in sorted(self.alg_types) | |
| 1237 | + if not (self.mask_edited and i in self.edited_imp)], | |
| 1238 | + style=wx.CB_READONLY) | |
| 1239 | + if self.mask_edited: | |
| 1240 | + self.cb_types.SetValue(_(u'Context aware smoothing')) | |
| 1241 | + self.ca_options.Enable() | |
| 1242 | + else: | |
| 1243 | + self.ca_options.Disable() | |
| 1244 | + | |
| 984 | 1245 | |
| 985 | 1246 | method_sizer = wx.BoxSizer(wx.HORIZONTAL) |
| 986 | 1247 | method_sizer.Add(wx.StaticText(self, -1, u'Method:'), 0, |
| ... | ... | @@ -990,7 +1251,6 @@ class SurfaceDialog(wx.Dialog): |
| 990 | 1251 | self.main_sizer = wx.BoxSizer(wx.VERTICAL) |
| 991 | 1252 | self.main_sizer.Add(method_sizer, 0, wx.EXPAND | wx.ALL, 5) |
| 992 | 1253 | self.main_sizer.Add(self.ca_options, 0, wx.EXPAND | wx.ALL, 5) |
| 993 | - self.main_sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 5) | |
| 994 | 1254 | |
| 995 | 1255 | self.SetSizer(self.main_sizer) |
| 996 | 1256 | self.Fit() |
| ... | ... | @@ -1000,7 +1260,7 @@ class SurfaceDialog(wx.Dialog): |
| 1000 | 1260 | |
| 1001 | 1261 | def _set_cb_types(self, evt): |
| 1002 | 1262 | print evt.GetString() |
| 1003 | - if self.alg_types[evt.GetSelection()] == self.alg_types[0]: | |
| 1263 | + if self.alg_types[evt.GetString()] == 'ca_smoothing': | |
| 1004 | 1264 | self.ca_options.Enable() |
| 1005 | 1265 | else: |
| 1006 | 1266 | self.ca_options.Disable() |
| ... | ... | @@ -1008,12 +1268,12 @@ class SurfaceDialog(wx.Dialog): |
| 1008 | 1268 | |
| 1009 | 1269 | def GetAlgorithmSelected(self): |
| 1010 | 1270 | try: |
| 1011 | - return self.alg_types[self.cb_types.GetSelection()] | |
| 1271 | + return self.alg_types[self.cb_types.GetValue()] | |
| 1012 | 1272 | except KeyError: |
| 1013 | 1273 | return self.alg_types[0] |
| 1014 | 1274 | |
| 1015 | 1275 | def GetOptions(self): |
| 1016 | - if self.GetAlgorithmSelected() == self.alg_types[0]: | |
| 1276 | + if self.GetAlgorithmSelected() == 'ca_smoothing': | |
| 1017 | 1277 | options = {'angle': self.ca_options.angle.GetValue(), |
| 1018 | 1278 | 'max distance': self.ca_options.max_distance.GetValue(), |
| 1019 | 1279 | 'min weight': self.ca_options.min_weight.GetValue(), |
| ... | ... | @@ -1022,4 +1282,20 @@ class SurfaceDialog(wx.Dialog): |
| 1022 | 1282 | options = {} |
| 1023 | 1283 | return options |
| 1024 | 1284 | |
| 1285 | + def GetValue(self): | |
| 1286 | + algorithm = self.GetAlgorithmSelected() | |
| 1287 | + options = self.GetOptions() | |
| 1025 | 1288 | |
| 1289 | + return {"algorithm": algorithm, | |
| 1290 | + "options": options} | |
| 1291 | + | |
| 1292 | + def ReloadMethodsOptions(self): | |
| 1293 | + self.cb_types.Clear() | |
| 1294 | + self.cb_types.AppendItems([i for i in sorted(self.alg_types) | |
| 1295 | + if not (self.mask_edited and i in self.edited_imp)]) | |
| 1296 | + if self.mask_edited: | |
| 1297 | + self.cb_types.SetValue(_(u'Context aware smoothing')) | |
| 1298 | + self.ca_options.Enable() | |
| 1299 | + else: | |
| 1300 | + self.cb_types.SetValue(_(u'Default')) | |
| 1301 | + self.ca_options.Disable() | ... | ... |
invesalius/gui/task_slice.py
| ... | ... | @@ -155,9 +155,19 @@ class InnerTaskPanel(wx.Panel): |
| 155 | 155 | options = dlgs.GetOptions() |
| 156 | 156 | else: |
| 157 | 157 | return |
| 158 | - Publisher.sendMessage('Create surface from index', | |
| 159 | - (self.GetMaskSelected(), | |
| 160 | - overwrite, algorithm, options)) | |
| 158 | + | |
| 159 | + mask_index = sl.current_mask.index | |
| 160 | + method = {'algorithm': 'Default', | |
| 161 | + 'options': options} | |
| 162 | + srf_options = {"index": mask_index, | |
| 163 | + "name": '', | |
| 164 | + "quality": _('Optimal *'), | |
| 165 | + "fill": False, | |
| 166 | + "keep_largest": False, | |
| 167 | + "overwrite": overwrite} | |
| 168 | + | |
| 169 | + Publisher.sendMessage('Create surface from index', | |
| 170 | + {'method': method, 'options': srf_options}) | |
| 161 | 171 | Publisher.sendMessage('Fold surface task') |
| 162 | 172 | else: |
| 163 | 173 | dlg.InexistentMask() | ... | ... |
invesalius/gui/task_surface.py
| ... | ... | @@ -23,6 +23,7 @@ import wx.lib.hyperlink as hl |
| 23 | 23 | from wx.lib.pubsub import pub as Publisher |
| 24 | 24 | |
| 25 | 25 | import constants as const |
| 26 | +import data.slice_ as slice_ | |
| 26 | 27 | import gui.dialogs as dlg |
| 27 | 28 | import gui.widgets.foldpanelbar as fpb |
| 28 | 29 | import gui.widgets.colourselect as csel |
| ... | ... | @@ -30,7 +31,6 @@ import gui.widgets.platebtn as pbtn |
| 30 | 31 | import project as prj |
| 31 | 32 | import utils as utl |
| 32 | 33 | |
| 33 | - | |
| 34 | 34 | #INTERPOLATION_MODE_LIST = ["Cubic", "Linear", "NearestNeighbor"] |
| 35 | 35 | MIN_TRANSPARENCY = 0 |
| 36 | 36 | MAX_TRANSPARENCY = 100 |
| ... | ... | @@ -131,7 +131,10 @@ class InnerTaskPanel(wx.Panel): |
| 131 | 131 | |
| 132 | 132 | def OnLinkNewSurface(self, evt=None): |
| 133 | 133 | #import gui.dialogs as dlg |
| 134 | - dialog = dlg.NewSurfaceDialog(self, -1, _('InVesalius 3 - New surface')) | |
| 134 | + sl = slice_.Slice() | |
| 135 | + dialog = dlg.SurfaceCreationDialog(self, -1, | |
| 136 | + _('InVesalius 3 - New surface'), | |
| 137 | + mask_edited=sl.current_mask.was_edited) | |
| 135 | 138 | |
| 136 | 139 | try: |
| 137 | 140 | if dialog.ShowModal() == wx.ID_OK: |
| ... | ... | @@ -142,26 +145,29 @@ class InnerTaskPanel(wx.Panel): |
| 142 | 145 | ok = 1 |
| 143 | 146 | |
| 144 | 147 | if (ok): |
| 145 | - # Retrieve information from dialog | |
| 146 | - (mask_index, surface_name, surface_quality, fill_holes,\ | |
| 147 | - keep_largest) = dialog.GetValue() | |
| 148 | - | |
| 149 | - # Retrieve information from mask | |
| 150 | - proj = prj.Project() | |
| 151 | - mask = proj.mask_dict[mask_index] | |
| 152 | - | |
| 153 | - # Send all information so surface can be created | |
| 154 | - surface_data = [proj.imagedata, | |
| 155 | - mask.colour, | |
| 156 | - mask.threshold_range, | |
| 157 | - mask.edited_points, | |
| 158 | - False, # overwrite | |
| 159 | - surface_name, | |
| 160 | - surface_quality, | |
| 161 | - fill_holes, | |
| 162 | - keep_largest] | |
| 163 | - | |
| 164 | - Publisher.sendMessage('Create surface', surface_data) | |
| 148 | + ## Retrieve information from dialog | |
| 149 | + #(mask_index, surface_name, surface_quality, fill_holes,\ | |
| 150 | + #keep_largest) = dialog.GetValue() | |
| 151 | + | |
| 152 | + ## Retrieve information from mask | |
| 153 | + #proj = prj.Project() | |
| 154 | + #mask = proj.mask_dict[mask_index] | |
| 155 | + | |
| 156 | + ## Send all information so surface can be created | |
| 157 | + #surface_data = [proj.imagedata, | |
| 158 | + #mask.colour, | |
| 159 | + #mask.threshold_range, | |
| 160 | + #mask.edited_points, | |
| 161 | + #False, # overwrite | |
| 162 | + #surface_name, | |
| 163 | + #surface_quality, | |
| 164 | + #fill_holes, | |
| 165 | + #keep_largest] | |
| 166 | + | |
| 167 | + | |
| 168 | + surface_options = dialog.GetValue() | |
| 169 | + | |
| 170 | + Publisher.sendMessage('Create surface from index', surface_options) | |
| 165 | 171 | dialog.Destroy() |
| 166 | 172 | if evt: |
| 167 | 173 | evt.Skip() | ... | ... |