Skip to content

Commit

Permalink
Use converter functions in renderers (#294)
Browse files Browse the repository at this point in the history
* Use converter functions in renderers

* Skip empty chunks
  • Loading branch information
ianthomas23 committed Oct 15, 2023
1 parent 34995b5 commit 408a282
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 123 deletions.
8 changes: 4 additions & 4 deletions lib/contourpy/util/bokeh_util.py
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, cast

from contourpy import FillType, LineType
from contourpy.util.mpl_util import mpl_codes_to_offsets
from contourpy.convert import _offsets_from_codes

if TYPE_CHECKING:
from contourpy._contourpy import (
Expand All @@ -25,7 +25,7 @@ def filled_to_bokeh(
if points is None:
continue
if have_codes:
offsets = mpl_codes_to_offsets(offsets)
offsets = _offsets_from_codes(offsets)
xs.append([]) # New outer with zero or more holes.
ys.append([])
for i in range(len(offsets)-1):
Expand All @@ -39,7 +39,7 @@ def filled_to_bokeh(
for j in range(len(outer_offsets)-1):
if fill_type == FillType.ChunkCombinedCodeOffset:
codes = codes_or_offsets[outer_offsets[j]:outer_offsets[j+1]]
offsets = mpl_codes_to_offsets(codes) + outer_offsets[j]
offsets = _offsets_from_codes(codes) + outer_offsets[j]
else:
offsets = codes_or_offsets[outer_offsets[j]:outer_offsets[j+1]+1]
xs.append([]) # New outer with zero or more holes.
Expand Down Expand Up @@ -78,7 +78,7 @@ def lines_to_bokeh(
if points is None:
continue
if line_type == LineType.ChunkCombinedCode:
offsets = mpl_codes_to_offsets(offsets)
offsets = _offsets_from_codes(offsets)

for i in range(len(offsets)-1):
line = points[offsets[i]:offsets[i+1]]
Expand Down
115 changes: 14 additions & 101 deletions lib/contourpy/util/mpl_renderer.py
Expand Up @@ -8,8 +8,9 @@
import numpy as np

from contourpy import FillType, LineType
from contourpy.convert import convert_fill_type, convert_line_type
from contourpy.enum_util import as_fill_type, as_line_type
from contourpy.util.mpl_util import filled_to_mpl_paths, lines_to_mpl_paths, mpl_codes_to_offsets
from contourpy.util.mpl_util import filled_to_mpl_paths, lines_to_mpl_paths
from contourpy.util.renderer import Renderer

if TYPE_CHECKING:
Expand Down Expand Up @@ -373,100 +374,6 @@ def _arrow(
))
ax.plot(arrow[:, 0], arrow[:, 1], "-", c=color, alpha=alpha)

def _filled_to_lists_of_points_and_offsets(
self,
filled: cpy.FillReturn,
fill_type: FillType,
) -> tuple[list[cpy.PointArray], list[cpy.OffsetArray]]:
if fill_type == FillType.OuterCode:
if TYPE_CHECKING:
filled = cast(cpy.FillReturn_OuterCode, filled)
all_points = filled[0]
all_offsets = [mpl_codes_to_offsets(codes) for codes in filled[1]]
elif fill_type == FillType.ChunkCombinedCode:
if TYPE_CHECKING:
filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
all_points = [points for points in filled[0] if points is not None]
all_offsets = [mpl_codes_to_offsets(codes) for codes in filled[1] if codes is not None]
elif fill_type == FillType.OuterOffset:
if TYPE_CHECKING:
filled = cast(cpy.FillReturn_OuterOffset, filled)
all_points = filled[0]
all_offsets = filled[1]
elif fill_type == FillType.ChunkCombinedOffset:
if TYPE_CHECKING:
filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
all_points = [points for points in filled[0] if points is not None]
all_offsets = [offsets for offsets in filled[1] if offsets is not None]
elif fill_type == FillType.ChunkCombinedCodeOffset:
if TYPE_CHECKING:
filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
all_points = []
all_offsets = []
for points, codes, outer_offsets in zip(*filled):
if points is None:
continue
if TYPE_CHECKING:
assert codes is not None and outer_offsets is not None
all_points += np.split(points, outer_offsets[1:-1])
all_codes = np.split(codes, outer_offsets[1:-1])
all_offsets += [mpl_codes_to_offsets(codes) for codes in all_codes]
elif fill_type == FillType.ChunkCombinedOffsetOffset:
if TYPE_CHECKING:
filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
all_points = []
all_offsets = []
for points, offsets, outer_offsets in zip(*filled):
if points is None:
continue
if TYPE_CHECKING:
assert offsets is not None and outer_offsets is not None
for i in range(len(outer_offsets)-1):
offs = offsets[outer_offsets[i]:outer_offsets[i+1]+1]
all_points.append(points[offs[0]:offs[-1]])
all_offsets.append(offs - offs[0])
else:
raise RuntimeError(f"Rendering FillType {fill_type} not implemented")

return all_points, all_offsets

def _lines_to_list_of_points(
self, lines: cpy.LineReturn, line_type: LineType,
) -> list[cpy.PointArray]:
if line_type == LineType.Separate:
if TYPE_CHECKING:
lines = cast(cpy.LineReturn_Separate, lines)
all_lines = lines
elif line_type == LineType.SeparateCode:
if TYPE_CHECKING:
lines = cast(cpy.LineReturn_SeparateCode, lines)
all_lines = lines[0]
elif line_type == LineType.ChunkCombinedCode:
if TYPE_CHECKING:
lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
all_lines = []
for points, codes in zip(*lines):
if points is not None:
if TYPE_CHECKING:
assert codes is not None
offsets = mpl_codes_to_offsets(codes)
for i in range(len(offsets)-1):
all_lines.append(points[offsets[i]:offsets[i+1]])
elif line_type == LineType.ChunkCombinedOffset:
if TYPE_CHECKING:
lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
all_lines = []
for points, all_offsets in zip(*lines):
if points is not None:
if TYPE_CHECKING:
assert all_offsets is not None
for i in range(len(all_offsets)-1):
all_lines.append(points[all_offsets[i]:all_offsets[i+1]])
else:
raise RuntimeError(f"Rendering LineType {line_type} not implemented")

return all_lines

def filled(
self,
filled: cpy.FillReturn,
Expand All @@ -487,11 +394,13 @@ def filled(
return

ax = self._get_ax(ax)
all_points, all_offsets = self._filled_to_lists_of_points_and_offsets(filled, fill_type)
filled = convert_fill_type(filled, fill_type, FillType.ChunkCombinedOffset)

# Lines.
if line_color is not None:
for points, offsets in zip(all_points, all_offsets):
for points, offsets in zip(*filled):
if points is None:
continue
for start, end in zip(offsets[:-1], offsets[1:]):
xys = points[start:end]
ax.plot(xys[:, 0], xys[:, 1], c=line_color, alpha=line_alpha)
Expand All @@ -503,7 +412,9 @@ def filled(

# Points.
if point_color is not None:
for points, offsets in zip(all_points, all_offsets):
for points, offsets in zip(*filled):
if points is None:
continue
mask = np.ones(offsets[-1], dtype=bool)
mask[offsets[1:]-1] = False # Exclude end points.
if start_point_color is not None:
Expand Down Expand Up @@ -535,15 +446,17 @@ def lines(
return

ax = self._get_ax(ax)
all_lines = self._lines_to_list_of_points(lines, line_type)
separate_lines = convert_line_type(lines, line_type, LineType.Separate)
if TYPE_CHECKING:
separate_lines = cast(cpy.LineReturn_Separate, separate_lines)

if arrow_size > 0.0:
for line in all_lines:
for line in separate_lines:
for i in range(len(line)-1):
self._arrow(ax, line[i], line[i+1], color, alpha, arrow_size)

if point_color is not None:
for line in all_lines:
for line in separate_lines:
start_index = 0
end_index = len(line)
if start_point_color is not None:
Expand Down
22 changes: 4 additions & 18 deletions lib/contourpy/util/mpl_util.py
Expand Up @@ -6,18 +6,17 @@
import numpy as np

from contourpy import FillType, LineType
from contourpy.convert import _codes_from_offsets

if TYPE_CHECKING:
from contourpy._contourpy import (
CodeArray, FillReturn, LineReturn, LineReturn_Separate, OffsetArray,
)
from contourpy._contourpy import FillReturn, LineReturn, LineReturn_Separate


def filled_to_mpl_paths(filled: FillReturn, fill_type: FillType) -> list[mpath.Path]:
if fill_type in (FillType.OuterCode, FillType.ChunkCombinedCode):
paths = [mpath.Path(points, codes) for points, codes in zip(*filled) if points is not None]
elif fill_type in (FillType.OuterOffset, FillType.ChunkCombinedOffset):
paths = [mpath.Path(points, offsets_to_mpl_codes(offsets))
paths = [mpath.Path(points, _codes_from_offsets(offsets))
for points, offsets in zip(*filled) if points is not None]
elif fill_type == FillType.ChunkCombinedCodeOffset:
paths = []
Expand All @@ -35,7 +34,7 @@ def filled_to_mpl_paths(filled: FillReturn, fill_type: FillType) -> list[mpath.P
for i in range(len(outer_offsets)-1):
offs = offsets[outer_offsets[i]:outer_offsets[i+1]+1]
pts = points[offs[0]:offs[-1]]
paths += [mpath.Path(pts, offsets_to_mpl_codes(offs - offs[0]))]
paths += [mpath.Path(pts, _codes_from_offsets(offs - offs[0]))]
else:
raise RuntimeError(f"Conversion of FillType {fill_type} to MPL Paths is not implemented")
return paths
Expand Down Expand Up @@ -64,16 +63,3 @@ def lines_to_mpl_paths(lines: LineReturn, line_type: LineType) -> list[mpath.Pat
else:
raise RuntimeError(f"Conversion of LineType {line_type} to MPL Paths is not implemented")
return paths


def mpl_codes_to_offsets(codes: CodeArray) -> OffsetArray:
offsets = np.nonzero(codes == 1)[0].astype(np.uint32)
offsets = np.append(offsets, len(codes))
return offsets


def offsets_to_mpl_codes(offsets: OffsetArray) -> CodeArray:
codes = np.full(offsets[-1]-offsets[0], 2, dtype=np.uint8) # LINETO = 2
codes[offsets[:-1]] = 1 # MOVETO = 1
codes[offsets[1:]-1] = 79 # CLOSEPOLY 79
return codes

0 comments on commit 408a282

Please sign in to comment.