Strip out all of the superseded `toast.py` code
Finally buckled down and updated the test suite to only use the newer APIs.
This commit is contained in:
Родитель
af77780b35
Коммит
82c39b1ed1
|
@ -49,7 +49,3 @@ Python API Reference
|
|||
.. automodapi:: toasty.toast
|
||||
:no-inheritance-diagram:
|
||||
:no-inherited-members:
|
||||
|
||||
.. automodapi:: toasty.viewer
|
||||
:no-inheritance-diagram:
|
||||
:no-inherited-members:
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
depth2tiles
|
||||
===========
|
||||
|
||||
.. currentmodule:: toasty
|
||||
|
||||
.. autofunction:: depth2tiles
|
|
@ -1,6 +0,0 @@
|
|||
gen_wtml
|
||||
========
|
||||
|
||||
.. currentmodule:: toasty
|
||||
|
||||
.. autofunction:: gen_wtml
|
|
@ -1,6 +0,0 @@
|
|||
generate_images
|
||||
===============
|
||||
|
||||
.. currentmodule:: toasty
|
||||
|
||||
.. autofunction:: generate_images
|
|
@ -1,6 +0,0 @@
|
|||
minmax_tile_filter
|
||||
==================
|
||||
|
||||
.. currentmodule:: toasty
|
||||
|
||||
.. autofunction:: minmax_tile_filter
|
|
@ -1,6 +0,0 @@
|
|||
gen_wtml
|
||||
========
|
||||
|
||||
.. currentmodule:: toasty.toast
|
||||
|
||||
.. autofunction:: gen_wtml
|
|
@ -1,6 +0,0 @@
|
|||
generate_images
|
||||
===============
|
||||
|
||||
.. currentmodule:: toasty.toast
|
||||
|
||||
.. autofunction:: generate_images
|
|
@ -1,6 +0,0 @@
|
|||
toast
|
||||
=====
|
||||
|
||||
.. currentmodule:: toasty.toast
|
||||
|
||||
.. autofunction:: toast
|
|
@ -1,29 +0,0 @@
|
|||
SimpleWWTHandler
|
||||
================
|
||||
|
||||
.. currentmodule:: toasty.viewer
|
||||
|
||||
.. autoclass:: SimpleWWTHandler
|
||||
:show-inheritance:
|
||||
|
||||
.. rubric:: Attributes Summary
|
||||
|
||||
.. autosummary::
|
||||
|
||||
~SimpleWWTHandler.wtml
|
||||
|
||||
.. rubric:: Methods Summary
|
||||
|
||||
.. autosummary::
|
||||
|
||||
~SimpleWWTHandler.send_head
|
||||
~SimpleWWTHandler.serve_string
|
||||
|
||||
.. rubric:: Attributes Documentation
|
||||
|
||||
.. autoattribute:: wtml
|
||||
|
||||
.. rubric:: Methods Documentation
|
||||
|
||||
.. automethod:: send_head
|
||||
.. automethod:: serve_string
|
|
@ -3,10 +3,3 @@
|
|||
# Licensed under the MIT License.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from .toast import (
|
||||
depth2tiles,
|
||||
generate_images,
|
||||
gen_wtml,
|
||||
minmax_tile_filter,
|
||||
)
|
||||
|
|
|
@ -303,10 +303,15 @@ class ImageLoader(object):
|
|||
-------
|
||||
A new :class:`Image`.
|
||||
"""
|
||||
# Special handling for Numpy arrays. TODO: it would be better to sniff
|
||||
# filetypes instead of just looking at extensions. But, lazy.
|
||||
|
||||
if path.endswith('.npy'):
|
||||
arr = np.load(path)
|
||||
return Image.from_array(ImageMode.F32, arr.astype(np.float32))
|
||||
|
||||
# Special handling for Photoshop files, used for some very large mosaics
|
||||
# with transparency (e.g. the PHAT M31/M33 images). TODO: it would be
|
||||
# better to sniff the PSD filetype instead of just looking at
|
||||
# extensions. But, lazy.
|
||||
# with transparency (e.g. the PHAT M31/M33 images).
|
||||
|
||||
if path.endswith('.psd') or path.endswith('.psb'):
|
||||
try:
|
||||
|
|
|
@ -19,28 +19,17 @@ try:
|
|||
except ImportError:
|
||||
HAS_ASTRO = False
|
||||
|
||||
from . import test_path
|
||||
from .. import toast
|
||||
from .._libtoasty import mid
|
||||
from ..image import ImageMode
|
||||
from ..image import ImageLoader, ImageMode
|
||||
from ..pyramid import Pos, PyramidIO
|
||||
from ..samplers import plate_carree_sampler, healpix_fits_file_sampler
|
||||
from ..toast import generate_images, gen_wtml, read_image, sample_layer, save_png
|
||||
|
||||
|
||||
def mock_sampler(x, y):
|
||||
return x
|
||||
|
||||
|
||||
@pytest.mark.parametrize('depth', (0, 1, 2))
|
||||
def test_generate_images_path(depth):
|
||||
result = set(r[0] for r in generate_images(mock_sampler, depth))
|
||||
expected = set(['{n}/{y}/{y}_{x}.png'.format(n=n, x=x, y=y)
|
||||
for n in range(depth + 1)
|
||||
for y in range(2 ** n)
|
||||
for x in range(2 ** n)])
|
||||
assert result == expected
|
||||
from ..toast import sample_layer
|
||||
|
||||
|
||||
def test_mid():
|
||||
from .._libtoasty import mid
|
||||
|
||||
result = mid((0, 0), (np.pi / 2, 0))
|
||||
expected = np.pi / 4, 0
|
||||
np.testing.assert_array_almost_equal(result, expected)
|
||||
|
@ -68,161 +57,43 @@ def image_test(expected, actual, err_msg):
|
|||
return
|
||||
|
||||
_, pth = mkstemp(suffix='.png')
|
||||
save_png(pth, np.hstack((expected, actual)))
|
||||
|
||||
import PIL.Image
|
||||
PIL.Image.fromarray(np.hstack((expected, actual))).save(pth)
|
||||
pytest.fail("%s. Saved to %s" % (err_msg, pth))
|
||||
|
||||
|
||||
def test_reference_wtml():
|
||||
ref = parseString(reference_wtml)
|
||||
opts = {'FolderName': 'ADS All Sky Survey',
|
||||
'Name': 'allSources_512',
|
||||
'Credits': 'ADS All Sky Survey',
|
||||
'CreditsUrl': 'adsass.org',
|
||||
'ThumbnailUrl': 'allSources_512.jpg'
|
||||
}
|
||||
wtml = gen_wtml('allSources_512', 3, **opts)
|
||||
val = parseString(wtml)
|
||||
|
||||
assert ref.getElementsByTagName('Folder')[0].getAttribute('Name') == \
|
||||
val.getElementsByTagName('Folder')[0].getAttribute('Name')
|
||||
|
||||
for n in ['Credits', 'CreditsUrl', 'ThumbnailUrl']:
|
||||
assert ref.getElementsByTagName(n)[0].childNodes[0].nodeValue == \
|
||||
val.getElementsByTagName(n)[0].childNodes[0].nodeValue
|
||||
|
||||
ref = ref.getElementsByTagName('ImageSet')[0]
|
||||
val = val.getElementsByTagName('ImageSet')[0]
|
||||
for k in ref.attributes.keys():
|
||||
assert ref.getAttribute(k) == val.getAttribute(k)
|
||||
|
||||
|
||||
def cwd():
|
||||
return os.path.split(os.path.abspath(__file__))[0]
|
||||
|
||||
|
||||
def test_wwt_compare_sky():
|
||||
"""Assert that the toast tiling looks similar to the WWT tiles"""
|
||||
direc = cwd()
|
||||
|
||||
im = read_image(os.path.join(direc, 'Equirectangular_projection_SW-tweaked.jpg'))
|
||||
sampler = plate_carree_sampler(im)
|
||||
|
||||
for pth, result in generate_images(sampler, depth=1):
|
||||
expected = read_image(os.path.join(direc, 'earth_toasted_sky', pth))
|
||||
expected = expected[:, :, :3]
|
||||
|
||||
image_test(expected, result, "Failed for %s" % pth)
|
||||
|
||||
|
||||
@pytest.mark.skipif('not HAS_ASTRO')
|
||||
def test_healpix_sampler_equ():
|
||||
direc = cwd()
|
||||
sampler = healpix_fits_file_sampler(os.path.join(direc, 'earth_healpix_equ.fits'))
|
||||
|
||||
for pth, result in generate_images(sampler, depth=1):
|
||||
expected = read_image(os.path.join(direc, 'earth_toasted_sky', pth))
|
||||
expected = expected.sum(axis=2) // 3
|
||||
|
||||
image_test(expected, result, "Failed for %s" % pth)
|
||||
|
||||
|
||||
@pytest.mark.skipif('not HAS_ASTRO')
|
||||
def test_healpix_sampler_gal():
|
||||
direc = cwd()
|
||||
sampler = healpix_fits_file_sampler(os.path.join(direc, 'earth_healpix_gal.fits'))
|
||||
|
||||
for pth, result in generate_images(sampler, depth=1):
|
||||
expected = read_image(os.path.join(direc, 'earth_toasted_sky', pth))
|
||||
expected = expected.sum(axis=2) // 3
|
||||
|
||||
image_test(expected, result, "Failed for %s" % pth)
|
||||
|
||||
|
||||
def test_merge():
|
||||
# test that merge function called on non-terminal nodes
|
||||
im = read_image(os.path.join(cwd(), 'Equirectangular_projection_SW-tweaked.jpg'))
|
||||
|
||||
def null_merge(mosaic):
|
||||
return np.zeros((256, 256, 3), dtype=np.uint8)
|
||||
|
||||
sampler = plate_carree_sampler(im)
|
||||
|
||||
for pth, im in generate_images(sampler, 2, null_merge):
|
||||
if pth[0] != '2':
|
||||
assert im.max() == 0
|
||||
else:
|
||||
assert im.max() != 0
|
||||
|
||||
|
||||
class TestToaster(object):
|
||||
|
||||
class TestSampleLayer(object):
|
||||
def setup_method(self, method):
|
||||
self.base = mkdtemp()
|
||||
self.cwd = cwd()
|
||||
|
||||
im = read_image(os.path.join(self.cwd, 'Equirectangular_projection_SW-tweaked.jpg'))
|
||||
self.sampler = plate_carree_sampler(im)
|
||||
|
||||
def teardown_method(self, method):
|
||||
rmtree(self.base)
|
||||
|
||||
def verify_toast(self):
|
||||
""" Zip the expected and actual tiles """
|
||||
for n, x, y in [(0, 0, 0), (1, 0, 0), (1, 0, 1),
|
||||
(1, 1, 0), (1, 1, 1)]:
|
||||
subpth = os.path.join(str(n), str(y), "%i_%i.png" % (y, x))
|
||||
a = read_image(os.path.join(self.base, subpth))[:, :, :3]
|
||||
b = read_image(os.path.join(self.cwd, 'earth_toasted_sky', subpth))[:, :, :3]
|
||||
image_test(b, a, 'Failed for %s' % subpth)
|
||||
|
||||
def test_default(self):
|
||||
|
||||
wtml = os.path.join(self.base, 'test.wtml')
|
||||
toast.toast(self.sampler, 1, self.base, wtml_file=wtml)
|
||||
|
||||
assert os.path.exists(wtml)
|
||||
self.verify_toast()
|
||||
|
||||
def test_no_merge(self):
|
||||
toast.toast(self.sampler, 1, self.base, merge=False)
|
||||
self.verify_toast()
|
||||
|
||||
|
||||
reference_wtml = """
|
||||
<Folder Name="ADS All Sky Survey">
|
||||
<ImageSet Generic="False" DataSetType="Sky" BandPass="Visible" Name="allSources_512" Url="allSources_512/{1}/{3}/{3}_{2}.png" BaseTileLevel="0" TileLevels="3" BaseDegreesPerTile="180" FileType=".png" BottomsUp="False" Projection="Toast" QuadTreeMap="" CenterX="0" CenterY="0" OffsetX="0" OffsetY="0" Rotation="0" Sparse="False" ElevationModel="False">
|
||||
<Credits> ADS All Sky Survey </Credits>
|
||||
<CreditsUrl>adsass.org</CreditsUrl>
|
||||
<ThumbnailUrl>allSources_512.jpg</ThumbnailUrl>
|
||||
<Description/>
|
||||
</ImageSet>
|
||||
</Folder>
|
||||
"""
|
||||
|
||||
|
||||
class TestSamplingLayer(object):
|
||||
def setup_method(self, method):
|
||||
self.base = mkdtemp()
|
||||
self.cwd = cwd()
|
||||
im = read_image(os.path.join(self.cwd, 'Equirectangular_projection_SW-tweaked.jpg'))
|
||||
self.sampler = plate_carree_sampler(im)
|
||||
|
||||
from ..pyramid import PyramidIO
|
||||
self.pio = PyramidIO(self.base)
|
||||
|
||||
def teardown_method(self, method):
|
||||
rmtree(self.base)
|
||||
|
||||
def verify_toast(self):
|
||||
""" Zip the expected and actual tiles """
|
||||
for n, x, y in [(1, 0, 0), (1, 0, 1),
|
||||
(1, 1, 0), (1, 1, 1)]:
|
||||
subpth = os.path.join(str(n), str(y), "%i_%i.png" % (y, x))
|
||||
a = read_image(os.path.join(self.base, subpth))[:, :, :3]
|
||||
b = read_image(os.path.join(self.cwd, 'earth_toasted_sky', subpth))[:, :, :3]
|
||||
image_test(b, a, 'Failed for %s' % subpth)
|
||||
def verify_level1(self, mode):
|
||||
for n, x, y in [(1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]:
|
||||
ref_path = test_path('earth_toasted_sky', str(n), str(y), "%i_%i.png" % (y, x))
|
||||
expected = ImageLoader().load_path(ref_path).asarray()
|
||||
if mode == ImageMode.F32:
|
||||
expected = expected.mean(axis=2)
|
||||
|
||||
def test_default(self):
|
||||
sample_layer(self.pio, ImageMode.RGB, self.sampler, 1)
|
||||
self.verify_toast()
|
||||
pos = Pos(n=n, x=x, y=y)
|
||||
observed = self.pio.read_toasty_image(pos, mode).asarray()
|
||||
|
||||
image_test(expected, observed, 'Failed for %s' % ref_path)
|
||||
|
||||
def test_plate_carree(self):
|
||||
img = ImageLoader().load_path(test_path('Equirectangular_projection_SW-tweaked.jpg'))
|
||||
sampler = plate_carree_sampler(img.asarray())
|
||||
sample_layer(self.pio, ImageMode.RGB, sampler, 1)
|
||||
self.verify_level1(ImageMode.RGB)
|
||||
|
||||
def test_healpix_equ(self):
|
||||
sampler = healpix_fits_file_sampler(test_path('earth_healpix_equ.fits'))
|
||||
sample_layer(self.pio, ImageMode.F32, sampler, 1)
|
||||
self.verify_level1(ImageMode.F32)
|
||||
|
||||
def test_healpix_gal(self):
|
||||
sampler = healpix_fits_file_sampler(test_path('earth_healpix_gal.fits'))
|
||||
sample_layer(self.pio, ImageMode.F32, sampler, 1)
|
||||
self.verify_level1(ImageMode.F32)
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# Copyright 2013-2019 Chris Beaumont and the AAS WorldWide Telescope project
|
||||
# Licensed under the MIT License.
|
||||
|
||||
from threading import Thread
|
||||
try:
|
||||
from SocketServer import TCPServer
|
||||
from urllib import urlopen
|
||||
except ImportError: # python 3.X
|
||||
from socketserver import TCPServer
|
||||
from urllib.request import urlopen
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from ..viewer import SimpleWWTHandler
|
||||
|
||||
|
||||
def cwd():
|
||||
return os.path.split(os.path.abspath(__file__))[0]
|
||||
|
||||
|
||||
class TestViewer(object):
|
||||
def setup_class(cls):
|
||||
sys.argv.append(os.path.join(cwd(), 'earth_toasted_sky'))
|
||||
PORT = 8000
|
||||
# configure to immediately release the socket on close
|
||||
# http://stackoverflow.com/questions/17659334
|
||||
cls.server = TCPServer(("", PORT), SimpleWWTHandler, False)
|
||||
cls.server.allow_reuse_address = True
|
||||
cls.server.server_bind()
|
||||
cls.server.server_activate()
|
||||
|
||||
cls.thread = Thread(target=cls.server.serve_forever)
|
||||
cls.thread.start()
|
||||
|
||||
def teardown_class(cls):
|
||||
cls.server.shutdown()
|
||||
|
||||
def test_wtml(self):
|
||||
data = urlopen('http://0.0.0.0:8000/toasty.wtml').read()
|
||||
assert '<ImageSet' in str(data)
|
||||
|
||||
def test_root(self):
|
||||
data = urlopen('http://0.0.0.0:8000/').read()
|
||||
assert 'WWTCanvas' in str(data)
|
307
toasty/toast.py
307
toasty/toast.py
|
@ -11,14 +11,11 @@ wwt_data_formats.
|
|||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = '''
|
||||
generate_images
|
||||
generate_tiles
|
||||
gen_wtml
|
||||
minmax_tile_filter
|
||||
nxy_tile_filter
|
||||
sample_layer
|
||||
Tile
|
||||
toast
|
||||
toast_tile_area
|
||||
'''.split()
|
||||
|
||||
|
@ -280,307 +277,3 @@ def sample_layer(pio, mode, sampler, depth):
|
|||
)
|
||||
sampled_data = sampler(lon, lat)
|
||||
pio.write_toasty_image(tile.pos, Image.from_array(mode, sampled_data))
|
||||
|
||||
|
||||
# This is where we start needing to revamp all of the I/O and pyramid-management stuff:
|
||||
|
||||
import PIL.Image
|
||||
|
||||
def save_png(pth, array):
|
||||
PIL.Image.fromarray(array).save(pth)
|
||||
|
||||
|
||||
def read_image(path):
|
||||
return np.asarray(PIL.Image.open(path))
|
||||
|
||||
|
||||
def generate_images(
|
||||
data_sampler,
|
||||
depth,
|
||||
merge = True,
|
||||
base_level_only = False,
|
||||
tile_filter = None,
|
||||
top = 0
|
||||
):
|
||||
"""
|
||||
Create a hierarchy of toast tiles
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data_sampler : func or string
|
||||
- A function that takes two 2D numpy arrays of (lon, lat) as input,
|
||||
and returns an image of the original dataset sampled
|
||||
at these locations; see :mod:`toasty.samplers`.
|
||||
- A string giving a base toast directory that contains the
|
||||
base level of toasted tiles, using this option, only the
|
||||
merge step takes place, the given directory must contain
|
||||
a "depth" directory for the given depth parameter
|
||||
|
||||
depth : int
|
||||
The maximum depth to tile to. A depth of N creates
|
||||
4^N pngs at the deepest level
|
||||
merge : bool or callable (default True)
|
||||
How to treat lower resolution tiles.
|
||||
|
||||
- If True, tiles above the lowest level (highest resolution)
|
||||
will be computed by averaging and downsampling the 4 subtiles.
|
||||
- If False, sampler will be called explicitly for all tiles
|
||||
- If a callable object, this object will be passed the
|
||||
4x oversampled image to downsample
|
||||
|
||||
base_level_only : bool (default False)
|
||||
If True only the bottem level of tiles will be created.
|
||||
In this case merge will be set to True, but no merging will happen,
|
||||
and only the highest resolution layer of images will be created.
|
||||
tile_filter: callable (optional)
|
||||
A function that takes a tile and determines if it is in toasting range.
|
||||
If not given default_tile_filter will be used which simply returns True.
|
||||
top: int (optional)
|
||||
The topmost layer of toast tiles to create (only relevant if
|
||||
base_level_only is False), default is 0.
|
||||
|
||||
Yields
|
||||
------
|
||||
(pth, tile) : str, ndarray
|
||||
pth is the relative path where the tile image should be saved
|
||||
"""
|
||||
if tile_filter is None:
|
||||
tile_filter = lambda t: True
|
||||
|
||||
if merge is True:
|
||||
merge = _default_merge
|
||||
|
||||
parents = defaultdict(dict)
|
||||
|
||||
for tile in generate_tiles(max(depth, 1), bottom_only=merge, tile_filter=tile_filter):
|
||||
n, x, y = tile.pos.n, tile.pos.x, tile.pos.y
|
||||
|
||||
if type(data_sampler) == str:
|
||||
img_dir = data_sampler + '/' + str(n) + '/'
|
||||
try:
|
||||
img = read_image(img_dir + str(y) + '/' + str(y) + '_' + str(x) + '.png')
|
||||
except: # could not read image
|
||||
img = None
|
||||
else:
|
||||
l, b = subsample(tile.corners[0], tile.corners[1], tile.corners[2], tile.corners[3], 256, tile.increasing)
|
||||
img = data_sampler(l, b)
|
||||
|
||||
# No image was returned by the sampler -- looks like either image data
|
||||
# was not available for this position
|
||||
if img is None and base_level_only:
|
||||
continue
|
||||
|
||||
if not base_level_only:
|
||||
for pth, img in _trickle_up(img, tile.pos, parents, merge, depth, top):
|
||||
if img is None:
|
||||
continue
|
||||
yield pth, img
|
||||
else:
|
||||
pth = os.path.join('%i' % n, '%i' % y, '%i_%i.png' % (y, x))
|
||||
yield pth, img
|
||||
|
||||
|
||||
def _trickle_up(im, node, parents, merge, depth, top=0):
|
||||
"""
|
||||
When a new toast tile is ready, propagate it up the hierarchy
|
||||
and recursively yield its completed parents
|
||||
"""
|
||||
|
||||
n, x, y = node.n, node.x, node.y
|
||||
|
||||
pth = os.path.join('%i' % n, '%i' % y, '%i_%i.png' % (y, x))
|
||||
|
||||
nparent = sum(len(v) for v in parents.values())
|
||||
assert nparent <= 4 * max(depth, 1)
|
||||
|
||||
if depth >= n: # handle special case of depth=0, n=1
|
||||
yield pth, im
|
||||
|
||||
if n == top: # This is the uppermost level desired
|
||||
return
|
||||
|
||||
# - If not merging and not at level 1, no need to accumulate
|
||||
if not merge and n > 1:
|
||||
return
|
||||
|
||||
parent, xc, yc = pos_parent(node)
|
||||
corners = parents[parent]
|
||||
corners[(xc, yc)] = im
|
||||
|
||||
if len(corners) < 4: # parent not yet ready
|
||||
return
|
||||
|
||||
parents.pop(parent)
|
||||
|
||||
# imgs = [ul,ur,bl,br]
|
||||
#imgs = np.array([corners[(0, 0)],corners[(1, 0)],corners[(1, 0)],corners[(1, 1)]])
|
||||
|
||||
ul = corners[(0, 0)]
|
||||
ur = corners[(1, 0)]
|
||||
bl = corners[(0, 1)]
|
||||
br = corners[(1, 1)]
|
||||
|
||||
# dealing with any children lacking image data
|
||||
if all(x is None for x in [ul,ur,bl,br]):
|
||||
im = None
|
||||
else:
|
||||
# get img shape
|
||||
imgShape = [x for x in [ul,ur,bl,br] if x is not None][0].shape
|
||||
|
||||
if not imgShape: # This shouldn't happen but...
|
||||
print([type(x) for x in [ul,ur,bl,br]])
|
||||
im = None
|
||||
else:
|
||||
|
||||
if ul is None:
|
||||
ul = np.zeros(imgShape,dtype=np.uint8)
|
||||
if ur is None:
|
||||
ur = np.zeros(imgShape,dtype=np.uint8)
|
||||
if bl is None:
|
||||
bl = np.zeros(imgShape,dtype=np.uint8)
|
||||
if br is None:
|
||||
br = np.zeros(imgShape,dtype=np.uint8)
|
||||
|
||||
try:
|
||||
mosaic = np.vstack((np.hstack((ul, ur)), np.hstack((bl, br))))
|
||||
im = (merge or _default_merge)(mosaic)
|
||||
except:
|
||||
print(imgShape)
|
||||
im = None
|
||||
|
||||
|
||||
for item in _trickle_up(im, parent, parents, merge, depth, top):
|
||||
yield item
|
||||
|
||||
|
||||
def _default_merge(mosaic):
|
||||
"""The default merge strategy -- just average all 4 pixels"""
|
||||
return (mosaic[::2, ::2] / 4. +
|
||||
mosaic[1::2, ::2] / 4. +
|
||||
mosaic[::2, 1::2] / 4. +
|
||||
mosaic[1::2, 1::2] / 4.).astype(mosaic.dtype)
|
||||
|
||||
|
||||
# XXX TODO: this should be superseded by use of wwt_data_formats
|
||||
def gen_wtml(base_dir, depth, **kwargs):
|
||||
"""
|
||||
Create a minimal WTML record for a pyramid generated by toasty
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base_dir : str
|
||||
The base path to a toast pyramid, as you wish for it to appear
|
||||
in the WTML file (i.e., this should be a path visible to a server)
|
||||
depth : int
|
||||
The maximum depth of the pyramid
|
||||
**kwargs
|
||||
Keyword arguments may be used to set parameters that appear in the
|
||||
generated WTML file. Keywords that are honored are:
|
||||
|
||||
- FolderName
|
||||
- BandPass
|
||||
- Name
|
||||
- Credits
|
||||
- CreditsUrl
|
||||
- ThumbnailUrl
|
||||
|
||||
Unhandled keywords are silently ignored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
wtml : str
|
||||
A WTML record
|
||||
"""
|
||||
kwargs.setdefault('FolderName', 'Toasty')
|
||||
kwargs.setdefault('BandPass', 'Visible')
|
||||
kwargs.setdefault('Name', 'Toasty map')
|
||||
kwargs.setdefault('Credits', 'Toasty')
|
||||
kwargs.setdefault('CreditsUrl', 'http://github.com/ChrisBeaumont/toasty')
|
||||
kwargs.setdefault('ThumbnailUrl', '')
|
||||
kwargs['url'] = base_dir
|
||||
kwargs['depth'] = depth
|
||||
|
||||
template = ('<Folder Name="{FolderName}">\n'
|
||||
'<ImageSet Generic="False" DataSetType="Sky" '
|
||||
'BandPass="{BandPass}" Name="{Name}" '
|
||||
'Url="{url}/{{1}}/{{3}}/{{3}}_{{2}}.png" BaseTileLevel="0" '
|
||||
'TileLevels="{depth}" BaseDegreesPerTile="180" '
|
||||
'FileType=".png" BottomsUp="False" Projection="Toast" '
|
||||
'QuadTreeMap="" CenterX="0" CenterY="0" OffsetX="0" '
|
||||
'OffsetY="0" Rotation="0" Sparse="False" '
|
||||
'ElevationModel="False">\n'
|
||||
'<Credits> {Credits} </Credits>\n'
|
||||
'<CreditsUrl>{CreditsUrl}</CreditsUrl>\n'
|
||||
'<ThumbnailUrl>{ThumbnailUrl}</ThumbnailUrl>\n'
|
||||
'<Description/>\n</ImageSet>\n</Folder>')
|
||||
return template.format(**kwargs)
|
||||
|
||||
|
||||
def toast(data_sampler, depth, base_dir,
|
||||
wtml_file=None, merge=True, base_level_only=False,
|
||||
tile_filter=None, top_layer=0):
|
||||
"""Build a directory of toast tiles
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data_sampler : func or string
|
||||
- A function of (lon, lat) that samples a dataset
|
||||
at the input 2D coordinate arrays
|
||||
- A string giving a base toast directory that contains the
|
||||
base level of toasted tiles, using this option, only the
|
||||
merge step takes place, the given directory must contain
|
||||
a "depth" directory for the given depth parameter
|
||||
depth : int
|
||||
The maximum depth to generate tiles for.
|
||||
4^n tiles are generated at each depth n
|
||||
base_dir : str
|
||||
The path to create the files at
|
||||
wtml_file : str (optional)
|
||||
The path to write a WTML file to. If not present,
|
||||
no file will be written
|
||||
merge : bool or callable (default True)
|
||||
How to treat lower resolution tiles.
|
||||
|
||||
- If True, tiles above the lowest level (highest resolution)
|
||||
will be computed by averaging and downsampling the 4 subtiles.
|
||||
- If False, sampler will be called explicitly for all tiles
|
||||
- If a callable object, this object will be passed the
|
||||
4x oversampled image to downsample
|
||||
base_level_only : bool (default False)
|
||||
If True only the bottem level of tiles will be created.
|
||||
In this case merge will be set to True, but no merging will happen,
|
||||
and only the highest resolution layer of images will be created.
|
||||
tile_filter : callable or None (the default)
|
||||
An optional function ``accept_tile(tile) -> bool`` that filters tiles;
|
||||
only tiles for which the fuction returns ``True`` will be
|
||||
processed.
|
||||
top_layer: int (optional)
|
||||
If merging this indicates the uppermost layer to be created.
|
||||
|
||||
"""
|
||||
if wtml_file is not None:
|
||||
wtml = gen_wtml(base_dir, depth)
|
||||
with open(wtml_file, 'w') as outfile:
|
||||
outfile.write(wtml)
|
||||
|
||||
if base_level_only:
|
||||
merge = True
|
||||
|
||||
num = 0
|
||||
for pth, tile in generate_images(data_sampler, depth, merge, base_level_only, tile_filter, top_layer):
|
||||
num += 1
|
||||
if num % 10 == 0:
|
||||
logging.getLogger(__name__).info("Finished %i of %i tiles" %
|
||||
(num, depth2tiles(depth)))
|
||||
pth = os.path.join(base_dir, pth)
|
||||
direc, _ = os.path.split(pth)
|
||||
if not os.path.exists(direc):
|
||||
try:
|
||||
os.makedirs(direc)
|
||||
except FileExistsError:
|
||||
print("%s already exists." % direc)
|
||||
try:
|
||||
save_png(pth, tile)
|
||||
except:
|
||||
print(pth)
|
||||
print(type(tile))
|
||||
|
|
163
toasty/viewer.py
163
toasty/viewer.py
|
@ -1,163 +0,0 @@
|
|||
# -*- mode: python; coding: utf-8 -*-
|
||||
# Copyright 2013-2019 Chris Beaumont and the AAS WorldWide Telescope project
|
||||
# Licensed under the MIT License.
|
||||
|
||||
"""Set up a minimal HTTP Server to preview a Toasty-generated tile pyramid.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
try:
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler, test
|
||||
from cStringIO import StringIO
|
||||
except: # python 3.X
|
||||
from http.server import SimpleHTTPRequestHandler, test
|
||||
from io import BytesIO
|
||||
|
||||
from . import gen_wtml
|
||||
|
||||
__all__ = '''
|
||||
SimpleWWTHandler
|
||||
'''.split()
|
||||
|
||||
|
||||
class SimpleWWTHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
def serve_string(self, contents):
|
||||
if sys.version_info.major == 2:
|
||||
return StringIO(contents)
|
||||
|
||||
return BytesIO(contents.encode('UTF-8'))
|
||||
|
||||
@property
|
||||
def wtml(self):
|
||||
if not hasattr(self, '_wtml'):
|
||||
base_dir = sys.argv[-1]
|
||||
depths = next(os.walk(base_dir))[1]
|
||||
max_depth = max(map(int, depths))
|
||||
self._wtml = gen_wtml(base_dir, max_depth)
|
||||
return self._wtml
|
||||
|
||||
def send_head(self):
|
||||
if self.path == '/toasty.wtml':
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/xml')
|
||||
self.send_header("Content-Length", str(len(self.wtml)))
|
||||
self.send_header("Last-Modified",
|
||||
self.date_time_string(int(time())))
|
||||
self.end_headers()
|
||||
return self.serve_string(self.wtml)
|
||||
|
||||
if self.path in ['/', '/index.html']:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", 'text/html')
|
||||
self.send_header("Content-Length", str(len(html)))
|
||||
self.send_header("Last-Modified",
|
||||
self.date_time_string(int(time())))
|
||||
self.end_headers()
|
||||
return self.serve_string(html)
|
||||
|
||||
return SimpleHTTPRequestHandler.send_head(self)
|
||||
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head>
|
||||
<title> Toasty Viewer </title>
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
padding: 0;
|
||||
margin: 0 0 0px 0;
|
||||
}
|
||||
|
||||
#UI {
|
||||
position: relative;
|
||||
top: -40px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
div {margin: 0 0 0px 0; padding: 0;}
|
||||
|
||||
</style>
|
||||
|
||||
<script src="http://www.worldwidetelescope.org/scripts/wwtsdk.aspx"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="init();" onresize="resize_canvas();" style="background-color:#000000">
|
||||
<script type="text/javascript">
|
||||
|
||||
function init() {
|
||||
wwt = wwtlib.WWTControl.initControl("WWTCanvas");
|
||||
wwt.add_ready(wwtReady);
|
||||
resize_canvas();
|
||||
}
|
||||
|
||||
function resize_canvas() {
|
||||
div = document.getElementById("WWTCanvas");
|
||||
|
||||
if (div.style.width != (window.innerWidth).toString() + "px") {
|
||||
div.style.width = (window.innerWidth).toString() + "px";
|
||||
}
|
||||
|
||||
if (div.style.height != (window.innerHeight).toString() + "px") {
|
||||
div.style.height = ((window.innerHeight)).toString() + "px";
|
||||
}
|
||||
}
|
||||
|
||||
function wwtReady() {
|
||||
wwt.settings.set_showCrosshairs(true);
|
||||
wwt.settings.set_showConstellationFigures(false);
|
||||
wwt.settings.set_showConstellationBoundries(false);
|
||||
wwt.loadImageCollection('/toasty.wtml');
|
||||
wwt.add_collectionLoaded(set_layers);
|
||||
$('#select-foreground').change(function(e){
|
||||
wwt.setBackgroundImageByName(this.value)
|
||||
});
|
||||
$('#opacity').change(function(e){
|
||||
wwt.setForegroundOpacity(this.value);
|
||||
});
|
||||
}
|
||||
|
||||
function set_layers() {
|
||||
wwt.setBackgroundImageByName('Wise All Sky (Infrared)');
|
||||
wwt.setForegroundImageByName('Toasty map');
|
||||
wwt.setForegroundOpacity(50);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="WWTCanvas" style="width: 750px; height: 750px; border-style: none; border-width: 0px;">
|
||||
</div>
|
||||
<div id="UI">
|
||||
|
||||
<select id="select-foreground">
|
||||
<option value="Digitized Sky Survey (Color)"> Optical </option>
|
||||
<option value="WMAP ILC 5-Year Cosmic Microwave Background"> WMAP 5-Year </option>
|
||||
<option value="SFD Dust Map (Infrared)"> SFD </option>
|
||||
<option value="IRIS: Improved Reprocessing of IRAS Survey (Infrared)"> IRIS </option>
|
||||
<option value="Hydrogen Alpha Full Sky Map"> H-alpha </option>
|
||||
</select>
|
||||
|
||||
<input id='opacity' type="range" name="opacity" min="10" max="100">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.argv.insert(1, '8000')
|
||||
test(HandlerClass=SimpleWWTHandler)
|
Загрузка…
Ссылка в новой задаче