| 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: |
| |
| 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}") |
|
|