Commit 76643a9e3602a8d1ac3bacd933812b19711dbe25
Committed by
GitHub
Exists in
master
and in
25 other branches
Merge pull request #42 from tfmoraes/measure_canvas
Add a canvas to draw over the Slice Viewer. This canvas is able to draw geometric forms, unicode text and has support to RGBA. Also, this adapts the measurement system to show the measurements using this canvas system.
Showing
7 changed files
with
570 additions
and
120 deletions
Show diff stats
invesalius/data/converters.py
... | ... | @@ -56,3 +56,25 @@ def to_vtk(n_array, spacing, slice_number, orientation): |
56 | 56 | image_copy.DeepCopy(image) |
57 | 57 | |
58 | 58 | return image_copy |
59 | + | |
60 | + | |
61 | +def np_rgba_to_vtk(n_array, spacing=(1.0, 1.0, 1.0)): | |
62 | + dy, dx, dc = n_array.shape | |
63 | + v_image = numpy_support.numpy_to_vtk(n_array.reshape(dy*dx, dc)) | |
64 | + | |
65 | + extent = (0, dx -1, 0, dy -1, 0, 0) | |
66 | + | |
67 | + # Generating the vtkImageData | |
68 | + image = vtk.vtkImageData() | |
69 | + image.SetOrigin(0, 0, 0) | |
70 | + image.SetSpacing(spacing) | |
71 | + image.SetDimensions(dx, dy, 1) | |
72 | + # SetNumberOfScalarComponents and SetScalrType were replaced by | |
73 | + # AllocateScalars | |
74 | + # image.SetNumberOfScalarComponents(1) | |
75 | + # image.SetScalarType(numpy_support.get_vtk_array_type(n_array.dtype)) | |
76 | + image.AllocateScalars(numpy_support.get_vtk_array_type(n_array.dtype), dc) | |
77 | + image.SetExtent(extent) | |
78 | + image.GetPointData().SetScalars(v_image) | |
79 | + | |
80 | + return image | ... | ... |
invesalius/data/measures.py
... | ... | @@ -5,6 +5,8 @@ import math |
5 | 5 | import random |
6 | 6 | |
7 | 7 | from wx.lib.pubsub import pub as Publisher |
8 | + | |
9 | +import numpy as np | |
8 | 10 | import vtk |
9 | 11 | |
10 | 12 | import constants as const |
... | ... | @@ -137,7 +139,7 @@ class MeasurementManager(object): |
137 | 139 | (actors, m.slice_number)) |
138 | 140 | self.current = None |
139 | 141 | |
140 | - if not m.is_shown: | |
142 | + if not m.visible: | |
141 | 143 | mr.SetVisibility(False) |
142 | 144 | if m.location == const.SURFACE: |
143 | 145 | Publisher.sendMessage('Render volume viewer') |
... | ... | @@ -307,7 +309,7 @@ class MeasurementManager(object): |
307 | 309 | def _set_visibility(self, pubsub_evt): |
308 | 310 | index, visibility = pubsub_evt.data |
309 | 311 | m, mr = self.measures[index] |
310 | - m.is_shown = visibility | |
312 | + m.visible = visibility | |
311 | 313 | mr.SetVisibility(visibility) |
312 | 314 | if m.location == const.SURFACE: |
313 | 315 | Publisher.sendMessage('Render volume viewer') |
... | ... | @@ -318,22 +320,24 @@ class MeasurementManager(object): |
318 | 320 | if self.current is None: |
319 | 321 | return |
320 | 322 | |
321 | - mr = self.current[1] | |
322 | - print "RM INC M", self.current, mr.IsComplete() | |
323 | + m, mr = self.current | |
323 | 324 | if not mr.IsComplete(): |
324 | 325 | print "---To REMOVE" |
325 | - self.measures.pop() | |
326 | + idx = self.measures._list_measures.index((m, mr)) | |
327 | + self.measures.remove((m, mr)) | |
328 | + Publisher.sendMessage("Remove GUI measurement", idx) | |
326 | 329 | actors = mr.GetActors() |
327 | 330 | slice_number = self.current[0].slice_number |
328 | - Publisher.sendMessage(('Remove actors ' + str(self.current[0].location)), | |
329 | - (actors, slice_number)) | |
331 | + if m.location == const.SURFACE: | |
332 | + Publisher.sendMessage(('Remove actors ' + str(self.current[0].location)), | |
333 | + (actors, slice_number)) | |
330 | 334 | if self.current[0].location == const.SURFACE: |
331 | 335 | Publisher.sendMessage('Render volume viewer') |
332 | 336 | else: |
333 | 337 | Publisher.sendMessage('Update slice viewer') |
334 | 338 | |
335 | - if self.measures: | |
336 | - self.measures.pop() | |
339 | + # if self.measures: | |
340 | + # self.measures.pop() | |
337 | 341 | self.current = None |
338 | 342 | |
339 | 343 | |
... | ... | @@ -349,7 +353,7 @@ class Measurement(): |
349 | 353 | self.type = const.LINEAR # ANGULAR |
350 | 354 | self.slice_number = 0 |
351 | 355 | self.points = [] |
352 | - self.is_shown = True | |
356 | + self.visible = True | |
353 | 357 | |
354 | 358 | def Load(self, info): |
355 | 359 | self.index = info["index"] |
... | ... | @@ -360,7 +364,7 @@ class Measurement(): |
360 | 364 | self.type = info["type"] |
361 | 365 | self.slice_number = info["slice_number"] |
362 | 366 | self.points = info["points"] |
363 | - self.is_shown = info["visible"] | |
367 | + self.visible = info["visible"] | |
364 | 368 | |
365 | 369 | class CirclePointRepresentation(object): |
366 | 370 | """ |
... | ... | @@ -559,12 +563,38 @@ class LinearMeasure(object): |
559 | 563 | a.GetProperty().SetOpacity(0.75) |
560 | 564 | self.text_actor = a |
561 | 565 | |
566 | + def draw_to_canvas(self, gc, canvas): | |
567 | + """ | |
568 | + Draws to an wx.GraphicsContext. | |
569 | + | |
570 | + Parameters: | |
571 | + gc: is a wx.GraphicsContext | |
572 | + canvas: the canvas it's being drawn. | |
573 | + """ | |
574 | + coord = vtk.vtkCoordinate() | |
575 | + points = [] | |
576 | + for p in self.points: | |
577 | + coord.SetValue(p) | |
578 | + cx, cy = coord.GetComputedDisplayValue(canvas.viewer.slice_data.renderer) | |
579 | + # canvas.draw_circle((cx, cy), 2.5) | |
580 | + points.append((cx, cy)) | |
581 | + | |
582 | + if len(points) > 1: | |
583 | + for (p0, p1) in zip(points[:-1:], points[1::]): | |
584 | + canvas.draw_line(p0, p1) | |
585 | + | |
586 | + txt = u"%.3f mm" % self.GetValue() | |
587 | + canvas.draw_text_box(txt, ((points[0][0]+points[1][0])/2.0, (points[0][1]+points[1][1])/2.0)) | |
588 | + | |
562 | 589 | def GetNumberOfPoints(self): |
563 | 590 | return len(self.points) |
564 | 591 | |
565 | 592 | def GetValue(self): |
566 | - p1, p2 = self.points | |
567 | - return math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2)) | |
593 | + if self.IsComplete(): | |
594 | + p1, p2 = self.points | |
595 | + return math.sqrt(vtk.vtkMath.Distance2BetweenPoints(p1, p2)) | |
596 | + else: | |
597 | + return 0.0 | |
568 | 598 | |
569 | 599 | def SetRenderer(self, renderer): |
570 | 600 | if self.point_actor1: |
... | ... | @@ -607,21 +637,22 @@ class LinearMeasure(object): |
607 | 637 | return actors |
608 | 638 | |
609 | 639 | def Remove(self): |
610 | - if self.point_actor1: | |
611 | - self.renderer.RemoveActor(self.point_actor1) | |
612 | - del self.point_actor1 | |
640 | + pass | |
641 | + # if self.point_actor1: | |
642 | + # self.renderer.RemoveActor(self.point_actor1) | |
643 | + # del self.point_actor1 | |
613 | 644 | |
614 | - if self.point_actor2: | |
615 | - self.renderer.RemoveActor(self.point_actor2) | |
616 | - del self.point_actor2 | |
645 | + # if self.point_actor2: | |
646 | + # self.renderer.RemoveActor(self.point_actor2) | |
647 | + # del self.point_actor2 | |
617 | 648 | |
618 | - if self.line_actor: | |
619 | - self.renderer.RemoveActor(self.line_actor) | |
620 | - del self.line_actor | |
649 | + # if self.line_actor: | |
650 | + # self.renderer.RemoveActor(self.line_actor) | |
651 | + # del self.line_actor | |
621 | 652 | |
622 | - if self.text_actor: | |
623 | - self.renderer.RemoveActor(self.text_actor) | |
624 | - del self.text_actor | |
653 | + # if self.text_actor: | |
654 | + # self.renderer.RemoveActor(self.text_actor) | |
655 | + # del self.text_actor | |
625 | 656 | |
626 | 657 | # def __del__(self): |
627 | 658 | # self.Remove() |
... | ... | @@ -630,7 +661,7 @@ class LinearMeasure(object): |
630 | 661 | class AngularMeasure(object): |
631 | 662 | def __init__(self, colour=(1, 0, 0), representation=None): |
632 | 663 | self.colour = colour |
633 | - self.points = [0, 0, 0] | |
664 | + self.points = [] | |
634 | 665 | self.number_of_points = 0 |
635 | 666 | self.point_actor1 = None |
636 | 667 | self.point_actor2 = None |
... | ... | @@ -658,7 +689,7 @@ class AngularMeasure(object): |
658 | 689 | |
659 | 690 | def SetPoint1(self, x, y, z): |
660 | 691 | if self.number_of_points == 0: |
661 | - self.points[0] = (x, y, z) | |
692 | + self.points.append((x, y, z)) | |
662 | 693 | self.number_of_points = 1 |
663 | 694 | self.point_actor1 = self.representation.GetRepresentation(x, y, z) |
664 | 695 | else: |
... | ... | @@ -677,7 +708,7 @@ class AngularMeasure(object): |
677 | 708 | def SetPoint2(self, x, y, z): |
678 | 709 | if self.number_of_points == 1: |
679 | 710 | self.number_of_points = 2 |
680 | - self.points[1] = (x, y, z) | |
711 | + self.points.append((x, y, z)) | |
681 | 712 | self.point_actor2 = self.representation.GetRepresentation(x, y, z) |
682 | 713 | else: |
683 | 714 | self.points[1] = (x, y, z) |
... | ... | @@ -695,7 +726,7 @@ class AngularMeasure(object): |
695 | 726 | def SetPoint3(self, x, y, z): |
696 | 727 | if self.number_of_points == 2: |
697 | 728 | self.number_of_points = 3 |
698 | - self.points[2] = (x, y, z) | |
729 | + self.points.append((x, y, z)) | |
699 | 730 | self.point_actor3 = self.representation.GetRepresentation(x, y, z) |
700 | 731 | self.CreateMeasure() |
701 | 732 | else: |
... | ... | @@ -793,11 +824,41 @@ class AngularMeasure(object): |
793 | 824 | a.GetPositionCoordinate().SetValue(x,y,z) |
794 | 825 | self.text_actor = a |
795 | 826 | |
827 | + def draw_to_canvas(self, gc, canvas): | |
828 | + """ | |
829 | + Draws to an wx.GraphicsContext. | |
830 | + | |
831 | + Parameters: | |
832 | + gc: is a wx.GraphicsContext | |
833 | + canvas: the canvas it's being drawn. | |
834 | + """ | |
835 | + | |
836 | + coord = vtk.vtkCoordinate() | |
837 | + points = [] | |
838 | + for p in self.points: | |
839 | + coord.SetValue(p) | |
840 | + cx, cy = coord.GetComputedDisplayValue(canvas.viewer.slice_data.renderer) | |
841 | + # canvas.draw_circle((cx, cy), 2.5) | |
842 | + points.append((cx, cy)) | |
843 | + | |
844 | + if len(points) > 1: | |
845 | + for (p0, p1) in zip(points[:-1:], points[1::]): | |
846 | + canvas.draw_line(p0, p1) | |
847 | + | |
848 | + if len(points) == 3: | |
849 | + txt = u"%.3f° / %.3f°" % (self.GetValue(), 360.0 - self.GetValue()) | |
850 | + | |
851 | + canvas.draw_arc(points[1], points[0], points[2]) | |
852 | + canvas.draw_text_box(txt, (points[1][0], points[1][1])) | |
853 | + | |
796 | 854 | def GetNumberOfPoints(self): |
797 | 855 | return self.number_of_points |
798 | 856 | |
799 | 857 | def GetValue(self): |
800 | - return self.CalculateAngle() | |
858 | + if self.IsComplete(): | |
859 | + return self.CalculateAngle() | |
860 | + else: | |
861 | + return 0.0 | |
801 | 862 | |
802 | 863 | def SetVisibility(self, v): |
803 | 864 | self.point_actor1.SetVisibility(v) |
... | ... | @@ -835,30 +896,35 @@ class AngularMeasure(object): |
835 | 896 | v2 = [j-i for i,j in zip(self.points[2], self.points[1])] |
836 | 897 | #print vtk.vtkMath.Normalize(v1) |
837 | 898 | #print vtk.vtkMath.Normalize(v2) |
838 | - cos = vtk.vtkMath.Dot(v1, v2)/(vtk.vtkMath.Norm(v1)*vtk.vtkMath.Norm(v2)) | |
899 | + try: | |
900 | + cos = vtk.vtkMath.Dot(v1, v2)/(vtk.vtkMath.Norm(v1)*vtk.vtkMath.Norm(v2)) | |
901 | + except ZeroDivisionError: | |
902 | + return 0.0 | |
903 | + | |
839 | 904 | angle = math.degrees(math.acos(cos)) |
840 | 905 | return angle |
841 | 906 | |
842 | 907 | def Remove(self): |
843 | - if self.point_actor1: | |
844 | - self.renderer.RemoveActor(self.point_actor1) | |
845 | - del self.point_actor1 | |
846 | - | |
847 | - if self.point_actor2: | |
848 | - self.renderer.RemoveActor(self.point_actor2) | |
849 | - del self.point_actor2 | |
850 | - | |
851 | - if self.point_actor3: | |
852 | - self.renderer.RemoveActor(self.point_actor3) | |
853 | - del self.point_actor3 | |
854 | - | |
855 | - if self.line_actor: | |
856 | - self.renderer.RemoveActor(self.line_actor) | |
857 | - del self.line_actor | |
858 | - | |
859 | - if self.text_actor: | |
860 | - self.renderer.RemoveActor(self.text_actor) | |
861 | - del self.text_actor | |
908 | + pass | |
909 | + # if self.point_actor1: | |
910 | + # self.renderer.RemoveActor(self.point_actor1) | |
911 | + # del self.point_actor1 | |
912 | + | |
913 | + # if self.point_actor2: | |
914 | + # self.renderer.RemoveActor(self.point_actor2) | |
915 | + # del self.point_actor2 | |
916 | + | |
917 | + # if self.point_actor3: | |
918 | + # self.renderer.RemoveActor(self.point_actor3) | |
919 | + # del self.point_actor3 | |
920 | + | |
921 | + # if self.line_actor: | |
922 | + # self.renderer.RemoveActor(self.line_actor) | |
923 | + # del self.line_actor | |
924 | + | |
925 | + # if self.text_actor: | |
926 | + # self.renderer.RemoveActor(self.text_actor) | |
927 | + # del self.text_actor | |
862 | 928 | |
863 | 929 | def SetRenderer(self, renderer): |
864 | 930 | if self.point_actor1: | ... | ... |
invesalius/data/slice_data.py
invesalius/data/styles.py
... | ... | @@ -354,6 +354,7 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): |
354 | 354 | |
355 | 355 | self.measures = MeasureData() |
356 | 356 | self.selected = None |
357 | + self.creating = None | |
357 | 358 | |
358 | 359 | self._type = const.LINEAR |
359 | 360 | |
... | ... | @@ -377,25 +378,57 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): |
377 | 378 | self._bind_events() |
378 | 379 | |
379 | 380 | def _bind_events(self): |
380 | - self.AddObserver("LeftButtonPressEvent", self.OnInsertLinearMeasurePoint) | |
381 | + self.AddObserver("LeftButtonPressEvent", self.OnInsertMeasurePoint) | |
381 | 382 | self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseMeasurePoint) |
382 | 383 | self.AddObserver("MouseMoveEvent", self.OnMoveMeasurePoint) |
384 | + self.AddObserver("LeaveEvent", self.OnLeaveMeasureInteractor) | |
383 | 385 | |
384 | - def OnInsertLinearMeasurePoint(self, obj, evt): | |
386 | + def OnInsertMeasurePoint(self, obj, evt): | |
385 | 387 | slice_number = self.slice_data.number |
386 | 388 | x, y, z = self._get_pos_clicked() |
389 | + mx, my = self.viewer.interactor.GetEventPosition() | |
387 | 390 | |
388 | - selected = self._verify_clicked(x, y, z) | |
391 | + if self.selected: | |
392 | + self.selected = None | |
393 | + self.viewer.scroll_enabled = True | |
394 | + return | |
395 | + | |
396 | + if self.creating: | |
397 | + n, m, mr = self.creating | |
398 | + if mr.IsComplete(): | |
399 | + self.creating = None | |
400 | + self.viewer.scroll_enabled = True | |
401 | + else: | |
402 | + Publisher.sendMessage("Add measurement point", | |
403 | + ((x, y, z), self._type, | |
404 | + ORIENTATIONS[self.orientation], | |
405 | + slice_number, self.radius)) | |
406 | + n = len(m.points)-1 | |
407 | + self.creating = n, m, mr | |
408 | + Publisher.sendMessage('Reload actual slice %s' % self.orientation) | |
409 | + self.viewer.scroll_enabled = False | |
410 | + return | |
411 | + | |
412 | + selected = self._verify_clicked_display(mx, my) | |
389 | 413 | if selected: |
390 | 414 | self.selected = selected |
415 | + self.viewer.scroll_enabled = False | |
391 | 416 | else: |
392 | 417 | if self.picker.GetViewProp(): |
393 | 418 | renderer = self.viewer.slice_data.renderer |
394 | 419 | Publisher.sendMessage("Add measurement point", |
395 | - ((x, y,z), self._type, | |
420 | + ((x, y, z), self._type, | |
421 | + ORIENTATIONS[self.orientation], | |
422 | + slice_number, self.radius)) | |
423 | + Publisher.sendMessage("Add measurement point", | |
424 | + ((x, y, z), self._type, | |
396 | 425 | ORIENTATIONS[self.orientation], |
397 | 426 | slice_number, self.radius)) |
427 | + | |
428 | + n, (m, mr) = 1, self.measures.measures[self._ori][slice_number][-1] | |
429 | + self.creating = n, m, mr | |
398 | 430 | Publisher.sendMessage('Reload actual slice %s' % self.orientation) |
431 | + self.viewer.scroll_enabled = False | |
399 | 432 | |
400 | 433 | def OnReleaseMeasurePoint(self, obj, evt): |
401 | 434 | if self.selected: |
... | ... | @@ -405,16 +438,41 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): |
405 | 438 | Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z))) |
406 | 439 | Publisher.sendMessage('Reload actual slice %s' % self.orientation) |
407 | 440 | self.selected = None |
441 | + self.viewer.scroll_enabled = True | |
408 | 442 | |
409 | 443 | def OnMoveMeasurePoint(self, obj, evt): |
444 | + x, y, z = self._get_pos_clicked() | |
410 | 445 | if self.selected: |
411 | 446 | n, m, mr = self.selected |
412 | - x, y, z = self._get_pos_clicked() | |
413 | 447 | idx = self.measures._list_measures.index((m, mr)) |
414 | 448 | Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z))) |
415 | 449 | |
416 | 450 | Publisher.sendMessage('Reload actual slice %s' % self.orientation) |
417 | 451 | |
452 | + elif self.creating: | |
453 | + n, m, mr = self.creating | |
454 | + idx = self.measures._list_measures.index((m, mr)) | |
455 | + Publisher.sendMessage('Change measurement point position', (idx, n, (x, y, z))) | |
456 | + | |
457 | + Publisher.sendMessage('Reload actual slice %s' % self.orientation) | |
458 | + | |
459 | + else: | |
460 | + mx, my = self.viewer.interactor.GetEventPosition() | |
461 | + if self._verify_clicked_display(mx, my): | |
462 | + self.viewer.interactor.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) | |
463 | + else: | |
464 | + self.viewer.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) | |
465 | + | |
466 | + def OnLeaveMeasureInteractor(self, obj, evt): | |
467 | + if self.creating or self.selected: | |
468 | + n, m, mr = self.creating | |
469 | + if not mr.IsComplete(): | |
470 | + Publisher.sendMessage("Remove incomplete measurements") | |
471 | + self.creating = None | |
472 | + self.selected = None | |
473 | + Publisher.sendMessage('Update slice viewer') | |
474 | + self.viewer.scroll_enabled = True | |
475 | + | |
418 | 476 | def CleanUp(self): |
419 | 477 | self.picker.PickFromListOff() |
420 | 478 | Publisher.sendMessage("Remove incomplete measurements") |
... | ... | @@ -449,6 +507,21 @@ class LinearMeasureInteractorStyle(DefaultInteractorStyle): |
449 | 507 | return (n, m, mr) |
450 | 508 | return None |
451 | 509 | |
510 | + def _verify_clicked_display(self, x, y, max_dist=5.0): | |
511 | + slice_number = self.slice_data.number | |
512 | + max_dist = max_dist**2 | |
513 | + coord = vtk.vtkCoordinate() | |
514 | + if slice_number in self.measures.measures[self._ori]: | |
515 | + for m, mr in self.measures.measures[self._ori][slice_number]: | |
516 | + if mr.IsComplete(): | |
517 | + for n, p in enumerate(m.points): | |
518 | + coord.SetValue(p) | |
519 | + cx, cy = coord.GetComputedDisplayValue(self.viewer.slice_data.renderer) | |
520 | + dist = ((cx-x)**2 + (cy-y)**2) | |
521 | + if dist <= max_dist: | |
522 | + return (n, m, mr) | |
523 | + return None | |
524 | + | |
452 | 525 | class AngularMeasureInteractorStyle(LinearMeasureInteractorStyle): |
453 | 526 | def __init__(self, viewer): |
454 | 527 | LinearMeasureInteractorStyle.__init__(self, viewer) | ... | ... |
invesalius/data/viewer_slice.py
... | ... | @@ -23,7 +23,7 @@ import collections |
23 | 23 | import itertools |
24 | 24 | import tempfile |
25 | 25 | |
26 | -import numpy | |
26 | +import numpy as np | |
27 | 27 | |
28 | 28 | import vtk |
29 | 29 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
... | ... | @@ -46,6 +46,8 @@ import project |
46 | 46 | import slice_data as sd |
47 | 47 | import utils |
48 | 48 | |
49 | +from data import converters | |
50 | + | |
49 | 51 | from data import measures |
50 | 52 | |
51 | 53 | ID_TO_TOOL_ITEM = {} |
... | ... | @@ -87,7 +89,7 @@ class ContourMIPConfig(wx.Panel): |
87 | 89 | " order to compound the" |
88 | 90 | " visualization instead of" |
89 | 91 | " ascending order."))) |
90 | - | |
92 | + | |
91 | 93 | txt_mip_size = wx.StaticText(self, -1, _("Number of slices"), style=wx.ALIGN_CENTER_HORIZONTAL) |
92 | 94 | self.txt_mip_border = wx.StaticText(self, -1, _("Sharpness")) |
93 | 95 | |
... | ... | @@ -111,7 +113,7 @@ class ContourMIPConfig(wx.Panel): |
111 | 113 | self.mip_size_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPSize) |
112 | 114 | self.border_spin.Bind(wx.EVT_SPINCTRL, self.OnSetMIPBorder) |
113 | 115 | self.inverted.Bind(wx.EVT_CHECKBOX, self.OnCheckInverted) |
114 | - | |
116 | + | |
115 | 117 | Publisher.subscribe(self._set_projection_type, 'Set projection type') |
116 | 118 | |
117 | 119 | def OnSetMIPSize(self, evt): |
... | ... | @@ -134,7 +136,7 @@ class ContourMIPConfig(wx.Panel): |
134 | 136 | self.inverted.Enable() |
135 | 137 | else: |
136 | 138 | self.inverted.Disable() |
137 | - | |
139 | + | |
138 | 140 | if tprojection in (const.PROJECTION_CONTOUR_MIP, |
139 | 141 | const.PROJECTION_CONTOUR_MIDA): |
140 | 142 | self.border_spin.Enable() |
... | ... | @@ -144,6 +146,262 @@ class ContourMIPConfig(wx.Panel): |
144 | 146 | self.txt_mip_border.Disable() |
145 | 147 | |
146 | 148 | |
149 | +class CanvasRendererCTX: | |
150 | + def __init__(self, viewer): | |
151 | + self.viewer = viewer | |
152 | + self.canvas_renderer = viewer.slice_data.canvas_renderer | |
153 | + self._size = self.canvas_renderer.GetSize() | |
154 | + self.gc = None | |
155 | + self._init_canvas() | |
156 | + viewer.slice_data.renderer.AddObserver("StartEvent", self.OnPaint) | |
157 | + | |
158 | + def _init_canvas(self): | |
159 | + w, h = self._size | |
160 | + self._array = np.zeros((h, w, 4), dtype=np.uint8) | |
161 | + | |
162 | + self._cv_image = converters.np_rgba_to_vtk(self._array) | |
163 | + | |
164 | + self.mapper = vtk.vtkImageMapper() | |
165 | + self.mapper.SetInputData(self._cv_image) | |
166 | + self.mapper.SetColorWindow(255) | |
167 | + self.mapper.SetColorLevel(128) | |
168 | + | |
169 | + self.actor = vtk.vtkActor2D() | |
170 | + self.actor.SetPosition(0, 0) | |
171 | + self.actor.SetMapper(self.mapper) | |
172 | + self.actor.GetProperty().SetOpacity(0.99) | |
173 | + | |
174 | + self.canvas_renderer.AddActor2D(self.actor) | |
175 | + | |
176 | + self.rgb = np.zeros((h, w, 3), dtype=np.uint8) | |
177 | + self.alpha = np.zeros((h, w, 1), dtype=np.uint8) | |
178 | + | |
179 | + self.bitmap = wx.EmptyBitmapRGBA(w, h) | |
180 | + self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha) | |
181 | + | |
182 | + def _resize_canvas(self, w, h): | |
183 | + self._array = np.zeros((h, w, 4), dtype=np.uint8) | |
184 | + self._cv_image = converters.np_rgba_to_vtk(self._array) | |
185 | + self.mapper.SetInputData(self._cv_image) | |
186 | + self.mapper.Update() | |
187 | + | |
188 | + self.rgb = np.zeros((h, w, 3), dtype=np.uint8) | |
189 | + self.alpha = np.zeros((h, w, 1), dtype=np.uint8) | |
190 | + | |
191 | + self.bitmap = wx.EmptyBitmapRGBA(w, h) | |
192 | + self.image = wx.ImageFromBuffer(w, h, self.rgb, self.alpha) | |
193 | + | |
194 | + def OnPaint(self, evt, obj): | |
195 | + self._array[:] = 0 | |
196 | + size = self.canvas_renderer.GetSize() | |
197 | + w, h = size | |
198 | + if self._size != size: | |
199 | + self._size = size | |
200 | + self._resize_canvas(w, h) | |
201 | + | |
202 | + coord = vtk.vtkCoordinate() | |
203 | + | |
204 | + self.image.SetDataBuffer(self.rgb) | |
205 | + self.image.SetAlphaBuffer(self.alpha) | |
206 | + self.image.Clear() | |
207 | + gc = wx.GraphicsContext.Create(self.image) | |
208 | + gc.SetAntialiasMode(0) | |
209 | + | |
210 | + self.gc = gc | |
211 | + | |
212 | + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) | |
213 | + # font.SetWeight(wx.BOLD) | |
214 | + font = gc.CreateFont(font, (0, 0, 255)) | |
215 | + gc.SetFont(font) | |
216 | + | |
217 | + pen = wx.Pen(wx.Colour(255, 0, 0, 128), 2, wx.SOLID) | |
218 | + brush = wx.Brush(wx.Colour(0, 255, 0, 128)) | |
219 | + gc.SetPen(pen) | |
220 | + gc.SetBrush(brush) | |
221 | + gc.Scale(1, -1) | |
222 | + | |
223 | + modified = False | |
224 | + for (m, mr) in self.viewer.measures.get(self.viewer.orientation, self.viewer.slice_data.number): | |
225 | + if not m.visible: | |
226 | + continue | |
227 | + mr.draw_to_canvas(gc, self) | |
228 | + modified = True | |
229 | + | |
230 | + gc.Destroy() | |
231 | + | |
232 | + self.gc = None | |
233 | + | |
234 | + if modified: | |
235 | + self.bitmap = self.image.ConvertToBitmap() | |
236 | + self.bitmap.CopyToBuffer(self._array, wx.BitmapBufferFormat_RGBA) | |
237 | + | |
238 | + self._cv_image.Modified() | |
239 | + | |
240 | + def draw_line(self, pos0, pos1, arrow_start=False, arrow_end=False, colour=(255, 0, 0, 128), width=2, style=wx.SOLID): | |
241 | + """ | |
242 | + Draw a line from pos0 to pos1 | |
243 | + """ | |
244 | + if self.gc is None: | |
245 | + return None | |
246 | + gc = self.gc | |
247 | + | |
248 | + p0x, p0y = pos0 | |
249 | + p1x, p1y = pos1 | |
250 | + | |
251 | + p0y = -p0y | |
252 | + p1y = -p1y | |
253 | + | |
254 | + pen = wx.Pen(wx.Colour(*colour), width, wx.SOLID) | |
255 | + pen.SetCap(wx.CAP_BUTT) | |
256 | + gc.SetPen(pen) | |
257 | + | |
258 | + path = gc.CreatePath() | |
259 | + path.MoveToPoint(p0x, p0y) | |
260 | + path.AddLineToPoint(p1x, p1y) | |
261 | + gc.StrokePath(path) | |
262 | + | |
263 | + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) | |
264 | + font = gc.CreateFont(font) | |
265 | + gc.SetFont(font) | |
266 | + w, h = gc.GetTextExtent("M") | |
267 | + | |
268 | + p0 = np.array((p0x, p0y)) | |
269 | + p3 = np.array((p1x, p1y)) | |
270 | + if arrow_start: | |
271 | + v = p3 - p0 | |
272 | + v = v / np.linalg.norm(v) | |
273 | + iv = np.array((v[1], -v[0])) | |
274 | + p1 = p0 + w*v + iv*w/2.0 | |
275 | + p2 = p0 + w*v + (-iv)*w/2.0 | |
276 | + | |
277 | + path = gc.CreatePath() | |
278 | + path.MoveToPoint(p0) | |
279 | + path.AddLineToPoint(p1) | |
280 | + path.MoveToPoint(p0) | |
281 | + path.AddLineToPoint(p2) | |
282 | + gc.StrokePath(path) | |
283 | + | |
284 | + if arrow_end: | |
285 | + v = p3 - p0 | |
286 | + v = v / np.linalg.norm(v) | |
287 | + iv = np.array((v[1], -v[0])) | |
288 | + p1 = p3 - w*v + iv*w/2.0 | |
289 | + p2 = p3 - w*v + (-iv)*w/2.0 | |
290 | + | |
291 | + path = gc.CreatePath() | |
292 | + path.MoveToPoint(p3) | |
293 | + path.AddLineToPoint(p1) | |
294 | + path.MoveToPoint(p3) | |
295 | + path.AddLineToPoint(p2) | |
296 | + gc.StrokePath(path) | |
297 | + | |
298 | + def draw_circle(self, center, radius, width=2, line_colour=(255, 0, 0, 128), fill_colour=(0, 0, 0, 0)): | |
299 | + """ | |
300 | + Draw a circle centered at center with the given radius. | |
301 | + | |
302 | + Params: | |
303 | + center: (x, y) position. | |
304 | + radius: float number. | |
305 | + width: line width. | |
306 | + line_colour: RGBA line colour | |
307 | + fill_colour: RGBA fill colour. | |
308 | + """ | |
309 | + if self.gc is None: | |
310 | + return None | |
311 | + gc = self.gc | |
312 | + | |
313 | + pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID) | |
314 | + gc.SetPen(pen) | |
315 | + | |
316 | + brush = wx.Brush(wx.Colour(*fill_colour)) | |
317 | + gc.SetBrush(brush) | |
318 | + | |
319 | + cx, cy = center | |
320 | + cy = -cy | |
321 | + | |
322 | + path = gc.CreatePath() | |
323 | + path.AddCircle(cx, cy, 2.5) | |
324 | + gc.StrokePath(path) | |
325 | + gc.FillPath(path) | |
326 | + | |
327 | + | |
328 | + def draw_text_box(self, text, pos, font=None, txt_colour=(255, 255, 255), bg_colour=(128, 128, 128, 128), border=5): | |
329 | + """ | |
330 | + Draw text inside a text box. | |
331 | + | |
332 | + Params: | |
333 | + text: an unicode text. | |
334 | + pos: (x, y) position. | |
335 | + font: if None it'll use the default gui font. | |
336 | + txt_colour: RGB text colour | |
337 | + bg_colour: RGBA box colour | |
338 | + border: the border size. | |
339 | + """ | |
340 | + if self.gc is None: | |
341 | + return None | |
342 | + gc = self.gc | |
343 | + | |
344 | + if font is None: | |
345 | + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) | |
346 | + | |
347 | + font = gc.CreateFont(font, txt_colour) | |
348 | + gc.SetFont(font) | |
349 | + w, h = gc.GetTextExtent(text) | |
350 | + | |
351 | + px, py = pos | |
352 | + py = -py | |
353 | + | |
354 | + # Drawing the box | |
355 | + cw, ch = w + border * 2, h + border * 2 | |
356 | + gc.SetBrush(wx.Brush(bg_colour)) | |
357 | + gc.SetPen(wx.Pen(bg_colour)) | |
358 | + gc.DrawRectangle(px, py, cw, ch) | |
359 | + | |
360 | + tpx, tpy = px + border, py + border | |
361 | + gc.DrawText(text, tpx, tpy) | |
362 | + | |
363 | + def draw_arc(self, center, p0, p1, line_colour=(255, 0, 0, 128), width=2): | |
364 | + """ | |
365 | + Draw an arc passing in p0 and p1 centered at center. | |
366 | + | |
367 | + Params: | |
368 | + center: (x, y) center of the arc. | |
369 | + p0: (x, y). | |
370 | + p1: (x, y). | |
371 | + line_colour: RGBA line colour. | |
372 | + width: width of the line. | |
373 | + """ | |
374 | + if self.gc is None: | |
375 | + return None | |
376 | + gc = self.gc | |
377 | + pen = wx.Pen(wx.Colour(*line_colour), width, wx.SOLID) | |
378 | + gc.SetPen(pen) | |
379 | + | |
380 | + c = np.array(center) | |
381 | + v0 = np.array(p0) - c | |
382 | + v1 = np.array(p1) - c | |
383 | + | |
384 | + c[1] = -c[1] | |
385 | + v0[1] = -v0[1] | |
386 | + v1[1] = -v1[1] | |
387 | + | |
388 | + s0 = np.linalg.norm(v0) | |
389 | + s1 = np.linalg.norm(v1) | |
390 | + | |
391 | + a0 = np.arctan2(v0[1] , v0[0]) | |
392 | + a1 = np.arctan2(v1[1] , v1[0]) | |
393 | + | |
394 | + if (a1 - a0) % (np.pi*2) < (a0 - a1) % (np.pi*2): | |
395 | + sa = a0 | |
396 | + ea = a1 | |
397 | + else: | |
398 | + sa = a1 | |
399 | + ea = a0 | |
400 | + | |
401 | + path = gc.CreatePath() | |
402 | + path.AddArc((c[0], c[1]), min(s0, s1), sa, ea) | |
403 | + gc.StrokePath(path) | |
404 | + | |
147 | 405 | |
148 | 406 | class Viewer(wx.Panel): |
149 | 407 | |
... | ... | @@ -157,7 +415,7 @@ class Viewer(wx.Panel): |
157 | 415 | |
158 | 416 | self._number_slices = const.PROJECTION_MIP_SIZE |
159 | 417 | self._mip_inverted = False |
160 | - | |
418 | + | |
161 | 419 | self.style = None |
162 | 420 | self.last_position_mouse_move = () |
163 | 421 | self.state = const.STATE_DEFAULT |
... | ... | @@ -178,6 +436,7 @@ class Viewer(wx.Panel): |
178 | 436 | |
179 | 437 | self.orientation = orientation |
180 | 438 | self.slice_number = 0 |
439 | + self.scroll_enabled = True | |
181 | 440 | |
182 | 441 | self.__init_gui() |
183 | 442 | |
... | ... | @@ -230,7 +489,7 @@ class Viewer(wx.Panel): |
230 | 489 | self.menu.caller = self |
231 | 490 | self.PopupMenu(self.menu) |
232 | 491 | evt.Skip() |
233 | - | |
492 | + | |
234 | 493 | def SetPopupMenu(self, menu): |
235 | 494 | self.menu = menu |
236 | 495 | |
... | ... | @@ -316,14 +575,14 @@ class Viewer(wx.Panel): |
316 | 575 | (slc.window_width, slc.window_level)) |
317 | 576 | |
318 | 577 | def SetWLText(self, window_width, window_level): |
319 | - value = STR_WL%(window_level, window_width) | |
578 | + value = STR_WL%(window_level, window_width) | |
320 | 579 | if (self.wl_text): |
321 | 580 | self.wl_text.SetValue(value) |
322 | 581 | #self.interactor.Render() |
323 | 582 | |
324 | 583 | def EnableText(self): |
325 | 584 | if not (self.wl_text): |
326 | - proj = project.Project() | |
585 | + proj = project.Project() | |
327 | 586 | colour = const.ORIENTATION_COLOUR[self.orientation] |
328 | 587 | |
329 | 588 | # Window & Level text |
... | ... | @@ -336,7 +595,7 @@ class Viewer(wx.Panel): |
336 | 595 | values = [_('P'), _('A'), _('T'), _('B')] |
337 | 596 | else: |
338 | 597 | values = [_('R'), _('L'), _('T'), _('B')] |
339 | - | |
598 | + | |
340 | 599 | left_text = self.left_text = vtku.TextZero() |
341 | 600 | left_text.ShadowOff() |
342 | 601 | left_text.SetColour(colour) |
... | ... | @@ -415,30 +674,30 @@ class Viewer(wx.Panel): |
415 | 674 | |
416 | 675 | elif(croll > 91 and croll <= 135): |
417 | 676 | self.RenderTextDirection([_("LP"), _("AL"), _("RA"), _("PR")]) |
418 | - | |
677 | + | |
419 | 678 | elif(croll > 135 and croll <= 177): |
420 | 679 | self.RenderTextDirection([_("PL"), _("LA"), _("AR"), _("RP")]) |
421 | - | |
680 | + | |
422 | 681 | elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): |
423 | 682 | self.RenderTextDirection([_("P"), _("L"), _("A"), _("R")]) |
424 | - | |
683 | + | |
425 | 684 | elif(croll >= -177 and croll <= -133): |
426 | 685 | self.RenderTextDirection([_("PR"), _("LP"), _("AL"), _("RA")]) |
427 | - | |
686 | + | |
428 | 687 | elif(croll >= -132 and croll <= -101): |
429 | 688 | self.RenderTextDirection([_("RP"), _("PL"), _("LA"), _("AR")]) |
430 | 689 | |
431 | 690 | elif(croll >= -101 and croll <= -87): |
432 | 691 | self.RenderTextDirection([_("R"), _("P"), _("L"), _("A")]) |
433 | - | |
692 | + | |
434 | 693 | elif(croll >= -86 and croll <= -42): |
435 | 694 | self.RenderTextDirection([_("RA"), _("PR"), _("LP"), _("AL")]) |
436 | - | |
695 | + | |
437 | 696 | elif(croll >= -41 and croll <= -2): |
438 | 697 | self.RenderTextDirection([_("AR"), _("RP"), _("PL"), _("LA")]) |
439 | 698 | |
440 | 699 | elif(self.orientation == "CORONAL"): |
441 | - | |
700 | + | |
442 | 701 | if (croll >= -2 and croll <= 1): |
443 | 702 | self.RenderTextDirection([_("T"), _("R"), _("B"), _("L")]) |
444 | 703 | |
... | ... | @@ -453,25 +712,25 @@ class Viewer(wx.Panel): |
453 | 712 | |
454 | 713 | elif(croll > 91 and croll <= 135): |
455 | 714 | self.RenderTextDirection([_("LB"), _("TL"), _("RT"), _("BR")]) |
456 | - | |
715 | + | |
457 | 716 | elif(croll > 135 and croll <= 177): |
458 | 717 | self.RenderTextDirection([_("BL"), _("LT"), _("TR"), _("RB")]) |
459 | - | |
718 | + | |
460 | 719 | elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): |
461 | 720 | self.RenderTextDirection([_("B"), _("L"), _("T"), _("R")]) |
462 | - | |
721 | + | |
463 | 722 | elif(croll >= -177 and croll <= -133): |
464 | 723 | self.RenderTextDirection([_("BR"), _("LB"), _("TL"), _("RT")]) |
465 | - | |
724 | + | |
466 | 725 | elif(croll >= -132 and croll <= -101): |
467 | 726 | self.RenderTextDirection([_("RB"), _("BL"), _("LT"), _("TR")]) |
468 | 727 | |
469 | 728 | elif(croll >= -101 and croll <= -87): |
470 | 729 | self.RenderTextDirection([_("R"), _("B"), _("L"), _("T")]) |
471 | - | |
730 | + | |
472 | 731 | elif(croll >= -86 and croll <= -42): |
473 | 732 | self.RenderTextDirection([_("RT"), _("BR"), _("LB"), _("TL")]) |
474 | - | |
733 | + | |
475 | 734 | elif(croll >= -41 and croll <= -2): |
476 | 735 | self.RenderTextDirection([_("TR"), _("RB"), _("BL"), _("LT")]) |
477 | 736 | |
... | ... | @@ -479,13 +738,13 @@ class Viewer(wx.Panel): |
479 | 738 | |
480 | 739 | if(croll >= -101 and croll <= -87): |
481 | 740 | self.RenderTextDirection([_("T"), _("P"), _("B"), _("A")]) |
482 | - | |
741 | + | |
483 | 742 | elif(croll >= -86 and croll <= -42): |
484 | 743 | self.RenderTextDirection([_("TA"), _("PT"), _("BP"), _("AB")]) |
485 | - | |
744 | + | |
486 | 745 | elif(croll >= -41 and croll <= -2): |
487 | 746 | self.RenderTextDirection([_("AT"), _("TP"), _("PB"), _("BA")]) |
488 | - | |
747 | + | |
489 | 748 | elif (croll >= -2 and croll <= 1): |
490 | 749 | self.RenderTextDirection([_("A"), _("T"), _("P"), _("B")]) |
491 | 750 | |
... | ... | @@ -500,16 +759,16 @@ class Viewer(wx.Panel): |
500 | 759 | |
501 | 760 | elif(croll > 91 and croll <= 135): |
502 | 761 | self.RenderTextDirection([_("BP"), _("AB"), _("TA"), _("PT")]) |
503 | - | |
762 | + | |
504 | 763 | elif(croll > 135 and croll <= 177): |
505 | 764 | self.RenderTextDirection([_("PB"), _("BA"), _("AT"), _("TP")]) |
506 | - | |
765 | + | |
507 | 766 | elif(croll >= -180 and croll <= -178) or (croll < 180 and croll > 177): |
508 | 767 | self.RenderTextDirection([_("P"), _("B"), _("A"), _("T")]) |
509 | - | |
768 | + | |
510 | 769 | elif(croll >= -177 and croll <= -133): |
511 | 770 | self.RenderTextDirection([_("PT"), _("BP"), _("AB"), _("TA")]) |
512 | - | |
771 | + | |
513 | 772 | elif(croll >= -132 and croll <= -101): |
514 | 773 | self.RenderTextDirection([_("TP"), _("PB"), _("BA"), _("AT")]) |
515 | 774 | |
... | ... | @@ -544,12 +803,12 @@ class Viewer(wx.Panel): |
544 | 803 | def Navigation(self, pubsub_evt): |
545 | 804 | # Get point from base change |
546 | 805 | x, y, z = pubsub_evt.data |
547 | - coord_cross = x, y, z | |
806 | + coord_cross = x, y, z | |
548 | 807 | position = self.slice_data.actor.GetInput().FindPoint(x, y, z) |
549 | 808 | coord_cross = self.slice_data.actor.GetInput().GetPoint(position) |
550 | - coord = self.calcultate_scroll_position(position) | |
809 | + coord = self.calcultate_scroll_position(position) | |
551 | 810 | Publisher.sendMessage('Update cross position', coord_cross) |
552 | - | |
811 | + | |
553 | 812 | self.ScrollSlice(coord) |
554 | 813 | self.interactor.Render() |
555 | 814 | |
... | ... | @@ -574,7 +833,7 @@ class Viewer(wx.Panel): |
574 | 833 | #for slice_data in self.slice_data_list: |
575 | 834 | #if slice_data.renderer is render: |
576 | 835 | #return slice_data |
577 | - # WARN: Return the only slice_data used in this slice_viewer. | |
836 | + # WARN: Return the only slice_data used in this slice_viewer. | |
578 | 837 | return self.slice_data |
579 | 838 | |
580 | 839 | def calcultate_scroll_position(self, position): |
... | ... | @@ -701,7 +960,7 @@ class Viewer(wx.Panel): |
701 | 960 | 'Hide text actors on viewers') |
702 | 961 | Publisher.subscribe(self.OnExportPicture,'Export picture to file') |
703 | 962 | Publisher.subscribe(self.SetDefaultCursor, 'Set interactor default cursor') |
704 | - | |
963 | + | |
705 | 964 | Publisher.subscribe(self.AddActors, 'Add actors ' + str(ORIENTATIONS[self.orientation])) |
706 | 965 | Publisher.subscribe(self.RemoveActors, 'Remove actors ' + str(ORIENTATIONS[self.orientation])) |
707 | 966 | Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') |
... | ... | @@ -727,11 +986,11 @@ class Viewer(wx.Panel): |
727 | 986 | |
728 | 987 | def SetDefaultCursor(self, pusub_evt): |
729 | 988 | self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) |
730 | - | |
989 | + | |
731 | 990 | def OnExportPicture(self, pubsub_evt): |
732 | 991 | Publisher.sendMessage('Begin busy cursor') |
733 | 992 | view_prop_list = [] |
734 | - view_prop_list.append(self.slice_data.box_actor) | |
993 | + view_prop_list.append(self.slice_data.box_actor) | |
735 | 994 | self.slice_data.renderer.RemoveViewProp(self.slice_data.box_actor) |
736 | 995 | |
737 | 996 | id, filename, filetype = pubsub_evt.data |
... | ... | @@ -793,7 +1052,7 @@ class Viewer(wx.Panel): |
793 | 1052 | def CloseProject(self): |
794 | 1053 | for slice_data in self.slice_data_list: |
795 | 1054 | del slice_data |
796 | - | |
1055 | + | |
797 | 1056 | self.slice_data_list = [] |
798 | 1057 | self.layout = (1, 1) |
799 | 1058 | self.orientation_texts = [] |
... | ... | @@ -805,10 +1064,10 @@ class Viewer(wx.Panel): |
805 | 1064 | def OnSetInteractorStyle(self, pubsub_evt): |
806 | 1065 | state = pubsub_evt.data |
807 | 1066 | self.SetInteractorStyle(state) |
808 | - | |
1067 | + | |
809 | 1068 | if (state != const.SLICE_STATE_EDITOR): |
810 | 1069 | Publisher.sendMessage('Set interactor default cursor') |
811 | - | |
1070 | + | |
812 | 1071 | def __bind_events_wx(self): |
813 | 1072 | self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar) |
814 | 1073 | self.scroll.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScrollBarRelease) |
... | ... | @@ -896,6 +1155,8 @@ class Viewer(wx.Panel): |
896 | 1155 | self.cam = self.slice_data.renderer.GetActiveCamera() |
897 | 1156 | self.__build_cross_lines() |
898 | 1157 | |
1158 | + canvas = CanvasRendererCTX(self) | |
1159 | + | |
899 | 1160 | # Set the slice number to the last slice to ensure the camera if far |
900 | 1161 | # enough to show all slices. |
901 | 1162 | self.set_slice_number(max_slice_number - 1) |
... | ... | @@ -958,13 +1219,20 @@ class Viewer(wx.Panel): |
958 | 1219 | renderer.SetLayer(0) |
959 | 1220 | cam = renderer.GetActiveCamera() |
960 | 1221 | |
1222 | + canvas_renderer = vtk.vtkRenderer() | |
1223 | + canvas_renderer.SetLayer(1) | |
1224 | + canvas_renderer.SetActiveCamera(cam) | |
1225 | + canvas_renderer.SetInteractive(0) | |
1226 | + canvas_renderer.PreserveDepthBufferOn() | |
1227 | + | |
961 | 1228 | overlay_renderer = vtk.vtkRenderer() |
962 | - overlay_renderer.SetLayer(1) | |
1229 | + overlay_renderer.SetLayer(2) | |
963 | 1230 | overlay_renderer.SetActiveCamera(cam) |
964 | 1231 | overlay_renderer.SetInteractive(0) |
965 | - | |
966 | - self.interactor.GetRenderWindow().SetNumberOfLayers(2) | |
1232 | + | |
1233 | + self.interactor.GetRenderWindow().SetNumberOfLayers(3) | |
967 | 1234 | self.interactor.GetRenderWindow().AddRenderer(overlay_renderer) |
1235 | + self.interactor.GetRenderWindow().AddRenderer(canvas_renderer) | |
968 | 1236 | self.interactor.GetRenderWindow().AddRenderer(renderer) |
969 | 1237 | |
970 | 1238 | actor = vtk.vtkImageActor() |
... | ... | @@ -974,12 +1242,14 @@ class Viewer(wx.Panel): |
974 | 1242 | slice_data = sd.SliceData() |
975 | 1243 | slice_data.SetOrientation(self.orientation) |
976 | 1244 | slice_data.renderer = renderer |
1245 | + slice_data.canvas_renderer = canvas_renderer | |
977 | 1246 | slice_data.overlay_renderer = overlay_renderer |
978 | 1247 | slice_data.actor = actor |
979 | 1248 | slice_data.SetBorderStyle(sd.BORDER_ALL) |
980 | 1249 | renderer.AddActor(actor) |
981 | 1250 | renderer.AddActor(slice_data.text.actor) |
982 | 1251 | renderer.AddViewProp(slice_data.box_actor) |
1252 | + | |
983 | 1253 | return slice_data |
984 | 1254 | |
985 | 1255 | def __update_camera(self): |
... | ... | @@ -1027,15 +1297,15 @@ class Viewer(wx.Panel): |
1027 | 1297 | def set_scroll_position(self, position): |
1028 | 1298 | self.scroll.SetThumbPosition(position) |
1029 | 1299 | self.OnScrollBar() |
1030 | - | |
1300 | + | |
1031 | 1301 | def UpdateSlice3D(self, pos): |
1032 | 1302 | original_orientation = project.Project().original_orientation |
1033 | 1303 | pos = self.scroll.GetThumbPosition() |
1034 | 1304 | Publisher.sendMessage('Change slice from slice plane',\ |
1035 | 1305 | (self.orientation, pos)) |
1036 | - | |
1306 | + | |
1037 | 1307 | def OnScrollBar(self, evt=None, update3D=True): |
1038 | - pos = self.scroll.GetThumbPosition() | |
1308 | + pos = self.scroll.GetThumbPosition() | |
1039 | 1309 | self.set_slice_number(pos) |
1040 | 1310 | if update3D: |
1041 | 1311 | self.UpdateSlice3D(pos) |
... | ... | @@ -1044,14 +1314,14 @@ class Viewer(wx.Panel): |
1044 | 1314 | # the actual orientation. |
1045 | 1315 | focal_point = self.cross.GetFocalPoint() |
1046 | 1316 | Publisher.sendMessage('Update cross position', focal_point) |
1047 | - Publisher.sendMessage('Update slice viewer') | |
1317 | + Publisher.sendMessage('Update slice viewer') | |
1048 | 1318 | else: |
1049 | - self.interactor.Render() | |
1319 | + self.interactor.Render() | |
1050 | 1320 | if evt: |
1051 | 1321 | if self._flush_buffer: |
1052 | 1322 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
1053 | 1323 | evt.Skip() |
1054 | - | |
1324 | + | |
1055 | 1325 | def OnScrollBarRelease(self, evt): |
1056 | 1326 | pos = self.scroll.GetThumbPosition() |
1057 | 1327 | evt.Skip() |
... | ... | @@ -1077,7 +1347,7 @@ class Viewer(wx.Panel): |
1077 | 1347 | if (evt.GetKeyCode() == wx.WXK_UP and pos > min): |
1078 | 1348 | self.OnScrollForward() |
1079 | 1349 | self.OnScrollBar() |
1080 | - | |
1350 | + | |
1081 | 1351 | elif (evt.GetKeyCode() == wx.WXK_DOWN and pos < max): |
1082 | 1352 | self.OnScrollBackward() |
1083 | 1353 | self.OnScrollBar() |
... | ... | @@ -1101,7 +1371,7 @@ class Viewer(wx.Panel): |
1101 | 1371 | Publisher.sendMessage('Set projection type', projections[evt.GetKeyCode()]) |
1102 | 1372 | Publisher.sendMessage('Reload actual slice') |
1103 | 1373 | skip = False |
1104 | - | |
1374 | + | |
1105 | 1375 | self.UpdateSlice3D(pos) |
1106 | 1376 | self.interactor.Render() |
1107 | 1377 | |
... | ... | @@ -1109,20 +1379,24 @@ class Viewer(wx.Panel): |
1109 | 1379 | evt.Skip() |
1110 | 1380 | |
1111 | 1381 | def OnScrollForward(self, evt=None, obj=None): |
1382 | + if not self.scroll_enabled: | |
1383 | + return | |
1112 | 1384 | pos = self.scroll.GetThumbPosition() |
1113 | 1385 | min = 0 |
1114 | - | |
1386 | + | |
1115 | 1387 | if(pos > min): |
1116 | 1388 | if self._flush_buffer: |
1117 | 1389 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
1118 | 1390 | pos = pos - 1 |
1119 | 1391 | self.scroll.SetThumbPosition(pos) |
1120 | 1392 | self.OnScrollBar() |
1121 | - | |
1393 | + | |
1122 | 1394 | def OnScrollBackward(self, evt=None, obj=None): |
1395 | + if not self.scroll_enabled: | |
1396 | + return | |
1123 | 1397 | pos = self.scroll.GetThumbPosition() |
1124 | 1398 | max = self.slice_.GetMaxSliceNumber(self.orientation) |
1125 | - | |
1399 | + | |
1126 | 1400 | if(pos < max): |
1127 | 1401 | if self._flush_buffer: |
1128 | 1402 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
... | ... | @@ -1131,7 +1405,7 @@ class Viewer(wx.Panel): |
1131 | 1405 | self.OnScrollBar() |
1132 | 1406 | |
1133 | 1407 | def OnSize(self, evt): |
1134 | - w, h = evt.GetSize() | |
1408 | + w, h = evt.GetSize() | |
1135 | 1409 | w = float(w) |
1136 | 1410 | h = float(h) |
1137 | 1411 | if self.slice_data: |
... | ... | @@ -1168,7 +1442,7 @@ class Viewer(wx.Panel): |
1168 | 1442 | self.mip_ctrls.Hide() |
1169 | 1443 | self.GetSizer().Remove(self.mip_ctrls) |
1170 | 1444 | self.Layout() |
1171 | - | |
1445 | + | |
1172 | 1446 | def OnSetOverwriteMask(self, pubsub_evt): |
1173 | 1447 | value = pubsub_evt.data |
1174 | 1448 | self.overwrite_mask = value |
... | ... | @@ -1184,14 +1458,14 @@ class Viewer(wx.Panel): |
1184 | 1458 | for actor in self.actors_by_slice_number[index]: |
1185 | 1459 | self.slice_data.renderer.AddActor(actor) |
1186 | 1460 | |
1187 | - for (m, mr) in self.measures.get(self.orientation, self.slice_data.number): | |
1188 | - for actor in mr.GetActors(): | |
1189 | - self.slice_data.renderer.RemoveActor(actor) | |
1461 | + # for (m, mr) in self.measures.get(self.orientation, self.slice_data.number): | |
1462 | + # for actor in mr.GetActors(): | |
1463 | + # self.slice_data.renderer.RemoveActor(actor) | |
1190 | 1464 | |
1191 | - for (m, mr) in self.measures.get(self.orientation, index): | |
1192 | - mr.renderer = self.slice_data.renderer | |
1193 | - for actor in mr.GetActors(): | |
1194 | - self.slice_data.renderer.AddActor(actor) | |
1465 | + # for (m, mr) in self.measures.get(self.orientation, index): | |
1466 | + # mr.renderer = self.slice_data.renderer | |
1467 | + # for actor in mr.GetActors(): | |
1468 | + # self.slice_data.renderer.AddActor(actor) | |
1195 | 1469 | |
1196 | 1470 | if self.slice_._type_projection == const.PROJECTION_NORMAL: |
1197 | 1471 | self.slice_data.SetNumber(index) |
... | ... | @@ -1225,7 +1499,7 @@ class Viewer(wx.Panel): |
1225 | 1499 | # orientation |
1226 | 1500 | axis0, axis1 = pubsub_evt.data |
1227 | 1501 | cursor = self.slice_data.cursor |
1228 | - spacing = cursor.spacing | |
1502 | + spacing = cursor.spacing | |
1229 | 1503 | if (axis0, axis1) == (2, 1): |
1230 | 1504 | cursor.SetSpacing((spacing[1], spacing[0], spacing[2])) |
1231 | 1505 | elif (axis0, axis1) == (2, 0): | ... | ... |
invesalius/gui/data_notebook.py
... | ... | @@ -943,6 +943,7 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
943 | 943 | Publisher.subscribe(self.OnShowSingle, 'Show single measurement') |
944 | 944 | Publisher.subscribe(self.OnShowMultiple, 'Show multiple measurements') |
945 | 945 | Publisher.subscribe(self.OnLoadData, 'Load measurement dict') |
946 | + Publisher.subscribe(self.OnRemoveGUIMeasure, 'Remove GUI measurement') | |
946 | 947 | |
947 | 948 | def __bind_events_wx(self): |
948 | 949 | self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) |
... | ... | @@ -959,6 +960,19 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
959 | 960 | elif (keycode == wx.WXK_DELETE): |
960 | 961 | self.RemoveMeasurements() |
961 | 962 | |
963 | + def OnRemoveGUIMeasure(self, pubsub_evt): | |
964 | + idx = pubsub_evt.data | |
965 | + self.DeleteItem(idx) | |
966 | + | |
967 | + old_dict = self._list_index | |
968 | + new_dict = {} | |
969 | + j = 0 | |
970 | + for i in old_dict: | |
971 | + if i != idx: | |
972 | + new_dict[j] = old_dict[i] | |
973 | + j+=1 | |
974 | + self._list_index = new_dict | |
975 | + | |
962 | 976 | def RemoveMeasurements(self): |
963 | 977 | """ |
964 | 978 | Remove items selected. |
... | ... | @@ -1105,7 +1119,7 @@ class MeasuresListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
1105 | 1119 | value = (u"%.2f°") % m.value |
1106 | 1120 | self.InsertNewItem(m.index, m.name, colour, location, type, value) |
1107 | 1121 | |
1108 | - if not m.is_shown: | |
1122 | + if not m.visible: | |
1109 | 1123 | self.SetItemImage(i, False) |
1110 | 1124 | |
1111 | 1125 | def AddItem_(self, pubsub_evt): | ... | ... |
invesalius/project.py
... | ... | @@ -190,7 +190,7 @@ class Project(object): |
190 | 190 | item["type"] = m.type |
191 | 191 | item["slice_number"] = m.slice_number |
192 | 192 | item["points"] = m.points |
193 | - item["visible"] = m.is_shown | |
193 | + item["visible"] = m.visible | |
194 | 194 | measures[str(m.index)] = item |
195 | 195 | return measures |
196 | 196 | ... | ... |