From 390b707680ac04675f4b29853d2801f018fc81bf Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 20:02:20 +0530 Subject: [PATCH 01/14] VIZ: Line slider initialized --- dipy/viz/ui.py | 134 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index 7936f8b4cf..ade98227d8 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -1292,3 +1292,137 @@ def key_press(i_ren, obj, textbox_object): i_ren.remove_active_prop(textbox_object.actor.get_actor()) i_ren.force_render() + + +class LineSlider2D(UI): + """ A 2D Line Slider. + Currently supports: + - A disk on a line (a thin rectangle). + - Setting disk position. + """ + def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 300), length=200): + + """ + Parameters + ---------- + line_width : int + Width of the line on which the disk will slide. + inner_radius : int + Inner radius of the disk (ring). + outer_radius : int + Outer radius of the disk. + center : (float, float) + Center of the slider. + length : int + Length of the slider. + """ + super(LineSlider2D, self).__init__() + + self.length = length + self.line_width = line_width + self.center = center + self.current_state = center[0] + self.left_x_position = center[0] - length / 2 + self.right_x_position = center[0] + length / 2 + + self.slider_line = None + self.slider_disk = None + self.text = None + + self.build_actors(inner_radius=inner_radius, outer_radius=outer_radius) + + def build_actors(self, inner_radius, outer_radius): + """ Builds required actors. + Parameters + ---------- + inner_radius: int + outer_radius: int + """ + self.slider_line = Rectangle2D(size=(self.length, self.line_width), center=self.center).actor + self.slider_line.GetProperty().SetColor(1, 0, 0) + + # create source + disk = vtk.vtkDiskSource() + disk.SetInnerRadius(inner_radius) + disk.SetOuterRadius(outer_radius) + disk.SetRadialResolution(10) + disk.SetCircumferentialResolution(50) + disk.Update() + + # mapper + mapper = vtk.vtkPolyDataMapper2D() + mapper.SetInputConnection(disk.GetOutputPort()) + + # actor + self.slider_disk = vtk.vtkActor2D() + self.slider_disk.SetMapper(mapper) + + self.slider_disk.SetPosition(self.center[0], self.center[1]) + + self.text = TextActor2D() + + self.text.position = (self.left_x_position-50, self.center[1]-10) + percentage = self.calculate_percentage(current_val=int(self.current_state)) + self.text.message = percentage + self.text.font_size = 16 + + def get_actors(self): + """ Returns the actors that compose this UI component. """ + return [self.slider_line, self.slider_disk, self.text.get_actor()] + + def set_position(self, position): + """ Sets the disk's position. + Parameters + ---------- + position : (float, float) + """ + x_position = position[0] + if x_position < self.center[0] - self.length/2: + x_position = self.center[0] - self.length/2 + if x_position > self.center[0] + self.length/2: + x_position = self.center[0] + self.length/2 + self.slider_disk.SetPosition(x_position, self.center[1]) + self.current_state = x_position + + def calculate_percentage(self, current_val): + """ Calculates the percentage to be displayed. + Parameters + ---------- + current_val : int + """ + percentage = int(((current_val-self.left_x_position)*100)/(self.right_x_position-self.left_x_position)) + if percentage < 0: + percentage = 0 + if percentage > 100: + percentage = 100 + return str(percentage) + "%" + + def set_percentage(self, current_val): + """ Sets text percentage. + Parameters + ---------- + current_val : int + This is the x-position of the slider in the 2D coordinate space + and not the percentage on the base scale. + """ + self.current_state = current_val + percentage = self.calculate_percentage(current_val=current_val) + self.text.message = percentage + + def set_center(self, position): + """ Sets the center of the slider to position. + Parameters + ---------- + position : (float, float) + """ + self.slider_line.SetPosition(position[0] - self.length / 2, position[1] - self.line_width / 2) + + x_change = position[0] - self.center[0] + self.current_state += x_change + self.center = position + self.set_position((self.current_state, self.center[1])) + + self.left_x_position = position[0] - self.length / 2 + self.right_x_position = position[0] + self.length / 2 + self.text.position = (position[0] - self.length / 2 - 40, position[1] - 10) + self.set_percentage(int(self.current_state)) \ No newline at end of file From 25d0bc78c7201430488211832949ea97a69d57a5 Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 20:05:05 +0530 Subject: [PATCH 02/14] VIZ: Line slider refactoring and example --- dipy/viz/ui.py | 32 ++++++++++++++++++++++++++++++-- doc/examples/viz_ui.py | 5 +++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index ade98227d8..36e6daaf98 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -1296,9 +1296,24 @@ def key_press(i_ren, obj, textbox_object): class LineSlider2D(UI): """ A 2D Line Slider. + Currently supports: - A disk on a line (a thin rectangle). - Setting disk position. + + Attributes + ---------- + line_width : int + Width of the line on which the disk will slide. + inner_radius : int + Inner radius of the disk (ring). + outer_radius : int + Outer radius of the disk. + center : (float, float) + Center of the slider. + length : int + Length of the slider. + """ def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 300), length=200): @@ -1315,6 +1330,7 @@ def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 3 Center of the slider. length : int Length of the slider. + """ super(LineSlider2D, self).__init__() @@ -1333,10 +1349,12 @@ def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 3 def build_actors(self, inner_radius, outer_radius): """ Builds required actors. + Parameters ---------- inner_radius: int outer_radius: int + """ self.slider_line = Rectangle2D(size=(self.length, self.line_width), center=self.center).actor self.slider_line.GetProperty().SetColor(1, 0, 0) @@ -1367,14 +1385,18 @@ def build_actors(self, inner_radius, outer_radius): self.text.font_size = 16 def get_actors(self): - """ Returns the actors that compose this UI component. """ + """ Returns the actors that compose this UI component. + + """ return [self.slider_line, self.slider_disk, self.text.get_actor()] def set_position(self, position): """ Sets the disk's position. + Parameters ---------- position : (float, float) + """ x_position = position[0] if x_position < self.center[0] - self.length/2: @@ -1386,9 +1408,11 @@ def set_position(self, position): def calculate_percentage(self, current_val): """ Calculates the percentage to be displayed. + Parameters ---------- current_val : int + """ percentage = int(((current_val-self.left_x_position)*100)/(self.right_x_position-self.left_x_position)) if percentage < 0: @@ -1399,11 +1423,13 @@ def calculate_percentage(self, current_val): def set_percentage(self, current_val): """ Sets text percentage. + Parameters ---------- current_val : int This is the x-position of the slider in the 2D coordinate space and not the percentage on the base scale. + """ self.current_state = current_val percentage = self.calculate_percentage(current_val=current_val) @@ -1411,9 +1437,11 @@ def set_percentage(self, current_val): def set_center(self, position): """ Sets the center of the slider to position. + Parameters ---------- position : (float, float) + """ self.slider_line.SetPosition(position[0] - self.length / 2, position[1] - self.line_width / 2) @@ -1425,4 +1453,4 @@ def set_center(self, position): self.left_x_position = position[0] - self.length / 2 self.right_x_position = position[0] + self.length / 2 self.text.position = (position[0] - self.length / 2 - 40, position[1] - 10) - self.set_percentage(int(self.current_state)) \ No newline at end of file + self.set_percentage(int(self.current_state)) diff --git a/doc/examples/viz_ui.py b/doc/examples/viz_ui.py index 163a515370..e10e0e03fc 100644 --- a/doc/examples/viz_ui.py +++ b/doc/examples/viz_ui.py @@ -98,6 +98,10 @@ def modify_button_callback(i_ren, obj, button): text = ui.TextBox2D(height=3, width=10) # /TextBox +# Line Slider +line_slider = ui.LineSlider2D() +# /Line Slider + # Show Manager current_size = (600, 600) show_manager = window.ShowManager(size=current_size, title="DIPY UI Example") @@ -106,5 +110,6 @@ def modify_button_callback(i_ren, obj, button): show_manager.ren.add(cube_actor_2) show_manager.ren.add(panel) show_manager.ren.add(text) +show_manager.ren.add(line_slider) show_manager.start() From c396b039ac332757897b198ff8dcfeab8f58003f Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 22:19:26 +0530 Subject: [PATCH 03/14] VIZ: Lineslider events --- dipy/viz/ui.py | 68 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index 36e6daaf98..1ba3de7923 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -1295,7 +1295,8 @@ def key_press(i_ren, obj, textbox_object): class LineSlider2D(UI): - """ A 2D Line Slider. + """ A 2D Line Slider. + A sliding ring on a line with a percentage indicator. Currently supports: - A disk on a line (a thin rectangle). @@ -1313,9 +1314,15 @@ class LineSlider2D(UI): Center of the slider. length : int Length of the slider. + slider_line : :class:`vtkActor` + The line on which the slider disk moves. + slider_disk : :class:`vtkActor` + The moving slider disk. + text : :class:`TextActor2D` + The text that shows percentage. """ - def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 300), length=200): + def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 300), length=200, text_size=16): """ Parameters @@ -1345,21 +1352,31 @@ def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 3 self.slider_disk = None self.text = None - self.build_actors(inner_radius=inner_radius, outer_radius=outer_radius) + self.build_actors(inner_radius=inner_radius, outer_radius=outer_radius, text_size=text_size) - def build_actors(self, inner_radius, outer_radius): + self.handle_events(None) + + def build_actors(self, inner_radius, outer_radius, text_size): """ Builds required actors. Parameters ---------- inner_radius: int + The inner radius of the sliding disk. outer_radius: int + The outer radius of the sliding disk. + text_size: int + Size of the text that displays percentage. """ + # Slider Line self.slider_line = Rectangle2D(size=(self.length, self.line_width), center=self.center).actor self.slider_line.GetProperty().SetColor(1, 0, 0) - # create source + # /Slider Line + + # Slider Disk + # Create source disk = vtk.vtkDiskSource() disk.SetInnerRadius(inner_radius) disk.SetOuterRadius(outer_radius) @@ -1367,22 +1384,26 @@ def build_actors(self, inner_radius, outer_radius): disk.SetCircumferentialResolution(50) disk.Update() - # mapper + # Mapper mapper = vtk.vtkPolyDataMapper2D() mapper.SetInputConnection(disk.GetOutputPort()) - # actor + # Actor self.slider_disk = vtk.vtkActor2D() self.slider_disk.SetMapper(mapper) self.slider_disk.SetPosition(self.center[0], self.center[1]) + # /Slider Disk + + # Slider Text self.text = TextActor2D() self.text.position = (self.left_x_position-50, self.center[1]-10) percentage = self.calculate_percentage(current_val=int(self.current_state)) self.text.message = percentage - self.text.font_size = 16 + self.text.font_size = text_size + # /Slider Text def get_actors(self): """ Returns the actors that compose this UI component. @@ -1396,6 +1417,7 @@ def set_position(self, position): Parameters ---------- position : (float, float) + The absolute position of the disk (x, y). """ x_position = position[0] @@ -1412,6 +1434,7 @@ def calculate_percentage(self, current_val): Parameters ---------- current_val : int + Absolute value of the disk's x position. """ percentage = int(((current_val-self.left_x_position)*100)/(self.right_x_position-self.left_x_position)) @@ -1441,6 +1464,7 @@ def set_center(self, position): Parameters ---------- position : (float, float) + The new center of the whole slider (x, y). """ self.slider_line.SetPosition(position[0] - self.length / 2, position[1] - self.line_width / 2) @@ -1454,3 +1478,31 @@ def set_center(self, position): self.right_x_position = position[0] + self.length / 2 self.text.position = (position[0] - self.length / 2 - 40, position[1] - 10) self.set_percentage(int(self.current_state)) + + @staticmethod + def line_click_callback(i_ren, obj, slider): + # Update disk position and grab the focus. + position = i_ren.event.position + slider.set_position(position) + slider.set_percentage(position[0]) + i_ren.force_render() + i_ren.event.abort() # Stop propagating the event. + + @staticmethod + def disk_press_callback(i_ren, obj, slider): + # Only need to grab the focus. + i_ren.event.abort() # Stop propagating the event. + + @staticmethod + def disk_move_callback(i_ren, obj, slider): + position = i_ren.event.position + slider.set_position(position) + slider.set_percentage(position[0]) + i_ren.force_render() + i_ren.event.abort() # Stop propagating the event. + + def handle_events(self, actor): + self.add_callback(self.slider_line, "LeftButtonPressEvent", self.line_click_callback) + self.add_callback(self.slider_disk, "LeftButtonPressEvent", self.disk_press_callback) + self.add_callback(self.slider_disk, "MouseMoveEvent", self.disk_move_callback) + self.add_callback(self.slider_line, "MouseMoveEvent", self.disk_move_callback) From 8efaa04cfa169733991f51c2bb7b1c6116883a8a Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 22:23:22 +0530 Subject: [PATCH 04/14] VIZ: LineSlider - PEP8 Docstrings --- dipy/viz/ui.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index 1ba3de7923..4cbbaa3828 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -1481,7 +1481,16 @@ def set_center(self, position): @staticmethod def line_click_callback(i_ren, obj, slider): - # Update disk position and grab the focus. + """ Update disk position and grab the focus. + + Parameters + ---------- + i_ren : :class:`CustomInteractorStyle` + obj : :class:`vtkActor` + The picked actor + slider : :class:`LineSlider2D` + + """ position = i_ren.event.position slider.set_position(position) slider.set_percentage(position[0]) @@ -1490,11 +1499,30 @@ def line_click_callback(i_ren, obj, slider): @staticmethod def disk_press_callback(i_ren, obj, slider): - # Only need to grab the focus. + """ Only need to grab the focus. + + Parameters + ---------- + i_ren : :class:`CustomInteractorStyle` + obj : :class:`vtkActor` + The picked actor + slider : :class:`LineSlider2D` + + """ i_ren.event.abort() # Stop propagating the event. @staticmethod def disk_move_callback(i_ren, obj, slider): + """ Actual disk movement. + + Parameters + ---------- + i_ren : :class:`CustomInteractorStyle` + obj : :class:`vtkActor` + The picked actor + slider : :class:`LineSlider2D` + + """ position = i_ren.event.position slider.set_position(position) slider.set_percentage(position[0]) @@ -1502,6 +1530,10 @@ def disk_move_callback(i_ren, obj, slider): i_ren.event.abort() # Stop propagating the event. def handle_events(self, actor): + """ Handle all events for the LineSlider. + Base method needs to be overridden due to multiple actors. + + """ self.add_callback(self.slider_line, "LeftButtonPressEvent", self.line_click_callback) self.add_callback(self.slider_disk, "LeftButtonPressEvent", self.disk_press_callback) self.add_callback(self.slider_disk, "MouseMoveEvent", self.disk_move_callback) From b31f0db11f370a31bf6667bdef73358abfea4e60 Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 22:29:01 +0530 Subject: [PATCH 05/14] VIZ: Added tests for LineSlider --- dipy/viz/tests/test_ui.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/dipy/viz/tests/test_ui.py b/dipy/viz/tests/test_ui.py index 7dbd568820..c005be01a0 100644 --- a/dipy/viz/tests/test_ui.py +++ b/dipy/viz/tests/test_ui.py @@ -250,6 +250,36 @@ def test_text_actor_2d(): # /TextActor2D +@npt.dec.skipif(not have_vtk or skip_it) +@xvfb_it +def test_ui_line_slider_2d(recording=False): + filename = "test_ui_line_slider_2d" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".pkl") + + line_slider_2d_test = ui.LineSlider2D() + line_slider_2d_test.set_center((300, 300)) + + # Assign the counter callback to every possible event. + event_counter = EventCounter() + event_counter.monitor(line_slider_2d_test) + + current_size = (600, 600) + show_manager = window.ShowManager(size=current_size, title="DIPY Line Slider") + + show_manager.ren.add(line_slider_2d_test) + + if recording: + show_manager.record_events_to_file(recording_filename) + print(list(event_counter.events_counts.items())) + event_counter.save(expected_events_counts_filename) + + else: + show_manager.play_events_from_file(recording_filename) + expected = EventCounter.load(expected_events_counts_filename) + event_counter.check_counts(expected) + + if __name__ == "__main__": if len(sys.argv) <= 1 or sys.argv[1] == "test_ui_button_panel": test_ui_button_panel(recording=True) From ef6c0288a12c76892cce8d24f573b141a9491d6d Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 22:29:52 +0530 Subject: [PATCH 06/14] VIZ: LineSlider2D test files --- dipy/data/files/test_ui_line_slider_2d.log.gz | Bin 0 -> 1665 bytes dipy/data/files/test_ui_line_slider_2d.pkl | Bin 0 -> 252 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 dipy/data/files/test_ui_line_slider_2d.log.gz create mode 100644 dipy/data/files/test_ui_line_slider_2d.pkl diff --git a/dipy/data/files/test_ui_line_slider_2d.log.gz b/dipy/data/files/test_ui_line_slider_2d.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..6036e351ccbdc0763b0ef0458f019d238db8bb05 GIT binary patch literal 1665 zcmV-{27dV;iwFoLIo((S|8!+@bYFF8UuG0dl)$z^WzaOuz|9toMATM6Ny*XaJ{BV4GbFf|x((O?G{rBSayZ6_}*Y7?YPX%=x zWOe}54_Z(P5mcG?gEq%OwE{5OL8}7LYXEeKt_1jGz!_k9u)zS!Wa|tF0X0g|3IamF zYVNoU03`rwO^N={K!<51+M;y?qCYfH%o-h5O%RA;S_5E2G0hQ(Vn&PJ5BgvrO5vcY zt49j12t-YKML-BxebP}q*t&!v5S`XhMOjpogVwAqs@hRSwZ_0ICh8B-qqU=|j!{lK z>M*r45Si?#vDTf{QCsNB1FGs6BekP?kVs*mOiH9MP;;+aZ=l91Q4a&Uby>RO6$0YHNC3;jU^n1kdFYb? z))t-BL(GFP@V=ko04M?G1hhs#T>#7?RNJ15+z-SED1A?N04Ny99&ZsCodG4F4+d5} z7y`;bAz=3hN5B|pd+G^5O#{6EAQu42*9SH)0E{#^DG!5H)L{3;L|_uI2M`42z(BPS zP>ld1_r+RZfFq2uQSRfj8@+uOB|s(tI&mJ>A_FW%ZIb{tfYd#IK(FU}nuJyeB`zUw zg|HJ~EQOUx2;yB00%MspC6}-@J%FGM0@gqZK{o;hOJO(xt5;({8q3S)d3bevbARxQ zza|ak(imVF6m|(MX&_Gzl!3|s8AM&lIPJACAb9Y244`RH8E6bR1A~CZirH8m8p}f? zlMA=PfH0ux6eOF;HW_dRSSc!bb?`!}+SruYisL7LtK@C9j)7Mw>)QH5>#S9jEaeU* zi@_!YXv09*db6y3S_q7#PzM8r02KyU3VVwKRQ0X~5Fex#I;n(CTC$T8I@#P<+lnwQ zc=u#&D#EyvxZ(XB8gCaKXZd#$?>N5Or;`2w;$z~tNW)urvPLVkjVLkoU5lY$@y+*Q zC|CgOc&~FUgs#<6ve$_~JX$cAWIJ^qmKcr>jR@XOZEJsRO!Ok71gv2lfSLz(0fT3BoD*8sTfMLMmr!CcoFLUdoO^MmltTE_zkAh*97yM(bztVtLwg?SqE z3jiYxCIMa^9ei?x`ODycrx<=c{&jqFeez{!NuB{XnM<7jgxtWV0Z9Qi%A5vj3e-md zEd|JP>jyX)*rNdd0i2!4{6B$_0_~yT?gMt2bUu5;opC`qtLVnqMAkStg`I7u+lz|)|mK}`cq19>R8 z`z`_O_Fcj*_;D(d10xN38h9F58q^B_`H%cc7O+z~&jNZHcp6w5wC@6F8k8*PGYv)> z^fd4^urz2F0P6FDyVsC1>D*q};3-M=gF_>S#7_Y0fOgM-b;h$=0?3SFjucp5ht8P+ z(L)>j5|0A+Lz9*Q5@2S4mHkUL3D7ja$bi}p4gC(ziJ+xGRCTiyh>Eh70c!iVkrF`0 z{)MLuh{hV405LT*sprRIT4&;kxExIT`SExFuTP%Nk6XlhBCZE(nutgJqJGNa9`)OL zBHs5GVY&Fs#jPab{*A{i8E^X_?D_Hf2ysos{Zp6c$D@3`ogc5!Kl@C?qko|J z360zSn)v*9j8E`q-&j1x7kJQ_#5L*%|B@MrkNqw5`SG~F;5+Q+@lQUZj`^+M`uOi3 L#(uHKvsC~9sQv#u literal 0 HcmV?d00001 diff --git a/dipy/data/files/test_ui_line_slider_2d.pkl b/dipy/data/files/test_ui_line_slider_2d.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9f9ae3b2aa29b9cd8386e212ddae0018b32ff1d7 GIT binary patch literal 252 zcmZo*sx4&D2$k^7Oi9T}bt)|>$D8VAh1eE0U zPOS_mN-ZvisAmQWiTI?ZL6sxPuz0hF^7-bM7N`2=mqATu_2ps=<#f(SEQ07}1L_t- m=tgk~J5awUl1q?`<^XEr2f71AJtxR4WPc%P;PPfD)dK(=7gs|7 literal 0 HcmV?d00001 From e0d708c0c67447af2a0d553b7fe01da13f43c933 Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Thu, 30 Mar 2017 23:43:15 +0530 Subject: [PATCH 07/14] VIZ: LineSlider2D test added to main --- dipy/viz/tests/test_ui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dipy/viz/tests/test_ui.py b/dipy/viz/tests/test_ui.py index c005be01a0..627181d24c 100644 --- a/dipy/viz/tests/test_ui.py +++ b/dipy/viz/tests/test_ui.py @@ -286,3 +286,6 @@ def test_ui_line_slider_2d(recording=False): if len(sys.argv) <= 1 or sys.argv[1] == "test_ui_textbox": test_ui_textbox(recording=True) + + if len(sys.argv) <= 1 or sys.argv[1] == "test_ui_line_slider_2d": + test_ui_line_slider_2d(recording=True) From 371b6d9a0775cb44ba799efa8b342ed8b9e18b7b Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Fri, 31 Mar 2017 20:09:24 +0530 Subject: [PATCH 08/14] VIZ, LineSlider2D: Fixed PEP8 Issues --- dipy/viz/ui.py | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index 4cbbaa3828..05b0a1b7ff 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -1295,13 +1295,13 @@ def key_press(i_ren, obj, textbox_object): class LineSlider2D(UI): - """ A 2D Line Slider. + """ A 2D Line Slider. A sliding ring on a line with a percentage indicator. - + Currently supports: - A disk on a line (a thin rectangle). - Setting disk position. - + Attributes ---------- line_width : int @@ -1320,9 +1320,11 @@ class LineSlider2D(UI): The moving slider disk. text : :class:`TextActor2D` The text that shows percentage. - + """ - def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 300), length=200, text_size=16): + def __init__(self, line_width=5, inner_radius=0, + outer_radius=10, center=(450, 300), + length=200, text_size=16): """ Parameters @@ -1337,7 +1339,7 @@ def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 3 Center of the slider. length : int Length of the slider. - + """ super(LineSlider2D, self).__init__() @@ -1352,13 +1354,14 @@ def __init__(self, line_width=5, inner_radius=0, outer_radius=10, center=(450, 3 self.slider_disk = None self.text = None - self.build_actors(inner_radius=inner_radius, outer_radius=outer_radius, text_size=text_size) + self.build_actors(inner_radius=inner_radius, + outer_radius=outer_radius, text_size=text_size) self.handle_events(None) def build_actors(self, inner_radius, outer_radius, text_size): """ Builds required actors. - + Parameters ---------- inner_radius: int @@ -1367,12 +1370,12 @@ def build_actors(self, inner_radius, outer_radius, text_size): The outer radius of the sliding disk. text_size: int Size of the text that displays percentage. - + """ # Slider Line - self.slider_line = Rectangle2D(size=(self.length, self.line_width), center=self.center).actor + self.slider_line = Rectangle2D(size=(self.length, self.line_width), + center=self.center).actor self.slider_line.GetProperty().SetColor(1, 0, 0) - # /Slider Line # Slider Disk @@ -1391,9 +1394,7 @@ def build_actors(self, inner_radius, outer_radius, text_size): # Actor self.slider_disk = vtk.vtkActor2D() self.slider_disk.SetMapper(mapper) - self.slider_disk.SetPosition(self.center[0], self.center[1]) - # /Slider Disk # Slider Text @@ -1406,19 +1407,19 @@ def build_actors(self, inner_radius, outer_radius, text_size): # /Slider Text def get_actors(self): - """ Returns the actors that compose this UI component. - + """ Returns the actors that compose this UI component. + """ return [self.slider_line, self.slider_disk, self.text.get_actor()] def set_position(self, position): """ Sets the disk's position. - + Parameters ---------- position : (float, float) The absolute position of the disk (x, y). - + """ x_position = position[0] if x_position < self.center[0] - self.length/2: @@ -1430,29 +1431,31 @@ def set_position(self, position): def calculate_percentage(self, current_val): """ Calculates the percentage to be displayed. - + Parameters ---------- current_val : int Absolute value of the disk's x position. - + """ - percentage = int(((current_val-self.left_x_position)*100)/(self.right_x_position-self.left_x_position)) + percentage = int(((current_val-self.left_x_position)*100 + )/(self.right_x_position-self.left_x_position)) if percentage < 0: percentage = 0 if percentage > 100: percentage = 100 + return str(percentage) + "%" def set_percentage(self, current_val): """ Sets text percentage. - + Parameters ---------- current_val : int This is the x-position of the slider in the 2D coordinate space and not the percentage on the base scale. - + """ self.current_state = current_val percentage = self.calculate_percentage(current_val=current_val) @@ -1460,14 +1463,15 @@ def set_percentage(self, current_val): def set_center(self, position): """ Sets the center of the slider to position. - + Parameters ---------- position : (float, float) The new center of the whole slider (x, y). - + """ - self.slider_line.SetPosition(position[0] - self.length / 2, position[1] - self.line_width / 2) + self.slider_line.SetPosition(position[0] - self.length / 2, + position[1] - self.line_width / 2) x_change = position[0] - self.center[0] self.current_state += x_change @@ -1482,14 +1486,14 @@ def set_center(self, position): @staticmethod def line_click_callback(i_ren, obj, slider): """ Update disk position and grab the focus. - + Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` - + """ position = i_ren.event.position slider.set_position(position) @@ -1500,28 +1504,28 @@ def line_click_callback(i_ren, obj, slider): @staticmethod def disk_press_callback(i_ren, obj, slider): """ Only need to grab the focus. - + Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` - + """ i_ren.event.abort() # Stop propagating the event. @staticmethod def disk_move_callback(i_ren, obj, slider): """ Actual disk movement. - + Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` - + """ position = i_ren.event.position slider.set_position(position) @@ -1531,8 +1535,8 @@ def disk_move_callback(i_ren, obj, slider): def handle_events(self, actor): """ Handle all events for the LineSlider. - Base method needs to be overridden due to multiple actors. - + Base method needs to be overridden due to multiple actors. + """ self.add_callback(self.slider_line, "LeftButtonPressEvent", self.line_click_callback) self.add_callback(self.slider_disk, "LeftButtonPressEvent", self.disk_press_callback) From bc1989c595ca3533177811d3544db97d510dc0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Alexandre=20C=C3=B4t=C3=A9?= Date: Sat, 1 Apr 2017 01:34:18 -0400 Subject: [PATCH 09/14] ENH: Can now use value or ratio to set automatically the position of the slider. --- dipy/viz/tests/test_ui.py | 6 +- dipy/viz/ui.py | 117 +++++++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/dipy/viz/tests/test_ui.py b/dipy/viz/tests/test_ui.py index 627181d24c..d2814cb6e7 100644 --- a/dipy/viz/tests/test_ui.py +++ b/dipy/viz/tests/test_ui.py @@ -257,7 +257,8 @@ def test_ui_line_slider_2d(recording=False): recording_filename = pjoin(DATA_DIR, filename + ".log.gz") expected_events_counts_filename = pjoin(DATA_DIR, filename + ".pkl") - line_slider_2d_test = ui.LineSlider2D() + line_slider_2d_test = ui.LineSlider2D(initial_value=-2, + min_value=-5, max_value=5) line_slider_2d_test.set_center((300, 300)) # Assign the counter callback to every possible event. @@ -265,7 +266,8 @@ def test_ui_line_slider_2d(recording=False): event_counter.monitor(line_slider_2d_test) current_size = (600, 600) - show_manager = window.ShowManager(size=current_size, title="DIPY Line Slider") + show_manager = window.ShowManager(size=current_size, + title="DIPY Line Slider") show_manager.ren.add(line_slider_2d_test) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index 05b0a1b7ff..eeaf53a951 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -1,3 +1,4 @@ +from __future__ import division from _warnings import warn import numpy as np @@ -1296,6 +1297,7 @@ def key_press(i_ren, obj, textbox_object): class LineSlider2D(UI): """ A 2D Line Slider. + A sliding ring on a line with a percentage indicator. Currently supports: @@ -1322,10 +1324,10 @@ class LineSlider2D(UI): The text that shows percentage. """ - def __init__(self, line_width=5, inner_radius=0, - outer_radius=10, center=(450, 300), - length=200, text_size=16): - + def __init__(self, line_width=5, inner_radius=0, outer_radius=10, + center=(450, 300), length=200, initial_value=50, + min_value=0, max_value=100, text_size=16, + text_template="{value:.1f} ({ratio:.0%})"): """ Parameters ---------- @@ -1339,16 +1341,35 @@ def __init__(self, line_width=5, inner_radius=0, Center of the slider. length : int Length of the slider. + initial_value : float + Initial value of the slider. + min_value : float + Minimum value of the slider. + max_value : float + Maximum value of the slider. + text_size : int + Size of the text to display alongside the slider (pt). + text_template : str, callable + If str, text template can contain one or multiple of the + replacement fields: `{value:}`, `{ratio:}`. + If callable, this instance of `:class:LineSlider2D` will be + passed as argument to the text template function. """ super(LineSlider2D, self).__init__() self.length = length + self.min_value = min_value + self.max_value = max_value + + self.text_template = text_template + self.line_width = line_width self.center = center self.current_state = center[0] self.left_x_position = center[0] - length / 2 self.right_x_position = center[0] + length / 2 + self._ratio = (self.current_state - self.left_x_position) / length self.slider_line = None self.slider_disk = None @@ -1357,6 +1378,10 @@ def __init__(self, line_width=5, inner_radius=0, self.build_actors(inner_radius=inner_radius, outer_radius=outer_radius, text_size=text_size) + # Setting the disk position will also update everything. + self.value = initial_value + # self.update() + self.handle_events(None) def build_actors(self, inner_radius, outer_radius, text_size): @@ -1394,15 +1419,11 @@ def build_actors(self, inner_radius, outer_radius, text_size): # Actor self.slider_disk = vtk.vtkActor2D() self.slider_disk.SetMapper(mapper) - self.slider_disk.SetPosition(self.center[0], self.center[1]) # /Slider Disk # Slider Text self.text = TextActor2D() - - self.text.position = (self.left_x_position-50, self.center[1]-10) - percentage = self.calculate_percentage(current_val=int(self.current_state)) - self.text.message = percentage + self.text.position = (self.left_x_position - 50, self.center[1] - 10) self.text.font_size = text_size # /Slider Text @@ -1422,44 +1443,63 @@ def set_position(self, position): """ x_position = position[0] + if x_position < self.center[0] - self.length/2: x_position = self.center[0] - self.length/2 + if x_position > self.center[0] + self.length/2: x_position = self.center[0] + self.length/2 - self.slider_disk.SetPosition(x_position, self.center[1]) + self.current_state = x_position + self.update() - def calculate_percentage(self, current_val): - """ Calculates the percentage to be displayed. + @property + def value(self): + return self._value - Parameters - ---------- - current_val : int - Absolute value of the disk's x position. + @value.setter + def value(self, value): + value_range = self.max_value - self.min_value + self.ratio = (value - self.min_value) / value_range - """ - percentage = int(((current_val-self.left_x_position)*100 - )/(self.right_x_position-self.left_x_position)) - if percentage < 0: - percentage = 0 - if percentage > 100: - percentage = 100 + @property + def ratio(self): + return self._ratio - return str(percentage) + "%" + @ratio.setter + def ratio(self, ratio): + position_x = self.left_x_position + ratio*self.length + self.set_position((position_x, None)) - def set_percentage(self, current_val): - """ Sets text percentage. + def format_text(self): + """ Returns formatted text to display along the slider. """ + if callable(self.text_template): + return self.text_template(self) - Parameters - ---------- - current_val : int - This is the x-position of the slider in the 2D coordinate space - and not the percentage on the base scale. + return self.text_template.format(ratio=self.ratio, value=self.value) - """ - self.current_state = current_val - percentage = self.calculate_percentage(current_val=current_val) - self.text.message = percentage + def update(self): + """ Updates the slider. """ + + # Compute the ratio determined by the position of the slider disk. + length = float(self.right_x_position - self.left_x_position) + assert length == self.length + self._ratio = (self.current_state - self.left_x_position) / length + + # Compute the selected value considering min_value and max_value. + value_range = self.max_value - self.min_value + self._value = self.min_value + self.ratio*value_range + + # Update text disk actor. + self.slider_disk.SetPosition(self.current_state, self.center[1]) + + # Update text. + text = self.format_text() + self.text.message = text + offset_x = 8 * len(text) / 2. + offset_y = 30 + self.text.position = (self.current_state - offset_x, + self.center[1] - offset_y) def set_center(self, position): """ Sets the center of the slider to position. @@ -1476,12 +1516,9 @@ def set_center(self, position): x_change = position[0] - self.center[0] self.current_state += x_change self.center = position - self.set_position((self.current_state, self.center[1])) - self.left_x_position = position[0] - self.length / 2 self.right_x_position = position[0] + self.length / 2 - self.text.position = (position[0] - self.length / 2 - 40, position[1] - 10) - self.set_percentage(int(self.current_state)) + self.set_position((self.current_state, self.center[1])) @staticmethod def line_click_callback(i_ren, obj, slider): @@ -1497,7 +1534,6 @@ def line_click_callback(i_ren, obj, slider): """ position = i_ren.event.position slider.set_position(position) - slider.set_percentage(position[0]) i_ren.force_render() i_ren.event.abort() # Stop propagating the event. @@ -1529,7 +1565,6 @@ def disk_move_callback(i_ren, obj, slider): """ position = i_ren.event.position slider.set_position(position) - slider.set_percentage(position[0]) i_ren.force_render() i_ren.event.abort() # Stop propagating the event. From 10dd1427e798867cb108ead2807ed49d42fb039c Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Fri, 7 Apr 2017 09:36:51 +0530 Subject: [PATCH 10/14] VIZ: Fix to position setter in TextActor --- dipy/viz/ui.py | 2 +- doc/examples/viz_ui.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dipy/viz/ui.py b/dipy/viz/ui.py index eeaf53a951..9534fa79aa 100644 --- a/dipy/viz/ui.py +++ b/dipy/viz/ui.py @@ -919,7 +919,7 @@ def position(self, position): The new position. (x, y) in pixels. """ - self.actor.SetDisplayPosition(*position) + self.actor.SetPosition(*position) class TextBox2D(UI): diff --git a/doc/examples/viz_ui.py b/doc/examples/viz_ui.py index e10e0e03fc..18ac029c6a 100644 --- a/doc/examples/viz_ui.py +++ b/doc/examples/viz_ui.py @@ -99,7 +99,8 @@ def modify_button_callback(i_ren, obj, button): # /TextBox # Line Slider -line_slider = ui.LineSlider2D() +line_slider = ui.LineSlider2D(initial_value=-2, + min_value=-5, max_value=5) # /Line Slider # Show Manager From 3fb2d9d8a480256bf790de4e26437acad2b11fdb Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Sat, 8 Apr 2017 02:20:48 +0530 Subject: [PATCH 11/14] VIZ: Fetch VIZ icons in doc/examples/viz_ui.py --- doc/examples/viz_ui.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/examples/viz_ui.py b/doc/examples/viz_ui.py index 18ac029c6a..af89802dd0 100644 --- a/doc/examples/viz_ui.py +++ b/doc/examples/viz_ui.py @@ -1,6 +1,6 @@ import numpy as np -from dipy.data import read_viz_icons +from dipy.data import read_viz_icons, fetch_viz_icons # Conditional import machinery for vtk. from dipy.utils.optpkg import optional_package @@ -41,6 +41,8 @@ def cube_maker(color=None, size=(0.2, 0.2, 0.2), center=None): # /Cube Actors # Buttons +fetch_viz_icons() + icon_files = dict() icon_files['stop'] = read_viz_icons(fname='stop2.png') icon_files['play'] = read_viz_icons(fname='play3.png') From f77d5911c661da1340a590c8600baa5ca07d203d Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Sat, 8 Apr 2017 02:41:16 +0530 Subject: [PATCH 12/14] VIZ: Added documentation for viz_ui --- doc/examples/viz_ui.py | 79 +++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/doc/examples/viz_ui.py b/doc/examples/viz_ui.py index af89802dd0..1b284cfe01 100644 --- a/doc/examples/viz_ui.py +++ b/doc/examples/viz_ui.py @@ -1,4 +1,14 @@ -import numpy as np +""" +============== +User Interface +============== + +This example shows how to use the UI API. +Currently includes button, textbox, panel, and line slider. + +First, a bunch of imports. + +""" from dipy.data import read_viz_icons, fetch_viz_icons @@ -19,8 +29,14 @@ numpy_support, have_ns, _ = optional_package('vtk.util.numpy_support') +""" +3D Elements +=========== + +Let's have some cubes in 3D. +""" + -# Cube Actors def cube_maker(color=None, size=(0.2, 0.2, 0.2), center=None): cube = vtk.vtkCubeSource() cube.SetXLength(size[0]) @@ -38,19 +54,36 @@ def cube_maker(color=None, size=(0.2, 0.2, 0.2), center=None): cube_actor_1 = cube_maker((1, 0, 0), (50, 50, 50), center=(0, 0, 0)) cube_actor_2 = cube_maker((0, 1, 0), (10, 10, 10), center=(100, 0, 0)) -# /Cube Actors -# Buttons +""" +Buttons +======= + +We first fetch the icons required for making the buttons. +""" + fetch_viz_icons() +""" +Add the icon filenames to a dict. +""" + icon_files = dict() icon_files['stop'] = read_viz_icons(fname='stop2.png') icon_files['play'] = read_viz_icons(fname='play3.png') icon_files['plus'] = read_viz_icons(fname='plus.png') icon_files['cross'] = read_viz_icons(fname='cross.png') +""" +Create a button through our API. +""" + button_example = ui.Button2D(icon_fnames=icon_files) +""" +We now add some click listeners. +""" + def left_mouse_button_click(i_ren, obj, button): print("Left Button Clicked") @@ -73,9 +106,17 @@ def right_mouse_button_click(i_ren, obj, button): button_example.on_right_mouse_button_drag = right_mouse_button_drag button_example.on_right_mouse_button_pressed = right_mouse_button_click +""" +Let's have another button. +""" second_button_example = ui.Button2D(icon_fnames=icon_files) +""" +This time, we will call the built in `next_icon` method +via a callback that is triggered on left click. +""" + def modify_button_callback(i_ren, obj, button): # i_ren: CustomInteractorStyle @@ -86,26 +127,40 @@ def modify_button_callback(i_ren, obj, button): second_button_example.on_left_mouse_button_pressed = modify_button_callback -# /Buttons +""" +Panels +====== +Simply create a panel and add elements to it. +""" -# Panel panel = ui.Panel2D(center=(440, 90), size=(300, 150), color=(1, 1, 1), align="right") panel.add_element(button_example, 'relative', (0.2, 0.2)) panel.add_element(second_button_example, 'absolute', (480, 100)) -# /Panel +""" +TextBox +======= +""" -# TextBox text = ui.TextBox2D(height=3, width=10) -# /TextBox -# Line Slider +""" +2D Line Slider +============== +""" + line_slider = ui.LineSlider2D(initial_value=-2, min_value=-5, max_value=5) -# /Line Slider -# Show Manager +""" +Adding Elements to the ShowManager +================================== + +Once all elements have been initialised, they have +to be added to the show manager in the following manner. +""" + current_size = (600, 600) show_manager = window.ShowManager(size=current_size, title="DIPY UI Example") From 8c8eda8c8fab133e2e1063d21d2ffcf83b28a8da Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Mon, 10 Apr 2017 00:48:26 +0530 Subject: [PATCH 13/14] VIZ: Added to documentation --- doc/examples/valid_examples.txt | 1 + doc/examples/viz_ui.py | 12 ++---------- doc/examples_index.rst | 1 + 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/doc/examples/valid_examples.txt b/doc/examples/valid_examples.txt index ef32d471e8..b782e8d9ec 100644 --- a/doc/examples/valid_examples.txt +++ b/doc/examples/valid_examples.txt @@ -51,3 +51,4 @@ workflow_creation.py combined_workflow_creation.py viz_surfaces.py + viz_ui.py diff --git a/doc/examples/viz_ui.py b/doc/examples/viz_ui.py index 1b284cfe01..0c1a5df52c 100644 --- a/doc/examples/viz_ui.py +++ b/doc/examples/viz_ui.py @@ -20,15 +20,6 @@ vtk, have_vtk, setup_module = optional_package('vtk') -if have_vtk: - vtkInteractorStyleUser = vtk.vtkInteractorStyleUser - version = vtk.vtkVersion.GetVTKSourceVersion().split(' ')[-1] - major_version = vtk.vtkVersion.GetVTKMajorVersion() -else: - vtkInteractorStyleUser = object - -numpy_support, have_ns, _ = optional_package('vtk.util.numpy_support') - """ 3D Elements =========== @@ -170,4 +161,5 @@ def modify_button_callback(i_ren, obj, button): show_manager.ren.add(text) show_manager.ren.add(line_slider) -show_manager.start() +# Uncomment this to start the visualisation +# show_manager.start() diff --git a/doc/examples_index.rst b/doc/examples_index.rst index f5a64e77de..82c2396fbb 100644 --- a/doc/examples_index.rst +++ b/doc/examples_index.rst @@ -228,6 +228,7 @@ Visualization (NEW) - :ref:`example_viz_bundles` - :ref:`example_viz_widgets` - :ref:`example_viz_surfaces` +- :ref:`example_viz_ui` --------------- Workflows (NEW) From 60e469bbfb8057778e018ea7ed3e5e83c31b16f2 Mon Sep 17 00:00:00 2001 From: Ranveer Aggarwal Date: Mon, 10 Apr 2017 01:40:06 +0530 Subject: [PATCH 14/14] VIZ: Removed unused imports from documentation --- doc/examples/viz_ui.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/doc/examples/viz_ui.py b/doc/examples/viz_ui.py index 0c1a5df52c..4a3f084bfb 100644 --- a/doc/examples/viz_ui.py +++ b/doc/examples/viz_ui.py @@ -12,13 +12,8 @@ from dipy.data import read_viz_icons, fetch_viz_icons -# Conditional import machinery for vtk. -from dipy.utils.optpkg import optional_package - -# Allow import, but disable doctests if we don't have vtk. from dipy.viz import ui, window -vtk, have_vtk, setup_module = optional_package('vtk') """ 3D Elements @@ -29,15 +24,15 @@ def cube_maker(color=None, size=(0.2, 0.2, 0.2), center=None): - cube = vtk.vtkCubeSource() + cube = window.vtk.vtkCubeSource() cube.SetXLength(size[0]) cube.SetYLength(size[1]) cube.SetZLength(size[2]) if center is not None: cube.SetCenter(*center) - cube_mapper = vtk.vtkPolyDataMapper() + cube_mapper = window.vtk.vtkPolyDataMapper() cube_mapper.SetInputConnection(cube.GetOutputPort()) - cube_actor = vtk.vtkActor() + cube_actor = window.vtk.vtkActor() cube_actor.SetMapper(cube_mapper) if color is not None: cube_actor.GetProperty().SetColor(color)