diff --git a/.codespellrc b/.codespellrc index cd52e60e1..c48cd788c 100644 --- a/.codespellrc +++ b/.codespellrc @@ -2,4 +2,4 @@ skip = .git,*.pdf,*.svg,*.bib,*.html # ue,lod,ans,lastr,numer,lamda,PrIs -- variable names # Hart,Flagg -- name -ignore-words-list = IST,nd,te,ue,lod,hart,ans,lastr,numer,lamda,flagg,pris,lod,IST +ignore-words-list = IST,nd,te,ue,lod,hart,ans,lastr,numer,lamda,flagg,pris,lod,IST,tese diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 764d78acb..000000000 --- a/.flake8 +++ /dev/null @@ -1,14 +0,0 @@ -[flake8] -max-line-length = 88 -ignore = D100,D101,D102,D103,D104,D105,D200,D201,D202,D204,D205,D208,D209,D210,D300,D301,D400,D401,D403,E24,E121,E123,E126,E226,E266,E402,E704,E731,F821,I100,I101,I201,N802,N803,N804,N806,W503,W504,W605, E203 -exclude = - .git - __pycache__ - build - dist - fury/_version.py - docs/source/conf.py - docs/experimental - *test* - *sphinx* - */__init__.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..a57e0b729 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/check_format.yml b/.github/workflows/check_format.yml new file mode 100644 index 000000000..7cae58e94 --- /dev/null +++ b/.github/workflows/check_format.yml @@ -0,0 +1,29 @@ +name: code format + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + pre-commit: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ['3.10'] + requires: ['latest'] + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install and run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 5768d7c63..6f32efeaf 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -14,6 +14,6 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Codespell - uses: codespell-project/actions-codespell@v1 + uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index b016e18c3..ae8a4c1fe 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -24,11 +24,11 @@ jobs: matrix: python-version: [3.9] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Dependencies @@ -62,5 +62,5 @@ jobs: ssh-key: ${{ secrets.ACTIONS_DEPLOY_KEY }} repository-name: fury-gl/fury-website folder: ./docs/build/html-web-only - target-folder: v0.9.x + target-folder: v0.10.x clean: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0648dfa9f..a3bdb5fb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,7 @@ defaults: jobs: build: runs-on: ${{ matrix.os }} + timeout-minutes: 60 defaults: run: shell: bash @@ -54,10 +55,10 @@ jobs: PRE_WHEELS: "https://pypi.anaconda.org/scipy-wheels-nightly/simple" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/cache@v2 + - uses: actions/cache@v4 if: startsWith(runner.os, 'Linux') with: path: | @@ -66,14 +67,14 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - - uses: actions/cache@v2 + - uses: actions/cache@v4 if: startsWith(runner.os, 'macOS') with: path: ~/Library/Caches/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - - uses: actions/cache@v2 + - uses: actions/cache@v4 if: startsWith(runner.os, 'Windows') with: path: ~/.pip @@ -81,7 +82,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set up Virtualenv @@ -90,7 +91,7 @@ jobs: python -m pip install --upgrade pip virtualenv virtualenv $VENV_ARGS venv - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 if: ${{ matrix.install-type == 'conda' }} with: auto-update-conda: true @@ -113,7 +114,7 @@ jobs: ci/run_tests.sh fi - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: ${{ matrix.coverage }} with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.mailmap b/.mailmap index c4f518731..e41e11bab 100644 --- a/.mailmap +++ b/.mailmap @@ -61,3 +61,5 @@ Sreekar Chigurupati sreekar chigurupati lej0hn Francois Rheault frheault Dwij Raj Hari Dwij Raj Hari <75260253+dwijrajhari@users.noreply.github.com> +Tania Castillo tvcastillod +Tania Castillo Tania Castillo <31288525+tvcastillod@users.noreply.github.com> diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 958c651fb..988c0ad67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,20 +14,8 @@ repos: - id: check-merge-conflict - id: check-vcs-permalinks - id: detect-aws-credentials + args: [--allow-missing-credentials] - id: detect-private-key - # - repo: https://github.com/grantjenks/blue - # rev: v0.9.1 - # hooks: - # - id: blue - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - exclude: "^(docs/experimental|tools)/" - repo: https://github.com/codespell-project/codespell rev: v2.2.2 hooks: @@ -49,3 +37,11 @@ repos: - importlib_resources args: ["fury"] pass_filenames: false + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.3 + hooks: + # Run the linter + - id: ruff + args: [ --fix] + # Run the formatter + - id: ruff-format diff --git a/LICENSE b/LICENSE index 6fda7b6f7..d67e68ac3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023, FURY - Free Unified Rendering in Python. All rights reserved. +Copyright (c) 2024, FURY - Free Unified Rendering in Python. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/docs/examples/_valid_examples.toml b/docs/examples/_valid_examples.toml index 0efd032ba..e5ecfb142 100644 --- a/docs/examples/_valid_examples.toml +++ b/docs/examples/_valid_examples.toml @@ -88,7 +88,7 @@ readme = "file: _animation.rst" position = 10 enable = true files = [ - "viz_interpolators.py", + # "viz_interpolators.py", "viz_bezier_interpolator.py", "viz_introduction.py", "viz_camera.py", diff --git a/docs/examples/collision-particles.py b/docs/examples/collision-particles.py index c93167615..1eedb520e 100644 --- a/docs/examples/collision-particles.py +++ b/docs/examples/collision-particles.py @@ -24,7 +24,6 @@ def box_edges(box_lx, box_ly, box_lz): - edge1 = 0.5 * np.array( [ [box_lx, box_ly, box_lz], @@ -54,7 +53,6 @@ def collision(): sec = int(num_vertices / num_particles) for i, j in np.ndindex(num_particles, num_particles): - if i == j: continue distance = np.linalg.norm(xyz[i] - xyz[j]) @@ -148,7 +146,7 @@ def collision(): counter = itertools.count() vertices = utils.vertices_from_actor(sphere_actor) -vcolors = utils.colors_from_actor(sphere_actor, 'colors') +vcolors = utils.colors_from_actor(sphere_actor, "colors") no_vertices_per_sphere = len(vertices) / num_particles initial_vertices = vertices.copy() - np.repeat(xyz, no_vertices_per_sphere, axis=0) @@ -177,4 +175,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(showm.scene, size=(900, 768), out_path='simple_collisions.png') +window.record(showm.scene, size=(900, 768), out_path="simple_collisions.png") diff --git a/docs/examples/viz_advanced.py b/docs/examples/viz_advanced.py index 2b8ff92fd..bf28b0b91 100644 --- a/docs/examples/viz_advanced.py +++ b/docs/examples/viz_advanced.py @@ -11,7 +11,6 @@ the main functions using the following modules. """ -import numpy as np from dipy.data.fetcher import fetch_bundles_2_subjects, read_bundles_2_subjects ############################################################################### @@ -36,6 +35,7 @@ # # First we need to fetch and load some datasets. from dipy.tracking.streamline import Streamlines +import numpy as np from fury import actor, ui, window @@ -46,19 +46,19 @@ # ``af left`` (left arcuate fasciculus) and maps, e.g. FA for a specific # subject. -res = read_bundles_2_subjects('subj_1', ['t1', 'fa'], ['af.left', 'cst.right', 'cc_1']) +res = read_bundles_2_subjects("subj_1", ["t1", "fa"], ["af.left", "cst.right", "cc_1"]) ############################################################################### # We will use 3 bundles, FA and the affine transformation that brings the voxel # coordinates to world coordinates (RAS 1mm). -streamlines = Streamlines(res['af.left']) -streamlines.extend(res['cst.right']) -streamlines.extend(res['cc_1']) +streamlines = Streamlines(res["af.left"]) +streamlines.extend(res["cst.right"]) +streamlines.extend(res["cc_1"]) -data = res['fa'] +data = res["fa"] shape = data.shape -affine = res['affine'] +affine = res["affine"] ############################################################################### # With our current design it is easy to decide in which space you want the @@ -134,7 +134,7 @@ min_value=0, max_value=shape[2] - 1, initial_value=shape[2] / 2, - text_template='{value:.0f}', + text_template="{value:.0f}", length=140, ) @@ -142,7 +142,7 @@ min_value=0, max_value=shape[0] - 1, initial_value=shape[0] / 2, - text_template='{value:.0f}', + text_template="{value:.0f}", length=140, ) @@ -150,7 +150,7 @@ min_value=0, max_value=shape[1] - 1, initial_value=shape[1] / 2, - text_template='{value:.0f}', + text_template="{value:.0f}", length=140, ) @@ -197,8 +197,8 @@ def build_label(text): label = ui.TextBlock2D() label.message = text label.font_size = 18 - label.font_family = 'Arial' - label.justification = 'left' + label.font_family = "Arial" + label.justification = "left" label.bold = False label.italic = False label.shadow = False @@ -208,15 +208,15 @@ def build_label(text): return label -line_slider_label_z = build_label(text='Z Slice') -line_slider_label_x = build_label(text='X Slice') -line_slider_label_y = build_label(text='Y Slice') -opacity_slider_label = build_label(text='Opacity') +line_slider_label_z = build_label(text="Z Slice") +line_slider_label_x = build_label(text="X Slice") +line_slider_label_y = build_label(text="Y Slice") +opacity_slider_label = build_label(text="Opacity") ############################################################################### # Now we will create a ``panel`` to contain the sliders and labels. -panel = ui.Panel2D(size=(300, 200), color=(1, 1, 1), opacity=0.1, align='right') +panel = ui.Panel2D(size=(300, 200), color=(1, 1, 1), opacity=0.1, align="right") panel.center = (1030, 120) panel.add_element(line_slider_label_x, (0.1, 0.75)) @@ -262,15 +262,13 @@ def win_callback(obj, _event): scene.reset_clipping_range() if interactive: - show_m.add_window_callback(win_callback) show_m.render() show_m.start() else: - window.record( - scene, out_path='bundles_and_3_slices.png', size=(1200, 900), reset_camera=False + scene, out_path="bundles_and_3_slices.png", size=(1200, 900), reset_camera=False ) del show_m diff --git a/docs/examples/viz_animated_surfaces.py b/docs/examples/viz_animated_surfaces.py index 2ff361e0e..da166421c 100644 --- a/docs/examples/viz_animated_surfaces.py +++ b/docs/examples/viz_animated_surfaces.py @@ -24,8 +24,7 @@ # the z coordinate is a function of time. -def update_surface(x, y, equation, cmap_name='viridis'): - +def update_surface(x, y, equation, cmap_name="viridis"): # z is the function F i.e. F(x, y, t) z = eval(equation) xyz = np.vstack([x, y, z]).T @@ -100,17 +99,17 @@ def create_surface(x, y, equation, colormap_name): ############################################################################### # Equations to be plotted -eq1 = 'np.abs(np.sin(x*2*np.pi*np.cos(time/2)))**1*np.cos(time/2)*\ - np.abs(np.cos(y*2*np.pi*np.sin(time/2)))**1*np.sin(time/2)*1.2' -eq2 = '((x**2 - y**2)/(x**2 + y**2))**(2)*np.cos(6*np.pi*x*y-1.8*time)*0.24' -eq3 = '(np.sin(np.pi*2*x-np.sin(1.8*time))*np.cos(np.pi*2*y+np.cos(1.8*time)))\ - *0.48' -eq4 = 'np.cos(24*np.sqrt(x**2 + y**2) - 2*time)*0.18' +eq1 = "np.abs(np.sin(x*2*np.pi*np.cos(time/2)))**1*np.cos(time/2)*\ + np.abs(np.cos(y*2*np.pi*np.sin(time/2)))**1*np.sin(time/2)*1.2" +eq2 = "((x**2 - y**2)/(x**2 + y**2))**(2)*np.cos(6*np.pi*x*y-1.8*time)*0.24" +eq3 = "(np.sin(np.pi*2*x-np.sin(1.8*time))*np.cos(np.pi*2*y+np.cos(1.8*time)))\ + *0.48" +eq4 = "np.cos(24*np.sqrt(x**2 + y**2) - 2*time)*0.18" equations = [eq1, eq2, eq3, eq4] ############################################################################### # List of colormaps to be used for the various functions. -cmap_names = ['hot', 'plasma', 'viridis', 'ocean'] +cmap_names = ["hot", "plasma", "viridis", "ocean"] ############################################################################### # Creating a list of surfaces. @@ -137,7 +136,7 @@ def create_surface(x, y, equation, colormap_name): text = [] for i in range(4): t_actor = actor.vector_text( - 'Function ' + str(i + 1), pos=(0, 0, 0), scale=(0.17, 0.2, 0.2) + "Function " + str(i + 1), pos=(0, 0, 0), scale=(0.17, 0.2, 0.2) ) text.append(t_actor) @@ -159,7 +158,7 @@ def create_surface(x, y, equation, colormap_name): ############################################################################### # Initializing text box to print the title of the animation tb = ui.TextBlock2D(bold=True, position=(200, 60)) -tb.message = 'Animated 2D functions' +tb.message = "Animated 2D functions" scene.add(tb) ############################################################################### @@ -207,4 +206,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(showm.scene, size=(600, 600), out_path='viz_animated_surfaces.png') +window.record(showm.scene, size=(600, 600), out_path="viz_animated_surfaces.png") diff --git a/docs/examples/viz_arrow.py b/docs/examples/viz_arrow.py index f65c6bf8e..bd6ac3845 100644 --- a/docs/examples/viz_arrow.py +++ b/docs/examples/viz_arrow.py @@ -50,4 +50,4 @@ if interactive: window.show(scene, size=(600, 600)) -window.record(scene, out_path='viz_arrow.png', size=(600, 600)) +window.record(scene, out_path="viz_arrow.png", size=(600, 600)) diff --git a/docs/examples/viz_ball_collide.py b/docs/examples/viz_ball_collide.py index 517673a2e..dd991cf8c 100644 --- a/docs/examples/viz_ball_collide.py +++ b/docs/examples/viz_ball_collide.py @@ -9,6 +9,7 @@ First some imports. """ + import itertools import numpy as np @@ -86,7 +87,7 @@ def sync_actor(actor, multibody): apply_force = True -tb = ui.TextBlock2D(position=(0, 600), font_size=30, color=(1, 0.5, 0), text='') +tb = ui.TextBlock2D(position=(0, 600), font_size=30, color=(1, 0.5, 0), text="") scene.add(tb) scene.set_camera( position=(0.30, -18.78, 0.89), focal_point=(0.15, 0.25, 0.40), view_up=(0, 0, 1.00) @@ -122,7 +123,7 @@ def timer_callback(_obj, _event): # Get various collision information using `p.getContactPoints`. contact = p.getContactPoints(red_ball, blue_ball, -1, -1) if len(contact) != 0: - tb.message = 'Collision!!' + tb.message = "Collision!!" p.stepSimulation() @@ -137,4 +138,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, size=(900, 700), out_path='viz_ball_collide.png') +window.record(scene, size=(900, 700), out_path="viz_ball_collide.png") diff --git a/docs/examples/viz_bezier_interpolator.py b/docs/examples/viz_bezier_interpolator.py index 241370a62..1e833b4c2 100644 --- a/docs/examples/viz_bezier_interpolator.py +++ b/docs/examples/viz_bezier_interpolator.py @@ -5,6 +5,7 @@ Keyframe animation using cubic Bezier interpolator. """ + import numpy as np from fury import actor, window @@ -44,19 +45,19 @@ # keyframe 1 -----------------> keyframe 2 # (time-1) (value-1) (out-cp-1) -----------------> (time-2) (value-2) (in-cp-2) -keyframe_1 = {'value': [-2, 0, 0], 'out_cp': [-15, 6, 0]} -keyframe_2 = {'value': [18, 0, 0], 'in_cp': [27, 18, 0]} +keyframe_1 = {"value": [-2, 0, 0], "out_cp": [-15, 6, 0]} +keyframe_2 = {"value": [18, 0, 0], "in_cp": [27, 18, 0]} ############################################################################### # Visualizing points pts_actor = actor.sphere( - np.array([keyframe_1.get('value'), keyframe_2.get('value')]), (1, 0, 0), radii=0.3 + np.array([keyframe_1.get("value"), keyframe_2.get("value")]), (1, 0, 0), radii=0.3 ) ############################################################################### # Visualizing the control points cps_actor = actor.sphere( - np.array([keyframe_2.get('in_cp'), keyframe_1.get('out_cp')]), (0, 0, 1), radii=0.6 + np.array([keyframe_2.get("in_cp"), keyframe_1.get("out_cp")]), (0, 0, 1), radii=0.6 ) ############################################################################### @@ -86,10 +87,10 @@ # will be the same as the position itself. animation.set_position( - 0.0, np.array(keyframe_1.get('value')), out_cp=np.array(keyframe_1.get('out_cp')) + 0.0, np.array(keyframe_1.get("value")), out_cp=np.array(keyframe_1.get("out_cp")) ) animation.set_position( - 5.0, np.array(keyframe_2.get('value')), in_cp=np.array(keyframe_2.get('in_cp')) + 5.0, np.array(keyframe_2.get("value")), in_cp=np.array(keyframe_2.get("in_cp")) ) ############################################################################### @@ -109,7 +110,7 @@ if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_animation_bezier_1.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_bezier_1.png", size=(900, 768)) ############################################################################### # A more complex scene scene @@ -126,9 +127,9 @@ # point it controls. keyframes = { # time - position - in control point - out control point - 0.0: {'value': [-2, 0, 0], 'out_cp': [-15, 6, 0]}, - 5.0: {'value': [18, 0, 0], 'in_cp': [27, 18, 0], 'out_cp': [27, -18, 0]}, - 9.0: {'value': [-5, -10, -10]}, + 0.0: {"value": [-2, 0, 0], "out_cp": [-15, 6, 0]}, + 5.0: {"value": [18, 0, 0], "in_cp": [27, 18, 0], "out_cp": [27, -18, 0]}, + 9.0: {"value": [-5, -10, -10]}, } ############################################################################### @@ -149,10 +150,10 @@ ########################################################################### # visualizing the points and control points (only for demonstration) -for t, keyframe in keyframes.items(): - pos = keyframe.get('value') - in_control_point = keyframe.get('in_cp') - out_control_point = keyframe.get('out_cp') +for keyframe in keyframes.values(): + pos = keyframe.get("value") + in_control_point = keyframe.get("in_cp") + out_control_point = keyframe.get("out_cp") ########################################################################### # visualizing position keyframe @@ -181,4 +182,4 @@ if interactive: show_manager.start() -window.record(scene, out_path='viz_keyframe_animation_bezier_2.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_bezier_2.png", size=(900, 768)) diff --git a/docs/examples/viz_billboard_sdf_spheres.py b/docs/examples/viz_billboard_sdf_spheres.py index 10bc5b698..e9c61e41c 100644 --- a/docs/examples/viz_billboard_sdf_spheres.py +++ b/docs/examples/viz_billboard_sdf_spheres.py @@ -51,15 +51,24 @@ # ============================ # FURY provides an easy way to create sphere glyphs from numpy arrays as # follows: -centers = np.array([ - [0, 0, 0], [-6, -6, -6], [8, 8, 8], [8.5, 9.5, 9.5], [10, -10, 10], - [-13, 13, -13], [-17, -17, 17]]) -colors = np.array([ - [1, 1, 0], [1, 0, 1], [0, 0, 1], [1, 1, 1], [1, 0, 0], [0, 1, 0], - [0, 1, 1]]) -scales = np.array([6, 1.2, 1, .2, .7, 3, 2]) +centers = np.array( + [ + [0, 0, 0], + [-6, -6, -6], + [8, 8, 8], + [8.5, 9.5, 9.5], + [10, -10, 10], + [-13, 13, -13], + [-17, -17, 17], + ] +) +colors = np.array( + [[1, 1, 0], [1, 0, 1], [0, 0, 1], [1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 1, 1]] +) +scales = np.array([6, 1.2, 1, 0.2, 0.7, 3, 2]) spheres_actor = actor.sphere( - centers, colors, radii=scales, phi=8, theta=8, use_primitive=False) + centers, colors, radii=scales, phi=8, theta=8, use_primitive=False +) ############################################################################### # To interactively visualize the recently created actors, we only need to add @@ -72,7 +81,7 @@ if interactive: window.show(scene) else: - window.record(scene, size=(600, 600), out_path='viz_regular_spheres.png') + window.record(scene, size=(600, 600), out_path="viz_regular_spheres.png") ############################################################################### # Now, let's explore our scene to understand what we have created. Traditional @@ -84,20 +93,21 @@ if interactive: window.show(scene) else: - window.record(scene, size=(600, 600), out_path='viz_low_res_wireframe.png') + window.record(scene, size=(600, 600), out_path="viz_low_res_wireframe.png") ############################################################################### # Let's clean the scene and play with the parameters `phi` and `theta`. scene.clear() spheres_actor = actor.sphere( - centers, colors, radii=scales, phi=16, theta=16, use_primitive=False) + centers, colors, radii=scales, phi=16, theta=16, use_primitive=False +) represent_actor_as_wireframe(spheres_actor) scene.add(spheres_actor) if interactive: window.show(scene) else: - window.record(scene, size=(600, 600), out_path='viz_hi_res_wireframe.png') + window.record(scene, size=(600, 600), out_path="viz_hi_res_wireframe.png") ############################################################################### # As you might have noticed, these parameters control the resolution of the @@ -128,8 +138,7 @@ if interactive: window.show(scene) else: - window.record( - scene, size=(600, 600), out_path='viz_billboards_wireframe.png') + window.record(scene, size=(600, 600), out_path="viz_billboards_wireframe.png") ############################################################################### # If you interacted with this actor, you might have noticed how it always @@ -141,23 +150,22 @@ ############################################################################### # FURY already includes a shader function with the definition of a Signed # Distance Sphere. So we can load it and use it like this: -sd_sphere = import_fury_shader(os.path.join('sdf', 'sd_sphere.frag')) +sd_sphere = import_fury_shader(os.path.join("sdf", "sd_sphere.frag")) ############################################################################### # Additionally, we need to define the radii of our spheres. Since we prefer # these to be determined by the billboards' size, we will use the maximum # radius distance allowed by our billboards. -sphere_radius = 'const float RADIUS = 1.;' +sphere_radius = "const float RADIUS = 1.;" ############################################################################### # Let's calculate the distance to the sphere by combining the previously # defined variables. -sphere_dist = 'float dist = sdSphere(point, RADIUS);' +sphere_dist = "float dist = sdSphere(point, RADIUS);" ############################################################################### # Now, evaluate the signed distance function. -sdf_eval = \ - """ +sdf_eval = """ if (dist < 0) fragOutput0 = vec4(color, opacity); else @@ -173,14 +181,14 @@ ############################################################################### # We are ready to create and visualize our SDF-billboard actors. spheres_actor = actor.billboard( - centers, colors=colors, scales=scales, fs_dec=fs_dec, fs_impl=fs_impl) + centers, colors=colors, scales=scales, fs_dec=fs_dec, fs_impl=fs_impl +) scene.add(spheres_actor) if interactive: window.show(scene) else: - window.record( - scene, size=(600, 600), out_path='viz_billboards_circles.png') + window.record(scene, size=(600, 600), out_path="viz_billboards_circles.png") ############################################################################### # Hold on, those actors don't look exactly like the ones we created using @@ -195,14 +203,12 @@ # into detail in the gradient and derivatives of SDFs, so we will use the # central differences technique implemented in the following FURY shader # function: -central_diffs_normal = import_fury_shader( - os.path.join('sdf', 'central_diffs.frag')) +central_diffs_normal = import_fury_shader(os.path.join("sdf", "central_diffs.frag")) ############################################################################### # To use the central differences technique, we need to define a map function # that wraps our SDF and evaluates only a point. -sd_sphere_normal = \ - """ +sd_sphere_normal = """ float map(vec3 p) { return sdSphere(p, RADIUS); @@ -212,14 +218,21 @@ ############################################################################### # Then we can load the Blinn-Phong illumination model. blinn_phong_model = import_fury_shader( - os.path.join('lighting', 'blinn_phong_model.frag')) + os.path.join("lighting", "blinn_phong_model.frag") +) ############################################################################### # Again, let's bring all of our declarations (constants and functions) # together. -fs_dec = compose_shader([ - sphere_radius, sd_sphere, sd_sphere_normal, central_diffs_normal, - blinn_phong_model]) +fs_dec = compose_shader( + [ + sphere_radius, + sd_sphere, + sd_sphere_normal, + central_diffs_normal, + blinn_phong_model, + ] +) ############################################################################### # Now, we can start our fragment shader implementation with the signed distance @@ -227,49 +240,48 @@ # if statement but a `step` function, which is a more efficient way to perform # this evaluation. You can also replace the `step` function with a `smoothstep` # operation and, in that way, add a very efficient form of antialiasing. -sdf_eval = 'opacity *= 1 - step(0, dist);' +sdf_eval = "opacity *= 1 - step(0, dist);" ############################################################################### # In this case, we also need the absolute value of the distance to compensate # for the depth of the SDF sphere. -abs_dist = 'float absDist = abs(dist);' +abs_dist = "float absDist = abs(dist);" ############################################################################### # We are ready to calculate the normals. -normal = 'vec3 normal = centralDiffsNormals(vec3(point.xy, absDist), .0001);' +normal = "vec3 normal = centralDiffsNormals(vec3(point.xy, absDist), .0001);" ############################################################################### # With the normals we can calculate a light attenuation factor. -light_attenuation = 'float lightAttenuation = normal.z;' +light_attenuation = "float lightAttenuation = normal.z;" ############################################################################### # Now, we are ready to calculate the color and output it. -color = \ - """ +color = """ color = blinnPhongIllumModel( lightAttenuation, lightColor0, diffuseColor, specularPower, specularColor, ambientColor); """ -frag_output = 'fragOutput0 = vec4(color, opacity);' +frag_output = "fragOutput0 = vec4(color, opacity);" ############################################################################### # As before, we can bring our implementation code together. -fs_impl = compose_shader([ - sphere_dist, sdf_eval, abs_dist, normal, light_attenuation, color, - frag_output]) +fs_impl = compose_shader( + [sphere_dist, sdf_eval, abs_dist, normal, light_attenuation, color, frag_output] +) ############################################################################### # Finally, recreate the SDF billboard actors and visualize them. spheres_actor = actor.billboard( - centers, colors=colors, scales=scales, fs_dec=fs_dec, fs_impl=fs_impl) + centers, colors=colors, scales=scales, fs_dec=fs_dec, fs_impl=fs_impl +) scene.add(spheres_actor) if interactive: window.show(scene) else: - window.record( - scene, size=(600, 600), out_path='viz_billboards_spheres.png') + window.record(scene, size=(600, 600), out_path="viz_billboards_spheres.png") ############################################################################### # References diff --git a/docs/examples/viz_brick_wall.py b/docs/examples/viz_brick_wall.py index b61166f18..4d490450e 100644 --- a/docs/examples/viz_brick_wall.py +++ b/docs/examples/viz_brick_wall.py @@ -9,6 +9,7 @@ First some imports. """ + import itertools import numpy as np @@ -230,7 +231,7 @@ def sync_actor(actor, multibody): fpss = np.array([]) tb = ui.TextBlock2D( - text='Avg. FPS: \nSim Steps: ', position=(0, 680), font_size=30, color=(1, 0.5, 0) + text="Avg. FPS: \nSim Steps: ", position=(0, 680), font_size=30, color=(1, 0.5, 0) ) scene.add(tb) @@ -259,7 +260,7 @@ def timer_callback(_obj, _event): fps = showm.frame_rate fpss = np.append(fpss, fps) tb.message = ( - 'Avg. FPS: ' + str(np.round(np.mean(fpss), 0)) + '\nSim Steps: ' + str(cnt) + "Avg. FPS: " + str(np.round(np.mean(fpss), 0)) + "\nSim Steps: " + str(cnt) ) # Get the position and orientation of the ball. @@ -299,4 +300,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, out_path='viz_brick_wall.png', size=(900, 768)) +window.record(scene, out_path="viz_brick_wall.png", size=(900, 768)) diff --git a/docs/examples/viz_brownian_motion.py b/docs/examples/viz_brownian_motion.py index 8150bc28c..65880f900 100644 --- a/docs/examples/viz_brownian_motion.py +++ b/docs/examples/viz_brownian_motion.py @@ -50,21 +50,21 @@ def particle( colors, - origin=[0, 0, 0], - num_total_steps=300, - total_time=5, - delta=1.8, - path_thickness=3, + _origin=origin, + _num_total_steps=num_total_steps, + _total_time=total_time, + _delta=delta, + _path_thickness=path_thickness, ): - origin = np.asarray(origin, dtype=float) - position = np.tile(origin, (num_total_steps, 1)) - path_actor = actor.line([position], colors, linewidth=path_thickness) + _origin = np.asarray(_origin, dtype=float) + position = np.tile(origin, (_num_total_steps, 1)) + path_actor = actor.line([position], colors, linewidth=_path_thickness) path_actor.position = position - path_actor.delta = delta - path_actor.num_total_steps = num_total_steps - path_actor.time_step = total_time / num_total_steps + path_actor.delta = _delta + path_actor.num_total_steps = _num_total_steps + path_actor.time_step = _total_time / _num_total_steps path_actor.vertices = utils.vertices_from_actor(path_actor) - path_actor.no_vertices_per_point = len(path_actor.vertices) / num_total_steps + path_actor.no_vertices_per_point = len(path_actor.vertices) / _num_total_steps path_actor.initial_vertices = path_actor.vertices.copy() - np.repeat( position, path_actor.no_vertices_per_point, axis=0 ) @@ -130,7 +130,7 @@ def update_path(act, counter_step): # Initializing text box to display the name of the animation tb = ui.TextBlock2D(bold=True, position=(235, 40), color=(0, 0, 0)) -tb.message = 'Brownian Motion' +tb.message = "Brownian Motion" scene.add(tb) @@ -155,4 +155,4 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 30, timer_callback) showm.start() -window.record(showm.scene, size=(600, 600), out_path='viz_brownian_motion.png') +window.record(showm.scene, size=(600, 600), out_path="viz_brownian_motion.png") diff --git a/docs/examples/viz_bundles.py b/docs/examples/viz_bundles.py index 147c1f70f..092f292de 100644 --- a/docs/examples/viz_bundles.py +++ b/docs/examples/viz_bundles.py @@ -7,9 +7,9 @@ which provides metrics and bundles. """ -import numpy as np from dipy.data import fetch_bundles_2_subjects, read_bundles_2_subjects from dipy.tracking.streamline import length, transform_streamlines +import numpy as np from fury import actor, window @@ -17,23 +17,23 @@ fetch_bundles_2_subjects() dix = read_bundles_2_subjects( - subj_id='subj_1', metrics=['fa'], bundles=['cg.left', 'cst.right'] + subj_id="subj_1", metrics=["fa"], bundles=["cg.left", "cst.right"] ) ############################################################################### # Store fractional anisotropy. -fa = dix['fa'] +fa = dix["fa"] ############################################################################### # Store grid to world transformation matrix. -affine = dix['affine'] +affine = dix["affine"] ############################################################################### # Store the cingulum bundle. A bundle is a list of streamlines. -bundle = dix['cg.left'] +bundle = dix["cg.left"] ############################################################################### # It happened that this bundle is in world coordinates and therefore we need to @@ -63,7 +63,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle1.png', size=(600, 600)) +window.record(scene, out_path="bundle1.png", size=(600, 600)) ############################################################################### # You may wonder how we knew how to set the camera. This is very easy. You just @@ -93,7 +93,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle2.png', size=(600, 600)) +window.record(scene, out_path="bundle2.png", size=(600, 600)) ############################################################################## # Show every point with a value from a volume with your colormap @@ -117,7 +117,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle3.png', size=(600, 600)) +window.record(scene, out_path="bundle3.png", size=(600, 600)) ############################################################################### # Show every bundle with a specific color @@ -134,7 +134,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle4.png', size=(600, 600)) +window.record(scene, out_path="bundle4.png", size=(600, 600)) ############################################################################### # Show every streamline of a bundle with a different color @@ -168,7 +168,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle5.png', size=(600, 600)) +window.record(scene, out_path="bundle5.png", size=(600, 600)) ############################################################################### # Show every point of every streamline with a different color @@ -189,7 +189,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle6.png', size=(600, 600)) +window.record(scene, out_path="bundle6.png", size=(600, 600)) ############################################################################### # Add depth cues to streamline rendering @@ -210,7 +210,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle7.png', size=(600, 600)) +window.record(scene, out_path="bundle7.png", size=(600, 600)) ############################################################################### # Render streamlines as fake tubes @@ -228,7 +228,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle8.png', size=(600, 600)) +window.record(scene, out_path="bundle8.png", size=(600, 600)) ############################################################################### # Combine depth cues with fake tubes @@ -247,7 +247,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle9.png', size=(600, 600)) +window.record(scene, out_path="bundle9.png", size=(600, 600)) ############################################################################### # Render streamlines as tubes @@ -267,7 +267,7 @@ if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='bundle10.png', size=(600, 600)) +window.record(scene, out_path="bundle10.png", size=(600, 600)) ############################################################################### # In summary, we showed that there are many useful ways for visualizing maps diff --git a/docs/examples/viz_buttons.py b/docs/examples/viz_buttons.py index 58eaf8d0a..8d89b74ce 100644 --- a/docs/examples/viz_buttons.py +++ b/docs/examples/viz_buttons.py @@ -9,6 +9,7 @@ First, some imports. """ + from fury import ui, window from fury.data import fetch_viz_icons, read_viz_icons @@ -21,15 +22,15 @@ # Let's create some buttons and text and put them in a panel. # First we'll make the panel. -panel = ui.Panel2D(size=(300, 150), color=(1, 1, 1), align='right') +panel = ui.Panel2D(size=(300, 150), color=(1, 1, 1), align="right") panel.center = (500, 400) ############################################################################### # Then we'll make two text labels and place them on the panel. # Note that we specify the position with integer numbers of pixels. -text = ui.TextBlock2D(text='Click me') -text2 = ui.TextBlock2D(text='Me too') +text = ui.TextBlock2D(text="Click me") +text2 = ui.TextBlock2D(text="Me too") panel.add_element(text, (50, 100)) panel.add_element(text2, (180, 100)) @@ -41,14 +42,14 @@ button_example = ui.Button2D( - icon_fnames=[('square', read_viz_icons(fname='stop2.png'))] + icon_fnames=[("square", read_viz_icons(fname="stop2.png"))] ) icon_files = [] -icon_files.append(('down', read_viz_icons(fname='circle-down.png'))) -icon_files.append(('left', read_viz_icons(fname='circle-left.png'))) -icon_files.append(('up', read_viz_icons(fname='circle-up.png'))) -icon_files.append(('right', read_viz_icons(fname='circle-right.png'))) +icon_files.append(("down", read_viz_icons(fname="circle-down.png"))) +icon_files.append(("left", read_viz_icons(fname="circle-left.png"))) +icon_files.append(("up", read_viz_icons(fname="circle-up.png"))) +icon_files.append(("right", read_viz_icons(fname="circle-right.png"))) second_button_example = ui.Button2D(icon_fnames=icon_files) @@ -61,7 +62,7 @@ def change_text_callback(i_ren, _obj, _button): - text.message = 'Clicked!' + text.message = "Clicked!" i_ren.force_render() @@ -78,7 +79,7 @@ def change_icon_callback(i_ren, _obj, _button): # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, title='FURY Button Example') +show_manager = window.ShowManager(size=current_size, title="FURY Button Example") show_manager.scene.add(panel) @@ -87,4 +88,4 @@ def change_icon_callback(i_ren, _obj, _button): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_button.png') +window.record(show_manager.scene, size=current_size, out_path="viz_button.png") diff --git a/docs/examples/viz_camera.py b/docs/examples/viz_camera.py index 6593a7a4a..ba67dfb2f 100644 --- a/docs/examples/viz_camera.py +++ b/docs/examples/viz_camera.py @@ -60,7 +60,7 @@ ############################################################################### # Creating "FURY" text # ==================== -fury_text = actor.vector_text('FURY', pos=(-4.3, 15, 0), scale=(2, 2, 2)) +fury_text = actor.vector_text("FURY", pos=(-4.3, 15, 0), scale=(2, 2, 2)) ############################################################################### # Creating an ``Animation`` to animate the opacity of ``fury_text`` @@ -81,7 +81,7 @@ # ================================= # -for i in range(50): +for _ in range(50): ########################################################################### # create a sphere actor that's centered at the origin and has random color # and radius. @@ -176,4 +176,4 @@ if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_animation_camera.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_camera.png", size=(900, 768)) diff --git a/docs/examples/viz_card.py b/docs/examples/viz_card.py index 61dbb5834..579a65fb9 100644 --- a/docs/examples/viz_card.py +++ b/docs/examples/viz_card.py @@ -8,6 +8,7 @@ First, some imports. """ + from fury import ui, window from fury.data import fetch_viz_icons @@ -19,27 +20,35 @@ ############################################################################### # Let's create a card and add it to the show manager -img_url = "https://raw.githubusercontent.com/fury-gl"\ - "/fury-communication-assets/main/fury-logo.png" +img_url = ( + "https://raw.githubusercontent.com/fury-gl" + "/fury-communication-assets/main/fury-logo.png" +) title = "FURY" -body = "FURY - Free Unified Rendering in pYthon."\ - "A software library for scientific visualization in Python." - -card = ui.elements.Card2D(image_path=img_url, title_text=title, - body_text=body, - image_scale=0.55, size=(300, 300), - bg_color=(1, 0.294, 0.180), - bg_opacity=0.8, border_width=5, - border_color=(0.1, 0.4, 0.4)) +body = ( + "FURY - Free Unified Rendering in pYthon." + "A software library for scientific visualization in Python." +) + +card = ui.elements.Card2D( + image_path=img_url, + title_text=title, + body_text=body, + image_scale=0.55, + size=(300, 300), + bg_color=(1, 0.294, 0.180), + bg_opacity=0.8, + border_width=5, + border_color=(0.1, 0.4, 0.4), +) ############################################################################### # Now that the card has been initialised, we add it to the show # manager. current_size = (1000, 1000) -show_manager = window.ShowManager(size=current_size, - title="FURY Card Example") +show_manager = window.ShowManager(size=current_size, title="FURY Card Example") show_manager.scene.add(card) # To interact with the UI, set interactive = True diff --git a/docs/examples/viz_card_sprite_sheet.py b/docs/examples/viz_card_sprite_sheet.py index b05620421..eb7465a5e 100644 --- a/docs/examples/viz_card_sprite_sheet.py +++ b/docs/examples/viz_card_sprite_sheet.py @@ -9,11 +9,13 @@ First, some imports. """ + import os +from tempfile import TemporaryDirectory as InTemporaryDirectory + from fury import ui, window from fury.data import fetch_viz_icons from fury.io import load_image, load_sprite_sheet, save_image -from tempfile import TemporaryDirectory as InTemporaryDirectory ############################################################################## # First we need to fetch some icons that are included in FURY. @@ -23,26 +25,40 @@ fetch_viz_icons() -sprite_sheet = load_sprite_sheet('https://i.imgur.com/0yKFTBQ.png', 5, 5) +sprite_sheet = load_sprite_sheet( + "https://raw.githubusercontent.com/fury-gl/" + "fury-data/master/unittests/fury_sprite.png", + 5, + 5, +) CURRENT_SPRITE_IDX = 0 vtk_sprites = [] ############################################################################### # Let's create a card and add it to the show manager -img_url = "https://raw.githubusercontent.com/fury-gl"\ - "/fury-communication-assets/main/fury-logo.png" +img_url = ( + "https://raw.githubusercontent.com/fury-gl" + "/fury-communication-assets/main/fury-logo.png" +) title = "FURY" -body = "FURY - Free Unified Rendering in pYthon."\ - "A software library for scientific visualization in Python." - -card = ui.elements.Card2D(image_path=img_url, title_text=title, - body_text=body, - image_scale=0.55, size=(300, 300), - bg_color=(1, 0.294, 0.180), - bg_opacity=0.8, border_width=5, - border_color=(0.1, 0.4, 0.8)) +body = ( + "FURY - Free Unified Rendering in pYthon." + "A software library for scientific visualization in Python." +) + +card = ui.elements.Card2D( + image_path=img_url, + title_text=title, + body_text=body, + image_scale=0.55, + size=(300, 300), + bg_color=(1, 0.294, 0.180), + bg_opacity=0.8, + border_width=5, + border_color=(0.1, 0.4, 0.8), +) ############################################################################### # Now we define the callback to update the image on card after some delay. @@ -53,11 +69,11 @@ def timer_callback(_obj, _evt): CURRENT_SPRITE_IDX += 1 sprite = vtk_sprites[CURRENT_SPRITE_IDX % len(vtk_sprites)] card.image.set_img(sprite) - i_ren = show_manager.scene.GetRenderWindow()\ - .GetInteractor().GetInteractorStyle() + i_ren = show_manager.scene.GetRenderWindow().GetInteractor().GetInteractorStyle() i_ren.force_render() + ############################################################################### # Lets create a function to convert the sprite to vtkImageData @@ -65,7 +81,7 @@ def timer_callback(_obj, _evt): def sprite_to_vtk(): with InTemporaryDirectory() as tdir: for idx, sprite in enumerate(list(sprite_sheet.values())): - sprite_path = os.path.join(tdir, f'{idx}.png') + sprite_path = os.path.join(tdir, f"{idx}.png") save_image(sprite, sprite_path, compression_quality=100) vtk_sprite = load_image(sprite_path, as_vtktype=True) vtk_sprites.append(vtk_sprite) @@ -76,8 +92,7 @@ def sprite_to_vtk(): # manager. current_size = (1000, 1000) -show_manager = window.ShowManager(size=current_size, - title="FURY Card Example") +show_manager = window.ShowManager(size=current_size, title="FURY Card Example") show_manager.scene.add(card) show_manager.initialize() diff --git a/docs/examples/viz_chain.py b/docs/examples/viz_chain.py index 90f5a8902..fc3502924 100644 --- a/docs/examples/viz_chain.py +++ b/docs/examples/viz_chain.py @@ -8,6 +8,7 @@ First some imports. """ + import itertools import numpy as np @@ -92,15 +93,15 @@ basePosition, baseOrientation, linkMasses=link_Masses, - linkCollisionShapeIndices=linkCollisionShapeIndices, - linkVisualShapeIndices=linkVisualShapeIndices, + linkCollisionShapeIndices=linkCollisionShapeIndices.astype(int), + linkVisualShapeIndices=linkVisualShapeIndices.astype(int), linkPositions=linkPositions, linkOrientations=linkOrientations, linkInertialFramePositions=linkInertialFramePositions, linkInertialFrameOrientations=linkInertialFrameOrns, - linkParentIndices=indices, - linkJointTypes=jointTypes, - linkJointAxis=axis, + linkParentIndices=indices.astype(int), + linkJointTypes=jointTypes.astype(int), + linkJointAxis=axis.astype(int), ) ############################################################################### @@ -163,6 +164,7 @@ ############################################################################### # We define a couple of syncing methods for the base and chain. + # Function for syncing actors with multi-bodies. def sync_actor(actor, multibody): pos, orn = p.getBasePositionAndOrientation(multibody) @@ -204,7 +206,7 @@ def sync_joints(actor_list, multibody): fpss = np.array([]) tb = ui.TextBlock2D( - position=(0, 680), font_size=30, color=(1, 0.5, 0), text='Avg. FPS: \nSim Steps: ' + position=(0, 680), font_size=30, color=(1, 0.5, 0), text="Avg. FPS: \nSim Steps: " ) scene.add(tb) @@ -227,7 +229,7 @@ def timer_callback(_obj, _event): fps = showm.frame_rate fpss = np.append(fpss, fps) tb.message = ( - 'Avg. FPS: ' + str(np.round(np.mean(fpss), 0)) + '\nSim Steps: ' + str(cnt) + "Avg. FPS: " + str(np.round(np.mean(fpss), 0)) + "\nSim Steps: " + str(cnt) ) # some trajectory @@ -262,4 +264,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, size=(900, 768), out_path='viz_chain.png') +window.record(scene, size=(900, 768), out_path="viz_chain.png") diff --git a/docs/examples/viz_check_boxes.py b/docs/examples/viz_check_boxes.py index 99c9fada1..4caed6c4b 100644 --- a/docs/examples/viz_check_boxes.py +++ b/docs/examples/viz_check_boxes.py @@ -90,9 +90,9 @@ def toggle_color(checkboxes): color_array = np.array([0, 0, 0]) for col in colors: - if col == 'Red': + if col == "Red": color_array[0] = 255 - elif col == 'Green': + elif col == "Green": color_array[1] = 255 else: color_array[2] = 255 @@ -104,26 +104,26 @@ def toggle_color(checkboxes): # We define a dictionary to store the actors with their names as keys. # A checkbox is created with actor names as it's options. -figure_dict = {'cube': cube, 'sphere': sphere, 'cone': cone, 'arrow': arrow} +figure_dict = {"cube": cube, "sphere": sphere, "cone": cone, "arrow": arrow} check_box = ui.Checkbox( list(figure_dict), list(figure_dict), padding=1, font_size=18, - font_family='Arial', + font_family="Arial", position=(400, 85), ) ############################################################################### # A similar checkbox is created for changing colors. -options = {'Blue': (0, 0, 1), 'Red': (1, 0, 0), 'Green': (0, 1, 0)} +options = {"Blue": (0, 0, 1), "Red": (1, 0, 0), "Green": (0, 1, 0)} color_toggler = ui.Checkbox( list(options), - checked_labels=['Blue'], + checked_labels=["Blue"], padding=1, font_size=16, - font_family='Arial', + font_family="Arial", position=(600, 120), ) @@ -140,7 +140,7 @@ def toggle_color(checkboxes): # manager. current_size = (1000, 1000) -show_manager = window.ShowManager(size=current_size, title='FURY Checkbox Example') +show_manager = window.ShowManager(size=current_size, title="FURY Checkbox Example") show_manager.scene.add(cube) show_manager.scene.add(sphere) @@ -161,4 +161,4 @@ def toggle_color(checkboxes): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_checkbox.png') +window.record(show_manager.scene, size=current_size, out_path="viz_checkbox.png") diff --git a/docs/examples/viz_color_interpolators.py b/docs/examples/viz_color_interpolators.py index a7347ef8b..af90f4aca 100644 --- a/docs/examples/viz_color_interpolators.py +++ b/docs/examples/viz_color_interpolators.py @@ -41,11 +41,11 @@ ############################################################################### # Static labels for different interpolators (for show) -linear_text = actor.vector_text('Linear', (-2.64, -1, 0)) -lab_text = actor.vector_text('LAB', (-0.37, -1, 0)) -hsv_text = actor.vector_text('HSV', (1.68, -1, 0)) -xyz_text = actor.vector_text('XYZ', (3.6, -1, 0)) -step_text = actor.vector_text('Step', (5.7, -1, 0)) +linear_text = actor.vector_text("Linear", (-2.64, -1, 0)) +lab_text = actor.vector_text("LAB", (-0.37, -1, 0)) +hsv_text = actor.vector_text("HSV", (1.68, -1, 0)) +xyz_text = actor.vector_text("XYZ", (3.6, -1, 0)) +step_text = actor.vector_text("Step", (5.7, -1, 0)) scene.add(step_text, lab_text, linear_text, hsv_text, xyz_text) ############################################################################### @@ -108,4 +108,4 @@ if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_animation_colors.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_colors.png", size=(900, 768)) diff --git a/docs/examples/viz_combobox.py b/docs/examples/viz_combobox.py index c6a6f5687..5b41808a9 100644 --- a/docs/examples/viz_combobox.py +++ b/docs/examples/viz_combobox.py @@ -8,6 +8,7 @@ First, some imports. """ + from fury import ui, window from fury.data import fetch_viz_icons @@ -23,9 +24,9 @@ position=(200, 300), font_size=40, color=(1, 0.5, 0), - justification='center', - vertical_justification='top', - text='FURY rocks!!!', + justification="center", + vertical_justification="top", + text="FURY rocks!!!", ) ######################################################################## @@ -33,13 +34,13 @@ # RGB values as its value. colors = { - 'Violet': (0.6, 0, 0.8), - 'Indigo': (0.3, 0, 0.5), - 'Blue': (0, 0, 1), - 'Green': (0, 1, 0), - 'Yellow': (1, 1, 0), - 'Orange': (1, 0.5, 0), - 'Red': (1, 0, 0), + "Violet": (0.6, 0, 0.8), + "Indigo": (0.3, 0, 0.5), + "Blue": (0, 0, 1), + "Green": (0, 1, 0), + "Yellow": (1, 1, 0), + "Orange": (1, 0.5, 0), + "Red": (1, 0, 0), } ######################################################################## @@ -50,7 +51,7 @@ color_combobox = ui.ComboBox2D( items=list(colors.keys()), - placeholder='Choose Text Color', + placeholder="Choose Text Color", position=(75, 50), size=(250, 150), ) @@ -77,7 +78,7 @@ def change_color(combobox): # Now we add label and combobox to the scene. current_size = (400, 400) -showm = window.ShowManager(size=current_size, title='ComboBox UI Example') +showm = window.ShowManager(size=current_size, title="ComboBox UI Example") showm.scene.add(label, color_combobox) # To interact with the UI, set interactive = True @@ -86,4 +87,4 @@ def change_color(combobox): if interactive: showm.start() -window.record(showm.scene, out_path='combobox_ui.png', size=(400, 400)) +window.record(showm.scene, out_path="combobox_ui.png", size=(400, 400)) diff --git a/docs/examples/viz_cone.py b/docs/examples/viz_cone.py index 188bafbfc..1ca7d2ca7 100644 --- a/docs/examples/viz_cone.py +++ b/docs/examples/viz_cone.py @@ -44,4 +44,4 @@ if interactive: window.show(scene, size=(600, 600)) -window.record(scene, out_path='viz_cone.png', size=(600, 600)) +window.record(scene, out_path="viz_cone.png", size=(600, 600)) diff --git a/docs/examples/viz_custom_interpolator.py b/docs/examples/viz_custom_interpolator.py index 067b45b80..1e30d70a3 100644 --- a/docs/examples/viz_custom_interpolator.py +++ b/docs/examples/viz_custom_interpolator.py @@ -5,6 +5,7 @@ Keyframe animation using custom interpolator. """ + import numpy as np from fury import actor, window @@ -66,11 +67,11 @@ def tan_cubic_spline_interpolator(keyframes): # Setting the tangent to a zero vector in this case is the best choice for time in keyframes: data = keyframes.get(time) - value = data.get('value') - if data.get('in_tangent') is None: - data['in_tangent'] = np.zeros_like(value) - if data.get('in_tangent') is None: - data['in_tangent'] = np.zeros_like(value) + value = data.get("value") + if data.get("in_tangent") is None: + data["in_tangent"] = np.zeros_like(value) + if data.get("in_tangent") is None: + data["in_tangent"] = np.zeros_like(value) def interpolate(t): # `get_previous_timestamp`and `get_next_timestamp` functions take @@ -96,10 +97,10 @@ def interpolate(t): # {'value': array(1, 1, 1), 'custom_field': array(2, 3, 1)} # # now we continue with the cubic spline equation. - p0 = keyframes.get(t0).get('value') - tan_0 = keyframes.get(t0).get('out_tangent') * time_delta - p1 = keyframes.get(t1).get('value') - tan_1 = keyframes.get(t1).get('in_tangent') * time_delta + p0 = keyframes.get(t0).get("value") + tan_0 = keyframes.get(t0).get("out_tangent") * time_delta + p1 = keyframes.get(t1).get("value") + tan_1 = keyframes.get(t1).get("in_tangent") * time_delta # cubic spline equation using tangents t2 = dt * dt t3 = t2 * dt @@ -163,4 +164,4 @@ def interpolate(t): if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_custom_interpolator.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_custom_interpolator.png", size=(900, 768)) diff --git a/docs/examples/viz_domino.py b/docs/examples/viz_domino.py index 8316c96eb..9bb54afa4 100644 --- a/docs/examples/viz_domino.py +++ b/docs/examples/viz_domino.py @@ -9,6 +9,7 @@ First some imports. """ + import itertools import numpy as np @@ -173,7 +174,7 @@ def sync_domino(object_index, multibody): fpss = np.array([]) tb = ui.TextBlock2D( - text='Avg. FPS: \nSim Steps: ', position=(0, 680), font_size=30, color=(1, 0.5, 0) + text="Avg. FPS: \nSim Steps: ", position=(0, 680), font_size=30, color=(1, 0.5, 0) ) scene.add(tb) @@ -202,7 +203,7 @@ def timer_callback(_obj, _event): fps = showm.frame_rate fpss = np.append(fpss, fps) tb.message = ( - 'Avg. FPS: ' + str(np.round(np.mean(fpss), 0)) + '\nSim Steps: ' + str(cnt) + "Avg. FPS: " + str(np.round(np.mean(fpss), 0)) + "\nSim Steps: " + str(cnt) ) # Get the position and orientation of the first domino. @@ -243,4 +244,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, out_path='viz_domino.png', size=(900, 768)) +window.record(scene, out_path="viz_domino.png", size=(900, 768)) diff --git a/docs/examples/viz_drawpanel.py b/docs/examples/viz_drawpanel.py index f872b5870..6b5295ebe 100644 --- a/docs/examples/viz_drawpanel.py +++ b/docs/examples/viz_drawpanel.py @@ -8,6 +8,7 @@ First, some imports. """ + from fury import ui, window from fury.data import fetch_viz_new_icons @@ -28,7 +29,7 @@ # Now we add DrawPanel to the scene. current_size = (650, 650) -showm = window.ShowManager(size=current_size, title='DrawPanel UI Example') +showm = window.ShowManager(size=current_size, title="DrawPanel UI Example") showm.scene.add(drawing_canvas) @@ -38,8 +39,8 @@ showm.start() else: # If the UI isn't interactive, then adding a circle to the canvas - drawing_canvas.current_mode = 'circle' - drawing_canvas.draw_shape(shape_type='circle', current_position=(275, 275)) + drawing_canvas.current_mode = "circle" + drawing_canvas.draw_shape(shape_type="circle", current_position=(275, 275)) drawing_canvas.shape_list[-1].resize((50, 50)) - window.record(showm.scene, size=current_size, out_path='viz_drawpanel.png') + window.record(showm.scene, size=current_size, out_path="viz_drawpanel.png") diff --git a/docs/examples/viz_dt_ellipsoids.py b/docs/examples/viz_dt_ellipsoids.py index d45536732..f9e7ec5ab 100644 --- a/docs/examples/viz_dt_ellipsoids.py +++ b/docs/examples/viz_dt_ellipsoids.py @@ -10,14 +10,14 @@ We start by importing the necessary modules: """ -import itertools -import numpy as np +import itertools from dipy.io.image import load_nifti +import numpy as np -from fury import window, actor, ui -from fury.actor import _fa, _color_fa +from fury import actor, ui, window +from fury.actor import _color_fa, _fa from fury.data import fetch_viz_dmri, read_viz_dmri from fury.primitive import prim_sphere @@ -32,12 +32,12 @@ # the decomposition of the diffusion tensor that describes the water diffusion # within a voxel. -slice_evecs, _ = load_nifti(read_viz_dmri('slice_evecs.nii.gz')) -slice_evals, _ = load_nifti(read_viz_dmri('slice_evals.nii.gz')) -roi_evecs, _ = load_nifti(read_viz_dmri('roi_evecs.nii.gz')) -roi_evals, _ = load_nifti(read_viz_dmri('roi_evals.nii.gz')) -whole_brain_evecs, _ = load_nifti(read_viz_dmri('whole_brain_evecs.nii.gz')) -whole_brain_evals, _ = load_nifti(read_viz_dmri('whole_brain_evals.nii.gz')) +slice_evecs, _ = load_nifti(read_viz_dmri("slice_evecs.nii.gz")) +slice_evals, _ = load_nifti(read_viz_dmri("slice_evals.nii.gz")) +roi_evecs, _ = load_nifti(read_viz_dmri("roi_evecs.nii.gz")) +roi_evals, _ = load_nifti(read_viz_dmri("roi_evals.nii.gz")) +whole_brain_evecs, _ = load_nifti(read_viz_dmri("whole_brain_evecs.nii.gz")) +whole_brain_evals, _ = load_nifti(read_viz_dmri("whole_brain_evals.nii.gz")) ############################################################################### # Using tensor_slicer actor @@ -49,13 +49,14 @@ # vertices that made up the sphere, which have a standard number of 100, 200, # and 724 vertices. -vertices, faces = prim_sphere('repulsion100', True) +vertices, faces = prim_sphere("repulsion100", True) ############################################################################### # As we need to provide a sphere object we create a class Sphere to which we # assign the values obtained from vertices and faces. + class Sphere: def __init__(self, vertices, faces): self.vertices = vertices @@ -69,8 +70,9 @@ def __init__(self, vertices, faces): # brain slice. We also define the scale so that the tensors are not so large # and overlap each other. -tensor_slice = actor.tensor_slicer(evals=slice_evals, evecs=slice_evecs, - sphere=sphere100, scale=.3) +tensor_slice = actor.tensor_slicer( + evals=slice_evals, evecs=slice_evecs, sphere=sphere100, scale=0.3 +) ############################################################################### # Next, we set up a new scene to add and visualize the tensor ellipsoids @@ -89,7 +91,7 @@ def __init__(self, vertices, faces): if interactive: showm.start() -window.record(showm.scene, size=(600, 600), out_path='tensor_slice_100.png') +window.record(showm.scene, size=(600, 600), out_path="tensor_slice_100.png") ############################################################################### # If we zoom in at the scene to see with detail the tensor ellipsoids displayed @@ -104,8 +106,12 @@ def __init__(self, vertices, faces): showm.render() showm.start() -window.record(showm.scene, out_path='tensor_slice_100_zoom.png', - size=(600, 300), reset_camera=False) +window.record( + showm.scene, + out_path="tensor_slice_100_zoom.png", + size=(600, 300), + reset_camera=False, +) ############################################################################### # To render the same tensor slice using a different sphere we redefine the @@ -128,6 +134,7 @@ def __init__(self, vertices, faces): # function to facilitate the correct setting of the parameters before passing # them to the actor. + def get_params(evecs, evals): # We define the centers which corresponds to the ellipsoids positions. valid_mask = np.abs(evecs).max(axis=(-2, -1)) > 0 @@ -159,14 +166,15 @@ def get_params(evecs, evals): # Now, we can use the ``ellipsoid`` actor to create the tensor ellipsoids as # follows. -tensors = actor.ellipsoid(centers=centers, colors=colors, axes=evecs, - lengths=evals, scales=.6) +tensors = actor.ellipsoid( + centers=centers, colors=colors, axes=evecs, lengths=evals, scales=0.6 +) showm.scene.add(tensors) if interactive: showm.start() -window.record(scene, size=(600, 600), out_path='tensor_slice_sdf.png') +window.record(scene, size=(600, 600), out_path="tensor_slice_sdf.png") ############################################################################### # Thus, one can see that the same result is obtained, however there is a @@ -184,8 +192,12 @@ def get_params(evecs, evals): showm.render() showm.start() -window.record(showm.scene, out_path='tensor_slice_sdf_zoom.png', - size=(600, 300), reset_camera=False) +window.record( + showm.scene, + out_path="tensor_slice_sdf_zoom.png", + size=(600, 300), + reset_camera=False, +) showm.scene.clear() showm.scene.pitch(-90) @@ -202,7 +214,9 @@ def get_params(evecs, evals): # We first set up the required data and create the actors. mevals = np.array([1.4, 1.0, 0.35]) * 10 ** (-3) -mevecs = np.array([[2/3, -2/3, 1/3], [1/3, 2/3, 2/3], [2/3, 1/3, -2/3]]) +mevecs = np.array( + [[2 / 3, -2 / 3, 1 / 3], [1 / 3, 2 / 3, 2 / 3], [2 / 3, 1 / 3, -2 / 3]] +) evals = np.zeros((1, 1, 1, 3)) evecs = np.zeros((1, 1, 1, 3, 3)) @@ -210,32 +224,39 @@ def get_params(evecs, evals): evals[..., :] = mevals evecs[..., :, :] = mevecs -vertices, faces = prim_sphere('repulsion200', True) +vertices, faces = prim_sphere("repulsion200", True) sphere200 = Sphere(vertices, faces) -vertices, faces = prim_sphere('repulsion724', True) +vertices, faces = prim_sphere("repulsion724", True) sphere724 = Sphere(vertices, faces) -tensor_100 = actor.tensor_slicer(evals=evals, evecs=evecs, - sphere=sphere100, scale=1.0) -tensor_200 = actor.tensor_slicer(evals=evals, evecs=evecs, - sphere=sphere200, scale=1.0) -tensor_724 = actor.tensor_slicer(evals=evals, evecs=evecs, - sphere=sphere724, scale=1.0) +tensor_100 = actor.tensor_slicer(evals=evals, evecs=evecs, sphere=sphere100, scale=1.0) +tensor_200 = actor.tensor_slicer(evals=evals, evecs=evecs, sphere=sphere200, scale=1.0) +tensor_724 = actor.tensor_slicer(evals=evals, evecs=evecs, sphere=sphere724, scale=1.0) centers, evecs, evals, colors = get_params(evecs=evecs, evals=evals) -tensor_sdf = actor.ellipsoid(centers=centers, axes=evecs, lengths=evals, - colors=colors, scales=2.0) +tensor_sdf = actor.ellipsoid( + centers=centers, axes=evecs, lengths=evals, colors=colors, scales=2.0 +) ############################################################################### # Next, we made use of `GridUI` which allows us to add the actors in a grid and # interact with them individually. objects = [tensor_100, tensor_200, tensor_724, tensor_sdf] -text = [actor.vector_text('Tensor 100'), actor.vector_text('Tensor 200'), - actor.vector_text('Tensor 724'), actor.vector_text('Tensor SDF')] - -grid_ui = ui.GridUI(actors=objects, captions=text, cell_padding=.1, - caption_offset=(-0.7, -2.5, 0), dim=(1, 4)) +text = [ + actor.vector_text("Tensor 100"), + actor.vector_text("Tensor 200"), + actor.vector_text("Tensor 724"), + actor.vector_text("Tensor SDF"), +] + +grid_ui = ui.GridUI( + actors=objects, + captions=text, + cell_padding=0.1, + caption_offset=(-0.7, -2.5, 0), + dim=(1, 4), +) scene = window.Scene() scene.background([255, 255, 255]) @@ -247,8 +268,13 @@ def get_params(evecs, evals): if interactive: showm.start() -window.record(showm.scene, size=(560, 200), out_path='tensor_comparison.png', - reset_camera=False, magnification=2) +window.record( + showm.scene, + size=(560, 200), + out_path="tensor_comparison.png", + reset_camera=False, + magnification=2, +) showm.scene.clear() @@ -259,12 +285,12 @@ def get_params(evecs, evals): # ``display_extent()``. Here we can see an example of a region of interest # (ROI) using a sphere of 100 vertices. -tensor_roi = actor.tensor_slicer(evals=roi_evals, evecs=roi_evecs, - sphere=sphere100, scale=.3) +tensor_roi = actor.tensor_slicer( + evals=roi_evals, evecs=roi_evecs, sphere=sphere100, scale=0.3 +) data_shape = roi_evals.shape[:3] -tensor_roi.display_extent( - 0, data_shape[0], 0, data_shape[1], 0, data_shape[2]) +tensor_roi.display_extent(0, data_shape[0], 0, data_shape[1], 0, data_shape[2]) showm.size = (600, 600) showm.scene.background([0, 0, 0]) @@ -274,7 +300,7 @@ def get_params(evecs, evals): if interactive: showm.start() -window.record(showm.scene, size=(600, 600), out_path='tensor_roi_100.png') +window.record(showm.scene, size=(600, 600), out_path="tensor_roi_100.png") showm.scene.clear() @@ -286,14 +312,15 @@ def get_params(evecs, evals): centers, evecs, evals, colors = get_params(roi_evecs, roi_evals) -tensors = actor.ellipsoid(centers=centers, colors=colors, axes=evecs, - lengths=evals, scales=.6) +tensors = actor.ellipsoid( + centers=centers, colors=colors, axes=evecs, lengths=evals, scales=0.6 +) showm.scene.add(tensors) if interactive: showm.start() -window.record(showm.scene, size=(600, 600), out_path='tensor_roi_sdf.png') +window.record(showm.scene, size=(600, 600), out_path="tensor_roi_sdf.png") showm.scene.clear() @@ -302,8 +329,7 @@ def get_params(evecs, evals): # the whole brain, which contains a much larger amount of data, to be exact # 184512 tensor ellipsoids are displayed at the same time. -centers, evecs, evals, colors = get_params(whole_brain_evecs, - whole_brain_evals) +centers, evecs, evals, colors = get_params(whole_brain_evecs, whole_brain_evals) # We remove all the noise around the brain to have a better visualization. fil = [len(set(elem)) != 1 for elem in evals] @@ -312,8 +338,9 @@ def get_params(evecs, evals): evecs = np.array(list(itertools.compress(evecs, fil))) evals = np.array(list(itertools.compress(evals, fil))) -tensors = actor.ellipsoid(centers=centers, colors=colors, axes=evecs, - lengths=evals, scales=.6) +tensors = actor.ellipsoid( + centers=centers, colors=colors, axes=evecs, lengths=evals, scales=0.6 +) scene = window.Scene() scene.add(tensors) @@ -323,7 +350,11 @@ def get_params(evecs, evals): if interactive: showm.start() -window.record(showm.scene, size=(600, 600), reset_camera=False, - out_path='tensor_whole_brain_sdf.png') +window.record( + showm.scene, + size=(600, 600), + reset_camera=False, + out_path="tensor_whole_brain_sdf.png", +) showm.scene.clear() diff --git a/docs/examples/viz_earth_animation.py b/docs/examples/viz_earth_animation.py index 38cd7f286..ff430a31b 100644 --- a/docs/examples/viz_earth_animation.py +++ b/docs/examples/viz_earth_animation.py @@ -30,7 +30,7 @@ # image. fetch_viz_textures() -earth_filename = read_viz_textures('1_earth_8k.jpg') +earth_filename = read_viz_textures("1_earth_8k.jpg") earth_image = io.load_image(earth_filename) ############################################################################## @@ -42,7 +42,7 @@ ############################################################################## # Then, do the same for the moon. -moon_filename = read_viz_textures('moon-8k.jpg') +moon_filename = read_viz_textures("moon-8k.jpg") moon_image = io.load_image(moon_filename) moon_actor = actor.texture_on_sphere(moon_image) @@ -100,7 +100,7 @@ # Also creating a text actor to add below the sphere. text_actor = actor.text_3d( - 'Bloomington, Indiana', (-0.42, 0.31, 0.03), window.colors.white, 0.004 + "Bloomington, Indiana", (-0.42, 0.31, 0.03), window.colors.white, 0.004 ) utils.rotate(text_actor, (-90, 0, 1, 0)) @@ -108,7 +108,7 @@ # Let's also import a model of a satellite to visualize circling the moon. fetch_viz_models() -satellite_filename = read_viz_models('satellite_obj.obj') +satellite_filename = read_viz_models("satellite_obj.obj") satellite = io.load_polydata(satellite_filename) satellite_actor = utils.get_actor_from_polydata(satellite) @@ -172,4 +172,4 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 35, timer_callback) showm.start() -window.record(showm.scene, size=(900, 768), out_path='viz_earth_animation.png') +window.record(showm.scene, size=(900, 768), out_path="viz_earth_animation.png") diff --git a/docs/examples/viz_earth_coordinates.py b/docs/examples/viz_earth_coordinates.py index 48232a3c0..3c3c10eec 100644 --- a/docs/examples/viz_earth_coordinates.py +++ b/docs/examples/viz_earth_coordinates.py @@ -23,7 +23,7 @@ scene = window.Scene() fetch_viz_textures() -earth_file = read_viz_textures('1_earth_16k.jpg') +earth_file = read_viz_textures("1_earth_16k.jpg") earth_image = io.load_image(earth_file) earth_actor = actor.texture_on_sphere(earth_image) scene.add(earth_actor) @@ -81,19 +81,19 @@ def latlong_coordinates(lat, lon): # geographical coordinates. nyc_actor = actor.text_3d( - 'New York City, New York\n40.7128° N, 74.0060° W', + "New York City, New York\n40.7128° N, 74.0060° W", (locationone[0] - 0.04, locationone[1], locationone[2] + 0.07), window.colors.white, 0.01, ) paris_actor = actor.text_3d( - 'Paris, France\n48.8566° N, 2.3522° E', + "Paris, France\n48.8566° N, 2.3522° E", (locationthree[0] - 0.04, locationthree[1], locationthree[2] - 0.07), window.colors.white, 0.01, ) beijing_actor = actor.text_3d( - 'Beijing, China\n39.9042° N, 116.4074° E', + "Beijing, China\n39.9042° N, 116.4074° E", (locationtwo[0] - 0.06, locationtwo[1], locationtwo[2] - 0.07), window.colors.white, 0.01, @@ -157,4 +157,4 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 25, timer_callback) showm.start() -window.record(showm.scene, size=(900, 768), out_path='viz_earth_coordinates.png') +window.record(showm.scene, size=(900, 768), out_path="viz_earth_coordinates.png") diff --git a/docs/examples/viz_emwave_animation.py b/docs/examples/viz_emwave_animation.py index 52beb9735..163e0d8d2 100644 --- a/docs/examples/viz_emwave_animation.py +++ b/docs/examples/viz_emwave_animation.py @@ -94,14 +94,14 @@ def update_coordinates(wavenumber, ang_frq, time, phase_angle): y = np.sin(wavenumber * x - angular_frq * time + phase_angle) z = np.array([0 for i in range(npoints)]) -pts = np.array([(a, b, c) for (a, b, c) in zip(x, y, z)]) +pts = np.array(list(zip(x, y, z))) pts = [pts] colors = window.colors.red wave_actor1 = actor.line(pts, colors, linewidth=3) scene.add(wave_actor1) vertices = utils.vertices_from_actor(wave_actor1) -vcolors = utils.colors_from_actor(wave_actor1, 'colors') +vcolors = utils.colors_from_actor(wave_actor1, "colors") no_vertices_per_point = len(vertices) / npoints initial_vertices = vertices.copy() - np.repeat(pts, no_vertices_per_point, axis=0) @@ -113,14 +113,14 @@ def update_coordinates(wavenumber, ang_frq, time, phase_angle): yy = np.array([0 for i in range(npoints)]) zz = np.sin(wavenumber * xx - angular_frq * time + phase_angle) -pts2 = np.array([(a, b, c) for (a, b, c) in zip(xx, yy, zz)]) +pts2 = np.array(list(zip(xx, yy, zz))) pts2 = [pts2] colors2 = window.colors.blue wave_actor2 = actor.line(pts2, colors2, linewidth=3) scene.add(wave_actor2) vertices2 = utils.vertices_from_actor(wave_actor2) -vcolors2 = utils.colors_from_actor(wave_actor2, 'colors') +vcolors2 = utils.colors_from_actor(wave_actor2, "colors") no_vertices_per_point2 = len(vertices2) / npoints initial_vertices2 = vertices2.copy() - np.repeat(pts2, no_vertices_per_point2, axis=0) @@ -129,7 +129,7 @@ def update_coordinates(wavenumber, ang_frq, time, phase_angle): # Initializing text box to display the title of the animation tb = ui.TextBlock2D(bold=True, position=(160, 90)) -tb.message = 'Electromagnetic Wave' +tb.message = "Electromagnetic Wave" scene.add(tb) ############################################################################### @@ -154,12 +154,12 @@ def timer_callback(_obj, _event): cnt = next(counter) x, y, z = update_coordinates(wavenumber, angular_frq, phase_angle, time) - pts = np.array([(a, b, c) for (a, b, c) in zip(x, y, z)]) + pts = np.array(list(zip(x, y, z))) vertices[:] = initial_vertices + np.repeat(pts, no_vertices_per_point, axis=0) utils.update_actor(wave_actor1) xx, zz, yy = update_coordinates(wavenumber, angular_frq, phase_angle, time) - pts2 = np.array([(a, b, c) for (a, b, c) in zip(xx, yy, zz)]) + pts2 = np.array(list(zip(xx, yy, zz))) vertices2[:] = initial_vertices2 + np.repeat(pts2, no_vertices_per_point2, axis=0) utils.update_actor(wave_actor2) @@ -178,4 +178,4 @@ def timer_callback(_obj, _event): interactive = False if interactive: showm.start() -window.record(showm.scene, size=(800, 600), out_path='viz_emwave.png') +window.record(showm.scene, size=(800, 600), out_path="viz_emwave.png") diff --git a/docs/examples/viz_fiber_odf.py b/docs/examples/viz_fiber_odf.py index de50c413e..ac327aa7f 100644 --- a/docs/examples/viz_fiber_odf.py +++ b/docs/examples/viz_fiber_odf.py @@ -7,12 +7,12 @@ orientation distribution functions (ODF) using fury's odf_slicer. """ +from dipy.data import get_sphere +from dipy.reconst.shm import sh_to_sf_matrix import nibabel as nib # First, we import some useful modules and methods. import numpy as np -from dipy.data import get_sphere -from dipy.reconst.shm import sh_to_sf_matrix from fury import actor, ui, window from fury.data import fetch_viz_dmri, fetch_viz_icons, read_viz_dmri @@ -24,7 +24,7 @@ fetch_viz_dmri() fetch_viz_icons() -fodf_img = nib.load(read_viz_dmri('fodf.nii.gz')) +fodf_img = nib.load(read_viz_dmri("fodf.nii.gz")) sh = fodf_img.get_fdata() affine = fodf_img.affine grid_shape = sh.shape[:-1] @@ -33,7 +33,7 @@ # We then define a low resolution sphere used to visualize SH coefficients # as spherical functions (SF) as well as a matrix `B_low` to project SH # onto the sphere. -sphere_low = get_sphere('repulsion100') +sphere_low = get_sphere("repulsion100") B_low = sh_to_sf_matrix(sphere_low, 8, return_inv=False) ############################################################################### @@ -111,7 +111,7 @@ min_value=0, max_value=grid_shape[2] - 1, initial_value=grid_shape[2] / 2, - text_template='{value:.0f}', + text_template="{value:.0f}", length=140, ) @@ -119,7 +119,7 @@ min_value=0, max_value=grid_shape[1] - 1, initial_value=grid_shape[1] / 2, - text_template='{value:.0f}', + text_template="{value:.0f}", length=140, ) @@ -127,14 +127,14 @@ min_value=0, max_value=grid_shape[0] - 1, initial_value=grid_shape[0] / 2, - text_template='{value:.0f}', + text_template="{value:.0f}", length=140, ) ############################################################################### # We also define a high resolution sphere to demonstrate the capability to # dynamically change the sphere used for SH to SF projection. -sphere_high = get_sphere('symmetric362') +sphere_high = get_sphere("symmetric362") # We fix the order of the faces' three vertices to a clockwise winding. This # ensures all faces have a normal going away from the center of the sphere. @@ -144,8 +144,8 @@ ############################################################################### # We add a combobox for choosing the sphere resolution during execution. sphere_dict = { - 'Low resolution': (sphere_low, B_low), - 'High resolution': (sphere_high, B_high), + "Low resolution": (sphere_low, B_low), + "High resolution": (sphere_high, B_high), } combobox = ui.ComboBox2D(items=list(sphere_dict)) scene.add(combobox) @@ -161,12 +161,12 @@ def change_slice_z(slider): def change_slice_y(slider): i = int(np.round(slider.value)) - odf_actor_y.slice_along_axis(i, 'yaxis') + odf_actor_y.slice_along_axis(i, "yaxis") def change_slice_x(slider): i = int(np.round(slider.value)) - odf_actor_x.slice_along_axis(i, 'xaxis') + odf_actor_x.slice_along_axis(i, "xaxis") def change_sphere(combobox): @@ -189,8 +189,8 @@ def build_label(text): label = ui.TextBlock2D() label.message = text label.font_size = 18 - label.font_family = 'Arial' - label.justification = 'left' + label.font_family = "Arial" + label.justification = "left" label.bold = False label.italic = False label.shadow = False @@ -200,11 +200,11 @@ def build_label(text): return label -line_slider_label_z = build_label(text='Z Slice') -line_slider_label_y = build_label(text='Y Slice') -line_slider_label_x = build_label(text='X Slice') +line_slider_label_z = build_label(text="Z Slice") +line_slider_label_y = build_label(text="Y Slice") +line_slider_label_x = build_label(text="X Slice") -panel = ui.Panel2D(size=(300, 200), color=(1, 1, 1), opacity=0.1, align='right') +panel = ui.Panel2D(size=(300, 200), color=(1, 1, 1), opacity=0.1, align="right") panel.center = (1030, 120) panel.add_element(line_slider_label_x, (0.1, 0.75)) @@ -246,7 +246,7 @@ def win_callback(obj, _event): show_m.start() else: window.record( - scene, out_path='odf_slicer_3D.png', size=(1200, 900), reset_camera=False + scene, out_path="odf_slicer_3D.png", size=(1200, 900), reset_camera=False ) del show_m diff --git a/docs/examples/viz_fine_tuning_gl_context.py b/docs/examples/viz_fine_tuning_gl_context.py index 8b3dec517..2f11ee588 100644 --- a/docs/examples/viz_fine_tuning_gl_context.py +++ b/docs/examples/viz_fine_tuning_gl_context.py @@ -29,21 +29,21 @@ actor_no_depth_test = actor.markers( centers, - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, ) actor_normal_blending = actor.markers( centers - np.array([[0, -0.5, 0]]), - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, ) actor_add_blending = actor.markers( centers - np.array([[0, -1, 0]]), - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, @@ -51,14 +51,14 @@ actor_sub_blending = actor.markers( centers - np.array([[0, -1.5, 0]]), - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, ) actor_mul_blending = actor.markers( centers - np.array([[0, -2, 0]]), - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, @@ -150,4 +150,4 @@ def timer_callback(obj, event): if interactive: showm.start() -window.record(scene, out_path='viz_fine_tuning_gl_context.png', size=(600, 600)) +window.record(scene, out_path="viz_fine_tuning_gl_context.png", size=(600, 600)) diff --git a/docs/examples/viz_fractals.py b/docs/examples/viz_fractals.py index 4c5c40113..acbd3061e 100644 --- a/docs/examples/viz_fractals.py +++ b/docs/examples/viz_fractals.py @@ -221,7 +221,7 @@ def gen_centers(depth, pos, center, side): # the Scene and ShowManager. scene = window.Scene() -showmgr = window.ShowManager(scene, 'Fractals', (800, 800), reset_camera=True) +showmgr = window.ShowManager(scene, "Fractals", (800, 800), reset_camera=True) ############################################################################### # These values are what work nicely on my machine without lagging. If you have @@ -235,16 +235,16 @@ def gen_centers(depth, pos, center, side): # fractals and add the selected one. This also resets the camera. options = { - 'Tetrix': 0, - 'Sponge': 1, - 'Snowflake': 2, + "Tetrix": 0, + "Sponge": 1, + "Snowflake": 2, } shape_chooser = ui.RadioButton( options.keys(), padding=10, font_size=16, - checked_labels=['Tetrix'], + checked_labels=["Tetrix"], position=(10, 10), ) @@ -289,4 +289,4 @@ def timer_callback(_obj, _event): if interactive: showmgr.start() else: - window.record(showmgr.scene, out_path='fractals.png', size=(800, 800)) + window.record(showmgr.scene, out_path="fractals.png", size=(800, 800)) diff --git a/docs/examples/viz_gltf.py b/docs/examples/viz_gltf.py index fd64cacad..8d1c50ca4 100644 --- a/docs/examples/viz_gltf.py +++ b/docs/examples/viz_gltf.py @@ -17,8 +17,8 @@ ############################################################################## # Retrieving the gltf model. -fetch_gltf('Duck', 'glTF') -filename = read_viz_gltf('Duck') +fetch_gltf("Duck", "glTF") +filename = read_viz_gltf("Duck") ############################################################################## # Initialize the glTF object and get actors using `actors` method. @@ -46,4 +46,4 @@ if interactive: window.show(scene, size=(1280, 720)) -window.record(scene, out_path='viz_gltf.png', size=(1280, 720)) +window.record(scene, out_path="viz_gltf.png", size=(1280, 720)) diff --git a/docs/examples/viz_gltf_animated.py b/docs/examples/viz_gltf_animated.py index 18fdc8b41..a7bf84ced 100644 --- a/docs/examples/viz_gltf_animated.py +++ b/docs/examples/viz_gltf_animated.py @@ -23,8 +23,8 @@ ############################################################################## # Retrieving the gltf model. -fetch_gltf('InterpolationTest', 'glTF') -filename = read_viz_gltf('InterpolationTest') +fetch_gltf("InterpolationTest", "glTF") +filename = read_viz_gltf("InterpolationTest") ############################################################################## # Initialize the glTF object and get actors using `actors` method. @@ -54,4 +54,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, out_path='viz_gltf_animated.png', size=(900, 768)) +window.record(scene, out_path="viz_gltf_animated.png", size=(900, 768)) diff --git a/docs/examples/viz_gltf_export.py b/docs/examples/viz_gltf_export.py index 6595c91cf..9d54a2f8a 100644 --- a/docs/examples/viz_gltf_export.py +++ b/docs/examples/viz_gltf_export.py @@ -4,6 +4,7 @@ ============================== In this tutorial, we will show how to create a glTF file for a scene. """ + import numpy as np from fury import actor, gltf, window @@ -30,8 +31,8 @@ sphere = actor.sphere(np.add(centers, np.array([0, 2, 0])), colors=colors) scene.add(sphere) -fetch_gltf('BoxTextured', 'glTF') -filename = read_viz_gltf('BoxTextured') +fetch_gltf("BoxTextured", "glTF") +filename = read_viz_gltf("BoxTextured") gltf_obj = gltf.glTF(filename) box_actor = gltf_obj.actors() scene.add(box_actor[0]) @@ -46,12 +47,12 @@ ############################################################################## # Exporting scene as a glTF file -gltf.export_scene(scene, filename='viz_gltf_export.gltf') +gltf.export_scene(scene, filename="viz_gltf_export.gltf") ############################################################################## # Reading the newly created glTF file and get actors. -gltf_obj = gltf.glTF('viz_gltf_export.gltf') +gltf_obj = gltf.glTF("viz_gltf_export.gltf") actors = gltf_obj.actors() ############################################################################## @@ -64,4 +65,4 @@ if interactive: window.show(scene, size=(1280, 720)) -window.record(scene, out_path='viz_gltf_export.png', size=(1280, 720)) +window.record(scene, out_path="viz_gltf_export.png", size=(1280, 720)) diff --git a/docs/examples/viz_helical_motion.py b/docs/examples/viz_helical_motion.py index 891bffeb0..b17f319ff 100644 --- a/docs/examples/viz_helical_motion.py +++ b/docs/examples/viz_helical_motion.py @@ -96,7 +96,7 @@ scene.add(charge_actor) vertices = utils.vertices_from_actor(charge_actor) -vcolors = utils.colors_from_actor(charge_actor, 'colors') +vcolors = utils.colors_from_actor(charge_actor, "colors") no_vertices_per_point = len(vertices) initial_vertices = vertices.copy() - np.repeat(pts, no_vertices_per_point, axis=0) @@ -105,8 +105,8 @@ # Initializing text box to display the name of the animation tb = ui.TextBlock2D(bold=True, position=(100, 90)) -m1 = 'Motion of a charged particle in a ' -m2 = 'combined electric and magnetic field' +m1 = "Motion of a charged particle in a " +m2 = "combined electric and magnetic field" tb.message = m1 + m2 scene.add(tb) @@ -166,4 +166,4 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 15, timer_callback) showm.start() -window.record(showm.scene, size=(800, 600), out_path='viz_helical_motion.png') +window.record(showm.scene, size=(800, 600), out_path="viz_helical_motion.png") diff --git a/docs/examples/viz_hierarchical_animation.py b/docs/examples/viz_hierarchical_animation.py index 35c9b454d..8b727e708 100644 --- a/docs/examples/viz_hierarchical_animation.py +++ b/docs/examples/viz_hierarchical_animation.py @@ -5,6 +5,7 @@ Creating hierarchical keyframes animation in fury """ + import numpy as np from fury import actor, window @@ -140,5 +141,5 @@ showm.start() window.record( - scene, out_path='viz_keyframe_hierarchical_animation.png', size=(900, 768) + scene, out_path="viz_keyframe_hierarchical_animation.png", size=(900, 768) ) diff --git a/docs/examples/viz_interaction.py b/docs/examples/viz_interaction.py index 775fccb8b..7becb5964 100644 --- a/docs/examples/viz_interaction.py +++ b/docs/examples/viz_interaction.py @@ -18,7 +18,6 @@ Notes ----- - If you don't have ffmpeg installed, you need to install it to use WebRTC Linux @@ -29,6 +28,7 @@ OS X `brew install ffmpeg opus libvpx pkg-config` + """ import multiprocessing @@ -44,7 +44,7 @@ # multiprocessing.set_start_method('spawn') from fury.stream.server.main import WEBRTC_AVAILABLE, web_server, web_server_raw_array -if __name__ == '__main__': +if __name__ == "__main__": interactive = False # `use_raw_array` is a flag to tell the server to use python RawArray # instead of SharedMemory which is a new feature in python 3.8 @@ -100,7 +100,7 @@ stream_interaction.circular_queue.head_tail_buffer, stream_interaction.circular_queue.buffer._buffer, 8000, - 'localhost', + "localhost", True, WEBRTC_AVAILABLE, ), @@ -115,7 +115,7 @@ stream_interaction.circular_queue.head_tail_buffer_name, stream_interaction.circular_queue.buffer.buffer_name, 8000, - 'localhost', + "localhost", True, WEBRTC_AVAILABLE, ), @@ -143,4 +143,4 @@ stream.cleanup() stream_interaction.cleanup() - window.record(showm.scene, size=window_size, out_path='viz_interaction.png') + window.record(showm.scene, size=window_size, out_path="viz_interaction.png") diff --git a/docs/examples/viz_interpolators.py b/docs/examples/viz_interpolators.py index 6a63c112b..d6793bb18 100644 --- a/docs/examples/viz_interpolators.py +++ b/docs/examples/viz_interpolators.py @@ -20,10 +20,10 @@ from fury.animation.interpolator import cubic_spline_interpolator keyframes = { - 1.0: {'value': np.array([0, 0, 0])}, - 2.0: {'value': np.array([-4, 1, 0])}, - 5.0: {'value': np.array([0, 0, 12])}, - 6.0: {'value': np.array([25, 0, 12])}, + 1.0: {"value": np.array([0, 0, 0])}, + 2.0: {"value": np.array([-4, 1, 0])}, + 5.0: {"value": np.array([0, 0, 12])}, + 6.0: {"value": np.array([25, 0, 12])}, } ############################################################################### @@ -133,4 +133,4 @@ if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_interpolator.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_interpolator.png", size=(900, 768)) diff --git a/docs/examples/viz_introduction.py b/docs/examples/viz_introduction.py index 86b5fa3d4..f3b2673bf 100644 --- a/docs/examples/viz_introduction.py +++ b/docs/examples/viz_introduction.py @@ -55,7 +55,6 @@ # For this tutorial, we are going to use the FURY animation module to translate # FURY sphere actor. - import numpy as np from fury import actor, window @@ -114,5 +113,5 @@ showm.start() window.record( - scene, out_path='viz_keyframe_animation_introduction.png', size=(900, 768) + scene, out_path="viz_keyframe_animation_introduction.png", size=(900, 768) ) diff --git a/docs/examples/viz_layout.py b/docs/examples/viz_layout.py index 98270c66e..c7fd19e92 100644 --- a/docs/examples/viz_layout.py +++ b/docs/examples/viz_layout.py @@ -24,22 +24,22 @@ ############################################################################### # Now we create two listboxes -listbox_1 = ui.ListBox2D(size=(150, 150), values=['First', 'Second', 'Third']) +listbox_1 = ui.ListBox2D(size=(150, 150), values=["First", "Second", "Third"]) -listbox_2 = ui.ListBox2D(size=(250, 250), values=['First', 'Second', 'Third']) +listbox_2 = ui.ListBox2D(size=(250, 250), values=["First", "Second", "Third"]) ############################################################################### # Now we create two different UI i.e. a slider and a listbox slider = ui.LineSlider2D(length=150) -listbox = ui.ListBox2D(size=(150, 150), values=['First', 'Second', 'Third']) +listbox = ui.ListBox2D(size=(150, 150), values=["First", "Second", "Third"]) ############################################################################### # Now, we create grids with different shapes rect_grid = GridLayout(position_offset=(0, 0, 0)) -square_grid = GridLayout(cell_shape='square', position_offset=(0, 300, 0)) -diagonal_grid = GridLayout(cell_shape='diagonal', position_offset=(0, 600, 0)) +square_grid = GridLayout(cell_shape="square", position_offset=(0, 300, 0)) +diagonal_grid = GridLayout(cell_shape="diagonal", position_offset=(0, 600, 0)) ############################################################################### @@ -50,7 +50,7 @@ diagonal_grid.apply([slider, listbox]) current_size = (1500, 1500) -show_manager = window.ShowManager(size=current_size, title='FURY UI Layout') +show_manager = window.ShowManager(size=current_size, title="FURY UI Layout") show_manager.scene.add(panel_1, panel_2, listbox_1, listbox_2, slider, listbox) @@ -60,4 +60,4 @@ if interactive: show_manager.start() -window.record(show_manager.scene, out_path='ui_layout.png', size=(400, 400)) +window.record(show_manager.scene, out_path="ui_layout.png", size=(400, 400)) diff --git a/docs/examples/viz_markers.py b/docs/examples/viz_markers.py index 6f2849569..600296c2d 100644 --- a/docs/examples/viz_markers.py +++ b/docs/examples/viz_markers.py @@ -5,6 +5,7 @@ This example shows how to use the marker actor. """ + import numpy as np from fury import actor, window @@ -15,7 +16,7 @@ # There are nine types 2d markers: circle, square, diamond, triangle, pentagon, # hexagon, heptagon, cross and plus. -marker_symbols = ['o', 's', 'd', '^', 'p', 'h', 's6', 'x', '+'] +marker_symbols = ["o", "s", "d", "^", "p", "h", "s6", "x", "+"] markers = [np.random.choice(marker_symbols) for i in range(n)] centers = np.random.normal(size=(n, 3), scale=10) @@ -39,7 +40,7 @@ nodes_3d_actor = actor.markers( centers + np.ones_like(centers) * 25, - marker='3d', + marker="3d", colors=colors, scales=0.5, ) @@ -54,4 +55,4 @@ if interactive: window.show(scene, size=(600, 600)) -window.record(scene, out_path='viz_markers.png', size=(600, 600)) +window.record(scene, out_path="viz_markers.png", size=(600, 600)) diff --git a/docs/examples/viz_morphing.py b/docs/examples/viz_morphing.py index 02f4e931f..9bf996759 100644 --- a/docs/examples/viz_morphing.py +++ b/docs/examples/viz_morphing.py @@ -13,8 +13,8 @@ # Retrieving the model with morphing in it (look at Khronoos samples). # We're choosing the `MorphStressTest` model here. -fetch_gltf('MorphStressTest', 'glTF') -filename = read_viz_gltf('MorphStressTest') +fetch_gltf("MorphStressTest", "glTF") +filename = read_viz_gltf("MorphStressTest") ############################################################################## # Initializing the glTF object, You can additionally set `apply_normals=True`. @@ -27,7 +27,7 @@ # name you want to visualize. # Note: If there's no name for animation, It's stored as `anim_0`, `anim_1` etc -animation = gltf_obj.morph_animation()['TheWave'] +animation = gltf_obj.morph_animation()["TheWave"] ############################################################################## # Call the `update_morph` method once, This moves initialise the morphing at @@ -71,4 +71,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, out_path='viz_morphing.png', size=(900, 768)) +window.record(scene, out_path="viz_morphing.png", size=(900, 768)) diff --git a/docs/examples/viz_multithread.py b/docs/examples/viz_multithread.py index a68e3ec37..33060f75f 100644 --- a/docs/examples/viz_multithread.py +++ b/docs/examples/viz_multithread.py @@ -10,8 +10,8 @@ adds and removes elements from the scene. """ -import time from threading import Thread +import time import numpy as np @@ -44,16 +44,16 @@ # Create a function to print a counter to the console def print_counter(): - print('') + print("") for i in range(100): - print('\rCounter: %d' % i, end='') + print("\rCounter: %d" % i, end="") message = "Let's count up to 100 and exit :" + str(i + 1) tb.message = message time.sleep(0.05) if showm.is_done(): break showm.exit() - print('') + print("") # Create a function to rotate the camera diff --git a/docs/examples/viz_network.py b/docs/examples/viz_network.py index 5ac3cc05b..13140a96f 100644 --- a/docs/examples/viz_network.py +++ b/docs/examples/viz_network.py @@ -15,9 +15,7 @@ import numpy as np -from fury import actor -from fury import colormap as cmap -from fury import window +from fury import actor, colormap as cmap, window from fury.data import fetch_viz_wiki_nw ############################################################################### @@ -102,7 +100,7 @@ if interactive: window.show(scene, size=(600, 600)) -window.record(scene, out_path='journal_networks.png', size=(600, 600)) +window.record(scene, out_path="journal_networks.png", size=(600, 600)) ############################################################################### # This example can be improved by adding some interactivy with slider, diff --git a/docs/examples/viz_network_animated.py b/docs/examples/viz_network_animated.py index cbdd5c179..d68a19e4c 100644 --- a/docs/examples/viz_network_animated.py +++ b/docs/examples/viz_network_animated.py @@ -18,9 +18,7 @@ import numpy as np -from fury import actor -from fury import colormap as cmap -from fury import window +from fury import actor, colormap as cmap, window from fury.utils import compute_bounds, update_actor, vertices_from_actor ############################################################################### @@ -257,4 +255,4 @@ def _timer(_obj, _event): showm.start() -window.record(showm.scene, size=(900, 768), out_path='viz_animated_networks.png') +window.record(showm.scene, size=(900, 768), out_path="viz_animated_networks.png") diff --git a/docs/examples/viz_no_interaction.py b/docs/examples/viz_no_interaction.py index 56862bbda..d68b70076 100644 --- a/docs/examples/viz_no_interaction.py +++ b/docs/examples/viz_no_interaction.py @@ -12,15 +12,12 @@ # multiprocessing.set_start_method('spawn') import numpy as np -from fury import actor -from fury import colormap as cmap -from fury import window +from fury import actor, colormap as cmap, window from fury.data.fetcher import fetch_viz_wiki_nw from fury.stream.client import FuryStreamClient from fury.stream.server.main import web_server_raw_array -if __name__ == '__main__': - +if __name__ == "__main__": interactive = False ########################################################################### # First we will set the resolution which it'll be used by the streamer @@ -55,7 +52,7 @@ sphere_actor = actor.sdf( centers=positions, colors=colors, - primitives='sphere', + primitives="sphere", scales=radii * 0.5, ) @@ -103,4 +100,4 @@ stream.stop() stream.cleanup() - window.record(showm.scene, size=window_size, out_path='viz_no_interaction.png') + window.record(showm.scene, size=window_size, out_path="viz_no_interaction.png") diff --git a/docs/examples/viz_pbr_interactive.py b/docs/examples/viz_pbr_interactive.py index 2bee8e0e7..3238e8ac5 100644 --- a/docs/examples/viz_pbr_interactive.py +++ b/docs/examples/viz_pbr_interactive.py @@ -106,7 +106,7 @@ def win_callback(obj, event): # The following function returns the full path of the 6 images composing the # skybox. -textures = read_viz_cubemap('skybox') +textures = read_viz_cubemap("skybox") ############################################################################### # Now that we have the location of the textures, let's load them and create a @@ -166,32 +166,32 @@ def win_callback(obj, event): # We will create one single panel with all of our labels and sliders. control_panel = ui.Panel2D( - (400, 500), position=(5, 5), color=(0.25, 0.25, 0.25), opacity=0.75, align='right' + (400, 500), position=(5, 5), color=(0.25, 0.25, 0.25), opacity=0.75, align="right" ) ############################################################################### # By using our previously defined function, we can easily create all the labels # we need for this demo. And then add them to the panel. -slider_label_metallic = ui.TextBlock2D(text='Metallic', font_size=16) -slider_label_roughness = ui.TextBlock2D(text='Roughness', font_size=16) -slider_label_anisotropy = ui.TextBlock2D(text='Anisotropy', font_size=16) +slider_label_metallic = ui.TextBlock2D(text="Metallic", font_size=16) +slider_label_roughness = ui.TextBlock2D(text="Roughness", font_size=16) +slider_label_anisotropy = ui.TextBlock2D(text="Anisotropy", font_size=16) slider_label_anisotropy_rotation = ui.TextBlock2D( - text='Anisotropy Rotation', font_size=16 + text="Anisotropy Rotation", font_size=16 ) slider_label_anisotropy_direction_x = ui.TextBlock2D( - text='Anisotropy Direction X', font_size=16 + text="Anisotropy Direction X", font_size=16 ) slider_label_anisotropy_direction_y = ui.TextBlock2D( - text='Anisotropy Direction Y', font_size=16 + text="Anisotropy Direction Y", font_size=16 ) slider_label_anisotropy_direction_z = ui.TextBlock2D( - text='Anisotropy Direction Z', font_size=16 + text="Anisotropy Direction Z", font_size=16 ) -slider_label_coat_strength = ui.TextBlock2D(text='Coat Strength', font_size=16) -slider_label_coat_roughness = ui.TextBlock2D(text='Coat Roughness', font_size=16) -slider_label_base_ior = ui.TextBlock2D(text='Base IoR', font_size=16) -slider_label_coat_ior = ui.TextBlock2D(text='Coat IoR', font_size=16) +slider_label_coat_strength = ui.TextBlock2D(text="Coat Strength", font_size=16) +slider_label_coat_roughness = ui.TextBlock2D(text="Coat Roughness", font_size=16) +slider_label_base_ior = ui.TextBlock2D(text="Base IoR", font_size=16) +slider_label_coat_ior = ui.TextBlock2D(text="Coat IoR", font_size=16) control_panel.add_element(slider_label_metallic, (0.01, 0.95)) control_panel.add_element(slider_label_roughness, (0.01, 0.86)) @@ -212,37 +212,37 @@ def win_callback(obj, event): initial_value=pbr_params.metallic, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_roughness = ui.LineSlider2D( initial_value=pbr_params.roughness, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_anisotropy = ui.LineSlider2D( initial_value=pbr_params.anisotropy, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_anisotropy_rotation = ui.LineSlider2D( initial_value=pbr_params.anisotropy_rotation, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_coat_strength = ui.LineSlider2D( initial_value=pbr_params.coat_strength, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_coat_roughness = ui.LineSlider2D( initial_value=pbr_params.coat_roughness, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) ############################################################################### @@ -255,21 +255,21 @@ def win_callback(obj, event): min_value=-1, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_anisotropy_direction_y = ui.LineSlider2D( initial_value=doa[1], min_value=-1, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) slider_slice_anisotropy_direction_z = ui.LineSlider2D( initial_value=doa[2], min_value=-1, max_value=1, length=195, - text_template='{value:.1f}', + text_template="{value:.1f}", ) ############################################################################### @@ -282,14 +282,14 @@ def win_callback(obj, event): min_value=1, max_value=2.3, length=195, - text_template='{value:.02f}', + text_template="{value:.02f}", ) slider_slice_coat_ior = ui.LineSlider2D( initial_value=pbr_params.coat_ior, min_value=1, max_value=2.3, length=195, - text_template='{value:.02f}', + text_template="{value:.02f}", ) ############################################################################### @@ -343,4 +343,4 @@ def win_callback(obj, event): if interactive: show_m.start() -window.record(scene, size=(1920, 1080), out_path='viz_pbr_interactive.png') +window.record(scene, size=(1920, 1080), out_path="viz_pbr_interactive.png") diff --git a/docs/examples/viz_pbr_spheres.py b/docs/examples/viz_pbr_spheres.py index 05f401b03..318fcc51e 100644 --- a/docs/examples/viz_pbr_spheres.py +++ b/docs/examples/viz_pbr_spheres.py @@ -35,15 +35,15 @@ # These subset of parameters have their values constrained in the 0 to 1 range. material_params = [ - [[1, 1, 0], {'metallic': 0, 'roughness': 0}], - [(0, 0, 1), {'roughness': 0}], - [(1, 0, 1), {'anisotropy': 0, 'metallic': 0.25, 'roughness': 0.5}], + [[1, 1, 0], {"metallic": 0, "roughness": 0}], + [(0, 0, 1), {"roughness": 0}], + [(1, 0, 1), {"anisotropy": 0, "metallic": 0.25, "roughness": 0.5}], [ (1, 0, 1), - {'anisotropy_rotation': 0, 'anisotropy': 1, 'metallic': 0.25, 'roughness': 0.5}, + {"anisotropy_rotation": 0, "anisotropy": 1, "metallic": 0.25, "roughness": 0.5}, ], - [(0, 1, 1), {'coat_strength': 0, 'roughness': 0}], - [(0, 1, 1), {'coat_roughness': 0, 'coat_strength': 1, 'roughness': 0}], + [(0, 1, 1), {"coat_strength": 0, "roughness": 0}], + [(0, 1, 1), {"coat_roughness": 0, "coat_strength": 1, "roughness": 0}], ] ############################################################################### @@ -73,17 +73,17 @@ # visualization. labels = [ - 'Metallic', - 'Roughness', - 'Anisotropy', - 'Anisotropy Rotation', - 'Coat Strength', - 'Coat Roughness', + "Metallic", + "Roughness", + "Anisotropy", + "Anisotropy Rotation", + "Coat Strength", + "Coat Roughness", ] -for i, l in enumerate(labels): +for i, name in enumerate(labels): pos = [-40, -5 * i, 0] - label = actor.vector_text(l, pos=pos, scale=(0.8, 0.8, 0.8), color=(0, 0, 0)) + label = actor.vector_text(name, pos=pos, scale=(0.8, 0.8, 0.8), color=(0, 0, 0)) scene.add(label) for j in range(num_values): @@ -105,14 +105,14 @@ iors = np.round(np.linspace(1, 2.3, num=num_values), decimals=2) ior_params = [ - [(0, 1, 1), {'base_ior': iors[0], 'roughness': 0}], + [(0, 1, 1), {"base_ior": iors[0], "roughness": 0}], [ (0, 1, 1), { - 'coat_ior': iors[0], - 'coat_roughness': 0.1, - 'coat_strength': 1, - 'roughness': 0, + "coat_ior": iors[0], + "coat_roughness": 0.1, + "coat_strength": 1, + "roughness": 0, }, ], ] @@ -132,17 +132,17 @@ ############################################################################### # Let's add the respective labels to the scene. -labels = ['Base IoR', 'Coat IoR'] +labels = ["Base IoR", "Coat IoR"] -for i, l in enumerate(labels): +for i, name in enumerate(labels): pos = [-40, -35 - (5 * i), 0] - label = actor.vector_text(l, pos=pos, scale=(0.8, 0.8, 0.8), color=(0, 0, 0)) + label = actor.vector_text(name, pos=pos, scale=(0.8, 0.8, 0.8), color=(0, 0, 0)) scene.add(label) for j in range(num_values): pos = [-26 + 5 * j, -32, 0] label = actor.vector_text( - '{:.02f}'.format(iors[j]), pos=pos, scale=(0.8, 0.8, 0.8), color=(0, 0, 0) + "{:.02f}".format(iors[j]), pos=pos, scale=(0.8, 0.8, 0.8), color=(0, 0, 0) ) scene.add(label) @@ -153,4 +153,4 @@ if interactive: window.show(scene) -window.record(scene, size=(600, 600), out_path='viz_pbr_spheres.png') +window.record(scene, size=(600, 600), out_path="viz_pbr_spheres.png") diff --git a/docs/examples/viz_picking.py b/docs/examples/viz_picking.py index 00b14c5f8..772ca79d5 100644 --- a/docs/examples/viz_picking.py +++ b/docs/examples/viz_picking.py @@ -24,10 +24,10 @@ ############################################################################### # Let's create a panel to show what is picked -panel = ui.Panel2D(size=(400, 200), color=(1, 0.5, 0.0), align='right') +panel = ui.Panel2D(size=(400, 200), color=(1, 0.5, 0.0), align="right") panel.center = (150, 200) -text_block = ui.TextBlock2D(text='Left click on object \n') +text_block = ui.TextBlock2D(text="Left click on object \n") panel.add_element(text_block, (0.3, 0.3)) ############################################################################### @@ -35,7 +35,7 @@ scene = window.Scene() -label_actor = actor.vector_text(text='Test') +label_actor = actor.vector_text(text="Test") ############################################################################### # This actor is made with 3 cubes of different orientation @@ -59,7 +59,7 @@ ############################################################################### # Access the memory of the colors of all the cubes -vcolors = utils.colors_from_actor(fury_actor, 'colors') +vcolors = utils.colors_from_actor(fury_actor, "colors") ############################################################################### # Adding an actor showing the axes of the world coordinates @@ -80,13 +80,12 @@ def left_click_callback(obj, event): - # Get the event position on display and pick event_pos = pickm.event_position(showm.iren) picked_info = pickm.pick(event_pos, showm.scene) - vertex_index = picked_info['vertex'] + vertex_index = picked_info["vertex"] # Calculate the objects index @@ -97,11 +96,11 @@ def left_click_callback(obj, event): if not selected[object_index]: scale = 6 / 5 - color_add = np.array([30, 30, 30], dtype='uint8') + color_add = np.array([30, 30, 30], dtype="uint8") selected[object_index] = True else: scale = 5 / 6 - color_add = np.array([-30, -30, -30], dtype='uint8') + color_add = np.array([-30, -30, -30], dtype="uint8") selected[object_index] = False # Update vertices positions @@ -120,14 +119,14 @@ def left_click_callback(obj, event): # Tell actor that memory is modified utils.update_actor(fury_actor) - face_index = picked_info['face'] + face_index = picked_info["face"] # Show some info - text = 'Object ' + str(object_index) + '\n' - text += 'Vertex ID ' + str(vertex_index) + '\n' - text += 'Face ID ' + str(face_index) + '\n' - text += 'World pos ' + str(np.round(picked_info['xyz'], 2)) + '\n' - text += 'Actor ID ' + str(id(picked_info['actor'])) + text = "Object " + str(object_index) + "\n" + text += "Vertex ID " + str(vertex_index) + "\n" + text += "Face ID " + str(face_index) + "\n" + text += "World pos " + str(np.round(picked_info["xyz"], 2)) + "\n" + text += "Actor ID " + str(id(picked_info["actor"])) text_block.message = text showm.render() @@ -135,7 +134,7 @@ def left_click_callback(obj, event): ############################################################################### # Bind the callback to the actor -fury_actor.AddObserver('LeftButtonPressEvent', left_click_callback, 1) +fury_actor.AddObserver("LeftButtonPressEvent", left_click_callback, 1) ############################################################################### # Make the window appear @@ -150,11 +149,10 @@ def left_click_callback(obj, event): interactive = False if interactive: - showm.start() ############################################################################### # Save the current framebuffer in a PNG file -window.record(showm.scene, size=(1024, 768), out_path='viz_picking.png') +window.record(showm.scene, size=(1024, 768), out_path="viz_picking.png") diff --git a/docs/examples/viz_play_video.py b/docs/examples/viz_play_video.py index 7437d0582..920d2b1a7 100644 --- a/docs/examples/viz_play_video.py +++ b/docs/examples/viz_play_video.py @@ -85,9 +85,9 @@ def run(self): # Create VideoPlayer Object and run it video_url = ( - 'http://commondatastorage.googleapis.com/' - + 'gtv-videos-bucket/sample/BigBuckBunny.mp4' + "http://commondatastorage.googleapis.com/" + + "gtv-videos-bucket/sample/BigBuckBunny.mp4" ) vp = VideoPlayer(video_url) vp.run() -window.record(vp.show_manager.scene, out_path='viz_play_video.png', size=(600, 600)) +window.record(vp.show_manager.scene, out_path="viz_play_video.png", size=(600, 600)) diff --git a/docs/examples/viz_principled_spheres.py b/docs/examples/viz_principled_spheres.py index 6408bfa62..018672433 100644 --- a/docs/examples/viz_principled_spheres.py +++ b/docs/examples/viz_principled_spheres.py @@ -36,16 +36,16 @@ # values between the range 0 to 1. material_params = [ - [(1, 1, 1), {'subsurface': 0}], - [[1, 1, 0], {'metallic': 0}], - [(1, 0, 0), {'specular': 0}], - [(1, 0, 0), {'specular_tint': 0, 'specular': 1}], - [(0, 0, 1), {'roughness': 0}], - [(1, 0, 1), {'anisotropic': 0, 'metallic': 0.25, 'roughness': 0.5}], - [[0, 1, 0.5], {'sheen': 0}], - [(0, 1, 0.5), {'sheen_tint': 0, 'sheen': 1}], - [(0, 1, 1), {'clearcoat': 0}], - [(0, 1, 1), {'clearcoat_gloss': 0, 'clearcoat': 1}], + [(1, 1, 1), {"subsurface": 0}], + [[1, 1, 0], {"metallic": 0}], + [(1, 0, 0), {"specular": 0}], + [(1, 0, 0), {"specular_tint": 0, "specular": 1}], + [(0, 0, 1), {"roughness": 0}], + [(1, 0, 1), {"anisotropic": 0, "metallic": 0.25, "roughness": 0.5}], + [[0, 1, 0.5], {"sheen": 0}], + [(0, 1, 0.5), {"sheen_tint": 0, "sheen": 1}], + [(0, 1, 1), {"clearcoat": 0}], + [(0, 1, 1), {"clearcoat_gloss": 0, "clearcoat": 1}], ] ############################################################################### @@ -68,16 +68,16 @@ # Finally, let's add some labels to guide us through our visualization. labels = [ - 'Subsurface', - 'Metallic', - 'Specular', - 'Specular Tint', - 'Roughness', - 'Anisotropic', - 'Sheen', - 'Sheen Tint', - 'Clearcoat', - 'Clearcoat Gloss', + "Subsurface", + "Metallic", + "Specular", + "Specular Tint", + "Roughness", + "Anisotropic", + "Sheen", + "Sheen Tint", + "Clearcoat", + "Clearcoat Gloss", ] for i in range(10): @@ -104,4 +104,4 @@ if interactive: window.show(scene) -window.record(scene, size=(600, 600), out_path='viz_principled_spheres.png') +window.record(scene, size=(600, 600), out_path="viz_principled_spheres.png") diff --git a/docs/examples/viz_radio_buttons.py b/docs/examples/viz_radio_buttons.py index 8cec217b0..71dd1c6fe 100644 --- a/docs/examples/viz_radio_buttons.py +++ b/docs/examples/viz_radio_buttons.py @@ -34,14 +34,14 @@ ) # Creating a dict of possible options and mapping it with their values. -options = {'Blue': (0, 0, 255), 'Red': (255, 0, 0), 'Green': (0, 255, 0)} +options = {"Blue": (0, 0, 255), "Red": (255, 0, 0), "Green": (0, 255, 0)} color_toggler = ui.RadioButton( list(options), - checked_labels=['Blue'], + checked_labels=["Blue"], padding=1, font_size=16, - font_family='Arial', + font_family="Arial", position=(200, 200), ) @@ -65,7 +65,7 @@ def toggle_color(radio): # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, title='FURY Sphere Example') +show_manager = window.ShowManager(size=current_size, title="FURY Sphere Example") show_manager.scene.add(sphere) show_manager.scene.add(color_toggler) @@ -82,4 +82,4 @@ def toggle_color(radio): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_radio_buttons.png') +window.record(show_manager.scene, size=current_size, out_path="viz_radio_buttons.png") diff --git a/docs/examples/viz_robot_arm_animation.py b/docs/examples/viz_robot_arm_animation.py index 24dc53700..1e9485d02 100644 --- a/docs/examples/viz_robot_arm_animation.py +++ b/docs/examples/viz_robot_arm_animation.py @@ -5,6 +5,7 @@ Tutorial on making a robot arm animation in FURY. """ + import numpy as np from fury import actor, window @@ -118,4 +119,4 @@ def rot_drill(t): if interactive: showm.start() -window.record(scene, out_path='viz_robot_arm.png', size=(900, 768)) +window.record(scene, out_path="viz_robot_arm.png", size=(900, 768)) diff --git a/docs/examples/viz_roi_contour.py b/docs/examples/viz_roi_contour.py index af3383ab1..17590a3df 100644 --- a/docs/examples/viz_roi_contour.py +++ b/docs/examples/viz_roi_contour.py @@ -13,13 +13,13 @@ from dipy.reconst.shm import CsaOdfModel try: - from dipy.tracking.local import LocalTracking from dipy.tracking.local import ( + LocalTracking, ThresholdTissueClassifier as ThresholdStoppingCriterion, ) except ImportError: - from dipy.tracking.stopping_criterion import ThresholdStoppingCriterion from dipy.tracking.local_tracking import LocalTracking + from dipy.tracking.stopping_criterion import ThresholdStoppingCriterion from dipy.tracking import utils from dipy.tracking.streamline import Streamlines @@ -97,4 +97,4 @@ # scene.zoom(1.5) # scene.reset_clipping_range() -window.record(scene, out_path='contour_from_roi_tutorial.png', size=(600, 600)) +window.record(scene, out_path="contour_from_roi_tutorial.png", size=(600, 600)) diff --git a/docs/examples/viz_sdf_cylinder.py b/docs/examples/viz_sdf_cylinder.py index b694e02bc..bebeb4f30 100644 --- a/docs/examples/viz_sdf_cylinder.py +++ b/docs/examples/viz_sdf_cylinder.py @@ -129,7 +129,7 @@ if interactive: window.show(scene) -window.record(scene, size=(600, 600), out_path='viz_poly_cylinder.png') +window.record(scene, size=(600, 600), out_path="viz_poly_cylinder.png") ############################################################################### # Visualize the surface geometry representation for the object. @@ -141,7 +141,7 @@ if interactive: window.show(scene) -window.record(scene, size=(600, 600), out_path='viz_poly_cylinder_geom.png') +window.record(scene, size=(600, 600), out_path="viz_poly_cylinder_geom.png") ############################################################################### # Then we clean the scene to render the boxes we will use to render our @@ -187,10 +187,10 @@ rep_radii = np.repeat(np.repeat(radius, 9), 8, axis=0) rep_heights = np.repeat(np.repeat(height, 9), 8, axis=0) -attribute_to_actor(box_actor, rep_centers, 'center') -attribute_to_actor(box_actor, rep_directions, 'direction') -attribute_to_actor(box_actor, rep_radii, 'radius') -attribute_to_actor(box_actor, rep_heights, 'height') +attribute_to_actor(box_actor, rep_centers, "center") +attribute_to_actor(box_actor, rep_directions, "direction") +attribute_to_actor(box_actor, rep_radii, "radius") +attribute_to_actor(box_actor, rep_heights, "height") ############################################################################### # Then we have the shader code implementation corresponding to vertex and @@ -225,7 +225,7 @@ # to apply our implementation to the shader creation process, this function # joins our code to the shader template that FURY has by default. -shader_to_actor(box_actor, 'vertex', decl_code=vs_dec, impl_code=vs_impl) +shader_to_actor(box_actor, "vertex", decl_code=vs_dec, impl_code=vs_impl) ############################################################################### # Fragment shaders are used to define the colors of each pixel being processed, @@ -253,13 +253,13 @@ # cylinder with respect to the box. vec_to_vec_rot_mat = import_fury_shader( - os.path.join('utils', 'vec_to_vec_rot_mat.glsl') + os.path.join("utils", "vec_to_vec_rot_mat.glsl") ) ############################################################################### # We calculate the distance using the SDF function for the cylinder. -sd_cylinder = import_fury_shader(os.path.join('sdf', 'sd_cylinder.frag')) +sd_cylinder = import_fury_shader(os.path.join("sdf", "sd_cylinder.frag")) ############################################################################### # This is used on calculations for surface normals of the cylinder. @@ -283,18 +283,18 @@ ############################################################################### # We use central differences technique for computing surface normals. -central_diffs_normal = import_fury_shader(os.path.join('sdf', 'central_diffs.frag')) +central_diffs_normal = import_fury_shader(os.path.join("sdf", "central_diffs.frag")) ############################################################################### # We use cast_ray for the implementation of Ray Marching. -cast_ray = import_fury_shader(os.path.join('ray_marching', 'cast_ray.frag')) +cast_ray = import_fury_shader(os.path.join("ray_marching", "cast_ray.frag")) ############################################################################### # For the illumination of the scene we use the Blinn-Phong model. blinn_phong_model = import_fury_shader( - os.path.join('lighting', 'blinn_phong_model.frag') + os.path.join("lighting", "blinn_phong_model.frag") ) ############################################################################### @@ -312,7 +312,7 @@ ] ) -shader_to_actor(box_actor, 'fragment', decl_code=fs_dec) +shader_to_actor(box_actor, "fragment", decl_code=fs_dec) ############################################################################### # Here we have the implementation of all the previous code with all the @@ -350,7 +350,7 @@ } """ -shader_to_actor(box_actor, 'fragment', impl_code=sdf_cylinder_frag_impl, block='light') +shader_to_actor(box_actor, "fragment", impl_code=sdf_cylinder_frag_impl, block="light") ############################################################################### # Finally, we visualize the cylinders made using ray marching and SDFs. @@ -360,7 +360,7 @@ if interactive: window.show(scene) -window.record(scene, size=(600, 600), out_path='viz_sdf_cylinder.png') +window.record(scene, size=(600, 600), out_path="viz_sdf_cylinder.png") ############################################################################### # References diff --git a/docs/examples/viz_sdfactor.py b/docs/examples/viz_sdfactor.py index 748d02ea1..1223d38aa 100644 --- a/docs/examples/viz_sdfactor.py +++ b/docs/examples/viz_sdfactor.py @@ -34,7 +34,7 @@ centers=centers, directions=dirs, colors=colors, - primitives=['sphere', 'torus', 'ellipsoid', 'capsule'], + primitives=["sphere", "torus", "ellipsoid", "capsule"], scales=scales, ) @@ -53,11 +53,11 @@ # manager. current_size = (1024, 720) -showm = window.ShowManager(scene, size=current_size, title='Visualize SDF Actor') +showm = window.ShowManager(scene, size=current_size, title="Visualize SDF Actor") interactive = False if interactive: showm.start() -window.record(scene, out_path='viz_sdfactor.png', size=current_size) +window.record(scene, out_path="viz_sdfactor.png", size=current_size) diff --git a/docs/examples/viz_selection.py b/docs/examples/viz_selection.py index 968d5f315..6c0a4c485 100644 --- a/docs/examples/viz_selection.py +++ b/docs/examples/viz_selection.py @@ -61,7 +61,7 @@ ############################################################################### # Access the memory of the colors of all the cubes -vcolors = utils.colors_from_actor(cube_actor, 'colors') +vcolors = utils.colors_from_actor(cube_actor, "colors") ############################################################################### # Create a rectangular 2d box as a texture @@ -78,7 +78,7 @@ ############################################################################### # Create the Selection Manager -selm = pick.SelectionManager(select='faces') +selm = pick.SelectionManager(select="faces") ############################################################################### # Tell Selection Manager to avoid selecting specific actors @@ -98,17 +98,17 @@ def hover_callback(_obj, _event): # defines selection region and returns information from selected objects info = selm.select(event_pos, showm.scene, (200 // 2, 100 // 2)) for node in info.keys(): - if info[node]['face'] is not None: - if info[node]['actor'] is cube_actor: - for face_index in info[node]['face']: + if info[node]["face"] is not None: + if info[node]["actor"] is cube_actor: + for face_index in info[node]["face"]: # generates an object_index to help with coloring # by dividing by the number of faces of each cube (6 * 2) object_index = face_index // 12 sec = int(num_vertices / num_objects) - color_change = np.array([150, 0, 0, 255], dtype='uint8') - vcolors[ - object_index * sec : object_index * sec + sec - ] = color_change + color_change = np.array([150, 0, 0, 255], dtype="uint8") + vcolors[object_index * sec : object_index * sec + sec] = ( + color_change + ) utils.update_actor(cube_actor) showm.render() @@ -132,11 +132,10 @@ def hover_callback(_obj, _event): interactive = False if interactive: - showm.start() ############################################################################### # Save the current framebuffer in a PNG file -window.record(showm.scene, size=(1024, 768), out_path='viz_selection.png') +window.record(showm.scene, size=(1024, 768), out_path="viz_selection.png") diff --git a/docs/examples/viz_shader.py b/docs/examples/viz_shader.py index 2c7feab13..19b1a36ba 100644 --- a/docs/examples/viz_shader.py +++ b/docs/examples/viz_shader.py @@ -19,7 +19,7 @@ fetch_viz_models() -model = read_viz_models('utah.obj') +model = read_viz_models("utah.obj") ############################################################################### @@ -59,10 +59,10 @@ """ shader_to_actor( - utah, 'vertex', impl_code=vertex_shader_code_impl, decl_code=vertex_shader_code_decl + utah, "vertex", impl_code=vertex_shader_code_impl, decl_code=vertex_shader_code_decl ) -shader_to_actor(utah, 'fragment', decl_code=fragment_shader_code_decl) -shader_to_actor(utah, 'fragment', impl_code=fragment_shader_code_impl, block='light') +shader_to_actor(utah, "fragment", decl_code=fragment_shader_code_decl) +shader_to_actor(utah, "fragment", impl_code=fragment_shader_code_impl, block="light") ############################################################################### # Let's create a scene. @@ -93,7 +93,7 @@ def shader_callback(_caller, _event, calldata=None): global timer if program is not None: try: - program.SetUniformf('time', timer) + program.SetUniformf("time", timer) except ValueError: pass @@ -103,7 +103,7 @@ def shader_callback(_caller, _event, calldata=None): # Let's add a textblock to the scene with a custom message tb = ui.TextBlock2D() -tb.message = 'Hello Shaders' +tb.message = "Hello Shaders" ############################################################################### # Show Manager @@ -124,4 +124,4 @@ def shader_callback(_caller, _event, calldata=None): if interactive: showm.start() -window.record(showm.scene, size=current_size, out_path='viz_shader.png') +window.record(showm.scene, size=current_size, out_path="viz_shader.png") diff --git a/docs/examples/viz_shapes.py b/docs/examples/viz_shapes.py index 130fadbcd..e7e64361c 100644 --- a/docs/examples/viz_shapes.py +++ b/docs/examples/viz_shapes.py @@ -39,7 +39,7 @@ # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, title='FURY Shapes Example') +show_manager = window.ShowManager(size=current_size, title="FURY Shapes Example") show_manager.scene.add(rect) show_manager.scene.add(disk) @@ -50,4 +50,4 @@ if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_shapes.png') +window.record(show_manager.scene, size=current_size, out_path="viz_shapes.png") diff --git a/docs/examples/viz_skinning.py b/docs/examples/viz_skinning.py index c7fc035f0..0657e0b1d 100644 --- a/docs/examples/viz_skinning.py +++ b/docs/examples/viz_skinning.py @@ -14,8 +14,8 @@ # Retrieving the model with skeletal animations. # We're choosing the `RiggedFigure` model here. -fetch_gltf('RiggedFigure', 'glTF') -filename = read_viz_gltf('RiggedFigure') +fetch_gltf("RiggedFigure", "glTF") +filename = read_viz_gltf("RiggedFigure") ############################################################################## # Initializing the glTF object, You can additionally set `apply_normals=True`. @@ -28,11 +28,11 @@ # name you want to visualize. # Note: If there's no name for animation, It's stored as `anim_0`, `anim_1` etc -animation = gltf_obj.skin_animation()['anim_0'] +animation = gltf_obj.skin_animation()["anim_0"] # After we get the timeline object, We want to initialise the skinning process. # You can set `bones=true` to visualize each bone transformation. Additionally, -# you can set `lenght` of bones in the `initialise_skin` method. +# you can set `length` of bones in the `initialise_skin` method. # Note: Make sure to call this method before you initialize ShowManager, else # bones won't be added to the scene. @@ -73,4 +73,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, out_path='viz_skinning.png', size=(900, 768)) +window.record(scene, out_path="viz_skinning.png", size=(900, 768)) diff --git a/docs/examples/viz_slice.py b/docs/examples/viz_slice.py index 8a41b61b3..0a8c01303 100644 --- a/docs/examples/viz_slice.py +++ b/docs/examples/viz_slice.py @@ -8,8 +8,8 @@ import os -import nibabel as nib from dipy.data import fetch_bundles_2_subjects +import nibabel as nib from fury import actor, ui, window @@ -19,12 +19,12 @@ fetch_bundles_2_subjects() fname_t1 = os.path.join( - os.path.expanduser('~'), - '.dipy', - 'exp_bundles_and_maps', - 'bundles_2_subjects', - 'subj_1', - 't1_warped.nii.gz', + os.path.expanduser("~"), + ".dipy", + "exp_bundles_and_maps", + "bundles_2_subjects", + "subj_1", + "t1_warped.nii.gz", ) @@ -86,7 +86,7 @@ ############################################################################### # Otherwise, you can save a screenshot using the following command. -window.record(scene, out_path='slices.png', size=(600, 600), reset_camera=False) +window.record(scene, out_path="slices.png", size=(600, 600), reset_camera=False) ############################################################################### # Render slices from FA with your colormap @@ -97,12 +97,12 @@ # colormap. fname_fa = os.path.join( - os.path.expanduser('~'), - '.dipy', - 'exp_bundles_and_maps', - 'bundles_2_subjects', - 'subj_1', - 'fa_1x1x1.nii.gz', + os.path.expanduser("~"), + ".dipy", + "exp_bundles_and_maps", + "bundles_2_subjects", + "subj_1", + "fa_1x1x1.nii.gz", ) img = nib.load(fname_fa) @@ -132,7 +132,7 @@ # window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='slices_lut.png', size=(600, 600), reset_camera=False) +window.record(scene, out_path="slices_lut.png", size=(600, 600), reset_camera=False) ############################################################################### # Now we would like to add the ability to click on a voxel and show its value @@ -148,14 +148,14 @@ ############################################################################### # We'll start by creating the panel and adding it to the ``ShowManager`` -label_position = ui.TextBlock2D(text='Position:') -label_value = ui.TextBlock2D(text='Value:') +label_position = ui.TextBlock2D(text="Position:") +label_value = ui.TextBlock2D(text="Value:") -result_position = ui.TextBlock2D(text='') -result_value = ui.TextBlock2D(text='') +result_position = ui.TextBlock2D(text="") +result_value = ui.TextBlock2D(text="") panel_picking = ui.Panel2D( - size=(250, 125), position=(20, 20), color=(0, 0, 0), opacity=0.75, align='left' + size=(250, 125), position=(20, 20), color=(0, 0, 0), opacity=0.75, align="left" ) panel_picking.add_element(label_position, (0.1, 0.55)) @@ -178,12 +178,12 @@ def left_click_callback(obj, _ev): obj.picker.Pick(event_pos[0], event_pos[1], 0, show_m.scene) i, j, k = obj.picker.GetPointIJK() - result_position.message = '({}, {}, {})'.format(str(i), str(j), str(k)) - result_value.message = '%.8f' % data[i, j, k] + result_position.message = "({}, {}, {})".format(str(i), str(j), str(k)) + result_value.message = "%.8f" % data[i, j, k] fa_actor.SetInterpolate(False) -fa_actor.AddObserver('LeftButtonPressEvent', left_click_callback, 1.0) +fa_actor.AddObserver("LeftButtonPressEvent", left_click_callback, 1.0) # show_m.start() @@ -198,10 +198,10 @@ def left_click_callback(obj, _ev): # parallel. We'll also need a new show manager and an associated callback. scene.clear() -scene.projection('parallel') +scene.projection("parallel") -result_position.message = '' -result_value.message = '' +result_position.message = "" +result_value.message = "" show_m_mosaic = window.ShowManager(scene, size=(1200, 900)) @@ -213,8 +213,8 @@ def left_click_callback_mosaic(obj, _ev): obj.picker.Pick(event_pos[0], event_pos[1], 0, show_m_mosaic.scene) i, j, k = obj.picker.GetPointIJK() - result_position.message = '({}, {}, {})'.format(str(i), str(j), str(k)) - result_value.message = '%.8f' % data[i, j, k] + result_position.message = "({}, {}, {})".format(str(i), str(j), str(k)) + result_value.message = "%.8f" % data[i, j, k] ############################################################################### @@ -239,7 +239,7 @@ def left_click_callback_mosaic(obj, _ev): ) slice_mosaic.SetInterpolate(False) slice_mosaic.AddObserver( - 'LeftButtonPressEvent', left_click_callback_mosaic, 1.0 + "LeftButtonPressEvent", left_click_callback_mosaic, 1.0 ) scene.add(slice_mosaic) cnt += 1 @@ -260,4 +260,4 @@ def left_click_callback_mosaic(obj, _ev): # zoom in/out using the scroll wheel, and pick voxels with left click. -window.record(scene, out_path='mosaic.png', size=(900, 600), reset_camera=False) +window.record(scene, out_path="mosaic.png", size=(900, 600), reset_camera=False) diff --git a/docs/examples/viz_solar_system.py b/docs/examples/viz_solar_system.py index 185b044a3..2e60bb351 100644 --- a/docs/examples/viz_solar_system.py +++ b/docs/examples/viz_solar_system.py @@ -23,11 +23,11 @@ # Create a panel and the start/pause buttons -panel = ui.Panel2D(size=(300, 100), color=(1, 1, 1), align='right') +panel = ui.Panel2D(size=(300, 100), color=(1, 1, 1), align="right") panel.center = (400, 50) -pause_button = ui.Button2D(icon_fnames=[('square', read_viz_icons(fname='pause2.png'))]) -start_button = ui.Button2D(icon_fnames=[('square', read_viz_icons(fname='play3.png'))]) +pause_button = ui.Button2D(icon_fnames=[("square", read_viz_icons(fname="pause2.png"))]) +start_button = ui.Button2D(icon_fnames=[("square", read_viz_icons(fname="play3.png"))]) # Add the buttons on the panel @@ -41,55 +41,55 @@ planets_data = [ { - 'filename': '8k_mercury.jpg', - 'position': 7, - 'earth_days': 58, - 'scale': (0.4, 0.4, 0.4), + "filename": "8k_mercury.jpg", + "position": 7, + "earth_days": 58, + "scale": (0.4, 0.4, 0.4), }, { - 'filename': '8k_venus_surface.jpg', - 'position': 9, - 'earth_days': 243, - 'scale': (0.6, 0.6, 0.6), + "filename": "8k_venus_surface.jpg", + "position": 9, + "earth_days": 243, + "scale": (0.6, 0.6, 0.6), }, { - 'filename': '1_earth_8k.jpg', - 'position': 11, - 'earth_days': 1, - 'scale': (0.4, 0.4, 0.4), + "filename": "1_earth_8k.jpg", + "position": 11, + "earth_days": 1, + "scale": (0.4, 0.4, 0.4), }, { - 'filename': '8k_mars.jpg', - 'position': 13, - 'earth_days': 1, - 'scale': (0.8, 0.8, 0.8), + "filename": "8k_mars.jpg", + "position": 13, + "earth_days": 1, + "scale": (0.8, 0.8, 0.8), }, - {'filename': 'jupiter.jpg', 'position': 16, 'earth_days': 0.41, 'scale': (2, 2, 2)}, + {"filename": "jupiter.jpg", "position": 16, "earth_days": 0.41, "scale": (2, 2, 2)}, { - 'filename': '8k_saturn.jpg', - 'position': 19, - 'earth_days': 0.45, - 'scale': (2, 2, 2), + "filename": "8k_saturn.jpg", + "position": 19, + "earth_days": 0.45, + "scale": (2, 2, 2), }, { - 'filename': '8k_saturn_ring_alpha.png', - 'position': 19, - 'earth_days': 0.45, - 'scale': (3, 0.5, 3), + "filename": "8k_saturn_ring_alpha.png", + "position": 19, + "earth_days": 0.45, + "scale": (3, 0.5, 3), }, { - 'filename': '2k_uranus.jpg', - 'position': 22, - 'earth_days': 0.70, - 'scale': (1, 1, 1), + "filename": "2k_uranus.jpg", + "position": 22, + "earth_days": 0.70, + "scale": (1, 1, 1), }, { - 'filename': '2k_neptune.jpg', - 'position': 25, - 'earth_days': 0.70, - 'scale': (1, 1, 1), + "filename": "2k_neptune.jpg", + "position": 25, + "earth_days": 0.70, + "scale": (1, 1, 1), }, - {'filename': '8k_sun.jpg', 'position': 0, 'earth_days': 27, 'scale': (5, 5, 5)}, + {"filename": "8k_sun.jpg", "position": 0, "earth_days": 27, "scale": (5, 5, 5)}, ] fetch_viz_textures() @@ -114,13 +114,13 @@ def init_planet(planet_data): planet_actor: actor The corresponding sphere actor with texture applied. """ - planet_file = read_viz_textures(planet_data['filename']) + planet_file = read_viz_textures(planet_data["filename"]) planet_image = io.load_image(planet_file) planet_actor = actor.texture_on_sphere(planet_image) - planet_actor.SetPosition(planet_data['position'], 0, 0) - if planet_data['filename'] != '8k_saturn_ring_alpha.png': + planet_actor.SetPosition(planet_data["position"], 0, 0) + if planet_data["filename"] != "8k_saturn_ring_alpha.png": utils.rotate(planet_actor, (90, 1, 0, 0)) - planet_actor.SetScale(planet_data['scale']) + planet_actor.SetScale(planet_data["scale"]) scene.add(planet_actor) return planet_actor @@ -153,7 +153,7 @@ def init_planet(planet_data): g_exponent = np.float_power(10, -11) g_constant = 6.673 * g_exponent -m_exponent = 1073741824 # np.power(10, 30) +m_exponent = 1073741824 # np.power(10, 30) m_constant = 1.989 * m_exponent miu = m_constant * g_constant @@ -242,10 +242,10 @@ def calculate_path(r_planet, c): # around itself. r_planets = [ - p_data['position'] + p_data["position"] for p_data in planets_data - if 'sun' not in p_data['filename'] - if 'saturn_ring' not in p_data['filename'] + if "sun" not in p_data["filename"] + if "saturn_ring" not in p_data["filename"] ] planet_actors = [ @@ -261,12 +261,12 @@ def calculate_path(r_planet, c): sun_data = { - 'actor': sun_actor, - 'position': planets_data[9]['position'], - 'earth_days': planets_data[9]['earth_days'], + "actor": sun_actor, + "position": planets_data[9]["position"], + "earth_days": planets_data[9]["earth_days"], } -r_times = [p_data['earth_days'] for p_data in planets_data] +r_times = [p_data["earth_days"] for p_data in planets_data] ############################################################################## # Here we are calculating and updating the path/orbit before animation starts. @@ -292,7 +292,7 @@ def timer_callback(_obj, _event): showm.render() # Rotating the sun actor - rotate_axial(sun_actor, sun_data['earth_days'], 1) + rotate_axial(sun_actor, sun_data["earth_days"], 1) for r_planet, p_actor, r_time in zip(r_planets, planet_actors, r_times): # if the planet is saturn then we also need to update the position @@ -331,4 +331,4 @@ def pause_animation(i_ren, _obj, _button): showm.add_timer_callback(True, 10, timer_callback) showm.start() -window.record(showm.scene, size=(900, 768), out_path='viz_solar_system_animation.png') +window.record(showm.scene, size=(900, 768), out_path="viz_solar_system_animation.png") diff --git a/docs/examples/viz_sphere.py b/docs/examples/viz_sphere.py index 45b9489da..92df3c73c 100644 --- a/docs/examples/viz_sphere.py +++ b/docs/examples/viz_sphere.py @@ -41,4 +41,4 @@ if interactive: window.show(scene, size=(600, 600)) -window.record(scene, out_path='viz_sphere.png', size=(600, 600)) +window.record(scene, out_path="viz_sphere.png", size=(600, 600)) diff --git a/docs/examples/viz_spiky.py b/docs/examples/viz_spiky.py index 0cf3ffcd5..3ab10c87c 100644 --- a/docs/examples/viz_spiky.py +++ b/docs/examples/viz_spiky.py @@ -24,7 +24,7 @@ # of the surface normal. # ``prim_sphere`` provides a sphere with evenly distributed points -vertices, triangles = primitive.prim_sphere(name='symmetric362', gen_faces=False) +vertices, triangles = primitive.prim_sphere(name="symmetric362", gen_faces=False) ############################################################################## # To be able to visualize the vertices, let's define a point actor with @@ -108,7 +108,7 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 200, timer_callback) showm.start() -window.record(showm.scene, size=(900, 768), out_path='viz_spiky.png') +window.record(showm.scene, size=(900, 768), out_path="viz_spiky.png") ############################################################################## # Instead of arrows, you can choose other geometrical objects diff --git a/docs/examples/viz_spinbox.py b/docs/examples/viz_spinbox.py index 1c8c8d7e8..1fbe02f97 100644 --- a/docs/examples/viz_spinbox.py +++ b/docs/examples/viz_spinbox.py @@ -9,9 +9,11 @@ First, some imports. """ + +import numpy as np + from fury import actor, ui, utils, window from fury.data import fetch_viz_icons -import numpy as np ############################################################################## # First we need to fetch some icons that are included in FURY. @@ -21,23 +23,31 @@ ############################################################################### # Let's create a Cone. -cone = actor.cone(centers=np.random.rand(1, 3), - directions=np.random.rand(1, 3), - colors=(1, 1, 1), heights=np.random.rand(1)) +cone = actor.cone( + centers=np.random.rand(1, 3), + directions=np.random.rand(1, 3), + colors=(1, 1, 1), + heights=np.random.rand(1), +) ############################################################################### # Creating the SpinBox UI. -spinbox = ui.SpinBox(position=(200, 100), size=(300, 100), min_val=0, - max_val=360, initial_val=180, step=10) +spinbox = ui.SpinBox( + position=(200, 100), + size=(300, 100), + min_val=0, + max_val=360, + initial_val=180, + step=10, +) ############################################################################### # Now that all the elements have been initialised, we add them to the show # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, - title="FURY SpinBox Example") +show_manager = window.ShowManager(size=current_size, title="FURY SpinBox Example") show_manager.scene.add(cone) show_manager.scene.add(spinbox) @@ -66,5 +76,4 @@ def rotate_cone(spinbox): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, - out_path="viz_spinbox.png") +window.record(show_manager.scene, size=current_size, out_path="viz_spinbox.png") diff --git a/docs/examples/viz_spline_interpolator.py b/docs/examples/viz_spline_interpolator.py index 2023647b0..c5ca86344 100644 --- a/docs/examples/viz_spline_interpolator.py +++ b/docs/examples/viz_spline_interpolator.py @@ -94,4 +94,4 @@ if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_animation_spline.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_spline.png", size=(900, 768)) diff --git a/docs/examples/viz_surfaces.py b/docs/examples/viz_surfaces.py index 6dbc9e124..6cc62c551 100644 --- a/docs/examples/viz_surfaces.py +++ b/docs/examples/viz_surfaces.py @@ -58,7 +58,7 @@ [1, 5, 7], [1, 7, 3], ], - dtype='i8', + dtype="i8", ) @@ -71,9 +71,9 @@ ############################################################################### # Save the ``PolyData`` -file_name = 'my_cube.vtk' +file_name = "my_cube.vtk" save_polydata(my_polydata, file_name) -print('Surface saved in ' + file_name) +print("Surface saved in " + file_name) ############################################################################### # Load the ``PolyData`` @@ -87,7 +87,7 @@ colors = cube_vertices * 255 utils.set_polydata_colors(cube_polydata, colors) -print('new surface colors') +print("new surface colors") print(utils.get_polydata_colors(cube_polydata)) ############################################################################### @@ -104,4 +104,4 @@ # display # window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, out_path='cube.png', size=(600, 600)) +window.record(scene, out_path="cube.png", size=(600, 600)) diff --git a/docs/examples/viz_tab.py b/docs/examples/viz_tab.py index adcfcbdc9..da6f542c1 100644 --- a/docs/examples/viz_tab.py +++ b/docs/examples/viz_tab.py @@ -12,6 +12,7 @@ First, some imports. """ + import numpy as np from fury import actor, ui, window @@ -27,28 +28,34 @@ tab_ui = ui.TabUI(position=(49, 94), size=(300, 300), nb_tabs=3, draggable=True) +############################################################################### +# We can also define the position of the Tab Bar. +# By default the Tab Bar is positioned at top + +tab_ui.tab_bar_pos = "bottom" + ############################################################################### # Slider Controls for a Cube for Tab Index 0 # ========================================== # # Now we prepare content for the first tab. -ring_slider = ui.RingSlider2D(initial_value=0, text_template='{angle:5.1f}°') +ring_slider = ui.RingSlider2D(initial_value=0, text_template="{angle:5.1f}°") line_slider_x = ui.LineSlider2D( initial_value=0, min_value=-10, max_value=10, - orientation='horizontal', - text_alignment='Top', + orientation="horizontal", + text_alignment="Top", ) line_slider_y = ui.LineSlider2D( initial_value=0, min_value=-10, max_value=10, - orientation='vertical', - text_alignment='Right', + orientation="vertical", + text_alignment="Right", ) cube = actor.box( @@ -87,7 +94,7 @@ def translate_cube_y(slider): ############################################################################### # After defining content, we define properties for the tab. -tab_ui.tabs[0].title = 'Sliders' +tab_ui.tabs[0].title = "Sliders" tab_ui.add_element(0, ring_slider, (0.3, 0.3)) tab_ui.add_element(0, line_slider_x, (0.0, 0.0)) tab_ui.add_element(0, line_slider_y, (0.0, 0.1)) @@ -107,8 +114,8 @@ def translate_cube_y(slider): sphere = actor.sphere(centers=np.array([[5, 0, 0]]), colors=(1, 1, 0)) -figure_dict = {'cylinder': cylinder, 'sphere': sphere} -checkbox = ui.Checkbox(labels=['cylinder', 'sphere']) +figure_dict = {"cylinder": cylinder, "sphere": sphere} +checkbox = ui.Checkbox(labels=["cylinder", "sphere"]) # Get difference between two lists. @@ -133,7 +140,7 @@ def set_figure_visiblity(checkboxes): ############################################################################### # After defining content, we define properties for the tab. -tab_ui.tabs[1].title = 'Checkbox' +tab_ui.tabs[1].title = "Checkbox" tab_ui.add_element(1, checkbox, (0.2, 0.2)) ############################################################################### @@ -146,24 +153,24 @@ def set_figure_visiblity(checkboxes): position=(600, 300), font_size=40, color=(1, 0.5, 0), - justification='center', - vertical_justification='top', - text='FURY rocks!!!', + justification="center", + vertical_justification="top", + text="FURY rocks!!!", ) colors = { - 'Violet': (0.6, 0, 0.8), - 'Indigo': (0.3, 0, 0.5), - 'Blue': (0, 0, 1), - 'Green': (0, 1, 0), - 'Yellow': (1, 1, 0), - 'Orange': (1, 0.5, 0), - 'Red': (1, 0, 0), + "Violet": (0.6, 0, 0.8), + "Indigo": (0.3, 0, 0.5), + "Blue": (0, 0, 1), + "Green": (0, 1, 0), + "Yellow": (1, 1, 0), + "Orange": (1, 0.5, 0), + "Red": (1, 0, 0), } color_combobox = ui.ComboBox2D( items=list(colors.keys()), - placeholder='Choose Text Color', + placeholder="Choose Text Color", size=(250, 150), draggable=True, ) @@ -178,7 +185,7 @@ def change_color(combobox): ############################################################################### # After defining content, we define properties for the tab. -tab_ui.tabs[2].title = 'Colors' +tab_ui.tabs[2].title = "Colors" tab_ui.add_element(2, color_combobox, (0.1, 0.3)) ############################################################################### @@ -188,13 +195,13 @@ def change_color(combobox): def hide_actors(tab_ui): - if tab_ui.tabs[tab_ui.active_tab_idx].title == 'Sliders': + if tab_ui.tabs[tab_ui.active_tab_idx].title == "Sliders": cube.SetVisibility(True) cylinder.SetVisibility(False) sphere.SetVisibility(False) label.set_visibility(False) - elif tab_ui.tabs[tab_ui.active_tab_idx].title == 'Checkbox': + elif tab_ui.tabs[tab_ui.active_tab_idx].title == "Checkbox": cube.SetVisibility(False) set_figure_visiblity(checkbox) label.set_visibility(False) @@ -221,7 +228,7 @@ def collapse(tab_ui): ############################################################################### # Next we prepare the scene and render it with the help of show manager. -sm = window.ShowManager(size=(800, 500), title='Viz Tab') +sm = window.ShowManager(size=(800, 500), title="Viz Tab") sm.scene.add(tab_ui, cube, cylinder, sphere, label) # To interact with the ui set interactive = True @@ -230,4 +237,4 @@ def collapse(tab_ui): if interactive: sm.start() -window.record(sm.scene, size=(500, 500), out_path='viz_tab.png') +window.record(sm.scene, size=(500, 500), out_path="viz_tab.png") diff --git a/docs/examples/viz_tesseract.py b/docs/examples/viz_tesseract.py index 64ea04702..f037c9026 100644 --- a/docs/examples/viz_tesseract.py +++ b/docs/examples/viz_tesseract.py @@ -150,7 +150,7 @@ def connect_points(verts3D): ############################################################################### # Initializing text box to display the name -tb = TextBlock2D(text='Tesseract', position=(900, 950), font_size=20) +tb = TextBlock2D(text="Tesseract", position=(900, 950), font_size=20) showm.scene.add(tb) ############################################################################### @@ -186,4 +186,4 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 20, timer_callback) showm.start() -window.record(showm.scene, size=(600, 600), out_path='viz_tesseract.png') +window.record(showm.scene, size=(600, 600), out_path="viz_tesseract.png") diff --git a/docs/examples/viz_texture.py b/docs/examples/viz_texture.py index 69b7426bc..31043b706 100644 --- a/docs/examples/viz_texture.py +++ b/docs/examples/viz_texture.py @@ -20,7 +20,7 @@ # to download the available textures. fetch_viz_textures() -filename = read_viz_textures('1_earth_8k.jpg') +filename = read_viz_textures("1_earth_8k.jpg") image = io.load_image(filename) ############################################################################## @@ -38,4 +38,4 @@ interactive = False if interactive: window.show(scene, size=(600, 600), reset_camera=False) -window.record(scene, size=(900, 768), out_path='viz_texture.png') +window.record(scene, size=(900, 768), out_path="viz_texture.png") diff --git a/docs/examples/viz_timeline.py b/docs/examples/viz_timeline.py index 65c65e949..45218aacc 100644 --- a/docs/examples/viz_timeline.py +++ b/docs/examples/viz_timeline.py @@ -16,7 +16,6 @@ # ``Timeline`` has playback methods such as ``play``, ``pause``, ``stop``, ... # which can be used to control the animation. - import numpy as np from fury import actor, window @@ -89,4 +88,4 @@ if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_animation_timeline.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_timeline.png", size=(900, 768)) diff --git a/docs/examples/viz_timers.py b/docs/examples/viz_timers.py index c529a5768..4618cee44 100644 --- a/docs/examples/viz_timers.py +++ b/docs/examples/viz_timers.py @@ -13,7 +13,6 @@ application will exit after the callback has been called 100 times. """ - import itertools import numpy as np @@ -67,4 +66,4 @@ def timer_callback(_obj, _event): showm.start() -window.record(showm.scene, size=(900, 768), out_path='viz_timer.png') +window.record(showm.scene, size=(900, 768), out_path="viz_timer.png") diff --git a/docs/examples/viz_ui.py b/docs/examples/viz_ui.py index 253cbd662..31eecdc0b 100644 --- a/docs/examples/viz_ui.py +++ b/docs/examples/viz_ui.py @@ -46,7 +46,7 @@ # Now we can create an image container. img = ui.ImageContainer2D( - img_path=read_viz_icons(fname='home3.png'), position=(450, 350) + img_path=read_viz_icons(fname="home3.png"), position=(450, 350) ) ############################################################################### @@ -56,15 +56,15 @@ # Let's create some buttons and text and put them in a panel. First we'll # make the panel. -panel = ui.Panel2D(size=(300, 150), color=(1, 1, 1), align='right') +panel = ui.Panel2D(size=(300, 150), color=(1, 1, 1), align="right") panel.center = (500, 400) ############################################################################### # Then we'll make two text labels and place them on the panel. # Note that we specify the position with integer numbers of pixels. -text = ui.TextBlock2D(text='Click me') -text2 = ui.TextBlock2D(text='Me too') +text = ui.TextBlock2D(text="Click me") +text2 = ui.TextBlock2D(text="Me too") panel.add_element(text, (50, 100)) panel.add_element(text2, (180, 100)) @@ -76,14 +76,14 @@ button_example = ui.Button2D( - icon_fnames=[('square', read_viz_icons(fname='stop2.png'))] + icon_fnames=[("square", read_viz_icons(fname="stop2.png"))] ) icon_files = [] -icon_files.append(('down', read_viz_icons(fname='circle-down.png'))) -icon_files.append(('left', read_viz_icons(fname='circle-left.png'))) -icon_files.append(('up', read_viz_icons(fname='circle-up.png'))) -icon_files.append(('right', read_viz_icons(fname='circle-right.png'))) +icon_files.append(("down", read_viz_icons(fname="circle-down.png"))) +icon_files.append(("left", read_viz_icons(fname="circle-left.png"))) +icon_files.append(("up", read_viz_icons(fname="circle-up.png"))) +icon_files.append(("right", read_viz_icons(fname="circle-right.png"))) second_button_example = ui.Button2D(icon_fnames=icon_files) @@ -95,7 +95,7 @@ def change_text_callback(i_ren, _obj, _button): - text.message = 'Clicked!' + text.message = "Clicked!" i_ren.force_render() @@ -125,7 +125,7 @@ def change_icon_callback(i_ren, _obj, _button): # Now we'll add three sliders: one circular and two linear. ring_slider = ui.RingSlider2D( - center=(740, 400), initial_value=0, text_template='{angle:5.1f}°' + center=(740, 400), initial_value=0, text_template="{angle:5.1f}°" ) line_slider_x = ui.LineSlider2D( @@ -133,7 +133,7 @@ def change_icon_callback(i_ren, _obj, _button): initial_value=0, min_value=-10, max_value=10, - orientation='horizontal', + orientation="horizontal", ) line_slider_y = ui.LineSlider2D( @@ -141,7 +141,7 @@ def change_icon_callback(i_ren, _obj, _button): initial_value=0, min_value=-10, max_value=10, - orientation='vertical', + orientation="vertical", ) ############################################################################### @@ -198,7 +198,7 @@ def translate_cube_y(slider): font_size=18, range_precision=2, value_precision=4, - shape='square', + shape="square", ) range_slider_y = ui.RangeSlider( @@ -212,8 +212,8 @@ def translate_cube_y(slider): font_size=18, range_precision=2, value_precision=4, - orientation='vertical', - shape='square', + orientation="vertical", + shape="square", ) ############################################################################### # Select menu @@ -252,12 +252,12 @@ def hide_all_examples(): # correspond with the examples. values = [ - 'Rectangle', - 'Disks', - 'Image', - 'Button Panel', - 'Line & Ring Slider', - 'Range Slider', + "Rectangle", + "Disks", + "Image", + "Button Panel", + "Line & Ring Slider", + "Range Slider", ] ############################################################################### @@ -291,7 +291,7 @@ def display_element(): # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, title='FURY UI Example') +show_manager = window.ShowManager(size=current_size, title="FURY UI Example") show_manager.scene.add(listbox) for example in examples: @@ -309,4 +309,4 @@ def display_element(): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_ui.png') +window.record(show_manager.scene, size=current_size, out_path="viz_ui.png") diff --git a/docs/examples/viz_ui_listbox.py b/docs/examples/viz_ui_listbox.py index 118a00e8a..c89335662 100644 --- a/docs/examples/viz_ui_listbox.py +++ b/docs/examples/viz_ui_listbox.py @@ -9,6 +9,7 @@ First, a bunch of imports. """ + from fury import ui, window from fury.data import fetch_viz_icons @@ -21,9 +22,9 @@ # Create some text blocks that will be shown when # list elements will be selected -welcome_text = ui.TextBlock2D(text='Welcome', font_size=30, position=(500, 400)) -bye_text = ui.TextBlock2D(text='Bye', font_size=30, position=(500, 400)) -fury_text = ui.TextBlock2D(text='Fury', font_size=30, position=(500, 400)) +welcome_text = ui.TextBlock2D(text="Welcome", font_size=30, position=(500, 400)) +bye_text = ui.TextBlock2D(text="Bye", font_size=30, position=(500, 400)) +fury_text = ui.TextBlock2D(text="Fury", font_size=30, position=(500, 400)) example = [welcome_text, bye_text, fury_text] @@ -41,7 +42,7 @@ def hide_all_examples(): ############################################################################### # Create ListBox with the values as parameter. -values = ['Welcome', 'Bye', 'Fury'] +values = ["Welcome", "Bye", "Fury"] listbox = ui.ListBox2D( values=values, position=(10, 300), size=(200, 200), multiselection=False ) @@ -63,7 +64,7 @@ def display_element(): # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, title='FURY UI ListBox_Example') +show_manager = window.ShowManager(size=current_size, title="FURY UI ListBox_Example") show_manager.scene.add(listbox) show_manager.scene.add(welcome_text) @@ -74,4 +75,4 @@ def display_element(): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_listbox.png') +window.record(show_manager.scene, size=current_size, out_path="viz_listbox.png") diff --git a/docs/examples/viz_ui_slider.py b/docs/examples/viz_ui_slider.py index 648d45dde..034761fc3 100644 --- a/docs/examples/viz_ui_slider.py +++ b/docs/examples/viz_ui_slider.py @@ -9,6 +9,7 @@ First, some imports. """ + import numpy as np from fury import actor, ui, window @@ -37,43 +38,43 @@ # By default the alignments are 'bottom' for horizontal and 'top' for vertical. ring_slider = ui.RingSlider2D( - center=(630, 400), initial_value=0, text_template='{angle:5.1f}°' + center=(630, 400), initial_value=0, text_template="{angle:5.1f}°" ) hor_line_slider_text_top = ui.LineSlider2D( center=(400, 230), initial_value=0, - orientation='horizontal', + orientation="horizontal", min_value=-10, max_value=10, - text_alignment='top', + text_alignment="top", ) hor_line_slider_text_bottom = ui.LineSlider2D( center=(400, 200), initial_value=0, - orientation='horizontal', + orientation="horizontal", min_value=-10, max_value=10, - text_alignment='bottom', + text_alignment="bottom", ) ver_line_slider_text_left = ui.LineSlider2D( center=(100, 400), initial_value=0, - orientation='vertical', + orientation="vertical", min_value=-10, max_value=10, - text_alignment='left', + text_alignment="left", ) ver_line_slider_text_right = ui.LineSlider2D( center=(150, 400), initial_value=0, - orientation='vertical', + orientation="vertical", min_value=-10, max_value=10, - text_alignment='right', + text_alignment="right", ) @@ -117,7 +118,7 @@ def translate_cube_hor(slider): # manager. current_size = (800, 800) -show_manager = window.ShowManager(size=current_size, title='FURY Cube Example') +show_manager = window.ShowManager(size=current_size, title="FURY Cube Example") show_manager.scene.add(cube) show_manager.scene.add(ring_slider) @@ -149,4 +150,4 @@ def translate_cube_hor(slider): if interactive: show_manager.start() -window.record(show_manager.scene, size=current_size, out_path='viz_slider.png') +window.record(show_manager.scene, size=current_size, out_path="viz_slider.png") diff --git a/docs/examples/viz_using_time_equations.py b/docs/examples/viz_using_time_equations.py index da8a5c2a7..baf680b2c 100644 --- a/docs/examples/viz_using_time_equations.py +++ b/docs/examples/viz_using_time_equations.py @@ -5,6 +5,7 @@ Tutorial on making keyframe-based animation in FURY using custom functions. """ + import numpy as np from fury import actor, window @@ -60,7 +61,7 @@ def scale_eval(t): anim.set_position_interpolator(pos_eval, is_evaluator=True) anim.set_rotation_interpolator(rotation_eval, is_evaluator=True) anim.set_color_interpolator(color_eval, is_evaluator=True) -anim.set_interpolator('scale', scale_eval, is_evaluator=True) +anim.set_interpolator("scale", scale_eval, is_evaluator=True) ############################################################################### # changing camera position to observe the animation better. @@ -76,4 +77,4 @@ def scale_eval(t): if interactive: showm.start() -window.record(scene, out_path='viz_keyframe_animation_evaluators.png', size=(900, 768)) +window.record(scene, out_path="viz_keyframe_animation_evaluators.png", size=(900, 768)) diff --git a/docs/examples/viz_widget.py b/docs/examples/viz_widget.py index f5ad3e39e..6768a843e 100644 --- a/docs/examples/viz_widget.py +++ b/docs/examples/viz_widget.py @@ -31,8 +31,9 @@ `brew install ffmpeg opus libvpx pkg-config` Notes ------- +----- For this example your python version should be 3.8 or greater + """ import asyncio @@ -69,7 +70,7 @@ # If you want to use the widget in a Windows environment without the WSL # you need to use the asyncio version of the widget. # -if platform.system() == 'Windows': +if platform.system() == "Windows": async def main(): widget.start() @@ -85,4 +86,4 @@ async def main(): time.sleep(time_sleep) widget.stop() -window.record(showm.scene, size=window_size, out_path='viz_widget.png') +window.record(showm.scene, size=window_size, out_path="viz_widget.png") diff --git a/docs/examples/viz_wrecking_ball.py b/docs/examples/viz_wrecking_ball.py index 31ec68c6c..1e574ccc3 100644 --- a/docs/examples/viz_wrecking_ball.py +++ b/docs/examples/viz_wrecking_ball.py @@ -9,6 +9,7 @@ First some imports. """ + import itertools import numpy as np @@ -177,15 +178,15 @@ basePosition, baseOrientation, linkMasses=link_Masses, - linkCollisionShapeIndices=linkCollisionShapeIndices, - linkVisualShapeIndices=linkVisualShapeIndices, - linkPositions=linkPositions, - linkOrientations=linkOrientations, - linkInertialFramePositions=linkInertialFramePositions, - linkInertialFrameOrientations=linkInertialFrameOrns, - linkParentIndices=indices, - linkJointTypes=jointTypes, - linkJointAxis=axis, + linkCollisionShapeIndices=linkCollisionShapeIndices.astype(int), + linkVisualShapeIndices=linkVisualShapeIndices.astype(int), + linkPositions=linkPositions.astype(int), + linkOrientations=linkOrientations.astype(int), + linkInertialFramePositions=linkInertialFramePositions.astype(int), + linkInertialFrameOrientations=linkInertialFrameOrns.astype(int), + linkParentIndices=indices.astype(int), + linkJointTypes=jointTypes.astype(int), + linkJointAxis=axis.astype(int), ) ############################################################################### @@ -263,6 +264,7 @@ ############################################################################### # We define methods to sync bricks and wrecking ball. + # Function for syncing actors with multibodies. def sync_brick(object_index, multibody): pos, orn = p.getBasePositionAndOrientation(multibody) @@ -315,7 +317,7 @@ def sync_chain(actor_list, multibody): counter = itertools.count() fpss = np.array([]) tb = ui.TextBlock2D( - position=(0, 680), font_size=30, color=(1, 0.5, 0), text='Avg. FPS: \nSim Steps: ' + position=(0, 680), font_size=30, color=(1, 0.5, 0), text="Avg. FPS: \nSim Steps: " ) scene.add(tb) @@ -335,7 +337,7 @@ def timer_callback(_obj, _event): fps = showm.frame_rate fpss = np.append(fpss, fps) tb.message = ( - 'Avg. FPS: ' + str(np.round(np.mean(fpss), 0)) + '\nSim Steps: ' + str(cnt) + "Avg. FPS: " + str(np.round(np.mean(fpss), 0)) + "\nSim Steps: " + str(cnt) ) # Updating the position and orientation of each individual brick. @@ -373,4 +375,4 @@ def timer_callback(_obj, _event): if interactive: showm.start() -window.record(scene, size=(900, 768), out_path='viz_wrecking_ball.png') +window.record(scene, size=(900, 768), out_path="viz_wrecking_ball.png") diff --git a/docs/experimental/viz_canvas.py b/docs/experimental/viz_canvas.py index adaf34164..7f2a84a4e 100644 --- a/docs/experimental/viz_canvas.py +++ b/docs/experimental/viz_canvas.py @@ -2,8 +2,8 @@ import vtk from vtk.util import numpy_support -import fury.primitive as fp from fury import actor, window +import fury.primitive as fp from fury.utils import ( get_actor_from_polydata, numpy_to_vtk_colors, @@ -194,7 +194,7 @@ def test_sh(): vec3 lin = 2.5*occ*vec3(1.0,1.00,1.00)*(0.6+0.4*nor.y); lin += 1.0*sss*vec3(1.0,0.95,0.70)*occ; - // surface-light interacion + // surface-light interaction col = mate.xyz * lin; } diff --git a/docs/experimental/viz_molecular_demo.py b/docs/experimental/viz_molecular_demo.py index 72ce5cf10..d72338a55 100644 --- a/docs/experimental/viz_molecular_demo.py +++ b/docs/experimental/viz_molecular_demo.py @@ -25,6 +25,7 @@ Importing necessary modules + """ import os @@ -32,9 +33,7 @@ import numpy as np -from fury import actor -from fury import molecular as mol -from fury import ui, window +from fury import actor, molecular as mol, ui, window ############################################################################### # Downloading the PDB file of the protein to be rendered (the user can change diff --git a/docs/experimental/viz_multisdf.py b/docs/experimental/viz_multisdf.py index 8ea5ed819..37a78c5b4 100644 --- a/docs/experimental/viz_multisdf.py +++ b/docs/experimental/viz_multisdf.py @@ -2,9 +2,9 @@ import vtk from vtk.util import numpy_support +from fury import actor, window import fury.primitive as fp import fury.shaders as fs -from fury import actor, window from fury.utils import get_actor_from_primitive verts, faces = fp.prim_box() diff --git a/docs/experimental/viz_shader_canvas.py b/docs/experimental/viz_shader_canvas.py index 51bc0a7cb..b40c31926 100644 --- a/docs/experimental/viz_shader_canvas.py +++ b/docs/experimental/viz_shader_canvas.py @@ -1,6 +1,6 @@ import numpy as np -import vtk from scipy.spatial import Delaunay +import vtk from vtk.util import numpy_support from fury.utils import ( @@ -160,6 +160,7 @@ def rectangle2(centers, colors, use_vertices=False, size=(2, 2)): centers : ndarray, shape (N, 3) colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,) RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1] + """ if np.array(colors).ndim == 1: colors = np.tile(colors, (len(centers), 1)) diff --git a/docs/experimental/viz_shader_canvas_sk.py b/docs/experimental/viz_shader_canvas_sk.py index b44505ae2..f2d29ed94 100644 --- a/docs/experimental/viz_shader_canvas_sk.py +++ b/docs/experimental/viz_shader_canvas_sk.py @@ -1,6 +1,6 @@ import numpy as np -import vtk from viz_shader_canvas import cube, disk, rectangle, rectangle2, square +import vtk from fury import actor, window diff --git a/docs/experimental/viz_shader_faces.py b/docs/experimental/viz_shader_faces.py index ef4ce532b..78d5bd08c 100644 --- a/docs/experimental/viz_shader_faces.py +++ b/docs/experimental/viz_shader_faces.py @@ -1,6 +1,6 @@ import numpy as np -import vtk from viz_shader_canvas import glyph_dot +import vtk from fury import actor, window diff --git a/docs/experimental/viz_shader_frag_fract.py b/docs/experimental/viz_shader_frag_fract.py index 6abb857b1..337073a40 100644 --- a/docs/experimental/viz_shader_frag_fract.py +++ b/docs/experimental/viz_shader_frag_fract.py @@ -1,5 +1,4 @@ -""" -This simple example demonstrates how to use shaders to modify the fragments in +"""This simple example demonstrates how to use shaders to modify the fragments in your scene. We will use the AddShaderReplacement() function to modify the fragment shader with VTK's shader template system. diff --git a/docs/experimental/viz_shader_parallax_truchet.py b/docs/experimental/viz_shader_parallax_truchet.py index 3590e52f8..c2eecbf67 100644 --- a/docs/experimental/viz_shader_parallax_truchet.py +++ b/docs/experimental/viz_shader_parallax_truchet.py @@ -1,5 +1,5 @@ -import vtk from viz_shader_canvas import cube +import vtk from fury import window diff --git a/docs/experimental/viz_shader_perlin_noise.py b/docs/experimental/viz_shader_perlin_noise.py index e1959c577..f4726a17f 100644 --- a/docs/experimental/viz_shader_perlin_noise.py +++ b/docs/experimental/viz_shader_perlin_noise.py @@ -1,7 +1,7 @@ import numpy as np -import vtk from scipy.spatial import Delaunay from viz_shader_canvas import surface +import vtk from fury import window diff --git a/docs/experimental/viz_shader_sines_1.py b/docs/experimental/viz_shader_sines_1.py index 11df76469..c4ac860e6 100644 --- a/docs/experimental/viz_shader_sines_1.py +++ b/docs/experimental/viz_shader_sines_1.py @@ -1,5 +1,5 @@ -import vtk from viz_shader_canvas import cube +import vtk from fury import window diff --git a/docs/experimental/viz_shader_square.py b/docs/experimental/viz_shader_square.py index cbaa0662e..42968ab1f 100644 --- a/docs/experimental/viz_shader_square.py +++ b/docs/experimental/viz_shader_square.py @@ -1,6 +1,6 @@ import numpy as np -import vtk from viz_shader_canvas import cube, square +import vtk from fury import actor, window diff --git a/docs/experimental/viz_shader_texture.py b/docs/experimental/viz_shader_texture.py index 0cb29ae98..eb67b9f1a 100644 --- a/docs/experimental/viz_shader_texture.py +++ b/docs/experimental/viz_shader_texture.py @@ -1,6 +1,6 @@ import numpy as np -import vtk from viz_shader_canvas import cube +import vtk from fury import window from fury.utils import rgb_to_vtk diff --git a/docs/experimental/viz_shader_wave.py b/docs/experimental/viz_shader_wave.py index 46c7b2093..5020a3e99 100644 --- a/docs/experimental/viz_shader_wave.py +++ b/docs/experimental/viz_shader_wave.py @@ -1,5 +1,5 @@ -import vtk from viz_shader_canvas import cube, disk, rectangle, square +import vtk from fury import actor, window diff --git a/docs/source/_static/css/comp-soft.css b/docs/source/_static/css/comp-soft.css index 7198f8892..a0702c4ee 100644 --- a/docs/source/_static/css/comp-soft.css +++ b/docs/source/_static/css/comp-soft.css @@ -51,7 +51,7 @@ width: var(--s); min-width: var(--s); margin: var(--m) var(--m) 0; - height: var(--s); + height: var(--s); font-size:initial; border-radius: 50%; background: white; @@ -73,4 +73,4 @@ .comp-soft-image-holder img { max-width: 80%; -} \ No newline at end of file +} diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index fa3fbba39..fbdd89b63 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -161,3 +161,14 @@ html[data-theme="light"] { --pst-color-navbar-link-hover: rgb(77, 171, 207); } +/* Log:3/3/24 +With updating the pydata-theme, there was issue with the elements (They were underlined and colored by default, unlike previously), adding this code snippet helped fix the issue. +You wouldn't want the links to be colored as there is a dark mode now, which will conflict with the color that pydata is assigning*/ + +a, a:visited { + text-decoration: none; + color: inherit; +} +a:hover, a:active { + text-decoration: underline; +} diff --git a/docs/source/_static/css/navbar.css b/docs/source/_static/css/navbar.css index ab752a350..695c043f5 100644 --- a/docs/source/_static/css/navbar.css +++ b/docs/source/_static/css/navbar.css @@ -4,18 +4,23 @@ @import "../vendor/vars.css"; - /* Introduces the color change and highlight */ + +/* Log:3/3/24 +with updating pydata-theme, I had to change some styles because dark mode was introduced. */ + +.bd-header { + background-color: var(--colorPrimaryDark) !important; +} + +.theme-switch-button { + color: white !important; +} + .bd-header .navbar-nav li a.nav-link, .search-button, .navbar-item { - color: var(--colorPrimaryDark); -} - -.bd-header .navbar-nav li a.nav-link:hover, -.search-button:hover, -.navbar-item:hover { - color: var(--colorPrimaryLight); + color: white; } /* To remove highlight for logo text */ diff --git a/docs/source/_static/css/sponsors.css b/docs/source/_static/css/sponsors.css index 0ef833868..e6a71ab64 100644 --- a/docs/source/_static/css/sponsors.css +++ b/docs/source/_static/css/sponsors.css @@ -55,4 +55,4 @@ .cite-wrapper { margin: 20px 20px 0; } -} \ No newline at end of file +} diff --git a/docs/source/_static/css/util.css b/docs/source/_static/css/util.css index abdd6032e..38cfce855 100644 --- a/docs/source/_static/css/util.css +++ b/docs/source/_static/css/util.css @@ -2961,4 +2961,4 @@ iframe {border: none;} .g-m-rows-3 { grid-template-rows: repeat(3, 1fr); } -} \ No newline at end of file +} diff --git a/docs/source/_static/vendor/display-card.css b/docs/source/_static/vendor/display-card.css index 201269588..79dfa9945 100644 --- a/docs/source/_static/vendor/display-card.css +++ b/docs/source/_static/vendor/display-card.css @@ -120,7 +120,7 @@ .h-card__divider { margin-top: -10px; - + } .h-card__data-background { diff --git a/docs/source/_static/vendor/styles.css b/docs/source/_static/vendor/styles.css index 1587eef19..7773f27ec 100644 --- a/docs/source/_static/vendor/styles.css +++ b/docs/source/_static/vendor/styles.css @@ -123,6 +123,7 @@ em { font-size: 20px; margin-top: 8px; letter-spacing: 8px; + color: white !important; } #feature-card { diff --git a/docs/source/_static/vendor/vars.css b/docs/source/_static/vendor/vars.css index 219b81322..26551c752 100644 --- a/docs/source/_static/vendor/vars.css +++ b/docs/source/_static/vendor/vars.css @@ -1,5 +1,9 @@ -:root { - --fontFamily: {{ .Site.Params.font.name }}; +/* Log:3/3/24 +With the pydata-theme update, the {{ .Site.Params.font.name }} doesn't work anymore(not quite sure why), so I had to add the font-family to the root element. This helped restore back to the original font. + */ + + :root { + --fontFamily: "Helvetica Neue", sans-serif; --colorPrimaryDark: rgb(153, 0, 0); --colorPrimary: rgb(77, 119, 207); diff --git a/docs/source/_static/versions_switcher.json b/docs/source/_static/versions_switcher.json index 1ba7ed4e6..bf8a8f2c8 100644 --- a/docs/source/_static/versions_switcher.json +++ b/docs/source/_static/versions_switcher.json @@ -5,8 +5,13 @@ "url": "https://fury.gl/dev/index.html" }, { - "name": "v0.9.x (stable)", + "name": "v0.10.x (stable)", "version": "stable", + "url": "https://fury.gl/v0.10.x/index.html" + }, + { + "name": "v0.9.x", + "version": "v0.9.x", "url": "https://fury.gl/v0.9.x/index.html" }, { diff --git a/docs/source/_templates/custom-title.html b/docs/source/_templates/custom-title.html index 77f6eca10..88a99deca 100644 --- a/docs/source/_templates/custom-title.html +++ b/docs/source/_templates/custom-title.html @@ -2,6 +2,6 @@ - + diff --git a/docs/source/_templates/home.html b/docs/source/_templates/home.html index d40f65fb0..a5601ed88 100644 --- a/docs/source/_templates/home.html +++ b/docs/source/_templates/home.html @@ -228,7 +228,7 @@

Engineering

Engineering - +
@@ -237,7 +237,7 @@

Physics

Physics - +
@@ -246,7 +246,7 @@

Chemistry

Chemistry - +
@@ -255,7 +255,7 @@

Astronomy

Chemistry - +
@@ -264,7 +264,7 @@

Aerospace

Aerospace - +
@@ -273,7 +273,7 @@

Biology

Biology - +
@@ -282,7 +282,7 @@

Data Science

Data Science - +
@@ -291,7 +291,7 @@

Network Science

Network Science - +
@@ -301,7 +301,7 @@

Mathematics

Mathematics - +
@@ -495,37 +495,37 @@
With FURY, it becomes simple to produce multipl physics domino - +
physics bricks - +
physic collision - +
brownian motion - +
Em wave - +
Helical path - +
diff --git a/docs/source/conf.py b/docs/source/conf.py index 8680badcf..6af7ae93f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,46 +17,46 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +from datetime import datetime import os import re import sys -from datetime import datetime # Add current path -sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath(".")) # Add doc in path for finding tutorial and examples -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath("../..")) # Add custom extensions -sys.path.insert(0, os.path.abspath('./ext')) +sys.path.insert(0, os.path.abspath("./ext")) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # -needs_sphinx = '4.3' +needs_sphinx = "4.3" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.githubpages', - 'sphinx.ext.intersphinx', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'IPython.sphinxext.ipython_directive', - 'IPython.sphinxext.ipython_console_highlighting', - 'matplotlib.sphinxext.plot_directive', - 'sphinx_copybutton', - 'ext.prepare_gallery', - 'sphinx_gallery.gen_gallery', - 'ext.build_modref_templates', - 'ext.github', - 'ext.github_tools', - 'ext.rstjinja', - 'ablog', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.githubpages", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "IPython.sphinxext.ipython_directive", + "IPython.sphinxext.ipython_console_highlighting", + "matplotlib.sphinxext.plot_directive", + "sphinx_copybutton", + "ext.prepare_gallery", + "sphinx_gallery.gen_gallery", + "ext.build_modref_templates", + "ext.github", + "ext.github_tools", + "ext.rstjinja", + "ablog", ] # Configuration options for plot_directive. See: @@ -72,7 +72,7 @@ # import ablog templates_path = [ - '_templates', + "_templates", # ablog.get_html_templates_path(), ] @@ -80,15 +80,15 @@ # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'FURY' -copyright = '2018-{0}, FURY'.format(datetime.now().year) -author = 'FURY' +project = "FURY" +copyright = "2018-{0}, FURY".format(datetime.now().year) +author = "FURY" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -106,7 +106,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -114,7 +114,7 @@ exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -158,53 +158,56 @@ # "version_dropdown": True, # "version_json": "_static/versions.json", # } -import pydata_sphinx_theme -html_theme = 'pydata_sphinx_theme' +html_theme = "pydata_sphinx_theme" # Define the json_url for our version switcher. -json_url = 'https://fury.gl/latest/_static/versions_switcher.json' +json_url = "https://fury.gl/latest/_static/versions_switcher.json" -if 'dev' in release: - version_match = 'latest' +if "dev" in release: + version_match = "latest" # We want to keep the relative reference if we are in dev mode # but we want the whole url if we are effectively in a released version - json_url = '/_static/versions_switcher.json' + json_url = "/_static/versions_switcher.json" else: - version_match = 'v' + release + version_match = "v" + release # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} + +# Log:3/3/24 ~ footer_items is deprecated, using new keys for footer. html_theme_options = { - 'navigation_depth': 1, + "navigation_depth": 1, + "navigation_with_keys": True, # "logo_link": 'index.html', - 'navbar_start': ['custom-title.html'], - 'navbar_center': '', - 'navbar_end': 'custom-navbar.html', - 'footer_items': ['custom-footer.html'], - 'switcher': { - 'json_url': json_url, - 'version_match': version_match, + "navbar_start": ["custom-title.html"], + "navbar_center": ["custom-navbar.html"], + "footer_start": ["custom-footer.html"], + "footer_center": "", + "footer_end": "", + "switcher": { + "json_url": json_url, + "version_match": version_match, }, } -html_additional_pages = {'video': 'home-video-page.html', 'index': 'home.html'} +html_additional_pages = {"video": "home-video-page.html", "index": "home.html"} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] -html_css_files = ['css/custom.css', 'vendor/fonts.css'] +html_css_files = ["css/custom.css", "vendor/fonts.css"] # html_baseurl = os.environ.get("SPHINX_HTML_BASE_URL", "http://127.0.0.1:8000/") -html_logo = '_static/images/logo.svg' +html_logo = "_static/images/logo.svg" -html_favicon = '_static/images/logo.ico' +html_favicon = "_static/images/logo.ico" # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -218,42 +221,73 @@ # 'versions.html', # ] # } + +# Log:3/3/24 ~ search-field is not necessary in sidebar anymore, +# it is in the Navbar. Also with this update it can be easily accessed, +# with the shortcut, which was not working previously. html_sidebars = { # "**": ["search-field", 'globaltoc.html',"sidebar-nav-bs"] - '**': ['search-field', 'globaltoc.html'] + # '**': ['search-field', 'globaltoc.html'] + "**": ["globaltoc.html"] } + # html_sidebars = { # "**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"], # } # html_sidebars = { -# "introduction/**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"], -# "getting_started": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"], -# "auto_examples/**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"], -# "auto_tutorials/**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"], -# "references/**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"], +# "introduction/**": [ +# "logo-text.html", +# "globaltoc.html", +# "localtoc.html", +# "searchbox.html", +# ], +# "getting_started": [ +# "logo-text.html", +# "globaltoc.html", +# "localtoc.html", +# "searchbox.html", +# ], +# "auto_examples/**": [ +# "logo-text.html", +# "globaltoc.html", +# "localtoc.html", +# "searchbox.html", +# ], +# "auto_tutorials/**": [ +# "logo-text.html", +# "globaltoc.html", +# "localtoc.html", +# "searchbox.html", +# ], +# "references/**": [ +# "logo-text.html", +# "globaltoc.html", +# "localtoc.html", +# "searchbox.html", +# ], # "blog": ["categories.html", "archives.html"], # "blog/**": ["categories.html", "archives.html"], # "posts/**": ["postcard.html"], # } # ghissue config -github_project_url = 'https://github.com/fury-gl/fury' +github_project_url = "https://github.com/fury-gl/fury" import github_tools as ght -all_versions = ght.get_all_versions(ignore='micro') +all_versions = ght.get_all_versions(ignore="micro") html_context = { - 'all_versions': all_versions, - 'versions_list': ['dev', 'latest'] + all_versions, - 'basic_stats': ght.fetch_basic_stats(), - 'contributors': ght.fetch_contributor_stats(), - 'default_mode': 'light', + "all_versions": all_versions, + "versions_list": ["dev", "latest"] + all_versions, + "basic_stats": ght.fetch_basic_stats(), + "contributors": ght.fetch_contributor_stats(), + "default_mode": "light", } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'fury' +htmlhelp_basename = "fury" # -- Options for LaTeX output --------------------------------------------- @@ -277,7 +311,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'fury.tex', 'FURY Documentation', 'Contributors', 'manual'), + (master_doc, "fury.tex", "FURY Documentation", "Contributors", "manual"), ] @@ -285,7 +319,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, 'fury', 'FURY Documentation', [author], 1)] +man_pages = [(master_doc, "fury", "FURY Documentation", [author], 1)] # -- Options for sphinx gallery ------------------------------------------- from scrap import ImageFileScraper @@ -293,28 +327,28 @@ sc = ImageFileScraper() sphinx_gallery_conf = { - 'doc_module': ('fury',), + "doc_module": ("fury",), # path to your examples scripts - 'examples_dirs': ['../examples_revamped'], + "examples_dirs": ["../examples_revamped"], # path where to save gallery generated examples - 'gallery_dirs': ['auto_examples'], - 'image_scrapers': (sc), - 'backreferences_dir': 'api', - 'reference_url': { - 'fury': None, + "gallery_dirs": ["auto_examples"], + "image_scrapers": (sc), + "backreferences_dir": "api", + "reference_url": { + "fury": None, }, - 'filename_pattern': re.escape(os.sep), - 'plot_gallery': "'True'", + "filename_pattern": re.escape(os.sep), + "plot_gallery": "'True'", } # -- Options for Blog ------------------------------------------- -blog_baseurl = 'https://fury.gl/' +blog_baseurl = "https://fury.gl/" blog_feed_fulltext = True blog_feed_length = 10 blog_feed_archives = True blog_authors = { - 'skoudoro': ('Serge Koudoro', 'https://github.com/skoudoro'), + "skoudoro": ("Serge Koudoro", "https://github.com/skoudoro"), } # -- Options for Texinfo output ------------------------------------------- @@ -325,25 +359,22 @@ texinfo_documents = [ ( master_doc, - 'fury', - 'FURY Documentation', + "fury", + "FURY Documentation", author, - 'fury', - 'Free Unified Rendering in Python', - 'Miscellaneous', + "fury", + "Free Unified Rendering in Python", + "Miscellaneous", ), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'numpy': ('https://numpy.org/doc/stable/', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/', None), - 'pandas': ('https://pandas.pydata.org/docs/', None), - 'matplotlib': ('https://matplotlib.org/stable/', None), - 'dipy': ( - 'https://dipy.org/documentation/latest', - 'https://dipy.org/documentation/latest/objects.inv/', - ), - 'scikit-learn': ('https://scikit-learn.org/stable/', None), + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "pandas": ("https://pandas.pydata.org/docs/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "dipy": ("https://docs.dipy.org/stable", None), + "scikit-learn": ("https://scikit-learn.org/stable/", None), } diff --git a/docs/source/ext/apigen.py b/docs/source/ext/apigen.py index 03348a660..6a3cb0433 100644 --- a/docs/source/ext/apigen.py +++ b/docs/source/ext/apigen.py @@ -1,5 +1,4 @@ -""" -Attempt to generate templates for module reference with Sphinx. +"""Attempt to generate templates for module reference with Sphinx. To include extension modules, first identify them as valid in the ``_uri2path`` method, then handle them in the ``_parse_module_with_import`` @@ -15,13 +14,14 @@ This is a modified version of a script originally shipped with the PyMVPA project, then adapted for use first in NIPY and then in skimage. PyMVPA is an MIT-licensed project. + """ # Stdlib imports import ast +from importlib import import_module import os import re -from importlib import import_module # suppress print statements (warnings for empty files) DEBUG = True @@ -29,15 +29,16 @@ class ApiDocWriter: """Class for automatic detection and parsing of API docs - to Sphinx-parsable reST format.""" + to Sphinx-parsable reST format. + """ # only separating first two levels - rst_section_levels = ['*', '=', '-', '~', '^'] + rst_section_levels = ["*", "=", "-", "~", "^"] def __init__( self, package_name, - rst_extension='.txt', + rst_extension=".txt", package_skip_patterns=None, module_skip_patterns=None, object_skip_patterns=None, @@ -76,13 +77,13 @@ def __init__( """ if package_skip_patterns is None: - package_skip_patterns = ['\\.tests$'] + package_skip_patterns = ["\\.tests$"] if module_skip_patterns is None: - module_skip_patterns = ['\\.setup$', '\\._'] + module_skip_patterns = ["\\.setup$", "\\._"] if object_skip_patterns is None: object_skip_patterns = [] - self.root_path = '' + self.root_path = "" self.written_modules = None self._package_name = None self.package_name = package_name @@ -115,14 +116,14 @@ def set_package_name(self, package_name): self.written_modules = None package_name = property( - get_package_name, set_package_name, None, 'get/set package_name' + get_package_name, set_package_name, None, "get/set package_name" ) @staticmethod def _import(name): """Import namespace package.""" mod = __import__(name) - components = name.split('.') + components = name.split(".") for comp in components[1:]: mod = getattr(mod, comp) return mod @@ -140,10 +141,10 @@ def _get_object_name(line): 'Klass' """ - name = line.split()[1].split('(')[0].strip() + name = line.split()[1].split("(")[0].strip() # in case we have classes which are not derived from object # ie. old style classes - return name.rstrip(':') + return name.rstrip(":") def _uri2path(self, uri): """Convert uri to absolute filepath. @@ -174,36 +175,36 @@ def _uri2path(self, uri): """ if uri == self.package_name: - return os.path.join(self.root_path, '__init__.py') - path = uri.replace(self.package_name + '.', '') - path = path.replace('.', os.path.sep) + return os.path.join(self.root_path, "__init__.py") + path = uri.replace(self.package_name + ".", "") + path = path.replace(".", os.path.sep) path = os.path.join(self.root_path, path) # XXX maybe check for extensions as well? - if os.path.exists(path + '.py'): # file - path += '.py' - elif os.path.exists(os.path.join(path, '__init__.py')): - path = os.path.join(path, '__init__.py') + if os.path.exists(path + ".py"): # file + path += ".py" + elif os.path.exists(os.path.join(path, "__init__.py")): + path = os.path.join(path, "__init__.py") else: return None return path def _path2uri(self, dirpath): """Convert directory path to uri.""" - package_dir = self.package_name.replace('.', os.path.sep) + package_dir = self.package_name.replace(".", os.path.sep) relpath = dirpath.replace(self.root_path, package_dir) if relpath.startswith(os.path.sep): relpath = relpath[1:] - return relpath.replace(os.path.sep, '.') + return relpath.replace(os.path.sep, ".") def _parse_module(self, uri): """Parse module defined in *uri*.""" filename = self._uri2path(uri) if filename is None: - print(filename, 'erk') + print(filename, "erk") # nothing that we could handle here. return ([], []) - f = open(filename, 'rt') + f = open(filename, "rt") functions, classes = self._parse_lines(f) f.close() return functions, classes @@ -226,7 +227,7 @@ def _parse_module_with_import(self, uri): """ mod = import_module(uri) - patterns = '(?:{0})'.format('|'.join(self.object_skip_patterns)) + patterns = "(?:{0})".format("|".join(self.object_skip_patterns)) pat = re.compile(patterns) with open(mod.__file__) as fi: @@ -235,17 +236,16 @@ def _parse_module_with_import(self, uri): functions = [] classes = [] for n in node.body: - - if not hasattr(n, 'name'): + if not hasattr(n, "name"): if not isinstance(n, ast.Assign): continue if isinstance(n, ast.ClassDef): - if n.name.startswith('_') or pat.search(n.name): + if n.name.startswith("_") or pat.search(n.name): continue classes.append(n.name) elif isinstance(n, ast.FunctionDef): - if n.name.startswith('_') or pat.search(n.name): + if n.name.startswith("_") or pat.search(n.name): continue functions.append(n.name) # Specific condition for vtk and fury @@ -255,7 +255,7 @@ def _parse_module_with_import(self, uri): if isinstance(n.targets[0], ast.Tuple): continue functions.append(n.targets[0].id) - elif hasattr(n.value, 'attr') and n.value.attr.startswith('vtk'): + elif hasattr(n.value, "attr") and n.value.attr.startswith("vtk"): classes.append(n.targets[0].id) except Exception: print(mod.__file__) @@ -269,15 +269,15 @@ def _parse_lines(self, linesource): functions = [] classes = [] for line in linesource: - if line.startswith('def ') and line.count('('): + if line.startswith("def ") and line.count("("): # exclude private stuff name = self._get_object_name(line) - if not name.startswith('_'): + if not name.startswith("_"): functions.append(name) - elif line.startswith('class '): + elif line.startswith("class "): # exclude private stuff name = self._get_object_name(line) - if not name.startswith('_'): + if not name.startswith("_"): classes.append(name) else: pass @@ -304,54 +304,54 @@ def generate_api_doc(self, uri): # get the names of all classes and functions functions, classes = self._parse_module_with_import(uri) if not len(functions) and not len(classes) and DEBUG: - print('WARNING: Empty -', uri) # dbg + print("WARNING: Empty -", uri) # dbg # Make a shorter version of the uri that omits the package name for # titles - uri_short = re.sub(r'^%s\.' % self.package_name, '', uri) + uri_short = re.sub(r"^%s\." % self.package_name, "", uri) - head = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' - body = '' + head = ".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n" + body = "" # Set the chapter title to read 'module' for all modules except for the # main packages - if '.' in uri_short: - title = 'Module: :mod:`' + uri_short + '`' - head += title + '\n' + self.rst_section_levels[2] * len(title) + if "." in uri_short: + title = "Module: :mod:`" + uri_short + "`" + head += title + "\n" + self.rst_section_levels[2] * len(title) else: - title = ':mod:`' + uri_short + '`' - head += title + '\n' + self.rst_section_levels[1] * len(title) + title = ":mod:`" + uri_short + "`" + head += title + "\n" + self.rst_section_levels[1] * len(title) - head += '\n.. automodule:: ' + uri + '\n' - head += '\n.. currentmodule:: ' + uri + '\n' - body += '\n.. currentmodule:: ' + uri + '\n\n' + head += "\n.. automodule:: " + uri + "\n" + head += "\n.. currentmodule:: " + uri + "\n" + body += "\n.. currentmodule:: " + uri + "\n\n" for c in classes: body += ( - '\n:class:`' + "\n:class:`" + c - + '`\n' + + "`\n" + self.rst_section_levels[3] * (len(c) + 9) - + '\n\n' + + "\n\n" ) - body += '\n.. autoclass:: ' + c + '\n' + body += "\n.. autoclass:: " + c + "\n" # must NOT exclude from index to keep cross-refs working body += ( - ' :members:\n' - ' :undoc-members:\n' - ' :show-inheritance:\n' - '\n' - ' .. automethod:: __init__\n\n' + " :members:\n" + " :undoc-members:\n" + " :show-inheritance:\n" + "\n" + " .. automethod:: __init__\n\n" ) - head += '.. autosummary::\n\n' + head += ".. autosummary::\n\n" for f in classes + functions: - head += ' ' + f + '\n' - head += '\n' + head += " " + f + "\n" + head += "\n" for f in functions: # must NOT exclude from index to keep cross-refs working - body += f + '\n' - body += self.rst_section_levels[3] * len(f) + '\n' - body += '\n.. autofunction:: ' + f + '\n\n' + body += f + "\n" + body += self.rst_section_levels[3] * len(f) + "\n" + body += "\n.. autofunction:: " + f + "\n\n" return head, body @@ -377,9 +377,9 @@ def _survives_exclude(self, matchstr, match_type): False """ - if match_type == 'module': + if match_type == "module": patterns = self.module_skip_patterns - elif match_type == 'package': + elif match_type == "package": patterns = self.package_skip_patterns else: raise ValueError('Cannot interpret match type "%s"' % match_type) @@ -389,7 +389,7 @@ def _survives_exclude(self, matchstr, match_type): matchstr = matchstr[L:] for pat in patterns: try: - pat.search + _ = pat.search except AttributeError: pat = re.compile(pat) if pat.search(matchstr): @@ -432,13 +432,13 @@ def discover_modules(self): filenames = [ f[:-3] for f in filenames - if f.endswith('.py') and not f.startswith('__init__') + if f.endswith(".py") and not f.startswith("__init__") ] for subpkg_name in dirnames + filenames: - package_uri = '.'.join((root_uri, subpkg_name)) + package_uri = ".".join((root_uri, subpkg_name)) package_path = self._uri2path(package_uri) - if package_path and self._survives_exclude(package_uri, 'package'): + if package_path and self._survives_exclude(package_uri, "package"): modules.append(package_uri) return sorted(modules) @@ -446,7 +446,7 @@ def discover_modules(self): def write_modules_api(self, modules, outdir): # upper-level modules ulms = [ - '.'.join(m.split('.')[:2]) if m.count('.') >= 1 else m.split('.')[0] + ".".join(m.split(".")[:2]) if m.count(".") >= 1 else m.split(".")[0] for m in modules ] @@ -463,12 +463,12 @@ def write_modules_api(self, modules, outdir): written_modules = [] for ulm, mods in module_by_ulm.items(): - print('Generating docs for %s:' % ulm) + print("Generating docs for %s:" % ulm) document_head = [] document_body = [] for m in mods: - print(' -> ' + m) + print(" -> " + m) head, body = self.generate_api_doc(m) document_head.append(head) @@ -476,7 +476,7 @@ def write_modules_api(self, modules, outdir): out_module = ulm + self.rst_extension outfile = os.path.join(outdir, out_module) - fileobj = open(outfile, 'wt') + fileobj = open(outfile, "wt") fileobj.writelines(document_head + document_body) fileobj.close() @@ -508,7 +508,7 @@ def write_api_docs(self, outdir): modules = self.discover_modules() self.write_modules_api(modules, outdir) - def write_index(self, outdir, froot='gen', relative_to=None): + def write_index(self, outdir, froot="gen", relative_to=None): """Make a reST API index file from written files. Parameters @@ -528,22 +528,22 @@ def write_index(self, outdir, froot='gen', relative_to=None): """ if self.written_modules is None: - raise ValueError('No modules written') + raise ValueError("No modules written") # Get full filename path path = os.path.join(outdir, froot + self.rst_extension) # Path written into index is relative to rootpath if relative_to is not None: - relpath = (outdir + os.path.sep).replace(relative_to + os.path.sep, '') + relpath = (outdir + os.path.sep).replace(relative_to + os.path.sep, "") else: relpath = outdir - idx = open(path, 'wt') + idx = open(path, "wt") w = idx.write - w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') + w(".. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n") - title = 'API Reference' - w(title + '\n') - w('=' * len(title) + '\n\n') - w('.. toctree::\n\n') + title = "API Reference" + w(title + "\n") + w("=" * len(title) + "\n\n") + w(".. toctree::\n\n") for f in self.written_modules: - w(' %s\n' % os.path.join(relpath, f)) + w(" %s\n" % os.path.join(relpath, f)) idx.close() diff --git a/docs/source/ext/build_modref_templates.py b/docs/source/ext/build_modref_templates.py index 4d5455551..2cba24f93 100755 --- a/docs/source/ext/build_modref_templates.py +++ b/docs/source/ext/build_modref_templates.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -"""Script to auto-generate our API docs. -""" +"""Script to auto-generate our API docs.""" + # stdlib imports -import sys from os.path import join as pjoin +import sys # local imports from apigen import ApiDocWriter @@ -15,55 +15,54 @@ def abort(error): - print('*WARNING* API documentation not generated: %s' % error) + print("*WARNING* API documentation not generated: %s" % error) exit() def generate_api_reference_rst( - app=None, package='fury', outdir='reference', defines=True + app=None, package="fury", outdir="reference", defines=True ): try: __import__(package) except ImportError: - abort('Can not import ' + package) + abort("Can not import " + package) module = sys.modules[package] installed_version = parse(module.__version__) - print('Generation API for {} v{}'.format(package, installed_version)) + print("Generation API for {} v{}".format(package, installed_version)) - docwriter = ApiDocWriter(package, rst_extension='.rst', other_defines=defines) + docwriter = ApiDocWriter(package, rst_extension=".rst", other_defines=defines) docwriter.package_skip_patterns += [ - r'.*test.*$', + r".*test.*$", # r'^\.utils.*', - r'\._version.*$', - r'\.interactor.*$', - r'\.optpkg.*$', + r"\._version.*$", + r"\.interactor.*$", + r"\.optpkg.*$", ] docwriter.object_skip_patterns += [ - r'.*FetcherError.*$', - r'.*urlopen.*', - r'.*add_callback.*', + r".*FetcherError.*$", + r".*urlopen.*", + r".*add_callback.*", ] if app is not None: outdir = pjoin(app.builder.srcdir, outdir) docwriter.write_api_docs(outdir) - docwriter.write_index(outdir, 'index', relative_to=outdir) - print('%d files written' % len(docwriter.written_modules)) + docwriter.write_index(outdir, "index", relative_to=outdir) + print("%d files written" % len(docwriter.written_modules)) def setup(app): """Setup sphinx extension for API reference generation.""" - - app.connect('builder-inited', generate_api_reference_rst) + app.connect("builder-inited", generate_api_reference_rst) # app.connect('build-finished', summarize_failing_examples) - metadata = {'parallel_read_safe': True, 'version': app.config.version} + metadata = {"parallel_read_safe": True, "version": app.config.version} return metadata -if __name__ == '__main__': +if __name__ == "__main__": package = sys.argv[1] outdir = sys.argv[2] try: @@ -71,7 +70,7 @@ def setup(app): except IndexError: other_defines = True else: - other_defines = other_defines in ('True', 'true', '1') + other_defines = other_defines in ("True", "true", "1") generate_api_reference_rst( app=None, package=package, outdir=outdir, defines=other_defines diff --git a/docs/source/ext/github.py b/docs/source/ext/github.py index a6df31f3c..374c840c4 100644 --- a/docs/source/ext/github.py +++ b/docs/source/ext/github.py @@ -30,30 +30,29 @@ def make_link_node(rawtext, app, type, slug, options): :param slug: ID of the thing to link to :param options: Options dictionary passed to role func. """ - try: base = app.config.github_project_url if not base: raise AttributeError - if not base.endswith('/'): - base += '/' + if not base.endswith("/"): + base += "/" except AttributeError as err: raise ValueError( - 'github_project_url configuration value is not set (%s)' % str(err) - ) + "github_project_url configuration value is not set (%s)" % str(err) + ) from err - ref = base + type + '/' + slug + '/' + ref = base + type + "/" + slug + "/" set_classes(options) - prefix = '#' - if type == 'pull': - prefix = 'PR ' + prefix + prefix = "#" + if type == "pull": + prefix = "PR " + prefix node = nodes.reference( rawtext, prefix + utils.unescape(slug), refuri=ref, **options ) return node -def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def ghissue_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Link to a GitHub issue. Returns 2 part tuple containing list of nodes to insert into the @@ -68,6 +67,10 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param options: Directive options for customization. :param content: The directive content for customization. """ + if options is None: + options = {} + + _ = content try: issue_num = int(text) @@ -75,7 +78,7 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): raise ValueError except ValueError: msg = inliner.reporter.error( - 'GitHub issue number must be a number greater than or equal to 1; ' + "GitHub issue number must be a number greater than or equal to 1; " '"%s" is invalid.' % text, line=lineno, ) @@ -83,10 +86,10 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): return [prb], [msg] app = inliner.document.settings.env.app # app.info('issue %r' % text) - if 'pull' in name.lower(): - category = 'pull' - elif 'issue' in name.lower(): - category = 'issues' + if "pull" in name.lower(): + category = "pull" + elif "issue" in name.lower(): + category = "issues" else: msg = inliner.reporter.error( 'GitHub roles include "ghpull" and "ghissue", ' '"%s" is invalid.' % name, @@ -98,7 +101,7 @@ def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]): return [node], [] -def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def ghuser_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Link to a GitHub user. Returns 2 part tuple containing list of nodes to insert into the @@ -113,14 +116,20 @@ def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param options: Directive options for customization. :param content: The directive content for customization. """ + if options is None: + options = {} + + _ = name + _ = lineno + _ = content _ = inliner.document.settings.env.app # app.info('user link %r' % text) - ref = 'https://www.github.com/' + text + ref = "https://www.github.com/" + text node = nodes.reference(rawtext, text, refuri=ref, **options) return [node], [] -def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): +def ghcommit_role(name, rawtext, text, lineno, inliner, options=None, content=None): """Link to a GitHub commit. Returns 2 part tuple containing list of nodes to insert into the @@ -135,18 +144,24 @@ def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): :param options: Directive options for customization. :param content: The directive content for customization. """ + if options is None: + options = {} + + _ = name + _ = lineno + _ = content app = inliner.document.settings.env.app # app.info('user link %r' % text) try: base = app.config.github_project_url if not base: raise AttributeError - if not base.endswith('/'): - base += '/' + if not base.endswith("/"): + base += "/" except AttributeError as err: raise ValueError( - 'github_project_url configuration value is not set (%s)' % str(err) - ) + "github_project_url configuration value is not set (%s)" % str(err) + ) from err ref = base + text node = nodes.reference(rawtext, text[:6], refuri=ref, **options) @@ -161,11 +176,11 @@ def setup(app): from sphinx.util import logging logger = logging.getLogger(__name__) - logger.info('Initializing GitHub plugin') + logger.info("Initializing GitHub plugin") # app.info('Initializing GitHub plugin') - app.add_role('ghissue', ghissue_role) - app.add_role('ghpull', ghissue_role) - app.add_role('ghuser', ghuser_role) - app.add_role('ghcommit', ghcommit_role) - app.add_config_value('github_project_url', None, 'env') + app.add_role("ghissue", ghissue_role) + app.add_role("ghpull", ghissue_role) + app.add_role("ghuser", ghuser_role) + app.add_role("ghcommit", ghcommit_role) + app.add_config_value("github_project_url", None, "env") return diff --git a/docs/source/ext/github_tools.py b/docs/source/ext/github_tools.py index 8e05caca7..fb6cdccc1 100644 --- a/docs/source/ext/github_tools.py +++ b/docs/source/ext/github_tools.py @@ -3,14 +3,15 @@ Taken from ipython """ + import argparse +from datetime import datetime, timedelta import json import operator import os import re -import sys -from datetime import datetime, timedelta from subprocess import check_output +import sys from urllib.request import Request, urlopen # ---------------------------------------------------------------------------- @@ -22,15 +23,15 @@ # Globals # ---------------------------------------------------------------------------- -ISO8601 = '%Y-%m-%dT%H:%M:%SZ' +ISO8601 = "%Y-%m-%dT%H:%M:%SZ" PER_PAGE = 100 -element_pat = re.compile(r'<(.+?)>') +element_pat = re.compile(r"<(.+?)>") rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]') LAST_RELEASE = datetime(2015, 3, 18) -CONTRIBUTORS_FILE = 'contributors.json' -GH_TOKEN = os.environ.get('GH_TOKEN', '') +CONTRIBUTORS_FILE = "contributors.json" +GH_TOKEN = os.environ.get("GH_TOKEN", "") # ---------------------------------------------------------------------------- @@ -49,26 +50,26 @@ def fetch_url(url): """ req = Request(url) if GH_TOKEN: - req.add_header('Authorization', 'token {0}'.format(GH_TOKEN)) + req.add_header("Authorization", "token {0}".format(GH_TOKEN)) try: - print('fetching %s' % url, file=sys.stderr) + print("fetching %s" % url, file=sys.stderr) # url = Request(url, # headers={'Accept': 'application/vnd.github.v3+json', # 'User-agent': 'Defined'}) - if not url.lower().startswith('http'): - msg = 'Please make sure you use http/https connection' + if not url.lower().startswith("http"): + msg = "Please make sure you use http/https connection" raise ValueError(msg) f = urlopen(req) except Exception as e: print(e) - print('return Empty data', file=sys.stderr) + print("return Empty data", file=sys.stderr) return {} return f def parse_link_header(headers): - link_s = headers.get('link', '') + link_s = headers.get("link", "") urls = element_pat.findall(link_s) rels = rel_pat.findall(link_s) d = {} @@ -97,14 +98,14 @@ def get_paged_request(url): continue results.extend(json.load(f)) links = parse_link_header(f.headers) - url = links.get('next') + url = links.get("next") return results -def get_issues(project='fury-gl/fury', state='closed', pulls=False): +def get_issues(project="fury-gl/fury", state="closed", pulls=False): """Get a list of the issues from the Github API.""" - which = 'pulls' if pulls else 'issues' - url = 'https://api.github.com/repos/%s/%s?state=%s&per_page=%i' % ( + which = "pulls" if pulls else "issues" + url = "https://api.github.com/repos/%s/%s?state=%s&per_page=%i" % ( project, which, state, @@ -113,13 +114,13 @@ def get_issues(project='fury-gl/fury', state='closed', pulls=False): return get_paged_request(url) -def get_tags(project='fury-gl/fury'): +def get_tags(project="fury-gl/fury"): """Get a list of the tags from the Github API.""" - url = 'https://api.github.com/repos/{0}/tags'.format(project) + url = "https://api.github.com/repos/{0}/tags".format(project) return get_paged_request(url) -def fetch_basic_stats(project='fury-gl/fury'): +def fetch_basic_stats(project="fury-gl/fury"): """Fetch the basic stats. Returns @@ -137,24 +138,24 @@ def fetch_basic_stats(project='fury-gl/fury'): """ desired_keys = [ - 'stargazers_count', - 'stargazers_url', - 'watchers_count', - 'watchers_url', - 'forks_count', - 'forks_url', - 'open_issues', - 'issues_url', - 'subscribers_count', - 'subscribers_url', + "stargazers_count", + "stargazers_url", + "watchers_count", + "watchers_url", + "forks_count", + "forks_url", + "open_issues", + "issues_url", + "subscribers_count", + "subscribers_url", ] - url = 'https://api.github.com/repos/{0}'.format(project) + url = "https://api.github.com/repos/{0}".format(project) r_json = get_json_from_url(url) - basic_stats = dict((k, r_json[k]) for k in desired_keys if k in r_json) + basic_stats = {k: r_json[k] for k in desired_keys if k in r_json} return basic_stats -def fetch_contributor_stats(project='fury-gl/fury'): +def fetch_contributor_stats(project="fury-gl/fury"): """Fetch stats of contributors. Returns @@ -183,67 +184,69 @@ def fetch_contributor_stats(project='fury-gl/fury'): } """ - url = 'https://api.github.com/repos/{0}/stats/contributors'.format(project) + url = "https://api.github.com/repos/{0}/stats/contributors".format(project) r_json = get_json_from_url(url) contributor_stats = {} - contributor_stats['total_contributors'] = len(r_json) - contributor_stats['contributors'] = [] + contributor_stats["total_contributors"] = len(r_json) + contributor_stats["contributors"] = [] cumulative_commits = 0 - desired_keys = ['login', 'avatar_url', 'html_url'] - with open(os.path.join(os.path.dirname(__file__), '..', CONTRIBUTORS_FILE)) as f: + desired_keys = ["login", "avatar_url", "html_url"] + with open(os.path.join(os.path.dirname(__file__), "..", CONTRIBUTORS_FILE)) as f: extra_info = json.load(f) - extra_info = extra_info['team'] + extra_info['core_team'] + extra_info = extra_info["team"] + extra_info["core_team"] for contributor in r_json: contributor_dict = dict( - (k, contributor['author'][k]) - for k in desired_keys - if k in contributor['author'] + { + (k, contributor["author"][k]) + for k in desired_keys + if k in contributor["author"] + } ) # check if "author" is null - if not contributor_dict['login']: + if not contributor_dict["login"]: continue # Replace key name - contributor_dict['username'] = usrname = contributor_dict.pop('login') - contributor_dict['nb_commits'] = contributor['total'] + contributor_dict["username"] = usrname = contributor_dict.pop("login") + contributor_dict["nb_commits"] = contributor["total"] # Add extra information like fullname and affiliation l_extra_info = [ - e for e in extra_info if e['username'].lower() == usrname.lower() + e for e in extra_info if e["username"].lower() == usrname.lower() ] l_extra_info = l_extra_info[0] if l_extra_info else {} contributor_dict.update(l_extra_info) # Update total commits - cumulative_commits += contributor_dict['nb_commits'] + cumulative_commits += contributor_dict["nb_commits"] total_additions = 0 total_deletions = 0 - for week in contributor['weeks']: - total_additions += week['a'] - total_deletions += week['d'] + for week in contributor["weeks"]: + total_additions += week["a"] + total_deletions += week["d"] - contributor_dict['total_additions'] = total_additions - contributor_dict['total_deletions'] = total_deletions + contributor_dict["total_additions"] = total_additions + contributor_dict["total_deletions"] = total_deletions # contributor_dict["weekly_commits"] = contributor["weeks"] - contributor_stats['contributors'].insert(0, contributor_dict) + contributor_stats["contributors"].insert(0, contributor_dict) - contributor_stats['contributors'] = sorted( - contributor_stats['contributors'], - key=lambda x: x.get('nb_commits'), + contributor_stats["contributors"] = sorted( + contributor_stats["contributors"], + key=lambda x: x.get("nb_commits"), reverse=True, ) - contributor_stats['total_commits'] = cumulative_commits + contributor_stats["total_commits"] = cumulative_commits return contributor_stats -def cumulative_contributors(project='fury-gl/fury', show=True): +def cumulative_contributors(project="fury-gl/fury", show=True): """Calculate total contributors as new contributors join with time. Parameters @@ -260,14 +263,14 @@ def cumulative_contributors(project='fury-gl/fury', show=True): ] """ - url = 'https://api.github.com/repos/{0}/stats/contributors'.format(project) + url = "https://api.github.com/repos/{0}/stats/contributors".format(project) r_json = get_json_from_url(url) contributors_join_date = {} for contributor in r_json: - for week in contributor['weeks']: - if week['c'] > 0: - join_date = week['w'] + for week in contributor["weeks"]: + if week["c"] > 0: + join_date = week["w"] if join_date not in contributors_join_date: contributors_join_date[join_date] = 0 contributors_join_date[join_date] += 1 @@ -293,30 +296,30 @@ def cumulative_contributors(project='fury-gl/fury', show=True): years_labels = [] for y in years_ticks: date = datetime.utcfromtimestamp(int(y)) - date_str = 'Q{} - '.format((date.month - 1) // 3 + 1) - date_str += date.strftime('%Y') + date_str = "Q{} - ".format((date.month - 1) // 3 + 1) + date_str += date.strftime("%Y") years_labels.append(date_str) - plt.fill_between(years, c_cum, color='skyblue', alpha=0.4) - plt.plot(years, c_cum, color='Slateblue', alpha=0.6, linewidth=2) + plt.fill_between(years, c_cum, color="skyblue", alpha=0.4) + plt.plot(years, c_cum, color="Slateblue", alpha=0.6, linewidth=2) plt.tick_params(labelsize=12) plt.xticks(years_ticks, years_labels, rotation=45, fontsize=8) plt.yticks(fontsize=8) - plt.xlabel('Date', size=12) - plt.ylabel('Contributors', size=12) + plt.xlabel("Date", size=12) + plt.ylabel("Contributors", size=12) plt.ylim(bottom=0) plt.grid(True) plt.legend( [ - mpatches.Patch(color='skyblue'), + mpatches.Patch(color="skyblue"), ], [ - 'Contributors', + "Contributors", ], bbox_to_anchor=(0.5, 1.1), - loc='upper center', + loc="upper center", ) - plt.savefig('fury_cumulative_contributors.png', dpi=150) + plt.savefig("fury_cumulative_contributors.png", dpi=150) plt.show() return cumulative_list @@ -334,43 +337,43 @@ def issues2dict(issues): """Convert a list of issues to a dict, keyed by issue number.""" idict = {} for i in issues: - idict[i['number']] = i + idict[i["number"]] = i return idict def is_pull_request(issue): """Return True if the given issue is a pull request.""" - return 'pull_request_url' in issue + return "pull_request_url" in issue -def issues_closed_since(period=LAST_RELEASE, project='fury-gl/fury', pulls=False): +def issues_closed_since(period=LAST_RELEASE, project="fury-gl/fury", pulls=False): """Get all issues closed since a particular point in time. period can either be a datetime object, or a timedelta object. In the latter case, it is used as a time before the present. """ - which = 'pulls' if pulls else 'issues' + which = "pulls" if pulls else "issues" if isinstance(period, timedelta): period = datetime.now() - period url = ( - 'https://api.github.com/repos/%s/%s?state=closed&sort=updated&' - 'since=%s&per_page=%i' + "https://api.github.com/repos/%s/%s?state=closed&sort=updated&" + "since=%s&per_page=%i" ) % (project, which, period.strftime(ISO8601), PER_PAGE) allclosed = get_paged_request(url) # allclosed = get_issues(project=project, state='closed', pulls=pulls, # since=period) - filtered = [i for i in allclosed if _parse_datetime(i['closed_at']) > period] + filtered = [i for i in allclosed if _parse_datetime(i["closed_at"]) > period] # exclude rejected PRs if pulls: - filtered = [pr for pr in filtered if pr['merged_at']] + filtered = [pr for pr in filtered if pr["merged_at"]] return filtered -def sorted_by_field(issues, field='closed_at', reverse=False): +def sorted_by_field(issues, field="closed_at", reverse=False): """Return a list of issues sorted by closing date date.""" return sorted(issues, key=lambda i: i[field], reverse=reverse) @@ -380,14 +383,14 @@ def report(issues, show_urls=False): # titles may have unicode in them, so we must encode everything below if show_urls: for i in issues: - role = 'ghpull' if 'merged_at' in i else 'ghissue' - print('* :%s:`%d`: %s' % (role, i['number'], i['title'])) + role = "ghpull" if "merged_at" in i else "ghissue" + print("* :%s:`%d`: %s" % (role, i["number"], i["title"])) else: for i in issues: - print('* %d: %s' % (i['number'], i['title'])) + print("* %d: %s" % (i["number"], i["title"])) -def get_all_versions(ignore='', project='fury-gl/fury'): +def get_all_versions(ignore="", project="fury-gl/fury"): """Return all releases version. Parameters @@ -406,47 +409,47 @@ def get_all_versions(ignore='', project='fury-gl/fury'): """ tags = get_tags(project=project) - l_version = [t['name'] for t in tags] + l_version = [t["name"] for t in tags] - if ignore.lower() in ['micro', 'minor']: - l_version = list(set([re.sub(r'(\d+)$', 'x', v) for v in l_version])) + if ignore.lower() in ["micro", "minor"]: + l_version = list({re.sub(r"(\d+)$", "x", v) for v in l_version}) - if ignore.lower() == 'minor': - l_version = list(set([re.sub(r'\.(\d+)\.', '.x.', v) for v in l_version])) + if ignore.lower() == "minor": + l_version = list({re.sub(r"\.(\d+)\.", ".x.", v) for v in l_version}) return l_version -def version_compare(current_version, version_number, op='eq', all_versions=None): +def version_compare(current_version, version_number, op="eq", all_versions=None): """Compare doc version. This is afilter for sphinx template.""" - p = re.compile(r'(\d+)\.(\d+)') + p = re.compile(r"(\d+)\.(\d+)") d_operator = { - '<': operator.lt, - 'lt': operator.lt, - '<=': operator.le, - 'le': operator.le, - '>': operator.gt, - 'gt': operator.gt, - '>=': operator.ge, - 'ge': operator.ge, - '==': operator.eq, - '=': operator.eq, - 'eq': operator.eq, - '!=': operator.ne, + "<": operator.lt, + "lt": operator.lt, + "<=": operator.le, + "le": operator.le, + ">": operator.gt, + "gt": operator.gt, + ">=": operator.ge, + "ge": operator.ge, + "==": operator.eq, + "=": operator.eq, + "eq": operator.eq, + "!=": operator.ne, } # Setup default value to op if op not in d_operator.keys(): - op = 'eq' + op = "eq" # check dev page - if current_version.lower() == 'dev': - return 'post' in version_number + if current_version.lower() == "dev": + return "post" in version_number # major and minor extraction current = p.search(current_version) ref = p.search(version_number) - if current_version.lower() == 'latest': + if current_version.lower() == "latest": # Check if it is the latest release all_versions = all_versions or get_all_versions() if not all_versions: @@ -455,28 +458,27 @@ def version_compare(current_version, version_number, op='eq', all_versions=None) last_version = p.search(last_version) if ( parse(last_version.group()) == parse(ref.group()) - and 'post' not in version_number + and "post" not in version_number ): return True return False - if 'post' in version_number: + if "post" in version_number: return False return d_operator[op](parse(current.group()), parse(ref.group())) def username_to_fullname(all_authors): - with open(os.path.join(os.path.dirname(__file__), '..', CONTRIBUTORS_FILE)) as f: - + with open(os.path.join(os.path.dirname(__file__), "..", CONTRIBUTORS_FILE)) as f: extra_info = json.load(f) - extra_info = extra_info['team'] + extra_info['core_team'] - extra_info = {i['username']: i['fullname'] for i in extra_info} + extra_info = extra_info["team"] + extra_info["core_team"] + extra_info = {i["username"]: i["fullname"] for i in extra_info} curent_authors = all_authors for i, author in enumerate(all_authors): if author[2:] in extra_info.keys(): - curent_authors[i] = '* ' + extra_info[author[2:]] + curent_authors[i] = "* " + extra_info[author[2:]] return curent_authors @@ -485,26 +487,26 @@ def github_stats(**kwargs): """Get release github stats.""" # Whether to add reST urls for all issues in printout. show_urls = True - save = kwargs.get('save', None) + save = kwargs.get("save", None) if save: - version = kwargs.get('version', 'vx.x.x') - fname = 'releasev' + version + '.rst' - fpath = os.path.join(os.path.dirname(__file__), '..', 'release_notes', fname) - f = open(fpath, 'w') + version = kwargs.get("version", "vx.x.x") + fname = "releasev" + version + ".rst" + fpath = os.path.join(os.path.dirname(__file__), "..", "release_notes", fname) + f = open(fpath, "w") orig_stdout = sys.stdout sys.stdout = f - print('.. _{}'.format(fname.replace('.rst', ':'))) + print(".. _{}".format(fname.replace(".rst", ":"))) print() print( ( - '==============================\n' - ' Release notes v{}\n ()' - '==============================' + "==============================\n" + " Release notes v{}\n ()" + "==============================" ).format(version) ) # By default, search one month back - tag = kwargs.get('tag', None) + tag = kwargs.get("tag", None) if tag is None: if len(sys.argv) > 1: try: @@ -513,17 +515,17 @@ def github_stats(**kwargs): tag = sys.argv[1] else: tag = check_output( - ['git', 'describe', '--abbrev=0'], universal_newlines=True + ["git", "describe", "--abbrev=0"], universal_newlines=True ).strip() if tag: - cmd = ['git', 'log', '-1', '--format=%ai', tag] - tagday, _ = check_output(cmd, universal_newlines=True).strip().rsplit(' ', 1) - since = datetime.strptime(tagday, '%Y-%m-%d %H:%M:%S') + cmd = ["git", "log", "-1", "--format=%ai", tag] + tagday, _ = check_output(cmd, universal_newlines=True).strip().rsplit(" ", 1) + since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S") else: since = datetime.now() - timedelta(days=days) - print('fetching GitHub stats since %s (tag: %s)' % (since, tag), file=sys.stderr) + print("fetching GitHub stats since %s (tag: %s)" % (since, tag), file=sys.stderr) # turn off to play interactively without redownloading, use %run -i if 1: issues = issues_closed_since(since, pulls=False) @@ -539,51 +541,51 @@ def github_stats(**kwargs): # Print summary report we can directly include into release notes. print() - since_day = since.strftime('%Y/%m/%d') - today = datetime.today().strftime('%Y/%m/%d') - print('GitHub stats for %s - %s (tag: %s)' % (since_day, today, tag)) + since_day = since.strftime("%Y/%m/%d") + today = datetime.today().strftime("%Y/%m/%d") + print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag)) print() print( - 'These lists are automatically generated, and may be incomplete or' - ' contain duplicates.' + "These lists are automatically generated, and may be incomplete or" + " contain duplicates." ) print() if tag: # print git info, in addition to GitHub info: - since_tag = tag + '..' - cmd = ['git', 'log', '--oneline', since_tag] + since_tag = tag + ".." + cmd = ["git", "log", "--oneline", since_tag] ncommits = check_output(cmd, universal_newlines=True).splitlines() ncommits = len(ncommits) - author_cmd = ['git', 'log', '--format=* %aN', since_tag] + author_cmd = ["git", "log", "--format=* %aN", since_tag] all_authors = check_output(author_cmd, universal_newlines=True).splitlines() # Replace username by author name all_authors = username_to_fullname(all_authors) unique_authors = sorted(set(all_authors)) if len(unique_authors) == 0: - print('No commits during this period.') + print("No commits during this period.") else: print( - 'The following %i authors contributed %i commits.' + "The following %i authors contributed %i commits." % (len(unique_authors), ncommits) ) print() - print('\n'.join(unique_authors)) + print("\n".join(unique_authors)) print() print() print( - 'We closed a total of %d issues, %d pull requests and %d' - ' regular issues;\nthis is the full list (generated with' - ' the script \n:file:`tools/github_stats.py`):' + "We closed a total of %d issues, %d pull requests and %d" + " regular issues;\nthis is the full list (generated with" + " the script \n:file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues) ) print() - print('Pull Requests (%d):\n' % n_pulls) + print("Pull Requests (%d):\n" % n_pulls) report(pulls, show_urls) print() - print('Issues (%d):\n' % n_issues) + print("Issues (%d):\n" % n_issues) report(issues, show_urls) if save: @@ -595,7 +597,7 @@ def github_stats(**kwargs): # Sphinx connection # ---------------------------------------------------------------------------- def add_jinja_filters(app): - app.builder.templates.environment.filters['version_compare'] = version_compare + app.builder.templates.environment.filters["version_compare"] = version_compare def setup(app): @@ -604,8 +606,8 @@ def setup(app): - Collect and clean authors - Adds extra jinja filters. """ - app.connect('builder-inited', add_jinja_filters) - app.add_css_file('css/custom_github.css') + app.connect("builder-inited", add_jinja_filters) + app.add_css_file("css/custom_github.css") # ---------------------------------------------------------------------------- @@ -613,24 +615,24 @@ def setup(app): # ---------------------------------------------------------------------------- -if __name__ == '__main__': +if __name__ == "__main__": # e.g github_tools.py --tag=v0.1.3 --save --version=0.1.4 parser = argparse.ArgumentParser() parser.add_argument( - '--tag', + "--tag", type=str, default=None, - help='from which tag version to get information', + help="from which tag version to get information", ) parser.add_argument( - '--version', type=str, default='', help='current release version' + "--version", type=str, default="", help="current release version" ) parser.add_argument( - '--save', - dest='save', - action='store_true', + "--save", + dest="save", + action="store_true", default=False, - help=('Save in the release folder' 'and add rst header'), + help=("Save in the release folder" "and add rst header"), ) args = parser.parse_args() diff --git a/docs/source/ext/prepare_gallery.py b/docs/source/ext/prepare_gallery.py index a2234a51c..10f0cae7e 100644 --- a/docs/source/ext/prepare_gallery.py +++ b/docs/source/ext/prepare_gallery.py @@ -1,8 +1,9 @@ +from dataclasses import dataclass, field import fnmatch import os -import shutil -from dataclasses import dataclass, field from pathlib import Path +import shutil +import sys from sphinx.util import logging @@ -28,31 +29,31 @@ def __post_init__(self): def abort(error): - print(f'*WARNING* Examples Revamp not generated: \n\n{error}') + print(f"*WARNING* Examples Revamp not generated: \n\n{error}") exit() def prepare_gallery(app=None): - examples_dir = os.path.join(app.srcdir, '..', 'examples') - examples_revamp_dir = os.path.join(app.srcdir, '..', 'examples_revamped') + examples_dir = os.path.join(app.srcdir, "..", "examples") + examples_revamp_dir = os.path.join(app.srcdir, "..", "examples_revamped") os.makedirs(examples_revamp_dir, exist_ok=True) - f_example_desc = Path(examples_dir, '_valid_examples.toml') + f_example_desc = Path(examples_dir, "_valid_examples.toml") if not f_example_desc.exists(): - msg = 'No valid examples description file found ' + msg = "No valid examples description file found " msg += "(e.g '_valid_examples.toml')" abort(msg) - with open(f_example_desc, 'rb') as fobj: + with open(f_example_desc, "rb") as fobj: try: desc_examples = tomllib.load(fobj) except Exception as e: - msg = f'Error Loading examples description file: {e}.\n\n' - msg += 'Please check the file format.' + msg = f"Error Loading examples description file: {e}.\n\n" + msg += "Please check the file format." abort(msg) - if 'main' not in desc_examples.keys(): - msg = 'No main section found in examples description file' + if "main" not in desc_examples.keys(): + msg = "No main section found in examples description file" abort(msg) try: @@ -60,18 +61,18 @@ def prepare_gallery(app=None): [examplesConfig(folder_name=k, **v) for k, v in desc_examples.items()] ) except Exception as e: - msg = f'Error parsing examples description file: {e}.\n\n' - msg += 'Please check the file format.' + msg = f"Error parsing examples description file: {e}.\n\n" + msg += "Please check the file format." abort(msg) if examples_config[0].position != 0: - msg = 'Main section must be first in examples description file with position=0' + msg = "Main section must be first in examples description file with position=0" abort(msg) - elif examples_config[0].folder_name != 'main': + elif examples_config[0].folder_name != "main": msg = "Main section must be named 'main' in examples description file" abort(msg) elif examples_config[0].enable is False: - msg = 'Main section must be enabled in examples description file' + msg = "Main section must be enabled in examples description file" abort(msg) disable_examples_section = [] @@ -84,7 +85,7 @@ def prepare_gallery(app=None): # Create folder for each example if example.position != 0: folder = Path( - examples_revamp_dir, f'{example.position:02d}_{example.folder_name}' + examples_revamp_dir, f"{example.position:02d}_{example.folder_name}" ) else: folder = Path(examples_revamp_dir) @@ -93,11 +94,11 @@ def prepare_gallery(app=None): os.makedirs(folder) # Create readme file - if example.readme.startswith('file:'): - filename = example.readme.split('file:')[1].strip() - shutil.copy(Path(examples_dir, filename), Path(folder, 'README.rst')) + if example.readme.startswith("file:"): + filename = example.readme.split("file:")[1].strip() + shutil.copy(Path(examples_dir, filename), Path(folder, "README.rst")) else: - with open(Path(folder, 'README.rst'), 'w') as fi: + with open(Path(folder, "README.rst"), "w") as fi: fi.write(example.readme) # Copy files to folder @@ -106,19 +107,19 @@ def prepare_gallery(app=None): for fi in example.files: if not Path(examples_dir, fi).exists(): - msg = f'\tFile {fi} not found in examples folder: {examples_dir}.\n\n' - msg += '\tPlease, Add the file or remove it from the description file.' + msg = f"\tFile {fi} not found in examples folder: {examples_dir}.\n\n" + msg += "\tPlease, Add the file or remove it from the description file." logger.info(msg) continue shutil.copy(Path(examples_dir, fi), Path(folder, fi)) # Check if all python examples are in the description file files_in_config = [fi for ex in examples_config for fi in ex.files] - all_examples = fnmatch.filter(os.listdir(examples_dir), '*.py') + all_examples = fnmatch.filter(os.listdir(examples_dir), "*.py") for all_ex in all_examples: if all_ex in files_in_config: continue - msg = f'File {all_ex} not found in examples description file: {f_example_desc}' + msg = f"File {all_ex} not found in examples description file: {f_example_desc}" logger.info(msg) @@ -130,18 +131,18 @@ def setup(app): app: Sphinx application context. """ - logger.info('Initializing Examples folder revamp plugin...') + logger.info("Initializing Examples folder revamp plugin...") - app.connect('builder-inited', prepare_gallery) + app.connect("builder-inited", prepare_gallery) # app.connect('build-finished', summarize_failing_examples) - metadata = {'parallel_read_safe': True, 'version': app.config.version} + metadata = {"parallel_read_safe": True, "version": app.config.version} return metadata -if __name__ == '__main__': +if __name__ == "__main__": gallery_name = sys.argv[1] outdir = sys.argv[2] - prepare_gallery(app=None, package=package, outdir=outdir) + prepare_gallery(app=None) diff --git a/docs/source/ext/rstjinja.py b/docs/source/ext/rstjinja.py index b5546583b..3c6a4b39b 100644 --- a/docs/source/ext/rstjinja.py +++ b/docs/source/ext/rstjinja.py @@ -4,7 +4,7 @@ def rstjinja(app, _docname, source): """Render our pages as a jinja template for fancy templating goodness.""" # Make sure we're outputting HTML - if app.builder.format != 'html': + if app.builder.format != "html": return src = source[0] rendered = app.builder.templates.render_string(src, app.config.html_context) @@ -12,4 +12,4 @@ def rstjinja(app, _docname, source): def setup(app): - app.connect('source-read', rstjinja) + app.connect("source-read", rstjinja) diff --git a/docs/source/ext/scrap.py b/docs/source/ext/scrap.py index f7004d418..73ae432a9 100644 --- a/docs/source/ext/scrap.py +++ b/docs/source/ext/scrap.py @@ -14,11 +14,11 @@ def __init__(self): def __call__(self, block, block_vars, gallery_conf): # Find all image files in the current directory. - path_example = os.path.dirname(block_vars['src_file']) + path_example = os.path.dirname(block_vars["src_file"]) image_files = _find_images(path_example) # Iterate through files, copy them to the SG output directory image_names = [] - image_path_iterator = block_vars['image_path_iterator'] + image_path_iterator = block_vars["image_path_iterator"] for path_orig in image_files: # If we already know about this image and it hasn't been modified # since starting, then skip it @@ -37,15 +37,18 @@ def __call__(self, block, block_vars, gallery_conf): shutil.copyfile(path_orig, path_new) if not image_names: - return '' + return "" - return figure_rst(image_names, gallery_conf['src_dir']) + return figure_rst(image_names, gallery_conf["src_dir"]) -def _find_images(path, image_extensions=['jpg', 'jpeg', 'png', 'gif']): +def _find_images(path, image_extensions=None): """Find all unique image paths for a set of extensions.""" + if image_extensions is None: + image_extensions = ["jpg", "jpeg", "png", "gif"] + image_files = set() for ext in image_extensions: - this_ext_files = set(glob.glob(os.path.join(path, '*.' + ext))) + this_ext_files = set(glob.glob(os.path.join(path, "*." + ext))) image_files = image_files.union(this_ext_files) return image_files diff --git a/docs/source/installation.rst b/docs/source/installation.rst index d5f6b5957..34c0e573c 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -136,6 +136,34 @@ Since Xvfb will require an X server (we also recommend to install XQuartz packag export DISPLAY=:0 xvfb-run --server-args="-screen 0 1920x1080x24" pytest -svv fury +Coding style +------------ + +FURY uses the standard Python `PEP8 `_ +style to ensure the readability and consistency across the toolkit. FURY has +adopted an automated style checking framework that uses `pre-commit `_ +and `ruff `_. Style conformance rules are +specified in the ``ruff.toml`` configuration file. When requesting to push to +FURY, the compliance to the set of rules is automatically checked using a +GitHub Actions workflow. + +In order to check the compliance locally, developers should install the +``[style]`` dependencies: + +.. code-block:: shell + + pip install -e .[style] + +The git hook scripts need to be installed then running: + +.. code-block:: shell + + pre-commit install + +``pre-commit`` will then run automatically on ``git commit``. + +Most text editors can be configured to check the compliance of your code with +the set of rules specified in the ``ruff.toml`` file. Populating our Documentation ---------------------------- diff --git a/docs/source/posts/2020/2020-06-28-week-5-lenix.rst b/docs/source/posts/2020/2020-06-28-week-5-lenix.rst index 6a9cc6816..2334fa9e3 100644 --- a/docs/source/posts/2020/2020-06-28-week-5-lenix.rst +++ b/docs/source/posts/2020/2020-06-28-week-5-lenix.rst @@ -13,7 +13,7 @@ This week, Spherical harmonics! What did you do this week? -------------------------- -The main task for the week was to include an implementation of spherical harmonics (upto the order of 4) as a FURY actor. This was the initial milestone to be achieved to work towards the support of using spherical harmonics as an visualization technique. I have added the GIFs for both the renders below. I also worked on a occlusion based lighting model. +The main task for the week was to include an implementation of spherical harmonics (up to the order of 4) as a FURY actor. This was the initial milestone to be achieved to work towards the support of using spherical harmonics as an visualization technique. I have added the GIFs for both the renders below. I also worked on a occlusion based lighting model. Spherical harmonics for different values of order and degree: diff --git a/docs/source/posts/2021/2021-06-28-gsoc-devmessias-4.rst b/docs/source/posts/2021/2021-06-28-gsoc-devmessias-4.rst index cfceb132f..94fcaa35f 100644 --- a/docs/source/posts/2021/2021-06-28-gsoc-devmessias-4.rst +++ b/docs/source/posts/2021/2021-06-28-gsoc-devmessias-4.rst @@ -156,7 +156,7 @@ must have a way to lock the write/read if the memory resource is busy. Meanwhile the `multiprocessing.Arrays `__ already has a context which allows lock (.get_lock()) SharedMemory -dosen’t[2]. The use of abstract class allowed me to deal with those +doesn’t[2]. The use of abstract class allowed me to deal with those peculiarities. `commit 358402e `__ diff --git a/docs/source/posts/2021/2021-07-05-gsoc-devmessias-5.rst b/docs/source/posts/2021/2021-07-05-gsoc-devmessias-5.rst index 416ac6c1f..d1c4b81b6 100644 --- a/docs/source/posts/2021/2021-07-05-gsoc-devmessias-5.rst +++ b/docs/source/posts/2021/2021-07-05-gsoc-devmessias-5.rst @@ -35,7 +35,7 @@ tasks related with this PR: `#424`_ now is possible to control all the visual characteristics at runtime. - 2D Layout: Meanwhile 3d network representations are very usefully - for exploring a dataset is hard to convice a group of network + for exploring a dataset is hard to convince a group of network scientists to use a visualization system which doesn't allow 2d representations. Because of that I started to coding the 2d behavior in the network visualization system. diff --git a/docs/source/posts/2021/2021-08-23-final-work-antriksh.rst b/docs/source/posts/2021/2021-08-23-final-work-antriksh.rst index 4dd4702a2..8bf4f39bf 100644 --- a/docs/source/posts/2021/2021-08-23-final-work-antriksh.rst +++ b/docs/source/posts/2021/2021-08-23-final-work-antriksh.rst @@ -83,7 +83,7 @@ Objectives Completed - **Add Accordion2D UI element to the UI sub-module** - Added Accordion2D to the UI sub-module. This Ui element allows users to visulize data in a tree with depth of one. Each node has a title and a content panel. The children for each node can be N if and only if the children are not nodes themselves. The child UIs can be placed inside the content panel by passing some coordinates, which can be absolute or normalized w.r.t the node content panel size. Tests and two demos were added for this UI element. Below is a screenshot for reference + Added Accordion2D to the UI sub-module. This Ui element allows users to visualize data in a tree with depth of one. Each node has a title and a content panel. The children for each node can be N if and only if the children are not nodes themselves. The child UIs can be placed inside the content panel by passing some coordinates, which can be absolute or normalized w.r.t the node content panel size. Tests and two demos were added for this UI element. Below is a screenshot for reference .. image:: https://camo.githubusercontent.com/9395d0ea572d7f253a051823f02496450c9f79d19ff0baf32841ec648b6f2860/68747470733a2f2f692e696d6775722e636f6d2f7854754f645a742e706e67 :width: 200 diff --git a/docs/source/posts/2023/2023-01-24-final-report-praneeth.rst b/docs/source/posts/2023/2023-01-24-final-report-praneeth.rst index c6428ecfe..fdd6230bd 100644 --- a/docs/source/posts/2023/2023-01-24-final-report-praneeth.rst +++ b/docs/source/posts/2023/2023-01-24-final-report-praneeth.rst @@ -133,7 +133,7 @@ Other Objectives - **Grouping Shapes** - Many times we need to perform some actions on a group of shapes so here we are with the grouping feature using which you can group shapes together, reposition them, rotate them and delete them together. To activate grouping of shapes you have to be on selection mode then by holding **Ctrl** key select the required shapes and they will get highlighted. To remove shape from the group just hold the **Ctrl** and click the shape again it will get deselected. Then once eveything is grouped you can use the normal transformation as normal i.e. for translation just drag the shapes around and for rotation the rotation slider appears at usual lower left corner which can be used. + Many times we need to perform some actions on a group of shapes so here we are with the grouping feature using which you can group shapes together, reposition them, rotate them and delete them together. To activate grouping of shapes you have to be on selection mode then by holding **Ctrl** key select the required shapes and they will get highlighted. To remove shape from the group just hold the **Ctrl** and click the shape again it will get deselected. Then once everything is grouped you can use the normal transformation as normal i.e. for translation just drag the shapes around and for rotation the rotation slider appears at usual lower left corner which can be used. *Pull Requests:* diff --git a/docs/source/posts/2023/2023-06-03-week-1-praneeth.rst b/docs/source/posts/2023/2023-06-03-week-1-praneeth.rst index 53edfb263..5b9e9a5e9 100644 --- a/docs/source/posts/2023/2023-06-03-week-1-praneeth.rst +++ b/docs/source/posts/2023/2023-06-03-week-1-praneeth.rst @@ -38,4 +38,4 @@ Looking ahead, here's what I have planned for the upcoming week: 1. Completing PR `#790 `__ - Fixing Textbox Alignment 2. Wrapping up PR `#499 `__ - SpinBoxUI -3. Initiating PR `#576 `__ - Icon Flaw in ComboBox \ No newline at end of file +3. Initiating PR `#576 `__ - Icon Flaw in ComboBox diff --git a/docs/source/posts/2023/2023-06-05-week-1-joaodellagli.rst b/docs/source/posts/2023/2023-06-05-week-1-joaodellagli.rst index 405c75bbf..2e09b29f6 100644 --- a/docs/source/posts/2023/2023-06-05-week-1-joaodellagli.rst +++ b/docs/source/posts/2023/2023-06-05-week-1-joaodellagli.rst @@ -11,24 +11,24 @@ This Past Week -------------- As mentioned in the last week's blogpost, the goal for that week was to investigate VTK's Framebuffer Object framework. -An update on that is that indeed, VTK has one more low-level working `FBO class `_ that can be used inside FURY, however, +An update on that is that indeed, VTK has one more low-level working `FBO class `_ that can be used inside FURY, however, they come with some issues that I will explain further below. My Current Problems ------------------- -The problems I am having with these FBO implementations are first something related to how a FBO works, and second related to how VTK works. +The problems I am having with these FBO implementations are first something related to how a FBO works, and second related to how VTK works. In OpenGL, a custom user's FBO needs some things to be complete (usable): 1. At least one buffer should be attached. This buffer can be the color, depth or stencil buffer. 2. If no color buffer will be attached then OpenGL needs to be warned no draw or read operations will be done to that buffer. Otherwise, there should be at least one color attachment. 3. All attachments should have their memory allocated. -4. Each buffer should have the same number of samples. +4. Each buffer should have the same number of samples. My first problem relies on the third requirement. VTK's implementation of FBO requires a `vtkTextureObject `_ as a texture attachment. I figured out how to work with this class, however, I cannot allocate memory for it, as its methods for it, `Allocate2D `_, `Create2D `_ and `Create2DFromRaw `_ -does not seem to work. Every time I try to use them, my program stops with no error message nor nothing. +does not seem to work. Every time I try to use them, my program stops with no error message nor nothing. For anyone interested in what is happening exactly, below is how I my tests are implemented: :: @@ -43,11 +43,11 @@ For anyone interested in what is happening exactly, below is how I my tests are | | color_texture.Allocate2D(width, height, 4, vtk.VTK_UNSIGNED_CHAR) # here is where the code stops -In contrast, for some reason, the methods for 3D textures, `Allocate3D `_ works just fine. -I could use it as a workaround, but I do not wish to, as this just does not make sense. +In contrast, for some reason, the methods for 3D textures, `Allocate3D `_ works just fine. +I could use it as a workaround, but I do not wish to, as this just does not make sense. My second problem relies on VTK. As VTK is a library that encapsulates some OpenGL functions in more palatable forms, it comes with some costs. -Working with FBOs is a more low-level work, that requires strict control of some OpenGL states and specific functions that would be simpler if it was the main API here. +Working with FBOs is a more low-level work, that requires strict control of some OpenGL states and specific functions that would be simpler if it was the main API here. However, some of this states and functions are all spread and implicit through VTK's complex classes and methods, which doubles the time expended to make some otherwise simple instructions, as I first need to dig in lines and lines of VTK's documentation, and worse, the code itself. @@ -56,6 +56,6 @@ What About Next Week? --------------------- For this next week, I plan to investigate further on why the first problem is happening. If that is accomplished, then things will be more simple, as it will be a lot easier for my project to move forward as I will finally be able -to implement the more pythonic functions needed to finally render some kernel distributions onto my screen. +to implement the more pythonic functions needed to finally render some kernel distributions onto my screen. Wish me luck! diff --git a/docs/source/posts/2023/2023-06-12-week-2-joaodellagli.rst b/docs/source/posts/2023/2023-06-12-week-2-joaodellagli.rst index 9f22c9358..679f401b5 100644 --- a/docs/source/posts/2023/2023-06-12-week-2-joaodellagli.rst +++ b/docs/source/posts/2023/2023-06-12-week-2-joaodellagli.rst @@ -12,9 +12,9 @@ Hello everybody, welcome to the week 2 of this project! I must admit I thought t This Last Week's Effort ----------------------- -Last week, I was facing some issues with a VTK feature essential so I could move forward with my project: Framebuffer Objects. +Last week, I was facing some issues with a VTK feature essential so I could move forward with my project: Framebuffer Objects. As described in my :doc:`last blogpost <2023-06-05-week-1-joaodellagli>`, for some reason the 2D allocation methods for it weren't working. -In a meeting with my mentors, while we were discussing and searching through VTK's FramebufferObject and TextureObject documentation, and the code itself for the problem, +In a meeting with my mentors, while we were discussing and searching through VTK's FramebufferObject and TextureObject documentation, and the code itself for the problem, one TextureObject method caught my attention: `vtkTextureObject.SetContext() `_. Where the Problem Was @@ -39,18 +39,18 @@ will be present, so it lacked a line, that should be added after ``Bind()``: :: - color_texture = vtk.vtkTextureObject() - color_texture.Bind() + color_texture = vtk.vtkTextureObject() + color_texture.Bind() color_texture.SetContext(manager.window) # set the context where the texture object will be present - color_texture.SetDataType(vtk.VTK_UNSIGNED_CHAR) - color_texture.SetInternalFormat(vtk.VTK_RGB) + color_texture.SetDataType(vtk.VTK_UNSIGNED_CHAR) + color_texture.SetInternalFormat(vtk.VTK_RGB) color_texture.SetFormat(vtk.VTK_RGB) - color_texture.SetMinificationFilter(0) - color_texture.SetMagnificationFilter(0) + color_texture.SetMinificationFilter(0) + color_texture.SetMagnificationFilter(0) -The code worked fine. But as my last blogpost showed, ``Allocate3D()`` method worked just fine without a (visible) problem, why is that? +The code worked fine. But as my last blogpost showed, ``Allocate3D()`` method worked just fine without a (visible) problem, why is that? Well, in fact, it **didn't work**. If we check the code for the ``Allocate2D()`` and ``Allocate3D()``, one difference can be spotted: @@ -78,9 +78,9 @@ implementation made it harder for me and my mentors to realise what was happenin This Week's Goals ----------------- -After making that work, this week's goal is to render something to the Framebuffer Object, now that is working. To do that, +After making that work, this week's goal is to render something to the Framebuffer Object, now that is working. To do that, first I will need to do some offscreen rendering to it, and afterwards render what it was drawn to its color attachment, the Texture Object I -was struggling to make work, into the screen, drawing its texture to a billboard. Also, I plan to start using vtkErrorMacro, as it seems like +was struggling to make work, into the screen, drawing its texture to a billboard. Also, I plan to start using vtkErrorMacro, as it seems like the main error interface when working with VTK, and that may make my life easier. -See you next week! \ No newline at end of file +See you next week! diff --git a/docs/source/posts/2023/2023-06-17-week-3-praneeth.rst b/docs/source/posts/2023/2023-06-17-week-3-praneeth.rst index 071a0aafa..2779b58e2 100644 --- a/docs/source/posts/2023/2023-06-17-week-3-praneeth.rst +++ b/docs/source/posts/2023/2023-06-17-week-3-praneeth.rst @@ -31,4 +31,4 @@ I faced a challenge while working on the text justification. It took me several What is coming up next? ----------------------- -Next week, I have several tasks lined up. Firstly, I will be working on the **CardUI** PR `#398 `__. Additionally, I plan to complete the segregation of the scrollbar component, ensuring its independence and clarity. Lastly, I will be working on issue `#540 `__, which involves updating the use of the ``numpy_to_vtk_image_data`` utility function. \ No newline at end of file +Next week, I have several tasks lined up. Firstly, I will be working on the **CardUI** PR `#398 `__. Additionally, I plan to complete the segregation of the scrollbar component, ensuring its independence and clarity. Lastly, I will be working on issue `#540 `__, which involves updating the use of the ``numpy_to_vtk_image_data`` utility function. diff --git a/docs/source/posts/2023/2023-06-19-week-3-joaodellagli.rst b/docs/source/posts/2023/2023-06-19-week-3-joaodellagli.rst index a043a22fa..47babbff4 100644 --- a/docs/source/posts/2023/2023-06-19-week-3-joaodellagli.rst +++ b/docs/source/posts/2023/2023-06-19-week-3-joaodellagli.rst @@ -7,34 +7,34 @@ Week 3: Watch Your Expectations :category: gsoc -Hello everyone, it's time for another weekly blogpost! This week, +Hello everyone, it's time for another weekly blogpost! This week, I will talk about how you should watch your expectations when working with any project. This Last Week's Effort ----------------------- -As I supposedly managed to make the texture allocation part working, this last week's goal was to render something to a FBO. Well, I could make +As I supposedly managed to make the texture allocation part working, this last week's goal was to render something to a FBO. Well, I could make textures work, but what I wasn't expecting and later realised, was that the FBO setup not working. Below I will describe where I got stuck. Where the Problem Was --------------------- -After getting the textures setup right, I was ready to render some color to the FBO. Well, I **was**, because I didn't expect -I would have another problem, this time, with the FBO setup. As described in my :doc:`week 1 blogpost <2023-06-05-week-1-joaodellagli>`, -a FBO needs some requirements to work. My current problem relies on the FBO method ``FBO.SetContext()``, that for some reason is not being able to generate the FBO. +After getting the textures setup right, I was ready to render some color to the FBO. Well, I **was**, because I didn't expect +I would have another problem, this time, with the FBO setup. As described in my :doc:`week 1 blogpost <2023-06-05-week-1-joaodellagli>`, +a FBO needs some requirements to work. My current problem relies on the FBO method ``FBO.SetContext()``, that for some reason is not being able to generate the FBO. Below, how the method is currently operating: .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/setcontext.png :align: center :alt: Image showing the SetContext's VTK implementation -Apparently, the method is stuck before the ``this->CreateFBO()``, that can be checked when we call ``FBO.GetFBOIndex()``, that returns a ``0`` value, -meaning the FBO was not generated by the ``glGenFramebuffers()`` function, that is inside the ``GetContext()`` method. +Apparently, the method is stuck before the ``this->CreateFBO()``, that can be checked when we call ``FBO.GetFBOIndex()``, that returns a ``0`` value, +meaning the FBO was not generated by the ``glGenFramebuffers()`` function, that is inside the ``GetContext()`` method. This Week's Goals ----------------- -As I got stuck again with this simple step, talking with my mentors we concluded that a plan B is needed for my GSoC participation as -my current project is not having much progress. This plan B that I am gonna start working on this week involves working on `FURY Speed `_, -a FURY addon that aims to develop optimized functions and algorithms to help speed up graphical applications. The suggestion was to -work on a PR I submitted months ago, `#783 `_, in a way to integrate that into FURY Speed. -Also, I plan to keep working on my current project to find the solution I will need to make the FBO usage work. +As I got stuck again with this simple step, talking with my mentors we concluded that a plan B is needed for my GSoC participation as +my current project is not having much progress. This plan B that I am gonna start working on this week involves working on `FURY Speed `_, +a FURY addon that aims to develop optimized functions and algorithms to help speed up graphical applications. The suggestion was to +work on a PR I submitted months ago, `#783 `_, in a way to integrate that into FURY Speed. +Also, I plan to keep working on my current project to find the solution I will need to make the FBO usage work. -Let's get to work! \ No newline at end of file +Let's get to work! diff --git a/docs/source/posts/2023/2023-06-24-week-4-praneeth.rst b/docs/source/posts/2023/2023-06-24-week-4-praneeth.rst index 0777ef8ba..3dd7306d6 100644 --- a/docs/source/posts/2023/2023-06-24-week-4-praneeth.rst +++ b/docs/source/posts/2023/2023-06-24-week-4-praneeth.rst @@ -27,4 +27,4 @@ No, fortunately, I didn't encounter any major obstacles or challenges during my What is coming up next? ----------------------- -Once the exams are over, I am eagerly looking forward to making a full comeback to development. My immediate plans include addressing the remaining issues in PR `#540 `_ and completing the pending tasks. I will also catch up on any missed discussions and sync up with the team to align our goals for the upcoming weeks. \ No newline at end of file +Once the exams are over, I am eagerly looking forward to making a full comeback to development. My immediate plans include addressing the remaining issues in PR `#540 `_ and completing the pending tasks. I will also catch up on any missed discussions and sync up with the team to align our goals for the upcoming weeks. diff --git a/docs/source/posts/2023/2023-06-26-week-4-joaodellagli.rst b/docs/source/posts/2023/2023-06-26-week-4-joaodellagli.rst index 97950b4eb..151b1c719 100644 --- a/docs/source/posts/2023/2023-06-26-week-4-joaodellagli.rst +++ b/docs/source/posts/2023/2023-06-26-week-4-joaodellagli.rst @@ -12,11 +12,11 @@ Welcome again to another weekly blogpost! Today, let's talk about the importance Last Week's Effort ----------------------- So, last week my project was struggling with some supposedly simple in concept, yet intricate in execution issues. If you recall from -my :doc:`last blogpost <2023-06-19-week-3-joaodellagli>`, I could not manage to make the Framebuffer Object setup work, as its method, -``SetContext()``, wasn't being able to generate the FBO inside OpenGL. Well, after some (more) research about that as I also dived in my -plan B, that involved studying numba as a way to accelerate a data structure I implemented on my PR `#783 `_, -me and one of my mentors decided we needed a pair programming session, that finally happened on thursday. After that session, -we could finally understand what was going on. +my :doc:`last blogpost <2023-06-19-week-3-joaodellagli>`, I could not manage to make the Framebuffer Object setup work, as its method, +``SetContext()``, wasn't being able to generate the FBO inside OpenGL. Well, after some (more) research about that as I also dived in my +plan B, that involved studying numba as a way to accelerate a data structure I implemented on my PR `#783 `_, +me and one of my mentors decided we needed a pair programming session, that finally happened on thursday. After that session, +we could finally understand what was going on. Where the Problem Was --------------------- @@ -30,24 +30,24 @@ Apparently, for the FBO generation to work, it is first needed to initialize the manager.initialize() # missing part that made everything work FBO.SetContext(manager.window) # Sets the context for the FBO. Finally, it works - FBO.PopulateFramebuffer(width, height, True, 1, vtk.VTK_UNSIGNED_CHAR, False, 24, 0) # And now I could populate the FBO with textures + FBO.PopulateFramebuffer(width, height, True, 1, vtk.VTK_UNSIGNED_CHAR, False, 24, 0) # And now I could populate the FBO with textures This simple missing line of code was responsible for ending weeks of suffer, as after that, I called: :: - print("FBO of index:", FBO.GetFBOIndex()) + print("FBO of index:", FBO.GetFBOIndex()) print("Number of color attachments:", FBO.GetNumberOfColorAttachments()) -That outputted: +That outputted: :: FBO of index: 4 Number of color attachments: 1 -That means the FBO generation was successful! One explanation that seems reasonable to me on why was that happening is that, as it was +That means the FBO generation was successful! One explanation that seems reasonable to me on why was that happening is that, as it was not initialized, the context was being passed ``null`` to the ``SetContext()`` method, that returned without any warning of what was happening. -Here, I would like to point out how my mentor was **essential** to this solution to come: I had struggled for some time with that, and could -not find a way out, but a single session of synchronous pair programming where I could expose clearly my problem and talk to someone +Here, I would like to point out how my mentor was **essential** to this solution to come: I had struggled for some time with that, and could +not find a way out, but a single session of synchronous pair programming where I could expose clearly my problem and talk to someone way more experienced than I, someone designated for that, was my way out of this torment, so value your mentors! Thanks Bruno! @@ -55,6 +55,6 @@ This Week's Goals ----------------- Now, with the FBO working, I plan to finally *render* something to it. For this week, I plan to come back to my original plan and experiment with simple shaders just as a proof of concept that the FBO will be really useful for this project. I hope the road is less -bumpier by now and I don't step on any other complicated problem. +bumpier by now and I don't step on any other complicated problem. -Wish me luck! \ No newline at end of file +Wish me luck! diff --git a/docs/source/posts/2023/2023-07-01-week-5-praneeth.rst b/docs/source/posts/2023/2023-07-01-week-5-praneeth.rst index 4563775ce..aab6ba6c6 100644 --- a/docs/source/posts/2023/2023-07-01-week-5-praneeth.rst +++ b/docs/source/posts/2023/2023-07-01-week-5-praneeth.rst @@ -12,7 +12,7 @@ What did you do this week? Due to ongoing exams, my productivity was limited this week. However, I managed to find some time to explore and review a few PRs submitted by contributors: 1. Ellipsoid PR `#791 `_: - This PR focuses on creating a new ellipsoid actor defined with SDF and raymarching techniques. + This PR focuses on creating a new ellipsoid actor defined with SDF and raymarching techniques. 2. Website Improvement PR `#812 `_: This PR includes changes for the new compatibility section on the FURY home page. @@ -25,4 +25,4 @@ Fortunately, I didn't encounter any major roadblocks or challenges that hindered What is coming up next? ------------------------ -With the action points provided by my mentor, I will be dedicating the next week to completing those tasks. \ No newline at end of file +With the action points provided by my mentor, I will be dedicating the next week to completing those tasks. diff --git a/docs/source/posts/2023/2023-07-03-week-5-joaodellagli.rst b/docs/source/posts/2023/2023-07-03-week-5-joaodellagli.rst index 752ae0e35..65236da69 100644 --- a/docs/source/posts/2023/2023-07-03-week-5-joaodellagli.rst +++ b/docs/source/posts/2023/2023-07-03-week-5-joaodellagli.rst @@ -12,13 +12,13 @@ Hello everyone, time for another weekly blogpost! Today, we will talk about taki Last Week's Effort ------------------ After having the FBO properly set up, the plan was to finally *render* something to it. Well, I wished for a less bumpy road -at my :doc:`last blogpost <2023-06-26-week-4-joaodellagli>` but as in this project things apparently tend to go wrong, -of course the same happened with this step. +at my :doc:`last blogpost <2023-06-26-week-4-joaodellagli>` but as in this project things apparently tend to go wrong, +of course the same happened with this step. Where the Problem Was --------------------- -Days passed without anything being rendered to the FBO. The setup I was working on followed the simplest OpenGL pipeline of rendering to +Days passed without anything being rendered to the FBO. The setup I was working on followed the simplest OpenGL pipeline of rendering to an FBO: 1. Setup the FBO @@ -27,9 +27,9 @@ an FBO: 4. Render to the FBO 5. Use the color attachment as texture attached to a billboard to render what was on the screen -But it seems like this pipeline doesn't translate well into VTK. I paired again on wednesday with my mentors, Bruno and Filipi, to try to figure out -where the problem was, but after hours we could not find it. Wednesday passed and then thursday came, and with thursday, a solution: -Bruno didn't give up on the idea and dug deep on VTK's documentation until he found a workaround to do what we wanted, that was retrieving a +But it seems like this pipeline doesn't translate well into VTK. I paired again on wednesday with my mentors, Bruno and Filipi, to try to figure out +where the problem was, but after hours we could not find it. Wednesday passed and then thursday came, and with thursday, a solution: +Bruno didn't give up on the idea and dug deep on VTK's documentation until he found a workaround to do what we wanted, that was retrieving a texture from what was rendered to the screen and pass it as a texture to render to the billboard. To do it, he figured out we needed to use a different class, `vtkWindowToImageFilter `_, a class that has the specific job of doing exactly what I described above. Below, the steps to do it: @@ -52,16 +52,16 @@ This is enough to bind to the desired actor a texture that corresponds to what w This Week's Goals ----------------- Having a solution to that, now its time to finally render some KDE's! This week's plans involve implementing the first version of a KDE -calculation. For anyone interested in understanding what a Kernel Density Estimation is, here is a brief summary from this +calculation. For anyone interested in understanding what a Kernel Density Estimation is, here is a brief summary from this `Wikipedia page `_: - In statistics, kernel density estimation (KDE) is the application of kernel smoothing for probability density estimation, i.e., a - non-parametric method to estimate the probability density function of a random variable based on kernels as weights. KDE answers a - fundamental data smoothing problem where inferences about the population are made, based on a finite data sample. In some fields - such as signal processing and econometrics it is also termed the Parzen–Rosenblatt window method, after Emanuel Parzen and Murray - Rosenblatt, who are usually credited with independently creating it in its current form. One of the famous applications of - kernel density estimation is in estimating the class-conditional marginal densities of data when using a naive Bayes classifier, + In statistics, kernel density estimation (KDE) is the application of kernel smoothing for probability density estimation, i.e., a + non-parametric method to estimate the probability density function of a random variable based on kernels as weights. KDE answers a + fundamental data smoothing problem where inferences about the population are made, based on a finite data sample. In some fields + such as signal processing and econometrics it is also termed the Parzen–Rosenblatt window method, after Emanuel Parzen and Murray + Rosenblatt, who are usually credited with independently creating it in its current form. One of the famous applications of + kernel density estimation is in estimating the class-conditional marginal densities of data when using a naive Bayes classifier, which can improve its prediction accuracy. This complicated sentence can be translated into the below image: @@ -70,8 +70,7 @@ This complicated sentence can be translated into the below image: :align: center :alt: KDE plot of 100 random points -That is what a KDE plot of 100 random points looks like. The greener the area, the greater the density of points. The plan is to implement +That is what a KDE plot of 100 random points looks like. The greener the area, the greater the density of points. The plan is to implement something like that with the tools we now have available. Let's get to work! - diff --git a/docs/source/posts/2023/2023-07-10-week-6-joaodellagli.rst b/docs/source/posts/2023/2023-07-10-week-6-joaodellagli.rst index e9335c541..0cce36b45 100644 --- a/docs/source/posts/2023/2023-07-10-week-6-joaodellagli.rst +++ b/docs/source/posts/2023/2023-07-10-week-6-joaodellagli.rst @@ -11,53 +11,53 @@ Hello everyone, time for a other weekly blogpost! Today, I will show you my curr What I did Last Week -------------------- -Last week I had the goal to implement KDE rendering to the screen (if you want to understand what this is, check my :doc:`last blogpost <2023-07-03-week-5-joaodellagli>`_). +Last week I had the goal to implement KDE rendering to the screen (if you want to understand what this is, check my :doc:`last blogpost <2023-07-03-week-5-joaodellagli>`_). After some days diving into the code, I finally managed to do it: .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/buffer_compose.png :align: center :alt: KDE render to a billboard -This render may seem clean and working, but the code isn't exactly like that. For this to work, some tricks and work arounds needed to +This render may seem clean and working, but the code isn't exactly like that. For this to work, some tricks and work arounds needed to be done, as I will describe in the section below. Also, I reviewed the shader part of Tania's PR `#791 `_, that implement ellipsoid actors inside FURY. It was my first review of a PR that isn't a blogpost, so it was an interesting experience and I hope I can get better at it. -It is important as well to point out that I had to dedicate myself to finishing my graduation capstone project's presentation that I will attend +It is important as well to point out that I had to dedicate myself to finishing my graduation capstone project's presentation that I will attend to this week, so I had limited time to polish my code, which I plan to do better this week. Where the Problem Was --------------------- -The KDE render basically works rendering the KDE of a point to a texture and summing that texture to the next render. For this to work, -the texture, rendered to a billboard, needs to be the same size of the screen, otherwise the captured texture will include the black background. -The problem I faced with that is that the billboard scaling isn't exactly well defined, so I had to guess for a fixed screen size -(in this example, I worked with *600x600*) what scaling value made the billboard fit exactly inside the screen (it's *3.4*). That is far from ideal as I -will need to modularize this behavior inside a function that needs to work for every case, so I will need to figure out a way to fix that +The KDE render basically works rendering the KDE of a point to a texture and summing that texture to the next render. For this to work, +the texture, rendered to a billboard, needs to be the same size of the screen, otherwise the captured texture will include the black background. +The problem I faced with that is that the billboard scaling isn't exactly well defined, so I had to guess for a fixed screen size +(in this example, I worked with *600x600*) what scaling value made the billboard fit exactly inside the screen (it's *3.4*). That is far from ideal as I +will need to modularize this behavior inside a function that needs to work for every case, so I will need to figure out a way to fix that for every screen size. For that, I have two options: 1. Find the scaling factor function that makes the billboard fit into any screen size. 2. Figure out how the scaling works inside the billboard actor to understand if it needs to be refactored. The first seems ok to do, but it is kind of a work around as well. The second one is a good general solution, but it is a more delicate one, -as it deals with how the billboard works and already existing applications of it may suffer problems if the scaling is changed. -I will see what is better talking with my mentors. +as it deals with how the billboard works and already existing applications of it may suffer problems if the scaling is changed. +I will see what is better talking with my mentors. -Another problem I faced (that is already fixed) relied on shaders. I didn't fully understood how shaders work inside FURY so I was -using my own fragment shader implementation, replacing the already existing one completely. That was working, but I was having an issue +Another problem I faced (that is already fixed) relied on shaders. I didn't fully understood how shaders work inside FURY so I was +using my own fragment shader implementation, replacing the already existing one completely. That was working, but I was having an issue with the texture coordinates of the rendering texture. As I completely replaced the fragment shader, I had to pass custom texture coordinates -to it, resulting in distorted textures that ruined the calculations. Those issues motivated me to learn the shaders API, which allowed me +to it, resulting in distorted textures that ruined the calculations. Those issues motivated me to learn the shaders API, which allowed me to use the right texture coordinates and finally render the results you see above. This Week's Goals ----------------- -For this week, I plan to try a different approach Filipi, one of my mentors, told me to do. This approach was supposed to be the original -one, but a communication failure lead to this path I am currently in. This approach renders each KDE calculation into its own billboard, -and those are rendered together with additive blending. After this first pass, this render is captured into a texture and then rendered to -another big billboard. +For this week, I plan to try a different approach Filipi, one of my mentors, told me to do. This approach was supposed to be the original +one, but a communication failure lead to this path I am currently in. This approach renders each KDE calculation into its own billboard, +and those are rendered together with additive blending. After this first pass, this render is captured into a texture and then rendered to +another big billboard. -Also, I plan to refactor my draft PR `#804 `_ to make it more understandable, as its description still dates back to the time I was using the +Also, I plan to refactor my draft PR `#804 `_ to make it more understandable, as its description still dates back to the time I was using the flawed Framebuffer implementation, and my fellow GSoC contributors will eventually review it, and to do so, they will need to understand it. -Wish me luck! \ No newline at end of file +Wish me luck! diff --git a/docs/source/posts/2023/2023-07-15-week-7-praneeth.rst b/docs/source/posts/2023/2023-07-15-week-7-praneeth.rst index db375bd7c..dfcf0e6f6 100644 --- a/docs/source/posts/2023/2023-07-15-week-7-praneeth.rst +++ b/docs/source/posts/2023/2023-07-15-week-7-praneeth.rst @@ -17,7 +17,7 @@ Text background greater than the actual maximum size: :align: center :alt: Text background greater than the actual maximum size in ComboBox2D -Text offset from center: +Text offset from center: .. image:: https://github.com/fury-gl/fury/assets/64432063/0a3bc1e6-a566-4c08-9ca4-a191525b9c97 :align: center @@ -33,4 +33,4 @@ While fixing the issues with the tests for the **TextBlock2D** bounding box, I e What is coming up next? ----------------------- -I will continue working on the **TreeUI** and resolve the **TextBlock2D** error to ensure both PRs progress smoothly. \ No newline at end of file +I will continue working on the **TreeUI** and resolve the **TextBlock2D** error to ensure both PRs progress smoothly. diff --git a/docs/source/posts/2023/2023-07-17-week-7-joaodellagli.rst b/docs/source/posts/2023/2023-07-17-week-7-joaodellagli.rst index 9237ec1d7..481ce9be2 100644 --- a/docs/source/posts/2023/2023-07-17-week-7-joaodellagli.rst +++ b/docs/source/posts/2023/2023-07-17-week-7-joaodellagli.rst @@ -10,7 +10,7 @@ Hello everyone, welcome to another weekly blogpost! Let's talk about the current Last Week's Effort ------------------ -Having accomplished a KDE rendering to a billboard last week, I was then tasked with trying a different approach to how the +Having accomplished a KDE rendering to a billboard last week, I was then tasked with trying a different approach to how the rendering was done. So, to recap, below was how I was doing it: 1. Render one point's KDE offscreen to a single billboard, passing its position and sigma to the fragment shader as uniforms. @@ -21,7 +21,7 @@ rendering was done. So, to recap, below was how I was doing it: 6. Apply post processing effects (colormapping). 7. Render the result to the screen. -This approach was good, but it had some later limitations and issues that would probably take more processing time and attention to details (correct matrix +This approach was good, but it had some later limitations and issues that would probably take more processing time and attention to details (correct matrix transformations, etc) than the ideal. The different idea is pretty similar, but with some differences: 1. Activate additive blending in OpenGL. @@ -35,7 +35,7 @@ So I needed to basically do that. Was it Hard? ------------ -Fortunately, it wasn't so hard to do it in the end. Following those steps turned out pretty smooth, and after some days, +Fortunately, it wasn't so hard to do it in the end. Following those steps turned out pretty smooth, and after some days, I had the below result: .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/final_2d_plot.png @@ -52,14 +52,14 @@ This is a 2D KDE render of random 1000 points. For this I used the *"viridis"* c :align: center :alt: 3D KDE render -After those results, I refactored my PR `#804 `_ to better fit its current status, and it is +After those results, I refactored my PR `#804 `_ to better fit its current status, and it is now ready for review. Success! This Week's Goals ----------------- -After finishing the first iteration of my experimental program, the next step is to work on an API for KDE rendering. I plan to meet +After finishing the first iteration of my experimental program, the next step is to work on an API for KDE rendering. I plan to meet with my mentors and talk about the details of this API, so expect an update next week. Also, I plan to take a better look on my fellow GSoC FURY contributors work so when their PRs are ready for review, I will have to be better prepared for it. -Let's get to work! \ No newline at end of file +Let's get to work! diff --git a/docs/source/posts/2023/2023-07-22-week-8-praneeth.rst b/docs/source/posts/2023/2023-07-22-week-8-praneeth.rst index c921698cc..5ac792a9b 100644 --- a/docs/source/posts/2023/2023-07-22-week-8-praneeth.rst +++ b/docs/source/posts/2023/2023-07-22-week-8-praneeth.rst @@ -24,4 +24,4 @@ The pair programming session with my mentor proved to be immensely helpful, as i What is coming up next? ------------------------ -I will dedicate time to further enhancing the **TreeUI**. My focus will be on updating tree nodes and ensuring proper node positioning during movement. \ No newline at end of file +I will dedicate time to further enhancing the **TreeUI**. My focus will be on updating tree nodes and ensuring proper node positioning during movement. diff --git a/docs/source/posts/2023/2023-07-24-week-8-joaodellagli.rst b/docs/source/posts/2023/2023-07-24-week-8-joaodellagli.rst index a3b7af50d..e74031109 100644 --- a/docs/source/posts/2023/2023-07-24-week-8-joaodellagli.rst +++ b/docs/source/posts/2023/2023-07-24-week-8-joaodellagli.rst @@ -7,53 +7,53 @@ Week 8: The Birth of a Versatile API :category: gsoc -Hello everyone, it's time for another weekly blogpost! Today, I am going to tell you all about how is the KDE API development going, and +Hello everyone, it's time for another weekly blogpost! Today, I am going to tell you all about how is the KDE API development going, and to show you the potential this holds for the future! Last Week's Effort ------------------ -Last week I told you how I managed to render some KDE renders to the screen, both in 2D and 3D, as you may check by my last blogpost. -My new task was, as I had this example working, to start the API development. In a meeting with Bruno, one of my mentors, we debated +Last week I told you how I managed to render some KDE renders to the screen, both in 2D and 3D, as you may check by my last blogpost. +My new task was, as I had this example working, to start the API development. In a meeting with Bruno, one of my mentors, we debated on how could this work, reaching two options: 1. Implement the KDE in a single, simple actor. 2. Implement a KDE rendering manager, as a class. -The first one would have the advantage of being simple and pretty straightforward, as a user would only need to call the actor and have -it working on their hands, having the tradeoff of leaving some important steps for a clean API hidden and static. These steps I mention -are related to how this rendering works, as I have previously :doc:`showed you <2023-07-03-week-5-joaodellagli>`, it relies on post-processing effects, -which need an offscreen rendering, that for example are done by the *callback functions*. +The first one would have the advantage of being simple and pretty straightforward, as a user would only need to call the actor and have +it working on their hands, having the tradeoff of leaving some important steps for a clean API hidden and static. These steps I mention +are related to how this rendering works, as I have previously :doc:`showed you <2023-07-03-week-5-joaodellagli>`, it relies on post-processing effects, +which need an offscreen rendering, that for example are done by the *callback functions*. In short, these functions are instructions the user gives to the interactor to run inside the interaction loop. Inside FURY there are tree types of callbacks passed to the window interactor: 1. **Timer Callbacks**: Added to the window interactor, they are a set of instructions that will be called from time to time, with interval defined by the user. 2. **Window Callbacks**: Added directly to the window, they are a set of instructions called whenever an specific event is triggered. -3. **Interactor Callbacks**: Added to the window interactor, they are a set of instructions called whenever an specific interaction, for example a mouse left-click, is triggered. +3. **Interactor Callbacks**: Added to the window interactor, they are a set of instructions called whenever an specific interaction, for example a mouse left-click, is triggered. -In this API, I will be using the *Interactor Callback*, set by the ``window.add_iren_callback()`` function, that will be called whenever a *Render* -interaction is detected, and needs to be first passed to the onscreen manager. +In this API, I will be using the *Interactor Callback*, set by the ``window.add_iren_callback()`` function, that will be called whenever a *Render* +interaction is detected, and needs to be first passed to the onscreen manager. -These details are more complicated, and would need, for example, for the user to pass the onscreen manager to the ``actor.kde()`` function. +These details are more complicated, and would need, for example, for the user to pass the onscreen manager to the ``actor.kde()`` function. Also, in the case of a kde actor not being used anymore and being declared, the callback then passed would still exist inside the manager and be called even when the kde actor is not on screen anymore, which is not ideal. -Knowing these problems, we thought of a second option, that would have the advantage of not leaving those details and steps behind. It has -the tradeoff of maybe complicating things as it would need to be called after calling the effects manager, but as I will show you below, -it is not that complicated *at all*. +Knowing these problems, we thought of a second option, that would have the advantage of not leaving those details and steps behind. It has +the tradeoff of maybe complicating things as it would need to be called after calling the effects manager, but as I will show you below, +it is not that complicated *at all*. -I also reviewed my fellow GSoC contributors PR's as well, PR `#810 `_ and +I also reviewed my fellow GSoC contributors PR's as well, PR `#810 `_ and `#803 `_. Bruno told me to take a look as well on `Conventional Commits `_ , a way to standardize commits by prefixes, so I did that as well. So how did it go? ----------------- -Well, the implemented manager class is named ``EffectManager()`` and to initialize it you only need to pass the onscreen manager. +Well, the implemented manager class is named ``EffectManager()`` and to initialize it you only need to pass the onscreen manager. The onscreen manager is the standard FURY window manager you would use in a normal FURY-based program: .. code-block:: python - + # Onscreen manager setup from fury import window @@ -90,9 +90,9 @@ point: :alt: 3D KDE render After having that working, I experimented beyond. See, as I previously said, we are dealing here with *post-processing effects*, with KDE -being only one of the many existing ones, as this `Wikipedia Page `_ on post processing shows. -Knowing that, I tried one of the first filters I learned, the Laplacian one. This filter is, as its name hints, applying the -`Discrete Laplace Operator `_ in an image. This filter shows sudden changes of value, a +being only one of the many existing ones, as this `Wikipedia Page `_ on post processing shows. +Knowing that, I tried one of the first filters I learned, the Laplacian one. This filter is, as its name hints, applying the +`Discrete Laplace Operator `_ in an image. This filter shows sudden changes of value, a good way to detect borders. The process is the same as the kde actor, requiring only the actor you want to apply the filter to. Below, the result I got from applying that to a box actor: @@ -107,7 +107,7 @@ after another? Well, the example below shows that is possible as well: :align: center :alt: Double laplacian application on the box actor. -It still needs some tweaks and suffers from some bugs, but it works! Those represent important progress as it shows the versatility this +It still needs some tweaks and suffers from some bugs, but it works! Those represent important progress as it shows the versatility this API may present. I have also already implemented `grayscale` and `3x3 gaussian blur` as well: .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/gaussian_blur.png @@ -120,8 +120,8 @@ API may present. I have also already implemented `grayscale` and `3x3 gaussian b This Week's Goals ----------------- -My plans for this week are to keep working and polishing the API, mainly the KDE part, so it can be ready for a first review. +My plans for this week are to keep working and polishing the API, mainly the KDE part, so it can be ready for a first review. When that is ready, I plan to experiment with more filters and make this more dynamic, maybe implementing a way to apply custom kernel transformations, passed by the user, to the rendering process. This has been a really exciting journey and I am getting happy with the results! -Wish me luck! \ No newline at end of file +Wish me luck! diff --git a/docs/source/posts/2023/2023-07-29-week-9-praneeth.rst b/docs/source/posts/2023/2023-07-29-week-9-praneeth.rst index 478a53e5e..a01a99aad 100644 --- a/docs/source/posts/2023/2023-07-29-week-9-praneeth.rst +++ b/docs/source/posts/2023/2023-07-29-week-9-praneeth.rst @@ -22,4 +22,4 @@ I encountered some peculiar test failures that were indirectly related to the *T What is coming up next? ------------------------ -My next priority will be completing the *SpinBoxUI* now that the *TextBlock2D* is fixed and successfully integrated. \ No newline at end of file +My next priority will be completing the *SpinBoxUI* now that the *TextBlock2D* is fixed and successfully integrated. diff --git a/docs/source/posts/2023/2023-07-31-week-9-joaodellagli.rst b/docs/source/posts/2023/2023-07-31-week-9-joaodellagli.rst index d02d9fd2a..90605dcc3 100644 --- a/docs/source/posts/2023/2023-07-31-week-9-joaodellagli.rst +++ b/docs/source/posts/2023/2023-07-31-week-9-joaodellagli.rst @@ -11,28 +11,28 @@ Hello everyone, it's time for another weekly blogpost! Today, I am going to upda Last Week's Effort ------------------ -After having finished a first draft of the API that will be used for the KDE rendering, and showing how it could be used -for other post-processing effects, my goal was to clean the code and try some details that would add to it so it could be better +After having finished a first draft of the API that will be used for the KDE rendering, and showing how it could be used +for other post-processing effects, my goal was to clean the code and try some details that would add to it so it could be better complete. Having that in mind, I invested in three work fronts: 1. Fixing some bugs related to the rendering more than one post-processing effect actor. 2. Experimenting with other rendering kernels (I was using the *gaussian* one only). -3. Completing the KDE render by renormalizing the values in relation to the number of points (one of the core KDE details). +3. Completing the KDE render by renormalizing the values in relation to the number of points (one of the core KDE details). Both three turned out more complicated than it initially seemed, as I will show below. So how did it go? ----------------- -The first one I did on monday-tuesday, and I had to deal with some issues regarding scaling and repositioning. Due to implementation -choices, the final post-processed effects were rendered either bigger than they were in reality, or out of their original place. -After some time dedicated to finding the root of the problems, I could fix the scaling issue, however I realised I would need to, -probably, rethink the way the API was implemented. As this general post-processing effects is a side-project that comes as a consequence of +The first one I did on monday-tuesday, and I had to deal with some issues regarding scaling and repositioning. Due to implementation +choices, the final post-processed effects were rendered either bigger than they were in reality, or out of their original place. +After some time dedicated to finding the root of the problems, I could fix the scaling issue, however I realised I would need to, +probably, rethink the way the API was implemented. As this general post-processing effects is a side-project that comes as a consequence of my main one, I decided to leave that investment to another time, as I would need to guarantee the quality of the second. -The second was an easy and rather interesting part of my week, as I just needed to setup new kernel shaders. Based on +The second was an easy and rather interesting part of my week, as I just needed to setup new kernel shaders. Based on `scikit-learn KDE documentation `_, I could successfully implement the following kernels: -* Gaussian +* Gaussian .. math:: K(x, y) = e^{\frac{-(x^2 + y^2)}{2\sigma^2}} @@ -70,26 +70,26 @@ That outputted the following (beautiful) results for a set of 1000 random points The third one is still being a trickier challenge. If you recall from my first blogposts, I spent something around *one month* trying to setup -float framebuffer objects to FURY with VTK so I could use them in my project. After spending all of that time with no results, -me and Bruno, my mentor, :doc:`found a way <2023-07-03-week-5-joaodellagli.rst>` to do what we wanted to do, but using a different VTK class, -`vtkWindowToImageFilter `_. Well, it was a good workaround back then and -it lead me all the way here, however now it is costing a price. The float framebuffers were an important part of the project because they -would allow us to pass *32-bit float information* from one shader to another, which would be important as they would allow the densities to +float framebuffer objects to FURY with VTK so I could use them in my project. After spending all of that time with no results, +me and Bruno, my mentor, :doc:`found a way <2023-07-03-week-5-joaodellagli.rst>` to do what we wanted to do, but using a different VTK class, +`vtkWindowToImageFilter `_. Well, it was a good workaround back then and +it lead me all the way here, however now it is costing a price. The float framebuffers were an important part of the project because they +would allow us to pass *32-bit float information* from one shader to another, which would be important as they would allow the densities to have higher precision and more fidelity to the calculations. When rendering a KDE of a given set of points, we use the below function: .. math:: KDE(x, y) = \frac{1}{n} \sum_{i = 0}^n K(x, y) -If the number of points :math:`n` is big enough, some KDE results will be really low. This presents a real problem to our implementation because, without -the float framebuffers, it is currently only being possible to pass *8-bit unsigned char* information, that only allows 256 values. +If the number of points :math:`n` is big enough, some KDE results will be really low. This presents a real problem to our implementation because, without +the float framebuffers, it is currently only being possible to pass *8-bit unsigned char* information, that only allows 256 values. This is far from ideal, as low values would have alone densities low enough to disappear. This presented a problem as to renormalize the densities, I was retrieving the texture to the CPU, calculating its minimum and maximum values, and passing to the fragment shader as uniforms for the renormalization, which didn't work if the maximum values calculated were zero. -One solution I thought to solve that was a really heavy workaround: if an unsigned float is 32-bit and I have exactly 4 8-bit -unsigned chars, why not try to pack this float into these 4 chars? Well, this is an interesting approach which I figured out is already an +One solution I thought to solve that was a really heavy workaround: if an unsigned float is 32-bit and I have exactly 4 8-bit +unsigned chars, why not try to pack this float into these 4 chars? Well, this is an interesting approach which I figured out is already an old one, being reported in `GPU Gems's chapter 12 `_. -Unfortunately I haven't tried yet this implementation yet, and went for one I thought myself, which haven't exactly worked. I also tried +Unfortunately I haven't tried yet this implementation yet, and went for one I thought myself, which haven't exactly worked. I also tried this implementation from `Aras Pranckevičius' website `_, which seems to be working, even though not perfectly: @@ -97,7 +97,7 @@ to be working, even though not perfectly: :align: center :alt: Noisy float to RGBA encoding -As you can see, this implementation is *really noisy*. I think this has to deal with floating point rounding errors, so to try to mitigate +As you can see, this implementation is *really noisy*. I think this has to deal with floating point rounding errors, so to try to mitigate that, I experimented applying a *13x13 gaussian blur* to it. Below, what I got from that: .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/blurred_kde.png @@ -108,9 +108,9 @@ That looks way better, even though not ideal yet. This Week's Goals ----------------- -Talking with my mentors, we decided it was better if I focused on the version without the renormalization for now, as it was already -done and running fine. So for this week, I plan to clean my PR to finally have it ready for a first review, and maybe add to it a little -UI tool to control the intensity of the densities. That should take me some time and discussion, but I hope for it to be ready by the +Talking with my mentors, we decided it was better if I focused on the version without the renormalization for now, as it was already +done and running fine. So for this week, I plan to clean my PR to finally have it ready for a first review, and maybe add to it a little +UI tool to control the intensity of the densities. That should take me some time and discussion, but I hope for it to be ready by the end of the week. Let's get to work! diff --git a/docs/source/posts/2023/2023-08-05-week-10-praneeth.rst b/docs/source/posts/2023/2023-08-05-week-10-praneeth.rst index 4dfeff75b..96ebdefef 100644 --- a/docs/source/posts/2023/2023-08-05-week-10-praneeth.rst +++ b/docs/source/posts/2023/2023-08-05-week-10-praneeth.rst @@ -18,4 +18,4 @@ Thankfully, this week was quite smooth sailing without any major roadblocks. What is coming up next? ----------------------- -Looking ahead, my plan is to finalize the integration of the updated ``TextBlock`` and ``SpinBoxUI`` components. This entails making sure that everything works seamlessly together and is ready for the next stages of development. \ No newline at end of file +Looking ahead, my plan is to finalize the integration of the updated ``TextBlock`` and ``SpinBoxUI`` components. This entails making sure that everything works seamlessly together and is ready for the next stages of development. diff --git a/docs/source/posts/2023/2023-08-07-week-10-joaodellagli.rst b/docs/source/posts/2023/2023-08-07-week-10-joaodellagli.rst index 51beb1e9f..6ee105e93 100644 --- a/docs/source/posts/2023/2023-08-07-week-10-joaodellagli.rst +++ b/docs/source/posts/2023/2023-08-07-week-10-joaodellagli.rst @@ -6,22 +6,22 @@ Week 10: Ready for Review! :tags: google :category: gsoc -Hello everyone, it's time for another weekly blogpost! +Hello everyone, it's time for another weekly blogpost! Last Week's Effort ------------------ -After talking with my mentors, I was tasked with getting my API PR `#826 `_ ready for review, -as it still needed some polishing, and the most important of all, it needed its tests working, as this was something I haven't invested time since its creation. +After talking with my mentors, I was tasked with getting my API PR `#826 `_ ready for review, +as it still needed some polishing, and the most important of all, it needed its tests working, as this was something I haven't invested time since its creation. Having that in mind, I have spent the whole week cleaning whatever needed, writing the tests, and also writing a simple example of its -usage. I also tried implementing a little piece of UI so the user could control the intensity of the bandwidth of the KDE render, but +usage. I also tried implementing a little piece of UI so the user could control the intensity of the bandwidth of the KDE render, but I had a little problem I will talk about below. So how did it go? ----------------- Fortunately, for the cleaning part, I didn't have any trouble, and my PR is finally ready for review! The most complicated part was to write the tests, as this is something that -requires attention to understand what needs to be tested, exactly. As for the UI part, I managed to have a slider working for the -intensity, however, it was crashing the whole program for a reason, so I decided to leave this idea behind for now. +requires attention to understand what needs to be tested, exactly. As for the UI part, I managed to have a slider working for the +intensity, however, it was crashing the whole program for a reason, so I decided to leave this idea behind for now. Below, an example of how this should work: .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/slider.gif @@ -31,7 +31,7 @@ Below, an example of how this should work: This Week's Goals ----------------- After a meeting with my mentors, we decided that this week's focus should be on finding a good usage example of the KDE rendering feature, -to have it as a showcase of the capability of this API. Also, they hinted me some changes that need to be done regarding the API, so I -will also invest some time on refactoring it. +to have it as a showcase of the capability of this API. Also, they hinted me some changes that need to be done regarding the API, so I +will also invest some time on refactoring it. -Wish me luck! \ No newline at end of file +Wish me luck! diff --git a/docs/source/posts/2023/2023-08-14-week-11-joaodellagli.rst b/docs/source/posts/2023/2023-08-14-week-11-joaodellagli.rst index fd5c9dace..e3dc59bf1 100644 --- a/docs/source/posts/2023/2023-08-14-week-11-joaodellagli.rst +++ b/docs/source/posts/2023/2023-08-14-week-11-joaodellagli.rst @@ -6,25 +6,25 @@ Week 11: A Refactor is Sometimes Needed :tags: google :category: gsoc -Hello everyone, it's time for another weekly blogpost! Today I am going to share some updates on the API refactoring +Hello everyone, it's time for another weekly blogpost! Today I am going to share some updates on the API refactoring I was working on with my mentors. Last Week's Effort ------------------ -As I shared with you :doc:`last week <2023-08-07-week-10-joaodellagli>`, the first draft of my API was finally ready for review, as +As I shared with you :doc:`last week <2023-08-07-week-10-joaodellagli>`, the first draft of my API was finally ready for review, as I finished tweaking some remaining details missing. I was tasked with finding a good example of the usage of the tools we proposed, -and I started to do that, however after testing it with some examples, I figured out some significant bugs were to be fixed. Also, -after some reviews and hints from some of my mentors and other GSoC contributors, we realised that some refactoring should be done, -mainly focused on avoiding bad API usage from the user. +and I started to do that, however after testing it with some examples, I figured out some significant bugs were to be fixed. Also, +after some reviews and hints from some of my mentors and other GSoC contributors, we realised that some refactoring should be done, +mainly focused on avoiding bad API usage from the user. So how did it go? ----------------- -Initially, I thought only one bug was the source of the issues the rendering presented, but it turned out to be two, which I will -explain further. +Initially, I thought only one bug was the source of the issues the rendering presented, but it turned out to be two, which I will +explain further. -The first bug was related to scaling and misalignment of the KDE render. The render of the points being post-processed was not only +The first bug was related to scaling and misalignment of the KDE render. The render of the points being post-processed was not only with sizes different from the original set size, but it was also misaligned, making it appear in positions different from the points' -original ones. After some time spent, I figured out the bug was related to the texture coordinates I was using. Before, this is how +original ones. After some time spent, I figured out the bug was related to the texture coordinates I was using. Before, this is how my fragment shader looked: .. code-block:: C @@ -33,26 +33,26 @@ my fragment shader looked: vec2 tex_coords = res_factor*normalizedVertexMCVSOutput.xy*0.5 + 0.5; float intensity = texture(screenTexture, tex_coords).r; -It turns out using this texture coordinates for *this case* was not the best choice, as even though it matches the fragment positions, -the idea here was to render the offscreen window, which has the same size as the onscreen one, to the billboard actor. With that in mind, -I realised the best choice was using texture coordinates that matched the whole screen positions, coordinates that were derived from the +It turns out using this texture coordinates for *this case* was not the best choice, as even though it matches the fragment positions, +the idea here was to render the offscreen window, which has the same size as the onscreen one, to the billboard actor. With that in mind, +I realised the best choice was using texture coordinates that matched the whole screen positions, coordinates that were derived from the ``gl_FragCoord.xy``, being the division of that by the resolution of the screen, for normalization. Below, the change made: .. code-block:: C vec2 tex_coords = gl_FragCoord.xy/res; float intensity = texture(screenTexture, tex_coords).r; - -This change worked initially, although with some problems, that later revealed the resolution of the offscreen window needed to be + +This change worked initially, although with some problems, that later revealed the resolution of the offscreen window needed to be updated inside the callback function as well. Fixing that, it was perfectly aligned and scaled! The second bug was related with the handling of the bandwidth, former sigma parameter. I realised I wasn't dealing properly with the option of the user passing only -one single bandwidth value being passed, so when trying that, only the first point was being rendered. I also fixed that and it worked, +one single bandwidth value being passed, so when trying that, only the first point was being rendered. I also fixed that and it worked, so cheers! -As I previously said, the bugs were not the only details I spent my time on last week. Being reviewed, the API design, even -though simple, showed itself vulnerable to bad usage from the user side, requiring some changes. The changes suggested by mentors were, -to, basically, take the ``kde`` method out of the ``EffectManager`` class, and create a new class from it inside an ``effects`` module, +As I previously said, the bugs were not the only details I spent my time on last week. Being reviewed, the API design, even +though simple, showed itself vulnerable to bad usage from the user side, requiring some changes. The changes suggested by mentors were, +to, basically, take the ``kde`` method out of the ``EffectManager`` class, and create a new class from it inside an ``effects`` module, like it was a special effects class. With this change, the KDE setup would go from: .. code-block:: python @@ -73,8 +73,8 @@ To: em.add(kde_effect) -Not a gain in line shortening, however, a gain in security, as preventing users from misusing the kde_actor. Something worth noting is -that I learned how to use the ``functools.partial`` function, that allowed me to partially call the callback function with only some +Not a gain in line shortening, however, a gain in security, as preventing users from misusing the kde_actor. Something worth noting is +that I learned how to use the ``functools.partial`` function, that allowed me to partially call the callback function with only some parameters passed. diff --git a/docs/source/posts/2023/2023-08-19-week-12-praneeth.rst b/docs/source/posts/2023/2023-08-19-week-12-praneeth.rst index d34b54ad0..f80ea0ebf 100644 --- a/docs/source/posts/2023/2023-08-19-week-12-praneeth.rst +++ b/docs/source/posts/2023/2023-08-19-week-12-praneeth.rst @@ -50,4 +50,3 @@ Among the challenges I faced, one notable instance involved addressing the visib What is coming up next? ----------------------- The ``FileDialog`` implementation is nearly finalized, and my plan is to work on any review, feedback or suggestions that might arise. Following this, I will shift my attention towards addressing the ``TreeUI``. - diff --git a/docs/source/posts/2023/2023-08-21-joaodellagli-final-report.rst b/docs/source/posts/2023/2023-08-21-joaodellagli-final-report.rst new file mode 100644 index 000000000..33650149a --- /dev/null +++ b/docs/source/posts/2023/2023-08-21-joaodellagli-final-report.rst @@ -0,0 +1,360 @@ +.. image:: https://developers.google.com/open-source/gsoc/resources/downloads/GSoC-logo-horizontal.svg + :height: 40 + :target: https://summerofcode.withgoogle.com/programs/2023/projects/ED0203De + +.. image:: https://www.python.org/static/img/python-logo@2x.png + :height: 40 + :target: https://summerofcode.withgoogle.com/programs/2023/organizations/python-software-foundation + +.. image:: https://python-gsoc.org/logos/fury_logo.png + :width: 40 + :target: https://fury.gl/latest/index.html + + + +Google Summer of Code Final Work Product +======================================== + +.. post:: August 21 2023 + :author: João Victor Dell Agli Floriano + :tags: google + :category: gsoc + +- **Name:** João Victor Dell Agli Floriano +- **Organisation:** Python Software Foundation +- **Sub-Organisation:** FURY +- **Project:** `FURY - Project 2. Fast 3D kernel-based density rendering using billboards. `_ + + +Abstract +-------- +This project had the goal to implement 3D Kernel Density Estimation rendering to FURY. Kernel Density Estimation, or KDE, is a +statistical method that uses kernel smoothing for modeling and estimating the density distribution of a set of points defined +inside a given region. For its graphical implementation, it was used post-processing techniques such as offscreen rendering to +framebuffers and colormap post-processing as tools to achieve the desired results. This was completed with a functional basic KDE +rendering result, that relies on a solid and easy-to-use API, as well as some additional features. + +Proposed Objectives +------------------- + +- **First Phase** : Implement framebuffer usage in FURY + * Investigate the usage of float framebuffers inside FURY's environment. + * Implement a float framebuffer API. + +- **Second Phase** : Shader-framebuffer integration + * Implement a shader that uses a colormap to render framebuffers. + * Escalate this rendering for composing multiple framebuffers. + +- **Third Phase** : KDE Calculations + * Investigate KDE calculation for point-cloud datasets. + * Implement KDE calculation inside the framebuffer rendering shaders. + * Test KDE for multiple datasets. + +Objectives Completed +-------------------- + +- **Implement framebuffer usage in FURY** + The first phase, addressed from *May/29* to *July/07*, started with the investigation of + `VTK's Framebuffer Object `_, a vital part of this project, to understand + how to use it properly. + + Framebuffer Objects, abbreviated as FBOs, are the key to post-processing effects in OpenGL, as they are used to render things offscreen and save the resulting image to a texture + that will be later used to apply the desired post-processing effects within the object's `fragment shader `_ + rendered to screen, in this case, a `billboard `_. In the case of the + `Kernel Density Estimation `_ post-processing effect, we need a special kind of FBO, one that stores textures' + values as floats, different from the standard 8-bit unsigned int storage. This is necessary because the KDE rendering involves rendering every KDE point calculation + to separate billboards, rendered to the same scene, which will have their intensities, divided by the number of points rendered, blended with + `OpenGL Additive Blending `_, and if a relative big number of points are rendered at the + same time, 32-bit float precision is needed to guarantee that small-intensity values will not be capped to zero, and disappear. + + After a month going through VTK's FBO documentation and weeks spent trying different approaches to this method, it would not work + properly, as some details seemed to be missing from the documentation, and asking the community haven't solved the problem as well. + Reporting that to my mentors, which unsuccessfully tried themselves to make it work, they decided it was better if another path was taken, using + `VTK's WindowToImageFilter `_ method as a workaround, described + in this `blogpost `_. This method helped the development of + three new functions to FURY, *window_to_texture()*, *texture_to_actor()* and *colormap_to_texture()*, that allow the passing of + different kinds of textures to FURY's actor's shaders, the first one to capture a window and pass it as a texture to an actor, + the second one to pass an external texture to an actor, and the third one to specifically pass a colormap as a texture to an + actor. It is important to say that *WindowToImageFilter()* is not the ideal way to make it work, as this method does not seem to + support float textures. However, a workaround to that is currently being worked on, as I will describe later on. + + *Pull Requests:* + + - **KDE Rendering Experimental Program (Needs major revision):** `https://github.com/fury-gl/fury/pull/804 `_ + + The result of this whole FBO and WindowToImageFilter experimentation is well documented in PR + `#804 `_ that implements an experimental version of a KDE rendering program. + The future of this PR, as discussed with my mentors, is to be better documented to be used as an example for developers on + how to develop features in FURY with the tools used, and it shall be done soon. + +- **Shader-framebuffer integration** + The second phase, which initially was thought of as "Implement a shader that uses a colormap to render framebuffers" and "Escalate this + rendering for composing multiple framebuffers" was actually a pretty simple phase that could be addressed in one week, *July/10* + to *July/17*, done at the same time as the third phase goal, documented in this + `blogpost `_. As FURY already had a tool for generating and + using colormaps, they were simply connected to the shader part of the program as textures, with the functions explained above. + Below, is the result of the *matplotlib viridis* colormap passed to a simple gaussian KDE render: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/final_2d_plot.png + :align: center + :alt: Final 2D plot + + That is also included in PR `#804 `_. Having the 2D plot ready, some time was taken to + figure out how to enable a 3D render, that includes rotation and other movement around the set rendered, which was solved by + learning about the callback properties that exist inside *VTK*. Callbacks are ways to enable code execution inside the VTK rendering + loop, enclosed inside *vtkRenderWindowInteractor.start()*. If it is desired to add a piece of code that, for example, passes a time + variable to the fragment shader over time, a callback function can be declared: + + .. code-block:: python + + from fury import window + t = 0 + showm = window.ShowManager(...) + + def callback_function: + t += 0.01 + pass_shader_uniforms_to_fs(t, "t") + + showm.add_iren_callback(callback_function, "RenderEvent") + + The piece of code above created a function that updates the time variable *t* in every *"RenderEvent"*, and passes it to the + fragment shader. With that property, the camera and some other parameters could be updated, which enabled 3D visualization, that + then, outputted the following result, using *matplotlib inferno* colormap: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/3d_kde_gif.gif + :align: center + :alt: 3D Render gif + +- **KDE Calculations** (ongoing) + As said before, the second and third phases were done simultaneously, so after having a way to capture the window and use it as a + texture ready, the colormap ready, and an initial KDE render ready, all it was needed to do was to improve the KDE calculations. + As this `Wikipedia page `_ explains, a KDE calculation is to estimate an + abstract density around a set of points defined inside a given region with a kernel, that is a function that models the density + around a point based on its associated distribution :math:`\sigma`. + + A well-known kernel is, for example, the **Gaussian Kernel**, that says that the density around a point :math:`p` with distribution + :math:`\sigma` is defined as: + + .. math:: + + GK_{\textbf{p}, \sigma} (\textbf{x}) = e^{-\frac{1}{2}\frac{||\textbf{x} - \textbf{p}||^2}{\sigma^2}} + + Using that kernel, we can calculate the KDE of a set of points :math:`P` with associated distributions :math:`S` calculating their individual + Gaussian distributions, summing them up and dividing them by the total number of points :math:`n`: + + .. math:: + + KDE(A, S)=\frac{1}{n}\sum_{i = 0}^{n}GK(x, p_{i}, \sigma_{i}) + + So I dove into implementing all of that into the offscreen rendering part, and that is when the lack of a float framebuffer would + charge its cost. As it can be seen above, just calculating each point's density isn't the whole part, as I also need to divide + everyone by the total number of points :math:`n`, and then sum them all. The problem is that, if the number of points its big enough, + the individual densities will be really low, and that would not be a problem for a 32-bit precision float framebuffer, but that is + *definitely* a problem for a 8-bit integer framebuffer, as small enough values will simply underflow and disappear. That issue is + currently under investigation, and some solutions have already being presented, as I will show in the **Objectives in Progress** + section. + + Apart from that, after having the experimental program ready, I focused on modularizing it into a functional and simple API + (without the :math:`n` division for now), and I could get a good set of results from that. The API I first developed implemented the + *EffectManager* class, responsible for managing all of the behind-the-scenes steps necessary for the kde render to work, + encapsulated inside the *ÈffectManager.kde()* method. It had the following look: + + .. code-block:: python + from fury.effect_manager import EffectManager + from fury import window + + showm = window.ShowManager(...) + + # KDE rendering setup + em = EffectManager(showm) + kde_actor = em.kde(...) + # End of KDE rendering setup + + showmn.scene.add(kde_actor) + + showm.start() + + Those straightforward instructions, that hid several lines of code and setup, could manage to output the following result: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/fianl_3d_plot.png + :align: center + :alt: API 3D KDE plot + + And this was not the only feature I had implemented for this API, as the use of *WindowToImageFilter* method opened doors for a + whole new world for FURY: The world of post-processing effects. With this features setup, I managed to implement a *gaussian blur* + effect, a *grayscale* effect and a *Laplacian* effect for calculating "borders": + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/gaussian_blur.png + :align: center + :alt: Gaussian Blur effect + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/grayscale.png + :align: center + :alt: Grayscale effect + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/laplacian1.gif + :align: center + :alt: Laplacian effect + + As this wasn't the initial goal of the project and I still had several issues to deal with, I have decided to leave these features as a + future addition. + + Talking with my mentors, we realized that the first KDE API, even though simple, could lead to bad usage from users, as the + *em.kde()* method, that outputted a *FURY actor*, had dependencies different from any other object of its kind, making it a new + class of actors, which could lead to confusion and bad handling. After some pair programming sessions, they instructed me to take + a similar, but different road from what I was doing, turning the kde actor into a new class, the *KDE* class. This class would + have almost the same set of instructions present in the prior method, but it would break them in a way it would only be completely + set up after being passed to the *EffectManager* via its add function. Below, how the refactoring handles it: + + .. code-block:: python + + from fury.effects import EffectManager, KDE + from fury import window + + showm = window.ShowManager(...) + + # KDE rendering setup + em = EffectManager(showm) + kde_effect = KDE(...) + em.add(kde_effect) + # End of KDE rendering setup + + showm.start() + + Which outputted the same results as shown above. It may have cost some simplicity as we are now one line farther from having it + working, but it is more explicit in telling the user this is not just a normal actor. + + Another detail I worked on was the kernel variety. The Gaussian Kernel isn't the only one available to model density distributions, + there are several others that can do that job, as it can be seen in this `scikit-learn piece of documentation `_ + and this `Wikipedia page on kernels `_. Based on the scikit-learn KDE + implementation, I worked on implementing the following kernels inside our API, that can be chosen as a parameter when calling the + *KDE* class: + + * Cosine + * Epanechnikov + * Exponential + * Gaussian + * Linear + * Tophat + + Below, the comparison between them using the same set of points and bandwidths: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/kernels.png + :align: center + :alt: Comparison between the six implemented kernels + + + *Pull Requests*: + + - **First Stage of the KDE Rendering API (will be merged soon)**: `https://github.com/fury-gl/fury/pull/826 `_ + + All of this work culminated in PR `#826 `_, that proposes to add the first stage of + this API (there are some details yet to be completed, like the :math:`n` division) to FURY. This PR added the described API, and also + proposed some minor changes to some already existing FURY functions related to callbacks, changes necessary for this and other + future applications that would use it to work. It also added the six kernels described, and a simple documented example on how + to use this feature. + +Other Objectives +---------------- + +- **Stretch Goals** : SDE Implementation, Network/Graph visualization using SDE/KDE, Tutorials + * Investigate SDE calculation for surface datasets. + * Implement SDE calculation inside the framebuffer rendering shaders. + * Test SDE for multiple datasets. + * Develop comprehensive tutorials that explain SDE concepts and FURY API usage. + * Create practical, scenario-based tutorials using real datasets and/or simulations. + +Objectives in Progress +---------------------- + +- **KDE Calculations** (ongoing) + The KDE rendering, even though almost complete, have the $n$ division, an important step, missing, as this normalization allows colormaps + to cover the whole range o values rendered. The lack of a float FBO made a big difference in the project, as the search for a functional implementation of it not only delayed the project, but it is vital for + the correct calculations to work. + + For the last part, a workaround thought was to try an approach I later figured out is an old one, as it can be check in + `GPU Gems 12.3.3 section `_: + If I need 32-bit float precision and I got 4 8-bit integer precision available, why not trying to pack this float into this RGBA + texture? I have first tried to do one myself, but it didn't work for some reason, so I tried `Aras Pranckevičius `_ + implementation, that does the following: + + .. code-block:: GLSL + + vec4 float_to_rgba(float value) { + vec4 bitEnc = vec4(1.,256.,65536.0,16777216.0); + vec4 enc = bitEnc * value; + enc = fract(enc); + enc -= enc.yzww * vec2(1./255., 0.).xxxy; + return enc; + } + + That initially worked, but for some reason I am still trying to understand, it is resulting in a really noisy texture: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/noisy%20kde.png + :align: center + :alt: Noisy KDE render + + One way to try to mitigate that while is to pass this by a gaussian blur filter, to try to smooth out the result: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/blurred_kde.png + :align: center + :alt: Blurred result + + But it is not an ideal solution as well, as it may lead to distortions in the actual density values, depending on the application of + the KDE. Now, my goal is to first find the root of the noise problem, and then, if that does not work, try to make the gaussian filter + work. + + Another detail that would be a good addition to the API is UI controls. Filipi, one of my mentors, told me it would be a good feature + if the user could control the intensities of the bandwidths for a better structural visualization of the render, and knowing FURY already + have a good set of `UI elements `_, I just needed to integrate + that into my program via callbacks. I tried implementing an intensity slider. However, for some reason, it is making the program crash + randomly, for reasons I still don't know, so that is another issue under investigation. Below, we show a first version of that feature, + which was working before the crashes: + + .. image:: https://raw.githubusercontent.com/JoaoDell/gsoc_assets/main/images/slider.gif + :align: center + :alt: Slider for bandwidths + + *Pull Requests* + + - **UI intensity slider for the KDE rendering API (draft)**: `https://github.com/fury-gl/fury/pull/849 `_ + - **Post-processing effects for FURY Effects API (draft)**: `https://github.com/fury-gl/fury/pull/850 `_ + + +GSoC Weekly Blogs +----------------- + +- My blog posts can be found at `FURY website `_ and `Python GSoC blog `_. + +Timeline +-------- + ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Date | Description | Blog Post Link | ++=====================+====================================================+===========================================================================================================================================================================================================+ +| Week 0 (29-05-2023) | The Beginning of Everything | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 1 (05-06-2022) | The FBO Saga | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 2 (12-06-2022) | The Importance of (good) Documentation | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 3 (19-06-2022) | Watch Your Expectations | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 4 (26-06-2022) | Nothing is Ever Lost | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 5 (03-07-2022) | All Roads Lead to Rome | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 6 (10-07-2022) | Things are Starting to Build Up | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 7 (17-07-2022) | Experimentation Done | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 8 (24-07-2022) | The Birth of a Versatile API | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 9 (31-07-2022) | It is Polishing Time! | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 10 (07-08-2022)| Ready for Review! | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 11 (14-08-2022)| A Refactor is Sometimes Needed | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 12 (21-08-2022)| Now That is (almost) a Wrap! | `FURY `__ - `Python `__ | ++---------------------+----------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/docs/source/posts/2023/2023-08-21-week-12-joaodellagli.rst b/docs/source/posts/2023/2023-08-21-week-12-joaodellagli.rst index 83ad86cf4..3f8289135 100644 --- a/docs/source/posts/2023/2023-08-21-week-12-joaodellagli.rst +++ b/docs/source/posts/2023/2023-08-21-week-12-joaodellagli.rst @@ -6,21 +6,21 @@ Week 12: Now That is (almost) a Wrap! :tags: google :category: gsoc -Hello everyone, it's time for another GSoC blogpost! Today, I am going to talk about some minor details I worked on last week on my +Hello everyone, it's time for another GSoC blogpost! Today, I am going to talk about some minor details I worked on last week on my project. Last Week's Effort ------------------ -After the API refactoring was done last week, I focused on addressing the reviews I would get from it. The first issues I addressed was related to -style, as there were some minor details my GSoC contributors pointed out that needed change. Also, I have addressed an issue I was having -with the `typed hint` of one of my functions. Filipi, my mentor, showed me there is a way to have more than one typed hint in the same parameter, +After the API refactoring was done last week, I focused on addressing the reviews I would get from it. The first issues I addressed was related to +style, as there were some minor details my GSoC contributors pointed out that needed change. Also, I have addressed an issue I was having +with the `typed hint` of one of my functions. Filipi, my mentor, showed me there is a way to have more than one typed hint in the same parameter, all I needed to do was to use the `Union` class from the `typing` module, as shown below: .. code-block:: python from typing import Union as tUnion from numpy import ndarray - + def function(variable : tUnion(float, np.ndarray)): pass @@ -32,8 +32,8 @@ All went fine with no difficult at all, thankfully. The Next Steps -------------- -My next plans are, after having PR `#826 `_ merged, to work on the float encoding issue described in -:doc:`this blogpost<2023-07-31-week-9-joaodellagli>`. Also, I plan to tackle the UI idea once again, to see if I can finally give the user +My next plans are, after having PR `#826 `_ merged, to work on the float encoding issue described in +:doc:`this blogpost<2023-07-31-week-9-joaodellagli>`. Also, I plan to tackle the UI idea once again, to see if I can finally give the user a way to control the intensities of the distributions. Wish me luck! diff --git a/docs/source/posts/2023/2023-08-24-final-report-tvcastillod.rst b/docs/source/posts/2023/2023-08-24-final-report-tvcastillod.rst new file mode 100644 index 000000000..a1d730e1b --- /dev/null +++ b/docs/source/posts/2023/2023-08-24-final-report-tvcastillod.rst @@ -0,0 +1,174 @@ +.. image:: https://developers.google.com/open-source/gsoc/resources/downloads/GSoC-logo-horizontal.svg + :height: 50 + :align: center + :target: https://summerofcode.withgoogle.com/programs/2023/projects/ymwnLwtT + +.. image:: https://www.python.org/static/community_logos/python-logo.png + :width: 40% + :target: https://summerofcode.withgoogle.com/programs/2023/organizations/python-software-foundation + +.. image:: https://python-gsoc.org/logos/FURY.png + :width: 25% + :target: https://fury.gl/latest/index.html + +Google Summer of Code Final Work Product +======================================== + +.. post:: August 24 2023 + :author: Tania Castillo + :tags: google + :category: gsoc + +- **Name:** Tania Castillo +- **Organisation:** Python Software Foundation +- **Sub-Organisation:** FURY +- **Project:** `SDF-based uncertainty representation for dMRI glyphs `_ + + +Abstract +-------------------- +Diffusion Magnetic Resonance Imaging (dMRI) is a non-invasive imaging technique used by neuroscientists to measure the diffusion of water molecules in biological tissue. The directional information is reconstructed using either a Diffusion Tensor Imaging (DTI) or High Angular Resolution Diffusion Imaging (HARDI) based model, which is graphically represented as tensors and Orientation Distribution Functions (ODF). Traditional rendering engines discretize Tensor and ODF surfaces using triangles or quadrilateral polygons, making their visual quality depending on the number of polygons used to build the 3D mesh, which might compromise real-time display performance. This project proposes a methodological approach to further improve the visualization of DTI tensors and HARDI ODFs glyphs by using well-established techniques in the field of computer graphics, such as geometry amplification, billboarding, signed distance functions (SDFs), and ray marching. + + +Proposed Objectives +------------------- + +- Implement a parallelized version of computer-generated billboards using geometry shaders for amplification. +- Model the mathematical functions that express the geometry of ellipsoid glyphs and implement them using Ray Marching techniques. +- Model the mathematical functions that express the geometry of ODF glyphs and implement them using Ray Marching techniques. +- Use SDF properties and techniques to represent the uncertainty of dMRI reconstruction models. + + +Objectives Completed +-------------------- + +Ellipsoid actor implemented with SDF +************************************ + +A first approach for tensor glyph generation has been made, using ray marching and SDF applied to a box. The current implementation (``tensor_slicer``) requires a sphere with a specific number of vertices to be deformed. Based on this model, a sphere with more vertices is needed to get a higher resolution. Because the ray marching technique does not use polygonal meshes, it is possible to define perfectly smooth surfaces and still obtain a fast rendering. + +Details of the implementation: + +- *Vertex shader pre-calculations*: Some minor calculations are done in the vertex shader. One, corresponding to the eigenvalues constraining and min-max normalization, are to avoid incorrect visualizations when the difference between the eigenvalues is too large. And the other is related to the tensor matrix calculation given by the diffusion tensor definition :math:`T = R^{−1}\Lambda R`, where :math:`R` is a rotation matrix that transforms the standard basis onto the eigenvector basis, and :math:`\Lambda` is the diagonal matrix of eigenvalues [4]_. +- *Ellipsoid SDF definition*: The definition of the SDF is done in the fragment shader inside the ``map`` function, which is used later for the ray marching algorithm and the normals calculation. We define the SDF more simply by transforming a sphere into an ellipsoid, considering that the SDF of a sphere is easily computed and the definition of a tensor gives us a linear transformation of a given geometry. Also, as scaling is not a rigid body transformation, we multiply the final result by a factor to compensate for the difference, which gave us the SDF of the ellipsoid defined as ``sdSphere(tensorMatrix * (position - centerMCVSOutput), scaleVSOutput*0.48) * scFactor``. +- *Ray marching algorithm and lighting*: For the ray marching algorithm, a small value of 20 was taken as the maximum distance since we apply the technique to each individual object and not all at the same time. Additionally, we set the convergence precision to 0.001. We use the central differences method to compute the normals necessary for the scene’s illumination, besides the Blinn-Phong lighting technique, which is high-quality and computationally cheap. +- *Visualization example*: Below is a detailed visualization of the ellipsoids created from this new implementation. + +.. image:: https://user-images.githubusercontent.com/31288525/244503195-a626718f-4a13-4275-a2b7-6773823e553c.png + :width: 376 + :align: center + +This implementation does show a better quality in the displayed glyphs, and supports the display of a large amount of data, as seen in the image below. For this reason, a tutorial was made to justify in more detail the value of this new implementation. Below are some images generated for the tutorial. + +.. image:: https://user-images.githubusercontent.com/31288525/260906510-d422e7b4-3ba3-4de6-bfd0-09c04bec8876.png + :width: 600 + :align: center + +*Pull Requests:* + +- **Ellipsoid actor implemented with SDF (Merged)** https://github.com/fury-gl/fury/pull/791 +- **Tutorial on using ellipsoid actor to visualize tensor ellipsoids for DTI (Merged)** https://github.com/fury-gl/fury/pull/818 + +**Future work:** In line with one of the initial objectives, it is expected to implement billboards later on to improve the performance, i.e., higher frame rate and less memory usage for the tensor ellipsoid creation. In addition to looking for ways to optimize the naive ray marching algorithm and the definition of SDFs. + +Objectives in Progress +---------------------- + +DTI uncertainty visualization +***************************** + +The DTI visualization pipeline is fairly complex, as a level of uncertainty arises, which, if visualized, helps to assess the model's accuracy. This measure is not currently implemented, and even though there are several methods to calculate and visualize the uncertainty in the DTI model, because of its simplicity and visual representation, we considered Matrix Perturbation Analysis (MPA) proposed by Basser [1]_. This measurement is visualized as double cones representing the variance of the main direction of diffusion, for which the ray marching technique was also used to create these objects. + +Details of the implementation: + +- *Source of uncertainty*: The method of MPA arises from the susceptibility of DTI to dMRI noise present in diffusion-weighted images (DWIs), and also because the model is inherently statistical, making the tensor estimation and other derived quantities to be random variables [1]_. For this reason, this method focus on the premise that image noise produces a random perturbation in the diffusion tensor estimation, and therefore in the calculation of eigenvalues and eigenvectors, particularly in the first eigenvector associated with the main diffusion direction. +- *Mathematical equation*: The description of the perturbation of the principal eigenvector is given by math formula where :math:`\Delta D` corresponds to the estimated perturbation matrix of :math:`D` given by the diagonal elements of the covariance matrix :math:`\Sigma_{\alpha} \approx (B^T\Sigma^{−1}_{e}B)^{−1}`, where :math:`\Sigma_{e}` is the covariance matrix of the error e, defined as a diagonal matrix made with the diagonal elements of :math:`(\Sigma^{−1}_{e}) = ⟨S(b)⟩^2 / \sigma^{2}_{\eta}`. Then, to get the angle :math:`\theta` between the perturbed principal eigenvector of :math:`D`, :math:`\varepsilon_1 + \Delta\varepsilon_1`, and the estimated eigenvector :math:`\varepsilon_1`, it can be approximated by :math:`\theta = \tan^{−1}( \| \Delta\varepsilon_1 \|)` [2]_. Taking into account the above, we define the function ``main_dir_uncertainty(evals, evecs, signal, sigma, b_matrix)`` that calculates the uncertainty of the eigenvector associated to the main direction of diffusion. +- *Double cone SDF definition*: The final SDF is composed by the union of 2 separately cones using the definition taken from this list of `distance functions `_, in this way we have the SDF for the double cone defined as ``opUnion(sdCone(p,a,h), sdCone(-p,a,h)) * scaleVSOutput`` +- *Visualization example*: Below is a demo of how this new feature is intended to be used, an image of diffusion tensor ellipsoids and their associated uncertainty cones. + +.. image:: https://user-images.githubusercontent.com/31288525/254747296-09a8674e-bfc0-4b3f-820f-8a1b1ad8c5c9.png + :width: 610 + :align: center + +The implementation is almost complete, but as it is a new addition that includes mathematical calculations and for which there is no direct reference for comparison, it requires a more detail review before it can be incorporated. + +*Pull Request:* + +- **DTI uncertainty visualization (Under Review)** https://github.com/fury-gl/fury/pull/810 + +**Future work:** A tutorial will be made explaining in more detail how to calculate the parameters needed for the uncertainty cones using **dipy** functions, specifically: `estimate_sigma `_ for the noise variance calculation, `design_matrix `_ to get the b-matrix, and `tensor_prediction `_ for the signal estimation. Additionally, when the ODF implementation is complete, uncertainty for this other reconstruction model is expected to be added, using semitransparent glyphs representing the mean directional information proposed by Tournier [3]_. + +ODF actor implemented with SDF +****************************** + +HARDI-based techniques require more images than DTI, however, they model the diffusion directions as probability distribution functions (PDFs), and the fitted values are returned as orientation distribution functions (ODFs). ODFs are more diffusion sensitive than the diffusion tensor and, therefore, can determine the structure of multi-directional voxels very common in the white matter regions of the brain [3]_. The current actor to display this kind of glyphs is the ``odf_slicer`` which, given an array of spherical harmonics (SH) coefficients renders a grid of ODFs, which are created from a sphere with a specific number of vertices that fit the data. + +For the application of this model using the same SDF ray marching techniques, we need the data of the SH coefficients, which are used to calculate the orientation distribution function (ODF) described `here `_. Different SH bases can be used, but for this first approach we focus on ``descoteaux07`` (as labeled in dipy). After performing the necessary calculations, we obtain an approximate result of the current implementation of FURY, as seen below. + +.. image:: https://user-images.githubusercontent.com/31288525/260909561-fd90033c-018a-465b-bd16-3586bb31ca36.png + :width: 580 + :align: center + +With a first implementation we start to solve some issues related to direction, color, and data handling, to obtain exactly the same results as the current implementation. + +Details on the issues: + +- *The direction and the scaling*: When the shape of the ODF is more sphere-like, the size of the glyph is smaller, so for the moment it needs to be adjusted manually, but the idea is to find a relationship between the coefficients and the final object size so it can be automatically scaled. Additionally, as seen in the image, the direction does not match. To fix this, an adjustment in the calculation of the spherical coordinates can be made, or pass the direction information directly. +- *Pass the coefficients data efficiently*: I'm currently creating one actor per glyph since I'm using a *uniform* array to pass the coefficients, but the idea is to pass all the data simultaneously. The first idea is to encode the coefficients data through a texture and retrieve them in the fragment shader. +- *The colormapping and the lighting*: As these objects present curvatures with quite a bit of detail in some cases, this requires more specific lighting work, in addition to having now not only one color but a color map. This can also be done with texture, but it is necessary to see in more detail how to adjust the texture to the glyph's shape. + +More details on current progress can be seen in blogpost of `week 11 `_ and `week 12 `_. + +*Working branch:* + +- **ODF implementation (Under Development)** + https://github.com/tvcastillod/fury/tree/SH-for-ODF-impl + + +GSoC Weekly Blogs +----------------- + +- My blog posts can be found on the `FURY website `__ and the `Python GSoC blog `__. + + +Timeline +-------- + ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Date | Description | Blog Post Link | ++=====================+========================================================================+==========================================================================================================================================================================+ +| Week 0(02-06-2022) | Community Bounding Period | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 1(05-06-2022) | Ellipsoid actor implemented with SDF | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 2(12-06-2022) | Making adjustments to the Ellipsoid Actor | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 3(19-06-2022) | Working on uncertainty and details of the first PR | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 4(27-06-2022) | First draft of the DTI uncertainty visualization | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 5(03-07-2022) | Preparing the data for the Ellipsoid tutorial | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 6(10-07-2022) | First draft of the Ellipsoid tutorial | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 7(17-07-2022) | Adjustments on the Uncertainty Cones visualization | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 8(25-07-2022) | Working on Ellipsoid Tutorial and exploring SH | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 9(31-07-2022) | Tutorial done and polishing DTI uncertainty | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 10(08-08-2022) | Start of SH implementation experiments | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 11(16-08-2022) | Adjusting ODF implementation and looking for solutions on issues found | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Week 12(24-08-2022) | Experimenting with ODFs implementation | `FURY `__ - `Python `__ | ++---------------------+------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +References +---------- + +.. [1] Basser, P. J. (1997). Quantifying errors in fiber direction and diffusion tensor field maps resulting from MR noise. In 5th Scientific Meeting of the ISMRM (Vol. 1740). +.. [2] Chang, L. C., Koay, C. G., Pierpaoli, C., & Basser, P. J. (2007). Variance of estimated DTI‐derived parameters via first‐order perturbation methods. Magnetic Resonance in Medicine: An Official Journal of the International Society for Magnetic Resonance in Medicine, 57(1), 141-149. +.. [3] J-Donald Tournier, Fernando Calamante, David G Gadian, and Alan Connelly. Direct estimation of the fiber orientation density function from diffusion-weighted mri data using spherical deconvolution. Neuroimage, 23(3):1176–1185, 2004. +.. [4] Gordon Kindlmann. Superquadric tensor glyphs. In Proceedings of the Sixth Joint Eurographics-IEEE TCVG conference on Visualization, pages 147–154, 2004. diff --git a/docs/source/posts/2023/2023-08-25-final-report-praneeth.rst b/docs/source/posts/2023/2023-08-25-final-report-praneeth.rst new file mode 100644 index 000000000..91824b8e2 --- /dev/null +++ b/docs/source/posts/2023/2023-08-25-final-report-praneeth.rst @@ -0,0 +1,216 @@ +.. image:: https://developers.google.com/open-source/gsoc/resources/downloads/GSoC-logo-horizontal.svg + :height: 50 + :align: center + :target: https://summerofcode.withgoogle.com/programs/2023/projects/BqfBWfwS + +.. image:: https://www.python.org/static/community_logos/python-logo.png + :width: 40% + :target: https://summerofcode.withgoogle.com/programs/2023/organizations/python-software-foundation + +.. image:: https://python-gsoc.org/logos/FURY.png + :width: 25% + :target: https://fury.gl/latest/index.html + +Google Summer of Code Final Work Product +======================================== + +.. post:: August 25 2023 + :author: Praneeth Shetty + :tags: google + :category: gsoc + +- **Name:** Praneeth Shetty +- **Organisation:** Python Software Foundation +- **Sub-Organisation:** FURY +- **Project:** `FURY - Update user interface widget + Explore new UI Framework `_ + + +Proposed Objectives +------------------- + +- SpinBoxUI +- Scrollbar as Independent Element +- FileDialog +- TreeUI +- AccordionUI +- ColorPickerUI + +- Stretch Goals: + - Exploring new UI Framework + - Implementing Borders for UI elements + +Objectives Completed +-------------------- + + +- **SpinBoxUI:** + The ``SpinBoxUI`` element is essential for user interfaces as it allows users to pick a numeric value from a set range. While we had an active pull request (PR) to add this element, updates in the main code caused conflicts and required further changes for added features. At one point, we noticed that text alignment wasn't centered properly within the box due to a flaw. To fix this, we began a PR to adjust the alignment, but it turned into a larger refactoring of the ``TextBlock2D``, a core component connected to various parts. This was a complex task that needed careful handling. After sorting out the ``TextBlock2D``, we returned to the ``SpinBoxUI`` and made a few tweaks. Once we were confident with the changes, the PR was successfully merged after thorough review and testing. + + **Pull Requests:** + - **SpinBoxUI (Merged)** - https://github.com/fury-gl/fury/pull/499 + + .. image:: https://user-images.githubusercontent.com/64432063/263165327-c0b19cdc-9ebd-433a-8ff1-99e706a76508.gif + :height: 500 + :align: center + :alt: SpinBoxUI + + + +- **`TextBlock2D` Refactoring:** + This was a significant aspect of the GSoC period and occupied a substantial portion of the timeline. The process began when we observed misaligned text in the ``SpinBoxUI``, as previously discussed. The root cause of the alignment issue was the mispositioning of the text actor concerning the background actor. The text actor's independent repositioning based on justification conflicted with the static position of the background actor, leading to the alignment problem. + + To address this, the initial focus was on resolving the justification issue. However, as the work progressed, we recognized that solely adjusting justification would not suffice. The alignment was inherently linked to the UI's size, which was currently retrieved only when a valid scene was present. This approach lacked scalability and efficiency, as it constrained size retrieval to scene availability. + + To overcome these challenges, we devised a solution involving the creation of a bounding box around the ``TextBlock2D``. This bounding box would encapsulate the size information, enabling proper text alignment. This endeavor spanned several weeks of development, culminating in a finalized solution that underwent rigorous testing before being merged. + + As a result of this refactoring effort, the ``TextBlock2D`` now offers three distinct modes: + + 1. **Fully Static Background:** This mode requires background setup during initialization. + 2. **Dynamic Background:** The background dynamically scales based on the text content. + 3. **Auto Font Scale Mode:** The font within the background box automatically scales to fill the available space. + + An issue has been identified with ``TextBlock2D`` where its text actor aligns with the top boundary of the background actor, especially noticeable with letters like "g," "y," and "j". These letters extend beyond the baseline of standard alphabets, causing the text box to shift upwards. + + However, resolving this matter is complex. Adjusting the text's position might lead to it touching the bottom boundary, especially in font scale mode, resulting in unexpected positioning and transformations. To address this, the plan is to defer discussions about this matter until after GSoC, allowing for thorough consideration and solutions. + + For more detailed insights into the individual steps and nuances of this process, you can refer to the comprehensive weekly blog post provided below. It delves into the entire journey of this ``TextBlock2D`` refactoring effort. + + **Pull Requests:** + - **Fixing Justification Issue - 1st Draft (Closed)** - https://github.com/fury-gl/fury/pull/790 + - **Adding BoundingBox and fixing Justificaiton (Merged)** - https://github.com/fury-gl/fury/pull/803 + - **Adding getters and setter for properties (Merged)** - https://github.com/fury-gl/fury/pull/830 + - **Text Offset PR (Closed)** - https://github.com/fury-gl/fury/pull/837 + + + .. image:: https://user-images.githubusercontent.com/64432063/258603191-d540105a-0612-450e-8ae3-ca8aa87916e6.gif + :height: 500 + :align: center + :alt: TextBlock2D Feature Demo + + .. image:: https://github-production-user-asset-6210df.s3.amazonaws.com/64432063/254652569-94212105-7259-48da-8fdc-41ee987bda84.png + :height: 500 + :align: center + :alt: TextBlock2D All Justification + +- **ScrollbarUI as Independent Element:** + We initially planned to make the scrollbar independent based on PR `#16 `_. The main goal was to avoid redundancy by not rewriting the scrollbar code for each element that requires it, such as the ``FileMenu2D``. However, upon further analysis, we realized that elements like the ``FileMenu2D`` and others utilize the ``Listbox2D``, which already includes an integrated scrollbar. We also examined other UI libraries and found that they also have independent scrollbars but lack a proper use case. Typically, display containers like ``Listbox2D`` are directly used instead of utilizing an independent scrollbar. + + Based on these findings, we have decided to close all related issues and pull requests for now. If the need arises in the future, we can revisit this topic. + + **Topic:** - https://github.com/fury-gl/fury/discussions/816 + + +Other Objectives +---------------- + +- **Reviewing & Merging:** + In this phase, my focus was not on specific coding but rather on facilitating the completion of ongoing PRs. Here are two instances where I played a role: + + 1. **CardUI PR:** + I assisted with the ``CardUI`` PR by aiding in the rebase process and reviewing the changes. The CardUI is a simple UI element consisting of an image and a description, designed to function like a flash card. I worked closely with my mentor to ensure a smooth rebase and review process. + + 2. **ComboBox Issue:** + There was an issue with the ``ComboBox2D`` functionality, where adding it to a ``TabUI`` caused all elements to open simultaneously, which shouldn't be the case. I tested various PRs addressing this problem and identified a suitable solution. I then helped the lead in reviewing the PR that fixed the issue, which was successfully merged. + + **Pull Requests:** + - **CardUI (Merged)** - https://github.com/fury-gl/fury/pull/398 + - **ComboBox Flaw (Merged)** - https://github.com/fury-gl/fury/pull/768 + + + .. image:: https://user-images.githubusercontent.com/54466356/112532305-b090ef80-8dce-11eb-90a0-8d06eed55993.png + :height: 500 + :align: center + :alt: CardUI + + +- **Updating Broken Website Links:** + I addressed an issue with malfunctioning links in the Scientific Section of the website. The problem emerged from alterations introduced in PR `#769 `_. These changes consolidated demos and examples into a unified "auto_examples" folder, and a toml file was utilized to retrieve this data and construct examples. However, this led to challenges with the paths employed in website generation. My responsibility was to rectify these links, ensuring they accurately direct users to the intended content. + + **Pull Requests:** + - **Updating Broken Links (Merged)** - https://github.com/fury-gl/fury/pull/820 + + +Objectives in Progress +---------------------- + +- **FileDialogUI:** + An existing ``FileDialog`` PR by Soham (`#294 `_) was worked upon. The primary task was to rebase the PR to match the current UI structure, resolving compatibility concerns with the older base. In PR `#832 `_, we detailed issues encompassing resizing ``FileDialog`` and components, addressing text overflow, fixing ``ZeroDivisionError``, and correcting ``ListBox2D`` item positioning. The PR is complete with comprehensive testing and documentation. Presently, it's undergoing review, and upon approval, it will be prepared for integration. + + **Pull Requests:** + - **Soham's FileDialog (Closed)** - https://github.com/fury-gl/fury/pull/294 + - **FileDialogUI (Under Review)** - https://github.com/fury-gl/fury/pull/832 + + + .. image:: https://user-images.githubusercontent.com/64432063/263189092-6b0891d5-f0ef-4185-8b17-c7104f1a7d60.gif + :height: 500 + :align: center + :alt: FileDialogUI + + +- **TreeUI:** + Continuing Antriksh's initial PR for ``TreeUI`` posed some challenges. Antriksh had set the foundation, and I picked up from there. The main issue was with the visibility of TreeUI due to updates in the ``set_visibility`` method of ``Panel2D``. These updates affected how ``TreeUI`` was displayed, and after investigating the actors involved, it was clear that the visibility features had changed. This took some time to figure out, and I had a helpful pair programming session with my mentor, Serge, to narrow down the problem. Now, I've updated the code to address this issue. However, I'm still a bit cautious about potential future problems. The PR is now ready for review. + + **Pull Requests:** + - **TreeUI (In Progress)** - https://github.com/fury-gl/fury/pull/821 + + + .. image:: https://user-images.githubusercontent.com/64432063/263237308-70e77ba0-1ce8-449e-a79c-d5e0fbb58b45.gif + :height: 500 + :align: center + :alt: TreeUI + +GSoC Weekly Blogs +----------------- + +- My blog posts can be found at `FURY website `__ + and `Python GSoC blog `__. + +Timeline +-------- + +.. list-table:: + :widths: 40 40 20 + :header-rows: 1 + + * - Date + - Description + - Blog Post Link + * - Week 0 (27-05-2023) + - Community Bounding Period + - `FURY `_ - `Python `_ + * - Week 1 (03-06-2023) + - Working with SpinBox and TextBox Enhancements + - `FURY `_ - `Python `_ + * - Week 2 (10-06-2023) + - Tackling Text Justification and Icon Flaw Issues + - `FURY `_ - `Python `_ + * - Week 3 (17-06-2023) + - Resolving Combobox Icon Flaw and TextBox Justification + - `FURY `_ - `Python `_ + * - Week 4 (24-06-2023) + - Exam Preparations and Reviewing + - `FURY `_ - `Python `_ + * - Week 5 (01-07-2023) + - Trying out PRs and Planning Ahead + - `FURY `_ - `Python `_ + * - Week 6 (08-07-2023) + - BoundingBox for TextBlock2D! + - `FURY `_ - `Python `_ + * - Week 7 (15-07-2023) + - Sowing the seeds for TreeUI + - `FURY `_ - `Python `_ + * - Week 8 (22-07-2023) + - Another week with TextBlockUI + - `FURY `_ - `Python `_ + * - Week 9 (29-07-2023) + - TextBlock2D is Finally Merged! + - `FURY `_ - `Python `_ + * - Week 10 (05-08-2023) + - Its time for a Spin-Box! + - `FURY `_ - `Python `_ + * - Week 11 (12-08-2023) + - Bye Bye SpinBox + - `FURY `_ - `Python `_ + * - Week 12 (19-08-2023) + - FileDialog Quest Begins! + - `FURY `_ - `Python `_ diff --git a/docs/source/release-history.rst b/docs/source/release-history.rst index d46eb07ad..78a1e25b9 100644 --- a/docs/source/release-history.rst +++ b/docs/source/release-history.rst @@ -7,6 +7,7 @@ For a full list of the features implemented in the most recent release cycle, ch .. toctree:: :maxdepth: 1 + release_notes/releasev0.10.0 release_notes/releasev0.9.0 release_notes/releasev0.8.0 release_notes/releasev0.7.1 diff --git a/docs/source/release_notes/releasev0.10.0.rst b/docs/source/release_notes/releasev0.10.0.rst new file mode 100644 index 000000000..308082bfe --- /dev/null +++ b/docs/source/release_notes/releasev0.10.0.rst @@ -0,0 +1,177 @@ +.. _releasev0.10.0: + +=================================== + Release notes v0.10.0 (2024/02/28) +=================================== + +Quick Overview +-------------- + +* Uncertainty Visualization added. +* New actors added. +* Many UI components updated. +* Multiple tutorials added and updated. +* Documentation updated. +* Website updated. + + +Details +------- + +GitHub stats for 2023/04/15 - 2024/02/27 (tag: v0.9.0) + +These lists are automatically generated, and may be incomplete or contain duplicates. + +The following 11 authors contributed 382 commits. + +* Antriksh Misri +* Dwij Raj Hari +* Eleftherios Garyfallidis +* Joao Victor Dell Agli +* Maharshi Gor +* Praneeth Shetty +* Robin Roy +* Serge Koudoro +* Tania Castillo +* dependabot[bot] +* maharshigor + + +We closed a total of 129 issues, 54 pull requests and 75 regular issues; +this is the full list (generated with the script +:file:`tools/github_stats.py`): + +Pull Requests (54): + +* :ghpull:`810`: DTI uncertainty visualization +* :ghpull:`861`: Added/Modified docstrings for 3 actor.py functions +* :ghpull:`863`: UI Bug fixes for Horizon +* :ghpull:`866`: build(deps): bump the actions group with 6 updates +* :ghpull:`865`: Fix ci +* :ghpull:`845`: GSoC: Final Report +* :ghpull:`847`: GSoC: Adding Final Report 23 +* :ghpull:`848`: Added Final Report +* :ghpull:`852`: add Code of conduct +* :ghpull:`846`: Added blogpost week 12 +* :ghpull:`844`: GSoC: Week 12 Blogpost +* :ghpull:`843`: GSoC: Adding Week 12 Blogpost +* :ghpull:`842`: GSoC: Week 11 Blogpost +* :ghpull:`839`: GSoC: Adding Week 10 Blogpost +* :ghpull:`840`: Added blogposts week 8, 9, 10, 11 +* :ghpull:`841`: GSoC: Adding Week 11 Blogpost +* :ghpull:`831`: GSoC: Week 9 Blogpost +* :ghpull:`833`: GSoC: Adding Week 9 Blogpost +* :ghpull:`836`: GSoC: Week 10 Blogpost +* :ghpull:`499`: Adding `SpinBoxUI` to the `UI` module +* :ghpull:`818`: Tutorial on using ellipsoid actor to visualize tensor ellipsoids for DTI +* :ghpull:`834`: citation section added +* :ghpull:`830`: UI: Adding getters and setters for the `TextBlock2D` properties +* :ghpull:`829`: GSoC: Adding Week 8 Blogpost +* :ghpull:`828`: GSoC: Week 8 Blogpost +* :ghpull:`803`: UI: Adding Bounding Box & Fixing Alignment issue in TextBlock2D +* :ghpull:`814`: physics-simulation done +* :ghpull:`827`: Added blogpost week 4, 5, 6, 7 +* :ghpull:`822`: GSoC: Week 7 Blogpost +* :ghpull:`823`: GSoC: Adding Week 6 - 7 Blogpost +* :ghpull:`791`: Ellipsoid actor implemented with SDF +* :ghpull:`817`: GSoC: Adding Week 5 Blogpost +* :ghpull:`820`: Updating broken links in the Scientific Domain Section +* :ghpull:`819`: GSoC: Week 6 Blogpost +* :ghpull:`815`: Week 5 blogpost +* :ghpull:`812`: Feature/compatible software +* :ghpull:`811`: GSoC: Adding Week 4 Blogpost +* :ghpull:`809`: Week 4 Blogpost +* :ghpull:`807`: Added blogpost week 3 +* :ghpull:`806`: Week 3 Blogpost +* :ghpull:`805`: GSoC: Adding Week3 Blogpost +* :ghpull:`398`: feat: added a Card2D widget to UI +* :ghpull:`800`: Week2 Blogpost +* :ghpull:`802`: Added blogpost week 2 +* :ghpull:`801`: [fix] update deprecated Test +* :ghpull:`799`: Adding Week2 BlogPost +* :ghpull:`798`: Added blogpost week 1 +* :ghpull:`768`: Overload set_visibility for Panel2D and Combobox2D +* :ghpull:`797`: Week 1 blogpost +* :ghpull:`796`: Adding Week1 Blogpost +* :ghpull:`792`: Adding week 0 blogpost +* :ghpull:`789`: Added blogpost week 0 +* :ghpull:`788`: Adding Week0 Blogpost +* :ghpull:`629`: Release preparation 0.9.0 + +Issues (75): + +* :ghissue:`810`: DTI uncertainty visualization +* :ghissue:`861`: Added/Modified docstrings for 3 actor.py functions +* :ghissue:`863`: UI Bug fixes for Horizon +* :ghissue:`866`: build(deps): bump the actions group with 6 updates +* :ghissue:`864`: Missing files for sprite test +* :ghissue:`865`: Fix ci +* :ghissue:`845`: GSoC: Final Report +* :ghissue:`847`: GSoC: Adding Final Report 23 +* :ghissue:`848`: Added Final Report +* :ghissue:`425`: WIP: Cube Axes Actor. +* :ghissue:`852`: add Code of conduct +* :ghissue:`846`: Added blogpost week 12 +* :ghissue:`844`: GSoC: Week 12 Blogpost +* :ghissue:`843`: GSoC: Adding Week 12 Blogpost +* :ghissue:`842`: GSoC: Week 11 Blogpost +* :ghissue:`397`: Card2D UI widget +* :ghissue:`839`: GSoC: Adding Week 10 Blogpost +* :ghissue:`840`: Added blogposts week 8, 9, 10, 11 +* :ghissue:`841`: GSoC: Adding Week 11 Blogpost +* :ghissue:`837`: UI: Adding Text Offset to contain text into the Background +* :ghissue:`831`: GSoC: Week 9 Blogpost +* :ghissue:`833`: GSoC: Adding Week 9 Blogpost +* :ghissue:`836`: GSoC: Week 10 Blogpost +* :ghissue:`499`: Adding `SpinBoxUI` to the `UI` module +* :ghissue:`818`: Tutorial on using ellipsoid actor to visualize tensor ellipsoids for DTI +* :ghissue:`834`: citation section added +* :ghissue:`830`: UI: Adding getters and setters for the `TextBlock2D` properties +* :ghissue:`294`: File Dialog UI component +* :ghissue:`829`: GSoC: Adding Week 8 Blogpost +* :ghissue:`828`: GSoC: Week 8 Blogpost +* :ghissue:`803`: UI: Adding Bounding Box & Fixing Alignment issue in TextBlock2D +* :ghissue:`814`: physics-simulation done +* :ghissue:`827`: Added blogpost week 4, 5, 6, 7 +* :ghissue:`822`: GSoC: Week 7 Blogpost +* :ghissue:`823`: GSoC: Adding Week 6 - 7 Blogpost +* :ghissue:`825`: [WIP] KDE Rendering API +* :ghissue:`824`: [WIP] KDE Rendering API +* :ghissue:`791`: Ellipsoid actor implemented with SDF +* :ghissue:`817`: GSoC: Adding Week 5 Blogpost +* :ghissue:`820`: Updating broken links in the Scientific Domain Section +* :ghissue:`819`: GSoC: Week 6 Blogpost +* :ghissue:`815`: Week 5 blogpost +* :ghissue:`460`: [WIP] Adding `Tree2D` to the UI sub-module +* :ghissue:`592`: Creating ScrollBar as a separate UI element +* :ghissue:`285`: Separation of Scrollbars as a standalone API. +* :ghissue:`222`: Attempt to refactor scrolling in FileMenu2D +* :ghissue:`812`: Feature/compatible software +* :ghissue:`811`: GSoC: Adding Week 4 Blogpost +* :ghissue:`809`: Week 4 Blogpost +* :ghissue:`808`: sponsors added +* :ghissue:`807`: Added blogpost week 3 +* :ghissue:`806`: Week 3 Blogpost +* :ghissue:`805`: GSoC: Adding Week3 Blogpost +* :ghissue:`402`: ImageContainer2D renders RGB .png images in black and white +* :ghissue:`398`: feat: added a Card2D widget to UI +* :ghissue:`800`: Week2 Blogpost +* :ghissue:`802`: Added blogpost week 2 +* :ghissue:`801`: [fix] update deprecated Test +* :ghissue:`799`: Adding Week2 BlogPost +* :ghissue:`794`: FURY dependencies aren't accurate in the README +* :ghissue:`790`: Fixing `TextBlock2D` justification issue +* :ghissue:`798`: Added blogpost week 1 +* :ghissue:`576`: Resolving icon flaw in comboBox2D +* :ghissue:`731`: Clicking the tab of a ComboBox2D opens dropdown without changing icon +* :ghissue:`562`: drop_down_menu icon flaw in ComboBox2D +* :ghissue:`768`: Overload set_visibility for Panel2D and Combobox2D +* :ghissue:`797`: Week 1 blogpost +* :ghissue:`796`: Adding Week1 Blogpost +* :ghissue:`792`: Adding week 0 blogpost +* :ghissue:`789`: Added blogpost week 0 +* :ghissue:`787`: Segmentation Fault while plotting (diffusion tractography) images on a non-interactive remote cluster +* :ghissue:`788`: Adding Week0 Blogpost +* :ghissue:`448`: Added the watcher class to UI +* :ghissue:`774`: WIP: Double arrow actor and a few utility functions +* :ghissue:`629`: Release preparation 0.9.0 diff --git a/docs/source/release_notes/releasev0.6.1.rst b/docs/source/release_notes/releasev0.6.1.rst index e5889fd85..6f6593b8e 100644 --- a/docs/source/release_notes/releasev0.6.1.rst +++ b/docs/source/release_notes/releasev0.6.1.rst @@ -53,7 +53,7 @@ Pull Requests (15): * :ghpull:`279`: Decreasing the size of the sun in solarsystem tutorial * :ghpull:`273`: Python GSoC Weekly blogs * :ghpull:`276`: Update Deprecated test -* :ghpull:`272`: Python GSoC Blogs upto 19th July 2020 +* :ghpull:`272`: Python GSoC Blogs up to 19th July 2020 Issues (27): @@ -83,4 +83,4 @@ Issues (27): * :ghissue:`273`: Python GSoC Weekly blogs * :ghissue:`277`: Sun * :ghissue:`276`: Update Deprecated test -* :ghissue:`272`: Python GSoC Blogs upto 19th July 2020 +* :ghissue:`272`: Python GSoC Blogs up to 19th July 2020 diff --git a/docs/upload_to_gh-pages.py b/docs/upload_to_gh-pages.py index e0724a10b..a506c4cc3 100644 --- a/docs/upload_to_gh-pages.py +++ b/docs/upload_to_gh-pages.py @@ -10,15 +10,15 @@ # Imports ############################################################################### import os -import re -import shutil -import sys from os import chdir as cd from os.path import join as pjoin +import re +import shutil from subprocess import PIPE, CalledProcessError, Popen, check_call +import sys if sys.version_info < (3, 4): - raise RuntimeError('Python 3.4 and above is required' ' for running this script') + raise RuntimeError("Python 3.4 and above is required" " for running this script") else: from pathlib import Path @@ -27,12 +27,12 @@ ############################################################################### docs_dir = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) -pkg_name = 'fury' -pkg_path = os.path.realpath(pjoin(docs_dir, '..')) -pages_dir = os.path.realpath(pjoin(docs_dir, 'gh-pages')) -html_dir = os.path.realpath(pjoin(docs_dir, 'build', 'html')) -pdf_dir = os.path.realpath(pjoin(docs_dir, 'build', 'latex')) -pages_repo = 'https://github.com/fury-gl/fury-website.git' +pkg_name = "fury" +pkg_path = os.path.realpath(pjoin(docs_dir, "..")) +pages_dir = os.path.realpath(pjoin(docs_dir, "gh-pages")) +html_dir = os.path.realpath(pjoin(docs_dir, "build", "html")) +pdf_dir = os.path.realpath(pjoin(docs_dir, "build", "latex")) +pages_repo = "https://github.com/fury-gl/fury-website.git" ############################################################################### @@ -75,7 +75,7 @@ def sh3(cmd): ############################################################################### # Script starts ############################################################################### -if __name__ == '__main__': +if __name__ == "__main__": # Get starting folder current_dir = os.getcwd() @@ -84,16 +84,15 @@ def sh3(cmd): mod = __import__(pkg_name) if pjoin(pkg_path, pkg_name).lower() != os.path.dirname(mod.__file__).lower(): - print(pjoin(pkg_path, pkg_name)) print(mod.__file__) raise RuntimeError( - 'You should work with the source and not the ' 'installed package' + "You should work with the source and not the " "installed package" ) # find the version number - tag = 'dev' - if any(t in mod.__version__.lower() for t in ['dev', 'post']): + tag = "dev" + if any(t in mod.__version__.lower() for t in ["dev", "post"]): tag = mod.__version__ if len(sys.argv) == 2: @@ -104,45 +103,43 @@ def sh3(cmd): # Documentation version {} # # using tag '{}' -##############################################""".format( - mod.__version__, tag - ) +##############################################""".format(mod.__version__, tag) print(intro_msg) if not os.path.exists(html_dir): raise RuntimeError( - 'Documentation build folder not found! You should ' - 'generate the documentation first via this ' + "Documentation build folder not found! You should " + "generate the documentation first via this " "command: 'make -C docs html')" ) if not os.path.exists(pages_dir): # clone the gh-pages repo if we haven't already. - sh('git clone {0} {1}'.format(pages_repo, pages_dir)) + sh("git clone {0} {1}".format(pages_repo, pages_dir)) # ensure up-to-date before operating cd(pages_dir) try: - sh('git checkout gh-pages') - sh('git pull') + sh("git checkout gh-pages") + sh("git pull") except BaseException: - print('\nLooks like gh-pages branch does not exist!') - print('Do you want to create a new one? (y/n)') + print("\nLooks like gh-pages branch does not exist!") + print("Do you want to create a new one? (y/n)") while 1: choice = str(input()).lower() - if choice == 'y': - sh('git checkout -b gh-pages') - sh('rm -rf *') - sh('git add .') + if choice == "y": + sh("git checkout -b gh-pages") + sh("rm -rf *") + sh("git add .") sh("git commit -m 'cleaning gh-pages branch'") - sh('git push origin gh-pages') + sh("git push origin gh-pages") break - elif choice == 'n': - print('Please manually create a new gh-pages branch' ' and try again.') + elif choice == "n": + print("Please manually create a new gh-pages branch" " and try again.") sys.exit(0) else: - print('Please enter valid choice ..') + print("Please enter valid choice ..") # delete tag version and copy the doc dest = os.path.join(pages_dir, tag) @@ -151,28 +148,28 @@ def sh3(cmd): try: cd(pages_dir) - status = sh2('LANG=en_US git status | head -1') - branch = re.match(b'On branch (.*)$', status).group(1) - if branch != b'gh-pages': + status = sh2("LANG=en_US git status | head -1") + branch = re.match(b"On branch (.*)$", status).group(1) + if branch != b"gh-pages": e = 'On %r, git branch is %r, MUST be "gh-pages"' % (pages_dir, branch) raise RuntimeError(e) # Add no jekyll file - if os.path.exists(pjoin(pages_dir, '.nojekyll')): - Path('.nojekyll').touch() - sh('git add .nojekyll') + if os.path.exists(pjoin(pages_dir, ".nojekyll")): + Path(".nojekyll").touch() + sh("git add .nojekyll") - sh('git add --all {}'.format(tag)) + sh("git add --all {}".format(tag)) - status = sh2('LANG=en_US git status | tail -1') - if not re.match(b'nothing to commit', status): + status = sh2("LANG=en_US git status | tail -1") + if not re.match(b"nothing to commit", status): sh2('git commit -m "Updated doc release: {}"'.format(tag)) else: - print('\n! Note: no changes to commit\n') + print("\n! Note: no changes to commit\n") - print('Most recent commit:') + print("Most recent commit:") sys.stdout.flush() - sh('git --no-pager log --oneline HEAD~1..') + sh("git --no-pager log --oneline HEAD~1..") # update stable symlink # latest_tag = sh2('git describe --exact-match --abbrev=0') # os.symlink() @@ -180,6 +177,6 @@ def sh3(cmd): finally: cd(current_dir) - print('') - print('Now verify the build in: %r' % dest) + print("") + print("Now verify the build in: %r" % dest) print("If everything looks good, run 'git push' inside doc/gh-pages.") diff --git a/fury/__init__.py b/fury/__init__.py index 64153fa99..1447797f9 100644 --- a/fury/__init__.py +++ b/fury/__init__.py @@ -1,4 +1,5 @@ """Init file for visualization package.""" + import warnings from fury.pkg_info import __version__, pkg_commit_hash @@ -8,17 +9,18 @@ def get_info(verbose=False): """Return dict describing the context of this package. Parameters - ------------ + ---------- pkg_path : str path containing __init__.py for package + Returns - ---------- + ------- context : dict with named parameters of interest """ - import sys from os.path import dirname + import sys import numpy import scipy @@ -26,31 +28,31 @@ def get_info(verbose=False): from fury.optpkg import optional_package - mpl, have_mpl, _ = optional_package('matplotlib') - dipy, have_dipy, _ = optional_package('dipy') + mpl, have_mpl, _ = optional_package("matplotlib") + dipy, have_dipy, _ = optional_package("dipy") install_type, commit_hash = pkg_commit_hash(dirname(__file__)) - info = dict( - fury_version=__version__, - pkg_path=dirname(__file__), - commit_hash=commit_hash, - sys_version=sys.version, - sys_executable=sys.executable, - sys_platform=sys.platform, - numpy_version=numpy.__version__, - scipy_version=scipy.__version__, - vtk_version=ccvtk.vtkVersion.GetVTKVersion(), - ) - - d_mpl = dict(matplotlib_version=mpl.__version__) if have_mpl else {} - d_dipy = dict(dipy_version=dipy.__version__) if have_dipy else {} + info = { + "fury_version": __version__, + "pkg_path": dirname(__file__), + "commit_hash": commit_hash, + "sys_version": sys.version, + "sys_executable": sys.executable, + "sys_platform": sys.platform, + "numpy_version": numpy.__version__, + "scipy_version": scipy.__version__, + "vtk_version": ccvtk.vtkVersion.GetVTKVersion(), + } + + d_mpl = {"matplotlib_version": mpl.__version__} if have_mpl else {} + d_dipy = {"dipy_version": dipy.__version__} if have_dipy else {} info.update(d_mpl) info.update(d_dipy) if verbose: - print('\n'.join(['{0}: {1}'.format(k, v) for k, v in info.items()])) + print("\n".join(["{0}: {1}".format(k, v) for k, v in info.items()])) return info @@ -64,9 +66,9 @@ def enable_warnings(warnings_origin=None): list origin ['all', 'fury', 'vtk', 'matplotlib', ...] """ - warnings_origin = warnings_origin or ('all',) + warnings_origin = warnings_origin or ("all",) - if 'all' in warnings_origin or 'vtk' in warnings_origin: + if "all" in warnings_origin or "vtk" in warnings_origin: import vtkmodules.vtkCommonCore as ccvtk ccvtk.vtkObject.GlobalWarningDisplayOn() @@ -81,16 +83,16 @@ def disable_warnings(warnings_origin=None): list origin ['all', 'fury', 'vtk', 'matplotlib', ...] """ - warnings_origin = warnings_origin or ('all',) + warnings_origin = warnings_origin or ("all",) - if 'all' in warnings_origin or 'vtk' in warnings_origin: + if "all" in warnings_origin or "vtk" in warnings_origin: import vtkmodules.vtkCommonCore as ccvtk ccvtk.vtkObject.GlobalWarningDisplayOff() # We switch off the warning display during the release -if not ('post' in __version__) and not ('dev' in __version__): +if "post" not in __version__ and "dev" not in __version__: disable_warnings() # Ignore this specific warning below from vtk < 8.2. @@ -99,9 +101,9 @@ def disable_warnings(warnings_origin=None): # treated as `np.complex128 == np.dtype(complex).type`. # assert not numpy.issubdtype(z.dtype, complex), \ warnings.filterwarnings( - 'ignore', - message='Conversion of the second argument of' - ' issubdtype from `complex` to' - ' `np.complexfloating` is deprecated.*', + "ignore", + message="Conversion of the second argument of" + " issubdtype from `complex` to" + " `np.complexfloating` is deprecated.*", category=FutureWarning, ) diff --git a/fury/actor.py b/fury/actor.py index b8a4b440b..3094ef112 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -1,16 +1,19 @@ """Module that provide actors to render.""" +from functools import partial import os import warnings -from functools import partial import numpy as np -import fury.primitive as fp -from fury import layout +from fury import layout as lyt from fury.actors.odf_slicer import OdfSlicerActor from fury.actors.peak import PeakActor -from fury.actors.tensor import tensor_ellipsoid +from fury.actors.tensor import ( + double_cone, + main_dir_uncertainty, + tensor_ellipsoid, +) from fury.colormap import colormap_lookup_table from fury.deprecator import deprecate_with_version, deprecated_params from fury.io import load_image @@ -33,8 +36,8 @@ ImageData, ImageMapToColors, ImageReslice, - LinearExtrusionFilter, LODActor, + LinearExtrusionFilter, LookupTable, LoopSubdivisionFilter, Matrix4x4, @@ -49,9 +52,9 @@ SplineFilter, TextActor3D, Texture, + TextureMapToPlane, TexturedActor2D, TexturedSphereSource, - TextureMapToPlane, Transform, TransformPolyDataFilter, TriangleFilter, @@ -59,6 +62,7 @@ VectorText, numpy_support, ) +import fury.primitive as fp from fury.shaders import ( add_shader_callback, attribute_to_actor, @@ -90,7 +94,7 @@ def slicer( value_range=None, opacity=1.0, lookup_colormap=None, - interpolation='linear', + interpolation="linear", picking_tol=0.025, ): """Cut 3D scalar or rgb volumes into 2D images. @@ -134,19 +138,19 @@ def slicer( if data.ndim != 3: if data.ndim == 4: if data.shape[3] != 3: - raise ValueError('Only RGB 3D arrays are currently supported.') + raise ValueError("Only RGB 3D arrays are currently supported.") else: nb_components = 3 else: - raise ValueError('Only 3D arrays are currently supported.') + raise ValueError("Only 3D arrays are currently supported.") else: nb_components = 1 vol = data im = ImageData() - I, J, K = vol.shape[:3] - im.SetDimensions(I, J, K) + i, j, k = vol.shape[:3] + im.SetDimensions(i, j, k) # for now setting up for 1x1x1 but transformation comes later. voxsz = (1.0, 1.0, 1.0) # im.SetOrigin(0,0,0) @@ -238,7 +242,6 @@ def __init__(self): self.outline_actor = None def input_connection(self, output): - # outline only outline = OutlineFilter() outline.SetInputData(vtk_resliced_data) @@ -300,7 +303,7 @@ def copy(self): im_actor.SetDisplayExtent(*self.GetDisplayExtent()) im_actor.opacity(self.GetOpacity()) im_actor.tolerance(self.picker.GetTolerance()) - if interpolation == 'nearest': + if interpolation == "nearest": im_actor.SetInterpolate(False) else: im_actor.SetInterpolate(True) @@ -332,7 +335,7 @@ def shallow_copy(self): image_actor.opacity(opacity) image_actor.tolerance(picking_tol) - if interpolation == 'nearest': + if interpolation == "nearest": image_actor.SetInterpolate(False) else: image_actor.SetInterpolate(True) @@ -388,7 +391,7 @@ def surface(vertices, faces=None, colors=None, smooth=None, subdivision=3): if faces is None: tri = Delaunay(vertices[:, [0, 1]]) - faces = np.array(tri.simplices, dtype='i8') + faces = np.array(tri.simplices, dtype="i8") set_polydata_triangles(triangle_poly_data, faces) @@ -402,14 +405,14 @@ def surface(vertices, faces=None, colors=None, smooth=None, subdivision=3): mapper.SetInputData(triangle_poly_data) surface_actor.SetMapper(mapper) - elif smooth == 'loop': + elif smooth == "loop": smooth_loop = LoopSubdivisionFilter() smooth_loop.SetNumberOfSubdivisions(subdivision) smooth_loop.SetInputConnection(clean_poly_data.GetOutputPort()) mapper.SetInputConnection(smooth_loop.GetOutputPort()) surface_actor.SetMapper(mapper) - elif smooth == 'butterfly': + elif smooth == "butterfly": smooth_butterfly = ButterflySubdivisionFilter() smooth_butterfly.SetNumberOfSubdivisions(subdivision) smooth_butterfly.SetInputConnection(clean_poly_data.GetOutputPort()) @@ -419,7 +422,7 @@ def surface(vertices, faces=None, colors=None, smooth=None, subdivision=3): return surface_actor -def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1): +def contour_from_roi(data, affine=None, color=None, opacity=1): """Generate surface actor from a binary ROI. The color and opacity of the surface can be customized. @@ -444,13 +447,16 @@ def contour_from_roi(data, affine=None, color=np.array([1, 0, 0]), opacity=1): """ if data.ndim != 3: - raise ValueError('Only 3D arrays are currently supported.') + raise ValueError("Only 3D arrays are currently supported.") + + if color is None: + color = np.array([1, 0, 0]) nb_components = 1 data = (data > 0) * 1 vol = np.interp(data, xp=[data.min(), data.max()], fp=[0, 255]) - vol = vol.astype('uint8') + vol = vol.astype("uint8") im = ImageData() di, dj, dk = vol.shape[:3] @@ -569,7 +575,7 @@ def contour_from_label(data, affine=None, color=None): 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') + raise ValueError("Incorrect color array shape") if color.shape == (nb_surfaces, 4): opacity = color[:, -1] @@ -598,7 +604,7 @@ def streamtube( lod_points_size=3, spline_subdiv=None, lookup_colormap=None, - replace_strips=False + replace_strips=False, ): """Use streamtubes to visualize polylines. @@ -726,7 +732,7 @@ def streamtube( poly_mapper = set_input(PolyDataMapper(), next_input) poly_mapper.ScalarVisibilityOn() poly_mapper.SetScalarModeToUsePointFieldData() - poly_mapper.SelectColorArray('colors') + poly_mapper.SelectColorArray("colors") poly_mapper.Update() # Color Scale with a lookup table @@ -850,7 +856,7 @@ def line( poly_mapper = set_input(PolyDataMapper(), next_input) poly_mapper.ScalarVisibilityOn() poly_mapper.SetScalarModeToUsePointFieldData() - poly_mapper.SelectColorArray('colors') + poly_mapper.SelectColorArray("colors") poly_mapper.Update() # Color Scale with a lookup table @@ -879,9 +885,9 @@ def line( def callback(_caller, _event, calldata=None): program = calldata if program is not None: - program.SetUniformf('linewidth', linewidth) + program.SetUniformf("linewidth", linewidth) - replace_shader_in_actor(actor, 'geometry', import_fury_shader('line.geom')) + replace_shader_in_actor(actor, "geometry", import_fury_shader("line.geom")) add_shader_callback(actor, callback) if fake_tube: @@ -890,7 +896,7 @@ def callback(_caller, _event, calldata=None): return actor -def scalar_bar(lookup_table=None, title=' '): +def scalar_bar(lookup_table=None, title=" "): """Default scalar bar actor for a given colormap (colorbar). Parameters @@ -1013,8 +1019,8 @@ def odf_slicer( n_dims = len(odfs.shape) if n_dims != 4: raise ValueError( - 'Invalid number of dimensions for odfs. Expected 4 ' - 'dimensions, got {0} dimensions.'.format(n_dims) + "Invalid number of dimensions for odfs. Expected 4 " + "dimensions, got {0} dimensions.".format(n_dims) ) # we generate indices for all nonzero voxels @@ -1026,7 +1032,7 @@ def odf_slicer( if sphere is None: # Use a default sphere with 100 vertices - vertices, faces = fp.prim_sphere('repulsion100') + vertices, faces = fp.prim_sphere("repulsion100") else: vertices = sphere.vertices faces = fix_winding_order(vertices, sphere.faces, clockwise=True) @@ -1034,14 +1040,16 @@ def odf_slicer( if B_matrix is None: if len(vertices) != odfs.shape[-1]: raise ValueError( - 'Invalid number of SF coefficients. ' - 'Expected {0}, got {1}.'.format(len(vertices), odfs.shape[-1]) + "Invalid number of SF coefficients. " "Expected {0}, got {1}.".format( + len(vertices), odfs.shape[-1] + ) ) else: if len(vertices) != B_matrix.shape[1]: raise ValueError( - 'Invalid number of SH coefficients. ' - 'Expected {0}, got {1}.'.format(len(vertices), B_matrix.shape[1]) + "Invalid number of SH coefficients. " "Expected {0}, got {1}.".format( + len(vertices), B_matrix.shape[1] + ) ) # create and return an instance of OdfSlicerActor @@ -1091,7 +1099,7 @@ def _roll_evals(evals, axis=-1): """ if evals.shape[-1] != 3: - msg = 'Expecting 3 eigenvalues, got {}'.format(evals.shape[-1]) + msg = "Expecting 3 eigenvalues, got {}".format(evals.shape[-1]) raise ValueError(msg) evals = np.rollaxis(evals, axis) @@ -1165,7 +1173,7 @@ def _color_fa(fa, evecs): """ if (fa.shape != evecs[..., 0, 0].shape) or ((3, 3) != evecs.shape[-2:]): - raise ValueError('Wrong number of dimensions for evecs') + raise ValueError("Wrong number of dimensions for evecs") return np.abs(evecs[..., 0]) * np.clip(fa, 0, 1)[..., None] @@ -1214,8 +1222,8 @@ def tensor_slicer( if not evals.shape == evecs.shape[:-1]: raise RuntimeError( "Eigenvalues shape {} is incompatible with eigenvectors' {}." - ' Please provide eigenvalue and' - ' eigenvector arrays that have compatible dimensions.'.format( + " Please provide eigenvalue and" + " eigenvector arrays that have compatible dimensions.".format( evals.shape, evecs.shape ) ) @@ -1331,11 +1339,11 @@ def _tensor_slicer_mapper( else: cfa = _makeNd(scalar_colors, 4) - cols = np.zeros((ijk.shape[0],) + sphere.vertices.shape, dtype='f4') + cols = np.zeros((ijk.shape[0],) + sphere.vertices.shape, dtype="f4") all_xyz = [] all_faces = [] - for (k, center) in enumerate(ijk): + for k, center in enumerate(ijk): ea = evals[tuple(center.astype(int))] if norm: ea /= ea.max() @@ -1350,7 +1358,7 @@ def _tensor_slicer_mapper( cols[k, ...] = np.interp( cfa[tuple(center.astype(int))], [0, 1], [0, 255] - ).astype('ubyte') + ).astype("ubyte") all_xyz = np.ascontiguousarray(np.concatenate(all_xyz)) all_xyz_vtk = numpy_support.numpy_to_vtk(all_xyz, deep=True) @@ -1361,14 +1369,14 @@ def _tensor_slicer_mapper( all_faces = np.concatenate(all_faces) cols = np.ascontiguousarray( - np.reshape(cols, (cols.shape[0] * cols.shape[1], cols.shape[2])), dtype='f4' + np.reshape(cols, (cols.shape[0] * cols.shape[1], cols.shape[2])), dtype="f4" ) vtk_colors = numpy_support.numpy_to_vtk( cols, deep=True, array_type=VTK_UNSIGNED_CHAR ) - vtk_colors.SetName('colors') + vtk_colors.SetName("colors") polydata = PolyData() polydata.SetPoints(points) @@ -1439,7 +1447,7 @@ def peak_slicer( """ peaks_dirs = np.asarray(peaks_dirs) if peaks_dirs.ndim > 5: - raise ValueError('Wrong shape') + raise ValueError("Wrong shape") peaks_dirs = _makeNd(peaks_dirs, 5) if peaks_values is not None: @@ -1455,7 +1463,6 @@ def __init__(self): self.line = None def display_extent(self, x1, x2, y1, y2, z1, z2): - tmp_mask = np.zeros(grid_shape, dtype=bool) tmp_mask[x1 : x2 + 1, y1 : y2 + 1, z1 : z2 + 1] = True tmp_mask = np.bitwise_and(tmp_mask, mask) @@ -1475,7 +1482,6 @@ def display_extent(self, x1, x2, y1, y2, z1, z2): xyz = ijk_trans[index][:, None] xyz = xyz.T for i in range(peaks_dirs[tuple(center)].shape[-2]): - if peaks_values is not None: pv = peaks_values[tuple(center)][i] else: @@ -1585,15 +1591,15 @@ def peak( """ if peaks_dirs.ndim != 5: raise ValueError( - 'Invalid peak directions. The shape of the structure ' - 'must be (XxYxZxDx3). Your data has {} dimensions.' - ''.format(peaks_dirs.ndim) + "Invalid peak directions. The shape of the structure " + "must be (XxYxZxDx3). Your data has {} dimensions." + "".format(peaks_dirs.ndim) ) if peaks_dirs.shape[4] != 3: raise ValueError( - 'Invalid peak directions. The shape of the last ' - 'dimension must be 3. Your data has a last dimension ' - 'of {}.'.format(peaks_dirs.shape[4]) + "Invalid peak directions. The shape of the last " + "dimension must be 3. Your data has a last dimension " + "of {}.".format(peaks_dirs.shape[4]) ) dirs_shape = peaks_dirs.shape @@ -1601,32 +1607,34 @@ def peak( if peaks_values is not None: if peaks_values.ndim != 4: raise ValueError( - 'Invalid peak values. The shape of the structure ' - 'must be (XxYxZxD). Your data has {} dimensions.' - ''.format(peaks_values.ndim) + "Invalid peak values. The shape of the structure " + "must be (XxYxZxD). Your data has {} dimensions." + "".format(peaks_values.ndim) ) vals_shape = peaks_values.shape if vals_shape != dirs_shape[:4]: raise ValueError( - 'Invalid peak values. The shape of the values ' - 'must coincide with the shape of the directions.' + "Invalid peak values. The shape of the values " + "must coincide with the shape of the directions." ) valid_mask = np.abs(peaks_dirs).max(axis=(-2, -1)) > 0 if mask is not None: if mask.ndim != 3: warnings.warn( - 'Invalid mask. The mask must be a 3D array. The ' - 'passed mask has {} dimensions. Ignoring passed ' - 'mask.'.format(mask.ndim), + "Invalid mask. The mask must be a 3D array. The " + "passed mask has {} dimensions. Ignoring passed " + "mask.".format(mask.ndim), UserWarning, + stacklevel=2, ) elif mask.shape != dirs_shape[:3]: warnings.warn( - 'Invalid mask. The shape of the mask must coincide ' - 'with the shape of the directions. Ignoring passed ' - 'mask.', + "Invalid mask. The shape of the mask must coincide " + "with the shape of the directions. Ignoring passed " + "mask.", UserWarning, + stacklevel=2, ) else: valid_mask = np.logical_and(valid_mask, mask) @@ -1671,14 +1679,14 @@ def dot(points, colors=None, opacity=None, dot_size=5): """ if points.ndim != 2: raise ValueError( - 'Invalid points. The shape of the structure must be ' - '(Nx3). Your data has {} dimensions.'.format(points.ndim) + "Invalid points. The shape of the structure must be " + "(Nx3). Your data has {} dimensions.".format(points.ndim) ) if points.shape[1] != 3: raise ValueError( - 'Invalid points. The shape of the last dimension ' - 'must be 3. Your data has a last dimension of {}.'.format(points.shape[1]) + "Invalid points. The shape of the last dimension " + "must be 3. Your data has a last dimension of {}.".format(points.shape[1]) ) vtk_vertices = Points() @@ -1722,7 +1730,7 @@ def dot(points, colors=None, opacity=None, dot_size=5): dots = deprecate_with_version( - message='dots function has been renamed dot', since='0.8.1', until='0.9.0' + message="dots function has been renamed dot", since="0.8.1", until="0.9.0" )(dot) @@ -1894,11 +1902,13 @@ def cylinder( vertices : ndarray, shape (N, 3) The point cloud defining the sphere. faces : ndarray, shape (M, 3) - If faces is None then a sphere is created based on theta and phi angles. + If faces is None then a sphere is created based on theta + and phi angles. If not then a sphere is created with the provided vertices and faces. repeat_primitive: bool If True, cylinder will be generated with primitives - If False, repeat_sources will be invoked to use VTK filters for cylinder. + If False, + repeat_sources will be invoked to use VTK filters for cylinder. Returns ------- @@ -1917,10 +1927,9 @@ def cylinder( """ if repeat_primitive: - if resolution < 8: # Sectors parameter should be greater than 7 in fp.prim_cylinder() - raise ValueError('resolution parameter should be greater than 7') + raise ValueError("resolution parameter should be greater than 7") verts, faces = fp.prim_cylinder( radius=radius, @@ -2128,7 +2137,7 @@ def rectangle(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=(1, 2, 0)) return square(centers=centers, directions=directions, colors=colors, scales=scales) -@deprecated_params(['size', 'heights'], ['scales', 'scales'], since='0.6', until='0.8') +@deprecated_params(["size", "heights"], ["scales", "scales"], since="0.6", until="0.8") def box(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=(1, 2, 3)): """Visualize one or many boxes with different features. @@ -2176,7 +2185,7 @@ def box(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=(1, 2, 3)): return box_actor -@deprecated_params('heights', 'scales', since='0.6', until='0.8') +@deprecated_params("heights", "scales", since="0.6", until="0.8") def cube(centers, directions=(1, 0, 0), colors=(1, 0, 0), scales=1): """Visualize one or many cubes with different features. @@ -2668,6 +2677,7 @@ def superquadric( >>> # window.show(scene) """ + def have_2_dimensions(arr): return all(isinstance(i, (list, tuple, np.ndarray)) for i in arr) @@ -2709,12 +2719,13 @@ def billboard( gs_prog=None, fs_dec=None, fs_impl=None, - bb_type='spherical' + bb_type="spherical", ): """Create a billboard actor. -- + - Billboards are 2D elements placed in a 3D world. They offer possibility to draw different shapes/elements at the fragment shader level. + Parameters ---------- centers : ndarray, shape (N, 3) @@ -2744,6 +2755,7 @@ def billboard( Returns ------- billboard_actor: Actor + """ verts, faces = fp.prim_square() res = fp.repeat_primitive( @@ -2758,116 +2770,97 @@ def billboard( ) bb_actor.GetMapper().SetVBOShiftScaleMethod(False) bb_actor.GetProperty().BackfaceCullingOff() - attribute_to_actor(bb_actor, big_centers, 'center') + attribute_to_actor(bb_actor, big_centers, "center") - bb_norm = import_fury_shader(os.path.join('utils', - 'billboard_normalization.glsl')) + bb_norm = import_fury_shader(os.path.join("utils", "billboard_normalization.glsl")) - if bb_type.lower() == 'cylindrical_x': - bb_type_sd = import_fury_shader(os.path.join('billboard', - 'cylindrical_x.glsl') - ) - v_pos_mc = \ - """ + if bb_type.lower() == "cylindrical_x": + bb_type_sd = import_fury_shader(os.path.join("billboard", "cylindrical_x.glsl")) + v_pos_mc = """ vec3 vertexPositionMC = cylindricalXVertexPos(center, MCVCMatrix, normalizedVertexMCVSOutput, shape); """ - elif bb_type.lower() == 'cylindrical_y': - bb_type_sd = import_fury_shader(os.path.join('billboard', - 'cylindrical_y.glsl') - ) - v_pos_mc = \ - """ + elif bb_type.lower() == "cylindrical_y": + bb_type_sd = import_fury_shader(os.path.join("billboard", "cylindrical_y.glsl")) + v_pos_mc = """ vec3 vertexPositionMC = cylindricalYVertexPos(center,MCVCMatrix, normalizedVertexMCVSOutput, shape); """ - elif bb_type.lower() == 'spherical': - bb_type_sd = import_fury_shader(os.path.join('billboard', - 'spherical.glsl')) - v_pos_mc = \ - """ + elif bb_type.lower() == "spherical": + bb_type_sd = import_fury_shader(os.path.join("billboard", "spherical.glsl")) + v_pos_mc = """ vec3 vertexPositionMC = sphericalVertexPos(center, MCVCMatrix, normalizedVertexMCVSOutput, shape); """ else: - bb_type_sd = import_fury_shader(os.path.join('billboard', - 'spherical.glsl')) - v_pos_mc = \ - """ + bb_type_sd = import_fury_shader(os.path.join("billboard", "spherical.glsl")) + v_pos_mc = """ vec3 vertexPositionMC = sphericalVertexPos(center, MCVCMatrix, normalizedVertexMCVSOutput, shape); """ - warnings.warn('Invalid option. The billboard will be generated ' - 'with the default spherical option. ', UserWarning) + warnings.warn( + "Invalid option. The billboard will be generated " + "with the default spherical option. ", + UserWarning, + stacklevel=2, + ) - gl_position = \ - ''' + gl_position = """ gl_Position = MCDCMatrix * vec4(vertexPositionMC, 1.); - ''' + """ - billboard_dec_vert = \ - ''' + billboard_dec_vert = """ /* Billboard vertex shader declaration */ in vec3 center; out vec3 centerVertexMCVSOutput; out vec3 normalizedVertexMCVSOutput; - ''' + """ - billboard_impl_vert = \ - ''' + billboard_impl_vert = """ /* Billboard vertex shader implementation */ centerVertexMCVSOutput = center; normalizedVertexMCVSOutput = bbNorm(vertexMC.xyz, center); float scalingFactor = 1. / abs(normalizedVertexMCVSOutput.x); float size = abs((vertexMC.xyz - center).x) * 2; vec2 shape = vec2(size, size); // Fixes the scaling issue - ''' + """ - billboard_dec_frag = \ - ''' + billboard_dec_frag = """ /* Billboard fragment shader declaration */ in vec3 centerVertexMCVSOutput; in vec3 normalizedVertexMCVSOutput; - ''' + """ - billboard_impl_frag = \ - ''' + billboard_impl_frag = """ /* Billboard Fragment shader implementation */ // Renaming variables passed from the Vertex Shader vec3 color = vertexColorVSOutput.rgb; vec3 point = normalizedVertexMCVSOutput; fragOutput0 = vec4(color, 1.); - ''' + """ - billboard_vert_impl = compose_shader( - [billboard_impl_vert, v_pos_mc, gl_position]) + billboard_vert_impl = compose_shader([billboard_impl_vert, v_pos_mc, gl_position]) vs_dec_code = compose_shader( - [billboard_dec_vert, compose_shader(vs_dec), bb_norm, bb_type_sd]) - vs_impl_code = compose_shader( - [compose_shader(vs_impl), billboard_vert_impl]) + [billboard_dec_vert, compose_shader(vs_dec), bb_norm, bb_type_sd] + ) + vs_impl_code = compose_shader([compose_shader(vs_impl), billboard_vert_impl]) gs_code = compose_shader(gs_prog) - fs_dec_code = compose_shader( - [billboard_dec_frag, compose_shader(fs_dec)] - ) - fs_impl_code = compose_shader( - [billboard_impl_frag, compose_shader(fs_impl)] - ) + fs_dec_code = compose_shader([billboard_dec_frag, compose_shader(fs_dec)]) + fs_impl_code = compose_shader([billboard_impl_frag, compose_shader(fs_impl)]) - shader_to_actor(bb_actor, 'vertex', impl_code=vs_impl_code, - decl_code=vs_dec_code) - replace_shader_in_actor(bb_actor, 'geometry', gs_code) - shader_to_actor(bb_actor, 'fragment', decl_code=fs_dec_code) - shader_to_actor(bb_actor, 'fragment', impl_code=fs_impl_code, - block='light') + shader_to_actor(bb_actor, "vertex", impl_code=vs_impl_code, decl_code=vs_dec_code) + replace_shader_in_actor(bb_actor, "geometry", gs_code) + shader_to_actor(bb_actor, "fragment", decl_code=fs_dec_code) + shader_to_actor(bb_actor, "fragment", impl_code=fs_impl_code, block="light") return bb_actor def vector_text( - text='Origin', + text="Origin", pos=(0, 0, 0), scale=(0.2, 0.2, 0.2), color=(1, 1, 1), @@ -2966,9 +2959,9 @@ def add_to_scene(scene): label = deprecate_with_version( - message='Label function has been renamed' ' vector_text', - since='0.7.1', - until='0.9.0', + message="Label function has been renamed" " vector_text", + since="0.7.1", + until="0.9.0", )(vector_text) @@ -2977,9 +2970,9 @@ def text_3d( position=(0, 0, 0), color=(1, 1, 1), font_size=12, - font_family='Arial', - justification='left', - vertical_justification='bottom', + font_family="Arial", + justification="left", + vertical_justification="bottom", bold=False, italic=False, shadow=False, @@ -3006,6 +2999,7 @@ def text_3d( Text3D """ + class Text3D(TextActor3D): def message(self, text): self.set_message(text) @@ -3020,27 +3014,27 @@ def font_size(self, size): self.GetTextProperty().SetFontSize(24) text_actor.SetScale((1.0 / 24.0 * size,) * 3) - def font_family(self, _family='Arial'): + def font_family(self, _family="Arial"): self.GetTextProperty().SetFontFamilyToArial() def justification(self, justification): tprop = self.GetTextProperty() - if justification == 'left': + if justification == "left": tprop.SetJustificationToLeft() - elif justification == 'center': + elif justification == "center": tprop.SetJustificationToCentered() - elif justification == 'right': + elif justification == "right": tprop.SetJustificationToRight() else: raise ValueError("Unknown justification: '{}'".format(justification)) def vertical_justification(self, justification): tprop = self.GetTextProperty() - if justification == 'top': + if justification == "top": tprop.SetVerticalJustificationToTop() - elif justification == 'middle': + elif justification == "middle": tprop.SetVerticalJustificationToCentered() - elif justification == 'bottom': + elif justification == "bottom": tprop.SetVerticalJustificationToBottom() else: raise ValueError( @@ -3102,14 +3096,16 @@ class Container: """ - def __init__(self, layout=layout.Layout()): - """ - - Parameters + def __init__(self, layout=None): + """Parameters ---------- layout : ``fury.layout.Layout`` object Items of this container will be arranged according to `layout`. + """ + if layout is None: + layout = lyt.Layout() + self.layout = layout self._items = [] self._need_update = True @@ -3136,11 +3132,12 @@ def add(self, *items, **kwargs): If True the items are added as-is, otherwise a shallow copy is made first. If you intend to reuse the items elsewhere you should set `borrow=False`. Default: True. + """ self._need_update = True for item in items: - if not kwargs.get('borrow', True): + if not kwargs.get("borrow", True): item = shallow_copy(item) self._items.append(item) @@ -3173,8 +3170,8 @@ def remove_from_scene(self, scene): def GetBounds(self): """Get the bounds of the container.""" - bounds = np.zeros(6) # x1, x2, y1, y2, z1, z2 - bounds[::2] = np.inf # x1, y1, z1 + bounds = np.zeros(6) # x1, x2, y1, y2, z1, z2 + bounds[::2] = np.inf # x1, y1, z1 bounds[1::2] = -np.inf # x2, y2, z2 for item in self.items: @@ -3237,7 +3234,7 @@ def grid( captions=None, caption_offset=(0, -100, 0), cell_padding=0, - cell_shape='rect', + cell_shape="rect", aspect_ratio=16 / 9.0, dim=None, ): @@ -3278,7 +3275,7 @@ def grid( captions, if any. """ - grid_layout = layout.GridLayout( + grid_layout = lyt.GridLayout( cell_padding=cell_padding, cell_shape=cell_shape, aspect_ratio=aspect_ratio, @@ -3289,13 +3286,12 @@ def grid( if captions is not None: actors_with_caption = [] for actor, caption in zip(actors, captions): - actor_center = np.array(actor.GetCenter()) # Offset accordingly the caption w.r.t. # the center of the associated actor. if isinstance(caption, str): - caption = text_3d(caption, justification='center') + caption = text_3d(caption, justification="center") else: caption = shallow_copy(caption) caption.SetPosition(actor_center + caption_offset) @@ -3315,7 +3311,7 @@ def grid( return grid -def figure(pic, interpolation='nearest'): +def figure(pic, interpolation="nearest"): """Return a figure as an image actor. Parameters @@ -3332,9 +3328,7 @@ def figure(pic, interpolation='nearest'): if isinstance(pic, str): vtk_image_data = load_image(pic, True) else: - if pic.ndim == 3 and pic.shape[2] == 4: - vtk_image_data = ImageData() vtk_image_data.AllocateScalars(VTK_UNSIGNED_CHAR, 4) @@ -3350,13 +3344,13 @@ def figure(pic, interpolation='nearest'): image_actor = ImageActor() image_actor.SetInputData(vtk_image_data) - if interpolation == 'nearest': + if interpolation == "nearest": image_actor.GetProperty().SetInterpolationTypeToNearest() - if interpolation == 'linear': + if interpolation == "linear": image_actor.GetProperty().SetInterpolationTypeToLinear() - if interpolation == 'cubic': + if interpolation == "cubic": image_actor.GetProperty().SetInterpolationTypeToCubic() image_actor.Update() @@ -3437,12 +3431,26 @@ def texture_update(texture_actor, arr): """ grid = texture_actor.GetTexture().GetInput() dim = arr.shape[-1] - img_data = np.flip(arr.swapaxes(0, 1), axis=1).reshape((-1, dim), order='F') + img_data = np.flip(arr.swapaxes(0, 1), axis=1).reshape((-1, dim), order="F") vtkarr = numpy_support.numpy_to_vtk(img_data, deep=False) grid.GetPointData().SetScalars(vtkarr) def _textured_sphere_source(theta=60, phi=60): + """Use vtkTexturedSphereSource to set the theta and phi. + + Parameters + ---------- + theta : int, optional + Set the number of points in the longitude direction. + phi : int, optional + Set the number of points in the latitude direction. + + Returns + ------- + tss : TexturedSphereSource + + """ tss = TexturedSphereSource() tss.SetThetaResolution(theta) tss.SetPhiResolution(phi) @@ -3451,7 +3459,24 @@ def _textured_sphere_source(theta=60, phi=60): def texture_on_sphere(rgb, theta=60, phi=60, interpolate=True): + """Map an RGB or RGBA texture on a sphere. + + Parameters + ---------- + rgb : ndarray + Input 2D RGB or RGBA array. Dtype should be uint8. + theta : int, optional + Set the number of points in the longitude direction. + phi : int, optional + Set the number of points in the latitude direction. + interpolate : bool, optional + Interpolate between grid centers. + Returns + ------- + earthActor : Actor + + """ tss = _textured_sphere_source(theta=theta, phi=phi) earthMapper = PolyDataMapper() earthMapper.SetInputConnection(tss.GetOutputPort()) @@ -3476,8 +3501,8 @@ def texture_2d(rgb, interp=False): ---------- rgb : ndarray Input 2D RGB or RGBA array. Dtype should be uint8. - interp : bool - Interpolate between grid centers. Default True. + interp : bool, optional + Interpolate between grid centers. Returns ------- @@ -3535,7 +3560,7 @@ def texture_2d(rgb, interp=False): return act -def sdf(centers, directions=(1, 0, 0), colors=(1, 0, 0), primitives='torus', scales=1): +def sdf(centers, directions=(1, 0, 0), colors=(1, 0, 0), primitives="torus", scales=1): """Create a SDF primitive based actor. Parameters @@ -3557,7 +3582,7 @@ def sdf(centers, directions=(1, 0, 0), colors=(1, 0, 0), primitives='torus', sca box_actor: Actor """ - prims = {'sphere': 1, 'torus': 2, 'ellipsoid': 3, 'capsule': 4} + prims = {"sphere": 1, "torus": 2, "ellipsoid": 3, "capsule": 4} verts, faces = fp.prim_box() repeated = fp.repeat_primitive( @@ -3581,9 +3606,10 @@ def sdf(centers, directions=(1, 0, 0), colors=(1, 0, 0), primitives='torus', sca if len(primitives) < len(centers): primlist = primlist + [2] * (len(centers) - len(primitives)) warnings.warn( - 'Not enough primitives provided,\ - defaulting to torus', + "Not enough primitives provided,\ + defaulting to torus", category=UserWarning, + stacklevel=2, ) rep_prims = np.repeat(primlist, verts.shape[0]) else: @@ -3599,19 +3625,19 @@ def sdf(centers, directions=(1, 0, 0), colors=(1, 0, 0), primitives='torus', sca else: rep_directions = np.repeat(directions, verts.shape[0], axis=0) - attribute_to_actor(box_actor, rep_centers, 'center') - attribute_to_actor(box_actor, rep_prims, 'primitive') - attribute_to_actor(box_actor, rep_scales, 'scale') - attribute_to_actor(box_actor, rep_directions, 'direction') + attribute_to_actor(box_actor, rep_centers, "center") + attribute_to_actor(box_actor, rep_prims, "primitive") + attribute_to_actor(box_actor, rep_scales, "scale") + attribute_to_actor(box_actor, rep_directions, "direction") - vs_dec_code = import_fury_shader('sdf_dec.vert') - vs_impl_code = import_fury_shader('sdf_impl.vert') - fs_dec_code = import_fury_shader('sdf_dec.frag') - fs_impl_code = import_fury_shader('sdf_impl.frag') + vs_dec_code = import_fury_shader("sdf_dec.vert") + vs_impl_code = import_fury_shader("sdf_impl.vert") + fs_dec_code = import_fury_shader("sdf_dec.frag") + fs_impl_code = import_fury_shader("sdf_impl.frag") - shader_to_actor(box_actor, 'vertex', impl_code=vs_impl_code, decl_code=vs_dec_code) - shader_to_actor(box_actor, 'fragment', decl_code=fs_dec_code) - shader_to_actor(box_actor, 'fragment', impl_code=fs_impl_code, block='light') + shader_to_actor(box_actor, "vertex", impl_code=vs_impl_code, decl_code=vs_dec_code) + shader_to_actor(box_actor, "fragment", decl_code=fs_dec_code) + shader_to_actor(box_actor, "fragment", impl_code=fs_impl_code, block="light") return box_actor @@ -3619,13 +3645,14 @@ def markers( centers, colors=(0, 1, 0), scales=1, - marker='3d', + marker="3d", marker_opacity=0.8, edge_width=0.0, edge_color=(255, 255, 255), edge_opacity=0.8, ): """Create a marker actor with different shapes. + Parameters ---------- centers : ndarray, shape (N, 3) @@ -3685,68 +3712,62 @@ def markers( sq_actor.GetMapper().SetVBOShiftScaleMethod(False) sq_actor.GetProperty().BackfaceCullingOff() - attribute_to_actor(sq_actor, big_centers, 'center') + attribute_to_actor(sq_actor, big_centers, "center") marker2id = { - 'o': 0, - 's': 1, - 'd': 2, - '^': 3, - 'p': 4, - 'h': 5, - 's6': 6, - 'x': 7, - '+': 8, - '3d': 0, + "o": 0, + "s": 1, + "d": 2, + "^": 3, + "p": 4, + "h": 5, + "s6": 6, + "x": 7, + "+": 8, + "3d": 0, } - bb_impl = \ - """ + bb_impl = """ vec3 vertexPositionMC = sphericalVertexPos(center, MCVCMatrix, normalizedVertexMCVSOutput, shape); gl_Position = MCDCMatrix * vec4(vertexPositionMC, 1.); """ - vs_dec_code = \ - ''' + vs_dec_code = """ /* Billboard vertex shader declaration */ in vec3 center; out vec3 centerVertexMCVSOutput; out vec3 normalizedVertexMCVSOutput; - ''' - vs_dec_code += \ - f'\n{import_fury_shader("utils/billboard_normalization.glsl")}' + """ + vs_dec_code += f'\n{import_fury_shader("utils/billboard_normalization.glsl")}' vs_dec_code += f'\n{import_fury_shader("billboard/spherical.glsl")}' vs_dec_code += f'\n{import_fury_shader("marker_billboard_dec.vert")}' - vs_impl_code = \ - ''' + vs_impl_code = """ /* Billboard vertex shader implementation */ centerVertexMCVSOutput = center; normalizedVertexMCVSOutput = bbNorm(vertexMC.xyz, center); float scalingFactor = 1. / abs(normalizedVertexMCVSOutput.x); float size = abs((vertexMC.xyz - center).x) * 2; vec2 shape = vec2(size, size); // Fixes the scaling issue - ''' - vs_impl_code += f'\n{compose_shader(bb_impl)}' + """ + vs_impl_code += f"\n{compose_shader(bb_impl)}" vs_impl_code += f'\n{import_fury_shader("marker_billboard_impl.vert")}' - fs_dec_code = \ - ''' + fs_dec_code = """ /* Billboard fragment shader declaration */ in vec3 centerVertexMCVSOutput; in vec3 normalizedVertexMCVSOutput; - ''' + """ fs_dec_code += f'\n{import_fury_shader("marker_billboard_dec.frag")}' - fs_impl_code = \ - ''' + fs_impl_code = """ /* Billboard Fragment shader implementation */ // Renaming variables passed from the Vertex Shader vec3 color = vertexColorVSOutput.rgb; vec3 point = normalizedVertexMCVSOutput; fragOutput0 = vec4(color, 1.); - ''' + """ - if marker == '3d': + if marker == "3d": fs_impl_code += f'{import_fury_shader("billboard_spheres_impl.frag")}' else: fs_impl_code += f'{import_fury_shader("marker_billboard_impl.frag")}' @@ -3755,59 +3776,51 @@ def markers( else: list_of_markers = [marker2id[i] for i in marker] - list_of_markers = np.repeat(list_of_markers, 4).astype('float') - attribute_to_actor(sq_actor, list_of_markers, 'marker') + list_of_markers = np.repeat(list_of_markers, 4).astype("float") + attribute_to_actor(sq_actor, list_of_markers, "marker") def callback( - _caller, _event, calldata=None, uniform_type='f', uniform_name=None, value=None + _caller, _event, calldata=None, uniform_type="f", uniform_name=None, value=None ): program = calldata if program is not None: - program.__getattribute__(f'SetUniform{uniform_type}')(uniform_name, value) + program.__getattribute__(f"SetUniform{uniform_type}")(uniform_name, value) add_shader_callback( sq_actor, - partial(callback, uniform_type='f', uniform_name='edgeWidth', value=edge_width), + partial(callback, uniform_type="f", uniform_name="edgeWidth", value=edge_width), ) add_shader_callback( sq_actor, partial( callback, - uniform_type='f', - uniform_name='markerOpacity', + uniform_type="f", + uniform_name="markerOpacity", value=marker_opacity, ), ) add_shader_callback( sq_actor, partial( - callback, uniform_type='f', uniform_name='edgeOpacity', value=edge_opacity + callback, uniform_type="f", uniform_name="edgeOpacity", value=edge_opacity ), ) add_shader_callback( sq_actor, partial( - callback, uniform_type='3f', uniform_name='edgeColor', value=edge_color + callback, uniform_type="3f", uniform_name="edgeColor", value=edge_color ), ) - shader_to_actor(sq_actor, 'vertex', impl_code=vs_impl_code, decl_code=vs_dec_code) - shader_to_actor(sq_actor, 'fragment', decl_code=fs_dec_code) - shader_to_actor(sq_actor, 'fragment', impl_code=fs_impl_code, block='light') + shader_to_actor(sq_actor, "vertex", impl_code=vs_impl_code, decl_code=vs_dec_code) + shader_to_actor(sq_actor, "fragment", decl_code=fs_dec_code) + shader_to_actor(sq_actor, "fragment", impl_code=fs_impl_code, block="light") return sq_actor -def ellipsoid( - centers, - axes, - lengths, - colors=(1, 0, 0), - scales=1.0, - opacity=1.0 -): - """ - VTK actor for visualizing ellipsoids. +def ellipsoid(centers, axes, lengths, colors=(1, 0, 0), scales=1.0, opacity=1.0): + """VTK actor for visualizing ellipsoids. Parameters ---------- @@ -3829,7 +3842,6 @@ def ellipsoid( tensor_ellipsoid: Actor """ - if not isinstance(centers, np.ndarray): centers = np.array(centers) if centers.ndim == 1: @@ -3840,16 +3852,18 @@ def ellipsoid( if axes.ndim == 2: axes = np.array([axes]) if axes.shape[0] != centers.shape[0]: - raise ValueError('number of axes defined does not match with number of' - 'centers') + raise ValueError( + "number of axes defined does not match with number of" "centers" + ) if not isinstance(lengths, np.ndarray): lengths = np.array(lengths) if lengths.ndim == 1: lengths = np.array([lengths]) if lengths.shape[0] != centers.shape[0]: - raise ValueError('number of lengths defined does not match with number' - 'of centers') + raise ValueError( + "number of lengths defined does not match with number" "of centers" + ) if not isinstance(scales, np.ndarray): scales = np.array(scales) @@ -3857,7 +3871,8 @@ def ellipsoid( scales = np.repeat(scales, centers.shape[0]) elif scales.size != centers.shape[0]: scales = np.concatenate( - (scales, np.ones(centers.shape[0] - scales.shape[0])), axis=None) + (scales, np.ones(centers.shape[0] - scales.shape[0])), axis=None + ) if isinstance(colors, tuple): colors = np.array([colors]) @@ -3868,3 +3883,49 @@ def ellipsoid( return tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity) + +def uncertainty_cone(evals, evecs, signal, sigma, b_matrix, scales=0.6, opacity=1.0): + """VTK actor for visualizing the cone of uncertainty representing the + variance of the main direction of diffusion. + + Parameters + ---------- + evals : ndarray (3, ) or (N, 3) + Eigenvalues. + evecs : ndarray (3, 3) or (N, 3, 3) + Eigenvectors. + signal : 3D or 4D ndarray + Predicted signal. + sigma : ndarray + Standard deviation of the noise. + b_matrix : array (N, 7) + Design matrix for DTI. + scales : float or ndarray (N, ), optional + Cones of uncertainty size. + opacity : float, optional + Takes values from 0 (fully transparent) to 1 (opaque), default(1.0). + + Returns + ------- + double_cone: Actor + + """ + valid_mask = np.abs(evecs).max(axis=(-2, -1)) > 0 + indices = np.nonzero(valid_mask) + + evecs = evecs[indices] + evals = evals[indices] + signal = signal[indices] + + centers = np.asarray(indices).T + colors = np.array([107, 107, 107]) + + x, y, z = evecs.shape + if not isinstance(scales, np.ndarray): + scales = np.array(scales) + if scales.size == 1: + scales = np.repeat(scales, x) + + angles = main_dir_uncertainty(evals, evecs, signal, sigma, b_matrix) + + return double_cone(centers, evecs, angles, colors, scales, opacity) diff --git a/fury/actors/odf_slicer.py b/fury/actors/odf_slicer.py index 253ea9cf1..8b75d200a 100644 --- a/fury/actors/odf_slicer.py +++ b/fury/actors/odf_slicer.py @@ -12,8 +12,7 @@ class OdfSlicerActor(Actor): - """ - VTK actor for visualizing slices of ODF field. + """VTK actor for visualizing slices of ODF field. Parameters ---------- @@ -49,6 +48,7 @@ class OdfSlicerActor(Actor): Optional SH to SF matrix for projecting `odfs` given in SH coefficients on the `sphere`. If None, then the input is assumed to be expressed in SF coefficients. + """ def __init__( @@ -108,14 +108,11 @@ def __init__( self.set_opacity(opacity) def set_opacity(self, opacity): - """ - Set opacity value of ODFs to display. - """ + """Set opacity value of ODFs to display.""" self.GetProperty().SetOpacity(opacity) def display_extent(self, x1, x2, y1, y2, z1, z2): - """ - Set visible volume from x1 (inclusive) to x2 (inclusive), + """Set visible volume from x1 (inclusive) to x2 (inclusive), y1 (inclusive) to y2 (inclusive), z1 (inclusive) to z2 (inclusive). """ @@ -125,12 +122,11 @@ def display_extent(self, x1, x2, y1, y2, z1, z2): self._update_mapper() - def slice_along_axis(self, slice_index, axis='zaxis'): - """ - Slice ODF field at given `slice_index` along axis + def slice_along_axis(self, slice_index, axis="zaxis"): + """Slice ODF field at given `slice_index` along axis in ['xaxis', 'yaxis', zaxis']. """ - if axis == 'xaxis': + if axis == "xaxis": self.display_extent( slice_index, slice_index, @@ -139,7 +135,7 @@ def slice_along_axis(self, slice_index, axis='zaxis'): 0, self.grid_shape[2] - 1, ) - elif axis == 'yaxis': + elif axis == "yaxis": self.display_extent( 0, self.grid_shape[0] - 1, @@ -148,7 +144,7 @@ def slice_along_axis(self, slice_index, axis='zaxis'): 0, self.grid_shape[2] - 1, ) - elif axis == 'zaxis': + elif axis == "zaxis": self.display_extent( 0, self.grid_shape[0] - 1, @@ -158,27 +154,23 @@ def slice_along_axis(self, slice_index, axis='zaxis'): slice_index, ) else: - raise ValueError('Invalid axis name {0}.'.format(axis)) + raise ValueError("Invalid axis name {0}.".format(axis)) def display(self, x=None, y=None, z=None): - """ - Display a slice along x, y, or z axis. - """ + """Display a slice along x, y, or z axis.""" if x is None and y is None and z is None: self.slice_along_axis(self.grid_shape[2] // 2) elif x is not None: - self.slice_along_axis(x, 'xaxis') + self.slice_along_axis(x, "xaxis") elif y is not None: - self.slice_along_axis(y, 'yaxis') + self.slice_along_axis(y, "yaxis") elif z is not None: - self.slice_along_axis(z, 'zaxis') + self.slice_along_axis(z, "zaxis") def update_sphere(self, vertices, faces, B): - """ - Dynamically change the sphere used for SH to SF projection. - """ + """Dynamically change the sphere used for SH to SF projection.""" if self.B is None: - raise ValueError("Can't update sphere when using " 'SF coefficients.') + raise ValueError("Can't update sphere when using " "SF coefficients.") self.vertices = vertices if self.affine is not None: self.w_verts = self.vertices.dot(self.affine[:3, :3]) @@ -189,9 +181,7 @@ def update_sphere(self, vertices, faces, B): self._update_mapper() def _update_mapper(self): - """ - Map vtkPolyData to the actor. - """ + """Map vtkPolyData to the actor.""" polydata = PolyData() offsets = self._get_odf_offsets(self.mask) @@ -215,25 +205,19 @@ def _update_mapper(self): self.mapper.SetInputData(polydata) def _get_odf_offsets(self, mask): - """ - Get the position of non-zero voxels inside `mask`. - """ + """Get the position of non-zero voxels inside `mask`.""" if self.affine is not None: return self.w_pos[mask[self.indices]] return np.asarray(self.indices).T[mask[self.indices]] def _get_sphere_directions(self): - """ - Get the sphere directions onto which is projected the signal. - """ + """Get the sphere directions onto which is projected the signal.""" if self.affine is not None: return self.w_verts return self.vertices def _get_sf(self, mask): - """ - Get SF coefficients inside `mask`. - """ + """Get SF coefficients inside `mask`.""" # when odfs are expressed in SH coefficients if self.B is not None: sf = self.odfs[mask[self.indices]].dot(self.B) @@ -246,9 +230,7 @@ def _get_sf(self, mask): return self.odfs[mask[self.indices]] def _get_all_vertices(self, offsets, sph_dirs, sf): - """ - Get array of all the vertices of the ODFs to display. - """ + """Get array of all the vertices of the ODFs to display.""" if self.radial_scale: # apply SF amplitudes to all sphere # directions and offset each voxel @@ -261,20 +243,16 @@ def _get_all_vertices(self, offsets, sph_dirs, sf): ) def _get_all_faces(self, nb_odfs, nb_dirs): - """ - Get array of all the faces of the ODFs to display. - """ + """Get array of all the faces of the ODFs to display.""" return np.tile(self.faces, (nb_odfs, 1)) + np.repeat( np.arange(nb_odfs) * nb_dirs, len(self.faces) ).reshape(-1, 1) def _generate_color_for_vertices(self, sf): - """ - Get array of all vertices colors. - """ + """Get array of all vertices colors.""" if self.global_cm: if self.colormap is None: - raise IOError('if global_cm=True, colormap must be defined.') + raise IOError("if global_cm=True, colormap must be defined.") else: all_colors = create_colormap(sf.ravel(), self.colormap) * 255 elif self.colormap is not None: diff --git a/fury/actors/peak.py b/fury/actors/peak.py index 338a9c7b0..931f9db5d 100644 --- a/fury/actors/peak.py +++ b/fury/actors/peak.py @@ -132,13 +132,13 @@ def __init__( self.__mapper.SetInputData(poly_data) self.__mapper.ScalarVisibilityOn() self.__mapper.SetScalarModeToUsePointFieldData() - self.__mapper.SelectColorArray('colors') + self.__mapper.SelectColorArray("colors") self.__mapper.Update() self.SetMapper(self.__mapper) - attribute_to_actor(self, centers_array, 'center') - attribute_to_actor(self, diffs_array, 'diff') + attribute_to_actor(self, centers_array, "center") + attribute_to_actor(self, diffs_array, "diff") vs_var_dec = """ in vec3 center; @@ -152,11 +152,11 @@ def __init__( uniform vec3 lowRanges; uniform vec3 highRanges; """ - orient_to_rgb = import_fury_shader(pjoin('utils', 'orient_to_rgb.glsl')) + orient_to_rgb = import_fury_shader(pjoin("utils", "orient_to_rgb.glsl")) visible_cross_section = import_fury_shader( - pjoin('interaction', 'visible_cross_section.glsl') + pjoin("interaction", "visible_cross_section.glsl") ) - visible_range = import_fury_shader(pjoin('interaction', 'visible_range.glsl')) + visible_range = import_fury_shader(pjoin("interaction", "visible_range.glsl")) vs_dec = compose_shader([vs_var_dec, orient_to_rgb]) fs_dec = compose_shader([fs_var_dec, visible_cross_section, visible_range]) @@ -182,9 +182,9 @@ def __init__( } """ - shader_to_actor(self, 'vertex', decl_code=vs_dec, impl_code=vs_impl) - shader_to_actor(self, 'fragment', decl_code=fs_dec) - shader_to_actor(self, 'fragment', impl_code=fs_impl, block='light') + shader_to_actor(self, "vertex", decl_code=vs_dec, impl_code=vs_impl) + shader_to_actor(self, "fragment", decl_code=fs_dec) + shader_to_actor(self, "fragment", impl_code=fs_impl, block="light") # Color scale with a lookup table if colors_are_scalars: @@ -216,10 +216,10 @@ def __init__( @calldata_type(VTK_OBJECT) def __display_peaks_vtk_callback(self, caller, event, calldata=None): if calldata is not None: - calldata.SetUniformi('isRange', self.__is_range) - calldata.SetUniform3f('highRanges', self.__high_ranges) - calldata.SetUniform3f('lowRanges', self.__low_ranges) - calldata.SetUniform3f('crossSection', self.__cross_section) + calldata.SetUniformi("isRange", self.__is_range) + calldata.SetUniform3f("highRanges", self.__high_ranges) + calldata.SetUniform3f("lowRanges", self.__low_ranges) + calldata.SetUniform3f("crossSection", self.__cross_section) def display_cross_section(self, x, y, z): if self.__is_range: @@ -275,9 +275,8 @@ def min_centers(self): return self.__min_centers -def _orientation_colors(points, cmap='rgb_standard'): +def _orientation_colors(points, cmap="rgb_standard"): """ - Parameters ---------- points : (N, 3) array or ndarray @@ -291,25 +290,24 @@ def _orientation_colors(points, cmap='rgb_standard'): list of Kx3 colors. Where K is the number of lines. """ - if cmap.lower() == 'rgb_standard': + if cmap.lower() == "rgb_standard": col_list = [ orient2rgb(points[i + 1] - points[i]) for i in range(0, len(points), 2) ] - elif cmap.lower() == 'boys_standard': + elif cmap.lower() == "boys_standard": col_list = [ boys2rgb(points[i + 1] - points[i]) for i in range(0, len(points), 2) ] else: raise ValueError( - 'Invalid colormap. The only available options are ' + "Invalid colormap. The only available options are " "'rgb_standard' and 'boys_standard'." ) return np.asarray(col_list) def _peaks_colors_from_points(points, colors=None, points_per_line=2): - """ - Returns a VTK scalar array containing colors information for each one of + """Return a VTK scalar array containing colors information for each one of the peaks according to the policy defined by the parameter colors. Parameters @@ -348,7 +346,7 @@ def _peaks_colors_from_points(points, colors=None, points_per_line=2): num_lines = num_pnts // points_per_line colors_are_scalars = False global_opacity = 1 - if colors is None or colors == 'rgb_standard': + if colors is None or colors == "rgb_standard": # Automatic RGB colors colors = np.asarray((0, 0, 0)) color_array = numpy_to_vtk_colors(np.tile(255 * colors, (num_pnts, 1))) @@ -374,14 +372,12 @@ def _peaks_colors_from_points(points, colors=None, points_per_line=2): global_opacity = 1 if colors.shape[1] == 3 else -1 color_array = numpy_to_vtk_colors(255 * colors) - color_array.SetName('colors') + color_array.SetName("colors") return color_array, colors_are_scalars, global_opacity def _points_to_vtk_cells(points, points_per_line=2): - """ - - Returns the VTK cell array for the peaks given the set of points + """Return the VTK cell array for the peaks given the set of points coordinates. Parameters diff --git a/fury/actors/tensor.py b/fury/actors/tensor.py index a3b473e57..5bb701b9e 100644 --- a/fury/actors/tensor.py +++ b/fury/actors/tensor.py @@ -3,12 +3,16 @@ import numpy as np from fury import actor -from fury.shaders import (attribute_to_actor, compose_shader, - import_fury_shader, shader_to_actor) +from fury.shaders import ( + attribute_to_actor, + compose_shader, + import_fury_shader, + shader_to_actor, +) + def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): - """ - Visualize one or many Tensor Ellipsoids with different features. + """Visualize one or many Tensor Ellipsoids with different features. Parameters ---------- @@ -30,7 +34,6 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): box_actor: Actor """ - box_actor = actor.box(centers, colors=colors, scales=scales) box_actor.GetMapper().SetVBOShiftScaleMethod(False) box_actor.GetProperty().SetOpacity(opacity) @@ -39,29 +42,28 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): n_verts = 8 big_centers = np.repeat(centers, n_verts, axis=0) - attribute_to_actor(box_actor, big_centers, 'center') + attribute_to_actor(box_actor, big_centers, "center") big_scales = np.repeat(scales, n_verts, axis=0) - attribute_to_actor(box_actor, big_scales, 'scale') + attribute_to_actor(box_actor, big_scales, "scale") big_values = np.repeat(np.array(lengths, dtype=float), n_verts, axis=0) - attribute_to_actor(box_actor, big_values, 'evals') + attribute_to_actor(box_actor, big_values, "evals") evec1 = np.array([item[0] for item in axes]) evec2 = np.array([item[1] for item in axes]) evec3 = np.array([item[2] for item in axes]) big_vectors_1 = np.repeat(evec1, n_verts, axis=0) - attribute_to_actor(box_actor, big_vectors_1, 'evec1') + attribute_to_actor(box_actor, big_vectors_1, "evec1") big_vectors_2 = np.repeat(evec2, n_verts, axis=0) - attribute_to_actor(box_actor, big_vectors_2, 'evec2') + attribute_to_actor(box_actor, big_vectors_2, "evec2") big_vectors_3 = np.repeat(evec3, n_verts, axis=0) - attribute_to_actor(box_actor, big_vectors_3, 'evec3') + attribute_to_actor(box_actor, big_vectors_3, "evec3") # Start of shader implementation - vs_dec = \ - """ + vs_dec = """ in vec3 center; in float scale; in vec3 evals; @@ -77,8 +79,7 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): """ # Variables assignment - v_assign = \ - """ + v_assign = """ vertexMCVSOutput = vertexMC; centerMCVSOutput = center; scaleVSOutput = scale; @@ -86,13 +87,12 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): # Normalization n_evals = "evalsVSOutput = evals/(max(evals.x, max(evals.y, evals.z)));" - + # Values constraint to avoid incorrect visualizations evals = "evalsVSOutput = clamp(evalsVSOutput,0.05,1);" - + # Scaling matrix - sc_matrix = \ - """ + sc_matrix = """ mat3 S = mat3(1/evalsVSOutput.x, 0.0, 0.0, 0.0, 1/evalsVSOutput.y, 0.0, 0.0, 0.0, 1/evalsVSOutput.z); @@ -100,18 +100,18 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): # Rotation matrix rot_matrix = "mat3 R = mat3(evec1, evec2, evec3);" - + # Tensor matrix t_matrix = "tensorMatrix = inverse(R) * S * R;" - vs_impl = compose_shader([v_assign, n_evals, evals, sc_matrix, rot_matrix, - t_matrix]) + vs_impl = compose_shader( + [v_assign, n_evals, evals, sc_matrix, rot_matrix, t_matrix] + ) # Adding shader implementation to actor - shader_to_actor(box_actor, 'vertex', decl_code=vs_dec, impl_code=vs_impl) + shader_to_actor(box_actor, "vertex", decl_code=vs_dec, impl_code=vs_impl) - fs_vars_dec = \ - """ + fs_vars_dec = """ in vec4 vertexMCVSOutput; in vec3 centerMCVSOutput; in float scaleVSOutput; @@ -122,11 +122,10 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): """ # Importing the sphere SDF - sd_sphere = import_fury_shader(os.path.join('sdf', 'sd_sphere.frag')) + sd_sphere = import_fury_shader(os.path.join("sdf", "sd_sphere.frag")) # SDF definition - sdf_map = \ - """ + sdf_map = """ float map(in vec3 position) { /* @@ -136,7 +135,7 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): */ float scFactor = min(evalsVSOutput.x, min(evalsVSOutput.y, evalsVSOutput.z)); - + /* The approximation of distance is calculated by stretching the space such that the ellipsoid becomes a sphere (multiplying by @@ -149,50 +148,233 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): """ # Importing central differences function for computing surface normals - central_diffs_normal = import_fury_shader(os.path.join( - 'sdf', 'central_diffs.frag')) + central_diffs_normal = import_fury_shader(os.path.join("sdf", "central_diffs.frag")) # Importing raymarching function - cast_ray = import_fury_shader(os.path.join( - 'ray_marching', 'cast_ray.frag')) + cast_ray = import_fury_shader(os.path.join("ray_marching", "cast_ray.frag")) + + # Importing the function that generates the ray components + ray_generation = import_fury_shader(os.path.join("ray_marching", "gen_ray.frag")) # Importing Blinn-Phong model for lighting - blinn_phong_model = import_fury_shader(os.path.join( - 'lighting', 'blinn_phong_model.frag')) + blinn_phong_model = import_fury_shader( + os.path.join("lighting", "blinn_phong_model.frag") + ) # Full fragment shader declaration - fs_dec = compose_shader([fs_vars_dec, sd_sphere, sdf_map, - central_diffs_normal, cast_ray, - blinn_phong_model]) + fs_dec = compose_shader( + [ + fs_vars_dec, + sd_sphere, + sdf_map, + central_diffs_normal, + cast_ray, + ray_generation, + blinn_phong_model, + ] + ) + + shader_to_actor(box_actor, "fragment", decl_code=fs_dec) + + ray_components = """ + vec3 ro; vec3 rd; float t; + gen_ray(ro, rd, t); + """ + + # Fragment shader output definition + # If surface is detected, color is assigned, otherwise, nothing is painted + frag_output_def = """ + if(t < 20) + { + vec3 pos = ro + t * rd; + vec3 normal = centralDiffsNormals(pos, .0001); + // Light Direction + vec3 ld = normalize(ro - pos); + // Light Attenuation + float la = dot(ld, normal); + vec3 color = blinnPhongIllumModel(la, lightColor0, + diffuseColor, specularPower, specularColor, ambientColor); + fragOutput0 = vec4(color, opacity); + } + else + { + discard; + } + """ + + # Full fragment shader implementation + sdf_frag_impl = compose_shader([ray_components, frag_output_def]) + + shader_to_actor(box_actor, "fragment", impl_code=sdf_frag_impl, block="light") + + return box_actor + + +def double_cone(centers, axes, angles, colors, scales, opacity): + """Visualize one or many Double Cones with different features. + + Parameters + ---------- + centers : ndarray(N, 3) + Cone positions. + axes : ndarray (3, 3) or (N, 3, 3) + Axes of the cone. + angles : float or ndarray (N, ) + Angles of the cone. + colors : ndarray (N, 3) or tuple (3,) + R, G and B should be in the range [0, 1]. + scales : float or ndarray (N, ) + Cone size. + opacity : float + Takes values from 0 (fully transparent) to 1 (opaque). - shader_to_actor(box_actor, 'fragment', decl_code=fs_dec) + Returns + ------- + box_actor: Actor - # Vertex in Model Coordinates. - point = "vec3 point = vertexMCVSOutput.xyz;" + """ + box_actor = actor.box(centers, colors=colors, scales=scales) + box_actor.GetMapper().SetVBOShiftScaleMethod(False) + box_actor.GetProperty().SetOpacity(opacity) - # Camera position in world space - ray_origin = "vec3 ro = (-MCVCMatrix[3] * MCVCMatrix).xyz;" + # Number of vertices that make up the box + n_verts = 8 - ray_direction = "vec3 rd = normalize(point - ro);" + big_centers = np.repeat(centers, n_verts, axis=0) + attribute_to_actor(box_actor, big_centers, "center") - light_direction = "vec3 ld = normalize(ro - point);" + big_scales = np.repeat(scales, n_verts, axis=0) + attribute_to_actor(box_actor, big_scales, "scale") - ray_origin_update = "ro += point - ro;" + evec1 = np.array([item[0] for item in axes]) + evec2 = np.array([item[1] for item in axes]) + evec3 = np.array([item[2] for item in axes]) - # Total distance traversed along the ray - distance = "float t = castRay(ro, rd);" + big_vectors_1 = np.repeat(evec1, n_verts, axis=0) + attribute_to_actor(box_actor, big_vectors_1, "evec1") + big_vectors_2 = np.repeat(evec2, n_verts, axis=0) + attribute_to_actor(box_actor, big_vectors_2, "evec2") + big_vectors_3 = np.repeat(evec3, n_verts, axis=0) + attribute_to_actor(box_actor, big_vectors_3, "evec3") + + big_angles = np.repeat(np.array(angles, dtype=float), n_verts, axis=0) + attribute_to_actor(box_actor, big_angles, "angle") + + # Start of shader implementation + + vs_dec = """ + in vec3 center; + in float scale; + in vec3 evec1; + in vec3 evec2; + in vec3 evec3; + in float angle; + + out vec4 vertexMCVSOutput; + out vec3 centerMCVSOutput; + out float scaleVSOutput; + out mat3 rotationMatrix; + out float angleVSOutput; + """ + + # Variables assignment + v_assign = """ + vertexMCVSOutput = vertexMC; + centerMCVSOutput = center; + scaleVSOutput = scale; + angleVSOutput = angle; + """ + + # Rotation matrix + rot_matrix = """ + mat3 R = mat3(normalize(evec1), normalize(evec2), normalize(evec3)); + float a = radians(90); + mat3 rot = mat3(cos(a),-sin(a), 0, + sin(a), cos(a), 0, + 0 , 0, 1); + rotationMatrix = transpose(R) * rot; + """ + + vs_impl = compose_shader([v_assign, rot_matrix]) + + shader_to_actor(box_actor, "vertex", decl_code=vs_dec, impl_code=vs_impl) + + fs_vars_dec = """ + in vec4 vertexMCVSOutput; + in vec3 centerMCVSOutput; + in float scaleVSOutput; + in mat3 rotationMatrix; + in float angleVSOutput; + + uniform mat4 MCVCMatrix; + """ + + # Importing the cone SDF + sd_cone = import_fury_shader(os.path.join("sdf", "sd_cone.frag")) + + # Importing the union operation SDF + sd_union = import_fury_shader(os.path.join("sdf", "sd_union.frag")) + + # SDF definition + sdf_map = """ + float map(in vec3 position) + { + vec3 p = (position - centerMCVSOutput)/scaleVSOutput + *rotationMatrix; + float angle = clamp(angleVSOutput, 0, 6.283); + vec2 a = vec2(sin(angle), cos(angle)); + float h = .5 * a.y; + return opUnion(sdCone(p,a,h), sdCone(-p,a,h)) * scaleVSOutput; + } + """ + + # Importing central differences function for computing surface normals + central_diffs_normal = import_fury_shader(os.path.join("sdf", "central_diffs.frag")) + + # Importing raymarching function + cast_ray = import_fury_shader(os.path.join("ray_marching", "cast_ray.frag")) + + # Importing the function that generates the ray components + ray_generation = import_fury_shader(os.path.join("ray_marching", "gen_ray.frag")) + + # Importing Blinn-Phong model for lighting + blinn_phong_model = import_fury_shader( + os.path.join("lighting", "blinn_phong_model.frag") + ) + + # Full fragment shader declaration + fs_dec = compose_shader( + [ + fs_vars_dec, + sd_cone, + sd_union, + sdf_map, + central_diffs_normal, + cast_ray, + ray_generation, + blinn_phong_model, + ] + ) + + shader_to_actor(box_actor, "fragment", decl_code=fs_dec) + + ray_components = """ + vec3 ro; vec3 rd; float t; + gen_ray(ro, rd, t); + """ # Fragment shader output definition # If surface is detected, color is assigned, otherwise, nothing is painted - frag_output_def = \ - """ + frag_output_def = """ if(t < 20) { vec3 pos = ro + t * rd; vec3 normal = centralDiffsNormals(pos, .0001); + // Light Direction + vec3 ld = normalize(ro - pos); // Light Attenuation float la = dot(ld, normal); - vec3 color = blinnPhongIllumModel(la, lightColor0, + vec3 color = blinnPhongIllumModel(la, lightColor0, diffuseColor, specularPower, specularColor, ambientColor); fragOutput0 = vec4(color, opacity); } @@ -203,11 +385,108 @@ def tensor_ellipsoid(centers, axes, lengths, colors, scales, opacity): """ # Full fragment shader implementation - sdf_frag_impl = compose_shader([point, ray_origin, ray_direction, - light_direction, ray_origin_update, - distance, frag_output_def]) + sdf_frag_impl = compose_shader([ray_components, frag_output_def]) - shader_to_actor(box_actor, 'fragment', impl_code=sdf_frag_impl, - block='light') + shader_to_actor(box_actor, "fragment", impl_code=sdf_frag_impl, block="light") return box_actor + + +def main_dir_uncertainty(evals, evecs, signal, sigma, b_matrix): + """Calculate the angle of the cone of uncertainty that represents the + perturbation of the main eigenvector of the diffusion tensor matrix. + + Parameters + ---------- + evals : ndarray (3, ) or (N, 3) + Eigenvalues. + evecs : ndarray (3, 3) or (N, 3, 3) + Eigenvectors. + signal : 3D or 4D ndarray + Predicted signal. + sigma : ndarray + Standard deviation of the noise. + b_matrix : array (N, 7) + Design matrix for DTI. + + Returns + ------- + angles: array + + Notes + ----- + The uncertainty calculation is based on first-order matrix perturbation + analysis described in [1]_. The idea is to estimate the variance of the + main eigenvector which corresponds to the main direction of diffusion, + directly from estimated D and its estimated covariance matrix + :math:`\\Delta D` (see [2]_, equation 4). The angle :math:`\\Theta` + between the perturbed principal eigenvector of D, + :math:`\\epsilon_1+\\Delta\\epsilon_1`, and the estimated eigenvector + :math:`\\epsilon_1`, measures the angular deviation of the main fiber + direction and can be approximated by: + + .. math:: + \\Theta=tan^{-1}(\\|\\Delta\\epsilon_1\\|) + + Giving way to a graphical construct for displaying both the main + eigenvector of D and its associated uncertainty, with the so-called + "uncertainty cone". + + References + ---------- + .. [1] Basser, P. J. (1997). Quantifying errors in fiber direction and + diffusion tensor field maps resulting from MR noise. In 5th Scientific + Meeting of the ISMRM (Vol. 1740). + + .. [2] Chang, L. C., Koay, C. G., Pierpaoli, C., & Basser, P. J. (2007). + Variance of estimated DTI-derived parameters via first-order perturbation + methods. Magnetic Resonance in Medicine: An Official Journal of the + International Society for Magnetic Resonance in Medicine, 57(1), 141-149. + + """ + angles = np.ones(evecs.shape[0]) + for i in range(evecs.shape[0]): + sigma_e = np.diag(signal[i] / sigma**2) + k = np.dot(np.transpose(b_matrix), sigma_e) + sigma_ = np.dot(k, b_matrix) + + dd = np.diag(sigma_) + delta_DD = np.array( + [[dd[0], dd[3], dd[4]], [dd[3], dd[1], dd[5]], [dd[4], dd[5], dd[2]]] + ) + + # perturbation matrix of tensor D + try: + delta_D = np.linalg.inv(delta_DD) + except np.linalg.LinAlgError: + delta_D = delta_DD + + D_ = evecs + eigen_vals = evals[i] + + e1, e2, e3 = np.array(D_[i, :, 0]), np.array(D_[i, :, 1]), np.array(D_[i, :, 2]) + lambda1, lambda2, lambda3 = eigen_vals[0], eigen_vals[1], eigen_vals[2] + + if lambda1 > lambda2 and lambda1 > lambda3: + # The perturbation of the eigenvector associated with the largest + # eigenvalue is given by + a = np.dot( + np.outer(np.dot(e1, delta_D), np.transpose(e2)) / (lambda1 - lambda2), + e2, + ) + b = np.dot( + np.outer(np.dot(e1, delta_D), np.transpose(e3)) / (lambda1 - lambda3), + e3, + ) + delta_e1 = a + b + + # The angle \theta between the perturbed principal eigenvector of D + theta = np.arctan(np.linalg.norm(delta_e1)) + angles[i] = theta + else: + # If the condition is not satisfied it means that there is not a + # predominant diffusion direction, hence the uncertainty will be + # higher and a default value close to pi/2 is assigned + theta = 1.39626 + + return angles diff --git a/fury/actors/tests/test_peak.py b/fury/actors/tests/test_peak.py index fbb688f33..13ea18742 100644 --- a/fury/actors/tests/test_peak.py +++ b/fury/actors/tests/test_peak.py @@ -42,11 +42,11 @@ def test__orientation_colors(): [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]] ) - colors = _orientation_colors(points, cmap='rgb_standard') + colors = _orientation_colors(points, cmap="rgb_standard") expected = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] npt.assert_array_equal(colors, expected) - npt.assert_raises(ValueError, _orientation_colors, points, cmap='test') + npt.assert_raises(ValueError, _orientation_colors, points, cmap="test") def test__peaks_colors_from_points(): @@ -61,7 +61,7 @@ def test__peaks_colors_from_points(): npt.assert_equal(colors_are_scalars, False) npt.assert_equal(global_opacity, 1) - colors_tuple = _peaks_colors_from_points(points, colors='rgb_standard') + colors_tuple = _peaks_colors_from_points(points, colors="rgb_standard") vtk_colors, colors_are_scalars, global_opacity = colors_tuple desired = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] npt.assert_array_equal(numpy_support.vtk_to_numpy(vtk_colors), desired) diff --git a/fury/animation/__init__.py b/fury/animation/__init__.py index 33129d5b4..10cd5a103 100644 --- a/fury/animation/__init__.py +++ b/fury/animation/__init__.py @@ -1,2 +1,8 @@ from fury.animation.animation import Animation, CameraAnimation from fury.animation.timeline import Timeline + +__all__ = [ + "Animation", + "CameraAnimation", + "Timeline", +] diff --git a/fury/animation/animation.py b/fury/animation/animation.py index ac232e47e..f2fa56bb9 100644 --- a/fury/animation/animation.py +++ b/fury/animation/animation.py @@ -39,10 +39,10 @@ class Animation: motion_path_res : int, default: None the number of line segments used to visualizer the animation's motion path (visualizing position). + """ def __init__(self, actors=None, length=None, loop=True, motion_path_res=None): - super().__init__() self._data = defaultdict(dict) self._animations = [] @@ -73,6 +73,7 @@ def update_duration(self): ------- float The duration of the animation. + """ if self._length is not None: self._duration = self._length @@ -92,6 +93,7 @@ def duration(self): ------- float The duration of the animation. + """ return self._duration @@ -103,6 +105,7 @@ def current_timestamp(self): ------- float The current time of the animation. + """ if self._timeline: return self._timeline.current_timestamp @@ -124,10 +127,10 @@ def update_motion_path(self): lines = [] colors = [] - if self.is_interpolatable('position'): + if self.is_interpolatable("position"): ts = np.linspace(0, self.duration, res) [lines.append(self.get_position(t).tolist()) for t in ts] - if self.is_interpolatable('color'): + if self.is_interpolatable("color"): [colors.append(self.get_color(t)) for t in ts] elif len(self._actors) >= 1: colors = sum([i.vcolors[0] / 255 for i in self._actors]) / len( @@ -156,6 +159,7 @@ def _get_data(self): ------- dict: The animation data containing keyframes and interpolators. + """ return self._data @@ -171,18 +175,19 @@ def _get_attribute_data(self, attrib): ------- dict: The animation data for a specific attribute. + """ data = self._get_data() if attrib not in data: data[attrib] = { - 'keyframes': defaultdict(dict), - 'interpolator': { - 'base': linear_interpolator if attrib != 'rotation' else slerp, - 'func': None, - 'args': defaultdict(), + "keyframes": defaultdict(dict), + "interpolator": { + "base": (linear_interpolator if attrib != "rotation" else slerp), + "func": None, + "args": defaultdict(), }, - 'callbacks': [], + "callbacks": [], } return data.get(attrib) @@ -194,15 +199,15 @@ def get_keyframes(self, attrib=None): attrib: str, optional, default: None The name of the attribute. If None, all keyframes for all set attributes will be returned. - """ + """ data = self._get_data() if attrib is None: attribs = data.keys() return { - attrib: data.get(attrib, {}).get('keyframes', {}) for attrib in attribs + attrib: data.get(attrib, {}).get("keyframes", {}) for attrib in attribs } - return data.get(attrib, {}).get('keyframes', {}) + return data.get(attrib, {}).get("keyframes", {}) def set_keyframe( self, attrib, timestamp, value, update_interpolator=True, **kwargs @@ -230,13 +235,13 @@ def set_keyframe( The in tangent at that position for the cubic spline curve. out_tangent: ndarray, shape (1, M), optional The out tangent at that position for the cubic spline curve. - """ + """ attrib_data = self._get_attribute_data(attrib) - keyframes = attrib_data.get('keyframes') + keyframes = attrib_data.get("keyframes") keyframes[timestamp] = { - 'value': np.array(value).astype(float), + "value": np.array(value).astype(float), **{ par: np.array(val).astype(float) for par, val in kwargs.items() @@ -245,11 +250,11 @@ def set_keyframe( } if update_interpolator: - interp = attrib_data.get('interpolator') + interp = attrib_data.get("interpolator") interp_base = interp.get( - 'base', linear_interpolator if attrib != 'rotation' else slerp + "base", linear_interpolator if attrib != "rotation" else slerp ) - args = interp.get('args', {}) + args = interp.get("args", {}) self.set_interpolator(attrib, interp_base, **args) if timestamp > self._max_timestamp: @@ -279,6 +284,7 @@ def set_keyframes(self, attrib, keyframes): >>> 2: {'value': [3, 4, 5], 'in_cp': [1, 2, 3]}} >>> pos_keyframes = {1: np.array([1, 2, 3]), 3: np.array([5, 5, 5])} >>> Animation.set_keyframes('position', pos_keyframes) + """ for t, keyframe in keyframes.items(): if isinstance(keyframe, dict): @@ -295,6 +301,7 @@ def is_inside_scene_at(self, timestamp): bool True if the Animation is set to be inside the scene at the given timestamp. + Notes ----- If the parent Animation is set to be out of the scene at that time, all @@ -306,8 +313,8 @@ def is_inside_scene_at(self, timestamp): if parent is not None: parent_in_scene = parent._added_to_scene - if self.is_interpolatable('in_scene'): - in_scene = parent_in_scene and self.get_value('in_scene', timestamp) + if self.is_interpolatable("in_scene"): + in_scene = parent_in_scene and self.get_value("in_scene", timestamp) else: in_scene = parent_in_scene return in_scene @@ -319,12 +326,13 @@ def add_to_scene_at(self, timestamp): ---------- timestamp: float Timestamp of the event. + """ - if not self.is_interpolatable('in_scene'): - self.set_keyframe('in_scene', timestamp, True) - self.set_interpolator('in_scene', step_interpolator) + if not self.is_interpolatable("in_scene"): + self.set_keyframe("in_scene", timestamp, True) + self.set_interpolator("in_scene", step_interpolator) else: - self.set_keyframe('in_scene', timestamp, True) + self.set_keyframe("in_scene", timestamp, True) def remove_from_scene_at(self, timestamp): """Set timestamp for removing Animation to scene event. @@ -333,12 +341,13 @@ def remove_from_scene_at(self, timestamp): ---------- timestamp: float Timestamp of the event. + """ - if not self.is_interpolatable('in_scene'): - self.set_keyframe('in_scene', timestamp, False) - self.set_interpolator('in_scene', step_interpolator) + if not self.is_interpolatable("in_scene"): + self.set_keyframe("in_scene", timestamp, False) + self.set_interpolator("in_scene", step_interpolator) else: - self.set_keyframe('in_scene', timestamp, False) + self.set_keyframe("in_scene", timestamp, False) def _handle_scene_event(self, timestamp): should_be_in_scene = self.is_inside_scene_at(timestamp) @@ -385,22 +394,22 @@ def set_interpolator(self, attrib, interpolator, is_evaluator=False, **kwargs): >>> pos_fun = lambda t: np.array([np.sin(t), np.cos(t), 0]) >>> Animation.set_interpolator('position', pos_fun) - """ + """ attrib_data = self._get_attribute_data(attrib) - keyframes = attrib_data.get('keyframes', {}) - interp_data = attrib_data.get('interpolator', {}) + keyframes = attrib_data.get("keyframes", {}) + interp_data = attrib_data.get("interpolator", {}) if is_evaluator: - interp_data['base'] = None - interp_data['func'] = interpolator + interp_data["base"] = None + interp_data["func"] = interpolator else: - interp_data['base'] = interpolator - interp_data['args'] = kwargs + interp_data["base"] = interpolator + interp_data["args"] = kwargs # Maintain interpolator base in case new keyframes are added. if len(keyframes) == 0: return new_interp = interpolator(keyframes, **kwargs) - interp_data['func'] = new_interp + interp_data["func"] = new_interp # update motion path self.update_duration() @@ -426,7 +435,7 @@ def is_interpolatable(self, attrib): """ data = self._data - return bool(data.get(attrib, {}).get('interpolator', {}).get('func')) + return bool(data.get(attrib, {}).get("interpolator", {}).get("func")) def set_position_interpolator(self, interpolator, is_evaluator=False, **kwargs): """Set the position interpolator. @@ -449,9 +458,10 @@ def set_position_interpolator(self, interpolator, is_evaluator=False, **kwargs): Examples -------- >>> Animation.set_position_interpolator(spline_interpolator, degree=5) + """ self.set_interpolator( - 'position', interpolator, is_evaluator=is_evaluator, **kwargs + "position", interpolator, is_evaluator=is_evaluator, **kwargs ) def set_scale_interpolator(self, interpolator, is_evaluator=False): @@ -469,8 +479,9 @@ def set_scale_interpolator(self, interpolator, is_evaluator=False): Examples -------- >>> Animation.set_scale_interpolator(step_interpolator) + """ - self.set_interpolator('scale', interpolator, is_evaluator=is_evaluator) + self.set_interpolator("scale", interpolator, is_evaluator=is_evaluator) def set_rotation_interpolator(self, interpolator, is_evaluator=False): """Set the rotation interpolator . @@ -487,8 +498,9 @@ def set_rotation_interpolator(self, interpolator, is_evaluator=False): Examples -------- >>> Animation.set_rotation_interpolator(slerp) + """ - self.set_interpolator('rotation', interpolator, is_evaluator=is_evaluator) + self.set_interpolator("rotation", interpolator, is_evaluator=is_evaluator) def set_color_interpolator(self, interpolator, is_evaluator=False): """Set the color interpolator. @@ -505,8 +517,9 @@ def set_color_interpolator(self, interpolator, is_evaluator=False): Examples -------- >>> Animation.set_color_interpolator(lab_color_interpolator) + """ - self.set_interpolator('color', interpolator, is_evaluator=is_evaluator) + self.set_interpolator("color", interpolator, is_evaluator=is_evaluator) def set_opacity_interpolator(self, interpolator, is_evaluator=False): """Set the opacity interpolator. @@ -519,11 +532,13 @@ def set_opacity_interpolator(self, interpolator, is_evaluator=False): is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. + Examples -------- >>> Animation.set_opacity_interpolator(step_interpolator) + """ - self.set_interpolator('opacity', interpolator, is_evaluator=is_evaluator) + self.set_interpolator("opacity", interpolator, is_evaluator=is_evaluator) def get_value(self, attrib, timestamp): """Return the value of an attribute at any given timestamp. @@ -534,9 +549,10 @@ def get_value(self, attrib, timestamp): The attribute name. timestamp: float The timestamp to interpolate at. + """ value = ( - self._data.get(attrib, {}).get('interpolator', {}).get('func')(timestamp) + self._data.get(attrib, {}).get("interpolator", {}).get("func")(timestamp) ) return value @@ -547,11 +563,12 @@ def get_current_value(self, attrib): ---------- attrib: str The attribute name. + """ return ( self._data.get(attrib) - .get('interpolator') - .get('func')(self._timeline.current_timestamp) + .get("interpolator") + .get("func")(self._timeline.current_timestamp) ) def set_position(self, timestamp, position, **kwargs): @@ -581,8 +598,9 @@ def set_position(self, timestamp, position, **kwargs): ----- `in_cp` and `out_cp` only needed when using the cubic bezier interpolation method. + """ - self.set_keyframe('position', timestamp, position, **kwargs) + self.set_keyframe("position", timestamp, position, **kwargs) def set_position_keyframes(self, keyframes): """Set a dict of position keyframes at once. @@ -598,8 +616,9 @@ def set_position_keyframes(self, keyframes): -------- >>> pos_keyframes = {1, np.array([0, 0, 0]), 3, np.array([50, 6, 6])} >>> Animation.set_position_keyframes(pos_keyframes) + """ - self.set_keyframes('position', keyframes) + self.set_keyframes("position", keyframes) def set_rotation(self, timestamp, rotation, **kwargs): """Set a rotation keyframe at a specific timestamp. @@ -616,22 +635,24 @@ def set_rotation(self, timestamp, rotation, **kwargs): ----- Euler rotations are executed by rotating first around Z then around X, and finally around Y. + """ no_components = len(np.array(rotation).flatten()) if no_components == 4: - self.set_keyframe('rotation', timestamp, rotation, **kwargs) + self.set_keyframe("rotation", timestamp, rotation, **kwargs) elif no_components == 3: # user is expected to set rotation order by default as setting # orientation of a `vtkActor` ordered as z->x->y. rotation = np.asarray(rotation, dtype=float) rotation = transform.Rotation.from_euler( - 'zxy', rotation[[2, 0, 1]], degrees=True + "zxy", rotation[[2, 0, 1]], degrees=True ).as_quat() - self.set_keyframe('rotation', timestamp, rotation, **kwargs) + self.set_keyframe("rotation", timestamp, rotation, **kwargs) else: warn( - f'Keyframe with {no_components} components is not a ' - f'valid rotation data. Skipped!' + f"Keyframe with {no_components} components is not a " + f"valid rotation data. Skipped!", + stacklevel=2, ) def set_rotation_as_vector(self, timestamp, vector, **kwargs): @@ -643,9 +664,10 @@ def set_rotation_as_vector(self, timestamp, vector, **kwargs): Timestamp of the keyframe vector: ndarray, shape(1, 3) Directional vector that describes the rotation. + """ quat = transform.Rotation.from_rotvec(vector).as_quat() - self.set_keyframe('rotation', timestamp, quat, **kwargs) + self.set_keyframe("rotation", timestamp, quat, **kwargs) def set_scale(self, timestamp, scalar, **kwargs): """Set a scale keyframe at a specific timestamp. @@ -656,8 +678,9 @@ def set_scale(self, timestamp, scalar, **kwargs): Timestamp of the keyframe scalar: ndarray, shape(1, 3) Scale keyframe value associated with the timestamp. + """ - self.set_keyframe('scale', timestamp, scalar, **kwargs) + self.set_keyframe("scale", timestamp, scalar, **kwargs) def set_scale_keyframes(self, keyframes): """Set a dict of scale keyframes at once. @@ -673,8 +696,9 @@ def set_scale_keyframes(self, keyframes): -------- >>> scale_keyframes = {1, np.array([1, 1, 1]), 3, np.array([2, 2, 3])} >>> Animation.set_scale_keyframes(scale_keyframes) + """ - self.set_keyframes('scale', keyframes) + self.set_keyframes("scale", keyframes) def set_color(self, timestamp, color, **kwargs): """Set color keyframe at a specific timestamp. @@ -685,8 +709,9 @@ def set_color(self, timestamp, color, **kwargs): Timestamp of the keyframe color: ndarray, shape(1, 3) Color keyframe value associated with the timestamp. + """ - self.set_keyframe('color', timestamp, color, **kwargs) + self.set_keyframe("color", timestamp, color, **kwargs) def set_color_keyframes(self, keyframes): """Set a dict of color keyframes at once. @@ -702,8 +727,9 @@ def set_color_keyframes(self, keyframes): -------- >>> color_keyframes = {1, np.array([1, 0, 1]), 3, np.array([0, 0, 1])} >>> Animation.set_color_keyframes(color_keyframes) + """ - self.set_keyframes('color', keyframes) + self.set_keyframes("color", keyframes) def set_opacity(self, timestamp, opacity, **kwargs): """Set opacity keyframe at a specific timestamp. @@ -714,8 +740,9 @@ def set_opacity(self, timestamp, opacity, **kwargs): Timestamp of the keyframe opacity: ndarray, shape(1, 3) Opacity keyframe value associated with the timestamp. + """ - self.set_keyframe('opacity', timestamp, opacity, **kwargs) + self.set_keyframe("opacity", timestamp, opacity, **kwargs) def set_opacity_keyframes(self, keyframes): """Set a dict of opacity keyframes at once. @@ -735,8 +762,9 @@ def set_opacity_keyframes(self, keyframes): -------- >>> opacity = {1, np.array([1, 1, 1]), 3, np.array([2, 2, 3])} >>> Animation.set_scale_keyframes(opacity) + """ - self.set_keyframes('opacity', keyframes) + self.set_keyframes("opacity", keyframes) def get_position(self, t): """Return the interpolated position. @@ -750,8 +778,9 @@ def get_position(self, t): ------- ndarray(1, 3): The interpolated position. + """ - return self.get_value('position', t) + return self.get_value("position", t) def get_rotation(self, t, as_quat=False): """Return the interpolated rotation. @@ -767,18 +796,19 @@ def get_rotation(self, t, as_quat=False): ------- ndarray(1, 3): The interpolated rotation as Euler degrees by default. + """ - rot = self.get_value('rotation', t) + rot = self.get_value("rotation", t) if len(rot) == 4: if as_quat: return rot r = transform.Rotation.from_quat(rot) - degrees = r.as_euler('zxy', degrees=True)[[1, 2, 0]] + degrees = r.as_euler("zxy", degrees=True)[[1, 2, 0]] return degrees elif not as_quat: return rot return transform.Rotation.from_euler( - 'zxy', rot[[2, 0, 1]], degrees=True + "zxy", rot[[2, 0, 1]], degrees=True ).as_quat() def get_scale(self, t): @@ -793,8 +823,9 @@ def get_scale(self, t): ------- ndarray(1, 3): The interpolated scale. + """ - return self.get_value('scale', t) + return self.get_value("scale", t) def get_color(self, t): """Return the interpolated color. @@ -808,8 +839,9 @@ def get_color(self, t): ------- ndarray(1, 3): The interpolated color. + """ - return self.get_value('color', t) + return self.get_value("color", t) def get_opacity(self, t): """Return the opacity value. @@ -823,8 +855,9 @@ def get_opacity(self, t): ------- ndarray(1, 1): The interpolated opacity. + """ - return self.get_value('opacity', t) + return self.get_value("opacity", t) def add(self, item): """Add an item to the Animation. @@ -835,6 +868,7 @@ def add(self, item): ---------- item: Animation, vtkActor, list[Animation], or list[vtkActor] Actor/s to be animated by the Animation. + """ if isinstance(item, list): for a in item: @@ -854,6 +888,7 @@ def add_child_animation(self, animation): ---------- animation: Animation or list[Animation] Animation/s to be added. + """ if isinstance(animation, list): for a in animation: @@ -875,6 +910,7 @@ def add_actor(self, actor, static=False): Indicated whether the actor should be animated and controlled by the animation or just a static actor that gets added to the scene along with the Animation. + """ if isinstance(actor, list): for a in actor: @@ -906,7 +942,6 @@ def timeline(self, timeline): Parameters ---------- - timeline: Timeline The Timeline handling the current animation, None, if there is no associated Timeline. @@ -937,6 +972,7 @@ def parent_animation(self, parent_animation): ---------- parent_animation: Animation The parent Animation instance. + """ self._parent_animation = parent_animation @@ -948,17 +984,19 @@ def actors(self): ------- list: List of actors controlled by the Animation. + """ return self._actors @property - def child_animations(self) -> 'list[Animation]': + def child_animations(self) -> "list[Animation]": """Return a list of child Animations. Returns ------- list: List of child Animations of this Animation. + """ return self._animations @@ -971,6 +1009,7 @@ def add_static_actor(self, actor): ---------- actor: vtkActor or list(vtkActor) Static actor/s. + """ self.add_actor(actor, static=True) @@ -982,6 +1021,7 @@ def static_actors(self): ------- list: List of static actors. + """ return self._static_actors @@ -996,6 +1036,7 @@ def remove_actor(self, actor): ---------- actor: vtkActor Actor to be removed from the Animation. + """ self._actors.remove(actor) @@ -1011,6 +1052,7 @@ def loop(self): ------- bool Whether the animation in loop mode (True) or play one mode (False). + """ return self._loop @@ -1023,6 +1065,7 @@ def loop(self, loop): loop: bool The loop condition to be set. (True) to loop the animation, and (False) to play only once. + """ self._loop = loop @@ -1043,12 +1086,13 @@ def add_update_callback(self, callback, prop=None): ----- If no attribute name was provided, current time of the animation will be provided instead of current value for the callback. + """ if prop is None: self._general_callbacks.append(callback) return attrib = self._get_attribute_data(prop) - attrib.get('callbacks', []).append(callback) + attrib.get("callbacks", []).append(callback) def update_animation(self, time=None): """Update the animation. @@ -1061,6 +1105,7 @@ def update_animation(self, time=None): time: float or int, optional, default: None The time to update animation at. If None, the animation will play without adding it to a Timeline. + """ has_handler = True if time is None: @@ -1085,26 +1130,26 @@ def update_animation(self, time=None): # actors properties if in_scene: - if self.is_interpolatable('position'): + if self.is_interpolatable("position"): position = self.get_position(time) self._transform.Translate(*position) - if self.is_interpolatable('opacity'): + if self.is_interpolatable("opacity"): opacity = self.get_opacity(time) [act.GetProperty().SetOpacity(opacity) for act in self.actors] - if self.is_interpolatable('rotation'): + if self.is_interpolatable("rotation"): x, y, z = self.get_rotation(time) # Rotate in the same order as VTK defaults. self._transform.RotateZ(z) self._transform.RotateX(x) self._transform.RotateY(y) - if self.is_interpolatable('scale'): + if self.is_interpolatable("scale"): scale = self.get_scale(time) self._transform.Scale(*scale) - if self.is_interpolatable('color'): + if self.is_interpolatable("color"): color = self.get_color(time) for act in self.actors: act.vcolors[:] = color * 255 @@ -1114,7 +1159,7 @@ def update_animation(self, time=None): [act.SetUserTransform(self._transform) for act in self.actors] for attrib in self._data: - callbacks = self._data.get(attrib, {}).get('callbacks', []) + callbacks = self._data.get(attrib, {}).get("callbacks", []) if callbacks != [] and self.is_interpolatable(attrib): value = self.get_value(attrib, time) [cbk(value) for cbk in callbacks] @@ -1169,6 +1214,7 @@ class CameraAnimation(Animation): motion_path_res : int, default: None the number of line segments used to visualizer the animation's motion path (visualizing position). + """ def __init__(self, camera=None, length=None, loop=True, motion_path_res=None): @@ -1195,7 +1241,6 @@ def camera(self, camera: Camera): Parameters ---------- - camera: Camera The camera to be animated @@ -1211,8 +1256,9 @@ def set_focal(self, timestamp, position, **kwargs): The time to interpolate opacity at. position: ndarray, shape(1, 3) The camera position + """ - self.set_keyframe('focal', timestamp, position, **kwargs) + self.set_keyframe("focal", timestamp, position, **kwargs) def set_view_up(self, timestamp, direction, **kwargs): """Set the camera view-up direction keyframe. @@ -1223,8 +1269,9 @@ def set_view_up(self, timestamp, direction, **kwargs): The time to interpolate at. direction: ndarray, shape(1, 3) The camera view-up direction + """ - self.set_keyframe('view_up', timestamp, direction, **kwargs) + self.set_keyframe("view_up", timestamp, direction, **kwargs) def set_focal_keyframes(self, keyframes): """Set multiple camera focal position keyframes at once. @@ -1241,8 +1288,9 @@ def set_focal_keyframes(self, keyframes): -------- >>> focal_pos = {0, np.array([1, 1, 1]), 3, np.array([20, 0, 0])} >>> CameraAnimation.set_focal_keyframes(focal_pos) + """ - self.set_keyframes('focal', keyframes) + self.set_keyframes("focal", keyframes) def set_view_up_keyframes(self, keyframes): """Set multiple camera view up direction keyframes. @@ -1259,8 +1307,9 @@ def set_view_up_keyframes(self, keyframes): -------- >>> view_ups = {0, np.array([1, 0, 0]), 3, np.array([0, 1, 0])} >>> CameraAnimation.set_view_up_keyframes(view_ups) + """ - self.set_keyframes('view_up', keyframes) + self.set_keyframes("view_up", keyframes) def get_focal(self, t): """Return the interpolated camera's focal position. @@ -1279,8 +1328,9 @@ def get_focal(self, t): ----- The returned focal position does not necessarily reflect the current camera's focal position, but the expected one. + """ - return self.get_value('focal', t) + return self.get_value("focal", t) def get_view_up(self, t): """Return the interpolated camera's view-up directional vector. @@ -1299,8 +1349,9 @@ def get_view_up(self, t): ----- The returned focal position does not necessarily reflect the actual camera view up directional vector, but the expected one. + """ - return self.get_value('view_up', t) + return self.get_value("view_up", t) def set_focal_interpolator(self, interpolator, is_evaluator=False): """Set the camera focal position interpolator. @@ -1313,8 +1364,9 @@ def set_focal_interpolator(self, interpolator, is_evaluator=False): is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. + """ - self.set_interpolator('focal', interpolator, is_evaluator=is_evaluator) + self.set_interpolator("focal", interpolator, is_evaluator=is_evaluator) def set_view_up_interpolator(self, interpolator, is_evaluator=False): """Set the camera up-view vector animation interpolator. @@ -1327,8 +1379,9 @@ def set_view_up_interpolator(self, interpolator, is_evaluator=False): is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. + """ - self.set_interpolator('view_up', interpolator, is_evaluator=is_evaluator) + self.set_interpolator("view_up", interpolator, is_evaluator=is_evaluator) def update_animation(self, time=None): """Update the camera animation. @@ -1338,6 +1391,7 @@ def update_animation(self, time=None): time: float or int, optional, default: None The time to update the camera animation at. If None, the animation will play. + """ if self._camera is None: if self._scene: @@ -1345,7 +1399,7 @@ def update_animation(self, time=None): self.update_animation(time) return else: - if self.is_interpolatable('rotation'): + if self.is_interpolatable("rotation"): pos = self._camera.GetPosition() translation = np.identity(4) translation[:3, 3] = pos @@ -1356,18 +1410,18 @@ def update_animation(self, time=None): rot = translation @ rot @ np.linalg.inv(translation) self._camera.SetModelTransformMatrix(rot.flatten()) - if self.is_interpolatable('position'): + if self.is_interpolatable("position"): cam_pos = self.get_position(time) self._camera.SetPosition(cam_pos) - if self.is_interpolatable('focal'): + if self.is_interpolatable("focal"): cam_foc = self.get_focal(time) self._camera.SetFocalPoint(cam_foc) - if self.is_interpolatable('view_up'): + if self.is_interpolatable("view_up"): cam_up = self.get_view_up(time) self._camera.SetViewUp(cam_up) - elif not self.is_interpolatable('view_up'): + elif not self.is_interpolatable("view_up"): # to preserve up-view as default after user interaction self._camera.SetViewUp(0, 1, 0) if self._scene: diff --git a/fury/animation/helpers.py b/fury/animation/helpers.py index 51f82bc55..03746f386 100644 --- a/fury/animation/helpers.py +++ b/fury/animation/helpers.py @@ -18,8 +18,8 @@ def get_previous_timestamp(timestamps, current_time, include_last=False): ------- float or int The previous timestamp - """ + """ for timestamp in timestamps[::-1] if include_last else timestamps[-2::-1]: if timestamp <= current_time: return timestamp @@ -43,6 +43,7 @@ def get_next_timestamp(timestamps, current_time, include_first=False): ------- float or int The next timestamp + """ for timestamp in timestamps[:] if include_first else timestamps[1:]: if timestamp > current_time: @@ -62,6 +63,7 @@ def get_timestamps_from_keyframes(keyframes): ------- ndarray Array of sorted timestamps extracted from the keyframes. + """ return np.sort(np.array(list(keyframes)), axis=None) @@ -78,9 +80,10 @@ def get_values_from_keyframes(keyframes): ------- ndarray Array of sorted values extracted from the keyframes. + """ return np.asarray( - [keyframes.get(t, {}).get('value', None) for t in sorted(keyframes.keys())] + [keyframes.get(t, {}).get("value", None) for t in sorted(keyframes.keys())] ) @@ -100,6 +103,7 @@ def get_time_tau(t, t0, t1): ------- float The time tau + """ return 0 if t <= t0 else 1 if t >= t1 else (t - t0) / (t1 - t0) @@ -124,6 +128,7 @@ def lerp(v0, v1, t0, t1, t): ------- ndarray or float The interpolated value + """ if t0 == t1: return v0 @@ -144,5 +149,6 @@ def euclidean_distances(points): ------- list A List of euclidean distance between each consecutive points or values. + """ return [np.linalg.norm(x - y) for x, y in zip(points, points[1:])] diff --git a/fury/animation/interpolator.py b/fury/animation/interpolator.py index bd081b0e4..a7228a7dd 100644 --- a/fury/animation/interpolator.py +++ b/fury/animation/interpolator.py @@ -36,9 +36,9 @@ def spline_interpolator(keyframes, degree): """ if len(keyframes) < (degree + 1): raise ValueError( - f'Minimum {degree + 1} ' - f'keyframes must be set in order to use ' - f'{degree}-degree spline' + f"Minimum {degree + 1} " + f"keyframes must be set in order to use " + f"{degree}-degree spline" ) timestamps = get_timestamps_from_keyframes(keyframes) @@ -102,13 +102,13 @@ def step_interpolator(keyframes): function The interpolation function that take time and return interpolated value at that time. - """ + """ timestamps = get_timestamps_from_keyframes(keyframes) def interpolate(t): previous_t = get_previous_timestamp(timestamps, t, include_last=True) - return keyframes.get(previous_t).get('value') + return keyframes.get(previous_t).get("value") return interpolate @@ -129,6 +129,7 @@ def linear_interpolator(keyframes): function The interpolation function that take time and return interpolated value at that time. + """ timestamps = get_timestamps_from_keyframes(keyframes) is_single = len(keyframes) == 1 @@ -136,11 +137,11 @@ def linear_interpolator(keyframes): def interpolate(t): if is_single: t = timestamps[0] - return keyframes.get(t).get('value') + return keyframes.get(t).get("value") t0 = get_previous_timestamp(timestamps, t) t1 = get_next_timestamp(timestamps, t) - p0 = keyframes.get(t0).get('value') - p1 = keyframes.get(t1).get('value') + p0 = keyframes.get(t0).get("value") + p1 = keyframes.get(t1).get("value") return lerp(p0, p1, t0, t1, t) return interpolate @@ -167,28 +168,28 @@ def cubic_bezier_interpolator(keyframes): ----- If no control points are set in the keyframes, The cubic Bézier interpolator will almost behave as a linear interpolator. - """ + """ timestamps = get_timestamps_from_keyframes(keyframes) for ts in timestamps: # keyframe at timestamp kf_ts = keyframes.get(ts) - if kf_ts.get('in_cp') is None: - kf_ts['in_cp'] = kf_ts.get('value') + if kf_ts.get("in_cp") is None: + kf_ts["in_cp"] = kf_ts.get("value") - if kf_ts.get('out_cp') is None: - kf_ts['out_cp'] = kf_ts.get('value') + if kf_ts.get("out_cp") is None: + kf_ts["out_cp"] = kf_ts.get("value") def interpolate(t): t0 = get_previous_timestamp(timestamps, t) t1 = get_next_timestamp(timestamps, t) k0 = keyframes.get(t0) k1 = keyframes.get(t1) - p0 = k0.get('value') - p1 = k0.get('out_cp') - p2 = k1.get('in_cp') - p3 = k1.get('value') + p0 = k0.get("value") + p1 = k0.get("out_cp") + p2 = k1.get("in_cp") + p3 = k1.get("value") dt = get_time_tau(t, t0, t1) val = ( (1 - dt) ** 3 * p0 @@ -226,7 +227,7 @@ def slerp(keyframes): quat_rots = [] for ts in timestamps: - quat_rots.append(keyframes.get(ts).get('value')) + quat_rots.append(keyframes.get(ts).get("value")) rotations = transform.Rotation.from_quat(quat_rots) # if only one keyframe specified, linear interpolator is used. if len(timestamps) == 1: @@ -271,12 +272,12 @@ def color_interpolator(keyframes, rgb2space, space2rgb): space_keyframes = {} is_single = len(keyframes) == 1 for ts, keyframe in keyframes.items(): - space_keyframes[ts] = rgb2space(keyframe.get('value')) + space_keyframes[ts] = rgb2space(keyframe.get("value")) def interpolate(t): if is_single: t = timestamps[0] - return keyframes.get(t).get('value') + return keyframes.get(t).get("value") t0 = get_previous_timestamp(timestamps, t) t1 = get_next_timestamp(timestamps, t) c0 = space_keyframes.get(t0) @@ -293,6 +294,7 @@ def hsv_color_interpolator(keyframes): See Also -------- color_interpolator + """ return color_interpolator(keyframes, rgb2hsv, hsv2rgb) @@ -303,6 +305,7 @@ def lab_color_interpolator(keyframes): See Also -------- color_interpolator + """ return color_interpolator(keyframes, rgb2lab, lab2rgb) @@ -313,6 +316,7 @@ def xyz_color_interpolator(keyframes): See Also -------- color_interpolator + """ return color_interpolator(keyframes, rgb2xyz, xyz2rgb) @@ -335,15 +339,14 @@ def tan_cubic_spline_interpolator(keyframes): value at that time. """ - timestamps = get_timestamps_from_keyframes(keyframes) for time in keyframes: data = keyframes.get(time) - value = data.get('value') - if data.get('in_tangent') is None: - data['in_tangent'] = np.zeros_like(value) - if data.get('in_tangent') is None: - data['in_tangent'] = np.zeros_like(value) + value = data.get("value") + if data.get("in_tangent") is None: + data["in_tangent"] = np.zeros_like(value) + if data.get("in_tangent") is None: + data["in_tangent"] = np.zeros_like(value) def interpolate(t): t0 = get_previous_timestamp(timestamps, t) @@ -353,10 +356,10 @@ def interpolate(t): time_delta = t1 - t0 - p0 = keyframes.get(t0).get('value') - tan_0 = keyframes.get(t0).get('out_tangent') * time_delta - p1 = keyframes.get(t1).get('value') - tan_1 = keyframes.get(t1).get('in_tangent') * time_delta + p0 = keyframes.get(t0).get("value") + tan_0 = keyframes.get(t0).get("out_tangent") * time_delta + p1 = keyframes.get(t1).get("value") + tan_1 = keyframes.get(t1).get("in_tangent") * time_delta # cubic spline equation using tangents t2 = dt * dt t3 = t2 * dt diff --git a/fury/animation/tests/fares b/fury/animation/tests/fares deleted file mode 100644 index b0bb73da9..000000000 --- a/fury/animation/tests/fares +++ /dev/null @@ -1,49 +0,0 @@ -#def myfun(x): -# if x > 0: -# return x*(myfun(x-1)) -# else: -# return 1 -#print(myfun(8)) -#ل=4 -#print(ل) -#x=lambda a,b,c:pow(b,c) -#print(x(5,6,7))# -class myclass(): - - notallow=['ahmed','omar'] - usren=0 - - def __init__(self,name,cname): - self.pname=name - self.ccname=cname - myclass.usren+=1 - def __str__(self): - return f'collage name is {self.pname} , dof = {self.ccname}' - - def print(self): - if self.pname in myclass.notallow: - print('not allowed') - else: - print(f'{self.pname} , {self.ccname}') - - def allinfo(): - print(f'the end of the class') - @classmethod - def usrenu(cls): - print(myclass.usren) - -class student(myclass): - - def __init__(self,name,cname,age): - super().__init__(name,cname) - self.age=age - - def __str__(self): - return f'your name is {self.pname} ,course name = {self.ccname}, your age is {self.age}' - - -print('\n') -s1=student('ahmed','c5',34) -print(str(s1)+"\n") -c1=myclass('edge1',2004) -print(str(c1)+"\n") diff --git a/fury/animation/tests/test_helpers.py b/fury/animation/tests/test_helpers.py index 222775e40..3b5daf4f2 100644 --- a/fury/animation/tests/test_helpers.py +++ b/fury/animation/tests/test_helpers.py @@ -7,9 +7,9 @@ def test_get_timestamps_from_keyframes(): keyframes = { - 0: {'value': np.array([0, 0, 0])}, - 1: {'value': np.array([1, 0, 0])}, - 2: {'value': np.array([2, 0, 0])}, + 0: {"value": np.array([0, 0, 0])}, + 1: {"value": np.array([1, 0, 0])}, + 2: {"value": np.array([2, 0, 0])}, } # Test `get_timestamps_from_keyframes` timestamps = helpers.get_timestamps_from_keyframes(keyframes) @@ -35,12 +35,12 @@ def test_lerp(): def test_get_values_from_keyframes(): keyframes = { - 0: {'value': np.array([0, 0, 0])}, - 1: {'value': np.array([1, 0, 0])}, - 2: {'value': np.array([2, 0, 0])}, + 0: {"value": np.array([0, 0, 0])}, + 1: {"value": np.array([1, 0, 0])}, + 2: {"value": np.array([2, 0, 0])}, } values = helpers.get_values_from_keyframes(keyframes) - npt.assert_array_equal(values, np.array([i['value'] for i in keyframes.values()])) + npt.assert_array_equal(values, np.array([i["value"] for i in keyframes.values()])) values = helpers.get_values_from_keyframes({}) npt.assert_array_equal(values, np.array([])) @@ -54,11 +54,11 @@ def test_get_next_timestamp(): for t in range(-100, 100, 1): t /= 10 next_ts = helpers.get_next_timestamp(timestamps, t) - npt.assert_(next_ts in timestamps, 'Timestamp is not valid') + npt.assert_(next_ts in timestamps, "Timestamp is not valid") ft.assert_greater_equal(next_ts, min(max(timestamps), t)) next_ts_2 = helpers.get_next_timestamp(timestamps, t, include_first=True) ft.assert_less_equal(next_ts_2, next_ts) - npt.assert_(next_ts_2 in timestamps, 'Timestamp is not valid') + npt.assert_(next_ts_2 in timestamps, "Timestamp is not valid") ts = helpers.get_next_timestamp(timestamps, 0.5, include_first=False) ft.assert_equal(ts, 2) @@ -71,11 +71,11 @@ def test_get_previous_timestamp(): for t in range(-100, 100, 1): t /= 10 previous_ts = helpers.get_previous_timestamp(timestamps, t) - npt.assert_(previous_ts in timestamps, 'Timestamp is not valid') + npt.assert_(previous_ts in timestamps, "Timestamp is not valid") ft.assert_less_equal(previous_ts, max(min(timestamps), t)) previous_ts_2 = helpers.get_previous_timestamp(timestamps, t, include_last=True) ft.assert_greater_equal(previous_ts_2, previous_ts) - npt.assert_(previous_ts_2 in timestamps, 'Timestamp is not valid') + npt.assert_(previous_ts_2 in timestamps, "Timestamp is not valid") ts = helpers.get_previous_timestamp(timestamps, 5.5, include_last=False) ft.assert_equal(ts, 5) diff --git a/fury/animation/tests/test_interpolators.py b/fury/animation/tests/test_interpolators.py index 80389d0a5..5856676d2 100644 --- a/fury/animation/tests/test_interpolators.py +++ b/fury/animation/tests/test_interpolators.py @@ -22,9 +22,9 @@ def assert_not_equal(x, y): def test_step_interpolator(): data = { - 1: {'value': np.array([1, 2, 3])}, - 2: {'value': np.array([0, 0, 0])}, - 3: {'value': np.array([5, 5, 5])}, + 1: {"value": np.array([1, 2, 3])}, + 2: {"value": np.array([0, 0, 0])}, + 3: {"value": np.array([5, 5, 5])}, } interpolator = step_interpolator(data) @@ -44,33 +44,33 @@ def test_step_interpolator(): npt.assert_equal(interpolator(999), pos_final) for t in range(-10, 40, 1): - npt.assert_equal(interpolator(t / 10).shape, data.get(1).get('value').shape) + npt.assert_equal(interpolator(t / 10).shape, data.get(1).get("value").shape) - for ts, pos in data.items(): - npt.assert_equal(interpolator(ts), data.get(ts).get('value')) + for ts in data.keys(): + npt.assert_equal(interpolator(ts), data.get(ts).get("value")) interp = step_interpolator({}) try: interp(1) - raise "This shouldn't work since no keyframes were provided!" + raise Exception("This shouldn't work since no keyframes were provided!") except IndexError: ... - data = {1: {'value': np.array([1, 2, 3])}} + data = {1: {"value": np.array([1, 2, 3])}} interp = step_interpolator(data) npt.assert_equal(interp(-100), np.array([1, 2, 3])) npt.assert_equal(interp(100), np.array([1, 2, 3])) - data = {1: {'value': None}} + data = {1: {"value": None}} interp = step_interpolator(data) npt.assert_equal(interp(-100), None) def test_linear_interpolator(): data = { - 1: {'value': np.array([1, 2, 3])}, - 2: {'value': np.array([0, 0, 0])}, - 3: {'value': np.array([5, 5, 5])}, + 1: {"value": np.array([1, 2, 3])}, + 2: {"value": np.array([0, 0, 0])}, + 3: {"value": np.array([5, 5, 5])}, } interpolator = linear_interpolator(data) @@ -79,13 +79,13 @@ def test_linear_interpolator(): pos2 = interpolator(2.1) assert_not_equal(pos1, pos2) - npt.assert_equal(pos1, data.get(2).get('value')) + npt.assert_equal(pos1, data.get(2).get("value")) - for ts, pos in data.items(): - npt.assert_equal(interpolator(ts), data.get(ts).get('value')) + for ts in data.keys(): + npt.assert_equal(interpolator(ts), data.get(ts).get("value")) for t in range(-10, 40, 1): - npt.assert_equal(interpolator(t / 10).shape, data.get(1).get('value').shape) + npt.assert_equal(interpolator(t / 10).shape, data.get(1).get("value").shape) pos_initial = interpolator(1) pos_final = interpolator(3) @@ -97,43 +97,43 @@ def test_linear_interpolator(): interp = linear_interpolator({}) try: interp(1) - raise "This shouldn't work since no keyframes were provided!" + raise Exception("This shouldn't work since no keyframes were provided!") except IndexError: ... - data = {1: {'value': np.array([1, 2, 3])}} + data = {1: {"value": np.array([1, 2, 3])}} interp = linear_interpolator(data) npt.assert_equal(interp(-100), np.array([1, 2, 3])) npt.assert_equal(interp(100), np.array([1, 2, 3])) - data = {1: {'value': None}, 2: {'value': np.array([1, 1, 1])}} + data = {1: {"value": None}, 2: {"value": np.array([1, 1, 1])}} interp = linear_interpolator(data) try: interp(1) - raise "This shouldn't work since invalid keyframes were provided!" + raise Exception("This shouldn't work since invalid keyframes were provided!") except TypeError: ... def test_cubic_spline_interpolator(): data = { - 1: {'value': np.array([1, 2, 3])}, - 2: {'value': np.array([0, 0, 0])}, - 3: {'value': np.array([5, 5, 5])}, - 4: {'value': np.array([7, 7, 7])}, + 1: {"value": np.array([1, 2, 3])}, + 2: {"value": np.array([0, 0, 0])}, + 3: {"value": np.array([5, 5, 5])}, + 4: {"value": np.array([7, 7, 7])}, } interpolator = cubic_spline_interpolator(data) pos1 = interpolator(2) - npt.assert_almost_equal(pos1, data.get(2).get('value')) + npt.assert_almost_equal(pos1, data.get(2).get("value")) - for ts, pos in data.items(): - npt.assert_almost_equal(interpolator(ts), data.get(ts).get('value')) + for ts in data.keys(): + npt.assert_almost_equal(interpolator(ts), data.get(ts).get("value")) for t in range(-10, 40, 1): npt.assert_almost_equal( - interpolator(t / 10).shape, data.get(1).get('value').shape + interpolator(t / 10).shape, data.get(1).get("value").shape ) pos_initial = interpolator(1) @@ -145,15 +145,15 @@ def test_cubic_spline_interpolator(): try: cubic_spline_interpolator({}) - raise 'At least 4 keyframes must be provided!' + raise Exception("At least 4 keyframes must be provided!") except ValueError: ... data = { - 1: {'value': None}, - 2: {'value': np.array([1, 1, 1])}, - 3: {'value': None}, - 4: {'value': None}, + 1: {"value": None}, + 2: {"value": np.array([1, 1, 1])}, + 3: {"value": None}, + 4: {"value": None}, } # Interpolator should not work with invalid data! @@ -162,11 +162,11 @@ def test_cubic_spline_interpolator(): def test_cubic_bezier_interpolator(): - data_1 = {1: {'value': np.array([-2, 0, 0])}, 2: {'value': np.array([18, 0, 0])}} + data_1 = {1: {"value": np.array([-2, 0, 0])}, 2: {"value": np.array([18, 0, 0])}} data_2 = { - 1: {'value': np.array([-2, 0, 0]), 'out_cp': np.array([-15, 6, 0])}, - 2: {'value': np.array([18, 0, 0]), 'in_cp': np.array([27, 18, 0])}, + 1: {"value": np.array([-2, 0, 0]), "out_cp": np.array([-15, 6, 0])}, + 2: {"value": np.array([18, 0, 0]), "in_cp": np.array([27, 18, 0])}, } # with control points @@ -186,14 +186,14 @@ def test_cubic_bezier_interpolator(): npt.assert_equal(interp_1(1), interp_2(1)) npt.assert_equal(interp_1(2), interp_2(2)) - for ts, pos in data_1.items(): - expected = data_1.get(ts).get('value') + for ts in data_1.keys(): + expected = data_1.get(ts).get("value") npt.assert_almost_equal(interp_1(ts), expected) npt.assert_almost_equal(interp_2(ts), expected) for t in range(-10, 40, 1): npt.assert_almost_equal( - interp_1(t / 10).shape, data_1.get(1).get('value').shape + interp_1(t / 10).shape, data_1.get(1).get("value").shape ) pos_initial = interp_1(1) @@ -210,26 +210,26 @@ def test_cubic_bezier_interpolator(): try: interp(1) - raise "This shouldn't work since no keyframes were provided!" + raise Exception("This shouldn't work since no keyframes were provided!") except IndexError: ... - data = {1: {'value': np.array([1, 2, 3])}} + data = {1: {"value": np.array([1, 2, 3])}} interp = cubic_bezier_interpolator(data) npt.assert_equal(interp(-10), np.array([1, 2, 3])) npt.assert_equal(interp(100), np.array([1, 2, 3])) - data = {1: {'value': None}, 2: {'value': np.array([1, 1, 1])}} + data = {1: {"value": None}, 2: {"value": np.array([1, 1, 1])}} interp = cubic_bezier_interpolator(data) try: interp(1) - raise "This shouldn't work since no keyframes were provided!" + raise Exception("This shouldn't work since no keyframes were provided!") except TypeError: ... def test_n_spline_interpolator(): - data = {i: {'value': np.random.random(3) * 10} for i in range(10)} + data = {i: {"value": np.random.random(3) * 10} for i in range(10)} interps = [spline_interpolator(data, degree=i) for i in range(1, 6)] @@ -237,20 +237,20 @@ def test_n_spline_interpolator(): npt.assert_equal(i(-999), i(0)) npt.assert_equal(i(999), i(10)) for t in range(10): - npt.assert_almost_equal(i(t), data.get(t).get('value')) + npt.assert_almost_equal(i(t), data.get(t).get("value")) for t in range(-100, 100, 1): i(t / 10) try: spline_interpolator({}, 5) - raise 'At least 6 keyframes must be provided!' + raise Exception("At least 6 keyframes must be provided!") except ValueError: ... data = { - 1: {'value': None}, - 2: {'value': np.array([1, 1, 1])}, - 3: {'value': None}, - 4: {'value': None}, + 1: {"value": None}, + 2: {"value": np.array([1, 1, 1])}, + 3: {"value": None}, + 4: {"value": None}, } # Interpolator should not work with invalid data! @@ -259,7 +259,7 @@ def test_n_spline_interpolator(): def test_color_interpolators(): - data = {1: {'value': np.array([1, 0.5, 0])}, 2: {'value': np.array([0.5, 0, 1])}} + data = {1: {"value": np.array([1, 0.5, 0])}, 2: {"value": np.array([0.5, 0, 1])}} color_interps = [ hsv_color_interpolator(data), @@ -289,22 +289,22 @@ def test_color_interpolators(): interp = interpolator({}) try: interp(1) - raise "This shouldn't work since no keyframes were provided!" + raise Exception("This shouldn't work since no keyframes were provided!") except IndexError: ... - data = {1: {'value': np.array([1, 2, 3])}} + data = {1: {"value": np.array([1, 2, 3])}} interp = interpolator(data) npt.assert_equal(interp(-10), np.array([1, 2, 3])) npt.assert_equal(interp(10), np.array([1, 2, 3])) - data = {1: {'value': None}, 2: {'value': np.array([1, 1, 1])}} + data = {1: {"value": None}, 2: {"value": np.array([1, 1, 1])}} try: interpolator(data) - msg = "This shouldn't work since invalid keyframes " + msg = "This shouldn't work since invalid keyframes " msg += "were provided! and hence can't be converted to" msg += "targeted color space." - raise msg + raise msg except ( TypeError, AttributeError, @@ -314,8 +314,8 @@ def test_color_interpolators(): def test_slerp(): data = { - 1: {'value': np.array([0, 0, 0, 1])}, - 2: {'value': np.array([0, 0.7071068, 0, 0.7071068])}, + 1: {"value": np.array([0, 0, 0, 1])}, + 2: {"value": np.array([0, 0.7071068, 0, 0.7071068])}, } interp_slerp = slerp(data) @@ -335,19 +335,19 @@ def test_slerp(): try: interp = slerp({}) interp(1) - raise "This shouldn't work since no keyframes were provided!" + raise Exception("This shouldn't work since no keyframes were provided!") except ValueError: ... - data = {1: {'value': np.array([1, 2, 3, 1])}} + data = {1: {"value": np.array([1, 2, 3, 1])}} interp = slerp(data) npt.assert_equal(interp(-100), np.array([1, 2, 3, 1])) npt.assert_equal(interp(100), np.array([1, 2, 3, 1])) - data = {1: {'value': None}, 2: {'value': np.array([1, 1, 1])}} + data = {1: {"value": None}, 2: {"value": np.array([1, 1, 1])}} try: interp = slerp(data) interp(1) - raise "This shouldn't work since invalid keyframes were provided!" + raise Exception("This shouldn't work since invalid keyframes were provided!") except ValueError: ... diff --git a/fury/animation/tests/test_timeline.py b/fury/animation/tests/test_timeline.py index 41350eccf..79a3301be 100644 --- a/fury/animation/tests/test_timeline.py +++ b/fury/animation/tests/test_timeline.py @@ -3,10 +3,9 @@ import numpy as np import numpy.testing as npt -import fury.testing as ft from fury.animation import Animation, Timeline +import fury.testing as ft from fury.ui import PlaybackPanel -from fury.window import ShowManager, Scene def assert_not_equal(x, y): diff --git a/fury/animation/timeline.py b/fury/animation/timeline.py index 1bfaa49bb..7156a809d 100644 --- a/fury/animation/timeline.py +++ b/fury/animation/timeline.py @@ -1,11 +1,13 @@ import os +from time import perf_counter + +from PIL import Image import numpy as np -from fury.lib import WindowToImageFilter, RenderWindow, numpy_support + from fury import window from fury.animation.animation import Animation +from fury.lib import RenderWindow, WindowToImageFilter, numpy_support from fury.ui.elements import PlaybackPanel -from PIL import Image -from time import perf_counter class Timeline: @@ -28,10 +30,10 @@ class Timeline: its length from the animations that it controls automatically. loop : bool, optional Whether loop playing the timeline or play once. + """ def __init__(self, animations=None, playback_panel=False, loop=True, length=None): - self._scene = None self.playback_panel = None self._current_timestamp = 0 @@ -69,6 +71,7 @@ def update_duration(self): ------- float The duration of the Timeline. + """ if self._length is not None: self._duration = self._length @@ -88,6 +91,7 @@ def duration(self): ------- float The duration of the Timeline. + """ return self._duration @@ -186,6 +190,7 @@ def playing(self): ------- bool True if the Timeline is playing. + """ return self._playing @@ -211,7 +216,6 @@ def paused(self): True if the Timeline is paused. """ - return not self.playing and self._current_timestamp is not None @property @@ -222,6 +226,7 @@ def speed(self): ------- float The speed of the timeline's playback. + """ return self._speed @@ -251,6 +256,7 @@ def loop(self): bool Whether the playback is in loop mode (True) or play one mode (False). + """ return self._loop @@ -263,6 +269,7 @@ def loop(self, loop): loop: bool The loop condition to be set. (True) to loop the playback, and (False) to play only once. + """ self._loop = loop @@ -273,16 +280,25 @@ def has_playback_panel(self): Returns ------- bool: 'True' if the `Timeline` has a playback panel. otherwise, 'False' + """ return self.playback_panel is not None - def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), - order_transparent=True, multi_samples=8, - max_peels=4, show_panel=False): + def record( + self, + fname=None, + fps=30, + speed=1.0, + size=(900, 768), + order_transparent=True, + multi_samples=8, + max_peels=4, + show_panel=False, + ): """Record the animation Parameters - ----------- + ---------- fname : str, optional The file name. Save a GIF file if name ends with '.gif', or mp4 video if name ends with'.mp4'. @@ -313,19 +329,20 @@ def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), Notes ----- It's recommended to use 50 or 30 FPS while recording to a GIF file. - """ + """ ext = os.path.splitext(fname)[-1] - mp4 = ext == '.mp4' + mp4 = ext == ".mp4" if mp4: try: import cv2 - except ImportError: - raise ImportError('OpenCV must be installed in order to ' - 'save as MP4 video.') - fourcc = cv2.VideoWriter.fourcc(*'mp4v') + except ImportError as err: + raise ImportError( + "OpenCV must be installed in order to " "save as MP4 video." + ) from err + fourcc = cv2.VideoWriter.fourcc(*"mp4v") out = cv2.VideoWriter(fname, fourcc, fps, size) duration = self.duration @@ -347,8 +364,7 @@ def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), render_window.SetSize(*size) if order_transparent: - window.antialiasing(scene, render_window, multi_samples, max_peels, - 0) + window.antialiasing(scene, render_window, multi_samples, max_peels, 0) render_window = RenderWindow() render_window.SetOffScreenRendering(1) @@ -360,7 +376,7 @@ def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), window_to_image_filter = WindowToImageFilter() - print('Recording...') + print("Recording...") while t < duration: self.seek(t) render_window.Render() @@ -371,8 +387,7 @@ def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), h, w, _ = vtk_image.GetDimensions() vtk_array = vtk_image.GetPointData().GetScalars() components = vtk_array.GetNumberOfComponents() - snap = numpy_support.vtk_to_numpy(vtk_array).reshape(w, h, - components) + snap = numpy_support.vtk_to_numpy(vtk_array).reshape(w, h, components) corrected_snap = np.flipud(snap) if mp4: @@ -384,7 +399,7 @@ def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), t += step - print('Saving...') + print("Saving...") if fname is None: return frames @@ -392,8 +407,13 @@ def record(self, fname=None, fps=30, speed=1.0, size=(900, 768), if mp4: out.release() else: - frames[0].save(fname, append_images=frames[1:], loop=0, - duration=1000 / fps, save_all=True) + frames[0].save( + fname, + append_images=frames[1:], + loop=0, + duration=1000 / fps, + save_all=True, + ) if _hide_panel: self.playback_panel.show() @@ -407,6 +427,7 @@ def add_animation(self, animation): ---------- animation: Animation or list[Animation] or tuple[Animation] Animation/s to be added. + """ if isinstance(animation, (list, tuple)): [self.add_animation(anim) for anim in animation] @@ -415,16 +436,17 @@ def add_animation(self, animation): self._animations.append(animation) self.update_duration() else: - raise TypeError('Expected an Animation, a list or a tuple.') + raise TypeError("Expected an Animation, a list or a tuple.") @property - def animations(self) -> 'list[Animation]': + def animations(self) -> "list[Animation]": """Return a list of Animations. Returns ------- list: List of Animations controlled by the timeline. + """ return self._animations diff --git a/fury/colormap.py b/fury/colormap.py index 6855d1d10..932191227 100644 --- a/fury/colormap.py +++ b/fury/colormap.py @@ -11,7 +11,7 @@ # Allow import, but disable doctests if we don't have matplotlib from fury.optpkg import optional_package -cm, have_matplotlib, _ = optional_package('matplotlib.cm') +cm, have_matplotlib, _ = optional_package("matplotlib.cm") def colormap_lookup_table( @@ -60,7 +60,7 @@ def ss(na, nd): def boys2rgb(v): - """boys 2 rgb cool colormap + """Boys 2 rgb cool colormap Maps a given field of undirected lines (line field) to rgb colors using Boy's Surface immersion of the real projective @@ -79,24 +79,23 @@ def boys2rgb(v): the FURY Team. Thank you Cagatay for putting this online. Parameters - ------------ + ---------- v : array, shape (N, 3) of unit vectors (e.g., principal eigenvectors of tensor data) representing one of the two directions of the undirected lines in a line field. Returns - --------- + ------- c : array, shape (N, 3) matrix of rgb colors corresponding to the vectors given in V. Examples - ---------- - + -------- >>> from fury import colormap >>> v = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) >>> c = colormap.boys2rgb(v) - """ + """ if v.ndim == 1: x = v[0] y = v[1] @@ -194,7 +193,6 @@ def boys2rgb(v): trl_z = -2.1899 if v.ndim == 2: - N = len(x) C = np.zeros((N, 3)) @@ -203,7 +201,6 @@ def boys2rgb(v): C[:, 2] = 0.9 * np.abs(((Z - trl_z) / w_z)) + 0.05 if v.ndim == 1: - C = np.zeros((3,)) C[0] = 0.9 * np.abs(((X - trl_x) / w_x)) + 0.05 C[1] = 0.9 * np.abs(((Y - trl_y) / w_y)) + 0.05 @@ -239,13 +236,13 @@ def orient2rgb(v): orient = np.abs(np.divide(v, orientn, where=orientn != 0)) else: raise IOError( - 'Wrong vector dimension, It should be an array' ' with a shape (N, 3)' + "Wrong vector dimension, It should be an array" " with a shape (N, 3)" ) return orient -def line_colors(streamlines, cmap='rgb_standard'): +def line_colors(streamlines, cmap="rgb_standard"): """Create colors for streamlines to be used in actor.line. Parameters @@ -258,12 +255,12 @@ def line_colors(streamlines, cmap='rgb_standard'): colors : ndarray """ - if cmap == 'rgb_standard': + if cmap == "rgb_standard": col_list = [ orient2rgb(streamline[-1] - streamline[0]) for streamline in streamlines ] - if cmap == 'boys_standard': + if cmap == "boys_standard": col_list = [ boys2rgb(streamline[-1] - streamline[0]) for streamline in streamlines ] @@ -271,23 +268,24 @@ def line_colors(streamlines, cmap='rgb_standard'): return np.vstack(col_list) -lowercase_cm_name = {'blues': 'Blues', 'accent': 'Accent'} +lowercase_cm_name = {"blues": "Blues", "accent": "Accent"} dipy_cmaps = None def get_cmap(name): """Make a callable, similar to maptlotlib.pyplot.get_cmap.""" - if name.lower() == 'accent': + if name.lower() == "accent": warn( - 'The `Accent` colormap is deprecated as of version' - + ' 0.2 of Fury and will be removed in a future ' - + 'version. Please use another colormap', + "The `Accent` colormap is deprecated as of version" + + " 0.2 of Fury and will be removed in a future " + + "version. Please use another colormap", PendingDeprecationWarning, + stacklevel=2, ) global dipy_cmaps if dipy_cmaps is None: - filename = pjoin(DATA_DIR, 'dipy_colormaps.json') + filename = pjoin(DATA_DIR, "dipy_colormaps.json") with open(filename) as f: dipy_cmaps = json.load(f) @@ -298,7 +296,7 @@ def get_cmap(name): def simple_cmap(v): """Emulate matplotlib colormap callable.""" rgba = np.ones((len(v), 4)) - for i, color in enumerate(('red', 'green', 'blue')): + for i, color in enumerate(("red", "green", "blue")): x, y0, _ = zip(*desc[color]) # Matplotlib allows more complex colormaps, but for users who do # not have Matplotlib fury makes a few simple colormaps available. @@ -310,7 +308,7 @@ def simple_cmap(v): return simple_cmap -def create_colormap(v, name='plasma', auto=True): +def create_colormap(v, name="plasma", auto=True): """Create colors from a specific colormap and return it as an array of shape (N,3) where every row gives the corresponding r,g,b value. The colormaps we use are similar with those of matplotlib. @@ -336,17 +334,17 @@ def create_colormap(v, name='plasma', auto=True): """ if not have_matplotlib: - msg = 'You do not have Matplotlib installed. Some colormaps' - msg += ' might not work for you. Consider downloading Matplotlib.' - warn(msg) + msg = "You do not have Matplotlib installed. Some colormaps" + msg += " might not work for you. Consider downloading Matplotlib." + warn(msg, stacklevel=2) - if name.lower() == 'jet': - msg = 'Jet is a popular colormap but can often be misleading' - msg += 'Use instead plasma, viridis, hot or inferno.' - warn(msg, PendingDeprecationWarning) + if name.lower() == "jet": + msg = "Jet is a popular colormap but can often be misleading" + msg += "Use instead plasma, viridis, hot or inferno." + warn(msg, PendingDeprecationWarning, stacklevel=2) if v.ndim > 1: - msg = 'This function works only with 1d arrays. Use ravel()' + msg = "This function works only with 1d arrays. Use ravel()" raise ValueError(msg) if auto: @@ -359,7 +357,7 @@ def create_colormap(v, name='plasma', auto=True): colormap = getattr(cm, newname) if have_matplotlib else get_cmap(newname) if colormap is None: - e_s = 'Colormap {} is not yet implemented '.format(name) + e_s = "Colormap {} is not yet implemented ".format(name) raise ValueError(e_s) rgba = colormap(v) @@ -513,7 +511,7 @@ def _lab2rgb(lab): return _xyz2rgb(tmp) -def distinguishable_colormap(bg=(0, 0, 0), exclude=[], nb_colors=None): +def distinguishable_colormap(bg=(0, 0, 0), exclude=None, nb_colors=None): """Generate colors that are maximally perceptually distinct. This function generates a set of colors which are distinguishable @@ -564,6 +562,9 @@ def distinguishable_colormap(bg=(0, 0, 0), exclude=[], nb_colors=None): original implementation (v1.2), 14 Dec 2010 (Updated 07 Feb 2011). """ + if exclude is None: + exclude = [] + NB_DIVISIONS = 30 # This constant come from the original code. # Generate a sizable number of RGB triples. This represents our space of @@ -626,18 +627,19 @@ def hex_to_rgb(color): >>> c = colormap.hex_to_rgb(color) """ - if color[0] == '#': + if color[0] == "#": color = color[1:] - r = int('0x' + color[0:2], 0) / 255 - g = int('0x' + color[2:4], 0) / 255 - b = int('0x' + color[4:6], 0) / 255 + r = int("0x" + color[0:2], 0) / 255 + g = int("0x" + color[2:4], 0) / 255 + b = int("0x" + color[4:6], 0) / 255 return np.array([r, g, b]) def rgb2hsv(rgb): """RGB to HSV color space conversion. + Parameters ---------- rgb : (..., 3, ...) array_like @@ -655,6 +657,7 @@ def rgb2hsv(rgb): it can be found at: https://github.com/scikit-image/scikit-image/blob/main/skimage/color/colorconv.py This implementation might have been modified. + """ input_is_one_pixel = rgb.ndim == 1 if input_is_one_pixel: @@ -668,7 +671,7 @@ def rgb2hsv(rgb): # -- S channel delta = rgb.ptp(-1) # Ignore warning for zero divided by zero - old_settings = np.seterr(invalid='ignore') + old_settings = np.seterr(invalid="ignore") out_s = delta / out_v out_s[delta == 0.0] = 0.0 @@ -725,7 +728,6 @@ def hsv2rgb(hsv): This implementation might have been modified. """ - hi = np.floor(hsv[..., 0] * 6) f = hsv[..., 0] * 6 - hi p = hsv[..., 2] * (1 - hsv[..., 1]) @@ -826,42 +828,42 @@ def rgb2xyz(rgb): # it can be found at: # https://github.com/scikit-image/scikit-image/blob/main/skimage/color/colorconv.py illuminants = { - 'A': { - '2': (1.098466069456375, 1, 0.3558228003436005), - '10': (1.111420406956693, 1, 0.3519978321919493), - 'R': (1.098466069456375, 1, 0.3558228003436005), + "A": { + "2": (1.098466069456375, 1, 0.3558228003436005), + "10": (1.111420406956693, 1, 0.3519978321919493), + "R": (1.098466069456375, 1, 0.3558228003436005), }, - 'B': { - '2': (0.9909274480248003, 1, 0.8531327322886154), - '10': (0.9917777147717607, 1, 0.8434930535866175), - 'R': (0.9909274480248003, 1, 0.8531327322886154), + "B": { + "2": (0.9909274480248003, 1, 0.8531327322886154), + "10": (0.9917777147717607, 1, 0.8434930535866175), + "R": (0.9909274480248003, 1, 0.8531327322886154), }, - 'C': { - '2': (0.980705971659919, 1, 1.1822494939271255), - '10': (0.9728569189782166, 1, 1.1614480488951577), - 'R': (0.980705971659919, 1, 1.1822494939271255), + "C": { + "2": (0.980705971659919, 1, 1.1822494939271255), + "10": (0.9728569189782166, 1, 1.1614480488951577), + "R": (0.980705971659919, 1, 1.1822494939271255), }, - 'D50': { - '2': (0.9642119944211994, 1, 0.8251882845188288), - '10': (0.9672062750333777, 1, 0.8142801513128616), - 'R': (0.9639501491621826, 1, 0.8241280285499208), + "D50": { + "2": (0.9642119944211994, 1, 0.8251882845188288), + "10": (0.9672062750333777, 1, 0.8142801513128616), + "R": (0.9639501491621826, 1, 0.8241280285499208), }, - 'D55': { - '2': (0.956797052643698, 1, 0.9214805860173273), - '10': (0.9579665682254781, 1, 0.9092525159847462), - 'R': (0.9565317453467969, 1, 0.9202554587037198), + "D55": { + "2": (0.956797052643698, 1, 0.9214805860173273), + "10": (0.9579665682254781, 1, 0.9092525159847462), + "R": (0.9565317453467969, 1, 0.9202554587037198), }, - 'D65': { - '2': (0.95047, 1.0, 1.08883), - '10': (0.94809667673716, 1, 1.0730513595166162), - 'R': (0.9532057125493769, 1, 1.0853843816469158), + "D65": { + "2": (0.95047, 1.0, 1.08883), + "10": (0.94809667673716, 1, 1.0730513595166162), + "R": (0.9532057125493769, 1, 1.0853843816469158), }, - 'D75': { - '2': (0.9497220898840717, 1, 1.226393520724154), - '10': (0.9441713925645873, 1, 1.2064272211720228), - 'R': (0.9497220898840717, 1, 1.226393520724154), + "D75": { + "2": (0.9497220898840717, 1, 1.226393520724154), + "10": (0.9441713925645873, 1, 1.2064272211720228), + "R": (0.9497220898840717, 1, 1.226393520724154), }, - 'E': {'2': (1.0, 1.0, 1.0), '10': (1.0, 1.0, 1.0), 'R': (1.0, 1.0, 1.0)}, + "E": {"2": (1.0, 1.0, 1.0), "10": (1.0, 1.0, 1.0), "R": (1.0, 1.0, 1.0)}, } @@ -875,6 +877,7 @@ def get_xyz_coords(illuminant, observer): observer : {"2", "10", "R"}, optional One of: 2-degree observer, 10-degree observer, or 'R' observer as in R function grDevices::convertColor. + Returns ------- out : array @@ -893,14 +896,14 @@ def get_xyz_coords(illuminant, observer): observer = observer.upper() try: return np.asarray(illuminants[illuminant][observer], dtype=float) - except KeyError: + except KeyError as err: raise ValueError( - f'Unknown illuminant/observer combination ' - f'(`{illuminant}`, `{observer}`)' - ) + f"Unknown illuminant/observer combination " + f"(`{illuminant}`, `{observer}`)" + ) from err -def xyz2lab(xyz, illuminant='D65', observer='2'): +def xyz2lab(xyz, illuminant="D65", observer="2"): """XYZ to CIE-LAB color space conversion. Parameters @@ -927,7 +930,6 @@ def xyz2lab(xyz, illuminant='D65', observer='2'): This implementation might have been modified. """ - xyz_ref_white = get_xyz_coords(illuminant, observer) # scale by CIE XYZ tristimulus values of the reference white point @@ -948,7 +950,7 @@ def xyz2lab(xyz, illuminant='D65', observer='2'): return np.concatenate([x[..., np.newaxis] for x in [L, a, b]], axis=-1) -def lab2xyz(lab, illuminant='D65', observer='2'): +def lab2xyz(lab, illuminant="D65", observer="2"): """CIE-LAB to XYZcolor space conversion. Parameters @@ -960,6 +962,7 @@ def lab2xyz(lab, illuminant='D65', observer='2'): The name of the illuminant (the function is NOT case-sensitive). observer : {"2", "10", "R"}, optional The aperture angle of the observer. + Returns ------- out : (..., 3, ...) ndarray @@ -981,7 +984,7 @@ def lab2xyz(lab, illuminant='D65', observer='2'): if np.any(z < 0): invalid = np.nonzero(z < 0) warn( - 'Color data out of range: Z < 0 in %s pixels' % invalid[0].size, + "Color data out of range: Z < 0 in %s pixels" % invalid[0].size, stacklevel=2, ) z[invalid] = 0 @@ -998,7 +1001,7 @@ def lab2xyz(lab, illuminant='D65', observer='2'): return out -def rgb2lab(rgb, illuminant='D65', observer='2'): +def rgb2lab(rgb, illuminant="D65", observer="2"): """Conversion from the sRGB color space (IEC 61966-2-1:1999) to the CIE Lab colorspace under the given illuminant and observer. @@ -1028,7 +1031,7 @@ def rgb2lab(rgb, illuminant='D65', observer='2'): return xyz2lab(rgb2xyz(rgb), illuminant, observer) -def lab2rgb(lab, illuminant='D65', observer='2'): +def lab2rgb(lab, illuminant="D65", observer="2"): """Lab to RGB color space conversion. Parameters diff --git a/fury/convert.py b/fury/convert.py index 86d391e0a..25ad4d1a8 100644 --- a/fury/convert.py +++ b/fury/convert.py @@ -43,18 +43,18 @@ def matplotlib_figure_to_numpy( """ if fname is None: with TemporaryDirectory() as tmpdir: - fname = os.path.join(tmpdir, 'tmp.png') + fname = os.path.join(tmpdir, "tmp.png") fig.savefig( fname, dpi=dpi, transparent=transparent, - bbox_inches='tight', + bbox_inches="tight", pad_inches=0, ) arr = load_image(fname) else: fig.savefig( - fname, dpi=dpi, transparent=transparent, bbox_inches='tight', pad_inches=0 + fname, dpi=dpi, transparent=transparent, bbox_inches="tight", pad_inches=0 ) arr = load_image(fname) diff --git a/fury/data/__init__.py b/fury/data/__init__.py index 29ca1c49b..bea342c0e 100644 --- a/fury/data/__init__.py +++ b/fury/data/__init__.py @@ -1,24 +1,42 @@ """Read or fetch test or example data.""" -from os.path import join as pjoin, dirname +from os.path import dirname, join as pjoin -from fury.data.fetcher import (fetch_viz_cubemaps, read_viz_cubemap, - fetch_viz_icons, fetch_viz_new_icons, - read_viz_icons, fetch_viz_wiki_nw, - fetch_viz_textures, read_viz_textures, - fetch_viz_models, read_viz_models, - fetch_viz_dmri, read_viz_dmri, - fetch_gltf, read_viz_gltf, - list_gltf_sample_models) +from fury.data.fetcher import ( + fetch_gltf, + fetch_viz_cubemaps, + fetch_viz_dmri, + fetch_viz_icons, + fetch_viz_models, + fetch_viz_new_icons, + fetch_viz_textures, + fetch_viz_wiki_nw, + list_gltf_sample_models, + read_viz_cubemap, + read_viz_dmri, + read_viz_gltf, + read_viz_icons, + read_viz_models, + read_viz_textures, +) +DATA_DIR = pjoin(dirname(__file__), "files") -DATA_DIR = pjoin(dirname(__file__), 'files') - -__all__ = ['DATA_DIR', 'fetch_viz_cubemaps', 'read_viz_cubemap', - 'fetch_viz_icons', 'fetch_viz_new_icons', - 'read_viz_icons', 'fetch_viz_textures', - 'read_viz_textures', 'fetch_viz_wiki_nw', - 'fetch_viz_models', 'read_viz_models', - 'fetch_viz_dmri', 'read_viz_dmri', - 'fetch_gltf', 'read_viz_gltf', - 'list_gltf_sample_models'] +__all__ = [ + "DATA_DIR", + "fetch_viz_cubemaps", + "read_viz_cubemap", + "fetch_viz_icons", + "fetch_viz_new_icons", + "read_viz_icons", + "fetch_viz_textures", + "read_viz_textures", + "fetch_viz_wiki_nw", + "fetch_viz_models", + "read_viz_models", + "fetch_viz_dmri", + "read_viz_dmri", + "fetch_gltf", + "read_viz_gltf", + "list_gltf_sample_models", +] diff --git a/fury/data/fetcher.py b/fury/data/fetcher.py index 330ae82e1..c3150d310 100644 --- a/fury/data/fetcher.py +++ b/fury/data/fetcher.py @@ -1,53 +1,51 @@ """Fetcher based on dipy.""" -import os -import sys +import asyncio import contextlib -import warnings -import json - -from os.path import join as pjoin, dirname from hashlib import sha256 +import json +import os +from os.path import dirname, join as pjoin +import platform from shutil import copyfileobj - +import sys import tarfile +from urllib.request import urlopen +import warnings import zipfile -from urllib.request import urlopen -import asyncio import aiohttp -import platform # Set a user-writeable file-system location to put files: -if 'FURY_HOME' in os.environ: - fury_home = os.environ['FURY_HOME'] +if "FURY_HOME" in os.environ: + fury_home = os.environ["FURY_HOME"] else: - fury_home = pjoin(os.path.expanduser('~'), '.fury') + fury_home = pjoin(os.path.expanduser("~"), ".fury") # The URL to the University of Washington Researchworks repository: -UW_RW_URL = \ - "https://digital.lib.washington.edu/researchworks/bitstream/handle/" +UW_RW_URL = "https://digital.lib.washington.edu/researchworks/bitstream/handle/" -NEW_ICONS_DATA_URL = \ - "https://raw.githubusercontent.com/fury-gl/fury-data/master/icons/new_icons/" +NEW_ICONS_DATA_URL = ( + "https://raw.githubusercontent.com/fury-gl/fury-data/master/icons/" "new_icons/" +) -CUBEMAP_DATA_URL = \ +CUBEMAP_DATA_URL = ( "https://raw.githubusercontent.com/fury-gl/fury-data/master/cubemaps/" +) -FURY_DATA_URL = \ - "https://raw.githubusercontent.com/fury-gl/fury-data/master/examples/" +FURY_DATA_URL = "https://raw.githubusercontent.com/fury-gl/fury-data/master/examples/" -MODEL_DATA_URL = \ - "https://raw.githubusercontent.com/fury-gl/fury-data/master/models/" +MODEL_DATA_URL = "https://raw.githubusercontent.com/fury-gl/fury-data/master/models/" -TEXTURE_DATA_URL = \ +TEXTURE_DATA_URL = ( "https://raw.githubusercontent.com/fury-gl/fury-data/master/textures/" +) -DMRI_DATA_URL = \ - "https://raw.githubusercontent.com/fury-gl/fury-data/master/dmri/" +DMRI_DATA_URL = "https://raw.githubusercontent.com/fury-gl/fury-data/master/dmri/" -GLTF_DATA_URL = \ +GLTF_DATA_URL = ( "https://api.github.com/repos/KhronosGroup/glTF-Sample-Models/contents/2.0/" # noqa +) class FetcherError(Exception): @@ -61,10 +59,10 @@ def update_progressbar(progress, total_length): """ # Try to set the bar_length according to the console size try: - if os.name == 'nt': + if os.name == "nt": bar_length = 20 else: - columns = os.popen('tput cols', 'r').read() + columns = os.popen("tput cols", "r").read() bar_length = int(columns) - 46 if bar_length < 1: bar_length = 20 @@ -74,7 +72,8 @@ def update_progressbar(progress, total_length): block = int(round(bar_length * progress)) size_string = "{0:.2f} MB".format(float(total_length) / (1024 * 1024)) text = "\rDownload Progress: [{0}] {1:.2f}% of {2}\n".format( - "#" * block + "-" * (bar_length - block), progress * 100, size_string) + "#" * block + "-" * (bar_length - block), progress * 100, size_string + ) sys.stdout.write(text) sys.stdout.flush() @@ -93,8 +92,8 @@ def copyfileobj_withprogress(fsrc, fdst, total_length, length=16 * 1024): def _already_there_msg(folder): """Print a message indicating that dataset is already in place.""" - msg = 'Dataset is already in place. If you want to fetch it again ' - msg += 'please first remove the folder %s ' % folder + msg = "Dataset is already in place. If you want to fetch it again " + msg += "please first remove the folder %s " % folder print(msg) @@ -113,8 +112,8 @@ def _get_file_sha(filename): """ sha256_data = sha256() - with open(filename, 'rb') as f: - for chunk in iter(lambda: f.read(256*sha256_data.block_size), b''): + with open(filename, "rb") as f: + for chunk in iter(lambda: f.read(256 * sha256_data.block_size), b""): sha256_data.update(chunk) return sha256_data.hexdigest() @@ -150,11 +149,11 @@ def check_sha(filename, stored_sha256=None): def _get_file_data(fname, url): with contextlib.closing(urlopen(url)) as opener: try: - response_size = opener.headers['content-length'] + response_size = opener.headers["content-length"] except KeyError: response_size = None - with open(fname, 'wb') as data: + with open(fname, "wb") as data: if response_size is None: copyfileobj(opener, data) else: @@ -183,20 +182,20 @@ def fetch_data(files, folder, data_size=None): Raises if the sha checksum of the file does not match the expected value. The downloaded file is not deleted when this error is raised. + """ if not os.path.exists(folder): print("Creating new folder %s" % (folder)) os.makedirs(folder) if data_size is not None: - print('Data size is approximately %s' % data_size) + print("Data size is approximately %s" % data_size) all_skip = True for f in files: url, sha = files[f] fullpath = pjoin(folder, f) - if os.path.exists(fullpath) and \ - (_get_file_sha(fullpath) == sha.lower()): + if os.path.exists(fullpath) and (_get_file_sha(fullpath) == sha.lower()): continue all_skip = False print('Downloading "%s" to %s' % (f, folder)) @@ -208,9 +207,18 @@ def fetch_data(files, folder, data_size=None): print("Files successfully downloaded to %s" % (folder)) -def _make_fetcher(name, folder, baseurl, remote_fnames, local_fnames, - sha_list=None, doc="", data_size=None, msg=None, - unzip=False): +def _make_fetcher( + name, + folder, + baseurl, + remote_fnames, + local_fnames, + sha_list=None, + doc="", + data_size=None, + msg=None, + unzip=False, +): """Create a new fetcher. Parameters @@ -248,11 +256,11 @@ def _make_fetcher(name, folder, baseurl, remote_fnames, local_fnames, inputs """ + def fetcher(): files = {} - for i, (f, n), in enumerate(zip(remote_fnames, local_fnames)): - files[n] = (baseurl + f, sha_list[i] if - sha_list is not None else None) + for i, (f, n) in enumerate(zip(remote_fnames, local_fnames)): + files[n] = (baseurl + f, sha_list[i] if sha_list is not None else None) fetch_data(files, folder, data_size) if msg is not None: @@ -260,19 +268,19 @@ def fetcher(): if unzip: for f in local_fnames: split_ext = os.path.splitext(f) - if split_ext[-1] == '.gz' or split_ext[-1] == '.bz2': - if os.path.splitext(split_ext[0])[-1] == '.tar': + if split_ext[-1] == ".gz" or split_ext[-1] == ".bz2": + if os.path.splitext(split_ext[0])[-1] == ".tar": ar = tarfile.open(pjoin(folder, f)) ar.extractall(path=folder) ar.close() else: - raise ValueError('File extension is not recognized') - elif split_ext[-1] == '.zip': - z = zipfile.ZipFile(pjoin(folder, f), 'r') + raise ValueError("File extension is not recognized") + elif split_ext[-1] == ".zip": + z = zipfile.ZipFile(pjoin(folder, f), "r") z.extractall(folder) z.close() else: - raise ValueError('File extension is not recognized') + raise ValueError("File extension is not recognized") return files, folder @@ -282,7 +290,7 @@ def fetcher(): async def _request(session, url): - """An asynchronous function to get the request data as json. + """Get the request data as json. Parameters ---------- @@ -295,6 +303,7 @@ async def _request(session, url): ------- response : dictionary The response of url request. + """ async with session.get(url) as response: if not response.status == 200: @@ -304,7 +313,7 @@ async def _request(session, url): async def _download(session, url, filename, size=None): - """An asynchronous function to download file from url. + """Download file from url. Parameters ---------- @@ -318,21 +327,21 @@ async def _download(session, url, filename, size=None): Length of the content in bytes """ if not os.path.exists(filename): - print(f'Downloading: {filename}') + print(f"Downloading: {filename}") async with session.get(url) as response: size = response.content_length if not size else size block = size copied = 0 - with open(filename, mode='wb') as f: + with open(filename, mode="wb") as f: async for chunk in response.content.iter_chunked(block): f.write(chunk) copied += len(chunk) - progress = float(copied)/float(size) + progress = float(copied) / float(size) update_progressbar(progress, size) async def _fetch_gltf(name, mode): - """An asynchronous function to fetch glTF samples. + """Fetch glTF samples. Parameters ---------- @@ -349,10 +358,10 @@ async def _fetch_gltf(name, mode): list of fetched all file names. folder : str Path to the fetched files. - """ + """ if name is None: - name = ['BoxTextured', 'Duck', 'CesiumMilkTruck', 'CesiumMan'] + name = ["BoxTextured", "Duck", "CesiumMilkTruck", "CesiumMan"] if isinstance(name, list): f_names = await asyncio.gather( @@ -360,26 +369,25 @@ async def _fetch_gltf(name, mode): ) return f_names else: - path = f'{name}/{mode}' - DATA_DIR = pjoin(dirname(__file__), 'files') - with open(pjoin(DATA_DIR, 'KhronosGltfSamples.json'), 'r') as f: + path = f"{name}/{mode}" + DATA_DIR = pjoin(dirname(__file__), "files") + with open(pjoin(DATA_DIR, "KhronosGltfSamples.json"), "r") as f: models = json.loads(f.read()) urls = models.get(path, None) if urls is None: - raise ValueError( - "Model name and mode combination doesn't exist") + raise ValueError("Model name and mode combination doesn't exist") path = pjoin(name, mode) - path = pjoin('glTF', path) + path = pjoin("glTF", path) folder = pjoin(fury_home, path) if not os.path.exists(folder): os.makedirs(folder) - d_urls = [file['download_url'] for file in urls] - sizes = [file['size'] for file in urls] - f_names = [url.split('/')[-1] for url in d_urls] + d_urls = [file["download_url"] for file in urls] + sizes = [file["size"] for file in urls] + f_names = [url.split("/")[-1] for url in d_urls] f_paths = [pjoin(folder, name) for name in f_names] zip_url = zip(d_urls, f_paths, sizes) @@ -391,7 +399,7 @@ async def _fetch_gltf(name, mode): return f_names, folder -def fetch_gltf(name=None, mode='glTF'): +def fetch_gltf(name=None, mode="glTF"): """Download glTF samples from Khronos Group Github. Parameters @@ -411,6 +419,7 @@ def fetch_gltf(name=None, mode='glTF'): ------- filenames : tuple tuple of feteched filenames (list) and folder (str) path. + """ if platform.system().lower() == "windows": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) @@ -422,159 +431,241 @@ def fetch_gltf(name=None, mode='glTF'): "fetch_viz_cubemaps", pjoin(fury_home, "cubemaps"), CUBEMAP_DATA_URL, - ['skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', 'skybox-px.jpg', - 'skybox-py.jpg', 'skybox-pz.jpg'], - ['skybox-nx.jpg', 'skybox-ny.jpg', 'skybox-nz.jpg', 'skybox-px.jpg', - 'skybox-py.jpg', 'skybox-pz.jpg'], - ['12B1CE6C91AA3AAF258A8A5944DF739A6C1CC76E89D4D7119D1F795A30FC1BF2', - 'E18FE2206B63D3DF2C879F5E0B9937A61D99734B6C43AC288226C58D2418D23E', - '00DDDD1B715D5877AF2A74C014FF6E47891F07435B471D213CD0673A8C47F2B2', - 'BF20ACD6817C9E7073E485BBE2D2CE56DACFF73C021C2B613BA072BA2DF2B754', - '16F0D692AF0B80E46929D8D8A7E596123C76729CC5EB7DFD1C9184B115DD143A', - 'B850B5E882889DF26BE9289D7C25BA30524B37E56BC2075B968A83197AD977F3'], - doc="Download cube map textures for fury" + [ + "skybox-nx.jpg", + "skybox-ny.jpg", + "skybox-nz.jpg", + "skybox-px.jpg", + "skybox-py.jpg", + "skybox-pz.jpg", + ], + [ + "skybox-nx.jpg", + "skybox-ny.jpg", + "skybox-nz.jpg", + "skybox-px.jpg", + "skybox-py.jpg", + "skybox-pz.jpg", + ], + [ + "12B1CE6C91AA3AAF258A8A5944DF739A6C1CC76E89D4D7119D1F795A30FC1BF2", + "E18FE2206B63D3DF2C879F5E0B9937A61D99734B6C43AC288226C58D2418D23E", + "00DDDD1B715D5877AF2A74C014FF6E47891F07435B471D213CD0673A8C47F2B2", + "BF20ACD6817C9E7073E485BBE2D2CE56DACFF73C021C2B613BA072BA2DF2B754", + "16F0D692AF0B80E46929D8D8A7E596123C76729CC5EB7DFD1C9184B115DD143A", + "B850B5E882889DF26BE9289D7C25BA30524B37E56BC2075B968A83197AD977F3", + ], + doc="Download cube map textures for fury", ) fetch_viz_icons = _make_fetcher( "fetch_viz_icons", pjoin(fury_home, "icons"), UW_RW_URL + "1773/38478/", - ['icomoon.tar.gz'], - ['icomoon.tar.gz'], - ['BC1FEEA6F58BA3601D6A0B029EB8DFC5F352E21F2A16BA41099A96AA3F5A4735'], + ["icomoon.tar.gz"], + ["icomoon.tar.gz"], + ["BC1FEEA6F58BA3601D6A0B029EB8DFC5F352E21F2A16BA41099A96AA3F5A4735"], data_size="12KB", doc="Download icons for fury", - unzip=True - ) + unzip=True, +) fetch_viz_new_icons = _make_fetcher( "fetch_viz_new_icons", pjoin(fury_home, "icons", "new_icons"), NEW_ICONS_DATA_URL, - ["circle-pressed.png", "circle.png", "delete-pressed.png", "delete.png", - "drawing-pressed.png", "drawing.png", "line-pressed.png", "line.png", - "polyline-pressed.png", "polyline.png", "quad-pressed.png", "quad.png", - "resize-pressed.png", "resize.png", "selection-pressed.png", "selection.png"], - ["circle-pressed.png", "circle.png", "delete-pressed.png", "delete.png", - "drawing-pressed.png", "drawing.png", "line-pressed.png", "line.png", - "polyline-pressed.png", "polyline.png", "quad-pressed.png", "quad.png", - "resize-pressed.png", "resize.png", "selection-pressed.png", "selection.png"], - ["CD859F244DF1BA719C65C869C3FAF6B8563ABF82F457730ADBFBD7CA72DDB7BC", - "5896BDC9FF9B3D1054134D7D9A854677CE9FA4E64F494F156BB2E3F0E863F207", - "937C46C25BC38B62021B01C97A4EE3CDE5F7C8C4A6D0DB75BF4E4CACE2AF1226", - "476E00A0A5373E1CCDA4AF8E7C9158E0AC9B46B540CE410C6EA47D97F364A0CD", - "08A914C5DC7997CB944B8C5FBB958951F80B715CFE04FF4F47A73F9D08C4B14B", - "FB2210B0393ECA8A5DD2B8F034DAE386BBB47EB95BB1CAC2A97DE807EE195ADF", - "8D1AC2BB7C5BAA34E68578DAAD85F64EF824BE7BCB828CAC18E52833D4CBF4C9", - "E6D833B6D958129E12FF0F6087282CE92CD43C6DAFCE03F185746ECCA89E42A9", - "CFF12B8DE48FC19DA5D5F0EA7FF2D23DD942D05468E19522E7C7BEB72F0FF66E", - "7AFE65EBAE0C0D0556393B979148AE15FC3E037D126CD1DA4A296F4E25F5B4AA", - "5FD43F1C2D37BF9AF05D9FC591172684AC51BA236980CD1B0795B0225B9247E2", - "A2DA0CB963401C174919E1D8028AA6F0CB260A736FD26421DB5AB08E9F3C4FDF", - "FF49DDF9DF24729F4F6345C30C88DE0A11E5B12B2F2FF28375EF9762FE5F8995", - "A2D850CDBA8F332DA9CD7B7C9459CBDA587C18AF0D3C12CA68D6E6A864EF54BB", - "54618FDC4589F0A039D531C07A110ED9BC57A256BB15A3B5429CF60E950887C3", - "CD573F5E4BF4A91A3B21F6124A95FFB3C036F926F8FEC1FD0180F5D27D8F48C0"], - doc="Download the new icons for DrawPanel" - ) + [ + "circle-pressed.png", + "circle.png", + "delete-pressed.png", + "delete.png", + "drawing-pressed.png", + "drawing.png", + "line-pressed.png", + "line.png", + "polyline-pressed.png", + "polyline.png", + "quad-pressed.png", + "quad.png", + "resize-pressed.png", + "resize.png", + "selection-pressed.png", + "selection.png", + ], + [ + "circle-pressed.png", + "circle.png", + "delete-pressed.png", + "delete.png", + "drawing-pressed.png", + "drawing.png", + "line-pressed.png", + "line.png", + "polyline-pressed.png", + "polyline.png", + "quad-pressed.png", + "quad.png", + "resize-pressed.png", + "resize.png", + "selection-pressed.png", + "selection.png", + ], + [ + "CD859F244DF1BA719C65C869C3FAF6B8563ABF82F457730ADBFBD7CA72DDB7BC", + "5896BDC9FF9B3D1054134D7D9A854677CE9FA4E64F494F156BB2E3F0E863F207", + "937C46C25BC38B62021B01C97A4EE3CDE5F7C8C4A6D0DB75BF4E4CACE2AF1226", + "476E00A0A5373E1CCDA4AF8E7C9158E0AC9B46B540CE410C6EA47D97F364A0CD", + "08A914C5DC7997CB944B8C5FBB958951F80B715CFE04FF4F47A73F9D08C4B14B", + "FB2210B0393ECA8A5DD2B8F034DAE386BBB47EB95BB1CAC2A97DE807EE195ADF", + "8D1AC2BB7C5BAA34E68578DAAD85F64EF824BE7BCB828CAC18E52833D4CBF4C9", + "E6D833B6D958129E12FF0F6087282CE92CD43C6DAFCE03F185746ECCA89E42A9", + "CFF12B8DE48FC19DA5D5F0EA7FF2D23DD942D05468E19522E7C7BEB72F0FF66E", + "7AFE65EBAE0C0D0556393B979148AE15FC3E037D126CD1DA4A296F4E25F5B4AA", + "5FD43F1C2D37BF9AF05D9FC591172684AC51BA236980CD1B0795B0225B9247E2", + "A2DA0CB963401C174919E1D8028AA6F0CB260A736FD26421DB5AB08E9F3C4FDF", + "FF49DDF9DF24729F4F6345C30C88DE0A11E5B12B2F2FF28375EF9762FE5F8995", + "A2D850CDBA8F332DA9CD7B7C9459CBDA587C18AF0D3C12CA68D6E6A864EF54BB", + "54618FDC4589F0A039D531C07A110ED9BC57A256BB15A3B5429CF60E950887C3", + "CD573F5E4BF4A91A3B21F6124A95FFB3C036F926F8FEC1FD0180F5D27D8F48C0", + ], + doc="Download the new icons for DrawPanel", +) fetch_viz_wiki_nw = _make_fetcher( "fetch_viz_wiki_nw", pjoin(fury_home, "examples", "wiki_nw"), FURY_DATA_URL, - ['wiki_categories.txt', 'wiki_edges.txt', - 'wiki_positions.txt'], - ['wiki_categories.txt', 'wiki_edges.txt', - 'wiki_positions.txt'], - ['1679241B13D2FD01209160F0C186E14AB55855478300B713D5369C12854CFF82', - '702EE8713994243C8619A29C9ECE32F95305737F583B747C307500F3EC4A6B56', - '044917A8FBD0EB980D93B6C406A577BEA416FA934E897C26C87E91C218EF4432'], + ["wiki_categories.txt", "wiki_edges.txt", "wiki_positions.txt"], + ["wiki_categories.txt", "wiki_edges.txt", "wiki_positions.txt"], + [ + "1679241B13D2FD01209160F0C186E14AB55855478300B713D5369C12854CFF82", + "702EE8713994243C8619A29C9ECE32F95305737F583B747C307500F3EC4A6B56", + "044917A8FBD0EB980D93B6C406A577BEA416FA934E897C26C87E91C218EF4432", + ], doc="Download the following wiki information" - "Interdisciplinary map of the journals", - msg=("More information about complex " - "networks can be found in this papers:" - " https://arxiv.org/abs/0711.3199") - ) + "Interdisciplinary map of the journals", + msg=( + "More information about complex " + "networks can be found in this papers:" + " https://arxiv.org/abs/0711.3199" + ), +) fetch_viz_models = _make_fetcher( "fetch_viz_models", pjoin(fury_home, "models"), MODEL_DATA_URL, - ['utah.obj', 'suzanne.obj', 'satellite_obj.obj', 'dragon.obj'], - ['utah.obj', 'suzanne.obj', 'satellite_obj.obj', 'dragon.obj'], - ['0B50F12CEDCDC27377AC702B1EE331223BECEC59593B3F00A9E06B57A9C1B7C3', - 'BB4FF4E65D65D71D53000E06D2DC7BF89B702223657C1F64748811A3A6C8D621', - '90213FAC81D89BBB59FA541643304E0D95C2D446157ACE044D46F259454C0E74', - 'A775D6160D04EAB9A4E90180104F148927CEFCCAF9F0BCD748265CB8EE86F41B'], - doc=" Download the models for shader tutorial" - ) + ["utah.obj", "suzanne.obj", "satellite_obj.obj", "dragon.obj"], + ["utah.obj", "suzanne.obj", "satellite_obj.obj", "dragon.obj"], + [ + "0B50F12CEDCDC27377AC702B1EE331223BECEC59593B3F00A9E06B57A9C1B7C3", + "BB4FF4E65D65D71D53000E06D2DC7BF89B702223657C1F64748811A3A6C8D621", + "90213FAC81D89BBB59FA541643304E0D95C2D446157ACE044D46F259454C0E74", + "A775D6160D04EAB9A4E90180104F148927CEFCCAF9F0BCD748265CB8EE86F41B", + ], + doc=" Download the models for shader tutorial", +) fetch_viz_dmri = _make_fetcher( "fetch_viz_dmri", pjoin(fury_home, "dmri"), DMRI_DATA_URL, - ['fodf.nii.gz', 'slice_evecs.nii.gz', 'slice_evals.nii.gz', - 'roi_evecs.nii.gz', 'roi_evals.nii.gz', 'whole_brain_evecs.nii.gz', - 'whole_brain_evals.nii.gz'], - ['fodf.nii.gz', 'slice_evecs.nii.gz', 'slice_evals.nii.gz', - 'roi_evecs.nii.gz', 'roi_evals.nii.gz', 'whole_brain_evecs.nii.gz', - 'whole_brain_evals.nii.gz'], - ['767ca3e4cd296e78421d83c32201b30be2d859c332210812140caac1b93d492b', - '8843ECF3224CB8E3315B7251D1E303409A17D7137D3498A8833853C4603C6CC2', - '3096B190B1146DD0EADDFECC0B4FBBD901F4933692ADD46A83F637F28B22122D', - '89E569858A897E72C852A8F05BBCE0B21C1CA726E55508087A2DA5A38C212A17', - 'F53C68CCCABF97F1326E93840A8B5CE2E767D66D692FFD955CA747FFF14EC781', - '8A894F6AB404240E65451FA6D10FB5D74E2D0BDCB2A56AD6BEA38215BF787248', - '47A73BBE68196381ED790F80F48E46AC07B699B506973FFA45A95A33023C7A77'] + [ + "fodf.nii.gz", + "slice_evecs.nii.gz", + "slice_evals.nii.gz", + "roi_evecs.nii.gz", + "roi_evals.nii.gz", + "whole_brain_evecs.nii.gz", + "whole_brain_evals.nii.gz", + ], + [ + "fodf.nii.gz", + "slice_evecs.nii.gz", + "slice_evals.nii.gz", + "roi_evecs.nii.gz", + "roi_evals.nii.gz", + "whole_brain_evecs.nii.gz", + "whole_brain_evals.nii.gz", + ], + [ + "767ca3e4cd296e78421d83c32201b30be2d859c332210812140caac1b93d492b", + "8843ECF3224CB8E3315B7251D1E303409A17D7137D3498A8833853C4603C6CC2", + "3096B190B1146DD0EADDFECC0B4FBBD901F4933692ADD46A83F637F28B22122D", + "89E569858A897E72C852A8F05BBCE0B21C1CA726E55508087A2DA5A38C212A17", + "F53C68CCCABF97F1326E93840A8B5CE2E767D66D692FFD955CA747FFF14EC781", + "8A894F6AB404240E65451FA6D10FB5D74E2D0BDCB2A56AD6BEA38215BF787248", + "47A73BBE68196381ED790F80F48E46AC07B699B506973FFA45A95A33023C7A77", + ], ) fetch_viz_textures = _make_fetcher( "fetch_viz_textures", pjoin(fury_home, "textures"), TEXTURE_DATA_URL, - ['1_earth_8k.jpg', '2_no_clouds_8k.jpg', - '5_night_8k.jpg', 'earth.ppm', - 'jupiter.jpg', 'masonry.bmp', - 'moon_8k.jpg', - '8k_mercury.jpg', '8k_venus_surface.jpg', - '8k_mars.jpg', '8k_saturn.jpg', - '8k_saturn_ring_alpha.png', - '2k_uranus.jpg', '2k_neptune.jpg', - '8k_sun.jpg', '1_earth_16k.jpg', - 'clouds.jpg'], - ['1_earth_8k.jpg', '2_no_clouds_8k.jpg', - '5_night_8k.jpg', 'earth.ppm', - 'jupiter.jpg', 'masonry.bmp', - 'moon-8k.jpg', - '8k_mercury.jpg', '8k_venus_surface.jpg', - '8k_mars.jpg', '8k_saturn.jpg', - '8k_saturn_ring_alpha.png', - '2k_uranus.jpg', '2k_neptune.jpg', - '8k_sun.jpg', '1_earth_16k.jpg', - 'clouds.jpg'], - ['0D66DC62768C43D763D3288CE67128AAED27715B11B0529162DC4117F710E26F', - '5CF740C72287AF7B3ACCF080C3951944ADCB1617083B918537D08CBD9F2C5465', - 'DF443F3E20C7724803690A350D9F6FDB36AD8EBC011B0345FB519A8B321F680A', - '34CE9AD183D7C7B11E2F682D7EBB84C803E661BE09E01ADB887175AE60C58156', - '5DF6A384E407BD0D5F18176B7DB96AAE1EEA3CFCFE570DDCE0D34B4F0E493668', - '045E30B2ABFEAE6318C2CF955040C4A37E6DE595ACE809CE6766D397C0EE205D', - '7397A6C2CE0348E148C66EBEFE078467DDB9D0370FF5E63434D0451477624839', - '5C8BD885AE3571C6BA2CD34B3446B9C6D767E314BF0EE8C1D5C147CADD388FC3', - '9BC21A50577ED8AC734CDA91058724C7A741C19427AA276224CE349351432C5B', - '4CC52149924ABC6AE507D63032F994E1D42A55CB82C09E002D1A567FF66C23EE', - '0D39A4A490C87C3EDABE00A3881A29BB3418364178C79C534FE0986E97E09853', - 'F1F826933C9FF87D64ECF0518D6256B8ED990B003722794F67E96E3D2B876AE4', - 'D15239D46F82D3EA13D2B260B5B29B2A382F42F2916DAE0694D0387B1204A09D', - 'CB42EA82709741D28B0AF44D8B283CBC6DBD0C521A7F0E1E1E010ADE00977DF6', - 'F22B1CFB306DDCE72A7E3B628668A0175B745038CE6268557CB2F7F1BDF98B9D', - '7DD1DAC926101B5D7B7F2E952E53ACF209421B5CCE57C03168BCE0AAD675998A', - '85043336E023C4C9394CFD6D48D257A5564B4F895BFCEC01C70E4898CC77F003'], - doc="Download textures for fury" - ) + [ + "1_earth_8k.jpg", + "2_no_clouds_8k.jpg", + "5_night_8k.jpg", + "earth.ppm", + "jupiter.jpg", + "masonry.bmp", + "moon_8k.jpg", + "8k_mercury.jpg", + "8k_venus_surface.jpg", + "8k_mars.jpg", + "8k_saturn.jpg", + "8k_saturn_ring_alpha.png", + "2k_uranus.jpg", + "2k_neptune.jpg", + "8k_sun.jpg", + "1_earth_16k.jpg", + "clouds.jpg", + ], + [ + "1_earth_8k.jpg", + "2_no_clouds_8k.jpg", + "5_night_8k.jpg", + "earth.ppm", + "jupiter.jpg", + "masonry.bmp", + "moon-8k.jpg", + "8k_mercury.jpg", + "8k_venus_surface.jpg", + "8k_mars.jpg", + "8k_saturn.jpg", + "8k_saturn_ring_alpha.png", + "2k_uranus.jpg", + "2k_neptune.jpg", + "8k_sun.jpg", + "1_earth_16k.jpg", + "clouds.jpg", + ], + [ + "0D66DC62768C43D763D3288CE67128AAED27715B11B0529162DC4117F710E26F", + "5CF740C72287AF7B3ACCF080C3951944ADCB1617083B918537D08CBD9F2C5465", + "DF443F3E20C7724803690A350D9F6FDB36AD8EBC011B0345FB519A8B321F680A", + "34CE9AD183D7C7B11E2F682D7EBB84C803E661BE09E01ADB887175AE60C58156", + "5DF6A384E407BD0D5F18176B7DB96AAE1EEA3CFCFE570DDCE0D34B4F0E493668", + "045E30B2ABFEAE6318C2CF955040C4A37E6DE595ACE809CE6766D397C0EE205D", + "7397A6C2CE0348E148C66EBEFE078467DDB9D0370FF5E63434D0451477624839", + "5C8BD885AE3571C6BA2CD34B3446B9C6D767E314BF0EE8C1D5C147CADD388FC3", + "9BC21A50577ED8AC734CDA91058724C7A741C19427AA276224CE349351432C5B", + "4CC52149924ABC6AE507D63032F994E1D42A55CB82C09E002D1A567FF66C23EE", + "0D39A4A490C87C3EDABE00A3881A29BB3418364178C79C534FE0986E97E09853", + "F1F826933C9FF87D64ECF0518D6256B8ED990B003722794F67E96E3D2B876AE4", + "D15239D46F82D3EA13D2B260B5B29B2A382F42F2916DAE0694D0387B1204A09D", + "CB42EA82709741D28B0AF44D8B283CBC6DBD0C521A7F0E1E1E010ADE00977DF6", + "F22B1CFB306DDCE72A7E3B628668A0175B745038CE6268557CB2F7F1BDF98B9D", + "7DD1DAC926101B5D7B7F2E952E53ACF209421B5CCE57C03168BCE0AAD675998A", + "85043336E023C4C9394CFD6D48D257A5564B4F895BFCEC01C70E4898CC77F003", + ], + doc="Download textures for fury", +) -def read_viz_cubemap(name, suffix_type=1, ext='.jpg'): +def read_viz_cubemap(name, suffix_type=1, ext=".jpg"): """Read specific cube map with specific suffix type and extension. Parameters @@ -599,24 +690,24 @@ def read_viz_cubemap(name, suffix_type=1, ext='.jpg'): # indexing number. For a correct creation and display of the skybox, # textures must be read in this order. suffix_types = { - 0: ['0', '1', '2', '3', '4', '5'], - 1: ['-px', '-nx', '-py', '-ny', '-pz', '-nz'], - 2: ['posx', 'negx', 'posy', 'negy', 'posz', 'negz'], - 3: ['right', 'left', 'top', 'bottom', 'front', 'back'] + 0: ["0", "1", "2", "3", "4", "5"], + 1: ["-px", "-nx", "-py", "-ny", "-pz", "-nz"], + 2: ["posx", "negx", "posy", "negy", "posz", "negz"], + 3: ["right", "left", "top", "bottom", "front", "back"], } if suffix_type in suffix_types: conv = suffix_types[suffix_type] else: - warnings.warn('read_viz_cubemap(): Invalid suffix_type.') + warnings.warn("read_viz_cubemap(): Invalid suffix_type.", stacklevel=2) return None cubemap_fnames = [] - folder = pjoin(fury_home, 'cubemaps') + folder = pjoin(fury_home, "cubemaps") for dir_conv in conv: cubemap_fnames.append(pjoin(folder, name + dir_conv + ext)) return cubemap_fnames -def read_viz_icons(style='icomoon', fname='infinity.png'): +def read_viz_icons(style="icomoon", fname="infinity.png"): """Read specific icon from specific style. Parameters @@ -633,12 +724,12 @@ def read_viz_icons(style='icomoon', fname='infinity.png'): Complete path of icon. """ - if not os.path.isdir(pjoin(fury_home, 'icons', style)): + if not os.path.isdir(pjoin(fury_home, "icons", style)): if style == "icomoon": fetch_viz_icons() elif style == "new_icons": fetch_viz_new_icons() - folder = pjoin(fury_home, 'icons', style) + folder = pjoin(fury_home, "icons", style) return pjoin(folder, fname) @@ -657,7 +748,7 @@ def read_viz_models(fname): Complete path of models. """ - folder = pjoin(fury_home, 'models') + folder = pjoin(fury_home, "models") return pjoin(folder, fname) @@ -676,7 +767,7 @@ def read_viz_textures(fname): Complete path of textures. """ - folder = pjoin(fury_home, 'textures') + folder = pjoin(fury_home, "textures") return pjoin(folder, fname) @@ -695,11 +786,11 @@ def read_viz_dmri(fname): Complete path of dMRI image. """ - folder = pjoin(fury_home, 'dmri') + folder = pjoin(fury_home, "dmri") return pjoin(folder, fname) -def read_viz_gltf(fname, mode='glTF'): +def read_viz_gltf(fname, mode="glTF"): """Read specific gltf sample. Parameters @@ -716,34 +807,36 @@ def read_viz_gltf(fname, mode='glTF'): ------- path : str Complete path of models. + """ - folder = pjoin(fury_home, 'glTF') + folder = pjoin(fury_home, "glTF") model = pjoin(folder, fname) sample = pjoin(model, mode) if not os.path.exists(sample): - raise ValueError(f'Model {sample} does not exists.') + raise ValueError(f"Model {sample} does not exists.") for filename in os.listdir(sample): - if filename.endswith('.gltf') or filename.endswith('.glb'): + if filename.endswith(".gltf") or filename.endswith(".glb"): return pjoin(sample, filename) def list_gltf_sample_models(): - """Returns all model name from the glTF-samples repository + """Return all model name from the glTF-samples repository. Returns ------- model_names : list Lists the name of glTF sample from https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 + """ - DATA_DIR = pjoin(dirname(__file__), 'files') - with open(pjoin(DATA_DIR, 'KhronosGltfSamples.json'), 'r') as f: + DATA_DIR = pjoin(dirname(__file__), "files") + with open(pjoin(DATA_DIR, "KhronosGltfSamples.json"), "r") as f: models = json.loads(f.read()) models = models.keys() - model_modes = [model.split('/')[0] for model in models] + model_modes = [model.split("/")[0] for model in models] model_names = [] for name in model_modes: @@ -751,13 +844,13 @@ def list_gltf_sample_models(): model_names.append(name) model_names = model_names[1:] # removing __comments__ - default_models = ['BoxTextured', 'Duck', 'CesiumMilkTruck', 'CesiumMan'] + default_models = ["BoxTextured", "Duck", "CesiumMilkTruck", "CesiumMan"] if not model_names: - print('Failed to get models list') + print("Failed to get models list") return None result = [model in model_names for model in default_models] for i, exist in enumerate(result): if not exist: - print(f'Default Model: {default_models[i]} not found!') + print(f"Default Model: {default_models[i]} not found!") return model_names diff --git a/fury/data/files/test_ui_combobox_2d.json b/fury/data/files/test_ui_combobox_2d.json index ec38facb7..31a74e353 100644 --- a/fury/data/files/test_ui_combobox_2d.json +++ b/fury/data/files/test_ui_combobox_2d.json @@ -1 +1 @@ -{"CharEvent": 0, "MouseMoveEvent": 695, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 13, "LeftButtonReleaseEvent": 13, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file +{"CharEvent": 0, "MouseMoveEvent": 296, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 16, "LeftButtonReleaseEvent": 16, "RightButtonPressEvent": 0, "RightButtonReleaseEvent": 0, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_combobox_2d.log.gz b/fury/data/files/test_ui_combobox_2d.log.gz index 95a0841d3..5c068917e 100644 Binary files a/fury/data/files/test_ui_combobox_2d.log.gz and b/fury/data/files/test_ui_combobox_2d.log.gz differ diff --git a/fury/data/files/test_ui_tab_ui_top_position.json b/fury/data/files/test_ui_tab_ui_top_position.json new file mode 100644 index 000000000..9934fd112 --- /dev/null +++ b/fury/data/files/test_ui_tab_ui_top_position.json @@ -0,0 +1 @@ +{"CharEvent": 0, "MouseMoveEvent": 1014, "KeyPressEvent": 0, "KeyReleaseEvent": 0, "LeftButtonPressEvent": 58, "LeftButtonReleaseEvent": 58, "RightButtonPressEvent": 10, "RightButtonReleaseEvent": 10, "MiddleButtonPressEvent": 0, "MiddleButtonReleaseEvent": 0} \ No newline at end of file diff --git a/fury/data/files/test_ui_tab_ui_top_position.log.gz b/fury/data/files/test_ui_tab_ui_top_position.log.gz new file mode 100644 index 000000000..e7aebcdd7 Binary files /dev/null and b/fury/data/files/test_ui_tab_ui_top_position.log.gz differ diff --git a/fury/data/tests/test_fetcher.py b/fury/data/tests/test_fetcher.py index ba9e736f3..08af70da6 100644 --- a/fury/data/tests/test_fetcher.py +++ b/fury/data/tests/test_fetcher.py @@ -1,24 +1,26 @@ +import json import os from os.path import join as pjoin -import json -from urllib.request import urlopen + import numpy.testing as npt -from fury.data import (fetch_gltf, read_viz_gltf, list_gltf_sample_models) -if 'FURY_HOME' in os.environ: - fury_home = os.environ['FURY_HOME'] +from fury.data import fetch_gltf, list_gltf_sample_models, read_viz_gltf + +if "FURY_HOME" in os.environ: + fury_home = os.environ["FURY_HOME"] else: - fury_home = pjoin(os.path.expanduser('~'), '.fury') + fury_home = pjoin(os.path.expanduser("~"), ".fury") -GLTF_DATA_URL = \ +GLTF_DATA_URL = ( "https://api.github.com/repos/KhronosGroup/glTF-Sample-Models/contents/2.0/" # noqa +) def tests_fetch_gltf(): - folder = pjoin(fury_home, 'glTF') - boxtex = pjoin(folder, 'BoxTextured') - boxtex = pjoin(boxtex, 'glTF') - models_list = ['BoxTextured', 'Box'] + folder = pjoin(fury_home, "glTF") + boxtex = pjoin(folder, "BoxTextured") + boxtex = pjoin(boxtex, "glTF") + models_list = ["BoxTextured", "Box"] if os.path.exists(boxtex): for path in os.listdir(boxtex): os.remove(pjoin(boxtex, path)) @@ -29,53 +31,53 @@ def tests_fetch_gltf(): results = [model in list_gltf for model in models_list] npt.assert_equal(results, [True, True]) - npt.assert_raises(ValueError, fetch_gltf, ['duck']) - npt.assert_raises(ValueError, fetch_gltf, ['Duck'], 'GLTF') + npt.assert_raises(ValueError, fetch_gltf, ["duck"]) + npt.assert_raises(ValueError, fetch_gltf, ["Duck"], "GLTF") fetch_gltf() list_gltf = os.listdir(folder) - default_list = ['BoxTextured', 'Duck', 'CesiumMilkTruck', 'CesiumMan'] + default_list = ["BoxTextured", "Duck", "CesiumMilkTruck", "CesiumMan"] results = [model in list_gltf for model in default_list] npt.assert_equal(results, [True, True, True, True]) items = os.listdir(boxtex) npt.assert_array_equal(len(items), 3) - filenames, path = fetch_gltf('Box', 'glTF-Binary') + filenames, path = fetch_gltf("Box", "glTF-Binary") npt.assert_equal(len(filenames), 1) npt.assert_equal(os.listdir(path), filenames) - gltf = pjoin(boxtex, 'BoxTextured.gltf') - with open(gltf, 'r') as f: + gltf = pjoin(boxtex, "BoxTextured.gltf") + with open(gltf, "r") as f: gltf = json.loads(f.read()) - validate_gltf = gltf.get('asset') - npt.assert_equal(validate_gltf['version'], str(2.0)) + validate_gltf = gltf.get("asset") + npt.assert_equal(validate_gltf["version"], str(2.0)) def test_list_gltf_sample_models(): fetch_names = list_gltf_sample_models() - default_list = ['BoxTextured', 'Duck', 'CesiumMilkTruck', 'CesiumMan'] + default_list = ["BoxTextured", "Duck", "CesiumMilkTruck", "CesiumMan"] result = [model in fetch_names for model in default_list] npt.assert_equal(result, [True, True, True, True]) def test_read_viz_gltf(): - gltf_dir = pjoin(fury_home, 'glTF') - filenames, path = fetch_gltf('Box', 'glTF-Binary') - filename = read_viz_gltf('Box', 'glTF-Binary') + gltf_dir = pjoin(fury_home, "glTF") + filenames, path = fetch_gltf("Box", "glTF-Binary") + filename = read_viz_gltf("Box", "glTF-Binary") npt.assert_equal(filename, pjoin(path, filenames[0])) - npt.assert_raises(ValueError, read_viz_gltf, 'FURY', 'glTF') + npt.assert_raises(ValueError, read_viz_gltf, "FURY", "glTF") - box_gltf = pjoin(gltf_dir, 'Box') + box_gltf = pjoin(gltf_dir, "Box") for path in os.listdir(box_gltf): mode = pjoin(box_gltf, path) for file in os.listdir(mode): os.remove(pjoin(mode, file)) os.rmdir(mode) - npt.assert_raises(ValueError, read_viz_gltf, 'Box') + npt.assert_raises(ValueError, read_viz_gltf, "Box") - filenames, path = fetch_gltf('Box') - out_path = read_viz_gltf('Box').split(os.sep) + filenames, path = fetch_gltf("Box") + out_path = read_viz_gltf("Box").split(os.sep) mode = out_path[-2:][0] - npt.assert_equal(mode, 'glTF') + npt.assert_equal(mode, "glTF") diff --git a/fury/decorators.py b/fury/decorators.py index a0085dbbb..4cb1ee2cf 100644 --- a/fury/decorators.py +++ b/fury/decorators.py @@ -1,13 +1,14 @@ """Decorators for FURY tests.""" + import platform import re import sys -skip_linux = is_linux = platform.system().lower() == 'linux' -skip_osx = is_osx = platform.system().lower() == 'darwin' -skip_win = is_win = platform.system().lower() == 'windows' +skip_linux = is_linux = platform.system().lower() == "linux" +skip_osx = is_osx = platform.system().lower() == "darwin" +skip_win = is_win = platform.system().lower() == "windows" is_py35 = sys.version_info.major == 3 and sys.version_info.minor == 5 -SKIP_RE = re.compile(r'(\s*>>>.*?)(\s*)#\s*skip\s+if\s+(.*)$') +SKIP_RE = re.compile(r"(\s*>>>.*?)(\s*)#\s*skip\s+if\s+(.*)$") def doctest_skip_parser(func): @@ -29,7 +30,7 @@ def doctest_skip_parser(func): >>> something """ - lines = func.__doc__.split('\n') + lines = func.__doc__.split("\n") new_lines = [] for line in lines: match = SKIP_RE.match(line) @@ -38,7 +39,7 @@ def doctest_skip_parser(func): continue code, space, expr = match.groups() if eval(expr, func.__globals__): - code = code + space + '# doctest: +SKIP' + code = code + space + "# doctest: +SKIP" new_lines.append(code) - func.__doc__ = '\n'.join(new_lines) + func.__doc__ = "\n".join(new_lines) return func diff --git a/fury/deprecator.py b/fury/deprecator.py index d848680c4..d928836b9 100644 --- a/fury/deprecator.py +++ b/fury/deprecator.py @@ -5,12 +5,13 @@ this file is copied (with minor modifications) from the Nibabel. https://github.com/nipy/nibabel. See COPYING file distributed along with the Nibabel package for the copyright and license terms. + """ import functools +from inspect import signature import re import warnings -from inspect import signature from fury import __version__ from fury.optpkg import optional_package @@ -18,9 +19,9 @@ # packaging.version.parse is a third-party utility but is used by setuptools # (so probably already installed) and is conformant to the current PEP 440. # But just if it is not the case, we use distutils -packaging, have_pkg, _ = optional_package('setuptools.extern.packaging') +packaging, have_pkg, _ = optional_package("setuptools.extern.packaging") -_LEADING_WHITE = re.compile(r'^(\s*)') +_LEADING_WHITE = re.compile(r"^(\s*)") class ExpiredDeprecationError(RuntimeError): @@ -49,7 +50,7 @@ def _ensure_cr(text): Ensures that ``text`` always ends with a carriage return """ - return text.rstrip() + '\n' + return text.rstrip() + "\n" def _add_dep_doc(old_doc, dep_doc): @@ -75,7 +76,9 @@ def _add_dep_doc(old_doc, dep_doc): old_doc = _ensure_cr(old_doc) old_lines = old_doc.splitlines() new_lines = [] - for line_no, line in enumerate(old_lines): + line_no = 0 + for line_no in range(len(old_lines)): + line = old_lines[line_no] if line.strip(): new_lines.append(line) else: @@ -83,10 +86,10 @@ def _add_dep_doc(old_doc, dep_doc): next_line = line_no + 1 if next_line >= len(old_lines): # nothing following first paragraph, just append message - return old_doc + '\n' + dep_doc + return old_doc + "\n" + dep_doc indent = _LEADING_WHITE.match(old_lines[next_line]).group() - dep_lines = [indent + L for L in [''] + dep_doc.splitlines() + ['']] - return '\n'.join(new_lines + dep_lines + old_lines[next_line:]) + '\n' + dep_lines = [indent + L for L in [""] + dep_doc.splitlines() + [""]] + return "\n".join(new_lines + dep_lines + old_lines[next_line:]) + "\n" def cmp_pkg_version(version_str, pkg_version_str=__version__): @@ -115,8 +118,8 @@ def cmp_pkg_version(version_str, pkg_version_str=__version__): """ version_cmp = packaging.version.parse if have_pkg else None - if any([re.match(r'^[a-z, A-Z]', v) for v in [version_str, pkg_version_str]]): - msg = 'Invalid version {0} or {1}'.format(version_str, pkg_version_str) + if any(re.match(r"^[a-z, A-Z]", v) for v in [version_str, pkg_version_str]): + msg = "Invalid version {0} or {1}".format(version_str, pkg_version_str) raise ValueError(msg) elif version_cmp(version_str) > version_cmp(pkg_version_str): return 1 @@ -133,8 +136,8 @@ def is_bad_version(version_str, version_comparator=cmp_pkg_version): def deprecate_with_version( message, - since='', - until='', + since="", + until="", version_comparator=cmp_pkg_version, warn_class=DeprecationWarning, error_class=ExpiredDeprecationError, @@ -178,17 +181,17 @@ def deprecate_with_version( """ messages = [message] - if (since, until) != ('', ''): - messages.append('') + if (since, until) != ("", ""): + messages.append("") if since: - messages.append('* deprecated from version: ' + since) + messages.append("* deprecated from version: " + since) if until: messages.append( - '* {0} {1} as of version: {2}'.format( - 'Raises' if is_bad_version(until) else 'Will raise', error_class, until + "* {0} {1} as of version: {2}".format( + "Raises" if is_bad_version(until) else "Will raise", error_class, until ) ) - message = '\n'.join(messages) + message = "\n".join(messages) def deprecator(func): @functools.wraps(func) @@ -207,13 +210,13 @@ def deprecated_func(*args, **kwargs): def deprecated_params( old_name, new_name=None, - since='', - until='', + since="", + until="", version_comparator=cmp_pkg_version, arg_in_kwargs=False, warn_class=ArgsDeprecationWarning, error_class=ExpiredDeprecationError, - alternative='', + alternative="", ): """Deprecate a *renamed* or *removed* function argument. @@ -313,19 +316,17 @@ def deprecated_params( if ( len( - set( - [ - len(old_name), - len(new_name), - len(since), - len(until), - len(arg_in_kwargs), - ] - ) + { + len(old_name), + len(new_name), + len(since), + len(until), + len(arg_in_kwargs), + } ) != 1 ): - raise ValueError('All parameters should have the same length') + raise ValueError("All parameters should have the same length") else: # To allow a uniform approach later on, wrap all arguments in lists. old_name = [old_name] @@ -351,7 +352,7 @@ def deprecator(function): # the only remaining possibility is that it should be caught # by some kind of **kwargs argument. msg = '"{}" was not specified in the function '.format(n_name) - msg += 'signature. If it was meant to be part of ' + msg += "signature. If it was meant to be part of " msg += '"**kwargs" then set "arg_in_kwargs" to "True"' raise TypeError(msg) @@ -368,7 +369,7 @@ def deprecator(function): # positional-only argument, varargs, varkwargs or some # unknown type: msg = 'cannot replace argument "{}" '.format(n_name) - msg += 'of kind {}.'.format(repr(param.kind)) + msg += "of kind {}.".format(repr(param.kind)) raise TypeError(msg) @functools.wraps(function) @@ -377,20 +378,20 @@ def wrapper(*args, **kwargs): messages = [ '"{}" was deprecated'.format(o_name), ] - if (since[i], until[i]) != ('', ''): - messages.append('') + if (since[i], until[i]) != ("", ""): + messages.append("") if since[i]: - messages.append('* deprecated from version: ' + str(since[i])) + messages.append("* deprecated from version: " + str(since[i])) if until[i]: messages.append( - '* {0} {1} as of version: {2}'.format( - 'Raises' if is_bad_version(until[i]) else 'Will raise', + "* {0} {1} as of version: {2}".format( + "Raises" if is_bad_version(until[i]) else "Will raise", error_class, until[i], ) ) - messages.append('') - message = '\n'.join(messages) + messages.append("") + message = "\n".join(messages) # The only way to have oldkeyword inside the function is # that it is passed as kwarg because the oldkeyword @@ -405,7 +406,7 @@ def wrapper(*args, **kwargs): if newarg_in_args or newarg_in_kwargs: msg = 'cannot specify both "{}"'.format(o_name) - msg += ' (deprecated parameter) and ' + msg += " (deprecated parameter) and " msg += '"{}" (new parameter name).'.format(n_name) raise TypeError(msg) @@ -417,7 +418,7 @@ def wrapper(*args, **kwargs): if n_name is not None: message += '* Use argument "{}" instead.'.format(n_name) elif alternative: - message += '* Use {} instead.'.format(alternative) + message += "* Use {} instead.".format(alternative) if until[i] and is_bad_version(until[i], version_comparator): raise error_class(message) @@ -427,7 +428,7 @@ def wrapper(*args, **kwargs): # positional argument. elif not n_name and positions[i] and len(args) > positions[i]: if alternative: - message += '* Use {} instead.'.format(alternative) + message += "* Use {} instead.".format(alternative) if until[i] and is_bad_version(until[i], version_comparator): raise error_class(message) diff --git a/fury/gltf.py b/fury/gltf.py index 400803eec..8dcf736f6 100644 --- a/fury/gltf.py +++ b/fury/gltf.py @@ -1,10 +1,12 @@ # TODO: Materials, Lights import base64 import os +from typing import Dict # noqa + +from PIL import Image import numpy as np import copy import pygltflib as gltflib -from PIL import Image from pygltflib.utils import glb2gltf, gltf2glb from fury import actor, io, transform, utils @@ -16,21 +18,15 @@ from fury.lib import Camera, Matrix4x4, Texture, Transform, numpy_support comp_type = { - 5120: {'size': 1, 'dtype': np.byte}, - 5121: {'size': 1, 'dtype': np.ubyte}, - 5122: {'size': 2, 'dtype': np.short}, - 5123: {'size': 2, 'dtype': np.ushort}, - 5125: {'size': 4, 'dtype': np.uint}, - 5126: {'size': 4, 'dtype': np.float32} + 5120: {"size": 1, "dtype": np.byte}, + 5121: {"size": 1, "dtype": np.ubyte}, + 5122: {"size": 2, "dtype": np.short}, + 5123: {"size": 2, "dtype": np.ushort}, + 5125: {"size": 4, "dtype": np.uint}, + 5126: {"size": 4, "dtype": np.float32}, } -acc_type = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT4': 16 -} +acc_type = {"SCALAR": 1, "VEC2": 2, "VEC3": 3, "VEC4": 4, "MAT4": 16} class glTF: @@ -46,13 +42,13 @@ def __init__(self, filename, apply_normals=False): If `True` applies normals to the mesh. """ - if filename in ['', None]: - raise IOError('Filename cannot be empty or None!') + if filename in ["", None]: + raise IOError("Filename cannot be empty or None!") name, extension = os.path.splitext(filename) - if extension == '.glb': - fname_gltf = f'{name}.gltf' + if extension == ".glb": + fname_gltf = f"{name}.gltf" if not os.path.exists(fname_gltf): glb2gltf(filename) filename = fname_gltf @@ -220,7 +216,7 @@ def transverse_node(self, nextnode_id, matrix, parent=None, self.bone_tranforms[nextnode_id] = next_matrix[:] if is_joint: - if not (nextnode_id in self.bone_tranforms): + if nextnode_id not in self.bone_tranforms: self.bone_tranforms[nextnode_id] = next_matrix[:] if node.mesh is not None: @@ -233,8 +229,7 @@ def transverse_node(self, nextnode_id, matrix, parent=None, for bone, ibm in zip(joints, ibms): self.bones.append(bone) self.ibms[bone] = ibm - self.transverse_node(joints[0], np.identity(4), parent, - is_joint=True) + self.transverse_node(joints[0], np.identity(4), parent, is_joint=True) if node.camera is not None: camera_id = node.camera @@ -258,7 +253,6 @@ def load_mesh(self, mesh_id, transform_mat, parent): primitives = self.gltf.meshes[mesh_id].primitives for primitive in primitives: - attributes = primitive.attributes vertices = self.get_acc_data(attributes.POSITION) @@ -335,7 +329,7 @@ def get_acc_data(self, acc_id): acc_byte_offset = accessor.byteOffset count = accessor.count d_type = comp_type.get(accessor.componentType) - d_size = d_type['size'] + d_size = d_type["size"] a_type = acc_type.get(accessor.type) buffview = self.gltf.bufferViews[buffview_id] @@ -348,8 +342,9 @@ def get_acc_data(self, acc_id): total_byte_offset = byte_offset + acc_byte_offset - buff_array = self.get_buff_array(buff_id, d_type['dtype'], byte_length, - total_byte_offset, byte_stride) + buff_array = self.get_buff_array( + buff_id, d_type["dtype"], byte_length, total_byte_offset, byte_stride + ) return buff_array[:, :a_type] def get_buff_array(self, buff_id, d_type, byte_length, @@ -388,13 +383,14 @@ def get_buff_array(self, buff_id, d_type, byte_length, byte_stride = int(byte_stride / 4) try: - if uri.startswith('data:application/octet-stream;base64') or \ - uri.startswith('data:application/gltf-buffer;base64'): - buff_data = uri.split(',')[1] + if uri.startswith("data:application/octet-stream;base64") or uri.startswith( + "data:application/gltf-buffer;base64" + ): + buff_data = uri.split(",")[1] buff_data = base64.b64decode(buff_data) - elif uri.endswith('.bin'): - with open(os.path.join(self.pwd, uri), 'rb') as f: + elif uri.endswith(".bin"): + with open(os.path.join(self.pwd, uri), "rb") as f: buff_data = f.read(-1) out_arr = np.frombuffer(buff_data, dtype=d_type, @@ -403,8 +399,8 @@ def get_buff_array(self, buff_id, d_type, byte_length, out_arr = out_arr.reshape(-1, byte_stride) return out_arr - except IOError as e: - print(f'Failed to read ! Error in opening file:', e) + except IOError: + print("Failed to read ! Error in opening file:") def get_materials(self, mat_id): """Get the material data. @@ -498,11 +494,11 @@ def get_texture(self, tex_id, srgb_colorspace=False, rgb=False): if file is None: mimetype = image.mimeType - if file is not None and file.startswith('data:image'): - buff_data = file.split(',')[1] + if file is not None and file.startswith("data:image"): + buff_data = file.split(",")[1] buff_data = base64.b64decode(buff_data) - extension = '.png' if file.startswith('data:image/png') else '.jpg' + extension = ".png" if file.startswith("data:image/png") else ".jpg" image_path = os.path.join(self.pwd, str("b64texture" + extension)) with open(image_path, "wb") as image_file: image_file.write(buff_data) @@ -513,10 +509,10 @@ def get_texture(self, tex_id, srgb_colorspace=False, rgb=False): bo = bv.byteOffset bl = bv.byteLength uri = self.gltf.buffers[buffer].uri - with open(os.path.join(self.pwd, uri), 'rb') as f: + with open(os.path.join(self.pwd, uri), "rb") as f: f.seek(bo) img_binary = f.read(bl) - extension = '.png' if mimetype == 'images/png' else '.jpg' + extension = ".png" if mimetype == "images/png" else ".jpg" image_path = os.path.join(self.pwd, str("bvtexture" + extension)) with open(image_path, "wb") as image_file: image_file.write(img_binary) @@ -624,11 +620,12 @@ def transverse_channels(self, animation: gltflib.Animation, count: int): pygltflib animation object. count : int Animation count. + """ name = animation.name if name is None: - name = str(f'anim_{count}') - anim_channel = {} + name = str(f"anim_{count}") + anim_channel = {} # type: Dict[int, np.ndarray] for channel in animation.channels: sampler = animation.samplers[channel.sampler] @@ -659,16 +656,19 @@ def get_sampler_data(self, sampler: gltflib.Sampler, node_id: int, sampler_data : dict dictionary of data containing timestamps, node transformations and interpolation type. + """ time_array = self.get_acc_data(sampler.input) transform_array = self.get_acc_data(sampler.output) interpolation = sampler.interpolation return { - 'node': node_id, 'input': time_array, - 'output': transform_array, - 'interpolation': interpolation, - 'property': transform_type} + "node": node_id, + "input": time_array, + "output": transform_array, + "interpolation": interpolation, + "property": transform_type, + } def get_matrix_from_sampler(self, prop, node, anim_channel, sampler: gltflib.Sampler): @@ -685,16 +685,19 @@ def get_matrix_from_sampler(self, prop, node, anim_channel, Containing previous animations with node as keys. sampler : gltflib.Sampler Sampler object for an animation channel. + """ time_array = self.get_acc_data(sampler.input) tran_array = self.get_acc_data(sampler.output) - if prop == 'weights': - tran_array = tran_array.reshape(-1, ) + if prop == "weights": + tran_array = tran_array.reshape( + -1, + ) tran_matrix = [] if node in anim_channel: - prev_arr = anim_channel[node]['matrix'] + prev_arr = anim_channel[node]["matrix"] else: prev_arr = [np.identity(4) for i in range(len(tran_array))] @@ -704,17 +707,14 @@ def get_matrix_from_sampler(self, prop, node, anim_channel, tran_matrix.append(np.dot(prev_arr[i], temp)) else: tran_matrix.append(temp) - data = { - 'timestamps': time_array, - 'matrix': tran_matrix - } + data = {"timestamps": time_array, "matrix": tran_matrix} self.sampler_matrices[node] = data return data def get_morph_data(self, target, mesh_id): weights_array = self.gltf.meshes[mesh_id].weights - if target.get('POSITION') is not None: - morphed_data = self.get_acc_data(target.get('POSITION')) + if target.get("POSITION") is not None: + morphed_data = self.get_acc_data(target.get("POSITION")) self.morph_weights.append(weights_array) return morphed_data @@ -732,6 +732,7 @@ def get_skin_data(self, skin_id): List of bones in the skin. inv_bind_matrix : ndarray Numpy array containing inverse bind pose for each bone. + """ skin = self.gltf.skins[skin_id] inv_bind_matrix = self.get_acc_data(skin.inverseBindMatrices) @@ -754,20 +755,26 @@ def generate_tmatrix(self, transf, prop): ------- matrix : ndarray (4, 4) ransformation matrix of shape (4, 4) with respective transforms. + """ - if prop == 'translation': + if prop == "translation": matrix = transform.translate(transf) - elif prop == 'rotation': + elif prop == "rotation": matrix = transform.rotate(transf) - elif prop == 'scale': + elif prop == "scale": matrix = transform.scale(transf) else: matrix = transf return matrix - def transverse_animations(self, animation, bone_id, timestamp, - joint_matrices, - parent_bone_deform=np.identity(4)): + def transverse_animations( + self, + animation, + bone_id, + timestamp, + joint_matrices, + parent_bone_deform=None, + ): """Calculate skinning matrix (Joint Matrices) and transform bone for each animation. @@ -784,8 +791,12 @@ def transverse_animations(self, animation, bone_id, timestamp, parent_bone_transform : ndarray (4, 4) Transformation matrix of the parent bone. (default=np.identity(4)) + """ - deform = animation.get_value('transform', timestamp) + if parent_bone_deform is None: + parent_bone_deform = np.identity(4) + + deform = animation.get_value("transform", timestamp) new_deform = np.dot(parent_bone_deform, deform) ibm = self.ibms[bone_id].T @@ -815,6 +826,7 @@ def update_skin(self, animation): ---------- animation : Animation Animation object. + """ animation.update_animation() timestamp = animation.current_timestamp @@ -852,6 +864,7 @@ def initialize_skin(self, animation, bones=False, length=0.2): length : float Length of the bones. (default=0.2) + """ self.show_bones = bones if bones: @@ -873,6 +886,7 @@ def apply_skin_matrix(self, vertices, joint_matrices, actor_index=0): ------- vertices : ndarray Modified vertices. + """ clone = np.copy(vertices) weights = self.weights_0[actor_index] @@ -908,6 +922,7 @@ def transverse_bones(self, bone_id, channel_name, parent_animation : Animation The animation of the parent bone. Should be `root_animation` by default. + """ node = self.gltf.nodes[bone_id] animation = Animation() @@ -917,13 +932,12 @@ def transverse_bones(self, bone_id, channel_name, orig_transform = np.identity(4) if bone_id in self.animation_channels[channel_name]: transforms = self.animation_channels[channel_name][bone_id] - timestamps = transforms['timestamps'] - metrices = transforms['matrix'] - for time, matrix in zip(timestamps, metrices): - animation.set_keyframe('transform', time[0], matrix) + timestamps = transforms["timestamps"] + matrices = transforms["matrix"] + for time, matrix in zip(timestamps, matrices): + animation.set_keyframe("transform", time[0], matrix) else: - animation.set_keyframe('transform', 0.0, - orig_transform) + animation.set_keyframe("transform", 0.0, orig_transform) parent_animation.add(animation) if node.children: @@ -937,6 +951,7 @@ def skin_animation(self): ------- root_animations : Dict An animation containing all the child animations for bones. + """ root_animations = {} self._vertices = [utils.vertices_from_actor(act) for act in @@ -961,6 +976,7 @@ def get_joint_actors(self, length=0.5, with_transforms=False): with_transforms : bool (default = False) Applies respective transformations to bone. Bones will be at origin if set to `False`. + """ origin = np.zeros((3, 3)) parent_transforms = self.bone_tranforms @@ -984,12 +1000,12 @@ def update_morph(self, animation): ---------- animation : Animation Animation object. + """ animation.update_animation() timestamp = animation.current_timestamp for i, vertex in enumerate(self._vertices): - weights = animation.child_animations[0].get_value('morph', - timestamp) + weights = animation.child_animations[0].get_value("morph", timestamp) vertex[:] = self.apply_morph_vertices(self._vcopy[i], weights, i) vertex[:] = transform.apply_transformation(vertex, self.transformations[i]) @@ -1008,6 +1024,7 @@ def apply_morph_vertices(self, vertices, weights, cnt): vertex. cnt : int Count of the actor. + """ clone = np.copy(vertices) target_vertices = np.copy(self.morph_vertices[cnt]) @@ -1026,6 +1043,7 @@ def morph_animation(self): root_animations : Dict A dictionary containing animations as values and animation name as keys. + """ animations = {} self._vertices = [utils.vertices_from_actor(act) for act in @@ -1038,12 +1056,12 @@ def morph_animation(self): for i, transforms in enumerate(data.values()): weights = self.morph_weights[i] animation = Animation() - timestamps = transforms['timestamps'] - metrices = transforms['matrix'] - metrices = np.array(metrices).reshape(-1, len(weights)) + timestamps = transforms["timestamps"] + matrices = transforms["matrix"] + matrices = np.array(matrices).reshape(-1, len(weights)) - for time, weights in zip(timestamps, metrices): - animation.set_keyframe('morph', time[0], weights) + for time, weights in zip(timestamps, matrices): + animation.set_keyframe("morph", time[0], weights) root_animation.add(animation) root_animation.add_actor(self._actors) @@ -1057,45 +1075,45 @@ def get_animations(self): ------- animations: List List of animations containing actors. + """ actors = self.actors() interpolators = { - 'LINEAR': linear_interpolator, - 'STEP': step_interpolator, - 'CUBICSPLINE': tan_cubic_spline_interpolator + "LINEAR": linear_interpolator, + "STEP": step_interpolator, + "CUBICSPLINE": tan_cubic_spline_interpolator, } rotation_interpolators = { - 'LINEAR': slerp, - 'STEP': step_interpolator, - 'CUBICSPLINE': tan_cubic_spline_interpolator + "LINEAR": slerp, + "STEP": step_interpolator, + "CUBICSPLINE": tan_cubic_spline_interpolator, } animations = [] for transforms in self.node_transform: - target_node = transforms['node'] + target_node = transforms["node"] for i, nodes in enumerate(self.nodes): animation = Animation() transform_mat = self.transformations[i] - position, rot, scale = transform.transform_from_matrix( - transform_mat) - animation.set_keyframe('position', 0.0, position) + position, rot, scale = transform.transform_from_matrix(transform_mat) + animation.set_keyframe("position", 0.0, position) if target_node in nodes: animation.add_actor(actors[i]) - timestamp = transforms['input'] - node_transform = transforms['output'] - prop = transforms['property'] + timestamp = transforms["input"] + node_transform = transforms["output"] + prop = transforms["property"] - interpolation_type = transforms['interpolation'] + interpolation_type = transforms["interpolation"] interpolator = interpolators.get(interpolation_type) rot_interp = rotation_interpolators.get( interpolation_type) timeshape = timestamp.shape transhape = node_transform.shape - if transforms['interpolation'] == 'CUBICSPLINE': + if transforms["interpolation"] == "CUBICSPLINE": node_transform = node_transform.reshape( (timeshape[0], -1, transhape[1])) @@ -1107,20 +1125,20 @@ def get_animations(self): trs = cubicspline[1] out_tan = cubicspline[2] - if prop == 'rotation': - animation.set_rotation(time[0], trs, - in_tangent=in_tan, - out_tangent=out_tan) + if prop == "rotation": + animation.set_rotation( + time[0], trs, in_tangent=in_tan, out_tangent=out_tan + ) animation.set_rotation_interpolator(rot_interp) - if prop == 'translation': - animation.set_position(time[0], trs, - in_tangent=in_tan, - out_tangent=out_tan) + if prop == "translation": + animation.set_position( + time[0], trs, in_tangent=in_tan, out_tangent=out_tan + ) animation.set_position_interpolator(interpolator) - if prop == 'scale': - animation.set_scale(time[0], trs, - in_tangent=in_tan, - out_tangent=out_tan) + if prop == "scale": + animation.set_scale( + time[0], trs, in_tangent=in_tan, out_tangent=out_tan + ) animation.set_scale_interpolator(interpolator) else: animation.add_static_actor(actors[i]) @@ -1135,6 +1153,7 @@ def main_animation(self): main_animation : Animation A parent animation containing all child animations for simple animation. + """ main_animation = Animation() animations = self.get_animations() @@ -1143,7 +1162,7 @@ def main_animation(self): return main_animation -def export_scene(scene, filename='default.gltf'): +def export_scene(scene, filename="default.gltf"): """Generate gltf from FURY scene. Parameters @@ -1152,14 +1171,15 @@ def export_scene(scene, filename='default.gltf'): FURY scene object. filename: str, optional Name of the model to be saved + """ gltf_obj = gltflib.GLTF2() name, extension = os.path.splitext(filename) - if extension not in ['.gltf', '.glb']: - raise IOError('Filename should be .gltf or .glb') + if extension not in [".gltf", ".glb"]: + raise IOError("Filename should be .gltf or .glb") - buffer_file = open(f'{name}.bin', 'wb') + buffer_file = open(f"{name}.bin", "wb") primitives = [] buffer_size = 0 bview_count = 0 @@ -1172,7 +1192,7 @@ def export_scene(scene, filename='default.gltf'): buffer_file.close() write_mesh(gltf_obj, primitives) - write_buffer(gltf_obj, size, f'{name}.bin') + write_buffer(gltf_obj, size, f"{name}.bin") camera = scene.camera() cam_id = None if camera: @@ -1181,9 +1201,9 @@ def export_scene(scene, filename='default.gltf'): write_node(gltf_obj, mesh_id=0, camera_id=cam_id) write_scene(gltf_obj, [0]) - gltf_obj.save(f'{name}.gltf') - if extension == '.glb': - gltf2glb(f'{name}.gltf', destination=filename) + gltf_obj.save(f"{name}.gltf") + if extension == ".glb": + gltf2glb(f"{name}.gltf", destination=filename) def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): @@ -1210,8 +1230,8 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): Offset size of a primitive count: int BufferView count after adding the primitive. - """ + """ polydata = actor.GetMapper().GetInput() colors = utils.colors_from_actor(actor) if colors is not None: @@ -1249,7 +1269,7 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): atype = acc_type.get(gltflib.SCALAR) indices = indices.astype(np.ushort) - blength = len(indices) * ctype['size'] + blength = len(indices) * ctype["size"] buff_file.write(indices.tobytes()) write_bufferview(gltf, 0, byteoffset, blength) write_accessor(gltf, count, 0, gltflib.UNSIGNED_SHORT, @@ -1265,8 +1285,8 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): ctype = comp_type.get(gltflib.FLOAT) atype = acc_type.get(gltflib.VEC3) - vertices = vertices.reshape((-1,)).astype(ctype['dtype']) - blength = len(vertices) * ctype['size'] + vertices = vertices.reshape((-1,)).astype(ctype["dtype"]) + blength = len(vertices) * ctype["size"] buff_file.write(vertices.tobytes()) write_bufferview(gltf, 0, byteoffset, blength) write_accessor(gltf, count, 0, gltflib.FLOAT, len(vertices) // @@ -1283,7 +1303,7 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): atype = acc_type.get(gltflib.VEC3) normals = normals.reshape((-1,)) - blength = len(normals) * ctype['size'] + blength = len(normals) * ctype["size"] buff_file.write(normals.tobytes()) write_bufferview(gltf, 0, byteoffset, blength) write_accessor(gltf, count, 0, gltflib.FLOAT, @@ -1299,8 +1319,8 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): ctype = comp_type.get(gltflib.FLOAT) atype = acc_type.get(gltflib.VEC2) - tcoords = tcoords.reshape((-1,)).astype(ctype['dtype']) - blength = len(tcoords) * ctype['size'] + tcoords = tcoords.reshape((-1,)).astype(ctype["dtype"]) + blength = len(tcoords) * ctype["size"] buff_file.write(tcoords.tobytes()) write_bufferview(gltf, 0, byteoffset, blength) write_accessor(gltf, count, 0, gltflib.FLOAT, @@ -1315,7 +1335,7 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): np_im = np.reshape(np_im, (rows, cols, -1)) img = Image.fromarray(np_im) - image_path = f'{name}BaseColorTexture.png' + image_path = f"{name}BaseColorTexture.png" img.save(image_path) write_material(gltf, 0, image_path) @@ -1327,8 +1347,8 @@ def _connect_primitives(gltf, actor, buff_file, byteoffset, count, name): colors = np.concatenate((colors, np.full((shape, 1), 255.)), axis=1) colors = colors / 255 - colors = colors.reshape((-1,)).astype(ctype['dtype']) - blength = len(colors) * ctype['size'] + colors = colors.reshape((-1,)).astype(ctype["dtype"]) + blength = len(colors) * ctype["size"] buff_file.write(colors.tobytes()) write_bufferview(gltf, 0, byteoffset, blength) write_accessor(gltf, count, 0, gltflib.FLOAT, shape, @@ -1350,6 +1370,7 @@ def write_scene(gltf, nodes): Pygltflib GLTF2 object nodes: list List of node indices. + """ scene = gltflib.Scene() scene.nodes = nodes @@ -1367,6 +1388,7 @@ def write_node(gltf, mesh_id=None, camera_id=None): Mesh index camera_id: int, optional Camera index. + """ node = gltflib.Node() if mesh_id is not None: @@ -1385,6 +1407,7 @@ def write_mesh(gltf, primitives): Pygltflib GLTF2 object. primitives: list List of Primitive object. + """ mesh = gltflib.Mesh() for prim in primitives: @@ -1402,6 +1425,7 @@ def write_camera(gltf, camera): Pygltflib GLTF2 object. camera: vtkCamera scene camera. + """ orthographic = camera.GetParallelProjection() cam = gltflib.Camera() @@ -1446,6 +1470,7 @@ def get_prim(vertex, index, color, tcoord, normal, material, mode=4): ------- prim: Primitive pygltflib primitive object. + """ prim = gltflib.Primitive() attr = gltflib.Attributes() @@ -1472,6 +1497,7 @@ def write_material(gltf, basecolortexture: int, uri: str): BaseColorTexture index. uri: str BaseColorTexture uri. + """ material = gltflib.Material() texture = gltflib.Texture() @@ -1512,6 +1538,7 @@ def write_accessor(gltf, bufferview, byte_offset, comp_type, Maximum elements of an array min: ndarray, optional Minimum elements of an array + """ accessor = gltflib.Accessor() accessor.bufferView = bufferview @@ -1542,6 +1569,7 @@ def write_bufferview(gltf, buffer, byte_offset, byte_length, the buffer byte_stride: int, optional Byte stride of the bufferview. + """ buffer_view = gltflib.BufferView() buffer_view.buffer = buffer @@ -1562,6 +1590,7 @@ def write_buffer(gltf, byte_length, uri): Length of the buffer uri: str Path to the external `.bin` file. + """ buffer = gltflib.Buffer() buffer.uri = uri diff --git a/fury/interactor.py b/fury/interactor.py index 2960b0ca6..399f44992 100644 --- a/fury/interactor.py +++ b/fury/interactor.py @@ -101,9 +101,9 @@ def __init__(self): self.history = deque(maxlen=10) # Events history. self.selected_props = { - 'left_button': set(), - 'right_button': set(), - 'middle_button': set(), + "left_button": set(), + "right_button": set(), + "middle_button": set(), } def add_active_prop(self, prop): @@ -141,34 +141,34 @@ def _process_event(self, obj, evt): self.event.update(evt, self.GetInteractor()) self.history.append( { - 'event': evt, - 'pos': self.event.position, + "event": evt, + "pos": self.event.position, } ) - if evt == 'LeftButtonPressEvent': + if evt == "LeftButtonPressEvent": self.on_left_button_down(obj, evt) - elif evt == 'LeftButtonReleaseEvent': + elif evt == "LeftButtonReleaseEvent": self.on_left_button_up(obj, evt) - elif evt == 'RightButtonPressEvent': + elif evt == "RightButtonPressEvent": self.on_right_button_down(obj, evt) - elif evt == 'RightButtonReleaseEvent': + elif evt == "RightButtonReleaseEvent": self.on_right_button_up(obj, evt) - elif evt == 'MiddleButtonPressEvent': + elif evt == "MiddleButtonPressEvent": self.on_middle_button_down(obj, evt) - elif evt == 'MiddleButtonReleaseEvent': + elif evt == "MiddleButtonReleaseEvent": self.on_middle_button_up(obj, evt) - elif evt == 'MouseMoveEvent': + elif evt == "MouseMoveEvent": self.on_mouse_move(obj, evt) - elif evt == 'CharEvent': + elif evt == "CharEvent": self.on_char(obj, evt) - elif evt == 'KeyPressEvent': + elif evt == "KeyPressEvent": self.on_key_press(obj, evt) - elif evt == 'KeyReleaseEvent': + elif evt == "KeyReleaseEvent": self.on_key_release(obj, evt) - elif evt == 'MouseWheelForwardEvent': + elif evt == "MouseWheelForwardEvent": self.on_mouse_wheel_forward(obj, evt) - elif evt == 'MouseWheelBackwardEvent': + elif evt == "MouseWheelBackwardEvent": self.on_mouse_wheel_backward(obj, evt) self.event.reset() # Event fully processed. @@ -177,10 +177,10 @@ def _button_clicked(self, button, last_event=-1, before_last_event=-2): if len(self.history) < abs(before_last_event): return False - if self.history[last_event]['event'] != button + 'ButtonReleaseEvent': + if self.history[last_event]["event"] != button + "ButtonReleaseEvent": return False - if self.history[before_last_event]['event'] != button + 'ButtonPressEvent': + if self.history[before_last_event]["event"] != button + "ButtonPressEvent": return False return True @@ -195,7 +195,7 @@ def on_left_button_down(self, _obj, evt): self.left_button_down = True prop = self.get_prop_at_event_position() if prop is not None: - self.selected_props['left_button'].add(prop) + self.selected_props["left_button"].add(prop) self.propagate_event(evt, prop) if not self.event.abort_flag: @@ -203,21 +203,21 @@ def on_left_button_down(self, _obj, evt): def on_left_button_up(self, _obj, evt): self.left_button_down = False - self.propagate_event(evt, *self.selected_props['left_button']) - self.selected_props['left_button'].clear() + self.propagate_event(evt, *self.selected_props["left_button"]) + self.selected_props["left_button"].clear() self.trackball_camera.OnLeftButtonUp() prop = self.get_prop_at_event_position() - if self._button_double_clicked('Left'): - self.propagate_event('LeftButtonDoubleClickEvent', prop) + if self._button_double_clicked("Left"): + self.propagate_event("LeftButtonDoubleClickEvent", prop) self.history.clear() def on_right_button_down(self, _obj, evt): self.right_button_down = True prop = self.get_prop_at_event_position() if prop is not None: - self.selected_props['right_button'].add(prop) + self.selected_props["right_button"].add(prop) self.propagate_event(evt, prop) if not self.event.abort_flag: @@ -225,20 +225,20 @@ def on_right_button_down(self, _obj, evt): def on_right_button_up(self, _obj, evt): self.right_button_down = False - self.propagate_event(evt, *self.selected_props['right_button']) - self.selected_props['right_button'].clear() + self.propagate_event(evt, *self.selected_props["right_button"]) + self.selected_props["right_button"].clear() self.trackball_camera.OnRightButtonUp() - if self._button_double_clicked('Right'): + if self._button_double_clicked("Right"): prop = self.get_prop_at_event_position() - self.propagate_event('RightButtonDoubleClickEvent', prop) + self.propagate_event("RightButtonDoubleClickEvent", prop) self.history.clear() def on_middle_button_down(self, _obj, evt): self.middle_button_down = True prop = self.get_prop_at_event_position() if prop is not None: - self.selected_props['middle_button'].add(prop) + self.selected_props["middle_button"].add(prop) self.propagate_event(evt, prop) if not self.event.abort_flag: @@ -246,13 +246,13 @@ def on_middle_button_down(self, _obj, evt): def on_middle_button_up(self, _obj, evt): self.middle_button_down = False - self.propagate_event(evt, *self.selected_props['middle_button']) - self.selected_props['middle_button'].clear() + self.propagate_event(evt, *self.selected_props["middle_button"]) + self.selected_props["middle_button"].clear() self.trackball_camera.OnMiddleButtonUp() - if self._button_double_clicked('Middle'): + if self._button_double_clicked("Middle"): prop = self.get_prop_at_event_position() - self.propagate_event('MiddleButtonDoubleClickEvent', prop) + self.propagate_event("MiddleButtonDoubleClickEvent", prop) self.history.clear() def on_mouse_move(self, _obj, evt): @@ -262,9 +262,9 @@ def on_mouse_move(self, _obj, evt): evt, *( self.active_props - | self.selected_props['left_button'] - | self.selected_props['right_button'] - | self.selected_props['middle_button'] + | self.selected_props["left_button"] + | self.selected_props["right_button"] + | self.selected_props["middle_button"] ), ) @@ -325,23 +325,23 @@ def SetInteractor(self, interactor): # # Note: Be sure that no observer has been manually added to the # `interactor` before setting the InteractorStyle. - interactor.RemoveObservers('TimerEvent') - interactor.RemoveObservers('EnterEvent') - interactor.RemoveObservers('LeaveEvent') - interactor.RemoveObservers('ExposeEvent') - interactor.RemoveObservers('ConfigureEvent') - interactor.RemoveObservers('CharEvent') - interactor.RemoveObservers('KeyPressEvent') - interactor.RemoveObservers('KeyReleaseEvent') - interactor.RemoveObservers('MouseMoveEvent') - interactor.RemoveObservers('LeftButtonPressEvent') - interactor.RemoveObservers('RightButtonPressEvent') - interactor.RemoveObservers('MiddleButtonPressEvent') - interactor.RemoveObservers('LeftButtonReleaseEvent') - interactor.RemoveObservers('RightButtonReleaseEvent') - interactor.RemoveObservers('MiddleButtonReleaseEvent') - interactor.RemoveObservers('MouseWheelForwardEvent') - interactor.RemoveObservers('MouseWheelBackwardEvent') + interactor.RemoveObservers("TimerEvent") + interactor.RemoveObservers("EnterEvent") + interactor.RemoveObservers("LeaveEvent") + interactor.RemoveObservers("ExposeEvent") + interactor.RemoveObservers("ConfigureEvent") + interactor.RemoveObservers("CharEvent") + interactor.RemoveObservers("KeyPressEvent") + interactor.RemoveObservers("KeyReleaseEvent") + interactor.RemoveObservers("MouseMoveEvent") + interactor.RemoveObservers("LeftButtonPressEvent") + interactor.RemoveObservers("RightButtonPressEvent") + interactor.RemoveObservers("MiddleButtonPressEvent") + interactor.RemoveObservers("LeftButtonReleaseEvent") + interactor.RemoveObservers("RightButtonReleaseEvent") + interactor.RemoveObservers("MiddleButtonReleaseEvent") + interactor.RemoveObservers("MouseWheelForwardEvent") + interactor.RemoveObservers("MouseWheelBackwardEvent") # This class is a `vtkClass` (instead of `object`), so `super()` # cannot be used. Also the method `SetInteractor` is not overridden in @@ -352,18 +352,18 @@ def SetInteractor(self, interactor): InteractorStyle.SetInteractor(self, interactor) # Keyboard events. - self.AddObserver('CharEvent', self._process_event) - self.AddObserver('KeyPressEvent', self._process_event) - self.AddObserver('KeyReleaseEvent', self._process_event) + self.AddObserver("CharEvent", self._process_event) + self.AddObserver("KeyPressEvent", self._process_event) + self.AddObserver("KeyReleaseEvent", self._process_event) # Mouse events. - self.AddObserver('MouseMoveEvent', self._process_event) - self.AddObserver('LeftButtonPressEvent', self._process_event) - self.AddObserver('LeftButtonReleaseEvent', self._process_event) - self.AddObserver('RightButtonPressEvent', self._process_event) - self.AddObserver('RightButtonReleaseEvent', self._process_event) - self.AddObserver('MiddleButtonPressEvent', self._process_event) - self.AddObserver('MiddleButtonReleaseEvent', self._process_event) + self.AddObserver("MouseMoveEvent", self._process_event) + self.AddObserver("LeftButtonPressEvent", self._process_event) + self.AddObserver("LeftButtonReleaseEvent", self._process_event) + self.AddObserver("RightButtonPressEvent", self._process_event) + self.AddObserver("RightButtonReleaseEvent", self._process_event) + self.AddObserver("MiddleButtonPressEvent", self._process_event) + self.AddObserver("MiddleButtonReleaseEvent", self._process_event) # Windows and special events. # TODO: we ever find them useful we could support them. @@ -376,14 +376,14 @@ def SetInteractor(self, interactor): # These observers need to be added directly to the interactor because # `vtkInteractorStyleUser` does not support wheel events prior 7.1. See # https://github.com/Kitware/VTK/commit/373258ed21f0915c425eddb996ce6ac13404be28 - interactor.AddObserver('MouseWheelForwardEvent', self._process_event) - interactor.AddObserver('MouseWheelBackwardEvent', self._process_event) + interactor.AddObserver("MouseWheelForwardEvent", self._process_event) + interactor.AddObserver("MouseWheelBackwardEvent", self._process_event) def force_render(self): """Causes the scene to refresh.""" self.GetInteractor().GetRenderWindow().Render() - def add_callback(self, prop, event_type, callback, priority=0, args=[]): + def add_callback(self, prop, event_type, callback, priority=0, args=None): """Add a callback associated to a specific event for a VTK prop. Parameters @@ -392,7 +392,10 @@ def add_callback(self, prop, event_type, callback, priority=0, args=[]): event_type : event code callback : function priority : int + """ + if args is None: + args = [] def _callback(_obj, event_name): # Update event information. @@ -400,8 +403,8 @@ def _callback(_obj, event_name): if interactor_ is not None: callback(self, prop, *args) else: - print('interactor is none') - print('event name is', event_name) + print("interactor is none") + print("event name is", event_name) # Dealing with custom events not defined in VTK. # Check whether the Event is predefined or not. diff --git a/fury/io.py b/fury/io.py index 8d0dac3e6..828002c2d 100644 --- a/fury/io.py +++ b/fury/io.py @@ -1,10 +1,10 @@ import os -import warnings from tempfile import TemporaryDirectory as InTemporaryDirectory from urllib.request import urlretrieve +import warnings -import numpy as np from PIL import Image +import numpy as np from fury.lib import ( BMPReader, @@ -24,9 +24,9 @@ PolyDataWriter, STLReader, STLWriter, - Texture, TIFFReader, TIFFWriter, + Texture, XMLPolyDataReader, XMLPolyDataWriter, numpy_support, @@ -51,7 +51,7 @@ def load_cubemap_texture(fnames, interpolate_on=True, mipmap_on=True): """ if len(fnames) != 6: - raise IOError('Expected 6 filenames, got {}'.format(len(fnames))) + raise IOError("Expected 6 filenames, got {}".format(len(fnames))) texture = Texture() texture.CubeMapOn() for idx, fn in enumerate(fnames): @@ -92,41 +92,43 @@ def load_image(filename, as_vtktype=False, use_pillow=True): desired image array """ - is_url = filename.lower().startswith('http://') or filename.lower().startswith( - 'https://' + is_url = filename.lower().startswith("http://") or filename.lower().startswith( + "https://" ) if is_url: image_name = os.path.basename(filename) - if len(image_name.split('.')) < 2: - raise IOError(f'{filename} is not a valid image URL') + if len(image_name.split(".")) < 2: + raise IOError(f"{filename} is not a valid image URL") urlretrieve(filename, image_name) filename = image_name if use_pillow: with Image.open(filename) as pil_image: - if pil_image.mode in ['P']: - pil_image = pil_image.convert('RGB') + if pil_image.mode in ["P"]: + pil_image = pil_image.convert("RGB") - if pil_image.mode in ['RGBA', 'RGB', 'L']: + if pil_image.mode in ["RGBA", "RGB", "L"]: image = np.asarray(pil_image) - elif pil_image.mode.startswith('I;16'): - raw = pil_image.tobytes('raw', pil_image.mode) - dtype = '>u2' if pil_image.mode.endswith('B') else ' 3: - raise IOError('Image Dimensions should be <=3') + raise IOError("Image Dimensions should be <=3") if isinstance(dpi, (float, int)): dpi = (dpi, dpi) d_writer = { - '.png': PNGWriter, - '.bmp': BMPWriter, - '.jpeg': JPEGWriter, - '.jpg': JPEGWriter, - '.tiff': TIFFWriter, - '.tif': TIFFWriter, + ".png": PNGWriter, + ".bmp": BMPWriter, + ".jpeg": JPEGWriter, + ".jpg": JPEGWriter, + ".tiff": TIFFWriter, + ".tif": TIFFWriter, } extension = os.path.splitext(os.path.basename(filename).lower())[1] if extension.lower() not in d_writer.keys(): raise IOError( - 'Impossible to save the file {0}: Unknown extension {1}'.format( + "Impossible to save the file {0}: Unknown extension {1}".format( filename, extension ) ) @@ -267,14 +270,17 @@ def save_image( im = Image.fromarray(arr) im.save(filename, quality=compression_quality, dpi=dpi) else: - warnings.warn(UserWarning('DPI value is ignored while saving images via vtk.')) + warnings.warn( + UserWarning("DPI value is ignored while saving images via vtk."), + stacklevel=2, + ) if arr.ndim == 2: arr = arr[..., None] shape = arr.shape arr = np.flipud(arr) if extension.lower() in [ - '.png', + ".png", ]: arr = arr.astype(np.uint8) arr = arr.reshape((shape[1] * shape[0], shape[2])) @@ -296,12 +302,12 @@ def save_image( writer = d_writer.get(extension)() writer.SetFileName(filename) writer.SetInputData(vtk_data) - if extension.lower() in ['.jpg', '.jpeg']: + if extension.lower() in [".jpg", ".jpeg"]: writer.ProgressiveOn() writer.SetQuality(compression_quality) - if extension.lower() in ['.tif', '.tiff']: - compression_type = compression_type or 'nocompression' - l_compression = ['nocompression', 'packbits', 'jpeg', 'deflate', 'lzw'] + if extension.lower() in [".tif", ".tiff"]: + compression_type = compression_type or "nocompression" + l_compression = ["nocompression", "packbits", "jpeg", "deflate", "lzw"] if compression_type.lower() in l_compression: comp_id = l_compression.index(compression_type.lower()) @@ -325,25 +331,24 @@ def load_polydata(file_name): output : vtkPolyData """ - # Check if file actually exists if not os.path.isfile(file_name): raise FileNotFoundError(file_name) - file_extension = file_name.split('.')[-1].lower() + file_extension = file_name.split(".")[-1].lower() poly_reader = { - 'vtk': PolyDataReader, - 'vtp': XMLPolyDataReader, - 'fib': PolyDataReader, - 'ply': PLYReader, - 'stl': STLReader, - 'xml': XMLPolyDataReader, + "vtk": PolyDataReader, + "vtp": XMLPolyDataReader, + "fib": PolyDataReader, + "ply": PLYReader, + "stl": STLReader, + "xml": XMLPolyDataReader, } if file_extension in poly_reader.keys(): reader = poly_reader.get(file_extension)() - elif file_extension == 'obj': + elif file_extension == "obj": # Special case, since there is two obj format reader = OBJReader() reader.SetFileName(file_name) @@ -351,7 +356,7 @@ def load_polydata(file_name): if reader.GetOutput().GetNumberOfCells() == 0: reader = MNIObjectReader() else: - raise IOError('.' + file_extension + ' is not supported by FURY') + raise IOError("." + file_extension + " is not supported by FURY") reader.SetFileName(file_name) reader.Update() @@ -372,34 +377,34 @@ def save_polydata(polydata, file_name, binary=False, color_array_name=None): """ # get file extension (type) - file_extension = file_name.split('.')[-1].lower() + file_extension = file_name.split(".")[-1].lower() poly_writer = { - 'vtk': PolyDataWriter, - 'vtp': XMLPolyDataWriter, - 'fib': PolyDataWriter, - 'ply': PLYWriter, - 'stl': STLWriter, - 'xml': XMLPolyDataWriter, + "vtk": PolyDataWriter, + "vtp": XMLPolyDataWriter, + "fib": PolyDataWriter, + "ply": PLYWriter, + "stl": STLWriter, + "xml": XMLPolyDataWriter, } if file_extension in poly_writer.keys(): writer = poly_writer.get(file_extension)() - elif file_extension == 'obj': + elif file_extension == "obj": # Special case, since there is two obj format - find_keyword = file_name.lower().split('.') - if 'mni' in find_keyword or 'mnc' in find_keyword: + find_keyword = file_name.lower().split(".") + if "mni" in find_keyword or "mnc" in find_keyword: writer = MNIObjectWriter() else: raise IOError( - 'Wavefront obj requires a scene \n' + "Wavefront obj requires a scene \n" " for MNI obj, use '.mni.obj' extension" ) else: - raise IOError('.' + file_extension + ' is not supported by FURY') + raise IOError("." + file_extension + " is not supported by FURY") writer.SetFileName(file_name) writer = set_input(writer, polydata) - if color_array_name is not None and file_extension == 'ply': + if color_array_name is not None and file_extension == "ply": writer.SetArrayName(color_array_name) if binary: @@ -448,7 +453,7 @@ def load_sprite_sheet(sheet_path, nb_rows, nb_cols, as_vtktype=False): sprite_arr = sprite_sheet[box[0] : box[2], box[1] : box[3]] if as_vtktype: with InTemporaryDirectory() as tdir: - tmp_img_path = os.path.join(tdir, f'{row}{col}.png') + tmp_img_path = os.path.join(tdir, f"{row}{col}.png") save_image(sprite_arr, tmp_img_path, compression_quality=100) sprite_dicts[(row, col)] = load_image(tmp_img_path, as_vtktype=True) diff --git a/fury/layout.py b/fury/layout.py index 187ae7854..7647949b5 100644 --- a/fury/layout.py +++ b/fury/layout.py @@ -13,11 +13,10 @@ def apply(self, actors): positions = self.compute_positions(actors) for a, pos in zip(actors, positions): - if is_ui(a): a.position = (pos[0], pos[1]) else: - anchor = np.array(getattr(a, 'anchor', (0, 0, 0))) + anchor = np.array(getattr(a, "anchor", (0, 0, 0))) a.AddPosition(pos - (np.array(a.GetCenter()) + anchor)) def compute_positions(self, _actors): @@ -36,13 +35,12 @@ class GridLayout(Layout): def __init__( self, cell_padding=0, - cell_shape='rect', + cell_shape="rect", aspect_ratio=16 / 9.0, dim=None, position_offset=(0, 0, 0), ): - """ - + """Initialize the grid layout. Parameters ---------- cell_padding : 2-tuple of float or float (optional) @@ -63,6 +61,7 @@ def __init__( `aspect_ratio` will be ignored. position_offset: tuple (optional) Offset the grid by some factor + """ self.cell_shape = cell_shape self.aspect_ratio = aspect_ratio @@ -88,16 +87,15 @@ def get_cells_shape(self, actors): The 2D shape (on the xy-plane) of every actors. """ - - if self.cell_shape == 'rect': + if self.cell_shape == "rect": bounding_box_sizes = np.asarray(list(map(self.compute_sizes, actors))) cell_shape = np.max(bounding_box_sizes, axis=0)[:2] shapes = [cell_shape] * len(actors) - elif self.cell_shape == 'square': + elif self.cell_shape == "square": bounding_box_sizes = np.asarray(list(map(self.compute_sizes, actors))) cell_shape = np.max(bounding_box_sizes, axis=0)[:2] shapes = [(max(cell_shape),) * 2] * len(actors) - elif self.cell_shape == 'diagonal': + elif self.cell_shape == "diagonal": # Size of every cell corresponds to the diagonal # of the largest bounding box. diagonals = [] @@ -119,14 +117,17 @@ def get_cells_shape(self, actors): def compute_positions(self, actors): """Compute the 3D coordinates of some actors. The coordinates will lie on the xy-plane and form a 2D grid. + Parameters ---------- actors : list of `vtkProp3D` objects Actors to be layout in a grid manner. + Returns ------- list of 3-tuple The computed 3D coordinates of every actors. + """ shapes = self.get_cells_shape(actors) @@ -139,15 +140,17 @@ def compute_positions(self, actors): def compute_sizes(self, actor): """Compute the bounding box size of the actor/UI element + Parameters - --------- + ---------- actor: `vtkProp3D` or `UI` element Actor/UI element whose size is to be calculated + Returns ------- bounding box sizes: tuple - """ + """ if is_ui(actor): width, height = actor.size return (width, height, 0) @@ -158,8 +161,9 @@ def compute_sizes(self, actor): class HorizontalLayout(GridLayout): """Provide functionalities for laying out actors in a horizontal layout.""" - def __init__(self, cell_padding=0, cell_shape='rect'): - """ + def __init__(self, cell_padding=0, cell_shape="rect"): + """Initialize the Horizontal layout. + Parameters ---------- cell_padding : 2-tuple of float or float (optional) @@ -173,6 +177,7 @@ def __init__(self, cell_padding=0, cell_shape='rect'): 'square' ensures the cells are as wide as high. 'diagonal' ensures the content of the cells can be rotated without colliding with content of the neighboring cells. + """ super(HorizontalLayout, self).__init__( cell_padding=cell_padding, cell_shape=cell_shape @@ -181,14 +186,17 @@ def __init__(self, cell_padding=0, cell_shape='rect'): def compute_positions(self, actors): """Compute the 3D coordinates of some actors. The coordinates will lie on the xy-plane and form a horizontal stack. + Parameters ---------- actors : list of `vtkProp3D` objects Actors to be layout in a horizontal fashion. + Returns ------- list of 3-tuple The computed 3D coordinates of every actors. + """ positions = [ np.asarray([0, 0, 0]), @@ -208,8 +216,9 @@ def compute_positions(self, actors): class VerticalLayout(GridLayout): """Provide functionalities for laying out actors in a vertical stack.""" - def __init__(self, cell_padding=0, cell_shape='rect'): - """ + def __init__(self, cell_padding=0, cell_shape="rect"): + """Initialize the Vertical layout. + Parameters ---------- cell_padding : 2-tuple of float or float (optional) @@ -223,6 +232,7 @@ def __init__(self, cell_padding=0, cell_shape='rect'): 'square' ensures the cells are as wide as high. 'diagonal' ensures the content of the cells can be rotated without colliding with content of the neighboring cells. + """ super(VerticalLayout, self).__init__( cell_padding=cell_padding, cell_shape=cell_shape @@ -230,14 +240,17 @@ def __init__(self, cell_padding=0, cell_shape='rect'): def compute_positions(self, actors): """Compute the 3D coordinates of some actors. + Parameters ---------- actors : list of `vtkProp3D` objects Actors to be layout in a vertical stack. + Returns ------- list of 3-tuple The computed 3D coordinates of every actors. + """ positions = [ np.asarray([0, 0, 0]), @@ -257,8 +270,9 @@ def compute_positions(self, actors): class XLayout(HorizontalLayout): """Provide functionalities for laying out actors along x-axis.""" - def __init__(self, direction='x+', cell_padding=0, cell_shape='rect'): - """ + def __init__(self, direction="x+", cell_padding=0, cell_shape="rect"): + """Initialize the X layout. + Parameters ---------- direction: str, optional @@ -276,11 +290,12 @@ def __init__(self, direction='x+', cell_padding=0, cell_shape='rect'): 'square' ensures the cells are as wide as high. 'diagonal' ensures the content of the cells can be rotated without colliding with content of the neighboring cells. + """ self.direction = direction.lower() - if self.direction not in ['x+', 'x-']: - raise ValueError(f'{direction} is not a valid direction') + if self.direction not in ["x+", "x-"]: + raise ValueError(f"{direction} is not a valid direction") super(XLayout, self).__init__(cell_padding=cell_padding, cell_shape=cell_shape) @@ -299,7 +314,7 @@ def get_cells_shape(self, actors): The 2D shape (on the xy-plane) of every actors. """ - if self.direction == 'x-': + if self.direction == "x-": actors = actors[::-1] return super().get_cells_shape(actors) @@ -319,15 +334,16 @@ def compute_positions(self, actors): ------- list of 3-tuple The computed 3D coordinates of every actors. + """ - if self.direction == 'x-': + if self.direction == "x-": actors = actors[::-1] return super().compute_positions(actors) def apply(self, actors): """Position the actors according to a certain layout.""" - if self.direction == 'x-': + if self.direction == "x-": actors = actors[::-1] return super().apply(actors) @@ -336,8 +352,9 @@ def apply(self, actors): class YLayout(VerticalLayout): """Provide functionalities for laying out actors along y-axis.""" - def __init__(self, direction='y+', cell_padding=0, cell_shape='rect'): - """ + def __init__(self, direction="y+", cell_padding=0, cell_shape="rect"): + """Initialize the Y layout. + Parameters ---------- direction: str, optional @@ -355,11 +372,12 @@ def __init__(self, direction='y+', cell_padding=0, cell_shape='rect'): 'square' ensures the cells are as wide as high. 'diagonal' ensures the content of the cells can be rotated without colliding with content of the neighboring cells. + """ self.direction = direction.lower() - if self.direction not in ['y+', 'y-']: - raise ValueError(f'{direction} is not a valid direction') + if self.direction not in ["y+", "y-"]: + raise ValueError(f"{direction} is not a valid direction") super(YLayout, self).__init__(cell_padding=cell_padding, cell_shape=cell_shape) @@ -378,7 +396,7 @@ def get_cells_shape(self, actors): The 2D shape (on the xy-plane) of every actors. """ - if self.direction == 'y-': + if self.direction == "y-": actors = actors[::-1] return super().get_cells_shape(actors) @@ -398,15 +416,16 @@ def compute_positions(self, actors): ------- list of 3-tuple The computed 3D coordinates of every actors. + """ - if self.direction == 'y-': + if self.direction == "y-": actors = actors[::-1] return super().compute_positions(actors) def apply(self, actors): """Position the actors according to a certain layout.""" - if self.direction == 'y-': + if self.direction == "y-": actors = actors[::-1] return super().apply(actors) @@ -415,8 +434,9 @@ def apply(self, actors): class ZLayout(GridLayout): """Provide functionalities for laying out actors along z-axis.""" - def __init__(self, direction='z+', cell_padding=0, cell_shape='rect'): - """ + def __init__(self, direction="z+", cell_padding=0, cell_shape="rect"): + """Initialize the Z layout. + Parameters ---------- direction: str, optional @@ -434,11 +454,12 @@ def __init__(self, direction='z+', cell_padding=0, cell_shape='rect'): 'square' ensures the cells are as wide as high. 'diagonal' ensures the content of the cells can be rotated without colliding with content of the neighboring cells. + """ self.direction = direction.lower() - if self.direction not in ['z+', 'z-']: - raise ValueError(f'{direction} is not a valid direction') + if self.direction not in ["z+", "z-"]: + raise ValueError(f"{direction} is not a valid direction") super(ZLayout, self).__init__(cell_padding=cell_padding, cell_shape=cell_shape) @@ -457,14 +478,14 @@ def get_cells_shape(self, actors): The shape (on the z-plane) of every actors. """ - if self.direction == 'z-': + if self.direction == "z-": actors = actors[::-1] - if self.cell_shape == 'rect' or self.cell_shape == 'square': + if self.cell_shape == "rect" or self.cell_shape == "square": bounding_box_sizes = np.asarray(list(map(get_bounding_box_sizes, actors))) cell_shape = np.max(bounding_box_sizes, axis=0)[2] shapes = [cell_shape] * len(actors) - elif self.cell_shape == 'diagonal': + elif self.cell_shape == "diagonal": # Size of every cell corresponds to the diagonal # of the largest bounding box. longest_diagonal = np.max([a.GetLength() for a in actors]) @@ -486,8 +507,9 @@ def compute_positions(self, actors): ------- list of 3-tuple The computed 3D coordinates of every actors. + """ - if self.direction == 'z-': + if self.direction == "z-": actors = actors[::-1] positions = [ @@ -505,7 +527,7 @@ def compute_positions(self, actors): def apply(self, actors): """Position the actors according to a certain layout.""" - if self.direction == 'z-': + if self.direction == "z-": actors = actors[::-1] return super().apply(actors) diff --git a/fury/lib.py b/fury/lib.py index b1ba71259..dc0bf6a47 100644 --- a/fury/lib.py +++ b/fury/lib.py @@ -1,3 +1,5 @@ +from vtkmodules.util import colors, numpy_support # type: ignore # noqa: F401 +from vtkmodules.util.misc import calldata_type # type: ignore # noqa: F401 import vtkmodules.vtkCommonCore as ccvtk # type: ignore import vtkmodules.vtkCommonDataModel as cdmvtk # type: ignore import vtkmodules.vtkCommonExecutionModel as cemvtk # type: ignore @@ -11,21 +13,19 @@ import vtkmodules.vtkFiltersModeling as fmvtk # type: ignore import vtkmodules.vtkFiltersSources as fsvtk # type: ignore import vtkmodules.vtkFiltersTexture as ftvtk # type: ignore -import vtkmodules.vtkImagingCore as icvtk # type: ignore -import vtkmodules.vtkInteractionStyle as isvtk # type: ignore import vtkmodules.vtkIOGeometry as iogvtk # type: ignore import vtkmodules.vtkIOImage as ioivtk # type: ignore import vtkmodules.vtkIOLegacy as iolvtk # type: ignore import vtkmodules.vtkIOMINC as iomincvtk # type: ignore import vtkmodules.vtkIOPLY as ioplyvtk # type: ignore import vtkmodules.vtkIOXML as ioxmlvtk # type: ignore +import vtkmodules.vtkImagingCore as icvtk # type: ignore +import vtkmodules.vtkInteractionStyle as isvtk # type: ignore import vtkmodules.vtkRenderingAnnotation as ravtk # type: ignore import vtkmodules.vtkRenderingCore as rcvtk # type: ignore import vtkmodules.vtkRenderingFreeType as rftvtk # type: ignore import vtkmodules.vtkRenderingLOD as rlodvtk # type: ignore import vtkmodules.vtkRenderingOpenGL2 as roglvtk # type: ignore -from vtkmodules.util import colors, numpy_support # type: ignore # noqa: F401 -from vtkmodules.util.misc import calldata_type # type: ignore # noqa: F401 VTK_VERSION = ccvtk.vtkVersion.GetVTKVersion() diff --git a/fury/material.py b/fury/material.py index 34ea358f1..22891395a 100644 --- a/fury/material.py +++ b/fury/material.py @@ -1,10 +1,13 @@ import os import warnings - -from fury.shaders import (add_shader_callback, compose_shader, - import_fury_shader, shader_to_actor) from fury.lib import VTK_OBJECT, calldata_type +from fury.shaders import ( + add_shader_callback, + compose_shader, + import_fury_shader, + shader_to_actor, +) class __PBRParams: @@ -41,16 +44,26 @@ class __PBRParams: coat_ior : float Index of refraction of the coat material. Default is 1.5. Values must be between 1.0 and 2.3. + """ - def __init__(self, actor_properties, metallic, roughness, - anisotropy, anisotropy_rotation, coat_strength, - coat_roughness, base_ior, coat_ior): + + def __init__( + self, + actor_properties, + metallic, + roughness, + anisotropy, + anisotropy_rotation, + coat_strength, + coat_roughness, + base_ior, + coat_ior, + ): self.__actor_properties = actor_properties self.__actor_properties.SetMetallic(metallic) self.__actor_properties.SetRoughness(roughness) self.__actor_properties.SetAnisotropy(anisotropy) - self.__actor_properties.SetAnisotropyRotation( - anisotropy_rotation) + self.__actor_properties.SetAnisotropyRotation(anisotropy_rotation) self.__actor_properties.SetCoatStrength(coat_strength) self.__actor_properties.SetCoatRoughness(coat_roughness) self.__actor_properties.SetBaseIOR(base_ior) @@ -121,9 +134,17 @@ def coat_ior(self, coat_ior): self.__actor_properties.SetCoatIOR(coat_ior) -def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, - anisotropy_rotation=0, coat_strength=0, coat_roughness=0, - base_ior=1.5, coat_ior=2): +def manifest_pbr( + actor, + metallic=0, + roughness=0.5, + anisotropy=0, + anisotropy_rotation=0, + coat_strength=0, + coat_roughness=0, + base_ior=1.5, + coat_ior=2, +): """Apply VTK's Physically Based Rendering properties to the selected actor. Parameters @@ -159,25 +180,48 @@ def manifest_pbr(actor, metallic=0, roughness=.5, anisotropy=0, prop = actor.GetProperty() try: prop.SetInterpolationToPBR() - pbr_params = __PBRParams(prop, metallic, roughness, anisotropy, - anisotropy_rotation, coat_strength, - coat_roughness, base_ior, coat_ior) + pbr_params = __PBRParams( + prop, + metallic, + roughness, + anisotropy, + anisotropy_rotation, + coat_strength, + coat_roughness, + base_ior, + coat_ior, + ) return pbr_params except AttributeError: warnings.warn( - 'PBR interpolation cannot be applied to this actor. The ' - 'material will not be applied.') + "PBR interpolation cannot be applied to this actor. The " + "material will not be applied.", + stacklevel=2, + ) return None except AttributeError: - warnings.warn('Actor does not have the attribute property. This ' - 'material will not be applied.') + warnings.warn( + "Actor does not have the attribute property. This " + "material will not be applied.", + stacklevel=2, + ) return None -def manifest_principled(actor, subsurface=0, metallic=0, specular=0, - specular_tint=0, roughness=0, anisotropic=0, - anisotropic_direction=[0, 1, .5], sheen=0, - sheen_tint=0, clearcoat=0, clearcoat_gloss=0): +def manifest_principled( + actor, + subsurface=0, + metallic=0, + specular=0, + specular_tint=0, + roughness=0, + anisotropic=0, + anisotropic_direction=None, + sheen=0, + sheen_tint=0, + clearcoat=0, + clearcoat_gloss=0, +): """Apply the Principled Shading properties to the selected actor. Parameters @@ -215,17 +259,24 @@ def manifest_principled(actor, subsurface=0, metallic=0, specular=0, Dictionary containing the Principled Shading parameters. """ + if anisotropic_direction is None: + anisotropic_direction = [0, 1, 0.5] try: prop = actor.GetProperty() principled_params = { - 'subsurface': subsurface, 'metallic': metallic, - 'specular': specular, 'specular_tint': specular_tint, - 'roughness': roughness, 'anisotropic': anisotropic, - 'anisotropic_direction': anisotropic_direction, 'sheen': sheen, - 'sheen_tint': sheen_tint, 'clearcoat': clearcoat, - 'clearcoat_gloss': clearcoat_gloss + "subsurface": subsurface, + "metallic": metallic, + "specular": specular, + "specular_tint": specular_tint, + "roughness": roughness, + "anisotropic": anisotropic, + "anisotropic_direction": anisotropic_direction, + "sheen": sheen, + "sheen_tint": sheen_tint, + "clearcoat": clearcoat, + "clearcoat_gloss": clearcoat_gloss, } prop.SetSpecular(specular) @@ -233,38 +284,31 @@ def manifest_principled(actor, subsurface=0, metallic=0, specular=0, @calldata_type(VTK_OBJECT) def uniforms_callback(_caller, _event, calldata=None): if calldata is not None: + calldata.SetUniformf("subsurface", principled_params["subsurface"]) + calldata.SetUniformf("metallic", principled_params["metallic"]) + calldata.SetUniformf("specularTint", principled_params["specular_tint"]) + calldata.SetUniformf("roughness", principled_params["roughness"]) + calldata.SetUniformf("anisotropic", principled_params["anisotropic"]) + calldata.SetUniformf("sheen", principled_params["sheen"]) + calldata.SetUniformf("sheenTint", principled_params["sheen_tint"]) + calldata.SetUniformf("clearcoat", principled_params["clearcoat"]) calldata.SetUniformf( - 'subsurface', principled_params['subsurface']) - calldata.SetUniformf( - 'metallic', principled_params['metallic']) - calldata.SetUniformf( - 'specularTint', principled_params['specular_tint']) - calldata.SetUniformf( - 'roughness', principled_params['roughness']) - calldata.SetUniformf( - 'anisotropic', principled_params['anisotropic']) - calldata.SetUniformf('sheen', principled_params['sheen']) - calldata.SetUniformf( - 'sheenTint', principled_params['sheen_tint']) - calldata.SetUniformf( - 'clearcoat', principled_params['clearcoat']) - calldata.SetUniformf( - 'clearcoatGloss', principled_params['clearcoat_gloss']) + "clearcoatGloss", principled_params["clearcoat_gloss"] + ) calldata.SetUniform3f( - 'anisotropicDirection', principled_params[ - 'anisotropic_direction']) + "anisotropicDirection", principled_params["anisotropic_direction"] + ) add_shader_callback(actor, uniforms_callback) # Start of shader implementation # Adding required constants - pi = '#define PI 3.14159265359' + pi = "#define PI 3.14159265359" # Adding uniforms - uniforms = \ - """ + uniforms = """ uniform float subsurface; uniform float metallic; uniform float specularTint; @@ -274,237 +318,273 @@ def uniforms_callback(_caller, _event, calldata=None): uniform float sheenTint; uniform float clearcoat; uniform float clearcoatGloss; - + uniform vec3 anisotropicDirection; """ # Importing functions in order # Importing utility functions - square = import_fury_shader(os.path.join('utils', 'square.glsl')) - pow5 = import_fury_shader(os.path.join('utils', 'pow5.glsl')) + square = import_fury_shader(os.path.join("utils", "square.glsl")) + pow5 = import_fury_shader(os.path.join("utils", "pow5.glsl")) # Importing utility function to update the tangent and bitangent # vectors given a direction of anisotropy update_tan_bitan = import_fury_shader( - os.path.join('utils', 'update_tan_bitan.glsl') + os.path.join("utils", "update_tan_bitan.glsl") ) # Importing color conversion gamma to linear space function gamma_to_linear = import_fury_shader( - os.path.join('lighting', 'gamma_to_linear.frag') + os.path.join("lighting", "gamma_to_linear.frag") ) # Importing color conversion linear to gamma space function linear_to_gamma = import_fury_shader( - os.path.join('lighting', 'linear_to_gamma.frag') + os.path.join("lighting", "linear_to_gamma.frag") ) # Importing linear-space CIE luminance tint approximation function cie_color_tint = import_fury_shader( - os.path.join('lighting', 'cie_color_tint.frag') + os.path.join("lighting", "cie_color_tint.frag") ) # Importing Schlick's weight approximation of the Fresnel equation schlick_weight = import_fury_shader( - os.path.join('lighting', 'schlick_weight.frag') + os.path.join("lighting", "schlick_weight.frag") ) # Importing Normal Distribution Function (NDF): Generalized # Trowbridge-Reitz with param gamma=1 (D_{GTR_1}) needed for the Clear # Coat lobe - gtr1 = import_fury_shader( - os.path.join('lighting', 'ndf', 'gtr1.frag') - ) + gtr1 = import_fury_shader(os.path.join("lighting", "ndf", "gtr1.frag")) # Importing Normal Distribution Function (NDF): Generalized # Trowbridge-Reitz with param gamma=2 (D_{GTR_2}) needed for the # Isotropic Specular lobe - gtr2 = import_fury_shader( - os.path.join('lighting', 'ndf', 'gtr2.frag') - ) + gtr2 = import_fury_shader(os.path.join("lighting", "ndf", "gtr2.frag")) # Importing Normal Distribution Function (NDF): Anisotropic form of the # Generalized Trowbridge-Reitz with param gamma=2 # (D_{GTR_2anisotropic}) needed for the respective Specular lobe gtr2_anisotropic = import_fury_shader( - os.path.join('lighting', 'ndf', 'gtr2_anisotropic.frag') + os.path.join("lighting", "ndf", "gtr2_anisotropic.frag") ) # Importing Geometry Shadowing and Masking Function (GF): Smith Ground # Glass Unknown (G_{GGX}) needed for the Isotropic Specular and Clear # Coat lobes - smith_ggx = import_fury_shader( - os.path.join('lighting', 'gf', 'smith_ggx.frag') - ) + smith_ggx = import_fury_shader(os.path.join("lighting", "gf", "smith_ggx.frag")) # Importing Geometry Shadowing and Masking Function (GF): Anisotropic # form of the Smith Ground Glass Unknown (G_{GGXanisotropic}) needed # for the respective Specular lobe smith_ggx_anisotropic = import_fury_shader( - os.path.join('lighting', 'gf', 'smith_ggx_anisotropic.frag') + os.path.join("lighting", "gf", "smith_ggx_anisotropic.frag") ) # Importing Principled components functions diffuse = import_fury_shader( - os.path.join('lighting', 'principled', 'diffuse.frag') + os.path.join("lighting", "principled", "diffuse.frag") ) subsurface = import_fury_shader( - os.path.join('lighting', 'principled', 'subsurface.frag') - ) - sheen = import_fury_shader( - os.path.join('lighting', 'principled', 'sheen.frag') + os.path.join("lighting", "principled", "subsurface.frag") ) + sheen = import_fury_shader(os.path.join("lighting", "principled", "sheen.frag")) specular_isotropic = import_fury_shader( - os.path.join('lighting', 'principled', 'specular_isotropic.frag') + os.path.join("lighting", "principled", "specular_isotropic.frag") ) specular_anisotropic = import_fury_shader( - os.path.join('lighting', 'principled', 'specular_anisotropic.frag') + os.path.join("lighting", "principled", "specular_anisotropic.frag") ) clearcoat = import_fury_shader( - os.path.join('lighting', 'principled', 'clearcoat.frag') + os.path.join("lighting", "principled", "clearcoat.frag") ) # Putting all the functions together before passing them to the actor - fs_dec = compose_shader([ - pi, uniforms, square, pow5, update_tan_bitan, gamma_to_linear, - linear_to_gamma, cie_color_tint, schlick_weight, gtr1, gtr2, - gtr2_anisotropic, smith_ggx, smith_ggx_anisotropic, diffuse, - subsurface, sheen, specular_isotropic, specular_anisotropic, - clearcoat - ]) + fs_dec = compose_shader( + [ + pi, + uniforms, + square, + pow5, + update_tan_bitan, + gamma_to_linear, + linear_to_gamma, + cie_color_tint, + schlick_weight, + gtr1, + gtr2, + gtr2_anisotropic, + smith_ggx, + smith_ggx_anisotropic, + diffuse, + subsurface, + sheen, + specular_isotropic, + specular_anisotropic, + clearcoat, + ] + ) # Adding shader functions to actor - shader_to_actor(actor, 'fragment', decl_code=fs_dec) + shader_to_actor(actor, "fragment", decl_code=fs_dec) # Start of the implementation code start_comment = "//Disney's Principled BRDF" # Preparing vectors and values - normal = 'vec3 normal = normalVCVSOutput;' + normal = "vec3 normal = normalVCVSOutput;" # VTK's default system is retroreflective, which means view = light - view = 'vec3 view = normalize(-vertexVC.xyz);' + view = "vec3 view = normalize(-vertexVC.xyz);" # Since VTK's default setup is retroreflective we only need to # calculate one single dot product - dot_n_v = 'float dotNV = clamp(dot(normal, view), 1e-5, 1);' + dot_n_v = "float dotNV = clamp(dot(normal, view), 1e-5, 1);" - dot_n_v_validation = \ - """ + dot_n_v_validation = """ if(dotNV < 0) fragOutput0 = vec4(vec3(0), opacity); """ # To work with anisotropic distributions is necessary to have a tangent # and bitangent vector per point on the surface - tangent = 'vec3 tangent = vec3(.0);' - bitangent = 'vec3 bitangent = vec3(.0);' + tangent = "vec3 tangent = vec3(.0);" + bitangent = "vec3 bitangent = vec3(.0);" # The shader function updateTanBitan aligns tangents and bitangents # according to a direction of anisotropy - update_aniso_vecs = \ - """ + update_aniso_vecs = """ updateTanBitan(normal, anisotropicDirection, tangent, bitangent); """ # Calculating dot products with tangent and bitangent - dot_t_v = 'float dotTV = dot(tangent, view);' - dot_b_v = 'float dotBV = dot(bitangent, view);' + dot_t_v = "float dotTV = dot(tangent, view);" + dot_b_v = "float dotBV = dot(bitangent, view);" # Converting color to linear space - linear_color = 'vec3 linColor = gamma2Linear(diffuseColor);' + linear_color = "vec3 linColor = gamma2Linear(diffuseColor);" # Calculating linear-space CIE luminance tint approximation - tint = 'vec3 tint = calculateTint(linColor);' + tint = "vec3 tint = calculateTint(linColor);" # Since VTK's default setup is retroreflective we only need to # calculate one single Schlick's weight - fsw = 'float fsw = schlickWeight(dotNV);' + fsw = "float fsw = schlickWeight(dotNV);" # Calculating the diffuse coefficient - diff_coeff = \ - """ + diff_coeff = """ float diffCoeff = evaluateDiffuse(roughness, fsw, fsw, dotNV); """ # Calculating the subsurface coefficient - subsurf_coeff = \ - """ - float subsurfCoeff = evaluateSubsurface(roughness, fsw, fsw, dotNV, + subsurf_coeff = """ + float subsurfCoeff = evaluateSubsurface(roughness, fsw, fsw, dotNV, dotNV, dotNV); """ # Calculating the sheen irradiance - sheen_rad = \ - """ + sheen_rad = """ vec3 sheenRad = evaluateSheen(sheen, sheenTint, tint, fsw); """ # Calculating the specular irradiance - spec_rad = \ - """ - vec3 specRad = evaluateSpecularAnisotropic(specularIntensity, - specularTint, metallic, anisotropic, roughness, tint, linColor, - fsw, dotNV, dotTV, dotBV, dotNV, dotTV, dotBV, dotNV, dotTV, + spec_rad = """ + vec3 specRad = evaluateSpecularAnisotropic(specularIntensity, + specularTint, metallic, anisotropic, roughness, tint, linColor, + fsw, dotNV, dotTV, dotBV, dotNV, dotTV, dotBV, dotNV, dotTV, dotBV); """ # Calculating the clear coat coefficient - clear_coat_coef = \ - """ - float coatCoeff = evaluateClearcoat(clearcoat, clearcoatGloss, fsw, + clear_coat_coef = """ + float coatCoeff = evaluateClearcoat(clearcoat, clearcoatGloss, fsw, dotNV, dotNV, dotNV); """ # Starting to put all together # Initializing the radiance vector - radiance = 'vec3 rad = (1 / PI) * linColor;' + radiance = "vec3 rad = (1 / PI) * linColor;" # Adding mix between the diffuse and the subsurface coefficients # controlled by the subsurface parameter - diff_subsurf_mix = 'rad *= mix(diffCoeff, subsurfCoeff, subsurface);' + diff_subsurf_mix = "rad *= mix(diffCoeff, subsurfCoeff, subsurface);" # Adding sheen radiance - sheen_add = 'rad += sheenRad;' + sheen_add = "rad += sheenRad;" # Balancing energy using metallic - metallic_balance = 'rad *= (1 - metallic);' + metallic_balance = "rad *= (1 - metallic);" # Adding specular radiance - specular_add = 'rad += specRad;' + specular_add = "rad += specRad;" # Adding clear coat coefficient - clearcoat_add = 'rad += coatCoeff;' + clearcoat_add = "rad += coatCoeff;" # Initializing the color vector using the final radiance and VTK's # additional information - color = 'vec3 color = rad * lightColor0;' + color = "vec3 color = rad * lightColor0;" # Converting color back to gamma space - gamma_color = 'color = linear2Gamma(color);' + gamma_color = "color = linear2Gamma(color);" # Clamping color values - color_clamp = 'color = clamp(color, vec3(0), vec3(1));' + color_clamp = "color = clamp(color, vec3(0), vec3(1));" # Fragment shader output - frag_output = 'fragOutput0 = vec4(color, opacity);' + frag_output = "fragOutput0 = vec4(color, opacity);" # Putting all the implementation together before passing it to the # actor - fs_impl = compose_shader([ - start_comment, normal, view, dot_n_v, dot_n_v_validation, tangent, - bitangent, update_aniso_vecs, dot_t_v, dot_b_v, linear_color, tint, - fsw, diff_coeff, subsurf_coeff, sheen_rad, spec_rad, - clear_coat_coef, radiance, diff_subsurf_mix, sheen_add, - metallic_balance, specular_add, clearcoat_add, color, gamma_color, - color_clamp, frag_output - ]) + fs_impl = compose_shader( + [ + start_comment, + normal, + view, + dot_n_v, + dot_n_v_validation, + tangent, + bitangent, + update_aniso_vecs, + dot_t_v, + dot_b_v, + linear_color, + tint, + fsw, + diff_coeff, + subsurf_coeff, + sheen_rad, + spec_rad, + clear_coat_coef, + radiance, + diff_subsurf_mix, + sheen_add, + metallic_balance, + specular_add, + clearcoat_add, + color, + gamma_color, + color_clamp, + frag_output, + ] + ) # Adding shader implementation to actor - shader_to_actor(actor, 'fragment', impl_code=fs_impl, block='light') + shader_to_actor(actor, "fragment", impl_code=fs_impl, block="light") return principled_params except AttributeError: - warnings.warn('Actor does not have the attribute property. This ' - 'material will not be applied.') + warnings.warn( + "Actor does not have the attribute property. This " + "material will not be applied.", + stacklevel=2, + ) return None -def manifest_standard(actor, ambient_level=0, ambient_color=(1, 1, 1), - diffuse_level=1, diffuse_color=(1, 1, 1), - specular_level=0, specular_color=(1, 1, 1), - specular_power=1, interpolation='gouraud'): +def manifest_standard( + actor, + ambient_level=0, + ambient_color=(1, 1, 1), + diffuse_level=1, + diffuse_color=(1, 1, 1), + specular_level=0, + specular_color=(1, 1, 1), + specular_power=1, + interpolation="gouraud", +): """Apply the standard material to the selected actor. Parameters @@ -537,17 +617,19 @@ def manifest_standard(actor, ambient_level=0, ambient_color=(1, 1, 1), interpolation = interpolation.lower() - if interpolation == 'flat': + if interpolation == "flat": prop.SetInterpolationToFlat() - elif interpolation == 'gouraud': + elif interpolation == "gouraud": prop.SetInterpolationToGouraud() - elif interpolation == 'phong': + elif interpolation == "phong": prop.SetInterpolationToPhong() else: - message = 'Unknown interpolation. Ignoring "{}" interpolation ' \ - 'option and using the default ("{}") option.' - message = message.format(interpolation, 'gouraud') - warnings.warn(message) + message = ( + 'Unknown interpolation. Ignoring "{}" interpolation ' + 'option and using the default ("{}") option.' + ) + message = message.format(interpolation, "gouraud") + warnings.warn(message, stacklevel=2) prop.SetAmbient(ambient_level) prop.SetAmbientColor(ambient_color) @@ -557,7 +639,9 @@ def manifest_standard(actor, ambient_level=0, ambient_color=(1, 1, 1), prop.SetSpecularColor(specular_color) prop.SetSpecularPower(specular_power) except AttributeError: - warnings.warn('Actor does not have the attribute property. This ' - 'material will not be applied.') + warnings.warn( + "Actor does not have the attribute property. This " + "material will not be applied.", + stacklevel=2, + ) return - diff --git a/fury/molecular.py b/fury/molecular.py index 53648051e..394591297 100644 --- a/fury/molecular.py +++ b/fury/molecular.py @@ -13,9 +13,7 @@ VTK_UNSIGNED_SHORT, Actor, DataSetAttributes, -) -from fury.lib import Molecule as Mol -from fury.lib import ( + Molecule as Mol, OpenGLMoleculeMapper, PeriodicTable, PolyData, @@ -23,8 +21,8 @@ ProteinRibbonFilter, SimpleBondPerceiver, StringArray, + numpy_support as nps, ) -from fury.lib import numpy_support as nps from fury.utils import numpy_to_vtk_points @@ -92,13 +90,14 @@ def __init__( atoms present in the molecule. Array containing a bool value to indicate if an atom is a heteroatom. + """ if atomic_numbers is None and coords is None: self.Initialize() elif not isinstance(atomic_numbers, np.ndarray) or not isinstance( coords, np.ndarray ): - raise ValueError('atom_types and coords must be numpy arrays.') + raise ValueError("atom_types and coords must be numpy arrays.") elif len(atomic_numbers) == len(coords): self.atom_names = atom_names self.model = model @@ -109,7 +108,7 @@ def __init__( self.is_hetatm = is_hetatm coords = numpy_to_vtk_points(coords) atom_nums = nps.numpy_to_vtk(atomic_numbers, array_type=VTK_UNSIGNED_SHORT) - atom_nums.SetName('Atomic Numbers') + atom_nums.SetName("Atomic Numbers") fieldData = DataSetAttributes() fieldData.AddArray(atom_nums) self.Initialize(coords, fieldData) @@ -117,8 +116,8 @@ def __init__( n1 = len(coords) n2 = len(atomic_numbers) raise ValueError( - 'Mismatch in length of atomic_numbers({0}) and ' - 'length of atomic_coords({1}).'.format(n1, n2) + "Mismatch in length of atomic_numbers({0}) and " + "length of atomic_coords({1}).".format(n1, n2) ) @property @@ -147,6 +146,7 @@ def add_atom(molecule, atomic_num, x_coord, y_coord, z_coord): y-coordinate of the atom. z_coord : float z-coordinate of the atom. + """ molecule.AppendAtom(atomic_num, x_coord, y_coord, z_coord) @@ -171,6 +171,7 @@ def add_bond(molecule, atom1_index, atom2_index, bond_order=1): Ensure that the total number of bonds between two atoms doesn't exceed 3. Calling ``add_bond`` to add bonds between atoms that already have a triple bond between them leads to erratic behavior and must be avoided. + """ molecule.AppendBond(atom1_index, atom2_index, bond_order) @@ -186,6 +187,7 @@ def get_atomic_number(molecule, atom_index): The molecule to which the atom belongs. atom_index : int Index of the atom whose atomic number is to be obtained. + """ return molecule.GetAtomAtomicNumber(atom_index) @@ -204,6 +206,7 @@ def set_atomic_number(molecule, atom_index, atomic_num): Index of the atom to whom the atomic number is to be assigned. atom_num : int Atomic number to be assigned to the atom. + """ molecule.SetAtomAtomicNumber(atom_index, atomic_num) @@ -219,6 +222,7 @@ def get_atomic_position(molecule, atom_index): The molecule to which the atom belongs. atom_index : int Index of the atom whose atomic coordinates are to be obtained. + """ return molecule.GetAtomPosition(atom_index) @@ -241,6 +245,7 @@ def set_atomic_position(molecule, atom_index, x_coord, y_coord, z_coord): y-coordinate of the atom. z_coord : float z-coordinate of the atom. + """ molecule.SetAtomPosition(atom_index, x_coord, y_coord, z_coord) @@ -257,6 +262,7 @@ def get_bond_order(molecule, bond_index): The molecule to which the bond belongs. bond_index : int Index of the bond whose order is to be obtained. + """ return molecule.GetBondOrder(bond_index) @@ -275,6 +281,7 @@ def set_bond_order(molecule, bond_index, bond_order): Index of the bond whose order is to be assigned. bond_order : int Bond order (whether it's a single/double/triple bond). + """ return molecule.SetBondOrder(bond_index, bond_order) @@ -287,6 +294,7 @@ def get_all_atomic_numbers(molecule): ---------- molecule : Molecule The molecule whose atomic number array is to be obtained. + """ return nps.vtk_to_numpy(molecule.GetAtomicNumberArray()) @@ -299,6 +307,7 @@ def get_all_bond_orders(molecule): ---------- molecule : Molecule The molecule whose bond types array is to be obtained. + """ return nps.vtk_to_numpy(molecule.GetBondOrdersArray()) @@ -311,13 +320,13 @@ def get_all_atomic_positions(molecule): ---------- molecule : Molecule The molecule whose atomic position array is to be obtained. + """ return nps.vtk_to_numpy(molecule.GetAtomicPositionArray().GetData()) def deep_copy_molecule(molecule1, molecule2): - """ - Deep copies the atomic information (atoms and bonds) from molecule2 into + """Deep copies the atomic information (atoms and bonds) from molecule2 into molecule1. Parameters @@ -326,13 +335,13 @@ def deep_copy_molecule(molecule1, molecule2): The molecule to which the atomic information is copied. molecule2 : Molecule The molecule from which the atomic information is copied. + """ molecule1.DeepCopyStructure(molecule2) def compute_bonding(molecule): - """ - Uses `vtkSimpleBondPerceiver` to generate bonding information for a + """Uses `vtkSimpleBondPerceiver` to generate bonding information for a molecule. `vtkSimpleBondPerceiver` performs a simple check of all interatomic distances and adds a single bond between atoms that are reasonably @@ -349,6 +358,7 @@ def compute_bonding(molecule): This algorithm does not consider valences, hybridization, aromaticity, or anything other than atomic separations. It will not produce anything other than single bonds. + """ bonder = SimpleBondPerceiver() bonder.SetInputData(molecule) @@ -376,6 +386,7 @@ def atomic_symbol(self, atomic_number): ---------- atomic_number : int Atomic number of the element whose symbol is to be obtained. + """ return self.GetSymbol(atomic_number) @@ -386,6 +397,7 @@ def element_name(self, atomic_number): ---------- atomic_number : int Atomic number of the element whose name is to be obtained. + """ return self.GetElementName(atomic_number) @@ -397,10 +409,11 @@ def atomic_number(self, element_name): ---------- element_name : string Name of the element whose atomic number is to be obtained. + """ return self.GetAtomicNumber(element_name) - def atomic_radius(self, atomic_number, radius_type='VDW'): + def atomic_radius(self, atomic_number, radius_type="VDW"): """Given an atomic number, return either the covalent radius of the atom (in Å) or return the Van Der Waals radius (in Å) of the atom depending on radius_type. @@ -416,15 +429,16 @@ def atomic_radius(self, atomic_number, radius_type='VDW'): * 'Covalent' : for covalent radius of the atom Default: 'VDW' + """ radius_type = radius_type.lower() - if radius_type == 'vdw': + if radius_type == "vdw": return self.GetVDWRadius(atomic_number) - elif radius_type == 'covalent': + elif radius_type == "covalent": return self.GetCovalentRadius(atomic_number) else: raise ValueError( - 'Incorrect radius_type specified. Please choose' + "Incorrect radius_type specified. Please choose" ' from "VDW" or "Covalent".' ) @@ -437,12 +451,13 @@ def atom_color(self, atomic_number): ---------- atomicNumber : int Atomic number of the element whose RGB tuple is to be obtained. + """ rgb = np.array(self.GetDefaultRGBTuple(atomic_number)) return rgb -def sphere_cpk(molecule, colormode='discrete'): +def sphere_cpk(molecule, colormode="discrete"): """Create an actor for sphere molecular representation. It's also referred to as CPK model and space-filling model. @@ -472,6 +487,7 @@ def sphere_cpk(molecule, colormode='discrete'): Peptides, and Proteins `Review of Scientific Instruments 1953, 24 (8), 621-627. `_ + """ colormode = colormode.lower() msp_mapper = OpenGLMoleculeMapper() @@ -480,13 +496,13 @@ def sphere_cpk(molecule, colormode='discrete'): msp_mapper.SetRenderBonds(False) msp_mapper.SetAtomicRadiusTypeToVDWRadius() msp_mapper.SetAtomicRadiusScaleFactor(1) - if colormode == 'discrete': + if colormode == "discrete": msp_mapper.SetAtomColorMode(1) - elif colormode == 'single': + elif colormode == "single": msp_mapper.SetAtomColorMode(0) else: msp_mapper.SetAtomColorMode(1) - warnings.warn('Incorrect colormode specified! Using discrete.') + warnings.warn("Incorrect colormode specified! Using discrete.", stacklevel=2) # To-Do manipulate shading properties to make it look aesthetic molecule_actor = Actor() @@ -496,7 +512,7 @@ def sphere_cpk(molecule, colormode='discrete'): def ball_stick( molecule, - colormode='discrete', + colormode="discrete", atom_scale_factor=0.3, bond_thickness=0.1, multiple_bonds=True, @@ -543,11 +559,12 @@ def ball_stick( Turner, M. Ball and stick models for organic chemistry `J. Chem. Educ. 1971, 48, 6, 407. `_ + """ if molecule.total_num_bonds == 0: raise ValueError( - 'No bonding data available for the molecule! Ball ' - 'and stick model cannot be made!' + "No bonding data available for the molecule! Ball " + "and stick model cannot be made!" ) colormode = colormode.lower() bs_mapper = OpenGLMoleculeMapper() @@ -561,21 +578,21 @@ def ball_stick( bs_mapper.SetUseMultiCylindersForBonds(1) else: bs_mapper.SetUseMultiCylindersForBonds(0) - if colormode == 'discrete': + if colormode == "discrete": bs_mapper.SetAtomColorMode(1) bs_mapper.SetBondColorMode(1) - elif colormode == 'single': + elif colormode == "single": bs_mapper.SetAtomColorMode(0) bs_mapper.SetBondColorMode(0) else: bs_mapper.SetAtomColorMode(1) - warnings.warn('Incorrect colormode specified! Using discrete.') + warnings.warn("Incorrect colormode specified! Using discrete.", stacklevel=2) molecule_actor = Actor() molecule_actor.SetMapper(bs_mapper) return molecule_actor -def stick(molecule, colormode='discrete', bond_thickness=0.1): +def stick(molecule, colormode="discrete", bond_thickness=0.1): """Create an actor for stick molecular representation. Parameters @@ -602,10 +619,11 @@ def stick(molecule, colormode='discrete', bond_thickness=0.1): molecule_actor : vtkActor Actor created to render the stick representation of the molecule to be visualized. + """ if molecule.total_num_bonds == 0: raise ValueError( - 'No bonding data available for the molecule! Stick ' 'model cannot be made!' + "No bonding data available for the molecule! Stick " "model cannot be made!" ) colormode = colormode.lower() mst_mapper = OpenGLMoleculeMapper() @@ -615,15 +633,15 @@ def stick(molecule, colormode='discrete', bond_thickness=0.1): mst_mapper.SetBondRadius(bond_thickness) mst_mapper.SetAtomicRadiusTypeToUnitRadius() mst_mapper.SetAtomicRadiusScaleFactor(bond_thickness) - if colormode == 'discrete': + if colormode == "discrete": mst_mapper.SetAtomColorMode(1) mst_mapper.SetBondColorMode(1) - elif colormode == 'single': + elif colormode == "single": mst_mapper.SetAtomColorMode(0) mst_mapper.SetBondColorMode(0) else: mst_mapper.SetAtomColorMode(1) - warnings.warn('Incorrect colormode specified! Using discrete.') + warnings.warn("Incorrect colormode specified! Using discrete.", stacklevel=2) molecule_actor = Actor() molecule_actor.SetMapper(mst_mapper) return molecule_actor @@ -648,25 +666,26 @@ def ribbon(molecule): Richardson, J.S. The anatomy and taxonomy of protein structure `Advances in Protein Chemistry, 1981, 34, 167-339. `_ + """ coords = get_all_atomic_positions(molecule) all_atomic_numbers = get_all_atomic_numbers(molecule) num_total_atoms = molecule.total_num_atoms secondary_structures = np.ones(num_total_atoms) for i in range(num_total_atoms): - secondary_structures[i] = ord('c') + secondary_structures[i] = ord("c") resi = molecule.residue_seq[i] for j, _ in enumerate(molecule.sheet): sheet = molecule.sheet[j] if molecule.chain[i] != sheet[0] or resi < sheet[1] or resi > sheet[3]: continue - secondary_structures[i] = ord('s') + secondary_structures[i] = ord("s") for j, _ in enumerate(molecule.helix): helix = molecule.helix[j] if molecule.chain[i] != helix[0] or resi < helix[1] or resi > helix[3]: continue - secondary_structures[i] = ord('h') + secondary_structures[i] = ord("h") output = PolyData() @@ -677,7 +696,7 @@ def ribbon(molecule): # setting the array name to atom_type as vtkProteinRibbonFilter requires # the array to be named atom_type - atomic_num_arr.SetName('atom_type') + atomic_num_arr.SetName("atom_type") output.GetPointData().AddArray(atomic_num_arr) @@ -686,7 +705,7 @@ def ribbon(molecule): # setting the array name to atom_types as vtkProteinRibbonFilter requires # the array to be named atom_types - atom_names.SetName('atom_types') + atom_names.SetName("atom_types") atom_names.SetNumberOfTuples(num_total_atoms) for i in range(num_total_atoms): atom_names.SetValue(i, molecule.atom_names[i]) @@ -697,47 +716,47 @@ def ribbon(molecule): residue_seq = nps.numpy_to_vtk( num_array=molecule.residue_seq, deep=True, array_type=VTK_ID_TYPE ) - residue_seq.SetName('residue') + residue_seq.SetName("residue") output.GetPointData().AddArray(residue_seq) # for chain chain = nps.numpy_to_vtk( num_array=molecule.chain, deep=True, array_type=VTK_UNSIGNED_CHAR ) - chain.SetName('chain') + chain.SetName("chain") output.GetPointData().AddArray(chain) # for secondary structures s_s = nps.numpy_to_vtk( num_array=secondary_structures, deep=True, array_type=VTK_UNSIGNED_CHAR ) - s_s.SetName('secondary_structures') + s_s.SetName("secondary_structures") output.GetPointData().AddArray(s_s) # for secondary structures begin newarr = np.ones(num_total_atoms) s_sb = nps.numpy_to_vtk(num_array=newarr, deep=True, array_type=VTK_UNSIGNED_CHAR) - s_sb.SetName('secondary_structures_begin') + s_sb.SetName("secondary_structures_begin") output.GetPointData().AddArray(s_sb) # for secondary structures end newarr = np.ones(num_total_atoms) s_se = nps.numpy_to_vtk(num_array=newarr, deep=True, array_type=VTK_UNSIGNED_CHAR) - s_se.SetName('secondary_structures_end') + s_se.SetName("secondary_structures_end") output.GetPointData().AddArray(s_se) # for is_hetatm is_hetatm = nps.numpy_to_vtk( num_array=molecule.is_hetatm, deep=True, array_type=VTK_UNSIGNED_CHAR ) - is_hetatm.SetName('ishetatm') + is_hetatm.SetName("ishetatm") output.GetPointData().AddArray(is_hetatm) # for model model = nps.numpy_to_vtk( num_array=molecule.model, deep=True, array_type=VTK_UNSIGNED_INT ) - model.SetName('model') + model.SetName("model") output.GetPointData().AddArray(model) table = PTable() @@ -747,15 +766,15 @@ def ribbon(molecule): rgb = np.ones((num_total_atoms, 3)) for i in range(num_total_atoms): - radii[i] = np.repeat(table.atomic_radius(all_atomic_numbers[i], 'VDW'), 3) + radii[i] = np.repeat(table.atomic_radius(all_atomic_numbers[i], "VDW"), 3) rgb[i] = table.atom_color(all_atomic_numbers[i]) Rgb = nps.numpy_to_vtk(num_array=rgb, deep=True, array_type=VTK_UNSIGNED_CHAR) - Rgb.SetName('rgb_colors') + Rgb.SetName("rgb_colors") output.GetPointData().SetScalars(Rgb) Radii = nps.numpy_to_vtk(num_array=radii, deep=True, array_type=VTK_FLOAT) - Radii.SetName('radius') + Radii.SetName("radius") output.GetPointData().SetVectors(Radii) # setting the coordinates diff --git a/fury/optpkg.py b/fury/optpkg.py index 00f737662..053b62c6b 100644 --- a/fury/optpkg.py +++ b/fury/optpkg.py @@ -26,7 +26,7 @@ def is_tripwire(obj): """ try: - obj.any_attribute + _ = obj.any_attribute except TripWireError: return True except Exception: @@ -45,7 +45,8 @@ class TripWire: ... import silly_module_name ... except ImportError: ... silly_module_name = TripWire('We do not have silly_module_name') - >>> silly_module_name.do_silly_thing('with silly string') #doctest: +IGNORE_EXCEPTION_DETAIL # noqa + >>> msg = 'with silly string' + >>> silly_module_name.do_silly_thing(msg) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TripWireError: We do not have silly_module_name @@ -121,13 +122,13 @@ def optional_package(name, trip_msg=None): return pkg, True, lambda: None if trip_msg is None: trip_msg = ( - 'We need package %s for these functions, but ' - '``import %s`` raised an ImportError' % (name, name) + "We need package %s for these functions, but " + "``import %s`` raised an ImportError" % (name, name) ) pkg = TripWire(trip_msg) def setup_module(): if have_pytest: - pytest.mark.skip('No {0} for these tests'.format(name)) + pytest.mark.skip("No {0} for these tests".format(name)) return pkg, False, setup_module diff --git a/fury/pick.py b/fury/pick.py index a31139978..6949638c5 100644 --- a/fury/pick.py +++ b/fury/pick.py @@ -20,7 +20,7 @@ def __init__(self, vertices=True, faces=True, actors=True, world_coords=True): """Initialize Picking Manager. Parameters - ----------- + ---------- vertices : bool If True allows to pick vertex indices. faces : bool @@ -33,13 +33,13 @@ def __init__(self, vertices=True, faces=True, actors=True, world_coords=True): """ self.pickers = {} if vertices: - self.pickers['vertices'] = PointPicker() + self.pickers["vertices"] = PointPicker() if faces: - self.pickers['faces'] = CellPicker() + self.pickers["faces"] = CellPicker() if actors: - self.pickers['actors'] = PropPicker() + self.pickers["actors"] = PropPicker() if world_coords: - self.pickers['world_coords'] = WorldPointPicker() + self.pickers["world_coords"] = WorldPointPicker() def pick(self, disp_xy, sc): """Pick on display coordinates. @@ -54,25 +54,25 @@ def pick(self, disp_xy, sc): """ x, y = disp_xy z = 0 - info = {'vertex': None, 'face': None, 'actor': None, 'xyz': None} + info = {"vertex": None, "face": None, "actor": None, "xyz": None} keys = self.pickers.keys() - if 'vertices' in keys: - self.pickers['vertices'].Pick(x, y, z, sc) - info['vertex'] = self.pickers['vertices'].GetPointId() + if "vertices" in keys: + self.pickers["vertices"].Pick(x, y, z, sc) + info["vertex"] = self.pickers["vertices"].GetPointId() - if 'faces' in keys: - self.pickers['faces'].Pick(x, y, z, sc) - info['vertex'] = self.pickers['faces'].GetPointId() - info['face'] = self.pickers['faces'].GetCellId() + if "faces" in keys: + self.pickers["faces"].Pick(x, y, z, sc) + info["vertex"] = self.pickers["faces"].GetPointId() + info["face"] = self.pickers["faces"].GetCellId() - if 'actors' in keys: - self.pickers['actors'].Pick(x, y, z, sc) - info['actor'] = self.pickers['actors'].GetViewProp() + if "actors" in keys: + self.pickers["actors"].Pick(x, y, z, sc) + info["actor"] = self.pickers["actors"].GetViewProp() - if 'world_coords' in keys: - self.pickers['world_coords'].Pick(x, y, z, sc) - info['xyz'] = self.pickers['world_coords'].GetPickPosition() + if "world_coords" in keys: + self.pickers["world_coords"].Pick(x, y, z, sc) + info["xyz"] = self.pickers["world_coords"].GetPickPosition() return info @@ -120,11 +120,11 @@ def pickable_off(self, actors): class SelectionManager: """Selection Manager helps with picking many objects simultaneously.""" - def __init__(self, select='faces'): + def __init__(self, select="faces"): """Initialize Selection Manager. Parameters - ----------- + ---------- select : 'faces' Options are 'faces', 'vertices' or 'actors'. Default 'faces'. @@ -142,21 +142,21 @@ def update_selection_type(self, select): """Update selection type. Parameters - ----------- + ---------- select : 'faces' Options are 'faces', 'vertices' or 'actors'. Default 'faces'. """ self.selected_type = select.lower() - if select == 'faces' or select == 'edges': + if select == "faces" or select == "edges": self.hsel.SetFieldAssociation(DataObject.FIELD_ASSOCIATION_CELLS) - elif select == 'points' or select == 'vertices': + elif select == "points" or select == "vertices": self.hsel.SetFieldAssociation(DataObject.FIELD_ASSOCIATION_POINTS) - elif select == 'actors': + elif select == "actors": self.hsel.SetActorPassOnly(True) else: - raise ValueError('Unkown parameter select') + raise ValueError("Unknown parameter select") def pick(self, disp_xy, sc): """Pick on display coordinates returns a single object. @@ -202,18 +202,16 @@ def select(self, disp_xy, sc, area=0): res = self.hsel.Select() except OverflowError: - return {0: {'node': None, 'vertex': None, 'face': None, 'actor': None}} + return {0: {"node": None, "vertex": None, "face": None, "actor": None}} num_nodes = res.GetNumberOfNodes() if num_nodes < 1: sel_node = None - return {0: {'node': None, 'vertex': None, 'face': None, 'actor': None}} + return {0: {"node": None, "vertex": None, "face": None, "actor": None}} else: - for i in range(num_nodes): - sel_node = res.GetNode(i) - info = {'node': None, 'vertex': None, 'face': None, 'actor': None} + info = {"node": None, "vertex": None, "face": None, "actor": None} if sel_node is not None: selected_nodes = set( @@ -222,12 +220,12 @@ def select(self, disp_xy, sc, area=0): ).astype(int) ) - info['node'] = sel_node - info['actor'] = sel_node.GetProperties().Get(sel_node.PROP()) - if self.selected_type == 'faces': - info['face'] = list(selected_nodes) - if self.selected_type == 'vertex': - info['vertex'] = list(selected_nodes) + info["node"] = sel_node + info["actor"] = sel_node.GetProperties().Get(sel_node.PROP()) + if self.selected_type == "faces": + info["face"] = list(selected_nodes) + if self.selected_type == "vertex": + info["vertex"] = list(selected_nodes) info_plus[i] = info return info_plus diff --git a/fury/pkg_info.py b/fury/pkg_info.py index ab39f4b87..7bd7da41e 100644 --- a/fury/pkg_info.py +++ b/fury/pkg_info.py @@ -7,9 +7,9 @@ try: from ._version import __version__ except ImportError: - __version__ = '0+unknown' + __version__ = "0+unknown" -COMMIT_HASH = '$Format:%h$' +COMMIT_HASH = "$Format:%h$" def pkg_commit_hash(pkg_path: str | None = None) -> tuple[str, str]: @@ -38,18 +38,19 @@ def pkg_commit_hash(pkg_path: str | None = None) -> tuple[str, str]: Where we got the hash from - description hash_str : str short form of hash + """ - if not COMMIT_HASH.startswith('$Format'): # it has been substituted - return 'archive substitution', COMMIT_HASH + if not COMMIT_HASH.startswith("$Format"): # it has been substituted + return "archive substitution", COMMIT_HASH ver = Version(__version__) - if ver.local is not None and ver.local.startswith('g'): - return 'installation', ver.local[1:8] + if ver.local is not None and ver.local.startswith("g"): + return "installation", ver.local[1:8] # maybe we are in a repository proc = run( - ('git', 'rev-parse', '--short', 'HEAD'), + ("git", "rev-parse", "--short", "HEAD"), capture_output=True, cwd=pkg_path, ) if proc.stdout: - return 'repository', proc.stdout.decode().strip() - return '(none found)', '' + return "repository", proc.stdout.decode().strip() + return "(none found)", "" diff --git a/fury/primitive.py b/fury/primitive.py index b896d1485..768a92626 100644 --- a/fury/primitive.py +++ b/fury/primitive.py @@ -1,4 +1,5 @@ """Module dedicated for basic primitive.""" + import math from os.path import join as pjoin @@ -11,21 +12,20 @@ from fury.transform import cart2sphere, sphere2cart from fury.utils import fix_winding_order -SCIPY_1_4_PLUS = parse(short_version) >= parse('1.4.0') +SCIPY_1_4_PLUS = parse(short_version) >= parse("1.4.0") SPHERE_FILES = { - 'symmetric362': pjoin(DATA_DIR, 'evenly_distributed_sphere_362.npz'), - 'symmetric642': pjoin(DATA_DIR, 'evenly_distributed_sphere_642.npz'), - 'symmetric724': pjoin(DATA_DIR, 'evenly_distributed_sphere_724.npz'), - 'repulsion724': pjoin(DATA_DIR, 'repulsion724.npz'), - 'repulsion100': pjoin(DATA_DIR, 'repulsion100.npz'), - 'repulsion200': pjoin(DATA_DIR, 'repulsion200.npz'), + "symmetric362": pjoin(DATA_DIR, "evenly_distributed_sphere_362.npz"), + "symmetric642": pjoin(DATA_DIR, "evenly_distributed_sphere_642.npz"), + "symmetric724": pjoin(DATA_DIR, "evenly_distributed_sphere_724.npz"), + "repulsion724": pjoin(DATA_DIR, "repulsion724.npz"), + "repulsion100": pjoin(DATA_DIR, "repulsion100.npz"), + "repulsion200": pjoin(DATA_DIR, "repulsion200.npz"), } def faces_from_sphere_vertices(vertices): - """ - Triangulate a set of vertices on the sphere. + """Triangulate a set of vertices on the sphere. Parameters ---------- @@ -38,7 +38,7 @@ def faces_from_sphere_vertices(vertices): Indices into vertices; forms triangular faces. """ - hull = ConvexHull(vertices, qhull_options='Qbb Qc') + hull = ConvexHull(vertices, qhull_options="Qbb Qc") faces = np.ascontiguousarray(hull.simplices) if len(vertices) < 2**16: return np.asarray(faces, np.uint16) @@ -47,7 +47,7 @@ def faces_from_sphere_vertices(vertices): def repeat_primitive_function( - func, centers, func_args=[], directions=(1, 0, 0), colors=(1, 0, 0), scales=1 + func, centers, func_args=None, directions=(1, 0, 0), colors=(1, 0, 0), scales=1 ): """Repeat Vertices and triangles of a specific primitive function. @@ -79,14 +79,17 @@ def repeat_primitive_function( Expanded colors applied to all vertices/faces """ + if func_args is None: + func_args = [] + # Get faces _, faces = func() if len(func_args) == 1: func_args = np.squeeze(np.array([func_args] * centers.shape[0])) elif len(func_args) != centers.shape[0]: raise IOError( - 'sq_params should 1 or equal to the numbers \ - of centers' + "sq_params should 1 or equal to the numbers \ + of centers" ) vertices = np.concatenate([func(i)[0] for i in func_args]) @@ -170,7 +173,7 @@ def repeat_primitive( axis=0, ).reshape((big_triangles.shape[0], 1)) - def normalize_input(arr, arr_name=''): + def normalize_input(arr, arr_name=""): if ( isinstance(arr, (tuple, list, np.ndarray)) and len(arr) in [3, 4] @@ -182,19 +185,19 @@ def normalize_input(arr, arr_name=''): elif arr is None: return np.array([]) elif len(arr) != len(centers): - msg = '{} size should be 1 or '.format(arr_name) - msg += 'equal to the numbers of centers' + msg = "{} size should be 1 or ".format(arr_name) + msg += "equal to the numbers of centers" raise IOError(msg) else: return np.array(arr) # update colors - colors = normalize_input(colors, 'colors') + colors = normalize_input(colors, "colors") big_colors = np.repeat(colors, unit_verts_size, axis=0) big_colors *= 255 # update orientations - directions = normalize_input(directions, 'directions') + directions = normalize_input(directions, "directions") for pts, dirs in enumerate(directions): # Normal vector of the object. dir_abs = np.linalg.norm(dirs) @@ -212,8 +215,9 @@ def normalize_input(arr, arr_name=''): rotation_matrix = -np.eye(3, dtype=np.float64) else: h = 1 / (1 + c) - rotation_matrix = np.eye(3, dtype=np.float64) + \ - Vmat + (Vmat.dot(Vmat) * h) + rotation_matrix = ( + np.eye(3, dtype=np.float64) + Vmat + (Vmat.dot(Vmat) * h) + ) else: rotation_matrix = np.identity(3) @@ -244,7 +248,7 @@ def prim_square(): vertices = np.array( [[-0.5, -0.5, 0.0], [-0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, -0.5, 0.0]] ) - triangles = np.array([[0, 1, 2], [2, 3, 0]], dtype='i8') + triangles = np.array([[0, 1, 2], [2, 3, 0]], dtype="i8") return vertices, triangles @@ -286,12 +290,12 @@ def prim_box(): [1, 5, 7], [1, 7, 3], ], - dtype='i8', + dtype="i8", ) return vertices, triangles -def prim_sphere(name='symmetric362', gen_faces=False, phi=None, theta=None): +def prim_sphere(name="symmetric362", gen_faces=False, phi=None, theta=None): """Provide vertices and triangles of the spheres. Parameters @@ -311,6 +315,7 @@ def prim_sphere(name='symmetric362', gen_faces=False, phi=None, theta=None): Set the number of points in the latitude direction theta : int, optional Set the number of points in the longitude direction + Returns ------- vertices: ndarray @@ -335,9 +340,9 @@ def prim_sphere(name='symmetric362', gen_faces=False, phi=None, theta=None): raise ValueError('No sphere called "%s"' % name) res = np.load(fname) - verts = res['vertices'].copy() - faces = faces_from_sphere_vertices(verts) if gen_faces else res['faces'] - faces = fix_winding_order(res['vertices'], faces, clockwise=True) + verts = res["vertices"].copy() + faces = faces_from_sphere_vertices(verts) if gen_faces else res["faces"] + faces = fix_winding_order(res["vertices"], faces, clockwise=True) return verts, faces else: phi = phi if phi >= 3 else 3 @@ -368,7 +373,7 @@ def prim_sphere(name='symmetric362', gen_faces=False, phi=None, theta=None): return verts, faces -def prim_superquadric(roundness=(1, 1), sphere_name='symmetric362'): +def prim_superquadric(roundness=(1, 1), sphere_name="symmetric362"): """Provide vertices and triangles of a superquadrics. Parameters @@ -439,7 +444,7 @@ def prim_tetrahedron(): [[0.5, 0.5, 0.5], [0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5]] ) - pyramid_triag = np.array([[2, 0, 1], [0, 2, 3], [0, 3, 1], [1, 3, 2]], dtype='i8') + pyramid_triag = np.array([[2, 0, 1], [0, 2, 3], [0, 3, 1], [1, 3, 2]], dtype="i8") return pyramid_vert, pyramid_triag @@ -497,7 +502,7 @@ def prim_icosahedron(): [10, 9, 6], [9, 10, 11], ], - dtype='i8', + dtype="i8", ) return icosahedron_vertices, icosahedron_mesh @@ -592,7 +597,7 @@ def prim_rhombicuboctahedron(): [22, 20, 5], [20, 1, 5], ], - dtype='i8', + dtype="i8", ) triangles = fix_winding_order(vertices, triangles, clockwise=True) @@ -643,7 +648,7 @@ def prim_star(dim=2): [3, 7, 9], [3, 5, 7], ], - dtype='i8', + dtype="i8", ) if dim == 3: @@ -694,7 +699,7 @@ def prim_star(dim=2): [3, 11, 2], [11, 1, 2], ], - dtype='i8', + dtype="i8", ) return vert, triangles @@ -712,7 +717,7 @@ def prim_triangularprism(): """ # Local variable to represent the square root of three rounded # to 7 decimal places - three = float('{:.7f}'.format(math.sqrt(3))) + three = float("{:.7f}".format(math.sqrt(3))) vertices = np.array( [ [0, -1 / three, 1 / 2], @@ -808,7 +813,7 @@ def prim_octagonalprism(): """ # Local variable to represent the square root of two rounded # to 7 decimal places - two = float('{:.7f}'.format(math.sqrt(2))) + two = float("{:.7f}".format(math.sqrt(2))) vertices = np.array( [ @@ -861,7 +866,7 @@ def prim_octagonalprism(): [10, 13, 14], [13, 10, 9], ], - dtype='u8', + dtype="u8", ) vertices /= 4 triangles = fix_winding_order(vertices, triangles, clockwise=True) @@ -906,7 +911,7 @@ def prim_frustum(): [5, 0, 1], [0, 5, 4], ], - dtype='u8', + dtype="u8", ) vertices /= 2 triangles = fix_winding_order(vertices, triangles, clockwise=True) @@ -933,12 +938,12 @@ def prim_cylinder(radius=0.5, height=1, sectors=36, capped=True): vertices coords that compose our cylinder triangles: ndarray triangles that compose our cylinder - """ + """ if not isinstance(sectors, int): - raise TypeError('Only integers are allowed for sectors parameter') + raise TypeError("Only integers are allowed for sectors parameter") if not sectors > 7: - raise ValueError('Sectors parameter should be greater than 7') + raise ValueError("Sectors parameter should be greater than 7") sector_step = 2 * math.pi / sectors unit_circle_vertices = [] @@ -996,7 +1001,7 @@ def prim_cylinder(radius=0.5, height=1, sectors=36, capped=True): k2 = sectors + 1 # triangles for the side surface - for i in range(sectors): + for _ in range(sectors): triangles.append(k1) triangles.append(k2) triangles.append(k1 + 1) @@ -1066,7 +1071,6 @@ def prim_arrow( Triangles of the Arrow """ - shaft_height = height - tip_length all_faces = [] @@ -1151,9 +1155,8 @@ def prim_cone(radius=0.5, height=1, sectors=10): triangles that compose our cone """ - if sectors < 3: - raise ValueError('Sectors parameter should be greater than 2') + raise ValueError("Sectors parameter should be greater than 2") sector_angles = 2 * np.pi / sectors * np.arange(sectors) diff --git a/fury/shaders/__init__.py b/fury/shaders/__init__.py index 8a8f11764..c8a9378d7 100644 --- a/fury/shaders/__init__.py +++ b/fury/shaders/__init__.py @@ -11,13 +11,13 @@ ) __all__ = [ - 'add_shader_callback', - 'attribute_to_actor', - 'compose_shader', - 'import_fury_shader', - 'load_shader', - 'load', - 'replace_shader_in_actor', - 'shader_apply_effects', - 'shader_to_actor', + "add_shader_callback", + "attribute_to_actor", + "compose_shader", + "import_fury_shader", + "load_shader", + "load", + "replace_shader_in_actor", + "shader_apply_effects", + "shader_to_actor", ] diff --git a/fury/shaders/base.py b/fury/shaders/base.py index 6caec9f5c..7540db6e8 100644 --- a/fury/shaders/base.py +++ b/fury/shaders/base.py @@ -1,5 +1,5 @@ -import os from functools import partial +import os from fury import enable_warnings from fury.deprecator import deprecate_with_version @@ -15,50 +15,58 @@ SHADERS_DIR = os.path.join(os.path.dirname(__file__)) -SHADERS_EXTS = ['.glsl', '.vert', '.tesc', '.tese', '.geom', '.frag', '.comp'] +SHADERS_EXTS = [ + ".glsl", + ".vert", + ".tesc", + ".tese", + ".geom", + ".frag", + ".comp", +] SHADERS_TYPE = { - 'vertex': Shader.Vertex, - 'geometry': Shader.Geometry, - 'fragment': Shader.Fragment, + "vertex": Shader.Vertex, + "geometry": Shader.Geometry, + "fragment": Shader.Fragment, } -REPLACEMENT_SHADERS_TYPES = {'vertex': Shader.Vertex, 'fragment': Shader.Fragment} +REPLACEMENT_SHADERS_TYPES = {"vertex": Shader.Vertex, "fragment": Shader.Fragment} SHADERS_BLOCK = { - 'position': '//VTK::PositionVC', # frag position in VC - 'normal': '//VTK::Normal', # optional normal declaration - 'light': '//VTK::Light', # extra lighting parameters - 'tcoord': '//VTK::TCoord', # Texture coordinates - 'color': '//VTK::Color', # material property values - 'clip': '//VTK::Clip', # clipping plane vars - 'camera': '//VTK::Camera', # camera and actor matrix values - 'prim_id': '//VTK::PrimID', # Apple Bug - 'valuepass': '//VTK::ValuePass', # Value raster - 'output': '//VTK::Output', # only for geometry shader - 'coincident': '//VTK::Coincident', # handle coincident offsets - 'zbufer': '//VTK::ZBuffer', - 'depth_peeling': '//VTK::DepthPeeling', # Depth Peeling Support - 'picking': '//VTK::Picking', # picking support + "position": "//VTK::PositionVC", # frag position in VC + "normal": "//VTK::Normal", # optional normal declaration + "light": "//VTK::Light", # extra lighting parameters + "tcoord": "//VTK::TCoord", # Texture coordinates + "color": "//VTK::Color", # material property values + "clip": "//VTK::Clip", # clipping plane vars + "camera": "//VTK::Camera", # camera and actor matrix values + "prim_id": "//VTK::PrimID", # Apple Bug + "valuepass": "//VTK::ValuePass", # Value raster + "output": "//VTK::Output", # only for geometry shader + "coincident": "//VTK::Coincident", # handle coincident offsets + "zbufer": "//VTK::ZBuffer", + "depth_peeling": "//VTK::DepthPeeling", # Depth Peeling Support + "picking": "//VTK::Picking", # picking support } # See [1] for a more extensive list of OpenGL constants # [1] https://docs.factorcode.org/content/vocab-opengl.gl.html GL_NUMBERS = { - 'GL_ONE': 1, - 'GL_ZERO': 0, - 'GL_BLEND': 3042, - 'GL_ONE_MINUS_SRC_ALPHA': 771, - 'GL_SRC_ALPHA': 770, - 'GL_DEPTH_TEST': 2929, - 'GL_DST_COLOR': 774, - 'GL_FUNC_SUBTRACT': 3277, - 'GL_CULL_FACE': 2884, - 'GL_ALPHA_TEST': 3008, - 'GL_CW': 2304, - 'GL_CCW': 2305, - 'GL_ONE_MINUS_SRC_COLOR': 769, - 'GL_SRC_COLOR': 768, + "GL_ONE": 1, + "GL_ZERO": 0, + "GL_BLEND": 3042, + "GL_ONE_MINUS_SRC_ALPHA": 771, + "GL_SRC_ALPHA": 770, + "GL_DEPTH_TEST": 2929, + "GL_DST_COLOR": 774, + "GL_FUNC_SUBTRACT": 3277, + "GL_CULL_FACE": 2884, + "GL_ALPHA_TEST": 3008, + "GL_CW": 2304, + "GL_CCW": 2305, + "GL_ONE_MINUS_SRC_COLOR": 769, + "GL_SRC_COLOR": 768, } @@ -76,17 +84,17 @@ def compose_shader(glsl_code): """ if not glsl_code: - return '' + return "" if not all(isinstance(i, str) for i in glsl_code): - raise IOError('The only supported format are string.') + raise IOError("The only supported format are string.") if isinstance(glsl_code, str): return glsl_code - code = '' + code = "" for content in glsl_code: - code += '\n' + code += "\n" code += content return code @@ -126,20 +134,21 @@ def load_shader(shader_file): ------- code : str GLSL shader code. + """ file_ext = os.path.splitext(os.path.basename(shader_file))[1] if file_ext not in SHADERS_EXTS: raise IOError( 'Shader file "{}" does not have one of the supported ' - 'extensions: {}.'.format(shader_file, SHADERS_EXTS) + "extensions: {}.".format(shader_file, SHADERS_EXTS) ) return load_text(shader_file) @deprecate_with_version( - message='Load function has been reimplemented as import_fury_shader.', - since='0.8.1', - until='0.9.0', + message="Load function has been reimplemented as import_fury_shader.", + since="0.8.1", + until="0.9.0", ) def load(filename): """Load a Fury shader file. @@ -162,9 +171,9 @@ def load(filename): def shader_to_actor( actor, shader_type, - impl_code='', - decl_code='', - block='valuepass', + impl_code="", + decl_code="", + block="valuepass", keep_default=True, replace_first=True, replace_all=False, @@ -208,27 +217,27 @@ def shader_to_actor( shader_type = shader_type.lower() shader_type = REPLACEMENT_SHADERS_TYPES.get(shader_type, None) if shader_type is None: - msg = 'Invalid Shader Type. Please choose between ' - msg += ', '.join(REPLACEMENT_SHADERS_TYPES.keys()) + msg = "Invalid Shader Type. Please choose between " + msg += ", ".join(REPLACEMENT_SHADERS_TYPES.keys()) raise ValueError(msg) block = block.lower() block = SHADERS_BLOCK.get(block, None) if block is None: - msg = 'Invalid Shader Type. Please choose between ' - msg += ', '.join(SHADERS_BLOCK.keys()) + msg = "Invalid Shader Type. Please choose between " + msg += ", ".join(SHADERS_BLOCK.keys()) raise ValueError(msg) - block_dec = block + '::Dec' - block_impl = block + '::Impl' + block_dec = block + "::Dec" + block_impl = block + "::Impl" if keep_default: - decl_code = block_dec + '\n' + decl_code - impl_code = block_impl + '\n' + impl_code + decl_code = block_dec + "\n" + decl_code + impl_code = block_impl + "\n" + impl_code if debug: enable_warnings() - error_msg = '\n\n--- DEBUG: THIS LINE GENERATES AN ERROR ---\n\n' + error_msg = "\n\n--- DEBUG: THIS LINE GENERATES AN ERROR ---\n\n" impl_code += error_msg sp = actor.GetShaderProperty() @@ -255,15 +264,15 @@ def replace_shader_in_actor(actor, shader_type, code): """ function_name = { - 'vertex': 'SetVertexShaderCode', - 'fragment': 'SetFragmentShaderCode', - 'geometry': 'SetGeometryShaderCode', + "vertex": "SetVertexShaderCode", + "fragment": "SetFragmentShaderCode", + "geometry": "SetGeometryShaderCode", } shader_type = shader_type.lower() function = function_name.get(shader_type, None) if function is None: - msg = 'Invalid Shader Type. Please choose between ' - msg += ', '.join(function_name.keys()) + msg = "Invalid Shader Type. Please choose between " + msg += ", ".join(function_name.keys()) raise ValueError(msg) sp = actor.GetShaderProperty() @@ -291,7 +300,7 @@ def add_shader_callback(actor, callback, priority=0.0): See more at: https://vtk.org/doc/nightly/html/classvtkObject.html Examples - --------- + -------- .. code-block:: python add_shader_callback(actor, func_call1) diff --git a/fury/shaders/lighting/principled/sheen.frag b/fury/shaders/lighting/principled/sheen.frag index 31476d22a..b547f46be 100644 --- a/fury/shaders/lighting/principled/sheen.frag +++ b/fury/shaders/lighting/principled/sheen.frag @@ -3,4 +3,4 @@ vec3 evaluateSheen(float sheen, float sheenTint, vec3 tint, { vec3 tintMix = mix(vec3(1), tint, sheenTint); return sheen * tintMix * schlickWeight; -} \ No newline at end of file +} diff --git a/fury/shaders/ray_marching/gen_ray.frag b/fury/shaders/ray_marching/gen_ray.frag new file mode 100644 index 000000000..1ddd92801 --- /dev/null +++ b/fury/shaders/ray_marching/gen_ray.frag @@ -0,0 +1,17 @@ +void gen_ray(out vec3 ro, out vec3 rd, out float t) +{ + // Vertex in Model Coordinates + vec3 point = vertexMCVSOutput.xyz; + + // Ray Origin + // Camera position in world space + ro = (-MCVCMatrix[3] * MCVCMatrix).xyz; + + // Ray Direction + rd = normalize(point - ro); + + ro += point - ro; + + // Total distance traversed along the ray + t = castRay(ro, rd); +} diff --git a/fury/shaders/sdf/sd_cone.frag b/fury/shaders/sdf/sd_cone.frag new file mode 100644 index 000000000..678e5107d --- /dev/null +++ b/fury/shaders/sdf/sd_cone.frag @@ -0,0 +1,15 @@ +float sdCone( vec3 p, vec2 c, float h ) +{ + // c is the sin/cos of the angle, h is height + // Alternatively pass q instead of (c,h), + // which is the point at the base in 2D + vec2 q = h*vec2(c.x/c.y,-1.0); + + vec2 w = vec2( length(p.xz), p.y ); + vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 ); + vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 ); + float k = sign( q.y ); + float d = min(dot( a, a ),dot(b, b)); + float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) ); + return sqrt(d)*sign(s); +} diff --git a/fury/shaders/sdf/sd_union.frag b/fury/shaders/sdf/sd_union.frag new file mode 100644 index 000000000..6bcfe8903 --- /dev/null +++ b/fury/shaders/sdf/sd_union.frag @@ -0,0 +1,4 @@ +float opUnion( float d1, float d2 ) +{ + return min(d1,d2); +} diff --git a/fury/shaders/tests/test_base.py b/fury/shaders/tests/test_base.py index 91c1dbc5b..7fc7f3438 100644 --- a/fury/shaders/tests/test_base.py +++ b/fury/shaders/tests/test_base.py @@ -145,10 +145,10 @@ def generate_cube_with_effect(): cube = actor.cube(np.array([[0, 0, 0]])) shader_to_actor( - cube, 'vertex', impl_code=vertex_impl, decl_code=vertex_dec, block='valuepass' + cube, "vertex", impl_code=vertex_impl, decl_code=vertex_dec, block="valuepass" ) shader_to_actor( - cube, 'fragment', impl_code=frag_impl, decl_code=frag_dec, block='light' + cube, "fragment", impl_code=frag_impl, decl_code=frag_dec, block="light" ) return cube @@ -210,7 +210,7 @@ def my_cbk(_caller, _event, calldata=None): if program is not None: try: - program.SetUniformf('time', timer.idx) + program.SetUniformf("time", timer.idx) except ValueError: pass @@ -234,8 +234,8 @@ def callbackLow(_caller, _event, calldata=None): id_observer = add_shader_callback(cone_actor, callbackLow, 0) - with pytest.raises(Exception): - add_shader_callback(cone_actor, callbackLow, priority='str') + with pytest.raises(Exception): # noqa: B017 + add_shader_callback(cone_actor, callbackLow, priority="str") mapper = cone_actor.GetMapper() mapper.RemoveObserver(id_observer) @@ -243,7 +243,7 @@ def callbackLow(_caller, _event, calldata=None): scene = window.Scene() scene.add(cone_actor) - arr1 = window.snapshot(scene, size=(200, 200)) + window.snapshot(scene, size=(200, 200)) assert len(test_values) == 0 test_values = [] @@ -264,7 +264,7 @@ def callbackMean(_caller, _event, calldata=None): id_mean = add_shader_callback(cone_actor, callbackMean, 500) # check the priority of each call - arr2 = window.snapshot(scene, size=(200, 200)) + window.snapshot(scene, size=(200, 200)) assert ( np.abs([test_values[0] - 999, test_values[1] - 500, test_values[2] - 0]).sum() == 0 @@ -274,7 +274,7 @@ def callbackMean(_caller, _event, calldata=None): mapper.RemoveObserver(id_mean) test_values = [] - arr3 = window.snapshot(scene, size=(200, 200)) + window.snapshot(scene, size=(200, 200)) assert np.abs([test_values[0] - 999, test_values[1] - 0]).sum() == 0 @@ -282,20 +282,20 @@ def test_attribute_to_actor(): cube = generate_cube_with_effect() test_arr = np.arange(24).reshape((8, 3)) - attribute_to_actor(cube, test_arr, 'test_arr') + attribute_to_actor(cube, test_arr, "test_arr") - arr = cube.GetMapper().GetInput().GetPointData().GetArray('test_arr') + arr = cube.GetMapper().GetInput().GetPointData().GetArray("test_arr") npt.assert_array_equal(test_arr, numpy_support.vtk_to_numpy(arr)) def test_compose_shader(): - str_test1 = 'Test1' - str_test2 = 'Test2' + str_test1 = "Test1" + str_test2 = "Test2" list_str1 = [str_test1, None] list_str2 = [str_test1, str_test2] # Test empty parameter code = compose_shader(None) - npt.assert_equal(code, '') + npt.assert_equal(code, "") # Test invalid list npt.assert_raises(IOError, compose_shader, list_str1) # Test str code @@ -303,13 +303,13 @@ def test_compose_shader(): npt.assert_equal(code, str_test1) # Test list of str code code = compose_shader(list_str2) - npt.assert_equal(code, '\n' + '\n'.join(list_str2)) + npt.assert_equal(code, "\n" + "\n".join(list_str2)) def test_import_fury_shader(): - str_test1 = 'Test1' - fname_test1 = 'test1.frag' - fname_test2 = 'test2.txt' + str_test1 = "Test1" + fname_test1 = "test1.frag" + fname_test2 = "test2.txt" pname_test1 = os.path.join(SHADERS_DIR, fname_test1) # Test invalid file extension @@ -319,7 +319,7 @@ def test_import_fury_shader(): npt.assert_raises(IOError, import_fury_shader, pname_test1) # Test valid file - with open(pname_test1, 'w') as f: + with open(pname_test1, "w") as f: f.write(str_test1) code = import_fury_shader(fname_test1) npt.assert_equal(code, str_test1) @@ -328,16 +328,16 @@ def test_import_fury_shader(): def test_load_shader(): - fname_test = 'test.text' + fname_test = "test.text" # Test invalid file extension npt.assert_raises(IOError, load_shader, fname_test) with InTemporaryDirectory() as tdir: - fname_test = 'test.frag' + fname_test = "test.frag" fname_test = os.path.join(tdir, fname_test) - str_test = 'Test1' - test_file = open(fname_test, 'w') + str_test = "Test1" + test_file = open(fname_test, "w") test_file.write(str_test) test_file.close() @@ -345,10 +345,10 @@ def test_load_shader(): def test_load(): - dummy_file_name = 'dummy.txt' - dummy_file_contents = 'This is some dummy text.' + dummy_file_name = "dummy.txt" + dummy_file_contents = "This is some dummy text." - dummy_file = open(os.path.join(SHADERS_DIR, dummy_file_name), 'w') + dummy_file = open(os.path.join(SHADERS_DIR, dummy_file_name), "w") dummy_file.write(dummy_file_contents) dummy_file.close() @@ -370,7 +370,7 @@ def test_replace_shader_in_actor(interactive=False): actual = ss[160, 40, :] npt.assert_array_equal(actual, [0, 0, 0]) scene.clear() - replace_shader_in_actor(test_actor, 'geometry', geometry_code) + replace_shader_in_actor(test_actor, "geometry", geometry_code) scene.add(test_actor) if interactive: window.show(scene) @@ -397,9 +397,9 @@ def test_shader_to_actor(interactive=False): npt.assert_equal(report.objects, 1) # test errors - npt.assert_raises(ValueError, shader_to_actor, cube, 'error', vertex_impl) - npt.assert_raises(ValueError, shader_to_actor, cube, 'geometry', vertex_impl) + npt.assert_raises(ValueError, shader_to_actor, cube, "error", vertex_impl) + npt.assert_raises(ValueError, shader_to_actor, cube, "geometry", vertex_impl) npt.assert_raises( - ValueError, shader_to_actor, cube, 'vertex', vertex_impl, block='error' + ValueError, shader_to_actor, cube, "vertex", vertex_impl, block="error" ) - npt.assert_raises(ValueError, replace_shader_in_actor, cube, 'error', vertex_impl) + npt.assert_raises(ValueError, replace_shader_in_actor, cube, "error", vertex_impl) diff --git a/fury/stream/client.py b/fury/stream/client.py index aac565e35..88da17caa 100644 --- a/fury/stream/client.py +++ b/fury/stream/client.py @@ -1,12 +1,12 @@ +from functools import partial import logging import platform import time -from functools import partial import numpy as np import vtk -from fury.stream.constants import _CQUEUE, PY_VERSION_8 +from fury.stream.constants import PY_VERSION_8, _CQUEUE from fury.stream.tools import ( ArrayCircularQueue, IntervalTimer, @@ -35,7 +35,7 @@ def callback_stream_client(stream_client): vtk_array = vtk_image.GetPointData().GetScalars() w, h, _ = vtk_image.GetDimensions() - np_arr = np.frombuffer(vtk_array, dtype='uint8') + np_arr = np.frombuffer(vtk_array, dtype="uint8") if np_arr is None: stream_client.in_request = False return @@ -55,8 +55,7 @@ def __init__( whithout_iren_start=False, num_buffers=2, ): - """ - A StreamClient extracts a framebuffer from the OpenGL context + """A StreamClient extracts a framebuffer from the OpenGL context and writes into a shared memory resource. Parameters @@ -77,7 +76,6 @@ def __init__( technique. """ - self._whithout_iren_start = whithout_iren_start self.showm = showm self.window2image_filter = vtk.vtkWindowToImageFilter() @@ -96,7 +94,7 @@ def __init__( self.max_size = max_window_size[0] * max_window_size[1] self.max_window_size = max_window_size if self.max_size < self.showm.size[0] * self.showm.size[1]: - raise ValueError('max_window_size must be greater than window_size') + raise ValueError("max_window_size must be greater than window_size") if not PY_VERSION_8 and not use_raw_array: raise ValueError( @@ -137,31 +135,30 @@ def start(self, ms=0, use_asyncio=False): """ def callback_for_vtk(caller, event, *args, **kwargs): - callback_stream_client(**{'stream_client': kwargs['stream_client']}) + callback_stream_client(**{"stream_client": kwargs["stream_client"]}) - use_asyncio = platform.system() == 'Windows' or use_asyncio + use_asyncio = platform.system() == "Windows" or use_asyncio if self._started: self.stop() if ms > 0: if self._whithout_iren_start: - Interval = IntervalTimer if use_asyncio else IntervalTimerThreading self._interval_timer = Interval( - ms / 1000, callback_stream_client, **{'stream_client': self} + ms / 1000, callback_stream_client, **{"stream_client": self} ) else: self._id_observer = self.showm.iren.AddObserver( - 'TimerEvent', partial(callback_for_vtk, **{'stream_client': self}) + "TimerEvent", partial(callback_for_vtk, **{"stream_client": self}) ) self._id_timer = self.showm.iren.CreateRepeatingTimer(ms) else: self._id_observer = self.showm.iren.AddObserver( - 'RenderEvent', partial(callback_for_vtk, **{'stream_client': self}) + "RenderEvent", partial(callback_for_vtk, **{"stream_client": self}) ) self.showm.window.Render() self.showm.iren.Render() self._started = True - callback_stream_client(**{'stream_client': self}) + callback_stream_client(**{"stream_client": self}) def stop(self): """Stop the stream client.""" @@ -195,17 +192,18 @@ def cleanup(self): self.img_manager.info_buffer.unlink() except FileNotFoundError: print( - f'Shared Memory {self.img_manager.info_buffer_name}\ - (info_buffer) File not found' + f"Shared Memory {self.img_manager.info_buffer_name}\ + (info_buffer) File not found" ) for buffer, name in zip( - self.img_manager.image_buffers, self.img_manager.image_buffer_names + self.img_manager.image_buffers, + self.img_manager.image_buffer_names, ): buffer.close() try: buffer.unlink() except FileNotFoundError: - print(f'Shared Memory {name}(buffer image) File not found') + print(f"Shared Memory {name}(buffer image) File not found") def interaction_callback(circular_queue, showm, iren, render_after): @@ -263,7 +261,7 @@ def interaction_callback(circular_queue, showm, iren, render_after): event_ids.right_btn_release: iren.RightButtonReleaseEvent, } mouse_actions[user_event_id]() - logging.info('Interaction: time to perform event ' + f'{ts-user_timestamp:.2f} ms') + logging.info("Interaction: time to perform event " + f"{ts-user_timestamp:.2f} ms") if render_after: showm.window.Render() showm.iren.Render() @@ -275,7 +273,7 @@ class FuryStreamInteraction: def __init__( self, showm, max_queue_size=50, use_raw_array=True, whithout_iren_start=False ): - """ + """Initialize the StreamInteraction obj. Parameters ---------- @@ -291,7 +289,6 @@ def __init__( instance. """ - self.showm = showm self.iren = self.showm.iren if use_raw_array: @@ -321,10 +318,9 @@ def start(self, ms=3, use_asyncio=False): separate thread. """ - - use_asyncio = platform.system() == 'Windows' or use_asyncio + use_asyncio = platform.system() == "Windows" or use_asyncio if ms <= 0: - raise ValueError('ms must be greater than zero') + raise ValueError("ms must be greater than zero") if self._started: self.stop() @@ -340,7 +336,7 @@ def start(self, ms=3, use_asyncio=False): def callback(caller, event, *args, **kwargs): interaction_callback(self.circular_queue, self.showm, self.iren, True) - self._id_observer = self.showm.iren.AddObserver('TimerEvent', callback) + self._id_observer = self.showm.iren.AddObserver("TimerEvent", callback) self._id_timer = self.showm.iren.CreateRepeatingTimer(ms) self._started = True diff --git a/fury/stream/constants.py b/fury/stream/constants.py index 401c4989e..edff52b52 100644 --- a/fury/stream/constants.py +++ b/fury/stream/constants.py @@ -1,5 +1,5 @@ -import sys from collections import namedtuple +import sys PY_VERSION_8 = sys.version_info.minor >= 8 @@ -16,20 +16,20 @@ # 7 | RightButtonPressEvent # 8 | RightButtonReleaseEvent _event_ids = { - 'mouse_weel': 1, - 'mouse_move': 2, - 'mouse_ids': (3, 4, 5, 6, 7, 8), - 'left_btn_press': 3, - 'left_btn_release': 4, - 'middle_btn_press': 5, - 'middle_btn_release': 6, - 'right_btn_press': 7, - 'right_btn_release': 8, + "mouse_weel": 1, + "mouse_move": 2, + "mouse_ids": (3, 4, 5, 6, 7, 8), + "left_btn_press": 3, + "left_btn_release": 4, + "middle_btn_press": 5, + "middle_btn_release": 6, + "right_btn_press": 7, + "right_btn_release": 8, } # This immutable object it's used to avoid any kind of # mistake in assignment of event ids -_CQUEUE_EVENT_IDs = namedtuple('CQUEUE_EVENT_IDS', list(_event_ids.keys()))( +_CQUEUE_EVENT_IDs = namedtuple("CQUEUE_EVENT_IDS", list(_event_ids.keys()))( **_event_ids ) @@ -45,24 +45,24 @@ # 5 | shift_key state (1 pressed 0 otherwise) # 6 | js event timestamp in mileseconds (ufloat) _index_info = { - 'weel': 1, - 'x': 2, - 'y': 3, - 'ctrl': 4, - 'shift': 5, - 'user_timestamp': 6, + "weel": 1, + "x": 2, + "y": 3, + "ctrl": 4, + "shift": 5, + "user_timestamp": 6, } -_CQUEUE_INDEX_INFO = namedtuple('CQUEUE_INDEX_INFO', list(_index_info.keys()))( +_CQUEUE_INDEX_INFO = namedtuple("CQUEUE_INDEX_INFO", list(_index_info.keys()))( **_index_info ) # dimension it's also a important parameter # A wrong value can cause a silent error or a segmentation fault -_CQUEUE = namedtuple('CQUEUE', ['dimension', 'event_ids', 'index_info'])( +_CQUEUE = namedtuple("CQUEUE", ["dimension", "event_ids", "index_info"])( **{ - 'event_ids': _CQUEUE_EVENT_IDs, - 'index_info': _CQUEUE_INDEX_INFO, - 'dimension': 8, + "event_ids": _CQUEUE_EVENT_IDs, + "index_info": _CQUEUE_INDEX_INFO, + "dimension": 8, } ) diff --git a/fury/stream/server/__init__.py b/fury/stream/server/__init__.py index d606b03e4..af5b0e0ce 100644 --- a/fury/stream/server/__init__.py +++ b/fury/stream/server/__init__.py @@ -1,3 +1,3 @@ from fury.stream.server.main import web_server, web_server_raw_array -__all__ = ['web_server', 'web_server_raw_array'] +__all__ = ["web_server", "web_server_raw_array"] diff --git a/fury/stream/server/async_app.py b/fury/stream/server/async_app.py index 2b07222d7..eac612818 100644 --- a/fury/stream/server/async_app.py +++ b/fury/stream/server/async_app.py @@ -1,12 +1,12 @@ import asyncio +from functools import partial import json import os import weakref -from functools import partial import aiohttp -import numpy as np from aiohttp import MultipartWriter, WSCloseCode, web +import numpy as np try: from aiortc import RTCPeerConnection, RTCSessionDescription @@ -15,7 +15,7 @@ WEBRTC_AVAILABLE = True except ImportError: WEBRTC_AVAILABLE = False - print('webrtc not available') + print("webrtc not available") import logging import time @@ -27,69 +27,69 @@ async def index(request, **kwargs): - folder = kwargs['folder'] - just_mjpeg = kwargs['just_mjpeg'] - index_file = 'index.html' + folder = kwargs["folder"] + just_mjpeg = kwargs["just_mjpeg"] + index_file = "index.html" if just_mjpeg: - index_file = 'index_mjpeg.html' - content = open(os.path.join(folder, index_file), 'r').read() - return web.Response(content_type='text/html', text=content) + index_file = "index_mjpeg.html" + content = open(os.path.join(folder, index_file), "r").read() + return web.Response(content_type="text/html", text=content) async def javascript(request, **kwargs): - folder = kwargs['folder'] - js = kwargs['js'] - content = open(os.path.join(folder, 'js/%s' % js), 'r').read() - return web.Response(content_type='application/javascript', text=content) + folder = kwargs["folder"] + js = kwargs["js"] + content = open(os.path.join(folder, "js/%s" % js), "r").read() + return web.Response(content_type="application/javascript", text=content) async def mjpeg_handler(request): """This async function it's responsible to create the MJPEG streaming. - Notes: - ------ + Notes + ----- endpoint : /video/mjpeg """ - my_boundary = 'image-boundary' + my_boundary = "image-boundary" response = web.StreamResponse( status=200, - reason='OK', + reason="OK", headers={ - 'Content-Type': 'multipart/x-mixed-replace;boundary={}'.format(my_boundary) + "Content-Type": "multipart/x-mixed-replace;boundary={}".format(my_boundary) }, ) await response.prepare(request) - image_buffer_manager = request.app['image_buffer_manager'] + image_buffer_manager = request.app["image_buffer_manager"] while True: jpeg_bytes = await image_buffer_manager.async_get_jpeg() - with MultipartWriter('image/jpeg', boundary=my_boundary) as mpwriter: - mpwriter.append(jpeg_bytes, {'Content-Type': 'image/jpeg'}) + with MultipartWriter("image/jpeg", boundary=my_boundary) as mpwriter: + mpwriter.append(jpeg_bytes, {"Content-Type": "image/jpeg"}) try: await mpwriter.write(response, close_boundary=False) except ConnectionResetError: - logging.info('Client connection closed') + logging.info("Client connection closed") break - await response.write(b'\r\n') + await response.write(b"\r\n") async def offer(request, **kwargs): - video = kwargs['video'] - if 'broadcast' in kwargs and kwargs['broadcast']: + video = kwargs["video"] + if "broadcast" in kwargs and kwargs["broadcast"]: video = MediaRelay().subscribe(video) params = await request.json() - offer = RTCSessionDescription(sdp=params['sdp'], type=params['type']) + offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) pc = RTCPeerConnection() pcs.add(pc) - @pc.on('connectionstatechange') + @pc.on("connectionstatechange") async def on_connectionstatechange(): - print('Connection state is %s' % pc.connectionState) - if pc.connectionState == 'failed': + print("Connection state is %s" % pc.connectionState) + if pc.connectionState == "failed": await pc.close() pcs.discard(pc) @@ -98,48 +98,48 @@ async def on_connectionstatechange(): await pc.setRemoteDescription(offer) for t in pc.getTransceivers(): - if t.kind == 'audio' and audio: + if t.kind == "audio" and audio: pc.addTrack(audio) - elif t.kind == 'video' and video: + elif t.kind == "video" and video: pc.addTrack(video) answer = await pc.createAnswer() await pc.setLocalDescription(answer) return web.Response( - content_type='application/json', + content_type="application/json", text=json.dumps( - {'sdp': pc.localDescription.sdp, 'type': pc.localDescription.type} + {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} ), ) def set_weel(data, circular_queue): - deltaY = float(data['deltaY']) - user_envent_ms = float(data['timestampInMs']) + deltaY = float(data["deltaY"]) + user_envent_ms = float(data["timestampInMs"]) ok = circular_queue.enqueue( np.array( [EVENT_IDs.mouse_weel, deltaY, 0, 0, 0, 0, user_envent_ms, 0], - dtype='float64', + dtype="float64", ) ) ts = time.time() * 1000 - logging.info(f'WEEL Time until enqueue {ts-user_envent_ms:.2f} ms') + logging.info(f"WEEL Time until enqueue {ts-user_envent_ms:.2f} ms") return ok def set_mouse(data, circular_queue): - x = float(data['x']) - y = float(data['y']) - ctrl_key = int(data['ctrlKey']) - shift_key = int(data['shiftKey']) + x = float(data["x"]) + y = float(data["y"]) + ctrl_key = int(data["ctrlKey"]) + shift_key = int(data["shiftKey"]) - user_envent_ms = float(data['timestampInMs']) + user_envent_ms = float(data["timestampInMs"]) circular_queue = circular_queue ok = circular_queue.enqueue( np.array( [EVENT_IDs.mouse_move, 0, x, y, ctrl_key, shift_key, user_envent_ms, 0], - dtype='float64', + dtype="float64", ) ) @@ -147,21 +147,20 @@ def set_mouse(data, circular_queue): def set_mouse_click(data, circular_queue): - """ - 3 | LeftButtonPressEvent + """3 | LeftButtonPressEvent 4 | LeftButtonReleaseEvent 5 | MiddleButtonPressEvent 6 | MiddleButtonReleaseEvent 7 | RightButtonPressEvent 8 | RightButtonReleaseEvent """ - on = 0 if data['on'] == 1 else 1 - ctrl = int(data['ctrlKey']) - shift = int(data['shiftKey']) - user_envent_ms = float(data['timestampInMs']) - x = float(data['x']) - y = float(data['y']) - mouse_button = int(data['mouseButton']) + on = 0 if data["on"] == 1 else 1 + ctrl = int(data["ctrlKey"]) + shift = int(data["shiftKey"]) + user_envent_ms = float(data["timestampInMs"]) + x = float(data["x"]) + y = float(data["y"]) + mouse_button = int(data["mouseButton"]) if mouse_button not in [0, 1, 2]: return False if ctrl not in [0, 1] or shift not in [0, 1]: @@ -169,7 +168,7 @@ def set_mouse_click(data, circular_queue): event_id = (mouse_button + 1) * 2 + on + 1 ok = circular_queue.enqueue( - np.array([event_id, 0, x, y, ctrl, shift, user_envent_ms, 0], dtype='float64') + np.array([event_id, 0, x, y, ctrl, shift, user_envent_ms, 0], dtype="float64") ) return ok @@ -180,39 +179,38 @@ async def on_shutdown(app): coros = [pc.close() for pc in pcs] await asyncio.gather(*coros) pcs.clear() - for ws in set(app['websockets']): - await ws.close(code=WSCloseCode.GOING_AWAY, message='Server shutdown') + for ws in set(app["websockets"]): + await ws.close(code=WSCloseCode.GOING_AWAY, message="Server shutdown") async def websocket_handler(request, **kwargs): - - circular_queue = kwargs['circular_queue'] + circular_queue = kwargs["circular_queue"] ws = web.WebSocketResponse() await ws.prepare(request) - request.app['websockets'].add(ws) + request.app["websockets"].add(ws) try: async for msg in ws: if msg.type == aiohttp.WSMsgType.TEXT: - if msg.data == 'close': + if msg.data == "close": await ws.close() else: data = json.loads(msg.data) logging.info(f'\nuser event time {data["timestampInMs"]}') - if data['type'] == 'weel': + if data["type"] == "weel": ts = time.time() * 1000 - interval = ts - data['timestampInMs'] - logging.info('WEEL request time approx ' + f'{interval:.2f} ms') + interval = ts - data["timestampInMs"] + logging.info("WEEL request time approx " + f"{interval:.2f} ms") set_weel(data, circular_queue) - elif data['type'] == 'mouseMove': + elif data["type"] == "mouseMove": set_mouse(data, circular_queue) - elif data['type'] == 'mouseLeftClick': + elif data["type"] == "mouseLeftClick": set_mouse_click(data, circular_queue) # await ws.send_str(msg.data + '/answer') elif msg.type == aiohttp.WSMsgType.ERROR: - print('ws connection closed with exception %s' % ws.exception()) + print("ws connection closed with exception {}".format(ws.exception())) finally: - request.app['websockets'].discard(ws) + request.app["websockets"].discard(ws) return ws @@ -225,12 +223,11 @@ def get_app( provides_mjpeg=False, broadcast=True, ): - if folder is None: - folder = f'{os.path.dirname(__file__)}/www/' + folder = f"{os.path.dirname(__file__)}/www/" app = web.Application() - app['websockets'] = weakref.WeakSet() + app["websockets"] = weakref.WeakSet() app.on_shutdown.append(on_shutdown) @@ -242,30 +239,30 @@ def get_app( # ) # ) app.router.add_get( - '/', partial(index, folder=folder, just_mjpeg=rtc_server is None) + "/", partial(index, folder=folder, just_mjpeg=rtc_server is None) ) js_files = [ - 'main.js', - 'main_just_mjpeg.js', - 'webrtc.js', - 'constants.js', - 'interaction.js', + "main.js", + "main_just_mjpeg.js", + "webrtc.js", + "constants.js", + "interaction.js", ] for js in js_files: - app.router.add_get('/js/%s' % js, partial(javascript, folder=folder, js=js)) + app.router.add_get("/js/%s" % js, partial(javascript, folder=folder, js=js)) - app['image_buffer_manager'] = image_buffer_manager + app["image_buffer_manager"] = image_buffer_manager if provides_mjpeg: - app.router.add_get('/video/mjpeg', mjpeg_handler) + app.router.add_get("/video/mjpeg", mjpeg_handler) if rtc_server is not None: app.router.add_post( - '/offer', partial(offer, video=rtc_server, broadcast=broadcast) + "/offer", partial(offer, video=rtc_server, broadcast=broadcast) ) if circular_queue is not None: app.add_routes( - [web.get('/ws', partial(websocket_handler, circular_queue=circular_queue))] + [web.get("/ws", partial(websocket_handler, circular_queue=circular_queue))] ) return app diff --git a/fury/stream/server/main.py b/fury/stream/server/main.py index f9e1352eb..1b7656857 100644 --- a/fury/stream/server/main.py +++ b/fury/stream/server/main.py @@ -1,10 +1,10 @@ # import os # os.environ['PYTHONASYNCIODEBUG'] = '1' # import logging -import numpy as np from aiohttp import web +import numpy as np -from fury.stream.constants import _CQUEUE, PY_VERSION_8 +from fury.stream.constants import PY_VERSION_8, _CQUEUE from fury.stream.server.async_app import get_app from fury.stream.tools import ( ArrayCircularQueue, @@ -48,7 +48,7 @@ def __init__( self, image_buffer_manager, ): - """ + """Initialize the RTCServer Parameters ---------- @@ -81,13 +81,13 @@ async def recv(self): or self.frame.planes[0].height != height ): if CYTHON_AVAILABLE: - self.frame = FuryVideoFrame(width, height, 'rgb24') + self.frame = FuryVideoFrame(width, height, "rgb24") self.image = image if not CYTHON_AVAILABLE: # if the buffer it's already flipped # self.frame.planes[0].update(self.image) - self.image = np.frombuffer(self.image, 'uint8')[ + self.image = np.frombuffer(self.image, "uint8")[ 0 : width * height * 3 ].reshape((height, width, 3)) self.image = np.flipud(self.image) @@ -117,7 +117,7 @@ def web_server_raw_array( queue_head_tail_buffer=None, queue_buffer=None, port=8000, - host='localhost', + host="localhost", provides_mjpeg=True, provides_webrtc=True, ms_jpeg=16, @@ -163,7 +163,6 @@ def web_server_raw_array( is used just to be able to test the server. """ - image_buffer_manager = RawArrayImageBufferManager( image_buffers=image_buffers, info_buffer=info_buffer ) @@ -208,7 +207,7 @@ def web_server( queue_head_tail_buffer_name=None, queue_buffer_name=None, port=8000, - host='localhost', + host="localhost", provides_mjpeg=True, provides_webrtc=True, avoid_unlink_shared_mem=True, @@ -258,7 +257,6 @@ def web_server( is used just to be able to test the server. """ - if avoid_unlink_shared_mem and PY_VERSION_8: remove_shm_from_resource_tracker() diff --git a/fury/stream/tools.py b/fury/stream/tools.py index 0362f0823..a9cbe1c3f 100644 --- a/fury/stream/tools.py +++ b/fury/stream/tools.py @@ -1,26 +1,26 @@ +from abc import ABC, abstractmethod import asyncio import io import logging import multiprocessing -import time -from abc import ABC, abstractmethod from threading import Timer +import time -import numpy as np from PIL import Image, ImageDraw +import numpy as np from fury.stream.constants import PY_VERSION_8 if PY_VERSION_8: from multiprocessing import resource_tracker, shared_memory else: - shared_memory = None # type: ignore + shared_memory = None # type: ignore -_FLOAT_ShM_TYPE = 'd' -_INT_ShM_TYPE = 'i' -_UINT_ShM_TYPE = 'I' -_BYTE_ShM_TYPE = 'B' +_FLOAT_ShM_TYPE = "d" +_INT_ShM_TYPE = "i" +_UINT_ShM_TYPE = "I" +_BYTE_ShM_TYPE = "B" _FLOAT_SIZE = np.dtype(_FLOAT_ShM_TYPE).itemsize _INT_SIZE = np.dtype(_INT_ShM_TYPE).itemsize @@ -39,7 +39,7 @@ def remove_shm_from_resource_tracker(): """ def fix_register(name, rtype): - if rtype == 'shared_memory': + if rtype == "shared_memory": return try: return resource_tracker._resource_tracker.register(self, name, rtype) @@ -49,7 +49,7 @@ def fix_register(name, rtype): resource_tracker.register = fix_register def fix_unregister(name, rtype): - if rtype == 'shared_memory': + if rtype == "shared_memory": return try: return resource_tracker._resource_tracker.unregister(self, name, rtype) @@ -58,15 +58,15 @@ def fix_unregister(name, rtype): resource_tracker.unregister = fix_unregister - if 'shared_memory' in resource_tracker._CLEANUP_FUNCS: - del resource_tracker._CLEANUP_FUNCS['shared_memory'] + if "shared_memory" in resource_tracker._CLEANUP_FUNCS: + del resource_tracker._CLEANUP_FUNCS["shared_memory"] class GenericMultiDimensionalBuffer(ABC): """This implements a abstract (generic) multidimensional buffer.""" def __init__(self, max_size=None, dimension=8): - """ + """Initialize the multidimensional buffer. Parameters ---------- @@ -101,12 +101,12 @@ def get_start_end(self, idx): def __getitem__(self, idx): start, end = self.get_start_end(idx) - logging.info(f'dequeue start {int(time.time()*1000)}') + logging.info(f"dequeue start {int(time.time()*1000)}") ts = time.time() * 1000 items = self._buffer_repr[start:end] te = time.time() * 1000 - logging.info(f'dequeue frombuffer cost {te-ts:.2f}') + logging.info(f"dequeue frombuffer cost {te-ts:.2f}") return items def __setitem__(self, idx, data): @@ -117,25 +117,20 @@ def __setitem__(self, idx, data): self._buffer_repr[start:end] = data @abstractmethod - def load_mem_resource(self): - ... # pragma: no cover + def load_mem_resource(self): ... # pragma: no cover @abstractmethod - def create_mem_resource(self): - ... # pragma: no cover + def create_mem_resource(self): ... # pragma: no cover @abstractmethod - def cleanup(self): - ... # pragma: no cover + def cleanup(self): ... # pragma: no cover class RawArrayMultiDimensionalBuffer(GenericMultiDimensionalBuffer): """This implements a multidimensional buffer with RawArray.""" def __init__(self, max_size, dimension=4, buffer=None): - """ - - Stream system uses that to implement the CircularQueue + """Stream system uses that to implement the CircularQueue with shared memory resources. Parameters @@ -180,12 +175,11 @@ def cleanup(self): class SharedMemMultiDimensionalBuffer(GenericMultiDimensionalBuffer): """This implements a generic multidimensional buffer - with SharedMemory.""" + with SharedMemory. + """ def __init__(self, max_size, dimension=4, buffer_name=None): - """ - - Stream system uses that to implement the + """Stream system uses that to implement the CircularQueue with shared memory resources. Parameters @@ -222,20 +216,20 @@ def create_mem_resource(self): self.buffer_name = self._buffer.name logging.info( [ - 'create repr multidimensional buffer ', + "create repr multidimensional buffer ", ] ) def load_mem_resource(self): self._buffer = shared_memory.SharedMemory(self.buffer_name) - sizes = np.ndarray(2, dtype='d', buffer=self._buffer.buf[0 : _FLOAT_SIZE * 2]) + sizes = np.ndarray(2, dtype="d", buffer=self._buffer.buf[0 : _FLOAT_SIZE * 2]) self.max_size = int(sizes[0]) self.dimension = int(sizes[1]) num_el = int((sizes[0] + 1) * sizes[1]) self._num_el = num_el logging.info( [ - 'load repr multidimensional buffer', + "load repr multidimensional buffer", ] ) @@ -247,11 +241,11 @@ def _create_repr(self): ) logging.info( [ - 'create repr multidimensional buffer', + "create repr multidimensional buffer", self._buffer_repr.shape, - 'max size', + "max size", self.max_size, - 'dimension', + "dimension", self.dimension, ] ) @@ -267,14 +261,15 @@ def cleanup(self): self._buffer.unlink() except FileNotFoundError: print( - f'Shared Memory {self.buffer_name}(queue_event_buffer)\ - File not found' + f"Shared Memory {self.buffer_name}(queue_event_buffer)\ + File not found" ) class GenericCircularQueue(ABC): """This implements a generic circular queue which works with - shared memory resources.""" + shared memory resources. + """ def __init__( self, @@ -284,7 +279,7 @@ def __init__( buffer=None, buffer_name=None, ): - """ + """Initialize the circular queue. Parameters ---------- @@ -391,12 +386,11 @@ def cleanup(self): class ArrayCircularQueue(GenericCircularQueue): """This implements a MultiDimensional Queue which works with - Arrays and RawArrays.""" + Arrays and RawArrays. + """ def __init__(self, max_size=10, dimension=6, head_tail_buffer=None, buffer=None): - """ - - Stream system uses that to implement user interactions + """Stream system uses that to implement user interactions Parameters ---------- @@ -416,7 +410,6 @@ def __init__(self, max_size=10, dimension=6, head_tail_buffer=None, buffer=None) RawArray to store the data """ - super().__init__(max_size, dimension, use_shared_mem=False, buffer=buffer) if head_tail_buffer is None: @@ -460,14 +453,13 @@ def cleanup(self): class SharedMemCircularQueue(GenericCircularQueue): """This implements a MultiDimensional Queue which works with - SharedMemory.""" + SharedMemory. + """ def __init__( self, max_size=10, dimension=6, head_tail_buffer_name=None, buffer_name=None ): - """ - - Stream system uses that to implement user interactions + """Stream system uses that to implement user interactions Parameters ---------- @@ -503,10 +495,10 @@ def __init__( ) logging.info( [ - 'create shared mem', - 'size repr', + "create shared mem", + "size repr", self.head_tail_buffer_repr.shape, - 'size buffer', + "size buffer", self.head_tail_buffer.size / _INT_SIZE, ] ) @@ -562,17 +554,18 @@ def cleanup(self): self.head_tail_buffer.unlink() except FileNotFoundError: print( - f'Shared Memory {self.head_tail_buffer_name}(head_tail)\ - File not found' + f"Shared Memory {self.head_tail_buffer_name}(head_tail)\ + File not found" ) class GenericImageBufferManager(ABC): """This implements a abstract (generic) ImageBufferManager with - the n-buffer technique.""" + the n-buffer technique. + """ def __init__(self, max_window_size=None, num_buffers=2, use_shared_mem=False): - """ + """Initialize the ImageBufferManager. Parameters ---------- @@ -600,12 +593,12 @@ def __init__(self, max_window_size=None, num_buffers=2, use_shared_mem=False): self._created = False size = (self.max_window_size[0], self.max_window_size[1]) - img = Image.new('RGB', size, color=(0, 0, 0)) + img = Image.new("RGB", size, color=(0, 0, 0)) d = ImageDraw.Draw(img) pos_text = (12, size[1] // 2) d.text( - pos_text, 'Image size have exceed the Buffer Max Size', fill=(255, 255, 0) + pos_text, "Image size have exceed the Buffer Max Size", fill=(255, 255, 0) ) img = np.flipud(img) self.img_exceed = np.asarray(img).flatten() @@ -657,8 +650,10 @@ def get_current_frame(self): def get_jpeg(self): """Returns a jpeg image from the buffer. - Returns: + Returns + ------- bytes: jpeg image. + """ width, height, image = self.get_current_frame() @@ -667,9 +662,9 @@ def get_jpeg(self): image = image[0 : width * height * 3].reshape((height, width, 3)) image = np.flipud(image) - image_encoded = Image.fromarray(image, mode='RGB') + image_encoded = Image.fromarray(image, mode="RGB") bytes_img_data = io.BytesIO() - image_encoded.save(bytes_img_data, format='jpeg') + image_encoded.save(bytes_img_data, format="jpeg") bytes_img = bytes_img_data.getvalue() return bytes_img @@ -702,7 +697,7 @@ def __init__( image_buffers=None, info_buffer=None, ): - """ + """Initialize the ImageBufferManager. Parameters ---------- @@ -717,6 +712,7 @@ def __init__( frame to be streamed and the respective sizes image_buffers : list of buffers, optional A list of buffers with each one containing a frame. + """ super().__init__(max_window_size, num_buffers, use_shared_mem=False) if image_buffers is None or info_buffer is None: @@ -768,7 +764,8 @@ def cleanup(self): class SharedMemImageBufferManager(GenericImageBufferManager): """This implements an ImageBufferManager using the - SharedMemory approach.""" + SharedMemory approach. + """ def __init__( self, @@ -777,11 +774,7 @@ def __init__( image_buffer_names=None, info_buffer_name=None, ): - """ - - Note - ----- - Python >=3.8 is a requirement to use this object. + """Initialize the ImageBufferManager. Parameters ---------- @@ -797,6 +790,10 @@ def __init__( image_buffer_names : list of str, optional a list of buffer names. Each buffer contains a frame + Notes + ----- + Python >=3.8 is a requirement to use this object. + """ super().__init__(max_window_size, num_buffers, use_shared_mem=True) if image_buffer_names is None or info_buffer_name is None: @@ -841,10 +838,10 @@ def create_mem_resource(self): ) logging.info( [ - 'info buffer create', - 'buffer size', + "info buffer create", + "buffer size", sizes[0], - 'repr size', + "repr size", self.info_buffer_repr.shape, ] ) @@ -862,10 +859,10 @@ def load_mem_resource(self): ) logging.info( [ - 'info buffer load', - 'buffer size', + "info buffer load", + "buffer size", sizes[0], - 'repr size', + "repr size", self.info_buffer_repr.shape, ] ) @@ -890,8 +887,8 @@ def cleanup(self): self.info_buffer.unlink() except FileNotFoundError: print( - f'Shared Memory {self.info_buffer_name}\ - (info_buffer) File not found' + f"Shared Memory {self.info_buffer_name}\ + (info_buffer) File not found" ) for buffer, name in zip(self.image_buffers, self.image_buffer_names): buffer.close() @@ -899,7 +896,7 @@ def cleanup(self): try: buffer.unlink() except FileNotFoundError: - print(f'Shared Memory {name}(buffer image) File not found') + print(f"Shared Memory {name}(buffer image) File not found") class IntervalTimerThreading: @@ -978,9 +975,7 @@ class IntervalTimer: """A object that creates a timer that calls a function periodically.""" def __init__(self, seconds, callback, *args, **kwargs): - """ - - Parameters + """Parameters ---------- seconds : float A positive float number. Represents the total amount of diff --git a/fury/stream/widget.py b/fury/stream/widget.py index 33e631489..469da2ca9 100644 --- a/fury/stream/widget.py +++ b/fury/stream/widget.py @@ -51,13 +51,14 @@ def __init__( showm, ms_stream=33, ms_interaction=33, - host='localhost', + host="localhost", port=None, - encoding='mjpeg', + encoding="mjpeg", ms_jpeg=33, queue_size=20, ): - """ + """Initialize the widget. + Parameters ---------- showm : ShowmManager @@ -79,8 +80,8 @@ def __init__( """ if not PY_VERSION_8: raise ImportError( - 'Python 3.8 or greater is required to use the\ - widget class' + "Python 3.8 or greater is required to use the\ + widget class" ) self.showm = showm self.window_size = self.showm.size @@ -112,21 +113,21 @@ def command_string(self): command_string : str """ - s = 'from fury.stream.server import web_server;' - s += 'web_server(image_buffer_names=' - s += f'{self.stream.img_manager.image_buffer_names}' + s = "from fury.stream.server import web_server;" + s += "web_server(image_buffer_names=" + s += f"{self.stream.img_manager.image_buffer_names}" s += f",info_buffer_name='{self.stream.img_manager.info_buffer_name}'," s += "queue_head_tail_buffer_name='" s += f"{self.stream_interaction.circular_queue.head_tail_buffer_name}'" s += ",queue_buffer_name='" s += f"{self.stream_interaction.circular_queue.buffer.buffer_name}'" - if self.encoding == 'mjpeg': - s += ',provides_mjpeg=True' - s += f',ms_jpeg={self.ms_jpeg}' - s += ',provides_webrtc=False' + if self.encoding == "mjpeg": + s += ",provides_mjpeg=True" + s += f",ms_jpeg={self.ms_jpeg}" + s += ",provides_webrtc=False" s += f",port={self._port},host='{self._host}'," - s += 'avoid_unlink_shared_mem=True' - s += ')' + s += "avoid_unlink_shared_mem=True" + s += ")" return s def _start_fury_client(self, use_asyncio=False): @@ -175,7 +176,7 @@ def run_command(self): return False if self._server_started: - args = [sys.executable, '-c', self.command_string] + args = [sys.executable, "-c", self.command_string] self.pserver = subprocess.Popen( args, # f'python -c "{self.command_string}"', @@ -188,14 +189,14 @@ def run_command(self): @property def url(self): """Return the url to access the server""" - url = f'http://{self._host}:{self._port}' - url += f'?iframe=1&encoding={self.encoding}' + url = f"http://{self._host}:{self._port}" + url += f"?iframe=1&encoding={self.encoding}" return url def return_iframe(self, height=200): """Return the jupyter div iframe used to show the stream""" if IPYTHON_AVAILABLE: - display(IFrame(self.url, '100%', f'{int(height)}px')) + display(IFrame(self.url, "100%", f"{int(height)}px")) def start(self, use_asyncio=False): """Start the fury client and the interaction client and return the url @@ -212,7 +213,7 @@ def start(self, use_asyncio=False): if not ok: self.stop() return False - print(f'url: {self.url}') + print(f"url: {self.url}") def display(self, height=150): """Start the server and display the url in an iframe""" diff --git a/fury/testing.py b/fury/testing.py index 50d8001ce..79f790f7a 100644 --- a/fury/testing.py +++ b/fury/testing.py @@ -1,17 +1,17 @@ """Utilities for testing.""" +from contextlib import contextmanager +from distutils.version import LooseVersion +from functools import partial import io import json import operator import sys import warnings -from contextlib import contextmanager -from distutils.version import LooseVersion -from functools import partial import numpy as np -import scipy # type: ignore from numpy.testing import assert_array_equal +import scipy # type: ignore @contextmanager @@ -37,21 +37,21 @@ def captured_output(): sys.stdout, sys.stderr = old_out, old_err -def assert_operator(value1, value2, msg='', op=operator.eq): +def assert_operator(value1, value2, msg="", op=operator.eq): """Check Boolean statement.""" if not op(value1, value2): raise AssertionError(msg.format(str(value2), str(value1))) -assert_greater_equal = partial(assert_operator, op=operator.ge, msg='{0} >= {1}') -assert_greater = partial(assert_operator, op=operator.gt, msg='{0} > {1}') -assert_less_equal = partial(assert_operator, op=operator.le, msg='{0} =< {1}') -assert_less = partial(assert_operator, op=operator.lt, msg='{0} < {1}') +assert_greater_equal = partial(assert_operator, op=operator.ge, msg="{0} >= {1}") +assert_greater = partial(assert_operator, op=operator.gt, msg="{0} > {1}") +assert_less_equal = partial(assert_operator, op=operator.le, msg="{0} =< {1}") +assert_less = partial(assert_operator, op=operator.lt, msg="{0} < {1}") assert_true = partial( - assert_operator, value2=True, op=operator.eq, msg='False is not true' + assert_operator, value2=True, op=operator.eq, msg="False is not true" ) assert_false = partial( - assert_operator, value2=False, op=operator.eq, msg='True is not false' + assert_operator, value2=False, op=operator.eq, msg="True is not false" ) assert_not_equal = partial(assert_operator, op=operator.ne) assert_equal = partial(assert_operator, op=operator.eq) @@ -63,21 +63,21 @@ def assert_arrays_equal(arrays1, arrays2): class EventCounter: - def __init__( - self, - events_names=[ - 'CharEvent', - 'MouseMoveEvent', - 'KeyPressEvent', - 'KeyReleaseEvent', - 'LeftButtonPressEvent', - 'LeftButtonReleaseEvent', - 'RightButtonPressEvent', - 'RightButtonReleaseEvent', - 'MiddleButtonPressEvent', - 'MiddleButtonReleaseEvent', - ], - ): + def __init__(self, events_names=None): + if events_names is None: + events_names = [ + "CharEvent", + "MouseMoveEvent", + "KeyPressEvent", + "KeyReleaseEvent", + "LeftButtonPressEvent", + "LeftButtonReleaseEvent", + "RightButtonPressEvent", + "RightButtonReleaseEvent", + "MiddleButtonPressEvent", + "MiddleButtonReleaseEvent", + ] + # Events to count self.events_counts = {name: 0 for name in events_names} @@ -91,13 +91,13 @@ def monitor(self, ui_component): ui_component.add_callback(obj_actor, event, self.count) def save(self, filename): - with open(filename, 'w') as f: + with open(filename, "w") as f: json.dump(self.events_counts, f) @classmethod def load(cls, filename): event_counter = cls() - with open(filename, 'r') as f: + with open(filename, "r") as f: event_counter.events_counts = json.load(f) return event_counter @@ -106,7 +106,7 @@ def check_counts(self, expected): assert_equal(len(self.events_counts), len(expected.events_counts)) # Useful loop for debugging. - msg = '{}: {} vs. {} (expected)' + msg = "{}: {} vs. {} (expected)" for event, count in expected.events_counts.items(): if self.events_counts[event] != count: print(msg.format(event, self.events_counts[event], count)) @@ -168,7 +168,7 @@ def __init__(self, record=True, modules=()): def __enter__(self): for mod in self.modules: - if hasattr(mod, '__warningregistry__'): + if hasattr(mod, "__warningregistry__"): mod_reg = mod.__warningregistry__ self._warnreg_copies[mod] = mod_reg.copy() mod_reg.clear() @@ -177,7 +177,7 @@ def __enter__(self): def __exit__(self, *exc_info): super(clear_and_catch_warnings, self).__exit__(*exc_info) for mod in self.modules: - if hasattr(mod, '__warningregistry__'): + if hasattr(mod, "__warningregistry__"): mod.__warningregistry__.clear() if mod in self._warnreg_copies: mod.__warningregistry__.update(self._warnreg_copies[mod]) @@ -188,20 +188,21 @@ def setup_test(): If imported into a file, nosetest will run this before any doctests. References - ----------- + ---------- https://github.com/numpy/numpy/commit/710e0327687b9f7653e5ac02d222ba62c657a718 https://github.com/numpy/numpy/commit/734b907fc2f7af6e40ec989ca49ee6d87e21c495 https://github.com/nipy/nibabel/pull/556 + """ - if LooseVersion(np.__version__) >= LooseVersion('1.14'): - np.set_printoptions(legacy='1.13') + if LooseVersion(np.__version__) >= LooseVersion("1.14"): + np.set_printoptions(legacy="1.13") # Temporary fix until scipy release in October 2018 # must be removed after that # print the first occurrence of matching warnings for each location # (module + line number) where the warning is issued if ( - LooseVersion(np.__version__) >= LooseVersion('1.15') - and LooseVersion(scipy.version.short_version) <= '1.1.0' + LooseVersion(np.__version__) >= LooseVersion("1.15") + and LooseVersion(scipy.version.short_version) <= "1.1.0" ): - warnings.simplefilter('default') + warnings.simplefilter("default") diff --git a/fury/tests/test_actors.py b/fury/tests/test_actors.py index 8d4722bdb..46625250d 100644 --- a/fury/tests/test_actors.py +++ b/fury/tests/test_actors.py @@ -7,9 +7,7 @@ import pytest from scipy.ndimage import center_of_mass -from fury import actor -from fury import primitive as fp -from fury import shaders, window +from fury import actor, primitive as fp, shaders, window from fury.actor import grid from fury.decorators import skip_linux, skip_osx, skip_win from fury.deprecator import ExpiredDeprecationError @@ -18,16 +16,14 @@ from fury.optpkg import optional_package from fury.primitive import prim_sphere from fury.testing import ( - assert_equal, assert_greater, assert_greater_equal, - assert_less_equal, assert_not_equal, ) from fury.utils import primitives_count_from_actor, rotate, shallow_copy # dipy, have_dipy, _ = optional_package('dipy') -matplotlib, have_matplotlib, _ = optional_package('matplotlib') +matplotlib, have_matplotlib, _ = optional_package("matplotlib") # if have_dipy: # from dipy.data import get_sphere @@ -44,7 +40,6 @@ class Sphere: - vertices = None faces = None @@ -62,7 +57,7 @@ def test_slicer(verbose=False): # window.show(scene) # copy pixels in numpy array directly - arr = window.snapshot(scene, 'test_slicer.png', offscreen=True) + arr = window.snapshot(scene, "test_slicer.png", offscreen=True) if verbose: print(arr.sum()) @@ -84,7 +79,7 @@ def test_slicer(verbose=False): # save pixels in png file not a numpy array with InTemporaryDirectory() as tmpdir: - fname = os.path.join(tmpdir, 'slice.png') + fname = os.path.join(tmpdir, "slice.png") window.snapshot(scene, fname, offscreen=True) report = window.analyze_snapshot(fname, find_objects=True) npt.assert_equal(report.objects, 1) @@ -96,7 +91,7 @@ def test_slicer(verbose=False): scene.clear() - rgb = np.zeros((30, 30, 30, 3), dtype='f8') + rgb = np.zeros((30, 30, 30, 3), dtype="f8") rgb[..., 0] = 255 rgb_actor = actor.slicer(rgb) @@ -143,7 +138,7 @@ def test_slicer(verbose=False): data = 255 * np.random.rand(50, 50, 50) affine = np.diag([1, 3, 2, 1]) - slicer = actor.slicer(data, affine, interpolation='nearest') + slicer = actor.slicer(data, affine, interpolation="nearest") slicer.display(None, None, 25) scene.add(slicer) @@ -165,7 +160,7 @@ def test_surface(): from scipy.spatial import Delaunay size = 11 - vertices = list() + vertices = [] for i in range(-size, size): for j in range(-size, size): fact1 = -math.sin(i) * math.cos(j) @@ -181,7 +176,7 @@ def test_surface(): c_loop = [None, c_arr] f_loop = [None, faces] - s_loop = [None, 'butterfly', 'loop'] + s_loop = [None, "butterfly", "loop"] for smooth_type in s_loop: for face in f_loop: @@ -192,13 +187,12 @@ def test_surface(): ) scene.add(surface_actor) # window.show(scene, size=(600, 600), reset_camera=False) - arr = window.snapshot(scene, 'test_surface.png', offscreen=True) + arr = window.snapshot(scene, "test_surface.png", offscreen=True) report = window.analyze_snapshot(arr, find_objects=True) npt.assert_equal(report.objects, 1) def test_contour_from_roi(interactive=False): - # Render volume scene = window.Scene() data = np.zeros((50, 50, 50)) @@ -234,8 +228,8 @@ def test_contour_from_roi(interactive=False): if interactive: window.show(scene2) - arr = window.snapshot(scene, 'test_surface.png', offscreen=True) - arr2 = window.snapshot(scene2, 'test_surface2.png', offscreen=True) + arr = window.snapshot(scene, "test_surface.png", offscreen=True) + arr2 = window.snapshot(scene2, "test_surface2.png", offscreen=True) report = window.analyze_snapshot(arr, find_objects=True) report2 = window.analyze_snapshot(arr2, find_objects=True) @@ -246,12 +240,12 @@ def test_contour_from_roi(interactive=False): @pytest.mark.skipif( skip_osx, - reason='This test does not work on macOS + ' - 'Travis. It works on a local machine' - ' with 3 different version of VTK. There' - ' are 2 problems to check: Travis macOS' - ' vs Azure macOS and an issue with' - ' vtkAssembly + actor opacity.', + reason="This test does not work on macOS + " + "Travis. It works on a local machine" + " with 3 different version of VTK. There" + " are 2 problems to check: Travis macOS" + " vs Azure macOS and an issue with" + " vtkAssembly + actor opacity.", ) def test_contour_from_label(interactive=False): # Render volume @@ -293,10 +287,10 @@ def test_contour_from_label(interactive=False): window.show(scene2) arr = window.snapshot( - scene, 'test_surface.png', offscreen=True, order_transparent=False + scene, "test_surface.png", offscreen=True, order_transparent=False ) arr2 = window.snapshot( - scene2, 'test_surface2.png', offscreen=True, order_transparent=True + scene2, "test_surface2.png", offscreen=True, order_transparent=True ) report = window.analyze_snapshot( @@ -356,7 +350,7 @@ def test_streamtube_and_line_actors(): shader_obj = c3.GetShaderProperty() mapper_code = shader_obj.GetGeometryShaderCode() - file_code = shaders.import_fury_shader('line.geom') + file_code = shaders.import_fury_shader("line.geom") npt.assert_equal(mapper_code, file_code) npt.assert_equal(c3.GetProperty().GetRenderLinesAsTubes(), True) @@ -406,7 +400,7 @@ def test_bundle_maps(): line = actor.line(bundle, metric, linewidth=0.1, lookup_colormap=lut) scene.add(line) - scene.add(actor.scalar_bar(lut, ' ')) + scene.add(actor.scalar_bar(lut, " ")) report = window.analyze_scene(scene) @@ -424,7 +418,7 @@ def test_bundle_maps(): # window.show(scene) report = window.analyze_scene(scene) - npt.assert_equal(report.actors_classnames[0], 'vtkLODActor') + npt.assert_equal(report.actors_classnames[0], "vtkLODActor") scene.clear() @@ -436,7 +430,7 @@ def test_bundle_maps(): # window.show(scene) report = window.analyze_scene(scene) - npt.assert_equal(report.actors_classnames[0], 'vtkLODActor') + npt.assert_equal(report.actors_classnames[0], "vtkLODActor") # window.show(scene) arr = window.snapshot(scene) @@ -458,7 +452,7 @@ def test_odf_slicer(interactive=False): # vertices and faces of a sphere rather that needing # a specific type of sphere. We can use prim_sphere # as an alternative to get_sphere. - vertices, faces = prim_sphere('repulsion100', True) + vertices, faces = prim_sphere("repulsion100", True) sphere = Sphere() sphere.vertices = vertices sphere.faces = faces @@ -479,12 +473,12 @@ def test_odf_slicer(interactive=False): # Test that affine and mask work odf_actor = actor.odf_slicer( - odfs, sphere=sphere, affine=affine, mask=mask, scale=0.25, colormap='blues' + odfs, sphere=sphere, affine=affine, mask=mask, scale=0.25, colormap="blues" ) k = 2 - I, J, _ = odfs.shape[:3] - odf_actor.display_extent(0, I - 1, 0, J - 1, k, k) + i, j, _ = odfs.shape[:3] + odf_actor.display_extent(0, i - 1, 0, j - 1, k, k) scene = window.Scene() scene.add(odf_actor) @@ -504,7 +498,7 @@ def test_odf_slicer(interactive=False): sphere=sphere, mask=mask, scale=0.25, - colormap='blues', + colormap="blues", norm=False, global_cm=True, ) @@ -544,7 +538,7 @@ def test_odf_slicer(interactive=False): sphere=sphere, mask=mask, scale=0.25, - colormap='blues', + colormap="blues", norm=False, global_cm=True, ) @@ -568,7 +562,7 @@ def test_odf_slicer(interactive=False): global_cm=True, ) - vertices2, faces2 = prim_sphere('repulsion200', True) + vertices2, faces2 = prim_sphere("repulsion200", True) sphere2 = Sphere() sphere2.vertices = vertices2 sphere2.faces = faces2 @@ -601,7 +595,7 @@ def test_odf_slicer(interactive=False): def test_peak_slicer(interactive=False): - _peak_dirs = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype='f4') + _peak_dirs = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype="f4") # peak_dirs.shape = (1, 1, 1) + peak_dirs.shape peak_dirs = np.zeros((11, 11, 11, 3, 3)) @@ -665,7 +659,7 @@ def test_peak_slicer(interactive=False): window.show(scene) report = window.analyze_scene(scene) - ex = ['vtkLODActor', 'vtkLODActor', 'vtkOpenGLActor'] + ex = ["vtkLODActor", "vtkLODActor", "vtkOpenGLActor"] npt.assert_equal(report.actors_classnames, ex) # 6d data @@ -736,7 +730,6 @@ def test_peak(): # @pytest.mark.skipif(not have_dipy, reason="Requires DIPY") def test_tensor_slicer(interactive=False): - evals = np.array([1.4, 0.35, 0.35]) * 10 ** (-3) evecs = np.eye(3) @@ -746,7 +739,7 @@ def test_tensor_slicer(interactive=False): mevals[..., :] = evals mevecs[..., :, :] = evecs - vertices, faces = prim_sphere('symmetric724', True) + vertices, faces = prim_sphere("symmetric724", True) sphere = Sphere() sphere.vertices = vertices sphere.faces = faces @@ -898,8 +891,8 @@ def test_points(interactive=False): def test_vector_text(interactive=False): - npt.assert_raises(ExpiredDeprecationError, actor.label, 'FURY Rocks') - text_actor = actor.vector_text('FURY Rocks', direction=None) + npt.assert_raises(ExpiredDeprecationError, actor.label, "FURY Rocks") + text_actor = actor.vector_text("FURY Rocks", direction=None) scene = window.Scene() scene.add(text_actor) @@ -911,35 +904,35 @@ def test_vector_text(interactive=False): if interactive: window.show(scene, reset_camera=False) - text_actor = actor.vector_text('FURY Rocks') + text_actor = actor.vector_text("FURY Rocks") npt.assert_equal(scene.GetActors().GetNumberOfItems(), 1) center = np.array(text_actor.GetCenter()) [assert_greater_equal(v, 0) for v in center] - text_actor_centered = actor.vector_text('FURY Rocks', align_center=True) + text_actor_centered = actor.vector_text("FURY Rocks", align_center=True) center = np.array(text_actor_centered.GetCenter()) npt.assert_equal(center, np.zeros(3)) - text_actor_rot_1 = actor.vector_text('FURY Rocks', direction=(1, 1, 1)) - text_actor_rot_2 = actor.vector_text('FURY Rocks', direction=(1, 1, 0)) + text_actor_rot_1 = actor.vector_text("FURY Rocks", direction=(1, 1, 1)) + text_actor_rot_2 = actor.vector_text("FURY Rocks", direction=(1, 1, 0)) center_1 = text_actor_rot_1.GetCenter() center_2 = text_actor_rot_2.GetCenter() assert_not_equal(np.linalg.norm(center_1), np.linalg.norm(center_2)) # test centered - text_centered = actor.vector_text('FURY Rocks', align_center=True) + text_centered = actor.vector_text("FURY Rocks", align_center=True) center_3 = text_centered.GetCenter() npt.assert_almost_equal(np.linalg.norm(center_3), 0.0) text_extruded = actor.vector_text( - 'FURY Rocks', scale=(0.2, 0.2, 0.2), extrusion=1.123 + "FURY Rocks", scale=(0.2, 0.2, 0.2), extrusion=1.123 ) z_max = text_extruded.GetBounds()[-1] npt.assert_almost_equal(z_max, 1.123) text_extruded_centered = actor.vector_text( - 'FURY Rocks', + "FURY Rocks", scale=(0.2, 0.2, 0.2), direction=None, align_center=True, @@ -993,7 +986,7 @@ def test_spheres(interactive=False): # test faces and vertices scene.clear() - vertices, faces = fp.prim_sphere(name='symmetric362', gen_faces=False) + vertices, faces = fp.prim_sphere(name="symmetric362", gen_faces=False) sphere_actor = actor.sphere( centers=xyzr[:, :3], colors=colors[:], @@ -1141,7 +1134,7 @@ def test_basic_geometry_actor(interactive=False): arr = window.snapshot(scene) report = window.analyze_snapshot(arr, colors=colors) - msg = 'Failed with {}, scale={}'.format(act_func.__name__, scale) + msg = "Failed with {}, scale={}".format(act_func.__name__, scale) npt.assert_equal(report.objects, 3, err_msg=msg) @@ -1152,10 +1145,10 @@ def test_advanced_geometry_actor(interactive=False): heights = np.array([5, 7, 10]) actor_list = [ - [actor.cone, {'heights': heights, 'resolution': 8}], - [actor.arrow, {'scales': heights, 'resolution': 9}], - [actor.cylinder, {'heights': heights, 'resolution': 10}], - [actor.disk, {'rinner': 4, 'router': 8, 'rresolution': 2, 'cresolution': 2}], + [actor.cone, {"heights": heights, "resolution": 8}], + [actor.arrow, {"scales": heights, "resolution": 9}], + [actor.cylinder, {"heights": heights, "resolution": 10}], + [actor.disk, {"rinner": 4, "router": 8, "rresolution": 2, "cresolution": 2}], ] scene = window.Scene() @@ -1189,34 +1182,34 @@ def test_advanced_geometry_actor(interactive=False): def test_text_3d(): - msg = 'I \nlove\n FURY' + msg = "I \nlove\n FURY" txt_actor = actor.text_3d(msg) npt.assert_equal(txt_actor.get_message().lower(), msg.lower()) - npt.assert_raises(ValueError, txt_actor.justification, 'middle') - npt.assert_raises(ValueError, txt_actor.vertical_justification, 'center') + npt.assert_raises(ValueError, txt_actor.justification, "middle") + npt.assert_raises(ValueError, txt_actor.vertical_justification, "center") scene = window.Scene() scene.add(txt_actor) - txt_actor.vertical_justification('middle') - txt_actor.justification('right') + txt_actor.vertical_justification("middle") + txt_actor.justification("right") arr_right = window.snapshot(scene, size=(1920, 1080), offscreen=True) scene.clear() - txt_actor.vertical_justification('middle') - txt_actor.justification('left') + txt_actor.vertical_justification("middle") + txt_actor.justification("left") scene.add(txt_actor) arr_left = window.snapshot(scene, size=(1920, 1080), offscreen=True) # X axis of right alignment should have a higher center of mass position # than left assert_greater(center_of_mass(arr_right)[0], center_of_mass(arr_left)[0]) scene.clear() - txt_actor.justification('center') - txt_actor.vertical_justification('top') + txt_actor.justification("center") + txt_actor.vertical_justification("top") scene.add(txt_actor) arr_top = window.snapshot(scene, size=(1920, 1080), offscreen=True) scene.clear() - txt_actor.justification('center') - txt_actor.vertical_justification('bottom') + txt_actor.justification("center") + txt_actor.vertical_justification("bottom") scene.add(txt_actor) arr_bottom = window.snapshot(scene, size=(1920, 1080), offscreen=True) assert_greater_equal(center_of_mass(arr_top)[0], center_of_mass(arr_bottom)[0]) @@ -1276,27 +1269,27 @@ def test_grid(_interactive=False): texts = [] actors.append(contour_actor1) - text_actor1 = actor.text_3d('cube 1', justification='center') + text_actor1 = actor.text_3d("cube 1", justification="center") texts.append(text_actor1) actors.append(contour_actor2) - text_actor2 = actor.text_3d('cube 2', justification='center') + text_actor2 = actor.text_3d("cube 2", justification="center") texts.append(text_actor2) actors.append(contour_actor3) - text_actor3 = actor.text_3d('cube 3', justification='center') + text_actor3 = actor.text_3d("cube 3", justification="center") texts.append(text_actor3) actors.append(shallow_copy(contour_actor1)) - text_actor1 = 'cube 4' + text_actor1 = "cube 4" texts.append(text_actor1) actors.append(shallow_copy(contour_actor2)) - text_actor2 = 'cube 5' + text_actor2 = "cube 5" texts.append(text_actor2) actors.append(shallow_copy(contour_actor3)) - text_actor3 = 'cube 6' + text_actor3 = "cube 6" texts.append(text_actor3) # show the grid without the captions @@ -1310,7 +1303,7 @@ def test_grid(_interactive=False): scene.add(container) - scene.projection('orthogonal') + scene.projection("orthogonal") counter = itertools.count() @@ -1358,7 +1351,7 @@ def timer_callback(_obj, _event): def test_direct_sphere_mapping(): - arr = 255 * np.ones((810, 1620, 3), dtype='uint8') + arr = 255 * np.ones((810, 1620, 3), dtype="uint8") rows, cols, _ = arr.shape rs = rows // 2 @@ -1380,7 +1373,7 @@ def test_direct_sphere_mapping(): def test_texture_mapping(): - arr = np.zeros((512, 212, 3), dtype='uint8') + arr = np.zeros((512, 212, 3), dtype="uint8") arr[:256, :] = np.array([255, 0, 0]) arr[256:, :] = np.array([0, 255, 0]) tp = actor.texture(arr, interp=True) @@ -1397,7 +1390,7 @@ def test_texture_mapping(): def test_texture_update(): - arr = np.zeros((512, 212, 3), dtype='uint8') + arr = np.zeros((512, 212, 3), dtype="uint8") arr[:256, :] = np.array([255, 0, 0]) arr[256:, :] = np.array([0, 255, 0]) # create a texture on plane @@ -1413,7 +1406,7 @@ def test_texture_update(): ) # update the texture - new_arr = np.zeros((512, 212, 3), dtype='uint8') + new_arr = np.zeros((512, 212, 3), dtype="uint8") new_arr[:, :] = np.array([255, 255, 255]) actor.texture_update(tp, new_arr) display = window.snapshot(scene) @@ -1431,11 +1424,11 @@ def test_texture_update(): def test_figure_vs_texture_actor(): - arr = (255 * np.ones((512, 212, 4))).astype('uint8') + arr = (255 * np.ones((512, 212, 4))).astype("uint8") arr[20:40, 20:40, 3] = 0 tp = actor.figure(arr) - arr[20:40, 20:40, :] = np.array([255, 0, 0, 255], dtype='uint8') + arr[20:40, 20:40, :] = np.array([255, 0, 0, 255], dtype="uint8") tp2 = actor.texture(arr) scene = window.Scene() scene.add(tp) @@ -1451,9 +1444,9 @@ def test_figure_vs_texture_actor(): npt.assert_equal(res.colors_found, [True, True]) -@pytest.mark.skipif(not have_matplotlib, reason='Requires MatplotLib') +@pytest.mark.skipif(not have_matplotlib, reason="Requires MatplotLib") def test_matplotlib_figure(): - names = ['group_a', 'group_b', 'group_c'] + names = ["group_a", "group_b", "group_c"] values = [1, 10, 100] fig = plt.figure(figsize=(9, 3)) @@ -1464,12 +1457,12 @@ def test_matplotlib_figure(): plt.scatter(names, values) plt.subplot(133) plt.plot(names, values) - plt.suptitle('Categorical Plotting') + plt.suptitle("Categorical Plotting") arr = matplotlib_figure_to_numpy(fig, dpi=500, transparent=True) - plt.close('all') - fig_actor = actor.figure(arr, 'cubic') - fig_actor2 = actor.figure(arr, 'cubic') + plt.close("all") + fig_actor = actor.figure(arr, "cubic") + fig_actor2 = actor.figure(arr, "cubic") scene = window.Scene() scene.background((1, 1, 1.0)) @@ -1480,9 +1473,9 @@ def test_matplotlib_figure(): ax_actor.SetPosition(-50, 500, -800) fig_actor2.SetPosition(500, 800, -400) display = window.snapshot( - scene, 'test_mpl.png', order_transparent=False, offscreen=True + scene, "test_mpl.png", order_transparent=False, offscreen=True ) - res = window.analyze_snapshot( + _ = window.analyze_snapshot( display, bg_color=(255, 255, 255.0), colors=[(31, 119, 180)], find_objects=False ) # omit assert from now until we know why snapshot creates @@ -1579,10 +1572,10 @@ def test_billboard_actor(interactive=False): npt.assert_equal(report.objects, 8) scene.clear() - centers = np.array([[0, 0, 0], [-15, 15, -5], [10, -10, 5], - [-30, 30, -10], [20, -20, 10]]) - colors = np.array([[1, 1, 0], [0, 0, 1], [1, 0, 1], - [1, 0, 0], [0, 1, 0]]) + centers = np.array( + [[0, 0, 0], [-15, 15, -5], [10, -10, 5], [-30, 30, -10], [20, -20, 10]] + ) + colors = np.array([[1, 1, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0], [0, 1, 0]]) scales = [3, 1, 2, 1, 1.5] b_point = """ @@ -1594,15 +1587,16 @@ def test_billboard_actor(interactive=False): {fragOutput0 = vec4(color, 1);} """ - b_type = ['spherical', 'cylindrical_x', 'cylindrical_y'] + b_type = ["spherical", "cylindrical_x", "cylindrical_y"] expected_val = [True, False, False] rotations = [[87, 0, -87, 87], [87, 0, -87, 87], [0, 87, 87, -87]] for i in range(3): - billboard = actor.billboard(centers, colors=colors, scales=scales, - bb_type=b_type[i], fs_impl=b_point) + billboard = actor.billboard( + centers, colors=colors, scales=scales, bb_type=b_type[i], fs_impl=b_point + ) scene.add(billboard) - if b_type[i] == 'spherical': + if b_type[i] == "spherical": arr = window.snapshot(scene) report = window.analyze_snapshot(arr, colors=255 * colors) npt.assert_equal(report.colors_found, [True] * 5) @@ -1635,10 +1629,10 @@ def test_billboard_actor(interactive=False): @pytest.mark.skipif( skip_win, - reason='This test does not work on Windows' - ' due to snapshot (memory access' - ' violation). Check what is causing this' - ' issue with shader', + reason="This test does not work on Windows" + " due to snapshot (memory access" + " violation). Check what is causing this" + " issue with shader", ) def test_sdf_actor(interactive=False): scene = window.Scene() @@ -1647,7 +1641,7 @@ def test_sdf_actor(interactive=False): colors = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0]]) directions = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1], [1, 1, 0]]) scales = [1, 2, 3, 4] - primitive = ['sphere', 'ellipsoid', 'torus', 'capsule'] + primitive = ["sphere", "ellipsoid", "torus", "capsule"] sdf_actor = actor.sdf(centers, directions, colors, primitive, scales) scene.add(sdf_actor) @@ -1661,7 +1655,7 @@ def test_sdf_actor(interactive=False): # Draw 3 spheres as the primitive type is str scene.clear() - primitive = 'sphere' + primitive = "sphere" sdf_actor = actor.sdf(centers, directions, colors, primitive, scales) scene.add(sdf_actor) scene.add(actor.axes()) @@ -1675,7 +1669,7 @@ def test_sdf_actor(interactive=False): # A sphere and default back to two torus # as the primitive type is list scene.clear() - primitive = ['sphere'] + primitive = ["sphere"] with npt.assert_warns(UserWarning): sdf_actor = actor.sdf(centers, directions, colors, primitive, scales) @@ -1691,7 +1685,7 @@ def test_sdf_actor(interactive=False): # One sphere and ellipsoid each # Default to torus scene.clear() - primitive = ['sphere', 'ellipsoid'] + primitive = ["sphere", "ellipsoid"] with npt.assert_warns(UserWarning): sdf_actor = actor.sdf(centers, directions, colors, primitive, scales) @@ -1707,17 +1701,17 @@ def test_sdf_actor(interactive=False): @pytest.mark.skipif( skip_linux, - reason='This test does not work on Ubuntu. It ' - 'works on a local machine. Check after ' - 'fixing memory leak with RenderWindow.', + reason="This test does not work on Ubuntu. It " + "works on a local machine. Check after " + "fixing memory leak with RenderWindow.", ) def test_marker_actor(interactive=False): scene = window.Scene() scene.background((1, 1, 1)) centers_3do = np.array([[4, 0, 0], [4, 4, 0], [4, 8, 0]]) - markers_2d = ['o', 's', 'd', '^', 'p', 'h', 's6', 'x', '+'] + markers_2d = ["o", "s", "d", "^", "p", "h", "s6", "x", "+"] center_markers_2d = np.array([[0, i * 2, 0] for i in range(len(markers_2d))]) - fake_spheres = actor.markers(centers_3do, colors=(0, 1, 0), scales=1, marker='3d') + fake_spheres = actor.markers(centers_3do, colors=(0, 1, 0), scales=1, marker="3d") markers_2d = actor.markers( center_markers_2d, colors=(0, 1, 0), scales=1, marker=markers_2d ) @@ -1737,33 +1731,47 @@ def test_marker_actor(interactive=False): def test_ellipsoid_actor(interactive=False): # number of axes does not match with number of centers centers = [-1, 1, 0] - axes = [[[1, 0, 0], [0, 1, 0], [0, 0, 1]], - [[1, 2, -2], [2, 1, 2], [2, -2, -1]]] + axes = [[[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[1, 2, -2], [2, 1, 2], [2, -2, -1]]] lengths = [[1, 1, 1]] npt.assert_raises(ValueError, actor.ellipsoid, centers, axes, lengths) # number of lengths does not match with number of centers - lengths = [[1, 1, 1], [1, 1, .5]] + lengths = [[1, 1, 1], [1, 1, 0.5]] npt.assert_raises(ValueError, actor.ellipsoid, centers, axes, lengths) scene = window.Scene() scene.background((0, 0, 0)) - axes = np.array([[[-.6, .5, -.6], [-.8, -.4, .5], [-.1, -.7, -.7]], - [[.1, .6, -.8], [.6, .5, .5], [-.8, .6, .3]], - [[.7, .5, -.5], [0, -.7, -.7], [-.7, .6, -.5]], - [[.7, -.3, -.6], [.2, -.8, .6], [.7, .6, .5]], - [[1, 2, -2], [2, 1, 2], [2, -2, -1]], - [[1, 0, 0], [0, 1, 0], [0, 0, 1]]]) - lengths = np.array([[1, 1, 1], [1, 1, .5], [1, .5, .5], - [1, .5, .25], [1, 1, .3], [1, .3, .3]]) - centers = np.array([[-1, 1, 0], [0, 1, 0], [1, 1, 0], - [-1, 0, 0], [0, 0, 0], [1, 0, 0]]) - colors = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], - [1, 1, 0], [1, 0, 1], [0, 1, 1]]) - - ellipsoids = actor.ellipsoid(axes=axes, lengths=lengths, centers=centers, - scales=1.0, colors=colors) + axes = np.array( + [ + [[-0.6, 0.5, -0.6], [-0.8, -0.4, 0.5], [-0.1, -0.7, -0.7]], + [[0.1, 0.6, -0.8], [0.6, 0.5, 0.5], [-0.8, 0.6, 0.3]], + [[0.7, 0.5, -0.5], [0, -0.7, -0.7], [-0.7, 0.6, -0.5]], + [[0.7, -0.3, -0.6], [0.2, -0.8, 0.6], [0.7, 0.6, 0.5]], + [[1, 2, -2], [2, 1, 2], [2, -2, -1]], + [[1, 0, 0], [0, 1, 0], [0, 0, 1]], + ] + ) + lengths = np.array( + [ + [1, 1, 1], + [1, 1, 0.5], + [1, 0.5, 0.5], + [1, 0.5, 0.25], + [1, 1, 0.3], + [1, 0.3, 0.3], + ] + ) + centers = np.array( + [[-1, 1, 0], [0, 1, 0], [1, 1, 0], [-1, 0, 0], [0, 0, 0], [1, 0, 0]] + ) + colors = np.array( + [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1]] + ) + + ellipsoids = actor.ellipsoid( + axes=axes, lengths=lengths, centers=centers, scales=1.0, colors=colors + ) scene.add(ellipsoids) if interactive: @@ -1773,15 +1781,93 @@ def test_ellipsoid_actor(interactive=False): npt.assert_equal(report.actors, 1) +def test_uncertainty_cone_actor(interactive=False): + scene = window.Scene() + + evals = np.array([1.4, 0.5, 0.35]) + evecs = np.eye(3) + + mevals = np.zeros((10, 10, 1, 3)) + mevecs = np.zeros((10, 10, 1, 3, 3)) + + mevals[..., :] = evals + mevecs[..., :, :] = evecs + + signal = np.ones((10, 10, 1, 10)) + sigma = np.array( + [ + 14.791911, + 14.999622, + 14.880976, + 14.933881, + 14.392784, + 14.132468, + 14.334953, + 14.409375, + 14.514647, + 14.409275, + ] + ) + + b_matrix = np.array( + [ + [-1.8, -1.9, -4.8, -4.4, -2.3, -1.2, -1.0], + [-5.4, -1.8, -1.6, -1.7, -6.1, -1.3, -1.0], + [-6.2, -5.1, -1.0, -1.9, -9.3, -2.2, -1.0], + [-2.8, -1.9, -4.8, -1.4, -2.1, -3.6, -1.0], + [-5.6, -1.3, -7.8, -2.4, -5.2, -4.2, -1.0], + [-1.8, -2.5, -1.8, -1.2, -2.3, -4.8, -1.0], + [-2.3, -1.9, -6.8, -4.4, -6.4, -1.9, -1.0], + [-1.8, -2.6, -4.8, -6.5, -7.7, -3.1, -1.0], + [-6.2, -1.9, -5.6, -4.6, -1.5, -2.0, -1.0], + [-2.4, -1.9, -4.5, -3.6, -2.5, -1.2, -1.0], + ] + ) + + uncert_cones = actor.uncertainty_cone( + evecs=mevecs, evals=mevals, signal=signal, sigma=sigma, b_matrix=b_matrix + ) + scene.add(uncert_cones) + + if interactive: + window.show(scene) + + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) + scene.clear() + + evals = np.array([1.4, 0.5, 0.35]) + evecs = np.eye(3) + + mevals = np.zeros((4, 4, 4, 3)) + mevecs = np.zeros((4, 4, 4, 3, 3)) + + mevals[..., :] = evals + mevecs[..., :, :] = evecs + + signal = np.ones((4, 4, 4, 10)) + uncert_cones = actor.uncertainty_cone( + evecs=mevecs, evals=mevals, signal=signal, sigma=sigma, b_matrix=b_matrix + ) + scene.add(uncert_cones) + + if interactive: + window.show(scene) + + report = window.analyze_scene(scene) + npt.assert_equal(report.actors, 1) + scene.clear() + + def test_actors_primitives_count(): centers = np.array([[1, 1, 1], [2, 2, 2]]) directions = np.array([[1, 0, 0], [1, 0, 0]]) colors = np.array([[1, 0, 0], [1, 0, 0]]) lines = np.array([[[0, 0, 0], [1, 1, 1]], [[1, 1, 1], [2, 2, 2]]]) - args_1 = {'centers': centers} - args_2 = {**args_1, 'colors': colors} - args_3 = {**args_2, 'directions': directions} + args_1 = {"centers": centers} + args_2 = {**args_1, "colors": colors} + args_3 = {**args_2, "directions": directions} cen_c = len(centers) lin_c = len(lines) @@ -1791,8 +1877,8 @@ def test_actors_primitives_count(): [actor.rectangle, args_1, cen_c], [actor.square, args_1, cen_c], [actor.cube, args_1, cen_c], - [actor.sphere, {**args_2, 'use_primitive': True}, cen_c], - [actor.sphere, {**args_2, 'use_primitive': False}, cen_c], + [actor.sphere, {**args_2, "use_primitive": True}, cen_c], + [actor.sphere, {**args_2, "use_primitive": False}, cen_c], [actor.sdf, args_1, cen_c], [actor.billboard, args_1, cen_c], [actor.superquadric, args_1, cen_c], @@ -1804,14 +1890,14 @@ def test_actors_primitives_count(): [actor.rhombicuboctahedron, args_1, cen_c], [actor.cylinder, args_3, cen_c], [actor.disk, args_3, cen_c], - [actor.cone, {**args_3, 'use_primitive': False}, cen_c], - [actor.cone, {**args_3, 'use_primitive': True}, cen_c], - [actor.arrow, {**args_3, 'repeat_primitive': False}, cen_c], - [actor.arrow, {**args_3, 'repeat_primitive': True}, cen_c], - [actor.dot, {'points': centers}, cen_c], - [actor.point, {'points': centers, 'colors': colors}, cen_c], - [actor.line, {'lines': lines}, lin_c], - [actor.streamtube, {'lines': lines}, lin_c], + [actor.cone, {**args_3, "use_primitive": False}, cen_c], + [actor.cone, {**args_3, "use_primitive": True}, cen_c], + [actor.arrow, {**args_3, "repeat_primitive": False}, cen_c], + [actor.arrow, {**args_3, "repeat_primitive": True}, cen_c], + [actor.dot, {"points": centers}, cen_c], + [actor.point, {"points": centers, "colors": colors}, cen_c], + [actor.line, {"lines": lines}, lin_c], + [actor.streamtube, {"lines": lines}, lin_c], ] for test_case in actors_test_cases: act_func = test_case[0] diff --git a/fury/tests/test_colormap.py b/fury/tests/test_colormap.py index 9ecac9235..14b79a117 100644 --- a/fury/tests/test_colormap.py +++ b/fury/tests/test_colormap.py @@ -6,7 +6,7 @@ from fury import colormap from fury.optpkg import optional_package -cm, have_matplotlib, _ = optional_package('matplotlib.cm') +cm, have_matplotlib, _ = optional_package("matplotlib.cm") def test_boys2rgb(): @@ -35,8 +35,8 @@ def test_orient2rgb(): def test_get_cmap(): - npt.assert_equal(colormap.get_cmap(''), None) - npt.assert_equal(colormap.get_cmap('blues'), None) + npt.assert_equal(colormap.get_cmap(""), None) + npt.assert_equal(colormap.get_cmap("blues"), None) expected = np.array( [ @@ -52,11 +52,11 @@ def test_get_cmap(): [0.498039, 0.788235, 0.498039, 1], ] ) - cmap = colormap.get_cmap('Blues') + cmap = colormap.get_cmap("Blues") npt.assert_array_almost_equal(cmap((1, 0, 0)), expected) with npt.assert_warns(PendingDeprecationWarning): - cmap = colormap.get_cmap('Accent') + cmap = colormap.get_cmap("Accent") npt.assert_array_almost_equal(cmap((1, 0, 0)), expected2) @@ -65,19 +65,19 @@ def test_line_colors(): s2 = np.array([np.arange(5)] * 4) # 5x4 streamlines = [s1, s2] - s_color = colormap.line_colors(streamlines, cmap='boys_standard') + s_color = colormap.line_colors(streamlines, cmap="boys_standard") npt.assert_equal(s_color.shape, (2, 3)) def test_create_colormap(): value = np.arange(25) npt.assert_raises(ValueError, colormap.create_colormap, value.reshape((5, 5))) - npt.assert_raises(AttributeError, colormap.create_colormap, value, name='fake') + npt.assert_raises(AttributeError, colormap.create_colormap, value, name="fake") npt.assert_warns( PendingDeprecationWarning, colormap.create_colormap, value, - name='jet', + name="jet", auto=False, ) @@ -134,17 +134,16 @@ def test_lab2rgb(): def test_hex_to_rgb(): expected = np.array([1, 1, 1]) - hexcode = '#FFFFFF' + hexcode = "#FFFFFF" res = colormap.hex_to_rgb(hexcode) npt.assert_array_almost_equal(res, expected) - hashed_hexcode = 'FFFFFF' + hashed_hexcode = "FFFFFF" res = colormap.hex_to_rgb(hashed_hexcode) npt.assert_array_almost_equal(res, expected) def test_color_converters(): - color = np.array([1, 1, 1]) colors = np.array([[1, 1, 1], [0, 0, 0], [0.2, 0.3, 0.4]]) @@ -161,8 +160,8 @@ def test_color_converters(): npt.assert_almost_equal(rgb_from_xyz_color, color) # testing rgb2lab and lab2rgb - illuminant = 'D65' - observer = '2' + illuminant = "D65" + observer = "2" expected_lab = np.array([31.57976662, -1.86550104, -17.84845331]) lab_color = colormap.rgb2lab(color, illuminant, observer) rgb_color = colormap.lab2rgb(expected_lab, illuminant, observer) diff --git a/fury/tests/test_convert.py b/fury/tests/test_convert.py index 3a40830b3..d7ac36f8b 100644 --- a/fury/tests/test_convert.py +++ b/fury/tests/test_convert.py @@ -9,7 +9,7 @@ # Optional packages from fury.optpkg import optional_package -matplotlib, have_matplotlib, _ = optional_package('matplotlib') +matplotlib, have_matplotlib, _ = optional_package("matplotlib") if have_matplotlib: import matplotlib.pyplot as plt @@ -17,9 +17,9 @@ from fury.convert import matplotlib_figure_to_numpy -@pytest.mark.skipif(not have_matplotlib, reason='Requires MatplotLib') +@pytest.mark.skipif(not have_matplotlib, reason="Requires MatplotLib") def test_convert(): - names = ['group_a', 'group_b', 'group_c'] + names = ["group_a", "group_b", "group_c"] values = [1, 10, 100] fig = plt.figure(figsize=(9, 3)) @@ -29,12 +29,14 @@ def test_convert(): plt.scatter(names, values) plt.subplot(133) plt.plot(names, values) - plt.suptitle('Categorical Plotting') + plt.suptitle("Categorical Plotting") arr2 = matplotlib_figure_to_numpy(fig, transparent=False, flip_up_down=False) with TemporaryDirectory() as tmpdir: - fname = os.path.join(tmpdir, 'tmp.png') + fname = os.path.join(tmpdir, "tmp.png") dpi = 100 - fig.savefig(fname, transparent=False, bbox_inches='tight', pad_inches=0) + fig.savefig( + fname, transparent=False, dpi=dpi, bbox_inches="tight", pad_inches=0 + ) arr1 = load_image(fname) npt.assert_array_equal(arr1, arr2) diff --git a/fury/tests/test_deprecator.py b/fury/tests/test_deprecator.py index aea8b4897..ca1bacad8 100644 --- a/fury/tests/test_deprecator.py +++ b/fury/tests/test_deprecator.py @@ -20,76 +20,76 @@ @pytest.mark.skipif( skip_win and is_py35, - reason='Issue with setuptools, check ' - 'https://github.com/pypa/setuptools/issues/1903', + reason="Issue with setuptools, check " + "https://github.com/pypa/setuptools/issues/1903", ) def test_cmp_pkg_version(): # Test version comparator npt.assert_equal(cmp_pkg_version(fury.__version__), 0) - npt.assert_equal(cmp_pkg_version('0.0'), -1) - npt.assert_equal(cmp_pkg_version('1000.1000.1'), 1) + npt.assert_equal(cmp_pkg_version("0.0"), -1) + npt.assert_equal(cmp_pkg_version("1000.1000.1"), 1) npt.assert_equal(cmp_pkg_version(fury.__version__, fury.__version__), 0) for test_ver, pkg_ver, exp_out in ( - ('1.0', '1.0', 0), - ('1.0.0', '1.0', 0), - ('1.0', '1.0.0', 0), - ('1.1', '1.1', 0), - ('1.2', '1.1', 1), - ('1.1', '1.2', -1), - ('1.1.1', '1.1.1', 0), - ('1.1.2', '1.1.1', 1), - ('1.1.1', '1.1.2', -1), - ('1.1', '1.1dev', 1), - ('1.1dev', '1.1', -1), - ('1.2.1', '1.2.1rc1', 1), - ('1.2.1rc1', '1.2.1', -1), - ('1.2.1rc1', '1.2.1rc', 1), - ('1.2.1rc', '1.2.1rc1', -1), - ('1.2.1rc1', '1.2.1rc', 1), - ('1.2.1rc', '1.2.1rc1', -1), - ('1.2.1b', '1.2.1a', 1), - ('1.2.1a', '1.2.1b', -1), + ("1.0", "1.0", 0), + ("1.0.0", "1.0", 0), + ("1.0", "1.0.0", 0), + ("1.1", "1.1", 0), + ("1.2", "1.1", 1), + ("1.1", "1.2", -1), + ("1.1.1", "1.1.1", 0), + ("1.1.2", "1.1.1", 1), + ("1.1.1", "1.1.2", -1), + ("1.1", "1.1dev", 1), + ("1.1dev", "1.1", -1), + ("1.2.1", "1.2.1rc1", 1), + ("1.2.1rc1", "1.2.1", -1), + ("1.2.1rc1", "1.2.1rc", 1), + ("1.2.1rc", "1.2.1rc1", -1), + ("1.2.1rc1", "1.2.1rc", 1), + ("1.2.1rc", "1.2.1rc1", -1), + ("1.2.1b", "1.2.1a", 1), + ("1.2.1a", "1.2.1b", -1), ): npt.assert_equal(cmp_pkg_version(test_ver, pkg_ver), exp_out) - npt.assert_raises(ValueError, cmp_pkg_version, 'foo.2') - npt.assert_raises(ValueError, cmp_pkg_version, 'foo.2', '1.0') - npt.assert_raises(ValueError, cmp_pkg_version, '1.0', 'foo.2') - npt.assert_raises(ValueError, cmp_pkg_version, 'foo') + npt.assert_raises(ValueError, cmp_pkg_version, "foo.2") + npt.assert_raises(ValueError, cmp_pkg_version, "foo.2", "1.0") + npt.assert_raises(ValueError, cmp_pkg_version, "1.0", "foo.2") + npt.assert_raises(ValueError, cmp_pkg_version, "foo") def test__ensure_cr(): # Make sure text ends with carriage return - npt.assert_equal(_ensure_cr(' foo'), ' foo\n') - npt.assert_equal(_ensure_cr(' foo\n'), ' foo\n') - npt.assert_equal(_ensure_cr(' foo '), ' foo\n') - npt.assert_equal(_ensure_cr('foo '), 'foo\n') - npt.assert_equal(_ensure_cr('foo \n bar'), 'foo \n bar\n') - npt.assert_equal(_ensure_cr('foo \n\n'), 'foo\n') + npt.assert_equal(_ensure_cr(" foo"), " foo\n") + npt.assert_equal(_ensure_cr(" foo\n"), " foo\n") + npt.assert_equal(_ensure_cr(" foo "), " foo\n") + npt.assert_equal(_ensure_cr("foo "), "foo\n") + npt.assert_equal(_ensure_cr("foo \n bar"), "foo \n bar\n") + npt.assert_equal(_ensure_cr("foo \n\n"), "foo\n") def test__add_dep_doc(): # Test utility function to add deprecation message to docstring - npt.assert_equal(_add_dep_doc('', 'foo'), 'foo\n') - npt.assert_equal(_add_dep_doc('bar', 'foo'), 'bar\n\nfoo\n') - npt.assert_equal(_add_dep_doc(' bar', 'foo'), ' bar\n\nfoo\n') - npt.assert_equal(_add_dep_doc(' bar', 'foo\n'), ' bar\n\nfoo\n') - npt.assert_equal(_add_dep_doc('bar\n\n', 'foo'), 'bar\n\nfoo\n') - npt.assert_equal(_add_dep_doc('bar\n \n', 'foo'), 'bar\n\nfoo\n') + npt.assert_equal(_add_dep_doc("", "foo"), "foo\n") + npt.assert_equal(_add_dep_doc("bar", "foo"), "bar\n\nfoo\n") + npt.assert_equal(_add_dep_doc(" bar", "foo"), " bar\n\nfoo\n") + npt.assert_equal(_add_dep_doc(" bar", "foo\n"), " bar\n\nfoo\n") + npt.assert_equal(_add_dep_doc("bar\n\n", "foo"), "bar\n\nfoo\n") + npt.assert_equal(_add_dep_doc("bar\n \n", "foo"), "bar\n\nfoo\n") npt.assert_equal( - _add_dep_doc(' bar\n\nSome explanation', 'foo\nbaz'), - ' bar\n\nfoo\nbaz\n\nSome explanation\n', + _add_dep_doc(" bar\n\nSome explanation", "foo\nbaz"), + " bar\n\nfoo\nbaz\n\nSome explanation\n", ) npt.assert_equal( - _add_dep_doc(' bar\n\n Some explanation', 'foo\nbaz'), - ' bar\n \n foo\n baz\n \n Some explanation\n', + _add_dep_doc(" bar\n\n Some explanation", "foo\nbaz"), + " bar\n \n foo\n baz\n \n Some explanation\n", ) @pytest.mark.skipif( skip_win and is_py35, - reason='Issue with setuptools, check ' - 'https://github.com/pypa/setuptools/issues/1903', + reason="Issue with setuptools, check " + "https://github.com/pypa/setuptools/issues/1903", ) def test_deprecate_with_version(): def func_no_doc(): @@ -107,81 +107,81 @@ class CustomError(Exception): my_mod = sys.modules[__name__] dec = deprecate_with_version - func = dec('foo')(func_no_doc) + func = dec("foo")(func_no_doc) with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(), None) npt.assert_equal(len(w), 1) assert_true(w[0].category is DeprecationWarning) - npt.assert_equal(func.__doc__, 'foo\n') - func = dec('foo')(func_doc) + npt.assert_equal(func.__doc__, "foo\n") + func = dec("foo")(func_doc) with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(1), None) npt.assert_equal(len(w), 1) - npt.assert_equal(func.__doc__, 'A docstring\n\nfoo\n') - func = dec('foo')(func_doc_long) + npt.assert_equal(func.__doc__, "A docstring\n\nfoo\n") + func = dec("foo")(func_doc_long) with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(1, 2), None) npt.assert_equal(len(w), 1) - npt.assert_equal(func.__doc__, 'A docstring\n \n foo\n \n Some text\n') + npt.assert_equal(func.__doc__, "A docstring\n \n foo\n \n Some text\n") # Try some since and until versions - func = dec('foo', '0.2')(func_no_doc) - npt.assert_equal(func.__doc__, 'foo\n\n* deprecated from version: 0.2\n') + func = dec("foo", "0.2")(func_no_doc) + npt.assert_equal(func.__doc__, "foo\n\n* deprecated from version: 0.2\n") with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(), None) npt.assert_equal(len(w), 1) - func = dec('foo', until='10.6')(func_no_doc) + func = dec("foo", until="10.6")(func_no_doc) with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(), None) npt.assert_equal(len(w), 1) npt.assert_equal( func.__doc__, - 'foo\n\n* Will raise {} as of version: 10.6\n'.format(ExpiredDeprecationError), + "foo\n\n* Will raise {} as of version: 10.6\n".format(ExpiredDeprecationError), ) - func = dec('foo', until='0.3')(func_no_doc) + func = dec("foo", until="0.3")(func_no_doc) npt.assert_raises(ExpiredDeprecationError, func) npt.assert_equal( func.__doc__, - 'foo\n\n* Raises {} as of version: 0.3\n'.format(ExpiredDeprecationError), + "foo\n\n* Raises {} as of version: 0.3\n".format(ExpiredDeprecationError), ) - func = dec('foo', '0.2', '0.3')(func_no_doc) + func = dec("foo", "0.2", "0.3")(func_no_doc) npt.assert_raises(ExpiredDeprecationError, func) npt.assert_equal( func.__doc__, - 'foo\n\n* deprecated from version: 0.2\n' - '* Raises {} as of version: 0.3\n'.format(ExpiredDeprecationError), + "foo\n\n* deprecated from version: 0.2\n" + "* Raises {} as of version: 0.3\n".format(ExpiredDeprecationError), ) - func = dec('foo', '0.2', '0.3')(func_doc_long) + func = dec("foo", "0.2", "0.3")(func_doc_long) npt.assert_equal( func.__doc__, - 'A docstring\n \n foo\n \n' - ' * deprecated from version: 0.2\n' - ' * Raises {} as of version: 0.3\n \n' - ' Some text\n'.format(ExpiredDeprecationError), + "A docstring\n \n foo\n \n" + " * deprecated from version: 0.2\n" + " * Raises {} as of version: 0.3\n \n" + " Some text\n".format(ExpiredDeprecationError), ) npt.assert_raises(ExpiredDeprecationError, func) # Check different warnings and errors - func = dec('foo', warn_class=UserWarning)(func_no_doc) + func = dec("foo", warn_class=UserWarning)(func_no_doc) with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(), None) npt.assert_equal(len(w), 1) assert_true(w[0].category is UserWarning) - func = dec('foo', error_class=CustomError)(func_no_doc) + func = dec("foo", error_class=CustomError)(func_no_doc) with clear_and_catch_warnings(modules=[my_mod]) as w: - warnings.simplefilter('always') + warnings.simplefilter("always") npt.assert_equal(func(), None) npt.assert_equal(len(w), 1) assert_true(w[0].category is DeprecationWarning) - func = dec('foo', until='0.3', error_class=CustomError)(func_no_doc) + func = dec("foo", until="0.3", error_class=CustomError)(func_no_doc) npt.assert_raises(CustomError, func) @@ -189,32 +189,32 @@ def test_deprecated_argument(): # Tests the decorator with function, method, staticmethod and classmethod. class CustomActor: @classmethod - @deprecated_params('height', 'scale', '0.3') + @deprecated_params("height", "scale", "0.3") def test1(cls, scale): return scale @staticmethod - @deprecated_params('height', 'scale', '0.3') + @deprecated_params("height", "scale", "0.3") def test2(scale): return scale - @deprecated_params('height', 'scale', '0.3') + @deprecated_params("height", "scale", "0.3") def test3(self, scale): return scale - @deprecated_params('height', 'scale', '0.3', '0.5') + @deprecated_params("height", "scale", "0.3", "0.5") def test4(self, scale): return scale - @deprecated_params('height', 'scale', '0.3', '10.0.0') + @deprecated_params("height", "scale", "0.3", "10.0.0") def test5(self, scale): return scale - @deprecated_params('height', 'scale', '0.3') + @deprecated_params("height", "scale", "0.3") def custom_actor(scale): return scale - @deprecated_params('height', 'scale', '0.3', '0.5') + @deprecated_params("height", "scale", "0.3", "0.5") def custom_actor_2(scale): return scale @@ -231,7 +231,7 @@ def custom_actor_2(scale): # As new keyword argument npt.assert_equal(method(scale=1), 1) # As old keyword argument - if method.__name__ not in ['test4', 'custom_actor_2']: + if method.__name__ not in ["test4", "custom_actor_2"]: res = npt.assert_warns(ArgsDeprecationWarning, method, height=1) npt.assert_equal(res, 1) else: @@ -246,13 +246,13 @@ def custom_actor_2(scale): def test_deprecated_argument_in_kwargs(): # To rename an argument that is consumed by "kwargs" the "arg_in_kwargs" # parameter is used. - @deprecated_params('height', 'scale', '0.3', arg_in_kwargs=True) + @deprecated_params("height", "scale", "0.3", arg_in_kwargs=True) def test(**kwargs): - return kwargs['scale'] + return kwargs["scale"] - @deprecated_params('height', 'scale', '0.3', '0.5', arg_in_kwargs=True) + @deprecated_params("height", "scale", "0.3", "0.5", arg_in_kwargs=True) def test2(**kwargs): - return kwargs['scale'] + return kwargs["scale"] # As positional argument only npt.assert_raises(TypeError, test, 1) @@ -273,11 +273,11 @@ def test2(**kwargs): def test_deprecated_argument_multi_deprecation(): - @deprecated_params(['x', 'y', 'z'], ['a', 'b', 'c'], [0.3, 0.2, 0.4]) + @deprecated_params(["x", "y", "z"], ["a", "b", "c"], [0.3, 0.2, 0.4]) def test(a, b, c): return a, b, c - @deprecated_params(['x', 'y', 'z'], ['a', 'b', 'c'], '0.3') + @deprecated_params(["x", "y", "z"], ["a", "b", "c"], "0.3") def test2(a, b, c): return a, b, c @@ -296,61 +296,61 @@ def test_deprecated_argument_not_allowed_use(): # arg_in_kwargs parameter. Without it it raises a TypeError. with pytest.raises(TypeError): - @deprecated_params('height', 'scale', '0.3') + @deprecated_params("height", "scale", "0.3") def test1(**kwargs): - return kwargs['scale'] + return kwargs["scale"] # Cannot replace "*args". with pytest.raises(TypeError): - @deprecated_params('scale', 'args', '0.3') + @deprecated_params("scale", "args", "0.3") def test2(*args): return args # Cannot replace "**kwargs". with pytest.raises(TypeError): - @deprecated_params('scale', 'kwargs', '0.3') + @deprecated_params("scale", "kwargs", "0.3") def test3(**kwargs): return kwargs # wrong number of arguments with pytest.raises(ValueError): - @deprecated_params(['a', 'b', 'c'], ['x', 'y'], '0.3') + @deprecated_params(["a", "b", "c"], ["x", "y"], "0.3") def test4(**kwargs): return kwargs def test_deprecated_argument_remove(): - @deprecated_params('x', None, '0.3', alternative='test2.y') + @deprecated_params("x", None, "0.3", alternative="test2.y") def test(dummy=11, x=3): return dummy, x - @deprecated_params('x', None, '0.3', '0.5', alternative='test2.y') + @deprecated_params("x", None, "0.3", "0.5", alternative="test2.y") def test2(dummy=11, x=3): return dummy, x - @deprecated_params(['dummy', 'x'], None, '0.3', alternative='test2.y') + @deprecated_params(["dummy", "x"], None, "0.3", alternative="test2.y") def test3(dummy=11, x=3): return dummy, x - @deprecated_params(['dummy', 'x'], None, '0.3', '0.5', alternative='test2.y') + @deprecated_params(["dummy", "x"], None, "0.3", "0.5", alternative="test2.y") def test4(dummy=11, x=3): return dummy, x - with pytest.warns(ArgsDeprecationWarning, match=r'Use test2\.y instead') as w: + with pytest.warns(ArgsDeprecationWarning, match=r"Use test2\.y instead") as w: npt.assert_equal(test(x=1), (11, 1)) npt.assert_equal(len(w), 1) - with pytest.warns(ArgsDeprecationWarning, match=r'Use test2\.y instead') as w: + with pytest.warns(ArgsDeprecationWarning, match=r"Use test2\.y instead") as w: npt.assert_equal(test(x=1, dummy=10), (10, 1)) npt.assert_equal(len(w), 1) - with pytest.warns(ArgsDeprecationWarning, match=r'Use test2\.y instead'): + with pytest.warns(ArgsDeprecationWarning, match=r"Use test2\.y instead"): npt.assert_equal(test(121, 1), (121, 1)) - with pytest.warns(ArgsDeprecationWarning, match=r'Use test2\.y instead') as w: + with pytest.warns(ArgsDeprecationWarning, match=r"Use test2\.y instead") as w: npt.assert_equal(test3(121, 1), (121, 1)) npt.assert_raises(ExpiredDeprecationError, test4, 121, 1) diff --git a/fury/tests/test_gltf.py b/fury/tests/test_gltf.py index 5f848c2e4..df1a7b5b4 100644 --- a/fury/tests/test_gltf.py +++ b/fury/tests/test_gltf.py @@ -1,12 +1,11 @@ import itertools import os -import sys +from PIL import Image import numpy as np import numpy.testing as npt -import pytest from packaging.version import parse -from PIL import Image +import pytest from scipy.ndimage import center_of_mass from scipy.version import short_version @@ -16,7 +15,7 @@ from fury.gltf import export_scene, glTF from fury.testing import assert_equal, assert_greater -SCIPY_1_8_PLUS = parse(short_version) >= parse('1.8.0') +SCIPY_1_8_PLUS = parse(short_version) >= parse("1.8.0") if SCIPY_1_8_PLUS: from scipy.ndimage._measurements import _stats @@ -25,8 +24,8 @@ def test_load_gltf(): - fetch_gltf('Duck') - filename = read_viz_gltf('Duck', 'glTF') + fetch_gltf("Duck") + filename = read_viz_gltf("Duck", "glTF") importer = glTF(filename) polydatas = importer.polydatas vertices = utils.get_polydata_vertices(polydatas[0]) @@ -45,8 +44,8 @@ def test_load_gltf(): def test_load_texture(): - fetch_gltf('Duck') - filename = read_viz_gltf('Duck', 'glTF') + fetch_gltf("Duck") + filename = read_viz_gltf("Duck", "glTF") importer = glTF(filename) actor = importer.actors()[0] @@ -63,8 +62,8 @@ def test_load_texture(): @pytest.mark.skipif(True, reason="This test is failing on CI, not sure why yet") def test_colors(): # vertex colors - fetch_gltf('BoxVertexColors') - file = read_viz_gltf('BoxVertexColors', 'glTF') + fetch_gltf("BoxVertexColors") + file = read_viz_gltf("BoxVertexColors", "glTF") importer = glTF(file) actor = importer.actors()[0] scene = window.Scene() @@ -80,8 +79,8 @@ def test_colors(): scene.clear() # material colors - fetch_gltf('BoxAnimated') - file = read_viz_gltf('BoxAnimated', 'glTF') + fetch_gltf("BoxAnimated") + file = read_viz_gltf("BoxAnimated", "glTF") importer = glTF(file) actors = importer.actors() scene.add(*actors) @@ -96,8 +95,8 @@ def test_colors(): def test_orientation(): - fetch_gltf('BoxTextured', 'glTF-Embedded') - file = read_viz_gltf('BoxTextured', 'glTF-Embedded') + fetch_gltf("BoxTextured", "glTF-Embedded") + file = read_viz_gltf("BoxTextured", "glTF-Embedded") importer = glTF(file) actor = importer.actors()[0] @@ -134,15 +133,15 @@ def test_export_gltf(): cube = actor.cube(np.add(centers, np.array([2, 0, 0])), colors=colors) scene.add(cube) - export_scene(scene, 'test.gltf') - gltf_obj = glTF('test.gltf') + export_scene(scene, "test.gltf") + gltf_obj = glTF("test.gltf") actors = gltf_obj.actors() npt.assert_equal(len(actors), 1) sphere = actor.sphere(centers, np.array([1, 0, 0]), use_primitive=False) scene.add(sphere) - export_scene(scene, 'test.gltf') - gltf_obj = glTF('test.gltf') + export_scene(scene, "test.gltf") + gltf_obj = glTF("test.gltf") actors = gltf_obj.actors() scene.clear() @@ -154,8 +153,8 @@ def test_export_gltf(): focal_point=(0.0, 0.0, 0.0), view_up=(0.0, 0.0, 1.0), ) - export_scene(scene, 'test.gltf') - gltf_obj = glTF('test.gltf') + export_scene(scene, "test.gltf") + gltf_obj = glTF("test.gltf") actors = gltf_obj.actors() scene.clear() @@ -167,15 +166,15 @@ def test_export_gltf(): scene.reset_camera_tight() scene.clear() - fetch_gltf('BoxTextured', 'glTF') - filename = read_viz_gltf('BoxTextured') + fetch_gltf("BoxTextured", "glTF") + filename = read_viz_gltf("BoxTextured") gltf_obj = glTF(filename) box_actor = gltf_obj.actors() scene.add(*box_actor) - export_scene(scene, 'test.gltf') + export_scene(scene, "test.gltf") scene.clear() - gltf_obj = glTF('test.gltf') + gltf_obj = glTF("test.gltf") actors = gltf_obj.actors() scene.add(*actors) @@ -190,8 +189,8 @@ def test_export_gltf(): def test_simple_animation(): - fetch_gltf('BoxAnimated', 'glTF') - file = read_viz_gltf('BoxAnimated') + fetch_gltf("BoxAnimated", "glTF") + file = read_viz_gltf("BoxAnimated") gltf_obj = glTF(file) timeline = Timeline() animation = gltf_obj.main_animation() @@ -204,7 +203,7 @@ def test_simple_animation(): # timestamp animation seek timeline.seek(0.0) - showm.save_screenshot('keyframe1.png') + showm.save_screenshot("keyframe1.png") timeline.seek(2.57) showm.save_screenshot('keyframe2.png') @@ -225,10 +224,10 @@ def test_simple_animation(): def test_skinning(): # animation test - fetch_gltf('SimpleSkin', 'glTF') - file = read_viz_gltf('SimpleSkin') + fetch_gltf("SimpleSkin", "glTF") + file = read_viz_gltf("SimpleSkin") gltf_obj = glTF(file) - animation = gltf_obj.skin_animation()['anim_0'] + animation = gltf_obj.skin_animation()["anim_0"] timeline = Timeline(animation) # checking weights and joints weights = np.array( @@ -274,9 +273,9 @@ def test_skinning(): timeline.seek(1.0) timeline.seek(4.00) - showm.save_screenshot('keyframe2.png') - res1 = np.asarray(Image.open('keyframe1.png')) - res2 = np.asarray(Image.open('keyframe2.png')) + showm.save_screenshot("keyframe2.png") + res1 = np.asarray(Image.open("keyframe1.png")) + res2 = np.asarray(Image.open("keyframe2.png")) avg = center_of_mass(res1) print(avg) @@ -299,9 +298,9 @@ def timer_callback(_obj, _event): joint_matrices = [] ibms = [] for i, bone in enumerate(gltf_obj.bones[0]): - if animation.is_interpolatable(f'transform{bone}'): + if animation.is_interpolatable(f"transform{bone}"): deform = animation.get_value( - f'transform{bone}', animation.current_timestamp + f"transform{bone}", animation.current_timestamp ) ibm = gltf_obj.ibms[0][i].T ibms.append(ibm) @@ -316,9 +315,9 @@ def timer_callback(_obj, _event): showm.render() if cnt == 10: - showm.save_screenshot('keyframe1.png') + showm.save_screenshot("keyframe1.png") if cnt == 100: - showm.save_screenshot('keyframe2.png') + showm.save_screenshot("keyframe2.png") if cnt == 150: showm.destroy_timer(timer_id) @@ -329,15 +328,15 @@ def timer_callback(_obj, _event): def test_morphing(): - fetch_gltf('MorphStressTest', 'glTF') - file = read_viz_gltf('MorphStressTest') + fetch_gltf("MorphStressTest", "glTF") + file = read_viz_gltf("MorphStressTest") gltf_obj = glTF(file) animations = gltf_obj.morph_animation() npt.assert_equal(len(gltf_obj._actors), 2) npt.assert_equal(len(gltf_obj.morph_weights), 16) - npt.assert_equal(list(animations.keys()), ['Individuals', 'TheWave', 'Pulse']) - anim_1 = animations['TheWave'] + npt.assert_equal(list(animations.keys()), ["Individuals", "TheWave", "Pulse"]) + anim_1 = animations["TheWave"] gltf_obj.update_morph(anim_1) scene = window.Scene() @@ -350,18 +349,18 @@ def test_morphing(): timeline_1.seek(0.1) gltf_obj.update_morph(anim_1) - showm.save_screenshot('keyframe1.png') - res_1 = window.analyze_snapshot('keyframe1.png') + showm.save_screenshot("keyframe1.png") + res_1 = window.analyze_snapshot("keyframe1.png") timeline_1.seek(1.50) gltf_obj.update_morph(anim_1) - showm.save_screenshot('keyframe2.png') - res_2 = window.analyze_snapshot('keyframe2.png') + showm.save_screenshot("keyframe2.png") + res_2 = window.analyze_snapshot("keyframe2.png") npt.assert_equal(res_1.colors_found, res_2.colors_found) - img_1 = np.asarray(Image.open('keyframe1.png').convert('L')) - img_2 = np.asarray(Image.open('keyframe2.png').convert('L')) + img_1 = np.asarray(Image.open("keyframe1.png").convert("L")) + img_2 = np.asarray(Image.open("keyframe2.png").convert("L")) stats_1, stats_2 = _stats(img_1), _stats(img_2) # Assert right image size assert_equal(stats_1[0], stats_2[0]) diff --git a/fury/tests/test_interactor.py b/fury/tests/test_interactor.py index 346dc4acb..33c6132ce 100644 --- a/fury/tests/test_interactor.py +++ b/fury/tests/test_interactor.py @@ -5,20 +5,18 @@ import numpy.testing as npt import pytest -from fury import actor, interactor, ui -from fury import utils as vtk_utils -from fury import window +from fury import actor, interactor, ui, utils as vtk_utils, window from fury.data import DATA_DIR -from fury.decorators import skip_osx, skip_win +from fury.decorators import skip_win from fury.lib import VTK_VERSION, Actor2D, PolyDataMapper2D, RegularPolygonSource @pytest.mark.skipif( - skip_win, reason='This test does not work on Windows.' ' Need to be introspected' + skip_win, reason="This test does not work on Windows." " Need to be introspected" ) def test_custom_interactor_style_events(recording=False): - print('Using VTK {}'.format(VTK_VERSION)) - filename = 'test_custom_interactor_style_events.log.gz' + print("Using VTK {}".format(VTK_VERSION)) + filename = "test_custom_interactor_style_events.log.gz" recording_filename = pjoin(DATA_DIR, filename) scene = window.Scene() @@ -50,7 +48,7 @@ def follow_mouse(iren, obj): iren.force_render() interactor_style.add_active_prop(cursor) - interactor_style.add_callback(cursor, 'MouseMoveEvent', follow_mouse) + interactor_style.add_callback(cursor, "MouseMoveEvent", follow_mouse) # create some minimalistic streamlines lines = [ @@ -71,16 +69,16 @@ def counter(iren, _obj): # Assign the counter callback to every possible event. for event in [ - 'CharEvent', - 'MouseMoveEvent', - 'KeyPressEvent', - 'KeyReleaseEvent', - 'LeftButtonPressEvent', - 'LeftButtonReleaseEvent', - 'RightButtonPressEvent', - 'RightButtonReleaseEvent', - 'MiddleButtonPressEvent', - 'MiddleButtonReleaseEvent', + "CharEvent", + "MouseMoveEvent", + "KeyPressEvent", + "KeyReleaseEvent", + "LeftButtonPressEvent", + "LeftButtonReleaseEvent", + "RightButtonPressEvent", + "RightButtonReleaseEvent", + "MiddleButtonPressEvent", + "MiddleButtonReleaseEvent", ]: interactor_style.add_callback(tube1, event, counter) @@ -99,20 +97,20 @@ def scale_down_obj(iren, obj): iren.force_render() iren.event.abort() # Stop propagating the event. - interactor_style.add_callback(tube2, 'MouseWheelForwardEvent', scale_up_obj) - interactor_style.add_callback(tube2, 'MouseWheelBackwardEvent', scale_down_obj) + interactor_style.add_callback(tube2, "MouseWheelForwardEvent", scale_up_obj) + interactor_style.add_callback(tube2, "MouseWheelBackwardEvent", scale_down_obj) # Add callback to hide/show tube1. def toggle_visibility(iren, obj): key = iren.event.key - if key.lower() == 'v': + if key.lower() == "v": obj.SetVisibility(not obj.GetVisibility()) iren.force_render() interactor_style.add_active_prop(tube1) interactor_style.add_active_prop(tube2) interactor_style.remove_active_prop(tube2) - interactor_style.add_callback(tube1, 'CharEvent', toggle_visibility) + interactor_style.add_callback(tube1, "CharEvent", toggle_visibility) if recording: show_manager.record_events_to_file(recording_filename) @@ -121,40 +119,40 @@ def toggle_visibility(iren, obj): show_manager.play_events_from_file(recording_filename) msg = "Wrong count for '{}'." expected = [ - ('CharEvent', 6), - ('KeyPressEvent', 6), - ('KeyReleaseEvent', 6), - ('MouseMoveEvent', 1652), - ('LeftButtonPressEvent', 1), - ('RightButtonPressEvent', 1), - ('MiddleButtonPressEvent', 2), - ('LeftButtonReleaseEvent', 1), - ('MouseWheelForwardEvent', 3), - ('MouseWheelBackwardEvent', 1), - ('MiddleButtonReleaseEvent', 2), - ('RightButtonReleaseEvent', 1), + ("CharEvent", 6), + ("KeyPressEvent", 6), + ("KeyReleaseEvent", 6), + ("MouseMoveEvent", 1652), + ("LeftButtonPressEvent", 1), + ("RightButtonPressEvent", 1), + ("MiddleButtonPressEvent", 2), + ("LeftButtonReleaseEvent", 1), + ("MouseWheelForwardEvent", 3), + ("MouseWheelBackwardEvent", 1), + ("MiddleButtonReleaseEvent", 2), + ("RightButtonReleaseEvent", 1), ] # Useful loop for debugging. for event, count in expected: if states[event] != count: - print('{}: {} vs. {} (expected)'.format(event, states[event], count)) + print("{}: {} vs. {} (expected)".format(event, states[event], count)) for event, count in expected: npt.assert_equal(states[event], count, err_msg=msg.format(event)) def test_double_click_events(recording=False): - filename = 'test_double_click_events.log.gz' + filename = "test_double_click_events.log.gz" recording_filename = pjoin(DATA_DIR, filename) label = ui.TextBlock2D( position=(400, 780), font_size=40, color=(1, 0.5, 0), - justification='center', - vertical_justification='top', - text='FURY rocks!!!', + justification="center", + vertical_justification="top", + text="FURY rocks!!!", ) cube = actor.cube( @@ -167,43 +165,43 @@ def test_double_click_events(recording=False): states = defaultdict(lambda: 0) def left_single_click(iren, obj): - states['LeftButtonPressEvent'] += 1 + states["LeftButtonPressEvent"] += 1 iren.force_render() def left_double_click(iren, obj): - states['LeftButtonDoubleClickEvent'] += 1 + states["LeftButtonDoubleClickEvent"] += 1 label.color = (1, 0, 0) iren.force_render() def right_single_click(iren, obj): - states['RightButtonPressEvent'] += 1 + states["RightButtonPressEvent"] += 1 iren.force_render() def right_double_click(iren, obj): - states['RightButtonDoubleClickEvent'] += 1 + states["RightButtonDoubleClickEvent"] += 1 label.color = (0, 1, 0) iren.force_render() def middle_single_click(iren, obj): - states['MiddleButtonPressEvent'] += 1 + states["MiddleButtonPressEvent"] += 1 iren.force_render() def middle_double_click(iren, obj): - states['MiddleButtonDoubleClickEvent'] += 1 + states["MiddleButtonDoubleClickEvent"] += 1 label.color = (0, 0, 1) iren.force_render() test_events = { - 'LeftButtonPressEvent': left_single_click, - 'LeftButtonDoubleClickEvent': left_double_click, - 'RightButtonPressEvent': right_single_click, - 'RightButtonDoubleClickEvent': right_double_click, - 'MiddleButtonPressEvent': middle_single_click, - 'MiddleButtonDoubleClickEvent': middle_double_click, + "LeftButtonPressEvent": left_single_click, + "LeftButtonDoubleClickEvent": left_double_click, + "RightButtonPressEvent": right_single_click, + "RightButtonDoubleClickEvent": right_double_click, + "MiddleButtonPressEvent": middle_single_click, + "MiddleButtonDoubleClickEvent": middle_double_click, } current_size = (800, 800) - showm = window.ShowManager(size=current_size, title='Double Click Test') + showm = window.ShowManager(size=current_size, title="Double Click Test") showm.scene.add(cube) showm.scene.add(label) @@ -217,23 +215,23 @@ def middle_double_click(iren, obj): showm.play_events_from_file(recording_filename) msg = "Wrong count for '{}'." expected = [ - ('LeftButtonPressEvent', 3), - ('LeftButtonDoubleClickEvent', 1), - ('MiddleButtonPressEvent', 3), - ('MiddleButtonDoubleClickEvent', 1), - ('RightButtonPressEvent', 2), - ('RightButtonDoubleClickEvent', 1), + ("LeftButtonPressEvent", 3), + ("LeftButtonDoubleClickEvent", 1), + ("MiddleButtonPressEvent", 3), + ("MiddleButtonDoubleClickEvent", 1), + ("RightButtonPressEvent", 2), + ("RightButtonDoubleClickEvent", 1), ] # Useful loop for debugging. for event, count in expected: if states[event] != count: - print('{}: {} vs. {} (expected)'.format(event, states[event], count)) + print("{}: {} vs. {} (expected)".format(event, states[event], count)) for event, count in expected: npt.assert_equal(states[event], count, err_msg=msg.format(event)) -if __name__ == '__main__': +if __name__ == "__main__": test_custom_interactor_style_events(recording=False) test_double_click_events(recording=False) diff --git a/fury/tests/test_io.py b/fury/tests/test_io.py index dff705055..277a25e2e 100644 --- a/fury/tests/test_io.py +++ b/fury/tests/test_io.py @@ -2,10 +2,10 @@ from os.path import join as pjoin from tempfile import TemporaryDirectory as InTemporaryDirectory +from PIL import Image import numpy as np import numpy.testing as npt import pytest -from PIL import Image from fury.decorators import skip_osx from fury.io import ( @@ -23,8 +23,8 @@ def test_save_and_load_polydata(): - l_ext = ['vtk', 'fib', 'ply', 'xml'] - fname = 'temp-io' + l_ext = ["vtk", "fib", "ply", "xml"] + fname = "temp-io" for ext in l_ext: with InTemporaryDirectory() as odir: @@ -33,7 +33,7 @@ def test_save_and_load_polydata(): pd = PolyData() pd.SetPoints(numpy_to_vtk_points(data)) - fname_path = pjoin(odir, '{0}.{1}'.format(fname, ext)) + fname_path = pjoin(odir, "{0}.{1}".format(fname, ext)) save_polydata(pd, fname_path) npt.assert_equal(os.path.isfile(fname_path), True) @@ -44,23 +44,23 @@ def test_save_and_load_polydata(): npt.assert_array_equal(data, out_data) - npt.assert_raises(IOError, save_polydata, PolyData(), 'test.vti') - npt.assert_raises(IOError, save_polydata, PolyData(), 'test.obj') - npt.assert_raises(IOError, load_polydata, 'test.vti') - npt.assert_raises(FileNotFoundError, load_polydata, 'does-not-exist.obj') + npt.assert_raises(IOError, save_polydata, PolyData(), "test.vti") + npt.assert_raises(IOError, save_polydata, PolyData(), "test.obj") + npt.assert_raises(IOError, load_polydata, "test.vti") + npt.assert_raises(FileNotFoundError, load_polydata, "does-not-exist.obj") def test_save_and_load_options(): - l_ext = ['ply', 'vtk'] + l_ext = ["ply", "vtk"] l_options = [ { - 'color_array_name': 'horizon', + "color_array_name": "horizon", }, { - 'binary': True, + "binary": True, }, ] - fname = 'temp-io' + fname = "temp-io" for ext, option in zip(l_ext, l_options): with InTemporaryDirectory() as odir: @@ -69,7 +69,7 @@ def test_save_and_load_options(): pd = PolyData() pd.SetPoints(numpy_to_vtk_points(data)) - fname_path = pjoin(odir, '{0}.{1}'.format(fname, ext)) + fname_path = pjoin(odir, "{0}.{1}".format(fname, ext)) save_polydata(pd, fname_path, **option) npt.assert_equal(os.path.isfile(fname_path), True) @@ -80,11 +80,11 @@ def test_save_and_load_options(): npt.assert_array_equal(data, out_data) - l_ext = ['vtk', 'vtp', 'ply', 'stl', 'mni.obj'] + l_ext = ["vtk", "vtp", "ply", "stl", "mni.obj"] l_options = [ {}, { - 'binary': False, + "binary": False, }, ] for ext, option in zip(l_ext, l_options): @@ -94,7 +94,7 @@ def test_save_and_load_options(): pd = PolyData() pd.SetPoints(numpy_to_vtk_points(data)) - fname_path = pjoin(odir, '{0}.{1}'.format(fname, ext)) + fname_path = pjoin(odir, "{0}.{1}".format(fname, ext)) save_polydata(pd, fname_path, **option) npt.assert_equal(os.path.isfile(fname_path), True) @@ -102,22 +102,22 @@ def test_save_and_load_options(): def test_save_load_image(): - l_ext = ['png', 'jpeg', 'jpg', 'bmp', 'tiff'] + l_ext = ["png", "jpeg", "jpg", "bmp", "tiff"] fury_logo_link = ( - 'https://raw.githubusercontent.com/fury-gl/' - 'fury-communication-assets/main/fury-logo.png' + "https://raw.githubusercontent.com/fury-gl/" + "fury-communication-assets/main/fury-logo.png" ) - invalid_link = 'https://picsum.photos/200' - fname = 'temp-io' + invalid_link = "https://picsum.photos/200" + fname = "temp-io" for ext in l_ext: with InTemporaryDirectory() as odir: data = np.random.randint(0, 255, size=(50, 3), dtype=np.uint8) url_image = load_image(fury_logo_link) - url_fname_path = pjoin(odir, f'fury_logo.{ext}') - fname_path = pjoin(odir, '{0}.{1}'.format(fname, ext)) + url_fname_path = pjoin(odir, f"fury_logo.{ext}") + fname_path = pjoin(odir, "{0}.{1}".format(fname, ext)) save_image(data, fname_path, compression_quality=100) save_image(url_image, url_fname_path, compression_quality=100) @@ -129,38 +129,37 @@ def test_save_load_image(): assert_greater(os.stat(url_fname_path).st_size, 0) out_image = load_image(fname_path) - if ext not in ['jpeg', 'jpg', 'tiff']: + if ext not in ["jpeg", "jpg", "tiff"]: npt.assert_array_equal(data[..., 0], out_image[..., 0]) else: - npt.assert_array_almost_equal( data[..., 0], out_image[..., 0], decimal=0 ) npt.assert_raises(IOError, load_image, invalid_link) - npt.assert_raises(IOError, load_image, 'test.vtk') - npt.assert_raises(IOError, load_image, 'test.vtk', use_pillow=False) + npt.assert_raises(IOError, load_image, "test.vtk") + npt.assert_raises(IOError, load_image, "test.vtk", use_pillow=False) npt.assert_raises( - IOError, save_image, np.random.randint(0, 255, size=(50, 3)), 'test.vtk' + IOError, save_image, np.random.randint(0, 255, size=(50, 3)), "test.vtk" ) npt.assert_raises( IOError, save_image, np.random.randint(0, 255, size=(50, 3)), - 'test.vtk', + "test.vtk", use_pillow=False, ) npt.assert_raises( - IOError, save_image, np.random.randint(0, 255, size=(50, 3, 1, 1)), 'test.png' + IOError, save_image, np.random.randint(0, 255, size=(50, 3, 1, 1)), "test.png" ) - compression_type = [None, 'bits', 'random'] + compression_type = [None, "bits", "random"] for ct in compression_type: with InTemporaryDirectory() as odir: try: data = np.random.randint(0, 255, size=(50, 3), dtype=np.uint8) - fname_path = pjoin(odir, '{0}.tif'.format(fname)) + fname_path = pjoin(odir, "{0}.tif".format(fname)) save_image(data, fname_path, compression_type=ct, use_pillow=False) npt.assert_equal(os.path.isfile(fname_path), True) @@ -171,15 +170,14 @@ def test_save_load_image(): @pytest.mark.skipif( skip_osx, - reason='This test does not work on OSX due to ' - 'libpng version conflict. Need to be ' - 'introspected on Travis', + reason="This test does not work on OSX due to " + "libpng version conflict. Need to be " + "introspected on Travis", ) def test_pillow(): - with InTemporaryDirectory() as odir: data = (255 * np.random.rand(400, 255, 4)).astype(np.uint8) - fname_path = pjoin(odir, 'test.png') + fname_path = pjoin(odir, "test.png") for opt1, opt2 in [(True, True), (False, True), (True, False), (False, False)]: if not opt1: @@ -194,32 +192,32 @@ def test_pillow(): dpi_tolerance = 0.01 save_image(data, fname_path, use_pillow=True) - img_dpi = Image.open(fname_path).info.get('dpi') + img_dpi = Image.open(fname_path).info.get("dpi") assert abs(72 - img_dpi[0]) < dpi_tolerance assert abs(72 - img_dpi[1]) < dpi_tolerance save_image(data, fname_path, use_pillow=True, dpi=300) - img_dpi = Image.open(fname_path).info.get('dpi') + img_dpi = Image.open(fname_path).info.get("dpi") assert abs(300 - img_dpi[0]) < dpi_tolerance assert abs(300 - img_dpi[1]) < dpi_tolerance save_image(data, fname_path, use_pillow=True, dpi=(45, 45)) - img_dpi = Image.open(fname_path).info.get('dpi') + img_dpi = Image.open(fname_path).info.get("dpi") assert abs(45 - img_dpi[0]) < dpi_tolerance assert abs(45 - img_dpi[1]) < dpi_tolerance save_image(data, fname_path, use_pillow=True, dpi=(300, 72)) - img_dpi = Image.open(fname_path).info.get('dpi') + img_dpi = Image.open(fname_path).info.get("dpi") assert abs(300 - img_dpi[0]) < dpi_tolerance assert abs(72 - img_dpi[1]) < dpi_tolerance def test_load_cubemap_texture(): - l_ext = ['jpg', 'jpeg', 'png', 'bmp', 'tif', 'tiff'] + l_ext = ["jpg", "jpeg", "png", "bmp", "tif", "tiff"] for ext in l_ext: with InTemporaryDirectory() as odir: data = np.random.randint(0, 255, size=(50, 50, 3), dtype=np.uint8) - fname_path = pjoin(odir, f'test.{ext}') + fname_path = pjoin(odir, f"test.{ext}") save_image(data, fname_path) fnames = [fname_path] * 5 @@ -241,15 +239,15 @@ def test_load_cubemap_texture(): def test_load_sprite_sheet(): sprite_URL = ( - 'https://raw.githubusercontent.com/' - 'antrikshmisri/DATA/master/fury/0yKFTBQ.png' + "https://raw.githubusercontent.com/" + "fury-gl/fury-data/master/unittests/fury_sprite.png" ) with InTemporaryDirectory() as tdir: sprites = load_sprite_sheet(sprite_URL, 5, 5) for idx, sprite in enumerate(list(sprites.values())): - img_name = f'{idx}.png' + img_name = f"{idx}.png" save_image(sprite, os.path.join(tdir, img_name)) sprite_count = len(os.listdir(tdir)) @@ -263,15 +261,15 @@ def test_load_sprite_sheet(): def test_load_text(): with InTemporaryDirectory() as tdir: - test_file_name = 'test.txt' + test_file_name = "test.txt" # Test file does not exist npt.assert_raises(IOError, load_text, test_file_name) # Saving file with content - test_file_contents = 'This is some test text.' + test_file_contents = "This is some test text." test_fname = os.path.join(tdir, test_file_name) - test_file = open(test_fname, 'w') + test_file = open(test_fname, "w") test_file.write(test_file_contents) test_file.close() diff --git a/fury/tests/test_layout.py b/fury/tests/test_layout.py index 136c47a1a..e7480b46c 100644 --- a/fury/tests/test_layout.py +++ b/fury/tests/test_layout.py @@ -13,12 +13,18 @@ ) from fury.ui.containers import Panel2D +# Define module-level singleton variables +DEFAULT_CENTERS = np.asarray([[[0, 0, 0]], [[5, 5, 5]]]) +DEFAULT_DIRECTIONS = np.asarray([[[0, 0, 0]], [[0, 0, 0]]]) +DEFAULT_COLORS = np.random.rand(2, 3) +DEFAULT_SCALES = [1, 1.5] + def get_default_cubes( - centers=np.asarray([[[0, 0, 0]], [[5, 5, 5]]]), - directions=np.asarray([[[0, 0, 0]], [[0, 0, 0]]]), - colors=np.random.rand(2, 3), - scales=[1, 1.5], + centers=DEFAULT_CENTERS, + directions=DEFAULT_DIRECTIONS, + colors=DEFAULT_COLORS, + scales=DEFAULT_SCALES, ): """Provides cube actors with default parameters @@ -32,6 +38,7 @@ def get_default_cubes( RGB or RGBA (for opacity) scales: list of 2 floats Cube Sizes + """ cube_first_center, cube_second_center = centers cube_first_direction, cube_second_direction = directions @@ -49,7 +56,7 @@ def get_default_cubes( return (cube_first, cube_second) -def get_default_panels(sizes=[(100, 100), (200, 200)], colors=np.random.rand(2, 3)): +def get_default_panels(sizes=None, colors=None): """Provides Panels with default parameters Parameters @@ -58,7 +65,13 @@ def get_default_panels(sizes=[(100, 100), (200, 200)], colors=np.random.rand(2, Sizes of the two panels colors: ndarray ndarray (2,3) or (2, 4) RGB or RGBA (for opacity) + """ + if sizes is None: + sizes = [(100, 100), (200, 200)] + if colors is None: + colors = np.random.rand(2, 3) + panel_first_size, panel_second_size = sizes panel_first_color, panel_second_color = colors @@ -69,7 +82,6 @@ def get_default_panels(sizes=[(100, 100), (200, 200)], colors=np.random.rand(2, def test_layout_apply(): - cube_first, cube_second = get_default_cubes() panel_first, panel_second = get_default_panels() @@ -93,7 +105,6 @@ def test_layout_apply(): def test_layout_compute_postions(): - cube_first, cube_second = get_default_cubes() panel_first, panel_second = get_default_panels() @@ -107,14 +118,13 @@ def test_layout_compute_postions(): def test_grid_layout_get_cell_shape(): - cube_first, cube_second = get_default_cubes() panel_first, panel_second = get_default_panels() grid = GridLayout() - grid_square = GridLayout(cell_shape='square') - grid_diagonal = GridLayout(cell_shape='diagonal') - invalid_gird = GridLayout(cell_shape='invalid') + grid_square = GridLayout(cell_shape="square") + grid_diagonal = GridLayout(cell_shape="diagonal") + invalid_gird = GridLayout(cell_shape="invalid") shape = grid.get_cells_shape([cube_first, cube_second]) shape_square = grid_square.get_cells_shape([cube_first, cube_second]) @@ -157,13 +167,12 @@ def test_grid_layout_get_cell_shape(): def test_grid_layout_compute_positions(): - cube_first, cube_second = get_default_cubes() panel_first, panel_second = get_default_panels() grid = GridLayout() - grid_square = GridLayout(cell_shape='square') - grid_diagonal = GridLayout(cell_shape='diagonal') + grid_square = GridLayout(cell_shape="square") + grid_diagonal = GridLayout(cell_shape="diagonal") position_rect = grid.compute_positions([cube_first, cube_second]) position_square = grid_square.compute_positions([cube_first, cube_second]) @@ -188,11 +197,10 @@ def test_grid_layout_compute_positions(): def test_grid_layout_apply(): - cube_first, cube_second = get_default_cubes() panel_first, panel_second = get_default_panels() - grid_diagonal = GridLayout(cell_shape='diagonal') + grid_diagonal = GridLayout(cell_shape="diagonal") grid_diagonal.apply([cube_first, cube_second]) grid_diagonal.apply([panel_first, panel_second]) @@ -211,8 +219,8 @@ def test_vertical_layout_compute_positions(): (cube_first, cube_second) = get_default_cubes() vertical_layout_rect = VerticalLayout() - vertical_layout_square = VerticalLayout(cell_shape='square') - vertical_layout_diagonal = VerticalLayout(cell_shape='diagonal') + vertical_layout_square = VerticalLayout(cell_shape="square") + vertical_layout_diagonal = VerticalLayout(cell_shape="diagonal") position_rect = vertical_layout_rect.compute_positions([cube_first, cube_second]) @@ -230,12 +238,11 @@ def test_vertical_layout_compute_positions(): def test_horizontal_layout_compute_positions(): - cube_first, cube_second = get_default_cubes() horizontal_rect = HorizontalLayout() - horizontal_square = HorizontalLayout(cell_shape='square') - horizontal_diagonal = HorizontalLayout(cell_shape='diagonal') + horizontal_square = HorizontalLayout(cell_shape="square") + horizontal_diagonal = HorizontalLayout(cell_shape="diagonal") position_rect = horizontal_rect.compute_positions([cube_first, cube_second]) @@ -252,11 +259,11 @@ def test_x_layout(): cube_first, cube_second = get_default_cubes() actors = [cube_first, cube_second] - positive_x_layout = XLayout(direction='x+') - negative_x_layout = XLayout(direction='x-') + positive_x_layout = XLayout(direction="x+") + negative_x_layout = XLayout(direction="x-") with npt.assert_raises(ValueError): - _ = XLayout(direction='Invalid direction') + _ = XLayout(direction="Invalid direction") positive_positions = positive_x_layout.compute_positions(actors) negative_positions = negative_x_layout.compute_positions(actors) @@ -289,11 +296,11 @@ def test_y_layout(): cube_first, cube_second = get_default_cubes() actors = [cube_first, cube_second] - positive_y_layout = YLayout(direction='y+') - negative_y_layout = YLayout(direction='y-') + positive_y_layout = YLayout(direction="y+") + negative_y_layout = YLayout(direction="y-") with npt.assert_raises(ValueError): - _ = YLayout(direction='Invalid direction') + _ = YLayout(direction="Invalid direction") positive_positions = positive_y_layout.compute_positions(actors) negative_positions = negative_y_layout.compute_positions(actors) @@ -326,15 +333,15 @@ def test_z_layout(): cube_first, cube_second = get_default_cubes() actors = [cube_first, cube_second] - positive_z_layout = ZLayout(direction='z+') - negative_z_layout = ZLayout(direction='z-') - diagonal_z_layout = ZLayout(direction='z+', cell_shape='diagonal') + positive_z_layout = ZLayout(direction="z+") + negative_z_layout = ZLayout(direction="z-") + diagonal_z_layout = ZLayout(direction="z+", cell_shape="diagonal") with npt.assert_raises(ValueError): - _ = XLayout(direction='Invalid direction') + _ = XLayout(direction="Invalid direction") with npt.assert_raises(ValueError): - invalid_shape_layout = ZLayout(direction='z+', cell_shape='Invalid Shape') + invalid_shape_layout = ZLayout(direction="z+", cell_shape="Invalid Shape") _ = invalid_shape_layout.get_cells_shape(actors) positive_positions = positive_z_layout.compute_positions(actors) diff --git a/fury/tests/test_material.py b/fury/tests/test_material.py index 8cc4091c4..2d9c32143 100644 --- a/fury/tests/test_material.py +++ b/fury/tests/test_material.py @@ -1,20 +1,15 @@ -import os -from tempfile import TemporaryDirectory - import numpy as np import numpy.testing as npt -import pytest from fury import actor, material, window -from fury.io import load_image from fury.optpkg import optional_package -dipy, have_dipy, _ = optional_package('dipy') +dipy, have_dipy, _ = optional_package("dipy") def test_manifest_pbr_vtk(): # Test non-supported property - test_actor = actor.text_3d('Test') + test_actor = actor.text_3d("Test") npt.assert_warns(UserWarning, material.manifest_pbr, test_actor) # Test non-supported PBR interpolation @@ -92,24 +87,24 @@ def test_manifest_pbr_vtk(): def test_manifest_principled(): # Test non-supported property - test_actor = actor.text_3d('Test') + test_actor = actor.text_3d("Test") npt.assert_warns(UserWarning, material.manifest_principled, test_actor) center = np.array([[0, 0, 0]]) # Test expected parameters expected_principled_params = { - 'subsurface': 0, - 'metallic': 0, - 'specular': 0, - 'specular_tint': 0, - 'roughness': 0, - 'anisotropic': 0, - 'anisotropic_direction': [0, 1, 0.5], - 'sheen': 0, - 'sheen_tint': 0, - 'clearcoat': 0, - 'clearcoat_gloss': 0, + "subsurface": 0, + "metallic": 0, + "specular": 0, + "specular_tint": 0, + "roughness": 0, + "anisotropic": 0, + "anisotropic_direction": [0, 1, 0.5], + "sheen": 0, + "sheen_tint": 0, + "clearcoat": 0, + "clearcoat_gloss": 0, } test_actor = actor.square(center, directions=(1, 1, 1), colors=(0, 0, 1)) actual_principled_params = material.manifest_principled(test_actor) @@ -118,7 +113,7 @@ def test_manifest_principled(): def test_manifest_standard(): # Test non-supported property - test_actor = actor.text_3d('Test') + test_actor = actor.text_3d("Test") npt.assert_warns(UserWarning, material.manifest_standard, test_actor) center = np.array([[0, 0, 0]]) @@ -126,7 +121,7 @@ def test_manifest_standard(): # Test non-supported interpolation method test_actor = actor.square(center, directions=(1, 1, 1), colors=(0, 0, 1)) npt.assert_warns( - UserWarning, material.manifest_standard, test_actor, interpolation='test' + UserWarning, material.manifest_standard, test_actor, interpolation="test" ) scene = window.Scene() # Setup scene diff --git a/fury/tests/test_molecular.py b/fury/tests/test_molecular.py index bc4a09510..f3ab32116 100644 --- a/fury/tests/test_molecular.py +++ b/fury/tests/test_molecular.py @@ -1,22 +1,21 @@ import numpy as np import numpy.testing as npt -from fury import molecular as mol -from fury import window +from fury import molecular as mol, window def test_periodic_table(): # Testing class PeriodicTable() table = mol.PTable() - npt.assert_equal(table.atomic_number('C'), 6) - npt.assert_equal(table.element_name(7), 'Nitrogen') - npt.assert_equal(table.atomic_symbol(8), 'O') - npt.assert_allclose(table.atomic_radius(1, 'VDW'), 1.2, 0.1, 0) - npt.assert_allclose(table.atomic_radius(6, 'Covalent'), 0.75, 0.1, 0) + npt.assert_equal(table.atomic_number("C"), 6) + npt.assert_equal(table.element_name(7), "Nitrogen") + npt.assert_equal(table.atomic_symbol(8), "O") + npt.assert_allclose(table.atomic_radius(1, "VDW"), 1.2, 0.1, 0) + npt.assert_allclose(table.atomic_radius(6, "Covalent"), 0.75, 0.1, 0) npt.assert_array_almost_equal(table.atom_color(1), np.array([1, 1, 1])) # Test errors - npt.assert_raises(ValueError, table.atomic_radius, 4, 'test') + npt.assert_raises(ValueError, table.atomic_radius, 4, "test") def get_default_molecular_info(all_info=False): @@ -33,7 +32,7 @@ def get_default_molecular_info(all_info=False): [0.6632858893e01, 0.6740709254e01, 0.4090898288e01], ] ) - atom_names = np.array(['CA', 'CA', 'H', 'H', 'H', 'H', 'H', 'H']) + atom_names = np.array(["CA", "CA", "H", "H", "H", "H", "H", "H"]) model = np.ones(8) residue = np.ones(8) chain = np.ones(8) * 65 @@ -67,7 +66,7 @@ def test_molecule_creation(): elements = np.array([6, 6]) npt.assert_raises(ValueError, mol.Molecule, elements, atom_coords) - elements = [i for i in range(8)] + elements = list(range(8)) npt.assert_raises(ValueError, mol.Molecule, elements, atom_coords) @@ -150,7 +149,7 @@ def test_sphere_cpk(interactive=False): atomic_numbers, atom_coords = get_default_molecular_info() molecule = mol.Molecule(atomic_numbers, atom_coords) table = mol.PTable() - colormodes = ['discrete', 'single'] + colormodes = ["discrete", "single"] colors = np.array( [ [table.atom_color(1), table.atom_color(6)], @@ -177,7 +176,7 @@ def test_sphere_cpk(interactive=False): scene.clear() # Testing warnings - npt.assert_warns(UserWarning, mol.sphere_cpk, molecule, 'multiple') + npt.assert_warns(UserWarning, mol.sphere_cpk, molecule, "multiple") def test_bstick(interactive=False): @@ -189,7 +188,7 @@ def test_bstick(interactive=False): npt.assert_raises(ValueError, mol.ball_stick, molecule) mol.add_bond(molecule, 0, 1, 1) - colormodes = ['discrete', 'single'] + colormodes = ["discrete", "single"] atom_scale_factor = [0.3, 0.4] bond_thickness = [0.1, 0.2] multiple_bonds = [True, False] @@ -225,7 +224,7 @@ def test_bstick(interactive=False): scene.clear() # Testing warnings - npt.assert_warns(UserWarning, mol.ball_stick, molecule, 'multiple') + npt.assert_warns(UserWarning, mol.ball_stick, molecule, "multiple") def test_stick(interactive=False): @@ -237,7 +236,7 @@ def test_stick(interactive=False): npt.assert_raises(ValueError, mol.stick, molecule) mol.add_bond(molecule, 0, 1, 1) - colormodes = ['discrete', 'single'] + colormodes = ["discrete", "single"] bond_thickness = [0.1, 0.12] table = mol.PTable() colors = np.array( @@ -265,11 +264,10 @@ def test_stick(interactive=False): scene.clear() # Testing warnings - npt.assert_warns(UserWarning, mol.stick, molecule, 'multiple') + npt.assert_warns(UserWarning, mol.stick, molecule, "multiple") def test_ribbon(interactive=False): - scene = window.Scene() # Testing if helices and sheets are rendered properly @@ -300,26 +298,26 @@ def test_ribbon(interactive=False): elements = np.array([7, 6, 6, 8, 6, 6, 6, 8, 7, 7, 6, 6, 8, 7, 6, 6, 8, 6, 8, 6]) atom_names = np.array( [ - 'N', - 'CA', - 'C', - 'O', - 'CB', - 'CG', - 'CD', - 'OE1', - 'NE2', - 'N', - 'CA', - 'C', - 'O', - 'N', - 'CA', - 'C', - 'O', - 'CB', - 'OG1', - 'OG2', + "N", + "CA", + "C", + "O", + "CB", + "CG", + "CD", + "OE1", + "NE2", + "N", + "CA", + "C", + "O", + "N", + "CA", + "C", + "O", + "CB", + "OG1", + "OG2", ] ) model = np.ones(20) diff --git a/fury/tests/test_optpkg.py b/fury/tests/test_optpkg.py index bb4c1bff3..1810db182 100644 --- a/fury/tests/test_optpkg.py +++ b/fury/tests/test_optpkg.py @@ -11,49 +11,49 @@ def test_get_info(): expected_keys = [ - 'fury_version', - 'pkg_path', - 'commit_hash', - 'sys_version', - 'sys_executable', - 'sys_platform', - 'numpy_version', - 'scipy_version', - 'vtk_version', + "fury_version", + "pkg_path", + "commit_hash", + "sys_version", + "sys_executable", + "sys_platform", + "numpy_version", + "scipy_version", + "vtk_version", ] info = get_info() current_keys = info.keys() for ek in expected_keys: assert_true(ek in current_keys) - assert_true(info[ek] not in [None, '']) + assert_true(info[ek] not in [None, ""]) def test_is_tripwire(): assert_false(is_tripwire(object())) - assert_true(is_tripwire(TripWire('some message'))) - assert_false(is_tripwire(ValueError('some message'))) + assert_true(is_tripwire(TripWire("some message"))) + assert_false(is_tripwire(ValueError("some message"))) def test_tripwire(): # Test tripwire object - silly_module_name = TripWire('We do not have silly_module_name') - npt.assert_raises(TripWireError, getattr, silly_module_name, 'do_silly_thing') + silly_module_name = TripWire("We do not have silly_module_name") + npt.assert_raises(TripWireError, getattr, silly_module_name, "do_silly_thing") npt.assert_raises(TripWireError, silly_module_name) # Check AttributeError can be checked too try: - silly_module_name.__wrapped__ + _ = silly_module_name.__wrapped__ except TripWireError as err: assert_true(isinstance(err, AttributeError)) else: - raise RuntimeError('No error raised, but expected') + raise RuntimeError("No error raised, but expected") def test_optional_package(): - pkg, have_pkg, _ = optional_package('fake_pkg') + pkg, have_pkg, _ = optional_package("fake_pkg") npt.assert_raises(TripWireError, pkg) assert_false(have_pkg) - pkg, have_pkg, _ = optional_package('os') + pkg, have_pkg, _ = optional_package("os") assert_true(isinstance(pkg, ModuleType)) - npt.assert_equal(pkg.__name__, 'os') + npt.assert_equal(pkg.__name__, "os") assert_true(have_pkg) diff --git a/fury/tests/test_pick.py b/fury/tests/test_pick.py index 5e09bb7c0..bfe5c09ac 100644 --- a/fury/tests/test_pick.py +++ b/fury/tests/test_pick.py @@ -24,12 +24,11 @@ def test_fake(): @pytest.mark.skipif( True, - reason='Pytests triggers segfault here that ' - 'cannot be replicated by individual' - 'tests', + reason="Pytests triggers segfault here that " + "cannot be replicated by individual" + "tests", ) def test_picking_manager(): - xyz = 10 * np.random.rand(100, 3) colors = np.random.rand(100, 4) radii = np.random.rand(100) + 0.5 @@ -51,7 +50,7 @@ def test_picking_manager(): pickm = pick.PickingManager() - record_indices = {'vertex_indices': [], 'face_indices': [], 'xyz': [], 'actor': []} + record_indices = {"vertex_indices": [], "face_indices": [], "xyz": [], "actor": []} def timer_callback(_obj, _event): cnt = next(counter) @@ -61,10 +60,10 @@ def timer_callback(_obj, _event): if cnt % 10 == 0: # pick at position info = pickm.pick((900 / 2, 768 / 2), scene) - record_indices['vertex_indices'].append(info['vertex']) - record_indices['face_indices'].append(info['face']) - record_indices['xyz'].append(info['xyz']) - record_indices['actor'].append(info['actor']) + record_indices["vertex_indices"].append(info["vertex"]) + record_indices["face_indices"].append(info["face"]) + record_indices["xyz"].append(info["xyz"]) + record_indices["actor"].append(info["actor"]) showm.render() if cnt == 15: @@ -76,14 +75,14 @@ def timer_callback(_obj, _event): showm.add_timer_callback(True, 200, timer_callback) showm.start() - assert_greater(np.sum(np.array(record_indices['vertex_indices'])), 1) - assert_greater(np.sum(np.array(record_indices['face_indices'])), 1) + assert_greater(np.sum(np.array(record_indices["vertex_indices"])), 1) + assert_greater(np.sum(np.array(record_indices["face_indices"])), 1) - for ac in record_indices['actor']: + for ac in record_indices["actor"]: if ac is not None: npt.assert_equal(ac is sphere_actor, True) - assert_greater(np.sum(np.abs(np.diff(np.array(record_indices['xyz']), axis=0))), 0) + assert_greater(np.sum(np.abs(np.diff(np.array(record_indices["xyz"]), axis=0))), 0) def _get_three_cubes(): @@ -95,12 +94,11 @@ def _get_three_cubes(): @pytest.mark.skipif( True, - reason='Pytests triggers segfault here that ' - 'cannot be replicated by individual' - 'tests', + reason="Pytests triggers segfault here that " + "cannot be replicated by individual" + "tests", ) def test_selector_manager(): - centers, colors, radii = _get_three_cubes() scene = window.Scene() @@ -126,7 +124,7 @@ def test_selector_manager(): # use itertools to avoid global variables counter = itertools.count() - selm = pick.SelectionManager(select='faces') + selm = pick.SelectionManager(select="faces") selm.selectable_off([tex_actor]) selm.selectable_on([tex_actor]) @@ -139,13 +137,13 @@ def timer_callback(_obj, _event): # select large area info_plus = selm.select((900 // 2, 768 // 2), scene, (30, 30)) for info in info_plus.keys(): - if info_plus[info]['actor'] in [cube_actor, pts_actor]: + if info_plus[info]["actor"] in [cube_actor, pts_actor]: npt.assert_(True) else: npt.assert_(False) # select single pixel info_ = selm.pick((900 // 2, 768 // 2), scene) - if info_['actor'] in [cube_actor, pts_actor]: + if info_["actor"] in [cube_actor, pts_actor]: npt.assert_(True) else: npt.assert_(False) @@ -164,14 +162,14 @@ def timer_callback(_obj, _event): @pytest.mark.skipif( True, - reason='Pytests triggers segfault here that ' - 'cannot be replicated by individual' - 'tests', + reason="Pytests triggers segfault here that " + "cannot be replicated by individual" + "tests", ) def test_hover_selection_faces(recording=False): # simply hover going through blue, green, red - recording_filename = join(DATA_DIR, 'selector_faces.log.gz') + recording_filename = join(DATA_DIR, "selector_faces.log.gz") centers, colors, radii = _get_three_cubes() @@ -181,7 +179,7 @@ def test_hover_selection_faces(recording=False): scene.add(cube_actor) - selm = pick.SelectionManager(select='faces') + selm = pick.SelectionManager(select="faces") showm = window.ShowManager( scene, size=(900, 768), reset_camera=False, order_transparent=True @@ -194,7 +192,7 @@ def hover_callback(_obj, _event): global track_objects event_pos = selm.event_position(showm.iren) info = selm.select(event_pos, showm.scene, (10, 10)) - selected_faces = info[0]['face'] + selected_faces = info[0]["face"] if selected_faces is not None: track_objects.append(selected_faces[0] // 12) showm.render() @@ -215,15 +213,15 @@ def hover_callback(_obj, _event): @pytest.mark.skipif( True, - reason='Pytests triggers segfault here that ' - 'cannot be replicated by individual' - 'tests', + reason="Pytests triggers segfault here that " + "cannot be replicated by individual" + "tests", ) def test_hover_selection_vertices(recording=False): # simply hover through blue, green, red cubes # close to any vertices of each of the cubes - recording_filename = join(DATA_DIR, 'selector_vertices.log.gz') + recording_filename = join(DATA_DIR, "selector_vertices.log.gz") centers, colors, radii = _get_three_cubes() @@ -233,7 +231,7 @@ def test_hover_selection_vertices(recording=False): scene.add(cube_actor) - selm = pick.SelectionManager(select='vertices') + selm = pick.SelectionManager(select="vertices") showm = window.ShowManager( scene, size=(900, 768), reset_camera=False, order_transparent=True @@ -246,7 +244,7 @@ def hover_callback(_obj, _event): global track_objects2 event_pos = selm.event_position(showm.iren) info = selm.select(event_pos, showm.scene, (100, 100)) - selected_triangles = info[0]['vertex'] + selected_triangles = info[0]["vertex"] if selected_triangles is not None: track_objects2.append(selected_triangles[0] // 8) showm.render() @@ -267,14 +265,14 @@ def hover_callback(_obj, _event): @pytest.mark.skipif( True, - reason='Pytests triggers segfault here that ' - 'cannot be replicated by individual' - 'tests', + reason="Pytests triggers segfault here that " + "cannot be replicated by individual" + "tests", ) def test_hover_selection_actors_only(recording=False): # simply hover going through blue, green, red cubes - recording_filename = join(DATA_DIR, 'selector_actors.log.gz') + recording_filename = join(DATA_DIR, "selector_actors.log.gz") centers, colors, radii = _get_three_cubes() @@ -284,7 +282,7 @@ def test_hover_selection_actors_only(recording=False): scene.add(cube_actor) - selm = pick.SelectionManager(select='actors') + selm = pick.SelectionManager(select="actors") showm = window.ShowManager( scene, size=(900, 768), reset_camera=False, order_transparent=True @@ -293,7 +291,7 @@ def test_hover_selection_actors_only(recording=False): def hover_callback(_obj, _event): event_pos = selm.event_position(showm.iren) info = selm.pick(event_pos, showm.scene) - selected_actor = info['actor'] + selected_actor = info["actor"] # print(id(selected_actor), id(cube_actor)) if selected_actor is not None: npt.assert_equal(id(cube_actor), id(selected_actor)) @@ -308,5 +306,5 @@ def hover_callback(_obj, _event): showm.play_events_from_file(recording_filename) -if __name__ == '__main__': +if __name__ == "__main__": npt.run_module_suite() diff --git a/fury/tests/test_primitive.py b/fury/tests/test_primitive.py index bffa4e396..baac44a2b 100644 --- a/fury/tests/test_primitive.py +++ b/fury/tests/test_primitive.py @@ -45,7 +45,7 @@ def test_vertices_primitives_octagonalprism(): # Testing the default vertices of the primitive octagonal prism. vertices, _ = fp.prim_octagonalprism() shape = (16, 3) - two = (1 + float('{:.7f}'.format(math.sqrt(2)))) / 4 + two = (1 + float("{:.7f}".format(math.sqrt(2)))) / 4 npt.assert_equal(vertices.shape, shape) npt.assert_equal(np.mean(vertices), 0) @@ -56,12 +56,8 @@ def test_vertices_primitives_octagonalprism(): def test_vertices_primitives_pentagonalprism(): # Testing the default vertices of the primitive pentagonal prism. vertices, _ = fp.prim_pentagonalprism() - lower_face = vertices[:, 0:2][ - 0:5, - ] - upper_face = vertices[:, 0:2][ - 5:10, - ] + lower_face = vertices[:, 0:2][0:5,] + upper_face = vertices[:, 0:2][5:10,] centroid_upper = np.mean(upper_face, 0) centroid_lower = np.mean(lower_face, 0) shape = (10, 3) @@ -78,7 +74,7 @@ def test_vertices_primitives_triangularprism(): # Testing the default vertices of the primitive triangular prism. vertices, _ = fp.prim_triangularprism() shape = (6, 3) - three = float('{:.7f}'.format(math.sqrt(3))) + three = float("{:.7f}".format(math.sqrt(3))) npt.assert_equal(vertices.shape, shape) npt.assert_equal(np.mean(vertices), 0) npt.assert_equal(vertices.min(), -1 / three) @@ -103,12 +99,12 @@ def test_triangles_primitives(): def test_spheres_primitives(): l_primitives = [ - ('symmetric362', 362, 720), - ('symmetric642', 642, 1280), - ('symmetric724', 724, 1444), - ('repulsion724', 724, 1444), - ('repulsion100', 100, 196), - ('repulsion200', 200, 396), + ("symmetric362", 362, 720), + ("symmetric642", 642, 1280), + ("symmetric724", 724, 1444), + ("repulsion724", 724, 1444), + ("repulsion100", 100, 196), + ("repulsion200", 200, 396), ] for name, nb_verts, nb_triangles in l_primitives: @@ -120,7 +116,7 @@ def test_spheres_primitives(): list(set(np.concatenate(faces, axis=None))), list(range(len(verts))) ) - npt.assert_raises(ValueError, fp.prim_sphere, 'sym362') + npt.assert_raises(ValueError, fp.prim_sphere, "sym362") l_primitives = [ (10, 10, 82, 160), @@ -142,7 +138,7 @@ def test_spheres_primitives(): def test_superquadric_primitives(): # test default, should be like a sphere 362 sq_verts, sq_faces = fp.prim_superquadric() - s_verts, s_faces = fp.prim_sphere('symmetric362') + s_verts, s_faces = fp.prim_sphere("symmetric362") npt.assert_equal(sq_verts.shape, s_verts.shape) npt.assert_equal(sq_faces.shape, s_faces.shape) @@ -266,7 +262,7 @@ def test_repeat_primitive_function(): colors = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) * 255 phi_theta = np.array([[1, 1], [1, 2], [2, 1]]) - res = fp.repeat_primitive_function( + _ = fp.repeat_primitive_function( func=fp.prim_superquadric, centers=centers, func_args=phi_theta, diff --git a/fury/tests/test_stream.py b/fury/tests/test_stream.py index cabf200a6..5db6a05da 100644 --- a/fury/tests/test_stream.py +++ b/fury/tests/test_stream.py @@ -1,7 +1,7 @@ import asyncio +from importlib import reload import sys import time -from importlib import reload from unittest import mock import numpy as np @@ -29,9 +29,10 @@ @pytest.fixture def loop(): - """ - Refs - ---- + """Use this fixture to get the event loop. + + References + ---------- https://promity.com/2020/06/03/testing-asynchronous-code-in-python/ """ loop = asyncio.new_event_loop() @@ -41,7 +42,7 @@ def loop(): def test_rtc_video_stream(loop: asyncio.AbstractEventLoop): if not WEBRTC_AVAILABLE: - print('\n aiortc not available -> skipping test\n') + print("\n aiortc not available -> skipping test\n") return def test(use_raw_array, ms_stream=16): @@ -134,7 +135,7 @@ def test_pillow(): img_buffer_manager.get_jpeg() width, height, frame = img_buffer_manager.get_current_frame() - image = np.frombuffer(frame, 'uint8')[0 : width * height * 3].reshape( + image = np.frombuffer(frame, "uint8")[0 : width * height * 3].reshape( (height, width, 3) ) report = window.analyze_snapshot(image, find_objects=True) @@ -146,14 +147,14 @@ def test_pillow(): def test_rtc_video_stream_whitout_cython(loop: asyncio.AbstractEventLoop): if not WEBRTC_AVAILABLE: - print('\n aiortc not available -> skipping test\n') + print("\n aiortc not available -> skipping test\n") return use_raw_array = True ms_stream = 0 # creates a context without cython - with mock.patch.dict(sys.modules, {'pyximport': None}): - reload(sys.modules['fury.stream.server.main']) + with mock.patch.dict(sys.modules, {"pyximport": None}): + reload(sys.modules["fury.stream.server.main"]) width_0 = 100 height_0 = 200 @@ -195,7 +196,7 @@ def test_rtc_video_stream_whitout_cython(loop: asyncio.AbstractEventLoop): stream.stop() stream.cleanup() - reload(sys.modules['fury.stream.server.main']) + reload(sys.modules["fury.stream.server.main"]) def test_client_and_buffer_manager(): @@ -239,7 +240,7 @@ def test(use_raw_array, ms_stream=16): width, height, frame = img_buffer_manager.get_current_frame() # assert width == showm.size[0] and height == showm.size[1] - image = np.frombuffer(frame, 'uint8')[0 : width * height * 3].reshape( + image = np.frombuffer(frame, "uint8")[0 : width * height * 3].reshape( (height, width, 3) ) # image = np.flipud(image) @@ -380,13 +381,13 @@ async def test(use_raw_array, ms_stream=16): for _ in range(10): stream_interaction.circular_queue.enqueue( np.array( - [_CQUEUE.event_ids.mouse_weel, 1, 0, 0, 0, 0, 0.1, 0], dtype='d' + [_CQUEUE.event_ids.mouse_weel, 1, 0, 0, 0, 0, 0.1, 0], dtype="d" ) ) for _ in range(10): stream_interaction.circular_queue.enqueue( np.array( - [_CQUEUE.event_ids.mouse_weel, -1, 0, 0, 0, 0, 0.1, 0], dtype='d' + [_CQUEUE.event_ids.mouse_weel, -1, 0, 0, 0, 0, 0.1, 0], dtype="d" ) ) dxs = [] @@ -396,7 +397,7 @@ async def test(use_raw_array, ms_stream=16): stream_interaction.circular_queue.enqueue( np.array( [_CQUEUE.event_ids.left_btn_press, 0, x, y, ctrl, shift, 0.1, 0], - dtype='d', + dtype="d", ) ) for i in range(50): @@ -412,13 +413,13 @@ async def test(use_raw_array, ms_stream=16): stream_interaction.circular_queue.enqueue( np.array( [_CQUEUE.event_ids.mouse_move, 0, x, y, ctrl, shift, 0.1, 0], - dtype='d', + dtype="d", ) ) stream_interaction.circular_queue.enqueue( np.array( [_CQUEUE.event_ids.left_btn_release, 0, x, y, ctrl, shift, 0.1, 0], - dtype='d', + dtype="d", ) ) @@ -536,7 +537,7 @@ def test(use_raw_array=False): m_buffer = tools.SharedMemMultiDimensionalBuffer( max_size=max_size, dimension=dimension ) - m_buffer.buffer = np.arange((max_size + 1) * dimension).astype('d') + m_buffer.buffer = np.arange((max_size + 1) * dimension).astype("d") m_buffer[1] = np.array([0.2, 0.3, 0.4, 0.5]) assert len(m_buffer[0]) == dimension if not use_raw_array: @@ -665,8 +666,8 @@ def test_comm(use_raw_array=True): def test_queue_and_webserver(): - """check if the correct - envent ids and the data are stored in the + """Check if the correct + event ids and the data are stored in the correct positions """ max_size = 3 @@ -678,7 +679,7 @@ def test_queue_and_webserver(): queue = tools.ArrayCircularQueue(max_size=max_size, dimension=dimension) else: queue = tools.SharedMemCircularQueue(max_size=max_size, dimension=dimension) - set_weel({'deltaY': 0.2, 'timestampInMs': 123}, queue) + set_weel({"deltaY": 0.2, "timestampInMs": 123}, queue) arr_queue = queue.dequeue() arr = np.zeros(dimension) arr[0] = _CQUEUE.event_ids.mouse_weel @@ -687,36 +688,36 @@ def test_queue_and_webserver(): npt.assert_equal(arr, arr_queue) # if the mouse position has been stored correctly in the circular queue - data = {'x': -3, 'y': 2.0, 'ctrlKey': 1, 'shiftKey': 0, 'timestampInMs': 123} + data = {"x": -3, "y": 2.0, "ctrlKey": 1, "shiftKey": 0, "timestampInMs": 123} set_mouse(data, queue) arr_queue = queue.dequeue() arr = np.zeros(dimension) arr[0] = _CQUEUE.event_ids.mouse_move - arr[_CQUEUE.index_info.x] = data['x'] - arr[_CQUEUE.index_info.y] = data['y'] - arr[_CQUEUE.index_info.ctrl] = data['ctrlKey'] - arr[_CQUEUE.index_info.shift] = data['shiftKey'] - arr[_CQUEUE.index_info.user_timestamp] = data['timestampInMs'] + arr[_CQUEUE.index_info.x] = data["x"] + arr[_CQUEUE.index_info.y] = data["y"] + arr[_CQUEUE.index_info.ctrl] = data["ctrlKey"] + arr[_CQUEUE.index_info.shift] = data["shiftKey"] + arr[_CQUEUE.index_info.user_timestamp] = data["timestampInMs"] npt.assert_equal(arr, arr_queue) data = { - 'mouseButton': 0, - 'on': 1, - 'x': -3, - 'y': 2.0, - 'ctrlKey': 1, - 'shiftKey': 0, - 'timestampInMs': 123, + "mouseButton": 0, + "on": 1, + "x": -3, + "y": 2.0, + "ctrlKey": 1, + "shiftKey": 0, + "timestampInMs": 123, } set_mouse_click(data, queue) arr_queue = queue.dequeue() arr = np.zeros(dimension) arr[0] = _CQUEUE.event_ids.left_btn_press - arr[_CQUEUE.index_info.x] = data['x'] - arr[_CQUEUE.index_info.y] = data['y'] - arr[_CQUEUE.index_info.ctrl] = data['ctrlKey'] - arr[_CQUEUE.index_info.shift] = data['shiftKey'] - arr[_CQUEUE.index_info.user_timestamp] = data['timestampInMs'] + arr[_CQUEUE.index_info.x] = data["x"] + arr[_CQUEUE.index_info.y] = data["y"] + arr[_CQUEUE.index_info.ctrl] = data["ctrlKey"] + arr[_CQUEUE.index_info.shift] = data["shiftKey"] + arr[_CQUEUE.index_info.user_timestamp] = data["timestampInMs"] npt.assert_equal(arr, arr_queue) queue.cleanup() @@ -748,7 +749,7 @@ def test(use_raw_array): stream_interaction.circular_queue.head_tail_buffer, stream_interaction.circular_queue.buffer._buffer, 8000, - 'localhost', + "localhost", True, True, run_app=False, @@ -760,7 +761,7 @@ def test(use_raw_array): stream_interaction.circular_queue.head_tail_buffer_name, stream_interaction.circular_queue.buffer.buffer_name, 8000, - 'localhost', + "localhost", True, True, True, @@ -776,7 +777,7 @@ def test(use_raw_array): test(False) -@pytest.mark.skipif(True, reason='Infinite loop. Need to check this test.') +@pytest.mark.skipif(True, reason="Infinite loop. Need to check this test.") def test_widget(): if not PY_VERSION_8: return diff --git a/fury/tests/test_testing.py b/fury/tests/test_testing.py index 40293f96f..39aae8bf7 100644 --- a/fury/tests/test_testing.py +++ b/fury/tests/test_testing.py @@ -1,28 +1,29 @@ """Testing file unittest.""" + import sys import warnings import numpy as np import numpy.testing as npt -import fury.testing as ft from fury import window from fury.lib import Actor2D +import fury.testing as ft from fury.ui.core import UI def test_callback(): events_name = [ - 'CharEvent', - 'MouseMoveEvent', - 'KeyPressEvent', - 'KeyReleaseEvent', - 'LeftButtonPressEvent', - 'LeftButtonReleaseEvent', - 'RightButtonPressEvent', - 'RightButtonReleaseEvent', - 'MiddleButtonPressEvent', - 'MiddleButtonReleaseEvent', + "CharEvent", + "MouseMoveEvent", + "KeyPressEvent", + "KeyReleaseEvent", + "LeftButtonPressEvent", + "LeftButtonReleaseEvent", + "RightButtonPressEvent", + "RightButtonReleaseEvent", + "MiddleButtonPressEvent", + "MiddleButtonReleaseEvent", ] class SimplestUI(UI): @@ -47,24 +48,24 @@ def _add_to_scene(self, _scene): simple_ui = SimplestUI() current_size = (900, 600) scene = window.Scene() - show_manager = window.ShowManager(scene, size=current_size, title='FURY GridUI') + show_manager = window.ShowManager(scene, size=current_size, title="FURY GridUI") scene.add(simple_ui) event_counter = ft.EventCounter() event_counter.monitor(simple_ui) - events_name = ['{0} 0 0 0 0 0 0 0'.format(name) for name in events_name] - events_str = '# StreamVersion 1\n' + '\n'.join(events_name) + events_name = ["{0} 0 0 0 0 0 0 0".format(name) for name in events_name] + events_str = "# StreamVersion 1\n" + "\n".join(events_name) show_manager.play_events(events_str) npt.assert_equal(len(event_counter.events_counts), len(events_name)) def test_captured_output(): def foo(): - print('hello world!') + print("hello world!") with ft.captured_output() as (out, _): foo() - npt.assert_equal(out.getvalue().strip(), 'hello world!') + npt.assert_equal(out.getvalue().strip(), "hello world!") def test_assert(): @@ -88,7 +89,7 @@ def assert_warn_len_equal(mod, n_in_context): # when raising warnings inside a catch_warnings block. So, there is a # warning generated by the tests within the context manager, but no # previous warnings. - if 'version' in mod_warns: + if "version" in mod_warns: npt.assert_equal(len(mod_warns), 2) # including 'version' else: npt.assert_equal(len(mod_warns), n_in_context) @@ -102,21 +103,21 @@ def test_clear_and_catch_warnings(): except AttributeError: pass - npt.assert_equal(getattr(my_mod, '__warningregistry__', {}), {}) + npt.assert_equal(getattr(my_mod, "__warningregistry__", {}), {}) with ft.clear_and_catch_warnings(modules=[my_mod]): - warnings.simplefilter('ignore') - warnings.warn('Some warning') + warnings.simplefilter("ignore") + warnings.warn("Some warning", stacklevel=1) npt.assert_equal(my_mod.__warningregistry__, {}) # Without specified modules, don't clear warnings during context with ft.clear_and_catch_warnings(): - warnings.warn('Some warning') + warnings.warn("Some warning", stacklevel=1) assert_warn_len_equal(my_mod, 1) # Confirm that specifying module keeps old warning, does not add new with ft.clear_and_catch_warnings(modules=[my_mod]): - warnings.warn('Another warning') + warnings.warn("Another warning", stacklevel=1) assert_warn_len_equal(my_mod, 1) # Another warning, no module spec does add to warnings dict, except on # Python 3 (see comments in `assert_warn_len_equal`) with ft.clear_and_catch_warnings(): - warnings.warn('Another warning') + warnings.warn("Another warning", stacklevel=1) assert_warn_len_equal(my_mod, 2) diff --git a/fury/tests/test_thread.py b/fury/tests/test_thread.py index 61e721681..35e2ac041 100644 --- a/fury/tests/test_thread.py +++ b/fury/tests/test_thread.py @@ -1,10 +1,7 @@ - -import time from threading import Thread +import time import numpy as np -import numpy.testing as npt -import pytest from fury import actor, window from fury.utils import rotate, update_actor, vertices_from_actor @@ -16,17 +13,15 @@ def test_multithreading(): radii = np.random.random(100) + 0.5 scene = window.Scene() - sphere_actor = actor.sphere(centers=xyz, - colors=colors, - radii=radii, - use_primitive=False) + sphere_actor = actor.sphere( + centers=xyz, colors=colors, radii=radii, use_primitive=False + ) scene.add(sphere_actor) # Preparing the show manager as usual - showm = window.ShowManager(scene, - size=(900, 768), - reset_camera=False, - order_transparent=True) + showm = window.ShowManager( + scene, size=(900, 768), reset_camera=False, order_transparent=True + ) # showm.initialize() @@ -45,9 +40,13 @@ def callback1(): showm.exit() # if not showm.is_done(): - # arr = window.snapshot(scene, render_window = showm.window, fname = "test.png") - # showm.exit() - # npt.assert_equal(np.sum(arr) > 1, True) + # arr = window.snapshot( + # scene, + # render_window=showm.window, + # fname="test.png", + # ) + # showm.exit() + # npt.assert_equal(np.sum(arr) > 1, True) thread_a = Thread(target=callback1) thread_a.start() diff --git a/fury/tests/test_transform.py b/fury/tests/test_transform.py index 2ecd92f62..8c463592e 100644 --- a/fury/tests/test_transform.py +++ b/fury/tests/test_transform.py @@ -66,7 +66,7 @@ def test_sphere_cart(): def test_euler_matrix(): - rotation = euler_matrix(1, 2, 3, 'syxz') + rotation = euler_matrix(1, 2, 3, "syxz") npt.assert_equal(np.allclose(np.sum(rotation[0]), -1.34786452), True) rotation = euler_matrix(1, 2, 3, (0, 1, 0, 1)) diff --git a/fury/tests/test_utils.py b/fury/tests/test_utils.py index 821fa386c..7fdc3230e 100644 --- a/fury/tests/test_utils.py +++ b/fury/tests/test_utils.py @@ -1,9 +1,9 @@ """Module for testing primitive.""" + import numpy as np import numpy.testing as npt import pytest -import fury.primitive as fp from fury import actor, utils, window from fury.lib import ( VTK_DOUBLE, @@ -21,6 +21,7 @@ numpy_support, ) from fury.optpkg import optional_package +import fury.primitive as fp from fury.ui.containers import Panel2D from fury.ui.core import UI from fury.utils import ( @@ -55,16 +56,16 @@ vtk_matrix_to_numpy, ) -dipy, have_dipy, _ = optional_package('dipy') +dipy, have_dipy, _ = optional_package("dipy") def test_apply_affine_to_actor(interactive=False): text_act = actor.text_3d( - 'ALIGN TOP RIGHT', justification='right', vertical_justification='top' + "ALIGN TOP RIGHT", justification="right", vertical_justification="top" ) text_act2 = TextActor3D() - text_act2.SetInput('ALIGN TOP RIGHT') + text_act2.SetInput("ALIGN TOP RIGHT") text_act2.GetTextProperty().SetFontFamilyToArial() text_act2.GetTextProperty().SetFontSize(24) text_act2.SetScale((1.0 / 24.0 * 12,) * 3) @@ -76,7 +77,7 @@ def test_apply_affine_to_actor(interactive=False): text_bounds = [0, 0, 0, 0] text_act2.GetBoundingBox(text_bounds) - initial_bounds = text_act2.GetBounds() + _ = text_act2.GetBounds() affine = np.eye(4) affine[:3, -1] += (-text_bounds[1], 0, 0) @@ -163,7 +164,7 @@ def test_polydata_polygon(interactive=False): [1, 5, 7], [1, 7, 3], ], - dtype='i8', + dtype="i8", ) my_vertices = np.array( [ @@ -251,60 +252,60 @@ def test_add_polydata_numeric_field(): poly_field_data = my_polydata.GetFieldData() npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) bool_data = True - add_polydata_numeric_field(my_polydata, 'Test Bool', bool_data) + add_polydata_numeric_field(my_polydata, "Test Bool", bool_data) npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) - npt.assert_equal(poly_field_data.GetArray('Test Bool').GetValue(0), bool_data) - poly_field_data.RemoveArray('Test Bool') + npt.assert_equal(poly_field_data.GetArray("Test Bool").GetValue(0), bool_data) + poly_field_data.RemoveArray("Test Bool") npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) int_data = 1 - add_polydata_numeric_field(my_polydata, 'Test Int', int_data) + add_polydata_numeric_field(my_polydata, "Test Int", int_data) npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) - npt.assert_equal(poly_field_data.GetArray('Test Int').GetValue(0), int_data) - poly_field_data.RemoveArray('Test Int') + npt.assert_equal(poly_field_data.GetArray("Test Int").GetValue(0), int_data) + poly_field_data.RemoveArray("Test Int") npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) float_data = 0.1 add_polydata_numeric_field( - my_polydata, 'Test Float', float_data, array_type=VTK_FLOAT + my_polydata, "Test Float", float_data, array_type=VTK_FLOAT ) npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) npt.assert_almost_equal( - poly_field_data.GetArray('Test Float').GetValue(0), float_data + poly_field_data.GetArray("Test Float").GetValue(0), float_data ) - poly_field_data.RemoveArray('Test Float') + poly_field_data.RemoveArray("Test Float") npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) double_data = 0.1 add_polydata_numeric_field( - my_polydata, 'Test Double', double_data, array_type=VTK_DOUBLE + my_polydata, "Test Double", double_data, array_type=VTK_DOUBLE ) npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) - npt.assert_equal(poly_field_data.GetArray('Test Double').GetValue(0), double_data) - poly_field_data.RemoveArray('Test Double') + npt.assert_equal(poly_field_data.GetArray("Test Double").GetValue(0), double_data) + poly_field_data.RemoveArray("Test Double") npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) array_data = [-1, 0, 1] - add_polydata_numeric_field(my_polydata, 'Test Array', array_data) + add_polydata_numeric_field(my_polydata, "Test Array", array_data) npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) npt.assert_equal( - numpy_support.vtk_to_numpy(poly_field_data.GetArray('Test Array')), array_data + numpy_support.vtk_to_numpy(poly_field_data.GetArray("Test Array")), array_data ) - poly_field_data.RemoveArray('Test Array') + poly_field_data.RemoveArray("Test Array") npt.assert_equal(poly_field_data.GetNumberOfArrays(), 0) ndarray_data = np.array([[-0.1, -0.1], [0, 0], [0.1, 0.1]]) add_polydata_numeric_field( - my_polydata, 'Test NDArray', ndarray_data, array_type=VTK_FLOAT + my_polydata, "Test NDArray", ndarray_data, array_type=VTK_FLOAT ) npt.assert_equal(poly_field_data.GetNumberOfArrays(), 1) npt.assert_almost_equal( - numpy_support.vtk_to_numpy(poly_field_data.GetArray('Test NDArray')), + numpy_support.vtk_to_numpy(poly_field_data.GetArray("Test NDArray")), ndarray_data, ) def test_get_polydata_field(): my_polydata = PolyData() - field_data = get_polydata_field(my_polydata, 'Test') + field_data = get_polydata_field(my_polydata, "Test") npt.assert_equal(field_data, None) data = 1 - field_name = 'Test' + field_name = "Test" vtk_data = numpy_support.numpy_to_vtk(data) vtk_data.SetName(field_name) my_polydata.GetFieldData().AddArray(vtk_data) @@ -331,20 +332,20 @@ def test_set_polydata_tangents(): array = np.array([[0, 0, 0], [1, 1, 1]]) set_polydata_tangents(my_polydata, array) npt.assert_equal(poly_point_data.GetNumberOfArrays(), 1) - npt.assert_equal(poly_point_data.HasArray('Tangents'), True) + npt.assert_equal(poly_point_data.HasArray("Tangents"), True) def test_asbytes(): - text = [b'test', 'test'] + text = [b"test", "test"] for t in text: - npt.assert_equal(utils.asbytes(t), b'test') + npt.assert_equal(utils.asbytes(t), b"test") def trilinear_interp_numpy(input_array, indices): """Evaluate the input_array data at the given indices.""" if input_array.ndim <= 2 or input_array.ndim >= 5: - raise ValueError('Input array can only be 3d or 4d') + raise ValueError("Input array can only be 3d or 4d") x_indices = indices[:, 0] y_indices = indices[:, 1] @@ -387,7 +388,6 @@ def trilinear_interp_numpy(input_array, indices): def test_trilinear_interp(): - A = np.zeros((5, 5, 5)) A[2, 2, 2] = 1 @@ -406,7 +406,6 @@ def test_trilinear_interp(): def test_vtk_matrix_to_numpy(): - A = np.array([[2.0, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]]) for shape in [3, 4]: @@ -445,7 +444,6 @@ def test_numpy_to_vtk_image_data(): def test_get_grid_cell_position(): - shapes = 10 * [(50, 50), (50, 50), (50, 50), (80, 50)] npt.assert_raises(ValueError, get_grid_cells_position, shapes=shapes, dim=(1, 1)) @@ -456,7 +454,6 @@ def test_get_grid_cell_position(): def test_rotate(interactive=False): - A = np.zeros((50, 50, 50)) A[20:30, 20:30, 10:40] = 100 @@ -495,14 +492,12 @@ def test_rotate(interactive=False): if interactive: window.show(scene) else: - arr = window.snapshot(scene, offscreen=True) red_sum_new = arr[..., 0].sum() npt.assert_equal(red_sum_new > red_sum, True) def test_triangle_order(): - test_vert = np.array([[-1, -2, 0], [1, -1, 0], [2, 1, 0], [3, 0, 0]]) test_tri = np.array([[0, 1, 2], [2, 1, 0]]) @@ -515,7 +510,6 @@ def test_triangle_order(): def test_change_vertices_order(): - triangles = np.array([[1, 2, 3], [3, 2, 1], [5, 4, 3], [3, 4, 5]]) npt.assert_equal(triangles[0], utils.change_vertices_order(triangles[1])) @@ -523,7 +517,6 @@ def test_change_vertices_order(): def test_winding_order(): - vertices = np.array([[0, 0, 0], [1, 2, 0], [3, 0, 0], [2, 0, 0]]) triangles = np.array([[0, 1, 3], [2, 1, 0]]) @@ -534,7 +527,6 @@ def test_winding_order(): def test_vertices_from_actor(interactive=False): - expected = np.array( [ [1.5, -0.5, 0.0], @@ -578,15 +570,15 @@ def test_vertices_from_actor(interactive=False): # test colors_from_actor: l_colors = utils.colors_from_actor(actr) l_colors_vtk = utils.colors_from_actor(actr, as_vtk=True) - l_colors_none = utils.colors_from_actor(actr, array_name='col') + l_colors_none = utils.colors_from_actor(actr, array_name="col") npt.assert_equal(l_colors_none, None) npt.assert_equal(isinstance(l_colors_vtk, UnsignedCharArray), True) npt.assert_equal(np.unique(l_colors, axis=0).shape, colors.shape) - l_array = utils.array_from_actor(actr, 'colors') - l_array_vtk = utils.array_from_actor(actr, 'colors', as_vtk=True) - l_array_none = utils.array_from_actor(actr, 'col') + l_array = utils.array_from_actor(actr, "colors") + l_array_vtk = utils.array_from_actor(actr, "colors", as_vtk=True) + l_array_none = utils.array_from_actor(actr, "col") npt.assert_array_equal(l_array, l_colors) npt.assert_equal(l_array_none, None) @@ -608,11 +600,11 @@ def test_normals_from_actor(): def test_normals_to_actor(): my_actor = actor.square(np.array([[0, 0, 0]])) poly_point_data = my_actor.GetMapper().GetInput().GetPointData() - npt.assert_equal(poly_point_data.HasArray('Normals'), False) + npt.assert_equal(poly_point_data.HasArray("Normals"), False) array = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]) normals_to_actor(my_actor, array) - npt.assert_equal(poly_point_data.HasArray('Normals'), True) - normals = numpy_support.vtk_to_numpy(poly_point_data.GetArray('Normals')) + npt.assert_equal(poly_point_data.HasArray("Normals"), True) + normals = numpy_support.vtk_to_numpy(poly_point_data.GetArray("Normals")) npt.assert_array_equal(normals, array) @@ -639,11 +631,11 @@ def test_tangents_from_direction_of_anisotropy(): def test_tangents_to_actor(): my_actor = actor.square(np.array([[0, 0, 0]])) poly_point_data = my_actor.GetMapper().GetInput().GetPointData() - npt.assert_equal(poly_point_data.HasArray('Tangents'), False) + npt.assert_equal(poly_point_data.HasArray("Tangents"), False) array = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]) tangents_to_actor(my_actor, array) - npt.assert_equal(poly_point_data.HasArray('Tangents'), True) - tangents = numpy_support.vtk_to_numpy(poly_point_data.GetArray('Tangents')) + npt.assert_equal(poly_point_data.HasArray("Tangents"), True) + tangents = numpy_support.vtk_to_numpy(poly_point_data.GetArray("Tangents")) npt.assert_array_equal(tangents, array) @@ -877,7 +869,7 @@ def test_color_check(): def test_is_ui(): panel = Panel2D(position=(0, 0), size=(100, 100)) valid_ui = DummyUI(act=[]) - invalid_ui = DummyActor(act='act') + invalid_ui = DummyActor(act="act") npt.assert_equal(True, is_ui(panel)) npt.assert_equal(True, is_ui(valid_ui)) @@ -894,7 +886,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 @@ -906,13 +898,13 @@ def test_set_polydata_primitives_count(): polydata = PolyData() set_polydata_primitives_count(polydata, 1) - prim_count = get_polydata_field(polydata, 'prim_count')[0] + prim_count = get_polydata_field(polydata, "prim_count")[0] npt.assert_equal(prim_count, 1) def test_get_polydata_primitives_count(): polydata = PolyData() - add_polydata_numeric_field(polydata, 'prim_count', 1, array_type=VTK_INT) + add_polydata_numeric_field(polydata, "prim_count", 1, array_type=VTK_INT) prim_count = get_polydata_primitives_count(polydata) npt.assert_equal(prim_count, 1) @@ -922,14 +914,14 @@ def test_primitives_count_to_actor(): act = actor.axes() primitives_count_to_actor(act, 1) polydata = act.GetMapper().GetInput() - prim_count = get_polydata_field(polydata, 'prim_count')[0] + prim_count = get_polydata_field(polydata, "prim_count")[0] npt.assert_equal(prim_count, 1) def test_primitives_count_from_actor(): act = actor.axes() polydata = act.GetMapper().GetInput() - add_polydata_numeric_field(polydata, 'prim_count', 1, array_type=VTK_INT) + add_polydata_numeric_field(polydata, "prim_count", 1, array_type=VTK_INT) prim_count = primitives_count_from_actor(act) npt.assert_equal(prim_count, 1) diff --git a/fury/tests/test_window.py b/fury/tests/test_window.py index f45607fc9..87115298e 100644 --- a/fury/tests/test_window.py +++ b/fury/tests/test_window.py @@ -8,7 +8,7 @@ from fury import actor, io, shaders, window from fury.animation import Animation, Timeline -from fury.decorators import skip_linux, skip_osx, skip_win +from fury.decorators import skip_osx, skip_win from fury.lib import ImageData, Texture, numpy_support from fury.testing import assert_greater, assert_less_equal, assert_true, captured_output from fury.utils import remove_observer_from_actor @@ -23,7 +23,7 @@ def test_scene(): # numerical errors when moving from float to int values bg_float = (1, 0.501, 0) # That will come in the image in the 0-255 uint scale - bg_color = tuple((np.round(255 * np.array(bg_float))).astype('uint8')) + bg_color = tuple((np.round(255 * np.array(bg_float))).astype("uint8")) scene.background(bg_float) arr = window.snapshot(scene) report = window.analyze_snapshot( @@ -73,12 +73,12 @@ def test_scene(): scene.camera_info() npt.assert_equal( out.getvalue().strip(), - '# Active Camera\n ' - 'Position (0.00, 0.00, 1.00)\n ' - 'Focal Point (0.00, 0.00, 0.00)\n ' - 'View Up (0.00, 1.00, 0.00)', + "# Active Camera\n " + "Position (0.00, 0.00, 1.00)\n " + "Focal Point (0.00, 0.00, 0.00)\n " + "View Up (0.00, 1.00, 0.00)", ) - npt.assert_equal(err.getvalue().strip(), '') + npt.assert_equal(err.getvalue().strip(), "") # Tests for skybox functionality # Test scene created without skybox scene = window.Scene() @@ -98,11 +98,11 @@ def test_scene(): vtk_img.SetDimensions(2, 2, 1) img_arr = np.zeros((2, 2, 3), dtype=np.uint8) img_arr[:, :, i // 2] = checker_arr - vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), order='F')) - vtk_arr.SetName('Image') + vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), order="F")) + vtk_arr.SetName("Image") img_point_data = vtk_img.GetPointData() img_point_data.AddArray(vtk_arr) - img_point_data.SetActiveScalars('Image') + img_point_data.SetActiveScalars("Image") test_tex.SetInputDataObject(i, vtk_img) test_tex.InterpolateOn() test_tex.MipmapOn() @@ -192,7 +192,6 @@ def test_active_camera(): def test_parallel_projection(): - scene = window.Scene() axes = actor.axes() axes2 = actor.axes() @@ -211,7 +210,7 @@ def test_parallel_projection(): scene.reset_camera() arr = window.snapshot(scene) - scene.projection('parallel') + scene.projection("parallel") # window.show(scene, reset_camera=False) arr2 = window.snapshot(scene) # Because of the parallel projection the two axes @@ -219,13 +218,12 @@ def test_parallel_projection(): # pixels rather than in perspective projection were # the axes being further will be smaller. npt.assert_equal(np.sum(arr2 > 0) > np.sum(arr > 0), True) - scene.projection('perspective') + scene.projection("perspective") arr2 = window.snapshot(scene) npt.assert_equal(np.sum(arr2 > 0), np.sum(arr > 0)) def test_order_transparent(): - scene = window.Scene() red_cube = actor.cube( @@ -288,11 +286,11 @@ def test_skybox(): vtk_img.SetDimensions(2, 2, 1) img_arr = np.zeros((2, 2, 3), dtype=np.uint8) img_arr[:, :, i // 2] = checker_arr - vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), order='F')) - vtk_arr.SetName('Image') + vtk_arr = numpy_support.numpy_to_vtk(img_arr.reshape((-1, 3), order="F")) + vtk_arr.SetName("Image") img_point_data = vtk_img.GetPointData() img_point_data.AddArray(vtk_arr) - img_point_data.SetActiveScalars('Image') + img_point_data.SetActiveScalars("Image") test_tex.SetInputDataObject(i, vtk_img) test_tex.InterpolateOn() test_tex.MipmapOn() @@ -327,7 +325,7 @@ def test_save_screenshot(): show_m = window.ShowManager(scene, size=window_sz) with InTemporaryDirectory(): - fname = 'test.png' + fname = "test.png" # Basic test show_m.save_screenshot(fname) npt.assert_equal(os.path.exists(fname), True) @@ -351,7 +349,7 @@ def test_save_screenshot(): @pytest.mark.skipif( - skip_win, reason='This test does not work on Windows.' ' Need to be introspected' + skip_win, reason="This test does not work on Windows." " Need to be introspected" ) def test_stereo(): scene = window.Scene() @@ -373,16 +371,16 @@ def test_stereo(): scene.reset_camera() mono = window.snapshot( - scene, fname='stereo_off.png', offscreen=True, size=(300, 300), stereo='off' + scene, fname="stereo_off.png", offscreen=True, size=(300, 300), stereo="off" ) with npt.assert_warns(UserWarning): stereo = window.snapshot( scene, - fname='stereo_horizontal.png', + fname="stereo_horizontal.png", offscreen=True, size=(300, 300), - stereo='On', + stereo="On", ) # mono render should have values in the center @@ -434,7 +432,7 @@ def timer_callback(_obj, _event): # assert_al(ideal_fps, actual_fps) this is very imprecise -@pytest.mark.skipif(True, reason='See TODO in the code') +@pytest.mark.skipif(True, reason="See TODO in the code") def test_record(): xyzr = np.array([[0, 0, 0, 10], [100, 0, 0, 25], [200, 0, 0, 50]]) colors = np.array([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1.0, 1]]) @@ -449,7 +447,7 @@ def test_record(): scene = window.Scene() scene.add(sphere_actor) - def test_content(filename='fury.png', colors_found=(True, True)): + def test_content(filename="fury.png", colors_found=(True, True)): npt.assert_equal(os.path.isfile(filename), True) arr = io.load_image(filename) report = window.analyze_snapshot(arr, colors=[(0, 255, 0), (0, 0, 255)]) @@ -464,16 +462,16 @@ def test_content(filename='fury.png', colors_found=(True, True)): # test out_path and path_numbering, n_frame with InTemporaryDirectory(): - filename = 'tmp_snapshot.png' + filename = "tmp_snapshot.png" window.record(scene, out_path=filename) test_content(filename) window.record(scene, out_path=filename, path_numbering=True) - test_content(filename + '000000.png') + test_content(filename + "000000.png") window.record(scene, out_path=filename, path_numbering=True, n_frames=3) - test_content(filename + '000000.png') - test_content(filename + '000001.png') - test_content(filename + '000002.png') - npt.assert_equal(os.path.isfile(filename + '000003.png'), False) + test_content(filename + "000000.png") + test_content(filename + "000001.png") + test_content(filename + "000002.png") + npt.assert_equal(os.path.isfile(filename + "000003.png"), False) # test verbose with captured_output() as (out, _): @@ -481,9 +479,9 @@ def test_content(filename='fury.png', colors_found=(True, True)): npt.assert_equal( out.getvalue().strip(), - 'Camera Position (315.32, 0.00, 536.73)\n' - 'Camera Focal Point (119.97, 0.00, 0.00)\n' - 'Camera View Up (0.00, 1.00, 0.00)', + "Camera Position (315.32, 0.00, 536.73)\n" + "Camera Focal Point (119.97, 0.00, 0.00)\n" + "Camera View Up (0.00, 1.00, 0.00)", ) # test camera option with InTemporaryDirectory(): @@ -499,18 +497,18 @@ def test_content(filename='fury.png', colors_found=(True, True)): if not skip_osx: with InTemporaryDirectory(): window.record( - scene, out_path='fury_1.png', size=(1000, 1000), magnification=5 + scene, out_path="fury_1.png", size=(1000, 1000), magnification=5 ) - npt.assert_equal(os.path.isfile('fury_1.png'), True) - arr = io.load_image('fury_1.png') + npt.assert_equal(os.path.isfile("fury_1.png"), True) + arr = io.load_image("fury_1.png") npt.assert_equal(arr.shape, (5000, 5000, 3)) window.record( - scene, out_path='fury_2.png', size=(5000, 5000), screen_clip=True + scene, out_path="fury_2.png", size=(5000, 5000), screen_clip=True ) - npt.assert_equal(os.path.isfile('fury_2.png'), True) - arr = io.load_image('fury_2.png') + npt.assert_equal(os.path.isfile("fury_2.png"), True) + arr = io.load_image("fury_2.png") assert_less_equal(arr.shape[0], 5000) assert_less_equal(arr.shape[1], 5000) @@ -530,14 +528,13 @@ def test_opengl_state_simple(): window.gl_set_subtractive_blending, window.gl_set_additive_blending_white_background, ]: - scene = window.Scene() centers = np.array([[0, 0, 0], [-0.1, 0, 0], [0.1, 0, 0]]) colors = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) actors = actor.markers( centers, - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, @@ -555,7 +552,7 @@ def test_opengl_state_simple(): showm.exit() -@pytest.mark.skipif(True, reason='See TODO in the code') +@pytest.mark.skipif(True, reason="See TODO in the code") def test_opengl_state_add_remove_and_check(): scene = window.Scene() centers = np.array([[0, 0, 0], [-0.1, 0, 0], [0.1, 0, 0]]) @@ -563,7 +560,7 @@ def test_opengl_state_add_remove_and_check(): actor_no_depth_test = actor.markers( centers, - marker='s', + marker="s", colors=colors, marker_opacity=0.5, scales=0.2, @@ -576,7 +573,7 @@ def test_opengl_state_add_remove_and_check(): showm.render() state = window.gl_get_current_state(showm.window.GetState()) - before_depth_test = state['GL_DEPTH_TEST'] + before_depth_test = state["GL_DEPTH_TEST"] npt.assert_equal(before_depth_test, True) # TODO: we are getting bad request for enum status # it seems we are not provide the correct values @@ -594,13 +591,13 @@ def test_opengl_state_add_remove_and_check(): showm.render() state = window.gl_get_current_state(showm.window.GetState()) # print('type', type(showm.window.GetState())) - after_depth_test = state['GL_DEPTH_TEST'] + after_depth_test = state["GL_DEPTH_TEST"] npt.assert_equal(after_depth_test, False) # removes the no_depth_test effect remove_observer_from_actor(actor_no_depth_test, id_observer) showm.render() state = window.gl_get_current_state(showm.window.GetState()) - after_remove_depth_test_observer = state['GL_DEPTH_TEST'] + after_remove_depth_test_observer = state["GL_DEPTH_TEST"] npt.assert_equal(after_remove_depth_test_observer, True) diff --git a/fury/transform.py b/fury/transform.py index ff5db9479..ac9c1aba8 100644 --- a/fury/transform.py +++ b/fury/transform.py @@ -8,48 +8,48 @@ # map axes strings to/from tuples of inner axis, parity, repetition, frame _AXES2TUPLE = { - 'sxyz': (0, 0, 0, 0), - 'sxyx': (0, 0, 1, 0), - 'sxzy': (0, 1, 0, 0), - 'sxzx': (0, 1, 1, 0), - 'syzx': (1, 0, 0, 0), - 'syzy': (1, 0, 1, 0), - 'syxz': (1, 1, 0, 0), - 'syxy': (1, 1, 1, 0), - 'szxy': (2, 0, 0, 0), - 'szxz': (2, 0, 1, 0), - 'szyx': (2, 1, 0, 0), - 'szyz': (2, 1, 1, 0), - 'rzyx': (0, 0, 0, 1), - 'rxyx': (0, 0, 1, 1), - 'ryzx': (0, 1, 0, 1), - 'rxzx': (0, 1, 1, 1), - 'rxzy': (1, 0, 0, 1), - 'ryzy': (1, 0, 1, 1), - 'rzxy': (1, 1, 0, 1), - 'ryxy': (1, 1, 1, 1), - 'ryxz': (2, 0, 0, 1), - 'rzxz': (2, 0, 1, 1), - 'rxyz': (2, 1, 0, 1), - 'rzyz': (2, 1, 1, 1), + "sxyz": (0, 0, 0, 0), + "sxyx": (0, 0, 1, 0), + "sxzy": (0, 1, 0, 0), + "sxzx": (0, 1, 1, 0), + "syzx": (1, 0, 0, 0), + "syzy": (1, 0, 1, 0), + "syxz": (1, 1, 0, 0), + "syxy": (1, 1, 1, 0), + "szxy": (2, 0, 0, 0), + "szxz": (2, 0, 1, 0), + "szyx": (2, 1, 0, 0), + "szyz": (2, 1, 1, 0), + "rzyx": (0, 0, 0, 1), + "rxyx": (0, 0, 1, 1), + "ryzx": (0, 1, 0, 1), + "rxzx": (0, 1, 1, 1), + "rxzy": (1, 0, 0, 1), + "ryzy": (1, 0, 1, 1), + "rzxy": (1, 1, 0, 1), + "ryxy": (1, 1, 1, 1), + "ryxz": (2, 0, 0, 1), + "rzxz": (2, 0, 1, 1), + "rxyz": (2, 1, 0, 1), + "rzyz": (2, 1, 1, 1), } -_TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items()) +_TUPLE2AXES = {v: k for k, v in _AXES2TUPLE.items()} -def euler_matrix(ai, aj, ak, axes='sxyz'): +def euler_matrix(ai, aj, ak, axes="sxyz"): """Return homogeneous rotation matrix from Euler angles and axis sequence. Code modified from the work of Christoph Gohlke link provided here http://www.lfd.uci.edu/~gohlke/code/transformations.py.html Parameters - ------------ + ---------- ai, aj, ak : Euler's roll, pitch and yaw angles axes : One of 24 axis sequences as string or encoded tuple Returns - --------- + ------- matrix : ndarray (4, 4) Code modified from the work of Christoph Gohlke link provided here @@ -138,7 +138,7 @@ def sphere2cart(r, theta, phi): as 'longitude' Parameters - ------------ + ---------- r : array_like radius theta : array_like @@ -147,16 +147,16 @@ def sphere2cart(r, theta, phi): azimuth angle Returns - --------- + ------- x : array - x coordinate(s) in Cartesion space + x coordinate(s) in Cartesian space y : array y coordinate(s) in Cartesian space z : array z coordinate Notes - -------- + ----- See these pages: * http://en.wikipedia.org/wiki/Spherical_coordinate_system @@ -197,7 +197,7 @@ def cart2sphere(x, y, z): $0\le\theta\mathrm{(theta)}\le\pi$ and $-\pi\le\phi\mathrm{(phi)}\le\pi$ Parameters - ------------ + ---------- x : array_like x coordinate in Cartesian space y : array_like @@ -206,7 +206,7 @@ def cart2sphere(x, y, z): z coordinate Returns - --------- + ------- r : array radius theta : array @@ -345,6 +345,7 @@ def apply_transformation(vertices, transformation): ------- vertices : ndarray (n, 3) transformed vertices of the mesh + """ shape = vertices.shape temp = np.full((shape[0], 1), 1) @@ -374,6 +375,7 @@ def transform_from_matrix(matrix): rotation component from the transformation matrix scale : ndarray (3, ) scale component from the transformation matrix. + """ translate = matrix[:, -1:].reshape((-1,))[:-1] diff --git a/fury/ui/__init__.py b/fury/ui/__init__.py index 8e45eb504..f6fdc9bd3 100644 --- a/fury/ui/__init__.py +++ b/fury/ui/__init__.py @@ -1,3 +1,50 @@ -from fury.ui.containers import * -from fury.ui.core import * -from fury.ui.elements import * +from fury.ui.containers import GridUI, ImageContainer2D, Panel2D, TabPanel2D, TabUI +from fury.ui.core import Button2D, Disk2D, Rectangle2D, TextBlock2D +from fury.ui.elements import ( + Card2D, + Checkbox, + ComboBox2D, + DrawPanel, + DrawShape, + FileMenu2D, + LineDoubleSlider2D, + LineSlider2D, + ListBox2D, + ListBoxItem2D, + Option, + PlaybackPanel, + RadioButton, + RangeSlider, + RingSlider2D, + SpinBox, + TextBox2D, +) + +__all__ = [ + "Panel2D", + "TabPanel2D", + "TabUI", + "ImageContainer2D", + "GridUI", + "Rectangle2D", + "Disk2D", + "TextBlock2D", + "Button2D", + "TextBox2D", + "LineSlider2D", + "LineDoubleSlider2D", + "RingSlider2D", + "RangeSlider", + "Checkbox", + "Option", + "RadioButton", + "ComboBox2D", + "ListBox2D", + "ListBoxItem2D", + "FileMenu2D", + "DrawShape", + "DrawPanel", + "PlaybackPanel", + "Card2D", + "SpinBox", +] diff --git a/fury/ui/containers.py b/fury/ui/containers.py index 1f5bc2021..ca1606108 100644 --- a/fury/ui/containers.py +++ b/fury/ui/containers.py @@ -1,6 +1,6 @@ """UI container module.""" -__all__ = ['Panel2D', 'TabPanel2D', 'TabUI', 'ImageContainer2D', 'GridUI'] +from warnings import warn import numpy as np @@ -29,6 +29,7 @@ class Panel2D(UI): ---------- alignment : [left, right] Alignment of the panel with respect to the overall screen. + """ def __init__( @@ -37,7 +38,7 @@ def __init__( position=(0, 0), color=(0.1, 0.1, 0.1), opacity=0.7, - align='left', + align="left", border_color=(1, 1, 1), border_width=0, has_border=False, @@ -62,6 +63,7 @@ def __init__( width of the border has_border: bool, optional If the panel should have borders. + """ self.has_border = has_border self._border_color = border_color @@ -86,17 +88,17 @@ def _setup(self): if self.has_border: self.borders = { - 'left': Rectangle2D(), - 'right': Rectangle2D(), - 'top': Rectangle2D(), - 'bottom': Rectangle2D(), + "left": Rectangle2D(), + "right": Rectangle2D(), + "top": Rectangle2D(), + "bottom": Rectangle2D(), } self.border_coords = { - 'left': (0.0, 0.0), - 'right': (1.0, 0.0), - 'top': (0.0, 1.0), - 'bottom': (0.0, 0.0), + "left": (0.0, 0.0), + "right": (1.0, 0.0), + "top": (0.0, 1.0), + "bottom": (0.0, 0.0), } for key in self.borders.keys(): @@ -132,6 +134,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ for element in self._elements: element.add_to_scene(scene) @@ -146,23 +149,24 @@ def resize(self, size): ---------- size : (float, float) Panel size (width, height) in pixels. + """ self.background.resize(size) if self.has_border: - self.borders['left'].resize( + self.borders["left"].resize( (self._border_width, size[1] + self._border_width) ) - self.borders['right'].resize( + self.borders["right"].resize( (self._border_width, size[1] + self._border_width) ) - self.borders['top'].resize( + self.borders["top"].resize( (self.size[0] + self._border_width, self._border_width) ) - self.borders['bottom'].resize( + self.borders["bottom"].resize( (self.size[0] + self._border_width, self._border_width) ) @@ -201,7 +205,7 @@ def opacity(self): def opacity(self, opacity): self.background.opacity = opacity - def add_element(self, element, coords, anchor='position'): + def add_element(self, element, coords, anchor="position"): """Add a UI component to the panel. The coordinates represent an offset from the lower left corner of the @@ -222,13 +226,13 @@ def add_element(self, element, coords, anchor='position'): if np.issubdtype(coords.dtype, np.floating): if np.any(coords < 0) or np.any(coords > 1): - raise ValueError('Normalized coordinates must be in [0,1].') + raise ValueError("Normalized coordinates must be in [0,1].") coords = coords * self.size - if anchor == 'center': + if anchor == "center": element.center = self.position + coords - elif anchor == 'position': + elif anchor == "position": element.position = self.position + coords else: msg = "Unknown anchor {}. Supported anchors are 'position'" " and 'center'." @@ -245,12 +249,13 @@ def remove_element(self, element): ---------- element : UI The UI item to be removed. + """ idx = self._elements.index(element) del self._elements[idx] del self.element_offsets[idx] - def update_element(self, element, coords, anchor='position'): + def update_element(self, element, coords, anchor="position"): """Update the position of a UI component in the panel. Parameters @@ -263,6 +268,7 @@ def update_element(self, element, coords, anchor='position'): between [0,1]. If int, pixels coordinates are assumed and it must fit within the panel's size. + """ self.remove_element(element) self.add_element(element, coords, anchor) @@ -286,22 +292,23 @@ def re_align(self, window_size_change): ---------- window_size_change : (int, int) New window size (width, height) in pixels. + """ - if self.alignment == 'left': + if self.alignment == "left": pass - elif self.alignment == 'right': + elif self.alignment == "right": self.position += np.array(window_size_change) else: - msg = 'You can only left-align or right-align objects in a panel.' + msg = "You can only left-align or right-align objects in a panel." raise ValueError(msg) def update_border_coords(self): """Update the coordinates of the borders""" self.border_coords = { - 'left': (0.0, 0.0), - 'right': (1.0, 0.0), - 'top': (0.0, 1.0), - 'bottom': (0.0, 0.0), + "left": (0.0, 0.0), + "right": (1.0, 0.0), + "top": (0.0, 1.0), + "bottom": (0.0, 0.0), } for key in self.borders.keys(): @@ -309,7 +316,7 @@ def update_border_coords(self): @property def border_color(self): - sides = ['left', 'right', 'top', 'bottom'] + sides = ["left", "right", "top", "bottom"] return [self.borders[side].color for side in sides] @border_color.setter @@ -320,26 +327,27 @@ def border_color(self, side_color): ---------- side_color: Iterable Iterable to pack side, color values + """ side, color = side_color - if side.lower() not in ['left', 'right', 'top', 'bottom']: - raise ValueError(f'{side} not a valid border side') + if side.lower() not in ["left", "right", "top", "bottom"]: + raise ValueError(f"{side} not a valid border side") self.borders[side].color = color @property def border_width(self): - sides = ['left', 'right', 'top', 'bottom'] + sides = ["left", "right", "top", "bottom"] widths = [] for side in sides: - if side in ['left', 'right']: + if side in ["left", "right"]: widths.append(self.borders[side].width) - elif side in ['top', 'bottom']: + elif side in ["top", "bottom"]: widths.append(self.borders[side].height) else: - raise ValueError(f'{side} not a valid border side') + raise ValueError(f"{side} not a valid border side") return widths @border_width.setter @@ -350,15 +358,16 @@ def border_width(self, side_width): ---------- side_width: Iterable Iterable to pack side, width values + """ side, border_width = side_width - if side.lower() in ['left', 'right']: + if side.lower() in ["left", "right"]: self.borders[side].width = border_width - elif side.lower() in ['top', 'bottom']: + elif side.lower() in ["top", "bottom"]: self.borders[side].height = border_width else: - raise ValueError(f'{side} not a valid border side') + raise ValueError(f"{side} not a valid border side") class TabPanel2D(UI): @@ -370,13 +379,14 @@ class TabPanel2D(UI): Hold all the content UI components. text_block: :class: 'TextBlock2D' Renders the title of the tab. + """ def __init__( self, position=(0, 0), size=(100, 100), - title='New Tab', + title="New Tab", color=(0.5, 0.5, 0.5), content_panel=None, ): @@ -395,6 +405,7 @@ def __init__( Background color of tab panel. content_panel : Panel2D Panel consisting of the content UI elements. + """ self.content_panel = content_panel self.panel_size = size @@ -444,7 +455,7 @@ def _set_position(self, _coords): self.panel.position = _coords def _get_size(self): - self.panel.size + return self.panel.size def resize(self, size): """Resize Tab panel. @@ -472,6 +483,7 @@ def color(self, color): Parameters ---------- color : list of 3 floats. + """ self.panel.color = color @@ -488,6 +500,7 @@ def title(self, text): ---------- text : str New title for tab panel. + """ self.text_block.message = text @@ -504,6 +517,7 @@ def title_bold(self, bold): ---------- bold : bool Bold property for a text title in a tab panel. + """ self.text_block.bold = bold @@ -520,6 +534,7 @@ def title_color(self, color): ---------- color : tuple New title color for tab panel. + """ self.text_block.color = color @@ -536,6 +551,7 @@ def title_font_size(self, font_size): ---------- font_size : int New title font size for tab panel. + """ self.text_block.font_size = font_size @@ -552,10 +568,11 @@ def title_italic(self, italic): ---------- italic : bool Italic property for a text title in a tab panel. + """ self.text_block.italic = italic - def add_element(self, element, coords, anchor='position'): + def add_element(self, element, coords, anchor="position"): """Add a UI component to the content panel. The coordinates represent an offset from the lower left corner of the @@ -570,6 +587,7 @@ def add_element(self, element, coords, anchor='position'): between [0,1]. If int, pixels coordinates are assumed and it must fit within the panel's size. + """ element.set_visibility(False) self.content_panel.add_element(element, coords, anchor) @@ -581,10 +599,11 @@ def remove_element(self, element): ---------- element : UI The UI item to be removed. + """ self.content_panel.remove_element(element) - def update_element(self, element, coords, anchor='position'): + def update_element(self, element, coords, anchor="position"): """Update the position of a UI component in the content panel. Parameters @@ -597,8 +616,9 @@ def update_element(self, element, coords, anchor='position'): between [0,1]. If int, pixels coordinates are assumed and it must fit within the panel's size. + """ - self.content_panel.update_element(element, coords, anchor='position') + self.content_panel.update_element(element, coords, anchor="position") class TabUI(UI): @@ -608,6 +628,7 @@ class TabUI(UI): ---------- tabs: :class: List of 'TabPanel2D' Stores all the instances of 'TabPanel2D' that renders the contents. + """ def __init__( @@ -619,6 +640,7 @@ def __init__( inactive_color=(0.5, 0.5, 0.5), draggable=False, startup_tab_id=None, + tab_bar_pos="top", ): """Init class instance. @@ -640,6 +662,8 @@ def __init__( startup_tab_id : int, optional Tab to be activated and uncollapsed on startup. by default None is activated/ all collapsed. + tab_bar_pos : str, optional + Position of the Tab Bar in the panel """ self.tabs = [] self.nb_tabs = nb_tabs @@ -650,6 +674,7 @@ def __init__( self.inactive_color = inactive_color self.active_tab_idx = startup_tab_id self.collapsed = True + self.tab_bar_pos = tab_bar_pos super(TabUI, self).__init__() self.position = position @@ -715,32 +740,47 @@ def _get_size(self): def update_tabs(self): """Update position, size and callbacks for tab panels.""" self.tab_panel_size = (self.size[0] // self.nb_tabs, int(0.1 * self.size[1])) + if self.tab_bar_pos.lower() not in ["top", "bottom"]: + warn("tab_bar_pos can only have value top/bottom", stacklevel=2) + self.tab_bar_pos = "top" + + if self.tab_bar_pos.lower() == "top": + tab_panel_pos = [0.0, 0.9] + elif self.tab_bar_pos.lower() == "bottom": + tab_panel_pos = [0.0, 0.0] - tab_panel_pos = [0.0, 0.9] for tab_panel in self.tabs: tab_panel.resize(self.tab_panel_size) tab_panel.content_panel.position = self.position content_panel = tab_panel.content_panel if self.draggable: - tab_panel.panel.background.on_left_mouse_button_pressed = \ + tab_panel.panel.background.on_left_mouse_button_pressed = ( self.left_button_pressed - content_panel.background.on_left_mouse_button_pressed = \ + ) + content_panel.background.on_left_mouse_button_pressed = ( self.left_button_pressed - tab_panel.text_block.on_left_mouse_button_pressed = \ + ) + tab_panel.text_block.on_left_mouse_button_pressed = ( self.left_button_pressed + ) - tab_panel.panel.background.on_left_mouse_button_dragged = \ + tab_panel.panel.background.on_left_mouse_button_dragged = ( self.left_button_dragged - content_panel.background.on_left_mouse_button_dragged = \ + ) + content_panel.background.on_left_mouse_button_dragged = ( self.left_button_dragged - tab_panel.text_block.on_left_mouse_button_dragged = \ + ) + tab_panel.text_block.on_left_mouse_button_dragged = ( self.left_button_dragged + ) else: - tab_panel.panel.background.on_left_mouse_button_dragged = \ + tab_panel.panel.background.on_left_mouse_button_dragged = ( lambda i_ren, _obj, _comp: i_ren.force_render - content_panel.background.on_left_mouse_button_dragged = \ + ) + content_panel.background.on_left_mouse_button_dragged = ( lambda i_ren, _obj, _comp: i_ren.force_render + ) tab_panel.text_block.on_left_mouse_button_clicked = self.select_tab_callback tab_panel.panel.background.on_left_mouse_button_clicked = ( @@ -754,7 +794,10 @@ def update_tabs(self): tab_panel.content_panel.resize(self.content_size) self.parent_panel.add_element(tab_panel, tab_panel_pos) - self.parent_panel.add_element(tab_panel.content_panel, (0.0, 0.0)) + if self.tab_bar_pos.lower() == "top": + self.parent_panel.add_element(tab_panel.content_panel, (0.0, 0.0)) + elif self.tab_bar_pos.lower() == "bottom": + self.parent_panel.add_element(tab_panel.content_panel, (0.0, 0.1)) tab_panel_pos[0] += 1 / self.nb_tabs def select_tab_callback(self, iren, _obj, _tab_comp): @@ -792,28 +835,28 @@ def collapse_tab_ui(self, iren, _obj, _tab_comp): iren.force_render() iren.event.abort() - def add_element(self, tab_idx, element, coords, anchor='position'): + def add_element(self, tab_idx, element, coords, anchor="position"): """Add element to content panel after checking its existence.""" if tab_idx < self.nb_tabs and tab_idx >= 0: self.tabs[tab_idx].add_element(element, coords, anchor) if tab_idx == self.active_tab_idx: element.set_visibility(True) else: - raise IndexError('Tab with index ' '{} does not exist'.format(tab_idx)) + raise IndexError("Tab with index " "{} does not exist".format(tab_idx)) def remove_element(self, tab_idx, element): """Remove element from content panel after checking its existence.""" if tab_idx < self.nb_tabs and tab_idx >= 0: self.tabs[tab_idx].remove_element(element) else: - raise IndexError('Tab with index ' '{} does not exist'.format(tab_idx)) + raise IndexError("Tab with index " "{} does not exist".format(tab_idx)) - def update_element(self, tab_idx, element, coords, anchor='position'): + def update_element(self, tab_idx, element, coords, anchor="position"): """Update element on content panel after checking its existence.""" if tab_idx < self.nb_tabs and tab_idx >= 0: self.tabs[tab_idx].update_element(element, coords, anchor) else: - raise IndexError('Tab with index ' '{} does not exist'.format(tab_idx)) + raise IndexError("Tab with index " "{} does not exist".format(tab_idx)) def left_button_pressed(self, i_ren, _obj, _sub_component): click_pos = np.array(i_ren.event.position) @@ -854,6 +897,7 @@ def __init__(self, img_path, position=(0, 0), size=(100, 100)): Absolute coordinates (x, y) of the lower-left corner of the image. size : (int, int), optional Width and height in pixels of the image. + """ super(ImageContainer2D, self).__init__(position) self.img = load_image(img_path, as_vtktype=True) @@ -874,6 +918,7 @@ def _setup(self): Returns ------- :class:`vtkTexturedActor2D` + """ self.texture_polydata = PolyData() self.texture_points = Points() @@ -927,6 +972,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ scene.add(self.actor) @@ -937,6 +983,7 @@ def resize(self, size): ---------- size : (float, float) image size (width, height) in pixels. + """ # Update actor. self.texture_points.SetPoint(0, 0, 0, 0.0) @@ -952,6 +999,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ self.actor.SetPosition(*coords) @@ -962,6 +1010,7 @@ def scale(self, factor): ---------- factor : (float, float) Scaling factor (width, height) in pixels. + """ self.resize(self.size * factor) @@ -985,13 +1034,12 @@ def __init__( captions=None, caption_offset=(0, -100, 0), cell_padding=0, - cell_shape='rect', + cell_shape="rect", aspect_ratio=16 / 9.0, dim=None, rotation_speed=1, rotation_axis=(0, 1, 0), ): - # TODO: add rotation axis None by default self.container = grid( @@ -1011,7 +1059,7 @@ def __init__( for item in self.container._items: actor = item if captions is None else item._items[0] self._actors.append(actor) - self._actors_dict[actor] = {'x': -np.inf, 'y': -np.inf} + self._actors_dict[actor] = {"x": -np.inf, "y": -np.inf} super(GridUI, self).__init__(position=(0, 0, 0)) @@ -1026,7 +1074,6 @@ def left_click_callback(istyle, _obj, _what): @staticmethod def left_release_callback(istyle, _obj, _what): - istyle.trackball_actor.OnLeftButtonUp() istyle.force_render() istyle.event.abort() @@ -1039,7 +1086,6 @@ def mouse_move_callback(istyle, _obj, _what): @staticmethod def left_click_callback2(istyle, obj, self): - rx, ry, rz = self.rotation_axis clockwise_rotation = np.array([self.rotation_speed, rx, ry, rz]) rotate(obj, clockwise_rotation) @@ -1049,33 +1095,29 @@ def left_click_callback2(istyle, obj, self): @staticmethod def left_release_callback2(istyle, _obj, _what): - istyle.force_render() istyle.event.abort() @staticmethod def mouse_move_callback2(istyle, obj, self): - - if self._actors_dict[obj]['y'] == -np.inf: - + if self._actors_dict[obj]["y"] == -np.inf: iren = istyle.GetInteractor() event_pos = iren.GetEventPosition() - self._actors_dict[obj]['y'] = event_pos[1] + self._actors_dict[obj]["y"] = event_pos[1] else: - iren = istyle.GetInteractor() event_pos = iren.GetEventPosition() rx, ry, rz = self.rotation_axis - if event_pos[1] >= self._actors_dict[obj]['y']: + if event_pos[1] >= self._actors_dict[obj]["y"]: clockwise_rotation = np.array([-self.rotation_speed, rx, ry, rz]) rotate(obj, clockwise_rotation) else: anti_clockwise_rotation = np.array([self.rotation_speed, rx, ry, rz]) rotate(obj, anti_clockwise_rotation) - self._actors_dict[obj]['y'] = event_pos[1] + self._actors_dict[obj]["y"] = event_pos[1] istyle.force_render() istyle.event.abort() @@ -1087,19 +1129,19 @@ def mouse_move_callback2(istyle, obj, self): def key_press_callback(self, istyle, obj, _what): has_changed = False - if istyle.event.key == 'Left': + if istyle.event.key == "Left": has_changed = True for a in self._actors: rotate(a, self.ANTICLOCKWISE_ROTATION_Y) - elif istyle.event.key == 'Right': + elif istyle.event.key == "Right": has_changed = True for a in self._actors: rotate(a, self.CLOCKWISE_ROTATION_Y) - elif istyle.event.key == 'Up': + elif istyle.event.key == "Up": has_changed = True for a in self._actors: rotate(a, self.ANTICLOCKWISE_ROTATION_X) - elif istyle.event.key == 'Down': + elif istyle.event.key == "Down": has_changed = True for a in self._actors: rotate(a, self.CLOCKWISE_ROTATION_X) @@ -1115,24 +1157,24 @@ def _setup(self): if self.rotation_axis is None: self.add_callback( - actor, 'LeftButtonPressEvent', self.left_click_callback + actor, "LeftButtonPressEvent", self.left_click_callback ) self.add_callback( - actor, 'LeftButtonReleaseEvent', self.left_release_callback + actor, "LeftButtonReleaseEvent", self.left_release_callback ) - self.add_callback(actor, 'MouseMoveEvent', self.mouse_move_callback) + self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback) else: self.add_callback( - actor, 'LeftButtonPressEvent', self.left_click_callback2 + actor, "LeftButtonPressEvent", self.left_click_callback2 ) # TODO: possibly add this too self.add_callback( - actor, 'LeftButtonReleaseEvent', self.left_release_callback2 + actor, "LeftButtonReleaseEvent", self.left_release_callback2 ) - self.add_callback(actor, 'MouseMoveEvent', self.mouse_move_callback2) + self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback2) # TODO: this is currently not running - self.add_callback(actor, 'KeyPressEvent', self.key_press_callback) + self.add_callback(actor, "KeyPressEvent", self.key_press_callback) # self.on_key_press = self.key_press_callback2 def _get_actors(self): @@ -1168,6 +1210,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ # coords = (0, 0, 0) pass diff --git a/fury/ui/core.py b/fury/ui/core.py index f184e7d16..5507a613a 100644 --- a/fury/ui/core.py +++ b/fury/ui/core.py @@ -1,9 +1,6 @@ """UI core module that describe UI abstract class.""" -__all__ = ['Rectangle2D', 'Disk2D', 'TextBlock2D', 'Button2D'] - import abc -from warnings import warn import numpy as np @@ -99,9 +96,9 @@ def __init__(self, position=(0, 0)): self._setup() # Setup needed actors and sub UI components. self.position = position - self.left_button_state = 'released' - self.right_button_state = 'released' - self.middle_button_state = 'released' + self.left_button_state = "released" + self.right_button_state = "released" + self.middle_button_state = "released" self.on_left_mouse_button_pressed = lambda i_ren, obj, element: None self.on_left_mouse_button_dragged = lambda i_ren, obj, element: None @@ -128,13 +125,13 @@ def _setup(self): components. """ - msg = 'Subclasses of UI must implement `_setup(self)`.' + msg = "Subclasses of UI must implement `_setup(self)`." raise NotImplementedError(msg) @abc.abstractmethod def _get_actors(self): """Get the actors composing this UI component.""" - msg = 'Subclasses of UI must implement `_get_actors(self)`.' + msg = "Subclasses of UI must implement `_get_actors(self)`." raise NotImplementedError(msg) @property @@ -151,7 +148,7 @@ def _add_to_scene(self, _scene): _scene : Scene """ - msg = 'Subclasses of UI must implement `_add_to_scene(self, scene)`.' + msg = "Subclasses of UI must implement `_add_to_scene(self, scene)`." raise NotImplementedError(msg) def add_to_scene(self, scene): @@ -170,13 +167,12 @@ def add_to_scene(self, scene): for callback in self._callbacks: if not isinstance(iren, CustomInteractorStyle): msg = ( - 'The ShowManager requires `CustomInteractorStyle` in' - ' order to use callbacks.' + "The ShowManager requires `CustomInteractorStyle` in" + " order to use callbacks." ) raise TypeError(msg) if callback[0] == self._scene: - iren.add_callback(iren, callback[1], callback[2], args=[self]) else: iren.add_callback(*callback, args=[self]) @@ -220,7 +216,7 @@ def _set_position(self, _coords): Absolute pixel coordinates (x, y). """ - msg = 'Subclasses of UI must implement `_set_position(self, coords)`.' + msg = "Subclasses of UI must implement `_set_position(self, coords)`." raise NotImplementedError(msg) @property @@ -229,7 +225,7 @@ def size(self): @abc.abstractmethod def _get_size(self): - msg = 'Subclasses of UI must implement property `size`.' + msg = "Subclasses of UI must implement property `size`." raise NotImplementedError(msg) @property @@ -246,8 +242,8 @@ def center(self, coords): Absolute pixel coordinates (x, y). """ - if not hasattr(self, 'size'): - msg = 'Subclasses of UI must implement the `size` property.' + if not hasattr(self, "size"): + msg = "Subclasses of UI must implement the `size` property." raise NotImplementedError(msg) new_center = np.array(coords) @@ -262,89 +258,89 @@ def set_visibility(self, visibility): def handle_events(self, actor): self.add_callback( - actor, 'LeftButtonPressEvent', self.left_button_click_callback + actor, "LeftButtonPressEvent", self.left_button_click_callback ) self.add_callback( - actor, 'LeftButtonReleaseEvent', self.left_button_release_callback + actor, "LeftButtonReleaseEvent", self.left_button_release_callback ) self.add_callback( - actor, 'RightButtonPressEvent', self.right_button_click_callback + actor, "RightButtonPressEvent", self.right_button_click_callback ) self.add_callback( - actor, 'RightButtonReleaseEvent', self.right_button_release_callback + actor, "RightButtonReleaseEvent", self.right_button_release_callback ) self.add_callback( - actor, 'MiddleButtonPressEvent', self.middle_button_click_callback + actor, "MiddleButtonPressEvent", self.middle_button_click_callback ) self.add_callback( - actor, 'MiddleButtonReleaseEvent', self.middle_button_release_callback + actor, "MiddleButtonReleaseEvent", self.middle_button_release_callback ) - self.add_callback(actor, 'MouseMoveEvent', self.mouse_move_callback) - self.add_callback(actor, 'KeyPressEvent', self.key_press_callback) + self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback) + self.add_callback(actor, "KeyPressEvent", self.key_press_callback) @staticmethod def left_button_click_callback(i_ren, obj, self): - self.left_button_state = 'pressing' + self.left_button_state = "pressing" self.on_left_mouse_button_pressed(i_ren, obj, self) i_ren.event.abort() @staticmethod def left_button_release_callback(i_ren, obj, self): - if self.left_button_state == 'pressing': + if self.left_button_state == "pressing": self.on_left_mouse_button_clicked(i_ren, obj, self) - self.left_button_state = 'released' + self.left_button_state = "released" self.on_left_mouse_button_released(i_ren, obj, self) @staticmethod def right_button_click_callback(i_ren, obj, self): - self.right_button_state = 'pressing' + self.right_button_state = "pressing" self.on_right_mouse_button_pressed(i_ren, obj, self) i_ren.event.abort() @staticmethod def right_button_release_callback(i_ren, obj, self): - if self.right_button_state == 'pressing': + if self.right_button_state == "pressing": self.on_right_mouse_button_clicked(i_ren, obj, self) - self.right_button_state = 'released' + self.right_button_state = "released" self.on_right_mouse_button_released(i_ren, obj, self) @staticmethod def middle_button_click_callback(i_ren, obj, self): - self.middle_button_state = 'pressing' + self.middle_button_state = "pressing" self.on_middle_mouse_button_pressed(i_ren, obj, self) i_ren.event.abort() @staticmethod def middle_button_release_callback(i_ren, obj, self): - if self.middle_button_state == 'pressing': + if self.middle_button_state == "pressing": self.on_middle_mouse_button_clicked(i_ren, obj, self) - self.middle_button_state = 'released' + self.middle_button_state = "released" self.on_middle_mouse_button_released(i_ren, obj, self) @staticmethod def mouse_move_callback(i_ren, obj, self): left_pressing_or_dragging = ( - self.left_button_state == 'pressing' or self.left_button_state == 'dragging' + self.left_button_state == "pressing" or self.left_button_state == "dragging" ) right_pressing_or_dragging = ( - self.right_button_state == 'pressing' - or self.right_button_state == 'dragging' + self.right_button_state == "pressing" + or self.right_button_state == "dragging" ) middle_pressing_or_dragging = ( - self.middle_button_state == 'pressing' - or self.middle_button_state == 'dragging' + self.middle_button_state == "pressing" + or self.middle_button_state == "dragging" ) if left_pressing_or_dragging: - self.left_button_state = 'dragging' + self.left_button_state = "dragging" self.on_left_mouse_button_dragged(i_ren, obj, self) elif right_pressing_or_dragging: - self.right_button_state = 'dragging' + self.right_button_state = "dragging" self.on_right_mouse_button_dragged(i_ren, obj, self) elif middle_pressing_or_dragging: - self.middle_button_state = 'dragging' + self.middle_button_state = "dragging" self.on_middle_mouse_button_dragged(i_ren, obj, self) @staticmethod @@ -368,6 +364,7 @@ def __init__(self, size=(0, 0), position=(0, 0), color=(1, 1, 1), opacity=1.0): Must take values in [0, 1]. opacity : float Must take values in [0, 1]. + """ super(Rectangle2D, self).__init__(position) self.color = color @@ -424,6 +421,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ scene.add(self.actor) @@ -594,6 +592,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ # Disk actor are positioned with respect to their center. self.actor.SetPosition(*coords + self.outer_radius) @@ -689,15 +688,16 @@ class TextBlock2D(UI): Automatically scale font according to the text bounding box. dynamic_bbox : bool Automatically resize the bounding box according to the content. + """ def __init__( self, - text='Text Block', + text="Text Block", font_size=18, - font_family='Arial', - justification='left', - vertical_justification='bottom', + font_family="Arial", + justification="left", + vertical_justification="bottom", bold=False, italic=False, shadow=False, @@ -706,7 +706,7 @@ def __init__( bg_color=None, position=(0, 0), auto_font_scale=False, - dynamic_bbox=False + dynamic_bbox=False, ): """Init class instance. @@ -740,6 +740,7 @@ def __init__( Automatically scale font according to the text bounding box. dynamic_bbox : bool, optional Automatically resize the bounding box according to the content. + """ self.boundingbox = [0, 0, 0, 0] super(TextBlock2D, self).__init__(position=position) @@ -776,6 +777,7 @@ def resize(self, size): ---------- size : (int, int) Text bounding box size(width, height) in pixels. + """ self.update_bounding_box(size) @@ -789,6 +791,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ scene.add(self.background, self.actor) @@ -800,6 +803,7 @@ def message(self): ------- str The current text message. + """ return self.actor.GetInput() @@ -811,6 +815,7 @@ def message(self, text): ---------- text : str The message to be set. + """ self.actor.SetInput(text) if self.dynamic_bbox: @@ -821,9 +826,10 @@ def font_size(self): """Get text font size. Returns - ---------- + ------- int Text font size. + """ return self.actor.GetTextProperty().GetFontSize() @@ -835,6 +841,7 @@ def font_size(self, size): ---------- size : int Text font size. + """ if not self.auto_font_scale: self.actor.SetTextScaleModeToNone() @@ -848,14 +855,15 @@ def font_family(self): """Get font family. Returns - ---------- + ------- str Text font family. + """ return self.actor.GetTextProperty().GetFontFamilyAsString() @font_family.setter - def font_family(self, family='Arial'): + def font_family(self, family="Arial"): """Set font family. Currently Arial and Courier are supported. @@ -864,13 +872,14 @@ def font_family(self, family='Arial'): ---------- family : str The font family. + """ - if family == 'Arial': + if family == "Arial": self.actor.GetTextProperty().SetFontFamilyToArial() - elif family == 'Courier': + elif family == "Courier": self.actor.GetTextProperty().SetFontFamilyToCourier() else: - raise ValueError('Font not supported yet: {}.'.format(family)) + raise ValueError("Font not supported yet: {}.".format(family)) @property def justification(self): @@ -880,6 +889,7 @@ def justification(self): ------- str Text justification. + """ return self._justification @@ -965,6 +975,7 @@ def italic(self, flag): ---------- flag : bool Italicises text if True. + """ self.actor.GetTextProperty().SetItalic(flag) @@ -976,6 +987,7 @@ def shadow(self): ------- bool Text is shadowed if True. + """ return self.actor.GetTextProperty().GetShadow() @@ -987,6 +999,7 @@ def shadow(self, flag): ---------- flag : bool Shadows text if True. + """ self.actor.GetTextProperty().SetShadow(flag) @@ -998,6 +1011,7 @@ def color(self): ------- (float, float, float) Returns text color in RGB. + """ return self.actor.GetTextProperty().GetColor() @@ -1022,6 +1036,7 @@ def background_color(self): (float, float, float) or None If None, there no background color. Otherwise, background color in RGB. + """ if not self.have_bg: return None @@ -1057,6 +1072,7 @@ def auto_font_scale(self): ------- bool Text is auto_font_scaled if True. + """ return self._auto_font_scale @@ -1068,6 +1084,7 @@ def auto_font_scale(self, flag): ---------- flag : bool Automatically scales the text font if True. + """ self._auto_font_scale = flag if flag: @@ -1085,6 +1102,7 @@ def dynamic_bbox(self): ------- bool Bounding box is dynamic if True. + """ return self._dynamic_bbox @@ -1096,52 +1114,54 @@ def dynamic_bbox(self, flag): ---------- flag : bool The text bounding box is dynamic if True. + """ self._dynamic_bbox = flag if flag: self.update_bounding_box() def update_alignment(self): - """Update Text Alignment. - """ + """Update Text Alignment.""" text_property = self.actor.GetTextProperty() updated_text_position = [0, 0] - if self.justification.lower() == 'left': + if self.justification.lower() == "left": text_property.SetJustificationToLeft() updated_text_position[0] = self.boundingbox[0] - elif self.justification.lower() == 'center': + elif self.justification.lower() == "center": text_property.SetJustificationToCentered() - updated_text_position[0] = self.boundingbox[0] + \ - (self.boundingbox[2]-self.boundingbox[0])//2 - elif self.justification.lower() == 'right': + updated_text_position[0] = ( + self.boundingbox[0] + (self.boundingbox[2] - self.boundingbox[0]) // 2 + ) + elif self.justification.lower() == "right": text_property.SetJustificationToRight() updated_text_position[0] = self.boundingbox[2] else: - msg = 'Text can only be justified left, right and center.' + msg = "Text can only be justified left, right and center." raise ValueError(msg) - if self.vertical_justification.lower() == 'bottom': + if self.vertical_justification.lower() == "bottom": text_property.SetVerticalJustificationToBottom() updated_text_position[1] = self.boundingbox[1] - elif self.vertical_justification.lower() == 'middle': + elif self.vertical_justification.lower() == "middle": text_property.SetVerticalJustificationToCentered() - updated_text_position[1] = self.boundingbox[1] + \ - (self.boundingbox[3]-self.boundingbox[1])//2 - elif self.vertical_justification.lower() == 'top': + updated_text_position[1] = ( + self.boundingbox[1] + (self.boundingbox[3] - self.boundingbox[1]) // 2 + ) + elif self.vertical_justification.lower() == "top": text_property.SetVerticalJustificationToTop() updated_text_position[1] = self.boundingbox[3] else: - msg = 'Vertical justification must be: bottom, middle or top.' + msg = "Vertical justification must be: bottom, middle or top." raise ValueError(msg) self.actor.SetPosition(updated_text_position) def cal_size_from_message(self): - "Calculate size of background according to the message it contains." + """Calculate size of background according to the message it contains.""" lines = self.message.split("\n") max_length = max(len(line) for line in lines) - return [max_length*self.font_size, len(lines)*self.font_size] + return [max_length * self.font_size, len(lines) * self.font_size] def update_bounding_box(self, size=None): """Update Text Bounding Box. @@ -1156,13 +1176,19 @@ def update_bounding_box(self, size=None): if size is None: size = self.cal_size_from_message() - self.boundingbox = [self.position[0], self.position[1], - self.position[0]+size[0], self.position[1]+size[1]] + self.boundingbox = [ + self.position[0], + self.position[1], + self.position[0] + size[0], + self.position[1] + size[1], + ] self.background.resize(size) if self.auto_font_scale: self.actor.SetPosition2( - self.boundingbox[2]-self.boundingbox[0], self.boundingbox[3]-self.boundingbox[1]) + self.boundingbox[2] - self.boundingbox[0], + self.boundingbox[3] - self.boundingbox[1], + ) else: self.update_alignment() @@ -1179,8 +1205,10 @@ def _set_position(self, position): self.background.position = position def _get_size(self): - bb_size = (self.boundingbox[2]-self.boundingbox[0], - self.boundingbox[3]-self.boundingbox[1]) + bb_size = ( + self.boundingbox[2] - self.boundingbox[0], + self.boundingbox[3] - self.boundingbox[1], + ) if self.dynamic_bbox or self.auto_font_scale or sum(bb_size): return bb_size return self.cal_size_from_message() @@ -1211,7 +1239,7 @@ def __init__(self, icon_fnames, position=(0, 0), size=(30, 30)): """ super(Button2D, self).__init__(position) - self.icon_extents = dict() + self.icon_extents = {} self.icons = self._build_icons(icon_fnames) self.icon_names = [icon[0] for icon in self.icons] self.current_icon_id = 0 @@ -1336,6 +1364,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ self.actor.SetPosition(*coords) diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 1bcefd54d..7bd17d997 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -1,39 +1,45 @@ """UI components module.""" __all__ = [ - 'TextBox2D', - 'LineSlider2D', - 'LineDoubleSlider2D', - 'RingSlider2D', - 'RangeSlider', - 'Checkbox', - 'Option', - 'RadioButton', - 'ComboBox2D', - 'ListBox2D', - 'ListBoxItem2D', - 'FileMenu2D', - 'DrawShape', - 'DrawPanel', - 'PlaybackPanel', - 'Card2D', - 'SpinBox' + "TextBox2D", + "LineSlider2D", + "LineDoubleSlider2D", + "RingSlider2D", + "RangeSlider", + "Checkbox", + "Option", + "RadioButton", + "ComboBox2D", + "ListBox2D", + "ListBoxItem2D", + "FileMenu2D", + "DrawShape", + "DrawPanel", + "PlaybackPanel", + "Card2D", + "SpinBox", ] -import os from collections import OrderedDict from numbers import Number +import os from string import printable -from PIL import UnidentifiedImageError, Image from urllib.request import urlopen +from PIL import Image, UnidentifiedImageError import numpy as np from fury.data import read_viz_icons from fury.lib import Command -from fury.ui.containers import Panel2D, ImageContainer2D +from fury.ui.containers import ImageContainer2D, Panel2D from fury.ui.core import UI, Button2D, Disk2D, Rectangle2D, TextBlock2D -from fury.ui.helpers import TWO_PI, cal_bounding_box_2d, clip_overflow, rotate_2d, wrap_overflow +from fury.ui.helpers import ( + TWO_PI, + cal_bounding_box_2d, + clip_overflow, + rotate_2d, + wrap_overflow, +) from fury.utils import set_polydata_vertices, update_actor, vertices_from_actor @@ -64,18 +70,19 @@ class TextBox2D(UI): Position of the caret in the text. init : bool Flag which says whether the textbox has just been initialized. + """ def __init__( self, width, height, - text='Enter Text', + text="Enter Text", position=(100, 10), color=(0, 0, 0), font_size=18, - font_family='Arial', - justification='left', + font_family="Arial", + justification="left", bold=False, italic=False, shadow=False, @@ -151,6 +158,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ self.text.add_to_scene(scene) @@ -200,12 +208,12 @@ def width_set_text(self, text): A multi line formatted text. """ - multi_line_text = '' + multi_line_text = "" for i, t in enumerate(text): multi_line_text += t if (i + 1) % self.width == 0: - multi_line_text += '\n' - return multi_line_text.rstrip('\n') + multi_line_text += "\n" + return multi_line_text.rstrip("\n") def handle_character(self, key, key_char): """Handle button events. @@ -215,18 +223,19 @@ def handle_character(self, key, key_char): Parameters ---------- character : str + """ - if key.lower() == 'return': + if key.lower() == "return": self.render_text(False) self.off_focus(self) return True - elif key_char != '' and key_char in printable: + elif key_char != "" and key_char in printable: self.add_character(key_char) - if key.lower() == 'backspace': + if key.lower() == "backspace": self.remove_character() - elif key.lower() == 'left': + elif key.lower() == "left": self.move_left() - elif key.lower() == 'right': + elif key.lower() == "right": self.move_right() self.render_text() @@ -268,10 +277,10 @@ def add_character(self, character): character : str """ - if len(character) > 1 and character.lower() != 'space': + if len(character) > 1 and character.lower() != "space": return - if character.lower() == 'space': - character = ' ' + if character.lower() == "space": + character = " " self.message = ( self.message[: self.caret_pos] + character + self.message[self.caret_pos :] ) @@ -322,7 +331,7 @@ def showable_text(self, show_caret): """ if show_caret: ret_text = ( - self.message[: self.caret_pos] + '_' + self.message[self.caret_pos :] + self.message[: self.caret_pos] + "_" + self.message[self.caret_pos :] ) else: ret_text = self.message @@ -339,14 +348,14 @@ def render_text(self, show_caret=True): """ text = self.showable_text(show_caret) - if text == '': - text = 'Enter Text' + if text == "": + text = "Enter Text" self.text.message = self.width_set_text(text) def edit_mode(self): """Turn on edit mode.""" if self.init: - self.message = '' + self.message = "" self.init = False self.caret_pos = 0 self.render_text() @@ -360,6 +369,7 @@ def left_button_press(self, i_ren, _obj, _textbox_object): obj: :class:`vtkActor` The picked actor _textbox_object: :class:`TextBox2D` + """ i_ren.add_active_prop(self.text.actor) self.edit_mode() @@ -409,6 +419,7 @@ class LineSlider2D(UI): Color of the handle when in unpressed state. active_color : (float, float, float) Color of the handle when it is pressed. + """ def __init__( @@ -423,10 +434,10 @@ def __init__( outer_radius=10, handle_side=20, font_size=16, - orientation='horizontal', - text_alignment='', - text_template='{value:.1f} ({ratio:.0%})', - shape='disk', + orientation="horizontal", + text_alignment="", + text_template="{value:.1f} ({ratio:.0%})", + shape="disk", ): """Init this UI element. @@ -470,24 +481,24 @@ def __init__( self.shape = shape self.orientation = orientation.lower().strip() self.align_dict = { - 'horizontal': ['top', 'bottom'], - 'vertical': ['left', 'right'], + "horizontal": ["top", "bottom"], + "vertical": ["left", "right"], } self.default_color = (1, 1, 1) self.active_color = (0, 0, 1) self.alignment = text_alignment.lower() super(LineSlider2D, self).__init__() - if self.orientation == 'horizontal': - self.alignment = 'bottom' if not self.alignment else self.alignment + if self.orientation == "horizontal": + self.alignment = "bottom" if not self.alignment else self.alignment self.track.width = length self.track.height = line_width - elif self.orientation == 'vertical': - self.alignment = 'left' if not self.alignment else self.alignment + elif self.orientation == "vertical": + self.alignment = "left" if not self.alignment else self.alignment self.track.width = line_width self.track.height = length else: - raise ValueError('Unknown orientation') + raise ValueError("Unknown orientation") if self.alignment not in self.align_dict[self.orientation]: raise ValueError( @@ -496,10 +507,10 @@ def __init__( ) ) - if shape == 'disk': + if shape == "disk": self.handle.inner_radius = inner_radius self.handle.outer_radius = outer_radius - elif shape == 'square': + elif shape == "square": self.handle.width = handle_side self.handle.height = handle_side self.center = center @@ -528,14 +539,14 @@ def _setup(self): self.track.color = (1, 0, 0) # Slider's handle - if self.shape == 'disk': + if self.shape == "disk": self.handle = Disk2D(outer_radius=1) - elif self.shape == 'square': + elif self.shape == "square": self.handle = Rectangle2D(size=(1, 1)) self.handle.color = self.default_color # Slider Text - self.text = TextBlock2D(justification='center', vertical_justification='top') + self.text = TextBlock2D(justification="center", vertical_justification="top") # Add default events listener for this UI component. self.track.on_left_mouse_button_pressed = self.track_click_callback @@ -564,7 +575,7 @@ def _get_size(self): # Consider the handle's size when computing the slider's size. width = None height = None - if self.orientation == 'horizontal': + if self.orientation == "horizontal": width = self.track.width + self.handle.size[0] height = max(self.track.height, self.handle.size[1]) else: @@ -580,10 +591,11 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ # Offset the slider line by the handle's radius. track_position = coords + self.handle.size / 2.0 - if self.orientation == 'horizontal': + if self.orientation == "horizontal": # Offset the slider line height by half the slider line width. track_position[1] -= self.track.size[1] / 2.0 else: @@ -594,14 +606,14 @@ def _set_position(self, coords): self.handle.position = self.handle.position.astype(float) self.handle.position += coords - self.position # Position the text below the handle. - if self.orientation == 'horizontal': - align = 35 if self.alignment == 'top' else -10 + if self.orientation == "horizontal": + align = 35 if self.alignment == "top" else -10 self.text.position = ( self.handle.center[0], self.handle.position[1] + align, ) else: - align = 70 if self.alignment == 'right' else -35 + align = 70 if self.alignment == "right" else -35 self.text.position = ( self.handle.position[0] + align, self.handle.center[1] + 2, @@ -630,10 +642,10 @@ def set_position(self, position): ---------- position : (float, float) The absolute position of the disk (x, y). - """ + """ # Move slider disk. - if self.orientation == 'horizontal': + if self.orientation == "horizontal": x_position = position[0] x_position = max(x_position, self.left_x_position) x_position = min(x_position, self.right_x_position) @@ -677,17 +689,17 @@ def update(self): disk_position_x = None disk_position_y = None - if self.orientation == 'horizontal': + if self.orientation == "horizontal": length = float(self.right_x_position - self.left_x_position) length = np.round(length, decimals=6) if length != self.track.width: - raise ValueError('Disk position outside the slider line') + raise ValueError("Disk position outside the slider line") disk_position_x = self.handle.center[0] self._ratio = (disk_position_x - self.left_x_position) / length else: length = float(self.top_y_position - self.bottom_y_position) if length != self.track.height: - raise ValueError('Disk position outside the slider line') + raise ValueError("Disk position outside the slider line") disk_position_y = self.handle.center[1] self._ratio = (disk_position_y - self.bottom_y_position) / length @@ -700,7 +712,7 @@ def update(self): self.text.message = text # Move the text below the slider's handle. - if self.orientation == 'horizontal': + if self.orientation == "horizontal": self.text.position = (disk_position_x, self.text.position[1]) else: self.text.position = (self.text.position[0], disk_position_y) @@ -784,6 +796,7 @@ class LineDoubleSlider2D(UI): Color of the handles when in unpressed state. active_color : (float, float, float) Color of the handles when they are pressed. + """ def __init__( @@ -798,9 +811,9 @@ def __init__( min_value=0, max_value=100, font_size=16, - text_template='{value:.1f}', - orientation='horizontal', - shape='disk', + text_template="{value:.1f}", + orientation="horizontal", + shape="disk", ): """Init this UI element. @@ -844,22 +857,22 @@ def __init__( self.orientation = orientation.lower() super(LineDoubleSlider2D, self).__init__() - if self.orientation == 'horizontal': + if self.orientation == "horizontal": self.track.width = length self.track.height = line_width - elif self.orientation == 'vertical': + elif self.orientation == "vertical": self.track.width = line_width self.track.height = length else: - raise ValueError('Unknown orientation') + raise ValueError("Unknown orientation") self.center = center - if shape == 'disk': + if shape == "disk": self.handles[0].inner_radius = inner_radius self.handles[0].outer_radius = outer_radius self.handles[1].inner_radius = inner_radius self.handles[1].outer_radius = outer_radius - elif shape == 'square': + elif shape == "square": self.handles[0].width = handle_side self.handles[0].height = handle_side self.handles[1].width = handle_side @@ -897,10 +910,10 @@ def _setup(self): # Handles self.handles = [] - if self.shape == 'disk': + if self.shape == "disk": self.handles.append(Disk2D(outer_radius=1)) self.handles.append(Disk2D(outer_radius=1)) - elif self.shape == 'square': + elif self.shape == "square": self.handles.append(Rectangle2D(size=(1, 1))) self.handles.append(Rectangle2D(size=(1, 1))) self.handles[0].color = self.default_color @@ -908,8 +921,8 @@ def _setup(self): # Slider Text self.text = [ - TextBlock2D(justification='center', vertical_justification='top'), - TextBlock2D(justification='center', vertical_justification='top'), + TextBlock2D(justification="center", vertical_justification="top"), + TextBlock2D(justification="center", vertical_justification="top"), ] # Add default events listener for this UI component. @@ -947,12 +960,12 @@ def _get_size(self): # Consider the handle's size when computing the slider's size. width = None height = None - if self.orientation == 'horizontal': - width = self.track.width + 2 * self.handles[0].size[0] + if self.orientation == "horizontal": + width = self.track.width + self.handles[0].size[0] height = max(self.track.height, self.handles[0].size[1]) else: width = max(self.track.width, self.handles[0].size[0]) - height = self.track.height + 2 * self.handles[0].size[1] + height = self.track.height + self.handles[0].size[1] return np.array([width, height]) @@ -966,13 +979,14 @@ def _set_position(self, coords): """ # Offset the slider line by the handle's radius. - track_position = coords + self.handles[0].size / 2.0 - if self.orientation == 'horizontal': + track_position = coords + if self.orientation == "horizontal": # Offset the slider line height by half the slider line width. track_position[1] -= self.track.size[1] / 2.0 else: # Offset the slider line width by half the slider line height. track_position[0] -= self.track.size[0] / 2.0 + self.track.position = track_position self.handles[0].position = self.handles[0].position.astype(float) @@ -981,15 +995,15 @@ def _set_position(self, coords): self.handles[0].position += coords - self.position self.handles[1].position += coords - self.position - if self.orientation == 'horizontal': + if self.orientation == "horizontal": # Position the text below the handles. self.text[0].position = ( self.handles[0].center[0], - self.handles[0].position[1] - 20, + self.handles[0].position[1] - 10, ) self.text[1].position = ( self.handles[1].center[0], - self.handles[1].position[1] - 20, + self.handles[1].position[1] - 10, ) else: # Position the text to the left of the handles. @@ -1035,8 +1049,9 @@ def ratio_to_coord(self, ratio): Parameters ---------- ratio : float + """ - if self.orientation == 'horizontal': + if self.orientation == "horizontal": return self.left_x_position + ratio * self.track.width return self.bottom_y_position + ratio * self.track.height @@ -1046,8 +1061,9 @@ def coord_to_ratio(self, coord): Parameters ---------- coord : float + """ - if self.orientation == 'horizontal': + if self.orientation == "horizontal": return (coord - self.left_x_position) / float(self.track.width) return (coord - self.bottom_y_position) / float(self.track.height) @@ -1073,7 +1089,7 @@ def set_position(self, position, disk_number): The index of disk being moved. """ - if self.orientation == 'horizontal': + if self.orientation == "horizontal": x_position = position[0] if disk_number == 0 and x_position >= self.handles[1].center[0]: @@ -1174,6 +1190,7 @@ def right_disk_value(self, right_disk_value): ---------- right_disk_value : float New value for the right disk. + """ self.right_disk_ratio = self.value_to_ratio(right_disk_value) self.on_value_changed(self) @@ -1210,6 +1227,7 @@ def top_disk_ratio(self, top_disk_ratio): ---------- top_disk_ratio : float New ratio for the top disk. + """ position_x = self.ratio_to_coord(top_disk_ratio) position_y = self.ratio_to_coord(top_disk_ratio) @@ -1228,6 +1246,7 @@ def left_disk_ratio(self, left_disk_ratio): ---------- left_disk_ratio : float New ratio for the left disk. + """ position_x = self.ratio_to_coord(left_disk_ratio) position_y = self.ratio_to_coord(left_disk_ratio) @@ -1246,6 +1265,7 @@ def right_disk_ratio(self, right_disk_ratio): ---------- right_disk_ratio : float New ratio for the right disk. + """ position_x = self.ratio_to_coord(right_disk_ratio) position_y = self.ratio_to_coord(right_disk_ratio) @@ -1258,6 +1278,7 @@ def format_text(self, disk_number): ---------- disk_number : int Index of the disk. + """ if callable(self.text_template): return self.text_template(self) @@ -1274,7 +1295,7 @@ def update(self, disk_number): """ # Compute the ratio determined by the position of the slider disk. - if self.orientation == 'horizontal': + if self.orientation == "horizontal": self._ratio[disk_number] = self.coord_to_ratio( self.handles[disk_number].center[0] ) @@ -1290,7 +1311,7 @@ def update(self, disk_number): text = self.format_text(disk_number) self.text[disk_number].message = text - if self.orientation == 'horizontal': + if self.orientation == "horizontal": self.text[disk_number].position = ( self.handles[disk_number].center[0], self.text[disk_number].position[1], @@ -1364,6 +1385,7 @@ class RingSlider2D(UI): Color of the handle when in unpressed state. active_color : (float, float, float) Color of the handle when it is pressed. + """ def __init__( @@ -1377,7 +1399,7 @@ def __init__( handle_inner_radius=0, handle_outer_radius=10, font_size=16, - text_template='{ratio:.0%}', + text_template="{ratio:.0%}", ): """Init this UI element. @@ -1406,6 +1428,7 @@ def __init__( replacement fields: `{value:}`, `{ratio:}`, `{angle:}`. If callable, this instance of `:class:RingSlider2D` will be passed as argument to the text template function. + """ self.default_color = (1, 1, 1) self.active_color = (0, 0, 1) @@ -1449,8 +1472,7 @@ def _setup(self): self.handle.color = self.default_color # Slider Text - self.text = TextBlock2D(justification='center', - vertical_justification='middle') + self.text = TextBlock2D(justification="center", vertical_justification="middle") # Add default events listener for this UI component. self.track.on_left_mouse_button_pressed = self.track_click_callback @@ -1469,6 +1491,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ self.track.add_to_scene(scene) self.handle.add_to_scene(scene) @@ -1484,6 +1507,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ self.track.position = coords + self.handle.size / 2.0 self.handle.position += coords - self.position @@ -1537,7 +1561,6 @@ def format_text(self): def update(self): """Update the slider.""" - # Compute the ratio determined by the position of the slider disk. self._ratio = self.angle / TWO_PI @@ -1624,7 +1647,6 @@ def handle_release_callback(self, i_ren, _obj, _slider): class RangeSlider(UI): - """A set of a LineSlider2D and a LineDoubleSlider2D. The double slider is used to set the min and max value for the LineSlider2D @@ -1639,6 +1661,7 @@ class RangeSlider(UI): The line slider which sets the min and max values value_slider : :class:`LineSlider2D` The line slider which sets the value + """ def __init__( @@ -1654,9 +1677,9 @@ def __init__( max_value=100, font_size=16, range_precision=1, - orientation='horizontal', + orientation="horizontal", value_precision=2, - shape='disk', + shape="disk", ): """Init this class instance. @@ -1704,8 +1727,8 @@ def __init__( self.shape = shape self.orientation = orientation.lower() - self.range_slider_text_template = '{value:.' + str(range_precision) + 'f}' - self.value_slider_text_template = '{value:.' + str(value_precision) + 'f}' + self.range_slider_text_template = "{value:." + str(range_precision) + "f}" + self.value_slider_text_template = "{value:." + str(value_precision) + "f}" self.range_slider_center = range_slider_center self.value_slider_center = value_slider_center @@ -1763,6 +1786,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ self.range_slider.add_to_scene(scene) self.value_slider.add_to_scene(scene) @@ -1800,7 +1824,6 @@ def range_slider_handle_move_callback(self, i_ren, obj, _slider): class Option(UI): - """A set of a Button2D and a TextBlock2D to act as a single option for checkboxes and radio buttons. Clicking the button toggles its checked/unchecked status. @@ -1844,15 +1867,15 @@ def _setup(self): """Setup this UI component.""" # Option's button self.button_icons = [] - self.button_icons.append(('unchecked', read_viz_icons(fname='stop2.png'))) - self.button_icons.append(('checked', read_viz_icons(fname='checkmark.png'))) + self.button_icons.append(("unchecked", read_viz_icons(fname="stop2.png"))) + self.button_icons.append(("checked", read_viz_icons(fname="checkmark.png"))) self.button = Button2D(icon_fnames=self.button_icons, size=self.button_size) self.text = TextBlock2D(text=self.label, font_size=self.font_size) # Display initial state if self.checked: - self.button.set_icon_by_name('checked') + self.button.set_icon_by_name("checked") # Add callbacks self.button.on_left_mouse_button_clicked = self.toggle @@ -1887,7 +1910,7 @@ def _set_position(self, coords): Absolute pixel coordinates (x, y). """ - num_newlines = self.label.count('\n') + num_newlines = self.label.count("\n") self.button.position = coords + (0, num_newlines * self.font_size * 0.5) offset = (self.button.size[0] + self.button_label_gap, 0) self.text.position = coords + offset @@ -1903,15 +1926,14 @@ def toggle(self, i_ren, _obj, _element): def select(self): self.checked = True - self.button.set_icon_by_name('checked') + self.button.set_icon_by_name("checked") def deselect(self): self.checked = False - self.button.set_icon_by_name('unchecked') + self.button.set_icon_by_name("unchecked") class Checkbox(UI): - """A 2D set of :class:'Option' objects. Multiple options can be selected. @@ -1923,6 +1945,7 @@ class Checkbox(UI): Dictionary of all the options in the checkbox set. padding : float Distance between two adjacent options + """ def __init__( @@ -1931,7 +1954,7 @@ def __init__( checked_labels=(), padding=1, font_size=18, - font_family='Arial', + font_family="Arial", position=(0, 0), ): """Init this class instance. @@ -1951,8 +1974,8 @@ def __init__( position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the button of the first option. - """ + """ self.labels = list(reversed(list(labels))) self._padding = padding self._font_size = font_size @@ -1966,7 +1989,6 @@ def _setup(self): self.options = OrderedDict() button_y = self.position[1] for label in self.labels: - option = Option( label=label, font_size=self.font_size, @@ -1977,7 +1999,7 @@ def _setup(self): line_spacing = option.text.actor.GetTextProperty().GetLineSpacing() button_y = ( button_y - + self.font_size * (label.count('\n') + 1) * (line_spacing + 0.1) + + self.font_size * (label.count("\n") + 1) * (line_spacing + 0.1) + self.padding ) self.options[label] = option @@ -2014,6 +2036,7 @@ def _handle_option_change(self, option): Parameters ---------- option : :class:`Option` + """ if option.checked: self.checked_labels.append(option.label) @@ -2038,7 +2061,7 @@ def _set_position(self, coords): button_y = ( button_y + self.font_size - * (self.labels[option_no].count('\n') + 1) + * (self.labels[option_no].count("\n") + 1) * (line_spacing + 0.1) + self.padding ) @@ -2075,7 +2098,7 @@ def __init__( checked_labels, padding=1, font_size=18, - font_family='Arial', + font_family="Arial", position=(0, 0), ): """Init class instance. @@ -2095,9 +2118,10 @@ def __init__( position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the button of the first option. + """ if len(checked_labels) > 1: - err_msg = 'Only one option can be pre-selected for radio buttons.' + err_msg = "Only one option can be pre-selected for radio buttons." raise ValueError(err_msg) super(RadioButton, self).__init__( @@ -2129,14 +2153,15 @@ class ComboBox2D(UI): Button to show or hide menu. drop_down_menu: :class: 'ListBox2D' Container for item list. + """ def __init__( self, - items=[], + items=None, position=(0, 0), size=(300, 200), - placeholder='Choose selection...', + placeholder="Choose selection...", draggable=True, selection_text_color=(0, 0, 0), selection_bg_color=(1, 1, 1), @@ -2185,7 +2210,11 @@ def __init__( The font size of selected text in pixels. line_spacing: float Distance between drop down menu's items in pixels. + """ + if items is None: + items = [] + self.items = items.copy() self.font_size = font_size self.reverse_scrolling = reverse_scrolling @@ -2205,13 +2234,13 @@ def __init__( self.menu_opacity = menu_opacity # Define subcomponent sizes. - self.text_block_size = (int(0.8 * size[0]), int(0.3 * size[1])) - self.drop_menu_size = (size[0], int(0.7 * size[1])) - self.drop_button_size = (int(0.2 * size[0]), int(0.3 * size[1])) + self.text_block_size = (int(0.9 * size[0]), int(0.1 * size[1])) + self.drop_menu_size = (int(0.9 * size[0]), int(0.7 * size[1])) + self.drop_button_size = (int(0.1 * size[0]), int(0.1 * size[1])) self._icon_files = [ - ('left', read_viz_icons(fname='circle-left.png')), - ('down', read_viz_icons(fname='circle-down.png')), + ("left", read_viz_icons(fname="circle-left.png")), + ("down", read_viz_icons(fname="circle-down.png")), ] super(ComboBox2D, self).__init__() @@ -2291,19 +2320,17 @@ def _setup(self): for slot in self.drop_down_menu.slots: slot.add_callback( slot.textblock.actor, - 'LeftButtonPressEvent', + "LeftButtonPressEvent", self.select_option_callback, ) slot.add_callback( slot.background.actor, - 'LeftButtonPressEvent', + "LeftButtonPressEvent", self.select_option_callback, ) - self.drop_down_button.on_left_mouse_button_clicked = ( - self.menu_toggle_callback - ) + self.drop_down_button.on_left_mouse_button_clicked = self.menu_toggle_callback # Offer some standard hooks to the user. self.on_change = lambda ui: None @@ -2319,12 +2346,13 @@ def resize(self, size): ---------- size : (int, int) ComboBox size(width, height) in pixels. + """ self.panel.resize(size) - self.text_block_size = (int(0.8 * size[0]), int(0.3 * size[1])) - self.drop_menu_size = (size[0], int(0.7 * size[1])) - self.drop_button_size = (int(0.2 * size[0]), int(0.3 * size[1])) + self.text_block_size = (int(0.9 * size[0]), int(0.1 * size[1])) + self.drop_menu_size = (int(0.9 * size[0]), int(0.7 * size[1])) + self.drop_button_size = (int(0.1 * size[0]), int(0.1 * size[1])) self.panel.update_element(self.selection_box, (0.001, 0.7)) self.panel.update_element(self.drop_down_button, (0.8, 0.7)) @@ -2344,6 +2372,10 @@ def _set_position(self, coords): """ self.panel.position = coords + self.panel.position = ( + self.panel.position[0], + self.panel.position[1] - self.drop_menu_size[1], + ) def _add_to_scene(self, scene): """Add all subcomponents or VTK props that compose this UI component. @@ -2388,7 +2420,7 @@ def append_item(self, *items): elif isinstance(item, (str, Number)): self.items.append(str(item)) else: - raise TypeError('Invalid item instance {}'.format(type(item))) + raise TypeError("Invalid item instance {}".format(type(item))) self.drop_down_menu.update_scrollbar() if not self._menu_visibility: @@ -2405,7 +2437,6 @@ def select_option_callback(self, i_ren, _obj, listboxitem): listboxitem: :class:`ListBoxItem2D` """ - # Set the Text of TextBlock2D to the text of listboxitem self._selection = listboxitem.element self._selection_ID = self.items.index(self._selection) @@ -2461,6 +2492,7 @@ class ListBox2D(UI): ---------- on_change: function Callback function for when the selected items have changed. + """ def __init__( @@ -2505,6 +2537,7 @@ def __init__( scroll_bar_active_color : tuple of 3 floats scroll_bar_inactive_color : tuple of 3 floats background_opacity : float + """ self.view_offset = 0 self.slots = [] @@ -2598,8 +2631,8 @@ def _setup(self): self.scroll_bar.on_left_mouse_button_dragged = self.scroll_drag_callback # Handle mouse wheel events on the panel. - up_event = 'MouseWheelForwardEvent' - down_event = 'MouseWheelBackwardEvent' + up_event = "MouseWheelForwardEvent" + down_event = "MouseWheelBackwardEvent" if self.reverse_scrolling: up_event, down_event = down_event, up_event # Swap events @@ -2896,6 +2929,7 @@ def __init__( unselected_color : tuple of 3 floats selected_color : tuple of 3 floats background_opacity : float + """ super(ListBoxItem2D, self).__init__() self._element = None @@ -2918,15 +2952,15 @@ def _setup(self): """ self.background = Rectangle2D() self.textblock = TextBlock2D( - justification='left', vertical_justification='middle' + justification="left", vertical_justification="middle" ) # Add default events listener for this UI component. self.add_callback( - self.textblock.actor, 'LeftButtonPressEvent', self.left_button_clicked + self.textblock.actor, "LeftButtonPressEvent", self.left_button_clicked ) self.add_callback( - self.background.actor, 'LeftButtonPressEvent', self.left_button_clicked + self.background.actor, "LeftButtonPressEvent", self.left_button_clicked ) def _get_actors(self): @@ -2939,6 +2973,7 @@ def _add_to_scene(self, scene): Parameters ---------- scene : scene + """ self.background.add_to_scene(scene) self.textblock.add_to_scene(scene) @@ -2953,6 +2988,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ self.textblock.position = coords # Center background underneath the text. @@ -2979,7 +3015,7 @@ def element(self): @element.setter def element(self, element): self._element = element - self.textblock.message = '' if self._element is None else str(element) + self.textblock.message = "" if self._element is None else str(element) def left_button_clicked(self, i_ren, _obj, _list_box_item): """Handle left click for this UI element. @@ -3048,12 +3084,13 @@ def __init__( The font size in pixels. line_spacing: float Distance between listbox's items in pixels. + """ self.font_size = font_size self.multiselection = multiselection self.reverse_scrolling = reverse_scrolling self.line_spacing = line_spacing - self.extensions = extensions or ['*'] + self.extensions = extensions or ["*"] self.current_directory = directory_path self.menu_size = size self.directory_contents = [] @@ -3080,12 +3117,12 @@ def _setup(self): ) self.add_callback( - self.listbox.scroll_bar.actor, 'MouseMoveEvent', self.scroll_callback + self.listbox.scroll_bar.actor, "MouseMoveEvent", self.scroll_callback ) # Handle mouse wheel events on the panel. - up_event = 'MouseWheelForwardEvent' - down_event = 'MouseWheelBackwardEvent' + up_event = "MouseWheelForwardEvent" + down_event = "MouseWheelBackwardEvent" if self.reverse_scrolling: up_event, down_event = down_event, up_event # Swap events @@ -3104,12 +3141,12 @@ def _setup(self): self.add_callback(slot.textblock.actor, down_event, self.scroll_callback) slot.add_callback( slot.textblock.actor, - 'LeftButtonPressEvent', + "LeftButtonPressEvent", self.directory_click_callback, ) slot.add_callback( slot.background.actor, - 'LeftButtonPressEvent', + "LeftButtonPressEvent", self.directory_click_callback, ) @@ -3157,11 +3194,11 @@ def get_all_file_names(self): directory_names = self.get_directory_names() for directory_name in directory_names: - all_file_names.append((directory_name, 'directory')) + all_file_names.append((directory_name, "directory")) file_names = self.get_file_names() for file_name in file_names: - all_file_names.append((file_name, 'file')) + all_file_names.append((file_name, "file")) return all_file_names @@ -3176,11 +3213,11 @@ def get_directory_names(self): """ # A list of directory names in the current directory directory_names = [] - for (_, dirnames, _) in os.walk(self.current_directory): + for _, dirnames, _ in os.walk(self.current_directory): directory_names += dirnames break directory_names.sort(key=lambda s: s.lower()) - directory_names.insert(0, '../') + directory_names.insert(0, "../") return directory_names def get_file_names(self): @@ -3193,16 +3230,18 @@ def get_file_names(self): """ # A list of file names with extension in the current directory - for (_, _, files) in os.walk(self.current_directory): + files = [] + for _, _, f in os.walk(self.current_directory): + files += f break file_names = [] - if '*' in self.extensions or '' in self.extensions: + if "*" in self.extensions or "" in self.extensions: file_names = files else: for ext in self.extensions: for file in files: - if file.endswith('.' + ext): + if file.endswith("." + ext): file_names.append(file) file_names.sort(key=lambda s: s.lower()) return file_names @@ -3215,9 +3254,9 @@ def set_slot_colors(self): list_idx = min( self.listbox.view_offset + idx, len(self.directory_contents) - 1 ) - if self.directory_contents[list_idx][1] == 'directory': + if self.directory_contents[list_idx][1] == "directory": slot.textblock.color = (0, 0.6, 0) - elif self.directory_contents[list_idx][1] == 'file': + elif self.directory_contents[list_idx][1] == "file": slot.textblock.color = (0, 0, 0.7) def scroll_callback(self, i_ren, _obj, _filemenu_item): @@ -3246,7 +3285,7 @@ def directory_click_callback(self, i_ren, _obj, listboxitem): listboxitem: :class:`ListBoxItem2D` """ - if (listboxitem.element, 'directory') in self.directory_contents: + if (listboxitem.element, "directory") in self.directory_contents: new_directory_path = os.path.join( self.current_directory, listboxitem.element ) @@ -3278,6 +3317,7 @@ def __init__(self, shape_type, drawpanel=None, position=(0, 0)): Reference to the main canvas on which it is drawn. position : (float, float), optional (x, y) in pixels. + """ self.shape = None self.shape_type = shape_type.lower() @@ -3292,14 +3332,14 @@ def _setup(self): Create a Shape. """ - if self.shape_type == 'line': + if self.shape_type == "line": self.shape = Rectangle2D(size=(3, 3)) - elif self.shape_type == 'quad': + elif self.shape_type == "quad": self.shape = Rectangle2D(size=(3, 3)) - elif self.shape_type == 'circle': + elif self.shape_type == "circle": self.shape = Disk2D(outer_radius=2) else: - raise IOError('Unknown shape type: {}.'.format(self.shape_type)) + raise IOError("Unknown shape type: {}.".format(self.shape_type)) self.shape.on_left_mouse_button_pressed = self.left_button_pressed self.shape.on_left_mouse_button_dragged = self.left_button_dragged @@ -3330,8 +3370,9 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ - if self.shape_type == 'circle': + if self.shape_type == "circle": self.shape.center = coords else: self.shape.position = coords @@ -3343,9 +3384,10 @@ def update_shape_position(self, center_position): ---------- center_position: (float, float) Absolute pixel coordinates (x, y). + """ new_center = self.clamp_position(center=center_position) - self.drawpanel.canvas.update_element(self, new_center, 'center') + self.drawpanel.canvas.update_element(self, new_center, "center") self.cal_bounding_box() @property @@ -3391,8 +3433,9 @@ def rotate(self, angle): ---------- angle: float Value by which the vertices are rotated in radian. + """ - if self.shape_type == 'circle': + if self.shape_type == "circle": return points_arr = vertices_from_actor(self.shape.actor) new_points_arr = rotate_2d(points_arr, angle) @@ -3425,6 +3468,7 @@ def clamp_position(self, center=None): ------- new_center: ndarray(int) New center for the shape. + """ center = self.center if center is None else center new_center = np.clip( @@ -3436,15 +3480,15 @@ def clamp_position(self, center=None): def resize(self, size): """Resize the UI.""" - if self.shape_type == 'line': + if self.shape_type == "line": hyp = np.hypot(size[0], size[1]) self.shape.resize((hyp, 3)) self.rotate(angle=np.arctan2(size[1], size[0])) - elif self.shape_type == 'quad': + elif self.shape_type == "quad": self.shape.resize(size) - elif self.shape_type == 'circle': + elif self.shape_type == "circle": hyp = np.hypot(size[0], size[1]) if self.max_size and hyp > self.max_size: hyp = self.max_size @@ -3459,14 +3503,14 @@ def remove(self): def left_button_pressed(self, i_ren, _obj, shape): mode = self.drawpanel.current_mode - if mode == 'selection': + if mode == "selection": self.drawpanel.update_shape_selection(self) click_pos = np.array(i_ren.event.position) self._drag_offset = click_pos - self.center self.drawpanel.show_rotation_slider() i_ren.event.abort() - elif mode == 'delete': + elif mode == "delete": self.remove() else: self.drawpanel.left_button_pressed(i_ren, _obj, self.drawpanel) @@ -3505,6 +3549,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): (x, y) in pixels. is_draggable : bool, optional Whether the background canvas will be draggble or not. + """ self.panel_size = size super(DrawPanel, self).__init__(position) @@ -3512,7 +3557,7 @@ def __init__(self, size=(400, 400), position=(0, 0), is_draggable=False): self.current_mode = None if is_draggable: - self.current_mode = 'selection' + self.current_mode = "selection" self.shape_list = [] self.current_shape = None @@ -3530,11 +3575,11 @@ def _setup(self): # Convert mode_data into a private variable and make it read-only # Then add the ability to insert user-defined mode mode_data = { - 'selection': ['selection.png', 'selection-pressed.png'], - 'line': ['line.png', 'line-pressed.png'], - 'quad': ['quad.png', 'quad-pressed.png'], - 'circle': ['circle.png', 'circle-pressed.png'], - 'delete': ['delete.png', 'delete-pressed.png'], + "selection": ["selection.png", "selection-pressed.png"], + "line": ["line.png", "line-pressed.png"], + "quad": ["quad.png", "quad-pressed.png"], + "circle": ["circle.png", "circle-pressed.png"], + "delete": ["delete.png", "delete-pressed.png"], } padding = 5 @@ -3546,9 +3591,9 @@ def _setup(self): for mode, fname in mode_data.items(): icon_files = [] - icon_files.append((mode, read_viz_icons(style='new_icons', fname=fname[0]))) + icon_files.append((mode, read_viz_icons(style="new_icons", fname=fname[0]))) icon_files.append( - (mode + '-pressed', read_viz_icons(style='new_icons', fname=fname[1])) + (mode + "-pressed", read_viz_icons(style="new_icons", fname=fname[1])) ) btn = Button2D(icon_fnames=icon_files) @@ -3564,12 +3609,13 @@ def mode_selector(i_ren, _obj, btn): self.canvas.add_element(self.mode_panel, (0, -mode_panel_size[1])) self.mode_text = TextBlock2D( - text='Select appropriate drawing mode using below icon' + text="Select appropriate drawing mode using below icon" ) self.canvas.add_element(self.mode_text, (0.0, 1.0)) - self.rotation_slider = RingSlider2D(initial_value=0, - text_template="{angle:5.1f}°") + self.rotation_slider = RingSlider2D( + initial_value=0, text_template="{angle:5.1f}°" + ) self.rotation_slider.set_visibility(False) def rotate_shape(slider): @@ -3581,7 +3627,8 @@ def rotate_shape(slider): self.current_shape.rotate(np.deg2rad(rotation_angle)) self.current_shape.rotation = slider.value self.current_shape.update_shape_position( - current_center - self.canvas.position) + current_center - self.canvas.position + ) self.rotation_slider.on_moving_slider = rotate_shape @@ -3610,11 +3657,13 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ self.canvas.position = coords + [0, self.mode_panel.size[1]] - slider_position = self.canvas.position + \ - [self.canvas.size[0] - self.rotation_slider.size[0]/2, - self.rotation_slider.size[1]/2] + slider_position = self.canvas.position + [ + self.canvas.size[0] - self.rotation_slider.size[0] / 2, + self.rotation_slider.size[1] / 2, + ] self.rotation_slider.center = slider_position def resize(self, size): @@ -3630,7 +3679,7 @@ def current_mode(self, mode): self.update_button_icons(mode) self._current_mode = mode if mode is not None: - self.mode_text.message = f'Mode: {mode}' + self.mode_text.message = f"Mode: {mode}" def cal_min_boundary_distance(self, position): """Calculate minimum distance between the current position and canvas boundary. @@ -3644,6 +3693,7 @@ def cal_min_boundary_distance(self, position): ------- float Minimum distance from the boundary. + """ distance_list = [] # calculate distance from element to left and lower boundary @@ -3662,11 +3712,12 @@ def draw_shape(self, shape_type, current_position): Type of shape - line, quad, circle. current_position: (float,float) Lower left corner position for the shape. + """ shape = DrawShape( shape_type=shape_type, drawpanel=self, position=current_position ) - if shape_type == 'circle': + if shape_type == "circle": shape.max_size = self.cal_min_boundary_distance(current_position) self.shape_list.append(shape) self._scene.add(shape) @@ -3680,6 +3731,7 @@ def resize_shape(self, current_position): ---------- current_position: (float,float) Lower left corner position for the shape. + """ self.current_shape = self.shape_list[-1] size = current_position - self.current_shape.position @@ -3693,8 +3745,7 @@ def update_shape_selection(self, selected_shape): shape.is_selected = False def show_rotation_slider(self): - """Display the RingSlider2D to allow rotation of shape from the center. - """ + """Display the RingSlider2D to allow rotation of shape from the center.""" self._scene.rm(*self.rotation_slider.actors) self.rotation_slider.add_to_scene(self._scene) self.rotation_slider.set_visibility(True) @@ -3706,6 +3757,7 @@ def update_button_icons(self, current_mode): ---------- current_mode: string Current mode of the UI. + """ for btn in self.mode_panel._elements[1:]: if btn.icon_names[0] == current_mode: @@ -3725,6 +3777,7 @@ def clamp_mouse_position(self, mouse_position): ------- list(float) New clipped position. + """ return np.clip( mouse_position, @@ -3733,11 +3786,11 @@ def clamp_mouse_position(self, mouse_position): ) def handle_mouse_click(self, position): - if self.current_mode == 'selection': + if self.current_mode == "selection": if self.is_draggable: self._drag_offset = position - self.position self.current_shape.is_selected = False - if self.current_mode in ['line', 'quad', 'circle']: + if self.current_mode in ["line", "quad", "circle"]: self.draw_shape(self.current_mode, position) def left_button_pressed(self, i_ren, _obj, element): @@ -3745,11 +3798,11 @@ def left_button_pressed(self, i_ren, _obj, element): i_ren.force_render() def handle_mouse_drag(self, position): - if self.is_draggable and self.current_mode == 'selection': + if self.is_draggable and self.current_mode == "selection": if self._drag_offset is not None: new_position = position - self._drag_offset self.position = new_position - if self.current_mode in ['line', 'quad', 'circle']: + if self.current_mode in ["line", "quad", "circle"]: self.resize_shape(position) def left_button_dragged(self, i_ren, _obj, element): @@ -3788,31 +3841,31 @@ def _setup(self): """Setup this Panel component.""" self.time_text = TextBlock2D() self.speed_text = TextBlock2D( - text='1', + text="1", font_size=21, color=(0.2, 0.2, 0.2), bold=True, - justification='center', - vertical_justification='middle', + justification="center", + vertical_justification="middle", ) self.panel = Panel2D( size=(190, 30), color=(1, 1, 1), - align='right', + align="right", has_border=True, border_color=(0, 0.3, 0), border_width=2, ) play_pause_icons = [ - ('play', read_viz_icons(fname='play3.png')), - ('pause', read_viz_icons(fname='pause2.png')), + ("play", read_viz_icons(fname="play3.png")), + ("pause", read_viz_icons(fname="pause2.png")), ] loop_icons = [ - ('once', read_viz_icons(fname='checkmark.png')), - ('loop', read_viz_icons(fname='infinite.png')), + ("once", read_viz_icons(fname="checkmark.png")), + ("loop", read_viz_icons(fname="infinite.png")), ] self._play_pause_btn = Button2D(icon_fnames=play_pause_icons) @@ -3820,25 +3873,25 @@ def _setup(self): self._loop_btn = Button2D(icon_fnames=loop_icons) self._stop_btn = Button2D( - icon_fnames=[('stop', read_viz_icons(fname='stop2.png'))] + icon_fnames=[("stop", read_viz_icons(fname="stop2.png"))] ) self._speed_up_btn = Button2D( - icon_fnames=[('plus', read_viz_icons(fname='plus.png'))], size=(15, 15) + icon_fnames=[("plus", read_viz_icons(fname="plus.png"))], size=(15, 15) ) self._slow_down_btn = Button2D( - icon_fnames=[('minus', read_viz_icons(fname='minus.png'))], size=(15, 15) + icon_fnames=[("minus", read_viz_icons(fname="minus.png"))], size=(15, 15) ) self._progress_bar = LineSlider2D( initial_value=0, - orientation='horizontal', + orientation="horizontal", min_value=0, max_value=100, - text_alignment='top', + text_alignment="top", length=590, - text_template='', + text_template="", line_width=9, ) @@ -3905,30 +3958,30 @@ def on_progress_change(slider): def play(self): """Play the playback""" self._playing = True - self._play_pause_btn.set_icon_by_name('pause') + self._play_pause_btn.set_icon_by_name("pause") self.on_play() def stop(self): """Stop the playback""" self._playing = False - self._play_pause_btn.set_icon_by_name('play') + self._play_pause_btn.set_icon_by_name("play") self.on_stop() def pause(self): """Pause the playback""" self._playing = False - self._play_pause_btn.set_icon_by_name('play') + self._play_pause_btn.set_icon_by_name("play") self.on_pause() def loop(self): """Set repeating mode to loop.""" self._loop = True - self._loop_btn.set_icon_by_name('loop') + self._loop_btn.set_icon_by_name("loop") def play_once(self): """Set repeating mode to repeat once.""" self._loop = False - self._loop_btn.set_icon_by_name('once') + self._loop_btn.set_icon_by_name("once") @property def final_time(self): @@ -3938,6 +3991,7 @@ def final_time(self): ------- float Final time for the progress slider. + """ return self._progress_bar.max_value @@ -3949,6 +4003,7 @@ def final_time(self, t): ---------- t: float Final time for the progress slider. + """ self._progress_bar.max_value = t @@ -3960,6 +4015,7 @@ def current_time(self): ------- float Progress slider current value. + """ return self._progress_bar.value @@ -3968,9 +4024,10 @@ def current_time(self, t): """Set progress slider value. Parameters - ------- + ---------- t: float Current time to be set. + """ self._progress_bar.value = t self.current_time_str = t @@ -4000,15 +4057,16 @@ def current_time_str(self, t): ----- This should only be used when the `current_value` is not being set since setting`current_value` automatically sets this property as well. + """ t = np.clip(t, 0, self.final_time) if self.final_time < 3600: m, s = divmod(t, 60) - t_str = r'%02d:%05.2f' % (m, s) + t_str = r"%02d:%05.2f" % (m, s) else: m, s = divmod(t, 60) h, m = divmod(m, 60) - t_str = r'%02d:%02d:%02d' % (h, m, s) + t_str = r"%02d:%02d:%02d" % (h, m, s) self.time_text.message = t_str @property @@ -4031,11 +4089,12 @@ def speed(self, speed): ---------- speed: float Speed value to be set in the speed_text counter. + """ if speed <= 0: speed = 0.01 self._speed = speed - speed_str = f'{speed}'.strip('0').rstrip('.') + speed_str = f"{speed}".strip("0").rstrip(".") self.speed_text.font_size = 21 if 0.01 <= speed < 100 else 14 self.speed_text.message = speed_str @@ -4080,6 +4139,7 @@ def width(self): ------- float The width of the PlaybackPanel. + """ return self._width @@ -4092,6 +4152,7 @@ def width(self, width): width: float The width of the whole panel. If set to None, The width will be the same as the window's width. + """ self._width = width if width is not None else 900 self._auto_width = width is None @@ -4121,17 +4182,28 @@ class Card2D(UI): Displays the title on card. body_box: :class: 'TextBLock2D' Displays the body text. - """ - def __init__(self, image_path, body_text="", draggable=True, - title_text="", padding=10, position=(0, 0), - size=(400, 400), image_scale=0.5, bg_color=(0.5, 0.5, 0.5), - bg_opacity=1, title_color=(0., 0., 0.), - body_color=(0., 0., 0.), border_color=(1., 1., 1.), - border_width=0, maintain_aspect=False): - """ + """ - Parameters + def __init__( + self, + image_path, + body_text="", + draggable=True, + title_text="", + padding=10, + position=(0, 0), + size=(400, 400), + image_scale=0.5, + bg_color=(0.5, 0.5, 0.5), + bg_opacity=1, + title_color=(0.0, 0.0, 0.0), + body_color=(0.0, 0.0, 0.0), + border_color=(1.0, 1.0, 1.0), + border_width=0, + maintain_aspect=False, + ): + """Parameters ---------- image_path: str Path of the image, supports png and jpg/jpeg images @@ -4164,14 +4236,15 @@ def __init__(self, image_path, body_text="", draggable=True, Width of the border maintain_aspect: bool, optional If the image should be scaled to maintain aspect ratio - """ + """ self.image_path = image_path self._basename = os.path.basename(self.image_path) - self._extension = self._basename.split('.')[-1] - if self._extension not in ['jpg', 'jpeg', 'png']: + self._extension = self._basename.split(".")[-1] + if self._extension not in ["jpg", "jpeg", "png"]: raise UnidentifiedImageError( - f'Image extension {self._extension} not supported') + f"Image extension {self._extension} not supported" + ) self.body_text = body_text self.title_text = title_text @@ -4192,8 +4265,7 @@ def __init__(self, image_path, body_text="", draggable=True, if self.maintain_aspect: self._true_image_size = Image.open(urlopen(self.image_path)).size - self._image_size = (self.card_size[0], self.card_size[1] * - self.image_scale) + self._image_size = (self.card_size[0], self.card_size[1] * self.image_scale) self.border_width = border_width self.has_border = bool(border_width) @@ -4202,70 +4274,74 @@ def __init__(self, image_path, body_text="", draggable=True, self.position = position if self.maintain_aspect: - self._new_size = (self._true_image_size[0], - self._true_image_size[1] // self.image_scale) + self._new_size = ( + self._true_image_size[0], + self._true_image_size[1] // self.image_scale, + ) self.resize(self._new_size) else: self.resize(size) def _setup(self): - """ Setup this UI component + """Setup this UI component Create the image. Create the title and body. Create a Panel2D widget to hold image, title, body. """ - self.image = ImageContainer2D(img_path=self.image_path, - size=self._image_size) + self.image = ImageContainer2D(img_path=self.image_path, size=self._image_size) - self.body_box = TextBlock2D(text=self.body_text, - color=self.body_color) + self.body_box = TextBlock2D(text=self.body_text, color=self.body_color) - self.title_box = TextBlock2D(text=self.title_text, bold=True, - color=self.title_color) + self.title_box = TextBlock2D( + text=self.title_text, bold=True, color=self.title_color + ) - self.panel = Panel2D(self.card_size, color=self.bg_color, - opacity=self.bg_opacity, - border_color=self.border_color, - border_width=self.border_width, - has_border=self.has_border) + self.panel = Panel2D( + self.card_size, + color=self.bg_color, + opacity=self.bg_opacity, + border_color=self.border_color, + border_width=self.border_width, + has_border=self.has_border, + ) - self.panel.add_element(self.image, (0., 0.)) - self.panel.add_element(self.title_box, (0., 0.)) - self.panel.add_element(self.body_box, (0., 0.)) + self.panel.add_element(self.image, (0.0, 0.0)) + self.panel.add_element(self.title_box, (0.0, 0.0)) + self.panel.add_element(self.body_box, (0.0, 0.0)) if self.draggable: - self.panel.background.on_left_mouse_button_dragged =\ - self.left_button_dragged - self.panel.background.on_left_mouse_button_pressed\ - = self.left_button_pressed - self.image.on_left_mouse_button_dragged =\ + self.panel.background.on_left_mouse_button_dragged = ( self.left_button_dragged - self.image.on_left_mouse_button_pressed =\ + ) + self.panel.background.on_left_mouse_button_pressed = ( self.left_button_pressed + ) + self.image.on_left_mouse_button_dragged = self.left_button_dragged + self.image.on_left_mouse_button_pressed = self.left_button_pressed else: - self.panel.background.on_left_mouse_button_dragged =\ + self.panel.background.on_left_mouse_button_dragged = ( lambda i_ren, _obj, _comp: i_ren.force_render + ) def _get_actors(self): - """ Get the actors composing this UI component. - """ - + """Get the actors composing this UI component.""" return self.panel.actors def _add_to_scene(self, _scene): - """ Add all subcomponents or VTK props that compose this UI component. + """Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene + """ self.panel.add_to_scene(_scene) if self.size[0] <= 200: - clip_overflow(self.body_box, self.size[0]-2*self.padding) + clip_overflow(self.body_box, self.size[0] - 2 * self.padding) else: - wrap_overflow(self.body_box, self.size[0]-2*self.padding) + wrap_overflow(self.body_box, self.size[0] - 2 * self.padding) - wrap_overflow(self.title_box, self.size[0]-2*self.padding) + wrap_overflow(self.title_box, self.size[0] - 2 * self.padding) def _get_size(self): return self.panel.size @@ -4277,29 +4353,36 @@ def resize(self, size): ---------- size : (int, int) Card2D size(width, height) in pixels. + """ _width, _height = size self.panel.resize(size) - self._image_size = (size[0]-int(self.border_width), - int(self.image_scale*size[1])) + self._image_size = ( + size[0] - int(self.border_width), + int(self.image_scale * size[1]), + ) - _title_box_size = (_width - 2 * self.padding, _height * - 0.34 * self.text_scale / 2) + _title_box_size = ( + _width - 2 * self.padding, + _height * 0.34 * self.text_scale / 2, + ) - _body_box_size = (_width - 2 * self.padding, _height * - self.text_scale / 2) + _body_box_size = (_width - 2 * self.padding, _height * self.text_scale / 2) - _img_coords = (int(self.border_width), - int(size[1] - self._image_size[1])) + _img_coords = (int(self.border_width), int(size[1] - self._image_size[1])) - _title_coords = (self.padding, int(_img_coords[1] - - _title_box_size[1] - self.padding + - self.border_width)) + _title_coords = ( + self.padding, + int(_img_coords[1] - _title_box_size[1] - self.padding + self.border_width), + ) - _text_coords = (self.padding, int(_title_coords[1] - - _body_box_size[1] - self.padding + - self.border_width)) + _text_coords = ( + self.padding, + int( + _title_coords[1] - _body_box_size[1] - self.padding + self.border_width + ), + ) self.panel.update_element(self.image, _img_coords) self.panel.update_element(self.body_box, _text_coords) @@ -4309,39 +4392,35 @@ def resize(self, size): self.title_box.resize(_title_box_size) def _set_position(self, _coords): - """ Position the lower-left corner of this UI component. + """Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). - """ + """ self.panel.position = _coords @property def color(self): - """ Returns the background color of card. - """ - + """Returns the background color of card.""" return self.panel.color @color.setter def color(self, color): - """ Sets background color of card. + """Sets background color of card. Parameters ---------- color : list of 3 floats. - """ + """ self.panel.color = color @property def body(self): - """ Returns the body text of the card. - """ - + """Returns the body text of the card.""" return self.body_box.message @body.setter @@ -4350,9 +4429,7 @@ def body(self, text): @property def title(self): - """ Returns the title text of the card - """ - + """Returns the title text of the card""" return self.title_box.message @title.setter @@ -4373,12 +4450,21 @@ def left_button_dragged(self, i_ren, _obj, _sub_component): class SpinBox(UI): - """SpinBox UI. - """ + """SpinBox UI.""" - def __init__(self, position=(350, 400), size=(300, 100), padding=10, - panel_color=(1, 1, 1), min_val=0, max_val=100, - initial_val=50, step=1, max_column=10, max_line=2): + def __init__( + self, + position=(350, 400), + size=(300, 100), + padding=10, + panel_color=(1, 1, 1), + min_val=0, + max_val=100, + initial_val=50, + step=1, + max_column=10, + max_line=2, + ): """Init this UI element. Parameters @@ -4404,6 +4490,7 @@ def __init__(self, position=(350, 400), size=(300, 100), padding=10, Max number of characters in a line. max_line: int, optional Max number of lines in the textbox. + """ self.panel_size = size self.padding = padding @@ -4428,24 +4515,23 @@ def _setup(self): """ self.panel = Panel2D(size=self.panel_size, color=self.panel_color) - self.textbox = TextBox2D(width=self.max_column, - height=self.max_line) + self.textbox = TextBox2D(width=self.max_column, height=self.max_line) self.textbox.text.dynamic_bbox = False self.textbox.text.auto_font_scale = True self.increment_button = Button2D( - icon_fnames=[("up", read_viz_icons(fname="circle-up.png"))]) + icon_fnames=[("up", read_viz_icons(fname="circle-up.png"))] + ) self.decrement_button = Button2D( - icon_fnames=[("down", read_viz_icons(fname="circle-down.png"))]) + icon_fnames=[("down", read_viz_icons(fname="circle-down.png"))] + ) self.panel.add_element(self.textbox, (0, 0)) self.panel.add_element(self.increment_button, (0, 0)) self.panel.add_element(self.decrement_button, (0, 0)) # Adding button click callbacks - self.increment_button.on_left_mouse_button_pressed = \ - self.increment_callback - self.decrement_button.on_left_mouse_button_pressed = \ - self.decrement_callback + self.increment_button.on_left_mouse_button_pressed = self.increment_callback + self.decrement_button.on_left_mouse_button_pressed = self.decrement_callback self.textbox.off_focus = self.textbox_update_value def resize(self, size): @@ -4455,6 +4541,7 @@ def resize(self, size): ---------- size : (float, float) SpinBox size(width, height) in pixels. + """ self.panel_size = size self.textbox_size = (int(0.7 * size[0]), int(0.8 * size[1])) @@ -4466,11 +4553,15 @@ def resize(self, size): self.increment_button.resize(self.button_size) self.decrement_button.resize(self.button_size) - textbox_pos = (self.padding, int((size[1] - self.textbox_size[1])/2)) - inc_btn_pos = (size[0] - self.padding - self.button_size[0], - int((1.5*size[1] - self.button_size[1])/2)) - dec_btn_pos = (size[0] - self.padding - self.button_size[0], - int((0.5*size[1] - self.button_size[1])/2)) + textbox_pos = (self.padding, int((size[1] - self.textbox_size[1]) / 2)) + inc_btn_pos = ( + size[0] - self.padding - self.button_size[0], + int((1.5 * size[1] - self.button_size[1]) / 2), + ) + dec_btn_pos = ( + size[0] - self.padding - self.button_size[0], + int((0.5 * size[1] - self.button_size[1]) / 2), + ) self.panel.update_element(self.textbox, textbox_pos) self.panel.update_element(self.increment_button, inc_btn_pos) @@ -4500,6 +4591,7 @@ def _set_position(self, coords): ---------- coords: (float, float) Absolute pixel coordinates (x, y). + """ self.panel.center = coords @@ -4540,6 +4632,7 @@ def validate_value(self, value): ------- int If valid return converted integer else the previous value. + """ if value.isnumeric(): return int(value) diff --git a/fury/ui/helpers.py b/fury/ui/helpers.py index db4e541b5..8a9c39de5 100644 --- a/fury/ui/helpers.py +++ b/fury/ui/helpers.py @@ -5,8 +5,9 @@ TWO_PI = 2 * np.pi -def clip_overflow(textblock, width, side='right'): +def clip_overflow(textblock, width, side="right"): """Clips overflowing text of TextBlock2D with respect to width. + Parameters ---------- textblock : TextBlock2D @@ -16,15 +17,17 @@ def clip_overflow(textblock, width, side='right'): side : str, optional Clips the overflowing text according to side. It takes values "left" or "right". + Returns ------- clipped text : str Clipped version of the text. + """ original_str = textblock.message prev_bg = textblock.have_bg - clip_idx = check_overflow(textblock, width, '...', side) + clip_idx = check_overflow(textblock, width, "...", side) if clip_idx == 0: return original_str @@ -33,8 +36,9 @@ def clip_overflow(textblock, width, side='right'): return textblock.message -def wrap_overflow(textblock, wrap_width, side='right'): +def wrap_overflow(textblock, wrap_width, side="right"): """Wraps overflowing text of TextBlock2D with respect to width. + Parameters ---------- textblock : TextBlock2D @@ -44,16 +48,18 @@ def wrap_overflow(textblock, wrap_width, side='right'): side : str, optional Clips the overflowing text according to side. It takes values "left" or "right". + Returns ------- wrapped text : str Wrapped version of the text. + """ original_str = textblock.message str_copy = textblock.message wrap_idxs = [] - wrap_idx = check_overflow(textblock, wrap_width, '', side) + wrap_idx = check_overflow(textblock, wrap_width, "", side) if wrap_idx == 0: return original_str @@ -63,19 +69,20 @@ def wrap_overflow(textblock, wrap_width, side='right'): while wrap_idx != 0: str_copy = str_copy[wrap_idx:] textblock.message = str_copy - wrap_idx = check_overflow(textblock, wrap_width, '', side) + wrap_idx = check_overflow(textblock, wrap_width, "", side) if wrap_idx != 0: wrap_idxs.append(wrap_idxs[-1] + wrap_idx + 1) for idx in wrap_idxs: - original_str = original_str[:idx] + '\n' + original_str[idx:] + original_str = original_str[:idx] + "\n" + original_str[idx:] textblock.message = original_str return textblock.message -def check_overflow(textblock, width, overflow_postfix='', side='right'): +def check_overflow(textblock, width, overflow_postfix="", side="right"): """Checks if the text is overflowing. + Parameters ---------- textblock : TextBlock2D @@ -84,13 +91,15 @@ def check_overflow(textblock, width, overflow_postfix='', side='right'): Required width of the text. overflow_postfix: str, optional Postfix to be added to the text if it is overflowing. + Returns ------- mid_ptr: int Overflow index of the text. + """ side = side.lower() - if side not in ['left', 'right']: + if side not in ["left", "right"]: raise ValueError("side can only take values 'left' or 'right'") original_str = textblock.message @@ -98,7 +107,7 @@ def check_overflow(textblock, width, overflow_postfix='', side='right'): mid_ptr = 0 end_ptr = len(original_str) - if side == 'left': + if side == "left": original_str = original_str[::-1] if textblock.cal_size_from_message()[0] <= width: @@ -113,8 +122,11 @@ def check_overflow(textblock, width, overflow_postfix='', side='right'): elif textblock.cal_size_from_message()[0] > width: end_ptr = mid_ptr - if mid_ptr == (start_ptr + end_ptr) // 2 or textblock.cal_size_from_message()[0] == width: - if side == 'left': + if ( + mid_ptr == (start_ptr + end_ptr) // 2 + or textblock.cal_size_from_message()[0] == width + ): + if side == "left": textblock.message = textblock.message[::-1] return mid_ptr @@ -126,10 +138,10 @@ def cal_bounding_box_2d(vertices): ---------- vertices : ndarray vertices of the actors. - """ + """ if vertices.ndim != 2 or vertices.shape[1] not in [2, 3]: - raise IOError('vertices should be a 2D array with shape (n,2) or (n,3).') + raise IOError("vertices should be a 2D array with shape (n,2) or (n,3).") if vertices.shape[1] == 3: vertices = vertices[:, :-1] @@ -147,9 +159,9 @@ def cal_bounding_box_2d(vertices): if y > max_y: max_y = y - bounding_box_min = np.asarray([min_x, min_y], dtype='int') - bounding_box_max = np.asarray([max_x, max_y], dtype='int') - bounding_box_size = np.asarray([max_x - min_x, max_y - min_y], dtype='int') + bounding_box_min = np.asarray([min_x, min_y], dtype="int") + bounding_box_max = np.asarray([max_x, max_y], dtype="int") + bounding_box_size = np.asarray([max_x - min_x, max_y - min_y], dtype="int") return bounding_box_min, bounding_box_max, bounding_box_size @@ -163,9 +175,10 @@ def rotate_2d(vertices, angle): vertices of the actors. angle: float Value by which the vertices are rotated in radian. + """ if vertices.ndim != 2 or vertices.shape[1] != 3: - raise IOError('vertices should be a 2D array with shape (n,3).') + raise IOError("vertices should be a 2D array with shape (n,3).") rotation_matrix = np.array( [ diff --git a/fury/ui/tests/test_containers.py b/fury/ui/tests/test_containers.py index 0f18d9853..333fb7b44 100644 --- a/fury/ui/tests/test_containers.py +++ b/fury/ui/tests/test_containers.py @@ -22,13 +22,13 @@ def setup_module(): def test_wrong_interactor_style(): panel = ui.Panel2D(size=(300, 150)) dummy_scene = window.Scene() - _ = window.ShowManager(dummy_scene, interactor_style='trackball') + _ = window.ShowManager(dummy_scene, interactor_style="trackball") npt.assert_raises(TypeError, panel.add_to_scene, dummy_scene) @pytest.mark.skipif( skip_linux or skip_win, - reason='This test does not work on Windows.' ' Need to be introspected', + reason="This test does not work on Windows." " Need to be introspected", ) def test_grid_ui1(interactive=False): vol1 = np.zeros((100, 100, 100)) @@ -52,39 +52,39 @@ def test_grid_ui1(interactive=False): texts = [] actors.append(contour_actor1) - text_actor1 = actor.text_3d('cube 1', justification='center') + text_actor1 = actor.text_3d("cube 1", justification="center") texts.append(text_actor1) actors.append(contour_actor2) - text_actor2 = actor.text_3d('cube 2', justification='center') + text_actor2 = actor.text_3d("cube 2", justification="center") texts.append(text_actor2) actors.append(contour_actor3) - text_actor3 = actor.text_3d('cube 3', justification='center') + text_actor3 = actor.text_3d("cube 3", justification="center") texts.append(text_actor3) actors.append(shallow_copy(contour_actor1)) - text_actor1 = actor.text_3d('cube 4', justification='center') + text_actor1 = actor.text_3d("cube 4", justification="center") texts.append(text_actor1) actors.append(shallow_copy(contour_actor2)) - text_actor2 = actor.text_3d('cube 5', justification='center') + text_actor2 = actor.text_3d("cube 5", justification="center") texts.append(text_actor2) actors.append(shallow_copy(contour_actor3)) - text_actor3 = actor.text_3d('cube 6', justification='center') + text_actor3 = actor.text_3d("cube 6", justification="center") texts.append(text_actor3) actors.append(shallow_copy(contour_actor1)) - text_actor1 = actor.text_3d('cube 7', justification='center') + text_actor1 = actor.text_3d("cube 7", justification="center") texts.append(text_actor1) actors.append(shallow_copy(contour_actor2)) - text_actor2 = actor.text_3d('cube 8', justification='center') + text_actor2 = actor.text_3d("cube 8", justification="center") texts.append(text_actor2) actors.append(shallow_copy(contour_actor3)) - text_actor3 = actor.text_3d('cube 9', justification='center') + text_actor3 = actor.text_3d("cube 9", justification="center") texts.append(text_actor3) counter = itertools.count() @@ -125,14 +125,13 @@ def timer_callback(_obj, _event): grid_ui_2 = ui.GridUI(actors=actors) new_sm.scene.add(grid_ui_2) t = 1 - except: + except: # noqa: E722 pass npt.assert_equal(t, 1) def test_grid_ui2(interactive=False): - vol1 = np.zeros((100, 100, 100)) vol1[25:75, 25:75, 25:75] = 100 @@ -154,52 +153,52 @@ def test_grid_ui2(interactive=False): texts = [] actors.append(contour_actor1) - text_actor1 = actor.text_3d('cube 1', justification='center') + text_actor1 = actor.text_3d("cube 1", justification="center") texts.append(text_actor1) actors.append(contour_actor2) - text_actor2 = actor.text_3d('cube 2', justification='center') + text_actor2 = actor.text_3d("cube 2", justification="center") texts.append(text_actor2) actors.append(contour_actor3) - text_actor3 = actor.text_3d('cube 3', justification='center') + text_actor3 = actor.text_3d("cube 3", justification="center") texts.append(text_actor3) actors.append(shallow_copy(contour_actor1)) - text_actor1 = actor.text_3d('cube 4', justification='center') + text_actor1 = actor.text_3d("cube 4", justification="center") texts.append(text_actor1) actors.append(shallow_copy(contour_actor2)) - text_actor2 = actor.text_3d('cube 5', justification='center') + text_actor2 = actor.text_3d("cube 5", justification="center") texts.append(text_actor2) actors.append(shallow_copy(contour_actor3)) - text_actor3 = actor.text_3d('cube 6', justification='center') + text_actor3 = actor.text_3d("cube 6", justification="center") texts.append(text_actor3) actors.append(shallow_copy(contour_actor1)) - text_actor1 = actor.text_3d('cube 7', justification='center') + text_actor1 = actor.text_3d("cube 7", justification="center") texts.append(text_actor1) actors.append(shallow_copy(contour_actor2)) - text_actor2 = actor.text_3d('cube 8', justification='center') + text_actor2 = actor.text_3d("cube 8", justification="center") texts.append(text_actor2) actors.append(shallow_copy(contour_actor3)) - text_actor3 = actor.text_3d('cube 9', justification='center') + text_actor3 = actor.text_3d("cube 9", justification="center") texts.append(text_actor3) # this needs to happen automatically when start() ends. # for act in actors: # act.RemoveAllObservers() - filename = 'test_grid_ui' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_grid_ui" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") current_size = (900, 600) scene = window.Scene() - show_manager = window.ShowManager(scene, size=current_size, title='FURY GridUI') + show_manager = window.ShowManager(scene, size=current_size, title="FURY GridUI") grid_ui2 = ui.GridUI( actors=actors, @@ -234,7 +233,7 @@ def test_grid_ui2(interactive=False): def test_ui_image_container_2d(interactive=False): - image_test = ui.ImageContainer2D(img_path=read_viz_icons(fname='home3.png')) + image_test = ui.ImageContainer2D(img_path=read_viz_icons(fname="home3.png")) image_test.center = (300, 300) npt.assert_equal(image_test.size, (100, 100)) @@ -243,73 +242,72 @@ def test_ui_image_container_2d(interactive=False): npt.assert_equal(image_test.size, (200, 200)) current_size = (600, 600) - show_manager = window.ShowManager(size=current_size, title='FURY Button') + show_manager = window.ShowManager(size=current_size, title="FURY Button") show_manager.scene.add(image_test) if interactive: show_manager.start() def test_ui_tab_ui(interactive=False): - filename = 'test_ui_tab_ui' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_tab_ui" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") + + tab_ui = ui.TabUI(position=(50, 50), size=(300, 300), nb_tabs=3, draggable=True) - tab_ui = ui.TabUI( - position=(50, 50), size=(300, 300), nb_tabs=3, draggable=True) + tab_ui.tabs[0].title = "Tab 1" + tab_ui.tabs[1].title = "Tab 2" + tab_ui.tabs[2].title = "Tab 3" - tab_ui.tabs[0].title = 'Tab 1' - tab_ui.tabs[1].title = 'Tab 2' - tab_ui.tabs[2].title = 'Tab 3' - npt.assert_equal(tab_ui.tabs[0].title_bold, False) npt.assert_equal(tab_ui.tabs[1].title_bold, False) npt.assert_equal(tab_ui.tabs[2].title_bold, False) - + tab_ui.tabs[0].title_bold = True tab_ui.tabs[1].title_bold = False tab_ui.tabs[2].title_bold = True - + npt.assert_equal(tab_ui.tabs[0].title_bold, True) npt.assert_equal(tab_ui.tabs[1].title_bold, False) npt.assert_equal(tab_ui.tabs[2].title_bold, True) - - npt.assert_equal(tab_ui.tabs[0].title_color, (.0, .0, .0)) - npt.assert_equal(tab_ui.tabs[1].title_color, (.0, .0, .0)) - npt.assert_equal(tab_ui.tabs[2].title_color, (.0, .0, .0)) - + + npt.assert_equal(tab_ui.tabs[0].title_color, (0.0, 0.0, 0.0)) + npt.assert_equal(tab_ui.tabs[1].title_color, (0.0, 0.0, 0.0)) + npt.assert_equal(tab_ui.tabs[2].title_color, (0.0, 0.0, 0.0)) + tab_ui.tabs[0].title_color = (1, 0, 0) tab_ui.tabs[1].title_color = (0, 1, 0) tab_ui.tabs[2].title_color = (0, 0, 1) - - npt.assert_equal(tab_ui.tabs[0].title_color, (1., .0, .0)) - npt.assert_equal(tab_ui.tabs[1].title_color, (.0, 1., .0)) - npt.assert_equal(tab_ui.tabs[2].title_color, (.0, .0, 1.)) - + + npt.assert_equal(tab_ui.tabs[0].title_color, (1.0, 0.0, 0.0)) + npt.assert_equal(tab_ui.tabs[1].title_color, (0.0, 1.0, 0.0)) + npt.assert_equal(tab_ui.tabs[2].title_color, (0.0, 0.0, 1.0)) + npt.assert_equal(tab_ui.tabs[0].title_font_size, 18) npt.assert_equal(tab_ui.tabs[1].title_font_size, 18) npt.assert_equal(tab_ui.tabs[2].title_font_size, 18) - + tab_ui.tabs[0].title_font_size = 10 tab_ui.tabs[1].title_font_size = 20 tab_ui.tabs[2].title_font_size = 30 - + npt.assert_equal(tab_ui.tabs[0].title_font_size, 10) npt.assert_equal(tab_ui.tabs[1].title_font_size, 20) npt.assert_equal(tab_ui.tabs[2].title_font_size, 30) - + npt.assert_equal(tab_ui.tabs[0].title_italic, False) npt.assert_equal(tab_ui.tabs[1].title_italic, False) npt.assert_equal(tab_ui.tabs[2].title_italic, False) - + tab_ui.tabs[0].title_italic = False tab_ui.tabs[1].title_italic = True tab_ui.tabs[2].title_italic = False - + npt.assert_equal(tab_ui.tabs[0].title_italic, False) npt.assert_equal(tab_ui.tabs[1].title_italic, True) npt.assert_equal(tab_ui.tabs[2].title_italic, False) - tab_ui.add_element(0, ui.Checkbox(['Option 1', 'Option 2']), (0.5, 0.5)) + tab_ui.add_element(0, ui.Checkbox(["Option 1", "Option 2"]), (0.5, 0.5)) tab_ui.add_element(1, ui.LineSlider2D(), (0.0, 0.5)) tab_ui.add_element(2, ui.TextBlock2D(), (0.5, 0.5)) @@ -322,9 +320,9 @@ def test_ui_tab_ui(interactive=False): with npt.assert_raises(IndexError): tab_ui.update_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) - npt.assert_equal('Tab 1', tab_ui.tabs[0].title) - npt.assert_equal('Tab 2', tab_ui.tabs[1].title) - npt.assert_equal('Tab 3', tab_ui.tabs[2].title) + npt.assert_equal("Tab 1", tab_ui.tabs[0].title) + npt.assert_equal("Tab 2", tab_ui.tabs[1].title) + npt.assert_equal("Tab 3", tab_ui.tabs[2].title) npt.assert_equal(3, tab_ui.nb_tabs) @@ -345,7 +343,7 @@ def tab_change(tab_ui): event_counter.monitor(tab_ui) current_size = (800, 800) - show_manager = window.ShowManager(size=current_size, title='Tab UI Test') + show_manager = window.ShowManager(size=current_size, title="Tab UI Test") show_manager.scene.add(tab_ui) if interactive: @@ -360,3 +358,112 @@ def tab_change(tab_ui): npt.assert_equal(0, tab_ui.active_tab_idx) npt.assert_equal(11, next(changes)) npt.assert_equal(5, next(collapses)) + + +def test_ui_tab_ui_position(interactive=False): + filename = "test_ui_tab_ui_top_position" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") + + tab_ui_top = ui.TabUI( + position=(50, 50), size=(300, 300), nb_tabs=3, draggable=True, tab_bar_pos="top" + ) + + tab_ui_top.tabs[0].title = "Tab 1" + tab_ui_top.tabs[1].title = "Tab 2" + tab_ui_top.tabs[2].title = "Tab 3" + + tab_ui_top.add_element(0, ui.Checkbox(["Option 1", "Option 2"]), (0.5, 0.5)) + tab_ui_top.add_element(1, ui.LineSlider2D(), (0.0, 0.5)) + tab_ui_top.add_element(2, ui.TextBlock2D(), (0.5, 0.5)) + + npt.assert_equal("Tab 1", tab_ui_top.tabs[0].title) + npt.assert_equal("Tab 2", tab_ui_top.tabs[1].title) + npt.assert_equal("Tab 3", tab_ui_top.tabs[2].title) + + npt.assert_equal(3, tab_ui_top.nb_tabs) + + npt.assert_equal((50, 50), tab_ui_top.position) + npt.assert_equal((300, 300), tab_ui_top.size) + + with npt.assert_raises(IndexError): + tab_ui_top.add_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + with npt.assert_raises(IndexError): + tab_ui_top.remove_element(3, ui.TextBlock2D()) + + with npt.assert_raises(IndexError): + tab_ui_top.update_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + tab_ui_bottom = ui.TabUI( + position=(350, 50), + size=(300, 300), + nb_tabs=3, + draggable=True, + tab_bar_pos="bottom", + ) + + tab_ui_bottom.tabs[0].title = "Tab 1" + tab_ui_bottom.tabs[1].title = "Tab 2" + tab_ui_bottom.tabs[2].title = "Tab 3" + + tab_ui_bottom.add_element(0, ui.Checkbox(["Option 1", "Option 2"]), (0.5, 0.5)) + tab_ui_bottom.add_element(1, ui.LineSlider2D(), (0.0, 0.5)) + tab_ui_bottom.add_element(2, ui.TextBlock2D(), (0.5, 0.5)) + + npt.assert_equal("Tab 1", tab_ui_bottom.tabs[0].title) + npt.assert_equal("Tab 2", tab_ui_bottom.tabs[1].title) + npt.assert_equal("Tab 3", tab_ui_bottom.tabs[2].title) + + npt.assert_equal(3, tab_ui_bottom.nb_tabs) + + npt.assert_equal((350, 50), tab_ui_bottom.position) + npt.assert_equal((300, 300), tab_ui_bottom.size) + + with npt.assert_raises(IndexError): + tab_ui_bottom.add_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + with npt.assert_raises(IndexError): + tab_ui_bottom.remove_element(3, ui.TextBlock2D()) + + with npt.assert_raises(IndexError): + tab_ui_bottom.update_element(3, ui.TextBlock2D(), (0.5, 0.5, 0.5)) + + collapses = itertools.count() + changes = itertools.count() + + def collapse(tab_ui_top): + if tab_ui_top.collapsed or tab_ui_bottom.collapsed: + next(collapses) + + def tab_change(tab_ui_top): + next(changes) + + tab_ui_top.on_change = tab_change + tab_ui_top.on_collapse = collapse + + tab_ui_bottom.on_change = tab_change + tab_ui_bottom.on_collapse = collapse + + event_counter = EventCounter() + event_counter.monitor(tab_ui_top) + event_counter.monitor(tab_ui_bottom) + + current_size = (800, 800) + show_manager = window.ShowManager(size=current_size, title="Tab UI Test") + show_manager.scene.add(tab_ui_top) + show_manager.scene.add(tab_ui_bottom) + + if interactive: + show_manager.record_events_to_file(recording_filename) + print(list(event_counter.events_counts.items())) + event_counter.save(expected_events_counts_filename) + else: + show_manager.play_events_from_file(recording_filename) + expected = EventCounter.load(expected_events_counts_filename) + event_counter.check_counts(expected) + + npt.assert_equal(0, tab_ui_top.active_tab_idx) + npt.assert_equal(0, tab_ui_bottom.active_tab_idx) + npt.assert_equal(14, next(changes)) + npt.assert_equal(5, next(collapses)) diff --git a/fury/ui/tests/test_core.py b/fury/ui/tests/test_core.py index c056aba4b..5e76a1cfe 100644 --- a/fury/ui/tests/test_core.py +++ b/fury/ui/tests/test_core.py @@ -1,5 +1,5 @@ """Core module testing.""" -import warnings + from os.path import join as pjoin import numpy as np @@ -11,9 +11,9 @@ def test_ui_button_panel(recording=False): - filename = 'test_ui_button_panel' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_button_panel" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") # Rectangle rectangle_test = ui.Rectangle2D(size=(10, 10)) @@ -23,8 +23,8 @@ def test_ui_button_panel(recording=False): fetch_viz_icons() icon_files = [] - icon_files.append(('stop', read_viz_icons(fname='stop2.png'))) - icon_files.append(('play', read_viz_icons(fname='play3.png'))) + icon_files.append(("stop", read_viz_icons(fname="stop2.png"))) + icon_files.append(("play", read_viz_icons(fname="play3.png"))) button_test = ui.Button2D(icon_fnames=icon_files) button_test.center = (20, 20) @@ -53,7 +53,7 @@ def modify_button_callback(i_ren, _obj, button): # TextBlock text_block_test = ui.TextBlock2D() - text_block_test.message = 'TextBlock' + text_block_test.message = "TextBlock" text_block_test.color = (0, 0, 0) # Panel @@ -61,13 +61,13 @@ def modify_button_callback(i_ren, _obj, button): size=(300, 150), position=(290, 15), color=(1, 1, 1), - align='right', + align="right", has_border=True, ) non_bordered_panel = ui.Panel2D(size=(100, 100), has_border=False) - npt.assert_equal(hasattr(non_bordered_panel, 'borders'), False) + npt.assert_equal(hasattr(non_bordered_panel, "borders"), False) panel.add_element(rectangle_test, (290, 135)) panel.add_element(button_test, (0.1, 0.1)) @@ -94,33 +94,33 @@ def modify_button_callback(i_ren, _obj, button): * 4, ) - panel.border_width = ['bottom', 10.0] + panel.border_width = ["bottom", 10.0] npt.assert_equal(panel.border_width[3], 10.0) - npt.assert_equal(panel.borders['bottom'].height, 10.0) + npt.assert_equal(panel.borders["bottom"].height, 10.0) - panel.border_width = ['right', 10.0] + panel.border_width = ["right", 10.0] npt.assert_equal(panel.border_width[1], 10.0) - npt.assert_equal(panel.borders['right'].width, 10.0) + npt.assert_equal(panel.borders["right"].width, 10.0) with npt.assert_raises(ValueError): - panel.border_width = ['invalid_label', 10.0] + panel.border_width = ["invalid_label", 10.0] - panel.border_color = ['bottom', (0.4, 0.5, 0.6)] + panel.border_color = ["bottom", (0.4, 0.5, 0.6)] npt.assert_equal(panel.border_color[3], (0.4, 0.5, 0.6)) with npt.assert_raises(ValueError): - panel.border_color = ['invalid_label', (0.4, 0.5, 0.6)] + panel.border_color = ["invalid_label", (0.4, 0.5, 0.6)] new_size = (400, 400) panel.resize(new_size) - npt.assert_equal(panel.borders['bottom'].width, 400.0) + npt.assert_equal(panel.borders["bottom"].width, 400.0) # Assign the counter callback to every possible event. event_counter = EventCounter() event_counter.monitor(button_test) event_counter.monitor(panel.background) current_size = (600, 600) - show_manager = window.ShowManager(size=current_size, title='FURY Button') + show_manager = window.ShowManager(size=current_size, title="FURY Button") show_manager.scene.add(panel) # from time import sleep @@ -219,27 +219,27 @@ def _check_property(obj, attr, values): setattr(obj, attr, value) npt.assert_equal(getattr(obj, attr), value) - _check_property(text_block, 'bold', [True, False]) - _check_property(text_block, 'italic', [True, False]) - _check_property(text_block, 'shadow', [True, False]) - _check_property(text_block, 'auto_font_scale', [True, False]) - _check_property(text_block, 'font_size', range(100)) - _check_property(text_block, 'message', ['', 'Hello World', 'Line\nBreak']) - _check_property(text_block, 'justification', ['left', 'center', 'right']) - _check_property(text_block, 'position', [(350, 350), (0.5, 0.5)]) - _check_property(text_block, 'color', [(0.0, 0.5, 1.0)]) - _check_property(text_block, 'background_color', [(0.0, 0.5, 1.0), None]) - _check_property(text_block, 'vertical_justification', ['top', 'middle', 'bottom']) - _check_property(text_block, 'font_family', ['Arial', 'Courier']) + _check_property(text_block, "bold", [True, False]) + _check_property(text_block, "italic", [True, False]) + _check_property(text_block, "shadow", [True, False]) + _check_property(text_block, "auto_font_scale", [True, False]) + _check_property(text_block, "font_size", range(100)) + _check_property(text_block, "message", ["", "Hello World", "Line\nBreak"]) + _check_property(text_block, "justification", ["left", "center", "right"]) + _check_property(text_block, "position", [(350, 350), (0.5, 0.5)]) + _check_property(text_block, "color", [(0.0, 0.5, 1.0)]) + _check_property(text_block, "background_color", [(0.0, 0.5, 1.0), None]) + _check_property(text_block, "vertical_justification", ["top", "middle", "bottom"]) + _check_property(text_block, "font_family", ["Arial", "Courier"]) with npt.assert_raises(ValueError): - text_block.font_family = 'Verdana' + text_block.font_family = "Verdana" with npt.assert_raises(ValueError): - text_block.justification = 'bottom' + text_block.justification = "bottom" with npt.assert_raises(ValueError): - text_block.vertical_justification = 'left' + text_block.vertical_justification = "left" def test_text_block_2d_justification(): @@ -276,103 +276,103 @@ def test_text_block_2d_justification(): texts = [] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(left, top), font_size=font_size, color=(1, 0, 0), bg_color=bg_color, - justification='left', - vertical_justification='top', + justification="left", + vertical_justification="top", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(center, top), font_size=font_size, color=(0, 1, 0), bg_color=bg_color, - justification='center', - vertical_justification='top', + justification="center", + vertical_justification="top", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(right, top), font_size=font_size, color=(0, 0, 1), bg_color=bg_color, - justification='right', - vertical_justification='top', + justification="right", + vertical_justification="top", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(left, middle), font_size=font_size, color=(1, 1, 0), bg_color=bg_color, - justification='left', - vertical_justification='middle', + justification="left", + vertical_justification="middle", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(center, middle), font_size=font_size, color=(0, 1, 1), bg_color=bg_color, - justification='center', - vertical_justification='middle', + justification="center", + vertical_justification="middle", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(right, middle), font_size=font_size, color=(1, 0, 1), bg_color=bg_color, - justification='right', - vertical_justification='middle', + justification="right", + vertical_justification="middle", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(left, bottom), font_size=font_size, color=(0.5, 0, 1), bg_color=bg_color, - justification='left', - vertical_justification='bottom', + justification="left", + vertical_justification="bottom", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(center, bottom), font_size=font_size, color=(1, 0.5, 0), bg_color=bg_color, - justification='center', - vertical_justification='bottom', + justification="center", + vertical_justification="bottom", ) ] texts += [ ui.TextBlock2D( - 'HH', + "HH", position=(right, bottom), font_size=font_size, color=(0, 1, 0.5), bg_color=bg_color, - justification='right', - vertical_justification='bottom', + justification="right", + vertical_justification="bottom", ) ] @@ -385,7 +385,6 @@ def test_text_block_2d_justification(): def test_text_block_2d_size(): - text_block_0 = ui.TextBlock2D() npt.assert_equal(text_block_0.actor.GetTextScaleMode(), 0) @@ -401,23 +400,33 @@ def test_text_block_2d_size(): text_block_1 = ui.TextBlock2D(dynamic_bbox=True) npt.assert_equal(text_block_1.actor.GetTextScaleMode(), 0) - npt.assert_equal(text_block_1.size, ((len("Text Block") * - text_block_1.font_size, text_block_1.font_size))) + npt.assert_equal( + text_block_1.size, + ((len("Text Block") * text_block_1.font_size, text_block_1.font_size)), + ) text_block_1.font_size = 50 - npt.assert_equal(text_block_1.size, (len("Text Block") * - text_block_1.font_size, text_block_1.font_size)) + npt.assert_equal( + text_block_1.size, + (len("Text Block") * text_block_1.font_size, text_block_1.font_size), + ) text_block_1.resize((500, 200)) npt.assert_equal(text_block_1.actor.GetTextScaleMode(), 0) npt.assert_equal(text_block_1.size, (500, 200)) text_block_2 = ui.TextBlock2D( - text="Just Another Text Block", dynamic_bbox=True, auto_font_scale=True) + text="Just Another Text Block", dynamic_bbox=True, auto_font_scale=True + ) npt.assert_equal(text_block_2.actor.GetTextScaleMode(), 1) - npt.assert_equal(text_block_2.size, (len("Just Another Text Block") * - text_block_2.font_size, text_block_2.font_size)) + npt.assert_equal( + text_block_2.size, + ( + len("Just Another Text Block") * text_block_2.font_size, + text_block_2.font_size, + ), + ) text_block_2.resize((500, 200)) npt.assert_equal(text_block_2.actor.GetTextScaleMode(), 1) diff --git a/fury/ui/tests/test_elements.py b/fury/ui/tests/test_elements.py index de82aa168..7df1645b1 100644 --- a/fury/ui/tests/test_elements.py +++ b/fury/ui/tests/test_elements.py @@ -1,7 +1,6 @@ """Test for components module.""" -import itertools + import os -import shutil from os.path import join as pjoin from tempfile import TemporaryDirectory as InTemporaryDirectory @@ -9,10 +8,9 @@ import numpy.testing as npt import pytest -from fury import actor, ui, window +from fury import ui, window from fury.data import DATA_DIR -from fury.decorators import skip_osx, skip_win -from fury.primitive import prim_sphere +from fury.decorators import skip_osx from fury.testing import ( EventCounter, assert_arrays_equal, @@ -30,16 +28,16 @@ def test_ui_textbox(recording=False): - filename = 'test_ui_textbox' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_textbox" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") print(recording_filename) # TextBox - textbox_test = ui.TextBox2D(height=3, width=10, text='Text') + textbox_test = ui.TextBox2D(height=3, width=10, text="Text") - another_textbox_test = ui.TextBox2D(height=3, width=10, text='Enter Text') - another_textbox_test.set_message('Enter Text') + another_textbox_test = ui.TextBox2D(height=3, width=10, text="Enter Text") + another_textbox_test.set_message("Enter Text") # Checking whether textbox went out of focus is_off_focused = [False] @@ -55,7 +53,7 @@ def _off_focus(textbox): event_counter.monitor(textbox_test) current_size = (600, 600) - show_manager = window.ShowManager(size=current_size, title='FURY TextBox') + show_manager = window.ShowManager(size=current_size, title="FURY TextBox") show_manager.scene.add(textbox_test) @@ -73,16 +71,16 @@ def _off_focus(textbox): def test_ui_line_slider_2d_horizontal_bottom(recording=False): - filename = 'test_ui_line_slider_2d_horizontal_bottom' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_line_slider_2d_horizontal_bottom" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") line_slider_2d_test = ui.LineSlider2D( initial_value=-2, min_value=-5, max_value=5, - orientation='horizontal', - text_alignment='bottom', + orientation="horizontal", + text_alignment="bottom", ) line_slider_2d_test.center = (300, 300) @@ -92,7 +90,7 @@ def test_ui_line_slider_2d_horizontal_bottom(recording=False): current_size = (600, 600) show_manager = window.ShowManager( - size=current_size, title='FURY Horizontal Line Slider' + size=current_size, title="FURY Horizontal Line Slider" ) show_manager.scene.add(line_slider_2d_test) @@ -109,16 +107,16 @@ def test_ui_line_slider_2d_horizontal_bottom(recording=False): def test_ui_line_slider_2d_horizontal_top(recording=False): - filename = 'test_ui_line_slider_2d_horizontal_top' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_line_slider_2d_horizontal_top" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") line_slider_2d_test = ui.LineSlider2D( initial_value=-2, min_value=-5, max_value=5, - orientation='horizontal', - text_alignment='top', + orientation="horizontal", + text_alignment="top", ) line_slider_2d_test.center = (300, 300) @@ -128,7 +126,7 @@ def test_ui_line_slider_2d_horizontal_top(recording=False): current_size = (600, 600) show_manager = window.ShowManager( - size=current_size, title='FURY Horizontal Line Slider' + size=current_size, title="FURY Horizontal Line Slider" ) show_manager.scene.add(line_slider_2d_test) @@ -145,16 +143,16 @@ def test_ui_line_slider_2d_horizontal_top(recording=False): def test_ui_line_slider_2d_vertical_left(recording=False): - filename = 'test_ui_line_slider_2d_vertical_left' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_line_slider_2d_vertical_left" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") line_slider_2d_test = ui.LineSlider2D( initial_value=-2, min_value=-5, max_value=5, - orientation='vertical', - text_alignment='left', + orientation="vertical", + text_alignment="left", ) line_slider_2d_test.center = (300, 300) @@ -164,7 +162,7 @@ def test_ui_line_slider_2d_vertical_left(recording=False): current_size = (600, 600) show_manager = window.ShowManager( - size=current_size, title='FURY Vertical Line Slider' + size=current_size, title="FURY Vertical Line Slider" ) show_manager.scene.add(line_slider_2d_test) @@ -181,16 +179,16 @@ def test_ui_line_slider_2d_vertical_left(recording=False): def test_ui_line_slider_2d_vertical_right(recording=False): - filename = 'test_ui_line_slider_2d_vertical_right' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_line_slider_2d_vertical_right" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") line_slider_2d_test = ui.LineSlider2D( initial_value=-2, min_value=-5, max_value=5, - orientation='vertical', - text_alignment='right', + orientation="vertical", + text_alignment="right", ) line_slider_2d_test.center = (300, 300) @@ -200,7 +198,7 @@ def test_ui_line_slider_2d_vertical_right(recording=False): current_size = (600, 600) show_manager = window.ShowManager( - size=current_size, title='FURY Vertical Line Slider' + size=current_size, title="FURY Vertical Line Slider" ) show_manager.scene.add(line_slider_2d_test) @@ -219,16 +217,16 @@ def test_ui_line_slider_2d_vertical_right(recording=False): def test_ui_2d_line_slider_hooks(recording=False): global changed, value_changed, slider_moved - filename = 'test_ui_line_slider_2d_hooks' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_line_slider_2d_hooks" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") line_slider_2d = ui.LineSlider2D(center=(300, 300)) event_counter = EventCounter() event_counter.monitor(line_slider_2d) - show_manager = window.ShowManager(size=(600, 600), title='FURY Line Slider hooks') + show_manager = window.ShowManager(size=(600, 600), title="FURY Line Slider hooks") # counters for the hooks to increment changed = value_changed = slider_moved = 0 @@ -272,7 +270,7 @@ def on_line_slider_value_changed(slider): def test_ui_line_double_slider_2d(interactive=False): line_double_slider_2d_horizontal_test = ui.LineDoubleSlider2D( center=(300, 300), - shape='disk', + shape="disk", outer_radius=15, min_value=-10, max_value=10, @@ -284,7 +282,7 @@ def test_ui_line_double_slider_2d(interactive=False): line_double_slider_2d_vertical_test = ui.LineDoubleSlider2D( center=(300, 300), - shape='disk', + shape="disk", outer_radius=15, min_value=-10, max_value=10, @@ -296,7 +294,7 @@ def test_ui_line_double_slider_2d(interactive=False): if interactive: show_manager = window.ShowManager( - size=(600, 600), title='FURY Line Double Slider' + size=(600, 600), title="FURY Line Double Slider" ) show_manager.scene.add(line_double_slider_2d_horizontal_test) show_manager.scene.add(line_double_slider_2d_vertical_test) @@ -304,9 +302,9 @@ def test_ui_line_double_slider_2d(interactive=False): line_double_slider_2d_horizontal_test = ui.LineDoubleSlider2D( center=(300, 300), - shape='square', + shape="square", handle_side=5, - orientation='horizontal', + orientation="horizontal", initial_values=(50, 40), ) npt.assert_equal(line_double_slider_2d_horizontal_test.handles[0].size, (5, 5)) @@ -317,9 +315,9 @@ def test_ui_line_double_slider_2d(interactive=False): line_double_slider_2d_vertical_test = ui.LineDoubleSlider2D( center=(300, 300), - shape='square', + shape="square", handle_side=5, - orientation='vertical', + orientation="vertical", initial_values=(50, 40), ) npt.assert_equal(line_double_slider_2d_vertical_test.handles[0].size, (5, 5)) @@ -329,11 +327,11 @@ def test_ui_line_double_slider_2d(interactive=False): npt.assert_equal(line_double_slider_2d_vertical_test.top_disk_ratio, 0.4) with npt.assert_raises(ValueError): - ui.LineDoubleSlider2D(orientation='Not_hor_not_vert') + ui.LineDoubleSlider2D(orientation="Not_hor_not_vert") if interactive: show_manager = window.ShowManager( - size=(600, 600), title='FURY Line Double Slider' + size=(600, 600), title="FURY Line Double Slider" ) show_manager.scene.add(line_double_slider_2d_horizontal_test) show_manager.scene.add(line_double_slider_2d_vertical_test) @@ -343,9 +341,9 @@ def test_ui_line_double_slider_2d(interactive=False): def test_ui_2d_line_double_slider_hooks(recording=False): global changed, value_changed, slider_moved - filename = 'test_ui_line_double_slider_2d_hooks' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_line_double_slider_2d_hooks" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") line_double_slider_2d = ui.LineDoubleSlider2D(center=(300, 300)) @@ -353,7 +351,7 @@ def test_ui_2d_line_double_slider_hooks(recording=False): event_counter.monitor(line_double_slider_2d) show_manager = window.ShowManager( - size=(600, 600), title='FURY Line Double Slider hooks' + size=(600, 600), title="FURY Line Double Slider hooks" ) # counters for the line double slider's changes @@ -397,9 +395,9 @@ def on_line_double_slider_value_changed(slider): def test_ui_ring_slider_2d(recording=False): - filename = 'test_ui_ring_slider_2d' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_ring_slider_2d" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") ring_slider_2d_test = ui.RingSlider2D() ring_slider_2d_test.center = (300, 300) @@ -410,7 +408,7 @@ def test_ui_ring_slider_2d(recording=False): event_counter.monitor(ring_slider_2d_test) current_size = (600, 600) - show_manager = window.ShowManager(size=current_size, title='FURY Ring Slider') + show_manager = window.ShowManager(size=current_size, title="FURY Ring Slider") show_manager.scene.add(ring_slider_2d_test) @@ -435,16 +433,16 @@ def test_ui_ring_slider_2d(recording=False): def test_ui_2d_ring_slider_hooks(recording=False): global changed, value_changed, slider_moved - filename = 'test_ui_ring_slider_2d_hooks' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_ring_slider_2d_hooks" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") ring_slider_2d = ui.RingSlider2D(center=(300, 300)) event_counter = EventCounter() event_counter.monitor(ring_slider_2d) - show_manager = window.ShowManager(size=(600, 600), title='FURY Ring Slider hooks') + show_manager = window.ShowManager(size=(600, 600), title="FURY Ring Slider hooks") # counters for the ring slider changes changed = value_changed = slider_moved = 0 @@ -486,12 +484,12 @@ def on_ring_slider_value_changed(slider): def test_ui_range_slider(interactive=False): - range_slider_test_horizontal = ui.RangeSlider(shape='square') - range_slider_test_vertical = ui.RangeSlider(shape='square', orientation='vertical') + range_slider_test_horizontal = ui.RangeSlider(shape="square") + range_slider_test_vertical = ui.RangeSlider(shape="square", orientation="vertical") if interactive: show_manager = window.ShowManager( - size=(600, 600), title='FURY Line Double Slider' + size=(600, 600), title="FURY Line Double Slider" ) show_manager.scene.add(range_slider_test_horizontal) show_manager.scene.add(range_slider_test_vertical) @@ -548,7 +546,7 @@ def test_ui_slider_value_range(): def test_ui_option(interactive=False): - option_test = ui.Option(label='option 1', position=(10, 10)) + option_test = ui.Option(label="option 1", position=(10, 10)) npt.assert_equal(option_test.checked, False) @@ -559,14 +557,14 @@ def test_ui_option(interactive=False): def test_ui_checkbox_initial_state(recording=False): - filename = 'test_ui_checkbox_initial_state' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_checkbox_initial_state" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") checkbox_test = ui.Checkbox( - labels=['option 1', 'option 2\nOption 2', 'option 3', 'option 4'], + labels=["option 1", "option 2\nOption 2", "option 3", "option 4"], position=(100, 100), - checked_labels=['option 1', 'option 4'], + checked_labels=["option 1", "option 4"], ) # Collect the sequence of options that have been checked in this list. @@ -582,7 +580,7 @@ def _on_change(checkbox): event_counter.monitor(checkbox_test) # Create a show manager and record/play events. - show_manager = window.ShowManager(size=(600, 600), title='FURY Checkbox') + show_manager = window.ShowManager(size=(600, 600), title="FURY Checkbox") show_manager.scene.add(checkbox_test) if recording: @@ -608,16 +606,16 @@ def _on_change(checkbox): # 10. Click on button of option 3. # Check if the right options were selected. expected = [ - ['option 4'], - ['option 4', 'option 2\nOption 2'], - ['option 4', 'option 2\nOption 2', 'option 1'], - ['option 4', 'option 2\nOption 2', 'option 1', 'option 3'], - ['option 4', 'option 2\nOption 2', 'option 3'], - ['option 2\nOption 2', 'option 3'], - ['option 2\nOption 2', 'option 3', 'option 1'], - ['option 3', 'option 1'], - ['option 3', 'option 1', 'option 4'], - ['option 1', 'option 4'], + ["option 4"], + ["option 4", "option 2\nOption 2"], + ["option 4", "option 2\nOption 2", "option 1"], + ["option 4", "option 2\nOption 2", "option 1", "option 3"], + ["option 4", "option 2\nOption 2", "option 3"], + ["option 2\nOption 2", "option 3"], + ["option 2\nOption 2", "option 3", "option 1"], + ["option 3", "option 1"], + ["option 3", "option 1", "option 4"], + ["option 1", "option 4"], ] npt.assert_equal(len(selected_options), len(expected)) @@ -625,12 +623,12 @@ def _on_change(checkbox): def test_ui_checkbox_default(recording=False): - filename = 'test_ui_checkbox_initial_state' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_checkbox_initial_state" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") checkbox_test = ui.Checkbox( - labels=['option 1', 'option 2\nOption 2', 'option 3', 'option 4'], + labels=["option 1", "option 2\nOption 2", "option 3", "option 4"], position=(10, 10), checked_labels=[], ) @@ -660,7 +658,7 @@ def _on_change(checkbox): event_counter.monitor(checkbox_test) # Create a show manager and record/play events. - show_manager = window.ShowManager(size=(600, 600), title='FURY Checkbox') + show_manager = window.ShowManager(size=(600, 600), title="FURY Checkbox") show_manager.scene.add(checkbox_test) if recording: @@ -686,15 +684,15 @@ def _on_change(checkbox): # Check if the right options were selected. expected = [ - ['option 1'], - ['option 1', 'option 2\nOption 2'], - ['option 2\nOption 2'], - ['option 2\nOption 2', 'option 3'], - ['option 2\nOption 2', 'option 3', 'option 1'], - ['option 2\nOption 2', 'option 3', 'option 1', 'option 4'], - ['option 2\nOption 2', 'option 3', 'option 4'], - ['option 3', 'option 4'], - ['option 3'], + ["option 1"], + ["option 1", "option 2\nOption 2"], + ["option 2\nOption 2"], + ["option 2\nOption 2", "option 3"], + ["option 2\nOption 2", "option 3", "option 1"], + ["option 2\nOption 2", "option 3", "option 1", "option 4"], + ["option 2\nOption 2", "option 3", "option 4"], + ["option 3", "option 4"], + ["option 3"], [], ] npt.assert_equal(len(selected_options), len(expected)) @@ -702,14 +700,14 @@ def _on_change(checkbox): def test_ui_radio_button_initial_state(recording=False): - filename = 'test_ui_radio_button_initial' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_radio_button_initial" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") radio_button_test = ui.RadioButton( - labels=['option 1', 'option 2\nOption 2', 'option 3', 'option 4'], + labels=["option 1", "option 2\nOption 2", "option 3", "option 4"], position=(100, 100), - checked_labels=['option 4'], + checked_labels=["option 4"], ) selected_option = [] @@ -724,7 +722,7 @@ def _on_change(radio_button): event_counter.monitor(radio_button_test) # Create a show manager and record/play events. - show_manager = window.ShowManager(size=(600, 600), title='FURY Checkbox') + show_manager = window.ShowManager(size=(600, 600), title="FURY Checkbox") show_manager.scene.add(radio_button_test) if recording: show_manager.record_events_to_file(recording_filename) @@ -746,26 +744,26 @@ def _on_change(radio_button): # Check if the right options were selected. expected = [ - ['option 1'], - ['option 2\nOption 2'], - ['option 2\nOption 2'], - ['option 2\nOption 2'], - ['option 1'], - ['option 3'], - ['option 4'], - ['option 4'], + ["option 1"], + ["option 2\nOption 2"], + ["option 2\nOption 2"], + ["option 2\nOption 2"], + ["option 1"], + ["option 3"], + ["option 4"], + ["option 4"], ] npt.assert_equal(len(selected_option), len(expected)) assert_arrays_equal(selected_option, expected) def test_ui_radio_button_default(recording=False): - filename = 'test_ui_radio_button_initial' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_radio_button_initial" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") radio_button_test = ui.RadioButton( - labels=['option 1', 'option 2\nOption 2', 'option 3', 'option 4'], + labels=["option 1", "option 2\nOption 2", "option 3", "option 4"], position=(10, 10), checked_labels=[], ) @@ -793,7 +791,7 @@ def _on_change(radio_button): event_counter.monitor(radio_button_test) # Create a show manager and record/play events. - show_manager = window.ShowManager(size=(600, 600), title='FURY Checkbox') + show_manager = window.ShowManager(size=(600, 600), title="FURY Checkbox") show_manager.scene.add(radio_button_test) if recording: show_manager.record_events_to_file(recording_filename) @@ -815,14 +813,14 @@ def _on_change(radio_button): # Check if the right options were selected. expected = [ - ['option 1'], - ['option 2\nOption 2'], - ['option 2\nOption 2'], - ['option 2\nOption 2'], - ['option 1'], - ['option 3'], - ['option 4'], - ['option 4'], + ["option 1"], + ["option 2\nOption 2"], + ["option 2\nOption 2"], + ["option 2\nOption 2"], + ["option 1"], + ["option 3"], + ["option 4"], + ["option 4"], ] npt.assert_equal(len(selected_option), len(expected)) assert_arrays_equal(selected_option, expected) @@ -832,22 +830,22 @@ def test_multiple_radio_button_pre_selected(): npt.assert_raises( ValueError, ui.RadioButton, - labels=['option 1', 'option 2\nOption 2', 'option 3', 'option 4'], - checked_labels=['option 1', 'option 4'], + labels=["option 1", "option 2\nOption 2", "option 3", "option 4"], + checked_labels=["option 1", "option 4"], ) @pytest.mark.skipif( - True, reason='Need investigation. Incorrect ' 'number of event for each vtk version' + True, reason="Need investigation. Incorrect " "number of event for each vtk version" ) def test_ui_listbox_2d(interactive=False): - filename = 'test_ui_listbox_2d' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_listbox_2d" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") # Values that will be displayed by the listbox. values = list(range(1, 42 + 1)) - values.append('A Very Very Long Item To Test Text Overflow of List Box 2D') + values.append("A Very Very Long Item To Test Text Overflow of List Box 2D") if interactive: listbox = ui.ListBox2D( @@ -860,7 +858,7 @@ def test_ui_listbox_2d(interactive=False): listbox.center = (300, 300) listbox.panel.opacity = 0.2 - show_manager = window.ShowManager(size=(600, 600), title='FURY ListBox') + show_manager = window.ShowManager(size=(600, 600), title="FURY ListBox") show_manager.scene.add(listbox) show_manager.start() @@ -894,7 +892,7 @@ def _on_change(): event_counter = EventCounter() event_counter.monitor(listbox) - show_manager = window.ShowManager(size=(600, 600), title='FURY ListBox') + show_manager = window.ShowManager(size=(600, 600), title="FURY ListBox") show_manager.scene.add(listbox) show_manager.play_events_from_file(recording_filename) expected = EventCounter.load(expected_events_counts_filename) @@ -906,8 +904,8 @@ def _on_change(): [1, 2], [1], [ - 'A Very Very Long Item To \ -Test Text Overflow of List Box 2D' + "A Very Very Long Item To \ +Test Text Overflow of List Box 2D" ], [1], values, @@ -926,13 +924,13 @@ def _on_change(): [2], [2], [ - 'A Very Very Long Item To \ -Test Text Overflow of List Box 2D' + "A Very Very Long Item To \ +Test Text Overflow of List Box 2D" ], [1], [ - 'A Very Very Long Item To Test \ -Text Overflow of List Box 2D' + "A Very Very Long Item To Test \ +Text Overflow of List Box 2D" ], ] npt.assert_equal(len(selected_values), len(expected)) @@ -941,12 +939,12 @@ def _on_change(): def test_ui_listbox_2d_visibility(): l1 = ui.ListBox2D( - values=['Violet', 'Indigo', 'Blue', 'Yellow'], + values=["Violet", "Indigo", "Blue", "Yellow"], position=(12, 10), size=(100, 100), ) l2 = ui.ListBox2D( - values=['Violet', 'Indigo', 'Blue', 'Yellow'], + values=["Violet", "Indigo", "Blue", "Yellow"], position=(10, 10), size=(100, 300), ) @@ -964,19 +962,19 @@ def assert_listbox(list_box, expected_scroll_bar_height): def test_ui_file_menu_2d(interactive=False): - filename = 'test_ui_file_menu_2d' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_file_menu_2d" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") with InTemporaryDirectory() as tmpdir: - test_dir = os.path.join(tmpdir, 'testdir') - os.makedirs(os.path.join(test_dir, 'tempdir')) + test_dir = os.path.join(tmpdir, "testdir") + os.makedirs(os.path.join(test_dir, "tempdir")) for i in range(10): - open(os.path.join(test_dir, 'tempdir', f'test{i}.txt'), 'wt').close() - open(os.path.join(test_dir, 'testfile.txt'), 'wt').close() + open(os.path.join(test_dir, "tempdir", f"test{i}.txt"), "wt").close() + open(os.path.join(test_dir, "testfile.txt"), "wt").close() filemenu = ui.FileMenu2D( - size=(500, 500), extensions=['txt'], directory_path=test_dir + size=(500, 500), extensions=["txt"], directory_path=test_dir ) # We will collect the sequence of files that have been selected. @@ -993,7 +991,7 @@ def _on_change(): event_counter.monitor(filemenu) # Create a show manager and record/play events. - show_manager = window.ShowManager(size=(600, 600), title='FURY FileMenu') + show_manager = window.ShowManager(size=(600, 600), title="FURY FileMenu") show_manager.scene.add(filemenu) # Recorded events: @@ -1009,38 +1007,38 @@ def _on_change(): # Check if the right files were selected. expected = [ - ['testfile.txt'], - ['tempdir'], - ['test0.txt'], + ["testfile.txt"], + ["tempdir"], + ["test0.txt"], [ - 'test0.txt', - 'test1.txt', - 'test2.txt', - 'test3.txt', - 'test4.txt', - 'test5.txt', - 'test6.txt', + "test0.txt", + "test1.txt", + "test2.txt", + "test3.txt", + "test4.txt", + "test5.txt", + "test6.txt", ], - ['../'], - ['testfile.txt'], + ["../"], + ["testfile.txt"], ] npt.assert_equal(len(selected_files), len(expected)) assert_arrays_equal(selected_files, expected) if interactive: filemenu = ui.FileMenu2D(size=(500, 500), directory_path=os.getcwd()) - show_manager = window.ShowManager(size=(600, 600), title='FURY FileMenu') + show_manager = window.ShowManager(size=(600, 600), title="FURY FileMenu") show_manager.scene.add(filemenu) show_manager.start() def test_ui_combobox_2d(interactive=False): - filename = 'test_ui_combobox_2d' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_combobox_2d" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") - values = ['An Item' + str(i) for i in range(0, 5)] - new_values = ['An Item5', 'An Item6'] + values = ["An Item" + str(i) for i in range(0, 5)] + new_values = ["An Item5", "An Item6"] combobox = ui.ComboBox2D(items=values, position=(400, 400), size=(300, 200)) @@ -1049,20 +1047,20 @@ def test_ui_combobox_2d(interactive=False): event_counter.monitor(combobox) current_size = (800, 800) - show_manager = window.ShowManager(size=current_size, title='ComboBox UI Example') + show_manager = window.ShowManager(size=current_size, title="ComboBox UI Example") show_manager.scene.add(combobox) values.extend(new_values) combobox.append_item(*new_values) npt.assert_equal(values, combobox.items) - values.append('An Item7') - combobox.append_item('An Item7') + values.append("An Item7") + combobox.append_item("An Item7") npt.assert_equal(values, combobox.items) - values.append('An Item8') - values.append('An Item9') - combobox.append_item('An Item8', 'An Item9') + values.append("An Item8") + values.append("An Item9") + combobox.append_item("An Item8", "An Item9") npt.assert_equal(values, combobox.items) complex_list = [[0], (1, [[2, 3], 4], 5)] @@ -1070,12 +1068,12 @@ def test_ui_combobox_2d(interactive=False): values.extend([str(i) for i in range(6)]) npt.assert_equal(values, combobox.items) - invalid_item = {'Hello': 1, 'World': 2} + invalid_item = {"Hello": 1, "World": 2} npt.assert_raises(TypeError, combobox.append_item, invalid_item) npt.assert_equal(values, combobox.items) - npt.assert_equal((60, 60), combobox.drop_button_size) - npt.assert_equal([300, 140], combobox.drop_menu_size) + npt.assert_equal((30, 20), combobox.drop_button_size) + npt.assert_equal([270, 140], combobox.drop_menu_size) npt.assert_equal([300, 200], combobox.size) ui.ComboBox2D(items=values, draggable=False) @@ -1090,20 +1088,19 @@ def test_ui_combobox_2d(interactive=False): expected = EventCounter.load(expected_events_counts_filename) event_counter.check_counts(expected) - npt.assert_equal('An Item1', combobox.selected_text) + npt.assert_equal("An Item1", combobox.selected_text) npt.assert_equal(1, combobox.selected_text_index) combobox.resize((450, 300)) - npt.assert_equal((360, 90), combobox.text_block_size) - npt.assert_equal((90, 90), combobox.drop_button_size) - npt.assert_equal((450, 210), combobox.drop_menu_size) + npt.assert_equal((405, 30), combobox.text_block_size) + npt.assert_equal((45, 30), combobox.drop_button_size) + npt.assert_equal((405, 210), combobox.drop_menu_size) def test_ui_combobox_2d_dropdown_visibility(interactive=False): + values = ["An Item" + str(i) for i in range(0, 5)] - values = ['An Item' + str(i) for i in range(0, 5)] - - tab_ui = ui.TabUI(position=(49, 94), size=(400, 400), nb_tabs=1 , draggable=True) + tab_ui = ui.TabUI(position=(49, 94), size=(400, 400), nb_tabs=1, draggable=True) combobox = ui.ComboBox2D(items=values, position=(400, 400), size=(300, 200)) tab_ui.add_element(0, combobox, (0.1, 0.3)) @@ -1114,7 +1111,7 @@ def test_ui_combobox_2d_dropdown_visibility(interactive=False): event_counter.monitor(tab_ui) current_size = (800, 800) - show_manager = window.ShowManager(size=current_size, title='ComboBox UI Example') + show_manager = window.ShowManager(size=current_size, title="ComboBox UI Example") show_manager.scene.add(tab_ui) tab_ui.tabs[0].content_panel.set_visibility(True) @@ -1143,22 +1140,22 @@ def test_ui_combobox_2d_dropdown_visibility(interactive=False): @pytest.mark.skipif( skip_osx, - reason='This test does not work on macOS.' - 'It works on the local machines.' - 'The colors provided for shapes are ' - 'normalized values whereas when we test' - 'it, the values returned are between ' - '0-255. So while conversion from one' - 'representation to another, there may be' - 'something which causes these issues.', + reason="This test does not work on macOS." + "It works on the local machines." + "The colors provided for shapes are " + "normalized values whereas when we test" + "it, the values returned are between " + "0-255. So while conversion from one" + "representation to another, there may be" + "something which causes these issues.", ) def test_ui_draw_shape(): - line = ui.DrawShape(shape_type='line', position=(150, 150)) - quad = ui.DrawShape(shape_type='quad', position=(300, 300)) - circle = ui.DrawShape(shape_type='circle', position=(150, 300)) + line = ui.DrawShape(shape_type="line", position=(150, 150)) + quad = ui.DrawShape(shape_type="quad", position=(300, 300)) + circle = ui.DrawShape(shape_type="circle", position=(150, 300)) with npt.assert_raises(IOError): - ui.DrawShape('poly') + ui.DrawShape("poly") line.resize((100, 5)) line.shape.color = (0, 1, 0) @@ -1167,14 +1164,14 @@ def test_ui_draw_shape(): circle.resize((25, 0)) circle.shape.color = (0, 0, 1) - line_color = np.round(255 * np.array(line.shape.color)).astype('uint8') - quad_color = np.round(255 * np.array(quad.shape.color)).astype('uint8') - circle_color = np.round(255 * np.array(circle.shape.color)).astype('uint8') + line_color = np.round(255 * np.array(line.shape.color)).astype("uint8") + quad_color = np.round(255 * np.array(quad.shape.color)).astype("uint8") + circle_color = np.round(255 * np.array(circle.shape.color)).astype("uint8") current_size = (900, 900) scene = window.Scene() show_manager = window.ShowManager( - scene, size=current_size, title='DrawShape UI Example' + scene, size=current_size, title="DrawShape UI Example" ) scene.add(line, circle, quad) @@ -1187,9 +1184,9 @@ def test_ui_draw_shape(): def test_ui_draw_panel_basic(interactive=False): - filename = 'test_ui_draw_panel_basic' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_draw_panel_basic" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") drawpanel = ui.DrawPanel(size=(600, 600), position=(30, 10)) @@ -1199,7 +1196,7 @@ def test_ui_draw_panel_basic(interactive=False): current_size = (680, 680) show_manager = window.ShowManager( - size=current_size, title='DrawPanel Basic UI Example' + size=current_size, title="DrawPanel Basic UI Example" ) show_manager.scene.add(drawpanel) @@ -1220,9 +1217,9 @@ def test_ui_draw_panel_basic(interactive=False): def test_ui_draw_panel_rotation(interactive=False): - filename = 'test_ui_draw_panel_rotation' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_ui_draw_panel_rotation" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") drawpanel = ui.DrawPanel(size=(600, 600), position=(30, 10)) @@ -1232,7 +1229,7 @@ def test_ui_draw_panel_rotation(interactive=False): current_size = (680, 680) show_manager = window.ShowManager( - size=current_size, title='DrawPanel Rotation UI Example' + size=current_size, title="DrawPanel Rotation UI Example" ) show_manager.scene.add(drawpanel) @@ -1258,12 +1255,12 @@ def test_playback_panel(interactive=False): current_size = (900, 620) show_manager = window.ShowManager( - size=current_size, title='PlaybackPanel UI Example' + size=current_size, title="PlaybackPanel UI Example" ) - filename = 'test_playback_panel' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_playback_panel" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") def play(): global playing @@ -1313,10 +1310,10 @@ def change_t(value): assert_true(stopped) assert_equal(playback.current_time, ts) assert_greater(playback.current_time, 0) - assert_not_equal(playback.current_time_str, '00:00.00') + assert_not_equal(playback.current_time_str, "00:00.00") playback.current_time = 5 assert_equal(playback.current_time, 5) - assert_equal(playback.current_time_str, '00:05.00') + assert_equal(playback.current_time_str, "00:05.00") # test show/hide playback.show() ss = window.snapshot(show_manager.scene) @@ -1327,20 +1324,28 @@ def change_t(value): def test_card_ui(interactive=False): - filename = 'test_card_ui' - recording_filename = pjoin(DATA_DIR, filename + '.log.gz') - expected_events_counts_filename = pjoin(DATA_DIR, filename + '.json') + filename = "test_card_ui" + recording_filename = pjoin(DATA_DIR, filename + ".log.gz") + expected_events_counts_filename = pjoin(DATA_DIR, filename + ".json") - img_url = "https://raw.githubusercontent.com/fury-gl"\ - "/fury-communication-assets/main/fury-logo.png" + img_url = ( + "https://raw.githubusercontent.com/fury-gl" + "/fury-communication-assets/main/fury-logo.png" + ) title = "FURY" - body = "FURY - Free Unified Rendering in pYthon."\ - "A software library for scientific visualization in Python." + body = ( + "FURY - Free Unified Rendering in pYthon." + "A software library for scientific visualization in Python." + ) - card = ui.elements.Card2D(image_path=img_url, draggable=True, - title_text=title, body_text=body, - image_scale=0.5) + card = ui.elements.Card2D( + image_path=img_url, + draggable=True, + title_text=title, + body_text=body, + image_scale=0.5, + ) # Assign the counter callback to every possible event. @@ -1354,11 +1359,11 @@ def test_card_ui(interactive=False): npt.assert_equal(card.color, (0.5, 0.5, 0.5)) npt.assert_equal(card.panel.position, (0, 0)) - card.title = 'Changed Title' - npt.assert_equal(card.title, 'Changed Title') + card.title = "Changed Title" + npt.assert_equal(card.title, "Changed Title") - card.body = 'Changed Body' - npt.assert_equal(card.body, 'Changed Body') + card.body = "Changed Body" + npt.assert_equal(card.body, "Changed Body") card.title = title card.body = body @@ -1368,7 +1373,7 @@ def test_card_ui(interactive=False): card.resize((300, 300)) npt.assert_equal(card.image.size[1], 150.0) current_size = (600, 600) - show_manager = window.ShowManager(size=current_size, title='FURY Card') + show_manager = window.ShowManager(size=current_size, title="FURY Card") show_manager.scene.add(card) if interactive: diff --git a/fury/ui/tests/test_elements_callback.py b/fury/ui/tests/test_elements_callback.py index dea15b13c..89c6aa506 100644 --- a/fury/ui/tests/test_elements_callback.py +++ b/fury/ui/tests/test_elements_callback.py @@ -1,24 +1,19 @@ """Test for components module.""" + import itertools -import os -import shutil -from os.path import join as pjoin -from tempfile import TemporaryDirectory as InTemporaryDirectory import numpy as np import numpy.testing as npt import pytest from fury import actor, ui, window -from fury.data import DATA_DIR from fury.decorators import skip_osx, skip_win from fury.primitive import prim_sphere -from fury.testing import EventCounter, assert_arrays_equal, assert_greater +from fury.testing import assert_greater def test_frame_rate_and_anti_aliasing(): """Testing frame rate with/out anti-aliasing""" - length_ = 200 multi_samples = 32 max_peels = 8 @@ -80,7 +75,7 @@ def timer_callback(_obj, _event): if cnt % 1 == 0: fps = np.round(showm.frame_rate, 0) frh.fpss.append(fps) - msg = 'FPS ' + str(fps) + ' ' + str(cnt) + msg = "FPS " + str(fps) + " " + str(cnt) tb.message = msg showm.render() if cnt > 10: @@ -136,9 +131,9 @@ def timer_callback(_obj, _event): @pytest.mark.skipif( skip_win, - reason='This test does not work on windows. It ' - 'works on a local machine. Check after ' - 'fixing memory leak with RenderWindow.', + reason="This test does not work on windows. It " + "works on a local machine. Check after " + "fixing memory leak with RenderWindow.", ) def test_timer(): """Testing add a timer and exit window and app from inside timer.""" @@ -150,14 +145,14 @@ def test_timer(): sphere_actor = actor.sphere(centers=xyzr[:, :3], colors=colors[:], radii=xyzr[:, 3]) - vertices, faces = prim_sphere('repulsion724') + vertices, faces = prim_sphere("repulsion724") sphere_actor2 = actor.sphere( centers=xyzr2[:, :3], colors=colors[:], radii=xyzr2[:, 3], vertices=vertices, - faces=faces.astype('i8'), + faces=faces.astype("i8"), ) scene.add(sphere_actor) diff --git a/fury/ui/tests/test_helpers.py b/fury/ui/tests/test_helpers.py index 3584ee48e..039d28d83 100644 --- a/fury/ui/tests/test_helpers.py +++ b/fury/ui/tests/test_helpers.py @@ -1,4 +1,5 @@ """Test helpers function .""" + import numpy as np import numpy.testing as npt @@ -13,88 +14,93 @@ def test_clip_overflow(): - text = ui.TextBlock2D(text='', position=(50, 50), color=(1, 0, 0), size=(100, 50)) + text = ui.TextBlock2D(text="", position=(50, 50), color=(1, 0, 0), size=(100, 50)) sm = window.ShowManager() sm.scene.add(text) - text.message = 'Hello' + text.message = "Hello" clip_overflow(text, text.size[0]) - npt.assert_equal('Hello', text.message) + npt.assert_equal("Hello", text.message) - text.message = 'Hello wassup' + text.message = "Hello what's up?" clip_overflow(text, text.size[0]) - npt.assert_equal('He...', text.message) + npt.assert_equal("He...", text.message) - text.message = 'A very very long message to clip text overflow' + text.message = "A very very long message to clip text overflow" clip_overflow(text, text.size[0]) - npt.assert_equal('A ...', text.message) + npt.assert_equal("A ...", text.message) - text.message = 'Hello' - clip_overflow(text, text.size[0], 'left') - npt.assert_equal('Hello', text.message) + text.message = "Hello" + clip_overflow(text, text.size[0], "left") + npt.assert_equal("Hello", text.message) - text.message = 'Hello wassup' - clip_overflow(text, text.size[0], 'left') - npt.assert_equal('...up', text.message) + text.message = "Hello wassup" + clip_overflow(text, text.size[0], "left") + npt.assert_equal("...up", text.message) - text.message = 'A very very long message to clip text overflow' - clip_overflow(text, text.size[0], 'left') - npt.assert_equal('...ow', text.message) + text.message = "A very very long message to clip text overflow" + clip_overflow(text, text.size[0], "left") + npt.assert_equal("...ow", text.message) - text.message = 'A very very long message to clip text overflow' - clip_overflow(text, text.size[0], 'LeFT') - npt.assert_equal('...ow', text.message) + text.message = "A very very long message to clip text overflow" + clip_overflow(text, text.size[0], "LeFT") + npt.assert_equal("...ow", text.message) - text.message = 'A very very long message to clip text overflow' - clip_overflow(text, text.size[0], 'RigHT') - npt.assert_equal('A ...', text.message) + text.message = "A very very long message to clip text overflow" + clip_overflow(text, text.size[0], "RigHT") + npt.assert_equal("A ...", text.message) - npt.assert_raises(ValueError, clip_overflow, text, text.size[0], 'middle') + npt.assert_raises(ValueError, clip_overflow, text, text.size[0], "middle") def test_wrap_overflow(): - text = ui.TextBlock2D(text='', position=(50, 50), color=(1, 0, 0), size=(100, 50)) + text = ui.TextBlock2D(text="", position=(50, 50), color=(1, 0, 0), size=(100, 50)) sm = window.ShowManager() sm.scene.add(text) - text.message = 'Hello' + text.message = "Hello" wrap_overflow(text, text.size[0]) - npt.assert_equal('Hello', text.message) + npt.assert_equal("Hello", text.message) - text.message = 'Hello wassup' + text.message = "Hello what's up?" wrap_overflow(text, text.size[0]) - npt.assert_equal('Hello\n wass\nup', text.message) + npt.assert_equal("Hello\n what\n's up\n?", text.message) - text.message = 'A very very long message to clip text overflow' + text.message = "A very very long message to clip text overflow" wrap_overflow(text, text.size[0]) npt.assert_equal( - 'A ver\ny ver\ny lon\ng mes\nsage \nto cl\nip te\nxt ov\nerflo\nw', text.message + "A ver\ny ver\ny lon\ng mes\nsage \nto cl\nip te\nxt ov\nerflo\nw", text.message ) - text.message = 'A very very long message to clip text overflow' + text.message = "A very very long message to clip text overflow" wrap_overflow(text, 0) - npt.assert_equal(text.message, '') + npt.assert_equal(text.message, "") - text.message = 'A very very long message to clip text overflow' + text.message = "A very very long message to clip text overflow" wrap_overflow(text, -2 * text.size[0]) - npt.assert_equal(text.message, '') + npt.assert_equal(text.message, "") def test_check_overflow(): - text = ui.TextBlock2D(text='', position=(50, 50), - color=(1, 0, 0), size=(100, 50), bg_color=(.5, .5, .5)) + text = ui.TextBlock2D( + text="", + position=(50, 50), + color=(1, 0, 0), + size=(100, 50), + bg_color=(0.5, 0.5, 0.5), + ) sm = window.ShowManager() sm.scene.add(text) - text.message = 'A very very long message to clip text overflow' + text.message = "A very very long message to clip text overflow" - overflow_idx = check_overflow(text, 100, '~') + overflow_idx = check_overflow(text, 100, "~") npt.assert_equal(4, overflow_idx) - npt.assert_equal('A ve~', text.message) + npt.assert_equal("A ve~", text.message) def test_cal_bounding_box_2d(): @@ -134,8 +140,8 @@ def test_rotate_2d(): new_vertices = rotate_2d(vertices, np.deg2rad(90)) npt.assert_equal( - np.array([[-1.0, 1.0, 0.0], [-10.0, 10.0, 0.0]], dtype='float32'), - new_vertices.astype('float32'), + np.array([[-1.0, 1.0, 0.0], [-10.0, 10.0, 0.0]], dtype="float32"), + new_vertices.astype("float32"), ) with npt.assert_raises(IOError): diff --git a/fury/utils.py b/fury/utils.py index 5928d383b..12aeb2214 100644 --- a/fury/utils.py +++ b/fury/utils.py @@ -34,12 +34,12 @@ def remove_observer_from_actor(actor, id): id of the observer to remove """ - if not hasattr(actor, 'GetMapper'): - raise ValueError('Invalid actor') + if not hasattr(actor, "GetMapper"): + raise ValueError("Invalid actor") mapper = actor.GetMapper() - if not hasattr(mapper, 'RemoveObserver'): - raise ValueError('Invalid mapper') + if not hasattr(mapper, "RemoveObserver"): + raise ValueError("Invalid mapper") mapper.RemoveObserver(id) @@ -56,7 +56,7 @@ def set_input(vtk_object, inp): vtk_object Notes - ------- + ----- This can be used in the following way:: from fury.utils import set_input poly_mapper = set_input(PolyDataMapper(), poly_data) @@ -136,7 +136,7 @@ def numpy_to_vtk_cells(data, is_coords=True): offsets_dtype = np.int64 else: offsets_dtype = np.dtype(data._offsets.dtype) - if offsets_dtype.kind == 'u': + if offsets_dtype.kind == "u": offsets_dtype = np.dtype(offsets_dtype.name[1:]) data = np.array(data, dtype=object) nb_cells = len(data) @@ -194,7 +194,7 @@ def numpy_to_vtk_image_data( """ if array.ndim not in [2, 3]: - raise IOError('only 2D (L, RGB, RGBA) or 3D image available') + raise IOError("only 2D (L, RGB, RGBA) or 3D image available") vtk_image = ImageData() depth = 1 if array.ndim == 2 else array.shape[2] @@ -230,7 +230,7 @@ def map_coordinates_3d_4d(input_array, indices): """ if input_array.ndim <= 2 or input_array.ndim >= 5: - raise ValueError('Input array can only be 3d or 4d') + raise ValueError("Input array can only be 3d or 4d") if input_array.ndim == 3: return map_coordinates(input_array, indices.T, order=1) @@ -279,13 +279,13 @@ def lines_to_vtk_polydata(lines, colors=None): """ # Get the 3d points_array - if lines.__class__.__name__ == 'ArraySequence': + if lines.__class__.__name__ == "ArraySequence": points_array = lines._data else: points_array = np.vstack(lines) if points_array.size == 0: - raise ValueError('Empty lines/streamlines data.') + raise ValueError("Empty lines/streamlines data.") # Set Points to vtk array format vtk_points = numpy_to_vtk_points(points_array) @@ -328,7 +328,7 @@ def lines_to_vtk_polydata(lines, colors=None): elif cols_arr.ndim == 1: if len(cols_arr) == nb_lines: # values for every streamline cols_arrx = [] - for (i, value) in enumerate(colors): + for i, value in enumerate(colors): cols_arrx += lines[i].shape[0] * [value] cols_arrx = np.array(cols_arrx) vtk_colors = numpy_support.numpy_to_vtk(cols_arrx, deep=True) @@ -347,7 +347,7 @@ def lines_to_vtk_polydata(lines, colors=None): vtk_colors = numpy_support.numpy_to_vtk(cols_arr, deep=True) color_is_scalar = True - vtk_colors.SetName('colors') + vtk_colors.SetName("colors") poly_data.GetPointData().SetScalars(vtk_colors) return poly_data, color_is_scalar @@ -398,7 +398,7 @@ def get_polydata_triangles(polydata): vtk_polys = numpy_support.vtk_to_numpy(polydata.GetPolys().GetData()) # test if its really triangles if not (vtk_polys[::4] == 3).all(): - raise AssertionError('Shape error: this is not triangles') + raise AssertionError("Shape error: this is not triangles") return np.vstack([vtk_polys[1::4], vtk_polys[2::4], vtk_polys[3::4]]).T @@ -555,7 +555,7 @@ def set_polydata_primitives_count(polydata, primitives_count): """ add_polydata_numeric_field( - polydata, 'prim_count', primitives_count, array_type=VTK_INT + polydata, "prim_count", primitives_count, array_type=VTK_INT ) @@ -569,8 +569,9 @@ def get_polydata_primitives_count(polydata): Returns ------- primitives count : int + """ - return get_polydata_field(polydata, 'prim_count')[0] + return get_polydata_field(polydata, "prim_count")[0] def primitives_count_to_actor(actor, primitives_count): @@ -596,6 +597,7 @@ def primitives_count_from_actor(actor): Returns ------- primitives count : int + """ polydata = actor.GetMapper().GetInput() return get_polydata_primitives_count(polydata) @@ -644,7 +646,7 @@ def set_polydata_normals(polydata, normals): vtk_normals = numpy_support.numpy_to_vtk(normals, deep=True) # VTK does not require a specific name for the normals array, however, for # readability purposes, we set it to "Normals" - vtk_normals.SetName('Normals') + vtk_normals.SetName("Normals") polydata.GetPointData().SetNormals(vtk_normals) return polydata @@ -661,12 +663,12 @@ def set_polydata_tangents(polydata, tangents): vtk_tangents = numpy_support.numpy_to_vtk(tangents, deep=True, array_type=VTK_FLOAT) # VTK does not require a specific name for the tangents array, however, for # readability purposes, we set it to "Tangents" - vtk_tangents.SetName('Tangents') + vtk_tangents.SetName("Tangents") polydata.GetPointData().SetTangents(vtk_tangents) return polydata -def set_polydata_colors(polydata, colors, array_name='colors'): +def set_polydata_colors(polydata, colors, array_name="colors"): """Set polydata colors with a numpy array (ndarrays Nx3 int). Parameters @@ -687,13 +689,15 @@ def set_polydata_colors(polydata, colors, array_name='colors'): def set_polydata_tcoords(polydata, tcoords): - """Set polydata texture coordinates with a numpy array (ndarrays Nx2 float). + """ + Set polydata texture coordinates with a numpy array (ndarrays Nx2 float). Parameters ---------- polydata : vtkPolyData tcoords : texture coordinates, represented as 2D ndarrays (Nx2) (one per vertex range (0, 1)) + """ vtk_tcoords = numpy_support.numpy_to_vtk(tcoords, deep=True, array_type=VTK_FLOAT) polydata.GetPointData().SetTCoords(vtk_tcoords) @@ -812,13 +816,13 @@ def get_actor_from_primitive( set_polydata_primitives_count(pd, prim_count) if isinstance(colors, np.ndarray): if len(colors) != len(vertices): - msg = 'Vertices and Colors should have the same size.' - msg += ' Please, update your color array or use the function ' - msg += '``fury.primitive.repeat_primitives`` to normalize your ' - msg += 'color array before calling this function. e.g.' + msg = "Vertices and Colors should have the same size." + msg += " Please, update your color array or use the function " + msg += "``fury.primitive.repeat_primitives`` to normalize your " + msg += "color array before calling this function. e.g." raise ValueError(msg) - set_polydata_colors(pd, colors, array_name='colors') + set_polydata_colors(pd, colors, array_name="colors") if isinstance(normals, np.ndarray): set_polydata_normals(pd, normals) @@ -839,27 +843,27 @@ def repeat_sources( ): """Transform a vtksource to glyph.""" if source is None and faces is None: - raise IOError('A source or faces should be defined') + raise IOError("A source or faces should be defined") if np.array(colors).ndim == 1: colors = np.tile(colors, (len(centers), 1)) pts = numpy_to_vtk_points(np.ascontiguousarray(centers)) cols = numpy_to_vtk_colors(255 * np.ascontiguousarray(colors)) - cols.SetName('colors') + cols.SetName("colors") if isinstance(active_scalars, (float, int)): active_scalars = np.tile(active_scalars, (len(centers), 1)) if isinstance(active_scalars, np.ndarray): ascalars = numpy_support.numpy_to_vtk( np.asarray(active_scalars), deep=True, array_type=VTK_DOUBLE ) - ascalars.SetName('active_scalars') + ascalars.SetName("active_scalars") if directions is not None: directions_fa = numpy_support.numpy_to_vtk( np.asarray(directions), deep=True, array_type=VTK_DOUBLE ) - directions_fa.SetName('directions') + directions_fa.SetName("directions") polydata_centers = PolyData() polydata_geom = PolyData() @@ -874,10 +878,10 @@ def repeat_sources( if directions is not None: polydata_centers.GetPointData().AddArray(directions_fa) - polydata_centers.GetPointData().SetActiveVectors('directions') + polydata_centers.GetPointData().SetActiveVectors("directions") if isinstance(active_scalars, np.ndarray): polydata_centers.GetPointData().AddArray(ascalars) - polydata_centers.GetPointData().SetActiveScalars('active_scalars') + polydata_centers.GetPointData().SetActiveScalars("active_scalars") glyph = Glyph3D() if faces is None: @@ -900,7 +904,7 @@ def repeat_sources( mapper = PolyDataMapper() mapper.SetInputData(glyph.GetOutput()) mapper.SetScalarModeToUsePointFieldData() - mapper.SelectColorArray('colors') + mapper.SelectColorArray("colors") actor = Actor() actor.SetMapper(mapper) @@ -920,6 +924,7 @@ def apply_affine_to_actor(act, affine): Returns ------- transformed_act: Actor + """ act.SetUserMatrix(numpy_to_vtk_matrix(affine)) return act @@ -997,7 +1002,7 @@ def apply_affine(aff, pts): def asbytes(s): if isinstance(s, bytes): return s - return s.encode('latin1') + return s.encode("latin1") def vtk_matrix_to_numpy(matrix): @@ -1027,7 +1032,7 @@ def numpy_to_vtk_matrix(array): elif array.shape == (3, 3): matrix = Matrix3x3() else: - raise ValueError('Invalid matrix shape: {0}'.format(array.shape)) + raise ValueError("Invalid matrix shape: {0}".format(array.shape)) for i in range(array.shape[0]): for j in range(array.shape[1]): @@ -1082,12 +1087,12 @@ def get_grid_cells_position(shapes, aspect_ratio=16 / 9.0, dim=None): n_rows, n_cols = dim if n_cols * n_rows < count: - msg = 'Size is too small, it cannot contain at least {} elements.' + msg = "Size is too small, it cannot contain at least {} elements." raise ValueError(msg.format(count)) # Use indexing="xy" so the cells are in row-major (C-order). Also, # the Y coordinates are negative so the cells are order from top to bottom. - X, Y, Z = np.meshgrid(np.arange(n_cols), -np.arange(n_rows), [0], indexing='xy') + X, Y, Z = np.meshgrid(np.arange(n_cols), -np.arange(n_rows), [0], indexing="xy") return cell_shape * np.array([X.flatten(), Y.flatten(), Z.flatten()]).T @@ -1156,11 +1161,11 @@ def rgb_to_vtk(data): grid.SetDimensions(data.shape[1], data.shape[0], 1) nd = data.shape[-1] vtkarr = numpy_support.numpy_to_vtk( - np.flip(data.swapaxes(0, 1), axis=1).reshape((-1, nd), order='F') + np.flip(data.swapaxes(0, 1), axis=1).reshape((-1, nd), order="F") ) - vtkarr.SetName('Image') + vtkarr.SetName("Image") grid.GetPointData().AddArray(vtkarr) - grid.GetPointData().SetActiveScalars('Image') + grid.GetPointData().SetActiveScalars("Image") grid.GetPointData().Update() return grid @@ -1169,7 +1174,7 @@ def normalize_v3(arr): """Normalize a numpy array of 3 component vectors shape=(N, 3). Parameters - ----------- + ---------- array : ndarray Shape (N, 3) @@ -1334,7 +1339,7 @@ def vertices_from_actor(actor, as_vtk=False): return numpy_support.vtk_to_numpy(vtk_array) -def colors_from_actor(actor, array_name='colors', as_vtk=False): +def colors_from_actor(actor, array_name="colors", as_vtk=False): """Access colors from actor which uses polydata. Parameters @@ -1499,6 +1504,7 @@ def represent_actor_as_wireframe(actor): Returns ------- actor : actor + """ return actor.GetProperty().SetRepresentationToWireframe() @@ -1511,6 +1517,7 @@ def update_surface_actor_colors(actor, colors): actor : surface actor colors : ndarray of shape (N, 3) having colors. The colors should be in the range [0, 1]. + """ actor.GetMapper().GetInput().GetPointData().SetScalars( numpy_to_vtk_colors(255 * colors) @@ -1518,8 +1525,7 @@ def update_surface_actor_colors(actor, colors): def color_check(pts_len, colors=None): - """ - Returns a VTK scalar array containing colors information for each one of + """Returns a VTK scalar array containing colors information for each one of the points according to the policy defined by the parameter colors. Parameters @@ -1557,7 +1563,7 @@ def color_check(pts_len, colors=None): opacities = np.unique(colors[:, 3]) global_opacity = opacities[0] if len(opacities) == 1 else -1 color_array = numpy_to_vtk_colors(255 * colors) - color_array.SetName('colors') + color_array.SetName("colors") return color_array, global_opacity @@ -1569,8 +1575,9 @@ def is_ui(actor): ---------- actor: :class: `UI` or `vtkProp3D` actor that is to be checked + """ - return all([hasattr(actor, attr) for attr in ['add_to_scene', '_setup']]) + return all(hasattr(actor, attr) for attr in ["add_to_scene", "_setup"]) def set_actor_origin(actor, center=None): @@ -1583,6 +1590,7 @@ def set_actor_origin(actor, center=None): center: ndarray, optional, default: None The new center position. If `None`, the origin will be set to the mean of the actor's vertices. + """ vertices = vertices_from_actor(actor) if center is None: diff --git a/fury/window.py b/fury/window.py index 649a3bf07..7c36cedf3 100644 --- a/fury/window.py +++ b/fury/window.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- import gzip -import time -import fury.animation as anim from tempfile import TemporaryDirectory as InTemporaryDirectory from threading import Lock +import time from warnings import warn import numpy as np from scipy import ndimage from fury import __version__ as fury_version +import fury.animation as anim from fury.interactor import CustomInteractorStyle from fury.io import load_image, save_image from fury.lib import ( @@ -32,7 +32,7 @@ from fury.utils import asbytes try: - basestring + _ = basestring except NameError: basestring = str @@ -83,7 +83,9 @@ def skybox(self, visible=True, gamma_correct=True): else: self.rm(self.__skybox_actor) else: - warn('Scene created without a skybox. Nothing to show or hide.') + warn( + "Scene created without a skybox. Nothing to show or hide.", stacklevel=2 + ) def add(self, *actors): """Add an actor to the scene.""" @@ -92,7 +94,7 @@ def add(self, *actors): self.AddVolume(actor) elif isinstance(actor, Actor2D): self.AddActor2D(actor) - elif hasattr(actor, 'add_to_scene'): + elif hasattr(actor, "add_to_scene"): actor.add_to_scene(self) else: self.AddActor(actor) @@ -110,7 +112,7 @@ def rm_all(self): """Remove all actors from the scene.""" self.RemoveAllViewProps() - def projection(self, proj_type='perspective'): + def projection(self, proj_type="perspective"): """Decide between parallel or perspective projection. Parameters @@ -119,7 +121,7 @@ def projection(self, proj_type='perspective'): Can be 'parallel' or 'perspective' (default). """ - if proj_type == 'parallel': + if proj_type == "parallel": self.GetActiveCamera().ParallelProjectionOn() else: self.GetActiveCamera().ParallelProjectionOff() @@ -173,10 +175,10 @@ def get_camera(self): def camera_info(self): """Return Camera information.""" cam = self.camera() - print('# Active Camera') - print(' Position (%.2f, %.2f, %.2f)' % cam.GetPosition()) - print(' Focal Point (%.2f, %.2f, %.2f)' % cam.GetFocalPoint()) - print(' View Up (%.2f, %.2f, %.2f)' % cam.GetViewUp()) + print("# Active Camera") + print(" Position (%.2f, %.2f, %.2f)" % cam.GetPosition()) + print(" Focal Point (%.2f, %.2f, %.2f)" % cam.GetFocalPoint()) + print(" View Up (%.2f, %.2f, %.2f)" % cam.GetViewUp()) def set_camera(self, position=None, focal_point=None, view_up=None): """Set up camera position / Focal Point / View Up.""" @@ -276,7 +278,6 @@ def camera_direction(self): @property def last_render_time(self): """Returns the last render time in seconds.""" - return self.GetLastRenderTimeInSeconds() def fxaa_on(self): @@ -292,13 +293,13 @@ class ShowManager: def __init__( self, scene=None, - title='FURY', + title="FURY", size=(300, 300), png_magnify=1, reset_camera=True, order_transparent=False, - interactor_style='custom', - stereo='off', + interactor_style="custom", + stereo="off", multi_samples=8, max_peels=4, occlusion_ratio=0.0, @@ -387,7 +388,7 @@ def __init__( self.window = RenderWindow() - if self.stereo.lower() != 'off': + if self.stereo.lower() != "off": enable_stereo(self.window, self.stereo) self.window.AddRenderer(scene) @@ -404,11 +405,11 @@ def __init__( occlusion_ratio=occlusion_ratio, ) - if self.interactor_style == 'image': + if self.interactor_style == "image": self.style = InteractorStyleImage() - elif self.interactor_style == 'trackball': + elif self.interactor_style == "trackball": self.style = InteractorStyleTrackballCamera() - elif self.interactor_style == 'custom': + elif self.interactor_style == "custom": self.style = CustomInteractorStyle() else: self.style = interactor_style @@ -435,6 +436,7 @@ def timelines(self): ------- list[Timeline]: List of Timelines. + """ return self._timelines @@ -446,6 +448,7 @@ def animations(self): ------- list[Animation]: List of Animations. + """ return self._animations @@ -460,6 +463,7 @@ def add_animation(self, animation): ---------- animation : Animation or Timeline The Animation or Timeline to be added to the ShowManager. + """ animation.add_to_scene(self.scene) if isinstance(animation, anim.Animation): @@ -491,8 +495,8 @@ def remove_animation(self, animation): ---------- animation : Animation or Timeline The Timeline to be removed. - """ + """ if animation in self.timelines or animation in self.animations: animation.remove_from_scene(self.scene) if isinstance(animation, anim.Animation): @@ -530,8 +534,8 @@ def start(self, multithreaded=False, desired_fps=60): """ try: - if self.title.upper() == 'FURY': - self.window.SetWindowName(self.title + ' ' + fury_version) + if self.title.upper() == "FURY": + self.window.SetWindowName(self.title + " " + fury_version) else: self.window.SetWindowName(self.title) if multithreaded: @@ -563,8 +567,8 @@ def start(self, multithreaded=False, desired_fps=60): interactor_style=self.interactor_style, ) self.render() - if self.title.upper() == 'FURY': - self.window.SetWindowName(self.title + ' ' + fury_version) + if self.title.upper() == "FURY": + self.window.SetWindowName(self.title + " " + fury_version) else: self.window.SetWindowName(self.title) self.iren.Start() @@ -590,10 +594,12 @@ def lock_current(self): Returns ------- successful : bool - Returns if the lock was acquired.""" + Returns if the lock was acquired. + + """ if self.is_done(): return False - if not hasattr(self, 'window'): + if not hasattr(self, "window"): return False try: self.lock() @@ -635,7 +641,7 @@ def record_events(self): """ with InTemporaryDirectory(): - filename = 'recorded_events.log' + filename = "recorded_events.log" recorder = InteractorEventRecorder() recorder.SetInteractor(self.iren) recorder.SetFileName(filename) @@ -645,7 +651,7 @@ def _stop_recording_and_close(_obj, _evt): recorder.Stop() self.iren.TerminateApp() - self.iren.AddObserver('ExitEvent', _stop_recording_and_close) + self.iren.AddObserver("ExitEvent", _stop_recording_and_close) recorder.EnabledOn() recorder.Record() @@ -656,11 +662,11 @@ def _stop_recording_and_close(_obj, _evt): # to close the file. recorder = None # Retrieved recorded events. - with open(filename, 'r') as f: + with open(filename, "r") as f: events = f.read() return events - def record_events_to_file(self, filename='record.log'): + def record_events_to_file(self, filename="record.log"): """Record events during the interaction. The recording is represented as a list of VTK events @@ -676,11 +682,11 @@ def record_events_to_file(self, filename='record.log'): events = self.record_events() # Compress file if needed - if filename.endswith('.gz'): - with gzip.open(filename, 'wb') as fgz: + if filename.endswith(".gz"): + with gzip.open(filename, "wb") as fgz: fgz.write(asbytes(events)) else: - with open(filename, 'w') as f: + with open(filename, "w") as f: f.write(events) def play_events(self, events): @@ -729,8 +735,8 @@ def play_events_from_file(self, filename): """ # Uncompress file if needed. - if filename.endswith('.gz'): - with gzip.open(filename, 'r') as f: + if filename.endswith(".gz"): + with gzip.open(filename, "r") as f: events = f.read() else: with open(filename) as f: @@ -746,7 +752,7 @@ def add_window_callback(self, win_callback, event=Command.ModifiedEvent): def add_timer_callback(self, repeat, duration, timer_callback): if not self.iren.GetInitialized(): self.initialize() - self.iren.AddObserver('TimerEvent', timer_callback) + self.iren.AddObserver("TimerEvent", timer_callback) if repeat: timer_id = self.iren.CreateRepeatingTimer(duration) @@ -755,7 +761,7 @@ def add_timer_callback(self, repeat, duration, timer_callback): self.timers.append(timer_id) return timer_id - def add_iren_callback(self, iren_callback, event='MouseMoveEvent'): + def add_iren_callback(self, iren_callback, event="MouseMoveEvent"): if not self.iren.GetInitialized(): self.initialize() self.iren.AddObserver(event, iren_callback) @@ -770,7 +776,6 @@ def destroy_timers(self): def exit(self): """Close window and terminate interactor.""" - # if is_osx and self.timers: # OSX seems to not destroy correctly timers # segfault 11 appears sometimes if we do not do it manually. @@ -826,12 +831,12 @@ def save_screenshot(self, fname, magnification=1, size=None, stereo=None): def show( scene, - title='FURY', + title="FURY", size=(300, 300), png_magnify=1, reset_camera=True, order_transparent=False, - stereo='off', + stereo="off", multi_samples=8, max_peels=4, occlusion_ratio=0.0, @@ -839,7 +844,7 @@ def show( """Show window with current scene. Parameters - ------------ + ---------- scene : Scene() or vtkRenderer() The scene that holds all the actors. title : string @@ -880,7 +885,7 @@ def show( Occlusion ration for depth peeling (Default 0 - exact image). Examples - ---------- + -------- >>> import numpy as np >>> from fury import window, actor >>> r = window.Scene() @@ -892,8 +897,8 @@ def show( >>> r.add(l) >>> #window.show(r) - See also - --------- + See Also + -------- fury.window.record fury.window.snapshot @@ -927,7 +932,7 @@ def record( size=(300, 300), reset_camera=True, screen_clip=False, - stereo='off', + stereo="off", verbose=False, ): """Record a video of your scene. @@ -936,7 +941,7 @@ def record( azimuth angle az_angle in every frame. Parameters - ----------- + ---------- scene : Scene() or vtkRenderer() object Scene instance cam_pos : None or sequence (3,), optional @@ -984,7 +989,7 @@ def record( print information about the camera. Default is False. Examples - --------- + -------- >>> from fury import window, actor >>> scene = window.Scene() >>> a = actor.axes() @@ -1009,7 +1014,7 @@ def record( if reset_camera: scene.ResetCamera() - if stereo.lower() != 'off': + if stereo.lower() != "off": enable_stereo(renWin, stereo) renderLarge = RenderLargeImage() @@ -1031,9 +1036,9 @@ def record( cam = scene.GetActiveCamera() if verbose: - print('Camera Position (%.2f, %.2f, %.2f)' % cam.GetPosition()) - print('Camera Focal Point (%.2f, %.2f, %.2f)' % cam.GetFocalPoint()) - print('Camera View Up (%.2f, %.2f, %.2f)' % cam.GetViewUp()) + print("Camera Position (%.2f, %.2f, %.2f)" % cam.GetPosition()) + print("Camera Focal Point (%.2f, %.2f, %.2f)" % cam.GetFocalPoint()) + print("Camera View Up (%.2f, %.2f, %.2f)" % cam.GetViewUp()) for i in range(n_frames): scene.GetActiveCamera().Azimuth(ang) @@ -1044,12 +1049,12 @@ def record( if path_numbering: if out_path is None: - filename = str(i).zfill(6) + '.png' + filename = str(i).zfill(6) + ".png" else: - filename = out_path + str(i).zfill(6) + '.png' + filename = out_path + str(i).zfill(6) + ".png" else: if out_path is None: - filename = 'fury.png' + filename = "fury.png" else: filename = out_path @@ -1117,18 +1122,17 @@ def snapshot( size=(300, 300), offscreen=True, order_transparent=False, - stereo='off', + stereo="off", multi_samples=8, max_peels=4, occlusion_ratio=0.0, dpi=(72, 72), render_window=None, ): - """Save a snapshot of the scene in a file or in memory. Parameters - ----------- + ---------- scene : Scene() or vtkRenderer Scene instance fname : str or None @@ -1179,7 +1183,7 @@ def snapshot( render_window = RenderWindow() if offscreen: render_window.SetOffScreenRendering(1) - if stereo.lower() != 'off': + if stereo.lower() != "off": enable_stereo(render_window, stereo) render_window.AddRenderer(scene) render_window.SetSize(width, height) @@ -1269,10 +1273,10 @@ class ReportSnapshot: colors_found = False def __str__(self): - msg = 'Report:\n-------\n' - msg += 'objects: {}\n'.format(self.objects) - msg += 'labels: \n{}\n'.format(self.labels) - msg += 'colors_found: {}\n'.format(self.colors_found) + msg = "Report:\n-------\n" + msg += "objects: {}\n".format(self.objects) + msg += "labels: \n{}\n".format(self.labels) + msg += "colors_found: {}\n".format(self.colors_found) return msg report = ReportSnapshot() @@ -1281,7 +1285,7 @@ def __str__(self): if isinstance(colors, tuple): colors = [colors] flags = [False] * len(colors) - for (i, col) in enumerate(colors): + for i, col in enumerate(colors): # find if the current color exist in the array flags[i] = np.any(np.any(np.all(np.equal(im[..., :3], col[:3]), axis=-1))) @@ -1330,17 +1334,20 @@ def enable_stereo(renwin, stereo_type): # stereo type ints from # https://gitlab.kitware.com/vtk/vtk/blob/master/Rendering/Core/vtkRenderWindow.h#L57 stereo_type_dictionary = { - 'opengl': 1, - 'interlaced': 3, - 'anaglyph': 7, - 'checkerboard': 8, - 'horizontal': 9, + "opengl": 1, + "interlaced": 3, + "anaglyph": 7, + "checkerboard": 8, + "horizontal": 9, } # default to horizontal since it is easy to see if it is working if stereo_type not in stereo_type_dictionary: - warn('Unknown stereo type provided. ' "Setting stereo type to 'horizontal'.") - stereo_type = 'horizontal' + warn( + "Unknown stereo type provided. " "Setting stereo type to 'horizontal'.", + stacklevel=2, + ) + stereo_type = "horizontal" renwin.SetStereoType(stereo_type_dictionary[stereo_type]) @@ -1388,7 +1395,7 @@ def gl_enable_depth(gl_state): gl_state : vtkOpenGLState """ - gl_state.vtkglEnable(_GL['GL_DEPTH_TEST']) + gl_state.vtkglEnable(_GL["GL_DEPTH_TEST"]) def gl_disable_depth(gl_state): @@ -1399,7 +1406,7 @@ def gl_disable_depth(gl_state): gl_state : vtkOpenGLState """ - gl_state.vtkglDisable(_GL['GL_DEPTH_TEST']) + gl_state.vtkglDisable(_GL["GL_DEPTH_TEST"]) def gl_enable_blend(gl_state): @@ -1410,7 +1417,7 @@ def gl_enable_blend(gl_state): gl_state : vtkOpenGLState """ - gl_state.vtkglEnable(_GL['GL_BLEND']) + gl_state.vtkglEnable(_GL["GL_BLEND"]) def gl_disable_blend(gl_state): @@ -1428,8 +1435,8 @@ def gl_disable_blend(gl_state): """ # noqa - gl_state.vtkglDisable(_GL['GL_CULL_FACE']) - gl_state.vtkglDisable(_GL['GL_BLEND']) + gl_state.vtkglDisable(_GL["GL_CULL_FACE"]) + gl_state.vtkglDisable(_GL["GL_BLEND"]) def gl_set_additive_blending(gl_state): @@ -1441,9 +1448,9 @@ def gl_set_additive_blending(gl_state): """ gl_reset_blend(gl_state) - gl_state.vtkglEnable(_GL['GL_BLEND']) - gl_state.vtkglDisable(_GL['GL_DEPTH_TEST']) - gl_state.vtkglBlendFunc(_GL['GL_SRC_ALPHA'], _GL['GL_ONE']) + gl_state.vtkglEnable(_GL["GL_BLEND"]) + gl_state.vtkglDisable(_GL["GL_DEPTH_TEST"]) + gl_state.vtkglBlendFunc(_GL["GL_SRC_ALPHA"], _GL["GL_ONE"]) def gl_set_additive_blending_white_background(gl_state): @@ -1455,13 +1462,13 @@ def gl_set_additive_blending_white_background(gl_state): """ gl_reset_blend(gl_state) - gl_state.vtkglEnable(_GL['GL_BLEND']) - gl_state.vtkglDisable(_GL['GL_DEPTH_TEST']) + gl_state.vtkglEnable(_GL["GL_BLEND"]) + gl_state.vtkglDisable(_GL["GL_DEPTH_TEST"]) gl_state.vtkglBlendFuncSeparate( - _GL['GL_SRC_ALPHA'], - _GL['GL_ONE_MINUS_SRC_ALPHA'], - _GL['GL_ONE'], - _GL['GL_ZERO'], + _GL["GL_SRC_ALPHA"], + _GL["GL_ONE_MINUS_SRC_ALPHA"], + _GL["GL_ONE"], + _GL["GL_ZERO"], ) @@ -1473,14 +1480,14 @@ def gl_set_normal_blending(gl_state): gl_state : vtkOpenGLState """ - gl_state.vtkglEnable(_GL['GL_BLEND']) - gl_state.vtkglEnable(_GL['GL_DEPTH_TEST']) - gl_state.vtkglBlendFunc(_GL['GL_ONE'], _GL['GL_ONE']) + gl_state.vtkglEnable(_GL["GL_BLEND"]) + gl_state.vtkglEnable(_GL["GL_DEPTH_TEST"]) + gl_state.vtkglBlendFunc(_GL["GL_ONE"], _GL["GL_ONE"]) gl_state.vtkglBlendFuncSeparate( - _GL['GL_SRC_ALPHA'], - _GL['GL_ONE_MINUS_SRC_ALPHA'], - _GL['GL_ONE'], - _GL['GL_ONE_MINUS_SRC_ALPHA'], + _GL["GL_SRC_ALPHA"], + _GL["GL_ONE_MINUS_SRC_ALPHA"], + _GL["GL_ONE"], + _GL["GL_ONE_MINUS_SRC_ALPHA"], ) @@ -1493,7 +1500,7 @@ def gl_set_multiplicative_blending(gl_state): """ gl_reset_blend(gl_state) - gl_state.vtkglBlendFunc(_GL['GL_ZERO'], _GL['GL_SRC_COLOR']) + gl_state.vtkglBlendFunc(_GL["GL_ZERO"], _GL["GL_SRC_COLOR"]) def gl_set_subtractive_blending(gl_state): @@ -1505,7 +1512,7 @@ def gl_set_subtractive_blending(gl_state): """ gl_reset_blend(gl_state) - gl_state.vtkglBlendFunc(_GL['GL_ZERO'], _GL['GL_ONE_MINUS_SRC_COLOR']) + gl_state.vtkglBlendFunc(_GL["GL_ZERO"], _GL["GL_ONE_MINUS_SRC_COLOR"]) def release_context(window): diff --git a/pyproject.toml b/pyproject.toml index b674d4aff..87a8a26bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ doc = [ "texext", "tomli; python_version < \"3.11\"", ] -style = ["flake8", "blue", "isort", "pre-commit"] +style = ["ruff", "pre-commit"] typing = ["mypy", "types-Pillow", "data-science-types"] test = [ "coverage", @@ -97,21 +97,6 @@ raw-options = { version_scheme = "release-branch-semver" } [tool.hatch.build.hooks.vcs] version-file = "fury/_version.py" -[tool.blue] -line_length = 88 -target-version = ["py38"] -force-exclude = """ -( - _version.py - *docs/experimental/* -) -""" - -[tool.isort] -profile = "black" -line_length = 88 -extend_skip = ["_version.py", "externals", "*docs/experimental*"] - [tool.mypy] python_version = "3.11" ignore_missing_imports = "True" diff --git a/requirements/README.md b/requirements/README.md index a1f1052e3..fd6b436db 100644 --- a/requirements/README.md +++ b/requirements/README.md @@ -12,8 +12,8 @@ ### Installing requirements ```bash -$ pip install -U -r requirements/default.txt -$ pip install -U -r requirements/optional.txt +pip install -U -r requirements/default.txt +pip install -U -r requirements/optional.txt ``` or @@ -25,8 +25,8 @@ conda install --yes --file=requirements/default.txt --file=requirements/optional ### Running the tests ```bash -$ pip install -U -r requirements/default.txt -$ pip install -U -r requirements/test.txt +pip install -U -r requirements/default.txt +pip install -U -r requirements/test.txt ``` or @@ -38,7 +38,7 @@ conda install --yes --file=requirements/default.txt --file=requirements/test.txt ### Running the Docs ```bash -$ pip install -U -r requirements/default.txt -$ pip install -U -r requirements/optional.txt -$ pip install -U -r requirements/docs.txt +pip install -U -r requirements/default.txt +pip install -U -r requirements/optional.txt +pip install -U -r requirements/docs.txt ``` diff --git a/requirements/docs.txt b/requirements/docs.txt index f664c809a..312b9d259 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -4,7 +4,7 @@ ipython matplotlib sphinx-copybutton sphinx-gallery>=0.10.0 -pydata-sphinx-theme==0.12.0 +pydata-sphinx-theme==0.15.2 tomli>=2.0.1 networkx ablog >=0.11.1 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..f69934243 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,44 @@ +target-version = "py310" +line-length = 88 +force-exclude = true +extend-exclude = [ + "__pycache__", + "build", + "_version.py", + "docs/experimental/**", +] + +[lint] +select = [ + "F", + "E", + "C", + "W", + "B", + "I", +] +ignore = [ + "B905", + "C901", + "E203" +] + +[lint.extend-per-file-ignores] +"docs/examples/**" = ["I001"] +"docs/source/conf.py" = ["E402"] + +[lint.flake8-quotes] +inline-quotes = "double" + +[lint.isort] +case-sensitive = true +combine-as-imports = true +force-sort-within-sections = true +known-first-party = ["fury"] +no-sections = false +order-by-type = true +relative-imports-order = "closest-to-furthest" +section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] + +[format] +quote-style = "double"