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