Skip to content

Commit

Permalink
Optimize VectorField min distance calculation (#2314)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Feb 9, 2018
1 parent 2dd184e commit 96bfff3
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 23 deletions.
3 changes: 2 additions & 1 deletion holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,18 @@ class VectorFieldPlot(ColorbarPlot):
def _get_lengths(self, element, ranges):
mag_dim = element.get_dimension(self.size_index)
(x0, x1), (y0, y1) = (element.range(i) for i in range(2))
base_dist = get_min_distance(element)
if mag_dim:
magnitudes = element.dimension_values(mag_dim)
_, max_magnitude = ranges[mag_dim.name]
if self.normalize_lengths and max_magnitude != 0:
magnitudes = magnitudes / max_magnitude
if self.rescale_lengths:
base_dist = get_min_distance(element)
magnitudes *= base_dist
else:
magnitudes = np.ones(len(element))
if self.rescale_lengths:
base_dist = get_min_distance(element)
magnitudes *= base_dist

return magnitudes
Expand Down
13 changes: 2 additions & 11 deletions holoviews/plotting/mpl/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,16 +603,6 @@ class VectorFieldPlot(ColorbarPlot):

_plot_methods = dict(single='quiver')

def __init__(self, *args, **params):
super(VectorFieldPlot, self).__init__(*args, **params)
self._min_dist = self._get_map_info(self.hmap)


def _get_map_info(self, vmap):
"""
Get the minimum sample distance and maximum magnitude
"""
return np.min([get_min_distance(vfield) for vfield in vmap])


def get_data(self, element, ranges, style):
Expand All @@ -625,7 +615,8 @@ def get_data(self, element, ranges, style):
if self.invert_axes: radians = radians+1.5*np.pi
angles = list(np.rad2deg(radians))
if self.rescale_lengths:
input_scale = input_scale / self._min_dist
min_dist = get_min_distance(element)
input_scale = input_scale / min_dist

mag_dim = element.get_dimension(self.size_index)
if mag_dim:
Expand Down
31 changes: 23 additions & 8 deletions holoviews/plotting/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import unicode_literals, absolute_import

from collections import defaultdict
import traceback
import warnings

import numpy as np
import param
Expand Down Expand Up @@ -549,19 +551,32 @@ def traverse_setter(obj, attribute, value):
obj.traverse(lambda x: setattr(x, attribute, value))


def _get_min_distance_numpy(element):
"""
NumPy based implementation of get_min_distance
"""
xys = element.array([0, 1])
with warnings.catch_warnings():
warnings.filterwarnings('ignore', r'invalid value encountered in')
xys = xys.astype('float32').view(np.complex64)
distances = np.abs(xys.T-xys)
np.fill_diagonal(distances, np.inf)
distances = distances[distances>0]
if len(distances):
return distances.min()
return 0


def get_min_distance(element):
"""
Gets the minimum sampling distance of the x- and y-coordinates
in a grid.
"""
xys = element.array([0, 1]).astype('float64').view(dtype=np.complex128)
m, n = np.meshgrid(xys, xys)
distances = np.abs(m-n)
np.fill_diagonal(distances, np.inf)
distances = distances[distances>0]
if len(distances):
return distances.min()
return 0
try:
from scipy.spatial.distance import pdist
return pdist(element.array([0, 1])).min()
except:
return _get_min_distance_numpy(element)


def rgb2hex(rgb):
Expand Down
21 changes: 18 additions & 3 deletions tests/testplotutils.py → tests/plotting/testplotutils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import absolute_import

from unittest import SkipTest
from nose.plugins.attrib import attr

Expand All @@ -12,7 +14,7 @@
from holoviews.operation import operation
from holoviews.plotting.util import (
compute_overlayable_zorders, get_min_distance, process_cmap,
initialize_dynamic, split_dmap_overlay)
initialize_dynamic, split_dmap_overlay, _get_min_distance_numpy)
from holoviews.streams import PointerX

try:
Expand Down Expand Up @@ -430,13 +432,12 @@ class TestPlotColorUtils(ComparisonTestCase):
def test_process_cmap_mpl(self):
colors = process_cmap('Greys', 3)
self.assertEqual(colors, ['#ffffff', '#959595', '#000000'])


def test_process_cmap_instance_mpl(self):
try:
from matplotlib.cm import get_cmap
except:
raise SkipError("Matplotlib needed to test matplotlib colormap instances")
raise SkipTest("Matplotlib needed to test matplotlib colormap instances")
cmap = get_cmap('Greys')
colors = process_cmap(cmap, 3)
self.assertEqual(colors, ['#ffffff', '#959595', '#000000'])
Expand Down Expand Up @@ -478,6 +479,20 @@ def test_get_min_distance_int32_type(self):
dist = get_min_distance(Points((X.flatten(), Y.flatten())))
self.assertEqual(dist, 1.0)

def test_get_min_distance_float32_type_no_scipy(self):
xs, ys = (np.arange(0, 2., .2, dtype='float32'),
np.arange(0, 2., .2, dtype='float32'))
X, Y = np.meshgrid(xs, ys)
dist = _get_min_distance_numpy(Points((X.flatten(), Y.flatten())))
self.assertEqual(dist, np.float32(0.2))

def test_get_min_distance_int32_type_no_scipy(self):
xs, ys = (np.arange(0, 10, dtype='int32'),
np.arange(0, 10, dtype='int32'))
X, Y = np.meshgrid(xs, ys)
dist = _get_min_distance_numpy(Points((X.flatten(), Y.flatten())))
self.assertEqual(dist, 1.0)


@attr(optional=1) # Flexx is optional
class TestBokehUtils(ComparisonTestCase):
Expand Down

0 comments on commit 96bfff3

Please sign in to comment.