up follow livre
This commit is contained in:
parent
70a5c3465c
commit
cffb31c1ef
12198 changed files with 2562132 additions and 35 deletions
285
venv/lib/python3.13/site-packages/contourpy/__init__.py
Normal file
285
venv/lib/python3.13/site-packages/contourpy/__init__.py
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
from contourpy._contourpy import (
|
||||
ContourGenerator,
|
||||
FillType,
|
||||
LineType,
|
||||
Mpl2005ContourGenerator,
|
||||
Mpl2014ContourGenerator,
|
||||
SerialContourGenerator,
|
||||
ThreadedContourGenerator,
|
||||
ZInterp,
|
||||
max_threads,
|
||||
)
|
||||
from contourpy._version import __version__
|
||||
from contourpy.chunk import calc_chunk_sizes
|
||||
from contourpy.convert import (
|
||||
convert_filled,
|
||||
convert_lines,
|
||||
convert_multi_filled,
|
||||
convert_multi_lines,
|
||||
)
|
||||
from contourpy.dechunk import (
|
||||
dechunk_filled,
|
||||
dechunk_lines,
|
||||
dechunk_multi_filled,
|
||||
dechunk_multi_lines,
|
||||
)
|
||||
from contourpy.enum_util import as_fill_type, as_line_type, as_z_interp
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from ._contourpy import CoordinateArray, MaskArray
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"contour_generator",
|
||||
"convert_filled",
|
||||
"convert_lines",
|
||||
"convert_multi_filled",
|
||||
"convert_multi_lines",
|
||||
"dechunk_filled",
|
||||
"dechunk_lines",
|
||||
"dechunk_multi_filled",
|
||||
"dechunk_multi_lines",
|
||||
"max_threads",
|
||||
"FillType",
|
||||
"LineType",
|
||||
"ContourGenerator",
|
||||
"Mpl2005ContourGenerator",
|
||||
"Mpl2014ContourGenerator",
|
||||
"SerialContourGenerator",
|
||||
"ThreadedContourGenerator",
|
||||
"ZInterp",
|
||||
]
|
||||
|
||||
|
||||
# Simple mapping of algorithm name to class name.
|
||||
_class_lookup: dict[str, type[ContourGenerator]] = {
|
||||
"mpl2005": Mpl2005ContourGenerator,
|
||||
"mpl2014": Mpl2014ContourGenerator,
|
||||
"serial": SerialContourGenerator,
|
||||
"threaded": ThreadedContourGenerator,
|
||||
}
|
||||
|
||||
|
||||
def _remove_z_mask(
|
||||
z: ArrayLike | np.ma.MaskedArray[Any, Any] | None,
|
||||
) -> tuple[CoordinateArray, MaskArray | None]:
|
||||
# Preserve mask if present.
|
||||
z_array = np.ma.asarray(z, dtype=np.float64) # type: ignore[no-untyped-call]
|
||||
z_masked = np.ma.masked_invalid(z_array, copy=False) # type: ignore[no-untyped-call]
|
||||
|
||||
if np.ma.is_masked(z_masked):
|
||||
mask = np.ma.getmask(z_masked)
|
||||
else:
|
||||
mask = None
|
||||
|
||||
return np.ma.getdata(z_masked), mask # type: ignore[no-untyped-call]
|
||||
|
||||
|
||||
def contour_generator(
|
||||
x: ArrayLike | None = None,
|
||||
y: ArrayLike | None = None,
|
||||
z: ArrayLike | np.ma.MaskedArray[Any, Any] | None = None,
|
||||
*,
|
||||
name: str = "serial",
|
||||
corner_mask: bool | None = None,
|
||||
line_type: LineType | str | None = None,
|
||||
fill_type: FillType | str | None = None,
|
||||
chunk_size: int | tuple[int, int] | None = None,
|
||||
chunk_count: int | tuple[int, int] | None = None,
|
||||
total_chunk_count: int | None = None,
|
||||
quad_as_tri: bool = False,
|
||||
z_interp: ZInterp | str | None = ZInterp.Linear,
|
||||
thread_count: int = 0,
|
||||
) -> ContourGenerator:
|
||||
"""Create and return a :class:`~.ContourGenerator` object.
|
||||
|
||||
The class and properties of the returned :class:`~.ContourGenerator` are determined by the
|
||||
function arguments, with sensible defaults.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,), optional): The x-coordinates of the ``z`` values.
|
||||
May be 2D with the same shape as ``z.shape``, or 1D with length ``nx = z.shape[1]``.
|
||||
If not specified are assumed to be ``np.arange(nx)``. Must be ordered monotonically.
|
||||
y (array-like of shape (ny, nx) or (ny,), optional): The y-coordinates of the ``z`` values.
|
||||
May be 2D with the same shape as ``z.shape``, or 1D with length ``ny = z.shape[0]``.
|
||||
If not specified are assumed to be ``np.arange(ny)``. Must be ordered monotonically.
|
||||
z (array-like of shape (ny, nx), may be a masked array): The 2D gridded values to calculate
|
||||
the contours of. May be a masked array, and any invalid values (``np.inf`` or
|
||||
``np.nan``) will also be masked out.
|
||||
name (str): Algorithm name, one of ``"serial"``, ``"threaded"``, ``"mpl2005"`` or
|
||||
``"mpl2014"``, default ``"serial"``.
|
||||
corner_mask (bool, optional): 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 these points are always masked
|
||||
out, other triangular corners comprising three unmasked points are contoured as usual.
|
||||
If not specified, uses the default provided by the algorithm ``name``.
|
||||
line_type (LineType or str, optional): The format of contour line data returned from calls
|
||||
to :meth:`~.ContourGenerator.lines`, specified either as a :class:`~.LineType` or its
|
||||
string equivalent such as ``"SeparateCode"``.
|
||||
If not specified, uses the default provided by the algorithm ``name``.
|
||||
The relationship between the :class:`~.LineType` enum and the data format returned from
|
||||
:meth:`~.ContourGenerator.lines` is explained at :ref:`line_type`.
|
||||
fill_type (FillType or str, optional): The format of filled contour data returned from calls
|
||||
to :meth:`~.ContourGenerator.filled`, specified either as a :class:`~.FillType` or its
|
||||
string equivalent such as ``"OuterOffset"``.
|
||||
If not specified, uses the default provided by the algorithm ``name``.
|
||||
The relationship between the :class:`~.FillType` enum and the data format returned from
|
||||
:meth:`~.ContourGenerator.filled` is explained at :ref:`fill_type`.
|
||||
chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
|
||||
size in both directions if only one value is specified.
|
||||
chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
|
||||
same count in both directions if only one value is specified.
|
||||
total_chunk_count (int, optional): Total number of chunks.
|
||||
quad_as_tri (bool): Enable/disable treating quads as 4 triangles, default ``False``.
|
||||
If ``False``, a contour line within a quad is a straight line between points on two of
|
||||
its edges. If ``True``, each full quad is divided into 4 triangles using a virtual point
|
||||
at the centre (mean x, y of the corner points) and a contour line is piecewise linear
|
||||
within those triangles. Corner-masked triangles are not affected by this setting, only
|
||||
full unmasked quads.
|
||||
z_interp (ZInterp or str, optional): How to interpolate ``z`` values when determining where
|
||||
contour lines intersect the edges of quads and the ``z`` values of the central points of
|
||||
quads, specified either as a :class:`~contourpy.ZInterp` or its string equivalent such
|
||||
as ``"Log"``. Default is ``ZInterp.Linear``.
|
||||
thread_count (int): Number of threads to use for contour calculation, default 0. Threads can
|
||||
only be used with an algorithm ``name`` that supports threads (currently only
|
||||
``name="threaded"``) and there must be at least the same number of chunks as threads.
|
||||
If ``thread_count=0`` and ``name="threaded"`` then it uses the maximum number of threads
|
||||
as determined by the C++11 call ``std::thread::hardware_concurrency()``. If ``name`` is
|
||||
something other than ``"threaded"`` then the ``thread_count`` will be set to ``1``.
|
||||
|
||||
Return:
|
||||
:class:`~.ContourGenerator`.
|
||||
|
||||
Note:
|
||||
A maximum of one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` may be
|
||||
specified.
|
||||
|
||||
Warning:
|
||||
The ``name="mpl2005"`` algorithm does not implement chunking for contour lines.
|
||||
"""
|
||||
x = np.asarray(x, dtype=np.float64)
|
||||
y = np.asarray(y, dtype=np.float64)
|
||||
z, mask = _remove_z_mask(z)
|
||||
|
||||
# Check arguments: z.
|
||||
if z.ndim != 2:
|
||||
raise TypeError(f"Input z must be 2D, not {z.ndim}D")
|
||||
|
||||
if z.shape[0] < 2 or z.shape[1] < 2:
|
||||
raise TypeError(f"Input z must be at least a (2, 2) shaped array, but has shape {z.shape}")
|
||||
|
||||
ny, nx = z.shape
|
||||
|
||||
# Check arguments: x and y.
|
||||
if x.ndim != y.ndim:
|
||||
raise TypeError(f"Number of dimensions of x ({x.ndim}) and y ({y.ndim}) do not match")
|
||||
|
||||
if x.ndim == 0:
|
||||
x = np.arange(nx, dtype=np.float64)
|
||||
y = np.arange(ny, dtype=np.float64)
|
||||
x, y = np.meshgrid(x, y)
|
||||
elif x.ndim == 1:
|
||||
if len(x) != nx:
|
||||
raise TypeError(f"Length of x ({len(x)}) must match number of columns in z ({nx})")
|
||||
if len(y) != ny:
|
||||
raise TypeError(f"Length of y ({len(y)}) must match number of rows in z ({ny})")
|
||||
x, y = np.meshgrid(x, y)
|
||||
elif x.ndim == 2:
|
||||
if x.shape != z.shape:
|
||||
raise TypeError(f"Shapes of x {x.shape} and z {z.shape} do not match")
|
||||
if y.shape != z.shape:
|
||||
raise TypeError(f"Shapes of y {y.shape} and z {z.shape} do not match")
|
||||
else:
|
||||
raise TypeError(f"Inputs x and y must be None, 1D or 2D, not {x.ndim}D")
|
||||
|
||||
# Check mask shape just in case.
|
||||
if mask is not None and mask.shape != z.shape:
|
||||
raise ValueError("If mask is set it must be a 2D array with the same shape as z")
|
||||
|
||||
# Check arguments: name.
|
||||
if name not in _class_lookup:
|
||||
raise ValueError(f"Unrecognised contour generator name: {name}")
|
||||
|
||||
# Check arguments: chunk_size, chunk_count and total_chunk_count.
|
||||
y_chunk_size, x_chunk_size = calc_chunk_sizes(
|
||||
chunk_size, chunk_count, total_chunk_count, ny, nx)
|
||||
|
||||
cls = _class_lookup[name]
|
||||
|
||||
# Check arguments: corner_mask.
|
||||
if corner_mask is None:
|
||||
# Set it to default, which is True if the algorithm supports it.
|
||||
corner_mask = cls.supports_corner_mask()
|
||||
elif corner_mask and not cls.supports_corner_mask():
|
||||
raise ValueError(f"{name} contour generator does not support corner_mask=True")
|
||||
|
||||
# Check arguments: line_type.
|
||||
if line_type is None:
|
||||
line_type = cls.default_line_type
|
||||
else:
|
||||
line_type = as_line_type(line_type)
|
||||
|
||||
if not cls.supports_line_type(line_type):
|
||||
raise ValueError(f"{name} contour generator does not support line_type {line_type}")
|
||||
|
||||
# Check arguments: fill_type.
|
||||
if fill_type is None:
|
||||
fill_type = cls.default_fill_type
|
||||
else:
|
||||
fill_type = as_fill_type(fill_type)
|
||||
|
||||
if not cls.supports_fill_type(fill_type):
|
||||
raise ValueError(f"{name} contour generator does not support fill_type {fill_type}")
|
||||
|
||||
# Check arguments: quad_as_tri.
|
||||
if quad_as_tri and not cls.supports_quad_as_tri():
|
||||
raise ValueError(f"{name} contour generator does not support quad_as_tri=True")
|
||||
|
||||
# Check arguments: z_interp.
|
||||
if z_interp is None:
|
||||
z_interp = ZInterp.Linear
|
||||
else:
|
||||
z_interp = as_z_interp(z_interp)
|
||||
|
||||
if z_interp != ZInterp.Linear and not cls.supports_z_interp():
|
||||
raise ValueError(f"{name} contour generator does not support z_interp {z_interp}")
|
||||
|
||||
# Check arguments: thread_count.
|
||||
if thread_count not in (0, 1) and not cls.supports_threads():
|
||||
raise ValueError(f"{name} contour generator does not support thread_count {thread_count}")
|
||||
|
||||
# Prepare args and kwargs for contour generator constructor.
|
||||
args = [x, y, z, mask]
|
||||
kwargs: dict[str, int | bool | LineType | FillType | ZInterp] = {
|
||||
"x_chunk_size": x_chunk_size,
|
||||
"y_chunk_size": y_chunk_size,
|
||||
}
|
||||
|
||||
if name not in ("mpl2005", "mpl2014"):
|
||||
kwargs["line_type"] = line_type
|
||||
kwargs["fill_type"] = fill_type
|
||||
|
||||
if cls.supports_corner_mask():
|
||||
kwargs["corner_mask"] = corner_mask
|
||||
|
||||
if cls.supports_quad_as_tri():
|
||||
kwargs["quad_as_tri"] = quad_as_tri
|
||||
|
||||
if cls.supports_z_interp():
|
||||
kwargs["z_interp"] = z_interp
|
||||
|
||||
if cls.supports_threads():
|
||||
kwargs["thread_count"] = thread_count
|
||||
|
||||
# Create contour generator.
|
||||
return cls(*args, **kwargs)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
199
venv/lib/python3.13/site-packages/contourpy/_contourpy.pyi
Normal file
199
venv/lib/python3.13/site-packages/contourpy/_contourpy.pyi
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
from typing import ClassVar, NoReturn, TypeAlias
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
import contourpy._contourpy as cpy
|
||||
|
||||
# Input numpy array types, the same as in common.h
|
||||
CoordinateArray: TypeAlias = npt.NDArray[np.float64]
|
||||
MaskArray: TypeAlias = npt.NDArray[np.bool_]
|
||||
LevelArray: TypeAlias = npt.ArrayLike
|
||||
|
||||
# Output numpy array types, the same as in common.h
|
||||
PointArray: TypeAlias = npt.NDArray[np.float64]
|
||||
CodeArray: TypeAlias = npt.NDArray[np.uint8]
|
||||
OffsetArray: TypeAlias = npt.NDArray[np.uint32]
|
||||
|
||||
# Types returned from filled()
|
||||
FillReturn_OuterCode: TypeAlias = tuple[list[PointArray], list[CodeArray]]
|
||||
FillReturn_OuterOffset: TypeAlias = tuple[list[PointArray], list[OffsetArray]]
|
||||
FillReturn_ChunkCombinedCode: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None]]
|
||||
FillReturn_ChunkCombinedOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None]]
|
||||
FillReturn_ChunkCombinedCodeOffset: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None], list[OffsetArray | None]]
|
||||
FillReturn_ChunkCombinedOffsetOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None], list[OffsetArray | None]]
|
||||
FillReturn_Chunk: TypeAlias = FillReturn_ChunkCombinedCode | FillReturn_ChunkCombinedOffset | FillReturn_ChunkCombinedCodeOffset | FillReturn_ChunkCombinedOffsetOffset
|
||||
FillReturn: TypeAlias = FillReturn_OuterCode | FillReturn_OuterOffset | FillReturn_Chunk
|
||||
|
||||
# Types returned from lines()
|
||||
LineReturn_Separate: TypeAlias = list[PointArray]
|
||||
LineReturn_SeparateCode: TypeAlias = tuple[list[PointArray], list[CodeArray]]
|
||||
LineReturn_ChunkCombinedCode: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None]]
|
||||
LineReturn_ChunkCombinedOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None]]
|
||||
LineReturn_ChunkCombinedNan: TypeAlias = tuple[list[PointArray | None]]
|
||||
LineReturn_Chunk: TypeAlias = LineReturn_ChunkCombinedCode | LineReturn_ChunkCombinedOffset | LineReturn_ChunkCombinedNan
|
||||
LineReturn: TypeAlias = LineReturn_Separate | LineReturn_SeparateCode | LineReturn_Chunk
|
||||
|
||||
|
||||
NDEBUG: int
|
||||
__version__: str
|
||||
|
||||
class FillType:
|
||||
ChunkCombinedCode: ClassVar[cpy.FillType]
|
||||
ChunkCombinedCodeOffset: ClassVar[cpy.FillType]
|
||||
ChunkCombinedOffset: ClassVar[cpy.FillType]
|
||||
ChunkCombinedOffsetOffset: ClassVar[cpy.FillType]
|
||||
OuterCode: ClassVar[cpy.FillType]
|
||||
OuterOffset: ClassVar[cpy.FillType]
|
||||
__members__: ClassVar[dict[str, cpy.FillType]]
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __getstate__(self) -> int: ...
|
||||
def __hash__(self) -> int: ...
|
||||
def __index__(self) -> int: ...
|
||||
def __init__(self, value: int) -> None: ...
|
||||
def __int__(self) -> int: ...
|
||||
def __ne__(self, other: object) -> bool: ...
|
||||
def __setstate__(self, state: int) -> NoReturn: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@property
|
||||
def value(self) -> int: ...
|
||||
|
||||
class LineType:
|
||||
ChunkCombinedCode: ClassVar[cpy.LineType]
|
||||
ChunkCombinedNan: ClassVar[cpy.LineType]
|
||||
ChunkCombinedOffset: ClassVar[cpy.LineType]
|
||||
Separate: ClassVar[cpy.LineType]
|
||||
SeparateCode: ClassVar[cpy.LineType]
|
||||
__members__: ClassVar[dict[str, cpy.LineType]]
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __getstate__(self) -> int: ...
|
||||
def __hash__(self) -> int: ...
|
||||
def __index__(self) -> int: ...
|
||||
def __init__(self, value: int) -> None: ...
|
||||
def __int__(self) -> int: ...
|
||||
def __ne__(self, other: object) -> bool: ...
|
||||
def __setstate__(self, state: int) -> NoReturn: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@property
|
||||
def value(self) -> int: ...
|
||||
|
||||
class ZInterp:
|
||||
Linear: ClassVar[cpy.ZInterp]
|
||||
Log: ClassVar[cpy.ZInterp]
|
||||
__members__: ClassVar[dict[str, cpy.ZInterp]]
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __getstate__(self) -> int: ...
|
||||
def __hash__(self) -> int: ...
|
||||
def __index__(self) -> int: ...
|
||||
def __init__(self, value: int) -> None: ...
|
||||
def __int__(self) -> int: ...
|
||||
def __ne__(self, other: object) -> bool: ...
|
||||
def __setstate__(self, state: int) -> NoReturn: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@property
|
||||
def value(self) -> int: ...
|
||||
|
||||
def max_threads() -> int: ...
|
||||
|
||||
class ContourGenerator:
|
||||
def create_contour(self, level: float) -> LineReturn: ...
|
||||
def create_filled_contour(self, lower_level: float, upper_level: float) -> FillReturn: ...
|
||||
def filled(self, lower_level: float, upper_level: float) -> FillReturn: ...
|
||||
def lines(self, level: float) -> LineReturn: ...
|
||||
def multi_filled(self, levels: LevelArray) -> list[FillReturn]: ...
|
||||
def multi_lines(self, levels: LevelArray) -> list[LineReturn]: ...
|
||||
@staticmethod
|
||||
def supports_corner_mask() -> bool: ...
|
||||
@staticmethod
|
||||
def supports_fill_type(fill_type: FillType) -> bool: ...
|
||||
@staticmethod
|
||||
def supports_line_type(line_type: LineType) -> bool: ...
|
||||
@staticmethod
|
||||
def supports_quad_as_tri() -> bool: ...
|
||||
@staticmethod
|
||||
def supports_threads() -> bool: ...
|
||||
@staticmethod
|
||||
def supports_z_interp() -> bool: ...
|
||||
@property
|
||||
def chunk_count(self) -> tuple[int, int]: ...
|
||||
@property
|
||||
def chunk_size(self) -> tuple[int, int]: ...
|
||||
@property
|
||||
def corner_mask(self) -> bool: ...
|
||||
@property
|
||||
def fill_type(self) -> FillType: ...
|
||||
@property
|
||||
def line_type(self) -> LineType: ...
|
||||
@property
|
||||
def quad_as_tri(self) -> bool: ...
|
||||
@property
|
||||
def thread_count(self) -> int: ...
|
||||
@property
|
||||
def z_interp(self) -> ZInterp: ...
|
||||
default_fill_type: cpy.FillType
|
||||
default_line_type: cpy.LineType
|
||||
|
||||
class Mpl2005ContourGenerator(ContourGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
x: CoordinateArray,
|
||||
y: CoordinateArray,
|
||||
z: CoordinateArray,
|
||||
mask: MaskArray,
|
||||
*,
|
||||
x_chunk_size: int = 0,
|
||||
y_chunk_size: int = 0,
|
||||
) -> None: ...
|
||||
|
||||
class Mpl2014ContourGenerator(ContourGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
x: CoordinateArray,
|
||||
y: CoordinateArray,
|
||||
z: CoordinateArray,
|
||||
mask: MaskArray,
|
||||
*,
|
||||
corner_mask: bool,
|
||||
x_chunk_size: int = 0,
|
||||
y_chunk_size: int = 0,
|
||||
) -> None: ...
|
||||
|
||||
class SerialContourGenerator(ContourGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
x: CoordinateArray,
|
||||
y: CoordinateArray,
|
||||
z: CoordinateArray,
|
||||
mask: MaskArray,
|
||||
*,
|
||||
corner_mask: bool,
|
||||
line_type: LineType,
|
||||
fill_type: FillType,
|
||||
quad_as_tri: bool,
|
||||
z_interp: ZInterp,
|
||||
x_chunk_size: int = 0,
|
||||
y_chunk_size: int = 0,
|
||||
) -> None: ...
|
||||
def _write_cache(self) -> NoReturn: ...
|
||||
|
||||
class ThreadedContourGenerator(ContourGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
x: CoordinateArray,
|
||||
y: CoordinateArray,
|
||||
z: CoordinateArray,
|
||||
mask: MaskArray,
|
||||
*,
|
||||
corner_mask: bool,
|
||||
line_type: LineType,
|
||||
fill_type: FillType,
|
||||
quad_as_tri: bool,
|
||||
z_interp: ZInterp,
|
||||
x_chunk_size: int = 0,
|
||||
y_chunk_size: int = 0,
|
||||
thread_count: int = 0,
|
||||
) -> None: ...
|
||||
def _write_cache(self) -> None: ...
|
||||
1
venv/lib/python3.13/site-packages/contourpy/_version.py
Normal file
1
venv/lib/python3.13/site-packages/contourpy/_version.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__version__ = "1.3.3"
|
||||
261
venv/lib/python3.13/site-packages/contourpy/array.py
Normal file
261
venv/lib/python3.13/site-packages/contourpy/array.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from itertools import chain, pairwise
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
from contourpy.typecheck import check_code_array, check_offset_array, check_point_array
|
||||
from contourpy.types import CLOSEPOLY, LINETO, MOVETO, code_dtype, offset_dtype, point_dtype
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import contourpy._contourpy as cpy
|
||||
|
||||
|
||||
def codes_from_offsets(offsets: cpy.OffsetArray) -> cpy.CodeArray:
|
||||
"""Determine codes from offsets, assuming they all correspond to closed polygons.
|
||||
"""
|
||||
check_offset_array(offsets)
|
||||
|
||||
n = offsets[-1]
|
||||
codes = np.full(n, LINETO, dtype=code_dtype)
|
||||
codes[offsets[:-1]] = MOVETO
|
||||
codes[offsets[1:] - 1] = CLOSEPOLY
|
||||
return codes
|
||||
|
||||
|
||||
def codes_from_offsets_and_points(
|
||||
offsets: cpy.OffsetArray,
|
||||
points: cpy.PointArray,
|
||||
) -> cpy.CodeArray:
|
||||
"""Determine codes from offsets and points, using the equality of the start and end points of
|
||||
each line to determine if lines are closed or not.
|
||||
"""
|
||||
check_offset_array(offsets)
|
||||
check_point_array(points)
|
||||
|
||||
codes = np.full(len(points), LINETO, dtype=code_dtype)
|
||||
codes[offsets[:-1]] = MOVETO
|
||||
|
||||
end_offsets = offsets[1:] - 1
|
||||
closed = np.all(points[offsets[:-1]] == points[end_offsets], axis=1)
|
||||
codes[end_offsets[closed]] = CLOSEPOLY
|
||||
|
||||
return codes
|
||||
|
||||
|
||||
def codes_from_points(points: cpy.PointArray) -> cpy.CodeArray:
|
||||
"""Determine codes for a single line, using the equality of the start and end points to
|
||||
determine if the line is closed or not.
|
||||
"""
|
||||
check_point_array(points)
|
||||
|
||||
n = len(points)
|
||||
codes = np.full(n, LINETO, dtype=code_dtype)
|
||||
codes[0] = MOVETO
|
||||
if np.all(points[0] == points[-1]):
|
||||
codes[-1] = CLOSEPOLY
|
||||
return codes
|
||||
|
||||
|
||||
def concat_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.CodeArray:
|
||||
"""Concatenate a list of codes arrays into a single code array.
|
||||
"""
|
||||
if not list_of_codes:
|
||||
raise ValueError("Empty list passed to concat_codes")
|
||||
|
||||
return np.concatenate(list_of_codes, dtype=code_dtype)
|
||||
|
||||
|
||||
def concat_codes_or_none(list_of_codes_or_none: list[cpy.CodeArray | None]) -> cpy.CodeArray | None:
|
||||
"""Concatenate a list of codes arrays or None into a single code array or None.
|
||||
"""
|
||||
list_of_codes = [codes for codes in list_of_codes_or_none if codes is not None]
|
||||
if list_of_codes:
|
||||
return concat_codes(list_of_codes)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def concat_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
|
||||
"""Concatenate a list of offsets arrays into a single offset array.
|
||||
"""
|
||||
if not list_of_offsets:
|
||||
raise ValueError("Empty list passed to concat_offsets")
|
||||
|
||||
n = len(list_of_offsets)
|
||||
cumulative = np.cumsum([offsets[-1] for offsets in list_of_offsets], dtype=offset_dtype)
|
||||
ret: cpy.OffsetArray = np.concatenate(
|
||||
(list_of_offsets[0], *(list_of_offsets[i+1][1:] + cumulative[i] for i in range(n-1))),
|
||||
dtype=offset_dtype,
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
def concat_offsets_or_none(
|
||||
list_of_offsets_or_none: list[cpy.OffsetArray | None],
|
||||
) -> cpy.OffsetArray | None:
|
||||
"""Concatenate a list of offsets arrays or None into a single offset array or None.
|
||||
"""
|
||||
list_of_offsets = [offsets for offsets in list_of_offsets_or_none if offsets is not None]
|
||||
if list_of_offsets:
|
||||
return concat_offsets(list_of_offsets)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def concat_points(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
|
||||
"""Concatenate a list of point arrays into a single point array.
|
||||
"""
|
||||
if not list_of_points:
|
||||
raise ValueError("Empty list passed to concat_points")
|
||||
|
||||
return np.concatenate(list_of_points, dtype=point_dtype)
|
||||
|
||||
|
||||
def concat_points_or_none(
|
||||
list_of_points_or_none: list[cpy.PointArray | None],
|
||||
) -> cpy.PointArray | None:
|
||||
"""Concatenate a list of point arrays or None into a single point array or None.
|
||||
"""
|
||||
list_of_points = [points for points in list_of_points_or_none if points is not None]
|
||||
if list_of_points:
|
||||
return concat_points(list_of_points)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def concat_points_or_none_with_nan(
|
||||
list_of_points_or_none: list[cpy.PointArray | None],
|
||||
) -> cpy.PointArray | None:
|
||||
"""Concatenate a list of points or None into a single point array or None, with NaNs used to
|
||||
separate each line.
|
||||
"""
|
||||
list_of_points = [points for points in list_of_points_or_none if points is not None]
|
||||
if list_of_points:
|
||||
return concat_points_with_nan(list_of_points)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def concat_points_with_nan(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
|
||||
"""Concatenate a list of points into a single point array with NaNs used to separate each line.
|
||||
"""
|
||||
if not list_of_points:
|
||||
raise ValueError("Empty list passed to concat_points_with_nan")
|
||||
|
||||
if len(list_of_points) == 1:
|
||||
return list_of_points[0]
|
||||
else:
|
||||
nan_spacer = np.full((1, 2), np.nan, dtype=point_dtype)
|
||||
list_of_points = [list_of_points[0],
|
||||
*list(chain(*((nan_spacer, x) for x in list_of_points[1:])))]
|
||||
return concat_points(list_of_points)
|
||||
|
||||
|
||||
def insert_nan_at_offsets(points: cpy.PointArray, offsets: cpy.OffsetArray) -> cpy.PointArray:
|
||||
"""Insert NaNs into a point array at locations specified by an offset array.
|
||||
"""
|
||||
check_point_array(points)
|
||||
check_offset_array(offsets)
|
||||
|
||||
if len(offsets) <= 2:
|
||||
return points
|
||||
else:
|
||||
nan_spacer = np.array([np.nan, np.nan], dtype=point_dtype)
|
||||
# Convert offsets to int64 to avoid numpy error when mixing signed and unsigned ints.
|
||||
return np.insert(points, offsets[1:-1].astype(np.int64), nan_spacer, axis=0)
|
||||
|
||||
|
||||
def offsets_from_codes(codes: cpy.CodeArray) -> cpy.OffsetArray:
|
||||
"""Determine offsets from codes using locations of MOVETO codes.
|
||||
"""
|
||||
check_code_array(codes)
|
||||
|
||||
return np.append(np.nonzero(codes == MOVETO)[0], len(codes)).astype(offset_dtype)
|
||||
|
||||
|
||||
def offsets_from_lengths(list_of_points: list[cpy.PointArray]) -> cpy.OffsetArray:
|
||||
"""Determine offsets from lengths of point arrays.
|
||||
"""
|
||||
if not list_of_points:
|
||||
raise ValueError("Empty list passed to offsets_from_lengths")
|
||||
|
||||
return np.cumsum([0] + [len(line) for line in list_of_points], dtype=offset_dtype)
|
||||
|
||||
|
||||
def outer_offsets_from_list_of_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.OffsetArray:
|
||||
"""Determine outer offsets from codes using locations of MOVETO codes.
|
||||
"""
|
||||
if not list_of_codes:
|
||||
raise ValueError("Empty list passed to outer_offsets_from_list_of_codes")
|
||||
|
||||
return np.cumsum([0] + [np.count_nonzero(codes == MOVETO) for codes in list_of_codes],
|
||||
dtype=offset_dtype)
|
||||
|
||||
|
||||
def outer_offsets_from_list_of_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
|
||||
"""Determine outer offsets from a list of offsets.
|
||||
"""
|
||||
if not list_of_offsets:
|
||||
raise ValueError("Empty list passed to outer_offsets_from_list_of_offsets")
|
||||
|
||||
return np.cumsum([0] + [len(offsets)-1 for offsets in list_of_offsets], dtype=offset_dtype)
|
||||
|
||||
|
||||
def remove_nan(points: cpy.PointArray) -> tuple[cpy.PointArray, cpy.OffsetArray]:
|
||||
"""Remove NaN from a points array, also return the offsets corresponding to the NaN removed.
|
||||
"""
|
||||
check_point_array(points)
|
||||
|
||||
nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
|
||||
if len(nan_offsets) == 0:
|
||||
return points, np.array([0, len(points)], dtype=offset_dtype)
|
||||
else:
|
||||
points = np.delete(points, nan_offsets, axis=0)
|
||||
nan_offsets -= np.arange(len(nan_offsets))
|
||||
offsets: cpy.OffsetArray = np.empty(len(nan_offsets)+2, dtype=offset_dtype)
|
||||
offsets[0] = 0
|
||||
offsets[1:-1] = nan_offsets
|
||||
offsets[-1] = len(points)
|
||||
return points, offsets
|
||||
|
||||
|
||||
def split_codes_by_offsets(codes: cpy.CodeArray, offsets: cpy.OffsetArray) -> list[cpy.CodeArray]:
|
||||
"""Split a code array at locations specified by an offset array into a list of code arrays.
|
||||
"""
|
||||
check_code_array(codes)
|
||||
check_offset_array(offsets)
|
||||
|
||||
if len(offsets) > 2:
|
||||
return np.split(codes, offsets[1:-1])
|
||||
else:
|
||||
return [codes]
|
||||
|
||||
|
||||
def split_points_by_offsets(
|
||||
points: cpy.PointArray,
|
||||
offsets: cpy.OffsetArray,
|
||||
) -> list[cpy.PointArray]:
|
||||
"""Split a point array at locations specified by an offset array into a list of point arrays.
|
||||
"""
|
||||
check_point_array(points)
|
||||
check_offset_array(offsets)
|
||||
|
||||
if len(offsets) > 2:
|
||||
return np.split(points, offsets[1:-1])
|
||||
else:
|
||||
return [points]
|
||||
|
||||
|
||||
def split_points_at_nan(points: cpy.PointArray) -> list[cpy.PointArray]:
|
||||
"""Split a points array at NaNs into a list of point arrays.
|
||||
"""
|
||||
check_point_array(points)
|
||||
|
||||
nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
|
||||
if len(nan_offsets) == 0:
|
||||
return [points]
|
||||
else:
|
||||
nan_offsets = np.concatenate(([-1], nan_offsets, [len(points)]))
|
||||
return [points[s+1:e] for s, e in pairwise(nan_offsets)]
|
||||
95
venv/lib/python3.13/site-packages/contourpy/chunk.py
Normal file
95
venv/lib/python3.13/site-packages/contourpy/chunk.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def calc_chunk_sizes(
|
||||
chunk_size: int | tuple[int, int] | None,
|
||||
chunk_count: int | tuple[int, int] | None,
|
||||
total_chunk_count: int | None,
|
||||
ny: int,
|
||||
nx: int,
|
||||
) -> tuple[int, int]:
|
||||
"""Calculate chunk sizes.
|
||||
|
||||
Args:
|
||||
chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
|
||||
size in both directions if only one is specified. Cannot be negative.
|
||||
chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
|
||||
same count in both directions if only one is specified. If less than 1, set to 1.
|
||||
total_chunk_count (int, optional): Total number of chunks. If less than 1, set to 1.
|
||||
ny (int): Number of grid points in y-direction.
|
||||
nx (int): Number of grid points in x-direction.
|
||||
|
||||
Return:
|
||||
tuple(int, int): Chunk sizes (y_chunk_size, x_chunk_size).
|
||||
|
||||
Note:
|
||||
Zero or one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` should be
|
||||
specified.
|
||||
"""
|
||||
if sum([chunk_size is not None, chunk_count is not None, total_chunk_count is not None]) > 1:
|
||||
raise ValueError("Only one of chunk_size, chunk_count and total_chunk_count should be set")
|
||||
|
||||
if nx < 2 or ny < 2:
|
||||
raise ValueError(f"(ny, nx) must be at least (2, 2), not ({ny}, {nx})")
|
||||
|
||||
if total_chunk_count is not None:
|
||||
max_chunk_count = (nx-1)*(ny-1)
|
||||
total_chunk_count = min(max(total_chunk_count, 1), max_chunk_count)
|
||||
if total_chunk_count == 1:
|
||||
chunk_size = 0
|
||||
elif total_chunk_count == max_chunk_count:
|
||||
chunk_size = (1, 1)
|
||||
else:
|
||||
factors = two_factors(total_chunk_count)
|
||||
if ny > nx:
|
||||
chunk_count = factors
|
||||
else:
|
||||
chunk_count = (factors[1], factors[0])
|
||||
|
||||
if chunk_count is not None:
|
||||
if isinstance(chunk_count, tuple):
|
||||
y_chunk_count, x_chunk_count = chunk_count
|
||||
else:
|
||||
y_chunk_count = x_chunk_count = chunk_count
|
||||
x_chunk_count = min(max(x_chunk_count, 1), nx-1)
|
||||
y_chunk_count = min(max(y_chunk_count, 1), ny-1)
|
||||
chunk_size = (math.ceil((ny-1) / y_chunk_count), math.ceil((nx-1) / x_chunk_count))
|
||||
|
||||
if chunk_size is None:
|
||||
y_chunk_size = x_chunk_size = 0
|
||||
elif isinstance(chunk_size, tuple):
|
||||
y_chunk_size, x_chunk_size = chunk_size
|
||||
else:
|
||||
y_chunk_size = x_chunk_size = chunk_size
|
||||
|
||||
if x_chunk_size < 0 or y_chunk_size < 0:
|
||||
raise ValueError("chunk_size cannot be negative")
|
||||
|
||||
return y_chunk_size, x_chunk_size
|
||||
|
||||
|
||||
def two_factors(n: int) -> tuple[int, int]:
|
||||
"""Split an integer into two integer factors.
|
||||
|
||||
The two factors will be as close as possible to the sqrt of n, and are returned in decreasing
|
||||
order. Worst case returns (n, 1).
|
||||
|
||||
Args:
|
||||
n (int): The integer to factorize, must be positive.
|
||||
|
||||
Return:
|
||||
tuple(int, int): The two factors of n, in decreasing order.
|
||||
"""
|
||||
if n < 0:
|
||||
raise ValueError(f"two_factors expects positive integer not {n}")
|
||||
|
||||
i = math.ceil(math.sqrt(n))
|
||||
while n % i != 0:
|
||||
i -= 1
|
||||
j = n // i
|
||||
if i > j:
|
||||
return i, j
|
||||
else:
|
||||
return j, i
|
||||
621
venv/lib/python3.13/site-packages/contourpy/convert.py
Normal file
621
venv/lib/python3.13/site-packages/contourpy/convert.py
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from itertools import pairwise
|
||||
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
|
||||
pairwise(outer_offsets)]
|
||||
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:
|
||||
"""Convert filled contours from one :class:`~.FillType` to another.
|
||||
|
||||
Args:
|
||||
filled (sequence of arrays): Filled contour polygons to convert, such as those returned by
|
||||
:meth:`.ContourGenerator.filled`.
|
||||
fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
|
||||
string equivalent.
|
||||
fill_type_to (FillType or str): :class:`~.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:
|
||||
"""Convert contour lines from one :class:`~.LineType` to another.
|
||||
|
||||
Args:
|
||||
lines (sequence of arrays): Contour lines to convert, such as those returned by
|
||||
:meth:`.ContourGenerator.lines`.
|
||||
line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
|
||||
string equivalent.
|
||||
line_type_to (LineType or str): :class:`~.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}")
|
||||
|
||||
|
||||
def convert_multi_filled(
|
||||
multi_filled: list[cpy.FillReturn],
|
||||
fill_type_from: FillType | str,
|
||||
fill_type_to: FillType | str,
|
||||
) -> list[cpy.FillReturn]:
|
||||
"""Convert multiple sets of filled contours from one :class:`~.FillType` to another.
|
||||
|
||||
Args:
|
||||
multi_filled (nested sequence of arrays): Filled contour polygons to convert, such as those
|
||||
returned by :meth:`.ContourGenerator.multi_filled`.
|
||||
fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
|
||||
string equivalent.
|
||||
fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
|
||||
equivalent.
|
||||
|
||||
Return:
|
||||
Converted sets 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.3.0
|
||||
"""
|
||||
fill_type_from = as_fill_type(fill_type_from)
|
||||
fill_type_to = as_fill_type(fill_type_to)
|
||||
|
||||
return [convert_filled(filled, fill_type_from, fill_type_to) for filled in multi_filled]
|
||||
|
||||
|
||||
def convert_multi_lines(
|
||||
multi_lines: list[cpy.LineReturn],
|
||||
line_type_from: LineType | str,
|
||||
line_type_to: LineType | str,
|
||||
) -> list[cpy.LineReturn]:
|
||||
"""Convert multiple sets of contour lines from one :class:`~.LineType` to another.
|
||||
|
||||
Args:
|
||||
multi_lines (nested sequence of arrays): Contour lines to convert, such as those returned by
|
||||
:meth:`.ContourGenerator.multi_lines`.
|
||||
line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
|
||||
string equivalent.
|
||||
line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
|
||||
equivalent.
|
||||
|
||||
Return:
|
||||
Converted set of 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.3.0
|
||||
"""
|
||||
line_type_from = as_line_type(line_type_from)
|
||||
line_type_to = as_line_type(line_type_to)
|
||||
|
||||
return [convert_lines(lines, line_type_from, line_type_to) for lines in multi_lines]
|
||||
207
venv/lib/python3.13/site-packages/contourpy/dechunk.py
Normal file
207
venv/lib/python3.13/site-packages/contourpy/dechunk.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from contourpy._contourpy import FillType, LineType
|
||||
from contourpy.array import (
|
||||
concat_codes_or_none,
|
||||
concat_offsets_or_none,
|
||||
concat_points_or_none,
|
||||
concat_points_or_none_with_nan,
|
||||
)
|
||||
from contourpy.enum_util import as_fill_type, as_line_type
|
||||
from contourpy.typecheck import check_filled, check_lines
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import contourpy._contourpy as cpy
|
||||
|
||||
|
||||
def dechunk_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> cpy.FillReturn:
|
||||
"""Return the specified filled contours with chunked data moved into the first chunk.
|
||||
|
||||
Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
|
||||
those that are but only contain a single chunk are returned unmodified. Individual polygons are
|
||||
unchanged, they are not geometrically combined.
|
||||
|
||||
Args:
|
||||
filled (sequence of arrays): Filled contour data, such as returned by
|
||||
:meth:`.ContourGenerator.filled`.
|
||||
fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
|
||||
equivalent.
|
||||
|
||||
Return:
|
||||
Filled contours in a single chunk.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
"""
|
||||
fill_type = as_fill_type(fill_type)
|
||||
|
||||
if fill_type in (FillType.OuterCode, FillType.OuterOffset):
|
||||
# No-op if fill_type is not chunked.
|
||||
return filled
|
||||
|
||||
check_filled(filled, fill_type)
|
||||
if len(filled[0]) < 2:
|
||||
# No-op if just one chunk.
|
||||
return filled
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_Chunk, filled)
|
||||
points = concat_points_or_none(filled[0])
|
||||
|
||||
if fill_type == FillType.ChunkCombinedCode:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
|
||||
if points is None:
|
||||
ret1: cpy.FillReturn_ChunkCombinedCode = ([None], [None])
|
||||
else:
|
||||
ret1 = ([points], [concat_codes_or_none(filled[1])])
|
||||
return ret1
|
||||
elif fill_type == FillType.ChunkCombinedOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
|
||||
if points is None:
|
||||
ret2: cpy.FillReturn_ChunkCombinedOffset = ([None], [None])
|
||||
else:
|
||||
ret2 = ([points], [concat_offsets_or_none(filled[1])])
|
||||
return ret2
|
||||
elif fill_type == FillType.ChunkCombinedCodeOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
|
||||
if points is None:
|
||||
ret3: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
|
||||
else:
|
||||
outer_offsets = concat_offsets_or_none(filled[2])
|
||||
ret3 = ([points], [concat_codes_or_none(filled[1])], [outer_offsets])
|
||||
return ret3
|
||||
elif fill_type == FillType.ChunkCombinedOffsetOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
|
||||
if points is None:
|
||||
ret4: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
|
||||
else:
|
||||
outer_offsets = concat_offsets_or_none(filled[2])
|
||||
ret4 = ([points], [concat_offsets_or_none(filled[1])], [outer_offsets])
|
||||
return ret4
|
||||
else:
|
||||
raise ValueError(f"Invalid FillType {fill_type}")
|
||||
|
||||
|
||||
def dechunk_lines(lines: cpy.LineReturn, line_type: LineType | str) -> cpy.LineReturn:
|
||||
"""Return the specified contour lines with chunked data moved into the first chunk.
|
||||
|
||||
Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
|
||||
those that are but only contain a single chunk are returned unmodified. Individual lines are
|
||||
unchanged, they are not geometrically combined.
|
||||
|
||||
Args:
|
||||
lines (sequence of arrays): Contour line data, such as returned by
|
||||
:meth:`.ContourGenerator.lines`.
|
||||
line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
|
||||
equivalent.
|
||||
|
||||
Return:
|
||||
Contour lines in a single chunk.
|
||||
|
||||
.. versionadded:: 1.2.0
|
||||
"""
|
||||
line_type = as_line_type(line_type)
|
||||
|
||||
if line_type in (LineType.Separate, LineType.SeparateCode):
|
||||
# No-op if line_type is not chunked.
|
||||
return lines
|
||||
|
||||
check_lines(lines, line_type)
|
||||
if len(lines[0]) < 2:
|
||||
# No-op if just one chunk.
|
||||
return lines
|
||||
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_Chunk, lines)
|
||||
|
||||
if line_type == LineType.ChunkCombinedCode:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
|
||||
points = concat_points_or_none(lines[0])
|
||||
if points is None:
|
||||
ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
|
||||
else:
|
||||
ret1 = ([points], [concat_codes_or_none(lines[1])])
|
||||
return ret1
|
||||
elif line_type == LineType.ChunkCombinedOffset:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
|
||||
points = concat_points_or_none(lines[0])
|
||||
if points is None:
|
||||
ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
|
||||
else:
|
||||
ret2 = ([points], [concat_offsets_or_none(lines[1])])
|
||||
return ret2
|
||||
elif line_type == LineType.ChunkCombinedNan:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
|
||||
points = concat_points_or_none_with_nan(lines[0])
|
||||
ret3: cpy.LineReturn_ChunkCombinedNan = ([points],)
|
||||
return ret3
|
||||
else:
|
||||
raise ValueError(f"Invalid LineType {line_type}")
|
||||
|
||||
|
||||
def dechunk_multi_filled(
|
||||
multi_filled: list[cpy.FillReturn],
|
||||
fill_type: FillType | str,
|
||||
) -> list[cpy.FillReturn]:
|
||||
"""Return multiple sets of filled contours with chunked data moved into the first chunks.
|
||||
|
||||
Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
|
||||
those that are but only contain a single chunk are returned unmodified. Individual polygons are
|
||||
unchanged, they are not geometrically combined.
|
||||
|
||||
Args:
|
||||
multi_filled (nested sequence of arrays): Filled contour data, such as returned by
|
||||
:meth:`.ContourGenerator.multi_filled`.
|
||||
fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
|
||||
equivalent.
|
||||
|
||||
Return:
|
||||
Multiple sets of filled contours in a single chunk.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
"""
|
||||
fill_type = as_fill_type(fill_type)
|
||||
|
||||
if fill_type in (FillType.OuterCode, FillType.OuterOffset):
|
||||
# No-op if fill_type is not chunked.
|
||||
return multi_filled
|
||||
|
||||
return [dechunk_filled(filled, fill_type) for filled in multi_filled]
|
||||
|
||||
|
||||
def dechunk_multi_lines(
|
||||
multi_lines: list[cpy.LineReturn],
|
||||
line_type: LineType | str,
|
||||
) -> list[cpy.LineReturn]:
|
||||
"""Return multiple sets of contour lines with all chunked data moved into the first chunks.
|
||||
|
||||
Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
|
||||
those that are but only contain a single chunk are returned unmodified. Individual lines are
|
||||
unchanged, they are not geometrically combined.
|
||||
|
||||
Args:
|
||||
multi_lines (nested sequence of arrays): Contour line data, such as returned by
|
||||
:meth:`.ContourGenerator.multi_lines`.
|
||||
line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
|
||||
equivalent.
|
||||
|
||||
Return:
|
||||
Multiple sets of contour lines in a single chunk.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
"""
|
||||
line_type = as_line_type(line_type)
|
||||
|
||||
if line_type in (LineType.Separate, LineType.SeparateCode):
|
||||
# No-op if line_type is not chunked.
|
||||
return multi_lines
|
||||
|
||||
return [dechunk_lines(lines, line_type) for lines in multi_lines]
|
||||
57
venv/lib/python3.13/site-packages/contourpy/enum_util.py
Normal file
57
venv/lib/python3.13/site-packages/contourpy/enum_util.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from contourpy._contourpy import FillType, LineType, ZInterp
|
||||
|
||||
|
||||
def as_fill_type(fill_type: FillType | str) -> FillType:
|
||||
"""Coerce a FillType or string value to a FillType.
|
||||
|
||||
Args:
|
||||
fill_type (FillType or str): Value to convert.
|
||||
|
||||
Return:
|
||||
FillType: Converted value.
|
||||
"""
|
||||
if isinstance(fill_type, str):
|
||||
try:
|
||||
return FillType.__members__[fill_type]
|
||||
except KeyError as e:
|
||||
raise ValueError(f"'{fill_type}' is not a valid FillType") from e
|
||||
else:
|
||||
return fill_type
|
||||
|
||||
|
||||
def as_line_type(line_type: LineType | str) -> LineType:
|
||||
"""Coerce a LineType or string value to a LineType.
|
||||
|
||||
Args:
|
||||
line_type (LineType or str): Value to convert.
|
||||
|
||||
Return:
|
||||
LineType: Converted value.
|
||||
"""
|
||||
if isinstance(line_type, str):
|
||||
try:
|
||||
return LineType.__members__[line_type]
|
||||
except KeyError as e:
|
||||
raise ValueError(f"'{line_type}' is not a valid LineType") from e
|
||||
else:
|
||||
return line_type
|
||||
|
||||
|
||||
def as_z_interp(z_interp: ZInterp | str) -> ZInterp:
|
||||
"""Coerce a ZInterp or string value to a ZInterp.
|
||||
|
||||
Args:
|
||||
z_interp (ZInterp or str): Value to convert.
|
||||
|
||||
Return:
|
||||
ZInterp: Converted value.
|
||||
"""
|
||||
if isinstance(z_interp, str):
|
||||
try:
|
||||
return ZInterp.__members__[z_interp]
|
||||
except KeyError as e:
|
||||
raise ValueError(f"'{z_interp}' is not a valid ZInterp") from e
|
||||
else:
|
||||
return z_interp
|
||||
0
venv/lib/python3.13/site-packages/contourpy/py.typed
Normal file
0
venv/lib/python3.13/site-packages/contourpy/py.typed
Normal file
203
venv/lib/python3.13/site-packages/contourpy/typecheck.py
Normal file
203
venv/lib/python3.13/site-packages/contourpy/typecheck.py
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import numpy as np
|
||||
|
||||
from contourpy import FillType, LineType
|
||||
from contourpy.enum_util import as_fill_type, as_line_type
|
||||
from contourpy.types import MOVETO, code_dtype, offset_dtype, point_dtype
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import contourpy._contourpy as cpy
|
||||
|
||||
|
||||
# Minimalist array-checking functions that check dtype, ndims and shape only.
|
||||
# They do not walk the arrays to check the contents for performance reasons.
|
||||
def check_code_array(codes: Any) -> None:
|
||||
if not isinstance(codes, np.ndarray):
|
||||
raise TypeError(f"Expected numpy array not {type(codes)}")
|
||||
if codes.dtype != code_dtype:
|
||||
raise ValueError(f"Expected numpy array of dtype {code_dtype} not {codes.dtype}")
|
||||
if not (codes.ndim == 1 and len(codes) > 1):
|
||||
raise ValueError(f"Expected numpy array of shape (?,) not {codes.shape}")
|
||||
if codes[0] != MOVETO:
|
||||
raise ValueError(f"First element of code array must be {MOVETO}, not {codes[0]}")
|
||||
|
||||
|
||||
def check_offset_array(offsets: Any) -> None:
|
||||
if not isinstance(offsets, np.ndarray):
|
||||
raise TypeError(f"Expected numpy array not {type(offsets)}")
|
||||
if offsets.dtype != offset_dtype:
|
||||
raise ValueError(f"Expected numpy array of dtype {offset_dtype} not {offsets.dtype}")
|
||||
if not (offsets.ndim == 1 and len(offsets) > 1):
|
||||
raise ValueError(f"Expected numpy array of shape (?,) not {offsets.shape}")
|
||||
if offsets[0] != 0:
|
||||
raise ValueError(f"First element of offset array must be 0, not {offsets[0]}")
|
||||
|
||||
|
||||
def check_point_array(points: Any) -> None:
|
||||
if not isinstance(points, np.ndarray):
|
||||
raise TypeError(f"Expected numpy array not {type(points)}")
|
||||
if points.dtype != point_dtype:
|
||||
raise ValueError(f"Expected numpy array of dtype {point_dtype} not {points.dtype}")
|
||||
if not (points.ndim == 2 and points.shape[1] ==2 and points.shape[0] > 1):
|
||||
raise ValueError(f"Expected numpy array of shape (?, 2) not {points.shape}")
|
||||
|
||||
|
||||
def _check_tuple_of_lists_with_same_length(
|
||||
maybe_tuple: Any,
|
||||
tuple_length: int,
|
||||
allow_empty_lists: bool = True,
|
||||
) -> None:
|
||||
if not isinstance(maybe_tuple, tuple):
|
||||
raise TypeError(f"Expected tuple not {type(maybe_tuple)}")
|
||||
if len(maybe_tuple) != tuple_length:
|
||||
raise ValueError(f"Expected tuple of length {tuple_length} not {len(maybe_tuple)}")
|
||||
for maybe_list in maybe_tuple:
|
||||
if not isinstance(maybe_list, list):
|
||||
msg = f"Expected tuple to contain {tuple_length} lists but found a {type(maybe_list)}"
|
||||
raise TypeError(msg)
|
||||
lengths = [len(item) for item in maybe_tuple]
|
||||
if len(set(lengths)) != 1:
|
||||
msg = f"Expected {tuple_length} lists with same length but lengths are {lengths}"
|
||||
raise ValueError(msg)
|
||||
if not allow_empty_lists and lengths[0] == 0:
|
||||
raise ValueError(f"Expected {tuple_length} non-empty lists")
|
||||
|
||||
|
||||
def check_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> None:
|
||||
fill_type = as_fill_type(fill_type)
|
||||
|
||||
if fill_type == FillType.OuterCode:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_OuterCode, filled)
|
||||
_check_tuple_of_lists_with_same_length(filled, 2)
|
||||
for i, (points, codes) in enumerate(zip(*filled)):
|
||||
check_point_array(points)
|
||||
check_code_array(codes)
|
||||
if len(points) != len(codes):
|
||||
raise ValueError(f"Points and codes have different lengths in polygon {i}")
|
||||
elif fill_type == FillType.OuterOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_OuterOffset, filled)
|
||||
_check_tuple_of_lists_with_same_length(filled, 2)
|
||||
for i, (points, offsets) in enumerate(zip(*filled)):
|
||||
check_point_array(points)
|
||||
check_offset_array(offsets)
|
||||
if offsets[-1] != len(points):
|
||||
raise ValueError(f"Inconsistent points and offsets in polygon {i}")
|
||||
elif fill_type == FillType.ChunkCombinedCode:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
|
||||
_check_tuple_of_lists_with_same_length(filled, 2, allow_empty_lists=False)
|
||||
for chunk, (points_or_none, codes_or_none) in enumerate(zip(*filled)):
|
||||
if points_or_none is not None and codes_or_none is not None:
|
||||
check_point_array(points_or_none)
|
||||
check_code_array(codes_or_none)
|
||||
if len(points_or_none) != len(codes_or_none):
|
||||
raise ValueError(f"Points and codes have different lengths in chunk {chunk}")
|
||||
elif not (points_or_none is None and codes_or_none is None):
|
||||
raise ValueError(f"Inconsistent Nones in chunk {chunk}")
|
||||
elif fill_type == FillType.ChunkCombinedOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
|
||||
_check_tuple_of_lists_with_same_length(filled, 2, allow_empty_lists=False)
|
||||
for chunk, (points_or_none, offsets_or_none) in enumerate(zip(*filled)):
|
||||
if points_or_none is not None and offsets_or_none is not None:
|
||||
check_point_array(points_or_none)
|
||||
check_offset_array(offsets_or_none)
|
||||
if offsets_or_none[-1] != len(points_or_none):
|
||||
raise ValueError(f"Inconsistent points and offsets in chunk {chunk}")
|
||||
elif not (points_or_none is None and offsets_or_none is None):
|
||||
raise ValueError(f"Inconsistent Nones in chunk {chunk}")
|
||||
elif fill_type == FillType.ChunkCombinedCodeOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
|
||||
_check_tuple_of_lists_with_same_length(filled, 3, allow_empty_lists=False)
|
||||
for i, (points_or_none, codes_or_none, outer_offsets_or_none) in enumerate(zip(*filled)):
|
||||
if (points_or_none is not None and codes_or_none is not None and
|
||||
outer_offsets_or_none is not None):
|
||||
check_point_array(points_or_none)
|
||||
check_code_array(codes_or_none)
|
||||
check_offset_array(outer_offsets_or_none)
|
||||
if len(codes_or_none) != len(points_or_none):
|
||||
raise ValueError(f"Points and codes have different lengths in chunk {i}")
|
||||
if outer_offsets_or_none[-1] != len(codes_or_none):
|
||||
raise ValueError(f"Inconsistent codes and outer_offsets in chunk {i}")
|
||||
elif not (points_or_none is None and codes_or_none is None and
|
||||
outer_offsets_or_none is None):
|
||||
raise ValueError(f"Inconsistent Nones in chunk {i}")
|
||||
elif fill_type == FillType.ChunkCombinedOffsetOffset:
|
||||
if TYPE_CHECKING:
|
||||
filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
|
||||
_check_tuple_of_lists_with_same_length(filled, 3, allow_empty_lists=False)
|
||||
for i, (points_or_none, offsets_or_none, outer_offsets_or_none) in enumerate(zip(*filled)):
|
||||
if (points_or_none is not None and offsets_or_none is not None and
|
||||
outer_offsets_or_none is not None):
|
||||
check_point_array(points_or_none)
|
||||
check_offset_array(offsets_or_none)
|
||||
check_offset_array(outer_offsets_or_none)
|
||||
if offsets_or_none[-1] != len(points_or_none):
|
||||
raise ValueError(f"Inconsistent points and offsets in chunk {i}")
|
||||
if outer_offsets_or_none[-1] != len(offsets_or_none) - 1:
|
||||
raise ValueError(f"Inconsistent offsets and outer_offsets in chunk {i}")
|
||||
elif not (points_or_none is None and offsets_or_none is None and
|
||||
outer_offsets_or_none is None):
|
||||
raise ValueError(f"Inconsistent Nones in chunk {i}")
|
||||
else:
|
||||
raise ValueError(f"Invalid FillType {fill_type}")
|
||||
|
||||
|
||||
def check_lines(lines: cpy.LineReturn, line_type: LineType | str) -> None:
|
||||
line_type = as_line_type(line_type)
|
||||
|
||||
if line_type == LineType.Separate:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_Separate, lines)
|
||||
if not isinstance(lines, list):
|
||||
raise TypeError(f"Expected list not {type(lines)}")
|
||||
for points in lines:
|
||||
check_point_array(points)
|
||||
elif line_type == LineType.SeparateCode:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_SeparateCode, lines)
|
||||
_check_tuple_of_lists_with_same_length(lines, 2)
|
||||
for i, (points, codes) in enumerate(zip(*lines)):
|
||||
check_point_array(points)
|
||||
check_code_array(codes)
|
||||
if len(points) != len(codes):
|
||||
raise ValueError(f"Points and codes have different lengths in line {i}")
|
||||
elif line_type == LineType.ChunkCombinedCode:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
|
||||
_check_tuple_of_lists_with_same_length(lines, 2, allow_empty_lists=False)
|
||||
for chunk, (points_or_none, codes_or_none) in enumerate(zip(*lines)):
|
||||
if points_or_none is not None and codes_or_none is not None:
|
||||
check_point_array(points_or_none)
|
||||
check_code_array(codes_or_none)
|
||||
if len(points_or_none) != len(codes_or_none):
|
||||
raise ValueError(f"Points and codes have different lengths in chunk {chunk}")
|
||||
elif not (points_or_none is None and codes_or_none is None):
|
||||
raise ValueError(f"Inconsistent Nones in chunk {chunk}")
|
||||
elif line_type == LineType.ChunkCombinedOffset:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
|
||||
_check_tuple_of_lists_with_same_length(lines, 2, allow_empty_lists=False)
|
||||
for chunk, (points_or_none, offsets_or_none) in enumerate(zip(*lines)):
|
||||
if points_or_none is not None and offsets_or_none is not None:
|
||||
check_point_array(points_or_none)
|
||||
check_offset_array(offsets_or_none)
|
||||
if offsets_or_none[-1] != len(points_or_none):
|
||||
raise ValueError(f"Inconsistent points and offsets in chunk {chunk}")
|
||||
elif not (points_or_none is None and offsets_or_none is None):
|
||||
raise ValueError(f"Inconsistent Nones in chunk {chunk}")
|
||||
elif line_type == LineType.ChunkCombinedNan:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
|
||||
_check_tuple_of_lists_with_same_length(lines, 1, allow_empty_lists=False)
|
||||
for _chunk, points_or_none in enumerate(lines[0]):
|
||||
if points_or_none is not None:
|
||||
check_point_array(points_or_none)
|
||||
else:
|
||||
raise ValueError(f"Invalid LineType {line_type}")
|
||||
13
venv/lib/python3.13/site-packages/contourpy/types.py
Normal file
13
venv/lib/python3.13/site-packages/contourpy/types.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
# dtypes of arrays returned by ContourPy.
|
||||
point_dtype = np.float64
|
||||
code_dtype = np.uint8
|
||||
offset_dtype = np.uint32
|
||||
|
||||
# Kind codes used in Matplotlib Paths.
|
||||
MOVETO = 1
|
||||
LINETO = 2
|
||||
CLOSEPOLY = 79
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from contourpy.util._build_config import build_config
|
||||
|
||||
__all__ = ["build_config"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,60 @@
|
|||
# _build_config.py.in is converted into _build_config.py during the meson build process.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def build_config() -> dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing build configuration settings.
|
||||
|
||||
All dictionary keys and values are strings, for example ``False`` is
|
||||
returned as ``"False"``.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
"""
|
||||
return dict(
|
||||
# Python settings
|
||||
python_version="3.13",
|
||||
python_install_dir=r"/usr/local/lib/python3.13/site-packages/",
|
||||
python_path=r"/tmp/build-env-3d4ff6rd/bin/python",
|
||||
|
||||
# Package versions
|
||||
contourpy_version="1.3.3",
|
||||
meson_version="1.8.2",
|
||||
mesonpy_version="0.18.0",
|
||||
pybind11_version="3.0.0",
|
||||
|
||||
# Misc meson settings
|
||||
meson_backend="ninja",
|
||||
build_dir=r"/project/.mesonpy-cga074fa/lib/contourpy/util",
|
||||
source_dir=r"/project/lib/contourpy/util",
|
||||
cross_build="False",
|
||||
|
||||
# Build options
|
||||
build_options=r"-Dbuildtype=release -Db_ndebug=if-release -Db_vscrt=md -Dvsenv=True --native-file=/project/.mesonpy-cga074fa/meson-python-native-file.ini",
|
||||
buildtype="release",
|
||||
cpp_std="c++17",
|
||||
debug="False",
|
||||
optimization="3",
|
||||
vsenv="True",
|
||||
b_ndebug="if-release",
|
||||
b_vscrt="from_buildtype",
|
||||
|
||||
# C++ compiler
|
||||
compiler_name="gcc",
|
||||
compiler_version="14.2.1",
|
||||
linker_id="ld.bfd",
|
||||
compile_command="c++",
|
||||
|
||||
# Host machine
|
||||
host_cpu="x86_64",
|
||||
host_cpu_family="x86_64",
|
||||
host_cpu_endian="little",
|
||||
host_cpu_system="linux",
|
||||
|
||||
# Build machine, same as host machine if not a cross_build
|
||||
build_cpu="x86_64",
|
||||
build_cpu_family="x86_64",
|
||||
build_cpu_endian="little",
|
||||
build_cpu_system="linux",
|
||||
)
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from bokeh.io import export_png, export_svg, show
|
||||
from bokeh.io.export import get_screenshot_as_png
|
||||
from bokeh.layouts import gridplot
|
||||
from bokeh.models.annotations.labels import Label
|
||||
from bokeh.palettes import Category10
|
||||
from bokeh.plotting import figure
|
||||
import numpy as np
|
||||
|
||||
from contourpy.enum_util import as_fill_type, as_line_type
|
||||
from contourpy.util.bokeh_util import filled_to_bokeh, lines_to_bokeh
|
||||
from contourpy.util.renderer import Renderer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bokeh.core.enums import OutputBackendType
|
||||
from bokeh.models import GridPlot
|
||||
from bokeh.palettes import Palette
|
||||
from numpy.typing import ArrayLike
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
|
||||
from contourpy import FillType, LineType
|
||||
from contourpy._contourpy import FillReturn, LineReturn
|
||||
|
||||
|
||||
class BokehRenderer(Renderer):
|
||||
"""Utility renderer using Bokeh to render a grid of plots over the same (x, y) range.
|
||||
|
||||
Args:
|
||||
nrows (int, optional): Number of rows of plots, default ``1``.
|
||||
ncols (int, optional): Number of columns of plots, default ``1``.
|
||||
figsize (tuple(float, float), optional): Figure size in inches (assuming 100 dpi), default
|
||||
``(9, 9)``.
|
||||
show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``.
|
||||
want_svg (bool, optional): Whether output is required in SVG format or not, default
|
||||
``False``.
|
||||
|
||||
Warning:
|
||||
:class:`~.BokehRenderer`, unlike :class:`~.MplRenderer`, needs to be told in advance if
|
||||
output to SVG format will be required later, otherwise it will assume PNG output.
|
||||
"""
|
||||
_figures: list[figure]
|
||||
_layout: GridPlot
|
||||
_palette: Palette
|
||||
_want_svg: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
nrows: int = 1,
|
||||
ncols: int = 1,
|
||||
figsize: tuple[float, float] = (9, 9),
|
||||
show_frame: bool = True,
|
||||
want_svg: bool = False,
|
||||
) -> None:
|
||||
self._want_svg = want_svg
|
||||
self._palette = Category10[10]
|
||||
|
||||
total_size = 100*np.asarray(figsize, dtype=int) # Assuming 100 dpi.
|
||||
|
||||
nfigures = nrows*ncols
|
||||
self._figures = []
|
||||
backend: OutputBackendType = "svg" if self._want_svg else "canvas"
|
||||
for _ in range(nfigures):
|
||||
fig = figure(output_backend=backend)
|
||||
fig.xgrid.visible = False
|
||||
fig.ygrid.visible = False
|
||||
self._figures.append(fig)
|
||||
if not show_frame:
|
||||
fig.outline_line_color = None
|
||||
fig.axis.visible = False
|
||||
|
||||
self._layout = gridplot(
|
||||
self._figures, ncols=ncols, toolbar_location=None, # type: ignore[arg-type]
|
||||
width=total_size[0] // ncols, height=total_size[1] // nrows)
|
||||
|
||||
def _convert_color(self, color: str) -> str:
|
||||
if isinstance(color, str) and color[0] == "C":
|
||||
index = int(color[1:])
|
||||
color = self._palette[index]
|
||||
return color
|
||||
|
||||
def _get_figure(self, ax: figure | int) -> figure:
|
||||
if isinstance(ax, int):
|
||||
ax = self._figures[ax]
|
||||
return ax
|
||||
|
||||
def filled(
|
||||
self,
|
||||
filled: FillReturn,
|
||||
fill_type: FillType | str,
|
||||
ax: figure | int = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 0.7,
|
||||
) -> None:
|
||||
"""Plot filled contours on a single plot.
|
||||
|
||||
Args:
|
||||
filled (sequence of arrays): Filled contour data as returned by
|
||||
:meth:`~.ContourGenerator.filled`.
|
||||
fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` data as returned
|
||||
by :attr:`~.ContourGenerator.fill_type`, or a string equivalent.
|
||||
ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
|
||||
color (str, optional): Color to plot with. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``Category10`` palette. Default ``"C0"``.
|
||||
alpha (float, optional): Opacity to plot with, default ``0.7``.
|
||||
"""
|
||||
fill_type = as_fill_type(fill_type)
|
||||
fig = self._get_figure(ax)
|
||||
color = self._convert_color(color)
|
||||
xs, ys = filled_to_bokeh(filled, fill_type)
|
||||
if len(xs) > 0:
|
||||
fig.multi_polygons(xs=[xs], ys=[ys], color=color, fill_alpha=alpha, line_width=0) # type: ignore[arg-type]
|
||||
|
||||
def grid(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
ax: figure | int = 0,
|
||||
color: str = "black",
|
||||
alpha: float = 0.1,
|
||||
point_color: str | None = None,
|
||||
quad_as_tri_alpha: float = 0,
|
||||
) -> None:
|
||||
"""Plot quad grid lines on a single plot.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
|
||||
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
|
||||
ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
|
||||
color (str, optional): Color to plot grid lines, default ``"black"``.
|
||||
alpha (float, optional): Opacity to plot lines with, default ``0.1``.
|
||||
point_color (str, optional): Color to plot grid points or ``None`` if grid points
|
||||
should not be plotted, default ``None``.
|
||||
quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default
|
||||
``0``.
|
||||
|
||||
Colors may be a string color or the letter ``"C"`` followed by an integer in the range
|
||||
``"C0"`` to ``"C9"`` to use a color from the ``Category10`` palette.
|
||||
|
||||
Warning:
|
||||
``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked.
|
||||
"""
|
||||
fig = self._get_figure(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
xs = list(x) + list(x.T)
|
||||
ys = list(y) + list(y.T)
|
||||
kwargs = {"line_color": color, "alpha": alpha}
|
||||
fig.multi_line(xs, ys, **kwargs)
|
||||
if quad_as_tri_alpha > 0:
|
||||
# Assumes no quad mask.
|
||||
xmid = (0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:])).ravel()
|
||||
ymid = (0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:])).ravel()
|
||||
fig.multi_line(
|
||||
list(np.stack((x[:-1, :-1].ravel(), xmid, x[1:, 1:].ravel()), axis=1)),
|
||||
list(np.stack((y[:-1, :-1].ravel(), ymid, y[1:, 1:].ravel()), axis=1)),
|
||||
**kwargs)
|
||||
fig.multi_line(
|
||||
list(np.stack((x[:-1, 1:].ravel(), xmid, x[1:, :-1].ravel()), axis=1)),
|
||||
list(np.stack((y[:-1, 1:].ravel(), ymid, y[1:, :-1].ravel()), axis=1)),
|
||||
**kwargs)
|
||||
if point_color is not None:
|
||||
fig.scatter(
|
||||
x=x.ravel(), y=y.ravel(), fill_color=color, line_color=None, alpha=alpha,
|
||||
marker="circle", size=8)
|
||||
|
||||
def lines(
|
||||
self,
|
||||
lines: LineReturn,
|
||||
line_type: LineType | str,
|
||||
ax: figure | int = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 1.0,
|
||||
linewidth: float = 1,
|
||||
) -> None:
|
||||
"""Plot contour lines on a single plot.
|
||||
|
||||
Args:
|
||||
lines (sequence of arrays): Contour line data as returned by
|
||||
:meth:`~.ContourGenerator.lines`.
|
||||
line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` data as returned
|
||||
by :attr:`~.ContourGenerator.line_type`, or a string equivalent.
|
||||
ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
|
||||
color (str, optional): Color to plot lines. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``Category10`` palette. Default ``"C0"``.
|
||||
alpha (float, optional): Opacity to plot lines with, default ``1.0``.
|
||||
linewidth (float, optional): Width of lines, default ``1``.
|
||||
|
||||
Note:
|
||||
Assumes all lines are open line strips not closed line loops.
|
||||
"""
|
||||
line_type = as_line_type(line_type)
|
||||
fig = self._get_figure(ax)
|
||||
color = self._convert_color(color)
|
||||
xs, ys = lines_to_bokeh(lines, line_type)
|
||||
if xs is not None:
|
||||
assert ys is not None
|
||||
fig.line(xs, ys, line_color=color, line_alpha=alpha, line_width=linewidth)
|
||||
|
||||
def mask(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike | np.ma.MaskedArray[Any, Any],
|
||||
ax: figure | int = 0,
|
||||
color: str = "black",
|
||||
) -> None:
|
||||
"""Plot masked out grid points as circles on a single plot.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
|
||||
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
|
||||
z (masked array of shape (ny, nx): z-values.
|
||||
ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
|
||||
color (str, optional): Circle color, default ``"black"``.
|
||||
"""
|
||||
mask = np.ma.getmask(z)
|
||||
if mask is np.ma.nomask:
|
||||
return
|
||||
fig = self._get_figure(ax)
|
||||
color = self._convert_color(color)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
fig.scatter(x[mask], y[mask], fill_color=color, marker="circle", size=10)
|
||||
|
||||
def save(
|
||||
self,
|
||||
filename: str,
|
||||
transparent: bool = False,
|
||||
*,
|
||||
webdriver: WebDriver | None = None,
|
||||
) -> None:
|
||||
"""Save plots to SVG or PNG file.
|
||||
|
||||
Args:
|
||||
filename (str): Filename to save to.
|
||||
transparent (bool, optional): Whether background should be transparent, default
|
||||
``False``.
|
||||
webdriver (WebDriver, optional): Selenium WebDriver instance to use to create the image.
|
||||
|
||||
.. versionadded:: 1.1.1
|
||||
|
||||
Warning:
|
||||
To output to SVG file, ``want_svg=True`` must have been passed to the constructor.
|
||||
"""
|
||||
if transparent:
|
||||
for fig in self._figures:
|
||||
fig.background_fill_color = None
|
||||
fig.border_fill_color = None
|
||||
|
||||
if self._want_svg:
|
||||
export_svg(self._layout, filename=filename, webdriver=webdriver)
|
||||
else:
|
||||
export_png(self._layout, filename=filename, webdriver=webdriver)
|
||||
|
||||
def save_to_buffer(self, *, webdriver: WebDriver | None = None) -> io.BytesIO:
|
||||
"""Save plots to an ``io.BytesIO`` buffer.
|
||||
|
||||
Args:
|
||||
webdriver (WebDriver, optional): Selenium WebDriver instance to use to create the image.
|
||||
|
||||
.. versionadded:: 1.1.1
|
||||
|
||||
Return:
|
||||
BytesIO: PNG image buffer.
|
||||
"""
|
||||
image = get_screenshot_as_png(self._layout, driver=webdriver)
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, "png")
|
||||
return buffer
|
||||
|
||||
def show(self) -> None:
|
||||
"""Show plots in web browser, in usual Bokeh manner.
|
||||
"""
|
||||
show(self._layout)
|
||||
|
||||
def title(self, title: str, ax: figure | int = 0, color: str | None = None) -> None:
|
||||
"""Set the title of a single plot.
|
||||
|
||||
Args:
|
||||
title (str): Title text.
|
||||
ax (int or Bokeh Figure, optional): Which plot to set the title of, default ``0``.
|
||||
color (str, optional): Color to set title. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``Category10`` palette. Default ``None`` which is ``black``.
|
||||
"""
|
||||
fig = self._get_figure(ax)
|
||||
fig.title = title
|
||||
fig.title.align = "center" # type: ignore[attr-defined]
|
||||
if color is not None:
|
||||
fig.title.text_color = self._convert_color(color) # type: ignore[attr-defined]
|
||||
|
||||
def z_values(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
ax: figure | int = 0,
|
||||
color: str = "green",
|
||||
fmt: str = ".1f",
|
||||
quad_as_tri: bool = False,
|
||||
) -> None:
|
||||
"""Show ``z`` values on a single plot.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
|
||||
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
|
||||
z (array-like of shape (ny, nx): z-values.
|
||||
ax (int or Bokeh Figure, optional): Which plot to use, default ``0``.
|
||||
color (str, optional): Color of added text. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``Category10`` palette. Default ``"green"``.
|
||||
fmt (str, optional): Format to display z-values, default ``".1f"``.
|
||||
quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centres
|
||||
of quads.
|
||||
|
||||
Warning:
|
||||
``quad_as_tri=True`` shows z-values for all quads, even if masked.
|
||||
"""
|
||||
fig = self._get_figure(ax)
|
||||
color = self._convert_color(color)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
z = np.asarray(z)
|
||||
ny, nx = z.shape
|
||||
kwargs = {"text_color": color, "text_align": "center", "text_baseline": "middle"}
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
label = Label(x=x[j, i], y=y[j, i], text=f"{z[j, i]:{fmt}}", **kwargs) # type: ignore[arg-type]
|
||||
fig.add_layout(label)
|
||||
if quad_as_tri:
|
||||
for j in range(ny-1):
|
||||
for i in range(nx-1):
|
||||
xx = np.mean(x[j:j+2, i:i+2])
|
||||
yy = np.mean(y[j:j+2, i:i+2])
|
||||
zz = np.mean(z[j:j+2, i:i+2])
|
||||
fig.add_layout(Label(x=xx, y=yy, text=f"{zz:{fmt}}", **kwargs)) # type: ignore[arg-type]
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from contourpy import FillType, LineType
|
||||
from contourpy.array import offsets_from_codes
|
||||
from contourpy.convert import convert_lines
|
||||
from contourpy.dechunk import dechunk_lines
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from contourpy._contourpy import (
|
||||
CoordinateArray,
|
||||
FillReturn,
|
||||
LineReturn,
|
||||
LineReturn_ChunkCombinedNan,
|
||||
)
|
||||
|
||||
|
||||
def filled_to_bokeh(
|
||||
filled: FillReturn,
|
||||
fill_type: FillType,
|
||||
) -> tuple[list[list[CoordinateArray]], list[list[CoordinateArray]]]:
|
||||
xs: list[list[CoordinateArray]] = []
|
||||
ys: list[list[CoordinateArray]] = []
|
||||
if fill_type in (FillType.OuterOffset, FillType.ChunkCombinedOffset,
|
||||
FillType.OuterCode, FillType.ChunkCombinedCode):
|
||||
have_codes = fill_type in (FillType.OuterCode, FillType.ChunkCombinedCode)
|
||||
|
||||
for points, offsets in zip(*filled):
|
||||
if points is None:
|
||||
continue
|
||||
if have_codes:
|
||||
offsets = offsets_from_codes(offsets)
|
||||
xs.append([]) # New outer with zero or more holes.
|
||||
ys.append([])
|
||||
for i in range(len(offsets)-1):
|
||||
xys = points[offsets[i]:offsets[i+1]]
|
||||
xs[-1].append(xys[:, 0])
|
||||
ys[-1].append(xys[:, 1])
|
||||
elif fill_type in (FillType.ChunkCombinedCodeOffset, FillType.ChunkCombinedOffsetOffset):
|
||||
for points, codes_or_offsets, outer_offsets in zip(*filled):
|
||||
if points is None:
|
||||
continue
|
||||
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 = 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.
|
||||
ys.append([])
|
||||
for k in range(len(offsets)-1):
|
||||
xys = points[offsets[k]:offsets[k+1]]
|
||||
xs[-1].append(xys[:, 0])
|
||||
ys[-1].append(xys[:, 1])
|
||||
else:
|
||||
raise RuntimeError(f"Conversion of FillType {fill_type} to Bokeh is not implemented")
|
||||
|
||||
return xs, ys
|
||||
|
||||
|
||||
def lines_to_bokeh(
|
||||
lines: LineReturn,
|
||||
line_type: LineType,
|
||||
) -> tuple[CoordinateArray | None, CoordinateArray | None]:
|
||||
lines = convert_lines(lines, line_type, LineType.ChunkCombinedNan)
|
||||
lines = dechunk_lines(lines, LineType.ChunkCombinedNan)
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(LineReturn_ChunkCombinedNan, lines)
|
||||
points = lines[0][0]
|
||||
if points is None:
|
||||
return None, None
|
||||
else:
|
||||
return points[:, 0], points[:, 1]
|
||||
78
venv/lib/python3.13/site-packages/contourpy/util/data.py
Normal file
78
venv/lib/python3.13/site-packages/contourpy/util/data.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from contourpy._contourpy import CoordinateArray
|
||||
|
||||
|
||||
def simple(
|
||||
shape: tuple[int, int], want_mask: bool = False,
|
||||
) -> tuple[CoordinateArray, CoordinateArray, CoordinateArray | np.ma.MaskedArray[Any, Any]]:
|
||||
"""Return simple test data consisting of the sum of two gaussians.
|
||||
|
||||
Args:
|
||||
shape (tuple(int, int)): 2D shape of data to return.
|
||||
want_mask (bool, optional): Whether test data should be masked or not, default ``False``.
|
||||
|
||||
Return:
|
||||
Tuple of 3 arrays: ``x``, ``y``, ``z`` test data, ``z`` will be masked if
|
||||
``want_mask=True``.
|
||||
"""
|
||||
ny, nx = shape
|
||||
x = np.arange(nx, dtype=np.float64)
|
||||
y = np.arange(ny, dtype=np.float64)
|
||||
x, y = np.meshgrid(x, y)
|
||||
|
||||
xscale = nx - 1.0
|
||||
yscale = ny - 1.0
|
||||
|
||||
# z is sum of 2D gaussians.
|
||||
amp = np.asarray([1.0, -1.0, 0.8, -0.9, 0.7])
|
||||
mid = np.asarray([[0.4, 0.2], [0.3, 0.8], [0.9, 0.75], [0.7, 0.3], [0.05, 0.7]])
|
||||
width = np.asarray([0.4, 0.2, 0.2, 0.2, 0.1])
|
||||
|
||||
z = np.zeros_like(x)
|
||||
for i in range(len(amp)):
|
||||
z += amp[i]*np.exp(-((x/xscale - mid[i, 0])**2 + (y/yscale - mid[i, 1])**2) / width[i]**2)
|
||||
|
||||
if want_mask:
|
||||
mask = np.logical_or(
|
||||
((x/xscale - 1.0)**2 / 0.2 + (y/yscale - 0.0)**2 / 0.1) < 1.0,
|
||||
((x/xscale - 0.2)**2 / 0.02 + (y/yscale - 0.45)**2 / 0.08) < 1.0,
|
||||
)
|
||||
z = np.ma.array(z, mask=mask) # type: ignore[no-untyped-call]
|
||||
|
||||
return x, y, z
|
||||
|
||||
|
||||
def random(
|
||||
shape: tuple[int, int], seed: int = 2187, mask_fraction: float = 0.0,
|
||||
) -> tuple[CoordinateArray, CoordinateArray, CoordinateArray | np.ma.MaskedArray[Any, Any]]:
|
||||
"""Return random test data in the range 0 to 1.
|
||||
|
||||
Args:
|
||||
shape (tuple(int, int)): 2D shape of data to return.
|
||||
seed (int, optional): Seed for random number generator, default 2187.
|
||||
mask_fraction (float, optional): Fraction of elements to mask, default 0.
|
||||
|
||||
Return:
|
||||
Tuple of 3 arrays: ``x``, ``y``, ``z`` test data, ``z`` will be masked if
|
||||
``mask_fraction`` is greater than zero.
|
||||
"""
|
||||
ny, nx = shape
|
||||
x = np.arange(nx, dtype=np.float64)
|
||||
y = np.arange(ny, dtype=np.float64)
|
||||
x, y = np.meshgrid(x, y)
|
||||
|
||||
rng = np.random.default_rng(seed)
|
||||
z = rng.uniform(size=shape)
|
||||
|
||||
if mask_fraction > 0.0:
|
||||
mask_fraction = min(mask_fraction, 0.99)
|
||||
mask = rng.uniform(size=shape) < mask_fraction
|
||||
z = np.ma.array(z, mask=mask) # type: ignore[no-untyped-call]
|
||||
|
||||
return x, y, z
|
||||
535
venv/lib/python3.13/site-packages/contourpy/util/mpl_renderer.py
Normal file
535
venv/lib/python3.13/site-packages/contourpy/util/mpl_renderer.py
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from itertools import pairwise
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import matplotlib.collections as mcollections
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from contourpy import FillType, LineType
|
||||
from contourpy.convert import convert_filled, convert_lines
|
||||
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
|
||||
from contourpy.util.renderer import Renderer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
from matplotlib.axes import Axes
|
||||
from matplotlib.figure import Figure
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
import contourpy._contourpy as cpy
|
||||
|
||||
|
||||
class MplRenderer(Renderer):
|
||||
"""Utility renderer using Matplotlib to render a grid of plots over the same (x, y) range.
|
||||
|
||||
Args:
|
||||
nrows (int, optional): Number of rows of plots, default ``1``.
|
||||
ncols (int, optional): Number of columns of plots, default ``1``.
|
||||
figsize (tuple(float, float), optional): Figure size in inches, default ``(9, 9)``.
|
||||
show_frame (bool, optional): Whether to show frame and axes ticks, default ``True``.
|
||||
backend (str, optional): Matplotlib backend to use or ``None`` for default backend.
|
||||
Default ``None``.
|
||||
gridspec_kw (dict, optional): Gridspec keyword arguments to pass to ``plt.subplots``,
|
||||
default None.
|
||||
"""
|
||||
_axes: Sequence[Axes]
|
||||
_fig: Figure
|
||||
_want_tight: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
nrows: int = 1,
|
||||
ncols: int = 1,
|
||||
figsize: tuple[float, float] = (9, 9),
|
||||
show_frame: bool = True,
|
||||
backend: str | None = None,
|
||||
gridspec_kw: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
if backend is not None:
|
||||
import matplotlib as mpl
|
||||
mpl.use(backend)
|
||||
|
||||
kwargs: dict[str, Any] = {"figsize": figsize, "squeeze": False,
|
||||
"sharex": True, "sharey": True}
|
||||
if gridspec_kw is not None:
|
||||
kwargs["gridspec_kw"] = gridspec_kw
|
||||
else:
|
||||
kwargs["subplot_kw"] = {"aspect": "equal"}
|
||||
|
||||
self._fig, axes = plt.subplots(nrows, ncols, **kwargs)
|
||||
self._axes = axes.flatten()
|
||||
if not show_frame:
|
||||
for ax in self._axes:
|
||||
ax.axis("off")
|
||||
|
||||
self._want_tight = True
|
||||
|
||||
def __del__(self) -> None:
|
||||
if hasattr(self, "_fig"):
|
||||
plt.close(self._fig)
|
||||
|
||||
def _autoscale(self) -> None:
|
||||
# Using axes._need_autoscale attribute if need to autoscale before rendering after adding
|
||||
# lines/filled. Only want to autoscale once per axes regardless of how many lines/filled
|
||||
# added.
|
||||
for ax in self._axes:
|
||||
if getattr(ax, "_need_autoscale", False):
|
||||
ax.autoscale_view(tight=True)
|
||||
ax._need_autoscale = False # type: ignore[attr-defined]
|
||||
if self._want_tight and len(self._axes) > 1:
|
||||
self._fig.tight_layout()
|
||||
|
||||
def _get_ax(self, ax: Axes | int) -> Axes:
|
||||
if isinstance(ax, int):
|
||||
ax = self._axes[ax]
|
||||
return ax
|
||||
|
||||
def filled(
|
||||
self,
|
||||
filled: cpy.FillReturn,
|
||||
fill_type: FillType | str,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 0.7,
|
||||
) -> None:
|
||||
"""Plot filled contours on a single Axes.
|
||||
|
||||
Args:
|
||||
filled (sequence of arrays): Filled contour data as returned by
|
||||
:meth:`~.ContourGenerator.filled`.
|
||||
fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` data as returned
|
||||
by :attr:`~.ContourGenerator.fill_type`, or string equivalent
|
||||
ax (int or Maplotlib Axes, optional): Which axes to plot on, default ``0``.
|
||||
color (str, optional): Color to plot with. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``tab10`` colormap. Default ``"C0"``.
|
||||
alpha (float, optional): Opacity to plot with, default ``0.7``.
|
||||
"""
|
||||
fill_type = as_fill_type(fill_type)
|
||||
ax = self._get_ax(ax)
|
||||
paths = filled_to_mpl_paths(filled, fill_type)
|
||||
collection = mcollections.PathCollection(
|
||||
paths, facecolors=color, edgecolors="none", lw=0, alpha=alpha)
|
||||
ax.add_collection(collection)
|
||||
ax._need_autoscale = True # type: ignore[attr-defined]
|
||||
|
||||
def grid(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "black",
|
||||
alpha: float = 0.1,
|
||||
point_color: str | None = None,
|
||||
quad_as_tri_alpha: float = 0,
|
||||
) -> None:
|
||||
"""Plot quad grid lines on a single Axes.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
|
||||
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
|
||||
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
|
||||
color (str, optional): Color to plot grid lines, default ``"black"``.
|
||||
alpha (float, optional): Opacity to plot lines with, default ``0.1``.
|
||||
point_color (str, optional): Color to plot grid points or ``None`` if grid points
|
||||
should not be plotted, default ``None``.
|
||||
quad_as_tri_alpha (float, optional): Opacity to plot ``quad_as_tri`` grid, default 0.
|
||||
|
||||
Colors may be a string color or the letter ``"C"`` followed by an integer in the range
|
||||
``"C0"`` to ``"C9"`` to use a color from the ``tab10`` colormap.
|
||||
|
||||
Warning:
|
||||
``quad_as_tri_alpha > 0`` plots all quads as though they are unmasked.
|
||||
"""
|
||||
ax = self._get_ax(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
kwargs: dict[str, Any] = {"color": color, "alpha": alpha}
|
||||
ax.plot(x, y, x.T, y.T, **kwargs)
|
||||
if quad_as_tri_alpha > 0:
|
||||
# Assumes no quad mask.
|
||||
xmid = 0.25*(x[:-1, :-1] + x[1:, :-1] + x[:-1, 1:] + x[1:, 1:])
|
||||
ymid = 0.25*(y[:-1, :-1] + y[1:, :-1] + y[:-1, 1:] + y[1:, 1:])
|
||||
kwargs["alpha"] = quad_as_tri_alpha
|
||||
ax.plot(
|
||||
np.stack((x[:-1, :-1], xmid, x[1:, 1:])).reshape((3, -1)),
|
||||
np.stack((y[:-1, :-1], ymid, y[1:, 1:])).reshape((3, -1)),
|
||||
np.stack((x[1:, :-1], xmid, x[:-1, 1:])).reshape((3, -1)),
|
||||
np.stack((y[1:, :-1], ymid, y[:-1, 1:])).reshape((3, -1)),
|
||||
**kwargs)
|
||||
if point_color is not None:
|
||||
ax.plot(x, y, color=point_color, alpha=alpha, marker="o", lw=0)
|
||||
ax._need_autoscale = True # type: ignore[attr-defined]
|
||||
|
||||
def lines(
|
||||
self,
|
||||
lines: cpy.LineReturn,
|
||||
line_type: LineType | str,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 1.0,
|
||||
linewidth: float = 1,
|
||||
) -> None:
|
||||
"""Plot contour lines on a single Axes.
|
||||
|
||||
Args:
|
||||
lines (sequence of arrays): Contour line data as returned by
|
||||
:meth:`~.ContourGenerator.lines`.
|
||||
line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` data as returned
|
||||
by :attr:`~.ContourGenerator.line_type`, or string equivalent.
|
||||
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
|
||||
color (str, optional): Color to plot lines. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``tab10`` colormap. Default ``"C0"``.
|
||||
alpha (float, optional): Opacity to plot lines with, default ``1.0``.
|
||||
linewidth (float, optional): Width of lines, default ``1``.
|
||||
"""
|
||||
line_type = as_line_type(line_type)
|
||||
ax = self._get_ax(ax)
|
||||
paths = lines_to_mpl_paths(lines, line_type)
|
||||
collection = mcollections.PathCollection(
|
||||
paths, facecolors="none", edgecolors=color, lw=linewidth, alpha=alpha)
|
||||
ax.add_collection(collection)
|
||||
ax._need_autoscale = True # type: ignore[attr-defined]
|
||||
|
||||
def mask(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike | np.ma.MaskedArray[Any, Any],
|
||||
ax: Axes | int = 0,
|
||||
color: str = "black",
|
||||
) -> None:
|
||||
"""Plot masked out grid points as circles on a single Axes.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
|
||||
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
|
||||
z (masked array of shape (ny, nx): z-values.
|
||||
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
|
||||
color (str, optional): Circle color, default ``"black"``.
|
||||
"""
|
||||
mask = np.ma.getmask(z)
|
||||
if mask is np.ma.nomask:
|
||||
return
|
||||
ax = self._get_ax(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
ax.plot(x[mask], y[mask], "o", c=color)
|
||||
|
||||
def save(self, filename: str, transparent: bool = False) -> None:
|
||||
"""Save plots to SVG or PNG file.
|
||||
|
||||
Args:
|
||||
filename (str): Filename to save to.
|
||||
transparent (bool, optional): Whether background should be transparent, default
|
||||
``False``.
|
||||
"""
|
||||
self._autoscale()
|
||||
self._fig.savefig(filename, transparent=transparent)
|
||||
|
||||
def save_to_buffer(self) -> io.BytesIO:
|
||||
"""Save plots to an ``io.BytesIO`` buffer.
|
||||
|
||||
Return:
|
||||
BytesIO: PNG image buffer.
|
||||
"""
|
||||
self._autoscale()
|
||||
buf = io.BytesIO()
|
||||
self._fig.savefig(buf, format="png")
|
||||
buf.seek(0)
|
||||
return buf
|
||||
|
||||
def show(self) -> None:
|
||||
"""Show plots in an interactive window, in the usual Matplotlib manner.
|
||||
"""
|
||||
self._autoscale()
|
||||
plt.show()
|
||||
|
||||
def title(self, title: str, ax: Axes | int = 0, color: str | None = None) -> None:
|
||||
"""Set the title of a single Axes.
|
||||
|
||||
Args:
|
||||
title (str): Title text.
|
||||
ax (int or Matplotlib Axes, optional): Which Axes to set the title of, default ``0``.
|
||||
color (str, optional): Color to set title. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``tab10`` colormap. Default is ``None`` which uses Matplotlib's default title color
|
||||
that depends on the stylesheet in use.
|
||||
"""
|
||||
if color:
|
||||
self._get_ax(ax).set_title(title, color=color)
|
||||
else:
|
||||
self._get_ax(ax).set_title(title)
|
||||
|
||||
def z_values(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "green",
|
||||
fmt: str = ".1f",
|
||||
quad_as_tri: bool = False,
|
||||
) -> None:
|
||||
"""Show ``z`` values on a single Axes.
|
||||
|
||||
Args:
|
||||
x (array-like of shape (ny, nx) or (nx,)): The x-coordinates of the grid points.
|
||||
y (array-like of shape (ny, nx) or (ny,)): The y-coordinates of the grid points.
|
||||
z (array-like of shape (ny, nx): z-values.
|
||||
ax (int or Matplotlib Axes, optional): Which Axes to plot on, default ``0``.
|
||||
color (str, optional): Color of added text. May be a string color or the letter ``"C"``
|
||||
followed by an integer in the range ``"C0"`` to ``"C9"`` to use a color from the
|
||||
``tab10`` colormap. Default ``"green"``.
|
||||
fmt (str, optional): Format to display z-values, default ``".1f"``.
|
||||
quad_as_tri (bool, optional): Whether to show z-values at the ``quad_as_tri`` centers
|
||||
of quads.
|
||||
|
||||
Warning:
|
||||
``quad_as_tri=True`` shows z-values for all quads, even if masked.
|
||||
"""
|
||||
ax = self._get_ax(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
z = np.asarray(z)
|
||||
ny, nx = z.shape
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
ax.text(x[j, i], y[j, i], f"{z[j, i]:{fmt}}", ha="center", va="center",
|
||||
color=color, clip_on=True)
|
||||
if quad_as_tri:
|
||||
for j in range(ny-1):
|
||||
for i in range(nx-1):
|
||||
xx = np.mean(x[j:j+2, i:i+2], dtype=np.float64)
|
||||
yy = np.mean(y[j:j+2, i:i+2], dtype=np.float64)
|
||||
zz = np.mean(z[j:j+2, i:i+2])
|
||||
ax.text(xx, yy, f"{zz:{fmt}}", ha="center", va="center", color=color,
|
||||
clip_on=True)
|
||||
|
||||
|
||||
class MplTestRenderer(MplRenderer):
|
||||
"""Test renderer implemented using Matplotlib.
|
||||
|
||||
No whitespace around plots and no spines/ticks displayed.
|
||||
Uses Agg backend, so can only save to file/buffer, cannot call ``show()``.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
nrows: int = 1,
|
||||
ncols: int = 1,
|
||||
figsize: tuple[float, float] = (9, 9),
|
||||
) -> None:
|
||||
gridspec = {
|
||||
"left": 0.01,
|
||||
"right": 0.99,
|
||||
"top": 0.99,
|
||||
"bottom": 0.01,
|
||||
"wspace": 0.01,
|
||||
"hspace": 0.01,
|
||||
}
|
||||
super().__init__(
|
||||
nrows, ncols, figsize, show_frame=True, backend="Agg", gridspec_kw=gridspec,
|
||||
)
|
||||
|
||||
for ax in self._axes:
|
||||
ax.set_xmargin(0.0)
|
||||
ax.set_ymargin(0.0)
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
|
||||
self._want_tight = False
|
||||
|
||||
|
||||
class MplDebugRenderer(MplRenderer):
|
||||
"""Debug renderer implemented using Matplotlib.
|
||||
|
||||
Extends ``MplRenderer`` to add extra information to help in debugging such as markers, arrows,
|
||||
text, etc.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
nrows: int = 1,
|
||||
ncols: int = 1,
|
||||
figsize: tuple[float, float] = (9, 9),
|
||||
show_frame: bool = True,
|
||||
) -> None:
|
||||
super().__init__(nrows, ncols, figsize, show_frame)
|
||||
|
||||
def _arrow(
|
||||
self,
|
||||
ax: Axes,
|
||||
line_start: cpy.CoordinateArray,
|
||||
line_end: cpy.CoordinateArray,
|
||||
color: str,
|
||||
alpha: float,
|
||||
arrow_size: float,
|
||||
) -> None:
|
||||
mid = 0.5*(line_start + line_end)
|
||||
along = line_end - line_start
|
||||
along /= np.sqrt(np.dot(along, along)) # Unit vector.
|
||||
right = np.asarray((along[1], -along[0]))
|
||||
arrow = np.stack((
|
||||
mid - (along*0.5 - right)*arrow_size,
|
||||
mid + along*0.5*arrow_size,
|
||||
mid - (along*0.5 + right)*arrow_size,
|
||||
))
|
||||
ax.plot(arrow[:, 0], arrow[:, 1], "-", c=color, alpha=alpha)
|
||||
|
||||
def filled(
|
||||
self,
|
||||
filled: cpy.FillReturn,
|
||||
fill_type: FillType | str,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "C1",
|
||||
alpha: float = 0.7,
|
||||
line_color: str = "C0",
|
||||
line_alpha: float = 0.7,
|
||||
point_color: str = "C0",
|
||||
start_point_color: str = "red",
|
||||
arrow_size: float = 0.1,
|
||||
) -> None:
|
||||
fill_type = as_fill_type(fill_type)
|
||||
super().filled(filled, fill_type, ax, color, alpha)
|
||||
|
||||
if line_color is None and point_color is None:
|
||||
return
|
||||
|
||||
ax = self._get_ax(ax)
|
||||
filled = convert_filled(filled, fill_type, FillType.ChunkCombinedOffset)
|
||||
|
||||
# Lines.
|
||||
if line_color is not None:
|
||||
for points, offsets in zip(*filled):
|
||||
if points is None:
|
||||
continue
|
||||
for start, end in pairwise(offsets):
|
||||
xys = points[start:end]
|
||||
ax.plot(xys[:, 0], xys[:, 1], c=line_color, alpha=line_alpha)
|
||||
|
||||
if arrow_size > 0.0:
|
||||
n = len(xys)
|
||||
for i in range(n-1):
|
||||
self._arrow(ax, xys[i], xys[i+1], line_color, line_alpha, arrow_size)
|
||||
|
||||
# Points.
|
||||
if point_color is not None:
|
||||
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:
|
||||
start_indices = offsets[:-1]
|
||||
mask[start_indices] = False # Exclude start points.
|
||||
ax.plot(
|
||||
points[:, 0][mask], points[:, 1][mask], "o", c=point_color, alpha=line_alpha)
|
||||
|
||||
if start_point_color is not None:
|
||||
ax.plot(points[:, 0][start_indices], points[:, 1][start_indices], "o",
|
||||
c=start_point_color, alpha=line_alpha)
|
||||
|
||||
def lines(
|
||||
self,
|
||||
lines: cpy.LineReturn,
|
||||
line_type: LineType | str,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 1.0,
|
||||
linewidth: float = 1,
|
||||
point_color: str = "C0",
|
||||
start_point_color: str = "red",
|
||||
arrow_size: float = 0.1,
|
||||
) -> None:
|
||||
line_type = as_line_type(line_type)
|
||||
super().lines(lines, line_type, ax, color, alpha, linewidth)
|
||||
|
||||
if arrow_size == 0.0 and point_color is None:
|
||||
return
|
||||
|
||||
ax = self._get_ax(ax)
|
||||
separate_lines = convert_lines(lines, line_type, LineType.Separate)
|
||||
if TYPE_CHECKING:
|
||||
separate_lines = cast(cpy.LineReturn_Separate, separate_lines)
|
||||
|
||||
if arrow_size > 0.0:
|
||||
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 separate_lines:
|
||||
start_index = 0
|
||||
end_index = len(line)
|
||||
if start_point_color is not None:
|
||||
ax.plot(line[0, 0], line[0, 1], "o", c=start_point_color, alpha=alpha)
|
||||
start_index = 1
|
||||
if line[0][0] == line[-1][0] and line[0][1] == line[-1][1]:
|
||||
end_index -= 1
|
||||
ax.plot(line[start_index:end_index, 0], line[start_index:end_index, 1], "o",
|
||||
c=color, alpha=alpha)
|
||||
|
||||
def point_numbers(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "red",
|
||||
) -> None:
|
||||
ax = self._get_ax(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
z = np.asarray(z)
|
||||
ny, nx = z.shape
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
quad = i + j*nx
|
||||
ax.text(x[j, i], y[j, i], str(quad), ha="right", va="top", color=color,
|
||||
clip_on=True)
|
||||
|
||||
def quad_numbers(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "blue",
|
||||
) -> None:
|
||||
ax = self._get_ax(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
z = np.asarray(z)
|
||||
ny, nx = z.shape
|
||||
for j in range(1, ny):
|
||||
for i in range(1, nx):
|
||||
quad = i + j*nx
|
||||
xmid = x[j-1:j+1, i-1:i+1].mean()
|
||||
ymid = y[j-1:j+1, i-1:i+1].mean()
|
||||
ax.text(xmid, ymid, str(quad), ha="center", va="center", color=color, clip_on=True)
|
||||
|
||||
def z_levels(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
lower_level: float,
|
||||
upper_level: float | None = None,
|
||||
ax: Axes | int = 0,
|
||||
color: str = "green",
|
||||
) -> None:
|
||||
ax = self._get_ax(ax)
|
||||
x, y = self._grid_as_2d(x, y)
|
||||
z = np.asarray(z)
|
||||
ny, nx = z.shape
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
zz = z[j, i]
|
||||
if upper_level is not None and zz > upper_level:
|
||||
z_level = 2
|
||||
elif zz > lower_level:
|
||||
z_level = 1
|
||||
else:
|
||||
z_level = 0
|
||||
ax.text(x[j, i], y[j, i], str(z_level), ha="left", va="bottom", color=color,
|
||||
clip_on=True)
|
||||
77
venv/lib/python3.13/site-packages/contourpy/util/mpl_util.py
Normal file
77
venv/lib/python3.13/site-packages/contourpy/util/mpl_util.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from itertools import pairwise
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
import matplotlib.path as mpath
|
||||
import numpy as np
|
||||
|
||||
from contourpy import FillType, LineType
|
||||
from contourpy.array import codes_from_offsets
|
||||
|
||||
if TYPE_CHECKING:
|
||||
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, codes_from_offsets(offsets))
|
||||
for points, offsets in zip(*filled) if points is not None]
|
||||
elif fill_type == FillType.ChunkCombinedCodeOffset:
|
||||
paths = []
|
||||
for points, codes, outer_offsets in zip(*filled):
|
||||
if points is None:
|
||||
continue
|
||||
points = np.split(points, outer_offsets[1:-1])
|
||||
codes = np.split(codes, outer_offsets[1:-1])
|
||||
paths += [mpath.Path(p, c) for p, c in zip(points, codes)]
|
||||
elif fill_type == FillType.ChunkCombinedOffsetOffset:
|
||||
paths = []
|
||||
for points, offsets, outer_offsets in zip(*filled):
|
||||
if points is None:
|
||||
continue
|
||||
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, codes_from_offsets(offs - offs[0]))]
|
||||
else:
|
||||
raise RuntimeError(f"Conversion of FillType {fill_type} to MPL Paths is not implemented")
|
||||
return paths
|
||||
|
||||
|
||||
def lines_to_mpl_paths(lines: LineReturn, line_type: LineType) -> list[mpath.Path]:
|
||||
if line_type == LineType.Separate:
|
||||
if TYPE_CHECKING:
|
||||
lines = cast(LineReturn_Separate, lines)
|
||||
paths = []
|
||||
for line in lines:
|
||||
# Drawing as Paths so that they can be closed correctly.
|
||||
closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
|
||||
paths.append(mpath.Path(line, closed=closed))
|
||||
elif line_type in (LineType.SeparateCode, LineType.ChunkCombinedCode):
|
||||
paths = [mpath.Path(points, codes) for points, codes in zip(*lines) if points is not None]
|
||||
elif line_type == LineType.ChunkCombinedOffset:
|
||||
paths = []
|
||||
for points, offsets in zip(*lines):
|
||||
if points is None:
|
||||
continue
|
||||
for i in range(len(offsets)-1):
|
||||
line = points[offsets[i]:offsets[i+1]]
|
||||
closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
|
||||
paths.append(mpath.Path(line, closed=closed))
|
||||
elif line_type == LineType.ChunkCombinedNan:
|
||||
paths = []
|
||||
for points in lines[0]:
|
||||
if points is None:
|
||||
continue
|
||||
nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
|
||||
nan_offsets = np.concatenate([[-1], nan_offsets, [len(points)]])
|
||||
for s, e in pairwise(nan_offsets):
|
||||
line = points[s+1:e]
|
||||
closed = line[0, 0] == line[-1, 0] and line[0, 1] == line[-1, 1]
|
||||
paths.append(mpath.Path(line, closed=closed))
|
||||
else:
|
||||
raise RuntimeError(f"Conversion of LineType {line_type} to MPL Paths is not implemented")
|
||||
return paths
|
||||
166
venv/lib/python3.13/site-packages/contourpy/util/renderer.py
Normal file
166
venv/lib/python3.13/site-packages/contourpy/util/renderer.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import io
|
||||
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from contourpy._contourpy import CoordinateArray, FillReturn, FillType, LineReturn, LineType
|
||||
|
||||
|
||||
class Renderer(ABC):
|
||||
"""Abstract base class for renderers."""
|
||||
|
||||
def _grid_as_2d(self, x: ArrayLike, y: ArrayLike) -> tuple[CoordinateArray, CoordinateArray]:
|
||||
x = np.asarray(x)
|
||||
y = np.asarray(y)
|
||||
if x.ndim == 1:
|
||||
x, y = np.meshgrid(x, y)
|
||||
return x, y
|
||||
|
||||
@abstractmethod
|
||||
def filled(
|
||||
self,
|
||||
filled: FillReturn,
|
||||
fill_type: FillType | str,
|
||||
ax: Any = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 0.7,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def grid(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
ax: Any = 0,
|
||||
color: str = "black",
|
||||
alpha: float = 0.1,
|
||||
point_color: str | None = None,
|
||||
quad_as_tri_alpha: float = 0,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def lines(
|
||||
self,
|
||||
lines: LineReturn,
|
||||
line_type: LineType | str,
|
||||
ax: Any = 0,
|
||||
color: str = "C0",
|
||||
alpha: float = 1.0,
|
||||
linewidth: float = 1,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mask(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike | np.ma.MaskedArray[Any, Any],
|
||||
ax: Any = 0,
|
||||
color: str = "black",
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def multi_filled(
|
||||
self,
|
||||
multi_filled: list[FillReturn],
|
||||
fill_type: FillType | str,
|
||||
ax: Any = 0,
|
||||
color: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Plot multiple sets of filled contours on a single axes.
|
||||
|
||||
Args:
|
||||
multi_filled (list of filled contour arrays): Multiple filled contour sets as returned
|
||||
by :meth:`.ContourGenerator.multi_filled`.
|
||||
fill_type (FillType or str): Type of filled data as returned by
|
||||
:attr:`~.ContourGenerator.fill_type`, or string equivalent.
|
||||
ax (int or Renderer-specific axes or figure object, optional): Which axes to plot on,
|
||||
default ``0``.
|
||||
color (str or None, optional): If a string color then this same color is used for all
|
||||
filled contours. If ``None``, the default, then the filled contour sets use colors
|
||||
from the ``tab10`` colormap in order, wrapping around to the beginning if more than
|
||||
10 sets of filled contours are rendered.
|
||||
kwargs: All other keyword argument are passed on to
|
||||
:meth:`.Renderer.filled` unchanged.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
"""
|
||||
if color is not None:
|
||||
kwargs["color"] = color
|
||||
for i, filled in enumerate(multi_filled):
|
||||
if color is None:
|
||||
kwargs["color"] = f"C{i % 10}"
|
||||
self.filled(filled, fill_type, ax, **kwargs)
|
||||
|
||||
def multi_lines(
|
||||
self,
|
||||
multi_lines: list[LineReturn],
|
||||
line_type: LineType | str,
|
||||
ax: Any = 0,
|
||||
color: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Plot multiple sets of contour lines on a single axes.
|
||||
|
||||
Args:
|
||||
multi_lines (list of contour line arrays): Multiple contour line sets as returned by
|
||||
:meth:`.ContourGenerator.multi_lines`.
|
||||
line_type (LineType or str): Type of line data as returned by
|
||||
:attr:`~.ContourGenerator.line_type`, or string equivalent.
|
||||
ax (int or Renderer-specific axes or figure object, optional): Which axes to plot on,
|
||||
default ``0``.
|
||||
color (str or None, optional): If a string color then this same color is used for all
|
||||
lines. If ``None``, the default, then the line sets use colors from the ``tab10``
|
||||
colormap in order, wrapping around to the beginning if more than 10 sets of lines
|
||||
are rendered.
|
||||
kwargs: All other keyword argument are passed on to
|
||||
:meth:`Renderer.lines` unchanged.
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
"""
|
||||
if color is not None:
|
||||
kwargs["color"] = color
|
||||
for i, lines in enumerate(multi_lines):
|
||||
if color is None:
|
||||
kwargs["color"] = f"C{i % 10}"
|
||||
self.lines(lines, line_type, ax, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
def save(self, filename: str, transparent: bool = False) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_to_buffer(self) -> io.BytesIO:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def show(self) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def title(self, title: str, ax: Any = 0, color: str | None = None) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def z_values(
|
||||
self,
|
||||
x: ArrayLike,
|
||||
y: ArrayLike,
|
||||
z: ArrayLike,
|
||||
ax: Any = 0,
|
||||
color: str = "green",
|
||||
fmt: str = ".1f",
|
||||
quad_as_tri: bool = False,
|
||||
) -> None:
|
||||
pass
|
||||
Loading…
Add table
Add a link
Reference in a new issue