amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of amulet-core might be problematic. Click here for more details.

Files changed (198) hide show
  1. amulet/__init__.py +27 -27
  2. amulet/__pyinstaller/__init__.py +2 -2
  3. amulet/__pyinstaller/hook-amulet.py +4 -4
  4. amulet/_version.py +21 -21
  5. amulet/api/__init__.py +2 -2
  6. amulet/api/abstract_base_entity.py +128 -128
  7. amulet/api/block.py +630 -630
  8. amulet/api/block_entity.py +71 -71
  9. amulet/api/cache.py +107 -107
  10. amulet/api/chunk/__init__.py +6 -6
  11. amulet/api/chunk/biomes.py +207 -207
  12. amulet/api/chunk/block_entity_dict.py +175 -175
  13. amulet/api/chunk/blocks.py +46 -46
  14. amulet/api/chunk/chunk.py +389 -389
  15. amulet/api/chunk/entity_list.py +75 -75
  16. amulet/api/chunk/status.py +167 -167
  17. amulet/api/data_types/__init__.py +4 -4
  18. amulet/api/data_types/generic_types.py +4 -4
  19. amulet/api/data_types/operation_types.py +16 -16
  20. amulet/api/data_types/world_types.py +49 -49
  21. amulet/api/data_types/wrapper_types.py +71 -71
  22. amulet/api/entity.py +74 -74
  23. amulet/api/errors.py +119 -119
  24. amulet/api/history/__init__.py +36 -36
  25. amulet/api/history/base/__init__.py +3 -3
  26. amulet/api/history/base/base_history.py +26 -26
  27. amulet/api/history/base/history_manager.py +63 -63
  28. amulet/api/history/base/revision_manager.py +73 -73
  29. amulet/api/history/changeable.py +15 -15
  30. amulet/api/history/data_types.py +7 -7
  31. amulet/api/history/history_manager/__init__.py +3 -3
  32. amulet/api/history/history_manager/container.py +102 -102
  33. amulet/api/history/history_manager/database.py +279 -279
  34. amulet/api/history/history_manager/meta.py +93 -93
  35. amulet/api/history/history_manager/object.py +116 -116
  36. amulet/api/history/revision_manager/__init__.py +2 -2
  37. amulet/api/history/revision_manager/disk.py +33 -33
  38. amulet/api/history/revision_manager/ram.py +12 -12
  39. amulet/api/item.py +75 -75
  40. amulet/api/level/__init__.py +4 -4
  41. amulet/api/level/base_level/__init__.py +1 -1
  42. amulet/api/level/base_level/base_level.py +1035 -1026
  43. amulet/api/level/base_level/chunk_manager.py +227 -227
  44. amulet/api/level/base_level/clone.py +389 -389
  45. amulet/api/level/base_level/player_manager.py +101 -101
  46. amulet/api/level/immutable_structure/__init__.py +1 -1
  47. amulet/api/level/immutable_structure/immutable_structure.py +94 -94
  48. amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
  49. amulet/api/level/structure.py +22 -22
  50. amulet/api/level/world.py +19 -19
  51. amulet/api/partial_3d_array/__init__.py +2 -2
  52. amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
  53. amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
  54. amulet/api/partial_3d_array/data_types.py +15 -15
  55. amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
  56. amulet/api/partial_3d_array/util.py +152 -152
  57. amulet/api/player.py +65 -65
  58. amulet/api/registry/__init__.py +2 -2
  59. amulet/api/registry/base_registry.py +34 -34
  60. amulet/api/registry/biome_manager.py +153 -153
  61. amulet/api/registry/block_manager.py +156 -156
  62. amulet/api/selection/__init__.py +2 -2
  63. amulet/api/selection/abstract_selection.py +315 -315
  64. amulet/api/selection/box.py +805 -805
  65. amulet/api/selection/group.py +488 -488
  66. amulet/api/structure.py +37 -37
  67. amulet/api/wrapper/__init__.py +8 -8
  68. amulet/api/wrapper/chunk/interface.py +441 -441
  69. amulet/api/wrapper/chunk/translator.py +567 -567
  70. amulet/api/wrapper/format_wrapper.py +772 -772
  71. amulet/api/wrapper/structure_format_wrapper.py +116 -116
  72. amulet/api/wrapper/world_format_wrapper.py +63 -63
  73. amulet/level/__init__.py +1 -1
  74. amulet/level/formats/anvil_forge_world.py +40 -40
  75. amulet/level/formats/anvil_world/__init__.py +3 -3
  76. amulet/level/formats/anvil_world/_sector_manager.py +291 -384
  77. amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
  78. amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
  79. amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
  80. amulet/level/formats/anvil_world/dimension.py +177 -177
  81. amulet/level/formats/anvil_world/format.py +769 -769
  82. amulet/level/formats/anvil_world/region.py +384 -384
  83. amulet/level/formats/construction/__init__.py +3 -3
  84. amulet/level/formats/construction/format_wrapper.py +515 -515
  85. amulet/level/formats/construction/interface.py +134 -134
  86. amulet/level/formats/construction/section.py +60 -60
  87. amulet/level/formats/construction/util.py +165 -165
  88. amulet/level/formats/leveldb_world/__init__.py +3 -3
  89. amulet/level/formats/leveldb_world/chunk.py +33 -33
  90. amulet/level/formats/leveldb_world/dimension.py +385 -419
  91. amulet/level/formats/leveldb_world/format.py +659 -641
  92. amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
  93. amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
  94. amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
  95. amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
  96. amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
  97. amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
  98. amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
  99. amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
  100. amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
  101. amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
  102. amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
  103. amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
  104. amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
  105. amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
  106. amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
  107. amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
  108. amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
  109. amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
  110. amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
  111. amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
  112. amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
  113. amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
  114. amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
  115. amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
  116. amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
  117. amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
  118. amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
  119. amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
  120. amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
  121. amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
  122. amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
  123. amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
  124. amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
  125. amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
  126. amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
  127. amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
  128. amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
  129. amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
  130. amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
  131. amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
  132. amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
  133. amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
  134. amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
  135. amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
  136. amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
  137. amulet/level/formats/mcstructure/__init__.py +3 -3
  138. amulet/level/formats/mcstructure/chunk.py +50 -50
  139. amulet/level/formats/mcstructure/format_wrapper.py +408 -408
  140. amulet/level/formats/mcstructure/interface.py +175 -175
  141. amulet/level/formats/schematic/__init__.py +3 -3
  142. amulet/level/formats/schematic/chunk.py +55 -55
  143. amulet/level/formats/schematic/data_types.py +4 -4
  144. amulet/level/formats/schematic/format_wrapper.py +373 -373
  145. amulet/level/formats/schematic/interface.py +142 -142
  146. amulet/level/formats/sponge_schem/__init__.py +4 -4
  147. amulet/level/formats/sponge_schem/chunk.py +62 -62
  148. amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
  149. amulet/level/formats/sponge_schem/interface.py +118 -118
  150. amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
  151. amulet/level/formats/sponge_schem/varint/varint.py +87 -87
  152. amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
  153. amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
  154. amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
  155. amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
  156. amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
  157. amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
  158. amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
  159. amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
  160. amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
  161. amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
  162. amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
  163. amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
  164. amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
  165. amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
  166. amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
  167. amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
  168. amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
  169. amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
  170. amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
  171. amulet/level/load.py +59 -59
  172. amulet/level/loader.py +95 -95
  173. amulet/level/translators/chunk/bedrock/__init__.py +267 -267
  174. amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
  175. amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
  176. amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
  177. amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
  178. amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
  179. amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
  180. amulet/libs/leveldb/__init__.py +7 -7
  181. amulet/operations/__init__.py +5 -5
  182. amulet/operations/clone.py +18 -18
  183. amulet/operations/delete_chunk.py +32 -32
  184. amulet/operations/fill.py +30 -30
  185. amulet/operations/paste.py +65 -65
  186. amulet/operations/replace.py +58 -58
  187. amulet/utils/__init__.py +14 -14
  188. amulet/utils/format_utils.py +41 -41
  189. amulet/utils/generator.py +15 -15
  190. amulet/utils/matrix.py +243 -243
  191. amulet/utils/numpy_helpers.py +46 -46
  192. amulet/utils/world_utils.py +349 -349
  193. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
  194. amulet_core-1.9.20.dist-info/RECORD +208 -0
  195. amulet_core-1.9.19.dist-info/RECORD +0 -208
  196. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
  197. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
  198. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
@@ -1,528 +1,528 @@
1
- from typing import overload, Tuple, Union, Optional, Generator
2
- import numpy
3
- import math
4
-
5
- from .data_types import (
6
- SliceSlicesType,
7
- UnpackedSlicesType,
8
- DtypeType,
9
- Integer,
10
- IntegerType,
11
- )
12
- from .base_partial_3d_array import BasePartial3DArray
13
- from .unbounded_partial_3d_array import UnboundedPartial3DArray
14
- from .util import to_slice, sanitise_slice, unpack_slice, stack_sanitised_slices
15
-
16
-
17
- class BoundedPartial3DArray(BasePartial3DArray):
18
- """
19
- This class should behave the same as a numpy array in all three axis
20
- but the data internally is stored in sections to minimise memory usage.
21
- The array has a fixed size in all three axis much like a numpy array.
22
- """
23
-
24
- @classmethod
25
- def from_partial_array(
26
- cls,
27
- parent_array: UnboundedPartial3DArray,
28
- start: Tuple[int, int, int],
29
- stop: Tuple[int, int, int],
30
- step: Tuple[int, int, int],
31
- ):
32
- """
33
- Create a :class:`BoundedPartial3DArray` from an :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray` and slices.
34
-
35
- :param parent_array: The :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray` to "extract" the slice from
36
- :param start: The starting point of the slice.
37
- :param stop: The end point of the slice.
38
- :param step: The steps of the slice.
39
- :return: A new instance of :class:`BoundedPartial3DArray` that behaves like a view into the parent array.
40
- """
41
- return cls(
42
- parent_array.dtype,
43
- parent_array.default_value,
44
- parent_array.section_shape,
45
- start,
46
- stop,
47
- step,
48
- parent_array,
49
- )
50
-
51
- def __init__(
52
- self,
53
- dtype: DtypeType,
54
- default_value: Union[int, bool],
55
- section_shape: Tuple[int, int, int],
56
- start: Tuple[Optional[int], int, Optional[int]],
57
- stop: Tuple[Optional[int], int, Optional[int]],
58
- step: Tuple[Optional[int], Optional[int], Optional[int]],
59
- parent_array: UnboundedPartial3DArray,
60
- ):
61
- """
62
- Construct a :class:`BoundedPartial3DArray`. This should not be used directly. You should instead use the :meth:`~amulet.api.partial_3d_array.UnboundedPartial3DArray.__getitem__` method of :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray`
63
-
64
- :param dtype: The dtype that all arrays will be stored in.
65
- :param default_value: The default value that all undefined arrays will be populated with if required.
66
- :param section_shape: The shape of each section array.
67
- :param start: The starting point of the slice.
68
- :param stop: The end point of the slice.
69
- :param step: The steps of the slice.
70
- :param parent_array: The original :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray` that the slice is viewing into.
71
- """
72
- assert isinstance(start[1], int) and isinstance(
73
- stop[1], int
74
- ), "start[1] and stop[1] must both be ints."
75
- assert isinstance(parent_array, UnboundedPartial3DArray)
76
- super().__init__(
77
- dtype, default_value, section_shape, start, stop, step, parent_array
78
- )
79
-
80
- def __repr__(self):
81
- return f"BoundedPartial3DArray(dtype={self.dtype}, shape={self.shape})"
82
-
83
- def __array__(self, dtype=None):
84
- """
85
- Get the data contained within as a numpy array.
86
-
87
- >>> numpy.array(partial_array)
88
-
89
- :param dtype: The dtype of the returned numpy array.
90
- :return: A numpy array of the contained data.
91
- """
92
- array = numpy.full(self.shape, self.default_value, dtype or self.dtype)
93
- for sy, slices, relative_slices in self._iter_slices(
94
- (
95
- (self.start_x, self.stop_x, self.step_x),
96
- (self.start_y, self.stop_y, self.step_y),
97
- (self.start_z, self.stop_z, self.step_z),
98
- )
99
- ):
100
- if sy in self._sections:
101
- array[relative_slices] = self._sections[sy][slices]
102
- return array
103
-
104
- def __eq__(self, value):
105
- """
106
- Check equality of this object and another object.
107
-
108
- Behaves mostly the same as a numpy array.
109
-
110
- >>> bounded_partial_array1 == bounded_partial_array2
111
-
112
- :param value: The object to compare to.
113
- :return:
114
- """
115
-
116
- def get_array(default: bool):
117
- return self.from_partial_array(
118
- UnboundedPartial3DArray(bool, default, self.section_shape, (0, 0)),
119
- self.start,
120
- self.stop,
121
- self.step,
122
- )
123
-
124
- if (
125
- isinstance(value, Integer) and numpy.issubdtype(self.dtype, numpy.integer)
126
- ) or (isinstance(value, bool) and numpy.issubdtype(self.dtype, bool)):
127
- out = get_array(value == self.default_value)
128
- for sy, slices, relative_slices in self._iter_slices(self.slices_tuple):
129
- if sy in self._sections:
130
- out[relative_slices] = self._sections[sy][slices] == value
131
- elif (
132
- isinstance(value, (numpy.ndarray, BoundedPartial3DArray))
133
- and (
134
- numpy.issubdtype(value.dtype, numpy.integer)
135
- and numpy.issubdtype(self.dtype, numpy.integer)
136
- )
137
- or (
138
- numpy.issubdtype(value.dtype, bool)
139
- and numpy.issubdtype(self.dtype, bool)
140
- )
141
- ):
142
- out = get_array(False)
143
- if self.shape != value.shape:
144
- raise ValueError(
145
- f"The shape of the index ({self.shape}) and the shape of the given array ({value.shape}) do not match."
146
- )
147
- for sy, slices, relative_slices in self._iter_slices(self.slices_tuple):
148
- if sy in self._sections:
149
- out[relative_slices] = self._sections[sy][slices] == numpy.asarray(
150
- value[relative_slices]
151
- )
152
- else:
153
- out[relative_slices] = self.default_value == numpy.asarray(
154
- value[relative_slices]
155
- )
156
- else:
157
- raise ValueError(f"Bad value {value}")
158
- return out
159
-
160
- def _iter_slices(
161
- self, slices: UnpackedSlicesType
162
- ) -> Generator[Tuple[int, SliceSlicesType, SliceSlicesType], None, None]:
163
- """
164
- split the sanitised slice into section based slices
165
- :return: Generator of section y, section slice, relative slice
166
- """
167
- slice_x = slice(*slices[0])
168
- slice_z = slice(*slices[2])
169
- relative_slice_x = slice(None)
170
- relative_slice_z = slice(None)
171
-
172
- start_y, stop_y, step_y = slices[1]
173
- sy = None
174
- section_start_y = None
175
- section_stop_y = None
176
- section_start_dy = None
177
- section_stop_dy = None
178
- for y in range(start_y, stop_y, step_y):
179
- sy_, dy_ = self._section_index(y)
180
- if sy_ != sy:
181
- # we are in a new section
182
- if sy is not None:
183
- yield sy, (
184
- slice_x,
185
- slice(section_start_dy, section_stop_dy, step_y),
186
- slice_z,
187
- ), (
188
- relative_slice_x,
189
- slice(
190
- math.ceil((section_start_y - start_y) / step_y),
191
- math.ceil((section_stop_y - start_y) / step_y),
192
- ),
193
- relative_slice_z,
194
- )
195
- sy = sy_
196
- section_start_y = y
197
- section_start_dy = dy_
198
- section_stop_y = y + int(math.copysign(1, step_y))
199
- section_stop_dy = dy_ + int(math.copysign(1, step_y))
200
- if sy is not None:
201
- yield sy, (
202
- slice_x,
203
- slice(section_start_dy, section_stop_dy, step_y),
204
- slice_z,
205
- ), (
206
- relative_slice_x,
207
- slice(
208
- math.ceil((section_start_y - start_y) / step_y),
209
- math.ceil((section_stop_y - start_y) / step_y),
210
- ),
211
- relative_slice_z,
212
- )
213
-
214
- def _relative_to_absolute(self, axis: int, relative_index: int) -> int:
215
- """Convert a relative index to the absolute value in the array."""
216
- value = relative_index
217
- start = self.start[axis]
218
- stop = self.stop[axis]
219
- step = self.step[axis]
220
- if value >= 0:
221
- value = start + value * step
222
- else:
223
- stop_max = start + math.ceil((stop - start) / step) * step
224
- value = stop_max + value * step
225
-
226
- if step > 0:
227
- if not start <= value < stop:
228
- raise IndexError(
229
- f"index {relative_index} is out of bounds for axis {axis} with size {self.shape[axis]}"
230
- )
231
- else:
232
- if not start >= value > stop:
233
- raise IndexError(
234
- f"index {relative_index} is out of bounds for axis {axis} with size {self.shape[axis]}"
235
- )
236
- value -= 1
237
-
238
- return value
239
-
240
- def _stack_slices(
241
- self, slices: Tuple[slice, slice, slice]
242
- ) -> Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]:
243
- return tuple(
244
- stack_sanitised_slices(
245
- start, stop, step, *sanitise_slice(*unpack_slice(to_slice(i)), shape)
246
- )
247
- for i, start, stop, step, shape in zip(
248
- slices, self.start, self.stop, self.step, self.shape
249
- )
250
- )
251
-
252
- @overload
253
- def __getitem__(
254
- self, slices: Tuple[IntegerType, IntegerType, IntegerType]
255
- ) -> Union[int, bool]:
256
- ...
257
-
258
- @overload
259
- def __getitem__(
260
- self,
261
- slices: Tuple[
262
- Union[IntegerType, slice],
263
- Union[IntegerType, slice],
264
- Union[IntegerType, slice],
265
- ],
266
- ) -> "BoundedPartial3DArray":
267
- ...
268
-
269
- @overload
270
- def __getitem__(
271
- self, slices: Union[numpy.ndarray, "BoundedPartial3DArray"]
272
- ) -> numpy.ndarray:
273
- ...
274
-
275
- def __getitem__(self, item):
276
- """
277
- Get a value or sub-section of the unbounded array.
278
-
279
- >>> # get the value at a given location
280
- >>> value = partial_array[3, 4, 5] # an integer
281
- >>> # get a cuboid volume in the array
282
- >>> value = partial_array[2:3, 4:5, 6:7] # BoundedPartial3DArray
283
- >>> # slice and int can be mixed
284
- >>> value = partial_array[2:3, 4, 6:7] # BoundedPartial3DArray
285
-
286
- :param item: The slices to extract.
287
- :return: The value or BoundedPartial3DArray viewing into this array.
288
- """
289
- if isinstance(item, tuple):
290
- if len(item) != 3:
291
- raise KeyError(f"Tuple item must be of length 3, got {len(item)}")
292
- if all(isinstance(i, Integer) for i in item):
293
- x, y, z = tuple(
294
- self._relative_to_absolute(axis, item[axis]) for axis in range(3)
295
- )
296
-
297
- sy, dy = self._section_index(y)
298
- if sy in self:
299
- return int(self._sections[sy][(x, dy, z)])
300
- else:
301
- return self.default_value
302
-
303
- elif all(isinstance(i, (int, numpy.integer, slice)) for i in item):
304
- item: Tuple[
305
- Tuple[int, int, int],
306
- Tuple[int, int, int],
307
- Tuple[int, int, int],
308
- ] = zip(*self._stack_slices(item))
309
-
310
- return BoundedPartial3DArray.from_partial_array(
311
- self._parent_array, *item
312
- )
313
- else:
314
- raise KeyError(f"Unsupported tuple {item} for getitem")
315
-
316
- elif isinstance(item, (numpy.ndarray, BoundedPartial3DArray)):
317
- if item.dtype == bool:
318
- if item.shape != self.shape:
319
- raise ValueError(
320
- f"The shape of the index ({self.shape}) and the shape of the given array ({item.shape}) do not match."
321
- )
322
- out = []
323
- for slices_x, relative_slices_x in zip(
324
- range(self.start_x, self.stop_x, self.step_x), range(0, self.size_x)
325
- ):
326
- for (
327
- sy,
328
- (_, slices_y, slices_z),
329
- (_, relative_slices_y, relative_slices_z),
330
- ) in self._iter_slices(self.slices_tuple):
331
- if sy in self._sections:
332
- out.append(
333
- self._sections[sy][slices_x, slices_y, slices_z][
334
- numpy.asarray(
335
- item[
336
- relative_slices_x,
337
- relative_slices_y,
338
- relative_slices_z,
339
- ]
340
- )
341
- ]
342
- )
343
- else:
344
- out.append(
345
- numpy.full(
346
- numpy.count_nonzero(
347
- numpy.asarray(
348
- item[
349
- relative_slices_x,
350
- relative_slices_y,
351
- relative_slices_z,
352
- ]
353
- )
354
- ),
355
- self.default_value,
356
- self.dtype,
357
- )
358
- )
359
- if out:
360
- return numpy.concatenate(out)
361
- else:
362
- return numpy.full(0, self.default_value, self.dtype)
363
- elif numpy.issubdtype(item.dtype, numpy.integer):
364
- if isinstance(item, BoundedPartial3DArray):
365
- raise ValueError(
366
- "Index array with a BoundedPartial3DArray is not valid"
367
- )
368
- raise NotImplementedError(
369
- "Index arrays are not currently supported"
370
- ) # TODO
371
- else:
372
- raise ValueError(
373
- f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
374
- )
375
- else:
376
- raise KeyError(
377
- f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
378
- )
379
-
380
- @overload
381
- def __setitem__(
382
- self,
383
- item: Tuple[
384
- Union[IntegerType, slice],
385
- Union[IntegerType, slice],
386
- Union[IntegerType, slice],
387
- ],
388
- value: Union[int, bool, numpy.ndarray, "BoundedPartial3DArray"],
389
- ):
390
- ...
391
-
392
- @overload
393
- def __setitem__(
394
- self,
395
- item: Union[numpy.ndarray, "BoundedPartial3DArray"],
396
- value: Union[int, bool, numpy.ndarray],
397
- ):
398
- ...
399
-
400
- def __setitem__(self, item, value):
401
- """
402
- Set a sub-section of the array.
403
-
404
- >>> # set the value at a given location
405
- >>> partial_array[3, 4, 5] = 1
406
- >>> # set a cuboid volume in the array
407
- >>> partial_array[2:3, 4:5, 6:7] = 1
408
- >>> # slice and int can be mixed
409
- >>> partial_array[2:3, 4, 6:7] = 1
410
-
411
- :param slices: The slices or locations that define the volume to set.
412
- :param value: The value to set at the given location. Can be an integer or array.
413
- """
414
- if isinstance(item, tuple):
415
- if len(item) != 3:
416
- raise KeyError(f"Tuple item must be of length 3, got {len(item)}")
417
- elif all(isinstance(i, (int, numpy.integer, slice)) for i in item):
418
- stacked_slices: Tuple[
419
- Tuple[int, int, int],
420
- Tuple[int, int, int],
421
- Tuple[int, int, int],
422
- ] = self._stack_slices(item)
423
- if (
424
- isinstance(value, Integer)
425
- and numpy.issubdtype(self.dtype, numpy.integer)
426
- ) or (isinstance(value, bool) and self.dtype == bool):
427
- for sy, slices, _ in self._iter_slices(stacked_slices):
428
- if sy in self._sections:
429
- self._sections[sy][slices] = value
430
- elif value != self.default_value:
431
- self._parent_array.create_section(sy)
432
- self._sections[sy][slices] = value
433
- elif (
434
- isinstance(value, (numpy.ndarray, BoundedPartial3DArray))
435
- and (
436
- numpy.issubdtype(value.dtype, numpy.integer)
437
- and numpy.issubdtype(self.dtype, numpy.integer)
438
- )
439
- or (
440
- numpy.issubdtype(value.dtype, bool)
441
- and numpy.issubdtype(self.dtype, bool)
442
- )
443
- ):
444
- size_array = self[item]
445
- if size_array.shape != value.shape:
446
- raise ValueError(
447
- f"The shape of the index ({size_array.shape}) and the shape of the given array ({value.shape}) do not match."
448
- )
449
- for sy, slices, relative_slices in size_array._iter_slices(
450
- stacked_slices
451
- ):
452
- if sy not in self._sections:
453
- self._parent_array.create_section(sy)
454
- self._sections[sy][slices] = numpy.asarray(
455
- value[relative_slices]
456
- )
457
- else:
458
- raise ValueError(f"Bad value {value}")
459
-
460
- else:
461
- raise KeyError(f"Unsupported tuple {item} for getitem")
462
- elif isinstance(item, (numpy.ndarray, BoundedPartial3DArray)):
463
- if item.dtype == bool:
464
- if item.shape != self.shape:
465
- raise ValueError(
466
- f"The shape of the index ({self.shape}) and the shape of the given array ({item.shape}) do not match."
467
- )
468
- if isinstance(value, (int, numpy.integer, bool)):
469
- for sy, slices, relative_slices in self._iter_slices(
470
- self.slices_tuple
471
- ):
472
- bool_array = numpy.asarray(item[relative_slices])
473
- if sy in self._sections:
474
- self._sections[sy][slices][bool_array] = value
475
- elif value != self.default_value and numpy.any(bool_array):
476
- self._parent_array.create_section(sy)
477
- self._sections[sy][slices][bool_array] = value
478
- elif isinstance(value, numpy.ndarray):
479
- start = 0
480
- true_count = numpy.count_nonzero(item)
481
- if true_count != value.size:
482
- raise ValueError(
483
- f"There are more True values ({true_count}) in the item array than there are values in the value array ({value.size})."
484
- )
485
-
486
- for slices_x, relative_slices_x in zip(
487
- range(self.start_x, self.stop_x, self.step_x),
488
- range(0, self.size_x),
489
- ):
490
- for (
491
- sy,
492
- (_, slices_y, slices_z),
493
- (_, relative_slices_y, relative_slices_z),
494
- ) in self._iter_slices(self.slices_tuple):
495
- if sy not in self._sections:
496
- self._parent_array.create_section(sy)
497
- bool_array = numpy.asarray(
498
- item[
499
- relative_slices_x,
500
- relative_slices_y,
501
- relative_slices_z,
502
- ]
503
- )
504
- count: int = numpy.count_nonzero(bool_array)
505
- self._sections[sy][slices_x, slices_y, slices_z][
506
- bool_array
507
- ] = value[start : start + count]
508
- start += count
509
- else:
510
- raise ValueError(
511
- f"When setting using a bool array the value must be an int, bool or numpy.ndarray. Got {item.__class__.__name__}({item})"
512
- )
513
- elif numpy.issubdtype(item.dtype, numpy.integer):
514
- if isinstance(item, BoundedPartial3DArray):
515
- raise ValueError(
516
- "Index array with a BoundedPartial3DArray is not valid"
517
- )
518
- raise NotImplementedError(
519
- "Index arrays are not currently supported"
520
- ) # TODO
521
- else:
522
- raise ValueError(
523
- f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
524
- )
525
- else:
526
- raise KeyError(
527
- f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
528
- )
1
+ from typing import overload, Tuple, Union, Optional, Generator
2
+ import numpy
3
+ import math
4
+
5
+ from .data_types import (
6
+ SliceSlicesType,
7
+ UnpackedSlicesType,
8
+ DtypeType,
9
+ Integer,
10
+ IntegerType,
11
+ )
12
+ from .base_partial_3d_array import BasePartial3DArray
13
+ from .unbounded_partial_3d_array import UnboundedPartial3DArray
14
+ from .util import to_slice, sanitise_slice, unpack_slice, stack_sanitised_slices
15
+
16
+
17
+ class BoundedPartial3DArray(BasePartial3DArray):
18
+ """
19
+ This class should behave the same as a numpy array in all three axis
20
+ but the data internally is stored in sections to minimise memory usage.
21
+ The array has a fixed size in all three axis much like a numpy array.
22
+ """
23
+
24
+ @classmethod
25
+ def from_partial_array(
26
+ cls,
27
+ parent_array: UnboundedPartial3DArray,
28
+ start: Tuple[int, int, int],
29
+ stop: Tuple[int, int, int],
30
+ step: Tuple[int, int, int],
31
+ ):
32
+ """
33
+ Create a :class:`BoundedPartial3DArray` from an :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray` and slices.
34
+
35
+ :param parent_array: The :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray` to "extract" the slice from
36
+ :param start: The starting point of the slice.
37
+ :param stop: The end point of the slice.
38
+ :param step: The steps of the slice.
39
+ :return: A new instance of :class:`BoundedPartial3DArray` that behaves like a view into the parent array.
40
+ """
41
+ return cls(
42
+ parent_array.dtype,
43
+ parent_array.default_value,
44
+ parent_array.section_shape,
45
+ start,
46
+ stop,
47
+ step,
48
+ parent_array,
49
+ )
50
+
51
+ def __init__(
52
+ self,
53
+ dtype: DtypeType,
54
+ default_value: Union[int, bool],
55
+ section_shape: Tuple[int, int, int],
56
+ start: Tuple[Optional[int], int, Optional[int]],
57
+ stop: Tuple[Optional[int], int, Optional[int]],
58
+ step: Tuple[Optional[int], Optional[int], Optional[int]],
59
+ parent_array: UnboundedPartial3DArray,
60
+ ):
61
+ """
62
+ Construct a :class:`BoundedPartial3DArray`. This should not be used directly. You should instead use the :meth:`~amulet.api.partial_3d_array.UnboundedPartial3DArray.__getitem__` method of :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray`
63
+
64
+ :param dtype: The dtype that all arrays will be stored in.
65
+ :param default_value: The default value that all undefined arrays will be populated with if required.
66
+ :param section_shape: The shape of each section array.
67
+ :param start: The starting point of the slice.
68
+ :param stop: The end point of the slice.
69
+ :param step: The steps of the slice.
70
+ :param parent_array: The original :class:`~amulet.api.partial_3d_array.UnboundedPartial3DArray` that the slice is viewing into.
71
+ """
72
+ assert isinstance(start[1], int) and isinstance(
73
+ stop[1], int
74
+ ), "start[1] and stop[1] must both be ints."
75
+ assert isinstance(parent_array, UnboundedPartial3DArray)
76
+ super().__init__(
77
+ dtype, default_value, section_shape, start, stop, step, parent_array
78
+ )
79
+
80
+ def __repr__(self):
81
+ return f"BoundedPartial3DArray(dtype={self.dtype}, shape={self.shape})"
82
+
83
+ def __array__(self, dtype=None):
84
+ """
85
+ Get the data contained within as a numpy array.
86
+
87
+ >>> numpy.array(partial_array)
88
+
89
+ :param dtype: The dtype of the returned numpy array.
90
+ :return: A numpy array of the contained data.
91
+ """
92
+ array = numpy.full(self.shape, self.default_value, dtype or self.dtype)
93
+ for sy, slices, relative_slices in self._iter_slices(
94
+ (
95
+ (self.start_x, self.stop_x, self.step_x),
96
+ (self.start_y, self.stop_y, self.step_y),
97
+ (self.start_z, self.stop_z, self.step_z),
98
+ )
99
+ ):
100
+ if sy in self._sections:
101
+ array[relative_slices] = self._sections[sy][slices]
102
+ return array
103
+
104
+ def __eq__(self, value):
105
+ """
106
+ Check equality of this object and another object.
107
+
108
+ Behaves mostly the same as a numpy array.
109
+
110
+ >>> bounded_partial_array1 == bounded_partial_array2
111
+
112
+ :param value: The object to compare to.
113
+ :return:
114
+ """
115
+
116
+ def get_array(default: bool):
117
+ return self.from_partial_array(
118
+ UnboundedPartial3DArray(bool, default, self.section_shape, (0, 0)),
119
+ self.start,
120
+ self.stop,
121
+ self.step,
122
+ )
123
+
124
+ if (
125
+ isinstance(value, Integer) and numpy.issubdtype(self.dtype, numpy.integer)
126
+ ) or (isinstance(value, bool) and numpy.issubdtype(self.dtype, bool)):
127
+ out = get_array(value == self.default_value)
128
+ for sy, slices, relative_slices in self._iter_slices(self.slices_tuple):
129
+ if sy in self._sections:
130
+ out[relative_slices] = self._sections[sy][slices] == value
131
+ elif (
132
+ isinstance(value, (numpy.ndarray, BoundedPartial3DArray))
133
+ and (
134
+ numpy.issubdtype(value.dtype, numpy.integer)
135
+ and numpy.issubdtype(self.dtype, numpy.integer)
136
+ )
137
+ or (
138
+ numpy.issubdtype(value.dtype, bool)
139
+ and numpy.issubdtype(self.dtype, bool)
140
+ )
141
+ ):
142
+ out = get_array(False)
143
+ if self.shape != value.shape:
144
+ raise ValueError(
145
+ f"The shape of the index ({self.shape}) and the shape of the given array ({value.shape}) do not match."
146
+ )
147
+ for sy, slices, relative_slices in self._iter_slices(self.slices_tuple):
148
+ if sy in self._sections:
149
+ out[relative_slices] = self._sections[sy][slices] == numpy.asarray(
150
+ value[relative_slices]
151
+ )
152
+ else:
153
+ out[relative_slices] = self.default_value == numpy.asarray(
154
+ value[relative_slices]
155
+ )
156
+ else:
157
+ raise ValueError(f"Bad value {value}")
158
+ return out
159
+
160
+ def _iter_slices(
161
+ self, slices: UnpackedSlicesType
162
+ ) -> Generator[Tuple[int, SliceSlicesType, SliceSlicesType], None, None]:
163
+ """
164
+ split the sanitised slice into section based slices
165
+ :return: Generator of section y, section slice, relative slice
166
+ """
167
+ slice_x = slice(*slices[0])
168
+ slice_z = slice(*slices[2])
169
+ relative_slice_x = slice(None)
170
+ relative_slice_z = slice(None)
171
+
172
+ start_y, stop_y, step_y = slices[1]
173
+ sy = None
174
+ section_start_y = None
175
+ section_stop_y = None
176
+ section_start_dy = None
177
+ section_stop_dy = None
178
+ for y in range(start_y, stop_y, step_y):
179
+ sy_, dy_ = self._section_index(y)
180
+ if sy_ != sy:
181
+ # we are in a new section
182
+ if sy is not None:
183
+ yield sy, (
184
+ slice_x,
185
+ slice(section_start_dy, section_stop_dy, step_y),
186
+ slice_z,
187
+ ), (
188
+ relative_slice_x,
189
+ slice(
190
+ math.ceil((section_start_y - start_y) / step_y),
191
+ math.ceil((section_stop_y - start_y) / step_y),
192
+ ),
193
+ relative_slice_z,
194
+ )
195
+ sy = sy_
196
+ section_start_y = y
197
+ section_start_dy = dy_
198
+ section_stop_y = y + int(math.copysign(1, step_y))
199
+ section_stop_dy = dy_ + int(math.copysign(1, step_y))
200
+ if sy is not None:
201
+ yield sy, (
202
+ slice_x,
203
+ slice(section_start_dy, section_stop_dy, step_y),
204
+ slice_z,
205
+ ), (
206
+ relative_slice_x,
207
+ slice(
208
+ math.ceil((section_start_y - start_y) / step_y),
209
+ math.ceil((section_stop_y - start_y) / step_y),
210
+ ),
211
+ relative_slice_z,
212
+ )
213
+
214
+ def _relative_to_absolute(self, axis: int, relative_index: int) -> int:
215
+ """Convert a relative index to the absolute value in the array."""
216
+ value = relative_index
217
+ start = self.start[axis]
218
+ stop = self.stop[axis]
219
+ step = self.step[axis]
220
+ if value >= 0:
221
+ value = start + value * step
222
+ else:
223
+ stop_max = start + math.ceil((stop - start) / step) * step
224
+ value = stop_max + value * step
225
+
226
+ if step > 0:
227
+ if not start <= value < stop:
228
+ raise IndexError(
229
+ f"index {relative_index} is out of bounds for axis {axis} with size {self.shape[axis]}"
230
+ )
231
+ else:
232
+ if not start >= value > stop:
233
+ raise IndexError(
234
+ f"index {relative_index} is out of bounds for axis {axis} with size {self.shape[axis]}"
235
+ )
236
+ value -= 1
237
+
238
+ return value
239
+
240
+ def _stack_slices(
241
+ self, slices: Tuple[slice, slice, slice]
242
+ ) -> Tuple[Tuple[int, int, int], Tuple[int, int, int], Tuple[int, int, int]]:
243
+ return tuple(
244
+ stack_sanitised_slices(
245
+ start, stop, step, *sanitise_slice(*unpack_slice(to_slice(i)), shape)
246
+ )
247
+ for i, start, stop, step, shape in zip(
248
+ slices, self.start, self.stop, self.step, self.shape
249
+ )
250
+ )
251
+
252
+ @overload
253
+ def __getitem__(
254
+ self, slices: Tuple[IntegerType, IntegerType, IntegerType]
255
+ ) -> Union[int, bool]:
256
+ ...
257
+
258
+ @overload
259
+ def __getitem__(
260
+ self,
261
+ slices: Tuple[
262
+ Union[IntegerType, slice],
263
+ Union[IntegerType, slice],
264
+ Union[IntegerType, slice],
265
+ ],
266
+ ) -> "BoundedPartial3DArray":
267
+ ...
268
+
269
+ @overload
270
+ def __getitem__(
271
+ self, slices: Union[numpy.ndarray, "BoundedPartial3DArray"]
272
+ ) -> numpy.ndarray:
273
+ ...
274
+
275
+ def __getitem__(self, item):
276
+ """
277
+ Get a value or sub-section of the unbounded array.
278
+
279
+ >>> # get the value at a given location
280
+ >>> value = partial_array[3, 4, 5] # an integer
281
+ >>> # get a cuboid volume in the array
282
+ >>> value = partial_array[2:3, 4:5, 6:7] # BoundedPartial3DArray
283
+ >>> # slice and int can be mixed
284
+ >>> value = partial_array[2:3, 4, 6:7] # BoundedPartial3DArray
285
+
286
+ :param item: The slices to extract.
287
+ :return: The value or BoundedPartial3DArray viewing into this array.
288
+ """
289
+ if isinstance(item, tuple):
290
+ if len(item) != 3:
291
+ raise KeyError(f"Tuple item must be of length 3, got {len(item)}")
292
+ if all(isinstance(i, Integer) for i in item):
293
+ x, y, z = tuple(
294
+ self._relative_to_absolute(axis, item[axis]) for axis in range(3)
295
+ )
296
+
297
+ sy, dy = self._section_index(y)
298
+ if sy in self:
299
+ return int(self._sections[sy][(x, dy, z)])
300
+ else:
301
+ return self.default_value
302
+
303
+ elif all(isinstance(i, (int, numpy.integer, slice)) for i in item):
304
+ item: Tuple[
305
+ Tuple[int, int, int],
306
+ Tuple[int, int, int],
307
+ Tuple[int, int, int],
308
+ ] = zip(*self._stack_slices(item))
309
+
310
+ return BoundedPartial3DArray.from_partial_array(
311
+ self._parent_array, *item
312
+ )
313
+ else:
314
+ raise KeyError(f"Unsupported tuple {item} for getitem")
315
+
316
+ elif isinstance(item, (numpy.ndarray, BoundedPartial3DArray)):
317
+ if item.dtype == bool:
318
+ if item.shape != self.shape:
319
+ raise ValueError(
320
+ f"The shape of the index ({self.shape}) and the shape of the given array ({item.shape}) do not match."
321
+ )
322
+ out = []
323
+ for slices_x, relative_slices_x in zip(
324
+ range(self.start_x, self.stop_x, self.step_x), range(0, self.size_x)
325
+ ):
326
+ for (
327
+ sy,
328
+ (_, slices_y, slices_z),
329
+ (_, relative_slices_y, relative_slices_z),
330
+ ) in self._iter_slices(self.slices_tuple):
331
+ if sy in self._sections:
332
+ out.append(
333
+ self._sections[sy][slices_x, slices_y, slices_z][
334
+ numpy.asarray(
335
+ item[
336
+ relative_slices_x,
337
+ relative_slices_y,
338
+ relative_slices_z,
339
+ ]
340
+ )
341
+ ]
342
+ )
343
+ else:
344
+ out.append(
345
+ numpy.full(
346
+ numpy.count_nonzero(
347
+ numpy.asarray(
348
+ item[
349
+ relative_slices_x,
350
+ relative_slices_y,
351
+ relative_slices_z,
352
+ ]
353
+ )
354
+ ),
355
+ self.default_value,
356
+ self.dtype,
357
+ )
358
+ )
359
+ if out:
360
+ return numpy.concatenate(out)
361
+ else:
362
+ return numpy.full(0, self.default_value, self.dtype)
363
+ elif numpy.issubdtype(item.dtype, numpy.integer):
364
+ if isinstance(item, BoundedPartial3DArray):
365
+ raise ValueError(
366
+ "Index array with a BoundedPartial3DArray is not valid"
367
+ )
368
+ raise NotImplementedError(
369
+ "Index arrays are not currently supported"
370
+ ) # TODO
371
+ else:
372
+ raise ValueError(
373
+ f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
374
+ )
375
+ else:
376
+ raise KeyError(
377
+ f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
378
+ )
379
+
380
+ @overload
381
+ def __setitem__(
382
+ self,
383
+ item: Tuple[
384
+ Union[IntegerType, slice],
385
+ Union[IntegerType, slice],
386
+ Union[IntegerType, slice],
387
+ ],
388
+ value: Union[int, bool, numpy.ndarray, "BoundedPartial3DArray"],
389
+ ):
390
+ ...
391
+
392
+ @overload
393
+ def __setitem__(
394
+ self,
395
+ item: Union[numpy.ndarray, "BoundedPartial3DArray"],
396
+ value: Union[int, bool, numpy.ndarray],
397
+ ):
398
+ ...
399
+
400
+ def __setitem__(self, item, value):
401
+ """
402
+ Set a sub-section of the array.
403
+
404
+ >>> # set the value at a given location
405
+ >>> partial_array[3, 4, 5] = 1
406
+ >>> # set a cuboid volume in the array
407
+ >>> partial_array[2:3, 4:5, 6:7] = 1
408
+ >>> # slice and int can be mixed
409
+ >>> partial_array[2:3, 4, 6:7] = 1
410
+
411
+ :param slices: The slices or locations that define the volume to set.
412
+ :param value: The value to set at the given location. Can be an integer or array.
413
+ """
414
+ if isinstance(item, tuple):
415
+ if len(item) != 3:
416
+ raise KeyError(f"Tuple item must be of length 3, got {len(item)}")
417
+ elif all(isinstance(i, (int, numpy.integer, slice)) for i in item):
418
+ stacked_slices: Tuple[
419
+ Tuple[int, int, int],
420
+ Tuple[int, int, int],
421
+ Tuple[int, int, int],
422
+ ] = self._stack_slices(item)
423
+ if (
424
+ isinstance(value, Integer)
425
+ and numpy.issubdtype(self.dtype, numpy.integer)
426
+ ) or (isinstance(value, bool) and self.dtype == bool):
427
+ for sy, slices, _ in self._iter_slices(stacked_slices):
428
+ if sy in self._sections:
429
+ self._sections[sy][slices] = value
430
+ elif value != self.default_value:
431
+ self._parent_array.create_section(sy)
432
+ self._sections[sy][slices] = value
433
+ elif (
434
+ isinstance(value, (numpy.ndarray, BoundedPartial3DArray))
435
+ and (
436
+ numpy.issubdtype(value.dtype, numpy.integer)
437
+ and numpy.issubdtype(self.dtype, numpy.integer)
438
+ )
439
+ or (
440
+ numpy.issubdtype(value.dtype, bool)
441
+ and numpy.issubdtype(self.dtype, bool)
442
+ )
443
+ ):
444
+ size_array = self[item]
445
+ if size_array.shape != value.shape:
446
+ raise ValueError(
447
+ f"The shape of the index ({size_array.shape}) and the shape of the given array ({value.shape}) do not match."
448
+ )
449
+ for sy, slices, relative_slices in size_array._iter_slices(
450
+ stacked_slices
451
+ ):
452
+ if sy not in self._sections:
453
+ self._parent_array.create_section(sy)
454
+ self._sections[sy][slices] = numpy.asarray(
455
+ value[relative_slices]
456
+ )
457
+ else:
458
+ raise ValueError(f"Bad value {value}")
459
+
460
+ else:
461
+ raise KeyError(f"Unsupported tuple {item} for getitem")
462
+ elif isinstance(item, (numpy.ndarray, BoundedPartial3DArray)):
463
+ if item.dtype == bool:
464
+ if item.shape != self.shape:
465
+ raise ValueError(
466
+ f"The shape of the index ({self.shape}) and the shape of the given array ({item.shape}) do not match."
467
+ )
468
+ if isinstance(value, (int, numpy.integer, bool)):
469
+ for sy, slices, relative_slices in self._iter_slices(
470
+ self.slices_tuple
471
+ ):
472
+ bool_array = numpy.asarray(item[relative_slices])
473
+ if sy in self._sections:
474
+ self._sections[sy][slices][bool_array] = value
475
+ elif value != self.default_value and numpy.any(bool_array):
476
+ self._parent_array.create_section(sy)
477
+ self._sections[sy][slices][bool_array] = value
478
+ elif isinstance(value, numpy.ndarray):
479
+ start = 0
480
+ true_count = numpy.count_nonzero(item)
481
+ if true_count != value.size:
482
+ raise ValueError(
483
+ f"There are more True values ({true_count}) in the item array than there are values in the value array ({value.size})."
484
+ )
485
+
486
+ for slices_x, relative_slices_x in zip(
487
+ range(self.start_x, self.stop_x, self.step_x),
488
+ range(0, self.size_x),
489
+ ):
490
+ for (
491
+ sy,
492
+ (_, slices_y, slices_z),
493
+ (_, relative_slices_y, relative_slices_z),
494
+ ) in self._iter_slices(self.slices_tuple):
495
+ if sy not in self._sections:
496
+ self._parent_array.create_section(sy)
497
+ bool_array = numpy.asarray(
498
+ item[
499
+ relative_slices_x,
500
+ relative_slices_y,
501
+ relative_slices_z,
502
+ ]
503
+ )
504
+ count: int = numpy.count_nonzero(bool_array)
505
+ self._sections[sy][slices_x, slices_y, slices_z][
506
+ bool_array
507
+ ] = value[start : start + count]
508
+ start += count
509
+ else:
510
+ raise ValueError(
511
+ f"When setting using a bool array the value must be an int, bool or numpy.ndarray. Got {item.__class__.__name__}({item})"
512
+ )
513
+ elif numpy.issubdtype(item.dtype, numpy.integer):
514
+ if isinstance(item, BoundedPartial3DArray):
515
+ raise ValueError(
516
+ "Index array with a BoundedPartial3DArray is not valid"
517
+ )
518
+ raise NotImplementedError(
519
+ "Index arrays are not currently supported"
520
+ ) # TODO
521
+ else:
522
+ raise ValueError(
523
+ f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
524
+ )
525
+ else:
526
+ raise KeyError(
527
+ f"{item.__class__.__name__}({item}) is not a supported input for __getitem__"
528
+ )