diff --git a/invesalius/gui/widgets/gradient.py b/invesalius/gui/widgets/gradient.py index cfdbdbeda..b1c9cc69b 100644 --- a/invesalius/gui/widgets/gradient.py +++ b/invesalius/gui/widgets/gradient.py @@ -19,12 +19,13 @@ # detalhes. # -------------------------------------------------------------------------- import sys +from typing import Any, Iterable, List, Literal, Optional, Sequence, SupportsInt, Tuple, Union -import numpy import wx from invesalius.gui.widgets.inv_spinctrl import InvSpinCtrl +ColourType = Union[Tuple[int, int, int], Tuple[int, int, int, int], List[int]] try: dc = wx.MemoryDC() font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) @@ -35,21 +36,23 @@ except Exception: PUSH_WIDTH = 7 -myEVT_SLIDER_CHANGED = wx.NewEventType() +myEVT_SLIDER_CHANGED: int = wx.NewEventType() EVT_SLIDER_CHANGED = wx.PyEventBinder(myEVT_SLIDER_CHANGED, 1) -myEVT_SLIDER_CHANGING = wx.NewEventType() +myEVT_SLIDER_CHANGING: int = wx.NewEventType() EVT_SLIDER_CHANGING = wx.PyEventBinder(myEVT_SLIDER_CHANGING, 1) -myEVT_THRESHOLD_CHANGED = wx.NewEventType() +myEVT_THRESHOLD_CHANGED: int = wx.NewEventType() EVT_THRESHOLD_CHANGED = wx.PyEventBinder(myEVT_THRESHOLD_CHANGED, 1) -myEVT_THRESHOLD_CHANGING = wx.NewEventType() +myEVT_THRESHOLD_CHANGING: int = wx.NewEventType() EVT_THRESHOLD_CHANGING = wx.PyEventBinder(myEVT_THRESHOLD_CHANGING, 1) class SliderEvent(wx.PyCommandEvent): - def __init__(self, evtType, id, minRange, maxRange, minValue, maxValue): + def __init__( + self, evtType: int, id: int, minRange: int, maxRange: int, minValue: int, maxValue: int + ): wx.PyCommandEvent.__init__(self, evtType, id) self.min_range = minRange self.max_range = maxRange @@ -61,13 +64,22 @@ class GradientSlider(wx.Panel): # This widget is formed by a gradient background (black-white), two push # buttons change the min and max values respectively and a slider which you can drag to # change the both min and max values. - def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): + def __init__( + self, + parent: wx.Window, + id: int, + minRange: int, + maxRange: int, + minValue: int, + maxValue: int, + colour: Iterable[SupportsInt], + ): # minRange: the minimal value # maxrange: the maximum value # minValue: the least value in the range # maxValue: the most value in the range # colour: colour used in this widget. - super(GradientSlider, self).__init__(parent, id) + super().__init__(parent, id) self._bind_events_wx() self.min_range = minRange @@ -75,13 +87,14 @@ def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): self.minimun = minValue self.maximun = maxValue self.selected = 0 + self.max_position: int - self._gradient_colours = None + self._gradient_colours: Optional[Sequence[ColourType]] = None self.SetColour(colour) self.CalculateControlPositions() - def _bind_events_wx(self): + def _bind_events_wx(self) -> None: self.Bind(wx.EVT_LEFT_DOWN, self.OnClick) self.Bind(wx.EVT_LEFT_UP, self.OnRelease) self.Bind(wx.EVT_PAINT, self.OnPaint) @@ -93,7 +106,7 @@ def _bind_events_wx(self): self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_SIZE, self.OnSize) - def OnLeaveWindow(self, evt): + def OnLeaveWindow(self, evt: Union[wx.KeyEvent, wx.SetCursorEvent, wx.SplitterEvent]) -> None: if self.selected == 0: return @@ -124,27 +137,29 @@ def OnLeaveWindow(self, evt): self._generate_event(myEVT_SLIDER_CHANGED) evt.Skip() - def OnPaint(self, evt): + def OnPaint(self, evt: wx.Event) -> None: # Where the magic happens. Here the controls are drawn. dc = wx.BufferedPaintDC(self) dc.Clear() w, h = self.GetSize() - width_gradient = w - 2 * PUSH_WIDTH - height_gradient = h - x_init_gradient = PUSH_WIDTH - y_init_gradient = 0 + # width_gradient = w - 2 * PUSH_WIDTH + # height_gradient = h + # x_init_gradient = PUSH_WIDTH + # y_init_gradient = 0 x_init_push1 = self.min_position - PUSH_WIDTH x_init_push2 = self.max_position width_transparency = self.max_position - self.min_position - gc = wx.GraphicsContext.Create(dc) + gc: wx.GraphicsContext = wx.GraphicsContext.Create(dc) - points = ((0, PUSH_WIDTH, (0, 0, 0), (0, 0, 0)), - (PUSH_WIDTH, w - PUSH_WIDTH, (0, 0, 0), (255, 255, 255)), - (w - PUSH_WIDTH, w, (255, 255, 255), (255, 255, 255))) + points: Sequence[Tuple[int, int, ColourType, ColourType]] = ( + (0, PUSH_WIDTH, (0, 0, 0), (0, 0, 0)), + (PUSH_WIDTH, w - PUSH_WIDTH, (0, 0, 0), (255, 255, 255)), + (w - PUSH_WIDTH, w, (255, 255, 255), (255, 255, 255)), + ) # Drawing the gradient background for p1, p2, c1, c2 in points: @@ -177,29 +192,26 @@ def OnPaint(self, evt): gc.FillPath(path) # Drawing the pushes - try: - n = wx.RendererNative.Get() - except AttributeError: - n = wx.RendererNative_Get() + n = wx.RendererNative.Get() # Drawing the push buttons n.DrawPushButton(self, dc, (x_init_push1, 0, PUSH_WIDTH, h)) n.DrawPushButton(self, dc, (x_init_push2, 0, PUSH_WIDTH, h)) # # Drawing the transparent slider. - # bytes = numpy.array(self.colour * width_transparency * h, "B") - # try: - # slider = wx.Bitmap.FromBufferRGBA(width_transparency, h, bytes) - # except: - # pass - # else: - # dc.DrawBitmap(slider, self.min_position, 0, True) - - def OnEraseBackGround(self, evt): + # bytes = numpy.array(self.colour * width_transparency * h, "B") + # try: + # slider = wx.Bitmap.FromBufferRGBA(width_transparency, h, bytes) + # except: + # pass + # else: + # dc.DrawBitmap(slider, self.min_position, 0, True) + + def OnEraseBackGround(self, evt: wx.Event) -> None: # Only to avoid this widget to flick. pass - def OnMotion(self, evt): + def OnMotion(self, evt: Union[wx.KeyEvent, wx.SetCursorEvent, wx.SplitterEvent]) -> None: x = evt.GetX() w, h = self.GetSize() @@ -267,7 +279,7 @@ def OnMotion(self, evt): self.Refresh() evt.Skip() - def OnClick(self, evt): + def OnClick(self, evt: Union[wx.KeyEvent, wx.SetCursorEvent, wx.SplitterEvent]) -> None: x = evt.GetX() self.selected = self._is_over_what(x) if self.selected == 1: @@ -278,18 +290,18 @@ def OnClick(self, evt): self._delta = x - self.min_position evt.Skip() - def OnRelease(self, evt): + def OnRelease(self, evt: wx.Event) -> None: if self.selected: self.selected = 0 self._generate_event(myEVT_SLIDER_CHANGED) evt.Skip() - def OnSize(self, evt): + def OnSize(self, evt: wx.Event) -> None: self.CalculateControlPositions() self.Refresh() evt.Skip() - def CalculateControlPositions(self): + def CalculateControlPositions(self) -> None: """ Calculates the Min and Max control position based on the size of this widget. @@ -298,14 +310,10 @@ def CalculateControlPositions(self): window_width = w - 2 * PUSH_WIDTH proportion = window_width / float(self.max_range - self.min_range) - self.min_position = ( - int(round((self.minimun - self.min_range) * proportion)) + PUSH_WIDTH - ) - self.max_position = ( - int(round((self.maximun - self.min_range) * proportion)) + PUSH_WIDTH - ) + self.min_position = int(round((self.minimun - self.min_range) * proportion)) + PUSH_WIDTH + self.max_position = int(round((self.maximun - self.min_range) * proportion)) + PUSH_WIDTH - def _max_position_to_maximun(self, max_position): + def _max_position_to_maximun(self, max_position: int) -> int: """ Calculates the min and max value based on the control positions. """ @@ -317,7 +325,7 @@ def _max_position_to_maximun(self, max_position): return maximun - def _min_position_to_minimun(self, min_position): + def _min_position_to_minimun(self, min_position: int) -> int: w, h = self.GetSize() window_width = w - 2 * PUSH_WIDTH proportion = window_width / float(self.max_range - self.min_range) @@ -326,7 +334,7 @@ def _min_position_to_minimun(self, min_position): return minimun - def _is_over_what(self, position_x): + def _is_over_what(self, position_x: int) -> Literal[0, 1, 2, 3]: # Test if the given position (x) is over some object. Return 1 to first # pus, 2 to second push, 3 to slide and 0 to nothing. if self.min_position - PUSH_WIDTH <= position_x <= self.min_position: @@ -338,39 +346,39 @@ def _is_over_what(self, position_x): else: return 0 - def SetColour(self, colour): + def SetColour(self, colour: Iterable[SupportsInt]) -> None: self.colour = [int(i) for i in colour] - def SetGradientColours(self, colors): + def SetGradientColours(self, colors: Optional[Sequence[ColourType]]) -> None: self._gradient_colours = colors - def SetMinRange(self, min_range): + def SetMinRange(self, min_range: int) -> None: self.min_range = min_range self.CalculateControlPositions() self.Refresh() - def SetMaxRange(self, max_range): + def SetMaxRange(self, max_range: int) -> None: self.max_range = max_range self.CalculateControlPositions() self.Refresh() - def SetMinimun(self, minimun): + def SetMinimun(self, minimun: int) -> None: self.minimun = minimun self.CalculateControlPositions() self.Refresh() - def SetMaximun(self, maximun): + def SetMaximun(self, maximun: int) -> None: self.maximun = maximun self.CalculateControlPositions() self.Refresh() - def GetMaxValue(self): + def GetMaxValue(self) -> int: return self.maximun - def GetMinValue(self): + def GetMinValue(self) -> int: return self.minimun - def _generate_event(self, event): + def _generate_event(self, event: int) -> None: evt = SliderEvent( event, self.GetId(), @@ -384,15 +392,24 @@ def _generate_event(self, event): class GradientNoSlide(wx.Panel): # This widget is formed by a gradient background (black-white) - # Unlike GradientSlide, here the widget is used as a colorbar to display + # Unlike GradientSlide, here the widget is used as a colorbar to display # the available colors (used in fmri support) - def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): + def __init__( + self, + parent: wx.Window, + id: int, + minRange: int, + maxRange: int, + minValue: int, + maxValue: int, + colours: Sequence[ColourType], + ): # minRange: the minimal value # maxrange: the maximum value # minValue: the least value in the range # maxValue: the most value in the range # colour: colour used in this widget. - super(GradientNoSlide, self).__init__(parent, id) + super().__init__(parent, id) self._bind_events_wx() self.min_range = minRange @@ -402,18 +419,17 @@ def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): self.selected = 0 self.min_position = 0 - w, h = self.GetSize() + w, h = self.GetSize() - self._gradient_colours = None - self.colour = colour + self._gradient_colours = colours - def _bind_events_wx(self): + def _bind_events_wx(self) -> None: self.Bind(wx.EVT_LEFT_DOWN, self.OnClick) self.Bind(wx.EVT_LEFT_UP, self.OnRelease) self.Bind(wx.EVT_PAINT, self.OutPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackGround) - def OutPaint(self, evt): + def OutPaint(self, evt: wx.Event) -> None: # Where the magic happens. Here the controls are drawn. dc = wx.BufferedPaintDC(self) dc.Clear() @@ -435,11 +451,11 @@ def OutPaint(self, evt): gc.StrokePath(path) gc.FillPath(path) - def OnEraseBackGround(self, evt): + def OnEraseBackGround(self, evt: wx.Event) -> None: # Only to avoid this widget to flick. pass - def OnMotion(self, evt): + def OnMotion(self, evt: Union[wx.KeyEvent, wx.SetCursorEvent, wx.SplitterEvent]) -> None: x = evt.GetX() w, h = self.GetSize() @@ -507,7 +523,7 @@ def OnMotion(self, evt): self.Refresh() evt.Skip() - def OnClick(self, evt): + def OnClick(self, evt: Union[wx.KeyEvent, wx.SetCursorEvent, wx.SplitterEvent]) -> None: x = evt.GetX() self.selected = self._is_over_what(x) if self.selected == 1: @@ -518,18 +534,18 @@ def OnClick(self, evt): self._delta = x - self.min_position evt.Skip() - def OnRelease(self, evt): + def OnRelease(self, evt: wx.Event) -> None: if self.selected: self.selected = 0 self._generate_event(myEVT_SLIDER_CHANGED) evt.Skip() - def OnSize(self, evt): + def OnSize(self, evt: wx.Event) -> None: self.CalculateControlPositions() self.Refresh() evt.Skip() - def CalculateControlPositions(self): + def CalculateControlPositions(self) -> None: """ Calculates the Min and Max control position based on the size of this widget. @@ -538,14 +554,10 @@ def CalculateControlPositions(self): window_width = w - 2 * PUSH_WIDTH proportion = window_width / float(self.max_range - self.min_range) - self.min_position = ( - int(round((self.minimun - self.min_range) * proportion)) + PUSH_WIDTH - ) - self.max_position = ( - int(round((self.maximun - self.min_range) * proportion)) + PUSH_WIDTH - ) + self.min_position = int(round((self.minimun - self.min_range) * proportion)) + PUSH_WIDTH + self.max_position = int(round((self.maximun - self.min_range) * proportion)) + PUSH_WIDTH - def _max_position_to_maximun(self, max_position): + def _max_position_to_maximun(self, max_position: int) -> int: """ Calculates the min and max value based on the control positions. """ @@ -557,7 +569,7 @@ def _max_position_to_maximun(self, max_position): return maximun - def _min_position_to_minimun(self, min_position): + def _min_position_to_minimun(self, min_position: int) -> int: w, h = self.GetSize() window_width = w - 2 * PUSH_WIDTH proportion = window_width / float(self.max_range - self.min_range) @@ -566,7 +578,7 @@ def _min_position_to_minimun(self, min_position): return minimun - def _is_over_what(self, position_x): + def _is_over_what(self, position_x: int) -> Literal[0, 1, 2, 3]: # Test if the given position (x) is over some object. Return 1 to first # pus, 2 to second push, 3 to slide and 0 to nothing. if self.min_position - PUSH_WIDTH <= position_x <= self.min_position: @@ -578,39 +590,36 @@ def _is_over_what(self, position_x): else: return 0 - def SetColour(self, colour): - self.colour = [int(i) for i in colour] - - def SetGradientColours(self, colors): + def SetGradientColours(self, colors: Sequence[ColourType]) -> None: self._gradient_colours = colors - def SetMinRange(self, min_range): + def SetMinRange(self, min_range: int) -> None: self.min_range = min_range self.CalculateControlPositions() self.Refresh() - def SetMaxRange(self, max_range): + def SetMaxRange(self, max_range: int) -> None: self.max_range = max_range self.CalculateControlPositions() self.Refresh() - def SetMinimun(self, minimun): + def SetMinimun(self, minimun: int) -> None: self.minimun = minimun self.CalculateControlPositions() self.Refresh() - def SetMaximun(self, maximun): + def SetMaximun(self, maximun: int) -> None: self.maximun = maximun self.CalculateControlPositions() self.Refresh() - def GetMaxValue(self): + def GetMaxValue(self) -> int: return self.maximun - def GetMinValue(self): + def GetMinValue(self) -> int: return self.minimun - def _generate_event(self, event): + def _generate_event(self, event: int) -> None: evt = SliderEvent( event, self.GetId(), @@ -623,8 +632,17 @@ def _generate_event(self, event): class GradientCtrl(wx.Panel): - def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): - super(GradientCtrl, self).__init__(parent, id) + def __init__( + self, + parent: wx.Window, + id: int, + minRange: int, + maxRange: int, + minValue: int, + maxValue: int, + colour: Sequence[float], + ): + super().__init__(parent, id) self.min_range = minRange self.max_range = maxRange self.minimun = minValue @@ -635,7 +653,7 @@ def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): self._bind_events_wx() self.Show() - def _draw_controls(self): + def _draw_controls(self) -> None: self.gradient_slider = GradientSlider( self, -1, @@ -680,28 +698,28 @@ def _draw_controls(self): self.sizer.Fit(self) # self.SetAutoLayout(1) - def _bind_events_wx(self): + def _bind_events_wx(self) -> None: self.gradient_slider.Bind(EVT_SLIDER_CHANGING, self.OnSliding) self.gradient_slider.Bind(EVT_SLIDER_CHANGED, self.OnSlider) self.spin_min.Bind(wx.EVT_SPINCTRL, self.OnMinMouseWheel) self.spin_max.Bind(wx.EVT_SPINCTRL, self.OnMaxMouseWheel) - def OnSlider(self, evt): + def OnSlider(self, evt: Any) -> None: self.spin_min.SetValue(evt.minimun) self.spin_max.SetValue(evt.maximun) self.minimun = evt.minimun self.maximun = evt.maximun self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def OnSliding(self, evt): + def OnSliding(self, evt: Any) -> None: self.spin_min.SetValue(evt.minimun) self.spin_max.SetValue(evt.maximun) self.minimun = evt.minimun self.maximun = evt.maximun self._GenerateEvent(myEVT_THRESHOLD_CHANGING) - def _FireSpinMinChange(self, evt): + def _FireSpinMinChange(self, evt: wx.Event) -> None: evt.Skip() value = int(self.spin_min.GetValue()) if value < self.min_range or value > self.max_range: @@ -712,11 +730,11 @@ def _FireSpinMinChange(self, evt): self.SetMinValue(value) self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def _FireSpinMinChanged(self, evt): + def _FireSpinMinChanged(self, evt: wx.Event) -> None: if self.changed: self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def _FireSpinMaxChange(self, evt): + def _FireSpinMaxChange(self, evt: wx.Event) -> None: evt.Skip() value = int(self.spin_max.GetValue()) if value < self.min_range or value > self.max_range: @@ -727,11 +745,11 @@ def _FireSpinMaxChange(self, evt): self.SetMaxValue(value) self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def _FireSpinMaxChanged(self, evt): + def _FireSpinMaxChanged(self, evt: wx.Event) -> None: if self.changed: self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def OnMinMouseWheel(self, e): + def OnMinMouseWheel(self, e: Any) -> None: """ When the user wheel the mouse over min texbox """ @@ -739,7 +757,7 @@ def OnMinMouseWheel(self, e): self.SetMinValue(v) self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def OnMaxMouseWheel(self, e): + def OnMaxMouseWheel(self, e: Any) -> None: """ When the user wheel the mouse over max texbox """ @@ -747,16 +765,16 @@ def OnMaxMouseWheel(self, e): self.SetMaxValue(v) self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def SetColour(self, colour): + def SetColour(self, colour: Sequence[SupportsInt]) -> None: colour = list(int(i) for i in colour[:3]) + [90] self.colour = colour self.gradient_slider.SetColour(colour) self.gradient_slider.Refresh() - def SetGradientColours(self, colors): + def SetGradientColours(self, colors: Sequence[ColourType]) -> None: self.gradient_slider.SetGradientColours(colors) - def SetMaxRange(self, value): + def SetMaxRange(self, value: int) -> None: self.spin_min.SetMax(value) self.spin_max.SetMax(value) self.spin_min.CalcSizeFromTextSize() @@ -770,7 +788,7 @@ def SetMaxRange(self, value): self.spin_max.CalcSizeFromTextSize() self.Layout() - def SetMinRange(self, value): + def SetMinRange(self, value: int) -> None: self.spin_min.SetMin(value) self.spin_max.SetMin(value) self.spin_min.CalcSizeFromTextSize() @@ -784,7 +802,7 @@ def SetMinRange(self, value): self.spin_max.CalcSizeFromTextSize() self.Layout() - def SetMaxValue(self, value): + def SetMaxValue(self, value: Optional[SupportsInt]) -> None: if value is not None: value = int(value) if value > self.max_range: @@ -797,7 +815,7 @@ def SetMaxValue(self, value): self.gradient_slider.SetMaximun(value) self.maximun = value - def SetMinValue(self, value): + def SetMinValue(self, value: Optional[SupportsInt]) -> None: if value is not None: value = int(value) if value < self.min_range: @@ -810,27 +828,13 @@ def SetMinValue(self, value): self.gradient_slider.SetMinimun(value) self.minimun = value - def ChangeMinValue(self, e): - # Why do I need to change slide min value if it has been changed for - # the user? - if not self.slided: - self.gradient_slider.SetMinValue(int(self.spin_min.GetValue())) - self._GenerateEvent(myEVT_THRESHOLD_CHANGE) - - def ChangeMaxValue(self, e): - # Why do I need to change slide max value if it has been changed for - # the user? - if not self.slided: - self.gradient_slider.SetMaxValue(int(self.spin_max.GetValue())) - self._GenerateEvent(myEVT_THRESHOLD_CHANGE) - - def GetMaxValue(self): + def GetMaxValue(self) -> int: return self.maximun - def GetMinValue(self): + def GetMinValue(self) -> int: return self.minimun - def _GenerateEvent(self, event): + def _GenerateEvent(self, event: int) -> None: if event == myEVT_THRESHOLD_CHANGING: self.changed = True elif event == myEVT_THRESHOLD_CHANGED: @@ -849,70 +853,65 @@ def _GenerateEvent(self, event): class GradientDisp(wx.Panel): # Class for colorbars gradient used in fmri support (showing different colormaps possible) - def __init__(self, parent, id, minRange, maxRange, minValue, maxValue, colour): - super(GradientDisp, self).__init__(parent, id) + def __init__( + self, + parent: wx.Window, + id: int, + minRange: int, + maxRange: int, + minValue: int, + maxValue: int, + colours: List[ColourType], + ): + super().__init__(parent, id) self.min_range = minRange self.max_range = maxRange self.minimun = minValue self.maximun = maxValue - self.colour = colour + self.colours = colours self.changed = False self._draw_controls() self._bind_events_wx() self.Show() - def _draw_controls(self): + def _draw_controls(self) -> None: self.gradient_slider = GradientNoSlide( - self, - -1, - self.min_range, - self.max_range, - self.minimun, - self.maximun, - self.colour + self, -1, self.min_range, self.max_range, self.minimun, self.maximun, self.colours ) - - self.gradient_slider.SetGradientColours(self.colour) + sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(1,30) + sizer.Add(1, 30) sizer.Add(self.gradient_slider, 1, wx.EXPAND) - sizer.Add(1,30) + sizer.Add(1, 30) self.sizer = wx.BoxSizer(wx.HORIZONTAL) self.sizer.Add(sizer, 1, wx.EXPAND) self.SetSizer(self.sizer) self.sizer.Fit(self) - def _bind_events_wx(self): - return + def _bind_events_wx(self) -> None: + return - def OnSlider(self, evt): + def OnSlider(self, evt: Any) -> None: self.minimun = evt.minimun self.maximun = evt.maximun self._GenerateEvent(myEVT_THRESHOLD_CHANGED) - def OnSliding(self, evt): + def OnSliding(self, evt: Any) -> None: self.minimun = evt.minimun self.maximun = evt.maximun self._GenerateEvent(myEVT_THRESHOLD_CHANGING) - - def SetColour(self, colour): - colour = list(int(i) for i in colour[:3]) - self.colour = colour - self.gradient_slider.SetColour(colour) - self.gradient_slider.Refresh() - - def SetGradientColours(self, colors): + def SetGradientColours(self, colors: Sequence[ColourType]) -> None: self.gradient_slider.SetGradientColours(colors) - def GetMaxValue(self): + def GetMaxValue(self) -> int: return self.maximun - def GetMinValue(self): + def GetMinValue(self) -> int: return self.minimun - def _GenerateEvent(self, event): + def _GenerateEvent(self, event: int) -> None: if event == myEVT_THRESHOLD_CHANGING: self.changed = True elif event == myEVT_THRESHOLD_CHANGED: @@ -926,4 +925,4 @@ def _GenerateEvent(self, event): self.minimun, self.maximun, ) - self.GetEventHandler().ProcessEvent(evt) \ No newline at end of file + self.GetEventHandler().ProcessEvent(evt) diff --git a/invesalius/gui/widgets/inv_spinctrl.py b/invesalius/gui/widgets/inv_spinctrl.py index ebcfb2800..f23f85e53 100644 --- a/invesalius/gui/widgets/inv_spinctrl.py +++ b/invesalius/gui/widgets/inv_spinctrl.py @@ -17,6 +17,7 @@ # detalhes. # -------------------------------------------------------------------------- import decimal +from typing import Any, Optional, Union import wx @@ -24,16 +25,16 @@ class InvSpinCtrl(wx.Panel): def __init__( self, - parent, - id=wx.ID_ANY, - value=0, - min_value=1, - max_value=100, - increment=1, - spin_button=True, - unit='', - size=wx.DefaultSize, - style=wx.TE_RIGHT, + parent: wx.Window, + id: int = wx.ID_ANY, + value: int = 0, + min_value: int = 1, + max_value: int = 100, + increment: int = 1, + spin_button: bool = True, + unit: str = "", + size: wx.Size = wx.DefaultSize, + style: int = wx.TE_RIGHT, ): super().__init__(parent, id, size=size) @@ -66,34 +67,34 @@ def __init__( self.__bind_events() - def __bind_events(self): + def __bind_events(self) -> None: self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) self._textctrl.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) if self._spinbtn: self._spinbtn.Bind(wx.EVT_SPIN_UP, self.OnSpinUp) self._spinbtn.Bind(wx.EVT_SPIN_DOWN, self.OnSpinDown) - def SetIncrement(self, increment): + def SetIncrement(self, increment: int) -> None: self._increment = increment - def SetMin(self, min_value): + def SetMin(self, min_value: int) -> None: self._min_value = min_value self.SetValue(self._value) self.CalcSizeFromTextSize() - def SetMax(self, max_value): + def SetMax(self, max_value: int) -> None: self._max_value = max_value self.SetValue(self._value) self.CalcSizeFromTextSize() - def SetRange(self, min_value, max_value): + def SetRange(self, min_value: int, max_value: int) -> None: self.SetMin(min_value) self.SetMax(max_value) - def GetValue(self): + def GetValue(self) -> int: return self._value - def SetValue(self, value): + def SetValue(self, value: Any) -> None: try: value = int(value) except ValueError: @@ -106,23 +107,20 @@ def SetValue(self, value): value = self._max_value self._value = value - self._textctrl.SetValue("{} {}".format(self._value, self.unit)) + self._textctrl.SetValue(f"{self._value} {self.unit}") self._last_value = self._value - - def GetUnit(self): + def GetUnit(self) -> str: return self.unit - def SetUnit(self, unit): + def SetUnit(self, unit: str) -> None: self.unit = unit self.SetValue(self.GetValue()) - def CalcSizeFromTextSize(self, text=None): + def CalcSizeFromTextSize(self, text: Optional[str] = None) -> None: # To calculate best width to spinctrl if text is None: - text = "{}".format( - max(len(str(self._max_value)), len(str(self._min_value)), 5) * "M" - ) + text = "{}".format(max(len(str(self._max_value)), len(str(self._min_value)), 5) * "M") dc = wx.WindowDC(self) dc.SetFont(self.GetFont()) @@ -148,7 +146,7 @@ def CalcSizeFromTextSize(self, text=None): self.SetMinSize((width, height)) self.Layout() - def OnMouseWheel(self, evt): + def OnMouseWheel(self, evt: wx.MouseEvent) -> None: r = evt.GetWheelRotation() if r > 0: self.SetValue(self.GetValue() + self._increment) @@ -157,23 +155,23 @@ def OnMouseWheel(self, evt): self.raise_event() evt.Skip() - def OnKillFocus(self, evt): + def OnKillFocus(self, evt: wx.FocusEvent) -> None: value = self._textctrl.GetValue() self.SetValue(value) self.raise_event() evt.Skip() - def OnSpinDown(self, evt): + def OnSpinDown(self, evt: wx.SpinEvent) -> None: self.SetValue(self.GetValue() - self._increment) self.raise_event() evt.Skip() - def OnSpinUp(self, evt): + def OnSpinUp(self, evt: wx.SpinEvent) -> None: self.SetValue(self.GetValue() + self._increment) self.raise_event() evt.Skip() - def raise_event(self): + def raise_event(self) -> None: event = wx.PyCommandEvent(wx.EVT_SPINCTRL.typeId, self.GetId()) self.GetEventHandler().ProcessEvent(event) @@ -181,16 +179,16 @@ def raise_event(self): class InvFloatSpinCtrl(wx.Panel): def __init__( self, - parent, - id=wx.ID_ANY, - value=0.0, - min_value=1.0, - max_value=100.0, - increment=0.1, - digits=1, - spin_button=True, - size=wx.DefaultSize, - style=wx.TE_RIGHT, + parent: wx.Window, + id: int = wx.ID_ANY, + value: float = 0.0, + min_value: float = 1.0, + max_value: float = 100.0, + increment: float = 0.1, + digits: int = 1, + spin_button: bool = True, + size: wx.Size = wx.DefaultSize, + style: int = wx.TE_RIGHT, ): super().__init__(parent, id, size=size) @@ -224,19 +222,19 @@ def __init__( self.__bind_events() - def __bind_events(self): + def __bind_events(self) -> None: self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) self._textctrl.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) if self._spinbtn: self._spinbtn.Bind(wx.EVT_SPIN_UP, self.OnSpinUp) self._spinbtn.Bind(wx.EVT_SPIN_DOWN, self.OnSpinDown) - def _to_decimal(self, value): + def _to_decimal(self, value: Union[decimal.Decimal, float, str]) -> decimal.Decimal: if not isinstance(value, str): value = "{:.{digits}f}".format(value, digits=self._digits) return decimal.Decimal(value, self._dec_context) - def SetDigits(self, digits): + def SetDigits(self, digits: int) -> None: self._digits = digits self._dec_context = decimal.Context(prec=digits) @@ -245,25 +243,29 @@ def SetDigits(self, digits): self.SetMax(self._max_value) self.SetValue(self._value) - def SetIncrement(self, increment): + def SetIncrement(self, increment: Union[decimal.Decimal, float, str]) -> None: self._increment = self._to_decimal(increment) - def SetMin(self, min_value): + def SetMin(self, min_value: Union[decimal.Decimal, float, str]) -> None: self._min_value = self._to_decimal(min_value) self.SetValue(self._value) - def SetMax(self, max_value): + def SetMax(self, max_value: Union[decimal.Decimal, float, str]) -> None: self._max_value = self._to_decimal(max_value) self.SetValue(self._value) - def SetRange(self, min_value, max_value): + def SetRange( + self, + min_value: Union[decimal.Decimal, float, str], + max_value: Union[decimal.Decimal, float, str], + ) -> None: self.SetMin(min_value) self.SetMax(max_value) - def GetValue(self): + def GetValue(self) -> float: return float(self._value) - def SetValue(self, value): + def SetValue(self, value: Any) -> None: try: value = self._to_decimal(value) except decimal.InvalidOperation: @@ -276,15 +278,13 @@ def SetValue(self, value): value = self._max_value self._value = value - self._textctrl.SetValue("{}".format(self._value)) + self._textctrl.SetValue(f"{self._value}") self._last_value = self._value - def CalcSizeFromTextSize(self, text=None): + def CalcSizeFromTextSize(self, text: Optional[str] = None) -> None: # To calculate best width to spinctrl if text is None: - text = "{}".format( - max(len(str(self._max_value)), len(str(self._min_value))) * "M" - ) + text = "{}".format(max(len(str(self._max_value)), len(str(self._min_value))) * "M") dc = wx.WindowDC(self) dc.SetFont(self.GetFont()) @@ -314,7 +314,7 @@ def CalcSizeFromTextSize(self, text=None): self.SetMinSize((width, height)) self.Layout() - def OnMouseWheel(self, evt): + def OnMouseWheel(self, evt: wx.MouseEvent) -> None: r = evt.GetWheelRotation() if r > 0: self.SetValue(self._value + self._increment) @@ -323,22 +323,22 @@ def OnMouseWheel(self, evt): self.raise_event() evt.Skip() - def OnKillFocus(self, evt): + def OnKillFocus(self, evt: wx.FocusEvent) -> None: value = self._textctrl.GetValue() self.SetValue(value) self.raise_event() evt.Skip() - def OnSpinDown(self, evt): + def OnSpinDown(self, evt: wx.SpinEvent) -> None: self.SetValue(self._value - self._increment) self.raise_event() evt.Skip() - def OnSpinUp(self, evt): + def OnSpinUp(self, evt: wx.SpinEvent) -> None: self.SetValue(self._value + self._increment) self.raise_event() evt.Skip() - def raise_event(self): + def raise_event(self) -> None: event = wx.PyCommandEvent(wx.EVT_SPINCTRL.typeId, self.GetId()) self.GetEventHandler().ProcessEvent(event) diff --git a/invesalius/gui/widgets/slice_menu.py b/invesalius/gui/widgets/slice_menu.py index 654451ca7..c4a602f6d 100644 --- a/invesalius/gui/widgets/slice_menu.py +++ b/invesalius/gui/widgets/slice_menu.py @@ -1,12 +1,12 @@ # -*- coding: UTF-8 -*- -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- # Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas # Copyright: (C) 2001 Centro de Pesquisas Renato Archer # Homepage: http://www.softwarepublico.gov.br # Contact: invesalius@cti.gov.br # License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- # Este programa e software livre; voce pode redistribui-lo e/ou # modifica-lo sob os termos da Licenca Publica Geral GNU, conforme # publicada pela Free Software Foundation; de acordo com a versao 2 @@ -17,13 +17,12 @@ # COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais # detalhes. -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- import sys -try: - from collections import OrderedDict -except(ImportError): - from ordereddict import OrderedDict + +from collections import OrderedDict +from typing import Dict import wx from invesalius.pubsub import pub as Publisher @@ -34,14 +33,18 @@ from invesalius.gui.dialogs import ClutImagedataDialog from invesalius.i18n import tr as _ -PROJECTIONS_ID = OrderedDict(((_('Normal'), const.PROJECTION_NORMAL), - (_('MaxIP'), const.PROJECTION_MaxIP), - (_('MinIP'), const.PROJECTION_MinIP), - (_('MeanIP'), const.PROJECTION_MeanIP), - (_('MIDA'), const.PROJECTION_MIDA), - (_('Contour MaxIP'), const.PROJECTION_CONTOUR_MIP), - (_('Contour MIDA'), const.PROJECTION_CONTOUR_MIDA),) ) - +PROJECTIONS_ID = OrderedDict( + ( + (_("Normal"), const.PROJECTION_NORMAL), + (_("MaxIP"), const.PROJECTION_MaxIP), + (_("MinIP"), const.PROJECTION_MinIP), + (_("MeanIP"), const.PROJECTION_MeanIP), + (_("MIDA"), const.PROJECTION_MIDA), + (_("Contour MaxIP"), const.PROJECTION_CONTOUR_MIP), + (_("Contour MIDA"), const.PROJECTION_CONTOUR_MIDA), + ) +) + class SliceMenu(wx.Menu): def __init__(self): @@ -49,57 +52,55 @@ def __init__(self): self.ID_TO_TOOL_ITEM = {} self.cdialog = None - #------------ Sub menu of the window and level ---------- + # ------------ Sub menu of the window and level ---------- submenu_wl = wx.Menu() self._gen_event = True - #Window and level from DICOM + # Window and level from DICOM new_id = self.id_wl_first = wx.NewIdRef() - wl_item = submenu_wl.Append(new_id, _('Default'), kind=wx.ITEM_RADIO) + wl_item = submenu_wl.Append(new_id, _("Default"), kind=wx.ITEM_RADIO) self.ID_TO_TOOL_ITEM[new_id] = wl_item - #Case the user change window and level + # Case the user change window and level new_id = self.other_wl_id = wx.NewIdRef() - wl_item = submenu_wl.Append(new_id, _('Manual'), kind=wx.ITEM_RADIO) + wl_item = submenu_wl.Append(new_id, _("Manual"), kind=wx.ITEM_RADIO) self.ID_TO_TOOL_ITEM[new_id] = wl_item for name in const.WINDOW_LEVEL: - if not(name == _('Default') or name == _('Manual')): + if not (name == _("Default") or name == _("Manual")): new_id = wx.NewIdRef() wl_item = submenu_wl.Append(new_id, name, kind=wx.ITEM_RADIO) self.ID_TO_TOOL_ITEM[new_id] = wl_item - #----------- Sub menu of the save and load options --------- - #submenu_wl.AppendSeparator() - #options = [_("Save current values"), + # ----------- Sub menu of the save and load options --------- + # submenu_wl.AppendSeparator() + # options = [_("Save current values"), # _("Save current values as..."),_("Load values")] - #for name in options: + # for name in options: # new_id = wx.NewIdRef() # wl_item = wx.MenuItem(submenu_wl, new_id,\ # name) # submenu_wl.Append(wl_item) # self.ID_TO_TOOL_ITEM[new_id] = wl_item - - #------------ Sub menu of the pseudo colors ---------------- - if sys.platform.startswith('linux'): + # ------------ Sub menu of the pseudo colors ---------------- + if sys.platform.startswith("linux"): mkind = wx.ITEM_CHECK else: mkind = wx.ITEM_RADIO - self.pseudo_color_items = {} submenu_pseudo_colours = wx.Menu() - self.pseudo_color_items = {} + self.pseudo_color_items: Dict[int, wx.MenuItem] = {} new_id = self.id_pseudo_first = wx.NewIdRef() color_item = submenu_pseudo_colours.Append(new_id, _("Default "), kind=mkind) - color_item.Check(1) + color_item.Check(True) self.ID_TO_TOOL_ITEM[new_id] = color_item self.pseudo_color_items[new_id] = color_item for name in sorted(const.SLICE_COLOR_TABLE): - if not(name == _("Default ")): + if not (name == _("Default ")): new_id = wx.NewIdRef() color_item = submenu_pseudo_colours.Append(new_id, name, kind=mkind) self.ID_TO_TOOL_ITEM[new_id] = color_item @@ -113,7 +114,7 @@ def __init__(self): self.pseudo_color_items[new_id] = color_item new_id = wx.NewIdRef() - color_item = submenu_pseudo_colours.Append(new_id, _('Custom'), kind=mkind) + color_item = submenu_pseudo_colours.Append(new_id, _("Custom"), kind=mkind) self.ID_TO_TOOL_ITEM[new_id] = color_item self.pseudo_color_items[new_id] = color_item @@ -122,20 +123,24 @@ def __init__(self): submenu_projection = wx.Menu() for name in PROJECTIONS_ID: new_id = wx.NewIdRef() - projection_item = submenu_projection.Append(new_id, name, kind=wx.ITEM_RADIO) + projection_item = submenu_projection.Append( + new_id, name, kind=wx.ITEM_RADIO + ) self.ID_TO_TOOL_ITEM[new_id] = projection_item self.projection_items[PROJECTIONS_ID[name]] = projection_item flag_tiling = False - #------------ Sub menu of the image tiling --------------- + # ------------ Sub menu of the image tiling --------------- submenu_image_tiling = wx.Menu() for name in sorted(const.IMAGE_TILING): new_id = wx.NewIdRef() - image_tiling_item = submenu_image_tiling.Append(new_id, name, kind=wx.ITEM_RADIO) + image_tiling_item = submenu_image_tiling.Append( + new_id, name, kind=wx.ITEM_RADIO + ) self.ID_TO_TOOL_ITEM[new_id] = image_tiling_item - #Save first id item - if not(flag_tiling): + # Save first id item + if not (flag_tiling): self.id_tiling_first = new_id flag_tiling = True @@ -148,7 +153,7 @@ def __init__(self): # It doesn't work in Linux self.Bind(wx.EVT_MENU, self.OnPopup) # In Linux the bind must be putted in the submenu - if sys.platform.startswith('linux') or sys.platform == 'darwin': + if sys.platform.startswith("linux") or sys.platform == "darwin": submenu_wl.Bind(wx.EVT_MENU, self.OnPopup) submenu_pseudo_colours.Bind(wx.EVT_MENU, self.OnPopup) submenu_image_tiling.Bind(wx.EVT_MENU, self.OnPopup) @@ -157,26 +162,26 @@ def __init__(self): self.__bind_events() def __bind_events(self): - Publisher.subscribe(self.CheckWindowLevelOther, 'Check window and level other') - Publisher.subscribe(self.FirstItemSelect, 'Select first item from slice menu') - Publisher.subscribe(self._close, 'Close project data') + Publisher.subscribe(self.CheckWindowLevelOther, "Check window and level other") + Publisher.subscribe(self.FirstItemSelect, "Select first item from slice menu") + Publisher.subscribe(self._close, "Close project data") + + Publisher.subscribe(self._check_projection_menu, "Check projection menu") - Publisher.subscribe(self._check_projection_menu, 'Check projection menu') - def FirstItemSelect(self): item = self.ID_TO_TOOL_ITEM[self.id_wl_first] item.Check(True) - + for i in self.pseudo_color_items: it = self.pseudo_color_items[i] if it.IsChecked(): it.Check(False) item = self.ID_TO_TOOL_ITEM[self.id_pseudo_first] item.Check(True) - + # item = self.ID_TO_TOOL_ITEM[self.id_tiling_first] - # item.Check(True) - + # item.Check(True) + def CheckWindowLevelOther(self): item = self.ID_TO_TOOL_ITEM[self.other_wl_id] item.Check() @@ -189,26 +194,29 @@ def OnPopup(self, evt): id = evt.GetId() item = self.ID_TO_TOOL_ITEM[evt.GetId()] key = item.GetItemLabelText() - if(key in const.WINDOW_LEVEL.keys()): + if key in const.WINDOW_LEVEL.keys(): window, level = const.WINDOW_LEVEL[key] - Publisher.sendMessage('Bright and contrast adjustment image', - window=window, level=level) - Publisher.sendMessage('Update window level value', - window=window, - level=level) + Publisher.sendMessage( + "Bright and contrast adjustment image", window=window, level=level + ) + Publisher.sendMessage( + "Update window level value", window=window, level=level + ) # Publisher.sendMessage('Update window and level text', - # "WL: %d WW: %d"%(level, window)) - Publisher.sendMessage('Update slice viewer') + # "WL: %d WW: %d"%(level, window)) + Publisher.sendMessage("Update slice viewer") - #Necessary update the slice plane in the volume case exists - Publisher.sendMessage('Render volume viewer') + # Necessary update the slice plane in the volume case exists + Publisher.sendMessage("Render volume viewer") - elif(key in const.SLICE_COLOR_TABLE.keys()): + elif key in const.SLICE_COLOR_TABLE.keys(): values = const.SLICE_COLOR_TABLE[key] - Publisher.sendMessage('Change colour table from background image', values=values) - Publisher.sendMessage('Update slice viewer') + Publisher.sendMessage( + "Change colour table from background image", values=values + ) + Publisher.sendMessage("Update slice viewer") - if sys.platform.startswith('linux'): + if sys.platform.startswith("linux"): for i in self.pseudo_color_items: it = self.pseudo_color_items[i] it.Check(False) @@ -219,10 +227,12 @@ def OnPopup(self, evt): elif key in self.plist_presets: values = presets.get_wwwl_preset_colours(self.plist_presets[key]) - Publisher.sendMessage('Change colour table from background image from plist', values=values) - Publisher.sendMessage('Update slice viewer') + Publisher.sendMessage( + "Change colour table from background image from plist", values=values + ) + Publisher.sendMessage("Update slice viewer") - if sys.platform.startswith('linux'): + if sys.platform.startswith("linux"): for i in self.pseudo_color_items: it = self.pseudo_color_items[i] it.Check(False) @@ -231,17 +241,17 @@ def OnPopup(self, evt): self.HideClutDialog() self._gen_event = True - elif(key in const.IMAGE_TILING.keys()): + elif key in const.IMAGE_TILING.keys(): values = const.IMAGE_TILING[key] - Publisher.sendMessage('Set slice viewer layout', layout=values) - Publisher.sendMessage('Update slice viewer') + Publisher.sendMessage("Set slice viewer layout", layout=values) + Publisher.sendMessage("Update slice viewer") elif key in PROJECTIONS_ID: pid = PROJECTIONS_ID[key] - Publisher.sendMessage('Set projection type', projection_id=pid) - Publisher.sendMessage('Reload actual slice') + Publisher.sendMessage("Set projection type", projection_id=pid) + Publisher.sendMessage("Reload actual slice") - elif key == _('Custom'): + elif key == _("Custom"): if self.cdialog is None: slc = sl.Slice() histogram = slc.histogram @@ -253,7 +263,7 @@ def OnPopup(self, evt): else: self.cdialog.Show(self._gen_event) - if sys.platform.startswith('linux'): + if sys.platform.startswith("linux"): for i in self.pseudo_color_items: it = self.pseudo_color_items[i] it.Check(False) diff --git a/invesalius/utils.py b/invesalius/utils.py index 61879d418..9fad30e6a 100644 --- a/invesalius/utils.py +++ b/invesalius/utils.py @@ -1,10 +1,10 @@ -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- # Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas # Copyright: (C) 2001 Centro de Pesquisas Renato Archer # Homepage: http://www.softwarepublico.gov.br # Contact: invesalius@cti.gov.br # License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) -#-------------------------------------------------------------------------- +# -------------------------------------------------------------------------- # Este programa e software livre; voce pode redistribui-lo e/ou # modifica-lo sob os termos da Licenca Publica Geral GNU, conforme # publicada pela Free Software Foundation; de acordo com a versao 2 @@ -15,34 +15,34 @@ # COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais # detalhes. -#-------------------------------------------------------------------------- -import platform -import time -import sys -import re +# -------------------------------------------------------------------------- +import collections.abc import locale import math +import platform +import re +import sys +import time import traceback -import collections.abc +from functools import wraps +from typing import Any, List, Optional, Tuple +import numpy as np from setuptools.extern.packaging.version import Version -from functools import wraps, partial -import numpy as np -def format_time(value): +def format_time(value: str) -> str: sp1 = value.split(".") sp2 = value.split(":") - if (len(sp1) == 2) and (len(sp2) == 3): - new_value = str(sp2[0]+sp2[1]+ - str(int(float(sp2[2])))) + if (len(sp1) == 2) and (len(sp2) == 3): + new_value = str(sp2[0] + sp2[1] + str(int(float(sp2[2])))) data = time.strptime(new_value, "%H%M%S") - elif (len(sp1) == 2): + elif len(sp1) == 2: data = time.gmtime(float(value)) - elif (len(sp1) > 2): + elif len(sp1) > 2: data = time.strptime(value, "%H.%M.%S") - elif(len(sp2) > 1): + elif len(sp2) > 1: data = time.strptime(value, "%H:%M:%S") else: try: @@ -50,38 +50,40 @@ def format_time(value): # If the time is not in a bad format only return it. except ValueError: return value - return time.strftime("%H:%M:%S",data) + return time.strftime("%H:%M:%S", data) -def format_date(value): +def format_date(value: str) -> str: sp1 = value.split(".") try: - - if (len(sp1) > 1): - if (len(sp1[0]) <= 2): + if len(sp1) > 1: + if len(sp1[0]) <= 2: data = time.strptime(value, "%D.%M.%Y") else: data = time.strptime(value, "%Y.%M.%d") - elif(len(value.split("//")) > 1): + elif len(value.split("//")) > 1: data = time.strptime(value, "%D/%M/%Y") else: data = time.strptime(value, "%Y%M%d") - return time.strftime("%d/%M/%Y",data) + return time.strftime("%d/%M/%Y", data) + + except ValueError: + return "" - except(ValueError): - return "" -def debug(error_str): +def debug(error_str: str) -> None: """ Redirects output to file, or to the terminal This should be used in the place of "print" """ from invesalius.session import Session + session = Session() - if session.GetConfig('debug'): + if session.GetConfig("debug"): print(error_str) -def next_copy_name(original_name, names_list): + +def next_copy_name(original_name: str, names_list: List[str]) -> str: """ Given original_name of an item and a list of existing names, builds up the name of a copy, keeping the pattern: @@ -96,86 +98,92 @@ def next_copy_name(original_name, names_list): else: parts = original_name.rpartition(" copy#") # is there any copy, might be numbered? - if parts[0] and parts[-1]: + if parts[0] and parts[-1]: # yes, lets check if it ends with a number if isinstance(eval(parts[-1]), int): - last_index = int(parts[-1]) - 1 - first_copy="%s copy"%parts[0] + last_index = int(parts[-1]) - 1 + first_copy = "%s copy" % parts[0] # no... well, so will build the copy name from zero else: last_index = -1 - first_copy = "%s copy"%original_name + first_copy = "%s copy" % original_name # apparently this isthe new copy name, check it - if not (first_copy in names_list): + if first_copy not in names_list: return first_copy - + else: # no, apparently there are no copies, as # separator was not found -- returned ("", " copy#", "") - last_index = -1 - first_copy = "%s copy"%original_name + last_index = -1 + first_copy = "%s copy" % original_name - # apparently this isthe new copy name, check it - if not (first_copy in names_list): + # apparently this isthe new copy name, check it + if first_copy not in names_list: return first_copy # lets build up the new name based on last pattern value got_new_name = False while not got_new_name: last_index += 1 - next_copy = "%s#%d"%(first_copy, last_index+1) - if not (next_copy in names_list): + next_copy = "%s#%d" % (first_copy, last_index + 1) + if next_copy not in names_list: got_new_name = True - return next_copy + return next_copy -def new_name_by_pattern(pattern): +def new_name_by_pattern(pattern: str) -> str: from invesalius.project import Project + proj = Project() mask_dict = proj.mask_dict names_list = [i.name for i in mask_dict.values() if i.name.startswith(pattern + "_")] count = len(names_list) + 1 - return "{}_{}".format(pattern, count) + return f"{pattern}_{count}" + +def VerifyInvalidPListCharacter(text: str) -> bool: + # print text + # text = unicode(text) -def VerifyInvalidPListCharacter(text): - #print text - #text = unicode(text) - _controlCharPat = re.compile( - r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" - r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]") + r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" + r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]" + ) m = _controlCharPat.search(text) if m is not None: return True else: - False + return False -#http://www.garyrobinson.net/2004/03/python_singleto.html +# http://www.garyrobinson.net/2004/03/python_singleto.html # Gary Robinson class Singleton(type): - def __init__(cls,name,bases,dic): - super(Singleton,cls).__init__(name,bases,dic) - cls.instance=None - def __call__(cls,*args,**kw): + def __init__(cls, name, bases, dic): + super().__init__(name, bases, dic) + cls.instance = None + + def __call__(cls, *args, **kw): if cls.instance is None: - cls.instance=super(Singleton,cls).__call__(*args,**kw) + cls.instance = super().__call__(*args, **kw) return cls.instance + # Another possible implementation -#class Singleton(object): +# class Singleton(object): # def __new__(cls, *args, **kwargs): # if '_inst' not in vars(cls): # cls._inst = type.__new__(cls, *args, **kwargs) # return cls._inst + class TwoWaysDictionary(dict): """ Dictionary that can be searched based on a key or on a item. The idea is to be able to search for the key given the item it maps. """ + def __init__(self, items=[]): dict.__init__(self, items) @@ -203,17 +211,19 @@ def get_value(self, key): """ return self[key] -def frange(start, end=None, inc=None): + +# DEPRECATED +def frange(start: float, end: Optional[float] = None, inc: Optional[float] = None) -> List[float]: "A range function, that accepts float increments." - if end == None: + if end is None: end = start + 0.0 start = 0.0 - if (inc == None) or (inc == 0): + if (inc is None) or (inc == 0): inc = 1.0 - L = [] + L: List[float] = [] while 1: next = start + len(L) * inc if inc > 0 and next >= end: @@ -225,30 +235,30 @@ def frange(start, end=None, inc=None): return L - -def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): +# Deprecated +def calculate_resizing_tofitmemory(x_size, y_size, n_slices, byte): """ - Predicts the percentage (between 0 and 1) to resize the image to fit the memory, + Predicts the percentage (between 0 and 1) to resize the image to fit the memory, giving the following information: x_size, y_size: image size n_slices: number of slices byte: bytes allocated for each pixel sample """ - imagesize = x_size * y_size * n_slices * byte * 28 - + imagesize = x_size * y_size * n_slices * byte * 28 + # USING LIBSIGAR - #import sigar - #sg = sigar.open() - #ram_free = sg.mem().actual_free() - #ram_total = sg.mem().total() - #swap_free = sg.swap().free() - #sg.close() - - # USING PSUTIL + # import sigar + # sg = sigar.open() + # ram_free = sg.mem().actual_free() + # ram_total = sg.mem().total() + # swap_free = sg.swap().free() + # sg.close() + + # USING PSUTIL import psutil try: - if (psutil.version_info>=(0,6,0)): + if psutil.version_info >= (0, 6, 0): ram_free = psutil.virtual_memory().available ram_total = psutil.virtual_memory().total swap_free = psutil.swap_memory().free @@ -256,39 +266,40 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): ram_free = psutil.phymem_usage().free + psutil.cached_phymem() + psutil.phymem_buffers() ram_total = psutil.phymem_usage().total swap_free = psutil.virtmem_usage().free - except: + except Exception: print("Exception! psutil version < 0.3 (not recommended)") ram_total = psutil.TOTAL_PHYMEM # this is for psutil < 0.3 - ram_free = 0.8 * psutil.TOTAL_PHYMEM + ram_free = 0.8 * psutil.TOTAL_PHYMEM swap_free = psutil.avail_virtmem() - + print("RAM_FREE=", ram_free) - print( "RAM_TOTAL=", ram_total) - - if (sys.platform == 'win32'): - if (platform.architecture()[0] == '32bit'): - if ram_free>1400000000: - ram_free=1400000000 - if ram_total>1400000000: - ram_total=1400000000 - - if sys.platform.startswith('linux'): - if (platform.architecture()[0] == '32bit'): - if ram_free>3500000000: - ram_free=3500000000 - if ram_total>3500000000: - ram_total=3500000000 - - if (swap_free>ram_total): - swap_free=ram_total - resize = (float((ram_free+0.5*swap_free)/imagesize)) - resize=math.sqrt(resize) # this gives the "resize" for each axis x and y - if (resize>1): - resize=1 - return round(resize,2) - - -def predict_memory(nfiles, x, y, p): + print("RAM_TOTAL=", ram_total) + + if sys.platform == "win32": + if platform.architecture()[0] == "32bit": + if ram_free > 1400000000: + ram_free = 1400000000 + if ram_total > 1400000000: + ram_total = 1400000000 + + if sys.platform.startswith("linux"): + if platform.architecture()[0] == "32bit": + if ram_free > 3500000000: + ram_free = 3500000000 + if ram_total > 3500000000: + ram_total = 3500000000 + + if swap_free > ram_total: + swap_free = ram_total + resize = float((ram_free + 0.5 * swap_free) / imagesize) + resize = math.sqrt(resize) # this gives the "resize" for each axis x and y + if resize > 1: + resize = 1 + return round(resize, 2) + + +# DEPRECATED +def predict_memory(nfiles: int, x: int, y: int, p: int) -> Tuple[float, float]: """ Predict how much memory will be used, giving the following information: @@ -297,68 +308,67 @@ def predict_memory(nfiles, x, y, p): p: bits allocated for each pixel sample """ m = nfiles * (x * y * p) - #physical_memory in Byte + # physical_memory in Byte physical_memory = get_physical_memory() - if (sys.platform == 'win32'): - - if (platform.architecture()[0] == '32bit'): - #(314859200 = 300 MB) - #(26999999 = 25 MB) - #case occupy more than 300 MB image is reduced to 1.5, - #and 25 MB each image is resized 0.04. - if (m >= 314859200): + if sys.platform == "win32": + if platform.architecture()[0] == "32bit": + # (314859200 = 300 MB) + # (26999999 = 25 MB) + # case occupy more than 300 MB image is reduced to 1.5, + # and 25 MB each image is resized 0.04. + if m >= 314859200: porcent = 1.5 + (m - 314859200) / 26999999 * 0.04 else: return (x, y) - else: #64 bits architecture - - #2147483648 byte = 2.0 GB - #4294967296 byte = 4.0 GB + else: # 64 bits architecture + # 2147483648 byte = 2.0 GB + # 4294967296 byte = 4.0 GB if (physical_memory <= 2147483648) and (nfiles <= 1200): porcent = 1.5 + (m - 314859200) / 26999999 * 0.04 - elif(physical_memory <= 2147483648) and (nfiles > 1200): - porcent = 1.5 + (m - 314859200) / 26999999 * 0.05 + elif (physical_memory <= 2147483648) and (nfiles > 1200): + porcent = 1.5 + (m - 314859200) / 26999999 * 0.05 - elif(physical_memory > 2147483648) and \ - (physical_memory <= 4294967296) and (nfiles <= 1200): + elif ( + (physical_memory > 2147483648) + and (physical_memory <= 4294967296) + and (nfiles <= 1200) + ): porcent = 1.5 + (m - 314859200) / 26999999 * 0.02 else: - return (x,y) - - return (x/porcent, y/porcent) + return (x, y) - elif sys.platform.startswith('linux'): + return (x / porcent, y / porcent) - if (platform.architecture()[0] == '32bit'): + elif sys.platform.startswith("linux"): + if platform.architecture()[0] == "32bit": # 839000000 = 800 MB if (m <= 839000000) and (physical_memory <= 2147483648): - return (x,y) + return (x, y) elif (m > 839000000) and (physical_memory <= 2147483648) and (nfiles <= 1200): porcent = 1.5 + (m - 314859200) / 26999999 * 0.02 else: - return (x,y) + return (x, y) else: - if (m <= 839000000) and (physical_memory <= 2147483648): return (x, y) elif (m > 839000000) and (physical_memory <= 2147483648) and (nfiles <= 1200): porcent = 1.5 + (m - 314859200) / 26999999 * 0.02 else: - return (x,y) - - return (x/porcent, y/porcent) + return (x, y) - elif(sys.platform == 'darwin'): - return (x/2,y/2) + return (x / porcent, y / porcent) + elif sys.platform == "darwin": + return (x / 2, y / 2) + raise Exception("Platform not supported") -#def convert_bytes(bytes): +# def convert_bytes(bytes): # if bytes >= 1073741824: # return str(bytes / 1024 / 1024 / 1024) + ' GB' # elif bytes >= 1048576: @@ -369,6 +379,7 @@ def predict_memory(nfiles, x, y, p): # return str(bytes) + ' bytes' +# DEPRECATED def get_physical_memory(): """ Return physical memory in bytes @@ -379,63 +390,69 @@ def get_physical_memory(): return int(mem.total()) - -def get_system_encoding(): - if (sys.platform == 'win32'): +def get_system_encoding() -> Optional[str]: + if sys.platform == "win32": return locale.getdefaultlocale()[1] else: - return 'utf-8' - + return "utf-8" -def UpdateCheck(): +def UpdateCheck() -> None: try: from urllib.parse import urlencode - from urllib.request import urlopen, Request + from urllib.request import Request, urlopen except ImportError: from urllib import urlencode - from urllib2 import urlopen, Request + + from urllib2 import Request, urlopen import wx + import invesalius.session as ses + def _show_update_info(): from invesalius.gui import dialogs - from invesalius.i18n import tr as _ - msg=_("A new version of InVesalius is available. Do you want to open the download website now?") - title=_("Invesalius Update") + # from invesalius.i18n import tr as _ + + # msg = _( + # "A new version of InVesalius is available. Do you want to open the download website now?" + # ) + # title = _("Invesalius Update") msgdlg = dialogs.UpdateMessageDialog(url) - #if (msgdlg.Show()==wx.ID_YES): - #wx.LaunchDefaultBrowser(url) + # if (msgdlg.Show()==wx.ID_YES): + # wx.LaunchDefaultBrowser(url) msgdlg.Show() - #msgdlg.Destroy() + # msgdlg.Destroy() print("Checking updates...") # Check if a language has been set. session = ses.Session() - lang = session.GetConfig('language') - random_id = session.GetConfig('random_id') + lang = session.GetConfig("language") + random_id = session.GetConfig("random_id") - install_lang = 0 if lang: # Fetch update data from server import invesalius.constants as const + url = "https://www.cti.gov.br/dt3d/invesalius/update/checkupdate.php" - headers = { 'User-Agent' : 'Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)' } - data = {'update_protocol_version' : '1', - 'invesalius_version' : const.INVESALIUS_VERSION, - 'platform' : sys.platform, - 'architecture' : platform.architecture()[0], - 'language' : lang, - 'random_id' : random_id } - data = urlencode(data).encode('utf8') + headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)"} + data: Any = { + "update_protocol_version": "1", + "invesalius_version": const.INVESALIUS_VERSION, + "platform": sys.platform, + "architecture": platform.architecture()[0], + "language": lang, + "random_id": random_id, + } + data = urlencode(data).encode("utf8") req = Request(url, data, headers) try: response = urlopen(req, timeout=10) - except: + except Exception: return - last = response.readline().rstrip().decode('utf8') - url = response.readline().rstrip().decode('utf8') + last = response.readline().rstrip().decode("utf8") + url = response.readline().rstrip().decode("utf8") try: last_ver = Version(last) @@ -444,7 +461,7 @@ def _show_update_info(): return if last_ver > actual_ver: - print(" ...New update found!!! -> version:", last) #, ", url=",url + print(" ...New update found!!! -> version:", last) # , ", url=",url wx.CallAfter(wx.CallLater, 1000, _show_update_info) @@ -456,19 +473,19 @@ def vtkarray_to_numpy(m): return nm -def touch(fname): - with open(fname, 'a'): +def touch(fname: str) -> None: + with open(fname, "a"): pass -def decode(text, encoding, *args): +def decode(text: Any, encoding: str, *args) -> Any: try: return text.decode(encoding, *args) except AttributeError: return text -def encode(text, encoding, *args): +def encode(text: Any, encoding: str, *args) -> Any: try: return text.encode(encoding, *args) except AttributeError: @@ -481,19 +498,21 @@ def wrapper(*args, **kwargs): start = time.time() result = f(*args, **kwargs) end = time.time() - print('{} elapsed time: {}'.format(f.__name__, end-start)) + print(f"{f.__name__} elapsed time: {end - start}") return result + return wrapper -def log_traceback(ex): - if hasattr(ex, '__traceback__'): +def log_traceback(ex: Any) -> str: + if hasattr(ex, "__traceback__"): ex_traceback = ex.__traceback__ else: _, _, ex_traceback = sys.exc_info() - tb_lines = [line.rstrip('\n') for line in - traceback.format_exception(ex.__class__, ex, ex_traceback)] - return ''.join(tb_lines) + tb_lines = [ + line.rstrip("\n") for line in traceback.format_exception(ex.__class__, ex, ex_traceback) + ] + return "".join(tb_lines) def deep_merge_dict(d, u):