polytope-python 1.0.31__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.
- polytope_feature/__init__.py +1 -0
- polytope_feature/datacube/__init__.py +1 -0
- polytope_feature/datacube/backends/__init__.py +1 -0
- polytope_feature/datacube/backends/datacube.py +171 -0
- polytope_feature/datacube/backends/fdb.py +399 -0
- polytope_feature/datacube/backends/mock.py +71 -0
- polytope_feature/datacube/backends/xarray.py +142 -0
- polytope_feature/datacube/datacube_axis.py +332 -0
- polytope_feature/datacube/index_tree_pb2.py +27 -0
- polytope_feature/datacube/tensor_index_tree.py +228 -0
- polytope_feature/datacube/transformations/__init__.py +1 -0
- polytope_feature/datacube/transformations/datacube_cyclic/__init__.py +1 -0
- polytope_feature/datacube/transformations/datacube_cyclic/datacube_cyclic.py +171 -0
- polytope_feature/datacube/transformations/datacube_mappers/__init__.py +1 -0
- polytope_feature/datacube/transformations/datacube_mappers/datacube_mappers.py +141 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/__init__.py +5 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/healpix.py +147 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +229 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +95 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +7896 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/reduced_gaussian.py +1459 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +5128 -0
- polytope_feature/datacube/transformations/datacube_mappers/mapper_types/regular.py +75 -0
- polytope_feature/datacube/transformations/datacube_merger/__init__.py +1 -0
- polytope_feature/datacube/transformations/datacube_merger/datacube_merger.py +95 -0
- polytope_feature/datacube/transformations/datacube_reverse/__init__.py +1 -0
- polytope_feature/datacube/transformations/datacube_reverse/datacube_reverse.py +65 -0
- polytope_feature/datacube/transformations/datacube_transformations.py +96 -0
- polytope_feature/datacube/transformations/datacube_type_change/__init__.py +1 -0
- polytope_feature/datacube/transformations/datacube_type_change/datacube_type_change.py +124 -0
- polytope_feature/datacube/tree_encoding.py +132 -0
- polytope_feature/engine/__init__.py +1 -0
- polytope_feature/engine/engine.py +19 -0
- polytope_feature/engine/hullslicer.py +316 -0
- polytope_feature/options.py +77 -0
- polytope_feature/polytope.py +71 -0
- polytope_feature/shapes.py +405 -0
- polytope_feature/utility/__init__.py +0 -0
- polytope_feature/utility/combinatorics.py +48 -0
- polytope_feature/utility/exceptions.py +45 -0
- polytope_feature/utility/geometry.py +26 -0
- polytope_feature/utility/list_tools.py +41 -0
- polytope_feature/utility/profiling.py +14 -0
- polytope_feature/version.py +1 -0
- polytope_python-1.0.31.dist-info/LICENSE +201 -0
- polytope_python-1.0.31.dist-info/METADATA +21 -0
- polytope_python-1.0.31.dist-info/RECORD +49 -0
- polytope_python-1.0.31.dist-info/WHEEL +5 -0
- polytope_python-1.0.31.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import OrderedDict
|
|
3
|
+
|
|
4
|
+
from sortedcontainers import SortedList
|
|
5
|
+
|
|
6
|
+
from .datacube_axis import IntDatacubeAxis, UnsliceableDatacubeAxis
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DatacubePath(OrderedDict):
|
|
10
|
+
def values(self):
|
|
11
|
+
return tuple(super().values())
|
|
12
|
+
|
|
13
|
+
def keys(self):
|
|
14
|
+
return tuple(super().keys())
|
|
15
|
+
|
|
16
|
+
def pprint(self):
|
|
17
|
+
result = ""
|
|
18
|
+
for k, v in self.items():
|
|
19
|
+
result += f"{k}={v},"
|
|
20
|
+
print(result[:-1])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TensorIndexTree(object):
|
|
24
|
+
root = IntDatacubeAxis()
|
|
25
|
+
root.name = "root"
|
|
26
|
+
|
|
27
|
+
def __init__(self, axis=root, values=tuple()):
|
|
28
|
+
# NOTE: the values here is a tuple so we can hash it
|
|
29
|
+
self.values = values
|
|
30
|
+
self.children = SortedList()
|
|
31
|
+
self._parent = None
|
|
32
|
+
self.result = []
|
|
33
|
+
self.axis = axis
|
|
34
|
+
self.ancestors = []
|
|
35
|
+
self.indexes = []
|
|
36
|
+
self.hidden = False
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def leaves(self):
|
|
40
|
+
leaves = []
|
|
41
|
+
self._collect_leaf_nodes(leaves)
|
|
42
|
+
return leaves
|
|
43
|
+
|
|
44
|
+
def _collect_leaf_nodes(self, leaves):
|
|
45
|
+
if len(self.children) == 0:
|
|
46
|
+
leaves.append(self)
|
|
47
|
+
self.ancestors.append(self)
|
|
48
|
+
for n in self.children:
|
|
49
|
+
for ancestor in self.ancestors:
|
|
50
|
+
n.ancestors.append(ancestor)
|
|
51
|
+
if self.axis != TensorIndexTree.root:
|
|
52
|
+
n.ancestors.append(self)
|
|
53
|
+
n._collect_leaf_nodes(leaves)
|
|
54
|
+
|
|
55
|
+
def __setitem__(self, key, value):
|
|
56
|
+
setattr(self, key, value)
|
|
57
|
+
|
|
58
|
+
def __getitem__(self, key):
|
|
59
|
+
return getattr(self, key)
|
|
60
|
+
|
|
61
|
+
def __delitem__(self, key):
|
|
62
|
+
return delattr(self, key)
|
|
63
|
+
|
|
64
|
+
def __hash__(self):
|
|
65
|
+
return hash((self.axis.name, self.values))
|
|
66
|
+
|
|
67
|
+
def __eq__(self, other):
|
|
68
|
+
if not isinstance(other, TensorIndexTree):
|
|
69
|
+
return False
|
|
70
|
+
if self.axis.name != other.axis.name:
|
|
71
|
+
return False
|
|
72
|
+
else:
|
|
73
|
+
if other.values == self.values:
|
|
74
|
+
return True
|
|
75
|
+
else:
|
|
76
|
+
if isinstance(self.axis, UnsliceableDatacubeAxis):
|
|
77
|
+
return False
|
|
78
|
+
else:
|
|
79
|
+
if len(other.values) != len(self.values):
|
|
80
|
+
return False
|
|
81
|
+
for i in range(len(other.values)):
|
|
82
|
+
other_val = other.values[i]
|
|
83
|
+
self_val = self.values[i]
|
|
84
|
+
if self.axis.can_round:
|
|
85
|
+
if abs(other_val - self_val) > 2 * max(other.axis.tol, self.axis.tol):
|
|
86
|
+
return False
|
|
87
|
+
else:
|
|
88
|
+
if other_val != self_val:
|
|
89
|
+
return False
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def __lt__(self, other):
|
|
93
|
+
return (self.axis.name, self.values) < (other.axis.name, other.values)
|
|
94
|
+
|
|
95
|
+
def __repr__(self):
|
|
96
|
+
if self.axis != "root":
|
|
97
|
+
return f"{self.axis.name}={self.values}"
|
|
98
|
+
else:
|
|
99
|
+
return f"{self.axis}"
|
|
100
|
+
|
|
101
|
+
def add_child(self, node):
|
|
102
|
+
self.children.add(node)
|
|
103
|
+
node._parent = self
|
|
104
|
+
|
|
105
|
+
def add_value(self, value):
|
|
106
|
+
new_values = list(self.values)
|
|
107
|
+
new_values.append(value)
|
|
108
|
+
new_values.sort()
|
|
109
|
+
self.values = tuple(new_values)
|
|
110
|
+
|
|
111
|
+
def create_child(self, axis, value, next_nodes):
|
|
112
|
+
node = TensorIndexTree(axis, (value,))
|
|
113
|
+
existing_child = self.find_child(node)
|
|
114
|
+
if not existing_child:
|
|
115
|
+
self.add_child(node)
|
|
116
|
+
return (node, next_nodes)
|
|
117
|
+
return (existing_child, next_nodes)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def parent(self):
|
|
121
|
+
return self._parent
|
|
122
|
+
|
|
123
|
+
@parent.setter
|
|
124
|
+
def set_parent(self, node):
|
|
125
|
+
if self.parent is not None:
|
|
126
|
+
self.parent.children.remove(self)
|
|
127
|
+
self._parent = node
|
|
128
|
+
self._parent.children.add(self)
|
|
129
|
+
|
|
130
|
+
def get_root(self):
|
|
131
|
+
node = self
|
|
132
|
+
while node.parent is not None:
|
|
133
|
+
node = node.parent
|
|
134
|
+
return node
|
|
135
|
+
|
|
136
|
+
def is_root(self):
|
|
137
|
+
return self.parent is None
|
|
138
|
+
|
|
139
|
+
def find_child(self, node):
|
|
140
|
+
index = self.children.bisect_left(node)
|
|
141
|
+
if index >= len(self.children):
|
|
142
|
+
return None
|
|
143
|
+
child = self.children[index]
|
|
144
|
+
if not child == node:
|
|
145
|
+
return None
|
|
146
|
+
return child
|
|
147
|
+
|
|
148
|
+
def add_node_layer_after(self, ax_name, vals):
|
|
149
|
+
ax = IntDatacubeAxis()
|
|
150
|
+
ax.name = ax_name
|
|
151
|
+
interm_node = TensorIndexTree(ax, vals)
|
|
152
|
+
interm_node.children = self.children
|
|
153
|
+
interm_node._parent = self
|
|
154
|
+
self.children = SortedList()
|
|
155
|
+
self.children.add(interm_node)
|
|
156
|
+
return interm_node
|
|
157
|
+
|
|
158
|
+
def delete_non_index_nodes(self, index_vals):
|
|
159
|
+
grandparent = self._parent._parent
|
|
160
|
+
grandparent.indexes.extend(index_vals)
|
|
161
|
+
self.remove_branch()
|
|
162
|
+
return grandparent
|
|
163
|
+
|
|
164
|
+
def hide_non_index_nodes(self, index_vals):
|
|
165
|
+
grandparent = self._parent._parent
|
|
166
|
+
grandparent.indexes.extend(index_vals)
|
|
167
|
+
self.hide_two_levels()
|
|
168
|
+
return grandparent
|
|
169
|
+
|
|
170
|
+
def hide_two_levels(self):
|
|
171
|
+
self.hidden = True
|
|
172
|
+
if self._parent.non_hidden_children() == 0:
|
|
173
|
+
self._parent.hidden = True
|
|
174
|
+
|
|
175
|
+
def non_hidden_children(self):
|
|
176
|
+
non_hidden_child_counter = 0
|
|
177
|
+
for c in self.children:
|
|
178
|
+
if not c.hidden:
|
|
179
|
+
non_hidden_child_counter += 1
|
|
180
|
+
return non_hidden_child_counter
|
|
181
|
+
|
|
182
|
+
def merge(self, other):
|
|
183
|
+
for other_child in other.children:
|
|
184
|
+
my_child = self.find_child(other_child)
|
|
185
|
+
if not my_child:
|
|
186
|
+
self.add_child(other_child)
|
|
187
|
+
else:
|
|
188
|
+
my_child.merge(other_child)
|
|
189
|
+
|
|
190
|
+
def pprint(self, level=0):
|
|
191
|
+
if self.axis.name == "root":
|
|
192
|
+
logging.debug("\n")
|
|
193
|
+
logging.debug("\t" * level + "\u21b3" + str(self))
|
|
194
|
+
for child in self.children:
|
|
195
|
+
if not child.hidden:
|
|
196
|
+
child.pprint(level + 1)
|
|
197
|
+
if len(self.children) == 0:
|
|
198
|
+
logging.debug("\t" * (level + 1) + "\u21b3" + str(self.result))
|
|
199
|
+
|
|
200
|
+
def remove_branch(self):
|
|
201
|
+
if not self.is_root():
|
|
202
|
+
old_parent = self._parent
|
|
203
|
+
self._parent.children.remove(self)
|
|
204
|
+
self._parent = None
|
|
205
|
+
if len(old_parent.children) == 0:
|
|
206
|
+
old_parent.remove_branch()
|
|
207
|
+
|
|
208
|
+
def remove_compressed_branch(self, value):
|
|
209
|
+
if value in self.values:
|
|
210
|
+
if len(self.values) == 1:
|
|
211
|
+
self.remove_branch()
|
|
212
|
+
else:
|
|
213
|
+
self.values = tuple(val for val in self.values if val != value)
|
|
214
|
+
|
|
215
|
+
def flatten(self):
|
|
216
|
+
path = DatacubePath()
|
|
217
|
+
ancestors = self.get_ancestors()
|
|
218
|
+
for ancestor in ancestors:
|
|
219
|
+
path[ancestor.axis.name] = ancestor.values
|
|
220
|
+
return path
|
|
221
|
+
|
|
222
|
+
def get_ancestors(self):
|
|
223
|
+
ancestors = []
|
|
224
|
+
current_node = self
|
|
225
|
+
while current_node.axis.name != "root":
|
|
226
|
+
ancestors.append(current_node)
|
|
227
|
+
current_node = current_node.parent
|
|
228
|
+
return ancestors[::-1]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ..transformations.datacube_transformations import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .datacube_cyclic import *
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from ....utility.list_tools import unique
|
|
5
|
+
from ..datacube_transformations import DatacubeAxisTransformation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatacubeAxisCyclic(DatacubeAxisTransformation):
|
|
9
|
+
# The transformation here will be to point the old axes to the new cyclic axes
|
|
10
|
+
|
|
11
|
+
def __init__(self, name, cyclic_options, datacube=None):
|
|
12
|
+
self.name = name
|
|
13
|
+
self.transformation_options = cyclic_options
|
|
14
|
+
self.range = cyclic_options.range
|
|
15
|
+
|
|
16
|
+
def generate_final_transformation(self):
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
def transformation_axes_final(self):
|
|
20
|
+
return [self.name]
|
|
21
|
+
|
|
22
|
+
def change_val_type(self, axis_name, values):
|
|
23
|
+
return values
|
|
24
|
+
|
|
25
|
+
def blocked_axes(self):
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
def unwanted_axes(self):
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
def update_range(self, axis):
|
|
32
|
+
axis.range = self.range
|
|
33
|
+
|
|
34
|
+
def _remap_range_to_axis_range(self, range, axis):
|
|
35
|
+
self.update_range(axis)
|
|
36
|
+
axis_lower = axis.range[0]
|
|
37
|
+
axis_upper = axis.range[1]
|
|
38
|
+
axis_range = axis_upper - axis_lower
|
|
39
|
+
lower = range[0]
|
|
40
|
+
upper = range[1]
|
|
41
|
+
if lower < axis_lower:
|
|
42
|
+
# In this case we need to calculate the number of loops between the axis lower
|
|
43
|
+
# and the lower to recenter the lower
|
|
44
|
+
loops = int((axis_lower - lower - axis.tol) / axis_range)
|
|
45
|
+
return_lower = lower + (loops + 1) * axis_range
|
|
46
|
+
return_upper = upper + (loops + 1) * axis_range
|
|
47
|
+
elif lower >= axis_upper:
|
|
48
|
+
# In this case we need to calculate the number of loops between the axis upper
|
|
49
|
+
# and the lower to recenter the lower
|
|
50
|
+
loops = int((lower - axis_upper) / axis_range)
|
|
51
|
+
return_lower = lower - (loops + 1) * axis_range
|
|
52
|
+
return_upper = upper - (loops + 1) * axis_range
|
|
53
|
+
else:
|
|
54
|
+
# In this case, the lower value is already in the right range
|
|
55
|
+
return_lower = lower
|
|
56
|
+
return_upper = upper
|
|
57
|
+
return [return_lower, return_upper]
|
|
58
|
+
|
|
59
|
+
def _remap_val_to_axis_range(self, value, axis):
|
|
60
|
+
value = self._remap_range_to_axis_range([value, value], axis)
|
|
61
|
+
return value[0]
|
|
62
|
+
|
|
63
|
+
def offset(self, range, axis, offset):
|
|
64
|
+
# We first unpad the range by the axis tolerance to make sure that
|
|
65
|
+
# we find the wanted range of the cyclic axis since we padded by the axis tolerance before.
|
|
66
|
+
# Also, it's safer that we find the offset of a value inside the range instead of on the border
|
|
67
|
+
unpadded_range = [range[0] + 1.5 * axis.tol, range[1] - 1.5 * axis.tol]
|
|
68
|
+
cyclic_range = self._remap_range_to_axis_range(unpadded_range, axis)
|
|
69
|
+
offset = unpadded_range[0] - cyclic_range[0]
|
|
70
|
+
return offset
|
|
71
|
+
|
|
72
|
+
def remap(self, range, ranges, axis):
|
|
73
|
+
self.update_range(axis)
|
|
74
|
+
if axis.range[0] - axis.tol <= range[0] <= axis.range[1] + axis.tol:
|
|
75
|
+
if axis.range[0] - axis.tol <= range[1] <= axis.range[1] + axis.tol:
|
|
76
|
+
# If we are already in the cyclic range, return it
|
|
77
|
+
return [range]
|
|
78
|
+
elif abs(range[0] - range[1]) <= 2 * axis.tol:
|
|
79
|
+
# If we have a range that is just one point, then it should still be counted
|
|
80
|
+
# and so we should take a small interval around it to find values inbetween
|
|
81
|
+
range = [
|
|
82
|
+
self._remap_val_to_axis_range(range[0], axis) - axis.tol,
|
|
83
|
+
self._remap_val_to_axis_range(range[0], axis) + axis.tol,
|
|
84
|
+
]
|
|
85
|
+
return [range]
|
|
86
|
+
range_intervals = self.to_intervals(range, [[]], axis)
|
|
87
|
+
ranges = []
|
|
88
|
+
for interval in range_intervals:
|
|
89
|
+
if abs(interval[0] - interval[1]) > 0:
|
|
90
|
+
# If the interval is not just a single point, we remap it to the axis range
|
|
91
|
+
range = self._remap_range_to_axis_range([interval[0], interval[1]], axis)
|
|
92
|
+
up = range[1]
|
|
93
|
+
low = range[0]
|
|
94
|
+
if up < low:
|
|
95
|
+
# Make sure we remap in the right order
|
|
96
|
+
ranges.append([up - axis.tol, low + axis.tol])
|
|
97
|
+
else:
|
|
98
|
+
ranges.append([low - axis.tol, up + axis.tol])
|
|
99
|
+
return ranges
|
|
100
|
+
|
|
101
|
+
def to_intervals(self, range, intervals, axis):
|
|
102
|
+
self.update_range(axis)
|
|
103
|
+
if range[0] == -math.inf:
|
|
104
|
+
range[0] = axis.range[0]
|
|
105
|
+
if range[1] == math.inf:
|
|
106
|
+
range[1] = axis.range[1]
|
|
107
|
+
axis_lower = axis.range[0]
|
|
108
|
+
axis_upper = axis.range[1]
|
|
109
|
+
axis_range = axis_upper - axis_lower
|
|
110
|
+
lower = range[0]
|
|
111
|
+
upper = range[1]
|
|
112
|
+
intervals = []
|
|
113
|
+
if lower < axis_upper:
|
|
114
|
+
# In this case, we want to go from lower to the first remapped cyclic axis upper
|
|
115
|
+
# or the asked upper range value.
|
|
116
|
+
# For example, if we have cyclic range [0,360] and we want to break [-270,180] into intervals,
|
|
117
|
+
# we first want to obtain [-270, 0] as the first range, where 0 is the remapped cyclic axis upper
|
|
118
|
+
# but if we wanted to break [-270, -180] into intervals, we would want to get [-270,-180],
|
|
119
|
+
# where -180 is the asked upper range value.
|
|
120
|
+
loops = int((axis_upper - lower) / axis_range)
|
|
121
|
+
remapped_up = axis_upper - (loops) * axis_range
|
|
122
|
+
new_upper = min(upper, remapped_up)
|
|
123
|
+
else:
|
|
124
|
+
# In this case, since lower >= axis_upper, we need to either go to the asked upper range
|
|
125
|
+
# or we need to go to the first remapped cyclic axis upper which is higher than lower
|
|
126
|
+
new_upper = min(axis_upper + axis_range, upper)
|
|
127
|
+
while new_upper < lower:
|
|
128
|
+
new_upper = min(new_upper + axis_range, upper)
|
|
129
|
+
intervals.append([lower, new_upper])
|
|
130
|
+
# Now that we have established what the first interval should be, we should just jump from cyclic range
|
|
131
|
+
# to cyclic range until we hit the asked upper range value.
|
|
132
|
+
new_up = deepcopy(new_upper)
|
|
133
|
+
while new_up < upper:
|
|
134
|
+
new_upper = new_up
|
|
135
|
+
new_up = min(upper, new_upper + axis_range)
|
|
136
|
+
intervals.append([new_upper, new_up])
|
|
137
|
+
# Once we have added all the in-between ranges, we need to add the last interval
|
|
138
|
+
intervals.append([new_up, upper])
|
|
139
|
+
return intervals
|
|
140
|
+
|
|
141
|
+
def find_indices_between(self, indexes_ranges, low, up, datacube, method, indexes_between_ranges, axis):
|
|
142
|
+
search_ranges = self.remap([low, up], [], axis)
|
|
143
|
+
original_search_ranges = self.to_intervals([low, up], [], axis)
|
|
144
|
+
# Find the offsets for each interval in the requested range, which we will need later
|
|
145
|
+
search_ranges_offset = []
|
|
146
|
+
for r in original_search_ranges:
|
|
147
|
+
offset = self.offset(r, axis, 0)
|
|
148
|
+
search_ranges_offset.append(offset)
|
|
149
|
+
idx_between = []
|
|
150
|
+
for i in range(len(search_ranges)):
|
|
151
|
+
r = search_ranges[i]
|
|
152
|
+
offset = search_ranges_offset[i]
|
|
153
|
+
low = r[0]
|
|
154
|
+
up = r[1]
|
|
155
|
+
indexes_between = axis.find_standard_indices_between(indexes_ranges, low, up, datacube, method)
|
|
156
|
+
# Now the indexes_between are values on the cyclic range so need to remap them to their original
|
|
157
|
+
# values before returning them
|
|
158
|
+
# if we have a special indexes between range that needs additional offset, treat it here
|
|
159
|
+
if len(indexes_between) == 0:
|
|
160
|
+
idx_between = idx_between
|
|
161
|
+
else:
|
|
162
|
+
for k in range(len(indexes_between)):
|
|
163
|
+
if offset is None:
|
|
164
|
+
indexes_between[k] = indexes_between[k]
|
|
165
|
+
else:
|
|
166
|
+
indexes_between[k] = round(indexes_between[k] + offset, int(-math.log10(axis.tol)))
|
|
167
|
+
idx_between.append(indexes_between[k])
|
|
168
|
+
if offset is not None:
|
|
169
|
+
# Note that we can only do unique if not dealing with time values
|
|
170
|
+
idx_between = unique(idx_between)
|
|
171
|
+
return idx_between
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .datacube_mappers import *
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
4
|
+
from ..datacube_transformations import DatacubeAxisTransformation
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatacubeMapper(DatacubeAxisTransformation):
|
|
8
|
+
# Needs to implements DatacubeAxisTransformation methods
|
|
9
|
+
|
|
10
|
+
def __init__(self, name, mapper_options, datacube=None):
|
|
11
|
+
self.transformation_options = mapper_options
|
|
12
|
+
self.grid_type = mapper_options.type
|
|
13
|
+
self.grid_resolution = mapper_options.resolution
|
|
14
|
+
self.grid_axes = mapper_options.axes
|
|
15
|
+
self.local_area = []
|
|
16
|
+
self.md5_hash = None
|
|
17
|
+
if mapper_options.md5_hash is not None:
|
|
18
|
+
self.md5_hash = mapper_options.md5_hash
|
|
19
|
+
if mapper_options.local is not None:
|
|
20
|
+
self.local_area = mapper_options.local
|
|
21
|
+
self._axis_reversed = None
|
|
22
|
+
if mapper_options.axis_reversed is not None:
|
|
23
|
+
self._axis_reversed = mapper_options.axis_reversed
|
|
24
|
+
self.old_axis = name
|
|
25
|
+
self._final_transformation = self.generate_final_transformation()
|
|
26
|
+
self._final_mapped_axes = self._final_transformation._mapped_axes
|
|
27
|
+
self._axis_reversed = self._final_transformation._axis_reversed
|
|
28
|
+
self.compressed_grid_axes = self._final_transformation.compressed_grid_axes
|
|
29
|
+
self.md5_hash = self._final_transformation.md5_hash
|
|
30
|
+
|
|
31
|
+
def generate_final_transformation(self):
|
|
32
|
+
map_type = _type_to_datacube_mapper_lookup[self.grid_type]
|
|
33
|
+
module = import_module(
|
|
34
|
+
"polytope_feature.datacube.transformations.datacube_mappers.mapper_types." + self.grid_type
|
|
35
|
+
)
|
|
36
|
+
constructor = getattr(module, map_type)
|
|
37
|
+
transformation = deepcopy(
|
|
38
|
+
constructor(
|
|
39
|
+
self.old_axis, self.grid_axes, self.grid_resolution, self.md5_hash, self.local_area, self._axis_reversed
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
return transformation
|
|
43
|
+
|
|
44
|
+
def blocked_axes(self):
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
def unwanted_axes(self):
|
|
48
|
+
return [self._final_mapped_axes[0]]
|
|
49
|
+
|
|
50
|
+
def transformation_axes_final(self):
|
|
51
|
+
final_axes = self._final_mapped_axes
|
|
52
|
+
return final_axes
|
|
53
|
+
|
|
54
|
+
# Needs to also implement its own methods
|
|
55
|
+
|
|
56
|
+
def change_val_type(self, axis_name, values):
|
|
57
|
+
# the new axis_vals created will be floats
|
|
58
|
+
return [0.0]
|
|
59
|
+
|
|
60
|
+
def _mapped_axes(self):
|
|
61
|
+
# NOTE: Each of the mapper method needs to call it's sub mapper method
|
|
62
|
+
final_axes = self._final_mapped_axes
|
|
63
|
+
return final_axes
|
|
64
|
+
|
|
65
|
+
def _base_axis(self):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def _resolution(self):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def first_axis_vals(self):
|
|
72
|
+
return self._final_transformation.first_axis_vals()
|
|
73
|
+
|
|
74
|
+
def second_axis_vals(self, first_val):
|
|
75
|
+
return self._final_transformation.second_axis_vals(first_val)
|
|
76
|
+
|
|
77
|
+
def map_first_axis(self, lower, upper):
|
|
78
|
+
return self._final_transformation.map_first_axis(lower, upper)
|
|
79
|
+
|
|
80
|
+
def map_second_axis(self, first_val, lower, upper):
|
|
81
|
+
return self._final_transformation.map_second_axis(first_val, lower, upper)
|
|
82
|
+
|
|
83
|
+
def find_second_idx(self, first_val, second_val):
|
|
84
|
+
return self._final_transformation.find_second_idx(first_val, second_val)
|
|
85
|
+
|
|
86
|
+
def unmap_first_val_to_start_line_idx(self, first_val):
|
|
87
|
+
return self._final_transformation.unmap_first_val_to_start_line_idx(first_val)
|
|
88
|
+
|
|
89
|
+
def unmap(self, first_val, second_val):
|
|
90
|
+
return self._final_transformation.unmap(first_val, second_val)
|
|
91
|
+
|
|
92
|
+
def find_modified_indexes(self, indexes, path, datacube, axis):
|
|
93
|
+
if axis.name == self._mapped_axes()[0]:
|
|
94
|
+
return self.first_axis_vals()
|
|
95
|
+
if axis.name == self._mapped_axes()[1]:
|
|
96
|
+
first_val = path[self._mapped_axes()[0]]
|
|
97
|
+
if not isinstance(first_val, tuple):
|
|
98
|
+
first_val = (first_val,)
|
|
99
|
+
return self.second_axis_vals(first_val)
|
|
100
|
+
|
|
101
|
+
def unmap_path_key(self, key_value_path, leaf_path, unwanted_path, axis):
|
|
102
|
+
value = key_value_path[axis.name]
|
|
103
|
+
if axis.name == self._mapped_axes()[0]:
|
|
104
|
+
unwanted_val = key_value_path[self._mapped_axes()[0]]
|
|
105
|
+
unwanted_path[axis.name] = unwanted_val
|
|
106
|
+
if axis.name == self._mapped_axes()[1]:
|
|
107
|
+
first_val = unwanted_path[self._mapped_axes()[0]]
|
|
108
|
+
unmapped_idx = []
|
|
109
|
+
for val in value:
|
|
110
|
+
unmapped_idx.append(self.unmap(first_val, (val,)))
|
|
111
|
+
# unmapped_idx = self.unmap(first_val, value)
|
|
112
|
+
leaf_path.pop(self._mapped_axes()[0], None)
|
|
113
|
+
key_value_path.pop(axis.name)
|
|
114
|
+
key_value_path[self.old_axis] = unmapped_idx
|
|
115
|
+
return (key_value_path, leaf_path, unwanted_path)
|
|
116
|
+
|
|
117
|
+
def unmap_tree_node(self, node, unwanted_path):
|
|
118
|
+
values = node.values
|
|
119
|
+
if node.axis.name == self._mapped_axes()[0]:
|
|
120
|
+
unwanted_path[node.axis.name] = values
|
|
121
|
+
returned_node = node
|
|
122
|
+
if node.axis.name == self._mapped_axes()[1]:
|
|
123
|
+
first_vals = unwanted_path[self._mapped_axes()[0]]
|
|
124
|
+
unmapped_idxs = []
|
|
125
|
+
for first_val in first_vals:
|
|
126
|
+
for val in values:
|
|
127
|
+
unmapped_idx = self.unmap([first_val], [val])
|
|
128
|
+
unmapped_idxs.append(unmapped_idx)
|
|
129
|
+
returned_node = node.hide_non_index_nodes(unmapped_idxs)
|
|
130
|
+
return (returned_node, unwanted_path)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
_type_to_datacube_mapper_lookup = {
|
|
134
|
+
"octahedral": "OctahedralGridMapper",
|
|
135
|
+
"healpix": "HealpixGridMapper",
|
|
136
|
+
"regular": "RegularGridMapper",
|
|
137
|
+
"reduced_ll": "ReducedLatLonMapper",
|
|
138
|
+
"local_regular": "LocalRegularGridMapper",
|
|
139
|
+
"healpix_nested": "NestedHealpixGridMapper",
|
|
140
|
+
"reduced_gaussian": "ReducedGaussianGridMapper",
|
|
141
|
+
}
|