convert.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. from __future__ import annotations
  2. from itertools import pairwise
  3. from typing import TYPE_CHECKING, cast
  4. import numpy as np
  5. from contourpy._contourpy import FillType, LineType
  6. import contourpy.array as arr
  7. from contourpy.enum_util import as_fill_type, as_line_type
  8. from contourpy.typecheck import check_filled, check_lines
  9. from contourpy.types import MOVETO, offset_dtype
  10. if TYPE_CHECKING:
  11. import contourpy._contourpy as cpy
  12. def _convert_filled_from_OuterCode(
  13. filled: cpy.FillReturn_OuterCode,
  14. fill_type_to: FillType,
  15. ) -> cpy.FillReturn:
  16. if fill_type_to == FillType.OuterCode:
  17. return filled
  18. elif fill_type_to == FillType.OuterOffset:
  19. return (filled[0], [arr.offsets_from_codes(codes) for codes in filled[1]])
  20. if len(filled[0]) > 0:
  21. points = arr.concat_points(filled[0])
  22. codes = arr.concat_codes(filled[1])
  23. else:
  24. points = None
  25. codes = None
  26. if fill_type_to == FillType.ChunkCombinedCode:
  27. return ([points], [codes])
  28. elif fill_type_to == FillType.ChunkCombinedOffset:
  29. return ([points], [None if codes is None else arr.offsets_from_codes(codes)])
  30. elif fill_type_to == FillType.ChunkCombinedCodeOffset:
  31. outer_offsets = None if points is None else arr.offsets_from_lengths(filled[0])
  32. ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([points], [codes], [outer_offsets])
  33. return ret1
  34. elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
  35. if codes is None:
  36. ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
  37. else:
  38. offsets = arr.offsets_from_codes(codes)
  39. outer_offsets = arr.outer_offsets_from_list_of_codes(filled[1])
  40. ret2 = ([points], [offsets], [outer_offsets])
  41. return ret2
  42. else:
  43. raise ValueError(f"Invalid FillType {fill_type_to}")
  44. def _convert_filled_from_OuterOffset(
  45. filled: cpy.FillReturn_OuterOffset,
  46. fill_type_to: FillType,
  47. ) -> cpy.FillReturn:
  48. if fill_type_to == FillType.OuterCode:
  49. separate_codes = [arr.codes_from_offsets(offsets) for offsets in filled[1]]
  50. return (filled[0], separate_codes)
  51. elif fill_type_to == FillType.OuterOffset:
  52. return filled
  53. if len(filled[0]) > 0:
  54. points = arr.concat_points(filled[0])
  55. offsets = arr.concat_offsets(filled[1])
  56. else:
  57. points = None
  58. offsets = None
  59. if fill_type_to == FillType.ChunkCombinedCode:
  60. return ([points], [None if offsets is None else arr.codes_from_offsets(offsets)])
  61. elif fill_type_to == FillType.ChunkCombinedOffset:
  62. return ([points], [offsets])
  63. elif fill_type_to == FillType.ChunkCombinedCodeOffset:
  64. if offsets is None:
  65. ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
  66. else:
  67. codes = arr.codes_from_offsets(offsets)
  68. outer_offsets = arr.offsets_from_lengths(filled[0])
  69. ret1 = ([points], [codes], [outer_offsets])
  70. return ret1
  71. elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
  72. if points is None:
  73. ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
  74. else:
  75. outer_offsets = arr.outer_offsets_from_list_of_offsets(filled[1])
  76. ret2 = ([points], [offsets], [outer_offsets])
  77. return ret2
  78. else:
  79. raise ValueError(f"Invalid FillType {fill_type_to}")
  80. def _convert_filled_from_ChunkCombinedCode(
  81. filled: cpy.FillReturn_ChunkCombinedCode,
  82. fill_type_to: FillType,
  83. ) -> cpy.FillReturn:
  84. if fill_type_to == FillType.ChunkCombinedCode:
  85. return filled
  86. elif fill_type_to == FillType.ChunkCombinedOffset:
  87. codes = [None if codes is None else arr.offsets_from_codes(codes) for codes in filled[1]]
  88. return (filled[0], codes)
  89. else:
  90. raise ValueError(
  91. f"Conversion from {FillType.ChunkCombinedCode} to {fill_type_to} not supported")
  92. def _convert_filled_from_ChunkCombinedOffset(
  93. filled: cpy.FillReturn_ChunkCombinedOffset,
  94. fill_type_to: FillType,
  95. ) -> cpy.FillReturn:
  96. if fill_type_to == FillType.ChunkCombinedCode:
  97. chunk_codes: list[cpy.CodeArray | None] = []
  98. for points, offsets in zip(*filled):
  99. if points is None:
  100. chunk_codes.append(None)
  101. else:
  102. if TYPE_CHECKING:
  103. assert offsets is not None
  104. chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
  105. return (filled[0], chunk_codes)
  106. elif fill_type_to == FillType.ChunkCombinedOffset:
  107. return filled
  108. else:
  109. raise ValueError(
  110. f"Conversion from {FillType.ChunkCombinedOffset} to {fill_type_to} not supported")
  111. def _convert_filled_from_ChunkCombinedCodeOffset(
  112. filled: cpy.FillReturn_ChunkCombinedCodeOffset,
  113. fill_type_to: FillType,
  114. ) -> cpy.FillReturn:
  115. if fill_type_to == FillType.OuterCode:
  116. separate_points = []
  117. separate_codes = []
  118. for points, codes, outer_offsets in zip(*filled):
  119. if points is not None:
  120. if TYPE_CHECKING:
  121. assert codes is not None
  122. assert outer_offsets is not None
  123. separate_points += arr.split_points_by_offsets(points, outer_offsets)
  124. separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
  125. return (separate_points, separate_codes)
  126. elif fill_type_to == FillType.OuterOffset:
  127. separate_points = []
  128. separate_offsets = []
  129. for points, codes, outer_offsets in zip(*filled):
  130. if points is not None:
  131. if TYPE_CHECKING:
  132. assert codes is not None
  133. assert outer_offsets is not None
  134. separate_points += arr.split_points_by_offsets(points, outer_offsets)
  135. separate_codes = arr.split_codes_by_offsets(codes, outer_offsets)
  136. separate_offsets += [arr.offsets_from_codes(codes) for codes in separate_codes]
  137. return (separate_points, separate_offsets)
  138. elif fill_type_to == FillType.ChunkCombinedCode:
  139. ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], filled[1])
  140. return ret1
  141. elif fill_type_to == FillType.ChunkCombinedOffset:
  142. all_offsets = [None if codes is None else arr.offsets_from_codes(codes)
  143. for codes in filled[1]]
  144. ret2: cpy.FillReturn_ChunkCombinedOffset = (filled[0], all_offsets)
  145. return ret2
  146. elif fill_type_to == FillType.ChunkCombinedCodeOffset:
  147. return filled
  148. elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
  149. chunk_offsets: list[cpy.OffsetArray | None] = []
  150. chunk_outer_offsets: list[cpy.OffsetArray | None] = []
  151. for codes, outer_offsets in zip(*filled[1:]):
  152. if codes is None:
  153. chunk_offsets.append(None)
  154. chunk_outer_offsets.append(None)
  155. else:
  156. if TYPE_CHECKING:
  157. assert outer_offsets is not None
  158. offsets = arr.offsets_from_codes(codes)
  159. outer_offsets = np.array([np.nonzero(offsets == oo)[0][0] for oo in outer_offsets],
  160. dtype=offset_dtype)
  161. chunk_offsets.append(offsets)
  162. chunk_outer_offsets.append(outer_offsets)
  163. ret3: cpy.FillReturn_ChunkCombinedOffsetOffset = (
  164. filled[0], chunk_offsets, chunk_outer_offsets,
  165. )
  166. return ret3
  167. else:
  168. raise ValueError(f"Invalid FillType {fill_type_to}")
  169. def _convert_filled_from_ChunkCombinedOffsetOffset(
  170. filled: cpy.FillReturn_ChunkCombinedOffsetOffset,
  171. fill_type_to: FillType,
  172. ) -> cpy.FillReturn:
  173. if fill_type_to == FillType.OuterCode:
  174. separate_points = []
  175. separate_codes = []
  176. for points, offsets, outer_offsets in zip(*filled):
  177. if points is not None:
  178. if TYPE_CHECKING:
  179. assert offsets is not None
  180. assert outer_offsets is not None
  181. codes = arr.codes_from_offsets_and_points(offsets, points)
  182. outer_offsets = offsets[outer_offsets]
  183. separate_points += arr.split_points_by_offsets(points, outer_offsets)
  184. separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
  185. return (separate_points, separate_codes)
  186. elif fill_type_to == FillType.OuterOffset:
  187. separate_points = []
  188. separate_offsets = []
  189. for points, offsets, outer_offsets in zip(*filled):
  190. if points is not None:
  191. if TYPE_CHECKING:
  192. assert offsets is not None
  193. assert outer_offsets is not None
  194. if len(outer_offsets) > 2:
  195. separate_offsets += [offsets[s:e+1] - offsets[s] for s, e in
  196. pairwise(outer_offsets)]
  197. else:
  198. separate_offsets.append(offsets)
  199. separate_points += arr.split_points_by_offsets(points, offsets[outer_offsets])
  200. return (separate_points, separate_offsets)
  201. elif fill_type_to == FillType.ChunkCombinedCode:
  202. chunk_codes: list[cpy.CodeArray | None] = []
  203. for points, offsets, outer_offsets in zip(*filled):
  204. if points is None:
  205. chunk_codes.append(None)
  206. else:
  207. if TYPE_CHECKING:
  208. assert offsets is not None
  209. assert outer_offsets is not None
  210. chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
  211. ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], chunk_codes)
  212. return ret1
  213. elif fill_type_to == FillType.ChunkCombinedOffset:
  214. return (filled[0], filled[1])
  215. elif fill_type_to == FillType.ChunkCombinedCodeOffset:
  216. chunk_codes = []
  217. chunk_outer_offsets: list[cpy.OffsetArray | None] = []
  218. for points, offsets, outer_offsets in zip(*filled):
  219. if points is None:
  220. chunk_codes.append(None)
  221. chunk_outer_offsets.append(None)
  222. else:
  223. if TYPE_CHECKING:
  224. assert offsets is not None
  225. assert outer_offsets is not None
  226. chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
  227. chunk_outer_offsets.append(offsets[outer_offsets])
  228. ret2: cpy.FillReturn_ChunkCombinedCodeOffset = (filled[0], chunk_codes, chunk_outer_offsets)
  229. return ret2
  230. elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
  231. return filled
  232. else:
  233. raise ValueError(f"Invalid FillType {fill_type_to}")
  234. def convert_filled(
  235. filled: cpy.FillReturn,
  236. fill_type_from: FillType | str,
  237. fill_type_to: FillType | str,
  238. ) -> cpy.FillReturn:
  239. """Convert filled contours from one :class:`~.FillType` to another.
  240. Args:
  241. filled (sequence of arrays): Filled contour polygons to convert, such as those returned by
  242. :meth:`.ContourGenerator.filled`.
  243. fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
  244. string equivalent.
  245. fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
  246. equivalent.
  247. Return:
  248. Converted filled contour polygons.
  249. When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
  250. chunked ones, all polygons are placed in the first chunk. When converting in the other
  251. direction, all chunk information is discarded. Converting a fill type that is not aware of the
  252. relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
  253. ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
  254. .. versionadded:: 1.2.0
  255. """
  256. fill_type_from = as_fill_type(fill_type_from)
  257. fill_type_to = as_fill_type(fill_type_to)
  258. check_filled(filled, fill_type_from)
  259. if fill_type_from == FillType.OuterCode:
  260. if TYPE_CHECKING:
  261. filled = cast(cpy.FillReturn_OuterCode, filled)
  262. return _convert_filled_from_OuterCode(filled, fill_type_to)
  263. elif fill_type_from == FillType.OuterOffset:
  264. if TYPE_CHECKING:
  265. filled = cast(cpy.FillReturn_OuterOffset, filled)
  266. return _convert_filled_from_OuterOffset(filled, fill_type_to)
  267. elif fill_type_from == FillType.ChunkCombinedCode:
  268. if TYPE_CHECKING:
  269. filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
  270. return _convert_filled_from_ChunkCombinedCode(filled, fill_type_to)
  271. elif fill_type_from == FillType.ChunkCombinedOffset:
  272. if TYPE_CHECKING:
  273. filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
  274. return _convert_filled_from_ChunkCombinedOffset(filled, fill_type_to)
  275. elif fill_type_from == FillType.ChunkCombinedCodeOffset:
  276. if TYPE_CHECKING:
  277. filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
  278. return _convert_filled_from_ChunkCombinedCodeOffset(filled, fill_type_to)
  279. elif fill_type_from == FillType.ChunkCombinedOffsetOffset:
  280. if TYPE_CHECKING:
  281. filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
  282. return _convert_filled_from_ChunkCombinedOffsetOffset(filled, fill_type_to)
  283. else:
  284. raise ValueError(f"Invalid FillType {fill_type_from}")
  285. def _convert_lines_from_Separate(
  286. lines: cpy.LineReturn_Separate,
  287. line_type_to: LineType,
  288. ) -> cpy.LineReturn:
  289. if line_type_to == LineType.Separate:
  290. return lines
  291. elif line_type_to == LineType.SeparateCode:
  292. separate_codes = [arr.codes_from_points(line) for line in lines]
  293. return (lines, separate_codes)
  294. elif line_type_to == LineType.ChunkCombinedCode:
  295. if not lines:
  296. ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
  297. else:
  298. points = arr.concat_points(lines)
  299. offsets = arr.offsets_from_lengths(lines)
  300. codes = arr.codes_from_offsets_and_points(offsets, points)
  301. ret1 = ([points], [codes])
  302. return ret1
  303. elif line_type_to == LineType.ChunkCombinedOffset:
  304. if not lines:
  305. ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
  306. else:
  307. ret2 = ([arr.concat_points(lines)], [arr.offsets_from_lengths(lines)])
  308. return ret2
  309. elif line_type_to == LineType.ChunkCombinedNan:
  310. if not lines:
  311. ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
  312. else:
  313. ret3 = ([arr.concat_points_with_nan(lines)],)
  314. return ret3
  315. else:
  316. raise ValueError(f"Invalid LineType {line_type_to}")
  317. def _convert_lines_from_SeparateCode(
  318. lines: cpy.LineReturn_SeparateCode,
  319. line_type_to: LineType,
  320. ) -> cpy.LineReturn:
  321. if line_type_to == LineType.Separate:
  322. # Drop codes.
  323. return lines[0]
  324. elif line_type_to == LineType.SeparateCode:
  325. return lines
  326. elif line_type_to == LineType.ChunkCombinedCode:
  327. if not lines[0]:
  328. ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
  329. else:
  330. ret1 = ([arr.concat_points(lines[0])], [arr.concat_codes(lines[1])])
  331. return ret1
  332. elif line_type_to == LineType.ChunkCombinedOffset:
  333. if not lines[0]:
  334. ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
  335. else:
  336. ret2 = ([arr.concat_points(lines[0])], [arr.offsets_from_lengths(lines[0])])
  337. return ret2
  338. elif line_type_to == LineType.ChunkCombinedNan:
  339. if not lines[0]:
  340. ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
  341. else:
  342. ret3 = ([arr.concat_points_with_nan(lines[0])],)
  343. return ret3
  344. else:
  345. raise ValueError(f"Invalid LineType {line_type_to}")
  346. def _convert_lines_from_ChunkCombinedCode(
  347. lines: cpy.LineReturn_ChunkCombinedCode,
  348. line_type_to: LineType,
  349. ) -> cpy.LineReturn:
  350. if line_type_to in (LineType.Separate, LineType.SeparateCode):
  351. separate_lines = []
  352. for points, codes in zip(*lines):
  353. if points is not None:
  354. if TYPE_CHECKING:
  355. assert codes is not None
  356. split_at = np.nonzero(codes == MOVETO)[0]
  357. if len(split_at) > 1:
  358. separate_lines += np.split(points, split_at[1:])
  359. else:
  360. separate_lines.append(points)
  361. if line_type_to == LineType.Separate:
  362. return separate_lines
  363. else:
  364. separate_codes = [arr.codes_from_points(line) for line in separate_lines]
  365. return (separate_lines, separate_codes)
  366. elif line_type_to == LineType.ChunkCombinedCode:
  367. return lines
  368. elif line_type_to == LineType.ChunkCombinedOffset:
  369. chunk_offsets = [None if codes is None else arr.offsets_from_codes(codes)
  370. for codes in lines[1]]
  371. return (lines[0], chunk_offsets)
  372. elif line_type_to == LineType.ChunkCombinedNan:
  373. points_nan: list[cpy.PointArray | None] = []
  374. for points, codes in zip(*lines):
  375. if points is None:
  376. points_nan.append(None)
  377. else:
  378. if TYPE_CHECKING:
  379. assert codes is not None
  380. offsets = arr.offsets_from_codes(codes)
  381. points_nan.append(arr.insert_nan_at_offsets(points, offsets))
  382. return (points_nan,)
  383. else:
  384. raise ValueError(f"Invalid LineType {line_type_to}")
  385. def _convert_lines_from_ChunkCombinedOffset(
  386. lines: cpy.LineReturn_ChunkCombinedOffset,
  387. line_type_to: LineType,
  388. ) -> cpy.LineReturn:
  389. if line_type_to in (LineType.Separate, LineType.SeparateCode):
  390. separate_lines = []
  391. for points, offsets in zip(*lines):
  392. if points is not None:
  393. if TYPE_CHECKING:
  394. assert offsets is not None
  395. separate_lines += arr.split_points_by_offsets(points, offsets)
  396. if line_type_to == LineType.Separate:
  397. return separate_lines
  398. else:
  399. separate_codes = [arr.codes_from_points(line) for line in separate_lines]
  400. return (separate_lines, separate_codes)
  401. elif line_type_to == LineType.ChunkCombinedCode:
  402. chunk_codes: list[cpy.CodeArray | None] = []
  403. for points, offsets in zip(*lines):
  404. if points is None:
  405. chunk_codes.append(None)
  406. else:
  407. if TYPE_CHECKING:
  408. assert offsets is not None
  409. chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
  410. return (lines[0], chunk_codes)
  411. elif line_type_to == LineType.ChunkCombinedOffset:
  412. return lines
  413. elif line_type_to == LineType.ChunkCombinedNan:
  414. points_nan: list[cpy.PointArray | None] = []
  415. for points, offsets in zip(*lines):
  416. if points is None:
  417. points_nan.append(None)
  418. else:
  419. if TYPE_CHECKING:
  420. assert offsets is not None
  421. points_nan.append(arr.insert_nan_at_offsets(points, offsets))
  422. return (points_nan,)
  423. else:
  424. raise ValueError(f"Invalid LineType {line_type_to}")
  425. def _convert_lines_from_ChunkCombinedNan(
  426. lines: cpy.LineReturn_ChunkCombinedNan,
  427. line_type_to: LineType,
  428. ) -> cpy.LineReturn:
  429. if line_type_to in (LineType.Separate, LineType.SeparateCode):
  430. separate_lines = []
  431. for points in lines[0]:
  432. if points is not None:
  433. separate_lines += arr.split_points_at_nan(points)
  434. if line_type_to == LineType.Separate:
  435. return separate_lines
  436. else:
  437. separate_codes = [arr.codes_from_points(points) for points in separate_lines]
  438. return (separate_lines, separate_codes)
  439. elif line_type_to == LineType.ChunkCombinedCode:
  440. chunk_points: list[cpy.PointArray | None] = []
  441. chunk_codes: list[cpy.CodeArray | None] = []
  442. for points in lines[0]:
  443. if points is None:
  444. chunk_points.append(None)
  445. chunk_codes.append(None)
  446. else:
  447. points, offsets = arr.remove_nan(points)
  448. chunk_points.append(points)
  449. chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
  450. return (chunk_points, chunk_codes)
  451. elif line_type_to == LineType.ChunkCombinedOffset:
  452. chunk_points = []
  453. chunk_offsets: list[cpy.OffsetArray | None] = []
  454. for points in lines[0]:
  455. if points is None:
  456. chunk_points.append(None)
  457. chunk_offsets.append(None)
  458. else:
  459. points, offsets = arr.remove_nan(points)
  460. chunk_points.append(points)
  461. chunk_offsets.append(offsets)
  462. return (chunk_points, chunk_offsets)
  463. elif line_type_to == LineType.ChunkCombinedNan:
  464. return lines
  465. else:
  466. raise ValueError(f"Invalid LineType {line_type_to}")
  467. def convert_lines(
  468. lines: cpy.LineReturn,
  469. line_type_from: LineType | str,
  470. line_type_to: LineType | str,
  471. ) -> cpy.LineReturn:
  472. """Convert contour lines from one :class:`~.LineType` to another.
  473. Args:
  474. lines (sequence of arrays): Contour lines to convert, such as those returned by
  475. :meth:`.ContourGenerator.lines`.
  476. line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
  477. string equivalent.
  478. line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
  479. equivalent.
  480. Return:
  481. Converted contour lines.
  482. When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
  483. chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
  484. ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
  485. other direction, all chunk information is discarded.
  486. .. versionadded:: 1.2.0
  487. """
  488. line_type_from = as_line_type(line_type_from)
  489. line_type_to = as_line_type(line_type_to)
  490. check_lines(lines, line_type_from)
  491. if line_type_from == LineType.Separate:
  492. if TYPE_CHECKING:
  493. lines = cast(cpy.LineReturn_Separate, lines)
  494. return _convert_lines_from_Separate(lines, line_type_to)
  495. elif line_type_from == LineType.SeparateCode:
  496. if TYPE_CHECKING:
  497. lines = cast(cpy.LineReturn_SeparateCode, lines)
  498. return _convert_lines_from_SeparateCode(lines, line_type_to)
  499. elif line_type_from == LineType.ChunkCombinedCode:
  500. if TYPE_CHECKING:
  501. lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
  502. return _convert_lines_from_ChunkCombinedCode(lines, line_type_to)
  503. elif line_type_from == LineType.ChunkCombinedOffset:
  504. if TYPE_CHECKING:
  505. lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
  506. return _convert_lines_from_ChunkCombinedOffset(lines, line_type_to)
  507. elif line_type_from == LineType.ChunkCombinedNan:
  508. if TYPE_CHECKING:
  509. lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
  510. return _convert_lines_from_ChunkCombinedNan(lines, line_type_to)
  511. else:
  512. raise ValueError(f"Invalid LineType {line_type_from}")
  513. def convert_multi_filled(
  514. multi_filled: list[cpy.FillReturn],
  515. fill_type_from: FillType | str,
  516. fill_type_to: FillType | str,
  517. ) -> list[cpy.FillReturn]:
  518. """Convert multiple sets of filled contours from one :class:`~.FillType` to another.
  519. Args:
  520. multi_filled (nested sequence of arrays): Filled contour polygons to convert, such as those
  521. returned by :meth:`.ContourGenerator.multi_filled`.
  522. fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
  523. string equivalent.
  524. fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
  525. equivalent.
  526. Return:
  527. Converted sets filled contour polygons.
  528. When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
  529. chunked ones, all polygons are placed in the first chunk. When converting in the other
  530. direction, all chunk information is discarded. Converting a fill type that is not aware of the
  531. relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
  532. ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
  533. .. versionadded:: 1.3.0
  534. """
  535. fill_type_from = as_fill_type(fill_type_from)
  536. fill_type_to = as_fill_type(fill_type_to)
  537. return [convert_filled(filled, fill_type_from, fill_type_to) for filled in multi_filled]
  538. def convert_multi_lines(
  539. multi_lines: list[cpy.LineReturn],
  540. line_type_from: LineType | str,
  541. line_type_to: LineType | str,
  542. ) -> list[cpy.LineReturn]:
  543. """Convert multiple sets of contour lines from one :class:`~.LineType` to another.
  544. Args:
  545. multi_lines (nested sequence of arrays): Contour lines to convert, such as those returned by
  546. :meth:`.ContourGenerator.multi_lines`.
  547. line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
  548. string equivalent.
  549. line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
  550. equivalent.
  551. Return:
  552. Converted set of contour lines.
  553. When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
  554. chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
  555. ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
  556. other direction, all chunk information is discarded.
  557. .. versionadded:: 1.3.0
  558. """
  559. line_type_from = as_line_type(line_type_from)
  560. line_type_to = as_line_type(line_type_to)
  561. return [convert_lines(lines, line_type_from, line_type_to) for lines in multi_lines]