Skip to content

Commit

Permalink
Revert "Merge pull request #188 from astrofrog/image-layer-controls"
Browse files Browse the repository at this point in the history
This reverts commit 171d4ca, reversing
changes made to c184f66.
  • Loading branch information
astrofrog committed Apr 8, 2019
1 parent 171d4ca commit 8894a45
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 353 deletions.
5 changes: 1 addition & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ language: c

os:
- linux
- osx

sudo: false

Expand All @@ -14,11 +13,9 @@ env:
- SETUP_XVFB=True
- CONDA_CHANNELS="astrofrog/label/dev"
- CONDA_DEPENDENCIES="astropy qtpy traitlets ipywidgets>=7.0 ipyevents widgetsnbextension pyqt pytest pytest-cov requests nomkl matplotlib beautifulsoup4 lxml jupyterlab flask flask-cors pyopengl"
- PIP_DEPENDENCIES="sphinx-automodapi numpydoc sphinx_rtd_theme pytest-faulthandler codecov reproject"
- PIP_DEPENDENCIES="sphinx-automodapi numpydoc sphinx_rtd_theme pytest-faulthandler codecov"
matrix:
- PYTHON_VERSION=2.7
- PYTHON_VERSION=3.5
- PYTHON_VERSION=3.6

install:
- git clone git://github.com/astropy/ci-helpers.git
Expand Down
40 changes: 0 additions & 40 deletions appveyor.yml

This file was deleted.

2 changes: 1 addition & 1 deletion pywwt/data_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def start_app(self):
pass
raise Exception("Could not start up data server")

def serve_file(self, filename, real_name=False, extension=''):
def serve_file(self, filename, real_name=True, extension=''):
with open(filename, 'rb') as f:
content = f.read()
if real_name:
Expand Down
14 changes: 12 additions & 2 deletions pywwt/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,18 @@ def _default_layout(self):
def _send_msg(self, **kwargs):
self.send(kwargs)

def _serve_file(self, filename, extension=''):
return serve_file(filename, extension=extension)
def load_fits_data(self, filename):
"""
Load a FITS file.
Parameters
----------
filename : str
The filename of the FITS file to display.
"""
self._validate_fits_data(filename)
url = serve_file(filename, extension='.fits')
self._send_msg(event='load_fits', url=url)

@property
def layer_controls(self):
Expand Down
160 changes: 2 additions & 158 deletions pywwt/layers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import sys
import uuid
import tempfile

if sys.version_info[0] == 2: # noqa
from io import BytesIO as StringIO
Expand All @@ -11,16 +10,14 @@
from base64 import b64encode

import numpy as np
from astropy.io import fits
from matplotlib.pyplot import cm
from matplotlib.colors import Colormap
from astropy import units as u

from traitlets import HasTraits, validate, observe
from .traits import Any, Unicode, Float, Color, Bool, to_hex
from .utils import sanitize_image

__all__ = ['LayerManager', 'TableLayer', 'ImageLayer']
__all__ = ['LayerManager', 'TableLayer']

VALID_FRAMES = ['sky', 'ecliptic', 'galactic', 'sun', 'mercury', 'venus',
'earth', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune',
Expand Down Expand Up @@ -48,8 +45,6 @@

VALID_MARKER_SCALES = ['screen', 'world']

VALID_STRETCHES = ['linear', 'log', 'power', 'sqrt', 'histeq']

# The following are columns that we add dynamically and internally, so we need
# to make sure they have unique names that won't clash with existing columns
SIZE_COLUMN_NAME = str(uuid.uuid4())
Expand Down Expand Up @@ -103,48 +98,12 @@ def __init__(self, parent=None):
self._layers = []
self._parent = parent

def add_image_layer(self, image=None, **kwargs):
"""
Add an image layer to the current view
Parameters
----------
image : str or :class:`~astropy.io.fits.ImageHDU` or tuple
The image to show, which should be given either as a filename,
an :class:`~astropy.io.fits.ImageHDU` object, or a tuple of the
form ``(array, wcs)`` where ``array`` is a Numpy array and ``wcs``
is an astropy :class:`~astropy.wcs.WCS` object
kwargs
Additional keyword arguments can be used to set properties on the
image layer.
Returns
-------
layer : :class:`~pywwt.layers.ImageLayer`
"""
layer = ImageLayer(self._parent, image=image, **kwargs)
self._add_layer(layer)
return layer

def add_table_layer(self, table=None, frame='Sky', **kwargs):
def add_data_layer(self, table=None, frame='Sky', **kwargs):
"""
Add a data layer to the current view
Parameters
----------
table : :class:`~astropy.table.Table`
The table containing the data to show.
frame : str
The reference frame to use for the data. This should be either
``'Sky'``, ``'Ecliptic'``, ``Galactic``, or the name of a planet
or a natural satellite in the solar system.
kwargs
Additional keyword arguments can be used to set properties on the
table layer.
Returns
-------
layer : :class:`~pywwt.layers.TableLayer`
"""

# Validate frame
Expand All @@ -161,13 +120,6 @@ def add_table_layer(self, table=None, frame='Sky', **kwargs):
self._add_layer(layer)
return layer

def add_data_layer(self, *args, **kwargs):
"""
Deprecated, use ``add_table_layer`` instead.
"""
warnings.warn('add_data_layer has been deprecated, use add_table_layer '
'instead', UserWarning)

def _add_layer(self, layer):
if layer in self._layers:
raise ValueError("layer already exists in layer manager")
Expand Down Expand Up @@ -553,111 +505,3 @@ def __str__(self):

def __repr__(self):
return '<{0}>'.format(str(self))


class ImageLayer(HasTraits):
"""
An image layer.
"""

vmin = Float(None, allow_none=True)
vmax = Float(None, allow_none=True)
stretch = Unicode('linear')
opacity = Float(1, help='The opacity of the markers').tag(wwt='opacity')

def __init__(self, parent=None, image=None, **kwargs):

self._image = image

self.parent = parent
self.id = str(uuid.uuid4())

# Attribute to keep track of the manager, so that we can notify the
# manager if a layer is removed.
self._manager = None
self._removed = False

# Transform the image so that it is always acceptable to WWT (Equatorial,
# TAN projection, double values) and write out to a temporary file
self._sanitized_image = tempfile.mktemp()
sanitize_image(image, self._sanitized_image)

# The first thing we need to do is make sure the image is being served.
# For now we assume that image is a filename, but we could do more
# detailed checks and reproject on-the-fly for example.

self._image_url = self.parent._serve_file(self._sanitized_image, extension='.fits')

# Because of the way the image loading works in WWT, we may end up with
# messages being applied out of order (see notes in image_layer_stretch
# in wwt_json_api.js)
self._stretch_version = 0

self._initialize_layer()

# Force defaults
self._on_trait_change({'name': 'opacity', 'new': self.opacity})

self.observe(self._on_trait_change, type='change')

if any(key not in self.trait_names() for key in kwargs):
raise KeyError('a key doesn\'t match any layer trait name')

super(ImageLayer, self).__init__(**kwargs)

# Determine initial stretch parameters
data = fits.getdata(self._sanitized_image)

self.vmin = np.nanpercentile(data, 0.5)
self.vmax = np.nanpercentile(data, 99.5)

@validate('stretch')
def _check_stretch(self, proposal):
if proposal['value'] in VALID_STRETCHES:
return proposal['value']
else:
raise ValueError('stretch should be one of {0}'.format('/'.join(str(x) for x in VALID_STRETCHES)))

def _initialize_layer(self):
self.parent._send_msg(event='image_layer_create',
id=self.id, url=self._image_url)

def remove(self):
"""
Remove the layer.
"""
if self._removed:
return
self.parent._send_msg(event='image_layer_remove', id=self.id)
self._removed = True
if self._manager is not None:
self._manager.remove_layer(self)

def _on_trait_change(self, changed):

if changed['name'] in ('stretch', 'vmin', 'vmax'):
if self.vmin is not None and self.vmax is not None:
stretch_id = VALID_STRETCHES.index(self.stretch)
self._stretch_version += 1
self.parent._send_msg(event='image_layer_stretch', id=self.id,
stretch=stretch_id,
vmin=self.vmin, vmax=self.vmax,
version=self._stretch_version)

# This method gets called anytime a trait gets changed. Since this class
# gets inherited by the Jupyter widgets class which adds some traits of
# its own, we only want to react to changes in traits that have the wwt
# metadata attribute (which indicates the name of the corresponding WWT
# setting).
wwt_name = self.trait_metadata(changed['name'], 'wwt')
if wwt_name is not None:
self.parent._send_msg(event='image_layer_set',
id=self.id,
setting=wwt_name,
value=changed['new'])

def __str__(self):
return 'ImageLayer'

def __repr__(self):
return '<{0}>'.format(str(self))
59 changes: 4 additions & 55 deletions pywwt/nbextension/static/wwt_json_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ function wwt_apply_json_message(wwt, msg) {
wwt.gotoRaDecZoom(msg['ra'], msg['dec'], msg['fov'], msg['instant']);
break;

case 'load_fits':
wwt.loadFits(msg['url']);
break;

case 'setting_set':
var name = msg['setting'];
wwt.settings["set_" + name](msg['value']);
Expand Down Expand Up @@ -183,61 +187,6 @@ function wwt_apply_json_message(wwt, msg) {
wwtlib.WWTControl.singleton.renderContext.set_solarSystemTrack(msg['code']);
break;

case 'image_layer_create':

layer = wwt.loadFits(msg['url']);
layer._stretch_version = 0;

wwt.layers[msg['id']] = layer;
break;

case 'image_layer_stretch':

var layer = wwt.layers[msg['id']];

if (layer.get_imageSet() == null) {

// When the image layer is created, the image is not immediately available.
// If the stretch is modified before the image layer is available, we
// call the wwt_apply_json_message function again at some small time
// interval in the future.

setTimeout(function(){ wwt_apply_json_message(wwt, msg); }, 200);

} else {

// Once we get here, the image has downloaded. If we are in a deferred
// call, we want to only apply the call if the version of the call
// is more recent than the currently set version. We do this check
// because there is no guarantee that the messages arrive in the right
// order.

if (msg['version'] > layer._stretch_version) {
layer.setImageScale(msg['stretch'], msg['vmin'], msg['vmax']);
layer._stretch_version = msg['version'];
layer.getFitsImage().transparentBlack = false;
}

}
break;

case 'image_layer_set':

var layer = wwt.layers[msg['id']];
var name = msg['setting'];

layer["set_" + name](msg['value']);

break;

case 'image_layer_remove':

// TODO: could combine with table_layer_remove

var layer = wwt.layers[msg['id']];
wwtlib.LayerManager.deleteLayerByID(layer.id, true, true);
break;

case 'table_layer_create':

// Decode table from base64
Expand Down
14 changes: 12 additions & 2 deletions pywwt/qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,18 @@ def _send_msg(self, asynchronous=True, **kwargs):
msg = json.dumps(kwargs)
return self.widget._run_js("wwt_apply_json_message(wwt, {0});".format(msg), asynchronous=asynchronous)

def _serve_file(self, filename, extension=''):
return self._data_server.serve_file(filename, extension=extension)
def load_fits_data(self, filename):
"""
Load a FITS file.
Parameters
----------
filename : str
The filename of the FITS file to display.
"""
self._validate_fits_data(filename)
url = self._data_server.serve_file(filename, extension='.fits')
self._send_msg(event='load_fits', url=url)

def render(self, filename):
"""
Expand Down
Loading

0 comments on commit 8894a45

Please sign in to comment.