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

Standardize actors colors #773

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions fury/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
set_polydata_triangles,
set_polydata_vertices,
shallow_copy,
check_color_range,
)


Expand Down Expand Up @@ -383,6 +384,7 @@ def surface(vertices, faces=None, colors=None, smooth=None, subdivision=3):
triangle_poly_data.SetPoints(points)

if colors is not None:
colors = check_color_range(colors)
triangle_poly_data.GetPointData().SetScalars(numpy_to_vtk_colors(255 * colors))

if faces is None:
Expand Down Expand Up @@ -528,6 +530,9 @@ def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1):
skin_actor = Actor()

skin_actor.SetMapper(skin_mapper)

color = check_color_range(color)

skin_actor.GetProperty().SetColor(color[0], color[1], color[2])
skin_actor.GetProperty().SetOpacity(opacity)

Expand Down Expand Up @@ -570,6 +575,8 @@ def contour_from_label(data, affine=None, color=None):
elif color.shape != (nb_surfaces, 3) and color.shape != (nb_surfaces, 4):
raise ValueError('Incorrect color array shape')

color = check_color_range(color)

if color.shape == (nb_surfaces, 4):
opacity = color[:, -1]
color = color[:, :-1]
Expand Down Expand Up @@ -680,6 +687,7 @@ def streamtube(

"""
# Poly data with lines and colors
colors = check_color_range(colors)
poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors)
next_input = poly_data

Expand Down Expand Up @@ -750,8 +758,6 @@ def streamtube(
actor.GetProperty().BackfaceCullingOn()
actor.GetProperty().SetOpacity(opacity)



return actor


Expand Down Expand Up @@ -833,6 +839,7 @@ def line(

"""
# Poly data with lines and colors
colors = check_color_range(colors)
poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors)
next_input = poly_data

Expand Down Expand Up @@ -950,6 +957,8 @@ def axes(
dirs = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
colors = np.array([colorx + (opacity,), colory + (opacity,), colorz + (opacity,)])

colors = check_color_range(colors)

scales = np.asarray(scale)
arrow_actor = arrow(centers, dirs, colors, scales, repeat_primitive=False)
return arrow_actor
Expand Down Expand Up @@ -1692,6 +1701,7 @@ def dot(points, colors=None, opacity=None, dot_size=5):
vtk_faces.InsertNextCell(1)
vtk_faces.InsertCellPoint(idd)

colors = check_color_range(colors)
color_tuple = color_check(len(points), colors)
color_array, global_opacity = color_tuple

Expand Down Expand Up @@ -1759,6 +1769,9 @@ def point(points, colors, point_radius=0.1, phi=8, theta=8, opacity=1.0):
>>> # window.show(scene)

"""

colors = check_color_range(colors)

return sphere(
centers=points,
colors=colors,
Expand Down Expand Up @@ -1820,6 +1833,7 @@ def sphere(
>>> # window.show(scene)

"""
colors = check_color_range(colors)
if not use_primitive:
src = SphereSource() if faces is None else None

Expand Down Expand Up @@ -1913,6 +1927,7 @@ def cylinder(
>>> # window.show(scene)

"""
colors = check_color_range(colors)
if faces is None:
src = CylinderSource()
src.SetCapping(capped)
Expand Down Expand Up @@ -2001,6 +2016,8 @@ def disk(
src = None
rotate = None

colors = check_color_range(colors)

disk_actor = repeat_sources(
centers=centers,
colors=colors,
Expand Down Expand Up @@ -2043,6 +2060,7 @@ def square(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_square()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2095,6 +2113,7 @@ def rectangle(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=(1, 2, 0))
>>> # window.show(scene)

"""
colors = check_color_range(colors)
return square(centers=centers, directions=directions, colors=colors, scales=scales)


Expand Down Expand Up @@ -2128,6 +2147,7 @@ def box(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=(1, 2, 3)):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_box()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2176,6 +2196,7 @@ def cube(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
return box(centers=centers, directions=directions, colors=colors, scales=scales)


Expand Down Expand Up @@ -2236,6 +2257,7 @@ def arrow(
>>> # window.show(scene)

"""
colors = check_color_range(colors)
if repeat_primitive:
vertices, faces = fp.prim_arrow()
res = fp.repeat_primitive(
Expand Down Expand Up @@ -2323,6 +2345,7 @@ def cone(
>>> # window.show(scene)

"""
colors = check_color_range(colors)
if not use_primitive:
src = ConeSource() if faces is None else None

Expand Down Expand Up @@ -2387,6 +2410,7 @@ def triangularprism(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_triangularprism()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2435,6 +2459,7 @@ def rhombicuboctahedron(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_rhombicuboctahedron()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2484,6 +2509,7 @@ def pentagonalprism(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_pentagonalprism()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2533,6 +2559,7 @@ def octagonalprism(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_octagonalprism()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2582,6 +2609,7 @@ def frustum(centers, directions=(1, 0, 0), colors=(0, 1, 0), scales=1):
>>> # window.show(scene)

"""
colors = check_color_range(colors)
verts, faces = fp.prim_frustum()
res = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -2653,6 +2681,8 @@ def have_2_dimensions(arr):
else:
roundness = np.array(roundness)

colors = check_color_range(colors)

res = fp.repeat_primitive_function(
func=fp.prim_superquadric,
centers=centers,
Expand Down Expand Up @@ -2715,6 +2745,7 @@ def billboard(
-------
billboard_actor: Actor
"""
colors = check_color_range(colors)
verts, faces = fp.prim_square()
res = fp.repeat_primitive(
verts, faces, centers=centers, colors=colors, scales=scales
Expand Down Expand Up @@ -2925,6 +2956,7 @@ def add_to_scene(scene):

texta.SetMapper(textm)

color = check_color_range(color)
texta.GetProperty().SetColor(color)

# Set ser rotation origin to the center of the text is following the camera
Expand Down Expand Up @@ -3047,6 +3079,8 @@ def get_position(self):
text_actor.set_position(position)
text_actor.font_family(font_family)
text_actor.font_style(bold, italic, shadow)

color = check_color_range(color)
text_actor.color(color)
text_actor.justification(justification)
text_actor.vertical_justification(vertical_justification)
Expand All @@ -3071,6 +3105,7 @@ class Container:
Default: (0, 0, 0, 0, 0, 0).

"""

def __init__(self, layout=layout.Layout()):
"""

Expand Down Expand Up @@ -3347,7 +3382,7 @@ def texture(rgb, interp=True):
act: Actor

"""
arr = rgb
arr = check_color_range(rgb)
grid = rgb_to_vtk(np.ascontiguousarray(arr))

Y, X = arr.shape[:2]
Expand Down Expand Up @@ -3528,6 +3563,7 @@ def sdf(centers, directions=(1, 0, 0), colors=(1, 0, 0), primitives='torus', sca
"""
prims = {'sphere': 1, 'torus': 2, 'ellipsoid': 3, 'capsule': 4}

colors = check_color_range(colors)
verts, faces = fp.prim_box()
repeated = fp.repeat_primitive(
verts,
Expand Down Expand Up @@ -3640,6 +3676,7 @@ def markers(
>>> # window.show(scene, size=(600, 600))

"""
colors = check_color_range(colors)
n_markers = centers.shape[0]
verts, faces = fp.prim_square()
res = fp.repeat_primitive(
Expand Down
39 changes: 38 additions & 1 deletion fury/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,43 @@ def test_color_check():
npt.assert_equal(global_opacity, 1)


def test_check_color_range():
# Test input None in color
none_color = None
assert utils.check_color_range(none_color) == None

# Test input data
valid_color_array = np.array([[0.1, 0.2, 0.3, 0.4], [0.4, 0.5, 0.6, 0.7]])
invalid_color_array = [[0.1, 0.2, 1.0, 0.3], [255, 255, 255, 0.7]]
invalid_color_array_expected = np.array(
[[0.1, 0.2, 1.0, 0.3], [1.0, 0.0, 0.0, 1.0]])

# Test for valid input
npt.assert_array_equal(utils.check_color_range(
valid_color_array), valid_color_array)

# Test for invalid input
npt.assert_array_equal(utils.check_color_range(
invalid_color_array), invalid_color_array_expected)

# Test for input of type tuple
color_tuple = (0.1, 0.2, 0.3)
color_tuple_expected = (0.1, 0.2, 0.3)
assert utils.check_color_range(color_tuple) == color_tuple_expected

# Test for input of type list (invalid)
color_list = [100, 150, 200, 0.4]
Clarkszw marked this conversation as resolved.
Show resolved Hide resolved
color_list_expected = np.array([1.0, 0.0, 0.0, 1.0])
npt.assert_array_equal(
utils.check_color_range(color_list), color_list_expected)

# Test for input of type 1d np.array
color_1d = np.array([0.1, 0.5, 0.9, 0.3])
color_1d_expected = np.array([0.1, 0.5, 0.9, 0.3])
npt.assert_array_equal(
utils.check_color_range(color_1d), color_1d_expected)


def test_is_ui():
panel = Panel2D(position=(0, 0), size=(100, 100))
valid_ui = DummyUI(act=[])
Expand All @@ -885,7 +922,7 @@ def test_empty_array_to_polydata():
npt.assert_raises(ValueError, utils.lines_to_vtk_polydata, lines)


@pytest.mark.skipif(not have_dipy, reason='Requires DIPY')
@ pytest.mark.skipif(not have_dipy, reason='Requires DIPY')
def test_empty_array_sequence_to_polydata():
from dipy.tracking.streamline import Streamlines

Expand Down
50 changes: 50 additions & 0 deletions fury/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import numpy as np
import warnings
from scipy.ndimage import map_coordinates

from fury.colormap import line_colors
Expand Down Expand Up @@ -1547,6 +1548,55 @@ def color_check(pts_len, colors=None):
return color_array, global_opacity


def check_color_range(color_array):
"""
Check if all values in a color array are within the range [0, 1].
If there is out of bounds values, the function sends a message,
replaces the out-of-bounds colors with the red color [1, 0, 0].

Args:
Clarkszw marked this conversation as resolved.
Show resolved Hide resolved
color_array (numpy.ndarray): An array of shape (N, 3) or (N, 4).

Returns:
Clarkszw marked this conversation as resolved.
Show resolved Hide resolved
The orignal array if all values are within the range [0,1].
Otherwise, the updated numpy.ndarray of color_array
"""
# Keep the option for color = None for some actors
if color_array is None:
return color_array

# Convert tuple or list to ndarray (N, 3)
if isinstance(color_array, list):
color_array = np.asarray(color_array)

is_tuple = False
if isinstance(color_array, tuple):
is_tuple = True
color_array = np.asarray(color_array)
Clarkszw marked this conversation as resolved.
Show resolved Hide resolved

# Inform there is out of bounds color
# Change the out of bounds color to red [1, 0, 0]
if color_array.ndim == 1:
if np.any(color_array > 1) or np.any(color_array < 0):
print(
f"{color_array} in the color array are outside the valid range [0, 1]")
color_array = [1, 0, 0, 1][:len(color_array)]
color_array = np.array(color_array)
print("It has been modified to red color")

elif color_array.ndim == 2:
for i, row in enumerate(color_array):
if np.any(row > 1) or np.any(row < 0):
print(f"{row} in the color array are outside the valid range [0, 1]")
color_array[i] = [1, 0, 0, 1][:len(row)]
print("It has been modified to red color")

if is_tuple:
color_array = tuple(color_array)
Clarkszw marked this conversation as resolved.
Show resolved Hide resolved

return color_array


def is_ui(actor):
"""Method to check if the passed actor is `UI` or `vtkProp3D`

Expand Down