Commit e83c555b326e9f084b4f6c3a32533dcd789b3702
1 parent
536f91e7
Exists in
mask_boolean_op_bkp
Doing some boolean operations
Showing
5 changed files
with
128 additions
and
8 deletions
Show diff stats
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
| ... | ... | @@ -1469,3 +1469,65 @@ class WatershedOptionsDialog(wx.Dialog): |
| 1469 | 1469 | self.SetSizer(sizer) |
| 1470 | 1470 | sizer.Fit(self) |
| 1471 | 1471 | self.Layout() |
| 1472 | + | |
| 1473 | + | |
| 1474 | +class MaskBooleanDialog(wx.Dialog): | |
| 1475 | + def __init__(self, masks): | |
| 1476 | + pre = wx.PreDialog() | |
| 1477 | + pre.Create(wx.GetApp().GetTopWindow(), -1, style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT) | |
| 1478 | + self.PostCreate(pre) | |
| 1479 | + | |
| 1480 | + self._init_gui(masks) | |
| 1481 | + | |
| 1482 | + def _init_gui(self, masks): | |
| 1483 | + mask_choices = [(masks[i].name, masks[i]) for i in sorted(masks)] | |
| 1484 | + self.mask1 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[]) | |
| 1485 | + self.mask2 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[]) | |
| 1486 | + | |
| 1487 | + for n, m in mask_choices: | |
| 1488 | + self.mask1.Append(n, m) | |
| 1489 | + self.mask2.Append(n, m) | |
| 1490 | + | |
| 1491 | + self.mask1.SetSelection(0) | |
| 1492 | + self.mask2.SetSelection(0) | |
| 1493 | + | |
| 1494 | + op_choices = ((u"Union", const.BOOLEAN_UNION), | |
| 1495 | + (u"Difference", const.BOOLEAN_DIFF)) | |
| 1496 | + self.op_boolean = wx.ComboBox(self, -1, op_choices[0][0], choices=[]) | |
| 1497 | + | |
| 1498 | + for n, i in op_choices: | |
| 1499 | + self.op_boolean.Append(n, i) | |
| 1500 | + | |
| 1501 | + self.op_boolean.SetSelection(0) | |
| 1502 | + | |
| 1503 | + btn_ok = wx.Button(self, wx.ID_OK) | |
| 1504 | + btn_ok.SetDefault() | |
| 1505 | + | |
| 1506 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | |
| 1507 | + | |
| 1508 | + btnsizer = wx.StdDialogButtonSizer() | |
| 1509 | + btnsizer.AddButton(btn_ok) | |
| 1510 | + btnsizer.AddButton(btn_cancel) | |
| 1511 | + btnsizer.Realize() | |
| 1512 | + | |
| 1513 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
| 1514 | + sizer.Add(self.mask1, 1, wx.EXPAND) | |
| 1515 | + sizer.Add(self.op_boolean, 1, wx.EXPAND) | |
| 1516 | + sizer.Add(self.mask2, 1, wx.EXPAND) | |
| 1517 | + sizer.Add(btnsizer, 1, wx.EXPAND) | |
| 1518 | + | |
| 1519 | + self.SetSizer(sizer) | |
| 1520 | + sizer.Fit(self) | |
| 1521 | + | |
| 1522 | + self.Centre() | |
| 1523 | + | |
| 1524 | + btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) | |
| 1525 | + | |
| 1526 | + def OnOk(self, evt): | |
| 1527 | + op = self.op_boolean.GetClientData(self.op_boolean.GetSelection()) | |
| 1528 | + m1 = self.mask1.GetClientData(self.mask1.GetSelection()) | |
| 1529 | + m2 = self.mask2.GetClientData(self.mask2.GetSelection()) | |
| 1530 | + | |
| 1531 | + print op, m1.name, m2.name | |
| 1532 | + | |
| 1533 | + 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() | ... | ... |