Skip to content

Commit 6149b1c

Browse files
committed
adding tests, example
1 parent 1c5895b commit 6149b1c

6 files changed

Lines changed: 152 additions & 16 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Sometimes you want more control over data normalization and color-mapping. This
3+
example demonstrates how to do that with BlockCollection data.
4+
"""
5+
6+
import numpy as np
7+
import yt
8+
9+
import yt_idv
10+
from yt_idv.cameras.trackball_camera import TrackballCamera
11+
from yt_idv.scene_components.blocks import BlockRendering
12+
from yt_idv.scene_data.block_collection import BlockCollection
13+
from yt_idv.scene_graph import SceneGraph
14+
15+
# create some data
16+
shp = (32, 32, 32)
17+
data = {"constant_field": np.full(shape=shp, fill_value=5.0)}
18+
ds = yt.load_uniform_grid(data, shp, length_unit=1)
19+
20+
21+
rc = yt_idv.render_context(height=800, width=800, gui=True)
22+
c = TrackballCamera.from_dataset(ds)
23+
rc.scene = SceneGraph(camera=c)
24+
25+
# the following BlockCollection initialization does the following:
26+
# 1. sets the min, max values to be used for data normalization when
27+
# loading textures
28+
# 2. specifies that those min, max vals should not be re-computed
29+
# (compute_min_max=False)
30+
# 3. specifies that the data should always be normalized -- usually
31+
# normalization is skipped for constant fields.
32+
block_coll = BlockCollection(
33+
data_source=ds.all_data(),
34+
min_val=0.0,
35+
max_val=10.0,
36+
compute_min_max=False,
37+
always_normalize=True,
38+
)
39+
block_coll.add_data(("stream", "constant_field"), no_ghost=True)
40+
rc.scene.data_objects.append(block_coll)
41+
42+
# set fixed cmap ranges. Data are in normalized space. A constant data value of 5.
43+
# (the fill value used above) with [min val, max val] of [0., 10.] normalizes to 0.5.
44+
# So setting the fixed cmap min and max to 0 and 1 will result in a constant color
45+
# at the middle of whatever colormap is selected.
46+
block_rendering = BlockRendering(
47+
data=block_coll,
48+
fixed_cmap_min=0.0,
49+
fixed_cmap_max=1.0,
50+
cmap_log=False,
51+
)
52+
53+
rc.scene.components.append(block_rendering)
54+
55+
rc.run()

yt_idv/scene_components/base_component.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ class SceneComponent(traitlets.HasTraits):
7777
_final_pass_invalid = True
7878

7979
# These attributes are just for colormap application
80+
fixed_cmap_min = traitlets.CFloat(None, allow_none=True)
81+
fixed_cmap_max = traitlets.CFloat(None, allow_none=True)
8082
cmap_min = traitlets.CFloat(None, allow_none=True)
8183
cmap_max = traitlets.CFloat(None, allow_none=True)
8284
cmap_log = traitlets.Bool(True)
@@ -584,16 +586,27 @@ def _construct_isolayer_table(self, imgui) -> bool:
584586
return changed
585587

586588
def _reset_cmap_bounds(self, print_new_bounds=True):
589+
587590
data = self.fb.data
588591
if self.use_db:
589592
data[:, :, :3] = self.fb.depth_data[:, :, None]
590593
data = data[data[:, :, 3] > 0][:, 0]
591594
if data.size > 0:
592595
self.cmap_min = data.min()
593596
self.cmap_max = data.max()
597+
594598
if data.size == 0:
595599
self.cmap_min = 0.0
596600
self.cmap_max = 1.0
597-
elif print_new_bounds:
601+
602+
# over-ride with the fixed values if they're set
603+
if self.fixed_cmap_max is not None:
604+
self.cmap_max = self.fixed_cmap_max
605+
606+
if self.fixed_cmap_min is not None:
607+
self.cmap_min = self.fixed_cmap_min
608+
609+
if print_new_bounds:
598610
print(f"Computed new cmap values {self.cmap_min} - {self.cmap_max}")
611+
599612
self._cmap_bounds_invalid = False

yt_idv/scene_data/block_collection.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class BlockCollection(SceneData):
1818
blocks_by_grid = traitlets.Instance(defaultdict, (list,))
1919
grids_by_block = traitlets.Dict(default_value=())
2020
_yt_geom_str = traitlets.Unicode("cartesian")
21-
_compute_min_max = traitlets.Bool(True)
21+
compute_min_max = traitlets.Bool(True)
22+
always_normalize = traitlets.Bool(False)
2223

2324
@traitlets.default("vertex_array")
2425
def _default_vertex_array(self):
@@ -78,7 +79,7 @@ def add_data(self, field, no_ghost=False):
7879
self.blocks_by_grid[g.id - g._id_offset].append((id(block), gi))
7980
self.grids_by_block[id(node.data)] = (g.id - g._id_offset, sl)
8081

81-
if self._compute_min_max:
82+
if self.compute_min_max:
8283
if hasattr(min_val, "in_units"):
8384
min_val = min_val.d
8485
if hasattr(max_val, "in_units"):
@@ -220,7 +221,7 @@ def _load_textures(self):
220221
vbo_i, block = self.blocks[block_id]
221222
n_data = np.abs(block.my_data[0]).copy(order="F").astype("float32").d
222223
# Avoid setting to NaNs
223-
if self.max_val != self.min_val:
224+
if self.max_val != self.min_val or self.always_normalize:
224225
n_data = self._normalize_by_min_max(n_data)
225226
# blocks filled with identically 0 values will be
226227
# skipped by the shader, so offset by a tiny value.

yt_idv/tests/conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import yt
66
from pytest_html import extras as html_extras
77

8+
import yt_idv
9+
810

911
def pytest_configure(config):
1012
# this will get run before all tests, before collection and
@@ -22,3 +24,12 @@ def _snap_image(rc):
2224
extras.append(html_extras.html("<br clear='all'/>"))
2325

2426
return _snap_image
27+
28+
29+
@pytest.fixture()
30+
def osmesa_empty_rc():
31+
"""yield an OSMesa empy context then destroy"""
32+
33+
rc = yt_idv.render_context("osmesa", width=1024, height=1024)
34+
yield rc
35+
rc.osmesa.OSMesaDestroyContext(rc.context)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import numpy as np
2+
import pytest
3+
import yt
4+
5+
from yt_idv.cameras.trackball_camera import TrackballCamera
6+
from yt_idv.scene_components.blocks import BlockRendering
7+
from yt_idv.scene_data.block_collection import BlockCollection
8+
from yt_idv.scene_graph import SceneGraph
9+
10+
11+
@pytest.fixture()
12+
def ds_yt_ugrid():
13+
# create some data
14+
shp = (8, 8, 8)
15+
data = {"constant_field": np.full(shape=shp, fill_value=5.0)}
16+
ds = yt.load_uniform_grid(data, shp, length_unit=1)
17+
return ds
18+
19+
20+
def test_block_collection_normalization(osmesa_empty_rc, ds_yt_ugrid):
21+
22+
block_coll: BlockCollection = BlockCollection(
23+
data_source=ds_yt_ugrid.all_data(),
24+
min_val=0.0,
25+
max_val=10.0,
26+
compute_min_max=False,
27+
always_normalize=True,
28+
)
29+
block_coll.add_data(("stream", "constant_field"), no_ghost=True)
30+
31+
assert np.allclose(block_coll.texture_objects[0].data, 0.5)
32+
33+
34+
def test_block_rendering_cmap_norms(osmesa_empty_rc, ds_yt_ugrid, image_store):
35+
36+
block_coll: BlockCollection = BlockCollection(
37+
data_source=ds_yt_ugrid.all_data(),
38+
min_val=0.0,
39+
max_val=10.0,
40+
compute_min_max=False,
41+
always_normalize=True,
42+
)
43+
block_coll.add_data(("stream", "constant_field"), no_ghost=True)
44+
45+
block_rendering: BlockRendering = BlockRendering(
46+
data=block_coll,
47+
fixed_cmap_min=0.2,
48+
fixed_cmap_max=0.8,
49+
cmap_log=False,
50+
)
51+
52+
block_rendering._reset_cmap_bounds()
53+
54+
assert block_rendering.cmap_min == 0.2
55+
assert block_rendering.cmap_max == 0.8
56+
57+
block_rendering.fixed_cmap_min = 0.0
58+
block_rendering.fixed_cmap_max = 1.0
59+
block_rendering._reset_cmap_bounds()
60+
assert block_rendering.cmap_min == 0.0
61+
assert block_rendering.cmap_max == 1.0
62+
63+
c = TrackballCamera.from_dataset(ds_yt_ugrid)
64+
osmesa_empty_rc.scene = SceneGraph(camera=c)
65+
osmesa_empty_rc.scene.data_objects.append(block_coll)
66+
osmesa_empty_rc.scene.components.append(block_rendering)
67+
68+
image_store(osmesa_empty_rc)

yt_idv/tests/test_spherical_vol_rendering.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,6 @@
22
import pytest
33
import yt
44

5-
import yt_idv
6-
7-
8-
@pytest.fixture()
9-
def osmesa_empty_rc():
10-
"""yield an OSMesa empy context then destroy"""
11-
12-
rc = yt_idv.render_context("osmesa", width=1024, height=1024)
13-
yield rc
14-
rc.osmesa.OSMesaDestroyContext(rc.context)
15-
16-
175
bbox_options = {
186
"partial": {
197
"bbox": np.array([[0.5, 1.0], [0.0, np.pi / 3], [np.pi / 4, np.pi / 2]]),

0 commit comments

Comments
 (0)