|
@@ -0,0 +1,555 @@
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from typing import TYPE_CHECKING, cast
|
|
|
+
|
|
|
+import numpy as np
|
|
|
+
|
|
|
+from contourpy._contourpy import FillType, LineType
|
|
|
+import contourpy.array as arr
|
|
|
+from contourpy.enum_util import as_fill_type, as_line_type
|
|
|
+from contourpy.typecheck import check_filled, check_lines
|
|
|
+from contourpy.types import MOVETO, offset_dtype
|
|
|
+
|
|
|
+if TYPE_CHECKING:
|
|
|
+ import contourpy._contourpy as cpy
|
|
|
+
|
|
|
+
|
|
|
+def _convert_filled_from_OuterCode(
|
|
|
+ filled: cpy.FillReturn_OuterCode,
|
|
|
+ fill_type_to: FillType,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ if fill_type_to == FillType.OuterCode:
|
|
|
+ return filled
|
|
|
+ elif fill_type_to == FillType.OuterOffset:
|
|
|
+ return (filled[0], [arr.offsets_from_codes(codes) for codes in filled[1]])
|
|
|
+
|
|
|
+ if len(filled[0]) > 0:
|
|
|
+ points = arr.concat_points(filled[0])
|
|
|
+ codes = arr.concat_codes(filled[1])
|
|
|
+ else:
|
|
|
+ points = None
|
|
|
+ codes = None
|
|
|
+
|
|
|
+ if fill_type_to == FillType.ChunkCombinedCode:
|
|
|
+ return ([points], [codes])
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffset:
|
|
|
+ return ([points], [None if codes is None else arr.offsets_from_codes(codes)])
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
|
|
|
+ outer_offsets = None if points is None else arr.offsets_from_lengths(filled[0])
|
|
|
+ ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([points], [codes], [outer_offsets])
|
|
|
+ return ret1
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
|
|
|
+ if codes is None:
|
|
|
+ ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
|
|
|
+ else:
|
|
|
+ offsets = arr.offsets_from_codes(codes)
|
|
|
+ outer_offsets = arr.outer_offsets_from_list_of_codes(filled[1])
|
|
|
+ ret2 = ([points], [offsets], [outer_offsets])
|
|
|
+ return ret2
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid FillType {fill_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_filled_from_OuterOffset(
|
|
|
+ filled: cpy.FillReturn_OuterOffset,
|
|
|
+ fill_type_to: FillType,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ if fill_type_to == FillType.OuterCode:
|
|
|
+ separate_codes = [arr.codes_from_offsets(offsets) for offsets in filled[1]]
|
|
|
+ return (filled[0], separate_codes)
|
|
|
+ elif fill_type_to == FillType.OuterOffset:
|
|
|
+ return filled
|
|
|
+
|
|
|
+ if len(filled[0]) > 0:
|
|
|
+ points = arr.concat_points(filled[0])
|
|
|
+ offsets = arr.concat_offsets(filled[1])
|
|
|
+ else:
|
|
|
+ points = None
|
|
|
+ offsets = None
|
|
|
+
|
|
|
+ if fill_type_to == FillType.ChunkCombinedCode:
|
|
|
+ return ([points], [None if offsets is None else arr.codes_from_offsets(offsets)])
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffset:
|
|
|
+ return ([points], [offsets])
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
|
|
|
+ if offsets is None:
|
|
|
+ ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
|
|
|
+ else:
|
|
|
+ codes = arr.codes_from_offsets(offsets)
|
|
|
+ outer_offsets = arr.offsets_from_lengths(filled[0])
|
|
|
+ ret1 = ([points], [codes], [outer_offsets])
|
|
|
+ return ret1
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
|
|
|
+ if points is None:
|
|
|
+ ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
|
|
|
+ else:
|
|
|
+ outer_offsets = arr.outer_offsets_from_list_of_offsets(filled[1])
|
|
|
+ ret2 = ([points], [offsets], [outer_offsets])
|
|
|
+ return ret2
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid FillType {fill_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_filled_from_ChunkCombinedCode(
|
|
|
+ filled: cpy.FillReturn_ChunkCombinedCode,
|
|
|
+ fill_type_to: FillType,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ if fill_type_to == FillType.ChunkCombinedCode:
|
|
|
+ return filled
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffset:
|
|
|
+ codes = [None if codes is None else arr.offsets_from_codes(codes) for codes in filled[1]]
|
|
|
+ return (filled[0], codes)
|
|
|
+ else:
|
|
|
+ raise ValueError(
|
|
|
+ f"Conversion from {FillType.ChunkCombinedCode} to {fill_type_to} not supported")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_filled_from_ChunkCombinedOffset(
|
|
|
+ filled: cpy.FillReturn_ChunkCombinedOffset,
|
|
|
+ fill_type_to: FillType,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ if fill_type_to == FillType.ChunkCombinedCode:
|
|
|
+ chunk_codes: list[cpy.CodeArray | None] = []
|
|
|
+ for points, offsets in zip(*filled):
|
|
|
+ if points is None:
|
|
|
+ chunk_codes.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
|
|
|
+ return (filled[0], chunk_codes)
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffset:
|
|
|
+ return filled
|
|
|
+ else:
|
|
|
+ raise ValueError(
|
|
|
+ f"Conversion from {FillType.ChunkCombinedOffset} to {fill_type_to} not supported")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_filled_from_ChunkCombinedCodeOffset(
|
|
|
+ filled: cpy.FillReturn_ChunkCombinedCodeOffset,
|
|
|
+ fill_type_to: FillType,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ if fill_type_to == FillType.OuterCode:
|
|
|
+ separate_points = []
|
|
|
+ separate_codes = []
|
|
|
+ for points, codes, outer_offsets in zip(*filled):
|
|
|
+ if points is not None:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert codes is not None
|
|
|
+ assert outer_offsets is not None
|
|
|
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
|
|
|
+ separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
|
|
|
+ return (separate_points, separate_codes)
|
|
|
+ elif fill_type_to == FillType.OuterOffset:
|
|
|
+ separate_points = []
|
|
|
+ separate_offsets = []
|
|
|
+ for points, codes, outer_offsets in zip(*filled):
|
|
|
+ if points is not None:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert codes is not None
|
|
|
+ assert outer_offsets is not None
|
|
|
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
|
|
|
+ separate_codes = arr.split_codes_by_offsets(codes, outer_offsets)
|
|
|
+ separate_offsets += [arr.offsets_from_codes(codes) for codes in separate_codes]
|
|
|
+ return (separate_points, separate_offsets)
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedCode:
|
|
|
+ ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], filled[1])
|
|
|
+ return ret1
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffset:
|
|
|
+ all_offsets = [None if codes is None else arr.offsets_from_codes(codes)
|
|
|
+ for codes in filled[1]]
|
|
|
+ ret2: cpy.FillReturn_ChunkCombinedOffset = (filled[0], all_offsets)
|
|
|
+ return ret2
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
|
|
|
+ return filled
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
|
|
|
+ chunk_offsets: list[cpy.OffsetArray | None] = []
|
|
|
+ chunk_outer_offsets: list[cpy.OffsetArray | None] = []
|
|
|
+ for codes, outer_offsets in zip(*filled[1:]):
|
|
|
+ if codes is None:
|
|
|
+ chunk_offsets.append(None)
|
|
|
+ chunk_outer_offsets.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert outer_offsets is not None
|
|
|
+ offsets = arr.offsets_from_codes(codes)
|
|
|
+ outer_offsets = np.array([np.nonzero(offsets == oo)[0][0] for oo in outer_offsets],
|
|
|
+ dtype=offset_dtype)
|
|
|
+ chunk_offsets.append(offsets)
|
|
|
+ chunk_outer_offsets.append(outer_offsets)
|
|
|
+ ret3: cpy.FillReturn_ChunkCombinedOffsetOffset = (
|
|
|
+ filled[0], chunk_offsets, chunk_outer_offsets,
|
|
|
+ )
|
|
|
+ return ret3
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid FillType {fill_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_filled_from_ChunkCombinedOffsetOffset(
|
|
|
+ filled: cpy.FillReturn_ChunkCombinedOffsetOffset,
|
|
|
+ fill_type_to: FillType,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ if fill_type_to == FillType.OuterCode:
|
|
|
+ separate_points = []
|
|
|
+ separate_codes = []
|
|
|
+ for points, offsets, outer_offsets in zip(*filled):
|
|
|
+ if points is not None:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ assert outer_offsets is not None
|
|
|
+ codes = arr.codes_from_offsets_and_points(offsets, points)
|
|
|
+ outer_offsets = offsets[outer_offsets]
|
|
|
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
|
|
|
+ separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
|
|
|
+ return (separate_points, separate_codes)
|
|
|
+ elif fill_type_to == FillType.OuterOffset:
|
|
|
+ separate_points = []
|
|
|
+ separate_offsets = []
|
|
|
+ for points, offsets, outer_offsets in zip(*filled):
|
|
|
+ if points is not None:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ assert outer_offsets is not None
|
|
|
+ if len(outer_offsets) > 2:
|
|
|
+ separate_offsets += [offsets[s:e+1] - offsets[s] for s, e in
|
|
|
+ zip(outer_offsets[:-1], outer_offsets[1:])]
|
|
|
+ else:
|
|
|
+ separate_offsets.append(offsets)
|
|
|
+ separate_points += arr.split_points_by_offsets(points, offsets[outer_offsets])
|
|
|
+ return (separate_points, separate_offsets)
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedCode:
|
|
|
+ chunk_codes: list[cpy.CodeArray | None] = []
|
|
|
+ for points, offsets, outer_offsets in zip(*filled):
|
|
|
+ if points is None:
|
|
|
+ chunk_codes.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ assert outer_offsets is not None
|
|
|
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
|
|
|
+ ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], chunk_codes)
|
|
|
+ return ret1
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffset:
|
|
|
+ return (filled[0], filled[1])
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
|
|
|
+ chunk_codes = []
|
|
|
+ chunk_outer_offsets: list[cpy.OffsetArray | None] = []
|
|
|
+ for points, offsets, outer_offsets in zip(*filled):
|
|
|
+ if points is None:
|
|
|
+ chunk_codes.append(None)
|
|
|
+ chunk_outer_offsets.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ assert outer_offsets is not None
|
|
|
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
|
|
|
+ chunk_outer_offsets.append(offsets[outer_offsets])
|
|
|
+ ret2: cpy.FillReturn_ChunkCombinedCodeOffset = (filled[0], chunk_codes, chunk_outer_offsets)
|
|
|
+ return ret2
|
|
|
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
|
|
|
+ return filled
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid FillType {fill_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def convert_filled(
|
|
|
+ filled: cpy.FillReturn,
|
|
|
+ fill_type_from: FillType | str,
|
|
|
+ fill_type_to: FillType | str,
|
|
|
+) -> cpy.FillReturn:
|
|
|
+ """Return the specified filled contours converted to a different :class:`~contourpy.FillType`.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ filled (sequence of arrays): Filled contour polygons to convert.
|
|
|
+ fill_type_from (FillType or str): :class:`~contourpy.FillType` to convert from as enum or
|
|
|
+ string equivalent.
|
|
|
+ fill_type_to (FillType or str): :class:`~contourpy.FillType` to convert to as enum or string
|
|
|
+ equivalent.
|
|
|
+
|
|
|
+ Return:
|
|
|
+ Converted filled contour polygons.
|
|
|
+
|
|
|
+ When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
|
|
|
+ chunked ones, all polygons are placed in the first chunk. When converting in the other
|
|
|
+ direction, all chunk information is discarded. Converting a fill type that is not aware of the
|
|
|
+ relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or)
|
|
|
+ ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
|
|
|
+
|
|
|
+ .. versionadded:: 1.2.0
|
|
|
+ """
|
|
|
+ fill_type_from = as_fill_type(fill_type_from)
|
|
|
+ fill_type_to = as_fill_type(fill_type_to)
|
|
|
+
|
|
|
+ check_filled(filled, fill_type_from)
|
|
|
+
|
|
|
+ if fill_type_from == FillType.OuterCode:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ filled = cast(cpy.FillReturn_OuterCode, filled)
|
|
|
+ return _convert_filled_from_OuterCode(filled, fill_type_to)
|
|
|
+ elif fill_type_from == FillType.OuterOffset:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ filled = cast(cpy.FillReturn_OuterOffset, filled)
|
|
|
+ return _convert_filled_from_OuterOffset(filled, fill_type_to)
|
|
|
+ elif fill_type_from == FillType.ChunkCombinedCode:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
|
|
|
+ return _convert_filled_from_ChunkCombinedCode(filled, fill_type_to)
|
|
|
+ elif fill_type_from == FillType.ChunkCombinedOffset:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
|
|
|
+ return _convert_filled_from_ChunkCombinedOffset(filled, fill_type_to)
|
|
|
+ elif fill_type_from == FillType.ChunkCombinedCodeOffset:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
|
|
|
+ return _convert_filled_from_ChunkCombinedCodeOffset(filled, fill_type_to)
|
|
|
+ elif fill_type_from == FillType.ChunkCombinedOffsetOffset:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
|
|
|
+ return _convert_filled_from_ChunkCombinedOffsetOffset(filled, fill_type_to)
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid FillType {fill_type_from}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_lines_from_Separate(
|
|
|
+ lines: cpy.LineReturn_Separate,
|
|
|
+ line_type_to: LineType,
|
|
|
+) -> cpy.LineReturn:
|
|
|
+ if line_type_to == LineType.Separate:
|
|
|
+ return lines
|
|
|
+ elif line_type_to == LineType.SeparateCode:
|
|
|
+ separate_codes = [arr.codes_from_points(line) for line in lines]
|
|
|
+ return (lines, separate_codes)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedCode:
|
|
|
+ if not lines:
|
|
|
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
|
|
|
+ else:
|
|
|
+ points = arr.concat_points(lines)
|
|
|
+ offsets = arr.offsets_from_lengths(lines)
|
|
|
+ codes = arr.codes_from_offsets_and_points(offsets, points)
|
|
|
+ ret1 = ([points], [codes])
|
|
|
+ return ret1
|
|
|
+ elif line_type_to == LineType.ChunkCombinedOffset:
|
|
|
+ if not lines:
|
|
|
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
|
|
|
+ else:
|
|
|
+ ret2 = ([arr.concat_points(lines)], [arr.offsets_from_lengths(lines)])
|
|
|
+ return ret2
|
|
|
+ elif line_type_to == LineType.ChunkCombinedNan:
|
|
|
+ if not lines:
|
|
|
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
|
|
|
+ else:
|
|
|
+ ret3 = ([arr.concat_points_with_nan(lines)],)
|
|
|
+ return ret3
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid LineType {line_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_lines_from_SeparateCode(
|
|
|
+ lines: cpy.LineReturn_SeparateCode,
|
|
|
+ line_type_to: LineType,
|
|
|
+) -> cpy.LineReturn:
|
|
|
+ if line_type_to == LineType.Separate:
|
|
|
+ # Drop codes.
|
|
|
+ return lines[0]
|
|
|
+ elif line_type_to == LineType.SeparateCode:
|
|
|
+ return lines
|
|
|
+ elif line_type_to == LineType.ChunkCombinedCode:
|
|
|
+ if not lines[0]:
|
|
|
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
|
|
|
+ else:
|
|
|
+ ret1 = ([arr.concat_points(lines[0])], [arr.concat_codes(lines[1])])
|
|
|
+ return ret1
|
|
|
+ elif line_type_to == LineType.ChunkCombinedOffset:
|
|
|
+ if not lines[0]:
|
|
|
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
|
|
|
+ else:
|
|
|
+ ret2 = ([arr.concat_points(lines[0])], [arr.offsets_from_lengths(lines[0])])
|
|
|
+ return ret2
|
|
|
+ elif line_type_to == LineType.ChunkCombinedNan:
|
|
|
+ if not lines[0]:
|
|
|
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
|
|
|
+ else:
|
|
|
+ ret3 = ([arr.concat_points_with_nan(lines[0])],)
|
|
|
+ return ret3
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid LineType {line_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_lines_from_ChunkCombinedCode(
|
|
|
+ lines: cpy.LineReturn_ChunkCombinedCode,
|
|
|
+ line_type_to: LineType,
|
|
|
+) -> cpy.LineReturn:
|
|
|
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
|
|
|
+ separate_lines = []
|
|
|
+ for points, codes in zip(*lines):
|
|
|
+ if points is not None:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert codes is not None
|
|
|
+ split_at = np.nonzero(codes == MOVETO)[0]
|
|
|
+ if len(split_at) > 1:
|
|
|
+ separate_lines += np.split(points, split_at[1:])
|
|
|
+ else:
|
|
|
+ separate_lines.append(points)
|
|
|
+ if line_type_to == LineType.Separate:
|
|
|
+ return separate_lines
|
|
|
+ else:
|
|
|
+ separate_codes = [arr.codes_from_points(line) for line in separate_lines]
|
|
|
+ return (separate_lines, separate_codes)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedCode:
|
|
|
+ return lines
|
|
|
+ elif line_type_to == LineType.ChunkCombinedOffset:
|
|
|
+ chunk_offsets = [None if codes is None else arr.offsets_from_codes(codes)
|
|
|
+ for codes in lines[1]]
|
|
|
+ return (lines[0], chunk_offsets)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedNan:
|
|
|
+ points_nan: list[cpy.PointArray | None] = []
|
|
|
+ for points, codes in zip(*lines):
|
|
|
+ if points is None:
|
|
|
+ points_nan.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert codes is not None
|
|
|
+ offsets = arr.offsets_from_codes(codes)
|
|
|
+ points_nan.append(arr.insert_nan_at_offsets(points, offsets))
|
|
|
+ return (points_nan,)
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid LineType {line_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_lines_from_ChunkCombinedOffset(
|
|
|
+ lines: cpy.LineReturn_ChunkCombinedOffset,
|
|
|
+ line_type_to: LineType,
|
|
|
+) -> cpy.LineReturn:
|
|
|
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
|
|
|
+ separate_lines = []
|
|
|
+ for points, offsets in zip(*lines):
|
|
|
+ if points is not None:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ separate_lines += arr.split_points_by_offsets(points, offsets)
|
|
|
+ if line_type_to == LineType.Separate:
|
|
|
+ return separate_lines
|
|
|
+ else:
|
|
|
+ separate_codes = [arr.codes_from_points(line) for line in separate_lines]
|
|
|
+ return (separate_lines, separate_codes)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedCode:
|
|
|
+ chunk_codes: list[cpy.CodeArray | None] = []
|
|
|
+ for points, offsets in zip(*lines):
|
|
|
+ if points is None:
|
|
|
+ chunk_codes.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
|
|
|
+ return (lines[0], chunk_codes)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedOffset:
|
|
|
+ return lines
|
|
|
+ elif line_type_to == LineType.ChunkCombinedNan:
|
|
|
+ points_nan: list[cpy.PointArray | None] = []
|
|
|
+ for points, offsets in zip(*lines):
|
|
|
+ if points is None:
|
|
|
+ points_nan.append(None)
|
|
|
+ else:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ assert offsets is not None
|
|
|
+ points_nan.append(arr.insert_nan_at_offsets(points, offsets))
|
|
|
+ return (points_nan,)
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid LineType {line_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def _convert_lines_from_ChunkCombinedNan(
|
|
|
+ lines: cpy.LineReturn_ChunkCombinedNan,
|
|
|
+ line_type_to: LineType,
|
|
|
+) -> cpy.LineReturn:
|
|
|
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
|
|
|
+ separate_lines = []
|
|
|
+ for points in lines[0]:
|
|
|
+ if points is not None:
|
|
|
+ separate_lines += arr.split_points_at_nan(points)
|
|
|
+ if line_type_to == LineType.Separate:
|
|
|
+ return separate_lines
|
|
|
+ else:
|
|
|
+ separate_codes = [arr.codes_from_points(points) for points in separate_lines]
|
|
|
+ return (separate_lines, separate_codes)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedCode:
|
|
|
+ chunk_points: list[cpy.PointArray | None] = []
|
|
|
+ chunk_codes: list[cpy.CodeArray | None] = []
|
|
|
+ for points in lines[0]:
|
|
|
+ if points is None:
|
|
|
+ chunk_points.append(None)
|
|
|
+ chunk_codes.append(None)
|
|
|
+ else:
|
|
|
+ points, offsets = arr.remove_nan(points)
|
|
|
+ chunk_points.append(points)
|
|
|
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
|
|
|
+ return (chunk_points, chunk_codes)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedOffset:
|
|
|
+ chunk_points = []
|
|
|
+ chunk_offsets: list[cpy.OffsetArray | None] = []
|
|
|
+ for points in lines[0]:
|
|
|
+ if points is None:
|
|
|
+ chunk_points.append(None)
|
|
|
+ chunk_offsets.append(None)
|
|
|
+ else:
|
|
|
+ points, offsets = arr.remove_nan(points)
|
|
|
+ chunk_points.append(points)
|
|
|
+ chunk_offsets.append(offsets)
|
|
|
+ return (chunk_points, chunk_offsets)
|
|
|
+ elif line_type_to == LineType.ChunkCombinedNan:
|
|
|
+ return lines
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid LineType {line_type_to}")
|
|
|
+
|
|
|
+
|
|
|
+def convert_lines(
|
|
|
+ lines: cpy.LineReturn,
|
|
|
+ line_type_from: LineType | str,
|
|
|
+ line_type_to: LineType | str,
|
|
|
+) -> cpy.LineReturn:
|
|
|
+ """Return the specified contour lines converted to a different :class:`~contourpy.LineType`.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ lines (sequence of arrays): Contour lines to convert.
|
|
|
+ line_type_from (LineType or str): :class:`~contourpy.LineType` to convert from as enum or
|
|
|
+ string equivalent.
|
|
|
+ line_type_to (LineType or str): :class:`~contourpy.LineType` to convert to as enum or string
|
|
|
+ equivalent.
|
|
|
+
|
|
|
+ Return:
|
|
|
+ Converted contour lines.
|
|
|
+
|
|
|
+ When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
|
|
|
+ chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
|
|
|
+ ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
|
|
|
+ other direction, all chunk information is discarded.
|
|
|
+
|
|
|
+ .. versionadded:: 1.2.0
|
|
|
+ """
|
|
|
+ line_type_from = as_line_type(line_type_from)
|
|
|
+ line_type_to = as_line_type(line_type_to)
|
|
|
+
|
|
|
+ check_lines(lines, line_type_from)
|
|
|
+
|
|
|
+ if line_type_from == LineType.Separate:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ lines = cast(cpy.LineReturn_Separate, lines)
|
|
|
+ return _convert_lines_from_Separate(lines, line_type_to)
|
|
|
+ elif line_type_from == LineType.SeparateCode:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ lines = cast(cpy.LineReturn_SeparateCode, lines)
|
|
|
+ return _convert_lines_from_SeparateCode(lines, line_type_to)
|
|
|
+ elif line_type_from == LineType.ChunkCombinedCode:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
|
|
|
+ return _convert_lines_from_ChunkCombinedCode(lines, line_type_to)
|
|
|
+ elif line_type_from == LineType.ChunkCombinedOffset:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
|
|
|
+ return _convert_lines_from_ChunkCombinedOffset(lines, line_type_to)
|
|
|
+ elif line_type_from == LineType.ChunkCombinedNan:
|
|
|
+ if TYPE_CHECKING:
|
|
|
+ lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
|
|
|
+ return _convert_lines_from_ChunkCombinedNan(lines, line_type_to)
|
|
|
+ else:
|
|
|
+ raise ValueError(f"Invalid LineType {line_type_from}")
|