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.
@@ -1,13 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: timelined_array
3
- Version: 0.1.2
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
@@ -17,6 +18,8 @@ Description-Content-Type: text/markdown
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
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.-->
@@ -5,6 +5,8 @@
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
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,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) -> Tuple[int, ...]: ...
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) -> "Type": ...
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("Upcoming function")
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 StartBoundary(Enum):
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 StoptBoundary(Enum):
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
- class EdgePolicy(Enum):
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__(self, array: "BaseTimeArray", start="inclusive", stop="exclusive"):
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(self, start="inclusive", stop="exclusive"):
189
- self._start_operation = EdgePolicy["start"].value[start].value
190
- 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
191
198
  return self
192
199
 
193
200
  def time_to_index(
194
- 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]],
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(*[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
+ )
219
232
  elif isinstance(time, (int, float)):
220
233
  return self.get_iindex(sec_start=time).start
221
234
  else:
222
- raise ValueError("Cannot process time to index")
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__(self, index) -> "TimelinedArray | MaskedTimelinedArray | np.ndarray":
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(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
+ )
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]}, 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} : "
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 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} : "
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
- if step < 1:
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: Tuple[int, ...]
356
+ shape: tuple[int, ...]
340
357
  __sub__: Callable
341
358
 
342
- 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:
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
- if (
369
+ return bool(
353
370
  axis is None
354
371
  or axis == self.time_dimension
355
- or (isinstance(axis, (list, tuple)) and self.time_dimension in axis)
356
- ):
357
- return True
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("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
+ )
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 " "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"
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]] 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
493
512
  )
494
513
 
495
514
  return index, final_timeline, final_time_dimension
496
515
 
497
- 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
+ ):
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, result: "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float", axis: int | Tuple[int, ...]
528
- ) -> "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:
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: Tuple[Any, Any, tuple] = super().__reduce__() # type: ignore
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)) # type: ignore
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(slice(None) if i != self.time_dimension else slice(None, element_nb) for i in range(self.ndim))
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] # type: ignore
668
+ return self.itime[start:][slices]
644
669
 
645
670
  def shift_values(
646
671
  self,
647
- 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,
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 = 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
+ )
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(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
+ )
688
722
 
689
- def swapaxes(self: "BaseTimeArray", axis1: int, axis2: int):
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: BaseTimeArray = np.swapaxes(np.asarray(self), axis1, axis2).view(cls) # type: ignore
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(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
+ ):
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(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
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(cls) # type: ignore
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)[self.time_dimension]
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(self: Tp, axis: int | Tuple[int, ...] | None = None, dtype=None, out=None, keepdims=False) -> Tp:
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(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:
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 | Tuple[int, ...] | None = None,
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(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
+ )
906
964
  return self._finish_axis_removing_operation(result, axis)
907
965
 
908
966
  def var(
909
967
  self: "BaseTimeArray",
910
- axis: int | Tuple[int, ...] | None = None,
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(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
+ )
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
- # type: ignore
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(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]:
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("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
+ )
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 ValueError("time_dimension must be an integer")
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: # type: ignore
1053
- data = np.stack(data) # type: ignore
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: Optional[Timeline | np.ndarray | list] = None,
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
- ) -> "TimelinedArray":
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, 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,
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) # type: ignore
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(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
+ )
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, index: int | Tuple[int, ...] | slice | Tuple[slice, ...] | List | np.ndarray
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(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
+ )
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: Optional[Timeline | np.ndarray | list] | None = None,
1277
- time_dimension: Optional[int] = None,
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 = BaseTimeArray.extract_time_from_data(
1303
- 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,
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.timeline = timeline
1320
- obj.time_dimension = time_dimension
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 | Tuple[int, ...] | slice | Tuple[slice] | List | np.ndarray
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(data=np.asarray(self), mask=self.mask, fill_value=self.fill_value).__getitem__(
1355
- index
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(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
+ )
1367
1444
 
1368
1445
 
1369
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.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
- ]
@@ -1,3 +0,0 @@
1
- __version__ = "0.1.2"
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)