Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose time functionality #103

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5af4063
Expose play/pause time and clock rate settings.
Carifio24 Oct 17, 2023
1c1077b
Start exposing time attribute and decay functionality.
Carifio24 Oct 17, 2023
a794a0e
Allow setting current WWT time from Qt glue. Basic time functionality…
Carifio24 Oct 24, 2023
bfad9d1
Improve performance of setting time attribute for table layer.
Carifio24 Oct 25, 2023
18ba293
Last set time doesn't need to be a callback property.
Carifio24 Oct 25, 2023
1af0533
Fix issues with setting time from dialog. Add label displaying curren…
Carifio24 Oct 25, 2023
6fd3bf5
Work on UI for Jupyter viewer. Refactor to allow sharing linked widge…
Carifio24 Oct 27, 2023
7660416
Fix small bugs in Jupyter UI and table layer code.
Carifio24 Nov 27, 2023
f8c7ae6
Work on modifying UI to allow setting min/max time and change time us…
Carifio24 Jan 3, 2024
b241da0
Adjusting the time via the time slider is working. Need to adjust sli…
Carifio24 Jan 3, 2024
82ffd79
Ensure that min, max, and current time always have correct relative o…
Carifio24 Jan 4, 2024
9619210
Use UTC as time spec for datetime widgets.
Carifio24 Jan 6, 2024
7de2868
Set the correct starting tab for the options widget.
Carifio24 Jan 9, 2024
901695c
More updates to Jupyter time-control UI.
Carifio24 Jan 11, 2024
bd516d6
Update slider fraction when bounds change.
Carifio24 Jan 11, 2024
a1b8b44
Fix a typo.
Carifio24 Jan 11, 2024
4729886
Round current time label to ms to prevent adjusting size of sidebar i…
Carifio24 Jan 12, 2024
9d6eb46
Update datetime edit format.
Carifio24 Jan 26, 2024
8bb7b8d
Codestyle fixes.
Carifio24 Mar 21, 2024
8cc2ff6
Reset default viewer state tab to first one.
Carifio24 Sep 5, 2024
f8650e1
Use dev version of glue-qt for devdeps.
Carifio24 Sep 9, 2024
7884fe8
Put slider update inside a try block.
Carifio24 Sep 9, 2024
8cbaeed
Account for the fact that image layers don't have a time attribute.
Carifio24 Sep 9, 2024
9843f23
astropy has dropped Python 3.10 support so we can't use that for devd…
Carifio24 Sep 9, 2024
5023841
Update timer configuration and cleanup.
Carifio24 Nov 5, 2024
0c5a92a
Catch errors from QWebEnginePage being closed.
Carifio24 Jan 5, 2025
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
38 changes: 38 additions & 0 deletions glue_wwt/viewer/data_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
from __future__ import absolute_import, division, print_function

from astropy.coordinates import SkyCoord
from astropy.time import Time
import astropy.units as u
from glue.core.coordinates import WCSCoordinates
from glue.logger import logger
from pywwt import ViewerNotAvailableError
from pywwt.layers import guess_lon_lat_columns
from threading import Timer
from numpy import datetime64

from .image_layer import WWTImageLayerArtist
from .table_layer import WWTTableLayerArtist
Expand All @@ -29,6 +32,10 @@
"equatorial_text": "grid_text"
}

_CLOCK_SETTINGS = [
"play_time", "clock_rate"
]

_UPDATE_SETTINGS = [
"equatorial_grid", "equatorial_grid_color", "equatorial_text",
"ecliptic_grid", "ecliptic_grid_color", "ecliptic_text",
Expand All @@ -48,9 +55,22 @@
self._wwt.actual_planet_scale = True
self.state.imagery_layers = list(self._wwt.available_layers)

# The more obvious thing to do would be to listen to the WWT widget's "wwt_view_state" message,
# which contains information about WWT's internal time. But we only get those messages when something
# changes with the WWT view, so we can't rely on that here.
# This just kicks off the first timer; the method repeatedly time-calls itself
self._current_time_timer = Timer(1.0, self._update_time)
self._current_time_timer.start()

self.state.add_global_callback(self._update_wwt)

self._update_wwt(force=True)

# NB: Specific frontend implementations need to call this
# We just consolidate the logic here
def _cleanup(self):
self._current_time_timer.cancel()

def _initialize_wwt(self):
raise NotImplementedError('subclasses should set _wwt here')

Expand All @@ -67,6 +87,15 @@
self._wwt.constellation_boundaries = self.state.constellation_boundaries != 'None'
self._wwt.constellation_selection = self.state.constellation_boundaries == 'Selection only'

if force or 'current_time' in kwargs:
self._wwt.set_current_time(Time(self.state.current_time))

if force or any(setting in kwargs for setting in self._CLOCK_SETTINGS):
if self.state.play_time:
self._wwt.play_time(self.state.clock_rate)

Check warning on line 95 in glue_wwt/viewer/data_viewer.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/data_viewer.py#L95

Added line #L95 was not covered by tests
else:
self._wwt.pause_time()

for setting in self._UPDATE_SETTINGS:
if force or setting in kwargs:
wwt_attr = self._GLUE_TO_WWT_ATTR_MAP.get(setting, setting)
Expand Down Expand Up @@ -138,3 +167,12 @@
self.state.lon_att = data.id[lon]
self.state.lat_att = data.id[lat]
return add

def _update_time(self):
try:
self.state.current_time = datetime64(self._wwt.get_current_time().to_string())
except ViewerNotAvailableError:
pass

self._current_time_timer = Timer(1.0, self._update_time)
self._current_time_timer.start()
33 changes: 33 additions & 0 deletions glue_wwt/viewer/jupyter_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from ipywidgets import Checkbox, ColorPicker, FloatText, Layout

Check warning on line 1 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L1

Added line #L1 was not covered by tests

from glue.utils import color2hex
from glue_jupyter.link import dlink, link

Check warning on line 4 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L3-L4

Added lines #L3 - L4 were not covered by tests

__all__ = ['linked_checkbox', 'linked_color_picker', 'set_enabled_from_checkbox']

Check warning on line 6 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L6

Added line #L6 was not covered by tests


def opposite(value):
return not value

Check warning on line 10 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L9-L10

Added lines #L9 - L10 were not covered by tests


def linked_checkbox(state, attr, description='', layout=None):
widget = Checkbox(getattr(state, 'attr', False), description=description,

Check warning on line 14 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L13-L14

Added lines #L13 - L14 were not covered by tests
indent=False, layout=layout or Layout())
link((state, attr), (widget, 'value'))
return widget

Check warning on line 17 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L16-L17

Added lines #L16 - L17 were not covered by tests


def linked_color_picker(state, attr, description='', layout=None):
widget = ColorPicker(concise=True, layout=layout or Layout(), description=description)
link((state, attr), (widget, 'value'), color2hex)
return widget

Check warning on line 23 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L20-L23

Added lines #L20 - L23 were not covered by tests


def linked_float_text(state, attr, default=0, description='', layout=None):
widget = FloatText(description=description, layout=layout or Layout())
link((state, attr), (widget, 'value'), lambda value: value or default)
return widget

Check warning on line 29 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L26-L29

Added lines #L26 - L29 were not covered by tests


def set_enabled_from_checkbox(widget, checkbox):
dlink((checkbox, 'value'), (widget, 'disabled'), opposite)

Check warning on line 33 in glue_wwt/viewer/jupyter_utils.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/jupyter_utils.py#L32-L33

Added lines #L32 - L33 were not covered by tests
187 changes: 118 additions & 69 deletions glue_wwt/viewer/jupyter_viewer.py

Large diffs are not rendered by default.

93 changes: 65 additions & 28 deletions glue_wwt/viewer/options_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,12 @@
from glue_qt.utils import load_ui
from echo.qt import autoconnect_callbacks_to_qt

from .qt_utils import enabled_if_combosel_in, set_enabled_from_checkbox
from .viewer_state import MODES_BODIES

__all__ = ['WWTOptionPanel']


def _set_enabled_from_checkbox(widget, checkbox):
checkbox.toggled.connect(widget.setEnabled)
widget.setEnabled(checkbox.isChecked())


def _enabled_if_combosel_in(widget, combo, options):
combo.currentTextChanged.connect(lambda text: widget.setEnabled(text in options))
widget.setEnabled(combo.currentText() in options)


class WWTOptionPanel(QtWidgets.QWidget):

def __init__(self, viewer_state, session=None, parent=None):
Expand All @@ -36,30 +27,38 @@

self._connect = autoconnect_callbacks_to_qt(self._viewer_state, self.ui, connect_kwargs)

self.ui.slider_current_time.valueChanged.connect(self._on_slider_changed)

self._changing_slider_from_time = False

self._viewer_state.add_callback('mode', self._update_visible_options)
self._viewer_state.add_callback('frame', self._update_visible_options)
self._viewer_state.add_callback('current_time', self._on_current_time_update)
self._viewer_state.add_callback('min_time', self._update_slider_fraction)
self._viewer_state.add_callback('max_time', self._update_slider_fraction)
self._viewer_state.add_callback('layers', self._update_time_bounds)
self._setup_widget_dependencies()
self._update_visible_options()

def _setup_widget_dependencies(self):
_set_enabled_from_checkbox(self.ui.bool_alt_az_text, self.ui.bool_alt_az_grid)
_set_enabled_from_checkbox(self.ui.color_alt_az_grid_color, self.ui.bool_alt_az_grid)
_set_enabled_from_checkbox(self.ui.bool_ecliptic_text, self.ui.bool_ecliptic_grid)
_set_enabled_from_checkbox(self.ui.color_ecliptic_grid_color, self.ui.bool_ecliptic_grid)
_set_enabled_from_checkbox(self.ui.bool_equatorial_text, self.ui.bool_equatorial_grid)
_set_enabled_from_checkbox(self.ui.color_equatorial_grid_color, self.ui.bool_equatorial_grid)
_set_enabled_from_checkbox(self.ui.bool_galactic_text, self.ui.bool_galactic_grid)
_set_enabled_from_checkbox(self.ui.color_galactic_grid_color, self.ui.bool_galactic_grid)
_set_enabled_from_checkbox(self.ui.color_constellation_figure_color, self.ui.bool_constellation_figures)
_set_enabled_from_checkbox(self.ui.color_ecliptic_color, self.ui.bool_ecliptic)
_set_enabled_from_checkbox(self.ui.color_precession_chart_color, self.ui.bool_precession_chart)

_enabled_if_combosel_in(self.ui.color_constellation_boundary_color,
self.ui.combosel_constellation_boundaries,
['All'])
_enabled_if_combosel_in(self.ui.color_constellation_selection_color,
self.ui.combosel_constellation_boundaries,
['All', 'Selection only'])
set_enabled_from_checkbox(self.ui.bool_alt_az_text, self.ui.bool_alt_az_grid)
set_enabled_from_checkbox(self.ui.color_alt_az_grid_color, self.ui.bool_alt_az_grid)
set_enabled_from_checkbox(self.ui.bool_ecliptic_text, self.ui.bool_ecliptic_grid)
set_enabled_from_checkbox(self.ui.color_ecliptic_grid_color, self.ui.bool_ecliptic_grid)
set_enabled_from_checkbox(self.ui.bool_equatorial_text, self.ui.bool_equatorial_grid)
set_enabled_from_checkbox(self.ui.color_equatorial_grid_color, self.ui.bool_equatorial_grid)
set_enabled_from_checkbox(self.ui.bool_galactic_text, self.ui.bool_galactic_grid)
set_enabled_from_checkbox(self.ui.color_galactic_grid_color, self.ui.bool_galactic_grid)
set_enabled_from_checkbox(self.ui.color_constellation_figure_color, self.ui.bool_constellation_figures)
set_enabled_from_checkbox(self.ui.color_ecliptic_color, self.ui.bool_ecliptic)
set_enabled_from_checkbox(self.ui.color_precession_chart_color, self.ui.bool_precession_chart)

enabled_if_combosel_in(self.ui.color_constellation_boundary_color,
self.ui.combosel_constellation_boundaries,
['All'])
enabled_if_combosel_in(self.ui.color_constellation_selection_color,
self.ui.combosel_constellation_boundaries,
['All', 'Selection only'])

def _update_visible_options(self, *args, **kwargs):

Expand Down Expand Up @@ -95,3 +94,41 @@
else:
self.ui.label_lon_att.setText('Longitude')
self.ui.label_lat_att.setText('Latitude')

def _update_slider_fraction(self, *args):
fraction = (self._viewer_state.current_time - self._viewer_state.min_time) / \
(self._viewer_state.max_time - self._viewer_state.min_time)
slider_min = self.ui.slider_current_time.minimum()
slider_max = self.ui.slider_current_time.maximum()
value = round(slider_min + fraction * (slider_max - slider_min))
self._changing_slider_from_time = True
self.ui.slider_current_time.setValue(value)

def _on_current_time_update(self, time):
self._update_slider_fraction()
try:
self.ui.label_current_time.setText(f"Current Time: {time.astype('datetime64[ms]')}")
except Exception:
pass

Check warning on line 112 in glue_wwt/viewer/options_widget.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/options_widget.py#L111-L112

Added lines #L111 - L112 were not covered by tests

def _on_slider_changed(self, *args):
if self._changing_slider_from_time:
self._changing_slider_from_time = False
return
value = self.ui.slider_current_time.value()
slider_min = self.ui.slider_current_time.minimum()
slider_max = self.ui.slider_current_time.maximum()
fraction = (value - slider_min) / (slider_max - slider_min)
self._viewer_state.current_time = self._viewer_state.min_time + \

Check warning on line 122 in glue_wwt/viewer/options_widget.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/options_widget.py#L118-L122

Added lines #L118 - L122 were not covered by tests
fraction * (self._viewer_state.max_time - self._viewer_state.min_time)

def _update_time_bounds(self, *args):
min_time = self._viewer_state.min_time
max_time = self._viewer_state.max_time
for layer_state in self._viewer_state.layers:
if layer_state.time_att is not None:
min_time = min(min_time, min(layer_state.layer[layer_state.time_att]))
max_time = max(max_time, max(layer_state.layer[layer_state.time_att]))

Check warning on line 131 in glue_wwt/viewer/options_widget.py

View check run for this annotation

Codecov / codecov/patch

glue_wwt/viewer/options_widget.py#L130-L131

Added lines #L130 - L131 were not covered by tests

self._viewer_state.min_time = min_time
self._viewer_state.max_time = max_time
Loading
Loading