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.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {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
|
+
)
|