harp-python 0.3.0__tar.gz → 0.4.1__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.
- {harp_python-0.3.0 → harp_python-0.4.1}/.github/workflows/build.yml +1 -1
- harp_python-0.4.1/.gitignore +23 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/PKG-INFO +4 -4
- harp_python-0.4.1/harp/__init__.py +5 -0
- harp_python-0.4.1/harp/io.py +274 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/harp/model.py +1 -8
- {harp_python-0.3.0 → harp_python-0.4.1}/harp/reader.py +27 -21
- {harp_python-0.3.0 → harp_python-0.4.1}/harp/schema.py +3 -0
- harp_python-0.4.1/harp/typing.py +13 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/harp_python.egg-info/PKG-INFO +4 -4
- {harp_python-0.3.0 → harp_python-0.4.1}/harp_python.egg-info/SOURCES.txt +3 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/pyproject.toml +5 -3
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/data/device.yml +22 -0
- harp_python-0.4.1/tests/data/device_44.bin +0 -0
- harp_python-0.4.1/tests/data/empty_0.bin +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/params.py +3 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/test_io.py +38 -2
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/test_reader.py +15 -5
- harp_python-0.3.0/.gitignore +0 -12
- harp_python-0.3.0/harp/__init__.py +0 -5
- harp_python-0.3.0/harp/io.py +0 -122
- {harp_python-0.3.0 → harp_python-0.4.1}/.gitmodules +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/LICENSE +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/MANIFEST.in +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/README.md +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/harp/common.yml +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/harp_python.egg-info/dependency_links.txt +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/harp_python.egg-info/requires.txt +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/harp_python.egg-info/top_level.txt +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/setup.cfg +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/__init__.py +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/data/device_0.bin +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/data/write_0.bin +0 -0
- {harp_python-0.3.0 → harp_python-0.4.1}/tests/test_schema.py +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
|
|
4
|
+
# Distribution / packaging
|
|
5
|
+
dist/
|
|
6
|
+
_version.py
|
|
7
|
+
*.egg-info/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# IDE
|
|
11
|
+
.vscode/*
|
|
12
|
+
|
|
13
|
+
# misc
|
|
14
|
+
log*.txt
|
|
15
|
+
scratch/
|
|
16
|
+
scratch*.py
|
|
17
|
+
|
|
18
|
+
# Test
|
|
19
|
+
.coverage
|
|
20
|
+
|
|
21
|
+
# Environment
|
|
22
|
+
.venv/
|
|
23
|
+
uv.lock
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: harp-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A low-level interface for loading binary Harp protocol data
|
|
5
5
|
Author-email: Goncalo Lopes <g.lopes@neurogears.org>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Bug Tracker, https://github.com/harp-tech/harp-python/issues
|
|
8
8
|
Project-URL: Source Code, https://github.com/harp-tech/harp-python
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -12,7 +12,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Requires-Python: >=3.9.0
|
|
17
16
|
Description-Content-Type: text/markdown
|
|
18
17
|
License-File: LICENSE
|
|
@@ -29,6 +28,7 @@ Requires-Dist: codespell; extra == "dev"
|
|
|
29
28
|
Provides-Extra: jupyter
|
|
30
29
|
Requires-Dist: ipykernel; extra == "jupyter"
|
|
31
30
|
Requires-Dist: matplotlib; extra == "jupyter"
|
|
31
|
+
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
# harp-python
|
|
34
34
|
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from typing import Any, BinaryIO, Optional, Union
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from pandas._typing import Axes # pyright: ignore[reportPrivateImportUsage]
|
|
10
|
+
|
|
11
|
+
from harp.typing import _BufferLike, _FileLike
|
|
12
|
+
|
|
13
|
+
REFERENCE_EPOCH = datetime(1904, 1, 1)
|
|
14
|
+
"""The reference epoch for UTC harp time."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MessageType(IntEnum):
|
|
18
|
+
"""Specifies the type of a Harp message."""
|
|
19
|
+
|
|
20
|
+
NA = 0
|
|
21
|
+
READ = 1
|
|
22
|
+
WRITE = 2
|
|
23
|
+
EVENT = 3
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_SECONDS_PER_TICK = 32e-6
|
|
27
|
+
_PAYLOAD_TIMESTAMP_MASK = 0x10
|
|
28
|
+
_messagetypes = [type.name for type in MessageType]
|
|
29
|
+
_dtypefrompayloadtype = {
|
|
30
|
+
1: np.dtype(np.uint8),
|
|
31
|
+
2: np.dtype(np.uint16),
|
|
32
|
+
4: np.dtype(np.uint32),
|
|
33
|
+
8: np.dtype(np.uint64),
|
|
34
|
+
129: np.dtype(np.int8),
|
|
35
|
+
130: np.dtype(np.int16),
|
|
36
|
+
132: np.dtype(np.int32),
|
|
37
|
+
136: np.dtype(np.int64),
|
|
38
|
+
68: np.dtype(np.float32),
|
|
39
|
+
}
|
|
40
|
+
_payloadtypefromdtype = {v: k for k, v in _dtypefrompayloadtype.items()}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def read(
|
|
44
|
+
file_or_buf: Union[_FileLike, _BufferLike],
|
|
45
|
+
address: Optional[int] = None,
|
|
46
|
+
dtype: Optional[np.dtype] = None,
|
|
47
|
+
length: Optional[int] = None,
|
|
48
|
+
columns: Optional[Axes] = None,
|
|
49
|
+
epoch: Optional[datetime] = None,
|
|
50
|
+
keep_type: bool = False,
|
|
51
|
+
):
|
|
52
|
+
"""Read single-register Harp data from the specified file or buffer.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
file_or_buf
|
|
57
|
+
File path, open file object, or buffer containing binary data from
|
|
58
|
+
a single device register.
|
|
59
|
+
address
|
|
60
|
+
Expected register address. If specified, the address of
|
|
61
|
+
the first message is used for validation.
|
|
62
|
+
dtype
|
|
63
|
+
Expected data type of the register payload. If specified, the
|
|
64
|
+
payload type of the first message is used for validation.
|
|
65
|
+
length
|
|
66
|
+
Expected number of elements in register payload. If specified, the
|
|
67
|
+
payload length of the first message is used for validation.
|
|
68
|
+
columns
|
|
69
|
+
The optional column labels to use for the data values.
|
|
70
|
+
epoch
|
|
71
|
+
Reference datetime at which time zero begins. If specified,
|
|
72
|
+
the result data frame will have a datetime index.
|
|
73
|
+
keep_type
|
|
74
|
+
Specifies whether to include a column with the message type.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
A pandas data frame containing message data, sorted by time.
|
|
79
|
+
"""
|
|
80
|
+
if isinstance(file_or_buf, (str, PathLike, BinaryIO)) or hasattr(file_or_buf, "readinto"):
|
|
81
|
+
# TODO: in the below we ignore the type as otherwise
|
|
82
|
+
# we have no way to runtime check _IOProtocol
|
|
83
|
+
data = np.fromfile(file_or_buf, dtype=np.uint8) # type: ignore
|
|
84
|
+
else:
|
|
85
|
+
data = np.frombuffer(file_or_buf, dtype=np.uint8)
|
|
86
|
+
|
|
87
|
+
if len(data) == 0:
|
|
88
|
+
return pd.DataFrame(
|
|
89
|
+
columns=columns,
|
|
90
|
+
index=pd.DatetimeIndex([], name="Time")
|
|
91
|
+
if epoch
|
|
92
|
+
else pd.Index([], dtype=np.float64, name="Time"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if address is not None and address != data[2]:
|
|
96
|
+
raise ValueError(f"expected address {address} but got {data[2]}")
|
|
97
|
+
|
|
98
|
+
index = None
|
|
99
|
+
stride = int(data[1] + 2)
|
|
100
|
+
nrows = len(data) // stride
|
|
101
|
+
payloadtype = data[4]
|
|
102
|
+
payloadoffset = 5
|
|
103
|
+
if payloadtype & _PAYLOAD_TIMESTAMP_MASK != 0:
|
|
104
|
+
seconds = np.ndarray(nrows, dtype=np.uint32, buffer=data, offset=payloadoffset, strides=stride)
|
|
105
|
+
payloadoffset += 4
|
|
106
|
+
micros = np.ndarray(nrows, dtype=np.uint16, buffer=data, offset=payloadoffset, strides=stride)
|
|
107
|
+
payloadoffset += 2
|
|
108
|
+
time = micros * _SECONDS_PER_TICK + seconds
|
|
109
|
+
payloadtype = payloadtype & ~np.uint8(_PAYLOAD_TIMESTAMP_MASK)
|
|
110
|
+
if epoch is not None:
|
|
111
|
+
time = epoch + pd.to_timedelta(time, "s") # type: ignore
|
|
112
|
+
index = pd.Series(time)
|
|
113
|
+
index.name = "Time"
|
|
114
|
+
|
|
115
|
+
payloadsize = stride - payloadoffset - 1
|
|
116
|
+
payloadtype = _dtypefrompayloadtype[payloadtype]
|
|
117
|
+
if dtype is not None and dtype != payloadtype:
|
|
118
|
+
raise ValueError(f"expected payload type {dtype} but got {payloadtype}")
|
|
119
|
+
|
|
120
|
+
elementsize = payloadtype.itemsize
|
|
121
|
+
payloadshape = (nrows, payloadsize // elementsize)
|
|
122
|
+
if length is not None and length != payloadshape[1]:
|
|
123
|
+
raise ValueError(f"expected payload length {length} but got {payloadshape[1]}")
|
|
124
|
+
|
|
125
|
+
payload = np.ndarray(
|
|
126
|
+
payloadshape,
|
|
127
|
+
dtype=payloadtype,
|
|
128
|
+
buffer=data,
|
|
129
|
+
offset=payloadoffset,
|
|
130
|
+
strides=(stride, elementsize),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
result = pd.DataFrame(payload, index=index, columns=columns)
|
|
134
|
+
if keep_type:
|
|
135
|
+
msgtype = np.ndarray(nrows, dtype=np.uint8, buffer=data, offset=0, strides=stride)
|
|
136
|
+
msgtype = pd.Categorical.from_codes(msgtype, categories=_messagetypes) # type: ignore
|
|
137
|
+
result[MessageType.__name__] = msgtype
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def to_file(
|
|
142
|
+
data: pd.DataFrame,
|
|
143
|
+
file: _FileLike,
|
|
144
|
+
address: int,
|
|
145
|
+
dtype: Optional[np.dtype] = None,
|
|
146
|
+
length: Optional[int] = None,
|
|
147
|
+
port: Optional[int] = None,
|
|
148
|
+
epoch: Optional[datetime] = None,
|
|
149
|
+
message_type: Optional[MessageType] = None,
|
|
150
|
+
):
|
|
151
|
+
"""Write single-register Harp data to the specified file.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
data
|
|
156
|
+
Pandas data frame containing message payload.
|
|
157
|
+
file
|
|
158
|
+
File path, or open file object in which to store binary data from
|
|
159
|
+
a single device register.
|
|
160
|
+
address
|
|
161
|
+
Register address used to identify all formatted Harp messages.
|
|
162
|
+
dtype
|
|
163
|
+
Data type of the register payload. If specified, all data will
|
|
164
|
+
be converted before formatting the binary payload.
|
|
165
|
+
length
|
|
166
|
+
Expected number of elements in register payload. If specified, the
|
|
167
|
+
number of columns in the input data frame is validated.
|
|
168
|
+
port
|
|
169
|
+
Optional port value used for all formatted Harp messages.
|
|
170
|
+
epoch
|
|
171
|
+
Reference datetime at which time zero begins. If specified,
|
|
172
|
+
the input data frame must have a datetime index.
|
|
173
|
+
message_type
|
|
174
|
+
Optional message type used for all formatted Harp messages.
|
|
175
|
+
If not specified, data must contain a MessageType column.
|
|
176
|
+
"""
|
|
177
|
+
buffer = to_buffer(data, address, dtype, port, length, epoch, message_type)
|
|
178
|
+
buffer.tofile(file)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def to_buffer(
|
|
182
|
+
data: pd.DataFrame,
|
|
183
|
+
address: int,
|
|
184
|
+
dtype: Optional[np.dtype] = None,
|
|
185
|
+
length: Optional[int] = None,
|
|
186
|
+
port: Optional[int] = None,
|
|
187
|
+
epoch: Optional[datetime] = None,
|
|
188
|
+
message_type: Optional[MessageType] = None,
|
|
189
|
+
) -> npt.NDArray[np.uint8]:
|
|
190
|
+
"""Convert single-register Harp data to a flat binary buffer.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
data
|
|
195
|
+
Pandas data frame containing message payload.
|
|
196
|
+
address
|
|
197
|
+
Register address used to identify all formatted Harp messages.
|
|
198
|
+
dtype
|
|
199
|
+
Data type of the register payload. If specified, all data will
|
|
200
|
+
be converted before formatting the binary payload.
|
|
201
|
+
length
|
|
202
|
+
Expected number of elements in register payload. If specified, the
|
|
203
|
+
number of columns in the input data frame is validated.
|
|
204
|
+
port
|
|
205
|
+
Optional port value used for all formatted Harp messages.
|
|
206
|
+
epoch
|
|
207
|
+
Reference datetime at which time zero begins. If specified,
|
|
208
|
+
the input data frame must have a datetime index.
|
|
209
|
+
message_type
|
|
210
|
+
Optional message type used for all formatted Harp messages.
|
|
211
|
+
If not specified, data must contain a MessageType column.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
An array object containing message data formatted according
|
|
216
|
+
to the Harp binary protocol.
|
|
217
|
+
"""
|
|
218
|
+
nrows = len(data)
|
|
219
|
+
if nrows == 0:
|
|
220
|
+
return np.empty(0, dtype=np.uint8)
|
|
221
|
+
|
|
222
|
+
if MessageType.__name__ in data.columns:
|
|
223
|
+
msgtype = data[MessageType.__name__].cat.codes
|
|
224
|
+
payload = data[data.columns.drop(MessageType.__name__)].values
|
|
225
|
+
elif message_type is not None:
|
|
226
|
+
msgtype = message_type
|
|
227
|
+
payload = data.values
|
|
228
|
+
else:
|
|
229
|
+
raise ValueError(f"message type must be specified either in the data or as argument")
|
|
230
|
+
|
|
231
|
+
time = data.index
|
|
232
|
+
is_timestamped = True
|
|
233
|
+
if epoch is not None:
|
|
234
|
+
if not isinstance(time, pd.DatetimeIndex):
|
|
235
|
+
raise ValueError(f"expected datetime index to encode with epoch but got {time.inferred_type}")
|
|
236
|
+
time = (time - epoch).total_seconds()
|
|
237
|
+
elif isinstance(time, pd.RangeIndex):
|
|
238
|
+
is_timestamped = False
|
|
239
|
+
|
|
240
|
+
if dtype is not None:
|
|
241
|
+
payload = payload.astype(dtype, copy=False)
|
|
242
|
+
|
|
243
|
+
ncols = payload.shape[1]
|
|
244
|
+
if length is not None and ncols != length:
|
|
245
|
+
raise ValueError(f"expected payload length {length} but got {ncols}")
|
|
246
|
+
|
|
247
|
+
if port is None:
|
|
248
|
+
port = 255
|
|
249
|
+
|
|
250
|
+
payloadtype = _payloadtypefromdtype[payload.dtype]
|
|
251
|
+
payloadlength = ncols * payload.dtype.itemsize
|
|
252
|
+
stride = payloadlength + 6
|
|
253
|
+
if is_timestamped:
|
|
254
|
+
payloadtype |= _PAYLOAD_TIMESTAMP_MASK
|
|
255
|
+
stride += 6
|
|
256
|
+
|
|
257
|
+
buffer = np.empty((nrows, stride), dtype=np.uint8)
|
|
258
|
+
buffer[:, 0] = msgtype
|
|
259
|
+
buffer[:, 1:5] = [stride - 2, address, port, payloadtype]
|
|
260
|
+
|
|
261
|
+
payloadoffset = 5
|
|
262
|
+
if is_timestamped:
|
|
263
|
+
seconds = time.astype(np.uint32)
|
|
264
|
+
micros = np.around(((time - seconds) / _SECONDS_PER_TICK).values).astype(np.uint16)
|
|
265
|
+
buffer[:, 5:9] = np.ndarray((nrows, 4), dtype=np.uint8, buffer=seconds.values)
|
|
266
|
+
buffer[:, 9:11] = np.ndarray((nrows, 2), dtype=np.uint8, buffer=micros)
|
|
267
|
+
payloadoffset += 6
|
|
268
|
+
|
|
269
|
+
payloadstop = payloadoffset + payloadlength
|
|
270
|
+
buffer[:, payloadoffset:payloadstop] = np.ndarray(
|
|
271
|
+
(nrows, payloadlength), dtype=np.uint8, buffer=np.ascontiguousarray(payload)
|
|
272
|
+
)
|
|
273
|
+
buffer[:, -1] = np.sum(buffer[:, 0:-1], axis=1, dtype=np.uint8)
|
|
274
|
+
return buffer.reshape(-1)
|
|
@@ -7,14 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from typing import Annotated, Dict, List, Optional, Union
|
|
9
9
|
|
|
10
|
-
from pydantic import
|
|
11
|
-
BaseModel,
|
|
12
|
-
BeforeValidator,
|
|
13
|
-
ConfigDict,
|
|
14
|
-
Field,
|
|
15
|
-
RootModel,
|
|
16
|
-
field_serializer,
|
|
17
|
-
)
|
|
10
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, RootModel, field_serializer
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
class PayloadType(str, Enum):
|
|
@@ -6,20 +6,21 @@ from functools import partial
|
|
|
6
6
|
from math import log2
|
|
7
7
|
from os import PathLike
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Callable, Iterable, Mapping, Optional, Protocol, Union
|
|
10
10
|
|
|
11
11
|
from numpy import dtype
|
|
12
12
|
from pandas import DataFrame, Series
|
|
13
|
-
from pandas._typing import Axes
|
|
13
|
+
from pandas._typing import Axes # pyright: ignore[reportPrivateImportUsage]
|
|
14
14
|
|
|
15
15
|
from harp.io import MessageType, read
|
|
16
16
|
from harp.model import BitMask, GroupMask, Model, PayloadMember, Register
|
|
17
17
|
from harp.schema import read_schema
|
|
18
|
+
from harp.typing import _BufferLike, _FileLike
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass
|
|
21
22
|
class _ReaderParams:
|
|
22
|
-
|
|
23
|
+
base_path: Path
|
|
23
24
|
epoch: Optional[datetime] = None
|
|
24
25
|
keep_type: bool = False
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ class _ReaderParams:
|
|
|
27
28
|
class _ReadRegister(Protocol):
|
|
28
29
|
def __call__(
|
|
29
30
|
self,
|
|
30
|
-
|
|
31
|
+
file_or_buf: Optional[Union[_FileLike, _BufferLike]] = None,
|
|
31
32
|
epoch: Optional[datetime] = None,
|
|
32
33
|
keep_type: bool = False,
|
|
33
34
|
) -> DataFrame: ...
|
|
@@ -81,12 +82,12 @@ def _compose_parser(
|
|
|
81
82
|
params: _ReaderParams,
|
|
82
83
|
) -> Callable[..., DataFrame]:
|
|
83
84
|
def parser(
|
|
84
|
-
|
|
85
|
+
data: Optional[Union[_FileLike, _BufferLike]] = None,
|
|
85
86
|
columns: Optional[Axes] = None,
|
|
86
87
|
epoch: Optional[datetime] = params.epoch,
|
|
87
88
|
keep_type: bool = params.keep_type,
|
|
88
89
|
):
|
|
89
|
-
df = g(
|
|
90
|
+
df = g(data, columns, epoch, keep_type)
|
|
90
91
|
result = f(df)
|
|
91
92
|
type_col = df.get(MessageType.__name__)
|
|
92
93
|
if type_col is not None:
|
|
@@ -167,16 +168,16 @@ def _create_payloadmember_parser(device: Model, member: PayloadMember):
|
|
|
167
168
|
|
|
168
169
|
def _create_register_reader(register: Register, params: _ReaderParams):
|
|
169
170
|
def reader(
|
|
170
|
-
|
|
171
|
+
file_or_buf: Optional[Union[_FileLike, _BufferLike]] = None,
|
|
171
172
|
columns: Optional[Axes] = None,
|
|
172
173
|
epoch: Optional[datetime] = params.epoch,
|
|
173
174
|
keep_type: bool = params.keep_type,
|
|
174
175
|
):
|
|
175
|
-
if
|
|
176
|
-
|
|
176
|
+
if file_or_buf is None:
|
|
177
|
+
file_or_buf = f"{params.base_path}_{register.address}.bin"
|
|
177
178
|
|
|
178
179
|
data = read(
|
|
179
|
-
|
|
180
|
+
file_or_buf,
|
|
180
181
|
address=register.address,
|
|
181
182
|
dtype=dtype(register.type),
|
|
182
183
|
length=register.length,
|
|
@@ -189,7 +190,7 @@ def _create_register_reader(register: Register, params: _ReaderParams):
|
|
|
189
190
|
return reader
|
|
190
191
|
|
|
191
192
|
|
|
192
|
-
def
|
|
193
|
+
def _create_register_handler(device: Model, name: str, params: _ReaderParams):
|
|
193
194
|
register = device.registers[name]
|
|
194
195
|
reader = _create_register_reader(register, params)
|
|
195
196
|
|
|
@@ -197,29 +198,34 @@ def _create_register_parser(device: Model, name: str, params: _ReaderParams):
|
|
|
197
198
|
key = register.maskType.root
|
|
198
199
|
bitMask = None if device.bitMasks is None else device.bitMasks.get(key)
|
|
199
200
|
if bitMask is not None:
|
|
200
|
-
|
|
201
|
-
reader = _compose_parser(
|
|
201
|
+
bitmask_parser = _create_bitmask_parser(bitMask)
|
|
202
|
+
reader = _compose_parser(bitmask_parser, reader, params)
|
|
202
203
|
return RegisterReader(register, reader)
|
|
203
204
|
|
|
204
205
|
groupMask = None if device.groupMasks is None else device.groupMasks.get(key)
|
|
205
206
|
if groupMask is not None:
|
|
206
|
-
|
|
207
|
-
reader = _compose_parser(
|
|
207
|
+
groupmask_parser = _create_groupmask_parser(name, groupMask)
|
|
208
|
+
reader = _compose_parser(groupmask_parser, reader, params)
|
|
208
209
|
return RegisterReader(register, reader)
|
|
209
210
|
|
|
210
211
|
if register.payloadSpec is not None:
|
|
211
|
-
|
|
212
|
+
member_parsers = [
|
|
212
213
|
(key, _create_payloadmember_parser(device, member))
|
|
213
214
|
for key, member in register.payloadSpec.items()
|
|
214
215
|
]
|
|
215
216
|
|
|
216
|
-
def
|
|
217
|
-
return DataFrame({n: f(df) for n, f in
|
|
217
|
+
def payload_parser(df: DataFrame):
|
|
218
|
+
return DataFrame({n: f(df) for n, f in member_parsers}, index=df.index)
|
|
218
219
|
|
|
219
|
-
reader = _compose_parser(
|
|
220
|
+
reader = _compose_parser(payload_parser, reader, params)
|
|
220
221
|
return RegisterReader(register, reader)
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
columns = (
|
|
224
|
+
[name]
|
|
225
|
+
if register.length is None or register.length == 1
|
|
226
|
+
else [f"{name}_{i}" for i in range(register.length)]
|
|
227
|
+
)
|
|
228
|
+
reader = partial(reader, columns=columns)
|
|
223
229
|
return RegisterReader(register, reader)
|
|
224
230
|
|
|
225
231
|
|
|
@@ -265,7 +271,7 @@ def create_reader(
|
|
|
265
271
|
base_path = path / device.device if is_dir else path.parent / device.device
|
|
266
272
|
|
|
267
273
|
reg_readers = {
|
|
268
|
-
name:
|
|
274
|
+
name: _create_register_handler(device, name, _ReaderParams(base_path, epoch, keep_type))
|
|
269
275
|
for name in device.registers.keys()
|
|
270
276
|
}
|
|
271
277
|
return DeviceReader(device, reg_readers)
|
|
@@ -8,6 +8,9 @@ from harp.model import Model, Registers
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _read_common_registers() -> Registers:
|
|
11
|
+
if __package__ is None:
|
|
12
|
+
raise ValueError("__package__ is None: unable to read common registers")
|
|
13
|
+
|
|
11
14
|
file = resources.files(__package__) / "common.yml"
|
|
12
15
|
with file.open("r") as fileIO:
|
|
13
16
|
return parse_yaml_raw_as(Registers, fileIO.read())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import mmap
|
|
2
|
+
import sys
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from typing import Any, BinaryIO, Union
|
|
5
|
+
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 12):
|
|
9
|
+
from collections.abc import Buffer as _BufferLike
|
|
10
|
+
else:
|
|
11
|
+
_BufferLike = Union[bytes, bytearray, memoryview, mmap.mmap, NDArray[Any]]
|
|
12
|
+
|
|
13
|
+
_FileLike = Union[str, PathLike[str], BinaryIO]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: harp-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A low-level interface for loading binary Harp protocol data
|
|
5
5
|
Author-email: Goncalo Lopes <g.lopes@neurogears.org>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Bug Tracker, https://github.com/harp-tech/harp-python/issues
|
|
8
8
|
Project-URL: Source Code, https://github.com/harp-tech/harp-python
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -12,7 +12,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Requires-Python: >=3.9.0
|
|
17
16
|
Description-Content-Type: text/markdown
|
|
18
17
|
License-File: LICENSE
|
|
@@ -29,6 +28,7 @@ Requires-Dist: codespell; extra == "dev"
|
|
|
29
28
|
Provides-Extra: jupyter
|
|
30
29
|
Requires-Dist: ipykernel; extra == "jupyter"
|
|
31
30
|
Requires-Dist: matplotlib; extra == "jupyter"
|
|
31
|
+
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
# harp-python
|
|
34
34
|
|
|
@@ -11,6 +11,7 @@ harp/io.py
|
|
|
11
11
|
harp/model.py
|
|
12
12
|
harp/reader.py
|
|
13
13
|
harp/schema.py
|
|
14
|
+
harp/typing.py
|
|
14
15
|
harp_python.egg-info/PKG-INFO
|
|
15
16
|
harp_python.egg-info/SOURCES.txt
|
|
16
17
|
harp_python.egg-info/dependency_links.txt
|
|
@@ -23,4 +24,6 @@ tests/test_reader.py
|
|
|
23
24
|
tests/test_schema.py
|
|
24
25
|
tests/data/device.yml
|
|
25
26
|
tests/data/device_0.bin
|
|
27
|
+
tests/data/device_44.bin
|
|
28
|
+
tests/data/empty_0.bin
|
|
26
29
|
tests/data/write_0.bin
|
|
@@ -7,7 +7,7 @@ description = "A low-level interface for loading binary Harp protocol data"
|
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.9.0"
|
|
9
9
|
dynamic = ["version"]
|
|
10
|
-
license =
|
|
10
|
+
license = "MIT"
|
|
11
11
|
|
|
12
12
|
dependencies = [
|
|
13
13
|
"pydantic-yaml",
|
|
@@ -20,8 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
21
|
"Programming Language :: Python :: 3.9",
|
|
22
22
|
"Programming Language :: Python :: 3.10",
|
|
23
|
-
"Operating System :: OS Independent"
|
|
24
|
-
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent"
|
|
25
24
|
]
|
|
26
25
|
|
|
27
26
|
[project.urls]
|
|
@@ -67,6 +66,9 @@ exclude = [
|
|
|
67
66
|
"reflex-generator"
|
|
68
67
|
]
|
|
69
68
|
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["I"]
|
|
71
|
+
|
|
70
72
|
[tool.pyright]
|
|
71
73
|
venvPath = "."
|
|
72
74
|
venv = ".venv"
|
|
@@ -18,6 +18,28 @@ registers:
|
|
|
18
18
|
type: U8
|
|
19
19
|
maskType: DigitalInputs
|
|
20
20
|
description: Reports the state of the digital input lines.
|
|
21
|
+
AnalogData:
|
|
22
|
+
address: 44
|
|
23
|
+
type: S16
|
|
24
|
+
length: 3
|
|
25
|
+
access: Event
|
|
26
|
+
description: Reports the current values of the analog input lines.
|
|
27
|
+
AnalogDataPayloadSpec:
|
|
28
|
+
address: 44
|
|
29
|
+
type: S16
|
|
30
|
+
length: 3
|
|
31
|
+
access: Event
|
|
32
|
+
description: Reports the current values of the analog input lines.
|
|
33
|
+
payloadSpec:
|
|
34
|
+
AnalogInput0:
|
|
35
|
+
offset: 0
|
|
36
|
+
description: The voltage at the output of the ADC channel 0.
|
|
37
|
+
Encoder:
|
|
38
|
+
offset: 1
|
|
39
|
+
description: The quadrature counter value on Port 2
|
|
40
|
+
AnalogInput1:
|
|
41
|
+
offset: 2
|
|
42
|
+
description: The voltage at the output of the ADC channel 1.
|
|
21
43
|
bitMasks:
|
|
22
44
|
DigitalInputs:
|
|
23
45
|
description: Specifies the state of the digital input lines.
|
|
Binary file
|
|
File without changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from os import PathLike
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Iterable, Optional, Type, Union
|
|
@@ -19,6 +20,8 @@ class DataFileParam:
|
|
|
19
20
|
expected_dtype: Optional[np.dtype] = None
|
|
20
21
|
expected_length: Optional[int] = None
|
|
21
22
|
expected_error: Optional[Type[BaseException]] = None
|
|
23
|
+
repeat_data: Optional[int] = None
|
|
24
|
+
epoch: Optional[datetime] = None
|
|
22
25
|
keep_type: bool = False
|
|
23
26
|
|
|
24
27
|
def __post_init__(self):
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from contextlib import nullcontext
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
4
5
|
import pytest
|
|
5
6
|
from pytest import mark
|
|
6
7
|
|
|
7
|
-
from harp.io import MessageType, read
|
|
8
|
+
from harp.io import REFERENCE_EPOCH, MessageType, read, to_buffer
|
|
8
9
|
from tests.params import DataFileParam
|
|
9
10
|
|
|
10
11
|
testdata = [
|
|
@@ -29,6 +30,10 @@ testdata = [
|
|
|
29
30
|
),
|
|
30
31
|
DataFileParam(path="data/write_0.bin", expected_address=0, expected_rows=4),
|
|
31
32
|
DataFileParam(path="data/write_0.bin", expected_address=0, expected_rows=4, keep_type=True),
|
|
33
|
+
DataFileParam(path="data/device_0.bin", expected_rows=300, repeat_data=300),
|
|
34
|
+
DataFileParam(path="data/device_0.bin", expected_rows=1, epoch=REFERENCE_EPOCH),
|
|
35
|
+
DataFileParam(path="data/empty_0.bin", expected_rows=0, epoch=REFERENCE_EPOCH),
|
|
36
|
+
DataFileParam(path="data/empty_0.bin", expected_rows=0),
|
|
32
37
|
]
|
|
33
38
|
|
|
34
39
|
|
|
@@ -36,17 +41,48 @@ testdata = [
|
|
|
36
41
|
def test_read(dataFile: DataFileParam):
|
|
37
42
|
context = pytest.raises if dataFile.expected_error else nullcontext
|
|
38
43
|
with context(dataFile.expected_error): # type: ignore
|
|
44
|
+
file_or_buf = dataFile.path
|
|
45
|
+
if dataFile.repeat_data:
|
|
46
|
+
with open(file_or_buf, "rb") as f:
|
|
47
|
+
file_or_buf = f.read() * dataFile.repeat_data
|
|
48
|
+
|
|
39
49
|
data = read(
|
|
40
|
-
|
|
50
|
+
file_or_buf,
|
|
41
51
|
address=dataFile.expected_address,
|
|
42
52
|
dtype=dataFile.expected_dtype,
|
|
43
53
|
length=dataFile.expected_length,
|
|
54
|
+
epoch=dataFile.epoch,
|
|
44
55
|
keep_type=dataFile.keep_type,
|
|
45
56
|
)
|
|
57
|
+
|
|
46
58
|
assert len(data) == dataFile.expected_rows
|
|
59
|
+
assert isinstance(data.index, pd.DatetimeIndex if dataFile.epoch else pd.Index)
|
|
47
60
|
if dataFile.keep_type:
|
|
48
61
|
assert MessageType.__name__ in data.columns and data[MessageType.__name__].dtype == "category"
|
|
49
62
|
|
|
50
63
|
if dataFile.expected_cols:
|
|
51
64
|
for col in dataFile.expected_cols:
|
|
52
65
|
assert col in data.columns
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
writedata = [
|
|
69
|
+
DataFileParam(path="data/device_0.bin", expected_rows=1, expected_address=0, keep_type=True),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@mark.parametrize("dataFile", writedata)
|
|
74
|
+
def test_write(dataFile: DataFileParam):
|
|
75
|
+
if dataFile.expected_address is None:
|
|
76
|
+
raise AssertionError("expected address must be defined for all write tests")
|
|
77
|
+
|
|
78
|
+
buffer = np.fromfile(dataFile.path, np.uint8)
|
|
79
|
+
data = read(
|
|
80
|
+
buffer,
|
|
81
|
+
address=dataFile.expected_address,
|
|
82
|
+
dtype=dataFile.expected_dtype,
|
|
83
|
+
length=dataFile.expected_length,
|
|
84
|
+
keep_type=dataFile.keep_type,
|
|
85
|
+
)
|
|
86
|
+
assert len(data) == dataFile.expected_rows
|
|
87
|
+
write_buffer = to_buffer(data, address=dataFile.expected_address, length=dataFile.expected_length)
|
|
88
|
+
assert np.array_equal(buffer, write_buffer)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pandas as pd
|
|
2
2
|
from pytest import mark
|
|
3
3
|
|
|
4
4
|
from harp.io import REFERENCE_EPOCH, MessageType
|
|
@@ -9,12 +9,17 @@ testdata = [
|
|
|
9
9
|
DeviceSchemaParam(
|
|
10
10
|
path="data",
|
|
11
11
|
expected_whoAmI=0,
|
|
12
|
-
expected_registers=["
|
|
12
|
+
expected_registers=["AnalogData"],
|
|
13
13
|
),
|
|
14
14
|
DeviceSchemaParam(
|
|
15
15
|
path="data/device.yml",
|
|
16
16
|
expected_whoAmI=0,
|
|
17
|
-
expected_registers=["
|
|
17
|
+
expected_registers=["AnalogData"],
|
|
18
|
+
),
|
|
19
|
+
DeviceSchemaParam(
|
|
20
|
+
path="data/device.yml",
|
|
21
|
+
expected_whoAmI=0,
|
|
22
|
+
expected_registers=["AnalogDataPayloadSpec"],
|
|
18
23
|
),
|
|
19
24
|
]
|
|
20
25
|
|
|
@@ -26,8 +31,13 @@ def test_create_reader(schemaFile: DeviceSchemaParam):
|
|
|
26
31
|
|
|
27
32
|
whoAmI = reader.WhoAmI.read()
|
|
28
33
|
assert reader.device.whoAmI == whoAmI.iloc[0, 0]
|
|
29
|
-
assert whoAmI.index
|
|
34
|
+
assert isinstance(whoAmI.index, pd.DatetimeIndex)
|
|
30
35
|
|
|
31
36
|
whoAmI = reader.WhoAmI.read(epoch=None, keep_type=True)
|
|
32
|
-
assert whoAmI.index
|
|
37
|
+
assert isinstance(whoAmI.index, pd.Index)
|
|
33
38
|
assert whoAmI.iloc[0, -1] == MessageType.READ.name
|
|
39
|
+
|
|
40
|
+
if schemaFile.expected_registers:
|
|
41
|
+
for register_name in schemaFile.expected_registers:
|
|
42
|
+
data = reader.registers[register_name].read()
|
|
43
|
+
assert isinstance(data.index, pd.DatetimeIndex)
|
harp_python-0.3.0/.gitignore
DELETED
harp_python-0.3.0/harp/io.py
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from enum import IntEnum
|
|
3
|
-
from os import PathLike
|
|
4
|
-
from typing import Any, BinaryIO, Optional, Union
|
|
5
|
-
|
|
6
|
-
import numpy as np
|
|
7
|
-
import pandas as pd
|
|
8
|
-
from pandas._typing import Axes
|
|
9
|
-
|
|
10
|
-
REFERENCE_EPOCH = datetime(1904, 1, 1)
|
|
11
|
-
"""The reference epoch for UTC harp time."""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class MessageType(IntEnum):
|
|
15
|
-
"""Specifies the type of a Harp message."""
|
|
16
|
-
|
|
17
|
-
NA = 0
|
|
18
|
-
READ = 1
|
|
19
|
-
WRITE = 2
|
|
20
|
-
EVENT = 3
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
_SECONDS_PER_TICK = 32e-6
|
|
24
|
-
_messagetypes = [type.name for type in MessageType]
|
|
25
|
-
_payloadtypes = {
|
|
26
|
-
1: np.dtype(np.uint8),
|
|
27
|
-
2: np.dtype(np.uint16),
|
|
28
|
-
4: np.dtype(np.uint32),
|
|
29
|
-
8: np.dtype(np.uint64),
|
|
30
|
-
129: np.dtype(np.int8),
|
|
31
|
-
130: np.dtype(np.int16),
|
|
32
|
-
132: np.dtype(np.int32),
|
|
33
|
-
136: np.dtype(np.int64),
|
|
34
|
-
68: np.dtype(np.float32),
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def read(
|
|
39
|
-
file: Union[str, bytes, PathLike[Any], BinaryIO],
|
|
40
|
-
address: Optional[int] = None,
|
|
41
|
-
dtype: Optional[np.dtype] = None,
|
|
42
|
-
length: Optional[int] = None,
|
|
43
|
-
columns: Optional[Axes] = None,
|
|
44
|
-
epoch: Optional[datetime] = None,
|
|
45
|
-
keep_type: bool = False,
|
|
46
|
-
):
|
|
47
|
-
"""Read single-register Harp data from the specified file.
|
|
48
|
-
|
|
49
|
-
Parameters
|
|
50
|
-
----------
|
|
51
|
-
file
|
|
52
|
-
Open file object or filename containing binary data from
|
|
53
|
-
a single device register.
|
|
54
|
-
address
|
|
55
|
-
Expected register address. If specified, the address of
|
|
56
|
-
the first message in the file is used for validation.
|
|
57
|
-
dtype
|
|
58
|
-
Expected data type of the register payload. If specified, the
|
|
59
|
-
payload type of the first message in the file is used for validation.
|
|
60
|
-
length
|
|
61
|
-
Expected number of elements in register payload. If specified, the
|
|
62
|
-
payload length of the first message in the file is used for validation.
|
|
63
|
-
columns
|
|
64
|
-
The optional column labels to use for the data values.
|
|
65
|
-
epoch
|
|
66
|
-
Reference datetime at which time zero begins. If specified,
|
|
67
|
-
the result data frame will have a datetime index.
|
|
68
|
-
keep_type
|
|
69
|
-
Specifies whether to include a column with the message type.
|
|
70
|
-
|
|
71
|
-
Returns
|
|
72
|
-
-------
|
|
73
|
-
A pandas data frame containing message data, sorted by time.
|
|
74
|
-
"""
|
|
75
|
-
data = np.fromfile(file, dtype=np.uint8)
|
|
76
|
-
if len(data) == 0:
|
|
77
|
-
return pd.DataFrame(columns=columns, index=pd.Index([], dtype=np.float64, name="Time"))
|
|
78
|
-
|
|
79
|
-
if address is not None and address != data[2]:
|
|
80
|
-
raise ValueError(f"expected address {address} but got {data[2]}")
|
|
81
|
-
|
|
82
|
-
index = None
|
|
83
|
-
stride = int(data[1] + 2)
|
|
84
|
-
nrows = len(data) // stride
|
|
85
|
-
payloadtype = data[4]
|
|
86
|
-
payloadoffset = 5
|
|
87
|
-
if payloadtype & 0x10 != 0:
|
|
88
|
-
seconds = np.ndarray(nrows, dtype=np.uint32, buffer=data, offset=payloadoffset, strides=stride)
|
|
89
|
-
payloadoffset += 4
|
|
90
|
-
micros = np.ndarray(nrows, dtype=np.uint16, buffer=data, offset=payloadoffset, strides=stride)
|
|
91
|
-
payloadoffset += 2
|
|
92
|
-
time = micros * _SECONDS_PER_TICK + seconds
|
|
93
|
-
payloadtype = payloadtype & ~np.uint8(0x10)
|
|
94
|
-
if epoch is not None:
|
|
95
|
-
time = epoch + pd.to_timedelta(time, "s") # type: ignore
|
|
96
|
-
index = pd.Series(time)
|
|
97
|
-
index.name = "Time"
|
|
98
|
-
|
|
99
|
-
payloadsize = stride - payloadoffset - 1
|
|
100
|
-
payloadtype = _payloadtypes[payloadtype]
|
|
101
|
-
if dtype is not None and dtype != payloadtype:
|
|
102
|
-
raise ValueError(f"expected payload type {dtype} but got {payloadtype}")
|
|
103
|
-
|
|
104
|
-
elementsize = payloadtype.itemsize
|
|
105
|
-
payloadshape = (nrows, payloadsize // elementsize)
|
|
106
|
-
if length is not None and length != payloadshape[1]:
|
|
107
|
-
raise ValueError(f"expected payload length {length} but got {payloadshape[1]}")
|
|
108
|
-
|
|
109
|
-
payload = np.ndarray(
|
|
110
|
-
payloadshape,
|
|
111
|
-
dtype=payloadtype,
|
|
112
|
-
buffer=data,
|
|
113
|
-
offset=payloadoffset,
|
|
114
|
-
strides=(stride, elementsize),
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
result = pd.DataFrame(payload, index=index, columns=columns)
|
|
118
|
-
if keep_type:
|
|
119
|
-
msgtype = np.ndarray(nrows, dtype=np.uint8, buffer=data, offset=0, strides=stride)
|
|
120
|
-
msgtype = pd.Categorical.from_codes(msgtype, categories=_messagetypes) # type: ignore
|
|
121
|
-
result[MessageType.__name__] = msgtype
|
|
122
|
-
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|