timelined_array 0.1.1__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.
@@ -1,13 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: timelined_array
3
- Version: 0.1.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-Email: Timothe Jost <44769559+JostTim@users.noreply.github.com>
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
@@ -16,7 +17,9 @@ Description-Content-Type: text/markdown
16
17
  ![PyPI - Version](https://img.shields.io/pypi/v/timelined_array)
17
18
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JostTim/timelined_array/publish.yml?label=Publishing)
18
19
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JostTim/timelined_array/test.yml?label=Testing)
19
- ![Codecov](https://img.shields.io/codecov/c/github/JostTim/timelined_array)
20
+ [![Codecov](https://img.shields.io/codecov/c/github/JostTim/timelined_array)](https://codecov.io/gh/JostTim/timelined_array)
21
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/timelined_array)
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.-->
@@ -4,7 +4,9 @@
4
4
  ![PyPI - Version](https://img.shields.io/pypi/v/timelined_array)
5
5
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JostTim/timelined_array/publish.yml?label=Publishing)
6
6
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JostTim/timelined_array/test.yml?label=Testing)
7
- ![Codecov](https://img.shields.io/codecov/c/github/JostTim/timelined_array)
7
+ [![Codecov](https://img.shields.io/codecov/c/github/JostTim/timelined_array)](https://codecov.io/gh/JostTim/timelined_array)
8
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/timelined_array)
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"
@@ -0,0 +1,5 @@
1
+ __version__ = "0.1.3"
2
+ __all__ = ["MaskedTimelinedArray", "Timeline", "TimelinedArray"]
3
+
4
+
5
+ from .time import MaskedTimelinedArray, Timeline, TimelinedArray
@@ -1,13 +1,14 @@
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
10
+
11
+ Tp = TypeVar("Tp", bound="TimelinedArray")
11
12
 
12
13
  OperatorType = Callable[[Any, Any], bool]
13
14
 
@@ -17,7 +18,6 @@ logger = getLogger("timelined_array")
17
18
 
18
19
 
19
20
  class TimeCompatibleProtocol(Protocol):
20
-
21
21
  time_dimension: int
22
22
  timeline: "Timeline"
23
23
 
@@ -26,7 +26,7 @@ class TimeCompatibleProtocol(Protocol):
26
26
  def __array__(self) -> np.ndarray: ...
27
27
 
28
28
  @property
29
- def shape(self) -> Tuple[int, ...]: ...
29
+ def shape(self) -> tuple[int, ...]: ...
30
30
 
31
31
  @property
32
32
  def ndim(self) -> int: ...
@@ -34,7 +34,7 @@ class TimeCompatibleProtocol(Protocol):
34
34
  @property
35
35
  def itime(self) -> "TimeIndexer": ...
36
36
 
37
- def _get_array_cls(self) -> "Type": ...
37
+ def _get_array_cls(self) -> "type": ...
38
38
 
39
39
  def transpose(self): ...
40
40
 
@@ -76,8 +76,6 @@ class Timeline(np.ndarray):
76
76
  None
77
77
  """
78
78
 
79
- pass
80
-
81
79
  def __setstate__(self, state):
82
80
  """Set the state of the object.
83
81
 
@@ -106,7 +104,7 @@ class Timeline(np.ndarray):
106
104
  return super().min().item()
107
105
 
108
106
  @classmethod
109
- def _uniformize(cls, timeline):
107
+ def _uniformize(cls: type[Self], timeline: Self) -> Self:
110
108
  """Uniformize the given timeline data.
111
109
 
112
110
  Args:
@@ -120,10 +118,14 @@ class Timeline(np.ndarray):
120
118
  None
121
119
  """
122
120
 
123
- raise NotImplementedError("Upcoming function")
121
+ raise NotImplementedError(
122
+ "Uniformization of space is not yet supported. Upcoming in future versions"
123
+ )
124
124
  # obj = np.linspace(input_array[0], input_array[1], len(input_array)).view(cls)
125
125
  # TODO : do numpy.interp(np.arange(0, len(a), 1.5), np.arange(0, len(a)), a)
126
- # 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)
127
129
 
128
130
  def uniformize(self):
129
131
  """Uniformize the elements of the list using the _uniformize method."""
@@ -154,23 +156,21 @@ class Timeline(np.ndarray):
154
156
  return self._max_step * self.max_step_mult
155
157
 
156
158
 
157
- class StartBoundary(Enum):
159
+ class StartEdgePolicy(Enum):
158
160
  inclusive = operator.ge
159
161
  exclusive = operator.gt
160
162
  inc = operator.ge
161
163
  exc = operator.gt
162
164
 
163
165
 
164
- class StoptBoundary(Enum):
166
+ class EndEdgePolicy(Enum):
165
167
  inclusive = operator.le
166
168
  exclusive = operator.lt
167
169
  inc = operator.le
168
170
  exc = operator.lt
169
171
 
170
172
 
171
- class EdgePolicy(Enum):
172
- start = StartBoundary
173
- stop = StoptBoundary
173
+ EdgePolicyString: TypeAlias = Literal["inclusive", "exclusive", "inc", "exc"]
174
174
 
175
175
 
176
176
  class TimeIndexer:
@@ -179,17 +179,30 @@ class TimeIndexer:
179
179
  _start_operation: OperatorType
180
180
  _stop_operation: OperatorType
181
181
 
182
- def __init__(self, array: "BaseTimeArray", start="inclusive", stop="exclusive"):
182
+ def __init__(
183
+ self,
184
+ array: "BaseTimeArray",
185
+ start: EdgePolicyString = "inclusive",
186
+ stop: EdgePolicyString = "exclusive",
187
+ ):
183
188
  self.array = array
184
189
  self.set_edge_policy(start, stop)
185
190
 
186
- def set_edge_policy(self, start="inclusive", stop="exclusive"):
187
- self._start_operation = EdgePolicy["start"].value[start].value
188
- self._stop_operation = EdgePolicy["stop"].value[stop].value
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
189
198
  return self
190
199
 
191
200
  def time_to_index(
192
- self, time: float | int | slice | Tuple[int | float] | List[float | int | slice | Tuple[int | float]]
201
+ self,
202
+ time: float
203
+ | slice
204
+ | tuple[int | float]
205
+ | list[float | int | slice | tuple[int | float]],
193
206
  ):
194
207
  """Converts time to index based on different input types.
195
208
 
@@ -213,11 +226,13 @@ class TimeIndexer:
213
226
  elif isinstance(time, list):
214
227
  return np.array([self.time_to_index(t) for t in time])
215
228
  elif isinstance(time, tuple):
216
- return self.get_iindex(*[time[i] if len(time) > i else None for i in range(3)])
229
+ return self.get_iindex(
230
+ *[time[i] if len(time) > i else None for i in range(3)]
231
+ )
217
232
  elif isinstance(time, (int, float)):
218
233
  return self.get_iindex(sec_start=time).start
219
234
  else:
220
- raise ValueError("Cannot process time to index")
235
+ raise TypeError("Cannot process time to index")
221
236
 
222
237
  seconds_to_index = time_to_index
223
238
 
@@ -238,7 +253,9 @@ class TimeIndexer:
238
253
 
239
254
  return tuple(full_index)
240
255
 
241
- def __getitem__(self, index) -> "TimelinedArray | MaskedTimelinedArray | np.ndarray":
256
+ def __getitem__(
257
+ self, index
258
+ ) -> "TimelinedArray | MaskedTimelinedArray | np.ndarray":
242
259
  """Get item from TimelinedArray, MaskedTimelinedArray, or np.ndarray based on the given index.
243
260
 
244
261
  Args:
@@ -262,7 +279,9 @@ class TimeIndexer:
262
279
  iindex_time = self.time_to_index(index)
263
280
  full_iindex = self._insert_time_index(iindex_time)
264
281
  # print("new full index : ",iindex_time)
265
- logger.debug(f"About to index over time with iindex_time {iindex_time} and full_iindex {full_iindex}")
282
+ logger.debug(
283
+ f"About to index over time with iindex_time {iindex_time} and full_iindex {full_iindex}"
284
+ )
266
285
  return self.array[full_iindex]
267
286
 
268
287
  def get_iindex(self, sec_start=None, sec_stop=None, sec_step=None):
@@ -291,7 +310,8 @@ class TimeIndexer:
291
310
  if abs(self.array.timeline[start] - sec_start) > timeline_max_step:
292
311
  raise IndexError(
293
312
  f"The start time value {sec_start} you searched for is not in the timeline of this array "
294
- f"(timeline starts at {self.array.timeline[0]}, allowed jitter = {timeline_max_step} :"
313
+ f"(timeline starts at {self.array.timeline[0]} and ends at {self.array.timeline[-1]}, "
314
+ f"allowed jitter = {timeline_max_step} : "
295
315
  " +/- 2 times the max step between two timeline points"
296
316
  )
297
317
 
@@ -309,7 +329,8 @@ class TimeIndexer:
309
329
  if abs(self.array.timeline[stop] - sec_stop) > timeline_max_step:
310
330
  raise IndexError(
311
331
  f"The end time value {sec_stop} you searched for is not in the timeline of this array "
312
- f"(timeline ends at {self.array.timeline[-1]} , allowed jitter = {timeline_max_step} : "
332
+ f"(timeline starts at {self.array.timeline[0]} and ends at {self.array.timeline[-1]}, "
333
+ f"allowed jitter = {timeline_max_step} : "
313
334
  "+/- 2 times the max step between two timeline points"
314
335
  )
315
336
 
@@ -317,8 +338,7 @@ class TimeIndexer:
317
338
  step = 1
318
339
  else:
319
340
  step = int(np.round(sec_step / self.array.timeline.step))
320
- if step < 1:
321
- step = 1
341
+ step = max(step, 1)
322
342
  return slice(start, stop, step)
323
343
 
324
344
  def __call__(self, start="inclusive", stop="exclusive"):
@@ -326,18 +346,17 @@ class TimeIndexer:
326
346
 
327
347
 
328
348
  class TimeMixin:
329
-
330
349
  time_dimension: int
331
350
  timeline: Timeline
332
- start_policy = "inclusive"
333
- stop_policy = "exclusive"
351
+ start_policy: EdgePolicyString = "inclusive"
352
+ stop_policy: EdgePolicyString = "exclusive"
334
353
 
335
354
  # ndarray inherited
336
355
  ndim: int
337
- shape: Tuple[int, ...]
356
+ shape: tuple[int, ...]
338
357
  __sub__: Callable
339
358
 
340
- def _time_dimension_in_axis(self, axis: int | Tuple[int, ...] | None) -> bool:
359
+ def _time_dimension_in_axis(self, axis: int | tuple[int, ...] | None) -> bool:
341
360
  """Check if the time dimension is present in the specified axis.
342
361
 
343
362
  Args:
@@ -347,13 +366,12 @@ class TimeMixin:
347
366
  bool: True if the time dimension is present in the axis, False otherwise.
348
367
  """
349
368
 
350
- if (
369
+ return bool(
351
370
  axis is None
352
371
  or axis == self.time_dimension
353
- or (isinstance(axis, (list, tuple)) and self.time_dimension in axis)
354
- ):
355
- return True
356
- return False
372
+ or isinstance(axis, (list, tuple))
373
+ and self.time_dimension in axis
374
+ )
357
375
 
358
376
  def _get_time_dimension_after_axis_removal(self, axis_removed) -> int:
359
377
  """Return the time dimension after removing specified axis.
@@ -377,7 +395,9 @@ class TimeMixin:
377
395
  if axis < self.time_dimension:
378
396
  final_time_dimension -= 1
379
397
  elif axis == self.time_dimension:
380
- raise ValueError("The time dimension would simply be discarded after axis removal")
398
+ raise ValueError(
399
+ "The time dimension would simply be discarded after axis removal"
400
+ )
381
401
 
382
402
  return final_time_dimension
383
403
 
@@ -402,7 +422,6 @@ class TimeMixin:
402
422
  # in that case, this is a boolean selection, to filter the array,
403
423
  # or an int selection, to filter and/or reorder the array
404
424
  if index.dtype == bool or index.dtype == int:
405
-
406
425
  # if it's boolean selecting on dimensions including the time_dim, we drop the timeline
407
426
  if len(index.shape) > self.time_dimension:
408
427
  # if time dimension is the first one, we filter the time dim in the same way we do for the array
@@ -428,7 +447,8 @@ class TimeMixin:
428
447
  # TimelinedArray(super().__getitem__(index), timeline=self.timeline, time_dimension=self.time_dimension)
429
448
  else:
430
449
  raise ValueError(
431
- "Cannot use advanced indexing with arrays " "that are not composed of either booleans or integers"
450
+ "Cannot use advanced indexing with arrays "
451
+ "that are not composed of either booleans or integers"
432
452
  )
433
453
 
434
454
  def _get_slice_indexed_times(self, index):
@@ -468,7 +488,6 @@ class TimeMixin:
468
488
 
469
489
  # a index at time_dimension or after, is a single integer
470
490
  elif isinstance(index[dimension], (int, np.integer)):
471
-
472
491
  # if the time dimension index itself is an integer,
473
492
  # we loose time related information and return a standard numpy array
474
493
  if dimension == time_dimension_in_index:
@@ -487,12 +506,17 @@ class TimeMixin:
487
506
  # we apply this reshaping to timeline too.
488
507
 
489
508
  final_timeline = (
490
- self.timeline[index[time_dimension_in_index]] if len(index) > time_dimension_in_index else self.timeline
509
+ self.timeline[index[time_dimension_in_index]]
510
+ if len(index) > time_dimension_in_index
511
+ else self.timeline
491
512
  )
492
513
 
493
514
  return index, final_timeline, final_time_dimension
494
515
 
495
- def _get_indexed_times(self, index: int | Tuple[int, ...] | slice | Tuple[slice, ...] | List | np.ndarray):
516
+ def _get_indexed_times(
517
+ self,
518
+ index: int | tuple[int, ...] | slice | tuple[slice, ...] | list | np.ndarray,
519
+ ):
496
520
  """Get indexed times based on the provided index.
497
521
 
498
522
  Args:
@@ -522,8 +546,10 @@ class TimeMixin:
522
546
  return obj.shape == ()
523
547
 
524
548
  def _finish_axis_removing_operation(
525
- self, result: "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float", axis: int | Tuple[int, ...]
526
- ) -> "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float | str":
549
+ self: Self,
550
+ result: "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float",
551
+ axis: int | tuple[int, ...],
552
+ ) -> Self | np.ndarray | float:
527
553
  """Finish axis removing operation.
528
554
 
529
555
  Args:
@@ -545,7 +571,6 @@ class TimeMixin:
545
571
 
546
572
 
547
573
  class BaseTimeArray(TimeMixin, np.ndarray):
548
-
549
574
  # # REDUCE and SETSTATE are used to instanciate the array from and to a pickled serialized object.
550
575
  # # We only need to store and retrieve time_dimension and timeline on top of the array's data
551
576
  def __reduce__(self):
@@ -553,9 +578,8 @@ class BaseTimeArray(TimeMixin, np.ndarray):
553
578
  object with additional attributes 'timeline' and 'time_dimension'."""
554
579
 
555
580
  # Get the parent's __reduce__ tuple
556
- pickled_state: Tuple[Any, Any, tuple] = super().__reduce__() # type: ignore
581
+ pickled_state: tuple[Any, Any, tuple] = super().__reduce__() # type: ignore
557
582
  # Create our own tuple to pass to __setstate__
558
- # type: ignore
559
583
  new_state = pickled_state[2] + (self.timeline, self.time_dimension)
560
584
 
561
585
  # self.logger.debug(f"Reduced to : time_dimension={self.time_dimension}. Array shape is : {new_state}")
@@ -586,7 +610,7 @@ class BaseTimeArray(TimeMixin, np.ndarray):
586
610
  int: Hash value of the object.
587
611
  """
588
612
 
589
- return hash((self.__array__(), self.timeline)) # type: ignore
613
+ return hash((self.__array__(), self.timeline))
590
614
 
591
615
  def _get_array_cls(self) -> "BaseTimeArray":
592
616
  """Return the class of the array that is compatible with time operations.
@@ -635,12 +659,22 @@ class BaseTimeArray(TimeMixin, np.ndarray):
635
659
  Returns:
636
660
  TimelinedArray: The synchronized and cut arrray.
637
661
  """
662
+ # this slice is to select the number of elements, on the time_dimension
663
+ slices = tuple(
664
+ slice(None) if i != self.time_dimension else slice(None, element_nb)
665
+ for i in range(self.ndim)
666
+ )
638
667
 
639
- return self.itime[start:][:element_nb] # type: ignore
668
+ return self.itime[start:][slices]
640
669
 
641
670
  def shift_values(
642
671
  self,
643
- period: int | Tuple[int, ...] | slice | Tuple[slice, ...] | List | np.ndarray = 0,
672
+ period: int
673
+ | tuple[int, ...]
674
+ | slice
675
+ | tuple[slice, ...]
676
+ | list
677
+ | np.ndarray = 0,
644
678
  axis=None,
645
679
  time_period=True,
646
680
  ):
@@ -668,7 +702,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
668
702
  indexer.append(slice(None))
669
703
  indexer = tuple(indexer)
670
704
 
671
- shift_area = self.itime.__getitem__(period) if time_period else self.__getitem__(period)
705
+ shift_area = (
706
+ self.itime.__getitem__(period) if time_period else self.__getitem__(period)
707
+ )
672
708
 
673
709
  # if not this : we lost a dimension because we sliced one axis to a single element, no need to no .mean
674
710
  if not len(shift_area.shape) < len(self.shape):
@@ -680,9 +716,11 @@ class BaseTimeArray(TimeMixin, np.ndarray):
680
716
  "is not yet implemented"
681
717
  )
682
718
 
683
- return self - np.repeat(shift_area.__getitem__(tuple(indexer)), self.shape[axis], axis=axis)
719
+ return self - np.repeat(
720
+ shift_area.__getitem__(tuple(indexer)), self.shape[axis], axis=axis
721
+ )
684
722
 
685
- def swapaxes(self: "BaseTimeArray", axis1: int, axis2: int):
723
+ def swapaxes(self: Self, axis1: int, axis2: int) -> Self:
686
724
  """Swap the two specified axes of the TimelinedArray.
687
725
 
688
726
  Args:
@@ -694,9 +732,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
694
732
  """
695
733
 
696
734
  # we re-instanciate a TimelinedArray with view instead of the full constructor : faster
697
- cls = self._get_array_cls()
735
+ cls: type[Self] = self._get_array_cls() # type: ignore
698
736
 
699
- swapped_array: BaseTimeArray = np.swapaxes(np.asarray(self), axis1, axis2).view(cls) # type: ignore
737
+ swapped_array = np.swapaxes(np.asarray(self), axis1, axis2).view(cls)
700
738
  swapped_array.timeline = self.timeline
701
739
 
702
740
  if axis1 == self.time_dimension:
@@ -742,7 +780,11 @@ class BaseTimeArray(TimeMixin, np.ndarray):
742
780
 
743
781
  return self.transpose()
744
782
 
745
- def moveaxis(self: "BaseTimeArray", source: int | Tuple[int, ...], destination: int | Tuple[int, ...]):
783
+ def moveaxis(
784
+ self: "BaseTimeArray",
785
+ source: int | tuple[int, ...],
786
+ destination: int | tuple[int, ...],
787
+ ):
746
788
  """Move the axis of the array to new positions.
747
789
 
748
790
  Args:
@@ -765,7 +807,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
765
807
  cls = self._get_array_cls()
766
808
 
767
809
  # we re-instanciate a TimelinedArray with view instead of the full constructor : faster
768
- moved_array: BaseTimeArray = np.moveaxis(np.asarray(self), source, destination).view(cls) # type: ignore
810
+ moved_array: BaseTimeArray = np.moveaxis(
811
+ np.asarray(self), source, destination
812
+ ).view(cls) # type: ignore
769
813
  moved_array.timeline = self.timeline
770
814
  moved_array.time_dimension = self.time_dimension
771
815
 
@@ -796,7 +840,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
796
840
  # we re-instanciate a TimelinedArray with view instead of the full constructor : faster
797
841
  cls = self._get_array_cls()
798
842
 
799
- rolled_array: BaseTimeArray = np.rollaxis(np.asarray(self), axis, start).view(cls) # type: ignore
843
+ rolled_array: BaseTimeArray = np.rollaxis(np.asarray(self), axis, start).view(
844
+ cls
845
+ ) # type: ignore
800
846
 
801
847
  # reinject timeline as is
802
848
  rolled_array.timeline = self.timeline
@@ -822,14 +868,22 @@ class BaseTimeArray(TimeMixin, np.ndarray):
822
868
  mapping = {i: new_order.index(i) for i in range(n)}
823
869
  return mapping
824
870
 
825
- rolled_array.time_dimension = rollaxis_mapping(self.shape, axis, start)[self.time_dimension]
871
+ rolled_array.time_dimension = rollaxis_mapping(self.shape, axis, start)[
872
+ self.time_dimension
873
+ ]
826
874
 
827
875
  return rolled_array
828
876
 
829
877
  def all_axes(self):
830
878
  return tuple([i for i in range(self.ndim)])
831
879
 
832
- def mean(self, axis: int | Tuple[int, ...] | None = None, dtype=None, out=None, keepdims=False):
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:
833
887
  """Calculates the mean along the specified axis.
834
888
 
835
889
  Args:
@@ -848,7 +902,13 @@ class BaseTimeArray(TimeMixin, np.ndarray):
848
902
  return self._finish_axis_removing_operation(result, axis)
849
903
 
850
904
  # Override other reduction methods similarly if needed
851
- def sum(self, axis: int | Tuple[int, ...] | None = None, dtype=None, out=None, keepdims=False):
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:
852
912
  """Calculate the sum along the specified axis.
853
913
 
854
914
  Args:
@@ -874,7 +934,7 @@ class BaseTimeArray(TimeMixin, np.ndarray):
874
934
 
875
935
  def std(
876
936
  self: "BaseTimeArray",
877
- axis: int | Tuple[int, ...] | None = None,
937
+ axis: int | tuple[int, ...] | None = None,
878
938
  dtype=None,
879
939
  out=None,
880
940
  ddof=0,
@@ -898,12 +958,14 @@ class BaseTimeArray(TimeMixin, np.ndarray):
898
958
  """
899
959
  if axis is None:
900
960
  axis = self.all_axes()
901
- result = super().std(axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)
961
+ result = super().std(
962
+ axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims
963
+ )
902
964
  return self._finish_axis_removing_operation(result, axis)
903
965
 
904
966
  def var(
905
967
  self: "BaseTimeArray",
906
- axis: int | Tuple[int, ...] | None = None,
968
+ axis: int | tuple[int, ...] | None = None,
907
969
  dtype=None,
908
970
  out=None,
909
971
  ddof=0,
@@ -928,7 +990,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
928
990
  """
929
991
  if axis is None:
930
992
  axis = self.all_axes()
931
- result = super().var(axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)
993
+ result = super().var(
994
+ axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims
995
+ )
932
996
  return self._finish_axis_removing_operation(result, axis)
933
997
 
934
998
  def rebase_timeline(self, at=0):
@@ -944,7 +1008,7 @@ class BaseTimeArray(TimeMixin, np.ndarray):
944
1008
  # returns a modified version of the array, with the first element of the array to time zero,
945
1009
  # and shift the rest accordingly
946
1010
  cls = self._get_array_cls()
947
- # type: ignore
1011
+
948
1012
  return cls(self, timeline=self.timeline - self.timeline[at]) # type: ignore
949
1013
 
950
1014
  def offset_timeline(self, offset):
@@ -984,7 +1048,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
984
1048
  return self.timeline.min()
985
1049
 
986
1050
  @staticmethod
987
- def extract_time_from_data(data, timeline=None, time_dimension=None, uniform_space=False):
1051
+ def extract_time_from_data(
1052
+ data, timeline=None, time_dimension=None, uniform_space=False
1053
+ ) -> tuple[NDArray, Timeline, int]:
988
1054
  """Extracts time-related information from the input data.
989
1055
 
990
1056
  Args:
@@ -1013,7 +1079,9 @@ class BaseTimeArray(TimeMixin, np.ndarray):
1013
1079
  break
1014
1080
 
1015
1081
  if timeline is None:
1016
- raise ValueError("timeline must be supplied if the input_array is not a TimelinedArray")
1082
+ raise ValueError(
1083
+ "timeline must be supplied if the input_array is not a TimelinedArray"
1084
+ )
1017
1085
 
1018
1086
  if time_dimension is None: # same thing for the time dimension.
1019
1087
  time_dimension = getattr(data, "time_dimension", None)
@@ -1039,14 +1107,14 @@ class BaseTimeArray(TimeMixin, np.ndarray):
1039
1107
  time_dimension = 0
1040
1108
 
1041
1109
  if not isinstance(time_dimension, int):
1042
- raise ValueError("time_dimension must be an integer")
1110
+ raise TypeError("time_dimension must be an integer")
1043
1111
 
1044
1112
  timeline = Timeline(timeline, uniform_space=uniform_space)
1045
1113
 
1046
1114
  if _unpacking:
1047
1115
  logger.debug(f"We are unpacking {type(data)} data")
1048
- if not isinstance(data, np.ndarray) or len(data.shape) <= time_dimension: # type: ignore
1049
- data = np.stack(data) # type: ignore
1116
+ if not isinstance(data, np.ndarray) or len(data.shape) <= time_dimension:
1117
+ data = np.stack(data)
1050
1118
 
1051
1119
  return data, timeline, time_dimension
1052
1120
 
@@ -1097,12 +1165,12 @@ class TimelinedArray(BaseTimeArray):
1097
1165
  TA_Timeline = Timeline
1098
1166
 
1099
1167
  def __new__(
1100
- cls,
1101
- data,
1102
- timeline: Optional[Timeline | np.ndarray | list] = None,
1168
+ cls: type[Self],
1169
+ data: npt.ArrayLike,
1170
+ timeline: Timeline | np.ndarray | list | None = None,
1103
1171
  time_dimension: int | None = None,
1104
1172
  uniform_space=False,
1105
- ) -> "TimelinedArray":
1173
+ ) -> Self:
1106
1174
  """Create a new TimelinedArray object from the input data.
1107
1175
 
1108
1176
  Args:
@@ -1116,12 +1184,15 @@ class TimelinedArray(BaseTimeArray):
1116
1184
  """
1117
1185
 
1118
1186
  data, timeline, time_dimension = BaseTimeArray.extract_time_from_data(
1119
- data, timeline=timeline, time_dimension=time_dimension, uniform_space=uniform_space
1187
+ data,
1188
+ timeline=timeline,
1189
+ time_dimension=time_dimension,
1190
+ uniform_space=uniform_space,
1120
1191
  )
1121
1192
 
1122
1193
  # if np.isscalar(timeline):
1123
1194
  # logger.debug(f"Scalar timeline found. Timeline is {timeline}")
1124
- # return np.asarray(input_array) # type: ignore
1195
+ # return np.asarray(input_array)
1125
1196
 
1126
1197
  # instanciate the np array as a view, as per numpy documentation on how to make ndarray child classes
1127
1198
  obj = np.asarray(data).view(cls)
@@ -1176,7 +1247,9 @@ class TimelinedArray(BaseTimeArray):
1176
1247
  logger.debug(f"wrapping array after ufunc {context[0].__name__}")
1177
1248
  output = super().__array_wrap__(out_arr, context, return_scalar)
1178
1249
  if len(output.shape) < len(self.shape):
1179
- logger.debug(f"shape reduced from : {self.shape} to : {output.shape}. outarray was : {out_arr.shape}")
1250
+ logger.debug(
1251
+ f"shape reduced from : {self.shape} to : {output.shape}. outarray was : {out_arr.shape}"
1252
+ )
1180
1253
  return output
1181
1254
 
1182
1255
  def __array_function__(self, func, types, args, kwargs):
@@ -1197,7 +1270,8 @@ class TimelinedArray(BaseTimeArray):
1197
1270
  return super().__array_function__(func, types, args, kwargs)
1198
1271
 
1199
1272
  def __getitem__(
1200
- self, index: int | Tuple[int, ...] | slice | Tuple[slice, ...] | List | np.ndarray
1273
+ self,
1274
+ index: int | tuple[int, ...] | slice | tuple[slice, ...] | list | np.ndarray,
1201
1275
  ) -> "TimelinedArray | np.ndarray":
1202
1276
  """Get item from TimelinedArray based on index or slice.
1203
1277
 
@@ -1221,7 +1295,9 @@ class TimelinedArray(BaseTimeArray):
1221
1295
  f"time_dimension {final_time_dimension} and timeline shape {final_timeline.shape}"
1222
1296
  )
1223
1297
 
1224
- return TimelinedArray(indexed_result, timeline=final_timeline, time_dimension=final_time_dimension)
1298
+ return TimelinedArray(
1299
+ indexed_result, timeline=final_timeline, time_dimension=final_time_dimension
1300
+ )
1225
1301
 
1226
1302
  # __repr__ and __str__ ARE OVERRIDEN TO AVOID HORRIBLE PERFORMANCE WHEN PRINTING
1227
1303
  # DUE TO CUSTOM __GETITEM__ PRE-CHECKS WITH RECURSIVE NATIVE NUMPY REPR
@@ -1238,7 +1314,7 @@ class TimelinedArray(BaseTimeArray):
1238
1314
  return type(self).__name__ + np.asarray(self).__str__()
1239
1315
 
1240
1316
  @staticmethod
1241
- def align_from_iterable(iterable) -> "TimelinedArray":
1317
+ def align_from_iterable(iterable: "Sequence[TimelinedArray]") -> "TimelinedArray":
1242
1318
  """Aligns arrays from an iterable based on their timelines.
1243
1319
 
1244
1320
  Args:
@@ -1260,7 +1336,7 @@ class TimelinedArray(BaseTimeArray):
1260
1336
 
1261
1337
  class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1262
1338
  def __new__(
1263
- cls,
1339
+ cls: type[Self],
1264
1340
  data,
1265
1341
  mask: NDArray[np.bool_] | np.bool_ | bool | np.ma.MaskedArray = np.ma.nomask,
1266
1342
  dtype=None,
@@ -1269,8 +1345,8 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1269
1345
  keep_mask=True,
1270
1346
  hard_mask=False,
1271
1347
  shrink=True,
1272
- timeline: Optional[Timeline | np.ndarray | list] | None = None,
1273
- time_dimension: Optional[int] = None,
1348
+ timeline: Timeline | np.ndarray | list | None = None,
1349
+ time_dimension: int | None = None,
1274
1350
  uniform_space=False,
1275
1351
  **kwargs,
1276
1352
  ):
@@ -1295,8 +1371,11 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1295
1371
  An instance of the class with the specified parameters.
1296
1372
  """
1297
1373
 
1298
- _, timeline, time_dimension = BaseTimeArray.extract_time_from_data(
1299
- data, timeline=timeline, time_dimension=time_dimension, uniform_space=uniform_space
1374
+ _, timeline, time_dimension = super().extract_time_from_data(
1375
+ data,
1376
+ timeline=timeline,
1377
+ time_dimension=time_dimension,
1378
+ uniform_space=uniform_space,
1300
1379
  )
1301
1380
 
1302
1381
  obj = super().__new__(
@@ -1312,8 +1391,8 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1312
1391
  **kwargs,
1313
1392
  )
1314
1393
 
1315
- obj.timeline = timeline
1316
- obj.time_dimension = time_dimension
1394
+ setattr(obj, "timeline", timeline)
1395
+ setattr(obj, "time_dimension", time_dimension)
1317
1396
  return obj
1318
1397
 
1319
1398
  def __array_finalize__(self, obj):
@@ -1333,7 +1412,7 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1333
1412
  self.time_dimension = getattr(obj, "time_dimension", 0)
1334
1413
 
1335
1414
  def __getitem__(
1336
- self, index: int | Tuple[int, ...] | slice | Tuple[slice] | List | np.ndarray
1415
+ self, index: int | tuple[int, ...] | slice | tuple[slice] | list | np.ndarray
1337
1416
  ) -> "MaskedTimelinedArray | np.ma.MaskedArray":
1338
1417
  """Get item from the MaskedTimelinedArray based on the provided index.
1339
1418
 
@@ -1347,9 +1426,9 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1347
1426
  index, final_timeline, final_time_dimension = self._get_indexed_times(index)
1348
1427
 
1349
1428
  if final_timeline is None or final_time_dimension is None:
1350
- return np.ma.MaskedArray(data=np.asarray(self), mask=self.mask, fill_value=self.fill_value).__getitem__(
1351
- index
1352
- )
1429
+ return np.ma.MaskedArray(
1430
+ data=np.asarray(self), mask=self.mask, fill_value=self.fill_value
1431
+ ).__getitem__(index)
1353
1432
 
1354
1433
  indexed_result = super().__getitem__(index)
1355
1434
 
@@ -1359,7 +1438,9 @@ class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
1359
1438
  f"time_dimension {final_time_dimension} and timeline shape {final_timeline.shape}"
1360
1439
  )
1361
1440
 
1362
- return MaskedTimelinedArray(indexed_result, timeline=final_timeline, time_dimension=final_time_dimension)
1441
+ return MaskedTimelinedArray(
1442
+ indexed_result, timeline=final_timeline, time_dimension=final_time_dimension
1443
+ )
1363
1444
 
1364
1445
 
1365
1446
  class Seconds(float):
@@ -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.1"
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
- ]
@@ -1,3 +0,0 @@
1
- __version__ = "0.1.1"
2
-
3
- from .time import TimelinedArray, MaskedTimelinedArray, Timeline
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)