Commit daaeeb87efad163fef9cf5c5f183464bc26900d0

Authored by Thiago Franco de Moraes
1 parent dbecc12e

Doing some boolean operations

invesalius/constants.py
... ... @@ -475,6 +475,8 @@ ID_SWAP_XY = wx.NewId()
475 475 ID_SWAP_XZ = wx.NewId()
476 476 ID_SWAP_YZ = wx.NewId()
477 477  
  478 +ID_BOOLEAN_MASK = wx.NewId()
  479 +
478 480 #---------------------------------------------------------
479 481 STATE_DEFAULT = 1000
480 482 STATE_WL = 1001
... ... @@ -565,3 +567,7 @@ PROJECTION_CONTOUR_MIDA=8
565 567 #------------ Projections defaults ------------------
566 568 PROJECTION_BORDER_SIZE=1.0
567 569 PROJECTION_MIP_SIZE=2
  570 +
  571 +# ------------- Boolean operations ------------------
  572 +BOOLEAN_UNION = 1
  573 +BOOLEAN_DIFF = 2
... ...
invesalius/control.py
... ... @@ -82,6 +82,8 @@ class Controller():
82 82 Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project')
83 83 Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog')
84 84  
  85 + Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog')
  86 +
85 87  
86 88 def OnCancelImport(self, pubsub_evt):
87 89 #self.cancel_import = True
... ... @@ -628,6 +630,6 @@ class Controller():
628 630 preset_name + '.plist')
629 631 plistlib.writePlist(preset, preset_dir)
630 632  
631   -
632   -
633   -
  633 + def ShowBooleanOpDialog(self, pubsub_evt):
  634 + dlg = dialogs.MaskBooleanDialog(prj.Project().mask_dict)
  635 + dlg.ShowModal()
... ...
invesalius/data/slice_.py
... ... @@ -160,6 +160,8 @@ class Slice(object):
160 160  
161 161 Publisher.subscribe(self._set_projection_type, 'Set projection type')
162 162  
  163 + Publisher.subscribe(self._do_boolean_op, 'Do boolean operation')
  164 +
163 165 Publisher.subscribe(self.OnExportMask,'Export mask to file')
164 166  
165 167 Publisher.subscribe(self.OnCloseProject, 'Close project data')
... ... @@ -1080,12 +1082,17 @@ class Slice(object):
1080 1082 else:
1081 1083 node.value += shiftWW * factor
1082 1084  
1083   - def do_threshold_to_a_slice(self, slice_matrix, mask):
  1085 + def do_threshold_to_a_slice(self, slice_matrix, mask, threshold=None):
1084 1086 """
1085 1087 Based on the current threshold bounds generates a threshold mask to
1086 1088 given slice_matrix.
1087 1089 """
1088   - thresh_min, thresh_max = self.current_mask.threshold_range
  1090 + if threshold:
  1091 + thresh_min, thresh_max = threshold
  1092 + else:
  1093 + thresh_min, thresh_max = self.current_mask.threshold_range
  1094 +
  1095 + print ">>>> THreshold", thresh_min, thresh_max
1089 1096 m = (((slice_matrix >= thresh_min) & (slice_matrix <= thresh_max)) * 255)
1090 1097 m[mask == 1] = 1
1091 1098 m[mask == 2] = 2
... ... @@ -1106,7 +1113,7 @@ class Slice(object):
1106 1113 for n in xrange(1, mask.matrix.shape[0]):
1107 1114 if mask.matrix[n, 0, 0] == 0:
1108 1115 m = mask.matrix[n, 1:, 1:]
1109   - mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m)
  1116 + mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m, mask.threshold_range)
1110 1117  
1111 1118 mask.matrix.flush()
1112 1119  
... ... @@ -1209,6 +1216,43 @@ class Slice(object):
1209 1216  
1210 1217 return blend_imagedata.GetOutput()
1211 1218  
  1219 + def _do_boolean_op(self, pubsub_evt):
  1220 + op, m1, m2 = pubsub_evt.data
  1221 + self.do_boolean_op(op, m1, m2)
  1222 +
  1223 + def do_boolean_op(self, op, m1, m2):
  1224 + name_ops = {const.BOOLEAN_UNION: _(u"Union"),
  1225 + const.BOOLEAN_DIFF: _(u"Diff")}
  1226 +
  1227 +
  1228 + name = u"%s_%s_%s" % (name_ops[op], m1.name, m2.name)
  1229 + proj = Project()
  1230 + mask_dict = proj.mask_dict
  1231 + names_list = [mask_dict[i].name for i in mask_dict.keys()]
  1232 + new_name = utils.next_copy_name(name, names_list)
  1233 +
  1234 + future_mask = Mask()
  1235 + future_mask.create_mask(self.matrix.shape)
  1236 + future_mask.name = new_name
  1237 +
  1238 + future_mask.matrix[:] = 1
  1239 + m = future_mask.matrix[1:, 1:, 1:]
  1240 +
  1241 + self.do_threshold_to_all_slices(m1)
  1242 + m1 = m1.matrix[1:, 1:, 1:]
  1243 +
  1244 + self.do_threshold_to_all_slices(m2)
  1245 + m2 = m2.matrix[1:, 1:, 1:]
  1246 +
  1247 + if op == const.BOOLEAN_UNION:
  1248 + m[:] = ((m1 > 2) + (m2 > 2)) * 255
  1249 +
  1250 + elif op == const.BOOLEAN_DIFF:
  1251 + m[:] = ((m1 > 2) - (m2 > 2)) * 255
  1252 +
  1253 + future_mask.was_edited = True
  1254 + self._add_mask_into_proj(future_mask)
  1255 +
1212 1256 def apply_slice_buffer_to_mask(self, orientation):
1213 1257 """
1214 1258 Apply the modifications (edition) in mask buffer to mask.
... ...
invesalius/gui/dialogs.py
... ... @@ -1384,3 +1384,65 @@ class ClutImagedataDialog(wx.Dialog):
1384 1384 super(wx.Dialog, self).Show(show)
1385 1385 if gen_evt:
1386 1386 self.clut_widget._generate_event()
  1387 +
  1388 +
  1389 +class MaskBooleanDialog(wx.Dialog):
  1390 + def __init__(self, masks):
  1391 + pre = wx.PreDialog()
  1392 + pre.Create(wx.GetApp().GetTopWindow(), -1, style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT)
  1393 + self.PostCreate(pre)
  1394 +
  1395 + self._init_gui(masks)
  1396 +
  1397 + def _init_gui(self, masks):
  1398 + mask_choices = [(masks[i].name, masks[i]) for i in sorted(masks)]
  1399 + self.mask1 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[])
  1400 + self.mask2 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[])
  1401 +
  1402 + for n, m in mask_choices:
  1403 + self.mask1.Append(n, m)
  1404 + self.mask2.Append(n, m)
  1405 +
  1406 + self.mask1.SetSelection(0)
  1407 + self.mask2.SetSelection(0)
  1408 +
  1409 + op_choices = ((u"Union", const.BOOLEAN_UNION),
  1410 + (u"Difference", const.BOOLEAN_DIFF))
  1411 + self.op_boolean = wx.ComboBox(self, -1, op_choices[0][0], choices=[])
  1412 +
  1413 + for n, i in op_choices:
  1414 + self.op_boolean.Append(n, i)
  1415 +
  1416 + self.op_boolean.SetSelection(0)
  1417 +
  1418 + btn_ok = wx.Button(self, wx.ID_OK)
  1419 + btn_ok.SetDefault()
  1420 +
  1421 + btn_cancel = wx.Button(self, wx.ID_CANCEL)
  1422 +
  1423 + btnsizer = wx.StdDialogButtonSizer()
  1424 + btnsizer.AddButton(btn_ok)
  1425 + btnsizer.AddButton(btn_cancel)
  1426 + btnsizer.Realize()
  1427 +
  1428 + sizer = wx.BoxSizer(wx.VERTICAL)
  1429 + sizer.Add(self.mask1, 1, wx.EXPAND)
  1430 + sizer.Add(self.op_boolean, 1, wx.EXPAND)
  1431 + sizer.Add(self.mask2, 1, wx.EXPAND)
  1432 + sizer.Add(btnsizer, 1, wx.EXPAND)
  1433 +
  1434 + self.SetSizer(sizer)
  1435 + sizer.Fit(self)
  1436 +
  1437 + self.Centre()
  1438 +
  1439 + btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
  1440 +
  1441 + def OnOk(self, evt):
  1442 + op = self.op_boolean.GetClientData(self.op_boolean.GetSelection())
  1443 + m1 = self.mask1.GetClientData(self.mask1.GetSelection())
  1444 + m2 = self.mask2.GetClientData(self.mask2.GetSelection())
  1445 +
  1446 + print op, m1.name, m2.name
  1447 +
  1448 + Publisher.sendMessage('Do boolean operation', (op, m1, m2))
... ...
invesalius/gui/frame.py
... ... @@ -397,6 +397,9 @@ class Frame(wx.Frame):
397 397 elif id == wx.ID_REDO:
398 398 self.OnRedo()
399 399  
  400 + elif id == const.ID_BOOLEAN_MASK:
  401 + self.OnMaskBoolean()
  402 +
400 403 def OnSize(self, evt):
401 404 """
402 405 Refresh GUI when frame is resized.
... ... @@ -490,10 +493,11 @@ class Frame(wx.Frame):
490 493 Publisher.sendMessage('Redo edition')
491 494  
492 495  
  496 + def OnMaskBoolean(self):
  497 + print "Mask boolean"
  498 + Publisher.sendMessage('Show boolean dialog')
493 499  
494 500  
495   -
496   -
497 501 # ------------------------------------------------------------------
498 502 # ------------------------------------------------------------------
499 503 # ------------------------------------------------------------------
... ... @@ -605,6 +609,8 @@ class MenuBar(wx.MenuBar):
605 609 #app(const.ID_EDIT_LIST, "Show Undo List...")
606 610 #################################################################
607 611  
  612 + file_edit.Append(const.ID_BOOLEAN_MASK, _(u"\tBoolean operations"))
  613 +
608 614  
609 615 # VIEW
610 616 #view_tool_menu = wx.Menu()
... ...