Commit daaeeb87efad163fef9cf5c5f183464bc26900d0
1 parent
dbecc12e
Exists in
mask_greatest_component
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
... | ... | @@ -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() | ... | ... |