From 631c505fe3063e229d4df45d48dc325dc8d89979 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 13:11:44 +0200 Subject: [PATCH 01/17] add function for check actors color values --- fury/tests/test_utils.py | 17 ++++++++++++++++- fury/utils.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 9216291f1..e1bc7ff2b 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -858,7 +858,22 @@ def test_color_check(): points = np.array([[0, 0, 0], [0, 1, 0], [1, 0, 0]]) - color_tuple = color_check(len(points)) + color_tuple = color_check(len(points))import numpy as np + + +def test_check_color_ndarray(): + # Test a valid color array + color_arr_valid = np.array([[0.2, 0.4, 0.6], [0.8, 0.9, 0.7], [0.5, 0.5, 0.5]]) + assert utils.check_color_ndarray(color_arr_valid) == True + + # Test an invalid color array + color_arr_invalid = np.array([[0.2, 0.4, 1.2], [-0.1, 0.8, 0.9], [0.5, -0.1, 0.5]]) + with pytest.warns(UserWarning): + assert utils.check_color_ndarray(color_arr_invalid) == False + assert np.array_equal(color_arr_invalid[0], np.array([0.2, 0.4, 1.0])) + assert np.array_equal(color_arr_invalid[1], np.array([0.0, 0.8, 0.9])) + assert np.array_equal(color_arr_invalid[2], np.array([0.5, 0.0, 0.5])) + color_array, global_opacity = color_tuple npt.assert_equal(color_array, np.floor(np.array([[1, 1, 1]] * 3) * 255)) diff --git a/fury/utils.py b/fury/utils.py index be62ce104..94c54d90d 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1,4 +1,5 @@ import numpy as np +import warnings from scipy.ndimage import map_coordinates from fury.colormap import line_colors @@ -1547,6 +1548,40 @@ def color_check(pts_len, colors=None): return color_array, global_opacity +def check_color_ndarray(color_arr): + """ + 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 values with the red color [1, 0, 0]. + + Args: + color_arr (numpy.ndarray): An array of shape (N, 3) or (N, 4). + + Returns: + The orignal array if all values are within the range [0,1]. + Otherwise, the updated numpy.ndarray of color_arr + """ + # Convert tuple or list to ndarray + if not isinstance(color_arr, np.ndarray): + color_arr = np.asarray(color_arr) + color_arr = color_arr.reshape(1, len(color_arr)) + + # Check that the array is of the correct shape + assert color_arr.ndim == 2 and color_arr.shape[1] in [3, 4], \ + "Invalid color array shape, expect a numpy.ndarray (N,3) or (N,4)" + + # Check that all values are within [0, 1] + for i, row in enumerate(color_arr): + if np.any(row > 1) or np.any(row < 0): + print(f"{row} in the color array are outside the valid range [0, 1]") + # if so, set entire row to [1, 0, 0] or [1, 0, 0, 1] + color_arr[i] = [1, 0, 0, 1][:len(row)] + print(color_arr[i]) + print("It has been modified to red color") + + return color_arr + + def is_ui(actor): """Method to check if the passed actor is `UI` or `vtkProp3D` From 383f759e4cf9526e67d5e7b8167acfc7a58a6b0a Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 13:58:56 +0200 Subject: [PATCH 02/17] add test for check_color_ndarray function --- fury/tests/test_utils.py | 45 ++++++++++++++++++++++++++-------------- fury/utils.py | 25 +++++++++++----------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index e1bc7ff2b..f895e853d 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -858,28 +858,41 @@ def test_color_check(): points = np.array([[0, 0, 0], [0, 1, 0], [1, 0, 0]]) - color_tuple = color_check(len(points))import numpy as np - - -def test_check_color_ndarray(): - # Test a valid color array - color_arr_valid = np.array([[0.2, 0.4, 0.6], [0.8, 0.9, 0.7], [0.5, 0.5, 0.5]]) - assert utils.check_color_ndarray(color_arr_valid) == True - - # Test an invalid color array - color_arr_invalid = np.array([[0.2, 0.4, 1.2], [-0.1, 0.8, 0.9], [0.5, -0.1, 0.5]]) - with pytest.warns(UserWarning): - assert utils.check_color_ndarray(color_arr_invalid) == False - assert np.array_equal(color_arr_invalid[0], np.array([0.2, 0.4, 1.0])) - assert np.array_equal(color_arr_invalid[1], np.array([0.0, 0.8, 0.9])) - assert np.array_equal(color_arr_invalid[2], np.array([0.5, 0.0, 0.5])) - + color_tuple = color_check(len(points)) color_array, global_opacity = color_tuple npt.assert_equal(color_array, np.floor(np.array([[1, 1, 1]] * 3) * 255)) npt.assert_equal(global_opacity, 1) +def test_check_color_ndarray(): + # 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_ndarray( + valid_color_array), valid_color_array) + + # Test for invalid input + npt.assert_array_equal(utils.check_color_ndarray( + invalid_color_array), invalid_color_array_expected) + + # Test for input of type tuple + color_tuple = (0.1, 0.2, 0.3) + color_tuple_expected = np.array([[0.1, 0.2, 0.3]]) + npt.assert_array_equal( + utils.check_color_ndarray(color_tuple), color_tuple_expected) + + # Test for input of type list (invalid) + color_list = [100, 150, 200, 0.4] + color_list_expected = np.array([[1.0, 0.0, 0.0, 1.0]]) + npt.assert_array_equal( + utils.check_color_ndarray(color_list), color_list_expected) + + def test_is_ui(): panel = Panel2D(position=(0, 0), size=(100, 100)) valid_ui = DummyUI(act=[]) diff --git a/fury/utils.py b/fury/utils.py index 94c54d90d..e371b6136 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1548,38 +1548,39 @@ def color_check(pts_len, colors=None): return color_array, global_opacity -def check_color_ndarray(color_arr): +def check_color_ndarray(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 values with the red color [1, 0, 0]. Args: - color_arr (numpy.ndarray): An array of shape (N, 3) or (N, 4). + color_array (numpy.ndarray): An array of shape (N, 3) or (N, 4). Returns: The orignal array if all values are within the range [0,1]. - Otherwise, the updated numpy.ndarray of color_arr + Otherwise, the updated numpy.ndarray of color_array """ - # Convert tuple or list to ndarray - if not isinstance(color_arr, np.ndarray): - color_arr = np.asarray(color_arr) - color_arr = color_arr.reshape(1, len(color_arr)) + # Convert tuple or list to ndarray (N, 3) + if isinstance(color_array, (list, tuple)): + color_array = np.asarray(color_array) + if color_array.ndim == 1: + color_array = color_array.reshape(1, len(color_array)) # Check that the array is of the correct shape - assert color_arr.ndim == 2 and color_arr.shape[1] in [3, 4], \ + assert color_array.ndim == 2 and color_array.shape[1] in [3, 4], \ "Invalid color array shape, expect a numpy.ndarray (N,3) or (N,4)" # Check that all values are within [0, 1] - for i, row in enumerate(color_arr): + 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]") # if so, set entire row to [1, 0, 0] or [1, 0, 0, 1] - color_arr[i] = [1, 0, 0, 1][:len(row)] - print(color_arr[i]) + color_array[i] = [1, 0, 0, 1][:len(row)] + print(color_array[i]) print("It has been modified to red color") - return color_arr + return color_array def is_ui(actor): From ac81ddc0afefd4b6f19726bec03f03bfa8cdb33a Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 14:12:31 +0200 Subject: [PATCH 03/17] bug fix for extra indentation for checking 1d array --- fury/actor.py | 3 +-- fury/tests/test_utils.py | 8 +++++++- fury/utils.py | 8 +++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index 4b5d035f1..821a69bbd 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -750,8 +750,6 @@ def streamtube( actor.GetProperty().BackfaceCullingOn() actor.GetProperty().SetOpacity(opacity) - - return actor @@ -3071,6 +3069,7 @@ class Container: Default: (0, 0, 0, 0, 0, 0). """ + def __init__(self, layout=layout.Layout()): """ diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index f895e853d..4dc297bc6 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -892,6 +892,12 @@ def test_check_color_ndarray(): npt.assert_array_equal( utils.check_color_ndarray(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_ndarray(color_1d), color_1d_expected) + def test_is_ui(): panel = Panel2D(position=(0, 0), size=(100, 100)) @@ -913,7 +919,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 diff --git a/fury/utils.py b/fury/utils.py index e371b6136..868180fb1 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1552,7 +1552,7 @@ def check_color_ndarray(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 values with the red color [1, 0, 0]. + replaces the out-of-bounds colors with the red color [1, 0, 0]. Args: color_array (numpy.ndarray): An array of shape (N, 3) or (N, 4). @@ -1564,8 +1564,10 @@ def check_color_ndarray(color_array): # Convert tuple or list to ndarray (N, 3) if isinstance(color_array, (list, tuple)): color_array = np.asarray(color_array) - if color_array.ndim == 1: - color_array = color_array.reshape(1, len(color_array)) + + # change ndarray (3,) or (4,) to (1,3) or (1,4) + if color_array.ndim == 1: + color_array = color_array.reshape(1, len(color_array)) # Check that the array is of the correct shape assert color_array.ndim == 2 and color_array.shape[1] in [3, 4], \ From de72a31a424738489549100456002d997c9d6d75 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 14:24:42 +0200 Subject: [PATCH 04/17] check color array function add before streamtube line 596 --- fury/actor.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fury/actor.py b/fury/actor.py index 821a69bbd..5994c6aa6 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -80,6 +80,7 @@ set_polydata_triangles, set_polydata_vertices, shallow_copy, + check_color_ndarray, ) @@ -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_ndarray(colors) triangle_poly_data.GetPointData().SetScalars(numpy_to_vtk_colors(255 * colors)) if faces is None: @@ -528,7 +530,10 @@ def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1): skin_actor = Actor() skin_actor.SetMapper(skin_mapper) - skin_actor.GetProperty().SetColor(color[0], color[1], color[2]) + + color = check_color_ndarray(color) + + skin_actor.GetProperty().SetColor(color[0][0], color[0][1], color[0][2]) skin_actor.GetProperty().SetOpacity(opacity) return skin_actor @@ -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_ndarray(color) + if color.shape == (nb_surfaces, 4): opacity = color[:, -1] color = color[:, :-1] From dfb5c9559d89737c856efe4ba125c71d5baaf2a6 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 14:32:13 +0200 Subject: [PATCH 05/17] add color = None option for some actors --- fury/tests/test_utils.py | 4 ++++ fury/utils.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 4dc297bc6..7bbff8f55 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -866,6 +866,10 @@ def test_color_check(): def test_check_color_ndarray(): + # Test input None in color + none_color = None + assert utils.check_color_ndarray(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]] diff --git a/fury/utils.py b/fury/utils.py index 868180fb1..5e9038563 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1561,6 +1561,10 @@ def check_color_ndarray(color_array): 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, tuple)): color_array = np.asarray(color_array) From 0b13854b868e3ca30605847789b670f147c4352b Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 15:08:04 +0200 Subject: [PATCH 06/17] add check color array before dot() line 1657 --- fury/actor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fury/actor.py b/fury/actor.py index 5994c6aa6..765d27e17 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -687,6 +687,7 @@ def streamtube( """ # Poly data with lines and colors + colors = check_color_ndarray(colors) poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors) next_input = poly_data @@ -838,6 +839,7 @@ def line( """ # Poly data with lines and colors + colors = check_color_ndarray(colors) poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors) next_input = poly_data @@ -955,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_ndarray(colors) + scales = np.asarray(scale) arrow_actor = arrow(centers, dirs, colors, scales, repeat_primitive=False) return arrow_actor @@ -1697,6 +1701,7 @@ def dot(points, colors=None, opacity=None, dot_size=5): vtk_faces.InsertNextCell(1) vtk_faces.InsertCellPoint(idd) + # colors = check_color_ndarray(colors) color_tuple = color_check(len(points), colors) color_array, global_opacity = color_tuple From 2ed3db00c7b36d562e26c8e7bfad87214a4eeb1a Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 15:37:01 +0200 Subject: [PATCH 07/17] if input is tuple, it returns a tuple --- fury/actor.py | 1 + fury/tests/test_utils.py | 5 ++--- fury/utils.py | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index 765d27e17..eff438e15 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -1702,6 +1702,7 @@ def dot(points, colors=None, opacity=None, dot_size=5): vtk_faces.InsertCellPoint(idd) # colors = check_color_ndarray(colors) + colors = check_color_ndarray(colors) color_tuple = color_check(len(points), colors) color_array, global_opacity = color_tuple diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 7bbff8f55..bca988852 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -886,9 +886,8 @@ def test_check_color_ndarray(): # Test for input of type tuple color_tuple = (0.1, 0.2, 0.3) - color_tuple_expected = np.array([[0.1, 0.2, 0.3]]) - npt.assert_array_equal( - utils.check_color_ndarray(color_tuple), color_tuple_expected) + color_tuple_expected = (0.1, 0.2, 0.3) + assert utils.check_color_ndarray(color_tuple) == color_tuple_expected # Test for input of type list (invalid) color_list = [100, 150, 200, 0.4] diff --git a/fury/utils.py b/fury/utils.py index 5e9038563..339523130 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1566,7 +1566,11 @@ def check_color_ndarray(color_array): return color_array # Convert tuple or list to ndarray (N, 3) - if isinstance(color_array, (list, tuple)): + is_tuple = False + if isinstance(color_array, list): + color_array = np.asarray(color_array) + if isinstance(color_array, tuple): + is_tuple = True color_array = np.asarray(color_array) # change ndarray (3,) or (4,) to (1,3) or (1,4) @@ -1586,6 +1590,9 @@ def check_color_ndarray(color_array): print(color_array[i]) print("It has been modified to red color") + if is_tuple: + color_array = tuple(color_array.flatten()) + return color_array From 08f6c3fb577f0c10c57ea3c4f9388d2eff8063f5 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 15:40:31 +0200 Subject: [PATCH 08/17] change function name to check_color_range --- fury/actor.py | 16 +++++++--------- fury/tests/test_utils.py | 14 +++++++------- fury/utils.py | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index eff438e15..bfac091c1 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -80,7 +80,7 @@ set_polydata_triangles, set_polydata_vertices, shallow_copy, - check_color_ndarray, + check_color_range, ) @@ -384,7 +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_ndarray(colors) + colors = check_color_range(colors) triangle_poly_data.GetPointData().SetScalars(numpy_to_vtk_colors(255 * colors)) if faces is None: @@ -531,7 +531,7 @@ def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1): skin_actor.SetMapper(skin_mapper) - color = check_color_ndarray(color) + color = check_color_range(color) skin_actor.GetProperty().SetColor(color[0][0], color[0][1], color[0][2]) skin_actor.GetProperty().SetOpacity(opacity) @@ -575,7 +575,7 @@ 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_ndarray(color) + color = check_color_range(color) if color.shape == (nb_surfaces, 4): opacity = color[:, -1] @@ -687,7 +687,7 @@ def streamtube( """ # Poly data with lines and colors - colors = check_color_ndarray(colors) + colors = check_color_range(colors) poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors) next_input = poly_data @@ -839,7 +839,7 @@ def line( """ # Poly data with lines and colors - colors = check_color_ndarray(colors) + colors = check_color_range(colors) poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors) next_input = poly_data @@ -957,7 +957,7 @@ 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_ndarray(colors) + colors = check_color_range(colors) scales = np.asarray(scale) arrow_actor = arrow(centers, dirs, colors, scales, repeat_primitive=False) @@ -1701,8 +1701,6 @@ def dot(points, colors=None, opacity=None, dot_size=5): vtk_faces.InsertNextCell(1) vtk_faces.InsertCellPoint(idd) - # colors = check_color_ndarray(colors) - colors = check_color_ndarray(colors) color_tuple = color_check(len(points), colors) color_array, global_opacity = color_tuple diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index bca988852..f667b3857 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -865,10 +865,10 @@ def test_color_check(): npt.assert_equal(global_opacity, 1) -def test_check_color_ndarray(): +def test_check_color_range(): # Test input None in color none_color = None - assert utils.check_color_ndarray(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]]) @@ -877,29 +877,29 @@ def test_check_color_ndarray(): [[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_ndarray( + 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_ndarray( + 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_ndarray(color_tuple) == color_tuple_expected + assert utils.check_color_range(color_tuple) == color_tuple_expected # Test for input of type list (invalid) color_list = [100, 150, 200, 0.4] color_list_expected = np.array([[1.0, 0.0, 0.0, 1.0]]) npt.assert_array_equal( - utils.check_color_ndarray(color_list), color_list_expected) + 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_ndarray(color_1d), color_1d_expected) + utils.check_color_range(color_1d), color_1d_expected) def test_is_ui(): diff --git a/fury/utils.py b/fury/utils.py index 339523130..84291b9ec 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1548,7 +1548,7 @@ def color_check(pts_len, colors=None): return color_array, global_opacity -def check_color_ndarray(color_array): +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, From 8628b614b80d97a09f83013f5ec95d18d22c1c05 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 16:32:22 +0200 Subject: [PATCH 09/17] update 1d list condition for passing test_spheres --- fury/actor.py | 5 +++++ fury/tests/test_utils.py | 4 ++-- fury/utils.py | 33 ++++++++++++++++----------------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index bfac091c1..b10402d55 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -1701,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 @@ -1768,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, @@ -1829,6 +1833,7 @@ def sphere( >>> # window.show(scene) """ + if not use_primitive: src = SphereSource() if faces is None else None diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index f667b3857..2fe20bd72 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -891,13 +891,13 @@ def test_check_color_range(): # Test for input of type list (invalid) color_list = [100, 150, 200, 0.4] - color_list_expected = np.array([[1.0, 0.0, 0.0, 1.0]]) + 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]]) + 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) diff --git a/fury/utils.py b/fury/utils.py index 84291b9ec..8085705ee 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1566,32 +1566,31 @@ def check_color_range(color_array): return color_array # Convert tuple or list to ndarray (N, 3) - is_tuple = False 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) - # change ndarray (3,) or (4,) to (1,3) or (1,4) if color_array.ndim == 1: - color_array = color_array.reshape(1, len(color_array)) - - # Check that the array is of the correct shape - assert color_array.ndim == 2 and color_array.shape[1] in [3, 4], \ - "Invalid color array shape, expect a numpy.ndarray (N,3) or (N,4)" - - # Check that all values are within [0, 1] - 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]") - # if so, set entire row to [1, 0, 0] or [1, 0, 0, 1] - color_array[i] = [1, 0, 0, 1][:len(row)] - print(color_array[i]) - print("It has been modified to red color") + 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)] + + 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]") + # if so, set entire row to [1, 0, 0] or [1, 0, 0, 1] + color_array[i] = [1, 0, 0, 1][:len(row)] + print(color_array[i]) + print("It has been modified to red color") if is_tuple: - color_array = tuple(color_array.flatten()) + color_array = tuple(color_array) return color_array From df8a6819ca04267c51a0859ee9cf52f5e65d9239 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 17:30:20 +0200 Subject: [PATCH 10/17] insert check_color_range() to the rest of the actors. add one print to 1d in the function --- fury/actor.py | 32 +++++++++++++++++++++++++++----- fury/utils.py | 5 +++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index b10402d55..c60db4f98 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -533,7 +533,7 @@ def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1): color = check_color_range(color) - skin_actor.GetProperty().SetColor(color[0][0], color[0][1], color[0][2]) + skin_actor.GetProperty().SetColor(color[0], color[1], color[2]) skin_actor.GetProperty().SetOpacity(opacity) return skin_actor @@ -570,13 +570,13 @@ def contour_from_label(data, affine=None, color=None): unique_roi_surfaces = Assembly() + # color = check_color_range(color) + if color is None: color = np.random.rand(nb_surfaces, 3) 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] @@ -1833,7 +1833,7 @@ def sphere( >>> # window.show(scene) """ - + colors = check_color_range(colors) if not use_primitive: src = SphereSource() if faces is None else None @@ -1927,6 +1927,7 @@ def cylinder( >>> # window.show(scene) """ + colors = check_color_range(colors) if faces is None: src = CylinderSource() src.SetCapping(capped) @@ -2015,6 +2016,8 @@ def disk( src = None rotate = None + colors = check_color_range(colors) + disk_actor = repeat_sources( centers=centers, colors=colors, @@ -2057,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, @@ -2109,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) @@ -2142,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, @@ -2190,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) @@ -2250,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( @@ -2337,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 @@ -2401,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, @@ -2449,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, @@ -2498,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, @@ -2547,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, @@ -2596,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, @@ -2667,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, @@ -2729,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 @@ -2939,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 @@ -3061,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) @@ -3362,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] @@ -3543,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, @@ -3655,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( diff --git a/fury/utils.py b/fury/utils.py index 8085705ee..e8c7dffc7 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1574,19 +1574,20 @@ def check_color_range(color_array): is_tuple = True color_array = np.asarray(color_array) + # 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)] + 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]") - # if so, set entire row to [1, 0, 0] or [1, 0, 0, 1] color_array[i] = [1, 0, 0, 1][:len(row)] - print(color_array[i]) print("It has been modified to red color") if is_tuple: From af0f2a69bd8dcdcfa8db9c3cbc9ba541eda1333a Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sun, 26 Mar 2023 17:51:02 +0200 Subject: [PATCH 11/17] pass the test_contour_from_label, keep the 1d np.array data type after new value --- fury/actor.py | 4 ++-- fury/utils.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index c60db4f98..7e131ff1e 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -570,13 +570,13 @@ def contour_from_label(data, affine=None, color=None): unique_roi_surfaces = Assembly() - # color = check_color_range(color) - if color is None: color = np.random.rand(nb_surfaces, 3) 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] diff --git a/fury/utils.py b/fury/utils.py index e8c7dffc7..002b75d95 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1581,6 +1581,7 @@ def check_color_range(color_array): 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: From c658e6f3b7933fca860aa47a2a6f53330337866c Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Mon, 27 Mar 2023 23:06:41 +0200 Subject: [PATCH 12/17] change function name to normalize_color() --- fury/actor.py | 60 ++++++++++++++++++++-------------------- fury/tests/test_utils.py | 14 +++++----- fury/utils.py | 2 +- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/fury/actor.py b/fury/actor.py index 7e131ff1e..6f87e4d58 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -80,7 +80,7 @@ set_polydata_triangles, set_polydata_vertices, shallow_copy, - check_color_range, + normalize_color, ) @@ -384,7 +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) + colors = normalize_color(colors) triangle_poly_data.GetPointData().SetScalars(numpy_to_vtk_colors(255 * colors)) if faces is None: @@ -531,7 +531,7 @@ def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1): skin_actor.SetMapper(skin_mapper) - color = check_color_range(color) + color = normalize_color(color) skin_actor.GetProperty().SetColor(color[0], color[1], color[2]) skin_actor.GetProperty().SetOpacity(opacity) @@ -575,7 +575,7 @@ 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) + color = normalize_color(color) if color.shape == (nb_surfaces, 4): opacity = color[:, -1] @@ -687,7 +687,7 @@ def streamtube( """ # Poly data with lines and colors - colors = check_color_range(colors) + colors = normalize_color(colors) poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors) next_input = poly_data @@ -839,7 +839,7 @@ def line( """ # Poly data with lines and colors - colors = check_color_range(colors) + colors = normalize_color(colors) poly_data, color_is_scalar = lines_to_vtk_polydata(lines, colors) next_input = poly_data @@ -957,7 +957,7 @@ 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) + colors = normalize_color(colors) scales = np.asarray(scale) arrow_actor = arrow(centers, dirs, colors, scales, repeat_primitive=False) @@ -1701,7 +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) + colors = normalize_color(colors) color_tuple = color_check(len(points), colors) color_array, global_opacity = color_tuple @@ -1770,7 +1770,7 @@ def point(points, colors, point_radius=0.1, phi=8, theta=8, opacity=1.0): """ - colors = check_color_range(colors) + colors = normalize_color(colors) return sphere( centers=points, @@ -1833,7 +1833,7 @@ def sphere( >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) if not use_primitive: src = SphereSource() if faces is None else None @@ -1927,7 +1927,7 @@ def cylinder( >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) if faces is None: src = CylinderSource() src.SetCapping(capped) @@ -2016,7 +2016,7 @@ def disk( src = None rotate = None - colors = check_color_range(colors) + colors = normalize_color(colors) disk_actor = repeat_sources( centers=centers, @@ -2060,7 +2060,7 @@ def square(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1): >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_square() res = fp.repeat_primitive( verts, @@ -2113,7 +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) + colors = normalize_color(colors) return square(centers=centers, directions=directions, colors=colors, scales=scales) @@ -2147,7 +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) + colors = normalize_color(colors) verts, faces = fp.prim_box() res = fp.repeat_primitive( verts, @@ -2196,7 +2196,7 @@ def cube(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1): >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) return box(centers=centers, directions=directions, colors=colors, scales=scales) @@ -2257,7 +2257,7 @@ def arrow( >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) if repeat_primitive: vertices, faces = fp.prim_arrow() res = fp.repeat_primitive( @@ -2345,7 +2345,7 @@ def cone( >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) if not use_primitive: src = ConeSource() if faces is None else None @@ -2410,7 +2410,7 @@ def triangularprism(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1): >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_triangularprism() res = fp.repeat_primitive( verts, @@ -2459,7 +2459,7 @@ def rhombicuboctahedron(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales= >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_rhombicuboctahedron() res = fp.repeat_primitive( verts, @@ -2509,7 +2509,7 @@ def pentagonalprism(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1): >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_pentagonalprism() res = fp.repeat_primitive( verts, @@ -2559,7 +2559,7 @@ def octagonalprism(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1): >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_octagonalprism() res = fp.repeat_primitive( verts, @@ -2609,7 +2609,7 @@ def frustum(centers, directions=(1, 0, 0), colors=(0, 1, 0), scales=1): >>> # window.show(scene) """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_frustum() res = fp.repeat_primitive( verts, @@ -2681,7 +2681,7 @@ def have_2_dimensions(arr): else: roundness = np.array(roundness) - colors = check_color_range(colors) + colors = normalize_color(colors) res = fp.repeat_primitive_function( func=fp.prim_superquadric, @@ -2745,7 +2745,7 @@ def billboard( ------- billboard_actor: Actor """ - colors = check_color_range(colors) + colors = normalize_color(colors) verts, faces = fp.prim_square() res = fp.repeat_primitive( verts, faces, centers=centers, colors=colors, scales=scales @@ -2956,7 +2956,7 @@ def add_to_scene(scene): texta.SetMapper(textm) - color = check_color_range(color) + color = normalize_color(color) texta.GetProperty().SetColor(color) # Set ser rotation origin to the center of the text is following the camera @@ -3080,7 +3080,7 @@ def get_position(self): text_actor.font_family(font_family) text_actor.font_style(bold, italic, shadow) - color = check_color_range(color) + color = normalize_color(color) text_actor.color(color) text_actor.justification(justification) text_actor.vertical_justification(vertical_justification) @@ -3382,7 +3382,7 @@ def texture(rgb, interp=True): act: Actor """ - arr = check_color_range(rgb) + arr = normalize_color(rgb) grid = rgb_to_vtk(np.ascontiguousarray(arr)) Y, X = arr.shape[:2] @@ -3563,7 +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) + colors = normalize_color(colors) verts, faces = fp.prim_box() repeated = fp.repeat_primitive( verts, @@ -3676,7 +3676,7 @@ def markers( >>> # window.show(scene, size=(600, 600)) """ - colors = check_color_range(colors) + colors = normalize_color(colors) n_markers = centers.shape[0] verts, faces = fp.prim_square() res = fp.repeat_primitive( diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 2fe20bd72..2566e2c91 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -865,10 +865,10 @@ def test_color_check(): npt.assert_equal(global_opacity, 1) -def test_check_color_range(): +def test_normalize_color(): # Test input None in color none_color = None - assert utils.check_color_range(none_color) == None + assert utils.normalize_color(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]]) @@ -877,29 +877,29 @@ def test_check_color_range(): [[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( + npt.assert_array_equal(utils.normalize_color( valid_color_array), valid_color_array) # Test for invalid input - npt.assert_array_equal(utils.check_color_range( + npt.assert_array_equal(utils.normalize_color( 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 + assert utils.normalize_color(color_tuple) == color_tuple_expected # Test for input of type list (invalid) color_list = [100, 150, 200, 0.4] 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) + utils.normalize_color(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) + utils.normalize_color(color_1d), color_1d_expected) def test_is_ui(): diff --git a/fury/utils.py b/fury/utils.py index 002b75d95..442dbc84b 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1548,7 +1548,7 @@ def color_check(pts_len, colors=None): return color_array, global_opacity -def check_color_range(color_array): +def normalize_color(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, From d0f3b91d606e1a8442c48cb6f4a34489a8e39798 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Mon, 27 Mar 2023 23:37:26 +0200 Subject: [PATCH 13/17] rewrite docstring to fit numpy standard --- fury/utils.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/fury/utils.py b/fury/utils.py index 442dbc84b..09c8b72e3 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1550,16 +1550,27 @@ def color_check(pts_len, colors=None): def normalize_color(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]. + Normalize an array of RGB or RGBA color values to be within the range + [0, 1]. - Args: - color_array (numpy.ndarray): An array of shape (N, 3) or (N, 4). + If any values are out of bounds, normalize the color array by scaling all + color values to fit within the valid range. + + Parameters + ---------- + color_array : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,) + An array of RGB or RGBA color values, where each row represents + a single color as an array of shape (3,) or (4,). Alternatively, + a tuple of length 3 or 4 can be passed to represent a single color. + + Returns + ------- + color_array : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,) + The original array if all values are within the range [0,1]. + If any values in the input array were out of bounds, a new array + or tuple is returned with the colors normalized to fit within the + valid range. - Returns: - 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: From 06b3b5ec56fa9257463880e0ee7fef6782deb4bc Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Tue, 28 Mar 2023 00:39:06 +0200 Subject: [PATCH 14/17] implement normalization of the color array --- fury/utils.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/fury/utils.py b/fury/utils.py index 09c8b72e3..af6a27ac2 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1585,22 +1585,19 @@ def normalize_color(color_array): is_tuple = True color_array = np.asarray(color_array) - # 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") + # Normalize the out of bounds array + if color_array.ndim == 1 and np.any(color_array > 1): + print( + f"{color_array} in the color array are outside the valid range [0, 1]") + color_array = color_array / 255.0 + print("It has been normalized to fit the range.") elif color_array.ndim == 2: for i, row in enumerate(color_array): - if np.any(row > 1) or np.any(row < 0): + if np.any(row > 1): 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") + color_array[i, :3] = color_array[i, :3] / 255.0 + print("It has been normalized to fit the range.") if is_tuple: color_array = tuple(color_array) From 97a9efff4a9215bb7a146fb0ddf47845447b153a Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Tue, 28 Mar 2023 00:53:56 +0200 Subject: [PATCH 15/17] update test_utils.py for new normalize color function --- fury/tests/test_utils.py | 19 ++++++++++--------- fury/utils.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 2566e2c91..89f8f885a 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -870,28 +870,29 @@ def test_normalize_color(): none_color = None assert utils.normalize_color(none_color) == None - # Test input data + # Test 2d 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]]) + outbound_color_array = [[0.1, 0.2, 1.0, 0.3], [255, 255, 255, 0.7]] + outbound_color_array_expected = np.array( + [[0.1, 0.2, 1.0, 0.3], [1.0, 1.0, 1.0, 0.7]]) - # Test for valid input + # Test for valid 2d input npt.assert_array_equal(utils.normalize_color( valid_color_array), valid_color_array) - # Test for invalid input + # Test for invalid 2d input npt.assert_array_equal(utils.normalize_color( - invalid_color_array), invalid_color_array_expected) + outbound_color_array), outbound_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.normalize_color(color_tuple) == color_tuple_expected - # Test for input of type list (invalid) + # Test for input of type list color_list = [100, 150, 200, 0.4] - color_list_expected = np.array([1.0, 0.0, 0.0, 1.0]) + color_list_expected = np.array(color_list) + color_list_expected[:3] = color_list_expected[:3]/255.0 npt.assert_array_equal( utils.normalize_color(color_list), color_list_expected) diff --git a/fury/utils.py b/fury/utils.py index af6a27ac2..c21bc6249 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1589,7 +1589,7 @@ def normalize_color(color_array): if color_array.ndim == 1 and np.any(color_array > 1): print( f"{color_array} in the color array are outside the valid range [0, 1]") - color_array = color_array / 255.0 + color_array[:3] = color_array[:3] / 255.0 print("It has been normalized to fit the range.") elif color_array.ndim == 2: From 0e2ae5efe31abc163efbbbe92bee1e3e5f059ba0 Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Tue, 28 Mar 2023 08:20:14 +0200 Subject: [PATCH 16/17] combine tuple and list condition. update color_check() to be compatible --- fury/tests/test_utils.py | 8 ++++---- fury/utils.py | 14 +++----------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 89f8f885a..872fedde6 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -839,7 +839,7 @@ def test_color_check(): npt.assert_equal(global_opacity, 1) points = np.array([[0, 0, 0], [0, 1, 0], [1, 0, 0]]) - colors = (1, 1, 1, 0.5) + colors = np.array([1, 1, 1, 0.5]) color_tuple = color_check(len(points), colors) color_array, global_opacity = color_tuple @@ -848,7 +848,7 @@ def test_color_check(): npt.assert_equal(global_opacity, 0.5) points = np.array([[0, 0, 0], [0, 1, 0], [1, 0, 0]]) - colors = (1, 0, 0) + colors = np.array([1, 0, 0]) color_tuple = color_check(len(points), colors) color_array, global_opacity = color_tuple @@ -886,8 +886,8 @@ def test_normalize_color(): # 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.normalize_color(color_tuple) == color_tuple_expected + color_tuple_expected = np.array([0.1, 0.2, 0.3]) + npt.assert_array_equal(utils.normalize_color(color_tuple), color_tuple_expected) # Test for input of type list color_list = [100, 150, 200, 0.4] diff --git a/fury/utils.py b/fury/utils.py index c21bc6249..ec47d6af6 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -1533,7 +1533,7 @@ def color_check(pts_len, colors=None): # Automatic RGB colors colors = np.asarray((1, 1, 1)) color_array = numpy_to_vtk_colors(np.tile(255 * colors, (pts_len, 1))) - elif type(colors) is tuple: + elif colors.shape in [(3,), (4,)]: global_opacity = 1 if len(colors) == 3 else colors[3] colors = np.asarray(colors) color_array = numpy_to_vtk_colors(np.tile(255 * colors, (pts_len, 1))) @@ -1576,13 +1576,8 @@ def normalize_color(color_array): 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 + # Convert tuple or list to ndarray + if isinstance(color_array, (tuple, list)): color_array = np.asarray(color_array) # Normalize the out of bounds array @@ -1599,9 +1594,6 @@ def normalize_color(color_array): color_array[i, :3] = color_array[i, :3] / 255.0 print("It has been normalized to fit the range.") - if is_tuple: - color_array = tuple(color_array) - return color_array From 4c3844911a9a51e7ff31a266c46983a750bf9fac Mon Sep 17 00:00:00 2001 From: Zhiwen Shi Date: Sat, 8 Apr 2023 10:29:43 +0200 Subject: [PATCH 17/17] add normalize_color() in Cylinder() --- fury/actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fury/actor.py b/fury/actor.py index 464456f7e..b04afe250 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -1931,7 +1931,7 @@ def cylinder( >>> # window.show(scene) """ - + colors = normalize_color(colors) if repeat_primitive: if resolution < 8: