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
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.0.0
current_version = 1.0.1
commit = True
tag = True

Expand Down
2 changes: 1 addition & 1 deletion docs/userguide/toolbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ from [PareView](https://www.paraview.org/).



![camera actions](../images/camera_actions.png){ width="25%", align=right }
![camera actions](../images/toolbar_camera_actions.png){ width="25%", align=right }

**Camera Actions**:
A set of buttons are provided to simultaneously adjust all views
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "quickview"
version = "1.0.0"
version = "1.0.1"
description = "An application to explore/analyze data for atmosphere component for E3SM"
authors = [
{name = "Kitware Inc."},
Expand Down
2 changes: 1 addition & 1 deletion quickview/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""QuickView: Visual Analysis for E3SM Atmosphere Data."""

__version__ = "1.0.0"
__version__ = "1.0.1"
__author__ = "Kitware Inc."
__license__ = "Apache-2.0"
110 changes: 67 additions & 43 deletions quickview/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from pathlib import Path
from typing import Union

from trame.app import get_server
from trame.decorators import TrameApp, life_cycle, trigger, change
from trame.app import TrameApp
from trame.decorators import life_cycle, trigger, change
from trame.ui.vuetify import SinglePageWithDrawerLayout

from trame.widgets import vuetify as v2, html, client
Expand Down Expand Up @@ -143,32 +143,26 @@ def get_logo_base64():
print("Error loading presets :", e)


@TrameApp()
class EAMApp:
class EAMApp(TrameApp):
def __init__(
self,
source: EAMVisSource = None,
initserver: Union[Server, str] = None,
initstate: dict = None,
workdir: Union[str, Path] = None,
) -> None:
server = get_server(initserver, client_type="vue2")
state = server.state
ctrl = server.controller
super().__init__(initserver, client_type="vue2")

self._ui = None
self._cached_layout = {} # Cache for layout positions by variable name

self.workdir = workdir
self.server = server

pvWidgets.initialize(server)
pvWidgets.initialize(self.server)

self.source = source
self.viewmanager = ViewManager(source, server, state)
self.viewmanager = ViewManager(source, self.server, self.state)

state = self.state
# Load state variables from the source object

state.data_file = source.data_file if source.data_file else ""
state.conn_file = source.conn_file if source.conn_file else ""

Expand Down Expand Up @@ -231,10 +225,12 @@ def __init__(
state.probe_enabled = False
state.probe_location = [] # Default probe

ctrl = self.ctrl
ctrl.view_update = self.viewmanager.render_all_views
ctrl.view_reset_camera = self.viewmanager.reset_camera
ctrl.on_server_ready.add(ctrl.view_update)
server.trigger_name(ctrl.view_reset_camera)

self.server.trigger_name(ctrl.view_reset_camera)

state.colormaps = noncvd

Expand Down Expand Up @@ -324,21 +320,21 @@ def update_state_from_config(self, initstate):

@trigger("layout_changed")
def on_layout_changed_trigger(self, layout, **kwargs):
"""Cache layout changes to ensure they are properly saved"""
# There should always be a 1:1 correspondence
# between the layout and the variables
assert len(layout) == len(self.state.variables)
# Cache the layout data with variable names as keys for easier lookup
self._cached_layout = {}
if layout and hasattr(self.state, "variables"):
for item in layout:
if isinstance(item, dict) and "i" in item:
idx = item["i"]
if idx < len(self.state.variables):
var_name = self.state.variables[idx]
self._cached_layout[var_name] = {
"x": item.get("x", 0),
"y": item.get("y", 0),
"w": item.get("w", 4),
"h": item.get("h", 3),
}
for idx, item in enumerate(layout):
if idx < len(self.state.variables):
var_name = self.state.variables[idx]
self._cached_layout[var_name] = {
"i": idx,
"x": item.get("x", 0),
"y": item.get("y", 0),
"w": item.get("w", 4),
"h": item.get("h", 3),
}

def generate_state(self):
# Force state synchronization
Expand Down Expand Up @@ -421,7 +417,7 @@ def load_data(self):
self.init_app_configuration()
else:
# Keep the defaults that were set in __init__
# but ensure arrays are empty if pipeline failed
# but ensure arrays are empty if pipeline
state.timesteps = []
state.midpoints = []
state.interfaces = []
Expand Down Expand Up @@ -477,10 +473,12 @@ def load_variables(self, use_cached_layout=False):
self.source.LoadVariables(surf, mid, intf)

vars = surf + mid + intf
varorigin = [0] * len(surf) + [1] * len(mid) + [2] * len(intf)

# Tracking variables to control camera and color properties
with self.state as state:
state.variables = vars
state.varorigin = varorigin

# Initialize arrays that are always needed regardless of cache status
# Color configuration arrays will be populated by ViewContext via sync_color_config_to_state
Expand Down Expand Up @@ -595,31 +593,34 @@ def pan_camera(self, dir):
self.viewmanager.pan_camera(0, 0)

def update_surface_var_selection(self, index, event):
self.state.surface_vars_state[index] = event
self.state.dirty("surface_vars_state")
with self.state as state:
state.surface_vars_state[index] = event
if self.ind_surface is not None:
ind = self.ind_surface[index]
self.surface_vars_state[ind] = event
else:
self.surface_vars_state[index] = event
self.state.dirty("surface_vars_state")

def update_midpoint_var_selection(self, index, event):
self.state.midpoint_vars_state[index] = event
self.state.dirty("midpoint_vars_state")
with self.state as state:
state.midpoint_vars_state[index] = event
if self.ind_midpoint is not None:
ind = self.ind_midpoint[index]
self.midpoint_vars_state[ind] = event
else:
self.midpoint_vars_state[index] = event
self.state.dirty("midpoint_vars_state")

def update_interface_var_selection(self, index, event):
self.state.interface_vars_state[index] = event
self.state.dirty("interface_vars_state")
with self.state as state:
state.interface_vars_state[index] = event
if self.ind_interface is not None:
ind = self.ind_interface[index]
self.interface_vars_state[ind] = event
else:
self.interface_vars_state[index] = event
self.state.dirty("interface_vars_state")

def search_surface_vars(self, search: str):
if search is None or len(search) == 0:
Expand Down Expand Up @@ -712,7 +713,30 @@ def clear_interface_vars(self, clear_var_name):
self.state.dirty("interface_vars_state")

def close_view(self, index):
print("Requested close view for ", index)
var = self.state.variables.pop(index)
origin = self.state.varorigin.pop(index)
self._cached_layout.pop(var)
self.state.dirty("variables")
self.state.dirty("varorigin")
self.viewmanager.close_view(var, index, self._cached_layout)
state = self.state

# Find variable to unselect from the UI
if origin == 0:
# Find and clear surface display
if var in state.surface_vars:
var_index = state.surface_vars.index(var)
self.update_surface_var_selection(var_index, False)
elif origin == 1:
# Find and clear midpoints display
if var in state.midpoint_vars:
var_index = state.midpoint_vars.index(var)
self.update_midpoint_var_selection(var_index, False)
elif origin == 2:
# Find and clear interface display
if var in state.interface_vars:
var_index = state.interface_vars.index(var)
self.update_interface_var_selection(var_index, False)

def start(self, **kwargs):
"""Initialize the UI and start the server for GeoTrame."""
Expand Down Expand Up @@ -802,14 +826,14 @@ def ui(self) -> SinglePageWithDrawerLayout:

with layout.content:
with grid.GridLayout(
layout=("layout"),
layout=("layout",),
col_num=12,
row_height=100,
is_draggable=True,
is_resizable=True,
vertical_compact=True,
layout_updated="layout = $event; trigger('layout_changed', [$event])",
):
) as self.grid:
with grid.GridItem(
v_for="vref, idx in views",
key="vref",
Expand Down Expand Up @@ -970,11 +994,11 @@ def ui(self) -> SinglePageWithDrawerLayout:
style="color: white;",
classes="font-weight-medium",
)
# with v2.VBtn(
# icon=True,
# style="position: absolute; top: 8px; right: 8px; padding: 4px 8px; z-index: 2; color: white;",
# click=(self.close_view, "[idx]"),
# ):
# v2.VIcon("mdi-close")
with v2.VBtn(
icon=True,
style="position: absolute; top: 8px; right: 8px; padding: 4px 8px; z-index: 2; color: white;",
click=(self.close_view, "[idx]"),
):
v2.VIcon("mdi-close")

return self._ui
8 changes: 4 additions & 4 deletions quickview/utils/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ def calculate_weighted_average(
The (weighted) average, handling NaN values
"""
data = np.array(data_array)

weights = np.array(weights)
# Handle NaN values
if np.isnan(data).any():
mask = ~np.isnan(data)
if not np.any(mask):
return np.nan # all values are NaN
data = data[mask]
if weights is not None:
weights = np.array(weights)[mask]
weights = weights[mask]

if weights is not None:
return np.average(data, weights=weights)
return float(np.average(data, weights=weights))
else:
return np.mean(data)
return float(np.mean(data))


def calculate_data_range(bounds: List[float]) -> Tuple[float, float, float]:
Expand Down
49 changes: 35 additions & 14 deletions quickview/view_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,48 @@ def _on_change_pipeline_valid(self, pipeline_valid, **kwargs):
self.state.dirty("views")
self.state.dirty("layout")

def close_view(self, var, index, layout_cache):
# Clear cache and remove layout and widget entry
with self.state as state:
self.registry.remove_view(var)
state.varcolor.pop(index)
state.varmin.pop(index)
state.varmax.pop(index)
state.uselogscale.pop(index)
state.override_range.pop(index)
state.invert.pop(index)
state.views.pop(index)
state.colorbar_images.pop(index)

# Rebuild layout
new_layout = []
vars = self.state.variables
for var in vars:
cache = layout_cache[var]
new_layout.append(cache)
self.state.layout = new_layout

self.state.dirty("varcolor")
self.state.dirty("varmin")
self.state.dirty("varmax")
self.state.dirty("uselogscale")
self.state.dirty("override_range")
self.state.dirty("invert")
self.state.dirty("layout")
self.state.dirty("views")
self.state.dirty("colorbar_images")

def update_views_for_timestep(self):
if len(self.registry) == 0:
return
data = sm.Fetch(self.source.views["atmosphere_data"])

first_view = None
for var, context in self.registry.items():
index = self.state.variables.index(var)
varavg = self.compute_average(var, vtkdata=data)
# Directly set average in trame state
self.state.varaverage[context.index] = varavg
self.state.varaverage[index] = varavg
self.state.dirty("varaverage")
if not context.config.override_range:
context.state.data_representation.RescaleTransferFunctionToDataRange(
Expand All @@ -237,8 +269,8 @@ def update_views_for_timestep(self):
range = self.compute_range(var=var)
context.config.min_value = range[0]
context.config.max_value = range[1]
self.sync_color_config_to_state(context.index, context)
self.generate_colorbar_image(context.index)
self.sync_color_config_to_state(index, context)
self.generate_colorbar_image(index)

# Track the first view for camera fitting
if first_view is None and context.state.view_proxy:
Expand Down Expand Up @@ -562,17 +594,6 @@ def rebuild_visualization_layout(self, cached_layout=None):
self.state.layout = layout
self.state.dirty("views")
self.state.dirty("layout")
# from trame.app import asynchronous
# asynchronous.create_task(self.flushViews())

"""
async def flushViews(self):
await self.server.network_completion
self.render_all_views()
import asyncio
await asyncio.sleep(1)
self.render_all_views()
"""

def update_colormap(self, index, value):
"""Update the colormap for a variable."""
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "1.0.0"
version = "1.0.1"
description = "QuickView: Visual Analyis for E3SM Atmosphere Data"
authors = ["Kitware"]
license = ""
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"package": {
"productName": "QuickView",
"version": "1.0.0"
"version": "1.0.1"
},
"tauri": {
"allowlist": {
Expand Down
Loading