np-struct 0.0.2__tar.gz → 0.0.4__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.
- {np_struct-0.0.2 → np_struct-0.0.4}/PKG-INFO +32 -22
- {np_struct-0.0.2 → np_struct-0.0.4}/README.md +31 -21
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct/__init__.py +1 -1
- np_struct-0.0.2/np_struct/fields.py → np_struct-0.0.4/np_struct/bitfields.py +10 -12
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct/ldarray.py +107 -69
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct/structures.py +3 -10
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct/transfer.py +128 -90
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct.egg-info/PKG-INFO +32 -22
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct.egg-info/SOURCES.txt +1 -1
- {np_struct-0.0.2 → np_struct-0.0.4}/pyproject.toml +1 -1
- {np_struct-0.0.2 → np_struct-0.0.4}/tests/test_ldarray.py +15 -0
- np_struct-0.0.4/tests/test_transfers.py +133 -0
- np_struct-0.0.2/tests/test_transfers.py +0 -62
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct.egg-info/dependency_links.txt +0 -0
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct.egg-info/requires.txt +0 -0
- {np_struct-0.0.2 → np_struct-0.0.4}/np_struct.egg-info/top_level.txt +0 -0
- {np_struct-0.0.2 → np_struct-0.0.4}/setup.cfg +0 -0
- {np_struct-0.0.2 → np_struct-0.0.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: np-struct
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Interface for NumPy structured arrays
|
|
5
5
|
Author-email: Rick Lyon <rlyon14@gmail.com>
|
|
6
6
|
Project-URL: repository, https://github.com/ricklyon/np_struct
|
|
@@ -18,9 +18,9 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
18
18
|
|
|
19
19
|
# np-struct
|
|
20
20
|
|
|
21
|
-
`np-struct`
|
|
21
|
+
`np-struct` is a user friendly interface to NumPy structured arrays, with added support for transferring arrays across interfaces.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
The `Struct` type is designed to mirror the struct typedef in C, but can be used for any complicated data structure. They behave similar to the standard `ndarray`, but support mixed data types, bitfields, labeling, and variable length arrays. Arrays are easily written or loaded from disk in the standard `.npy` binary format.
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
@@ -38,7 +38,7 @@ import numpy as np
|
|
|
38
38
|
|
|
39
39
|
### Structures
|
|
40
40
|
|
|
41
|
-
Create a
|
|
41
|
+
Create a C-style structure with Numpy types.
|
|
42
42
|
|
|
43
43
|
```python
|
|
44
44
|
class example(Struct):
|
|
@@ -55,23 +55,20 @@ ex[0].data2 = 1 + 2j
|
|
|
55
55
|
>>> ex
|
|
56
56
|
Struct example: (3,)
|
|
57
57
|
[
|
|
58
|
-
psize: uint16[0]
|
|
59
58
|
data1: uint32[0]
|
|
60
59
|
data2: complex128[1.+2.j 1.+2.j 1.+2.j]
|
|
61
60
|
]
|
|
62
61
|
...
|
|
63
62
|
[
|
|
64
|
-
psize: uint16[0]
|
|
65
63
|
data1: uint32[0]
|
|
66
64
|
data2: complex128[0.+0.j 0.+0.j 0.+0.j]
|
|
67
65
|
]
|
|
68
66
|
```
|
|
69
67
|
|
|
70
|
-
Members can also be initialized by passing in their name to the constructor with an
|
|
68
|
+
Members can also be initialized by passing in their name to the constructor with an initial value.
|
|
71
69
|
```python
|
|
72
70
|
>>> example(data2 = np.zeros(shape=(3,2)))
|
|
73
71
|
Struct example:
|
|
74
|
-
psize: uint16[0]
|
|
75
72
|
data1: uint32[0]
|
|
76
73
|
data2: complex128[[0.+0.j 0.+0.j]
|
|
77
74
|
[0.+0.j 0.+0.j]
|
|
@@ -79,36 +76,49 @@ Struct example:
|
|
|
79
76
|
```
|
|
80
77
|
|
|
81
78
|
The structure inherits from np.ndarray and supports all math functions that a normal structured array does.
|
|
82
|
-
To cast as a standard numpy array
|
|
79
|
+
To cast as a standard numpy structured array,
|
|
83
80
|
|
|
84
81
|
```python
|
|
85
|
-
>>>
|
|
86
|
-
array([([0], [0
|
|
87
|
-
|
|
82
|
+
>>> ex.view(np.ndarray)
|
|
83
|
+
array([([0], [0.+0.j, 0.+0.j, 0.+0.j]), ([0], [0.+0.j, 0.+0.j, 0.+0.j]),
|
|
84
|
+
([0], [0.+0.j, 0.+0.j, 0.+0.j])],
|
|
85
|
+
dtype=[('data1', '<u4', (1,)), ('data2', '<c16', (3,))])
|
|
88
86
|
```
|
|
89
87
|
|
|
90
|
-
Nested structures are also supported
|
|
88
|
+
Nested structures are also supported,
|
|
91
89
|
```python
|
|
92
90
|
class nested(Struct):
|
|
93
91
|
field1 = example()
|
|
94
92
|
field2 = example()
|
|
95
93
|
|
|
96
94
|
n = nested()
|
|
97
|
-
n.field1.data2 +=
|
|
95
|
+
n.field1.data2 += 1j*np.pi
|
|
98
96
|
```
|
|
99
97
|
```bash
|
|
100
98
|
>>> n
|
|
101
99
|
Struct nested:
|
|
102
100
|
field1: Struct example:
|
|
103
|
-
psize: uint16[0]
|
|
104
101
|
data1: uint32[0]
|
|
105
102
|
data2: complex128[0.+3.14159265j 0.+3.14159265j 0.+3.14159265j]
|
|
106
103
|
field2: Struct example:
|
|
107
|
-
psize: uint16[0]
|
|
108
104
|
data1: uint32[0]
|
|
109
105
|
data2: complex128[0.+0.j 0.+0.j 0.+0.j]
|
|
110
106
|
```
|
|
111
107
|
|
|
108
|
+
To save to disk,
|
|
109
|
+
```python
|
|
110
|
+
n = nested(shape=2)
|
|
111
|
+
n[1].field2.data1 = 3
|
|
112
|
+
np.save("test.npy", n)
|
|
113
|
+
n_disk = nested(np.load("test.npy"))
|
|
114
|
+
```
|
|
115
|
+
```python
|
|
116
|
+
>>> n_disk.field2.data1
|
|
117
|
+
array([[[0]],
|
|
118
|
+
|
|
119
|
+
[[3]]], dtype=uint32)
|
|
120
|
+
```
|
|
121
|
+
|
|
112
122
|
### Labeled Arrays
|
|
113
123
|
|
|
114
124
|
`np-struct` implements a very scaled down version of [Xarray](https://docs.xarray.dev/en/stable/) DataArrays.
|
|
@@ -116,10 +126,9 @@ Struct nested:
|
|
|
116
126
|
written to disk in the standard `.npy` binary format, and supports indexing with coordinates.
|
|
117
127
|
|
|
118
128
|
Math operations that change the coordinates or array shape (i.e. sum or transpose) silently revert the labeled array
|
|
119
|
-
to a standard numpy array without coordinates.
|
|
129
|
+
to a standard numpy array without coordinates. `np-struct` leaves it up to the user to re-cast the array as an
|
|
130
|
+
`ldarray` with the appropriate coordinates.
|
|
120
131
|
|
|
121
|
-
Real or complex-valued arrays can be saved with the ``ldarray.save()`` method, and
|
|
122
|
-
loaded with the ``ldarray.load()`` class function.
|
|
123
132
|
|
|
124
133
|
```python
|
|
125
134
|
>>> from np_struct import ldarray
|
|
@@ -165,8 +174,9 @@ Coordinates: (2,)
|
|
|
165
174
|
a: ['data1' 'data2']
|
|
166
175
|
```
|
|
167
176
|
|
|
168
|
-
|
|
169
|
-
use `ldarray.save()` and `load()`. Array is stored as a structured array in the usual
|
|
177
|
+
Real or complex-valued arrays can be written to disk uses the normal numpy methods if the coordinates are not needed,
|
|
178
|
+
or, to keep the coords, use `ldarray.save()` and `load()`. Array is stored as a structured array in the usual
|
|
179
|
+
`.npy` binary format.
|
|
170
180
|
```python
|
|
171
181
|
ld.save("ld_file.npy")
|
|
172
182
|
ldarray.load("ld_file.npy")
|
|
@@ -174,7 +184,7 @@ ldarray.load("ld_file.npy")
|
|
|
174
184
|
|
|
175
185
|
## Examples
|
|
176
186
|
|
|
177
|
-
[
|
|
187
|
+
[Struct example](./examples/structures.ipynb)
|
|
178
188
|
|
|
179
189
|
|
|
180
190
|
## License
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# np-struct
|
|
2
2
|
|
|
3
|
-
`np-struct`
|
|
3
|
+
`np-struct` is a user friendly interface to NumPy structured arrays, with added support for transferring arrays across interfaces.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The `Struct` type is designed to mirror the struct typedef in C, but can be used for any complicated data structure. They behave similar to the standard `ndarray`, but support mixed data types, bitfields, labeling, and variable length arrays. Arrays are easily written or loaded from disk in the standard `.npy` binary format.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ import numpy as np
|
|
|
20
20
|
|
|
21
21
|
### Structures
|
|
22
22
|
|
|
23
|
-
Create a
|
|
23
|
+
Create a C-style structure with Numpy types.
|
|
24
24
|
|
|
25
25
|
```python
|
|
26
26
|
class example(Struct):
|
|
@@ -37,23 +37,20 @@ ex[0].data2 = 1 + 2j
|
|
|
37
37
|
>>> ex
|
|
38
38
|
Struct example: (3,)
|
|
39
39
|
[
|
|
40
|
-
psize: uint16[0]
|
|
41
40
|
data1: uint32[0]
|
|
42
41
|
data2: complex128[1.+2.j 1.+2.j 1.+2.j]
|
|
43
42
|
]
|
|
44
43
|
...
|
|
45
44
|
[
|
|
46
|
-
psize: uint16[0]
|
|
47
45
|
data1: uint32[0]
|
|
48
46
|
data2: complex128[0.+0.j 0.+0.j 0.+0.j]
|
|
49
47
|
]
|
|
50
48
|
```
|
|
51
49
|
|
|
52
|
-
Members can also be initialized by passing in their name to the constructor with an
|
|
50
|
+
Members can also be initialized by passing in their name to the constructor with an initial value.
|
|
53
51
|
```python
|
|
54
52
|
>>> example(data2 = np.zeros(shape=(3,2)))
|
|
55
53
|
Struct example:
|
|
56
|
-
psize: uint16[0]
|
|
57
54
|
data1: uint32[0]
|
|
58
55
|
data2: complex128[[0.+0.j 0.+0.j]
|
|
59
56
|
[0.+0.j 0.+0.j]
|
|
@@ -61,36 +58,49 @@ Struct example:
|
|
|
61
58
|
```
|
|
62
59
|
|
|
63
60
|
The structure inherits from np.ndarray and supports all math functions that a normal structured array does.
|
|
64
|
-
To cast as a standard numpy array
|
|
61
|
+
To cast as a standard numpy structured array,
|
|
65
62
|
|
|
66
63
|
```python
|
|
67
|
-
>>>
|
|
68
|
-
array([([0], [0
|
|
69
|
-
|
|
64
|
+
>>> ex.view(np.ndarray)
|
|
65
|
+
array([([0], [0.+0.j, 0.+0.j, 0.+0.j]), ([0], [0.+0.j, 0.+0.j, 0.+0.j]),
|
|
66
|
+
([0], [0.+0.j, 0.+0.j, 0.+0.j])],
|
|
67
|
+
dtype=[('data1', '<u4', (1,)), ('data2', '<c16', (3,))])
|
|
70
68
|
```
|
|
71
69
|
|
|
72
|
-
Nested structures are also supported
|
|
70
|
+
Nested structures are also supported,
|
|
73
71
|
```python
|
|
74
72
|
class nested(Struct):
|
|
75
73
|
field1 = example()
|
|
76
74
|
field2 = example()
|
|
77
75
|
|
|
78
76
|
n = nested()
|
|
79
|
-
n.field1.data2 +=
|
|
77
|
+
n.field1.data2 += 1j*np.pi
|
|
80
78
|
```
|
|
81
79
|
```bash
|
|
82
80
|
>>> n
|
|
83
81
|
Struct nested:
|
|
84
82
|
field1: Struct example:
|
|
85
|
-
psize: uint16[0]
|
|
86
83
|
data1: uint32[0]
|
|
87
84
|
data2: complex128[0.+3.14159265j 0.+3.14159265j 0.+3.14159265j]
|
|
88
85
|
field2: Struct example:
|
|
89
|
-
psize: uint16[0]
|
|
90
86
|
data1: uint32[0]
|
|
91
87
|
data2: complex128[0.+0.j 0.+0.j 0.+0.j]
|
|
92
88
|
```
|
|
93
89
|
|
|
90
|
+
To save to disk,
|
|
91
|
+
```python
|
|
92
|
+
n = nested(shape=2)
|
|
93
|
+
n[1].field2.data1 = 3
|
|
94
|
+
np.save("test.npy", n)
|
|
95
|
+
n_disk = nested(np.load("test.npy"))
|
|
96
|
+
```
|
|
97
|
+
```python
|
|
98
|
+
>>> n_disk.field2.data1
|
|
99
|
+
array([[[0]],
|
|
100
|
+
|
|
101
|
+
[[3]]], dtype=uint32)
|
|
102
|
+
```
|
|
103
|
+
|
|
94
104
|
### Labeled Arrays
|
|
95
105
|
|
|
96
106
|
`np-struct` implements a very scaled down version of [Xarray](https://docs.xarray.dev/en/stable/) DataArrays.
|
|
@@ -98,10 +108,9 @@ Struct nested:
|
|
|
98
108
|
written to disk in the standard `.npy` binary format, and supports indexing with coordinates.
|
|
99
109
|
|
|
100
110
|
Math operations that change the coordinates or array shape (i.e. sum or transpose) silently revert the labeled array
|
|
101
|
-
to a standard numpy array without coordinates.
|
|
111
|
+
to a standard numpy array without coordinates. `np-struct` leaves it up to the user to re-cast the array as an
|
|
112
|
+
`ldarray` with the appropriate coordinates.
|
|
102
113
|
|
|
103
|
-
Real or complex-valued arrays can be saved with the ``ldarray.save()`` method, and
|
|
104
|
-
loaded with the ``ldarray.load()`` class function.
|
|
105
114
|
|
|
106
115
|
```python
|
|
107
116
|
>>> from np_struct import ldarray
|
|
@@ -147,8 +156,9 @@ Coordinates: (2,)
|
|
|
147
156
|
a: ['data1' 'data2']
|
|
148
157
|
```
|
|
149
158
|
|
|
150
|
-
|
|
151
|
-
use `ldarray.save()` and `load()`. Array is stored as a structured array in the usual
|
|
159
|
+
Real or complex-valued arrays can be written to disk uses the normal numpy methods if the coordinates are not needed,
|
|
160
|
+
or, to keep the coords, use `ldarray.save()` and `load()`. Array is stored as a structured array in the usual
|
|
161
|
+
`.npy` binary format.
|
|
152
162
|
```python
|
|
153
163
|
ld.save("ld_file.npy")
|
|
154
164
|
ldarray.load("ld_file.npy")
|
|
@@ -156,7 +166,7 @@ ldarray.load("ld_file.npy")
|
|
|
156
166
|
|
|
157
167
|
## Examples
|
|
158
168
|
|
|
159
|
-
[
|
|
169
|
+
[Struct example](./examples/structures.ipynb)
|
|
160
170
|
|
|
161
171
|
|
|
162
172
|
## License
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import numpy as np
|
|
3
|
-
from copy import deepcopy as dcopy
|
|
4
3
|
|
|
5
4
|
|
|
6
|
-
class
|
|
5
|
+
class bitfield(np.ndarray):
|
|
7
6
|
|
|
8
7
|
def __new__(cls, input_=None, bits=None, doc=None, dtype=None, enum=None):
|
|
9
8
|
|
|
@@ -32,32 +31,31 @@ class npfield(np.ndarray):
|
|
|
32
31
|
self.doc = getattr(obj, 'doc', "")
|
|
33
32
|
self.enum = getattr(obj, 'enum', None)
|
|
34
33
|
|
|
35
|
-
def __array_wrap__(self, out_arr, context=None):
|
|
34
|
+
def __array_wrap__(self, out_arr, context=None, return_scalar=False):
|
|
36
35
|
|
|
37
36
|
dtype = np.dtype(self.__class__.__name__.lower())
|
|
38
|
-
|
|
39
37
|
return out_arr.astype(dtype)
|
|
40
38
|
|
|
41
|
-
class uint8(
|
|
39
|
+
class uint8(bitfield):
|
|
42
40
|
pass
|
|
43
41
|
|
|
44
|
-
class int8(
|
|
42
|
+
class int8(bitfield):
|
|
45
43
|
pass
|
|
46
44
|
|
|
47
|
-
class uint16(
|
|
45
|
+
class uint16(bitfield):
|
|
48
46
|
pass
|
|
49
47
|
|
|
50
|
-
class int16(
|
|
48
|
+
class int16(bitfield):
|
|
51
49
|
pass
|
|
52
50
|
|
|
53
|
-
class uint32(
|
|
51
|
+
class uint32(bitfield):
|
|
54
52
|
pass
|
|
55
53
|
|
|
56
|
-
class int32(
|
|
54
|
+
class int32(bitfield):
|
|
57
55
|
pass
|
|
58
56
|
|
|
59
|
-
class float64(
|
|
57
|
+
class float64(bitfield):
|
|
60
58
|
pass
|
|
61
59
|
|
|
62
|
-
class float32(
|
|
60
|
+
class float32(bitfield):
|
|
63
61
|
pass
|
|
@@ -32,6 +32,8 @@ def datetime_idx_handler(v, coords: np.ndarray):
|
|
|
32
32
|
# if str, treat as UTC formatted string, use same format as is printed with the ldarray __str__ method.
|
|
33
33
|
elif isinstance(v, str):
|
|
34
34
|
v_ts = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M').timestamp()
|
|
35
|
+
else:
|
|
36
|
+
raise NotImplementedError(f"Unsupported datetime index: {type(v)}")
|
|
35
37
|
|
|
36
38
|
# get index of the minimum distance to the indexing timestamp
|
|
37
39
|
return np.argmin(np.abs(coords_ts - v_ts))
|
|
@@ -132,35 +134,39 @@ class Coords(OrderedDict):
|
|
|
132
134
|
def __setitem__(self, k, v):
|
|
133
135
|
# adds new values to the dictionary
|
|
134
136
|
|
|
135
|
-
# cast as
|
|
136
|
-
v =
|
|
137
|
-
|
|
138
|
-
# cast dates (only day/month/year) to more general datetime objects
|
|
139
|
-
if isinstance(v[0], datetime.date):
|
|
140
|
-
v = np.array([dt.datetime(year=d.year, month=d.month, day=d.day) for d in v])
|
|
137
|
+
# cast as list
|
|
138
|
+
v = [v] if isinstance(v, (str, int, float)) else v
|
|
139
|
+
v_1d = np.atleast_1d(v)
|
|
141
140
|
|
|
142
141
|
f64 = np.dtype(np.float64)
|
|
143
142
|
f32 = np.dtype(np.float32)
|
|
144
143
|
|
|
145
144
|
# Provide default values for index precision if the values are floats
|
|
146
|
-
if
|
|
147
|
-
if
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
if v_1d.dtype in [f64, f32]:
|
|
146
|
+
# add entry to the index precision for this dimension if it doesn't exist
|
|
147
|
+
if k not in self.idx_precision.keys():
|
|
148
|
+
if len(v_1d) == 1:
|
|
149
|
+
self.idx_precision[k] = 1e-10
|
|
150
|
+
else:
|
|
151
|
+
self.idx_precision[k] = np.average(np.diff(v_1d))
|
|
152
|
+
|
|
153
|
+
super().__setitem__(k, v_1d)
|
|
154
|
+
|
|
155
|
+
elif isinstance(v_1d[0], (datetime.datetime, datetime.date)):
|
|
156
|
+
# cast dates (only day/month/year) to more general datetime objects
|
|
157
|
+
if isinstance(v_1d[0], datetime.date):
|
|
158
|
+
v_1d = np.array([dt.datetime(year=d.year, month=d.month, day=d.day) for d in v])
|
|
159
|
+
|
|
153
160
|
# use index handler for datetime objects
|
|
154
161
|
self.idx_handlers[k] = datetime_idx_handler
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
super().__setitem__(k, v_1d)
|
|
163
|
+
|
|
157
164
|
else:
|
|
165
|
+
# add to lookup table
|
|
158
166
|
self.idx_label_lut[k] = {vv:i for i,vv in enumerate(v)}
|
|
167
|
+
super().__setitem__(k, v)
|
|
159
168
|
|
|
160
|
-
# call ordered dictionary __setitem__
|
|
161
|
-
super().__setitem__(k, v)
|
|
162
169
|
|
|
163
|
-
|
|
164
170
|
def get_axis_idx(self, key):
|
|
165
171
|
"""
|
|
166
172
|
Returns the axis (dimension) index that 'key' has in the lddarray that uses this lddim.
|
|
@@ -337,22 +343,30 @@ class ldarray(np.ndarray):
|
|
|
337
343
|
return results
|
|
338
344
|
|
|
339
345
|
|
|
340
|
-
def
|
|
341
|
-
|
|
346
|
+
def __copy__(self):
|
|
347
|
+
obj = super().__copy__()
|
|
348
|
+
obj.coords = dcopy(self.coords)
|
|
349
|
+
return obj
|
|
342
350
|
|
|
343
351
|
|
|
344
|
-
def
|
|
345
|
-
|
|
346
|
-
return self.getitem(key)
|
|
347
|
-
except Exception:
|
|
348
|
-
return super().__getitem__(key)
|
|
352
|
+
def __deepcopy__(self, memo):
|
|
353
|
+
return self.__copy__()
|
|
349
354
|
|
|
350
355
|
|
|
351
|
-
def
|
|
356
|
+
def sel(self, **keys):
|
|
357
|
+
return self[keys]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def __getitem__(self, key):
|
|
352
361
|
# called whenever array is indexed
|
|
353
362
|
|
|
354
363
|
# if coords were dropped, use the normal ndarray __getitem__, dictionary coordinates will raise an error here
|
|
355
364
|
if self.coords is None:
|
|
365
|
+
|
|
366
|
+
# raise an error if key is a dictionary
|
|
367
|
+
if isinstance(key, dict):
|
|
368
|
+
raise IndexError(f"Unable to use dictionary {key} to index an array without coords.")
|
|
369
|
+
|
|
356
370
|
return super().__getitem__(key)
|
|
357
371
|
|
|
358
372
|
# if index is a dictionary, use the dimension labels to index
|
|
@@ -368,8 +382,7 @@ class ldarray(np.ndarray):
|
|
|
368
382
|
return obj
|
|
369
383
|
|
|
370
384
|
# index is a standard index of slices or integers so pass key to the numpy indexing routine.
|
|
371
|
-
# this object will have the
|
|
372
|
-
# the __array_finalize__ method declared above to construct the new array.
|
|
385
|
+
# this object will have the coords set to None by __array_finalize___
|
|
373
386
|
obj = super(ldarray, self).__getitem__(key)
|
|
374
387
|
|
|
375
388
|
# shape length can be greater after indexing if np.newaxis was used. In this case just
|
|
@@ -387,47 +400,52 @@ class ldarray(np.ndarray):
|
|
|
387
400
|
|
|
388
401
|
# At this point, we need to index the dimension dictionary so it matches the obj data,
|
|
389
402
|
# and remove axis that were indexed out completely.
|
|
403
|
+
try:
|
|
404
|
+
# Cast index key as a tuple if it's a single value
|
|
405
|
+
nkey = tuple(key) if isinstance(key, (tuple, list)) else (key,)
|
|
390
406
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if isinstance(k, type(Ellipsis)):
|
|
404
|
-
# key after an Ellipsis indexes the dimension starting from the end of the key list
|
|
405
|
-
idx_i = len(idx) - (len(nkey) - idx_i)
|
|
407
|
+
# Initialize list of indices for each dimension that will be used to index the label arrays in dim.
|
|
408
|
+
# Length is the original array shape length so it matches ndim.
|
|
409
|
+
idx = [slice(None,None) for i in range(len(self.shape))]
|
|
410
|
+
|
|
411
|
+
# step through index keys and update idx with the appropriate keys.
|
|
412
|
+
# Keys are always in order of the array dimensions, but axis can be skipped with the Ellipsis operator.
|
|
413
|
+
idx_i = 0
|
|
414
|
+
for ii, k in enumerate(nkey):
|
|
415
|
+
# jump the current index (idx_i) ahead if there is an Ellipsis.
|
|
416
|
+
if isinstance(k, type(Ellipsis)):
|
|
417
|
+
# key after an Ellipsis indexes the dimension starting from the end of the key list
|
|
418
|
+
idx_i = len(idx) - (len(nkey) - idx_i)
|
|
406
419
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
420
|
+
else:
|
|
421
|
+
# update idx with the key, if no key is given for a axis it defaults to ':'
|
|
422
|
+
idx[idx_i] = k
|
|
410
423
|
|
|
411
|
-
|
|
424
|
+
idx_i += 1
|
|
412
425
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
426
|
+
# use idx to index each array of dimension labels in ndim
|
|
427
|
+
for i, (k,v) in enumerate(self.coords.items()):
|
|
428
|
+
# numpy removes the dimension if indexed with a integer, so remove it from the dimension label dictionary.
|
|
429
|
+
if isinstance(idx[i], int):
|
|
430
|
+
ncoords.pop(k)
|
|
418
431
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
432
|
+
else:
|
|
433
|
+
# reduce the label array for the current axis to match the indexed numpy array.
|
|
434
|
+
# idx has a value for every dimension so we can use i to get the correct index key
|
|
435
|
+
ncoords[k] = np.array(v)[idx[i]]
|
|
423
436
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
437
|
+
# revert to standard numpy array if we weren't able to keep coords consistent with the numpy array data
|
|
438
|
+
if not check_shapes(obj.shape, ncoords.shape):
|
|
439
|
+
return obj.view(np.ndarray)
|
|
427
440
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
441
|
+
# if dim and the obj shape match, update the dim member of the indexed obj and return
|
|
442
|
+
obj.coords = ncoords
|
|
443
|
+
return obj
|
|
444
|
+
|
|
445
|
+
# if the coords were unable to be indexed, clear the coords and return a unlabeled numpy array.
|
|
446
|
+
except Exception:
|
|
447
|
+
obj.coords = None
|
|
448
|
+
return obj
|
|
431
449
|
|
|
432
450
|
|
|
433
451
|
def __setitem__(self, key, value):
|
|
@@ -462,7 +480,8 @@ class ldarray(np.ndarray):
|
|
|
462
480
|
|
|
463
481
|
|
|
464
482
|
def __str__(self):
|
|
465
|
-
|
|
483
|
+
|
|
484
|
+
LEN_THRESHOLD = 7
|
|
466
485
|
s = super().__repr__()
|
|
467
486
|
|
|
468
487
|
if self.coords is None:
|
|
@@ -474,8 +493,19 @@ class ldarray(np.ndarray):
|
|
|
474
493
|
|
|
475
494
|
if isinstance(v[0], datetime.datetime):
|
|
476
495
|
v = np.array(v).astype('datetime64[m]')
|
|
496
|
+
if isinstance(v, np.ndarray):
|
|
497
|
+
v_str = np.array2string(v, threshold=LEN_THRESHOLD, suppress_small=True, edgeitems=2, prefix=" ")
|
|
498
|
+
else:
|
|
499
|
+
# abbreviate long coordinate lists
|
|
500
|
+
if len(v) > LEN_THRESHOLD:
|
|
501
|
+
v_start = v[:int(LEN_THRESHOLD / 2)]
|
|
502
|
+
v_end = v[-int(LEN_THRESHOLD / 2):]
|
|
503
|
+
v_str = str(v_start)[:-1] + ", ... " + str(v_end)[1:]
|
|
504
|
+
else:
|
|
505
|
+
v_str = str(v)
|
|
506
|
+
|
|
477
507
|
|
|
478
|
-
s+='\n '+k + ': '+
|
|
508
|
+
s+='\n '+k + ': '+ v_str
|
|
479
509
|
|
|
480
510
|
return s + '\n'
|
|
481
511
|
|
|
@@ -506,10 +536,17 @@ class ldarray(np.ndarray):
|
|
|
506
536
|
coords_k = self.coords[k]
|
|
507
537
|
|
|
508
538
|
is_idx_slice = isinstance(v, slice)
|
|
539
|
+
is_idx_list = isinstance(v, (list, tuple, np.ndarray))
|
|
540
|
+
|
|
541
|
+
# allow single value coordinates as lists or tuples
|
|
542
|
+
if is_idx_list and len(v) > 1:
|
|
543
|
+
raise IndexError("Only single item lists are supported as indices.")
|
|
509
544
|
|
|
510
545
|
# cast v as a slice if not already, this allows us to use v.start and v.stop below. Both v.start and
|
|
511
546
|
# v.stop will be the same when a single value is cast to a slice.
|
|
512
|
-
if
|
|
547
|
+
if is_idx_list:
|
|
548
|
+
v = slice(v[0], v[0], None)
|
|
549
|
+
elif not is_idx_slice:
|
|
513
550
|
v = slice(v, v, None)
|
|
514
551
|
|
|
515
552
|
# check if this dimension has a custom handler defined
|
|
@@ -518,10 +555,10 @@ class ldarray(np.ndarray):
|
|
|
518
555
|
handler = self.coords.idx_handlers[k]
|
|
519
556
|
|
|
520
557
|
# call handler for each start, stop and step value
|
|
521
|
-
slc_list = [handler(vv, coords_k) if vv is not None else None for vv in [v.start, v.stop
|
|
558
|
+
slc_list = [handler(vv, coords_k) if vv is not None else None for vv in [v.start, v.stop]]
|
|
522
559
|
|
|
523
560
|
# populate numpy index with slice of standard indices
|
|
524
|
-
np_index[np_i] = slice(slc_list[0], slc_list[1] + 1,
|
|
561
|
+
np_index[np_i] = slice(slc_list[0], slc_list[1] + 1, v.step)
|
|
525
562
|
|
|
526
563
|
# The dimensional key will be in the idx_precision dictionary if the labels are floats.
|
|
527
564
|
# Use approximate indexing based on index precision given in the lddim class.
|
|
@@ -576,7 +613,7 @@ class ldarray(np.ndarray):
|
|
|
576
613
|
np_index[np_i] = slice(s_start, s_stop, s_step)
|
|
577
614
|
|
|
578
615
|
# if indexed with single values, convert the index from a slice to a integer
|
|
579
|
-
if not is_idx_slice:
|
|
616
|
+
if not is_idx_slice and not is_idx_list:
|
|
580
617
|
np_index[np_i] = int(np_index[np_i].start)
|
|
581
618
|
|
|
582
619
|
return tuple(np_index)
|
|
@@ -596,7 +633,8 @@ class ldarray(np.ndarray):
|
|
|
596
633
|
|
|
597
634
|
# build dtype and value from dimension labels
|
|
598
635
|
# dtype for a structured array is a tuple in the format (name, dtype, shape)
|
|
599
|
-
for k,v in self.coords.items():
|
|
636
|
+
for k, v in self.coords.items():
|
|
637
|
+
v = np.atleast_1d(v)
|
|
600
638
|
dim_value.append(v)
|
|
601
639
|
dim_dtype.append((k, v.dtype, v.shape))
|
|
602
640
|
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
import numpy as np
|
|
3
|
-
from .
|
|
4
|
-
from copy import deepcopy as dcopy
|
|
2
|
+
from . bitfields import bitfield
|
|
5
3
|
from collections import OrderedDict as od
|
|
6
4
|
|
|
7
|
-
_PROTECTED_FIELD_NAMES = ["value", "dtype", "shape", "unpack", "byte_order", "get_size"]
|
|
8
|
-
|
|
9
|
-
_BYTE_ORDER_TOKENS = ("=", "<", ">", "|")
|
|
10
|
-
|
|
11
5
|
_SUPPORTED_NP_TYPES = (np.uint8, np.uint16, np.uint32, np.int16, np.int32, np.int64, np.float32, np.float64, np.complex128)
|
|
12
6
|
|
|
13
7
|
class StructMeta(type):
|
|
@@ -30,7 +24,7 @@ class StructMeta(type):
|
|
|
30
24
|
for key, item in classdict.items():
|
|
31
25
|
|
|
32
26
|
## ignore any class definitions that aren't supported numpy types
|
|
33
|
-
if not isinstance(item, (np.ndarray, Struct,
|
|
27
|
+
if not isinstance(item, (np.ndarray, Struct, bitfield) + _SUPPORTED_NP_TYPES):
|
|
34
28
|
continue
|
|
35
29
|
|
|
36
30
|
## error if any private variables are used in class definition, or if there is a naming collision
|
|
@@ -164,7 +158,6 @@ class Struct(np.ndarray, metaclass=StructMeta):
|
|
|
164
158
|
else:
|
|
165
159
|
return ret
|
|
166
160
|
|
|
167
|
-
|
|
168
161
|
def unpack(self, bytes):
|
|
169
162
|
"""
|
|
170
163
|
Unpacks byte data into the structured array for this object.
|
|
@@ -212,7 +205,7 @@ class Struct(np.ndarray, metaclass=StructMeta):
|
|
|
212
205
|
if len(idx) > 1:
|
|
213
206
|
build += tabs + '[\n'
|
|
214
207
|
for k, v in self._cls_defs.items():
|
|
215
|
-
item = getattr(self[
|
|
208
|
+
item = getattr(self[tuple(item_i)], k)
|
|
216
209
|
|
|
217
210
|
key_tab = ' '*(self._printwidth-len(str(k))-1)
|
|
218
211
|
|