Skip to content

Commit

Permalink
New C++ contour code with corner_mask
Browse files Browse the repository at this point in the history
  • Loading branch information
ianthomas23 committed Feb 19, 2015
1 parent 1fe5b9c commit 27154d3
Show file tree
Hide file tree
Showing 19 changed files with 12,589 additions and 1,215 deletions.
16 changes: 10 additions & 6 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
2015-02-19 Rewrite of C++ code that calculates contours to add support for
corner masking. This is controlled by the 'corner_mask' keyword
in plotting commands 'contour' and 'contourf'. - IMT

2015-01-23 Text bounding boxes are now computed with advance width rather than
ink area. This may result in slightly different placement of text.
ink area. This may result in slightly different placement of text.

2014-10-27 Allowed selection of the backend using the `MPLBACKEND` environment
variable. Added documentation on backend selection methods.

2014-09-27 Overhauled `colors.LightSource`. Added `LightSource.hillshade` to
allow the independent generation of illumination maps. Added new
types of blending for creating more visually appealing shaded relief
plots (e.g. `blend_mode="overlay"`, etc, in addition to the legacy
"hsv" mode).
allow the independent generation of illumination maps. Added new
types of blending for creating more visually appealing shaded relief
plots (e.g. `blend_mode="overlay"`, etc, in addition to the legacy
"hsv" mode).

2014-06-10 Added Colorbar.remove()

Expand Down Expand Up @@ -64,7 +68,7 @@
of a generator at line 263.

2014-04-02 Added `clipon=False` to patch creation of wedges and shadows
in `pie`.
in `pie`.

2014-02-25 In backend_qt4agg changed from using update -> repaint under
windows. See comment in source near `self._priv_update` for
Expand Down
25 changes: 25 additions & 0 deletions doc/api/api_changes/2015-02-19-IMT.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Changed behaviour of contour plots
``````````````````````````````````

The default behaviour of :func:`~matplotlib.pyplot.contour` and
:func:`~matplotlib.pyplot.contourf` when using a masked array is now determined
by the new keyword argument `corner_mask`, or if this is not specified then
the new rcParam `contour.corner_mask` instead. The new default behaviour is
equivalent to using `corner_mask=True`; the previous behaviour can be obtained
using `corner_mask=False` or by changing the rcParam. The example
http://matplotlib.org/examples/pylab_examples/contour_corner_mask.py
demonstrates the difference. Use of the old contouring algorithm, which is
obtained with `corner_mask='legacy'`, is now deprecated.

Contour labels may now appear in different places than in earlier versions of
matplotlib.

In addition, the keyword argument `nchunk` now applies to
:func:`~matplotlib.pyplot.contour` as well as
:func:`~matplotlib.pyplot.contourf`, and it subdivides the domain into
subdomains of exactly `nchunk` by `nchunk` quads, whereas previously it was
only roughly `nchunk` by `nchunk` quads.

The C/C++ object that performs contour calculations used to be stored in the
public attribute QuadContourSet.Cntr, but is now stored in a private attribute
and should not be accessed by end users.
16 changes: 16 additions & 0 deletions doc/users/whats_new/plotting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,19 @@ Added center and frame kwargs to pie

These control where the center of the pie graph are and if
the Axes frame is shown.

Contour plot corner masking
```````````````````````````

Ian Thomas rewrote the C++ code that calculates contours to add support for
corner masking. This is controlled by a new keyword argument
``corner_mask`` in the functions :func:`~matplotlib.pyplot.contour` and
:func:`~matplotlib.pyplot.contourf`. The previous behaviour, which is now
obtained using ``corner_mask=False``, was for a single masked point to
completely mask out all four quads touching that point. The new behaviour,
obtained using ``corner_mask=True``, only masks the corners of those
quads touching the point; any triangular corners comprising three unmasked
points are contoured as usual. If the ``corner_mask`` keyword argument is not
specified, the default value is taken from rcParams.

.. plot:: mpl_examples/pylab_examples/contour_corner_mask.py
35 changes: 35 additions & 0 deletions examples/pylab_examples/contour_corner_mask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python
"""
Illustrate the difference between corner_mask=False and corner_mask=True
for masked contour plots.
"""
import matplotlib.pyplot as plt
import numpy as np

# Data to plot.
x, y = np.meshgrid(np.arange(7), np.arange(10))
z = np.sin(0.5*x)*np.cos(0.52*y)

# Mask various z values.
mask = np.zeros_like(z, dtype=np.bool)
mask[2, 3:5] = True
mask[3:5, 4] = True
mask[7, 2] = True
mask[5, 0] = True
mask[0, 6] = True
z = np.ma.array(z, mask=mask)

corner_masks = [False, True]
for i, corner_mask in enumerate(corner_masks):
plt.subplot(1, 2, i+1)
cs = plt.contourf(x, y, z, corner_mask=corner_mask)
plt.contour(cs, colors='k')
plt.title('corner_mask = {}'.format(corner_mask))

# Plot grid.
plt.grid(c='k', ls='-', alpha=0.3)

# Indicate masked points with red circles.
plt.plot(np.ma.array(x, mask=~mask), y, 'ro')

plt.show()
95 changes: 71 additions & 24 deletions lib/matplotlib/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import numpy as np
from numpy import ma
import matplotlib._cntr as _cntr
import matplotlib._contour as _contour
import matplotlib.path as mpath
import matplotlib.ticker as ticker
import matplotlib.cm as cm
Expand Down Expand Up @@ -989,9 +990,10 @@ def get_transform(self):

def __getstate__(self):
state = self.__dict__.copy()
# the C object Cntr cannot currently be pickled. This isn't a big issue
# as it is not actually used once the contour has been calculated
state['Cntr'] = None
# the C object _contour_generator cannot currently be pickled. This
# isn't a big issue as it is not actually used once the contour has
# been calculated.
state['_contour_generator'] = None
return state

def legend_elements(self, variable_name='x', str_format=str):
Expand Down Expand Up @@ -1433,18 +1435,34 @@ def _process_args(self, *args, **kwargs):
Process args and kwargs.
"""
if isinstance(args[0], QuadContourSet):
C = args[0].Cntr
if self.levels is None:
self.levels = args[0].levels
self.zmin = args[0].zmin
self.zmax = args[0].zmax
self._corner_mask = args[0]._corner_mask
if self._corner_mask == 'legacy':
contour_generator = args[0].Cntr
else:
contour_generator = args[0]._contour_generator
else:
x, y, z = self._contour_args(args, kwargs)

_mask = ma.getmask(z)
if _mask is ma.nomask:
if _mask is ma.nomask or not _mask.any():
_mask = None
C = _cntr.Cntr(x, y, z.filled(), _mask)

self._corner_mask = kwargs.get('corner_mask', None)
if self._corner_mask is None:
self._corner_mask = mpl.rcParams['contour.corner_mask']

if self._corner_mask == 'legacy':
cbook.warn_deprecated('1.5',
name="corner_mask='legacy'",
alternative='corner_mask=False or True')
contour_generator = _cntr.Cntr(x, y, z.filled(), _mask)
else:
contour_generator = _contour.QuadContourGenerator(
x, y, z.filled(), _mask, self._corner_mask, self.nchunk)

t = self.get_transform()

Expand All @@ -1465,7 +1483,10 @@ def _process_args(self, *args, **kwargs):
self.ax.update_datalim([(x0, y0), (x1, y1)])
self.ax.autoscale_view(tight=True)

self.Cntr = C
if self._corner_mask == 'legacy':
self.Cntr = contour_generator
else:
self._contour_generator = contour_generator

def _get_allsegs_and_allkinds(self):
"""
Expand All @@ -1476,20 +1497,28 @@ def _get_allsegs_and_allkinds(self):
lowers, uppers = self._get_lowers_and_uppers()
allkinds = []
for level, level_upper in zip(lowers, uppers):
nlist = self.Cntr.trace(level, level_upper,
nchunk=self.nchunk)
nseg = len(nlist) // 2
segs = nlist[:nseg]
kinds = nlist[nseg:]
allsegs.append(segs)
if self._corner_mask == 'legacy':
nlist = self.Cntr.trace(level, level_upper,
nchunk=self.nchunk)
nseg = len(nlist) // 2
vertices = nlist[:nseg]
kinds = nlist[nseg:]
else:
vertices, kinds = \
self._contour_generator.create_filled_contour(
level, level_upper)
allsegs.append(vertices)
allkinds.append(kinds)
else:
allkinds = None
for level in self.levels:
nlist = self.Cntr.trace(level)
nseg = len(nlist) // 2
segs = nlist[:nseg]
allsegs.append(segs)
if self._corner_mask == 'legacy':
nlist = self.Cntr.trace(level)
nseg = len(nlist) // 2
vertices = nlist[:nseg]
else:
vertices = self._contour_generator.create_contour(level)
allsegs.append(vertices)
return allsegs, allkinds

def _contour_args(self, args, kwargs):
Expand Down Expand Up @@ -1672,6 +1701,20 @@ def _initialize_x_y(self, z):
Optional keyword arguments:
*corner_mask*: [ *True* | *False* | 'legacy' ]
Enable/disable corner masking, which only has an effect if *Z* is
a masked array. If *False*, any quad touching a masked point is
masked out. If *True*, only the triangular corners of quads
nearest those points are always masked out, other triangular
corners comprising three unmasked points are contoured as usual.
If 'legacy', the old contouring algorithm is used, which is
equivalent to *False* and is deprecated, only remaining whilst the
new algorithm is tested fully.
If not specified, the default is taken from
rcParams['contour.corner_mask'], which is True unless it has
been modified.
*colors*: [ *None* | string | (mpl_colors) ]
If *None*, the colormap specified by cmap will be used.
Expand Down Expand Up @@ -1750,6 +1793,15 @@ def _initialize_x_y(self, z):
filled contours, the default is *True*. For line contours,
it is taken from rcParams['lines.antialiased'].
*nchunk*: [ 0 | integer ]
If 0, no subdivision of the domain. Specify a positive integer to
divide the domain into subdomains of *nchunk* by *nchunk* quads.
Chunking reduces the maximum length of polygons generated by the
contouring algorithm which reduces the rendering workload passed
on to the backend and also requires slightly less RAM. It can
however introduce rendering artifacts at chunk boundaries depending
on the backend, the *antialiased* flag and value of *alpha*.
contour-only keyword arguments:
*linewidths*: [ *None* | number | tuple of numbers ]
Expand All @@ -1774,13 +1826,6 @@ def _initialize_x_y(self, z):
contourf-only keyword arguments:
*nchunk*: [ 0 | integer ]
If 0, no subdivision of the domain. Specify a positive integer to
divide the domain into subdomains of roughly *nchunk* by *nchunk*
points. This may never actually be advantageous, so this option may
be removed. Chunking introduces artifacts at the chunk boundaries
unless *antialiased* is *False*.
*hatches*:
A list of cross hatch patterns to use on the filled areas.
If None, no hatching will be added to the contour.
Expand All @@ -1802,4 +1847,6 @@ def _initialize_x_y(self, z):
.. plot:: mpl_examples/pylab_examples/contour_demo.py
.. plot:: mpl_examples/pylab_examples/contourf_demo.py
.. plot:: mpl_examples/pylab_examples/contour_corner_mask.py
"""
9 changes: 9 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,13 @@ def validate_negative_linestyle_legacy(s):
return (0, dashes) # (offset, (solid, blank))


def validate_corner_mask(s):
if s == 'legacy':
return s
else:
return validate_bool(s)


def validate_tkpythoninspect(s):
# Introduced 2010/07/05
warnings.warn("tk.pythoninspect is obsolete, and has no effect")
Expand Down Expand Up @@ -589,8 +596,10 @@ def __call__(self, s):
'image.origin': ['upper', six.text_type], # lookup table
'image.resample': [False, validate_bool],

# contour props
'contour.negative_linestyle': ['dashed',
validate_negative_linestyle_legacy],
'contour.corner_mask': [True, validate_corner_mask],

# axes props
'axes.axisbelow': [False, validate_bool],
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 27154d3

Please sign in to comment.