From b3c4334b0a361a119d4e359eec1c3786fb14fc06 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 25 Dec 2023 12:40:59 +0000 Subject: [PATCH 1/6] Use preserve_aspect when determining how to scale data. Add function for clipping to bounds. --- glue_ar/scatter.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/glue_ar/scatter.py b/glue_ar/scatter.py index 3b33cf0..039058e 100644 --- a/glue_ar/scatter.py +++ b/glue_ar/scatter.py @@ -1,3 +1,5 @@ +from math import floor + import pyvista as pv from glue_ar.utils import layer_color, xyz_bounds, xyz_for_layer @@ -37,13 +39,15 @@ def scatter_layer_as_multiblock(viewer_state, layer_state, theta_resolution=8, phi_resolution=8, scaled=True): - data = xyz_for_layer(viewer_state, layer_state, scaled=scaled) + data = xyz_for_layer(viewer_state, layer_state, scaled=scaled, preserve_aspect=viewer_state.native_aspect) bounds = xyz_bounds(viewer_state) factor = max((abs(b[1] - b[0]) for b in bounds)) - radius = layer_state.size_scaling * layer_state.size / factor + radius = layer_state.size_scaling * layer_state.size + radius /= factor spheres = [pv.Sphere(center=p, radius=radius, phi_resolution=phi_resolution, theta_resolution=theta_resolution) for p in data] blocks = pv.MultiBlock(spheres) geometry = blocks.extract_geometry() + info = { "data": geometry, "opacity": layer_state.alpha @@ -66,4 +70,5 @@ def scatter_layer_as_multiblock(viewer_state, layer_state, info["cmap"] = cmap info["clim"] = clim info["scalars"] = "colors" + return info From 84374f50e75e22722866d38e6cf7e84a9a272f4d Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Dec 2023 03:30:49 +0000 Subject: [PATCH 2/6] Add clip to bounds option to export dialog. --- glue_ar/export_dialog.py | 2 ++ glue_ar/export_dialog.ui | 33 ++++++++++++++++++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/glue_ar/export_dialog.py b/glue_ar/export_dialog.py index 2d2cd3c..7934fa8 100644 --- a/glue_ar/export_dialog.py +++ b/glue_ar/export_dialog.py @@ -7,6 +7,7 @@ from glue.core.data_combo_helper import ComboHelper from glue.core.state_objects import State from glue_qt.utils import load_ui +from glue_vispy_viewers.volume.layer_state import CallbackProperty from qtpy.QtGui import QDoubleValidator, QIntValidator @@ -39,6 +40,7 @@ def adder(export_state_class): class ARExportDialogState(State): + clip_to_bounds = CallbackProperty(True) filetype = SelectionCallbackProperty() layer = SelectionCallbackProperty() diff --git a/glue_ar/export_dialog.ui b/glue_ar/export_dialog.ui index b382746..ad4532b 100644 --- a/glue_ar/export_dialog.ui +++ b/glue_ar/export_dialog.ui @@ -14,14 +14,13 @@ Export 3D Volume - - - - Set the export settings for each layer - - + + + + + @@ -60,21 +59,29 @@ + + + + Select the export filetype + + + - - - - + - Select the export filetype + Set the export settings for each layer - - + + + + Clip to viewer bounds + + From 895428ea80134636322522a903261056004c430b Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Dec 2023 03:31:49 +0000 Subject: [PATCH 3/6] Use clip to bounds option in export tools. --- glue_ar/scatter.py | 8 ++++++-- glue_ar/tools.py | 25 ++++++++++++++++++------- glue_ar/utils.py | 45 ++++++++++++++++++++++++++++++++++++--------- glue_ar/volume.py | 7 +++---- 4 files changed, 63 insertions(+), 22 deletions(-) diff --git a/glue_ar/scatter.py b/glue_ar/scatter.py index 039058e..19b62d2 100644 --- a/glue_ar/scatter.py +++ b/glue_ar/scatter.py @@ -38,8 +38,12 @@ def scatter_layer_as_glyphs(viewer_state, layer_state, glyph): def scatter_layer_as_multiblock(viewer_state, layer_state, theta_resolution=8, phi_resolution=8, - scaled=True): - data = xyz_for_layer(viewer_state, layer_state, scaled=scaled, preserve_aspect=viewer_state.native_aspect) + scaled=True, + clip_to_bounds=True): + data = xyz_for_layer(viewer_state, layer_state, + preserve_aspect=viewer_state.native_aspect, + clip_to_bounds=clip_to_bounds, + scaled=scaled) bounds = xyz_bounds(viewer_state) factor = max((abs(b[1] - b[0]) for b in bounds)) radius = layer_state.size_scaling * layer_state.size diff --git a/glue_ar/tools.py b/glue_ar/tools.py index f1b90d1..9c21f01 100644 --- a/glue_ar/tools.py +++ b/glue_ar/tools.py @@ -13,6 +13,7 @@ from glue_ar.scatter import scatter_layer_as_multiblock from glue_ar.export import export_gl, export_modelviewer +from glue_ar.utils import bounds_3d_from_layers from glue_ar.volume import bounds_3d, meshes_for_volume_layer __all__ = ["GLScatterExportTool", "GLVolumeExportTool"] @@ -45,9 +46,13 @@ def activate(self): layer_states = [layer.state for layer in self.viewer.layers if layer.enabled and layer.state.visible] for layer_state in layer_states: layer_info = dialog.state_dictionary[layer_state.layer.label].as_dict() - mesh_info = scatter_layer_as_multiblock(self.viewer.state, layer_state, **layer_info, scaled=True) - data = mesh_info.pop("data") - plotter.add_mesh(data, **mesh_info) + mesh_info = scatter_layer_as_multiblock(self.viewer.state, layer_state, + scaled=True, + clip_to_bounds=dialog.state.clip_to_bounds, + **layer_info) + mesh = mesh_info.pop("data") + if len(mesh.points) > 0: + plotter.add_mesh(mesh, **mesh_info) dir, base = split(export_path) name, ext = splitext(base) @@ -79,8 +84,11 @@ def activate(self): plotter = pv.Plotter() layer_states = [layer.state for layer in self.viewer.layers if layer.enabled and layer.state.visible] - bounds = bounds_3d(self.viewer.state) frbs = {} + if dialog.state.clip_to_bounds: + bounds = bounds_3d(self.viewer.state) + else: + bounds = bounds_3d_from_layers(self.viewer.state, layer_states) for layer_state in layer_states: layer_info = dialog.state_dictionary[layer_state.layer.label].as_dict() if isinstance(layer_state, VolumeLayerState): @@ -89,9 +97,12 @@ def activate(self): precomputed_frbs=frbs, **layer_info) else: - mesh_info = scatter_layer_as_multiblock(self.viewer.state, layer_state, **layer_info, scaled=False) - data = mesh_info.pop("data") - plotter.add_mesh(data, **mesh_info) + mesh_info = scatter_layer_as_multiblock(self.viewer.state, layer_state, + scaled=False, clip_to_bounds=dialog.state.clip_to_bounds, + **layer_info) + mesh = mesh_info.pop("data") + if len(mesh.points) > 0: + plotter.add_mesh(mesh, **mesh_info) dir, base = split(export_path) name, ext = splitext(base) diff --git a/glue_ar/utils.py b/glue_ar/utils.py index d638157..201aa34 100644 --- a/glue_ar/utils.py +++ b/glue_ar/utils.py @@ -1,5 +1,5 @@ from glue.core.subset_group import GroupedSubset -from numpy import array +from numpy import array, inf def isomin_for_layer(viewer_state, layer): @@ -17,6 +17,18 @@ def xyz_bounds(viewer_state): (viewer_state.z_min, viewer_state.z_max)] +def bounds_3d_from_layers(viewer_state, layer_states): + mins = [inf, inf, inf] + maxes = [-inf, -inf, -inf] + atts = viewer_state.x_att, viewer_state.y_att, viewer_state.z_att + for state in layer_states: + data = state.layer.layer + mins = [min(min(data[att]), m) for m, att in zip(mins, atts)] + maxes = [max(max(data[att]), m) for m, att in zip(maxes, atts)] + print(mins) + return [(l, u) for l, u in zip(mins, maxes)] + + # TODO: Make this better? # glue-plotly has had to deal with similar issues, # the utilities there are at least better than this @@ -27,8 +39,6 @@ def layer_color(layer_state): return layer_color -# data should be list of numpy arrays -# Think about being more format-agnostic later def scale(data, bounds, preserve_aspect=True): if preserve_aspect: ranges = [abs(bds[1] - bds[0]) for bds in bounds] @@ -37,25 +47,42 @@ def scale(data, bounds, preserve_aspect=True): bds = bounds[index] m = 2 / (bds[1] - bds[0]) b = (bds[0] + bds[1]) / (bds[1] - bds[0]) - return [m * d + b for d in data] + scaled = [[m * v + b for v in d] for d in data] else: scaled = [] for idx, bds in enumerate(bounds): m = 2 / (bds[1] - bds[0]) b = (bds[0] + bds[1]) / (bds[1] - bds[0]) - scaled.append(m * data[idx] + b) - return scaled + scaled.append([m * d + b for d in data[idx]]) + + return scaled # TODO: Worry about efficiency later -def xyz_for_layer(viewer_state, layer_state, scaled=False, preserve_aspect=True): +def xyz_for_layer(viewer_state, layer_state, + scaled=False, + preserve_aspect=True, + clip_to_bounds=True): xs = layer_state.layer[viewer_state.x_att] ys = layer_state.layer[viewer_state.y_att] zs = layer_state.layer[viewer_state.z_att] vals = [xs, ys, zs] - if scaled: + if scaled or clip_to_bounds: bounds = xyz_bounds(viewer_state) - vals = scale(vals, bounds, preserve_aspect=preserve_aspect) + if clip_to_bounds: + xs, ys, zs = [], [], [] + for x, y, z in zip(*vals): + if (x >= bounds[0][0] and x <= bounds[0][1]) and \ + (y >= bounds[1][0] and y <= bounds[1][1]) and \ + (z >= bounds[2][0] and z <= bounds[2][1]): + xs.append(x) + ys.append(y) + zs.append(z) + vals = [xs, ys, zs] + + + if scaled: + vals = scale(vals, bounds, preserve_aspect=preserve_aspect) return array(list(zip(*vals))) diff --git a/glue_ar/volume.py b/glue_ar/volume.py index 532167b..215703e 100644 --- a/glue_ar/volume.py +++ b/glue_ar/volume.py @@ -16,20 +16,19 @@ def meshes_for_volume_layer(viewer_state, layer_state, bounds, use_gaussian_filter=False, smoothing_iterations=0, precomputed_frbs=None): - precomputed_frbs = precomputed_frbs or {} - layer_content = layer_state.layer parent = layer_content.data if isinstance(layer_content, GroupedSubset) else layer_content parent_label = parent.label - if parent_label in precomputed_frbs: + if precomputed_frbs is not None and parent_label in precomputed_frbs: data = precomputed_frbs[parent_label] else: data = parent.compute_fixed_resolution_buffer( target_data=viewer_state.reference_data, bounds=bounds, target_cid=layer_state.attribute) - precomputed_frbs[parent_label] = data + if precomputed_frbs is not None: + precomputed_frbs[parent_label] = data if isinstance(layer_state.layer, GroupedSubset): subcube = parent.compute_fixed_resolution_buffer( From 870fe6ad6c883f6e06b5b218073d601d94187e8b Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Dec 2023 12:07:54 +0000 Subject: [PATCH 4/6] Turns out clip_data is an option in the vispy viewer state, so use that instead of a dialog option. --- glue_ar/export_dialog.py | 1 - glue_ar/export_dialog.ui | 15 ++++----------- glue_ar/scatter.py | 8 ++++---- glue_ar/tools.py | 11 ++++++----- glue_ar/volume.py | 2 +- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/glue_ar/export_dialog.py b/glue_ar/export_dialog.py index 7934fa8..73417f0 100644 --- a/glue_ar/export_dialog.py +++ b/glue_ar/export_dialog.py @@ -40,7 +40,6 @@ def adder(export_state_class): class ARExportDialogState(State): - clip_to_bounds = CallbackProperty(True) filetype = SelectionCallbackProperty() layer = SelectionCallbackProperty() diff --git a/glue_ar/export_dialog.ui b/glue_ar/export_dialog.ui index ad4532b..10f7ed9 100644 --- a/glue_ar/export_dialog.ui +++ b/glue_ar/export_dialog.ui @@ -14,13 +14,13 @@ Export 3D Volume - + - + - + @@ -69,20 +69,13 @@ - + Set the export settings for each layer - - - - Clip to viewer bounds - - - diff --git a/glue_ar/scatter.py b/glue_ar/scatter.py index 19b62d2..0f8eb29 100644 --- a/glue_ar/scatter.py +++ b/glue_ar/scatter.py @@ -8,7 +8,7 @@ def scatter_layer_as_points(viewer_state, layer_state): xyz = xyz_for_layer(viewer_state, layer_state) return { - "data": xyz, + "mesh": xyz, "color": layer_color(layer_state), "opacity": layer_state.alpha, "style": "points_gaussian", @@ -20,7 +20,7 @@ def scatter_layer_as_points(viewer_state, layer_state): def scatter_layer_as_spheres(viewer_state, layer_state): data = xyz_for_layer(viewer_state, layer_state) return { - "data": [pv.Sphere(center=p) for p in data] + "mesh": [pv.Sphere(center=p) for p in data] } @@ -29,7 +29,7 @@ def scatter_layer_as_glyphs(viewer_state, layer_state, glyph): points = pv.PointSet(data) glyphs = points.glyph(geom=glyph, orient=False, scale=False) return { - "data": glyphs, + "mesh": glyphs, "color": layer_color(layer_state), "opacity": layer_state.alpha, } @@ -53,7 +53,7 @@ def scatter_layer_as_multiblock(viewer_state, layer_state, geometry = blocks.extract_geometry() info = { - "data": geometry, + "mesh": geometry, "opacity": layer_state.alpha } if layer_state.color_mode == "Fixed": diff --git a/glue_ar/tools.py b/glue_ar/tools.py index 9c21f01..7fa5ca8 100644 --- a/glue_ar/tools.py +++ b/glue_ar/tools.py @@ -48,9 +48,9 @@ def activate(self): layer_info = dialog.state_dictionary[layer_state.layer.label].as_dict() mesh_info = scatter_layer_as_multiblock(self.viewer.state, layer_state, scaled=True, - clip_to_bounds=dialog.state.clip_to_bounds, + clip_to_bounds=self.viewer.state.clip_data, **layer_info) - mesh = mesh_info.pop("data") + mesh = mesh_info.pop("mesh") if len(mesh.points) > 0: plotter.add_mesh(mesh, **mesh_info) @@ -85,7 +85,7 @@ def activate(self): plotter = pv.Plotter() layer_states = [layer.state for layer in self.viewer.layers if layer.enabled and layer.state.visible] frbs = {} - if dialog.state.clip_to_bounds: + if self.viewer.state.clip_data: bounds = bounds_3d(self.viewer.state) else: bounds = bounds_3d_from_layers(self.viewer.state, layer_states) @@ -98,9 +98,10 @@ def activate(self): **layer_info) else: mesh_info = scatter_layer_as_multiblock(self.viewer.state, layer_state, - scaled=False, clip_to_bounds=dialog.state.clip_to_bounds, + scaled=False, + clip_to_bounds=self.viewer.state.clip_data, **layer_info) - mesh = mesh_info.pop("data") + mesh = mesh_info.pop("mesh") if len(mesh.points) > 0: plotter.add_mesh(mesh, **mesh_info) diff --git a/glue_ar/volume.py b/glue_ar/volume.py index 215703e..977b142 100644 --- a/glue_ar/volume.py +++ b/glue_ar/volume.py @@ -59,7 +59,7 @@ def meshes_for_volume_layer(viewer_state, layer_state, bounds, isodata = isodata.smooth(n_iter=int(smoothing_iterations)) return { - "data": isodata, + "mesh": isodata, "color": layer_color(layer_state), "opacity": layer_state.alpha, # "isomin": isomin, From 12e4bb1ed80e504907a4d87375343cd0beb85822 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Dec 2023 12:51:03 +0000 Subject: [PATCH 5/6] Minor updates. --- glue_ar/scatter.py | 3 +-- glue_ar/utils.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/glue_ar/scatter.py b/glue_ar/scatter.py index 0f8eb29..749fd77 100644 --- a/glue_ar/scatter.py +++ b/glue_ar/scatter.py @@ -46,8 +46,7 @@ def scatter_layer_as_multiblock(viewer_state, layer_state, scaled=scaled) bounds = xyz_bounds(viewer_state) factor = max((abs(b[1] - b[0]) for b in bounds)) - radius = layer_state.size_scaling * layer_state.size - radius /= factor + radius = (layer_state.size_scaling * layer_state.size) / factor spheres = [pv.Sphere(center=p, radius=radius, phi_resolution=phi_resolution, theta_resolution=theta_resolution) for p in data] blocks = pv.MultiBlock(spheres) geometry = blocks.extract_geometry() diff --git a/glue_ar/utils.py b/glue_ar/utils.py index 201aa34..855d183 100644 --- a/glue_ar/utils.py +++ b/glue_ar/utils.py @@ -59,6 +59,7 @@ def scale(data, bounds, preserve_aspect=True): # TODO: Worry about efficiency later +# and just generally make this better def xyz_for_layer(viewer_state, layer_state, scaled=False, preserve_aspect=True, @@ -81,7 +82,6 @@ def xyz_for_layer(viewer_state, layer_state, zs.append(z) vals = [xs, ys, zs] - if scaled: vals = scale(vals, bounds, preserve_aspect=preserve_aspect) From fd05288ef5bc932844c6096e9391e2878930ed3b Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Tue, 26 Dec 2023 12:53:56 +0000 Subject: [PATCH 6/6] Remove unused import. --- glue_ar/export_dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/glue_ar/export_dialog.py b/glue_ar/export_dialog.py index 73417f0..2d2cd3c 100644 --- a/glue_ar/export_dialog.py +++ b/glue_ar/export_dialog.py @@ -7,7 +7,6 @@ from glue.core.data_combo_helper import ComboHelper from glue.core.state_objects import State from glue_qt.utils import load_ui -from glue_vispy_viewers.volume.layer_state import CallbackProperty from qtpy.QtGui import QDoubleValidator, QIntValidator