class GradientEditor(tk.Toplevel): """The gradient editor window, i.e. the thing that contains the gradient display, the function controls and the buttons.""" def __init__(self, master, vtk_table, on_change_color_table = None): """Initialize the gradient editor window. Parameters ---------- master Owning widget, for example a tk root object. VtkTable Instance of vtkLookupTable, designating the table which is to be edited. OnChangeColorTable Callback function taking no arguments. Called when the color table was changed and rendering is requested.""" # Inner dimensions of the color control gui-elements in pixels. gradient_preview_width = 300 gradient_preview_height = 50 channel_function_width = gradient_preview_width channel_function_height = 80 tk.Toplevel.__init__(self, master) self.title("Color Gradient Editor") self.minsize( gradient_preview_width+4, gradient_preview_height + 5 * \ channel_function_height + 50 ) self.gradient_table = GradientTable(gradient_preview_width) self.vtk_color_table = vtk_table # create controls. self.gradient_control = GradientControl(self, self.gradient_table, gradient_preview_width, gradient_preview_height ) self.gradient_control.grid(row=0,column=1,sticky="we") def on_gradient_table_changed( final_update ): # update all function controls. self.function_control_rgb.update() for control in self.function_controls_hsv: control.update() # repaint the gradient display or the external windows only # when the instant*** options are set or when the update was final. if final_update or ( 1 == self.show_instant_gradients.get() ): self.gradient_control.update() if final_update or ( 1 == self.show_instant_feedback.get() ): self.gradient_table.store_to_vtk_lookup_table( self.vtk_color_table ) on_change_color_table() self.on_gradient_table_changed = on_gradient_table_changed self.function_control_rgb = FunctionControl(self, self.gradient_table, "rgb", channel_function_width, channel_function_height, on_gradient_table_changed) label = tk.Label( self, text = "rgb" ) label.grid(row=1, column=0) self.function_control_rgb.grid(row=1,column=1,sticky="we") self.function_controls_hsv = [] for it in [("hue",2), ("sat",3), ("val",4), ("alp", 5) ]: control = FunctionControl(self, self.gradient_table, it[0][0], channel_function_width, channel_function_height, on_gradient_table_changed ) control.grid(row=it[1],column=1,sticky="we") self.function_controls_hsv.append(control) label = tk.Label( self, text = it[0] ) label.grid(row=it[1], column=0) # buttons and the instruction label get into an own subframe for # easier control. button_frame = tk.Frame(self) button_frame.grid(row=6,column=0,columnspan=2) ok_button = tk.Button(button_frame, text="ok", command=self.ok) ok_button.grid(row=0,column=1) #CancelButton = tk.Button(ButtonFrame, text="cancel", command=self.Cancel) #CancelButton.grid(row=0,column=2) spacer = tk.Frame(button_frame, width=10 ) spacer.grid(row=0,column=3) save_button = tk.Button(button_frame, text="save", command=self.save_gradient) save_button.grid(row=0,column=4) load_button = tk.Button(button_frame, text="load", command=self.load_gradient) load_button.grid(row=0,column=5) spacer = tk.Frame(button_frame, width=10 ) spacer.grid(row=0,column=6) label = tk.Label(button_frame,text="instant:") label.grid(row=0,column=7) # these two buttons control whether gradient and render target # updates are executed during movement of control points or # only at the end of such changes. self.show_instant_gradients = tk.IntVar() self.show_instant_gradients.set(1) # enable instant gradients by default self.show_instant_feedback = tk.IntVar() self.show_instant_feedback.set(0) # disable instant feedback by default instant_gradient_button = tk.Checkbutton(button_frame, text="grad") instant_gradient_button.grid(row=0,column=8) instant_gradient_button.configure(variable=self.show_instant_gradients) instant_feedback_button = tk.Checkbutton(button_frame, text="feed") instant_feedback_button.grid(row=0,column=9) instant_feedback_button.configure(variable=self.show_instant_feedback) instruction_label = tk.Label(button_frame, text="left button: move point; right click: toggle point") instruction_label.grid(column=0,columnspan=9,row=1) # insert a ratio button which decides whether the controls for nonlinear # scaling of the gradient are shown and activated. self.nonlinear_scaling_enabled = tk.IntVar() self.nonlinear_scaling_enabled.set(0) nonlinear_enabled_button = tk.Checkbutton(button_frame, text="nonlin") nonlinear_enabled_button.grid(column=9,row=1) nonlinear_enabled_button.configure(variable=self.nonlinear_scaling_enabled, command=self.nonlinear_scaling_option_changed) # the controls for the nonlinear scaling also get into an own frame. # this one can be shown or hidden when the "nonlin"-button is pressed nonlin_frame = tk.Frame(self) self.nonlin_frame = nonlin_frame label = tk.Label(nonlin_frame, text="f(x) =") label.grid(row=0, column=0) self.nonlinear_function_string = tk.StringVar() self.nonlinear_function_string.set( "x**(4*a)" ) function_edit = tk.Entry(nonlin_frame, width=35, textvariable=self.nonlinear_function_string) function_edit.bind("<Return>", self.nonlinear_function_string_changed ) function_edit.grid(row=0, column=1) label = tk.Label(nonlin_frame, text="param a:") label.grid(row=1, column=0) self.parameter_scale = tk.Scale(nonlin_frame, from_=0.0, to=1.0, resolution=0.001, length=250, orient="horizontal") self.parameter_scale.bind("<ButtonRelease>", lambda event: self.nonlinear_parameter_scale_changed(final_update=True)) self.parameter_scale.bind("<Motion>", lambda event:self.nonlinear_parameter_scale_changed(final_update=False)) self.parameter_scale.set(0.5) self.parameter_scale.grid(row=1, column=1) label = tk.Label(nonlin_frame, text= \ "f(x) should map [0..1] to [0..1]. It rescales the gradient.") label.grid(column=0,columnspan=2,row=2) # finally, write the current gradient out into main program on_gradient_table_changed(final_update = True) def nonlinear_scaling_option_changed(self): """called when the 'nonlin'-button is pressed to toggle if nonlinear- scaling is activated and the corresponding controls are shown""" if ( 1 == self.nonlinear_scaling_enabled.get() ): # activate the nonlinear scaling controls self.nonlin_frame.grid(row=7,column=0,columnspan=2) self.nonlinear_parameter_scale_changed(final_update=False) self.nonlinear_function_string_changed(None) else: # disable the nonlinear scaling controls (and the scaling) self.nonlin_frame.pack(side=tk.LEFT, anchor=tk.NW) self.nonlin_frame.pack_forget() self.gradient_table.set_scaling_function("") self.on_gradient_table_changed(final_update=True) def nonlinear_parameter_scale_changed(self,final_update): """Event Handler for the nonlinear-parameter scaling bar. FinalUpdate is true on ButtonRelease and False on Motion""" self.gradient_table.set_scaling_function_parameter(self.parameter_scale.get()) self.on_gradient_table_changed(final_update = final_update) def nonlinear_function_string_changed(self,event): """Invoked when Return is pressed in the nonlinear-function edit""" self.gradient_table.set_scaling_function(self.nonlinear_function_string.get()) self.on_gradient_table_changed(final_update = True) def ok(self): self.destroy() def save_gradient(self): filetypes = [("Gradient Files","*.grad"),("All Files","*")] file_name = tkFileDialog.asksaveasfilename(defaultextension=".grad", filetypes=filetypes) if file_name: # there is probably a way to find out which file type the user # actually selected. But since I don't know it and also don't really # know how to find it out, i rely on this error prone method... if ( ".lut" == file_name[len(file_name)-4:] ): self.gradient_table.save(file_name) self.gradient_table.save(file_name) def load_gradient(self): filetypes = [("Gradient Files","*.grad"), ("All Files","*")] file_name = tkFileDialog.askopenfilename(defaultextension=".grad", filetypes=filetypes) if file_name: self.gradient_table.load(file_name) self.on_gradient_table_changed(final_update = True) if self.gradient_table.scaling_function: self.parameter_scale.set(self.gradient_table.scaling_function_parameter) self.nonlinear_function_string.set(self.gradient_table.scaling_function_string) self.nonlinear_scaling_enabled.set(1) self.nonlinear_scaling_option_changed() else: self.nonlinear_scaling_enabled.set(0) self.nonlinear_scaling_option_changed()
class GradientEditor(tk.Toplevel): """The gradient editor window, i.e. the thing that contains the gradient display, the function controls and the buttons.""" def __init__(self, master, vtk_table, on_change_color_table=None): """Initialize the gradient editor window. Parameters ---------- master Owning widget, for example a tk root object. VtkTable Instance of vtkLookupTable, designating the table which is to be edited. OnChangeColorTable Callback function taking no arguments. Called when the color table was changed and rendering is requested.""" # Inner dimensions of the color control gui-elements in pixels. gradient_preview_width = 300 gradient_preview_height = 50 channel_function_width = gradient_preview_width channel_function_height = 80 tk.Toplevel.__init__(self, master) self.title("Color Gradient Editor") self.minsize( gradient_preview_width+4, gradient_preview_height + 5 * \ channel_function_height + 50 ) self.gradient_table = GradientTable(gradient_preview_width) self.vtk_color_table = vtk_table # create controls. self.gradient_control = GradientControl(self, self.gradient_table, gradient_preview_width, gradient_preview_height) self.gradient_control.grid(row=0, column=1, sticky="we") def on_gradient_table_changed(final_update): # update all function controls. self.function_control_rgb.update() for control in self.function_controls_hsv: control.update() # repaint the gradient display or the external windows only # when the instant*** options are set or when the update was final. if final_update or (1 == self.show_instant_gradients.get()): self.gradient_control.update() if final_update or (1 == self.show_instant_feedback.get()): self.gradient_table.store_to_vtk_lookup_table( self.vtk_color_table) on_change_color_table() self.on_gradient_table_changed = on_gradient_table_changed self.function_control_rgb = FunctionControl(self, self.gradient_table, "rgb", channel_function_width, channel_function_height, on_gradient_table_changed) label = tk.Label(self, text="rgb") label.grid(row=1, column=0) self.function_control_rgb.grid(row=1, column=1, sticky="we") self.function_controls_hsv = [] for it in [("hue", 2), ("sat", 3), ("val", 4), ("alp", 5)]: control = FunctionControl(self, self.gradient_table, it[0][0], channel_function_width, channel_function_height, on_gradient_table_changed) control.grid(row=it[1], column=1, sticky="we") self.function_controls_hsv.append(control) label = tk.Label(self, text=it[0]) label.grid(row=it[1], column=0) # buttons and the instruction label get into an own subframe for # easier control. button_frame = tk.Frame(self) button_frame.grid(row=6, column=0, columnspan=2) ok_button = tk.Button(button_frame, text="ok", command=self.ok) ok_button.grid(row=0, column=1) #CancelButton = tk.Button(ButtonFrame, text="cancel", command=self.Cancel) #CancelButton.grid(row=0,column=2) spacer = tk.Frame(button_frame, width=10) spacer.grid(row=0, column=3) save_button = tk.Button(button_frame, text="save", command=self.save_gradient) save_button.grid(row=0, column=4) load_button = tk.Button(button_frame, text="load", command=self.load_gradient) load_button.grid(row=0, column=5) spacer = tk.Frame(button_frame, width=10) spacer.grid(row=0, column=6) label = tk.Label(button_frame, text="instant:") label.grid(row=0, column=7) # these two buttons control whether gradient and render target # updates are executed during movement of control points or # only at the end of such changes. self.show_instant_gradients = tk.IntVar() self.show_instant_gradients.set( 1) # enable instant gradients by default self.show_instant_feedback = tk.IntVar() self.show_instant_feedback.set( 0) # disable instant feedback by default instant_gradient_button = tk.Checkbutton(button_frame, text="grad") instant_gradient_button.grid(row=0, column=8) instant_gradient_button.configure(variable=self.show_instant_gradients) instant_feedback_button = tk.Checkbutton(button_frame, text="feed") instant_feedback_button.grid(row=0, column=9) instant_feedback_button.configure(variable=self.show_instant_feedback) instruction_label = tk.Label( button_frame, text="left button: move point; right click: toggle point") instruction_label.grid(column=0, columnspan=9, row=1) # insert a ratio button which decides whether the controls for nonlinear # scaling of the gradient are shown and activated. self.nonlinear_scaling_enabled = tk.IntVar() self.nonlinear_scaling_enabled.set(0) nonlinear_enabled_button = tk.Checkbutton(button_frame, text="nonlin") nonlinear_enabled_button.grid(column=9, row=1) nonlinear_enabled_button.configure( variable=self.nonlinear_scaling_enabled, command=self.nonlinear_scaling_option_changed) # the controls for the nonlinear scaling also get into an own frame. # this one can be shown or hidden when the "nonlin"-button is pressed nonlin_frame = tk.Frame(self) self.nonlin_frame = nonlin_frame label = tk.Label(nonlin_frame, text="f(x) =") label.grid(row=0, column=0) self.nonlinear_function_string = tk.StringVar() self.nonlinear_function_string.set("x**(4*a)") function_edit = tk.Entry(nonlin_frame, width=35, textvariable=self.nonlinear_function_string) function_edit.bind("<Return>", self.nonlinear_function_string_changed) function_edit.grid(row=0, column=1) label = tk.Label(nonlin_frame, text="param a:") label.grid(row=1, column=0) self.parameter_scale = tk.Scale(nonlin_frame, from_=0.0, to=1.0, resolution=0.001, length=250, orient="horizontal") self.parameter_scale.bind( "<ButtonRelease>", lambda event: self. nonlinear_parameter_scale_changed(final_update=True)) self.parameter_scale.bind( "<Motion>", lambda event: self.nonlinear_parameter_scale_changed( final_update=False)) self.parameter_scale.set(0.5) self.parameter_scale.grid(row=1, column=1) label = tk.Label(nonlin_frame, text= \ "f(x) should map [0..1] to [0..1]. It rescales the gradient.") label.grid(column=0, columnspan=2, row=2) # finally, write the current gradient out into main program on_gradient_table_changed(final_update=True) def nonlinear_scaling_option_changed(self): """called when the 'nonlin'-button is pressed to toggle if nonlinear- scaling is activated and the corresponding controls are shown""" if (1 == self.nonlinear_scaling_enabled.get()): # activate the nonlinear scaling controls self.nonlin_frame.grid(row=7, column=0, columnspan=2) self.nonlinear_parameter_scale_changed(final_update=False) self.nonlinear_function_string_changed(None) else: # disable the nonlinear scaling controls (and the scaling) self.nonlin_frame.pack(side=tk.LEFT, anchor=tk.NW) self.nonlin_frame.pack_forget() self.gradient_table.set_scaling_function("") self.on_gradient_table_changed(final_update=True) def nonlinear_parameter_scale_changed(self, final_update): """Event Handler for the nonlinear-parameter scaling bar. FinalUpdate is true on ButtonRelease and False on Motion""" self.gradient_table.set_scaling_function_parameter( self.parameter_scale.get()) self.on_gradient_table_changed(final_update=final_update) def nonlinear_function_string_changed(self, event): """Invoked when Return is pressed in the nonlinear-function edit""" self.gradient_table.set_scaling_function( self.nonlinear_function_string.get()) self.on_gradient_table_changed(final_update=True) def ok(self): self.destroy() def save_gradient(self): filetypes = [("Gradient Files", "*.grad"), ("All Files", "*")] file_name = tkFileDialog.asksaveasfilename(defaultextension=".grad", filetypes=filetypes) if file_name: # there is probably a way to find out which file type the user # actually selected. But since I don't know it and also don't really # know how to find it out, i rely on this error prone method... if (".lut" == file_name[len(file_name) - 4:]): self.gradient_table.save(file_name) self.gradient_table.save(file_name) def load_gradient(self): filetypes = [("Gradient Files", "*.grad"), ("All Files", "*")] file_name = tkFileDialog.askopenfilename(defaultextension=".grad", filetypes=filetypes) if file_name: self.gradient_table.load(file_name) self.on_gradient_table_changed(final_update=True) if self.gradient_table.scaling_function: self.parameter_scale.set( self.gradient_table.scaling_function_parameter) self.nonlinear_function_string.set( self.gradient_table.scaling_function_string) self.nonlinear_scaling_enabled.set(1) self.nonlinear_scaling_option_changed() else: self.nonlinear_scaling_enabled.set(0) self.nonlinear_scaling_option_changed()
class wxGradientEditorWidget(wx.Panel, AbstractGradEditor): """A Gradient Editor widget that can be used anywhere. """ def __init__(self, master, vtk_table, on_change_color_table=None, colors=None): """ Parameters: ----------- vtk_table : the `tvtk.LookupTable` or `tvtk.VolumeProperty` object to set. on_change_color_table : A callback called when the color table changes. colors : list of 'rgb', 'hsv', 'h', 's', 'v', 'a' (Default : ['rgb', 'hsv', 'a']) 'rgb' creates one panel to edit Red, Green and Blue colors. 'hsv' creates one panel to edit Hue, Saturation and Value. 'h', 's', 'v', 'r', 'g', 'b', 'a' separately specified creates different panels for each. """ wx.Panel.__init__(self, master) if colors is None: colors = ['rgb', 'hsv', 'a'] gradient_preview_width = 300 gradient_preview_height = 50 channel_function_width = gradient_preview_width channel_function_height = 80 self.gradient_table = GradientTable(gradient_preview_width) self.vtk_color_table = vtk_table if isinstance(vtk_table, tvtk.LookupTable): self.vtk_table_is_lut = True else: # This is a tvtk.VolumeProperty self.vtk_table_is_lut = False # Initialize the editor with the volume property. self.gradient_table.load_from_vtk_volume_prop(vtk_table) self.on_change_color_table = on_change_color_table # set up all the panels in a gridbagsizer (i.e. a big grid) # 6x2 size: 6 rows, 2 columns... sizer = wx.GridBagSizer(2, 2) # "Gradient Viewer" panel, in position (0,1) for sizer self.gradient_control = wxGradientControl(self, self.gradient_table, gradient_preview_width, gradient_preview_height) tt = wx.ToolTip('Right click for menu') self.gradient_control.Bind(wx.EVT_CONTEXT_MENU, self.on_gradient_menu) self.gradient_control.SetToolTip(tt) sizer.Add(self.gradient_control, pos=(0,1)) # Add the function controls: function_controls = [] self.function_controls = function_controls tooltip_text = 'Left click: move control points\n'\ 'Right click: add/remove control points' editor_data = {'rgb': ('', 'RGB'), 'hsv': ('Hue: Red; Saturation: Green; '\ 'Value: Blue\n', 'HSV' ), 'h': ('', 'HUE'), 's': ('', 'SAT'), 'v': ('', 'VAL'), 'r': ('', 'RED'), 'g': ('', 'GREEN'), 'b': ('', 'BLUE'), 'a': ('', 'ALPHA'), } row = 1 for color in colors: data = editor_data[color] control = wxFunctionControl(self, self.gradient_table, color, channel_function_width, channel_function_height) txt = data[0] + tooltip_text control.SetToolTip(wx.ToolTip(txt)) # Add name of editor (to left side of editor) sizer.Add(wx.StaticText(self, -1, data[1]), pos=(row, 0), flag=wx.ALIGN_CENTER|wx.ALL) # Add the "RGB" control point editor sizer.Add(control, pos=(row, 1)) function_controls.append(control) row += 1 # The status text. self.text = wx.StaticText(self, -1, 'status') sizer.Add(self.text, (row,0), (row,2)) row += 1 # set the appropriate sizer. sizer.SetSizeHints(self) self.SetSizerAndFit(sizer) ###################################################################### # `wxGradientEditorWidget` interface. ###################################################################### def set_status_text(self, msg): t = self.text t.SetLabel(msg) t.Refresh() t.Update() def on_gradient_table_changed(self, final_update ): """ Update the gradient table and vtk lookuptable...""" # update all function controls. for control in self.function_controls: control.update() # repaint the gradient display or the external windows only # when the instant*** options are set or when the update was final. #if final_update or ( 1 == self.show_instant_gradients.get() ): if True: self.gradient_control.update() #if final_update or ( 1 == self.show_instant_feedback.get() ): if final_update: vtk_table = self.vtk_color_table if self.vtk_table_is_lut: self.gradient_table.store_to_vtk_lookup_table(vtk_table) else: rng = self.get_table_range() self.gradient_table.store_to_vtk_volume_prop(vtk_table, rng) cb = self.on_change_color_table if cb is not None: cb() def get_table_range(self): vtk_table = self.vtk_color_table if self.vtk_table_is_lut: return vtk_table.table_range else: return vtk_table.get_scalar_opacity().range def load(self, file_name): """Set the state of the color table using the given file. """ if len(file_name) == 0: return self.gradient_table.load(file_name) self.on_gradient_table_changed(final_update = True) def save(self, file_name): """Store the color table to the given file. This actually generates 3 files, a '.grad', a '.lut' file and a '.jpg' file. The .lut file can be used to setup a lookup table. The .grad file is used to set the state of the gradient table and the JPG file is an image of the how the lut will look. """ if len(file_name) == 0: return self.gradient_table.save(file_name) ###################################################################### # wxPython event methods. ###################################################################### def on_gradient_menu(self, event): if not hasattr(self, 'save_menuid'): # Do this only the first time. self.save_menuid = wx.NewId() self.load_menuid = wx.NewId() self.Bind(wx.EVT_MENU, self.on_save, id=self.save_menuid) self.Bind(wx.EVT_MENU, self.on_load, id=self.load_menuid) menu = wx.Menu() menu.Append(self.save_menuid, "Save as") menu.Append(self.load_menuid, "Load") self.PopupMenu(menu) menu.Destroy() def on_save(self, event): """ Open "Save" dialog, write lookuptable to 3 files: ``*.lut`` (lookuptable) ``*.grad`` (gradient table for use with this program), and ``*.jpg`` (image of the gradient) """ dlg = wx.FileDialog(self, "Save LUT to...", style=wx.SAVE) wildcard = "Gradient Files (.grad)|*.grad|" \ "All files (*.*)|*.*" dlg.SetWildcard(wildcard) if (dlg.ShowModal() == wx.ID_OK): file_name = dlg.GetPath() if file_name: self.save(file_name) def on_load(self, event): """ Load a ``*.grad`` lookuptable file using wxpython dialog """ style = wx.OPEN | wx.HIDE_READONLY dlg = wx.FileDialog(self, "Open a file", style=style) wildcard = "Gradient Files (.grad)|*.grad|" \ "All files (*.*)|*.*" dlg.SetWildcard(wildcard) if (dlg.ShowModal() == wx.ID_OK): file_name = dlg.GetPath() if file_name: self.load(file_name)