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

Support time-series layers #187

Merged
merged 20 commits into from
Sep 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
8 changes: 6 additions & 2 deletions .check_enabled.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import logging
import sys

from jupyterlab.commands import get_app_info
from notebook.nbextensions import validate_nbextension
from notebook.serverextensions import validate_serverextension

if validate_nbextension('pywwt/extension') != []:
# If there's a problem and we don't provide this, the validate function crashes :-(
logger = logging.getLogger('')

if validate_nbextension('pywwt/extension', logger=logger) != []:
print("Issue detected with nbextension")
sys.exit(1)

Expand All @@ -14,6 +18,6 @@
print("Issue detected with labextension")
sys.exit(1)

if validate_serverextension('pywwt') != []:
if validate_serverextension('pywwt', logger=logger) != []:
print("Issue detected with serverextension")
sys.exit(1)
8 changes: 7 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
0.7.1
------------------

- Incorporate time series behavior for data layers; add method that
returns current time in the viewer. [#187]

0.7.0 (2019-09-20)
------------------

- You can now save your WWT views as interactive figures to be used in journal
articles! (Actually, they're just standalone webpages, so they can be used
articles! (Actually, they're just standalone web pages, so they can be used
anywhere you've got a web server.) This feature is new so it will still have
some rough edges — keep your eyes open for improvements. And the docs haven't
been written yet :-( [#215, #227]
Expand Down
3 changes: 3 additions & 0 deletions azure-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ jobs:
- bash: python -m pip install .[test,lab] pyopengl
displayName: Installing PyWWT and dependencies

- bash: python -c "import pywwt"
displayName: Test basic PyWWT importability

- bash: jupyter nbextension list
displayName: Listing Jupyter Notebook extensions

Expand Down
5 changes: 5 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ if using pip). For the record, these dependencies are as follows:
* `ipywidgets <https://ipywidgets.readthedocs.io>`_ 7.0.0 or later
* `ipyevents <https://github.com/mwcraig/ipyevents>`_
* `traitlets <https://traitlets.readthedocs.io>`_
* `reproject <https://reproject.readthedocs.io/>`_
* `six <https://six.readthedocs.io/>`_
* `pytz <http://pythonhosted.org/pytz>`_

In addition, if you want to use the Qt widget, you will need:

Expand All @@ -79,6 +82,8 @@ In addition, if you want to use the Qt widget, you will need:
<https://riverbankcomputing.com/software/pyqtwebengine/intro>`__ (both PyQt4
and PyQt5 are supported)
* `QtPy <https://pypi.org/project/QtPy/>`__ 1.2 or later
* `flask <https://palletsprojects.com/p/flask/>`_
* `flask-cors <https://github.com/corydolphin/flask-cors>`_

For the Jupyter widget, you will need:

Expand Down
7 changes: 7 additions & 0 deletions lib/wwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var WWTModel = widgets.DOMWidgetModel.extend({
_ra : 0.0,
_dec : 0.0,
_fov : 60.0,
_datetime : '2017-03-09T16:30:00',

})
});
Expand Down Expand Up @@ -184,6 +185,12 @@ var WWTView = widgets.DOMWidgetView.extend({
needUpdate = true;
}

var stc = this.wwt_window.wwtlib.SpaceTimeController;
if (this.model.get('_datetime') != stc.get_now().toISOString()) {
this.model.set({ '_datetime': stc.get_now().toISOString() });
needUpdate = true;
}

if (needUpdate) {
this.touch();
}
Expand Down
2 changes: 1 addition & 1 deletion pywwt/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def pytest_unconfigure(config):
cleanup_qapp()


REFERENCE_TIME = datetime(2017, 1, 1, 0, 0, 0, 0)
REFERENCE_TIME = datetime(2017, 2, 1, 0, 0, 0, 0)


@pytest.fixture(scope='session')
Expand Down
38 changes: 21 additions & 17 deletions pywwt/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from astropy import units as u
from astropy.time import Time
from astropy.coordinates import SkyCoord
from datetime import datetime

# We import the trait classes from .traits since we do various customizations
from .traits import Color, Bool, Float, Unicode, AstropyQuantity
Expand All @@ -12,6 +13,7 @@
from .solar_system import SolarSystem
from .layers import LayerManager
from .instruments import Instruments
from .utils import ensure_utc

import json
import os
Expand Down Expand Up @@ -160,7 +162,7 @@ def get_center(self):

def get_fov(self):
"""
Return the view's current field of view in degrees
Return the view's current field of view in degrees.
"""
return self._get_view_data('fov') * u.deg

Expand Down Expand Up @@ -209,6 +211,12 @@ def play_time(self, rate=1):
"""
self._send_msg(event='resume_time', rate=rate)

def get_current_time(self):
"""
Return the viewer's current time as an `~astropy.time.Time` object.
"""
return Time(self._get_view_data('datetime'), format='isot')

def set_current_time(self, dt=None):
"""
Set the viewer time to match the real-world time.
Expand All @@ -220,14 +228,9 @@ def set_current_time(self, dt=None):
astropy :class:`astropy.time.Time` object. If not specified, this
uses the current time
"""
if dt is None:
dt = Time.now()
if isinstance(dt, Time):
dt = dt.datetime
self._send_msg(event='set_datetime',
year=dt.year, month=dt.month, day=dt.day,
hour=dt.hour, minute=dt.minute, second=dt.second,
millisecond=int(dt.microsecond / 1000.))
# Ensure the object received is a datetime or Time; convert it to UTC
utc_tm = ensure_utc(dt, str_allowed=False)
self._send_msg(event='set_datetime', isot=utc_tm)

def center_on_coordinates(self, coord, fov=60 * u.deg, instant=True):
"""
Expand Down Expand Up @@ -392,7 +395,7 @@ def available_layers(self):
return sorted(self._available_layers)

foreground = Unicode('Digitized Sky Survey (Color)',
help='The layer to show in the foreground (`str`)').tag(wwt_reset=True)
help='The layer to show in the foreground (`str`)').tag(wwt=None, wwt_reset=True)

@observe('foreground')
def _on_foreground_change(self, changed):
Expand All @@ -409,7 +412,7 @@ def _validate_foreground(self, proposal):
raise TraitError('foreground is not one of the available layers')

background = Unicode('Hydrogen Alpha Full Sky Map',
help='The layer to show in the background (`str`)').tag(wwt_reset=True)
help='The layer to show in the background (`str`)').tag(wwt=None, wwt_reset=True)

@observe('background')
def _on_background_change(self, changed):
Expand All @@ -426,7 +429,8 @@ def _validate_background(self, proposal):
raise TraitError('background is not one of the available layers')

foreground_opacity = Float(0.8, help='The opacity of the foreground layer '
'(`float`)').tag(wwt_reset=True)
'(`float`)').tag(wwt=None,
wwt_reset=True)

@observe('foreground_opacity')
def _on_foreground_opacity_change(self, changed):
Expand Down Expand Up @@ -502,7 +506,7 @@ def add_line(self, points=None, **kwargs):
@property
def instruments(self):
"""
Instruments available for use in `add_fov`
A list of instruments available for use in `add_fov`.
"""
return self._instruments

Expand Down Expand Up @@ -566,14 +570,14 @@ def reset(self):
def save_as_html_bundle(self, dest, title=None, max_width=None, max_height=None):
"""
Save the current view as a web page with supporting files.

This feature is currently under development, so not all
settings/features that can be set in pyWWT will be saved

Parameters
----------
dest : `str`
The path to output the bundle to. The path must represent a
The path to output the bundle to. The path must represent a
directory (which will be created if it does not exist) or a zip file.
title : `str`, optional
The desired title for the HTML page. If blank, a generic title will be used.
Expand All @@ -595,7 +599,7 @@ def save_as_html_bundle(self, dest, title=None, max_width=None, max_height=None)
if not os.path.exists(dest):
os.makedirs(os.path.abspath(dest))
figure_dir = dest

nbexten_dir = os.path.join(os.path.dirname(__file__), 'nbextension', 'static')
fig_src_dir = os.path.join(nbexten_dir, 'interactive_figure')
shutil.copy(os.path.join(fig_src_dir, "index.html"), figure_dir)
Expand Down
15 changes: 11 additions & 4 deletions pywwt/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ class WWTJupyterWidget(widgets.DOMWidget, BaseWWTWidget):
_model_module = Unicode('pywwt').tag(sync=True)
_view_module_version = Unicode(VIEW_MODULE_VERSION).tag(sync=True)
_model_module_version = Unicode(MODEL_MODULE_VERSION).tag(sync=True)
_ra = Float(0.0).tag(sync=True)
_dec = Float(0.0).tag(sync=True)
_fov = Float(60.0).tag(sync=True)

_ra = Float(0.0).tag(sync=True, wwt=None)
_dec = Float(0.0).tag(sync=True, wwt=None)
_fov = Float(60.0).tag(sync=True, wwt=None)
_datetime = Unicode('2017-03-09T12:30:00').tag(sync=True, wwt=None)
# wwt=None tag needed to avoid linkage to 'wwt.settings.set_' type traits
# (see _on_trait_change() in core.py)

def __init__(self):
widgets.DOMWidget.__init__(self)
Expand Down Expand Up @@ -67,8 +71,11 @@ def _get_view_data(self, field):
return self._dec
elif field == 'fov':
return self._fov
elif field == 'datetime':
return self._datetime
else:
raise ValueError("'field' should be one of: 'ra', 'dec', or 'fov'")
raise ValueError("'field' should be one of: 'ra', 'dec', "
"'fov', or 'datetime'")

def _create_image_layer(self, **kwargs):
"""Returns a specialized subclass of ImageLayer that has some extra hooks for
Expand Down
Loading