timelined_array 0.1.2__tar.gz → 0.1.3__tar.gz
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.
- {timelined_array-0.1.2 → timelined_array-0.1.3}/PKG-INFO +8 -5
- {timelined_array-0.1.2 → timelined_array-0.1.3}/README.md +2 -0
- timelined_array-0.1.3/pyproject.toml +73 -0
- timelined_array-0.1.3/pyproject.toml.orig +64 -0
- timelined_array-0.1.3/src/timelined_array/__init__.py +5 -0
- {timelined_array-0.1.2 → timelined_array-0.1.3}/src/timelined_array/time.py +177 -100
- timelined_array-0.1.2/LICENSE +0 -21
- timelined_array-0.1.2/pyproject.toml +0 -90
- timelined_array-0.1.2/src/timelined_array/__init__.py +0 -3
- timelined_array-0.1.2/tests/__init__.py +0 -0
- timelined_array-0.1.2/tests/test_timelined_array.py +0 -267
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: timelined_array
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays.
|
|
5
|
-
Author
|
|
5
|
+
Author: Timothe Jost
|
|
6
|
+
Author-email: Timothe Jost <44769559+JostTim@users.noreply.github.com>
|
|
6
7
|
License: MIT
|
|
8
|
+
Requires-Dist: numpy>=2
|
|
9
|
+
Requires-Python: >=3.11
|
|
7
10
|
Project-URL: Homepage, https://pypi.org/project/timelined_array/
|
|
8
11
|
Project-URL: Source, https://github.com/JostTim/timelined_array
|
|
9
|
-
Requires-Python: >=3.11
|
|
10
|
-
Requires-Dist: numpy>=2
|
|
11
12
|
Description-Content-Type: text/markdown
|
|
12
13
|
|
|
13
14
|
# timelined_array
|
|
@@ -17,6 +18,8 @@ Description-Content-Type: text/markdown
|
|
|
17
18
|

|
|
18
19
|

|
|
19
20
|
[](https://codecov.io/gh/JostTim/timelined_array)
|
|
21
|
+

|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
<!-- eventually, add ?cache-bust=1 to badges url above, and update the value of cache-bust randomly at git commits (or based on the commit name) so that we always see the latest badge produced, and not the cached badge in the browser.-->
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|

|
|
6
6
|

|
|
7
7
|
[](https://codecov.io/gh/JostTim/timelined_array)
|
|
8
|
+

|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
<!-- eventually, add ?cache-bust=1 to badges url above, and update the value of cache-bust randomly at git commits (or based on the commit name) so that we always see the latest badge produced, and not the cached badge in the browser.-->
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "timelined_array"
|
|
3
|
+
description = "Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays."
|
|
4
|
+
version = "0.1.3"
|
|
5
|
+
dependencies = ["numpy>=2"]
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[[project.authors]]
|
|
10
|
+
name = "Timothe Jost"
|
|
11
|
+
email = "44769559+JostTim@users.noreply.github.com"
|
|
12
|
+
|
|
13
|
+
[project.license]
|
|
14
|
+
text = "MIT"
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://pypi.org/project/timelined_array/"
|
|
18
|
+
Source = "https://github.com/JostTim/timelined_array"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=9.1.1",
|
|
23
|
+
"pytest-cov>=7.1.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
28
|
+
build-backend = "uv_build"
|
|
29
|
+
|
|
30
|
+
[tool.uv]
|
|
31
|
+
package = true
|
|
32
|
+
|
|
33
|
+
[tool.ty.rules]
|
|
34
|
+
invalid-method-override = "ignore"
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
ignore = [
|
|
38
|
+
"B010",
|
|
39
|
+
"PIE796",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
addopts = "-ra -q --pdbcls=IPython.terminal.debugger:TerminalPdb --cov-report xml --cov=timelined_array --junitxml=pytest_results.xml --cov-config=pyproject.toml"
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[tool.coverage.run]
|
|
47
|
+
omit = [
|
|
48
|
+
".vscode/*",
|
|
49
|
+
".tests/*",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.bumpversion]
|
|
53
|
+
current_version = "0.1.3"
|
|
54
|
+
parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)'
|
|
55
|
+
serialize = ["{major}.{minor}.{patch}"]
|
|
56
|
+
search = "{current_version}"
|
|
57
|
+
replace = "{new_version}"
|
|
58
|
+
regex = false
|
|
59
|
+
ignore_missing_version = false
|
|
60
|
+
tag = false
|
|
61
|
+
sign_tags = false
|
|
62
|
+
tag_name = "v{new_version}"
|
|
63
|
+
tag_message = "Bump version: {current_version} → {new_version}"
|
|
64
|
+
allow_dirty = true
|
|
65
|
+
commit = false
|
|
66
|
+
message = "Bump version: {current_version} → {new_version}"
|
|
67
|
+
commit_args = ""
|
|
68
|
+
|
|
69
|
+
[[tool.bumpversion.files]]
|
|
70
|
+
filename = "pyproject.toml"
|
|
71
|
+
|
|
72
|
+
[[tool.bumpversion.files]]
|
|
73
|
+
filename = "src/timelined_array/__init__.py"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "timelined_array"
|
|
3
|
+
description = "Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays."
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Timothe Jost", email = "44769559+JostTim@users.noreply.github.com" },
|
|
6
|
+
]
|
|
7
|
+
version = "0.1.3"
|
|
8
|
+
dependencies = ["numpy>=2"]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
"Homepage" = "https://pypi.org/project/timelined_array/"
|
|
15
|
+
"Source" = "https://github.com/JostTim/timelined_array"
|
|
16
|
+
|
|
17
|
+
[dependency-groups]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=9.1.1",
|
|
20
|
+
"pytest-cov>=7.1.0"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
25
|
+
build-backend = "uv_build"
|
|
26
|
+
|
|
27
|
+
[tool.uv]
|
|
28
|
+
package = true
|
|
29
|
+
|
|
30
|
+
[tool.ty.rules]
|
|
31
|
+
invalid-method-override = "ignore"
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
ignore = ["B010", "PIE796"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
addopts = "-ra -q --pdbcls=IPython.terminal.debugger:TerminalPdb --cov-report xml --cov=timelined_array --junitxml=pytest_results.xml --cov-config=pyproject.toml"
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
|
|
40
|
+
[tool.coverage.run]
|
|
41
|
+
omit = [".vscode/*", ".tests/*"]
|
|
42
|
+
|
|
43
|
+
[tool.bumpversion]
|
|
44
|
+
current_version = "0.1.3"
|
|
45
|
+
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
46
|
+
serialize = ["{major}.{minor}.{patch}"]
|
|
47
|
+
search = "{current_version}"
|
|
48
|
+
replace = "{new_version}"
|
|
49
|
+
regex = false
|
|
50
|
+
ignore_missing_version = false
|
|
51
|
+
tag = false
|
|
52
|
+
sign_tags = false
|
|
53
|
+
tag_name = "v{new_version}"
|
|
54
|
+
tag_message = "Bump version: {current_version} → {new_version}"
|
|
55
|
+
allow_dirty = true
|
|
56
|
+
commit = false
|
|
57
|
+
message = "Bump version: {current_version} → {new_version}"
|
|
58
|
+
commit_args = ""
|
|
59
|
+
|
|
60
|
+
[[tool.bumpversion.files]]
|
|
61
|
+
filename = "pyproject.toml"
|
|
62
|
+
|
|
63
|
+
[[tool.bumpversion.files]]
|
|
64
|
+
filename = "src/timelined_array/__init__.py"
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from logging import getLogger
|
|
5
|
-
from enum import Enum
|
|
6
1
|
import operator
|
|
2
|
+
from collections.abc import Callable, Sequence
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from typing import Any, Literal, Protocol, Self, TypeAlias, TypeVar
|
|
7
6
|
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
8
9
|
from numpy.typing import NDArray
|
|
9
|
-
from types import MethodType
|
|
10
|
-
from typing import Tuple, List, Protocol, Type, Callable, Any, Optional, TypeVar, Sequence
|
|
11
10
|
|
|
12
11
|
Tp = TypeVar("Tp", bound="TimelinedArray")
|
|
13
12
|
|
|
@@ -19,7 +18,6 @@ logger = getLogger("timelined_array")
|
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
class TimeCompatibleProtocol(Protocol):
|
|
22
|
-
|
|
23
21
|
time_dimension: int
|
|
24
22
|
timeline: "Timeline"
|
|
25
23
|
|
|
@@ -28,7 +26,7 @@ class TimeCompatibleProtocol(Protocol):
|
|
|
28
26
|
def __array__(self) -> np.ndarray: ...
|
|
29
27
|
|
|
30
28
|
@property
|
|
31
|
-
def shape(self) ->
|
|
29
|
+
def shape(self) -> tuple[int, ...]: ...
|
|
32
30
|
|
|
33
31
|
@property
|
|
34
32
|
def ndim(self) -> int: ...
|
|
@@ -36,7 +34,7 @@ class TimeCompatibleProtocol(Protocol):
|
|
|
36
34
|
@property
|
|
37
35
|
def itime(self) -> "TimeIndexer": ...
|
|
38
36
|
|
|
39
|
-
def _get_array_cls(self) -> "
|
|
37
|
+
def _get_array_cls(self) -> "type": ...
|
|
40
38
|
|
|
41
39
|
def transpose(self): ...
|
|
42
40
|
|
|
@@ -78,8 +76,6 @@ class Timeline(np.ndarray):
|
|
|
78
76
|
None
|
|
79
77
|
"""
|
|
80
78
|
|
|
81
|
-
pass
|
|
82
|
-
|
|
83
79
|
def __setstate__(self, state):
|
|
84
80
|
"""Set the state of the object.
|
|
85
81
|
|
|
@@ -108,7 +104,7 @@ class Timeline(np.ndarray):
|
|
|
108
104
|
return super().min().item()
|
|
109
105
|
|
|
110
106
|
@classmethod
|
|
111
|
-
def _uniformize(cls, timeline):
|
|
107
|
+
def _uniformize(cls: type[Self], timeline: Self) -> Self:
|
|
112
108
|
"""Uniformize the given timeline data.
|
|
113
109
|
|
|
114
110
|
Args:
|
|
@@ -122,10 +118,14 @@ class Timeline(np.ndarray):
|
|
|
122
118
|
None
|
|
123
119
|
"""
|
|
124
120
|
|
|
125
|
-
raise NotImplementedError(
|
|
121
|
+
raise NotImplementedError(
|
|
122
|
+
"Uniformization of space is not yet supported. Upcoming in future versions"
|
|
123
|
+
)
|
|
126
124
|
# obj = np.linspace(input_array[0], input_array[1], len(input_array)).view(cls)
|
|
127
125
|
# TODO : do numpy.interp(np.arange(0, len(a), 1.5), np.arange(0, len(a)), a)
|
|
128
|
-
# interp to get a fixed number of points ?
|
|
126
|
+
# interp on the paretn array ? to get a fixed number of points ?
|
|
127
|
+
# strategy of this is still to make up...
|
|
128
|
+
# (sampling interpolation, with more/less points ? policies /arguments to make up for this)
|
|
129
129
|
|
|
130
130
|
def uniformize(self):
|
|
131
131
|
"""Uniformize the elements of the list using the _uniformize method."""
|
|
@@ -156,23 +156,21 @@ class Timeline(np.ndarray):
|
|
|
156
156
|
return self._max_step * self.max_step_mult
|
|
157
157
|
|
|
158
158
|
|
|
159
|
-
class
|
|
159
|
+
class StartEdgePolicy(Enum):
|
|
160
160
|
inclusive = operator.ge
|
|
161
161
|
exclusive = operator.gt
|
|
162
162
|
inc = operator.ge
|
|
163
163
|
exc = operator.gt
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
class
|
|
166
|
+
class EndEdgePolicy(Enum):
|
|
167
167
|
inclusive = operator.le
|
|
168
168
|
exclusive = operator.lt
|
|
169
169
|
inc = operator.le
|
|
170
170
|
exc = operator.lt
|
|
171
171
|
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
start = StartBoundary
|
|
175
|
-
stop = StoptBoundary
|
|
173
|
+
EdgePolicyString: TypeAlias = Literal["inclusive", "exclusive", "inc", "exc"]
|
|
176
174
|
|
|
177
175
|
|
|
178
176
|
class TimeIndexer:
|
|
@@ -181,17 +179,30 @@ class TimeIndexer:
|
|
|
181
179
|
_start_operation: OperatorType
|
|
182
180
|
_stop_operation: OperatorType
|
|
183
181
|
|
|
184
|
-
def __init__(
|
|
182
|
+
def __init__(
|
|
183
|
+
self,
|
|
184
|
+
array: "BaseTimeArray",
|
|
185
|
+
start: EdgePolicyString = "inclusive",
|
|
186
|
+
stop: EdgePolicyString = "exclusive",
|
|
187
|
+
):
|
|
185
188
|
self.array = array
|
|
186
189
|
self.set_edge_policy(start, stop)
|
|
187
190
|
|
|
188
|
-
def set_edge_policy(
|
|
189
|
-
self
|
|
190
|
-
|
|
191
|
+
def set_edge_policy(
|
|
192
|
+
self,
|
|
193
|
+
start: EdgePolicyString = "inclusive",
|
|
194
|
+
stop: EdgePolicyString = "exclusive",
|
|
195
|
+
):
|
|
196
|
+
self._start_operation = StartEdgePolicy[start].value
|
|
197
|
+
self._stop_operation = EndEdgePolicy[stop].value
|
|
191
198
|
return self
|
|
192
199
|
|
|
193
200
|
def time_to_index(
|
|
194
|
-
self,
|
|
201
|
+
self,
|
|
202
|
+
time: float
|
|
203
|
+
| slice
|
|
204
|
+
| tuple[int | float]
|
|
205
|
+
| list[float | int | slice | tuple[int | float]],
|
|
195
206
|
):
|
|
196
207
|
"""Converts time to index based on different input types.
|
|
197
208
|
|
|
@@ -215,11 +226,13 @@ class TimeIndexer:
|
|
|
215
226
|
elif isinstance(time, list):
|
|
216
227
|
return np.array([self.time_to_index(t) for t in time])
|
|
217
228
|
elif isinstance(time, tuple):
|
|
218
|
-
return self.get_iindex(
|
|
229
|
+
return self.get_iindex(
|
|
230
|
+
*[time[i] if len(time) > i else None for i in range(3)]
|
|
231
|
+
)
|
|
219
232
|
elif isinstance(time, (int, float)):
|
|
220
233
|
return self.get_iindex(sec_start=time).start
|
|
221
234
|
else:
|
|
222
|
-
raise
|
|
235
|
+
raise TypeError("Cannot process time to index")
|
|
223
236
|
|
|
224
237
|
seconds_to_index = time_to_index
|
|
225
238
|
|
|
@@ -240,7 +253,9 @@ class TimeIndexer:
|
|
|
240
253
|
|
|
241
254
|
return tuple(full_index)
|
|
242
255
|
|
|
243
|
-
def __getitem__(
|
|
256
|
+
def __getitem__(
|
|
257
|
+
self, index
|
|
258
|
+
) -> "TimelinedArray | MaskedTimelinedArray | np.ndarray":
|
|
244
259
|
"""Get item from TimelinedArray, MaskedTimelinedArray, or np.ndarray based on the given index.
|
|
245
260
|
|
|
246
261
|
Args:
|
|
@@ -264,7 +279,9 @@ class TimeIndexer:
|
|
|
264
279
|
iindex_time = self.time_to_index(index)
|
|
265
280
|
full_iindex = self._insert_time_index(iindex_time)
|
|
266
281
|
# print("new full index : ",iindex_time)
|
|
267
|
-
logger.debug(
|
|
282
|
+
logger.debug(
|
|
283
|
+
f"About to index over time with iindex_time {iindex_time} and full_iindex {full_iindex}"
|
|
284
|
+
)
|
|
268
285
|
return self.array[full_iindex]
|
|
269
286
|
|
|
270
287
|
def get_iindex(self, sec_start=None, sec_stop=None, sec_step=None):
|
|
@@ -293,7 +310,8 @@ class TimeIndexer:
|
|
|
293
310
|
if abs(self.array.timeline[start] - sec_start) > timeline_max_step:
|
|
294
311
|
raise IndexError(
|
|
295
312
|
f"The start time value {sec_start} you searched for is not in the timeline of this array "
|
|
296
|
-
f"(timeline starts at {self.array.timeline[0]}
|
|
313
|
+
f"(timeline starts at {self.array.timeline[0]} and ends at {self.array.timeline[-1]}, "
|
|
314
|
+
f"allowed jitter = {timeline_max_step} : "
|
|
297
315
|
" +/- 2 times the max step between two timeline points"
|
|
298
316
|
)
|
|
299
317
|
|
|
@@ -311,7 +329,8 @@ class TimeIndexer:
|
|
|
311
329
|
if abs(self.array.timeline[stop] - sec_stop) > timeline_max_step:
|
|
312
330
|
raise IndexError(
|
|
313
331
|
f"The end time value {sec_stop} you searched for is not in the timeline of this array "
|
|
314
|
-
f"(timeline
|
|
332
|
+
f"(timeline starts at {self.array.timeline[0]} and ends at {self.array.timeline[-1]}, "
|
|
333
|
+
f"allowed jitter = {timeline_max_step} : "
|
|
315
334
|
"+/- 2 times the max step between two timeline points"
|
|
316
335
|
)
|
|
317
336
|
|
|
@@ -319,8 +338,7 @@ class TimeIndexer:
|
|
|
319
338
|
step = 1
|
|
320
339
|
else:
|
|
321
340
|
step = int(np.round(sec_step / self.array.timeline.step))
|
|
322
|
-
|
|
323
|
-
step = 1
|
|
341
|
+
step = max(step, 1)
|
|
324
342
|
return slice(start, stop, step)
|
|
325
343
|
|
|
326
344
|
def __call__(self, start="inclusive", stop="exclusive"):
|
|
@@ -328,18 +346,17 @@ class TimeIndexer:
|
|
|
328
346
|
|
|
329
347
|
|
|
330
348
|
class TimeMixin:
|
|
331
|
-
|
|
332
349
|
time_dimension: int
|
|
333
350
|
timeline: Timeline
|
|
334
|
-
start_policy = "inclusive"
|
|
335
|
-
stop_policy = "exclusive"
|
|
351
|
+
start_policy: EdgePolicyString = "inclusive"
|
|
352
|
+
stop_policy: EdgePolicyString = "exclusive"
|
|
336
353
|
|
|
337
354
|
# ndarray inherited
|
|
338
355
|
ndim: int
|
|
339
|
-
shape:
|
|
356
|
+
shape: tuple[int, ...]
|
|
340
357
|
__sub__: Callable
|
|
341
358
|
|
|
342
|
-
def _time_dimension_in_axis(self, axis: int |
|
|
359
|
+
def _time_dimension_in_axis(self, axis: int | tuple[int, ...] | None) -> bool:
|
|
343
360
|
"""Check if the time dimension is present in the specified axis.
|
|
344
361
|
|
|
345
362
|
Args:
|
|
@@ -349,13 +366,12 @@ class TimeMixin:
|
|
|
349
366
|
bool: True if the time dimension is present in the axis, False otherwise.
|
|
350
367
|
"""
|
|
351
368
|
|
|
352
|
-
|
|
369
|
+
return bool(
|
|
353
370
|
axis is None
|
|
354
371
|
or axis == self.time_dimension
|
|
355
|
-
or
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return False
|
|
372
|
+
or isinstance(axis, (list, tuple))
|
|
373
|
+
and self.time_dimension in axis
|
|
374
|
+
)
|
|
359
375
|
|
|
360
376
|
def _get_time_dimension_after_axis_removal(self, axis_removed) -> int:
|
|
361
377
|
"""Return the time dimension after removing specified axis.
|
|
@@ -379,7 +395,9 @@ class TimeMixin:
|
|
|
379
395
|
if axis < self.time_dimension:
|
|
380
396
|
final_time_dimension -= 1
|
|
381
397
|
elif axis == self.time_dimension:
|
|
382
|
-
raise ValueError(
|
|
398
|
+
raise ValueError(
|
|
399
|
+
"The time dimension would simply be discarded after axis removal"
|
|
400
|
+
)
|
|
383
401
|
|
|
384
402
|
return final_time_dimension
|
|
385
403
|
|
|
@@ -404,7 +422,6 @@ class TimeMixin:
|
|
|
404
422
|
# in that case, this is a boolean selection, to filter the array,
|
|
405
423
|
# or an int selection, to filter and/or reorder the array
|
|
406
424
|
if index.dtype == bool or index.dtype == int:
|
|
407
|
-
|
|
408
425
|
# if it's boolean selecting on dimensions including the time_dim, we drop the timeline
|
|
409
426
|
if len(index.shape) > self.time_dimension:
|
|
410
427
|
# if time dimension is the first one, we filter the time dim in the same way we do for the array
|
|
@@ -430,7 +447,8 @@ class TimeMixin:
|
|
|
430
447
|
# TimelinedArray(super().__getitem__(index), timeline=self.timeline, time_dimension=self.time_dimension)
|
|
431
448
|
else:
|
|
432
449
|
raise ValueError(
|
|
433
|
-
"Cannot use advanced indexing with arrays "
|
|
450
|
+
"Cannot use advanced indexing with arrays "
|
|
451
|
+
"that are not composed of either booleans or integers"
|
|
434
452
|
)
|
|
435
453
|
|
|
436
454
|
def _get_slice_indexed_times(self, index):
|
|
@@ -470,7 +488,6 @@ class TimeMixin:
|
|
|
470
488
|
|
|
471
489
|
# a index at time_dimension or after, is a single integer
|
|
472
490
|
elif isinstance(index[dimension], (int, np.integer)):
|
|
473
|
-
|
|
474
491
|
# if the time dimension index itself is an integer,
|
|
475
492
|
# we loose time related information and return a standard numpy array
|
|
476
493
|
if dimension == time_dimension_in_index:
|
|
@@ -489,12 +506,17 @@ class TimeMixin:
|
|
|
489
506
|
# we apply this reshaping to timeline too.
|
|
490
507
|
|
|
491
508
|
final_timeline = (
|
|
492
|
-
self.timeline[index[time_dimension_in_index]]
|
|
509
|
+
self.timeline[index[time_dimension_in_index]]
|
|
510
|
+
if len(index) > time_dimension_in_index
|
|
511
|
+
else self.timeline
|
|
493
512
|
)
|
|
494
513
|
|
|
495
514
|
return index, final_timeline, final_time_dimension
|
|
496
515
|
|
|
497
|
-
def _get_indexed_times(
|
|
516
|
+
def _get_indexed_times(
|
|
517
|
+
self,
|
|
518
|
+
index: int | tuple[int, ...] | slice | tuple[slice, ...] | list | np.ndarray,
|
|
519
|
+
):
|
|
498
520
|
"""Get indexed times based on the provided index.
|
|
499
521
|
|
|
500
522
|
Args:
|
|
@@ -524,8 +546,10 @@ class TimeMixin:
|
|
|
524
546
|
return obj.shape == ()
|
|
525
547
|
|
|
526
548
|
def _finish_axis_removing_operation(
|
|
527
|
-
self
|
|
528
|
-
|
|
549
|
+
self: Self,
|
|
550
|
+
result: "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float",
|
|
551
|
+
axis: int | tuple[int, ...],
|
|
552
|
+
) -> Self | np.ndarray | float:
|
|
529
553
|
"""Finish axis removing operation.
|
|
530
554
|
|
|
531
555
|
Args:
|
|
@@ -547,7 +571,6 @@ class TimeMixin:
|
|
|
547
571
|
|
|
548
572
|
|
|
549
573
|
class BaseTimeArray(TimeMixin, np.ndarray):
|
|
550
|
-
|
|
551
574
|
# # REDUCE and SETSTATE are used to instanciate the array from and to a pickled serialized object.
|
|
552
575
|
# # We only need to store and retrieve time_dimension and timeline on top of the array's data
|
|
553
576
|
def __reduce__(self):
|
|
@@ -555,9 +578,8 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
555
578
|
object with additional attributes 'timeline' and 'time_dimension'."""
|
|
556
579
|
|
|
557
580
|
# Get the parent's __reduce__ tuple
|
|
558
|
-
pickled_state:
|
|
581
|
+
pickled_state: tuple[Any, Any, tuple] = super().__reduce__() # type: ignore
|
|
559
582
|
# Create our own tuple to pass to __setstate__
|
|
560
|
-
# type: ignore
|
|
561
583
|
new_state = pickled_state[2] + (self.timeline, self.time_dimension)
|
|
562
584
|
|
|
563
585
|
# self.logger.debug(f"Reduced to : time_dimension={self.time_dimension}. Array shape is : {new_state}")
|
|
@@ -588,7 +610,7 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
588
610
|
int: Hash value of the object.
|
|
589
611
|
"""
|
|
590
612
|
|
|
591
|
-
return hash((self.__array__(), self.timeline))
|
|
613
|
+
return hash((self.__array__(), self.timeline))
|
|
592
614
|
|
|
593
615
|
def _get_array_cls(self) -> "BaseTimeArray":
|
|
594
616
|
"""Return the class of the array that is compatible with time operations.
|
|
@@ -638,13 +660,21 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
638
660
|
TimelinedArray: The synchronized and cut arrray.
|
|
639
661
|
"""
|
|
640
662
|
# this slice is to select the number of elements, on the time_dimension
|
|
641
|
-
slices = tuple(
|
|
663
|
+
slices = tuple(
|
|
664
|
+
slice(None) if i != self.time_dimension else slice(None, element_nb)
|
|
665
|
+
for i in range(self.ndim)
|
|
666
|
+
)
|
|
642
667
|
|
|
643
|
-
return self.itime[start:][slices]
|
|
668
|
+
return self.itime[start:][slices]
|
|
644
669
|
|
|
645
670
|
def shift_values(
|
|
646
671
|
self,
|
|
647
|
-
period: int
|
|
672
|
+
period: int
|
|
673
|
+
| tuple[int, ...]
|
|
674
|
+
| slice
|
|
675
|
+
| tuple[slice, ...]
|
|
676
|
+
| list
|
|
677
|
+
| np.ndarray = 0,
|
|
648
678
|
axis=None,
|
|
649
679
|
time_period=True,
|
|
650
680
|
):
|
|
@@ -672,7 +702,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
672
702
|
indexer.append(slice(None))
|
|
673
703
|
indexer = tuple(indexer)
|
|
674
704
|
|
|
675
|
-
shift_area =
|
|
705
|
+
shift_area = (
|
|
706
|
+
self.itime.__getitem__(period) if time_period else self.__getitem__(period)
|
|
707
|
+
)
|
|
676
708
|
|
|
677
709
|
# if not this : we lost a dimension because we sliced one axis to a single element, no need to no .mean
|
|
678
710
|
if not len(shift_area.shape) < len(self.shape):
|
|
@@ -684,9 +716,11 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
684
716
|
"is not yet implemented"
|
|
685
717
|
)
|
|
686
718
|
|
|
687
|
-
return self - np.repeat(
|
|
719
|
+
return self - np.repeat(
|
|
720
|
+
shift_area.__getitem__(tuple(indexer)), self.shape[axis], axis=axis
|
|
721
|
+
)
|
|
688
722
|
|
|
689
|
-
def swapaxes(self:
|
|
723
|
+
def swapaxes(self: Self, axis1: int, axis2: int) -> Self:
|
|
690
724
|
"""Swap the two specified axes of the TimelinedArray.
|
|
691
725
|
|
|
692
726
|
Args:
|
|
@@ -698,9 +732,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
698
732
|
"""
|
|
699
733
|
|
|
700
734
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
701
|
-
cls = self._get_array_cls()
|
|
735
|
+
cls: type[Self] = self._get_array_cls() # type: ignore
|
|
702
736
|
|
|
703
|
-
swapped_array
|
|
737
|
+
swapped_array = np.swapaxes(np.asarray(self), axis1, axis2).view(cls)
|
|
704
738
|
swapped_array.timeline = self.timeline
|
|
705
739
|
|
|
706
740
|
if axis1 == self.time_dimension:
|
|
@@ -746,7 +780,11 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
746
780
|
|
|
747
781
|
return self.transpose()
|
|
748
782
|
|
|
749
|
-
def moveaxis(
|
|
783
|
+
def moveaxis(
|
|
784
|
+
self: "BaseTimeArray",
|
|
785
|
+
source: int | tuple[int, ...],
|
|
786
|
+
destination: int | tuple[int, ...],
|
|
787
|
+
):
|
|
750
788
|
"""Move the axis of the array to new positions.
|
|
751
789
|
|
|
752
790
|
Args:
|
|
@@ -769,7 +807,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
769
807
|
cls = self._get_array_cls()
|
|
770
808
|
|
|
771
809
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
772
|
-
moved_array: BaseTimeArray = np.moveaxis(
|
|
810
|
+
moved_array: BaseTimeArray = np.moveaxis(
|
|
811
|
+
np.asarray(self), source, destination
|
|
812
|
+
).view(cls) # type: ignore
|
|
773
813
|
moved_array.timeline = self.timeline
|
|
774
814
|
moved_array.time_dimension = self.time_dimension
|
|
775
815
|
|
|
@@ -800,7 +840,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
800
840
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
801
841
|
cls = self._get_array_cls()
|
|
802
842
|
|
|
803
|
-
rolled_array: BaseTimeArray = np.rollaxis(np.asarray(self), axis, start).view(
|
|
843
|
+
rolled_array: BaseTimeArray = np.rollaxis(np.asarray(self), axis, start).view(
|
|
844
|
+
cls
|
|
845
|
+
) # type: ignore
|
|
804
846
|
|
|
805
847
|
# reinject timeline as is
|
|
806
848
|
rolled_array.timeline = self.timeline
|
|
@@ -826,14 +868,22 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
826
868
|
mapping = {i: new_order.index(i) for i in range(n)}
|
|
827
869
|
return mapping
|
|
828
870
|
|
|
829
|
-
rolled_array.time_dimension = rollaxis_mapping(self.shape, axis, start)[
|
|
871
|
+
rolled_array.time_dimension = rollaxis_mapping(self.shape, axis, start)[
|
|
872
|
+
self.time_dimension
|
|
873
|
+
]
|
|
830
874
|
|
|
831
875
|
return rolled_array
|
|
832
876
|
|
|
833
877
|
def all_axes(self):
|
|
834
878
|
return tuple([i for i in range(self.ndim)])
|
|
835
879
|
|
|
836
|
-
def mean(
|
|
880
|
+
def mean(
|
|
881
|
+
self: Self,
|
|
882
|
+
axis: int | tuple[int, ...] | None = None,
|
|
883
|
+
dtype=None,
|
|
884
|
+
out=None,
|
|
885
|
+
keepdims=False,
|
|
886
|
+
) -> Self | np.ndarray | float:
|
|
837
887
|
"""Calculates the mean along the specified axis.
|
|
838
888
|
|
|
839
889
|
Args:
|
|
@@ -852,7 +902,13 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
852
902
|
return self._finish_axis_removing_operation(result, axis)
|
|
853
903
|
|
|
854
904
|
# Override other reduction methods similarly if needed
|
|
855
|
-
def sum(
|
|
905
|
+
def sum(
|
|
906
|
+
self,
|
|
907
|
+
axis: int | tuple[int, ...] | None = None,
|
|
908
|
+
dtype=None,
|
|
909
|
+
out=None,
|
|
910
|
+
keepdims=False,
|
|
911
|
+
) -> Self | np.ndarray | float:
|
|
856
912
|
"""Calculate the sum along the specified axis.
|
|
857
913
|
|
|
858
914
|
Args:
|
|
@@ -878,7 +934,7 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
878
934
|
|
|
879
935
|
def std(
|
|
880
936
|
self: "BaseTimeArray",
|
|
881
|
-
axis: int |
|
|
937
|
+
axis: int | tuple[int, ...] | None = None,
|
|
882
938
|
dtype=None,
|
|
883
939
|
out=None,
|
|
884
940
|
ddof=0,
|
|
@@ -902,12 +958,14 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
902
958
|
"""
|
|
903
959
|
if axis is None:
|
|
904
960
|
axis = self.all_axes()
|
|
905
|
-
result = super().std(
|
|
961
|
+
result = super().std(
|
|
962
|
+
axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims
|
|
963
|
+
)
|
|
906
964
|
return self._finish_axis_removing_operation(result, axis)
|
|
907
965
|
|
|
908
966
|
def var(
|
|
909
967
|
self: "BaseTimeArray",
|
|
910
|
-
axis: int |
|
|
968
|
+
axis: int | tuple[int, ...] | None = None,
|
|
911
969
|
dtype=None,
|
|
912
970
|
out=None,
|
|
913
971
|
ddof=0,
|
|
@@ -932,7 +990,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
932
990
|
"""
|
|
933
991
|
if axis is None:
|
|
934
992
|
axis = self.all_axes()
|
|
935
|
-
result = super().var(
|
|
993
|
+
result = super().var(
|
|
994
|
+
axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims
|
|
995
|
+
)
|
|
936
996
|
return self._finish_axis_removing_operation(result, axis)
|
|
937
997
|
|
|
938
998
|
def rebase_timeline(self, at=0):
|
|
@@ -948,7 +1008,7 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
948
1008
|
# returns a modified version of the array, with the first element of the array to time zero,
|
|
949
1009
|
# and shift the rest accordingly
|
|
950
1010
|
cls = self._get_array_cls()
|
|
951
|
-
|
|
1011
|
+
|
|
952
1012
|
return cls(self, timeline=self.timeline - self.timeline[at]) # type: ignore
|
|
953
1013
|
|
|
954
1014
|
def offset_timeline(self, offset):
|
|
@@ -988,7 +1048,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
988
1048
|
return self.timeline.min()
|
|
989
1049
|
|
|
990
1050
|
@staticmethod
|
|
991
|
-
def extract_time_from_data(
|
|
1051
|
+
def extract_time_from_data(
|
|
1052
|
+
data, timeline=None, time_dimension=None, uniform_space=False
|
|
1053
|
+
) -> tuple[NDArray, Timeline, int]:
|
|
992
1054
|
"""Extracts time-related information from the input data.
|
|
993
1055
|
|
|
994
1056
|
Args:
|
|
@@ -1017,7 +1079,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
1017
1079
|
break
|
|
1018
1080
|
|
|
1019
1081
|
if timeline is None:
|
|
1020
|
-
raise ValueError(
|
|
1082
|
+
raise ValueError(
|
|
1083
|
+
"timeline must be supplied if the input_array is not a TimelinedArray"
|
|
1084
|
+
)
|
|
1021
1085
|
|
|
1022
1086
|
if time_dimension is None: # same thing for the time dimension.
|
|
1023
1087
|
time_dimension = getattr(data, "time_dimension", None)
|
|
@@ -1043,14 +1107,14 @@ class BaseTimeArray(TimeMixin, np.ndarray):
|
|
|
1043
1107
|
time_dimension = 0
|
|
1044
1108
|
|
|
1045
1109
|
if not isinstance(time_dimension, int):
|
|
1046
|
-
raise
|
|
1110
|
+
raise TypeError("time_dimension must be an integer")
|
|
1047
1111
|
|
|
1048
1112
|
timeline = Timeline(timeline, uniform_space=uniform_space)
|
|
1049
1113
|
|
|
1050
1114
|
if _unpacking:
|
|
1051
1115
|
logger.debug(f"We are unpacking {type(data)} data")
|
|
1052
|
-
if not isinstance(data, np.ndarray) or len(data.shape) <= time_dimension:
|
|
1053
|
-
data = np.stack(data)
|
|
1116
|
+
if not isinstance(data, np.ndarray) or len(data.shape) <= time_dimension:
|
|
1117
|
+
data = np.stack(data)
|
|
1054
1118
|
|
|
1055
1119
|
return data, timeline, time_dimension
|
|
1056
1120
|
|
|
@@ -1101,12 +1165,12 @@ class TimelinedArray(BaseTimeArray):
|
|
|
1101
1165
|
TA_Timeline = Timeline
|
|
1102
1166
|
|
|
1103
1167
|
def __new__(
|
|
1104
|
-
cls,
|
|
1105
|
-
data,
|
|
1106
|
-
timeline:
|
|
1168
|
+
cls: type[Self],
|
|
1169
|
+
data: npt.ArrayLike,
|
|
1170
|
+
timeline: Timeline | np.ndarray | list | None = None,
|
|
1107
1171
|
time_dimension: int | None = None,
|
|
1108
1172
|
uniform_space=False,
|
|
1109
|
-
) ->
|
|
1173
|
+
) -> Self:
|
|
1110
1174
|
"""Create a new TimelinedArray object from the input data.
|
|
1111
1175
|
|
|
1112
1176
|
Args:
|
|
@@ -1120,12 +1184,15 @@ class TimelinedArray(BaseTimeArray):
|
|
|
1120
1184
|
"""
|
|
1121
1185
|
|
|
1122
1186
|
data, timeline, time_dimension = BaseTimeArray.extract_time_from_data(
|
|
1123
|
-
data,
|
|
1187
|
+
data,
|
|
1188
|
+
timeline=timeline,
|
|
1189
|
+
time_dimension=time_dimension,
|
|
1190
|
+
uniform_space=uniform_space,
|
|
1124
1191
|
)
|
|
1125
1192
|
|
|
1126
1193
|
# if np.isscalar(timeline):
|
|
1127
1194
|
# logger.debug(f"Scalar timeline found. Timeline is {timeline}")
|
|
1128
|
-
# return np.asarray(input_array)
|
|
1195
|
+
# return np.asarray(input_array)
|
|
1129
1196
|
|
|
1130
1197
|
# instanciate the np array as a view, as per numpy documentation on how to make ndarray child classes
|
|
1131
1198
|
obj = np.asarray(data).view(cls)
|
|
@@ -1180,7 +1247,9 @@ class TimelinedArray(BaseTimeArray):
|
|
|
1180
1247
|
logger.debug(f"wrapping array after ufunc {context[0].__name__}")
|
|
1181
1248
|
output = super().__array_wrap__(out_arr, context, return_scalar)
|
|
1182
1249
|
if len(output.shape) < len(self.shape):
|
|
1183
|
-
logger.debug(
|
|
1250
|
+
logger.debug(
|
|
1251
|
+
f"shape reduced from : {self.shape} to : {output.shape}. outarray was : {out_arr.shape}"
|
|
1252
|
+
)
|
|
1184
1253
|
return output
|
|
1185
1254
|
|
|
1186
1255
|
def __array_function__(self, func, types, args, kwargs):
|
|
@@ -1201,7 +1270,8 @@ class TimelinedArray(BaseTimeArray):
|
|
|
1201
1270
|
return super().__array_function__(func, types, args, kwargs)
|
|
1202
1271
|
|
|
1203
1272
|
def __getitem__(
|
|
1204
|
-
self,
|
|
1273
|
+
self,
|
|
1274
|
+
index: int | tuple[int, ...] | slice | tuple[slice, ...] | list | np.ndarray,
|
|
1205
1275
|
) -> "TimelinedArray | np.ndarray":
|
|
1206
1276
|
"""Get item from TimelinedArray based on index or slice.
|
|
1207
1277
|
|
|
@@ -1225,7 +1295,9 @@ class TimelinedArray(BaseTimeArray):
|
|
|
1225
1295
|
f"time_dimension {final_time_dimension} and timeline shape {final_timeline.shape}"
|
|
1226
1296
|
)
|
|
1227
1297
|
|
|
1228
|
-
return TimelinedArray(
|
|
1298
|
+
return TimelinedArray(
|
|
1299
|
+
indexed_result, timeline=final_timeline, time_dimension=final_time_dimension
|
|
1300
|
+
)
|
|
1229
1301
|
|
|
1230
1302
|
# __repr__ and __str__ ARE OVERRIDEN TO AVOID HORRIBLE PERFORMANCE WHEN PRINTING
|
|
1231
1303
|
# DUE TO CUSTOM __GETITEM__ PRE-CHECKS WITH RECURSIVE NATIVE NUMPY REPR
|
|
@@ -1264,7 +1336,7 @@ class TimelinedArray(BaseTimeArray):
|
|
|
1264
1336
|
|
|
1265
1337
|
class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
1266
1338
|
def __new__(
|
|
1267
|
-
cls,
|
|
1339
|
+
cls: type[Self],
|
|
1268
1340
|
data,
|
|
1269
1341
|
mask: NDArray[np.bool_] | np.bool_ | bool | np.ma.MaskedArray = np.ma.nomask,
|
|
1270
1342
|
dtype=None,
|
|
@@ -1273,8 +1345,8 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
|
1273
1345
|
keep_mask=True,
|
|
1274
1346
|
hard_mask=False,
|
|
1275
1347
|
shrink=True,
|
|
1276
|
-
timeline:
|
|
1277
|
-
time_dimension:
|
|
1348
|
+
timeline: Timeline | np.ndarray | list | None = None,
|
|
1349
|
+
time_dimension: int | None = None,
|
|
1278
1350
|
uniform_space=False,
|
|
1279
1351
|
**kwargs,
|
|
1280
1352
|
):
|
|
@@ -1299,8 +1371,11 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
|
1299
1371
|
An instance of the class with the specified parameters.
|
|
1300
1372
|
"""
|
|
1301
1373
|
|
|
1302
|
-
_, timeline, time_dimension =
|
|
1303
|
-
data,
|
|
1374
|
+
_, timeline, time_dimension = super().extract_time_from_data(
|
|
1375
|
+
data,
|
|
1376
|
+
timeline=timeline,
|
|
1377
|
+
time_dimension=time_dimension,
|
|
1378
|
+
uniform_space=uniform_space,
|
|
1304
1379
|
)
|
|
1305
1380
|
|
|
1306
1381
|
obj = super().__new__(
|
|
@@ -1316,8 +1391,8 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
|
1316
1391
|
**kwargs,
|
|
1317
1392
|
)
|
|
1318
1393
|
|
|
1319
|
-
obj
|
|
1320
|
-
obj
|
|
1394
|
+
setattr(obj, "timeline", timeline)
|
|
1395
|
+
setattr(obj, "time_dimension", time_dimension)
|
|
1321
1396
|
return obj
|
|
1322
1397
|
|
|
1323
1398
|
def __array_finalize__(self, obj):
|
|
@@ -1337,7 +1412,7 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
|
1337
1412
|
self.time_dimension = getattr(obj, "time_dimension", 0)
|
|
1338
1413
|
|
|
1339
1414
|
def __getitem__(
|
|
1340
|
-
self, index: int |
|
|
1415
|
+
self, index: int | tuple[int, ...] | slice | tuple[slice] | list | np.ndarray
|
|
1341
1416
|
) -> "MaskedTimelinedArray | np.ma.MaskedArray":
|
|
1342
1417
|
"""Get item from the MaskedTimelinedArray based on the provided index.
|
|
1343
1418
|
|
|
@@ -1351,9 +1426,9 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
|
1351
1426
|
index, final_timeline, final_time_dimension = self._get_indexed_times(index)
|
|
1352
1427
|
|
|
1353
1428
|
if final_timeline is None or final_time_dimension is None:
|
|
1354
|
-
return np.ma.MaskedArray(
|
|
1355
|
-
|
|
1356
|
-
)
|
|
1429
|
+
return np.ma.MaskedArray(
|
|
1430
|
+
data=np.asarray(self), mask=self.mask, fill_value=self.fill_value
|
|
1431
|
+
).__getitem__(index)
|
|
1357
1432
|
|
|
1358
1433
|
indexed_result = super().__getitem__(index)
|
|
1359
1434
|
|
|
@@ -1363,7 +1438,9 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
|
1363
1438
|
f"time_dimension {final_time_dimension} and timeline shape {final_timeline.shape}"
|
|
1364
1439
|
)
|
|
1365
1440
|
|
|
1366
|
-
return MaskedTimelinedArray(
|
|
1441
|
+
return MaskedTimelinedArray(
|
|
1442
|
+
indexed_result, timeline=final_timeline, time_dimension=final_time_dimension
|
|
1443
|
+
)
|
|
1367
1444
|
|
|
1368
1445
|
|
|
1369
1446
|
class Seconds(float):
|
timelined_array-0.1.2/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 Timothe Jost
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "timelined_array"
|
|
3
|
-
description = "Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays."
|
|
4
|
-
authors = [
|
|
5
|
-
{ name = "Timothe Jost", email = "44769559+JostTim@users.noreply.github.com" },
|
|
6
|
-
]
|
|
7
|
-
dependencies = [
|
|
8
|
-
"numpy>=2",
|
|
9
|
-
]
|
|
10
|
-
requires-python = ">=3.11"
|
|
11
|
-
readme = "README.md"
|
|
12
|
-
dynamic = []
|
|
13
|
-
version = "0.1.2"
|
|
14
|
-
|
|
15
|
-
[project.license]
|
|
16
|
-
text = "MIT"
|
|
17
|
-
|
|
18
|
-
[project.urls]
|
|
19
|
-
Homepage = "https://pypi.org/project/timelined_array/"
|
|
20
|
-
Source = "https://github.com/JostTim/timelined_array"
|
|
21
|
-
|
|
22
|
-
[build-system]
|
|
23
|
-
requires = [
|
|
24
|
-
"pdm-backend",
|
|
25
|
-
]
|
|
26
|
-
build-backend = "pdm.backend"
|
|
27
|
-
|
|
28
|
-
[tool.pdm]
|
|
29
|
-
distribution = true
|
|
30
|
-
|
|
31
|
-
[tool.pdm.version]
|
|
32
|
-
source = "file"
|
|
33
|
-
path = "src/timelined_array/__init__.py"
|
|
34
|
-
|
|
35
|
-
[tool.pdm.dev-dependencies]
|
|
36
|
-
dev = [
|
|
37
|
-
"pytest>=8.3.2",
|
|
38
|
-
"pytest-cov>=5.0.0",
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
[tool.pytest.ini_options]
|
|
42
|
-
addopts = "--pdbcls=IPython.terminal.debugger:TerminalPdb --cov-report xml --cov=timelined_array --junitxml=pytest_results.xml --cov-config=pyproject.toml"
|
|
43
|
-
testpaths = [
|
|
44
|
-
"tests",
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
[tool.coverage.run]
|
|
48
|
-
omit = [
|
|
49
|
-
".vscode/*",
|
|
50
|
-
".tests/*",
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
[tool.black]
|
|
54
|
-
preview = true
|
|
55
|
-
line-length = 120
|
|
56
|
-
|
|
57
|
-
[tool.flake8]
|
|
58
|
-
max-line-length = 120
|
|
59
|
-
ignore = [
|
|
60
|
-
"F401",
|
|
61
|
-
"F403",
|
|
62
|
-
"F841",
|
|
63
|
-
"E401",
|
|
64
|
-
"E265",
|
|
65
|
-
"E704",
|
|
66
|
-
"E266",
|
|
67
|
-
"E203",
|
|
68
|
-
"E712",
|
|
69
|
-
"W503",
|
|
70
|
-
"W605",
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
[tool.pyright]
|
|
74
|
-
reportGeneralTypeIssues = "information"
|
|
75
|
-
reportOptionalMemberAccess = "information"
|
|
76
|
-
reportMissingImports = "information"
|
|
77
|
-
reportMissingModuleSource = "information"
|
|
78
|
-
reportInvalidStringEscapeSequence = "information"
|
|
79
|
-
reportOptionalOperand = "none"
|
|
80
|
-
reportOptionalSubscript = "none"
|
|
81
|
-
reportOptionalIterable = "none"
|
|
82
|
-
reportAttributeAccessIssue = "information"
|
|
83
|
-
typeCheckingMode = "basic"
|
|
84
|
-
include = [
|
|
85
|
-
"src",
|
|
86
|
-
"tests",
|
|
87
|
-
]
|
|
88
|
-
exclude = [
|
|
89
|
-
"**/__pycache__",
|
|
90
|
-
]
|
|
File without changes
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pytest
|
|
3
|
-
from timelined_array import TimelinedArray, MaskedTimelinedArray
|
|
4
|
-
from timelined_array.time import (
|
|
5
|
-
Timeline,
|
|
6
|
-
StartBoundary,
|
|
7
|
-
StoptBoundary,
|
|
8
|
-
EdgePolicy,
|
|
9
|
-
TimeIndexer,
|
|
10
|
-
TimeMixin,
|
|
11
|
-
TimePacker,
|
|
12
|
-
Seconds,
|
|
13
|
-
)
|
|
14
|
-
import pickle
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@pytest.fixture
|
|
19
|
-
def timelined_array_1D():
|
|
20
|
-
return TimelinedArray(np.random.rand(50), timeline=np.arange(50), time_dimension=0)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@pytest.fixture
|
|
24
|
-
def timelined_array_3D():
|
|
25
|
-
return TimelinedArray(np.random.rand(25, 50, 75), timeline=np.arange(50), time_dimension=1)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@pytest.fixture
|
|
29
|
-
def masked_timelined_array_1D():
|
|
30
|
-
data = np.random.rand(50)
|
|
31
|
-
mask = data > 0.5
|
|
32
|
-
return MaskedTimelinedArray(data, mask=mask, timeline=np.arange(50), time_dimension=0)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@pytest.fixture
|
|
36
|
-
def pickle_path():
|
|
37
|
-
file_path = Path("serialized_test_3D_ta_array.pickle")
|
|
38
|
-
yield file_path
|
|
39
|
-
if file_path.exists():
|
|
40
|
-
file_path.unlink(missing_ok=True)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_timelined_array_1D_properties(timelined_array_1D):
|
|
44
|
-
assert timelined_array_1D.time_dimension == 0
|
|
45
|
-
assert timelined_array_1D.timeline.shape == (50,)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def test_timelined_array_3D_properties(timelined_array_3D):
|
|
49
|
-
assert timelined_array_3D.time_dimension == 1
|
|
50
|
-
assert timelined_array_3D.timeline.shape == (50,)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_masked_timelined_array_1D_properties(masked_timelined_array_1D):
|
|
54
|
-
assert masked_timelined_array_1D.time_dimension == 0
|
|
55
|
-
assert np.all(masked_timelined_array_1D.mask == (masked_timelined_array_1D.data > 0.5))
|
|
56
|
-
assert masked_timelined_array_1D.timeline.shape == (50,)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_timelined_array_indexing(timelined_array_1D):
|
|
60
|
-
sub_array = timelined_array_1D[10:20]
|
|
61
|
-
assert isinstance(sub_array, TimelinedArray)
|
|
62
|
-
assert sub_array.shape == (10,)
|
|
63
|
-
assert np.array_equal(sub_array.timeline, np.arange(10, 20))
|
|
64
|
-
assert timelined_array_1D[[1]] == timelined_array_1D[1]
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def test_masked_timelined_array_indexing(masked_timelined_array_1D):
|
|
68
|
-
sub_array = masked_timelined_array_1D[10:20]
|
|
69
|
-
assert isinstance(sub_array, MaskedTimelinedArray)
|
|
70
|
-
assert sub_array.shape == (10,)
|
|
71
|
-
assert np.array_equal(sub_array.timeline, np.arange(10, 20))
|
|
72
|
-
assert np.all(sub_array.mask == (sub_array.data > 0.5))
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def get_axis_parameters_3D():
|
|
76
|
-
# affected_axis, time_dimension, shape
|
|
77
|
-
# if None, we expect an AttributeError
|
|
78
|
-
return [(0, 0, (50, 75)), (1, None, (25, 75)), (2, 1, (25, 50)), ((0, 2), 0, (50,)), (None, None, None)]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@pytest.mark.parametrize("shape, timeline_dimension", [((12,), 0), ((10, 30, 50), 2), ((29, 34, 61, 15, 4), 0)])
|
|
82
|
-
def test_ta_array_shape(shape, timeline_dimension):
|
|
83
|
-
ta_array = TimelinedArray(
|
|
84
|
-
np.random.rand(*shape), timeline=np.arange(shape[timeline_dimension]), time_dimension=timeline_dimension
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
assert ta_array.shape == shape
|
|
88
|
-
assert ta_array.timeline.shape == (shape[timeline_dimension],)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def test_timeline_creation():
|
|
92
|
-
timeline = Timeline(np.arange(10))
|
|
93
|
-
assert isinstance(timeline, Timeline)
|
|
94
|
-
assert timeline.max() == 9
|
|
95
|
-
assert timeline.min() == 0
|
|
96
|
-
assert 5 in timeline
|
|
97
|
-
assert timeline.step == 1
|
|
98
|
-
|
|
99
|
-
with pytest.raises(NotImplementedError):
|
|
100
|
-
timeline = Timeline(np.random.rand(10), uniform_space=True)
|
|
101
|
-
# assert np.unique(np.diff(timeline)).size == 1
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def test_timelined_array_creation(timelined_array_1D):
|
|
105
|
-
|
|
106
|
-
with pytest.raises(ValueError):
|
|
107
|
-
array = TimelinedArray(np.array(timelined_array_1D))
|
|
108
|
-
|
|
109
|
-
with pytest.raises(ValueError):
|
|
110
|
-
array = TimelinedArray(np.array(timelined_array_1D), time_dimension=5.5)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def test_pickle_unpickle(timelined_array_3D, pickle_path):
|
|
114
|
-
# Pickle the numpy array
|
|
115
|
-
with open(pickle_path, "wb") as f:
|
|
116
|
-
pickle.dump(timelined_array_3D, f)
|
|
117
|
-
|
|
118
|
-
# Unpickle the numpy array
|
|
119
|
-
with open(pickle_path, "rb") as f:
|
|
120
|
-
unserialized_3D_array = pickle.load(f)
|
|
121
|
-
|
|
122
|
-
# Assert that the original and unpickled arrays are the same
|
|
123
|
-
np.testing.assert_array_equal(timelined_array_3D, unserialized_3D_array)
|
|
124
|
-
np.testing.assert_array_equal(timelined_array_3D.timeline, unserialized_3D_array.timeline)
|
|
125
|
-
assert timelined_array_3D.time_dimension == unserialized_3D_array.time_dimension
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def test_start_boundary():
|
|
129
|
-
assert StartBoundary.inclusive.value(5, 5) # 5 is => than 5
|
|
130
|
-
assert not StartBoundary.exclusive.value(5, 5) # 5 is not > than 5
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def test_stop_boundary():
|
|
134
|
-
assert StoptBoundary.inclusive.value(5, 5) # 5 is =< than 5
|
|
135
|
-
assert not StoptBoundary.exclusive.value(5, 5) # 5 is not < than 5
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def test_edge_policy():
|
|
139
|
-
assert EdgePolicy.start.value == StartBoundary
|
|
140
|
-
assert EdgePolicy.stop.value == StoptBoundary
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def test_time_indexer(timelined_array_1D):
|
|
144
|
-
indexer = TimeIndexer(timelined_array_1D)
|
|
145
|
-
assert indexer.time_to_index(5) == 5
|
|
146
|
-
assert indexer.time_to_index(slice(2, 5)) == slice(2, 5, 1)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def test_time_mixin_methods(timelined_array_1D):
|
|
150
|
-
assert timelined_array_1D.sec_max() == 49
|
|
151
|
-
assert timelined_array_1D.sec_min() == 0
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def test_ta_transpose(timelined_array_3D):
|
|
155
|
-
transposed = timelined_array_3D.transpose(2, 0, 1)
|
|
156
|
-
assert transposed.shape == (75, 25, 50)
|
|
157
|
-
assert transposed.time_dimension == 2
|
|
158
|
-
|
|
159
|
-
transposed = timelined_array_3D.T
|
|
160
|
-
assert transposed.shape == (75, 50, 25)
|
|
161
|
-
assert transposed.time_dimension == 1
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def test_ta_swapaxes(timelined_array_3D):
|
|
165
|
-
swapped = timelined_array_3D.swapaxes(1, 2)
|
|
166
|
-
assert swapped.shape == (25, 75, 50)
|
|
167
|
-
assert swapped.time_dimension == 2
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
@pytest.mark.parametrize(
|
|
171
|
-
"rolled_axis, end_position, expected_time_position, expected_shape",
|
|
172
|
-
[(0, 2, 0, (50, 25, 75)), (2, 1, 2, (25, 75, 50)), (1, 3, 2, (25, 75, 50))],
|
|
173
|
-
)
|
|
174
|
-
def test_ta_rollaxis(rolled_axis, end_position, expected_time_position, expected_shape, timelined_array_3D):
|
|
175
|
-
# roll back the axis
|
|
176
|
-
rolled = timelined_array_3D.rollaxis(rolled_axis, end_position)
|
|
177
|
-
assert rolled.shape == expected_shape
|
|
178
|
-
assert rolled.time_dimension == expected_time_position
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def axis_affecting_functions_blueprint(timelined_array_3D, func, affected_axis, time_dimension, shape):
|
|
182
|
-
result = func(timelined_array_3D, axis=affected_axis)
|
|
183
|
-
if time_dimension is None:
|
|
184
|
-
with pytest.raises(AttributeError):
|
|
185
|
-
result.time_dimension
|
|
186
|
-
else:
|
|
187
|
-
assert result.time_dimension == time_dimension
|
|
188
|
-
|
|
189
|
-
if shape is None:
|
|
190
|
-
with pytest.raises(AttributeError):
|
|
191
|
-
result.shape
|
|
192
|
-
else:
|
|
193
|
-
assert result.shape == shape
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
197
|
-
def test_ta_mean(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
198
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.mean, affected_axis, time_dimension, shape)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
202
|
-
def test_ta_sum(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
203
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.sum, affected_axis, time_dimension, shape)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
207
|
-
def test_ta_std(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
208
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.std, affected_axis, time_dimension, shape)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
212
|
-
def test_ta_var(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
213
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.var, affected_axis, time_dimension, shape)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def test_masked_ta_creation():
|
|
217
|
-
data = np.random.rand(10, 10)
|
|
218
|
-
mask = data > 0.5
|
|
219
|
-
masked_ta = MaskedTimelinedArray(data, mask=mask, timeline=np.arange(10), time_dimension=0)
|
|
220
|
-
assert isinstance(masked_ta, MaskedTimelinedArray)
|
|
221
|
-
assert masked_ta.shape == (10, 10)
|
|
222
|
-
assert masked_ta.time_dimension == 0
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def test_seconds_to_index():
|
|
226
|
-
sec = Seconds(10)
|
|
227
|
-
assert sec.to_index(2) == 20
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def test_timelined_array_from_iterable(timelined_array_3D):
|
|
231
|
-
array = TimelinedArray.align_from_iterable(timelined_array_3D)
|
|
232
|
-
assert np.all(array == timelined_array_3D)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def test_rebase_timelined_array(timelined_array_1D):
|
|
236
|
-
|
|
237
|
-
array = timelined_array_1D.rebase_timeline(at=16)
|
|
238
|
-
assert array.timeline[16] == 0
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def test_offset_timeline(timelined_array_1D):
|
|
242
|
-
array = timelined_array_1D.offset_timeline(offset=84)
|
|
243
|
-
assert array.timeline[22] == (22 + 84)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def test_shift_period(timelined_array_1D):
|
|
247
|
-
|
|
248
|
-
with pytest.raises(NotImplementedError):
|
|
249
|
-
array = timelined_array_1D.shift_values(period=slice(8, 10))
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def test_moveaxis(timelined_array_3D):
|
|
253
|
-
|
|
254
|
-
array = timelined_array_3D.moveaxis(1, 2)
|
|
255
|
-
assert array.time_dimension == 2
|
|
256
|
-
assert array.shape[2] == 50
|
|
257
|
-
|
|
258
|
-
array = timelined_array_3D.moveaxis(0, 2)
|
|
259
|
-
assert array.time_dimension == 0
|
|
260
|
-
assert array.shape[1] == 75
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def test_pack(timelined_array_1D):
|
|
264
|
-
|
|
265
|
-
timeline, array = timelined_array_1D.pack
|
|
266
|
-
assert timeline is timelined_array_1D.timeline
|
|
267
|
-
assert np.all(array == timelined_array_1D)
|