From 0eb1081c8e5485001fa1937fc419b221fdecbaed Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 9 Sep 2024 17:14:27 -0400 Subject: [PATCH 1/2] Add support for ipyvolume volume viewer. --- glue_ar/__init__.py | 2 ++ glue_ar/common/export_options.py | 1 + glue_ar/common/marching_cubes.py | 14 +++++++++-- glue_ar/common/tests/__init__.py | 0 glue_ar/common/voxels.py | 16 ++++++++++--- glue_ar/utils.py | 41 ++++++++++++++++++++++++++++---- 6 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 glue_ar/common/tests/__init__.py diff --git a/glue_ar/__init__.py b/glue_ar/__init__.py index 6e32e16..e1448f4 100644 --- a/glue_ar/__init__.py +++ b/glue_ar/__init__.py @@ -51,7 +51,9 @@ def setup_jupyter(): pass from glue_jupyter.ipyvolume.scatter import IpyvolumeScatterView + from glue_jupyter.ipyvolume.volume import IpyvolumeVolumeView IpyvolumeScatterView.tools = [t for t in IpyvolumeScatterView.tools] + ["save:ar_jupyter"] + IpyvolumeVolumeView.tools = [t for t in IpyvolumeVolumeView.tools] + ["save:ar_jupyter"] def setup(): diff --git a/glue_ar/common/export_options.py b/glue_ar/common/export_options.py index b83460a..3741021 100644 --- a/glue_ar/common/export_options.py +++ b/glue_ar/common/export_options.py @@ -65,6 +65,7 @@ def __call__(self, multiple: bool = False): def adder(export_method: Callable): self.add(layer_state_cls, name, layer_options_state, extensions, multiple, export_method) + return export_method return adder diff --git a/glue_ar/common/marching_cubes.py b/glue_ar/common/marching_cubes.py index 17a3482..54f1eac 100644 --- a/glue_ar/common/marching_cubes.py +++ b/glue_ar/common/marching_cubes.py @@ -40,7 +40,7 @@ def add_isosurface_layer_gltf(builder: GLTFBuilder, (viewer_state.x_min, viewer_state.x_max), (viewer_state.z_min, viewer_state.z_max), ) - resolution = viewer_state.resolution + resolution = getattr(viewer_state, 'resolution', None) or getattr(layer_state, 'max_resolution') x_range = viewer_state.x_max - viewer_state.x_min y_range = viewer_state.y_max - viewer_state.y_min z_range = viewer_state.z_max - viewer_state.z_min @@ -141,7 +141,7 @@ def add_isosurface_layer_usd( (viewer_state.x_min, viewer_state.x_max), (viewer_state.z_min, viewer_state.z_max), ) - resolution = viewer_state.resolution + resolution = getattr(viewer_state, 'resolution', None) or getattr(layer_state, 'max_resolution') x_range = viewer_state.x_max - viewer_state.x_min y_range = viewer_state.y_max - viewer_state.y_min z_range = viewer_state.z_max - viewer_state.z_min @@ -164,3 +164,13 @@ def add_isosurface_layer_usd( points = [tuple((-1 + (index + 0.5) * side) for index, side in zip(pt, clip_sides)) for pt in points] points = [[p[1], p[0], p[2]] for p in points] builder.add_mesh(points, triangles, color_components, alpha) + + +try: + from glue_jupyter.ipyvolume.volume import VolumeLayerState as IPVVolumeLayerState + ar_layer_export.add(IPVVolumeLayerState, "Isosurface", ARIsosurfaceExportOptions, + ("gltf", "glb"), False, add_isosurface_layer_gltf) + ar_layer_export.add(IPVVolumeLayerState, "Isosurface", ARIsosurfaceExportOptions, + ("usda", "usdc"), False, add_isosurface_layer_usd) +except ImportError: + pass diff --git a/glue_ar/common/tests/__init__.py b/glue_ar/common/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glue_ar/common/voxels.py b/glue_ar/common/voxels.py index 493c929..7835656 100644 --- a/glue_ar/common/voxels.py +++ b/glue_ar/common/voxels.py @@ -9,7 +9,7 @@ from glue_ar.common.usd_builder import USDBuilder from glue_ar.common.volume_export_options import ARVoxelExportOptions from glue_ar.usd_utils import material_for_color -from glue_ar.utils import BoundsWithResolution, alpha_composite, frb_for_layer, hex_to_components, \ +from glue_ar.utils import BoundsWithResolution, alpha_composite, frb_for_layer, get_resolution, hex_to_components, \ isomin_for_layer, isomax_for_layer, layer_color, unique_id, xyz_bounds from glue_ar.gltf_utils import add_points_to_bytearray, add_triangles_to_bytearray, \ @@ -26,7 +26,7 @@ def add_voxel_layers_gltf(builder: GLTFBuilder, options: Iterable[ARVoxelExportOptions], bounds: Optional[BoundsWithResolution] = None): - resolution = viewer_state.resolution + resolution = get_resolution(viewer_state) bounds = bounds or xyz_bounds(viewer_state, with_resolution=True) x_range = viewer_state.x_max - viewer_state.x_min y_range = viewer_state.y_max - viewer_state.y_min @@ -167,7 +167,7 @@ def add_voxel_layers_usd(builder: USDBuilder, options: Iterable[ARVoxelExportOptions], bounds: Optional[BoundsWithResolution] = None): - resolution = viewer_state.resolution + resolution = get_resolution(viewer_state) bounds = bounds or xyz_bounds(viewer_state, with_resolution=True) x_range = viewer_state.x_max - viewer_state.x_min y_range = viewer_state.y_max - viewer_state.y_min @@ -242,3 +242,13 @@ def add_voxel_layers_usd(builder: USDBuilder, builder.add_translated_reference(mesh, translation, material) return builder + + +try: + from glue_jupyter.ipyvolume.volume import VolumeLayerState as IPVVolumeLayerState + ar_layer_export.add(IPVVolumeLayerState, "Voxel", ARVoxelExportOptions, + ("gltf", "glb"), True, add_voxel_layers_gltf) + ar_layer_export.add(IPVVolumeLayerState, "Voxel", ARVoxelExportOptions, + ("usda", "usdc"), True, add_voxel_layers_usd) +except ImportError: + pass diff --git a/glue_ar/utils.py b/glue_ar/utils.py index 4387f34..645efb0 100644 --- a/glue_ar/utils.py +++ b/glue_ar/utils.py @@ -8,8 +8,9 @@ from glue.viewers.common.viewer import LayerArtist, Viewer from glue_vispy_viewers.common.layer_state import LayerState, VispyLayerState +from glue_vispy_viewers.volume.volume_viewer import VispyVolumeViewerMixin from glue_vispy_viewers.volume.layer_state import VolumeLayerState -from glue_vispy_viewers.volume.viewer_state import Vispy3DViewerState +from glue_vispy_viewers.volume.viewer_state import Vispy3DViewerState, Vispy3DVolumeViewerState from numpy import array, inf, isnan, ndarray try: @@ -81,7 +82,8 @@ def xyz_bounds(viewer_state: Viewer3DState, with_resolution: bool) -> Union[Boun (viewer_state.y_min, viewer_state.y_max), (viewer_state.z_min, viewer_state.z_max)] if with_resolution: - return [(*b, viewer_state.resolution) for b in bounds] + resolution = get_resolution(viewer_state) + return [(*b, resolution) for b in bounds] return bounds @@ -110,7 +112,8 @@ def bounds_3d_from_layers(viewer_state: Viewer3DState, maxes = [max(max(data[att]), m) for m, att in zip(maxes, atts)] bounds = [(lo, hi) for lo, hi in zip(mins, maxes)] if with_resolution: - return [(*b, viewer_state.resolution) for b in bounds] + resolution = get_resolution(viewer_state) + return [(*b, resolution) for b in bounds] return bounds @@ -216,8 +219,9 @@ def frb_for_layer(viewer_state: ViewerState, data = data_for_layer(layer_or_state) layer_state = layer_or_state if isinstance(layer_or_state, LayerState) else layer_or_state.state is_data_layer = data is layer_or_state.layer + target_data = getattr(viewer_state, 'reference_data', data) data_frb = data.compute_fixed_resolution_buffer( - target_data=viewer_state.reference_data, + target_data=target_data, bounds=bounds, target_cid=layer_state.attribute ) @@ -226,7 +230,7 @@ def frb_for_layer(viewer_state: ViewerState, return data_frb else: subcube = data.compute_fixed_resolution_buffer( - target_data=viewer_state.reference_data, + target_data=target_data, bounds=bounds, subset_state=layer_state.layer.subset_state ) @@ -247,3 +251,30 @@ def iterator_count(iter: Iterator) -> int: Note that this consumes the iterator. """ return sum(1 for _ in iter) + + +def is_volume_viewer(viewer: Viewer) -> bool: + if isinstance(viewer, VispyVolumeViewerMixin): + return True + try: + from glue_jupyter.ipyvolume.volume import IpyvolumeVolumeView + if isinstance(viewer, IpyvolumeVolumeView): + return True + except ImportError: + pass + + return False + + +def get_resolution(viewer_state: Viewer3DState) -> int: + if isinstance(viewer_state, Vispy3DVolumeViewerState): + return viewer_state.resolution + + try: + from glue_jupyter.common.state3d import VolumeViewerState + if isinstance(viewer_state, VolumeViewerState): + return max((getattr(state, 'max_resolution', 256) for state in viewer_state.layers), default=256) + except ImportError: + pass + + return 256 From b924c95d3f8c422ba56b9cf366102ad0cb7199ad Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 9 Sep 2024 17:30:11 -0400 Subject: [PATCH 2/2] Update condition for asking for resolution in Jupyter export dialog. --- glue_ar/jupyter/export_tool.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/glue_ar/jupyter/export_tool.py b/glue_ar/jupyter/export_tool.py index ad7d1f4..fdc277f 100644 --- a/glue_ar/jupyter/export_tool.py +++ b/glue_ar/jupyter/export_tool.py @@ -3,11 +3,10 @@ from glue.config import viewer_tool from glue.viewers.common.tool import Tool -from glue_vispy_viewers.volume.qt.volume_viewer import VispyVolumeViewerMixin from glue_ar.common.export import export_viewer from glue_ar.jupyter.export_dialog import JupyterARExportDialog -from glue_ar.utils import AR_ICON, xyz_bounds +from glue_ar.utils import AR_ICON, is_volume_viewer, xyz_bounds import ipyvuetify as v # noqa from ipywidgets import HBox, Layout # noqa @@ -109,7 +108,7 @@ def on_no_click(button, event, data): self.viewer.output_widget.clear_output() def save_figure(self, filepath): - bounds = xyz_bounds(self.viewer.state, with_resolution=isinstance(self.viewer, VispyVolumeViewerMixin)) + bounds = xyz_bounds(self.viewer.state, with_resolution=is_volume_viewer(self.viewer)) layer_states = [layer.state for layer in self.viewer.layers if layer.enabled and layer.state.visible] state_dict = self.export_dialog.state_dictionary export_viewer(viewer_state=self.viewer.state,