Commit 6cf4c91a91de4ed7c5475b9a311a8bb48bd79a81

Authored by Thiago Franco de Moraes
1 parent 536f91e7

Adds boolean operation to mask (union, xor, diff, intersection)

Implements boolean operations to masks. The user has a gui to choose the
masks and the operation (union, xor, diff, intersection). Then
InVesalius creates a new mask with the result of the operation.

Doing some boolean operations

Added Intesection and XOR

There was an error in the bool diff operation

Gui improvements

changed "xor" to "exclusive disjuction"

added the icons

Improvements to the boolean operation dialog

Added a menu to mask operations (booleans is there)
icons/bool_difference.png 0 → 100644

884 Bytes

icons/bool_disjunction.png 0 → 100644

840 Bytes

icons/bool_intersection.png 0 → 100644

820 Bytes

icons/bool_union.png 0 → 100644

843 Bytes

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,9 @@ 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
  574 +BOOLEAN_AND = 3
  575 +BOOLEAN_XOR = 4
... ...
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,51 @@ 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 + const.BOOLEAN_AND: _(u"Intersection"),
  1227 + const.BOOLEAN_XOR: _(u"XOR")}
  1228 +
  1229 +
  1230 + name = u"%s_%s_%s" % (name_ops[op], m1.name, m2.name)
  1231 + proj = Project()
  1232 + mask_dict = proj.mask_dict
  1233 + names_list = [mask_dict[i].name for i in mask_dict.keys()]
  1234 + new_name = utils.next_copy_name(name, names_list)
  1235 +
  1236 + future_mask = Mask()
  1237 + future_mask.create_mask(self.matrix.shape)
  1238 + future_mask.name = new_name
  1239 +
  1240 + future_mask.matrix[:] = 1
  1241 + m = future_mask.matrix[1:, 1:, 1:]
  1242 +
  1243 + self.do_threshold_to_all_slices(m1)
  1244 + m1 = m1.matrix[1:, 1:, 1:]
  1245 +
  1246 + self.do_threshold_to_all_slices(m2)
  1247 + m2 = m2.matrix[1:, 1:, 1:]
  1248 +
  1249 + if op == const.BOOLEAN_UNION:
  1250 + m[:] = ((m1 > 2) + (m2 > 2)) * 255
  1251 +
  1252 + elif op == const.BOOLEAN_DIFF:
  1253 + m[:] = ((m1 > 2) - ((m1 > 2) & (m2 > 2))) * 255
  1254 +
  1255 + elif op == const.BOOLEAN_AND:
  1256 + m[:] = ((m1 > 2) & (m2 > 2)) * 255
  1257 +
  1258 + elif op == const.BOOLEAN_XOR:
  1259 + m[:] = numpy.logical_xor((m1 > 2), (m2 > 2)) * 255
  1260 +
  1261 + future_mask.was_edited = True
  1262 + self._add_mask_into_proj(future_mask)
  1263 +
1212 1264 def apply_slice_buffer_to_mask(self, orientation):
1213 1265 """
1214 1266 Apply the modifications (edition) in mask buffer to mask.
... ...
invesalius/gui/dialogs.py
... ... @@ -23,6 +23,7 @@ import random
23 23 import sys
24 24  
25 25 import wx
  26 +import wx.combo
26 27 from wx.lib import masked
27 28 from wx.lib.agw import floatspin
28 29 from wx.lib.wordwrap import wordwrap
... ... @@ -1469,3 +1470,82 @@ class WatershedOptionsDialog(wx.Dialog):
1469 1470 self.SetSizer(sizer)
1470 1471 sizer.Fit(self)
1471 1472 self.Layout()
  1473 +
  1474 +
  1475 +class MaskBooleanDialog(wx.Dialog):
  1476 + def __init__(self, masks):
  1477 + pre = wx.PreDialog()
  1478 + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Booleans operations"), style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT)
  1479 + self.PostCreate(pre)
  1480 +
  1481 + self._init_gui(masks)
  1482 +
  1483 + def _init_gui(self, masks):
  1484 + mask_choices = [(masks[i].name, masks[i]) for i in sorted(masks)]
  1485 + self.mask1 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[])
  1486 + self.mask2 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[])
  1487 +
  1488 + for n, m in mask_choices:
  1489 + self.mask1.Append(n, m)
  1490 + self.mask2.Append(n, m)
  1491 +
  1492 + self.mask1.SetSelection(0)
  1493 +
  1494 + if len(mask_choices) > 1:
  1495 + self.mask2.SetSelection(1)
  1496 + else:
  1497 + self.mask2.SetSelection(0)
  1498 +
  1499 + icon_folder = '../icons/'
  1500 + op_choices = ((_(u"Union"), const.BOOLEAN_UNION, 'bool_union.png'),
  1501 + (_(u"Difference"), const.BOOLEAN_DIFF, 'bool_difference.png'),
  1502 + (_(u"Intersection"), const.BOOLEAN_AND, 'bool_intersection.png'),
  1503 + (_(u"Exclusive disjunction"), const.BOOLEAN_XOR, 'bool_disjunction.png'))
  1504 + self.op_boolean = wx.combo.BitmapComboBox(self, -1, op_choices[0][0], choices=[])
  1505 +
  1506 + for n, i, f in op_choices:
  1507 + bmp = wx.Bitmap(os.path.join(icon_folder, f), wx.BITMAP_TYPE_PNG)
  1508 + self.op_boolean.Append(n, bmp, i)
  1509 +
  1510 + self.op_boolean.SetSelection(0)
  1511 +
  1512 + btn_ok = wx.Button(self, wx.ID_OK)
  1513 + btn_ok.SetDefault()
  1514 +
  1515 + btn_cancel = wx.Button(self, wx.ID_CANCEL)
  1516 +
  1517 + btnsizer = wx.StdDialogButtonSizer()
  1518 + btnsizer.AddButton(btn_ok)
  1519 + btnsizer.AddButton(btn_cancel)
  1520 + btnsizer.Realize()
  1521 +
  1522 + gsizer = wx.FlexGridSizer(rows=3, cols=2, hgap=5, vgap=5)
  1523 +
  1524 + gsizer.Add(wx.StaticText(self, -1, _(u"Mask 1")))
  1525 + gsizer.Add(self.mask1, 1, wx.EXPAND)
  1526 + gsizer.Add(wx.StaticText(self, -1, _(u"Operation")))
  1527 + gsizer.Add(self.op_boolean, 1, wx.EXPAND)
  1528 + gsizer.Add(wx.StaticText(self, -1, _(u"Mask 2")))
  1529 + gsizer.Add(self.mask2, 1, wx.EXPAND)
  1530 +
  1531 + sizer = wx.BoxSizer(wx.VERTICAL)
  1532 + sizer.Add(gsizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=5)
  1533 + sizer.Add(btnsizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=5)
  1534 +
  1535 + self.SetSizer(sizer)
  1536 + sizer.Fit(self)
  1537 +
  1538 + self.Centre()
  1539 +
  1540 + btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
  1541 +
  1542 + def OnOk(self, evt):
  1543 + op = self.op_boolean.GetClientData(self.op_boolean.GetSelection())
  1544 + m1 = self.mask1.GetClientData(self.mask1.GetSelection())
  1545 + m2 = self.mask2.GetClientData(self.mask2.GetSelection())
  1546 +
  1547 + Publisher.sendMessage('Do boolean operation', (op, m1, m2))
  1548 + Publisher.sendMessage('Reload actual slice')
  1549 +
  1550 + self.Close()
  1551 + self.Destroy()
... ...
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,11 @@ class MenuBar(wx.MenuBar):
605 609 #app(const.ID_EDIT_LIST, "Show Undo List...")
606 610 #################################################################
607 611  
  612 + # Mask Menu
  613 + mask_menu = wx.Menu()
  614 + mask_menu.Append(const.ID_BOOLEAN_MASK, _(u"Boolean operations"))
  615 + file_edit.AppendMenu(-1, _(u"Mask"), mask_menu)
  616 +
608 617  
609 618 # VIEW
610 619 #view_tool_menu = wx.Menu()
... ...