Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Up to Date ColorPicker UI Element #651

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
239 changes: 237 additions & 2 deletions fury/ui/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
__all__ = ["TextBox2D", "LineSlider2D", "LineDoubleSlider2D",
"RingSlider2D", "RangeSlider", "Checkbox", "Option", "RadioButton",
"ComboBox2D", "ListBox2D", "ListBoxItem2D", "FileMenu2D",
"DrawShape", "DrawPanel"]
"DrawShape", "DrawPanel", "ColorPicker"]

import os
from collections import OrderedDict
Expand All @@ -20,6 +20,8 @@
from fury.ui.core import Button2D
from fury.utils import (set_polydata_vertices, vertices_from_actor,
update_actor)
# this line was changed
from fury.lib import ScalarBarActor, LookupTable, UnsignedCharArray, VTK_VERSION


class TextBox2D(UI):
Expand Down Expand Up @@ -874,7 +876,6 @@ def _setup(self):
self.handles[1].on_left_mouse_button_released = \
self.handle_release_callback


def _get_actors(self):
"""Get the actors composing this UI component."""
return (self.track.actors + self.handles[0].actors +
Expand Down Expand Up @@ -2839,6 +2840,7 @@ def left_button_clicked(self, i_ren, _obj, _list_box_item):
def resize(self, size):
self.background.resize(size)


class FileMenu2D(UI):
"""A menu to select files in the current folder.

Expand Down Expand Up @@ -3484,3 +3486,236 @@ def left_button_dragged(self, i_ren, _obj, element):
mouse_position = self.clamp_mouse_position(i_ren.event.position)
self.handle_mouse_drag(mouse_position)
i_ren.force_render()


class ColorPicker(UI):
""" A color picker that uses the HSV color model.
It consists of a vertical bar that selects the hue and
a square that selects the saturation and value for the selected hue.
"""

def __init__(self, position=(0, 0)):
""" Initialize the color picker.
Parameters
----------
side : int
The side length of the square(and height of the hue bar) in pixels.
position : (float, float)
Coordinates (x, y) of the lower-left corner of the sqaure.
"""
super(ColorPicker, self).__init__(position)
self.side = 100
self.pointer.position = self.ColorSelectionSquare.center
self.position = position
self.current_hue = 0
self.current_saturation = 0.5
self.current_value = 0.5
# Offer some standard hooks to the user.
self.on_change = lambda color_picker, r, g, b: None

def _setup(self):
""" Setup this UI component.
"""
# Setup the hue bar
# this line was changed
self.HueBar = ScalarBarActor()
self.HueBar.GetPositionCoordinate().SetCoordinateSystemToDisplay()
self.HueBar.GetPosition2Coordinate().SetCoordinateSystemToDisplay()
self.HueBar.SetNumberOfLabels(0)
hueLut = LookupTable() # this line was changed
hueLut.SetTableRange(0, 1)
hueLut.SetHueRange(0, 1)
hueLut.SetSaturationRange(1, 1)
hueLut.SetValueRange(1, 1)
hueLut.Build()
self.HueBar.SetLookupTable(hueLut)
self.add_callback(self.HueBar, "MouseMoveEvent",
self.hue_select_callback)
# Setup colors for the square
colors = UnsignedCharArray() # this line was changed
colors.SetNumberOfComponents(3)
colors.SetName("Colors")
colors.InsertNextTuple3(*self.hsv2rgb(0, 0, 0))
colors.InsertNextTuple3(*self.hsv2rgb(0, 1, 0))
colors.InsertNextTuple3(*self.hsv2rgb(0, 1, 1))
colors.InsertNextTuple3(*self.hsv2rgb(0, 0, 1))
# Setup the square
self.ColorSelectionSquare = Rectangle2D()
self.ColorSelectionSquare._polygonPolyData.GetPointData().\
SetScalars(colors)
# if int(VTK_VERSION) <= 5:
# self.ColorSelectionSquare._polygonPolyData.Update()
self.ColorSelectionSquare.on_left_mouse_button_dragged = \
self.color_select_callback
# Setup the circle pointer
self.pointer = Disk2D(outer_radius=1)

def _get_actors(self):
""" Get the actors composing this UI component.
"""
return self.ColorSelectionSquare.actors + self.HueBar +\
self.pointer.actors

def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.

Parameters
----------
scene : scene
"""
self.ColorSelectionSquare._add_to_scene(scene) # this line was changed
self.pointer._add_to_scene(scene) # this line was changed
Comment on lines +3566 to +3567
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @saran820 ,
I am not sure if you are still working on it or not but I figured out why the interaction wasn't working for Rectangle2D.

If you change these lines of code as suggested, the below code would work.

Suggested change
self.ColorSelectionSquare._add_to_scene(scene) # this line was changed
self.pointer._add_to_scene(scene) # this line was changed
self.ColorSelectionSquare.add_to_scene(scene) # this line was changed
self.pointer.add_to_scene(scene) # this line was changed

Modified Code:

from fury import ui, window
from fury.data import fetch_viz_icons
import sys
import numpy as np

cp = ui.ColorPicker(side=300, position=(0, 0))

rect = ui.Rectangle2D(size=(100, 100), position=(400, 400))


def change_rect_color(color_picker, r, g, b):
    rect.color = np.asarray(color_picker.hsv2rgb(color_picker.current_hue, color_picker.current_saturation,
                                                 color_picker.current_value)) / 255


cp.on_change = change_rect_color

current_size = (800, 800)
show_manager = window.ShowManager(size=current_size,
                                  title="Colorpicker on Rectangle")

show_manager.scene.add(cp)
show_manager.scene.add(rect)

interactive = True

if interactive:
    show_manager.start()

window.record(show_manager.scene, size=current_size,
              out_path="viz_colorpicker.png")

scene.add(self.HueBar)
# def _add_to_renderer(self, ren):
# """ Add all subcomponents or VTK props that compose this UI component.
# Parameters
# ----------
# ren : renderer
# """
# self.ColorSelectionSquare.add_to_renderer(ren)
# self.pointer.add_to_renderer(ren)
# ren.add(self.HueBar)

def _get_size(self):
return (6*self.side/5, self.side)

@property
def side(self):
return self.HueBar.GetPosition2()[1]

@side.setter
def side(self, side):
self.HueBar.SetPosition2(side / 5, side)
self.ColorSelectionSquare.resize((side, side))
self.pointer.outer_radius = side/50
self.pointer.inner_radius = side/50 - 1

@property
def current_hue(self):
color = self.ColorSelectionSquare._polygonPolyData.GetPointData().\
GetScalars().GetTuple(2)
return self.rgb2hsv(*color)[0]

@current_hue.setter
def current_hue(self, hue):
colors = UnsignedCharArray() # this line was changed
colors.SetNumberOfComponents(3)
colors.SetName("Colors")
colors.InsertNextTuple3(*self.hsv2rgb(hue, 0, 0))
colors.InsertNextTuple3(*self.hsv2rgb(hue, 1, 0))
colors.InsertNextTuple3(*self.hsv2rgb(hue, 1, 1))
colors.InsertNextTuple3(*self.hsv2rgb(hue, 0, 1))
self.ColorSelectionSquare._polygonPolyData.GetPointData().\
SetScalars(colors)
# if int(VTK_VERSION) <= 5: # this line was changed
# self.ColorSelectionSquare._polygonPolyData.Update()

def rgb2hsv(self, R, G, B):
""" Function to convert given RGB value to HSV
Parameters
----------
R : float[0-255]
Red value.
G : float[0-255]
Green value.
B : float[0-255]
Blue value.
"""
(r, g, b) = (R/255, G/255, B/255)
cmax = max(r, g, b)
cmin = min(r, g, b)
delta = cmax - cmin
if delta == 0:
H = 0
elif cmax == r:
H = 60*(((g-b)/delta) % 6)
elif cmax == g:
H = 60*(((b-r)/delta)+2)
elif cmax == b:
H = 60*(((r-g)/delta)+4)
if cmax == 0:
S = 0
else:
S = delta/cmax
V = cmax
return H, S, V

def hsv2rgb(self, H, S, V):
""" Function to convert given HSV value to RGB
Parameters
----------
H : float[0-360]
Hue.
S : float[0-1]
Saturation.
V : float[0-1]
Value.
"""
C = V * S
X = C * (1 - abs((H / 60) % 2 - 1))
m = V - C
if H < 60:
(r, g, b) = (C, X, 0)
elif H < 120:
(r, g, b) = (X, C, 0)
elif H < 180:
(r, g, b) = (0, C, X)
elif H < 240:
(r, g, b) = (0, X, C)
elif H < 300:
(r, g, b) = (X, 0, C)
else:
(r, g, b) = (C, 0, X)
(R, G, B) = ((r+m)*255, (g+m)*255, (b+m)*255)
return R, G, B

def _set_position(self, coords):
""" Position the lower-left corner of this UI component.
Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
offset = coords - self.position
self.ColorSelectionSquare.position = coords
self.pointer.center += offset
self.HueBar.SetPosition(coords[0] + self.side, coords[1])

def hue_select_callback(self, i_ren, obj, color_picker):
""" A callback to select the hue from the hue bar.
Parameters
----------
i_ren: :class:`CustomInteractorStyle`
obj: :class:`vtkActor`
The picked actor
color_picker: :class:`ColorPicker`
"""
FractionalHue = (i_ren.event.position[1] - self.position[1])/self.side
self.current_hue = FractionalHue * 360
self.on_change(
self, *self.hsv2rgb(self.current_hue, self.current_saturation,
self.current_value))
i_ren.force_render()
i_ren.event.abort()

def color_select_callback(self, i_ren, obj, rectangle2d):
""" A callback to select the hue and saturation from the sqaure.
Parameters
----------
i_ren: :class:`CustomInteractorStyle`
obj: :class:`vtkActor`
The picked actor
rectangle2d: :class:`Rectangle2D`
"""
coords = i_ren.event.position
# Ensure coords are within the square
coords = np.maximum(coords, self.position)
coords = np.minimum(coords, self.position + self.side)
self.pointer.center = coords
relative_coords = coords - self.position
self.current_saturation, self.current_value = relative_coords/self.side
self.on_change(
self, *self.hsv2rgb(self.current_hue, self.current_saturation,
self.current_value))
i_ren.force_render()
i_ren.event.abort()