Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added tests/assets/volume/00_startup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/00_startup_github.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/00_startup_github_gpu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/01_update_pwf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/01_update_pwf_github.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/01_update_pwf_github_gpu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/01_update_pwf_gpu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/02_unmount.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/03_remount.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/03_remount_github.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/03_remount_github_gpu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/volume/03_remount_gpu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
141 changes: 126 additions & 15 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def compare_screenshot(page, baseline_image, result_directory, threshold=0

img_test = Image.open(test_image)
img_diff = Image.new("RGBA", img_test.size)
mismatches = []
mismatches = [999]

for ref_file in baseline_image.parent.glob(f"{baseline_image.name}*.png"):
img_ref = Image.open(ref_file)
Expand All @@ -38,23 +38,110 @@ async def compare_screenshot(page, baseline_image, result_directory, threshold=0
return min(mismatches) < threshold


def create_vtk_pipeline():
renderer = vtk.vtkRenderer()
rw = vtk.vtkRenderWindow()
rw.AddRenderer(renderer)
rwi = vtk.vtkRenderWindowInteractor(render_window=rw)
rwi.interactor_style.SetCurrentStyleToTrackballCamera()
MAPPERS = {
"FixedPoint": vtk.vtkFixedPointVolumeRayCastMapper,
"Smart": vtk.vtkSmartVolumeMapper,
"GPU": vtk.vtkOpenGLGPUVolumeRayCastMapper,
"RayCast": vtk.vtkGPUVolumeRayCastMapper,
}

cone = vtk.vtkConeSource()

mapper = vtk.vtkPolyDataMapper(input_connection=cone.output_port)
actor = vtk.vtkActor(mapper=mapper)
class VolumeRendering(TrameApp):
def __init__(
self, server=None, mode="wasm32", exec="sync", rendering="webgl", mapper="Smart"
):
super().__init__(server)
self.state.wasm_conf = {
"mode": mode,
"exec": exec,
"rendering": rendering,
}
print(self.state.wasm_conf)
enable_testing(self.server, "local_volume_rendering_ready")
self._setup_vtk(mapper)
self._build_ui()

renderer.AddActor(actor)
renderer.background = (0.1, 0.2, 0.4)
renderer.ResetCamera()
def _setup_vtk(self, mapper_type):
renderer = vtk.vtkRenderer()
rw = vtk.vtkRenderWindow()
rw.AddRenderer(renderer)
rwi = vtk.vtkRenderWindowInteractor(render_window=rw)
rwi.interactor_style.SetCurrentStyleToTrackballCamera()
renderer.background = (1, 1, 1)

pwf = vtk.vtkPiecewiseFunction()
pwf.AddPoint(20, 0.0)
pwf.AddPoint(255, 0.2)

lut = vtk.vtkColorTransferFunction()
lut.AddRGBPoint(0.0, 0.0, 0.0, 0.0)
lut.AddRGBPoint(64.0, 1.0, 0.0, 0.0)
lut.AddRGBPoint(128.0, 0.0, 0.0, 1.0)
lut.AddRGBPoint(192.0, 0.0, 1.0, 0.0)
lut.AddRGBPoint(255.0, 0.0, 0.2, 0.0)

source = vtk.vtkRTAnalyticSource()

property = vtk.vtkVolumeProperty(color=lut, scalar_opacity=pwf, shade=True)
property.SetColor(lut)
property.SetScalarOpacity(pwf)
property.ShadeOn()
property.SetScalarOpacityUnitDistance(10)
property.SetInterpolationTypeToLinear()

mapper = MAPPERS[mapper_type]()
source >> mapper

volume = vtk.vtkVolume(mapper=mapper, property=property)
renderer.AddVolume(volume)

cube = vtk.vtkCubeAxesActor()
cube.SetCamera(renderer.GetActiveCamera())
cube.SetBounds(source.GetOutput().GetBounds())
renderer.AddActor(cube)

renderer.ResetCamera()

self.render_window = rw
self.pwf = pwf
self.lut = lut

def update_pwf(self):
self.pwf.RemoveAllPoints()
self.pwf.AddPoint(20, 0.0)
self.pwf.AddPoint(150, 1.0)
self.pwf.AddPoint(255, 0.0)
self.ctx.view.update()

return rw, cone
@property
def mounted(self):
return self.state.mounted

@mounted.setter
def mounted(self, v):
with self.state:
self.state.mounted = bool(v)

def _build_ui(self):
self.state.local_volume_rendering_ready = 0
with DivLayout(self.server) as self.ui:
html.Div(
"{{ local_volume_rendering_ready }}",
classes="readyCount",
)
client.Style(
"body { margin: 0; } .readyCount { z-index: 10; position: absolute; left: -100px; top: 0; }"
)
with html.Div(
style=ui.FULL_SCREEN,
v_if=("mounted", True),
):
vtklocal.LocalView(
self.render_window,
ctx_name="view",
config=["wasm_conf"],
updated="local_volume_rendering_ready++",
)


class Cone(TrameApp):
Expand All @@ -66,9 +153,28 @@ def __init__(self, server=None, mode="wasm32", exec="sync", rendering="webgl"):
"rendering": rendering,
}
enable_testing(self.server, "local_rendering_ready")
self.render_window, self.cone = create_vtk_pipeline()
self._setup_vtk()
self._build_ui()

def _setup_vtk(self):
renderer = vtk.vtkRenderer()
rw = vtk.vtkRenderWindow()
rw.AddRenderer(renderer)
rwi = vtk.vtkRenderWindowInteractor(render_window=rw)
rwi.interactor_style.SetCurrentStyleToTrackballCamera()

cone = vtk.vtkConeSource()

mapper = vtk.vtkPolyDataMapper(input_connection=cone.output_port)
actor = vtk.vtkActor(mapper=mapper)

renderer.AddActor(actor)
renderer.background = (0.1, 0.2, 0.4)
renderer.ResetCamera()

self.render_window = rw
self.cone = cone

@property
def resolution(self):
return self.state.resolution
Expand Down Expand Up @@ -136,3 +242,8 @@ def utils():
@pytest.fixture
def ConeApp():
return Cone


@pytest.fixture
def VolumeApp():
return VolumeRendering
99 changes: 99 additions & 0 deletions tests/test_volume_rendering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import asyncio
from pathlib import Path

import pytest
from playwright.async_api import async_playwright, expect

BASELINES = [
Path(__file__).with_name("assets") / "volume" / name
for name in [
"00_startup",
"01_update_pwf",
"02_unmount",
"03_remount",
]
]


@pytest.mark.asyncio
@pytest.mark.parametrize(
"mapper_type",
[
"Smart",
# "FixedPoint",
"GPU",
"RayCast",
],
)
@pytest.mark.parametrize(
"config",
[
("wasm32", "sync", "webgl"),
("wasm32", "async", "webgl"),
("wasm64", "sync", "webgl"),
("wasm64", "async", "webgl"),
("wasm32", "async", "webgpu"),
("wasm64", "async", "webgpu"),
],
)
async def test_volume_rendering(VolumeApp, utils, config, mapper_type):
wasm_mode, wasm_exec, wasm_rendering = config
conf_key = f"{wasm_mode}-{wasm_exec}-{wasm_rendering}-{mapper_type}"
app = VolumeApp(
f"volume-rendering-{conf_key}",
wasm_mode,
wasm_exec,
wasm_rendering,
mapper_type,
)
task = app.server.start(exec_mode="task", port=0)
await app.server.ready
RESULT_BASE = Path(__file__).with_name("results") / "volume" / conf_key
valid_image_comparisons = []

async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.set_viewport_size({"width": 300, "height": 300})

await page.goto(f"http://localhost:{app.server.port}/")
await asyncio.sleep(0.1) # wait for page load
await expect(page.locator(".readyCount")).to_have_text("1")
valid_image_comparisons.append(
await utils.compare_screenshot(
page, BASELINES[0], RESULT_BASE, threshold=0.1
)
)

app.update_pwf()
await expect(page.locator(".readyCount")).to_have_text("2")
valid_image_comparisons.append(
await utils.compare_screenshot(
page, BASELINES[1], RESULT_BASE, threshold=0.1
)
)

app.mounted = False
app.resolution = 4
valid_image_comparisons.append(
await utils.compare_screenshot(
page, BASELINES[2], RESULT_BASE, threshold=0.1
)
)

app.mounted = True
await asyncio.sleep(0.1) # Debounced resize needs complete
await expect(page.locator(".readyCount")).to_have_text("3")
valid_image_comparisons.append(
await utils.compare_screenshot(
page, BASELINES[3], RESULT_BASE, threshold=0.1
)
)

assert all(valid_image_comparisons), "Some images don't match"

# Clean up resource
await browser.close()

await app.server.stop()
await task
Loading