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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: np-struct
3
- Version: 0.0.2
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` extends structured arrays in NumPy to be a bit more user friendly and intuitive, with added support for transferring structured arrays across serial or socket interfaces.
21
+ `np-struct` is a user friendly interface to NumPy structured arrays, with added support for transferring arrays across interfaces.
22
22
 
23
- Structured arrays are built to mirror the struct typedef in C/C++, but can be used for any complicated data structure. They behave similar to standard arrays, but support mixed data types, labeling, and unequal length arrays. Arrays are easily written or loaded from disk in the standard `.npy` binary format.
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 c-style structure with Numpy types.
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 inital value.
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
- >>> ex2.view(np.ndarray)
86
- array([([0], [0], [[0.+0.j, 0.+0.j], [0.+0.j, 0.+0.j], [0.+0.j, 0.+0.j]])],
87
- dtype=[('psize', '<u2', (1,)), ('data1', '<u4', (1,)), ('data2', '<c16', (3, 2))])
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 += j*np.pi
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
- Arrays can be written to disk uses the normal numpy methods if the coordinates are not needed, or, to keep the coords,
169
- use `ldarray.save()` and `load()`. Array is stored as a structured array in the usual `.npy` binary format.
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
- [Transfering structures over an interface](./examples/structures.ipynb)
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` extends structured arrays in NumPy to be a bit more user friendly and intuitive, with added support for transferring structured arrays across serial or socket interfaces.
3
+ `np-struct` is a user friendly interface to NumPy structured arrays, with added support for transferring arrays across interfaces.
4
4
 
5
- Structured arrays are built to mirror the struct typedef in C/C++, but can be used for any complicated data structure. They behave similar to standard arrays, but support mixed data types, labeling, and unequal length arrays. Arrays are easily written or loaded from disk in the standard `.npy` binary format.
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 c-style structure with Numpy types.
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 inital value.
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
- >>> ex2.view(np.ndarray)
68
- array([([0], [0], [[0.+0.j, 0.+0.j], [0.+0.j, 0.+0.j], [0.+0.j, 0.+0.j]])],
69
- dtype=[('psize', '<u2', (1,)), ('data1', '<u4', (1,)), ('data2', '<c16', (3, 2))])
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 += j*np.pi
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
- Arrays can be written to disk uses the normal numpy methods if the coordinates are not needed, or, to keep the coords,
151
- use `ldarray.save()` and `load()`. Array is stored as a structured array in the usual `.npy` binary format.
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
- [Transfering structures over an interface](./examples/structures.ipynb)
169
+ [Struct example](./examples/structures.ipynb)
160
170
 
161
171
 
162
172
  ## License
@@ -2,4 +2,4 @@
2
2
  from . structures import Struct
3
3
  from . ldarray import ldarray, Coords
4
4
  from . transfer import Packet
5
- from . import fields
5
+ from . import bitfields
@@ -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 npfield(np.ndarray):
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(npfield):
39
+ class uint8(bitfield):
42
40
  pass
43
41
 
44
- class int8(npfield):
42
+ class int8(bitfield):
45
43
  pass
46
44
 
47
- class uint16(npfield):
45
+ class uint16(bitfield):
48
46
  pass
49
47
 
50
- class int16(npfield):
48
+ class int16(bitfield):
51
49
  pass
52
50
 
53
- class uint32(npfield):
51
+ class uint32(bitfield):
54
52
  pass
55
53
 
56
- class int32(npfield):
54
+ class int32(bitfield):
57
55
  pass
58
56
 
59
- class float64(npfield):
57
+ class float64(bitfield):
60
58
  pass
61
59
 
62
- class float32(npfield):
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 numpy array
136
- v = np.atleast_1d(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 (k not in self.idx_precision.keys()) and (v.dtype in [f64, f32]):
147
- if len(v) == 1:
148
- self.idx_precision[k] = 1e-10
149
- else:
150
- self.idx_precision[k] = np.average(np.diff(v))
151
-
152
- elif isinstance(v[0], datetime.datetime):
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
- # add to lookup table otherwise
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 sel(self, **keys):
341
- return self[keys]
346
+ def __copy__(self):
347
+ obj = super().__copy__()
348
+ obj.coords = dcopy(self.coords)
349
+ return obj
342
350
 
343
351
 
344
- def __getitem__(self, key):
345
- try:
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 getitem(self, key):
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 same coords (not copied) as the object it was indexed from since numpy uses
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
- # Cast index key as a tuple if it's a single value
392
- nkey = tuple(key) if isinstance(key, (tuple, list)) else (key,)
393
-
394
- # Initialize list of indices for each dimension that will be used to index the label arrays in dim.
395
- # Length is the original array shape length so it matches ndim.
396
- idx = [slice(None,None) for i in range(len(self.shape))]
397
-
398
- # step through index keys and update idx with the appropriate keys.
399
- # Keys are always in order of the array dimensions, but axis can be skipped with the Ellipsis operator.
400
- idx_i = 0
401
- for ii, k in enumerate(nkey):
402
- # jump the current index (idx_i) ahead if there is an Ellipsis.
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
- else:
408
- # update idx with the key, if no key is given for a axis it defaults to ':'
409
- idx[idx_i] = k
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
- idx_i += 1
424
+ idx_i += 1
412
425
 
413
- # use idx to index each array of dimension labels in ndim
414
- for i, (k,v) in enumerate(self.coords.items()):
415
- # numpy removes the dimension if indexed with a integer, so remove it from the dimension label dictionary.
416
- if isinstance(idx[i], int):
417
- ncoords.pop(k)
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
- else:
420
- # reduce the label array for the current axis to match the indexed numpy array.
421
- # idx has a value for every dimension so we can use i to get the correct index key
422
- ncoords[k] = v[idx[i]]
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
- # revert to standard numpy array if we weren't able to keep coords consistent with the numpy array data
425
- if not check_shapes(obj.shape, ncoords.shape):
426
- return obj.view(np.ndarray)
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
- # if dim and the obj shape match, update the dim member of the indexed obj and return
429
- obj.coords = ncoords
430
- return obj
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 + ': '+ np.array2string(v, threshold=3, suppress_small=True, edgeitems=2, prefix=" ")
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 not is_idx_slice:
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, v.step]]
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, slc_list[2])
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 . fields import npfield
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, npfield) + _SUPPORTED_NP_TYPES):
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[*tuple(item_i)], k)
208
+ item = getattr(self[tuple(item_i)], k)
216
209
 
217
210
  key_tab = ' '*(self._printwidth-len(str(k))-1)
218
211