|
@ -14,6 +14,8 @@
|
|||
- Incorporate time series behavior for data layers; add method that
|
||||
returns current time in the viewer. [#187]
|
||||
|
||||
- Drop support for Python 2.7 and now require at least Python 3.6. [#259]
|
||||
|
||||
0.7.0 (2019-09-20)
|
||||
------------------
|
||||
|
||||
|
|
|
@ -1,17 +1,38 @@
|
|||
resources:
|
||||
repositories:
|
||||
- repository: OpenAstronomy
|
||||
type: github
|
||||
endpoint: WorldWideTelescope
|
||||
name: OpenAstronomy/azure-pipelines-templates
|
||||
ref: master
|
||||
|
||||
jobs:
|
||||
|
||||
- template: azure-template.yml
|
||||
- template: run-tox-env.yml@OpenAstronomy
|
||||
parameters:
|
||||
name: Linux
|
||||
os: linux
|
||||
|
||||
- template: azure-template.yml
|
||||
parameters:
|
||||
name: Windows
|
||||
os: windows
|
||||
xvfb: true
|
||||
coverage: codecov
|
||||
libraries:
|
||||
apt:
|
||||
- libxkbcommon-x11-0
|
||||
- libgl1-mesa-dev
|
||||
|
||||
# Don't test on MacOS X for now
|
||||
# - template: azure-template.yml
|
||||
# parameters:
|
||||
# name: MacOSX
|
||||
# os: macosx
|
||||
envs:
|
||||
|
||||
- linux: codestyle
|
||||
libraries: {}
|
||||
coverage: 'false'
|
||||
|
||||
- linux: py36-test
|
||||
- linux: py37-test
|
||||
|
||||
- windows: py36-test
|
||||
- windows: py37-test
|
||||
|
||||
# - macos: py36-test
|
||||
# - macos: py37-test
|
||||
|
||||
- linux: py36-docs
|
||||
- windows: py37-docs
|
||||
- macos: py37-docs
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
parameters:
|
||||
python_versions: ['27', '36', '37']
|
||||
|
||||
jobs:
|
||||
|
||||
- ${{ each python_version in parameters.python_versions }}:
|
||||
- ${{ if or(ne(parameters.os, 'windows'), ne(python_version, '27')) }}:
|
||||
|
||||
- job: ${{ parameters.name }}_${{ python_version }}
|
||||
pool:
|
||||
${{ if eq(parameters.os, 'macosx') }}:
|
||||
vmImage: macOS 10.13
|
||||
${{ if eq(parameters.os, 'linux') }}:
|
||||
vmImage: Ubuntu 16.04
|
||||
${{ if eq(parameters.os, 'windows') }}:
|
||||
vmImage: vs2017-win2016
|
||||
|
||||
steps:
|
||||
|
||||
- ${{ if eq(parameters.os, 'linux') }}:
|
||||
- bash: |
|
||||
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid \
|
||||
--make-pidfile --background --exec /usr/bin/Xvfb \
|
||||
-- :99 -screen 0 1920x1200x24 -ac \
|
||||
+extension GLX +render -noreset
|
||||
displayName: Starting Xvfb
|
||||
|
||||
- ${{ if eq(python_version, '27') }}:
|
||||
|
||||
- task: CondaEnvironment@1
|
||||
inputs:
|
||||
packageSpecs: 'python=2.7 pyqt'
|
||||
updateConda: true
|
||||
cleanEnvironment: true
|
||||
|
||||
- ${{ if ne(python_version, '27') }}:
|
||||
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
${{ if eq(python_version, '36') }}:
|
||||
versionSpec: '3.6'
|
||||
${{ if eq(python_version, '37') }}:
|
||||
versionSpec: '3.7'
|
||||
architecture: 'x64'
|
||||
|
||||
- bash: python -m pip install "PyQt5==5.9.*"
|
||||
displayName: Installing PyQt5
|
||||
|
||||
- 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
|
||||
|
||||
- bash: jupyter labextension list
|
||||
displayName: Listing Jupyter Lab extensions
|
||||
|
||||
- bash: jupyter serverextension list
|
||||
displayName: Listing Jupyter Server extensions
|
||||
|
||||
- bash: python .check_enabled.py
|
||||
displayName: Checking that plugins are enabled
|
||||
|
||||
- ${{ if ne(parameters.os, 'macosx') }}:
|
||||
- bash: python -m pytest pywwt --cov pywwt
|
||||
displayName: Running tests
|
||||
env:
|
||||
DISPLAY: :99.0
|
||||
CI: true
|
||||
|
||||
- bash: python -m pip install codecov
|
||||
displayName: Installing codecov
|
||||
|
||||
- bash: python -m codecov --name ${{ parameters.name }}_${{ python_version }} -t $(codecov.token)
|
||||
displayName: Running codecov
|
||||
|
||||
- bash: python -m pip install .[docs]
|
||||
displayName: Installing documentation dependencies
|
||||
|
||||
- bash: make -C docs html linkcheck
|
||||
displayName: Building docs
|
|
@ -60,7 +60,7 @@ If you install pywwt using pip or conda as described above, any required
|
|||
dependencies will get installed automatically (with the exception of PyQt/PySide
|
||||
if using pip). For the record, these dependencies are as follows:
|
||||
|
||||
* `Python <https://www.python.org>`_ 2.7, or 3.5 or later
|
||||
* `Python <https://www.python.org>`_ 3.6 or later
|
||||
* `NumPy <https://www.numpy.org>`_ 1.9 or later
|
||||
* `Matplotlib <https://matplotlib.org>`_ 1.5 or later
|
||||
* `Astropy <https://www.astropy.org>`_ 1.0 or later
|
||||
|
@ -72,7 +72,6 @@ if using pip). For the record, these dependencies are as follows:
|
|||
* `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:
|
||||
|
@ -82,8 +81,7 @@ 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>`_
|
||||
* `tornado <https://www.tornadoweb.org/en/stable/>`_
|
||||
|
||||
For the Jupyter widget, you will need:
|
||||
|
||||
|
|
|
@ -6,5 +6,5 @@ __version__ = '%s.%s.%s%s' % (
|
|||
version_info[0],
|
||||
version_info[1],
|
||||
version_info[2],
|
||||
'' if version_info[3]=='final' else _specifier_[version_info[3]]+str(version_info[4])
|
||||
'' if version_info[3] == 'final' else _specifier_[version_info[3]]+str(version_info[4])
|
||||
)
|
||||
|
|
|
@ -2,7 +2,6 @@ import uuid
|
|||
from traitlets import HasTraits, TraitError, validate
|
||||
from astropy import units as u
|
||||
from astropy.coordinates import concatenate, SkyCoord
|
||||
import requests
|
||||
import numpy as np
|
||||
|
||||
from .traits import (Color, ColorWithOpacity, Bool,
|
||||
|
@ -164,7 +163,7 @@ class Circle(Annotation):
|
|||
|
||||
def _serialize_state(self):
|
||||
state = super(Circle, self)._serialize_state()
|
||||
state['settings']['skyRelative'] = self.radius.unit.is_equivalent(u.degree)
|
||||
state['settings']['skyRelative'] = self.radius.unit.is_equivalent(u.degree)
|
||||
state['center'] = {'ra': self._center.ra.deg,
|
||||
'dec': self._center.dec.deg}
|
||||
return state
|
||||
|
|
|
@ -16,5 +16,6 @@ def get_qapp():
|
|||
|
||||
def cleanup_qapp():
|
||||
global app
|
||||
app.quit()
|
||||
app = None
|
||||
if app is not None:
|
||||
app.exit()
|
||||
app = None
|
||||
|
|
|
@ -42,6 +42,7 @@ if QT_INSTALLED and OPENGL_INSTALLED:
|
|||
|
||||
_cached_opengl_renderer = ''
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
global _cached_opengl_renderer
|
||||
|
||||
|
@ -88,7 +89,6 @@ def pytest_report_header(config):
|
|||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
|
||||
if QT_INSTALLED:
|
||||
from .app import cleanup_qapp
|
||||
cleanup_qapp()
|
||||
|
|
|
@ -3,7 +3,6 @@ from traitlets import HasTraits, observe, validate, TraitError
|
|||
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
|
||||
|
@ -16,7 +15,6 @@ from .instruments import Instruments
|
|||
from .utils import ensure_utc
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
|
@ -128,11 +126,17 @@ class BaseWWTWidget(HasTraits):
|
|||
help='Whether to only show boundaries for '
|
||||
'the selected constellation '
|
||||
'(`bool`)').tag(wwt='showConstellationSelection', wwt_reset=True)
|
||||
#constellation_pictures = Bool(False, help='Whether to show pictures of the constellations\' mythological representations (`bool`)').tag(wwt='showConstellationPictures', wwt_reset=True)
|
||||
#constellation_labels = Bool(False, help='Whether to show labelss for constellations (`bool`)').tag(wwt='showConstellationLabels', wwt_reset=True)
|
||||
|
||||
# constellation_pictures = Bool(False,
|
||||
# help='Whether to show pictures of the constellations\' '
|
||||
# 'mythological representations '
|
||||
# '(`bool`)').tag(wwt='showConstellationPictures', wwt_reset=True)
|
||||
# constellation_labels = Bool(False,
|
||||
# help='Whether to show labelss for constellations '
|
||||
# '(`bool`)').tag(wwt='showConstellationLabels', wwt_reset=True)
|
||||
|
||||
crosshairs = Bool(False, help='Whether to show crosshairs at the center of '
|
||||
'the field (`bool`)').tag(wwt='showCrosshairs', wwt_reset=True)
|
||||
'the field (`bool`)').tag(wwt='showCrosshairs', wwt_reset=True)
|
||||
crosshairs_color = Color('white',
|
||||
help='The color of the crosshairs '
|
||||
'(`str` or `tuple`)').tag(wwt='crosshairsColor', wwt_reset=True)
|
||||
|
@ -260,10 +264,14 @@ class BaseWWTWidget(HasTraits):
|
|||
'in the viewer (`bool`)').tag(wwt='galacticMode', wwt_reset=True)
|
||||
galactic_grid = Bool(False, help='Whether to show a grid relative to the '
|
||||
'galactic plane (`bool`)').tag(wwt='showGalacticGrid', wwt_reset=True)
|
||||
#galactic_text = Bool(False, help='Whether to show labels for the galactic grid\'s text (`bool`)').tag(wwt='showGalacticGridText', wwt_reset=True)
|
||||
# galactic_text = Bool(False,
|
||||
# help='Whether to show labels for the galactic grid\'s text '
|
||||
# '(`bool`)').tag(wwt='showGalacticGridText', wwt_reset=True)
|
||||
alt_az_grid = Bool(False, help='Whether to show an altitude-azimuth grid '
|
||||
'(`bool`)').tag(wwt='showAltAzGrid', wwt_reset=True)
|
||||
#alt_az_text = Bool(False, help='Whether to show labels for the altitude-azimuth grid\'s text (`bool`)').tag(wwt='showAltAzGridText', wwt_reset=True)
|
||||
# alt_az_text = Bool(False,
|
||||
# help='Whether to show labels for the altitude-azimuth grid\'s text '
|
||||
# '(`bool`)').tag(wwt='showAltAzGridText', wwt_reset=True)
|
||||
|
||||
local_horizon_mode = Bool(False, help='Whether the view should be that of '
|
||||
'a local latitude, longitude, and '
|
||||
|
@ -274,8 +282,8 @@ class BaseWWTWidget(HasTraits):
|
|||
'(:class:`~astropy.units.Quantity`)').tag(wwt='locationAltitude', wwt_reset=True)
|
||||
location_latitude = AstropyQuantity(47.633 * u.deg,
|
||||
help='The latitude of the viewing '
|
||||
'location in local horizon mode '
|
||||
'(:class:`~astropy.units.Quantity`)').tag(wwt='locationLat', wwt_reset=True)
|
||||
'location in local horizon mode '
|
||||
'(:class:`~astropy.units.Quantity`)').tag(wwt='locationLat', wwt_reset=True)
|
||||
location_longitude = AstropyQuantity(122.133333 * u.deg,
|
||||
help='The longitude of the viewing '
|
||||
'location in local horizon mode '
|
||||
|
@ -589,7 +597,7 @@ class BaseWWTWidget(HasTraits):
|
|||
If left blank, the WWT viewport will fill the enitre height of the browser.
|
||||
"""
|
||||
dest_root, dest_extension = os.path.splitext(dest)
|
||||
if (dest_extension and dest_extension != ".zip"):
|
||||
if (dest_extension and dest_extension != ".zip"):
|
||||
raise ValueError("'dest' must be either a directory or a .zip file")
|
||||
|
||||
is_compressed = dest_extension == '.zip'
|
||||
|
@ -610,10 +618,10 @@ class BaseWWTWidget(HasTraits):
|
|||
shutil.copy(os.path.join(nbexten_dir, 'wwt_json_api.js'), script_dir)
|
||||
shutil.copy(os.path.join(fig_src_dir, "interactive_figure.js"), script_dir)
|
||||
|
||||
self._serialize_to_json(os.path.join(figure_dir,'wwt_figure.json'), title, max_width, max_height)
|
||||
self._serialize_to_json(os.path.join(figure_dir, 'wwt_figure.json'), title, max_width, max_height)
|
||||
|
||||
if len(self.layers) > 0:
|
||||
data_dir = os.path.join(figure_dir,'data')
|
||||
data_dir = os.path.join(figure_dir, 'data')
|
||||
if not os.path.exists(data_dir):
|
||||
os.mkdir(data_dir)
|
||||
self._save_added_data(data_dir)
|
||||
|
@ -662,8 +670,8 @@ class BaseWWTWidget(HasTraits):
|
|||
|
||||
def _serialize_to_json(self, file, title, max_width, max_height):
|
||||
state = self._serialize_state(title, max_width, max_height)
|
||||
with open(file,'w') as file_obj:
|
||||
json.dump(state,file_obj)
|
||||
with open(file, 'w') as file_obj:
|
||||
json.dump(state, file_obj)
|
||||
|
||||
def _save_added_data(self, dir):
|
||||
self.layers._save_all_data_for_serialization(dir)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import time
|
||||
import asyncio
|
||||
import socket
|
||||
import logging
|
||||
from hashlib import md5
|
||||
|
@ -12,7 +13,7 @@ _data_server = None
|
|||
|
||||
def get_data_server(verbose=True):
|
||||
"""
|
||||
This starts up a flask server and returns a handle to a DataServer
|
||||
This starts up a tornado server and returns a handle to a DataServer
|
||||
object which can be used to register files to serve.
|
||||
"""
|
||||
|
||||
|
@ -21,32 +22,28 @@ def get_data_server(verbose=True):
|
|||
if _data_server is not None:
|
||||
return _data_server
|
||||
|
||||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import RequestHandler, Application
|
||||
from tornado.routing import PathMatches
|
||||
|
||||
class FlaskWrapper(Flask):
|
||||
class WebServer(Application):
|
||||
|
||||
port = None
|
||||
host = None
|
||||
port = None
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
self.host = kwargs.get('host', None)
|
||||
self.port = kwargs.get('port', None)
|
||||
def run(self, host=None, port=8886):
|
||||
self.host = host
|
||||
self.port = port
|
||||
try:
|
||||
super(FlaskWrapper, self).run(*args, **kwargs)
|
||||
self.listen(port)
|
||||
IOLoop.instance().start()
|
||||
finally:
|
||||
self.host = None
|
||||
self.port = None
|
||||
|
||||
app = FlaskWrapper('DataServer')
|
||||
CORS(app)
|
||||
if verbose:
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
class DataServer(object):
|
||||
|
||||
def __init__(self):
|
||||
def start(self, app):
|
||||
self._files = {}
|
||||
self._thread = Thread(target=self.start_app)
|
||||
self._thread.daemon = True
|
||||
|
@ -64,15 +61,22 @@ def get_data_server(verbose=True):
|
|||
return self._app.host
|
||||
|
||||
def start_app(self):
|
||||
host = socket.gethostbyname('localhost')
|
||||
for port in range(8000, 9000):
|
||||
try:
|
||||
return app.run(host=host, port=port)
|
||||
except Exception:
|
||||
pass
|
||||
raise Exception("Could not start up data server")
|
||||
|
||||
def serve_file(self, filename, real_name=False, extension=''):
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
||||
host = socket.gethostbyname('localhost')
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.bind(('localhost', 0))
|
||||
port = sock.getsockname()[1]
|
||||
sock.close()
|
||||
|
||||
access_log = logging.getLogger("tornado.access")
|
||||
access_log.setLevel('ERROR')
|
||||
|
||||
self._app.run(host=host, port=port)
|
||||
|
||||
def serve_file(self, filename, real_name=True, extension=''):
|
||||
with open(filename, 'rb') as f:
|
||||
content = f.read()
|
||||
if real_name:
|
||||
|
@ -88,9 +92,13 @@ def get_data_server(verbose=True):
|
|||
|
||||
ds = DataServer()
|
||||
|
||||
@app.route("/data/<hash>")
|
||||
def data(hash):
|
||||
return ds.get_file_contents(hash)
|
||||
class DataHandler(RequestHandler):
|
||||
async def get(self, hash):
|
||||
self.write(ds.get_file_contents(hash))
|
||||
|
||||
app = WebServer([(PathMatches(r"/data/(?P<hash>\S+)"), DataHandler)])
|
||||
|
||||
ds.start(app)
|
||||
|
||||
_data_server = ds
|
||||
|
||||
|
|
|
@ -60,35 +60,35 @@ class ImageryLayers():
|
|||
# (og_list) into a dict.
|
||||
for layer in og_list:
|
||||
|
||||
if re.search(r'(?i)gamma',layer) is not None:
|
||||
if re.search(r'(?i)gamma', layer) is not None:
|
||||
self._add2dict(self._layers, layer, 'gamma')
|
||||
continue # automatically advance to next iteration
|
||||
continue # automatically advance to next iteration
|
||||
|
||||
if re.search(r'(?i)x(-|\s)?ray',layer) is not None:
|
||||
if re.search(r'(?i)x(-|\s)?ray', layer) is not None:
|
||||
self._add2dict(self._layers, layer, 'x')
|
||||
continue
|
||||
|
||||
if (re.search(r'(?i)ultra(-|\s)?violet',layer) is not None or
|
||||
re.search(r'(?i)[^\d\w]+uv|uv[^\d\w]+',layer) is not None):
|
||||
if (re.search(r'(?i)ultra(-|\s)?violet', layer) is not None or
|
||||
re.search(r'(?i)[^\d\w]+uv|uv[^\d\w]+', layer) is not None):
|
||||
self._add2dict(self._layers, layer, 'uv')
|
||||
continue
|
||||
|
||||
if (re.search(r'(?i)optical',layer) is not None or
|
||||
re.search(r'(?i)visible',layer) is not None):
|
||||
if (re.search(r'(?i)optical', layer) is not None or
|
||||
re.search(r'(?i)visible', layer) is not None):
|
||||
self._add2dict(self._layers, layer, 'visible')
|
||||
continue
|
||||
|
||||
if (re.search(r'(?i)infrared',layer) is not None or
|
||||
re.search(r'(?i)[^\d\w]+ir|ir[^\d\w]+',layer) is not None):
|
||||
if (re.search(r'(?i)infrared', layer) is not None or
|
||||
re.search(r'(?i)[^\d\w]+ir|ir[^\d\w]+', layer) is not None):
|
||||
self._add2dict(self._layers, layer, 'ir')
|
||||
continue
|
||||
|
||||
if (re.search(r'(?i)microwave',layer) is not None or
|
||||
re.search(r'(?i)[^\d\w]+cmb|cmb[^\d\w]+',layer) is not None):
|
||||
if (re.search(r'(?i)microwave', layer) is not None or
|
||||
re.search(r'(?i)[^\d\w]+cmb|cmb[^\d\w]+', layer) is not None):
|
||||
self._add2dict(self._layers, layer, 'micro')
|
||||
continue
|
||||
|
||||
if re.search(r'(?i)radio',layer) is not None:
|
||||
if re.search(r'(?i)radio', layer) is not None:
|
||||
self._add2dict(self._layers, layer, 'radio')
|
||||
continue
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import requests
|
||||
|
||||
|
||||
class Instruments():
|
||||
"""
|
||||
A supplemental class that enables tab-completion for available
|
||||
|
|
|
@ -3,14 +3,10 @@
|
|||
# because we instead use JSON messages to transmit any changes between the
|
||||
# Python and Javascript parts so that we can re-use this for the Qt client.
|
||||
|
||||
import sys
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
import ipywidgets as widgets
|
||||
from traitlets import Unicode, Float, default, link, directional_link
|
||||
|
||||
if not PY2:
|
||||
from ipyevents import Event as DOMListener
|
||||
from ipyevents import Event as DOMListener
|
||||
|
||||
from .core import BaseWWTWidget
|
||||
from .layers import ImageLayer
|
||||
|
@ -21,8 +17,7 @@ __all__ = ['WWTJupyterWidget']
|
|||
VIEW_MODULE_VERSION = '0.8.0' # synchronize these with lib/wwt.js
|
||||
MODEL_MODULE_VERSION = '0.8.0'
|
||||
|
||||
if not PY2:
|
||||
dom_listener = DOMListener()
|
||||
dom_listener = DOMListener()
|
||||
|
||||
|
||||
@widgets.register
|
||||
|
@ -48,10 +43,9 @@ class WWTJupyterWidget(widgets.DOMWidget, BaseWWTWidget):
|
|||
def __init__(self):
|
||||
widgets.DOMWidget.__init__(self)
|
||||
BaseWWTWidget.__init__(self)
|
||||
if not PY2:
|
||||
dom_listener.source = self
|
||||
dom_listener.prevent_default_action = True
|
||||
dom_listener.watched_events = ['wheel']
|
||||
dom_listener.source = self
|
||||
dom_listener.prevent_default_action = True
|
||||
dom_listener.watched_events = ['wheel']
|
||||
self._controls = None
|
||||
|
||||
@default('layout')
|
||||
|
@ -107,7 +101,7 @@ class JupyterImageLayer(ImageLayer):
|
|||
|
||||
@property
|
||||
def controls(self):
|
||||
from .layers import VALID_STRETCHES, VALID_COLORMAPS, UI_COLORMAPS
|
||||
from .layers import VALID_STRETCHES, UI_COLORMAPS
|
||||
|
||||
if self._controls is not None:
|
||||
return self._controls
|
||||
|
|
|
@ -88,7 +88,7 @@ def _compute_notebook_server_base_url():
|
|||
for s in list_running_servers():
|
||||
response = requests.get(
|
||||
requests.compat.urljoin(s['url'], 'api/sessions'),
|
||||
params = {'token': s.get('token', '')}
|
||||
params={'token': s.get('token', '')}
|
||||
)
|
||||
|
||||
for n in json.loads(response.text):
|
||||
|
@ -100,6 +100,7 @@ def _compute_notebook_server_base_url():
|
|||
|
||||
_server_base_url = None
|
||||
|
||||
|
||||
def get_notebook_server_base_url():
|
||||
"""Get the "base_url" of the current Jupyter notebook server.
|
||||
|
||||
|
@ -144,9 +145,9 @@ def load_jupyter_server_extension(nb_server_app):
|
|||
with open(CONFIG, 'w') as f:
|
||||
json.dump(config, f)
|
||||
|
||||
mimetypes.add_type('image/fits','.fits')
|
||||
mimetypes.add_type('image/fits','.fts')
|
||||
mimetypes.add_type('image/fits','.fit')
|
||||
mimetypes.add_type('image/fits', '.fits')
|
||||
mimetypes.add_type('image/fits', '.fts')
|
||||
mimetypes.add_type('image/fits', '.fit')
|
||||
|
||||
route_pattern = url_path_join(web_app.settings['base_url'], '/wwt/(.*)')
|
||||
web_app.add_handlers(host_pattern, [(route_pattern, WWTFileHandler)])
|
||||
|
|
|
@ -24,7 +24,6 @@ from astropy.table import Column
|
|||
from astropy.time import Time
|
||||
from datetime import datetime
|
||||
|
||||
from ipywidgets import HBox, Dropdown, FloatText, FloatSlider, link
|
||||
from traitlets import HasTraits, validate, observe
|
||||
from .traits import Color, Bool, Float, Unicode, AstropyQuantity, Any, to_hex
|
||||
from .utils import sanitize_image, validate_traits, ensure_utc
|
||||
|
@ -81,7 +80,7 @@ UI_COLORMAPS = OrderedDict([
|
|||
|
||||
# Save string types for validating ISOT strings in time series tables
|
||||
if sys.version_info[0] == 2:
|
||||
STR_TYPE = basestring
|
||||
STR_TYPE = basestring # noqa
|
||||
NP_STR_TYPE = np.string_
|
||||
else:
|
||||
STR_TYPE = str
|
||||
|
@ -176,7 +175,7 @@ def csv_table_win_newline(table):
|
|||
s = StringIO()
|
||||
table.write(s, format='ascii.basic', delimiter=',', comment=False)
|
||||
s.seek(0)
|
||||
#Replace single \r or \n characters with \r\n
|
||||
# Replace single \r or \n characters with \r\n
|
||||
return re.sub(r"(?<![\r\n])(\r|\n)(?![\r\n])", "\r\n", s.read())
|
||||
|
||||
|
||||
|
@ -299,7 +298,7 @@ class LayerManager(object):
|
|||
|
||||
return layer_states
|
||||
|
||||
def _save_all_data_for_serialization (self, dir):
|
||||
def _save_all_data_for_serialization(self, dir):
|
||||
for layer in self._layers:
|
||||
layer._save_data_for_serialization(dir)
|
||||
|
||||
|
@ -506,7 +505,6 @@ class TableLayer(HasTraits):
|
|||
else:
|
||||
raise ValueError('alt_type should be one of {0}'.format('/'.join(str(x) for x in VALID_ALT_TYPES)))
|
||||
|
||||
|
||||
@validate('time_att')
|
||||
def _check_time_att(self, proposal):
|
||||
# Parse the time_att column and make sure it's in the proper format
|
||||
|
@ -514,11 +512,11 @@ class TableLayer(HasTraits):
|
|||
col = self.table[proposal['value']]
|
||||
|
||||
if (all(isinstance(t, datetime) for t in col)
|
||||
or all(isinstance(t, Time) for t in col)):
|
||||
or all(isinstance(t, Time) for t in col)):
|
||||
return proposal['value']
|
||||
|
||||
elif (isinstance(col, STR_TYPE)
|
||||
or np.issubdtype(col.dtype, NP_STR_TYPE)):
|
||||
or np.issubdtype(col.dtype, NP_STR_TYPE)):
|
||||
|
||||
try:
|
||||
Time(col, format='isot')
|
||||
|
@ -753,7 +751,7 @@ class TableLayer(HasTraits):
|
|||
@observe('time_att')
|
||||
def _on_time_att_change(self, *value):
|
||||
|
||||
if len(self.time_att) == 0 or self.time_series == False:
|
||||
if len(self.time_att) == 0 or self.time_series is False:
|
||||
self.parent._send_msg(event='table_layer_set', id=self.id,
|
||||
setting='startDateColumn', value=-1)
|
||||
return
|
||||
|
@ -883,9 +881,9 @@ class TableLayer(HasTraits):
|
|||
return state
|
||||
|
||||
def _save_data_for_serialization(self, dir):
|
||||
file_path = path.join(dir,"{0}.csv".format(self.id))
|
||||
file_path = path.join(dir, "{0}.csv".format(self.id))
|
||||
table_str = csv_table_win_newline(self.table)
|
||||
with open(file_path, 'wb') as file: # binary mode to preserve windows line endings
|
||||
with open(file_path, 'wb') as file: # binary mode to preserve windows line endings
|
||||
file.write(table_str.encode('ascii', errors='replace'))
|
||||
|
||||
def __str__(self):
|
||||
|
@ -1030,7 +1028,8 @@ class ImageLayer(HasTraits):
|
|||
'settings': {}
|
||||
}
|
||||
|
||||
#A bit overkill for just the opacity, but more future-proof in case we add more wwt traits
|
||||
# A bit overkill for just the opacity, but more future-proof in case
|
||||
# we add more wwt traits
|
||||
for trait in self.traits().values():
|
||||
wwt_name = trait.metadata.get('wwt')
|
||||
if wwt_name:
|
||||
|
@ -1047,8 +1046,8 @@ class ImageLayer(HasTraits):
|
|||
return state
|
||||
|
||||
def _save_data_for_serialization(self, dir):
|
||||
file_path = path.join(dir,"{0}.fits".format(self.id))
|
||||
shutil.copyfile(self._sanitized_image,file_path)
|
||||
file_path = path.join(dir, "{0}.fits".format(self.id))
|
||||
shutil.copyfile(self._sanitized_image, file_path)
|
||||
|
||||
def __str__(self):
|
||||
return 'ImageLayer'
|
||||
|
|
|
@ -5,4 +5,3 @@ def _jupyter_nbextension_paths():
|
|||
'dest': 'pywwt',
|
||||
'require': 'pywwt/extension'
|
||||
}]
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ function wwt_apply_json_message(wwt, msg) {
|
|||
// order.
|
||||
|
||||
if (msg['version'] > layer._stretch_version) {
|
||||
layer.setImageScale(msg['stretch'], msg['vmin'], msg['vmax']);
|
||||
layer.setImageScalePhysical(msg['stretch'], msg['vmin'], msg['vmax']);
|
||||
layer._stretch_version = msg['version'];
|
||||
layer.getFitsImage().transparentBlack = false;
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ function wwt_apply_json_message(wwt, msg) {
|
|||
} else if(name == 'color') {
|
||||
value = wwtlib.Color.fromHex(msg['value']);
|
||||
} else if(name == 'colorMapper') {
|
||||
value = wwtlib.ColorMapContainer.fromNestedLists(msg['value']);
|
||||
value = wwtlib.ColorMapContainer.fromArgbList(msg['value']);
|
||||
} else if(name == 'altUnit') {
|
||||
value = wwtlib.AltUnits[msg['value']];
|
||||
} else if(name == 'raUnits') {
|
||||
|
|
|
@ -16,7 +16,7 @@ class SolarSystem(HasTraits):
|
|||
super(SolarSystem, self).__init__()
|
||||
self.base_widget = base_wwt_widget
|
||||
self.observe(self._on_trait_change, type='change')
|
||||
self._tracked_obj_id = 0 #Default to tracking sun
|
||||
self._tracked_obj_id = 0 # Default to tracking sun
|
||||
|
||||
def _on_trait_change(self, changed):
|
||||
# This method gets called anytime a trait gets changed. Since this class
|
||||
|
@ -31,21 +31,23 @@ class SolarSystem(HasTraits):
|
|||
new_value = new_value.value
|
||||
|
||||
self.base_widget._send_msg(event='setting_set',
|
||||
setting=wwt_name,
|
||||
value=new_value)
|
||||
setting=wwt_name,
|
||||
value=new_value)
|
||||
|
||||
#cmb = Bool(False, help='Whether to show the cosmic microwave background in solar system mode (`bool`)').tag(wwt='solarSystemCMB') ###
|
||||
cosmos = Bool(True, help='Whether to show data from the SDSS survey (`bool`)').tag(wwt='solarSystemCosmos') ###
|
||||
#display = Bool(False, help='Whether to show the solar system while in solar system mode (`bool`)').tag(wwt='solarSystemOverlays') ###
|
||||
# cmb = Bool(False, help='Whether to show the cosmic microwave background in solar system mode (`bool`)').tag(wwt='solarSystemCMB')
|
||||
cosmos = Bool(True, help='Whether to show data from the SDSS survey (`bool`)').tag(wwt='solarSystemCosmos')
|
||||
# display = Bool(False, help='Whether to show the solar system while in solar system mode (`bool`)').tag(wwt='solarSystemOverlays')
|
||||
lighting = Bool(True,
|
||||
help='Whether to show the lighting effect of the Sun on the'
|
||||
' solar system (`bool`)').tag(wwt='solarSystemLighting')
|
||||
milky_way = Bool(True, help='Whether to show the galactic bulge in the '
|
||||
'background in solar system mode '
|
||||
'(`bool`)').tag(wwt='solarSystemMilkyWay')
|
||||
#multi_res = Bool(False, help='Whether to show the multi-resolution textures for planets where available (`bool`)').tag(wwt='solarSystemMultiRes') ###
|
||||
minor_orbits = Bool(False, help='Whether to show the orbits of minor planets in solar system mode (`bool`)').tag(wwt='solarSystemMinorOrbits')
|
||||
#minor_planets = Bool(False, help='Whether to show minor planets in solar system mode (`bool`)').tag(wwt='solarSystemMinorPlanets') ###
|
||||
# multi_res = Bool(False, help='Whether to show the multi-resolution textures for planets where available (`bool`)').tag(wwt='solarSystemMultiRes')
|
||||
minor_orbits = Bool(False,
|
||||
help='Whether to show the orbits of minor planets '
|
||||
'in solar system mode (`bool`)').tag(wwt='solarSystemMinorOrbits')
|
||||
# minor_planets = Bool(False, help='Whether to show minor planets in solar system mode (`bool`)').tag(wwt='solarSystemMinorPlanets')
|
||||
orbits = Bool(True,
|
||||
help='Whether to show orbit paths when the solar system is '
|
||||
'displayed (`bool`)').tag(wwt='solarSystemOrbits')
|
||||
|
|
Двоичные данные
pywwt/tests/data/webengine/image_layer_equ.png
До Ширина: | Высота: | Размер: 3.4 KiB После Ширина: | Высота: | Размер: 5.5 KiB |
Двоичные данные
pywwt/tests/data/webengine/image_layer_gal.png
До Ширина: | Высота: | Размер: 18 KiB После Ширина: | Высота: | Размер: 32 KiB |
Двоичные данные
pywwt/tests/data/webengine/sky_layers.png
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 22 KiB |
Двоичные данные
pywwt/tests/data/webengine_osx/image_layer_equ.png
До Ширина: | Высота: | Размер: 3.4 KiB После Ширина: | Высота: | Размер: 5.5 KiB |
Двоичные данные
pywwt/tests/data/webengine_osx/image_layer_gal.png
До Ширина: | Высота: | Размер: 18 KiB После Ширина: | Высота: | Размер: 32 KiB |
Двоичные данные
pywwt/tests/data/webengine_osx/sky_layers.png
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 22 KiB |
Двоичные данные
pywwt/tests/data/webkit/image_layer_equ.png
До Ширина: | Высота: | Размер: 3.4 KiB После Ширина: | Высота: | Размер: 5.5 KiB |
Двоичные данные
pywwt/tests/data/webkit/image_layer_gal.png
До Ширина: | Высота: | Размер: 18 KiB После Ширина: | Высота: | Размер: 32 KiB |
Двоичные данные
pywwt/tests/data/webkit/sky_layers.png
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 22 KiB |
Двоичные данные
pywwt/tests/data/webkit_win/image_layer_equ.png
До Ширина: | Высота: | Размер: 3.4 KiB После Ширина: | Высота: | Размер: 5.5 KiB |
Двоичные данные
pywwt/tests/data/webkit_win/image_layer_gal.png
До Ширина: | Высота: | Размер: 18 KiB После Ширина: | Высота: | Размер: 32 KiB |
Двоичные данные
pywwt/tests/data/webkit_win/sky_layers.png
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 22 KiB |
|
@ -178,7 +178,7 @@ class TestLayers:
|
|||
|
||||
self.table['flux'].unit = 'm'
|
||||
layer = self.client.layers.add_table_layer(table=self.table,
|
||||
lon_att='ra', lat_att='dec', alt_att='flux')
|
||||
lon_att='ra', lat_att='dec', alt_att='flux')
|
||||
|
||||
assert layer.lon_att == 'ra'
|
||||
assert layer.lon_unit is u.deg
|
||||
|
@ -213,31 +213,28 @@ class TestLayers:
|
|||
assert layer.alt_att == ''
|
||||
|
||||
def test_line_endings(self):
|
||||
self.table['ra'] = [1,2,3]
|
||||
self.table['ra'] = [1, 2, 3]
|
||||
expected_str = "flux,dec,ra\r\n"\
|
||||
"2,4,1\r\n"\
|
||||
"3,5,2\r\n"\
|
||||
"4,6,3\r\n"
|
||||
"2,4,1\r\n"\
|
||||
"3,5,2\r\n"\
|
||||
"4,6,3\r\n"
|
||||
assert csv_table_win_newline(self.table) == expected_str
|
||||
|
||||
def test_deprecated_api_call(self, capsys):
|
||||
def test_deprecated_api_call(self):
|
||||
"""For the time being, test that the deprecated name for this function still
|
||||
works, but issues a warning
|
||||
|
||||
"""
|
||||
import warnings
|
||||
|
||||
assert len(self.client.layers) == 0
|
||||
assert str(self.client.layers) == 'Layer manager with no layers'
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
layer1 = self.client.layers.add_data_layer(table=self.table)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
self.client.layers.add_data_layer(table=self.table)
|
||||
|
||||
assert len(self.client.layers) == 1
|
||||
assert str(self.client.layers) == ('Layer manager with 1 layers:\n\n'
|
||||
' [0]: TableLayer with 3 markers\n')
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[-1].category, DeprecationWarning)
|
||||
|
||||
def test_cartesian_layer(self):
|
||||
|
||||
|
@ -320,7 +317,7 @@ def test_table_layers_image(tmpdir, wwt_qt_client):
|
|||
table['dec'] = [84, 85, 86, 87, 88]
|
||||
table['ra'] = [250, 260, 270, 280, 290] * u.deg
|
||||
|
||||
layer1 = wwt.layers.add_table_layer(table=table)
|
||||
layer1 = wwt.layers.add_table_layer(table=table) # noqa
|
||||
|
||||
# Case where we change the default values on initialization
|
||||
|
||||
|
@ -330,7 +327,7 @@ def test_table_layers_image(tmpdir, wwt_qt_client):
|
|||
table['ra'] = [250, 260, 270, 280, 290] * u.deg
|
||||
table['other'] = [255, 265, 275, 285, 295] * u.deg
|
||||
|
||||
layer2 = wwt.layers.add_table_layer(table=table, color='red', lon_att='other', size_scale=100, opacity=0.5)
|
||||
layer2 = wwt.layers.add_table_layer(table=table, color='red', lon_att='other', size_scale=100, opacity=0.5) # noqa
|
||||
|
||||
# Case where we change the values after initialization
|
||||
|
||||
|
@ -357,7 +354,7 @@ def test_table_layers_image(tmpdir, wwt_qt_client):
|
|||
table['ra'] = [250, 260, 270, 280, 290] * u.deg
|
||||
table['other'] = [255, 265, 275, 285, 295] * u.deg
|
||||
|
||||
layer4 = wwt.layers.add_table_layer(table=table, cmap_att='other', size_att='flux')
|
||||
layer4 = wwt.layers.add_table_layer(table=table, cmap_att='other', size_att='flux', size_scale=100) # noqa
|
||||
|
||||
# Case with size and color encoding where we change the values after initialization
|
||||
|
||||
|
@ -373,6 +370,7 @@ def test_table_layers_image(tmpdir, wwt_qt_client):
|
|||
|
||||
layer5.cmap_att = 'other'
|
||||
layer5.size_att = 'flux'
|
||||
layer5.size_scale = 100
|
||||
|
||||
wwt.wait(2)
|
||||
|
||||
|
@ -403,16 +401,17 @@ def test_table_layers_cartesian_image(tmpdir, wwt_qt_client):
|
|||
table['y'] = [0, 0.2, 0.4, 0.6, 0.8] * u.au
|
||||
table['z'] = [0, 0.1, 0.2, 0.3, 0.4] * u.au
|
||||
|
||||
layer1 = wwt.layers.add_table_layer(table=table, coord_type='rectangular', size_scale=100, frame='Sky')
|
||||
layer1 = wwt.layers.add_table_layer(table=table, coord_type='rectangular', size_scale=100, frame='Sky') # noqa
|
||||
|
||||
table = Table()
|
||||
table['x'] = [1, 2, 3, 4, 5] * u.au
|
||||
table['y'] = [-0.2, 0, 0.2, 0.4, 0.6 ] * u.au
|
||||
table['y'] = [-0.2, 0, 0.2, 0.4, 0.6] * u.au
|
||||
table['z'] = [0, 0.2, 0.4, 0.6, 0.8] * u.au
|
||||
|
||||
layer2 = wwt.layers.add_table_layer(table=table, coord_type='rectangular', frame='Sky')
|
||||
layer2.cmap_att = 'x'
|
||||
layer2.size_att = 'x'
|
||||
layer2.size_scale = 100
|
||||
|
||||
wwt.wait(2)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from ..core import BaseWWTWidget
|
||||
from ..layers import SIZE_COLUMN_NAME, CMAP_COLUMN_NAME
|
||||
from ..layers import SIZE_COLUMN_NAME, CMAP_COLUMN_NAME
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
@ -18,8 +18,11 @@ STARDARD_WWT_SETTINGS = ['actualPlanetScale', 'showAltAzGrid', 'showConstellatio
|
|||
'locationAltitude', 'locationLat', 'locationLng']
|
||||
|
||||
|
||||
# Mock class so that we test serialization without instantiating an actual widget
|
||||
class MockWWTWidget(BaseWWTWidget):
|
||||
"""
|
||||
Mock class so that we test serialization without instantiating an actual widget
|
||||
"""
|
||||
|
||||
def quick_serialize(self):
|
||||
return self._serialize_state(None, None, None)
|
||||
|
||||
|
@ -30,6 +33,7 @@ class MockWWTWidget(BaseWWTWidget):
|
|||
def _serve_file(self, filename, extension=''):
|
||||
return filename
|
||||
|
||||
|
||||
def test_basic_serialization():
|
||||
widget = MockWWTWidget()
|
||||
test_state = widget._serialize_state('Title', 100, 200)
|
||||
|
@ -62,6 +66,7 @@ def test_basic_serialization():
|
|||
assert 'annotations' in test_state
|
||||
assert test_state['annotations'] == []
|
||||
|
||||
|
||||
def test_widget_settings_serialization():
|
||||
widget = MockWWTWidget()
|
||||
widget.actual_planet_scale = True
|
||||
|
@ -71,9 +76,9 @@ def test_widget_settings_serialization():
|
|||
widget.constellation_figure_color = '#24680b'
|
||||
widget.constellation_figures = False
|
||||
widget.constellation_selection = True
|
||||
widget.constellation_selection_color = 'c' #cyan
|
||||
widget.constellation_selection_color = 'c' # cyan
|
||||
widget.crosshairs = True
|
||||
widget.crosshairs_color = (128./255.,64./255.,16./255.)
|
||||
widget.crosshairs_color = (128./255., 64./255., 16./255.)
|
||||
widget.ecliptic = False
|
||||
widget.ecliptic_grid = True
|
||||
widget.galactic_grid = False
|
||||
|
@ -83,36 +88,63 @@ def test_widget_settings_serialization():
|
|||
widget.location_altitude = 7*u.m
|
||||
widget.location_latitude = 12*u.deg
|
||||
widget.location_longitude = -18*u.deg
|
||||
expected_settings = {'actualPlanetScale': True, 'showAltAzGrid': False, 'showConstellationBoundries': True,
|
||||
'constellationBoundryColor': '#ff0000', 'constellationFigureColor': '#24680b',
|
||||
'showConstellationFigures': False, 'showConstellationSelection': True,
|
||||
'constellationSelectionColor': '#00bfbf', 'showCrosshairs': True, 'crosshairsColor': '#804010',
|
||||
'showEcliptic': False, 'showEclipticGrid': True, 'showGalacticGrid': False,
|
||||
'galacticMode': True, 'showGrid': False, 'localHorizonMode': True, 'locationAltitude': 7,
|
||||
'locationLat': 12., 'locationLng': -18.}
|
||||
expected_settings = {'actualPlanetScale': True,
|
||||
'showAltAzGrid': False,
|
||||
'showConstellationBoundries': True,
|
||||
'constellationBoundryColor': '#ff0000',
|
||||
'constellationFigureColor': '#24680b',
|
||||
'showConstellationFigures': False,
|
||||
'showConstellationSelection': True,
|
||||
'constellationSelectionColor': '#00bfbf',
|
||||
'showCrosshairs': True,
|
||||
'crosshairsColor': '#804010',
|
||||
'showEcliptic': False,
|
||||
'showEclipticGrid': True,
|
||||
'showGalacticGrid': False,
|
||||
'galacticMode': True,
|
||||
'showGrid': False,
|
||||
'localHorizonMode': True,
|
||||
'locationAltitude': 7,
|
||||
'locationLat': 12.,
|
||||
'locationLng': -18.}
|
||||
state = widget.quick_serialize()
|
||||
assert state['wwt_settings'] == expected_settings
|
||||
|
||||
|
||||
def test_mode_serialization():
|
||||
view_mode_map = {'sky': 'sky', 'Sun': 'sun', 'Mercury': 'mercury', 'venus': 'venus', 'Earth': 'Bing Maps Aerial',
|
||||
'moon': 'moon', 'mars': 'Visible Imagery', 'jupiter': 'jupiter', 'callisto': 'callisto',
|
||||
'europa': 'europa', 'ganymede': 'ganymede', 'Io': 'io', 'saturn': 'saturn', 'Uranus': 'uranus',
|
||||
'neptune': 'neptune', 'Pluto': 'pluto', 'panorama': 'panorama',
|
||||
'Solar System': '3D Solar System View', 'milky way': '3D Solar System View',
|
||||
view_mode_map = {'sky': 'sky',
|
||||
'Sun': 'sun',
|
||||
'Mercury': 'mercury',
|
||||
'venus': 'venus',
|
||||
'Earth': 'Bing Maps Aerial',
|
||||
'moon': 'moon',
|
||||
'mars': 'Visible Imagery',
|
||||
'jupiter': 'jupiter',
|
||||
'callisto': 'callisto',
|
||||
'europa': 'europa',
|
||||
'ganymede': 'ganymede',
|
||||
'Io': 'io',
|
||||
'saturn': 'saturn',
|
||||
'Uranus': 'uranus',
|
||||
'neptune': 'neptune',
|
||||
'Pluto': 'pluto',
|
||||
'panorama': 'panorama',
|
||||
'Solar System': '3D Solar System View',
|
||||
'milky way': '3D Solar System View',
|
||||
'universe': '3D Solar System View'}
|
||||
|
||||
widget = MockWWTWidget()
|
||||
for in_mode, out_mode in view_mode_map.items():
|
||||
widget.set_view(in_mode)
|
||||
assert widget.quick_serialize()['view_settings']['mode'] == out_mode, 'Mismatch for requested mode: {0}'.format(in_mode)
|
||||
assert widget.quick_serialize()['view_settings']['mode'] == out_mode, \
|
||||
'Mismatch for requested mode: {0}'.format(in_mode)
|
||||
|
||||
|
||||
def test_3d_serialization():
|
||||
widget = MockWWTWidget()
|
||||
widget.set_view('milky way')
|
||||
widget.solar_system.cosmos=True
|
||||
widget.solar_system.lighting=False
|
||||
widget.solar_system.cosmos = True
|
||||
widget.solar_system.lighting = False
|
||||
widget.solar_system.milky_way = True
|
||||
widget.solar_system.minor_orbits = False
|
||||
widget.solar_system.orbits = True
|
||||
|
@ -120,10 +152,14 @@ def test_3d_serialization():
|
|||
widget.solar_system.scale = 8
|
||||
widget.solar_system.stars = True
|
||||
|
||||
expected_3d_settings = {'solarSystemCosmos':True, 'solarSystemLighting':False, 'solarSystemMilkyWay':True,
|
||||
'solarSystemMinorOrbits':False, 'solarSystemOrbits':True, 'solarSystemPlanets':False,
|
||||
expected_3d_settings = {'solarSystemCosmos': True,
|
||||
'solarSystemLighting': False,
|
||||
'solarSystemMilkyWay': True,
|
||||
'solarSystemMinorOrbits': False,
|
||||
'solarSystemOrbits': True,
|
||||
'solarSystemPlanets': False,
|
||||
'solarSystemScale': '8', # The validation method casts the int to a string
|
||||
'solarSystemStars':True}
|
||||
'solarSystemStars': True}
|
||||
|
||||
init_state = widget.quick_serialize()
|
||||
settings = init_state['wwt_settings']
|
||||
|
@ -135,13 +171,31 @@ def test_3d_serialization():
|
|||
assert 'tracked_object_id' in init_state['view_settings']
|
||||
assert init_state['view_settings']['tracked_object_id'] == 0
|
||||
|
||||
track_id_map = {'sun': 0, 'mercury': 1, 'venus': 2, 'mars': 3, 'jupiter': 4, 'saturn': 5, 'uranus': 6, 'neptune': 7,
|
||||
'pluto': 8, 'moon': 9, 'io': 10, 'europa': 11, 'ganymede': 12, 'callisto': 13, 'ioshadow': 14,
|
||||
'europashadow': 15, 'ganymedeshadow': 16, 'callistoshadow': 17, 'suneclipsed': 18, 'earth': 19}
|
||||
track_id_map = {'sun': 0,
|
||||
'mercury': 1,
|
||||
'venus': 2,
|
||||
'mars': 3,
|
||||
'jupiter': 4,
|
||||
'saturn': 5,
|
||||
'uranus': 6,
|
||||
'neptune': 7,
|
||||
'pluto': 8,
|
||||
'moon': 9,
|
||||
'io': 10,
|
||||
'europa': 11,
|
||||
'ganymede': 12,
|
||||
'callisto': 13,
|
||||
'ioshadow': 14,
|
||||
'europashadow': 15,
|
||||
'ganymedeshadow': 16,
|
||||
'callistoshadow': 17,
|
||||
'suneclipsed': 18,
|
||||
'earth': 19}
|
||||
|
||||
for obj_name, obj_id in track_id_map.items():
|
||||
widget.solar_system.track_object(obj_name)
|
||||
assert widget.quick_serialize()['view_settings']['tracked_object_id'] == obj_id, "ID mismatch for {0}".format(obj_name)
|
||||
assert widget.quick_serialize()['view_settings']['tracked_object_id'] == obj_id, \
|
||||
"ID mismatch for {0}".format(obj_name)
|
||||
|
||||
|
||||
def test_add_remove_annotation_serialization():
|
||||
|
@ -168,7 +222,7 @@ def test_add_remove_annotation_serialization():
|
|||
def test_circle_annotation_serialization():
|
||||
widget = MockWWTWidget()
|
||||
circ = widget.add_circle(fill_color='#012345', radius=0.3*u.deg)
|
||||
circ.set_center(SkyCoord(0.1*u.deg,0.2*u.deg))
|
||||
circ.set_center(SkyCoord(0.1 * u.deg, 0.2 * u.deg))
|
||||
circ.fill = True
|
||||
circ.tag = 'Test Circ Tag'
|
||||
circ.line_color = 'orange'
|
||||
|
@ -176,8 +230,16 @@ def test_circle_annotation_serialization():
|
|||
circ.opacity = 0.7
|
||||
circ.label = 'Test Circ Label'
|
||||
circ.hover_label = True
|
||||
expected_settings = {'radius': 0.3, 'fill': True, 'tag': 'Test Circ Tag', 'fillColor': '#012345', 'lineColor': '#ffa500',
|
||||
'lineWidth': 5, 'opacity': 0.7, 'label': 'Test Circ Label', 'showHoverLabel': True, 'skyRelative': True}
|
||||
expected_settings = {'radius': 0.3,
|
||||
'fill': True,
|
||||
'tag': 'Test Circ Tag',
|
||||
'fillColor': '#012345',
|
||||
'lineColor': '#ffa500',
|
||||
'lineWidth': 5,
|
||||
'opacity': 0.7,
|
||||
'label': 'Test Circ Label',
|
||||
'showHoverLabel': True,
|
||||
'skyRelative': True}
|
||||
|
||||
annot_state = widget.quick_serialize()['annotations'][0]
|
||||
|
||||
|
@ -199,16 +261,16 @@ def test_circle_annotation_serialization():
|
|||
annot_state = widget.quick_serialize()['annotations'][0]
|
||||
assert annot_state['settings'] == expected_settings
|
||||
|
||||
#Check circle annotation with no specified center
|
||||
# Check circle annotation with no specified center
|
||||
circ.remove()
|
||||
circ2 = widget.add_circle()
|
||||
center = widget.quick_serialize()['annotations'][0]['center']
|
||||
assert center['ra'] == pytest.approx(5.)
|
||||
assert center['dec'] == 10.
|
||||
|
||||
#Circle annotation with center in constructor
|
||||
# Circle annotation with center in constructor
|
||||
circ2.remove()
|
||||
widget.add_circle(center = SkyCoord(15*u.deg,16*u.deg))
|
||||
widget.add_circle(center=SkyCoord(15 * u.deg, 16 * u.deg))
|
||||
center = widget.quick_serialize()['annotations'][0]['center']
|
||||
assert center['ra'] == 15
|
||||
assert center['dec'] == 16
|
||||
|
@ -216,25 +278,31 @@ def test_circle_annotation_serialization():
|
|||
|
||||
def test_poly_annotation_setting():
|
||||
widget = MockWWTWidget()
|
||||
poly = widget.add_polygon(fill = True, tag='Test Poly Tag')
|
||||
poly = widget.add_polygon(fill=True, tag='Test Poly Tag')
|
||||
poly.fill_color = '#123456'
|
||||
poly.line_color = 'antiquewhite'
|
||||
poly.line_width = 9*u.pix
|
||||
poly.opacity = 0.9
|
||||
poly.label = 'Test Poly Label'
|
||||
poly.hover_label = False
|
||||
poly.add_point(SkyCoord([1,2,3]*u.deg,[5,6,7]*u.deg))
|
||||
poly.add_point(SkyCoord(4*u.deg,8*u.deg))
|
||||
expected_settings = {'fill': True, 'tag': 'Test Poly Tag', 'fillColor': '#123456', 'lineColor': '#faebd7',
|
||||
'lineWidth': 9, 'opacity': 0.9, 'label': 'Test Poly Label', 'showHoverLabel': False}
|
||||
poly.add_point(SkyCoord([1, 2, 3]*u.deg, [5, 6, 7]*u.deg))
|
||||
poly.add_point(SkyCoord(4 * u.deg, 8 * u.deg))
|
||||
expected_settings = {'fill': True,
|
||||
'tag': 'Test Poly Tag',
|
||||
'fillColor': '#123456',
|
||||
'lineColor': '#faebd7',
|
||||
'lineWidth': 9,
|
||||
'opacity': 0.9,
|
||||
'label': 'Test Poly Label',
|
||||
'showHoverLabel': False}
|
||||
|
||||
annot_state = widget.quick_serialize()['annotations'][0]
|
||||
|
||||
assert annot_state['id'] == poly.id
|
||||
assert annot_state['shape'] == 'polygon'
|
||||
|
||||
expected_ras = [1,2,3,4]
|
||||
expected_decs = [5,6,7,8]
|
||||
expected_ras = [1, 2, 3, 4]
|
||||
expected_decs = [5, 6, 7, 8]
|
||||
assert 'points' in annot_state
|
||||
pts = annot_state['points']
|
||||
assert len(pts) == 4
|
||||
|
@ -245,26 +313,31 @@ def test_poly_annotation_setting():
|
|||
assert 'settings' in annot_state
|
||||
assert annot_state['settings'] == expected_settings
|
||||
|
||||
|
||||
def test_line_annotation_setting():
|
||||
widget = MockWWTWidget()
|
||||
line = widget.add_line(color = '#abcde0')
|
||||
line = widget.add_line(color='#abcde0')
|
||||
line.tag = 'Test Line Tag'
|
||||
line.width = 11*u.pix
|
||||
line.opacity = 0.2
|
||||
line.label = 'Test Line Label'
|
||||
line.hover_label = True
|
||||
line.add_point(SkyCoord([2,4,6]*u.deg,[10,12,14]*u.deg))
|
||||
line.add_point(SkyCoord(8*u.deg,16*u.deg))
|
||||
expected_settings = {'tag': 'Test Line Tag', 'lineColor': '#abcde0', 'lineWidth': 11, 'opacity': 0.2,
|
||||
'label': 'Test Line Label', 'showHoverLabel': True}
|
||||
line.add_point(SkyCoord([2, 4, 6] * u.deg, [10, 12, 14] * u.deg))
|
||||
line.add_point(SkyCoord(8 * u.deg, 16 * u.deg))
|
||||
expected_settings = {'tag': 'Test Line Tag',
|
||||
'lineColor': '#abcde0',
|
||||
'lineWidth': 11,
|
||||
'opacity': 0.2,
|
||||
'label': 'Test Line Label',
|
||||
'showHoverLabel': True}
|
||||
|
||||
annot_state = widget.quick_serialize()['annotations'][0]
|
||||
|
||||
assert annot_state['id'] == line.id
|
||||
assert annot_state['shape'] == 'line'
|
||||
|
||||
expected_ras = [2,4,6,8]
|
||||
expected_decs = [10,12,14,16]
|
||||
expected_ras = [2, 4, 6, 8]
|
||||
expected_decs = [10, 12, 14, 16]
|
||||
assert 'points' in annot_state
|
||||
pts = annot_state['points']
|
||||
assert len(pts) == 4
|
||||
|
@ -309,7 +382,7 @@ def test_add_remove_layer_serialization():
|
|||
widget.layers.remove_layer(img2)
|
||||
state = widget.quick_serialize()
|
||||
|
||||
layer_ids = [table1.id,img1.id]
|
||||
layer_ids = [table1.id, img1.id]
|
||||
assert len(state['layers']) == 2
|
||||
for layer in state['layers']:
|
||||
assert layer['id'] in layer_ids
|
||||
|
@ -319,6 +392,7 @@ def test_add_remove_layer_serialization():
|
|||
state = widget.quick_serialize()
|
||||
assert len(state['layers']) == 0
|
||||
|
||||
|
||||
def test_table_setting_serialization():
|
||||
widget = MockWWTWidget()
|
||||
|
||||
|
@ -341,21 +415,31 @@ def test_table_setting_serialization():
|
|||
assert layer_state['frame'] == 'Earth'
|
||||
|
||||
assert 'settings' in layer_state
|
||||
expected_settings = {'lngColumn':'ra', 'raUnits': 'degrees',
|
||||
'latColumn': 'dec', 'altColumn': 'flux',
|
||||
'timeSeries': False, 'decay': 16 * u.day,
|
||||
'altUnit': None, 'altType': 'distance',
|
||||
'color': '#aacc00', 'scaleFactor': 14,
|
||||
'opacity': 0.75, 'plotType': 'square',
|
||||
'markerScale': 'world', 'showFarSide': True,
|
||||
'sizeColumn': -1, '_colorMap': 0,
|
||||
'colorMapColumn': -1, 'xAxisColumn': '',
|
||||
'yAxisColumn': '', 'zAxisColumn': '',
|
||||
expected_settings = {'lngColumn': 'ra',
|
||||
'raUnits': 'degrees',
|
||||
'latColumn': 'dec',
|
||||
'altColumn': 'flux',
|
||||
'timeSeries': False,
|
||||
'decay': 16 * u.day,
|
||||
'altUnit': None,
|
||||
'altType': 'distance',
|
||||
'color': '#aacc00',
|
||||
'scaleFactor': 14,
|
||||
'opacity': 0.75,
|
||||
'plotType': 'square',
|
||||
'markerScale': 'world',
|
||||
'showFarSide': True,
|
||||
'sizeColumn': -1,
|
||||
'_colorMap': 0,
|
||||
'colorMapColumn': -1,
|
||||
'xAxisColumn': '',
|
||||
'yAxisColumn': '',
|
||||
'zAxisColumn': '',
|
||||
'cartesianScale': None,
|
||||
'coordinatesType': 'spherical'}
|
||||
assert layer_state['settings'] == expected_settings
|
||||
|
||||
#Check when we have colormap and scaling
|
||||
# Check when we have colormap and scaling
|
||||
layer.cmap_att = 'flux'
|
||||
layer.size_att = 'dec'
|
||||
layer.alt_unit = u.Mpc
|
||||
|
@ -368,6 +452,7 @@ def test_table_setting_serialization():
|
|||
|
||||
assert widget.quick_serialize()['layers'][0]['settings'] == expected_settings
|
||||
|
||||
|
||||
def test_image_setting_serialization():
|
||||
widget = MockWWTWidget()
|
||||
|
||||
|
@ -400,4 +485,5 @@ def test_image_setting_serialization():
|
|||
stretches = {'linear': 0, 'log': 1, 'power': 2, 'sqrt': 3, 'histeq': 4}
|
||||
for stretch_name, stretch_id in stretches.items():
|
||||
layer.stretch = stretch_name
|
||||
assert widget.quick_serialize()['layers'][0]['stretch_info']['stretch'] == stretch_id, "Stretch id mismatch for: {0}".format(stretch_name)
|
||||
assert widget.quick_serialize()['layers'][0]['stretch_info']['stretch'] == stretch_id, \
|
||||
"Stretch id mismatch for: {0}".format(stretch_name)
|
||||
|
|
|
@ -5,7 +5,6 @@ from traitlets import (TraitType, TraitError,
|
|||
Int as OriginalInt,
|
||||
Unicode as OriginalUnicode)
|
||||
from astropy import units as u
|
||||
import six
|
||||
|
||||
try:
|
||||
from matplotlib.colors import to_hex
|
||||
|
@ -89,8 +88,8 @@ class Color(TraitType):
|
|||
self.from_cwo = False
|
||||
|
||||
def validate(self, obj, value):
|
||||
if (isinstance(value, six.string_types)
|
||||
or (isinstance(value, tuple) and len(value) == 3)):
|
||||
if (isinstance(value, str) or
|
||||
(isinstance(value, tuple) and len(value) == 3)):
|
||||
return to_hex(value)
|
||||
else:
|
||||
if self.from_cwo:
|
||||
|
|
|
@ -43,6 +43,7 @@ def validate_traits(cls, traits):
|
|||
mismatch,
|
||||
'' if len(mismatch) > 1 else 'es'))
|
||||
|
||||
|
||||
def ensure_utc(tm, str_allowed):
|
||||
'''
|
||||
Helper function to convert a time object (Time, datetime, or UTC string
|
||||
|
@ -65,7 +66,7 @@ def ensure_utc(tm, str_allowed):
|
|||
utc_tm = tm.to_datetime(pytz.UTC).isoformat()
|
||||
|
||||
else:
|
||||
if str_allowed: # is an ISOT string
|
||||
if str_allowed: # is an ISOT string
|
||||
dt = Time(tm, format='isot').to_datetime(pytz.UTC)
|
||||
utc_tm = dt.isoformat()
|
||||
else:
|
||||
|
|
7
setup.py
|
@ -99,10 +99,11 @@ setup_args = dict(
|
|||
'Framework :: Jupyter',
|
||||
],
|
||||
include_package_data = True,
|
||||
python_requires = '>=3.6',
|
||||
install_requires = [
|
||||
'numpy>=1.9',
|
||||
'matplotlib>1.5',
|
||||
'astropy>=1.0',
|
||||
'astropy>=1.0,!=4.0.1',
|
||||
'requests',
|
||||
'beautifulsoup4',
|
||||
'python-dateutil',
|
||||
|
@ -112,9 +113,7 @@ setup_args = dict(
|
|||
'traitlets',
|
||||
'reproject>=0.4',
|
||||
'qtpy',
|
||||
'flask',
|
||||
'flask-cors',
|
||||
'six',
|
||||
'tornado',
|
||||
'pytz',
|
||||
],
|
||||
extras_require = {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
[tox]
|
||||
envlist = py{36,37,38}-{test,docs}
|
||||
requires = pip >= 18.0
|
||||
setuptools >= 30.3.0
|
||||
|
||||
[testenv]
|
||||
passenv =
|
||||
DISPLAY
|
||||
HOME
|
||||
setenv =
|
||||
CI = true
|
||||
changedir =
|
||||
docs: docs
|
||||
deps:
|
||||
PyQt5==5.9.*
|
||||
test: pyopengl
|
||||
extras =
|
||||
test: test,lab
|
||||
docs: docs
|
||||
commands =
|
||||
test: pip freeze
|
||||
test: python -c 'import pywwt'
|
||||
test: jupyter nbextension list
|
||||
test: jupyter labextension list
|
||||
test: jupyter serverextension list
|
||||
test: python .check_enabled.py
|
||||
test: pytest pywwt --cov pywwt -p no:warnings {posargs}
|
||||
docs: sphinx-build -n -b html -d _build/doctrees . _build/html
|
||||
docs: sphinx-build -n -b linkcheck -d _build/doctrees . _build/html
|
||||
|
||||
[testenv:codestyle]
|
||||
deps = flake8
|
||||
skip_install = true
|
||||
commands =
|
||||
flake8 --max-line-length=200 pywwt
|