├── .gitignore
├── .gitmodules
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── doc
├── Homepage.ipynb
├── Introductory_Tutorial.ipynb
├── Introductory_Tutorial.rst
├── Makefile
├── _static
│ └── rtd_bootstrap.css
├── _templates
│ ├── footer.html
│ ├── layout.html
│ └── page.html
├── conf.py
├── index.rst
└── latest_news.html
├── holocube
├── __init__.py
├── element
│ ├── __init__.py
│ ├── cube.py
│ ├── geo.py
│ └── util.py
└── plotting
│ └── __init__.py
├── notebooks
├── Colocated_cube_pair.ipynb
├── CubeExplorer_Prototype.ipynb
└── Dashboard_Demo.ipynb
├── setup.py
└── tests
└── test_cube.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask instance folder
57 | instance/
58 |
59 | # Sphinx documentation
60 | docs/_build/
61 |
62 | # PyBuilder
63 | target/
64 |
65 | # IPython Notebook
66 | .ipynb_checkpoints
67 |
68 | # pyenv
69 | .python-version
70 |
71 | .DS_Store
72 | # Website
73 | doc/_build
74 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "doc/nbpublisher"]
2 | path = doc/nbpublisher
3 | url = https://github.com/ioam/ioam-builder.git
4 | branch = nbpublisher
5 | [submodule "doc/builder"]
6 | path = doc/builder
7 | url = https://github.com/ioam/ioam-builder.git
8 | branch = docbuilder
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | sudo: false
4 |
5 | python:
6 | - "2.7"
7 | - "3.4"
8 |
9 | install:
10 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
11 | wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
12 | else
13 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
14 | fi
15 | - bash miniconda.sh -b -p $HOME/miniconda
16 | - export PATH="$HOME/miniconda/bin:$PATH"
17 | - hash -r
18 | - conda config --set always_yes yes --set changeps1 no
19 | - conda update -q conda
20 | # Useful for debugging any issues with conda
21 | - conda info -a
22 | - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION nose numpy matplotlib bokeh pandas scipy jupyter ipython param freetype=2.5.2 flake8
23 | - source activate test-environment
24 | - if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then
25 | conda install python=3.4.3;
26 | fi
27 | - pip install coveralls
28 | - pip install git+https://github.com/ioam/holoviews.git
29 | - conda install -c scitools iris
30 | - conda install -c scitools mo_pack
31 | - python setup.py install
32 |
33 | script:
34 | - nosetests --with-doctest --with-coverage --cover-package=holocube
35 | - flake8 --ignore=E,W . --exclude=./doc
36 |
37 | after_success: coveralls
38 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Cube Explorer
2 |
3 | In proposing contributions to this project, you are agreeing to the following contribution terms.
4 |
5 | Grant of Copyright Licence. Subject to the terms and conditions of this Agreement, You hereby grant to the Met Office and to recipients of software distributed by the Met Office a perpetual, worldwide, non-exclusive, royalty-free, irrevocable copyright licence to reproduce, prepare derivative works of, publicly display, publicly perform, sublicence, and distribute Your Contributions and such derivative works under the terms of the:
6 | http://opensource.org/licenses/BSD-3-Clause
7 |
8 | Intellectual Property Infringement. If any third party makes any claim against You or any other entity, alleging that your Contribution, or the Work to which you have contributed, infringes the intellectual property rights of that third party , then You shall inform the Met Office within 5 Working Days of such claim in order for the Met Office to take all appropriate action it deems necessary in relation to the claim.
9 |
10 | You represent that you are legally entitled to grant the above licence. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to the Met Office.
11 |
12 | You represent that each of Your Contributions is Your original creation and that you have not assigned or otherwise given up your interest in the Contribution to any third party You represent that Your Contribution submissions include complete details of any third-party licence or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, CubeBrowser
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of cube-explorer nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/CubeBrowser/cube-explorer)
2 | [](https://coveralls.io/github/CubeBrowser/cube-explorer)
3 | [](https://waffle.io/CubeBrowser/cube-explorer)
4 | [](https://gitter.im/CubeBrowser/cube-explorer)
5 |
6 | # cube-explorer
7 |
8 | The code base here is frozen, the resulting projects are available here:
9 |
10 | * https://github.com/ioam/holoviews
11 | * https://github.com/ioam/geoviews
12 | * https://github.com/SciTools/cube_browser
13 |
14 |
15 | Exploration and visualization of https://github.com/SciTools/iris cubes in a web browser, including a Jupyter notebook.
16 |
17 | To install, first install HoloViews and Iris. At the moment, cube-explorer relies on the latest git version of both those packages, and the easiest way to get all their dependencies is to install them via conda:
18 |
19 | ```
20 | conda install -c ioam holoviews
21 | conda install -c scitools iris
22 | ```
23 |
24 | and then install the latest git version of the two packages, e.g. via:
25 |
26 | ```
27 | pip install https://github.com/ioam/holoviews/zipball/master
28 | pip install https://github.com/CubeBrowser/cube-explorer/zipball/master
29 | ```
30 |
31 | Then run setup on a copy of this git repository:
32 |
33 | ```
34 | git clone https://github.com/CubeBrowser/cube-explorer.git
35 | cd cube-explorer
36 | python setup.py develop
37 | ```
38 |
39 | You will probably also want a copy of the Iris sample data. Sample
40 | bash commands for downloading and linking to it:
41 |
42 | ```
43 | cd ~
44 | git clone https://github.com/SciTools/iris-sample-data.git
45 | DIR=`python -c 'import iris ; print(iris.sample_data_path())'`
46 | cd `dirname $DIR`
47 | ln -s ~/iris-sample-data/sample_data .
48 | ```
49 |
50 | You should now be able to run the examples in the `notebooks` directory:
51 |
52 | ```
53 | cd ~/cube-explorer
54 | cd notebooks
55 | jupyter notebook
56 | ```
57 |
--------------------------------------------------------------------------------
/doc/Homepage.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "HoloCube is a [Python](http://python.org) library that makes it easy to explore and visualize geographical, meterological, oceanographic, and other multidimensional gridded datasets. HoloCube interfaces between the [HoloViews](http://holoviews.org) library for flexible visualizations of multidimensional data, the [Iris](http://scitools.org.uk/iris) library for storing and processing climate and weather data, and the [Cartopy](http://scitools.org.uk/cartopy) library for working with cartographic projections and visualizations in [Matplotlib](http://matplotlib.org/). Specifically, HoloCube:\n",
8 | "\n",
9 | "1. Extends HoloViews objects to allow them to use data stored in [Iris](http://scitools.org.uk/iris) [cubes](http://scitools.org.uk/iris/docs/latest/userguide/iris_cubes.html). After `import holocube`, data can be provided to any Holoviews `Element` directly as a cube, without needing to first convert into one of the other supported formats (NumPy arrays, Pandas data frames, etc.). This support is independent of the other support below -- data from Iris cubes can be used even in non-geographic `Element`s, and most geographic Elements can accept data in any format.\n",
10 | "\n",
11 | "2. Adds a set of new HoloViews `Element`s that have an associated geographic projection (`GeoElement`s), based on `cartopy.crs`. These currently include `GeoFeature`, `WMTS`, `GeoTiles`, `Points`, `Contours`, `Image`, and `Text` objects, each of which can easily be overlaid in the same plots. E.g. an object with temperature data can be overlaid with coastline data using an expression like ``Image(temp_cube)*hc.GeoFeature(cartopy.feature.COASTLINE)``. Each `GeoElement` can also be freely combined in layouts with any other HoloViews `Element`, making it simple to make even complex multi-figure layours.\n",
12 | "\n",
13 | "With HoloCube, you can now work easily and naturally with large, multidimensional datasets, instantly visualizing any subset or combination of them, while always being able to access the raw data underlying any plot. Here's a simple example:"
14 | ]
15 | },
16 | {
17 | "cell_type": "code",
18 | "execution_count": null,
19 | "metadata": {
20 | "collapsed": false
21 | },
22 | "outputs": [],
23 | "source": [
24 | "import holoviews as hv\n",
25 | "import holocube as hc\n",
26 | "from cartopy import crs\n",
27 | "from cartopy import feature as cf\n",
28 | "\n",
29 | "hv.notebook_extension()"
30 | ]
31 | },
32 | {
33 | "cell_type": "code",
34 | "execution_count": null,
35 | "metadata": {
36 | "collapsed": false
37 | },
38 | "outputs": [],
39 | "source": [
40 | "%%opts GeoFeature [projection=crs.Geostationary()]\n",
41 | "\n",
42 | "coasts = hc.GeoFeature(cf.COASTLINE)\n",
43 | "borders = hc.GeoFeature(cf.BORDERS)\n",
44 | "ocean = hc.GeoFeature(cf.OCEAN)\n",
45 | "\n",
46 | "ocean + borders + (ocean*borders).relabel(\"Overlay\")"
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {},
52 | "source": [
53 | "The following example loads a cube from [iris-sample-data](https://github.com/SciTools/iris-sample-data) and displays it as follows:"
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "execution_count": null,
59 | "metadata": {
60 | "collapsed": false
61 | },
62 | "outputs": [],
63 | "source": [
64 | "import iris\n",
65 | "surface_temp = iris.load_cube(iris.sample_data_path('GloSea4', 'ensemble_001.pp'))\n",
66 | "print surface_temp.summary()"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "With HoloViews, you can quickly view the data in the cube interactively:"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "metadata": {
80 | "collapsed": false
81 | },
82 | "outputs": [],
83 | "source": [
84 | "%%opts GeoImage [colorbar=True] (cmap='viridis')\n",
85 | "(hc.HoloCube(surface_temp).groupby(['time'], group_type=hc.Image) * hc.GeoFeature(cf.COASTLINE))"
86 | ]
87 | }
88 | ],
89 | "metadata": {
90 | "kernelspec": {
91 | "display_name": "Python 2",
92 | "language": "python",
93 | "name": "python2"
94 | },
95 | "language_info": {
96 | "codemirror_mode": {
97 | "name": "ipython",
98 | "version": 2
99 | },
100 | "file_extension": ".py",
101 | "mimetype": "text/x-python",
102 | "name": "python",
103 | "nbconvert_exporter": "python",
104 | "pygments_lexer": "ipython2",
105 | "version": "2.7.11"
106 | }
107 | },
108 | "nbformat": 4,
109 | "nbformat_minor": 0
110 | }
111 |
--------------------------------------------------------------------------------
/doc/Introductory_Tutorial.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "The Iris cubes used in this notebook are publicly available in the [``SciTools/iris-sample-data``](https://github.com/SciTools/iris-sample-data) repository. This notebook is based on the user story described in [Issue #7](https://github.com/CubeBrowser/cube-explorer/issues/7) with the following [image](http://www.wetterzentrale.de/wz/pics/Recm1201.gif) suggested for inspiration."
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {
14 | "collapsed": false
15 | },
16 | "outputs": [],
17 | "source": [
18 | "import datetime\n",
19 | "import iris\n",
20 | "import numpy as np\n",
21 | "import holoviews as hv\n",
22 | "import holocube as hc\n",
23 | "from cartopy import crs\n",
24 | "from cartopy import feature as cf\n",
25 | "hv.notebook_extension()"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {},
31 | "source": [
32 | "## Setting some notebook-wide options"
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {},
38 | "source": [
39 | "Let's start by setting some normalization options (discussed below) and always enable colorbars for the elements we will be displaying:"
40 | ]
41 | },
42 | {
43 | "cell_type": "code",
44 | "execution_count": null,
45 | "metadata": {
46 | "collapsed": false
47 | },
48 | "outputs": [],
49 | "source": [
50 | "iris.FUTURE.strict_grib_load = True\n",
51 | "%opts Image {+framewise} [colorbar=True] Contours [colorbar=True] {+framewise} Curve [xrotation=60]"
52 | ]
53 | },
54 | {
55 | "cell_type": "markdown",
56 | "metadata": {},
57 | "source": [
58 | "Note that it is easy to set global defaults for a project allowing any suitable settings can be made into a default on a per-element basis. Now lets specify the maximum number of frames we will be displaying:"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": null,
64 | "metadata": {
65 | "collapsed": false
66 | },
67 | "outputs": [],
68 | "source": [
69 | "%output max_frames=1000 "
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "metadata": {},
75 | "source": [
76 | "\n",
77 | "
When working on a live server append ``widgets='live'`` to the line above for greatly improved performance and memory usage
\n"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "metadata": {},
83 | "source": [
84 | "## Loading our first cube"
85 | ]
86 | },
87 | {
88 | "cell_type": "markdown",
89 | "metadata": {},
90 | "source": [
91 | "Here is the summary of the first cube containing some surface temperature data:"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": null,
97 | "metadata": {
98 | "collapsed": false
99 | },
100 | "outputs": [],
101 | "source": [
102 | "iris_cube = iris.load_cube(iris.sample_data_path('GloSea4', 'ensemble_001.pp'))\n",
103 | "iris_cube.coord('latitude').guess_bounds()\n",
104 | "iris_cube.coord('longitude').guess_bounds()"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": null,
110 | "metadata": {
111 | "collapsed": false
112 | },
113 | "outputs": [],
114 | "source": [
115 | "print iris_cube.summary()"
116 | ]
117 | },
118 | {
119 | "cell_type": "markdown",
120 | "metadata": {},
121 | "source": [
122 | "Now we can wrap this Iris cube in a ``HoloCube``:"
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": null,
128 | "metadata": {
129 | "collapsed": false
130 | },
131 | "outputs": [],
132 | "source": [
133 | "surface_temperature = hc.HoloCube(iris_cube)\n",
134 | "surface_temperature"
135 | ]
136 | },
137 | {
138 | "cell_type": "markdown",
139 | "metadata": {},
140 | "source": [
141 | "# A Simple example"
142 | ]
143 | },
144 | {
145 | "cell_type": "markdown",
146 | "metadata": {},
147 | "source": [
148 | "Here is a simple example of viewing the ``surface_temperature`` cube over time with a single line of code. In HoloViews, this datastructure is a ``HoloMap`` of ``Image`` elements:"
149 | ]
150 | },
151 | {
152 | "cell_type": "code",
153 | "execution_count": null,
154 | "metadata": {
155 | "collapsed": false
156 | },
157 | "outputs": [],
158 | "source": [
159 | "surface_temperature.to.image(['longitude', 'latitude'])"
160 | ]
161 | },
162 | {
163 | "cell_type": "markdown",
164 | "metadata": {},
165 | "source": [
166 | "You can drag the slider to view the surface temperature at different times. Here is how you can view the values of time in the cube via the HoloViews API:"
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": null,
172 | "metadata": {
173 | "collapsed": false
174 | },
175 | "outputs": [],
176 | "source": [
177 | "surface_temperature.dimension_values('time')"
178 | ]
179 | },
180 | {
181 | "cell_type": "markdown",
182 | "metadata": {},
183 | "source": [
184 | "The times shown in the slider are long making the text rather small. We can use the fact that all times are recorded in the year 2011 on the 16th of each month to shorten these dates. Defining how all dates should be formatted as follows will help with readability:"
185 | ]
186 | },
187 | {
188 | "cell_type": "code",
189 | "execution_count": null,
190 | "metadata": {
191 | "collapsed": false
192 | },
193 | "outputs": [],
194 | "source": [
195 | "hv.Dimension.type_formatters[datetime.datetime] = \"%m/%y %Hh\""
196 | ]
197 | },
198 | {
199 | "cell_type": "markdown",
200 | "metadata": {},
201 | "source": [
202 | "Now let us load a cube showing the pre-industrial air temperature:"
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": null,
208 | "metadata": {
209 | "collapsed": false
210 | },
211 | "outputs": [],
212 | "source": [
213 | "air_temperature = hc.HoloCube(iris.load_cube(iris.sample_data_path('pre-industrial.pp')),\n",
214 | " group='Pre-industrial air temperature')\n",
215 | "air_temperature.data.coord('longitude').guess_bounds()\n",
216 | "air_temperature.data.coord('latitude').guess_bounds()\n",
217 | "air_temperature # Use air_temperature.data.summary() to see the Iris summary (.data is the Iris cube)"
218 | ]
219 | },
220 | {
221 | "cell_type": "markdown",
222 | "metadata": {},
223 | "source": [
224 | "Note that we have the ``air_temperature`` available over ``longitude`` and ``latitude`` but *not* the ``time`` dimensions. As a result, this cube is a single frame when visualized as a temperature map."
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "metadata": {
231 | "collapsed": false
232 | },
233 | "outputs": [],
234 | "source": [
235 | "(surface_temperature.to.image(['longitude', 'latitude'])+\n",
236 | " air_temperature.to.image(['longitude', 'latitude'])(plot=dict(projection=crs.PlateCarree())))"
237 | ]
238 | },
239 | {
240 | "cell_type": "markdown",
241 | "metadata": {},
242 | "source": [
243 | "Next is a fairly involved example that plots data side-by-side in a ``Layout`` without using the ``+`` operator. \n",
244 | "\n",
245 | "This shows how complex plots can be generated with little code and also demonstrates how different HoloViews elements can be combined together. In the following visualization, the curve is a sample of the ``surface_temperature`` at longitude and latitude *(0,10)*:"
246 | ]
247 | },
248 | {
249 | "cell_type": "code",
250 | "execution_count": null,
251 | "metadata": {
252 | "collapsed": false
253 | },
254 | "outputs": [],
255 | "source": [
256 | "%%opts Layout [fig_inches=(12,7)] Curve [aspect=2 xticks=4 xrotation=20] Points (color=2) Overlay [aspect='equal']\n",
257 | "%%opts Image [projection=crs.PlateCarree()]\n",
258 | "# Sample the surface_temperature at (0,10)\n",
259 | "temp_curve = surface_temperature.to.curve('time', dynamic=True)[0, 10]\n",
260 | "# Show surface_temperature and air_temperature with Point (0,10) marked\n",
261 | "temp_maps = [cb.to.image(['longitude', 'latitude']) * hc.Points([(0,10)]) \n",
262 | " for cb in [surface_temperature, air_temperature]]\n",
263 | "# Show everything in a two column layout\n",
264 | "hv.Layout(temp_maps + [temp_curve]).cols(2).display('all')"
265 | ]
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "metadata": {},
270 | "source": [
271 | "## Overlaying data and normalization"
272 | ]
273 | },
274 | {
275 | "cell_type": "markdown",
276 | "metadata": {},
277 | "source": [
278 | "Lets view the surface temperatures together with the global coastline:"
279 | ]
280 | },
281 | {
282 | "cell_type": "code",
283 | "execution_count": null,
284 | "metadata": {
285 | "collapsed": false
286 | },
287 | "outputs": [],
288 | "source": [
289 | "cf.COASTLINE.scale='1000m'"
290 | ]
291 | },
292 | {
293 | "cell_type": "code",
294 | "execution_count": null,
295 | "metadata": {
296 | "collapsed": false
297 | },
298 | "outputs": [],
299 | "source": [
300 | "%%opts Image [projection=crs.Geostationary()] (cmap='Greens')\n",
301 | "surface_temperature.to.image(['longitude', 'latitude']) * hc.GeoFeature(cf.COASTLINE)"
302 | ]
303 | },
304 | {
305 | "cell_type": "markdown",
306 | "metadata": {},
307 | "source": [
308 | "Notice that every frame uses the full dynamic range of the Greens color map. This is because normalization is set to ``+framewise`` at the top of the notebook which means every frame is normalized independently. \n",
309 | "\n",
310 | "To control normalization, we need to decide on the normalization limits. Let's see the maximum temperature in the cube:"
311 | ]
312 | },
313 | {
314 | "cell_type": "code",
315 | "execution_count": null,
316 | "metadata": {
317 | "collapsed": false
318 | },
319 | "outputs": [],
320 | "source": [
321 | "max_surface_temp = surface_temperature.data.data.max()\n",
322 | "max_surface_temp"
323 | ]
324 | },
325 | {
326 | "cell_type": "code",
327 | "execution_count": null,
328 | "metadata": {
329 | "collapsed": false
330 | },
331 | "outputs": [],
332 | "source": [
333 | "%%opts Image [projection=crs.Geostationary()] (cmap='Greens')\n",
334 | "# Declare a humidity dimension with a range from 0 to 0.01\n",
335 | "surface_temp_dim = hv.Dimension('surface_temperature', range=(300, max_surface_temp))\n",
336 | "# Use it to declare the value dimension of a HoloCube\n",
337 | "(hc.HoloCube(surface_temperature, vdims=[surface_temp_dim]).to.image(['longitude', 'latitude']) * hc.GeoFeature(cf.COASTLINE))"
338 | ]
339 | },
340 | {
341 | "cell_type": "markdown",
342 | "metadata": {},
343 | "source": [
344 | "By specifying the normalization range we can reveal different aspects of the data. In the example above we can see a warming effect over time as the dark green areas close to the bottom of the normalization range (200K) vanish. Values outside this range are clipped to the ends of the color map."
345 | ]
346 | },
347 | {
348 | "cell_type": "markdown",
349 | "metadata": {},
350 | "source": [
351 | "Lastly, here is a demo of a conversion from ``surface_temperature`` to ``Contours``:"
352 | ]
353 | },
354 | {
355 | "cell_type": "code",
356 | "execution_count": null,
357 | "metadata": {
358 | "collapsed": false
359 | },
360 | "outputs": [],
361 | "source": [
362 | "%%opts Contours [levels=10]\n",
363 | "(surface_temperature.to.contours(['longitude', 'latitude']) * hc.GeoFeature(cf.COASTLINE))"
364 | ]
365 | }
366 | ],
367 | "metadata": {
368 | "kernelspec": {
369 | "display_name": "Python 2",
370 | "language": "python",
371 | "name": "python2"
372 | },
373 | "language_info": {
374 | "codemirror_mode": {
375 | "name": "ipython",
376 | "version": 2
377 | },
378 | "file_extension": ".py",
379 | "mimetype": "text/x-python",
380 | "name": "python",
381 | "nbconvert_exporter": "python",
382 | "pygments_lexer": "ipython2",
383 | "version": "2.7.11"
384 | }
385 | },
386 | "nbformat": 4,
387 | "nbformat_minor": 0
388 | }
389 |
--------------------------------------------------------------------------------
/doc/Introductory_Tutorial.rst:
--------------------------------------------------------------------------------
1 | Introductory Tutorial
2 | =====================
3 |
4 | .. notebook:: holocube Introductory_Tutorial.ipynb
5 |
6 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | PROJECT = 'holocube'
5 | MODULE = 'holocube'
6 |
7 | include ./builder/Makefile
8 | export
9 |
10 | %:
11 | $(MAKE) -f ./builder/Makefile $(CFLAGS)
12 |
--------------------------------------------------------------------------------
/doc/_static/rtd_bootstrap.css:
--------------------------------------------------------------------------------
1 | .wy-menu-vertical ul {
2 | margin: 0 0 0 0 !important;
3 | }
4 |
5 | div.affix {
6 | position: relative !important;
7 | }
8 |
9 | body {
10 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif !important;
11 | }
12 |
13 | .a:hover {
14 | color: #b3b3b3 !important;
15 | }
16 |
17 | a.fa {
18 | font-size: 200%;
19 | }
20 |
21 | .code :not(h1, h2, h3, h4, h5, h6) {
22 | font-size: 80%;
23 | }
24 |
25 | div.wy-nav-content {
26 | max-width: 90%;
27 | }
28 |
29 | input {
30 | height: 30px !important;
31 | }
32 |
33 | code {
34 | font-size: inherit !important;
35 | color: #000 !important;
36 | }
37 |
38 | .rst-content dl:not(.docutils) dt {
39 | margin: 0;
40 | color: black;
41 | background: none;
42 | border-top: none;
43 | }
44 |
45 | .rst-content dd {
46 | line-height: 2;
47 | }
48 |
--------------------------------------------------------------------------------
/doc/_templates/footer.html:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------
/doc/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {# TEMPLATE VAR SETTINGS #}
2 | {%- set url_root = pathto('', 1) %}
3 | {%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
4 | {%- if not embedded and docstitle %}
5 | {%- set titlesuffix = " — "|safe + docstitle|e %}
6 | {%- else %}
7 | {%- set titlesuffix = "" %}
8 | {%- endif %}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {% block htmltitle %}
19 | {{ title|striptags|e }}{{ titlesuffix }}
20 | {% endblock %}
21 |
22 | {# FAVICON #}
23 | {% if favicon %}
24 |
25 | {% endif %}
26 |
27 | {# CSS #}
28 |
29 |
30 | {# OPENSEARCH #}
31 | {% if not embedded %}
32 | {% if use_opensearch %}
33 |
34 | {% endif %}
35 |
36 | {% endif %}
37 |
38 | {# RTD hosts this file, so just load on non RTD builds #}
39 | {% if not READTHEDOCS %}
40 |
41 | {% endif %}
42 |
43 | {% for cssfile in css_files %}
44 |
45 | {% endfor %}
46 |
47 | {%- block linktags %}
48 | {%- if hasdoc('about') %}
49 |
51 | {%- endif %}
52 | {%- if hasdoc('genindex') %}
53 |
55 | {%- endif %}
56 | {%- if hasdoc('search') %}
57 |
58 | {%- endif %}
59 | {%- if hasdoc('copyright') %}
60 |
61 | {%- endif %}
62 |
63 | {%- if parents %}
64 |
65 | {%- endif %}
66 | {%- if next %}
67 |
68 | {%- endif %}
69 | {%- if prev %}
70 |
71 | {%- endif %}
72 | {%- endblock %}
73 | {%- block extrahead %}
74 | {%- for scriptfile in script_files %}
75 |
76 | {%- endfor %}
77 | {% endblock %}
78 |
79 |
80 | {# Keep modernizr in head - http://modernizr.com/docs/#installing #}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | {# SIDE NAV, TOGGLES ON MOBILE #}
90 |
112 |
113 |
114 |
115 | {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
116 |
120 |
121 |
122 | {# PAGE CONTENT #}
123 |
124 |
125 |
126 | {% block body %}{% endblock %}
127 |
128 | {% include "footer.html" %}
129 |
130 |
131 |
132 |
133 |
134 |
135 | {% include "versions.html" %}
136 |
137 | {% if not embedded %}
138 |
139 |
148 | {% endif %}
149 |
150 | {# STICKY NAVIGATION #}
151 | {% if theme_sticky_navigation %}
152 |
157 | {% endif %}
158 |
159 | {%- block footer %} {% endblock %}
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/doc/_templates/page.html:
--------------------------------------------------------------------------------
1 | {% extends "!page.html" %}
2 | {# add our custom css and js #}
3 | {% set css_files = css_files + ["_static/bootstrap.css", "_static/codehilite.css", "_static/custom.css", "_static/rtd_bootstrap.css"] %}
4 |
--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sys, os
4 | sys.path.insert(0, os.getcwd())
5 |
6 | from builder.shared_conf import * # noqa (API import)
7 |
8 | paths = ['../param/', '.', '..']
9 | add_paths(paths)
10 |
11 | # # General information about the project.
12 | project = u'HoloCube'
13 | copyright = u'2016, HoloCube developers'
14 | authors = u'HoloCube developers'
15 | module = 'holocube'
16 | description = 'Browser based exploration of Iris cubes'
17 |
18 |
19 | # # The short X.Y version.
20 | version = u'v0.1'
21 | # The full version, including alpha/beta/rc tags.
22 | release = u'v0.1'
23 |
24 | # Override default theme
25 | import sphinx_rtd_theme
26 | html_theme = "sphinx_rtd_theme"
27 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
28 | html_logo = ''
29 | html_favicon = ''
30 |
31 | # -------------------------------------------------------------------------
32 | # -- The remaining items are less likely to need changing for a new project
33 |
34 | # List of patterns, relative to source directory, that match files and
35 | # directories to ignore when looking for source files.
36 | exclude_patterns = ['_build', 'test_data', 'reference_data', 'nbpublisher',
37 | 'builder']
38 |
39 | # The name for this set of Sphinx documents. If None, it defaults to
40 | # " v documentation".
41 | html_title = project
42 |
43 | # Add any paths that contain custom static files (such as style sheets) here,
44 | # relative to this directory. They are copied after the builtin static files,
45 | # so a file named "default.css" will overwrite the builtin "default.css".
46 | html_static_path = ['_static', 'builder/_shared_static']
47 |
48 | # Output file base name for HTML help builder.
49 | htmlhelp_basename = module+'doc'
50 |
51 |
52 | # Grouping the document tree into LaTeX files. List of tuples
53 | # (source start file, target name, title,
54 | # author, documentclass [howto, manual, or own class]).
55 | latex_documents = [
56 | ('index', module+'.tex', project+u' Documentation', authors, 'manual'),
57 | ]
58 |
59 | # One entry per manual page. List of tuples
60 | # (source start file, name, description, authors, manual section).
61 | man_pages = [
62 | ('index', module, project+u' Documentation', [authors], 1)
63 | ]
64 | # If true, show URL addresses after external links.
65 | #man_show_urls = False
66 |
67 |
68 | # Grouping the document tree into Texinfo files. List of tuples
69 | # (source start file, target name, title, author,
70 | # dir menu entry, description, category)
71 | texinfo_documents = [
72 | ('index', project, project+u' Documentation', authors, project, description,
73 | 'Miscellaneous'),
74 | ]
75 |
76 |
77 | # Example configuration for intersphinx: refer to the Python standard library.
78 | intersphinx_mapping = {'http://docs.python.org/': None,
79 | 'http://ipython.org/ipython-doc/2/': None,
80 | 'http://ioam.github.io/param/': None}
81 |
82 | js_includes = ['js/theme.js', 'bootstrap.js', 'custom.js', 'require.js']
83 |
84 | from builder.paramdoc import param_formatter
85 | from nbpublisher import nbbuild
86 |
87 |
88 | def setup(app):
89 | app.connect('autodoc-process-docstring', param_formatter)
90 | for js in js_includes:
91 | app.add_javascript(js)
92 |
93 | try:
94 | import runipy # noqa (Warning import)
95 | nbbuild.setup(app)
96 | except:
97 | print('RunIPy could not be imported; pages including the '
98 | 'Notebook directive will not build correctly')
99 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | .. HoloCube documentation master file
2 |
3 | .. raw:: html
4 | :file: latest_news.html
5 |
6 | Introduction
7 | ____________
8 |
9 | .. notebook:: holocube Homepage.ipynb
10 |
11 | ------------
12 |
13 | This website is still under construction but you can get a taste of what
14 | is to come in our `first tutorial `_.
15 |
16 | Installation
17 | ____________
18 |
19 | See the `github repo `_
20 | for installation instructions and to get started with the examples.
21 |
22 | ------------
23 |
24 | Support
25 | _______
26 |
27 | HoloCube was developed through a collaboration between
28 | `Continuum Analytics `_ and the `UK Met Office
29 | `_. HoloCube is completely `open source
30 | `_, available under a BSD license
31 | freely for both commercial and non-commercial use. Please file
32 | bug reports and feature requests on our
33 | `github site `_.
34 |
35 | .. toctree::
36 | :titlesonly:
37 | :hidden:
38 | :maxdepth: 2
39 |
40 | Home
41 | Introductory Tutorial
42 | Github source
43 |
--------------------------------------------------------------------------------
/doc/latest_news.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/holocube/__init__.py:
--------------------------------------------------------------------------------
1 | from .element import (GeoElement, HoloCube, GeoFeature, # noqa (API import)
2 | GeoTiles, WMTS, Contours, Text,
3 | Image, Points)
4 | from . import plotting # noqa (API import)
5 |
--------------------------------------------------------------------------------
/holocube/element/__init__.py:
--------------------------------------------------------------------------------
1 | from .cube import HoloCube # noqa (API import)
2 | from .geo import (GeoElement, GeoFeature, GeoTiles, # noqa (API import)
3 | WMTS, Points, Image, Text, Contours)
4 |
--------------------------------------------------------------------------------
/holocube/element/cube.py:
--------------------------------------------------------------------------------
1 | from itertools import product
2 | import iris
3 | import numpy as np
4 |
5 | import param
6 | from holoviews.core.dimension import Dimension
7 | from holoviews.core.data import Columns, DataColumns, GridColumns
8 | from holoviews.core.ndmapping import (NdMapping, item_check,
9 | sorted_context)
10 | from holoviews.core.spaces import HoloMap, DynamicMap
11 | from holoviews.element.tabular import TableConversion
12 | from . import util
13 |
14 |
15 |
16 | class HoloCube(Columns):
17 | """
18 | The HoloCube Element provides an interface to wrap and display
19 | :class:`Iris.cube.Cube` objects. The Cube automatically
20 | infers the key and value dimensions of the iris data
21 | and provides useful methods for accessing, grouping
22 | and slicing the data.
23 | """
24 |
25 | datatype = param.List(default=Columns.datatype+['cube'])
26 |
27 | group = param.String(default='HoloCube')
28 |
29 | @property
30 | def to(self):
31 | """
32 | Property to create a conversion table with methods to convert
33 | to any type.
34 | """
35 | return CubeConversion(self)
36 |
37 |
38 |
39 | class CubeConversion(TableConversion):
40 | """
41 | CubeConversion is a very simple container object which can
42 | be given an existing HoloCube and provides methods to convert
43 | the HoloCube into most other Element types. If the requested
44 | key dimensions correspond to geographical coordinates the
45 | conversion interface will automatically use a geographical
46 | Element type while all other plot will use regular HoloViews
47 | Elements.
48 | """
49 |
50 | def __init__(self, cube):
51 | self._table = cube
52 |
53 | def contours(self, kdims=None, vdims=None, mdims=None, **kwargs):
54 | from .geo import Contours
55 | return self(Contours, kdims, vdims, mdims, **kwargs)
56 |
57 | def image(self, kdims=None, vdims=None, mdims=None, **kwargs):
58 | from .geo import Image
59 | return self(Image, kdims, vdims, mdims, **kwargs)
60 |
61 | def points(self, kdims=None, vdims=None, mdims=None, **kwargs):
62 | if kdims is None: kdims = self._table.kdims
63 | kdims = [self._table.get_dimension(d) for d in kdims]
64 | geo = all(self._table.data.coord(kd.name).coord_system for kd in kdims)
65 | if len(kdims) == 2 and geo:
66 | from .geo import Points as Points
67 | else:
68 | from holoviews.element import Points
69 | return self(Points, kdims, vdims, mdims, **kwargs)
70 |
71 |
72 |
73 | class CubeInterface(GridColumns):
74 | """
75 | The CubeInterface provides allows HoloViews
76 | to interact with iris Cube data. When passing
77 | an iris Cube to a HoloViews Element the init
78 | method will infer the dimensions of the HoloCube
79 | from its coordinates. Currently the interface
80 | only provides the basic methods required for
81 | HoloViews to work with an object.
82 | """
83 |
84 | types = (iris.cube.Cube,)
85 |
86 | datatype = 'cube'
87 |
88 | @classmethod
89 | def init(cls, eltype, data, kdims, vdims):
90 | if not isinstance(data, iris.cube.Cube):
91 | raise TypeError('HoloCube data must be be an iris HoloCube type.')
92 |
93 | if kdims:
94 | if len(kdims) != len(data.dim_coords):
95 | raise ValueError('Supplied key dimensions must match '
96 | 'HoloCube dim_coords.')
97 | coords = []
98 | for kd in kdims:
99 | coord = data.coords(kd.name if isinstance(kd, Dimension) else kd)
100 | if len(coord) == 0:
101 | raise ValueError('Key dimension %s not found in '
102 | 'Iris cube.' % kd)
103 | coords.append(coord[0])
104 | else:
105 | coords = data.dim_coords
106 | coords = sorted(coords, key=util.sort_coords)
107 | kdims = [util.coord_to_dimension(crd) for crd in coords]
108 | if vdims is None:
109 | vdims = [Dimension(data.name(), unit=str(data.units))]
110 |
111 | return data, kdims, vdims
112 |
113 |
114 | @classmethod
115 | def validate(cls, holocube):
116 | pass
117 |
118 |
119 | @classmethod
120 | def values(cls, holocube, dim, expanded=True, flat=True):
121 | """
122 | Returns an array of the values along the supplied dimension.
123 | """
124 | dim = holocube.get_dimension(dim)
125 | if dim in holocube.vdims:
126 | data = holocube.data.copy().data
127 | coord_names = [c.name() for c in holocube.data.dim_coords
128 | if c.name() in holocube.kdims]
129 | dim_inds = [coord_names.index(d.name) for d in holocube.kdims]
130 | data = data.transpose(dim_inds)
131 | elif expanded:
132 | idx = holocube.get_dimension_index(dim)
133 | data = util.cartesian_product([holocube.data.coords(d.name)[0].points
134 | for d in holocube.kdims])[idx]
135 | else:
136 | data = holocube.data.coords(dim.name)[0].points
137 | return data.flatten() if flat else data
138 |
139 |
140 | @classmethod
141 | def reindex(cls, holocube, kdims=None, vdims=None):
142 | """
143 | Since cubes are never indexed directly the data itself
144 | does not need to be reindexed, the Element can simply
145 | reorder its key dimensions.
146 | """
147 | return holocube
148 |
149 |
150 | @classmethod
151 | def groupby(cls, holocube, dims, container_type=HoloMap, group_type=None, **kwargs):
152 | """
153 | Groups the data by one or more dimensions returning a container
154 | indexed by the grouped dimensions containing slices of the
155 | cube wrapped in the group_type. This makes it very easy to
156 | break up a high-dimensional HoloCube into smaller viewable chunks.
157 | """
158 | if not isinstance(dims, list): dims = [dims]
159 | dynamic = kwargs.pop('dynamic', False)
160 | dims = [holocube.get_dimension(d) for d in dims]
161 | constraints = [d.name for d in dims]
162 | slice_dims = [d for d in holocube.kdims if d not in dims]
163 |
164 | if dynamic:
165 | def load_subset(*args):
166 | constraint = iris.Constraint(**dict(zip(constraints, args)))
167 | return holocube.clone(holocube.data.extract(constraint),
168 | new_type=group_type,
169 | **dict(kwargs, kdims=slice_dims))
170 | dynamic_dims = [d(values=list(cls.values(holocube, d, False))) for d in dims]
171 | return DynamicMap(load_subset, kdims=dynamic_dims)
172 |
173 | unique_coords = product(*[cls.values(holocube, d, expanded=False)
174 | for d in dims])
175 | data = []
176 | for key in unique_coords:
177 | constraint = iris.Constraint(**dict(zip(constraints, key)))
178 | cube = holocube.clone(holocube.data.extract(constraint),
179 | new_type=group_type,
180 | **dict(kwargs, kdims=slice_dims))
181 | data.append((key, cube))
182 | if issubclass(container_type, NdMapping):
183 | with item_check(False), sorted_context(False):
184 | return container_type(data, kdims=dims)
185 | else:
186 | return container_type(data)
187 |
188 |
189 | @classmethod
190 | def range(cls, holocube, dimension):
191 | """
192 | Computes the range along a particular dimension.
193 | """
194 | dim = holocube.get_dimension(dimension)
195 | values = holocube.dimension_values(dim, False)
196 | return (np.nanmin(values), np.nanmax(values))
197 |
198 |
199 | @classmethod
200 | def length(cls, holocube):
201 | """
202 | Returns the total number of samples in the HoloCube.
203 | """
204 | return np.product([len(d.points) for d in holocube.data.coords()])
205 |
206 |
207 | DataColumns.register(CubeInterface)
208 |
--------------------------------------------------------------------------------
/holocube/element/geo.py:
--------------------------------------------------------------------------------
1 | import param
2 |
3 | import iris
4 | from cartopy import crs
5 | from cartopy.feature import Feature
6 | from cartopy.io.img_tiles import GoogleTiles
7 | from holoviews.core import Element2D, Dimension
8 | from holoviews.core import util
9 | from holoviews.element import Text as HVText
10 |
11 | from .cube import HoloCube
12 |
13 |
14 | class GeoElement(Element2D):
15 | """
16 | Baseclass for Element2D types with associated cartopy
17 | coordinate reference system.
18 | """
19 |
20 | _abstract = True
21 |
22 | crs = param.ClassSelector(class_=crs.CRS, doc="""
23 | Cartopy coordinate-reference-system specifying the
24 | coordinate system of the data. Inferred automatically
25 | when GeoElement wraps Iris Feature object.""")
26 |
27 | def __init__(self, data, **kwargs):
28 | crs = None
29 | crs_data = data.data if isinstance(data, HoloCube) else data
30 | if isinstance(crs_data, iris.cube.Cube):
31 | coord_sys = crs_data.coord_system()
32 | if hasattr(coord_sys, 'as_cartopy_projection'):
33 | crs = coord_sys.as_cartopy_projection()
34 | elif isinstance(crs_data, (Feature, GoogleTiles)):
35 | crs = crs_data.crs
36 |
37 | supplied_crs = kwargs.get('crs', None)
38 | if supplied_crs and crs and crs != supplied_crs:
39 | raise ValueError('Supplied coordinate reference '
40 | 'system must match crs of the data.')
41 | elif crs:
42 | kwargs['crs'] = crs
43 | super(GeoElement, self).__init__(data, **kwargs)
44 |
45 |
46 | def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides):
47 | if 'crs' not in overrides: overrides['crs'] = self.crs
48 | return super(GeoElement, self).clone(data, shared_data, new_type,
49 | *args, **overrides)
50 |
51 |
52 | class GeoFeature(GeoElement):
53 | """
54 | A GeoFeature represents a geographical feature
55 | specified as a cartopy Feature type.
56 | """
57 |
58 | group = param.String(default='GeoFeature')
59 |
60 | _auxiliary_component = True
61 |
62 | def __init__(self, data, **params):
63 | if not isinstance(data, Feature):
64 | raise TypeError('%s data has to be an cartopy Feature type'
65 | % type(data).__name__)
66 | super(GeoFeature, self).__init__(data, **params)
67 |
68 |
69 | class WMTS(GeoElement):
70 | """
71 | The WMTS Element represents a Web Map Tile Service
72 | specified as a tuple of the API URL and
73 | """
74 |
75 | group = param.String(default='WMTS')
76 |
77 | layer = param.String(doc="The layer on the tile service")
78 |
79 | _auxiliary_component = True
80 |
81 | def __init__(self, data, **params):
82 | if not isinstance(data, util.basestring):
83 | raise TypeError('%s data has to be a tile service URL'
84 | % type(data).__name__)
85 | super(WMTS, self).__init__(data, **params)
86 |
87 |
88 | class GeoTiles(GeoElement):
89 | """
90 | GeoTiles represents an image tile source to dynamically
91 | load data from depending on the zoom level.
92 | """
93 |
94 | group = param.String(default='GeoTiles')
95 |
96 | _auxiliary_component = True
97 |
98 | def __init__(self, data, **params):
99 | if not isinstance(data, GoogleTiles):
100 | raise TypeError('%s data has to be a cartopy GoogleTiles type'
101 | % type(data).__name__)
102 | super(GeoTiles, self).__init__(data, **params)
103 |
104 |
105 | class Points(GeoElement, HoloCube):
106 | """
107 | Points represent a collection of points with
108 | an associated cartopy coordinate-reference system.
109 | """
110 |
111 | kdims = param.List(default=['longitude', 'latitude'])
112 |
113 | group = param.String(default='Points')
114 |
115 |
116 | class Contours(GeoElement, HoloCube):
117 | """
118 | Contours represents a 2D array of some quantity with
119 | some associated coordinates, which may be discretized
120 | into one or more contours.
121 | """
122 |
123 | kdims = param.List(default=[Dimension('x'), Dimension('y')])
124 |
125 | vdims = param.List(default=[Dimension('z')], bounds=(1, 1))
126 |
127 | group = param.String(default='Contours')
128 |
129 |
130 | class Image(GeoElement, HoloCube):
131 | """
132 | Image represents a 2D array of some quantity with
133 | some associated coordinates.
134 | """
135 |
136 | kdims = param.List(default=[Dimension('x'), Dimension('y')])
137 |
138 | vdims = param.List(default=[Dimension('z')], bounds=(1, 1))
139 |
140 | group = param.String(default='Image')
141 |
142 |
143 | class Text(HVText, GeoElement):
144 | """
145 | An annotation containing some text at an x, y coordinate
146 | along with a coordinate reference system.
147 | """
148 |
--------------------------------------------------------------------------------
/holocube/element/util.py:
--------------------------------------------------------------------------------
1 | from iris.util import guess_coord_axis
2 | from holoviews.core.dimension import Dimension
3 | from holoviews.core.util import * # noqa (API import)
4 |
5 | import datetime
6 |
7 | def get_date_format(coord):
8 | def date_formatter(val, pos=None):
9 | date = coord.units.num2date(val)
10 | date_format = Dimension.type_formatters.get(datetime.datetime, None)
11 | if date_format:
12 | return date.strftime(date_format)
13 | else:
14 | return date
15 |
16 | return date_formatter
17 |
18 | def coord_to_dimension(coord):
19 | """
20 | Converts an iris coordinate to a HoloViews dimension.
21 | """
22 | kwargs = {}
23 | if coord.units.is_time_reference():
24 | kwargs['value_format'] = get_date_format(coord)
25 | else:
26 | kwargs['unit'] = str(coord.units)
27 | return Dimension(coord.name(), **kwargs)
28 |
29 | def sort_coords(coord):
30 | """
31 | Sorts a list of DimCoords trying to ensure that
32 | dates and pressure levels appear first and the
33 | longitude and latitude appear last in the correct
34 | order.
35 | """
36 | order = {'T': -2, 'Z': -1, 'X': 1, 'Y': 2}
37 | axis = guess_coord_axis(coord)
38 | return (order.get(axis, 0), coord and coord.name())
--------------------------------------------------------------------------------
/holocube/plotting/__init__.py:
--------------------------------------------------------------------------------
1 | import copy
2 |
3 | import param
4 | import iris.plot as iplt
5 | from cartopy import crs as ccrs
6 | from holoviews.core import (Store, HoloMap, Layout, Overlay,
7 | CompositeOverlay, Element)
8 | from holoviews.plotting.mpl import (ElementPlot, ColorbarPlot, PointPlot,
9 | AnnotationPlot, TextPlot,
10 | LayoutPlot as HvLayoutPlot,
11 | OverlayPlot as HvOverlayPlot)
12 |
13 | from ..element import (Contours, Image, Points, GeoFeature,
14 | WMTS, GeoTiles, Text, util)
15 |
16 |
17 | def _get_projection(el):
18 | """
19 | Get coordinate reference system from non-auxiliary elements.
20 | Return value is a tuple of a precedence integer and the projection,
21 | to allow non-auxiliary components to take precedence.
22 | """
23 | return (int(el._auxiliary_component), el.crs) if hasattr(el, 'crs') else None
24 |
25 |
26 | class ProjectionPlot(object):
27 | """
28 | Implements custom _get_projection method to make the coordinate
29 | reference system available to HoloViews plots as a projection.
30 | """
31 |
32 | def _get_projection(cls, obj):
33 | # Look up custom projection in options
34 | isoverlay = lambda x: isinstance(x, CompositeOverlay)
35 | opts = cls._traverse_options(obj, 'plot', ['projection'],
36 | [CompositeOverlay, Element],
37 | keyfn=isoverlay, defaults=False)
38 | from_overlay = not all(p is None for p in opts[True]['projection'])
39 | projections = opts[from_overlay]['projection']
40 | custom_projs = [p for p in projections if p is not None]
41 | if len(set([type(p) for p in custom_projs])) > 1:
42 | raise Exception("An axis may only be assigned one projection type")
43 | elif custom_projs:
44 | return custom_projs[0]
45 |
46 | # If no custom projection is supplied traverse object to get
47 | # the custom projections and sort by precedence
48 | projections = sorted([p for p in obj.traverse(_get_projection, [Element])
49 | if p is not None and p[1] is not None])
50 | if projections:
51 | return projections[0][1]
52 | else:
53 | return None
54 |
55 |
56 | class LayoutPlot(ProjectionPlot, HvLayoutPlot):
57 | """
58 | Extends HoloViews LayoutPlot with functionality to determine
59 | the correct projection for each axis.
60 | """
61 |
62 | class OverlayPlot(ProjectionPlot, HvOverlayPlot):
63 | """
64 | Extends HoloViews OverlayPlot with functionality to determine
65 | the correct projection for each axis.
66 | """
67 |
68 |
69 | class GeoPlot(ProjectionPlot, ElementPlot):
70 | """
71 | Plotting baseclass for geographic plots with a cartopy projection.
72 | """
73 |
74 | projection = param.Parameter(default=ccrs.PlateCarree())
75 |
76 | def __init__(self, element, **params):
77 | if 'projection' not in params:
78 | el = element.last if isinstance(element, HoloMap) else element
79 | params['projection'] = el.crs
80 | super(GeoPlot, self).__init__(element, **params)
81 | self.aspect = 'equal'
82 | self.apply_ranges = False
83 |
84 | def teardown_handles(self):
85 | """
86 | Delete artist handle so it can be redrawn.
87 | """
88 | try:
89 | self.handles['artist'].remove()
90 | except ValueError:
91 | pass
92 |
93 |
94 | class GeoContourPlot(GeoPlot, ColorbarPlot):
95 | """
96 | Draws a contour or contourf plot from the data in
97 | a Contours.
98 | """
99 |
100 | filled = param.Boolean(default=True, doc="""
101 | Whether to draw filled or unfilled contours""")
102 |
103 | levels = param.ClassSelector(default=5, class_=(int, list))
104 |
105 | style_opts = ['antialiased', 'alpha', 'cmap']
106 |
107 | def get_data(self, element, ranges, style):
108 | args = (element.data.copy(),)
109 | if isinstance(self.levels, int):
110 | args += (self.levels,)
111 | else:
112 | style['levels'] = self.levels
113 | return args, style, {}
114 |
115 | def init_artists(self, ax, plot_args, plot_kwargs):
116 | plotfn = iplt.contourf if self.filled else iplt.contour
117 | artists = {'artist': plotfn(*plot_args, axes=ax, **plot_kwargs)}
118 | return artists
119 |
120 | def teardown_handles(self):
121 | """
122 | Iterate over the artists in the collection and remove
123 | them individually.
124 | """
125 | if 'artist' in self.handles:
126 | for coll in self.handles['artist'].collections:
127 | try:
128 | coll.remove()
129 | except ValueError:
130 | pass
131 |
132 |
133 | class GeoImagePlot(GeoPlot, ColorbarPlot):
134 |
135 | """
136 | Draws a pcolormesh plot from the data in a Image Element.
137 | """
138 |
139 | style_opts = ['alpha', 'cmap', 'visible', 'filterrad', 'clims', 'norm']
140 |
141 | def get_data(self, element, ranges, style):
142 | self._norm_kwargs(element, ranges, style, element.vdims[0])
143 | style.pop('interpolation')
144 | return (element.data.copy(),), style, {}
145 |
146 | def init_artists(self, ax, plot_args, plot_kwargs):
147 | return {'artist': iplt.pcolormesh(*plot_args, axes=ax, **plot_kwargs)}
148 |
149 |
150 |
151 | class GeoPointPlot(GeoPlot, PointPlot):
152 | """
153 | Draws a scatter plot from the data in a Points Element.
154 | """
155 |
156 | def get_data(self, element, ranges, style):
157 | data = super(GeoPointPlot, self).get_data(element, ranges, style)
158 | args, style, axis_kwargs = data
159 | style['transform'] = element.crs
160 | return args, style, axis_kwargs
161 |
162 |
163 | ########################################
164 | # Geographic features and annotations #
165 | ########################################
166 |
167 |
168 | class GeoFeaturePlot(GeoPlot):
169 | """
170 | Draws a feature from a GeoFeatures Element.
171 | """
172 |
173 | scale = param.ObjectSelector(default='110m', objects=['10m', '50m', '110m'],
174 | doc="The scale of the GeoFeature in meters.")
175 |
176 | style_opts = ['alpha', 'facecolor', 'edgecolor', 'linestyle', 'linewidth',
177 | 'visible']
178 |
179 | def get_data(self, element, ranges, style):
180 | feature = copy.copy(element.data)
181 | feature.scale = self.scale
182 | return (feature,), style, {}
183 |
184 | def init_artists(self, ax, plot_args, plot_kwargs):
185 | return {'artist': ax.add_feature(*plot_args, **plot_kwargs)}
186 |
187 |
188 |
189 | class WMTSPlot(GeoPlot):
190 | """
191 | Adds a Web Map Tile Service from a WMTS Element.
192 | """
193 |
194 | style_opts = ['alpha', 'cmap', 'interpolation', 'visible',
195 | 'filterrad', 'clims', 'norm']
196 |
197 | def get_data(self, element, ranges, style):
198 | return (element.data, element.layer), style, {}
199 |
200 | def init_artists(self, ax, plot_args, plot_kwargs):
201 | return {'artist': ax.add_wmts(*plot_args, **plot_kwargs)}
202 |
203 |
204 | class GeoTilePlot(GeoPlot):
205 | """
206 | Draws image tiles specified by a GeoTiles Element.
207 | """
208 |
209 | zoom = param.Integer(default=8, doc="""
210 | Controls the zoom level of the tile source.""")
211 |
212 | style_opts = ['alpha', 'cmap', 'interpolation', 'visible',
213 | 'filterrad', 'clims', 'norm']
214 |
215 | def get_data(self, element, ranges, style):
216 | return (element.data, self.zoom), style, {}
217 |
218 | def init_artists(self, ax, plot_args, plot_kwargs):
219 | return {'artist': ax.add_image(*plot_args, **plot_kwargs)}
220 |
221 |
222 | class GeoAnnotationPlot(AnnotationPlot):
223 | """
224 | AnnotationPlot handles the display of all annotation elements.
225 | """
226 |
227 | def initialize_plot(self, ranges=None):
228 | annotation = self.hmap.last
229 | key = self.keys[-1]
230 | ranges = self.compute_ranges(self.hmap, key, ranges)
231 | ranges = util.match_spec(annotation, ranges)
232 | axis = self.handles['axis']
233 | opts = self.style[self.cyclic_index]
234 | handles = self.draw_annotation(axis, annotation.data, annotation.crs, opts)
235 | self.handles['annotations'] = handles
236 | return self._finalize_axis(key, ranges=ranges)
237 |
238 | def update_handles(self, key, axis, annotation, ranges, style):
239 | # Clear all existing annotations
240 | for element in self.handles['annotations']:
241 | element.remove()
242 |
243 | self.handles['annotations'] = self.draw_annotation(axis,
244 | annotation.data,
245 | annotation.crs, style)
246 |
247 |
248 | class GeoTextPlot(GeoAnnotationPlot, TextPlot):
249 | "Draw the Text annotation object"
250 |
251 | def draw_annotation(self, axis, data, crs, opts):
252 | (x,y, text, fontsize,
253 | horizontalalignment, verticalalignment, rotation) = data
254 | opts['fontsize'] = fontsize
255 | x, y = axis.projection.transform_point(x, y, src_crs=crs)
256 | return [axis.text(x, y, text,
257 | horizontalalignment=horizontalalignment,
258 | verticalalignment=verticalalignment,
259 | rotation=rotation, **opts)]
260 |
261 |
262 |
263 | # Register plots with HoloViews
264 | Store.register({Contours: GeoContourPlot,
265 | Image: GeoImagePlot,
266 | GeoFeature: GeoFeaturePlot,
267 | WMTS: WMTSPlot,
268 | GeoTiles: GeoTilePlot,
269 | Points: GeoPointPlot,
270 | Text: GeoTextPlot,
271 | Layout: LayoutPlot,
272 | Overlay: OverlayPlot}, 'matplotlib')
273 |
274 |
275 | # Define plot and style options
276 | opts = Store.options(backend='matplotlib')
277 | OverlayPlot.aspect = 'equal'
278 |
--------------------------------------------------------------------------------
/notebooks/Colocated_cube_pair.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "This notebook is based on the user story described in [Issue #7](https://github.com/CubeBrowser/cube-explorer/issues/7) where the main objective is as follows:\n",
8 | "\n",
9 | "> Co-located cubes shall be rendered as sets, with shared sliders across non-plot dimensions ...\n",
10 | "\n",
11 | "The material presented here is inspired by the code example given in the issue and this example visualization:"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "metadata": {
18 | "collapsed": false
19 | },
20 | "outputs": [],
21 | "source": [
22 | "from IPython.display import Image as IPImage\n",
23 | "IPImage(url=\"http://www.wetterzentrale.de/wz/pics/Recm1201.gif\", width=500)"
24 | ]
25 | },
26 | {
27 | "cell_type": "code",
28 | "execution_count": null,
29 | "metadata": {
30 | "collapsed": false
31 | },
32 | "outputs": [],
33 | "source": [
34 | "import iris\n",
35 | "import numpy as np\n",
36 | "import holoviews as hv\n",
37 | "import holocube as hc\n",
38 | "from cartopy import crs\n",
39 | "from cartopy import feature as cf\n",
40 | "hv.notebook_extension()"
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": null,
46 | "metadata": {
47 | "collapsed": false
48 | },
49 | "outputs": [],
50 | "source": [
51 | "iris.FUTURE.strict_grib_load = True\n",
52 | "%output widgets='live' max_frames=1000 # Plot data on request\n",
53 | "%opts Image {+framewise} Contours {+framewise} "
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "metadata": {},
59 | "source": [
60 | "In this notebook we will work with the following 900MB file:"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "metadata": {
67 | "collapsed": false
68 | },
69 | "outputs": [],
70 | "source": [
71 | "ls -lh files/*.pp"
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": null,
77 | "metadata": {
78 | "collapsed": false
79 | },
80 | "outputs": [],
81 | "source": [
82 | "cubelist = iris.load('files/prodm_op_ukv_20160207_21_022.pp')\n",
83 | "print \"%d cubes have been loaded\" % len(cubelist)"
84 | ]
85 | },
86 | {
87 | "cell_type": "markdown",
88 | "metadata": {},
89 | "source": [
90 | "Here is the summary of the first cube containing data:"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "metadata": {
97 | "collapsed": false
98 | },
99 | "outputs": [],
100 | "source": [
101 | "print cubelist[1].summary()"
102 | ]
103 | },
104 | {
105 | "cell_type": "markdown",
106 | "metadata": {},
107 | "source": [
108 | "First guess the bounds for all cubes:"
109 | ]
110 | },
111 | {
112 | "cell_type": "code",
113 | "execution_count": null,
114 | "metadata": {
115 | "collapsed": false
116 | },
117 | "outputs": [],
118 | "source": [
119 | "for c in cubelist:\n",
120 | " c.coord('grid_longitude').guess_bounds()\n",
121 | " c.coord('grid_latitude').guess_bounds()"
122 | ]
123 | },
124 | {
125 | "cell_type": "markdown",
126 | "metadata": {},
127 | "source": [
128 | "In a similar way, we can now load up the other cubes that contain data to display:"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "metadata": {
135 | "collapsed": false
136 | },
137 | "outputs": [],
138 | "source": [
139 | "cubes = {cb.vdims[0].name:cb for cb in [hc.HoloCube(c) for c in cubelist]} # Load cubes into dictionary\n",
140 | "cubes = {k:v for k,v in cubes.items() if k!='unknown'} # Filter as desired"
141 | ]
142 | },
143 | {
144 | "cell_type": "markdown",
145 | "metadata": {},
146 | "source": [
147 | "Note that this uses simple dictionary but you could use a HoloViews ``Layout`` which would offer tab-completion access. We can can view the dictionary keys as follows:"
148 | ]
149 | },
150 | {
151 | "cell_type": "code",
152 | "execution_count": null,
153 | "metadata": {
154 | "collapsed": false
155 | },
156 | "outputs": [],
157 | "source": [
158 | "print \"Available model variables:\\n %s\" % ',\\n '.join(cubes.keys())"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "metadata": {},
164 | "source": [
165 | "# A Simple example"
166 | ]
167 | },
168 | {
169 | "cell_type": "markdown",
170 | "metadata": {},
171 | "source": [
172 | "Here is a very simple example of viewing the ``specific_humidity`` cube as a ``HoloMap`` of ``Image`` elements:"
173 | ]
174 | },
175 | {
176 | "cell_type": "code",
177 | "execution_count": null,
178 | "metadata": {
179 | "collapsed": false
180 | },
181 | "outputs": [],
182 | "source": [
183 | "cubes['specific_humidity'].to.image(['grid_longitude', 'grid_latitude'])"
184 | ]
185 | },
186 | {
187 | "cell_type": "markdown",
188 | "metadata": {},
189 | "source": [
190 | "Note that the time slider does work but there are only two time values in the cube (try dragging the slider through the full range). Here is how you can view the values of time in the cube via the HoloViews API:"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "metadata": {
197 | "collapsed": false
198 | },
199 | "outputs": [],
200 | "source": [
201 | "cubes['specific_humidity'].dimension_values('time')"
202 | ]
203 | },
204 | {
205 | "cell_type": "markdown",
206 | "metadata": {},
207 | "source": [
208 | "We can easily view the data in two cubes side-by-side. Here the ``specific_humidity`` cube is visualized next to the ``air_pressure`` cube:"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "metadata": {
215 | "collapsed": false
216 | },
217 | "outputs": [],
218 | "source": [
219 | "( cubes['specific_humidity'].to.image(['grid_longitude', 'grid_latitude']) \n",
220 | " + cubes['air_pressure'].to.image(['grid_longitude', 'grid_latitude']))"
221 | ]
222 | },
223 | {
224 | "cell_type": "markdown",
225 | "metadata": {},
226 | "source": [
227 | "Here is an example of four types of variable plotted as a layout without using the ``+`` operator:"
228 | ]
229 | },
230 | {
231 | "cell_type": "code",
232 | "execution_count": null,
233 | "metadata": {
234 | "collapsed": false
235 | },
236 | "outputs": [],
237 | "source": [
238 | "%%opts Layout [fig_inches=(8, 6)]\n",
239 | "keys = ['specific_humidity', 'air_temperature', 'surface_air_pressure', 'cloud_volume_fraction_in_atmosphere_layer']\n",
240 | "hv.Layout([cubes[k].to.image(['grid_longitude', 'grid_latitude']).relabel(group=k) for k in keys]).cols(2).display('all')"
241 | ]
242 | },
243 | {
244 | "cell_type": "markdown",
245 | "metadata": {},
246 | "source": [
247 | "## Overlaying data and normalization"
248 | ]
249 | },
250 | {
251 | "cell_type": "markdown",
252 | "metadata": {},
253 | "source": [
254 | "Lets view the specific humidity data together with the UK coastline:"
255 | ]
256 | },
257 | {
258 | "cell_type": "code",
259 | "execution_count": null,
260 | "metadata": {
261 | "collapsed": true
262 | },
263 | "outputs": [],
264 | "source": [
265 | "%output size=400"
266 | ]
267 | },
268 | {
269 | "cell_type": "markdown",
270 | "metadata": {},
271 | "source": [
272 | "Let's use a fairly high resolution map:"
273 | ]
274 | },
275 | {
276 | "cell_type": "code",
277 | "execution_count": null,
278 | "metadata": {
279 | "collapsed": false
280 | },
281 | "outputs": [],
282 | "source": [
283 | "cf.COASTLINE.scale='50m'"
284 | ]
285 | },
286 | {
287 | "cell_type": "code",
288 | "execution_count": null,
289 | "metadata": {
290 | "collapsed": false
291 | },
292 | "outputs": [],
293 | "source": [
294 | "%%opts Image [projection=crs.Geostationary()] (cmap='Greens')\n",
295 | "cubes['specific_humidity'].to.image(['grid_longitude', 'grid_latitude']) * hc.GeoFeature(cf.COASTLINE)"
296 | ]
297 | },
298 | {
299 | "cell_type": "markdown",
300 | "metadata": {},
301 | "source": [
302 | "Notice that every frame uses the full dynamic range of the Greens color map. This is because normalization is set to ``+framewise`` at the top of the notebook which means every frame is normalized independently. We can specify a fixed normalization range as follows:"
303 | ]
304 | },
305 | {
306 | "cell_type": "code",
307 | "execution_count": null,
308 | "metadata": {
309 | "collapsed": false
310 | },
311 | "outputs": [],
312 | "source": [
313 | "%%opts Image [projection=crs.Geostationary()] (cmap='Greens')\n",
314 | "# Declare a humidity dimension with a range from 0 to 0.01\n",
315 | "humidity_dim = hv.Dimension('specific_humidity', range=(0,0.01))\n",
316 | "# Use it to declare the value dimension of a HoloCube\n",
317 | "(hc.HoloCube(cubes['specific_humidity'], vdims=[humidity_dim]).to.image(['grid_longitude', 'grid_latitude'])\n",
318 | " * hc.GeoFeature(cf.COASTLINE))"
319 | ]
320 | },
321 | {
322 | "cell_type": "markdown",
323 | "metadata": {},
324 | "source": [
325 | "With the fixed normalization we can see that as the ``model_level_number`` increases, the specific humidity also increases. Using a fixed normalization helps us view specific humidity changes over time but can mean we lose the ability to see the variation in humidity at a specific time if only a small fraction of the available range is used."
326 | ]
327 | },
328 | {
329 | "cell_type": "markdown",
330 | "metadata": {},
331 | "source": [
332 | "Lastly, here is a demo of a conversion from cloud volume fraction to ``Contours``:"
333 | ]
334 | },
335 | {
336 | "cell_type": "code",
337 | "execution_count": null,
338 | "metadata": {
339 | "collapsed": false
340 | },
341 | "outputs": [],
342 | "source": [
343 | "%%opts Contours [levels=10]\n",
344 | "(hc.GeoFeature(cf.COASTLINE) \n",
345 | " * cubes['cloud_volume_fraction_in_atmosphere_layer'].to.contours(['grid_longitude', 'grid_latitude']))"
346 | ]
347 | }
348 | ],
349 | "metadata": {
350 | "kernelspec": {
351 | "display_name": "Python 2",
352 | "language": "python",
353 | "name": "python2"
354 | },
355 | "language_info": {
356 | "codemirror_mode": {
357 | "name": "ipython",
358 | "version": 2
359 | },
360 | "file_extension": ".py",
361 | "mimetype": "text/x-python",
362 | "name": "python",
363 | "nbconvert_exporter": "python",
364 | "pygments_lexer": "ipython2",
365 | "version": "2.7.11"
366 | }
367 | },
368 | "nbformat": 4,
369 | "nbformat_minor": 0
370 | }
371 |
--------------------------------------------------------------------------------
/notebooks/CubeExplorer_Prototype.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "This is an initial prototype for the cube-explorer project to integrate Iris, Cartopy and HoloViews to allow easily exploring Iris Cubes of N-dimensional gridded data."
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "For now we've called the library that interfaces between Iris/Cartopy and HoloViews ``holocube`` and we will import it as ``hc``."
15 | ]
16 | },
17 | {
18 | "cell_type": "code",
19 | "execution_count": null,
20 | "metadata": {
21 | "collapsed": false,
22 | "scrolled": true
23 | },
24 | "outputs": [],
25 | "source": [
26 | "import iris\n",
27 | "import numpy as np\n",
28 | "import holoviews as hv\n",
29 | "import holocube as hc\n",
30 | "from cartopy import crs\n",
31 | "from cartopy import feature as cf\n",
32 | "\n",
33 | "hv.notebook_extension()"
34 | ]
35 | },
36 | {
37 | "cell_type": "markdown",
38 | "metadata": {},
39 | "source": [
40 | "# Plotting with projections"
41 | ]
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "metadata": {},
46 | "source": [
47 | "The ``holocube`` package provides a library of Element types which make itvery easy to plot data on various geographic projections. Depending on the type of data the plotting code will automatically infer the correct ``transform`` and a default ``projection``.\n",
48 | "\n",
49 | "A simple example is the ``GeoFeature`` Element, which accepts various cartopy features. We can easily overlay these features by constructing an ``Overlay`` of GeoFeature elements."
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": null,
55 | "metadata": {
56 | "collapsed": false
57 | },
58 | "outputs": [],
59 | "source": [
60 | "%%output size=400\n",
61 | "feats = [cf.LAND, cf.OCEAN, cf.RIVERS, cf.LAKES, cf.BORDERS, cf.COASTLINE]\n",
62 | "features = hv.Overlay([hc.GeoFeature(feature) for feature in feats])\n",
63 | "features"
64 | ]
65 | },
66 | {
67 | "cell_type": "markdown",
68 | "metadata": {},
69 | "source": [
70 | "Below is the full list of cartopy projections that can be displayed using matplotlib."
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": null,
76 | "metadata": {
77 | "collapsed": false
78 | },
79 | "outputs": [],
80 | "source": [
81 | "projections = [crs.RotatedPole, crs.TransverseMercator, crs.Mercator, crs.LambertCylindrical,\n",
82 | " crs.Geostationary, crs.AzimuthalEquidistant, crs.OSGB, crs.EuroPP, crs.Gnomonic,\n",
83 | " crs.PlateCarree, crs.Mollweide, crs.OSNI, crs.Miller, crs.InterruptedGoodeHomolosine,\n",
84 | " crs.LambertConformal, crs.SouthPolarStereo, crs.AlbersEqualArea, crs.Orthographic,\n",
85 | " crs.NorthPolarStereo, crs.Robinson, crs.Stereographic]"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "metadata": {},
91 | "source": [
92 | "We can test the different projections by creating a Layout of ``GeoFeature`` elements, each with a different projection:"
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": null,
98 | "metadata": {
99 | "collapsed": false,
100 | "scrolled": false
101 | },
102 | "outputs": [],
103 | "source": [
104 | "hv.Layout([hc.GeoFeature(cf.COASTLINE, group=p.__name__)(plot=dict(projection=p()))\n",
105 | " for p in projections]).display('all')"
106 | ]
107 | },
108 | {
109 | "cell_type": "markdown",
110 | "metadata": {},
111 | "source": [
112 | "To change the projection we can use the call method on HoloViews objects and set it as a plot option, this way we can easily compose plots with different projections. Here we compose a StockImage using the Mollweide projection with an Overlay of a StockImage and Coastlines Element set to a GeoStationary projection."
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "metadata": {
119 | "collapsed": true
120 | },
121 | "outputs": [],
122 | "source": [
123 | "%output size=250"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": null,
129 | "metadata": {
130 | "collapsed": false
131 | },
132 | "outputs": [],
133 | "source": [
134 | "(features(plot=dict(projection=crs.Mollweide())) +\n",
135 | "features.relabel(group='Geostationary Overlay')(plot=dict(projection=crs.Geostationary())))"
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "metadata": {},
141 | "source": [
142 | "This way we can also use different Element types such as this ``WMTS`` Element, which allows wrapping any webmap tilesource:"
143 | ]
144 | },
145 | {
146 | "cell_type": "code",
147 | "execution_count": null,
148 | "metadata": {
149 | "collapsed": false
150 | },
151 | "outputs": [],
152 | "source": [
153 | "%%output backend='matplotlib:nbagg' widgets='live' size=200\n",
154 | "url = 'http://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi'\n",
155 | "layer = 'VIIRS_CityLights_2012'\n",
156 | "hc.WMTS(url, layer=layer)(plot=dict(projection=crs.PlateCarree()))"
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "metadata": {},
162 | "source": [
163 | "In some cases the data does not define the coordinate system the data is in automatically, e.g. when using points. In this case we can supply a coordinate reference system directly. Here we display a GeoTiles object drawn from the MapQuest ordinance survey map of Great Britain, and overlay this tile source with points corresponding to the tube map locations. Since these coordinates are in Ordinance Survery GB coordinates we declare that explicitly via the crs parameter on the GeoPoints."
164 | ]
165 | },
166 | {
167 | "cell_type": "code",
168 | "execution_count": null,
169 | "metadata": {
170 | "collapsed": false
171 | },
172 | "outputs": [],
173 | "source": [
174 | "%%output size=200\n",
175 | "%%opts Overlay [apply_extents=True]\n",
176 | "from cartopy.io.img_tiles import MapQuestOSM\n",
177 | "from matplotlib.path import Path\n",
178 | "\n",
179 | "def tube_locations():\n",
180 | " \"\"\"\n",
181 | " Returns an (n, 2) array of selected London Tube locations in Ordnance\n",
182 | " Survey GB coordinates.\n",
183 | "\n",
184 | " Source: http://www.doogal.co.uk/london_stations.php\n",
185 | "\n",
186 | " \"\"\"\n",
187 | " return np.array([[531738., 180890.], [532379., 179734.],\n",
188 | " [531096., 181642.], [530234., 180492.],\n",
189 | " [531688., 181150.], [530242., 180982.],\n",
190 | " [531940., 179144.], [530406., 180380.],\n",
191 | " [529012., 180283.], [530553., 181488.],\n",
192 | " [531165., 179489.], [529987., 180812.],\n",
193 | " [532347., 180962.], [529102., 181227.],\n",
194 | " [529612., 180625.], [531566., 180025.],\n",
195 | " [529629., 179503.], [532105., 181261.],\n",
196 | " [530995., 180810.], [529774., 181354.],\n",
197 | " [528941., 179131.], [531050., 179933.],\n",
198 | " [530240., 179718.]])\n",
199 | "\n",
200 | "theta = np.linspace(0, 2 * np.pi, 100)\n",
201 | "circle_verts = np.vstack([np.sin(theta), np.cos(theta)]).T\n",
202 | "concentric_circle = Path.make_compound_path(Path(circle_verts[::-1]),\n",
203 | " Path(circle_verts * 0.6))\n",
204 | "\n",
205 | "rectangle = Path([[-1.1, -0.2], [1, -0.2], [1, 0.3], [-1.1, 0.3]])\n",
206 | "\n",
207 | "tiles = MapQuestOSM()\n",
208 | "hc.GeoTiles(tiles)(plot=dict(projection=tiles.crs, zoom=14)) *\\\n",
209 | "hc.Points(tube_locations(), crs=crs.OSGB())(style=dict(color='r', s=100, marker=concentric_circle)) *\\\n",
210 | "hc.Points(tube_locations(), crs=crs.OSGB())(style=dict(color='b', s=100, marker=rectangle))"
211 | ]
212 | },
213 | {
214 | "cell_type": "markdown",
215 | "metadata": {},
216 | "source": [
217 | "# Loading and displaying data"
218 | ]
219 | },
220 | {
221 | "cell_type": "markdown",
222 | "metadata": {},
223 | "source": [
224 | "Now we will load some real data using from the Iris sample datasets:"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "metadata": {
231 | "collapsed": false
232 | },
233 | "outputs": [],
234 | "source": [
235 | "import numpy as np\n",
236 | "import iris\n",
237 | "import iris.coords\n",
238 | "\n",
239 | "def realization_metadata(cube, field, fname):\n",
240 | " if not cube.coords('realization'):\n",
241 | " realization_number = fname[-6:-3]\n",
242 | " realization_coord = iris.coords.AuxCoord(np.int32(realization_number), 'realization')\n",
243 | " cube.add_aux_coord(realization_coord)\n",
244 | "\n",
245 | "surface_temp = iris.load_cube(iris.sample_data_path('GloSea4', 'ensemble_???.pp'),\n",
246 | " iris.Constraint('surface_temperature', realization=lambda value: True),\n",
247 | " callback=realization_metadata)"
248 | ]
249 | },
250 | {
251 | "cell_type": "markdown",
252 | "metadata": {},
253 | "source": [
254 | "The Cube Element defined above wraps the Iris Cubes, converting coordinates to HoloViews dimensions and tries to infer the correct order of dimensions:"
255 | ]
256 | },
257 | {
258 | "cell_type": "code",
259 | "execution_count": null,
260 | "metadata": {
261 | "collapsed": false
262 | },
263 | "outputs": [],
264 | "source": [
265 | "cube = hc.HoloCube(surface_temp)\n",
266 | "cube"
267 | ]
268 | },
269 | {
270 | "cell_type": "markdown",
271 | "metadata": {},
272 | "source": [
273 | "We'll come back to the Cube Element later for now we will slice this cube up manually. By taking slices along latitude and longitude we can slice the data up into 2D chunks and wrap them in ``GeoImage`` a subclass of ``Cube`` which can be visualized. We place these object into a HoloMap with the remaining dimensions ``time`` and ``realization`` as key dimensions."
274 | ]
275 | },
276 | {
277 | "cell_type": "code",
278 | "execution_count": null,
279 | "metadata": {
280 | "collapsed": false
281 | },
282 | "outputs": [],
283 | "source": [
284 | "kdims = ['time', 'realization']\n",
285 | "img_hmap = hv.HoloMap(kdims=kdims)\n",
286 | "for cb in surface_temp.slices(['longitude', 'latitude']):\n",
287 | " key = tuple(cb.coord(kd).points[0] for kd in kdims)\n",
288 | " img_hmap[key] = hc.Image(cb)"
289 | ]
290 | },
291 | {
292 | "cell_type": "markdown",
293 | "metadata": {},
294 | "source": [
295 | "The HoloMap can summarize the contained data:"
296 | ]
297 | },
298 | {
299 | "cell_type": "code",
300 | "execution_count": null,
301 | "metadata": {
302 | "collapsed": false
303 | },
304 | "outputs": [],
305 | "source": [
306 | "img_hmap.info"
307 | ]
308 | },
309 | {
310 | "cell_type": "markdown",
311 | "metadata": {},
312 | "source": [
313 | "A convenient way of accessing a single Element in a HoloMap is the .last attribute. Now that we have a handle on it we can customize it a number of ways using the call method as above or using the options magic:"
314 | ]
315 | },
316 | {
317 | "cell_type": "code",
318 | "execution_count": null,
319 | "metadata": {
320 | "collapsed": false
321 | },
322 | "outputs": [],
323 | "source": [
324 | "%opts Image [colorbar=True projection=crs.Geostationary()] (cmap='viridis')\n",
325 | "img_hmap.last * hc.GeoFeature(cf.COASTLINE)"
326 | ]
327 | },
328 | {
329 | "cell_type": "markdown",
330 | "metadata": {},
331 | "source": [
332 | "## Groupby and conversions"
333 | ]
334 | },
335 | {
336 | "cell_type": "markdown",
337 | "metadata": {},
338 | "source": [
339 | "\n",
340 | "
\n",
341 | " Warning! At this point it's important to point out that a HoloMap renders all available frames by default, therefore we'll set the widgets output to ``live``, which means that updating is linked to the live server. If you want to embed the output into the notebook remove this line or set it to ``embed`` explicitly.\n",
342 | "
"
343 | ]
344 | },
345 | {
346 | "cell_type": "code",
347 | "execution_count": null,
348 | "metadata": {
349 | "collapsed": true
350 | },
351 | "outputs": [],
352 | "source": [
353 | "%output widgets='live' size=300"
354 | ]
355 | },
356 | {
357 | "cell_type": "markdown",
358 | "metadata": {},
359 | "source": [
360 | "Slicing a Cube up in the way we saw before is often very useful but it's also a little bit of effort. To make this easier HoloViews interfaces usually implement a ``groupby`` method. Here we show how to achieve the same thing as above but using ``groupby`` instead. We may add another clearer interface eventually but ``groupby`` will provide the low level API for any such conversion interface.\n",
361 | "\n",
362 | "If we group the cube by realization and time we are left with a bunch of 2D slices of latitude and longitude, by supplying a ``group_type`` we wrap each of the sliced 2D cubes in an Image."
363 | ]
364 | },
365 | {
366 | "cell_type": "code",
367 | "execution_count": null,
368 | "metadata": {
369 | "collapsed": false
370 | },
371 | "outputs": [],
372 | "source": [
373 | "%%opts GeoImage [colorbar=True] (cmap='viridis')\n",
374 | "(cube.groupby(['time', 'realization'], group_type=hc.Image) * hc.GeoFeature(cf.COASTLINE))"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "metadata": {},
380 | "source": [
381 | "As you can see it has automatically converted the cube to an widget allowing you to explore this space. We can repeat the same groupby operation this time with a ``Contours`` Element as the ``group_type``."
382 | ]
383 | },
384 | {
385 | "cell_type": "code",
386 | "execution_count": null,
387 | "metadata": {
388 | "collapsed": false
389 | },
390 | "outputs": [],
391 | "source": [
392 | "%%opts Contours [colorbar=True] (cmap='viridis')\n",
393 | "(cube.groupby(['time', 'realization'], group_type=hc.Contours) * hc.GeoFeature(cf.COASTLINE))"
394 | ]
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "metadata": {},
399 | "source": [
400 | "# Working with non-geographic Element types\n",
401 | "\n",
402 | "iris Cubes can also be used as the data source for regular Element types, however unlike their holocube counterparts they won't know anything about the geographic projections of the data. \n",
403 | "\n",
404 | "As the most basic example we can use the conversion interface to display a widget for each time and realization, but this time we declare just the key dimension of the points object letting the interface infer the value and HoloMap dimensions:"
405 | ]
406 | },
407 | {
408 | "cell_type": "code",
409 | "execution_count": null,
410 | "metadata": {
411 | "collapsed": true
412 | },
413 | "outputs": [],
414 | "source": [
415 | "%%opts Points [color_index=2 size_index=None]\n",
416 | "cube.to.points(['longitude', 'latitude'])"
417 | ]
418 | },
419 | {
420 | "cell_type": "markdown",
421 | "metadata": {},
422 | "source": [
423 | "We can also collapse specific dimensions on the iris Cube first and then view the reduced Cube using regular HoloViews Element types. Here we collapse the longitude and latitude dimensions on the iris Cube by taking the weighted mean, wrap it in a HoloCube and then view the mean surface temperature for each realization and ``overlay`` the curves."
424 | ]
425 | },
426 | {
427 | "cell_type": "code",
428 | "execution_count": null,
429 | "metadata": {
430 | "collapsed": true
431 | },
432 | "outputs": [],
433 | "source": [
434 | "%%opts Curve [aspect=2 xticks=4 ] (linestyle='--') NdOverlay [aspect=2 legend_position='right']\n",
435 | "if cube.data.coord('latitude').bounds is None:\n",
436 | " cube.data.coord('latitude').guess_bounds()\n",
437 | "if cube.data.coord('longitude').bounds is None:\n",
438 | " cube.data.coord('longitude').guess_bounds()\n",
439 | "grid_weights = iris.analysis.cartography.area_weights(cube.data)\n",
440 | "collapsed_cube = cube.data.collapsed(['longitude', 'latitude'], iris.analysis.MEAN, weights=grid_weights)\n",
441 | "hc.HoloCube(collapsed_cube).to.curve(['time']).overlay()"
442 | ]
443 | },
444 | {
445 | "cell_type": "markdown",
446 | "metadata": {},
447 | "source": [
448 | "Similarly we can collapse the forecast period, leaving just latitude and longitude coordinates and then view slices along each longitude:"
449 | ]
450 | },
451 | {
452 | "cell_type": "code",
453 | "execution_count": null,
454 | "metadata": {
455 | "collapsed": true
456 | },
457 | "outputs": [],
458 | "source": [
459 | "%%opts Curve [aspect=3 xticks=10]\n",
460 | "collapsed_cube = cube.data.collapsed('forecast_period', iris.analysis.MEAN, weights=grid_weights)\n",
461 | "hc.HoloCube(collapsed_cube).to.curve(['latitude'])"
462 | ]
463 | },
464 | {
465 | "cell_type": "markdown",
466 | "metadata": {},
467 | "source": [
468 | "This notebook has outlined a basic API to explore cube datasets using HoloViews. While quite a bit works already there are a large number of issues to still be sorted out:\n",
469 | "\n",
470 | "* Dates should be formatted correctly in the slider\n",
471 | "* Decide on the API of the Cube Elements:\n",
472 | " - How should slicing semantics behave?\n",
473 | " - Should aggregation/reduce and sample operations be exposed?\n",
474 | "* Updating of plots currently reinstantiates artist each time, considerable speedup could be achieved if artists could be updated directly. [Existing issue](https://github.com/SciTools/cartopy/issues/233) on cartopy suggests implementing this for pcolormesh already."
475 | ]
476 | }
477 | ],
478 | "metadata": {
479 | "environment": {
480 | "dependencies": [
481 | "appnope=0.1.0=py27_0",
482 | "backports_abc=0.4=py27_0",
483 | "biggus=0.13.0=py27_0",
484 | "cartopy=0.13.1=np110py27_0",
485 | "cf_units=1.1=py27_0",
486 | "curl=7.43.0=1",
487 | "cycler=0.9.0=py27_0",
488 | "decorator=4.0.6=py27_0",
489 | "ecmwf_grib=1.14.2=np110py27_0",
490 | "freetype=2.5.5=0",
491 | "funcsigs=0.4=py27_0",
492 | "geos=3.4.2=4",
493 | "hdf5=1.8.15.1=2",
494 | "ipykernel=4.2.2=py27_0",
495 | "ipython=4.1.1=py27_0",
496 | "ipython_genutils=0.1.0=py27_0",
497 | "ipywidgets=4.1.1=py27_0",
498 | "jasper=1.900.1=5",
499 | "jbig=2.1=0",
500 | "jinja2=2.8=py27_0",
501 | "jpeg=8d=1",
502 | "jsonschema=2.4.0=py27_0",
503 | "jupyter=1.0.0=py27_1",
504 | "jupyter_client=4.1.1=py27_0",
505 | "jupyter_console=4.1.0=py27_0",
506 | "jupyter_core=4.0.6=py27_0",
507 | "krb5=1.13.2=0",
508 | "libmo_unpack=3.0=3",
509 | "libnetcdf=4.3.3.1=1",
510 | "libpng=1.6.17=0",
511 | "libtiff=4.0.6=1",
512 | "libxml2=2.9.2=0",
513 | "libxslt=1.1.28=2",
514 | "lxml=3.5.0=py27_0",
515 | "markupsafe=0.23=py27_0",
516 | "matplotlib=1.5.1=np110py27_0",
517 | "mistune=0.7.1=py27_0",
518 | "mkl=11.3.1=0",
519 | "mock=1.3.0=py27_0",
520 | "nbconvert=4.1.0=py27_0",
521 | "nbformat=4.0.1=py27_0",
522 | "netcdf4=1.2.2=np110py27_0",
523 | "nose=1.3.7=py27_0",
524 | "notebook=4.1.0=py27_0",
525 | "numpy=1.10.4=py27_0",
526 | "openssl=1.0.1k=1",
527 | "owslib=0.10.3=py27_0",
528 | "path.py=8.1.2=py27_1",
529 | "pbr=1.3.0=py27_0",
530 | "pexpect=3.3=py27_0",
531 | "pickleshare=0.5=py27_0",
532 | "pillow=3.1.1=py27_0",
533 | "pip=8.0.2=py27_0",
534 | "proj.4=4.9.1=1",
535 | "ptyprocess=0.5=py27_0",
536 | "pyepsg=0.2.0=py27_0",
537 | "pygments=2.1=py27_0",
538 | "pyke=1.1.1=py27_2",
539 | "pyparsing=2.0.3=py27_0",
540 | "pyproj=1.9.4=py27_1",
541 | "pyqt=4.11.4=py27_1",
542 | "pyshp=1.2.3=py27_0",
543 | "python=2.7.10=1",
544 | "python-dateutil=2.4.2=py27_0",
545 | "python.app=1.2=py27_4",
546 | "pytz=2015.7=py27_0",
547 | "pyzmq=15.2.0=py27_0",
548 | "qt=4.8.7=1",
549 | "qtconsole=4.1.1=py27_0",
550 | "readline=6.2=2",
551 | "requests=2.9.1=py27_0",
552 | "scipy=0.17.0=np110py27_0",
553 | "setuptools=19.6.2=py27_0",
554 | "shapely=1.5.13=np110py27_1",
555 | "simplegeneric=0.8.1=py27_0",
556 | "singledispatch=3.4.0.3=py27_0",
557 | "sip=4.16.9=py27_0",
558 | "six=1.10.0=py27_0",
559 | "sqlite=3.9.2=0",
560 | "ssl_match_hostname=3.4.0.2=py27_0",
561 | "terminado=0.5=py27_1",
562 | "tk=8.5.18=0",
563 | "tornado=4.3=py27_0",
564 | "traitlets=4.1.0=py27_0",
565 | "udunits2=2.2.20=0",
566 | "wheel=0.29.0=py27_0",
567 | "xz=5.0.5=1",
568 | "zlib=1.2.8=0",
569 | {
570 | "pip": [
571 | "backports-abc==0.4",
572 | "backports.ssl-match-hostname==3.4.0.2",
573 | "cf-units==1.1",
574 | "holoviews (/Users/prudiger/topographica/external/holoviews)==1.4.3",
575 | "ipython-genutils==0.1.0",
576 | "iris (/Users/prudiger/iris/lib)==1.10.0.dev0",
577 | "jupyter-client==4.1.1",
578 | "jupyter-console==4.1.0",
579 | "jupyter-core==4.0.6",
580 | "param==1.3.2"
581 | ]
582 | }
583 | ],
584 | "name": "metoffice"
585 | },
586 | "hide_input": false,
587 | "kernelspec": {
588 | "display_name": "Python 2",
589 | "language": "python",
590 | "name": "python2"
591 | },
592 | "language_info": {
593 | "codemirror_mode": {
594 | "name": "ipython",
595 | "version": 2
596 | },
597 | "file_extension": ".py",
598 | "mimetype": "text/x-python",
599 | "name": "python",
600 | "nbconvert_exporter": "python",
601 | "pygments_lexer": "ipython2",
602 | "version": "2.7.11"
603 | }
604 | },
605 | "nbformat": 4,
606 | "nbformat_minor": 0
607 | }
608 |
--------------------------------------------------------------------------------
/notebooks/Dashboard_Demo.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {
7 | "collapsed": false,
8 | "urth": {
9 | "dashboard": {
10 | "hidden": true
11 | }
12 | }
13 | },
14 | "outputs": [],
15 | "source": [
16 | "import iris\n",
17 | "import param\n",
18 | "import numpy as np\n",
19 | "import holoviews as hv\n",
20 | "import holocube as hc\n",
21 | "\n",
22 | "from cartopy import feature as cf\n",
23 | "from paramnb import NbParams, FileSelector\n",
24 | "\n",
25 | "hv.notebook_extension(width=90)\n",
26 | "%output widgets='live' size=400\n",
27 | "%opts Image {+framewise} [colorbar=True] Contours {+framewise}"
28 | ]
29 | },
30 | {
31 | "cell_type": "markdown",
32 | "metadata": {
33 | "urth": {
34 | "dashboard": {
35 | "hidden": true
36 | }
37 | }
38 | },
39 | "source": [
40 | "Declaring a set of widgets using paramNB is as simply as declaring a Parameterized class. Here we declare a ``CubeLoader`` class with a ``FileSelector`` parameter, a cache and a load method, which will load cubes from a file on disk. The ``NbParams`` function accepts the ``CubeLoader`` instance and generates a dropdown widget for the ``cube_path`` from it. By declaring a callback we can tell it to load the file from disk (or the cache) whenever the user executes the widget. Finally we can declare the ``execute`` mode, which allows declaring no execution with ``execute=None`` or adds a button to execute the 'next' cell or all cells 'below'."
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": null,
46 | "metadata": {
47 | "collapsed": false,
48 | "urth": {
49 | "dashboard": {
50 | "layout": {
51 | "col": 0,
52 | "height": 4,
53 | "row": 0,
54 | "width": 4
55 | }
56 | }
57 | }
58 | },
59 | "outputs": [],
60 | "source": [
61 | "class CubeLoader(param.Parameterized):\n",
62 | " \n",
63 | " cube_path = FileSelector(default='files/*.pp')\n",
64 | "\n",
65 | " cache = {}\n",
66 | " cubes = None\n",
67 | " \n",
68 | " @classmethod\n",
69 | " def load(cls, cube_loader):\n",
70 | " if cls.cube_path not in cls.cache:\n",
71 | " cubelist = iris.load(cls.cube_path)\n",
72 | " for c in cubelist:\n",
73 | " c.coord('grid_longitude').guess_bounds()\n",
74 | " c.coord('grid_latitude').guess_bounds()\n",
75 | " cls.cache[cls.cube_path] = cubelist\n",
76 | " else:\n",
77 | " cubelist = cls.cache[cls.cube_path]\n",
78 | " cubes = {cb.vdims[0].name:cb for cb in [hc.HoloCube(c) for c in cubelist]} # Load cubes into dictionary\n",
79 | " cls.cubes = {k:v for k,v in cubes.items() if k!='unknown'} # Filter as desired\n",
80 | "\n",
81 | "NbParams(CubeLoader, execute='next', callback=CubeLoader.load)"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": null,
87 | "metadata": {
88 | "collapsed": false,
89 | "urth": {
90 | "dashboard": {
91 | "layout": {
92 | "col": 4,
93 | "height": 7,
94 | "row": 0,
95 | "width": 3
96 | }
97 | }
98 | }
99 | },
100 | "outputs": [],
101 | "source": [
102 | "from cartopy import crs as ccrs\n",
103 | "from matplotlib.cm import cmap_d\n",
104 | "\n",
105 | "projections = {k: crs for k, crs in param.concrete_descendents(ccrs.CRS).items()\n",
106 | " if hasattr(crs, '_as_mpl_axes') and not k[0] == '_'}\n",
107 | "\n",
108 | "class CubeBrowser(param.Parameterized):\n",
109 | " \"\"\"\n",
110 | " CubeBrowser defines a small example GUI to demonstrate\n",
111 | " how to define a small set of widgets to control plotting\n",
112 | " of an iris Cube. It exposes control over the colormap,\n",
113 | " the quantity to be plotted, the element to plot it with\n",
114 | " and the projection to plot it on.\n",
115 | " \"\"\"\n",
116 | "\n",
117 | " cmap = param.ObjectSelector(default='viridis',\n",
118 | " objects=list(cmap_d.keys()))\n",
119 | "\n",
120 | " quantity = param.ObjectSelector(default=CubeLoader.cubes.keys()[0],\n",
121 | " objects=list(CubeLoader.cubes.keys()))\n",
122 | "\n",
123 | " element = param.ObjectSelector(default=hc.Image,\n",
124 | " objects=[hc.Image, hc.Contours])\n",
125 | "\n",
126 | " projection = param.ObjectSelector(default='default',\n",
127 | " objects=['default']+sorted(projections.keys()))\n",
128 | " \n",
129 | " cache = {}\n",
130 | "\n",
131 | " @classmethod\n",
132 | " def view(cls):\n",
133 | " key = (cls.quantity, cls.element)\n",
134 | " if key in CubeBrowser.cache:\n",
135 | " converted = cls.cache[key]\n",
136 | " else:\n",
137 | " holocube = CubeLoader.cubes[cls.quantity]\n",
138 | " converted = holocube.to(cls.element, ['grid_longitude', 'grid_latitude'], dynamic=True)\n",
139 | " cls.cache[key] = converted\n",
140 | " styled = converted(style={cls.element.name: dict(cmap=cls.cmap)})\n",
141 | " projection = projections[cls.projection]() if cls.projection != 'default' else None\n",
142 | " projected = styled({'Image': dict(plot=dict(projection=projection))}) if projection else styled\n",
143 | " return (projected * hc.GeoFeature(cf.COASTLINE)(plot=dict(scale='50m')))\n",
144 | "\n",
145 | "NbParams(CubeBrowser, execute='next')"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": null,
151 | "metadata": {
152 | "collapsed": false,
153 | "urth": {
154 | "dashboard": {
155 | "layout": {
156 | "col": 0,
157 | "height": 36,
158 | "row": 7,
159 | "width": 12
160 | }
161 | }
162 | }
163 | },
164 | "outputs": [],
165 | "source": [
166 | "# Finally we can declare a cell which uses the settings defined via the widgets to render the requested plot.\n",
167 | "# We simply look up the correct cube, convert it to the desired Element type and then display it with the requested options.\n",
168 | "CubeBrowser.view()"
169 | ]
170 | }
171 | ],
172 | "metadata": {
173 | "environment": {
174 | "dependencies": [
175 | "appnope=0.1.0=py27_0",
176 | "backports=1.0=py27_0",
177 | "backports_abc=0.4=py27_0",
178 | "biggus=0.13.0=py27_0",
179 | "cartopy=0.13.1=np110py27_0",
180 | "cf_units=1.1=py27_0",
181 | "configparser=3.5.0b2=py27_1",
182 | "curl=7.45.0=0",
183 | "cycler=0.10.0=py27_0",
184 | "cython=0.24=py27_0",
185 | "decorator=4.0.9=py27_0",
186 | "ecmwf_grib=1.14.2=np110py27_0",
187 | "entrypoints=0.2=py27_1",
188 | "freetype=2.5.5=0",
189 | "funcsigs=1.0.0=py27_0",
190 | "geos=3.4.2=4",
191 | "hdf5=1.8.15.1=2",
192 | "ipykernel=4.3.1=py27_0",
193 | "ipython=4.1.2=py27_2",
194 | "ipython_genutils=0.1.0=py27_0",
195 | "ipywidgets=4.1.1=py27_0",
196 | "iris=1.9.2=np110_0",
197 | "jasper=1.900.1=5",
198 | "jbig=2.1=0",
199 | "jinja2=2.8=py27_0",
200 | "jpeg=8d=1",
201 | "jsonschema=2.4.0=py27_0",
202 | "jupyter=1.0.0=py27_2",
203 | "jupyter_client=4.2.2=py27_0",
204 | "jupyter_console=4.1.1=py27_0",
205 | "jupyter_core=4.1.0=py27_0",
206 | "libmo_unpack=3.0=3",
207 | "libnetcdf=4.3.3.1=3",
208 | "libpng=1.6.17=0",
209 | "libtiff=4.0.6=1",
210 | "libxml2=2.9.2=0",
211 | "libxslt=1.1.28=2",
212 | "lxml=3.6.0=py27_0",
213 | "markupsafe=0.23=py27_0",
214 | "matplotlib=1.5.1=np110py27_0",
215 | "mistune=0.7.2=py27_1",
216 | "mkl=11.3.1=0",
217 | "mo_pack=0.2.0=np110py27_1",
218 | "mock=2.0.0=py27_0",
219 | "nbconvert=4.2.0=py27_0",
220 | "nbformat=4.0.1=py27_0",
221 | "netcdf4=1.2.2=np110py27_0",
222 | "nose=1.3.7=py27_0",
223 | "notebook=4.1.0=py27_2",
224 | "numpy=1.10.4=py27_0",
225 | "openssl=1.0.2g=0",
226 | "owslib=0.10.3=py27_0",
227 | "pandas=0.18.0=np111py27_0",
228 | "path.py=8.2=py27_0",
229 | "pbr=1.8.0=py27_0",
230 | "pexpect=4.0.1=py27_0",
231 | "pickleshare=0.5=py27_0",
232 | "pillow=3.2.0=py27_0",
233 | "pip=8.1.1=py27_1",
234 | "proj.4=4.9.1=1",
235 | "ptyprocess=0.5=py27_0",
236 | "pyepsg=0.2.0=py27_0",
237 | "pygments=2.1.3=py27_0",
238 | "pyke=1.1.1=py27_2",
239 | "pyparsing=2.0.3=py27_0",
240 | "pyproj=1.9.4=py27_1",
241 | "pyqt=4.11.4=py27_1",
242 | "pyshp=1.2.3=py27_0",
243 | "python=2.7.11=0",
244 | "python-dateutil=2.5.2=py27_0",
245 | "pytz=2016.3=py27_0",
246 | "pyzmq=15.2.0=py27_0",
247 | "qt=4.8.7=1",
248 | "qtconsole=4.2.1=py27_0",
249 | "readline=6.2=2",
250 | "requests=2.9.1=py27_0",
251 | "scipy=0.17.0=np110py27_0",
252 | "setuptools=20.6.7=py27_0",
253 | "shapely=1.5.13=np110py27_1",
254 | "simplegeneric=0.8.1=py27_0",
255 | "singledispatch=3.4.0.3=py27_0",
256 | "sip=4.16.9=py27_0",
257 | "six=1.10.0=py27_0",
258 | "sqlite=3.9.2=0",
259 | "ssl_match_hostname=3.4.0.2=py27_1",
260 | "terminado=0.5=py27_1",
261 | "tk=8.5.18=0",
262 | "tornado=4.3=py27_0",
263 | "traitlets=4.2.1=py27_0",
264 | "udunits2=2.2.20=0",
265 | "wheel=0.29.0=py27_0",
266 | "xz=5.0.5=1",
267 | "zlib=1.2.8=0",
268 | {
269 | "pip": [
270 | "backports-abc==0.4",
271 | "backports.ssl-match-hostname==3.4.0.2",
272 | "cartopy (/Users/philippjfr/cartopy/lib)==0.14.0",
273 | "cf-units==1.1",
274 | "holocube (/Users/philippjfr/cube-explorer)==0.0.1",
275 | "holoviews==1.4.3",
276 | "ipython-genutils==0.1.0",
277 | "iris (/Users/philippjfr/iris/lib)==1.10.0.dev0",
278 | "jupyter-client==4.2.2",
279 | "jupyter-console==4.1.1",
280 | "jupyter-core==4.1.0",
281 | "lancet-ioam==0.9.0",
282 | "mo-pack==0.2.0",
283 | "param==1.3.2",
284 | "paramnb (/Users/philippjfr/paramnb)==0.0.1"
285 | ]
286 | }
287 | ],
288 | "name": "metoffice"
289 | },
290 | "hide_input": false,
291 | "kernelspec": {
292 | "display_name": "Python 2",
293 | "language": "python",
294 | "name": "python2"
295 | },
296 | "language_info": {
297 | "codemirror_mode": {
298 | "name": "ipython",
299 | "version": 2
300 | },
301 | "file_extension": ".py",
302 | "mimetype": "text/x-python",
303 | "name": "python",
304 | "nbconvert_exporter": "python",
305 | "pygments_lexer": "ipython2",
306 | "version": "2.7.11"
307 | },
308 | "urth": {
309 | "dashboard": {
310 | "cellMargin": 10,
311 | "defaultCellHeight": 20,
312 | "layoutStrategy": "packed",
313 | "maxColumns": 12
314 | }
315 | }
316 | },
317 | "nbformat": 4,
318 | "nbformat_minor": 0
319 | }
320 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | try:
5 | from setuptools import setup
6 | except ImportError:
7 | from distutils.core import setup
8 |
9 |
10 | setup_args = {}
11 | install_requires = ['param>=1.3.2', 'numpy>=1.0', 'holoviews>=1.4.3']
12 | extras_require={}
13 |
14 | # Notebook dependencies of IPython
15 | extras_require['notebook-dependencies'] = ['jupyter', 'pyzmq', 'jinja2', 'tornado',
16 | 'jsonschema', 'ipython', 'pygments']
17 |
18 | setup_args.update(dict(
19 | name='holocube',
20 | version="0.0.1",
21 | install_requires = install_requires,
22 | extras_require = extras_require,
23 | description='HoloCube.',
24 | long_description=open('README.md').read() if os.path.isfile('README.md') else 'Consult README.md',
25 | platforms=['Windows', 'Mac OS X', 'Linux'],
26 | license='BSD',
27 | url='https://github.com/CubeBrowser/cube-explorer',
28 | packages = ["holocube",
29 | "holocube.element",
30 | "holocube.plotting"],
31 | classifiers = [
32 | "License :: OSI Approved :: BSD License",
33 | "Development Status :: 1 - Planning Development Status",
34 | "Programming Language :: Python :: 2.7",
35 | "Programming Language :: Python :: 3.3",
36 | "Programming Language :: Python :: 3.4",
37 | "Operating System :: OS Independent",
38 | "Intended Audience :: Science/Research",
39 | "Intended Audience :: Developers",
40 | "Natural Language :: English",
41 | "Topic :: Scientific/Engineering",
42 | "Topic :: Software Development :: Libraries"]
43 | ))
44 |
45 | if __name__=="__main__":
46 | setup(**setup_args)
47 |
--------------------------------------------------------------------------------
/tests/test_cube.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from holocube.element.cube import HoloCube
3 | from holocube.element.util import coord_to_dimension
4 | from iris.tests.stock import lat_lon_cube
5 | from holoviews.element.comparison import ComparisonTestCase
6 |
7 |
8 | class TestCube(ComparisonTestCase):
9 |
10 | def setUp(self):
11 | self.cube = lat_lon_cube()
12 |
13 | def test_dim_to_coord(self):
14 | dim = coord_to_dimension(self.cube.coords()[0])
15 | self.assertEqual(dim.name, 'latitude')
16 | self.assertEqual(dim.unit, 'degrees')
17 |
18 | def test_initialize_cube(self):
19 | cube = HoloCube(self.cube)
20 | self.assertEqual(cube.dimensions(label=True),
21 | ['longitude', 'latitude', 'unknown'])
22 |
23 | def test_initialize_cube_with_kdims(self):
24 | cube = HoloCube(self.cube, kdims=['longitude', 'latitude'])
25 | self.assertEqual(cube.dimensions('key', True),
26 | ['longitude', 'latitude'])
27 |
28 | def test_initialize_cube_with_vdims(self):
29 | cube = HoloCube(self.cube, vdims=['Quantity'])
30 | self.assertEqual(cube.dimensions('value', True),
31 | ['Quantity'])
32 |
33 | def test_dimension_values_kdim_expanded(self):
34 | cube = HoloCube(self.cube, kdims=['longitude', 'latitude'])
35 | self.assertEqual(cube.dimension_values('longitude'),
36 | np.array([-1, -1, -1, 0, 0, 0,
37 | 1, 1, 1, 2, 2, 2], dtype=np.int32))
38 |
39 | def test_dimension_values_kdim(self):
40 | cube = HoloCube(self.cube, kdims=['longitude', 'latitude'])
41 | self.assertEqual(cube.dimension_values('longitude', expanded=False),
42 | np.array([-1, 0, 1, 2], dtype=np.int32))
43 |
44 | def test_dimension_values_vdim(self):
45 | cube = HoloCube(self.cube, kdims=['longitude', 'latitude'])
46 | self.assertEqual(cube.dimension_values('unknown', flat=False),
47 | np.array([[ 0, 4, 8],
48 | [ 1, 5, 9],
49 | [ 2, 6, 10],
50 | [ 3, 7, 11]], dtype=np.int32))
51 |
52 | def test_range_kdim(self):
53 | cube = HoloCube(self.cube, kdims=['longitude', 'latitude'])
54 | self.assertEqual(cube.range('longitude'), (-1, 2))
55 |
56 | def test_range_vdim(self):
57 | cube = HoloCube(self.cube, kdims=['longitude', 'latitude'])
58 | self.assertEqual(cube.range('unknown'), (0, 11))
59 |
--------------------------------------------------------------------------------