Commit dc6fb66558962f136f708ffa20cd292240b9b932
1 parent
9716c1f9
Exists in
master
and in
6 other branches
ADD: New mask dialog and slice creation, based on threshold and colour
Showing
5 changed files
with
145 additions
and
113 deletions
Show diff stats
invesalius/constants.py
... | ... | @@ -28,7 +28,7 @@ from project import Project |
28 | 28 | #--------------- |
29 | 29 | |
30 | 30 | # Measurements |
31 | -MEASURE_NAME_PATTERN = _("Measure %d") | |
31 | +MEASURE_NAME_PATTERN = _("M %d") | |
32 | 32 | MEASURE_LINEAR = 101 |
33 | 33 | MEASURE_ANGULAR = 102 |
34 | 34 | |
... | ... | @@ -182,16 +182,16 @@ THRESHOLD_OUTVALUE = 0 |
182 | 182 | MASK_NAME_PATTERN = _("Mask %d") |
183 | 183 | MASK_OPACITY = 0.40 |
184 | 184 | #MASK_OPACITY = 0.35 |
185 | -MASK_COLOUR = [(0.33, 1, 0.33), | |
186 | - (1, 1, 0.33), | |
187 | - (0.33, 0.91, 1), | |
188 | - (1, 0.33, 1), | |
189 | - (1, 0.68, 0.33), | |
190 | - (1, 0.33, 0.33), | |
191 | - (0.33333333333333331, 0.33333333333333331, 1.0), | |
185 | +MASK_COLOUR = [[0.33, 1, 0.33], | |
186 | + [1, 1, 0.33], | |
187 | + [0.33, 0.91, 1], | |
188 | + [1, 0.33, 1], | |
189 | + [1, 0.68, 0.33], | |
190 | + [1, 0.33, 0.33], | |
191 | + [0.33333333333333331, 0.33333333333333331, 1.0], | |
192 | 192 | #(1.0, 0.33333333333333331, 0.66666666666666663), |
193 | - (0.74901960784313726, 1.0, 0.0), | |
194 | - (0.83529411764705885, 0.33333333333333331, 1.0)]#, | |
193 | + [0.74901960784313726, 1.0, 0.0], | |
194 | + [0.83529411764705885, 0.33333333333333331, 1.0]]#, | |
195 | 195 | #(0.792156862745098, 0.66666666666666663, 1.0), |
196 | 196 | #(1.0, 0.66666666666666663, 0.792156862745098), # too "light" |
197 | 197 | #(0.33333333333333331, 1.0, 0.83529411764705885),#], | ... | ... |
invesalius/data/slice_.py
... | ... | @@ -49,7 +49,7 @@ class Slice(object): |
49 | 49 | ps.Publisher().subscribe(self.CreateSurfaceFromIndex, |
50 | 50 | 'Create surface from index') |
51 | 51 | # Mask control |
52 | - ps.Publisher().subscribe(self.__add_mask, 'Create new mask') | |
52 | + ps.Publisher().subscribe(self.__add_mask_thresh, 'Create new mask') | |
53 | 53 | ps.Publisher().subscribe(self.__select_current_mask, |
54 | 54 | 'Change mask selected') |
55 | 55 | # Mask properties |
... | ... | @@ -169,6 +169,14 @@ class Slice(object): |
169 | 169 | self.CreateMask(name=mask_name) |
170 | 170 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) |
171 | 171 | |
172 | + def __add_mask_thresh(self, pubsub_evt): | |
173 | + mask_name = pubsub_evt.data[0] | |
174 | + thresh = pubsub_evt.data[1] | |
175 | + colour = pubsub_evt.data[2] | |
176 | + self.CreateMask(name=mask_name, threshold_range=thresh, colour =colour) | |
177 | + self.SetMaskColour(self.current_mask.index, self.current_mask.colour) | |
178 | + self.SelectCurrentMask(self.current_mask.index) | |
179 | + | |
172 | 180 | def __select_current_mask(self, pubsub_evt): |
173 | 181 | mask_index = pubsub_evt.data |
174 | 182 | self.SelectCurrentMask(mask_index) |
... | ... | @@ -519,8 +527,6 @@ class Slice(object): |
519 | 527 | future_mask.colour = colour |
520 | 528 | if opacity: |
521 | 529 | future_mask.opacity = opacity |
522 | - if threshold_range: | |
523 | - future_mask.threshold_range = threshold_range | |
524 | 530 | if edition_threshold_range: |
525 | 531 | future_mask.edition_threshold_range = edition_threshold_range |
526 | 532 | if edited_points: |
... | ... | @@ -532,11 +538,15 @@ class Slice(object): |
532 | 538 | imagedata = old_mask.imagedata |
533 | 539 | future_mask.threshold_range = old_mask.threshold_range |
534 | 540 | |
535 | - # if not defined in the method call, this will have been computed on | |
536 | - # previous if | |
537 | - future_mask.imagedata = vtk.vtkImageData() | |
538 | - future_mask.imagedata.DeepCopy(imagedata) | |
539 | - future_mask.imagedata.Update() | |
541 | + if threshold_range: | |
542 | + future_mask.threshold_range = threshold_range | |
543 | + future_mask.imagedata = self.__create_mask_threshold(self.imagedata, | |
544 | + threshold_range) | |
545 | + else: | |
546 | + future_mask.imagedata = vtk.vtkImageData() | |
547 | + future_mask.imagedata.DeepCopy(imagedata) | |
548 | + future_mask.imagedata.Update() | |
549 | + | |
540 | 550 | |
541 | 551 | # when this is not the first instance, user will have defined a name |
542 | 552 | if name is not None: |
... | ... | @@ -551,6 +561,9 @@ class Slice(object): |
551 | 561 | proj = Project() |
552 | 562 | index = proj.AddMask(future_mask) |
553 | 563 | future_mask.index = index |
564 | + if threshold_range: | |
565 | + self.SetMaskThreshold(index, threshold_range) | |
566 | + future_mask.edited_points = {} | |
554 | 567 | |
555 | 568 | # update gui related to mask |
556 | 569 | ps.Publisher().sendMessage('Add mask', |
... | ... | @@ -630,8 +643,11 @@ class Slice(object): |
630 | 643 | return img_colours_mask.GetOutput() |
631 | 644 | |
632 | 645 | |
633 | - def __create_mask_threshold(self, imagedata): | |
634 | - thresh_min, thresh_max = self.current_mask.threshold_range | |
646 | + def __create_mask_threshold(self, imagedata, threshold_range=None): | |
647 | + if not threshold_range: | |
648 | + thresh_min, thresh_max = self.current_mask.threshold_range | |
649 | + else: | |
650 | + thresh_min, thresh_max = threshold_range | |
635 | 651 | |
636 | 652 | # flexible threshold |
637 | 653 | img_thresh_mask = vtk.vtkImageThreshold() | ... | ... |
invesalius/gui/data_notebook.py
... | ... | @@ -293,16 +293,20 @@ class ButtonControlPanel(wx.Panel): |
293 | 293 | |
294 | 294 | def OnNew(self): |
295 | 295 | dialog = dlg.NewMask() |
296 | - | |
296 | + | |
297 | 297 | try: |
298 | - answer = dialog.ShowModal() | |
299 | - except(wx._core.PyAssertionError): #TODO: FIX win64 | |
300 | - answer = wx.ID_YES | |
298 | + if dialog.ShowModal() == wx.ID_OK: | |
299 | + ok = 1 | |
300 | + else: | |
301 | + ok = 0 | |
302 | + except(wx._core.PyAssertionError): #TODO FIX: win64 | |
303 | + ok = 1 | |
301 | 304 | |
302 | - if wx.ID_YES: | |
303 | - mask_name = dialog.GetValue() | |
305 | + if ok: | |
306 | + mask_name, thresh, colour = dialog.GetValue() | |
304 | 307 | if mask_name: |
305 | - ps.Publisher().sendMessage('Create new mask', mask_name) | |
308 | + ps.Publisher().sendMessage('Create new mask', | |
309 | + (mask_name, thresh, colour)) | |
306 | 310 | |
307 | 311 | def OnRemove(self): |
308 | 312 | self.parent.listctrl.RemoveMasks() | ... | ... |
invesalius/gui/dialogs.py
... | ... | @@ -19,6 +19,7 @@ |
19 | 19 | # detalhes. |
20 | 20 | #-------------------------------------------------------------------------- |
21 | 21 | import os |
22 | +import random | |
22 | 23 | import sys |
23 | 24 | |
24 | 25 | import wx |
... | ... | @@ -26,8 +27,8 @@ from wx.lib import masked |
26 | 27 | from wx.lib.wordwrap import wordwrap |
27 | 28 | import wx.lib.pubsub as ps |
28 | 29 | |
29 | - | |
30 | 30 | import constants as const |
31 | +import gui.widgets.gradient as grad | |
31 | 32 | import project as proj |
32 | 33 | import session as ses |
33 | 34 | import utils |
... | ... | @@ -340,6 +341,17 @@ def SurfaceSelectionRequiredForRemoval(): |
340 | 341 | dlg.Destroy() |
341 | 342 | |
342 | 343 | |
344 | +def MeasureSelectionRequiredForRemoval(): | |
345 | + msg = _("No measures were selected for removal.") | |
346 | + if sys.platform == 'darwin': | |
347 | + dlg = wx.MessageDialog(None, "", msg, | |
348 | + wx.ICON_INFORMATION | wx.OK) | |
349 | + else: | |
350 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3", | |
351 | + wx.ICON_INFORMATION | wx.OK) | |
352 | + dlg.ShowModal() | |
353 | + dlg.Destroy() | |
354 | + | |
343 | 355 | def MaskSelectionRequiredForDuplication(): |
344 | 356 | msg = _("No masks were selected for duplication.") |
345 | 357 | if sys.platform == 'darwin': |
... | ... | @@ -364,28 +376,7 @@ def SurfaceSelectionRequiredForDuplication(): |
364 | 376 | dlg.ShowModal() |
365 | 377 | dlg.Destroy() |
366 | 378 | |
367 | - | |
368 | - | |
369 | -def NewMask(): | |
370 | - import data.mask as mask | |
371 | - dlg = wx.TextEntryDialog(None, _('Name of new mask:'), | |
372 | - _('InVesalius 3 - New mask')) | |
373 | - | |
374 | - dlg.CenterOnScreen() | |
375 | - default_mask_name = const.MASK_NAME_PATTERN %(mask.Mask.general_index+2) | |
376 | - dlg.SetValue(default_mask_name) | |
377 | - | |
378 | - try: | |
379 | - op = dlg.ShowModal() == wx.ID_OK | |
380 | - except(wx._core.PyAssertionError): | |
381 | - print "win64 - wx._core.PyAssertionError" | |
382 | - op = True | |
383 | - | |
384 | - if op: | |
385 | - return dlg.GetValue() | |
386 | - return None | |
387 | - | |
388 | -class NewMaskDialog(wx.Dialog): | |
379 | +class NewMask(wx.Dialog): | |
389 | 380 | def __init__(self, |
390 | 381 | parent=None, |
391 | 382 | ID=-1, |
... | ... | @@ -395,7 +386,7 @@ class NewMaskDialog(wx.Dialog): |
395 | 386 | style=wx.DEFAULT_DIALOG_STYLE, |
396 | 387 | useMetal=False): |
397 | 388 | import constants as const |
398 | - import data.surface as surface | |
389 | + import data.mask as mask | |
399 | 390 | import project as prj |
400 | 391 | |
401 | 392 | # Instead of calling wx.Dialog.__init__ we precreate the dialog |
... | ... | @@ -421,69 +412,60 @@ class NewMaskDialog(wx.Dialog): |
421 | 412 | |
422 | 413 | # LINE 1: Surface name |
423 | 414 | |
424 | - label_surface = wx.StaticText(self, -1, _("New mask name:")) | |
415 | + label_mask = wx.StaticText(self, -1, _("New mask name:")) | |
425 | 416 | |
426 | - default_name = const.SURFACE_NAME_PATTERN %(surface.Surface.general_index+2) | |
417 | + default_name = const.MASK_NAME_PATTERN %(mask.Mask.general_index+2) | |
427 | 418 | text = wx.TextCtrl(self, -1, "", size=(80,-1)) |
428 | 419 | text.SetHelpText(_("Name the mask to be created")) |
429 | 420 | text.SetValue(default_name) |
430 | 421 | self.text = text |
431 | 422 | |
432 | - # LINE 2: Mask of reference | |
423 | + # LINE 2: Threshold of reference | |
433 | 424 | |
434 | 425 | # Informative label |
435 | - label_mask = wx.StaticText(self, -1, _("Threshold preset:")) | |
426 | + label_thresh = wx.StaticText(self, -1, _("Threshold preset:")) | |
436 | 427 | |
437 | 428 | # Retrieve existing masks |
438 | 429 | project = prj.Project() |
439 | 430 | thresh_list = project.threshold_modes.keys() |
440 | 431 | thresh_list.sort() |
441 | - default_index = proj.threshold_modes.get_key(_("Default")) | |
432 | + default_index = thresh_list.index(_("Bone")) | |
442 | 433 | self.thresh_list = thresh_list |
443 | 434 | |
444 | 435 | # Mask selection combo |
445 | - combo_mask = wx.ComboBox(self, -1, "", choices= self.thresh_list, | |
436 | + combo_thresh = wx.ComboBox(self, -1, "", choices= self.thresh_list, | |
446 | 437 | style=wx.CB_DROPDOWN|wx.CB_READONLY) |
447 | - combo_mask.SetSelection(len(self.thresh_list)-1) | |
448 | - if sys.platform != 'win32': | |
449 | - combo_mask.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
450 | - self.combo_mask = combo_mask | |
451 | - | |
452 | - # LINE 3: Surface quality | |
453 | - label_quality = wx.StaticText(self, -1, _("Surface quality:")) | |
454 | - | |
455 | - choices = const.SURFACE_QUALITY_LIST, | |
456 | - style = wx.CB_DROPDOWN|wx.CB_READONLY | |
457 | - combo_quality = wx.ComboBox(self, -1, "", | |
458 | - choices= choices, | |
459 | - style=style) | |
460 | - combo_quality.SetSelection(3) | |
438 | + combo_thresh.SetSelection(default_index) | |
461 | 439 | if sys.platform != 'win32': |
462 | - combo_quality.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
463 | - self.combo_quality = combo_quality | |
464 | - | |
440 | + combo_thresh.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
441 | + self.combo_thresh = combo_thresh | |
442 | + | |
443 | + # LINE 3: Gradient | |
444 | + bound_min, bound_max = project.threshold_range | |
445 | + thresh_min, thresh_max = project.threshold_modes[_("Bone")] | |
446 | + original_colour = random.choice(const.MASK_COLOUR) | |
447 | + self.colour = original_colour | |
448 | + colour = [255*i for i in original_colour] | |
449 | + colour.append(100) | |
450 | + gradient = grad.GradientSlider(self, -1, int(bound_min), | |
451 | + int(bound_max), | |
452 | + int(thresh_min), int(thresh_max), | |
453 | + colour) | |
454 | + self.gradient = gradient | |
465 | 455 | |
466 | 456 | # OVERVIEW |
467 | 457 | # Sizer that joins content above |
468 | 458 | flag_link = wx.EXPAND|wx.GROW|wx.ALL |
469 | 459 | flag_button = wx.ALL | wx.EXPAND| wx.GROW |
470 | 460 | |
471 | - fixed_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=10, vgap=0) | |
461 | + fixed_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=10, vgap=10) | |
472 | 462 | fixed_sizer.AddGrowableCol(0, 1) |
473 | - fixed_sizer.AddMany([ (label_surface, 1, flag_link, 5), | |
463 | + fixed_sizer.AddMany([ (label_mask, 1, flag_link, 5), | |
474 | 464 | (text, 1, flag_button, 2), |
475 | - (label_mask, 1, flag_link, 5), | |
476 | - (combo_mask, 0, flag_button, 1), | |
477 | - (label_quality, 1, flag_link, 5), | |
478 | - (combo_quality, 0, flag_button, 1)]) | |
479 | - | |
480 | - | |
481 | - # LINES 4 and 5: Checkboxes | |
482 | - check_box_holes = wx.CheckBox(self, -1, _("Fill holes")) | |
483 | - check_box_holes.SetValue(True) | |
484 | - self.check_box_holes = check_box_holes | |
485 | - check_box_largest = wx.CheckBox(self, -1, _("Keep largest region")) | |
486 | - self.check_box_largest = check_box_largest | |
465 | + (label_thresh, 1, flag_link, 5), | |
466 | + (combo_thresh, 0, flag_button, 1)])#, | |
467 | + #(label_quality, 1, flag_link, 5), | |
468 | + #(combo_quality, 0, flag_button, 1)]) | |
487 | 469 | |
488 | 470 | # LINE 6: Buttons |
489 | 471 | |
... | ... | @@ -499,28 +481,47 @@ class NewMaskDialog(wx.Dialog): |
499 | 481 | # OVERVIEW |
500 | 482 | # Merge all sizers and checkboxes |
501 | 483 | sizer = wx.BoxSizer(wx.VERTICAL) |
502 | - sizer.Add(fixed_sizer, 0, wx.TOP|wx.RIGHT|wx.LEFT|wx.GROW|wx.EXPAND, 20) | |
503 | - sizer.Add(check_box_holes, 0, wx.RIGHT|wx.LEFT, 30) | |
504 | - sizer.Add(check_box_largest, 0, wx.RIGHT|wx.LEFT, 30) | |
505 | - sizer.Add(btnsizer, 0, wx.ALIGN_RIGHT|wx.ALL, 10) | |
484 | + sizer.Add(fixed_sizer, 0, wx.ALL|wx.GROW|wx.EXPAND, 15) | |
485 | + sizer.Add(gradient, 1, wx.BOTTOM|wx.RIGHT|wx.LEFT|wx.EXPAND|wx.GROW, 20) | |
486 | + sizer.Add(btnsizer, 0, wx.ALIGN_RIGHT|wx.BOTTOM, 10) | |
506 | 487 | |
507 | 488 | self.SetSizer(sizer) |
508 | 489 | sizer.Fit(self) |
509 | 490 | |
510 | - def GetValue(self): | |
511 | - mask_index = self.combo_mask.GetSelection() | |
512 | - surface_name = self.text.GetValue() | |
513 | - quality = const.SURFACE_QUALITY_LIST[self.combo_quality.GetSelection()] | |
514 | - fill_holes = self.check_box_holes.GetValue() | |
515 | - keep_largest = self.check_box_largest.GetValue() | |
516 | - return (mask_index, surface_name, quality, fill_holes, keep_largest) | |
517 | - | |
518 | - | |
519 | - | |
491 | + self.Bind(grad.EVT_THRESHOLD_CHANGE, self.OnSlideChanged, self.gradient) | |
492 | + self.combo_thresh.Bind(wx.EVT_COMBOBOX, self.OnComboThresh) | |
520 | 493 | |
521 | 494 | |
495 | + def OnComboThresh(self, evt): | |
496 | + import project as prj | |
497 | + proj = prj.Project() | |
498 | + (thresh_min, thresh_max) = proj.threshold_modes[evt.GetString()] | |
499 | + self.gradient.SetMinValue(thresh_min, True) | |
500 | + self.gradient.SetMaxValue(thresh_max, True) | |
522 | 501 | |
502 | + def OnSlideChanged(self, evt): | |
503 | + import project as prj | |
504 | + thresh_min = self.gradient.GetMinValue() | |
505 | + thresh_max = self.gradient.GetMaxValue() | |
506 | + thresh = (thresh_min, thresh_max) | |
507 | + proj = prj.Project() | |
508 | + if thresh in proj.threshold_modes.values(): | |
509 | + preset_name = proj.threshold_modes.get_key(thresh)[0] | |
510 | + index = self.thresh_list.index(preset_name) | |
511 | + self.combo_thresh.SetSelection(index) | |
512 | + else: | |
513 | + index = self.thresh_list.index(_("Custom")) | |
514 | + self.combo_thresh.SetSelection(index) | |
523 | 515 | |
516 | + def GetValue(self): | |
517 | + #mask_index = self.combo_mask.GetSelection() | |
518 | + mask_name = self.text.GetValue() | |
519 | + thresh_value = [self.gradient.GetMinValue(), self.gradient.GetMaxValue()] | |
520 | + #quality = const.SURFACE_QUALITY_LIST[self.combo_quality.GetSelection()] | |
521 | + #fill_holes = self.check_box_holes.GetValue() | |
522 | + #keep_largest = self.check_box_largest.GetValue() | |
523 | + #return (mask_index, surface_name, quality, fill_holes, keep_largest) | |
524 | + return mask_name, thresh_value, self.colour | |
524 | 525 | |
525 | 526 | |
526 | 527 | def InexistentPath(path): |
... | ... | @@ -595,24 +596,26 @@ def ShowAboutDialog(parent): |
595 | 596 | info.WebSite = ("http://svn.softwarepublico.gov.br/trac/invesalius") |
596 | 597 | info.License = _("GNU GPL (General Public License) version 2") |
597 | 598 | |
598 | - #info.Translators = "" | |
599 | - #info.Artists = | |
600 | 599 | info.Developers = ["Tatiana Al-Chueyr", |
601 | - "Paulo Henrique Junqueira Amorim", | |
602 | - "Thiago Franco de Moraes"] | |
603 | - #info.DocWriters = | |
600 | + "Paulo Henrique Junqueira Amorim", | |
601 | + "Thiago Franco de Moraes"] | |
602 | + | |
604 | 603 | info.Translators = ["Alex P. Natsios (EL)", |
605 | 604 | "Andreas Loupasakis (EL)", |
606 | 605 | "Cheng-Chia Tseng (ZH)", |
607 | 606 | "Dimitris Glezos (EL)", |
607 | + "Eugene Liscio (EN)", | |
608 | 608 | u"Frédéric Lopez (FR)", |
609 | 609 | "J. Javier de Lima Moreno (ES)" |
610 | 610 | "Nikos Korkakakis (EL)", |
611 | 611 | "Sebastian Hilbert (DE)"] |
612 | 612 | |
613 | + info.DocWriters = ["Eugene Liscio (EN)", | |
614 | + "Fabio Francisco da Silva (PT)"] | |
615 | + | |
613 | 616 | info.Artists = ["Otavio Henrique Junqueira Amorim"] |
614 | 617 | |
615 | - # Then we call wx.AboutBox giving its info object | |
618 | + # Then we call wx.AboutBox providing its info object | |
616 | 619 | wx.AboutBox(info) |
617 | 620 | |
618 | 621 | ... | ... |
invesalius/gui/task_slice.py
... | ... | @@ -152,12 +152,21 @@ class InnerTaskPanel(wx.Panel): |
152 | 152 | dlg.InexistentMask() |
153 | 153 | |
154 | 154 | def OnLinkNewMask(self, evt=None): |
155 | - mask_name = dlg.NewMask() | |
156 | - if mask_name: | |
157 | - ps.Publisher().sendMessage('Create new mask', mask_name) | |
155 | + dialog = dlg.NewMask() | |
158 | 156 | |
159 | - if evt: | |
160 | - evt.Skip() | |
157 | + try: | |
158 | + if dialog.ShowModal() == wx.ID_OK: | |
159 | + ok = 1 | |
160 | + else: | |
161 | + ok = 0 | |
162 | + except(wx._core.PyAssertionError): #TODO FIX: win64 | |
163 | + ok = 1 | |
164 | + | |
165 | + if ok: | |
166 | + mask_name, thresh, colour = dialog.GetValue() | |
167 | + if mask_name: | |
168 | + ps.Publisher().sendMessage('Create new mask', | |
169 | + (mask_name, thresh, colour)) | |
161 | 170 | |
162 | 171 | def GetMaskSelected(self): |
163 | 172 | return self.fold_panel.GetMaskSelected() | ... | ... |