mat-io 0.1.0__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.
- mat_io-0.1.0/LICENSE +28 -0
- mat_io-0.1.0/PKG-INFO +14 -0
- mat_io-0.1.0/mat_io.egg-info/PKG-INFO +14 -0
- mat_io-0.1.0/mat_io.egg-info/SOURCES.txt +17 -0
- mat_io-0.1.0/mat_io.egg-info/dependency_links.txt +1 -0
- mat_io-0.1.0/mat_io.egg-info/requires.txt +4 -0
- mat_io-0.1.0/mat_io.egg-info/top_level.txt +6 -0
- mat_io-0.1.0/matio/__init__.py +3 -0
- mat_io-0.1.0/matio/class_parser.py +259 -0
- mat_io-0.1.0/matio/readmat.py +170 -0
- mat_io-0.1.0/matio/subsystem.py +457 -0
- mat_io-0.1.0/pyproject.toml +23 -0
- mat_io-0.1.0/setup.cfg +4 -0
- mat_io-0.1.0/tests/test_mcos/test_datetime/test_datetime.py +126 -0
- mat_io-0.1.0/tests/test_mcos/test_duration/test_duration.py +139 -0
- mat_io-0.1.0/tests/test_mcos/test_enum/test_enum.py +297 -0
- mat_io-0.1.0/tests/test_mcos/test_simple/test_simple.py +114 -0
- mat_io-0.1.0/tests/test_mcos/test_string/test_string.py +66 -0
- mat_io-0.1.0/tests/test_mcos/test_user_defined/test_user_defined.py +310 -0
mat_io-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, foreverallama
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
mat_io-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mat-io
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A package for reading MATLAB .mat files
|
|
5
|
+
Author-email: foreverallama <klockatoo@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/foreverallama/matio
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: scipy>=1.15.0
|
|
11
|
+
Requires-Dist: numpy>=2.2.2
|
|
12
|
+
Requires-Dist: pytz
|
|
13
|
+
Requires-Dist: pandas>=2.2.0
|
|
14
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mat-io
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A package for reading MATLAB .mat files
|
|
5
|
+
Author-email: foreverallama <klockatoo@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/foreverallama/matio
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: scipy>=1.15.0
|
|
11
|
+
Requires-Dist: numpy>=2.2.2
|
|
12
|
+
Requires-Dist: pytz
|
|
13
|
+
Requires-Dist: pandas>=2.2.0
|
|
14
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
pyproject.toml
|
|
3
|
+
mat_io.egg-info/PKG-INFO
|
|
4
|
+
mat_io.egg-info/SOURCES.txt
|
|
5
|
+
mat_io.egg-info/dependency_links.txt
|
|
6
|
+
mat_io.egg-info/requires.txt
|
|
7
|
+
mat_io.egg-info/top_level.txt
|
|
8
|
+
matio/__init__.py
|
|
9
|
+
matio/class_parser.py
|
|
10
|
+
matio/readmat.py
|
|
11
|
+
matio/subsystem.py
|
|
12
|
+
tests/test_mcos/test_datetime/test_datetime.py
|
|
13
|
+
tests/test_mcos/test_duration/test_duration.py
|
|
14
|
+
tests/test_mcos/test_enum/test_enum.py
|
|
15
|
+
tests/test_mcos/test_simple/test_simple.py
|
|
16
|
+
tests/test_mcos/test_string/test_string.py
|
|
17
|
+
tests/test_mcos/test_user_defined/test_user_defined.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from zoneinfo import ZoneInfo
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
# TODO: Add support for following classes:
|
|
9
|
+
# 1. dynamicprops
|
|
10
|
+
# 2. function_handle
|
|
11
|
+
# 3. event.proplistener
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_tz_offset(tz):
|
|
15
|
+
"""Get timezone offset in milliseconds
|
|
16
|
+
Inputs:
|
|
17
|
+
1. tz (str): Timezone string
|
|
18
|
+
Returns:
|
|
19
|
+
1. offset (int): Timezone offset in milliseconds
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
tzinfo = ZoneInfo(tz)
|
|
23
|
+
utc_offset = tzinfo.utcoffset(datetime.now())
|
|
24
|
+
if utc_offset is not None:
|
|
25
|
+
offset = int(utc_offset.total_seconds() * 1000)
|
|
26
|
+
else:
|
|
27
|
+
offset = 0
|
|
28
|
+
except Exception as e:
|
|
29
|
+
warnings.warn(
|
|
30
|
+
f"Could not get timezone offset for {tz}: {e}. Defaulting to UTC."
|
|
31
|
+
)
|
|
32
|
+
offset = 0
|
|
33
|
+
return offset
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def MatDatetime(props):
|
|
37
|
+
"""Convert MATLAB datetime to Python datetime
|
|
38
|
+
Datetime returned as numpy.datetime64[ms]
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
data = props[0, 0].get("data", np.array([]))
|
|
42
|
+
if data.size == 0:
|
|
43
|
+
props[0, 0]["data"] = np.array([], dtype="datetime64[ms]")
|
|
44
|
+
return props
|
|
45
|
+
tz = props[0, 0].get("tz", None)
|
|
46
|
+
if tz.size > 0:
|
|
47
|
+
offset = get_tz_offset(tz.item())
|
|
48
|
+
else:
|
|
49
|
+
offset = 0
|
|
50
|
+
|
|
51
|
+
millis = data.real + data.imag * 1e3 + offset
|
|
52
|
+
|
|
53
|
+
props[0, 0]["data"] = millis.astype("datetime64[ms]")
|
|
54
|
+
return props
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def MatDuration(props):
|
|
58
|
+
"""Convert MATLAB duration to Python timedelta
|
|
59
|
+
Duration returned as numpy.timedelta64
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
millis = props[0, 0]["millis"]
|
|
63
|
+
if millis.size == 0:
|
|
64
|
+
props[0, 0]["millis"] = np.array([], dtype="timedelta64[ms]")
|
|
65
|
+
return props
|
|
66
|
+
|
|
67
|
+
fmt = props[0, 0].get("fmt", None)
|
|
68
|
+
if fmt == "s":
|
|
69
|
+
count = millis / 1000 # Seconds
|
|
70
|
+
dur = count.astype("timedelta64[s]")
|
|
71
|
+
elif fmt == "m":
|
|
72
|
+
count = millis / 60000 # Minutes
|
|
73
|
+
dur = count.astype("timedelta64[m]")
|
|
74
|
+
elif fmt == "h":
|
|
75
|
+
count = millis / 3600000 # Hours
|
|
76
|
+
dur = count.astype("timedelta64[h]")
|
|
77
|
+
elif fmt == "d":
|
|
78
|
+
count = millis / 86400000 # Days
|
|
79
|
+
dur = count.astype("timedelta64[D]")
|
|
80
|
+
else:
|
|
81
|
+
count = millis
|
|
82
|
+
dur = count.astype("timedelta64[ms]")
|
|
83
|
+
# Default case
|
|
84
|
+
|
|
85
|
+
props[0, 0]["millis"] = dur
|
|
86
|
+
return props
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def MatString(props, byte_order):
|
|
90
|
+
"""Parse string data from MATLAB file
|
|
91
|
+
Strings are stored within a uint64 array with the following format:
|
|
92
|
+
1. version
|
|
93
|
+
2. ndims
|
|
94
|
+
3. shape
|
|
95
|
+
4. char_counts
|
|
96
|
+
5. List of null-terminated strings as uint16 integers
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
data = props[0, 0].get("any", np.array([]))
|
|
100
|
+
if data.size == 0:
|
|
101
|
+
data = np.array([[]], dtype="U")
|
|
102
|
+
props[0, 0]["any"] = data
|
|
103
|
+
return props
|
|
104
|
+
|
|
105
|
+
version = data[0, 0]
|
|
106
|
+
if version != 1:
|
|
107
|
+
warnings.warn(
|
|
108
|
+
"String saved from a different MAT-file version. This may work unexpectedly",
|
|
109
|
+
UserWarning,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
ndims = data[0, 1]
|
|
113
|
+
shape = data[0, 2 : 2 + ndims]
|
|
114
|
+
num_strings = np.prod(shape)
|
|
115
|
+
char_counts = data[0, 2 + ndims : 2 + ndims + num_strings]
|
|
116
|
+
offset = 2 + ndims + num_strings # start of string data
|
|
117
|
+
byte_data = data[0, offset:].tobytes()
|
|
118
|
+
|
|
119
|
+
strings = []
|
|
120
|
+
pos = 0
|
|
121
|
+
encoding = "utf-16-le" if byte_order[0] == "<" else "utf-16-be"
|
|
122
|
+
for char_count in char_counts:
|
|
123
|
+
byte_length = char_count * 2 # UTF-16 encoding
|
|
124
|
+
extracted_string = byte_data[pos : pos + byte_length].decode(encoding)
|
|
125
|
+
strings.append(extracted_string)
|
|
126
|
+
pos += byte_length
|
|
127
|
+
|
|
128
|
+
arr_str = np.reshape(strings, shape, order="F")
|
|
129
|
+
props[0, 0]["any"] = arr_str
|
|
130
|
+
return props
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MatTable:
|
|
134
|
+
# TODO: Collect cases and fix
|
|
135
|
+
def __init__(self, props, defaults):
|
|
136
|
+
self.data = defaults["data"]
|
|
137
|
+
|
|
138
|
+
for field in [
|
|
139
|
+
"data",
|
|
140
|
+
"ndims",
|
|
141
|
+
"nrows",
|
|
142
|
+
"nvars",
|
|
143
|
+
"rownames",
|
|
144
|
+
"varnames",
|
|
145
|
+
"props",
|
|
146
|
+
]:
|
|
147
|
+
if field in props.dtype.names:
|
|
148
|
+
setattr(self, field, props[field])
|
|
149
|
+
|
|
150
|
+
self.df = self._build_dataframe()
|
|
151
|
+
|
|
152
|
+
def __repr__(self):
|
|
153
|
+
return repr(self.df)
|
|
154
|
+
|
|
155
|
+
def __str__(self):
|
|
156
|
+
return str(self.df)
|
|
157
|
+
|
|
158
|
+
def _extract_cell_value(self, cell):
|
|
159
|
+
if isinstance(cell, np.ndarray) and cell.dtype == object:
|
|
160
|
+
return cell[0, 0]["__fields__"]
|
|
161
|
+
if isinstance(cell, dict):
|
|
162
|
+
return cell["__properties__"]
|
|
163
|
+
return cell
|
|
164
|
+
|
|
165
|
+
def _build_dataframe(self):
|
|
166
|
+
columns = {}
|
|
167
|
+
for i in range(int(self.nvars.item())):
|
|
168
|
+
varname = self._extract_cell_value(self.varnames[0, i]).item()
|
|
169
|
+
coldata = [
|
|
170
|
+
data.item() for data in self._extract_cell_value(self.data[0, i])
|
|
171
|
+
]
|
|
172
|
+
columns[varname] = coldata
|
|
173
|
+
|
|
174
|
+
df = pd.DataFrame(columns)
|
|
175
|
+
if self.rownames.size > 0:
|
|
176
|
+
rownames = [self._extract_cell_value(rn) for rn in self.rownames[0]]
|
|
177
|
+
if len(rownames) == self.nrows:
|
|
178
|
+
df.index = rownames
|
|
179
|
+
|
|
180
|
+
return df
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class MatTimetable:
|
|
184
|
+
# TODO: Collect cases and fix
|
|
185
|
+
def __init__(self, obj_dict):
|
|
186
|
+
self.any = obj_dict.get("any")[0, 0]
|
|
187
|
+
self.data = self.any["data"]
|
|
188
|
+
self.numDims = self.any["numDims"]
|
|
189
|
+
self.dimNames = self.any["dimNames"]
|
|
190
|
+
self.varNames = self.any["varNames"]
|
|
191
|
+
self.numRows = self.any["numRows"]
|
|
192
|
+
self.numVars = self.any["numVars"]
|
|
193
|
+
self.rowTimes = self.any["rowTimes"]
|
|
194
|
+
self.df = self._build_dataframe()
|
|
195
|
+
|
|
196
|
+
def __str__(self):
|
|
197
|
+
return str(self.df)
|
|
198
|
+
|
|
199
|
+
def __repr__(self):
|
|
200
|
+
return repr(self.df)
|
|
201
|
+
|
|
202
|
+
def _extract_cell_value(self, cell):
|
|
203
|
+
if isinstance(cell, np.ndarray) and cell.dtype == object:
|
|
204
|
+
return cell[0, 0]["__fields__"]
|
|
205
|
+
return cell
|
|
206
|
+
|
|
207
|
+
def _build_dataframe(self):
|
|
208
|
+
columns = {}
|
|
209
|
+
for i in range(int(self.numVars.item())):
|
|
210
|
+
varname = self._extract_cell_value(self.varNames[0, i]).item()
|
|
211
|
+
coldata = [
|
|
212
|
+
data.item() for data in self._extract_cell_value(self.data[0, i])
|
|
213
|
+
]
|
|
214
|
+
columns[varname] = coldata
|
|
215
|
+
|
|
216
|
+
df = pd.DataFrame(columns)
|
|
217
|
+
time_arr = self.rowTimes[0, 0]["__fields__"]
|
|
218
|
+
times = [time_arr[i].item() for i in range(int(self.numRows.item()))]
|
|
219
|
+
df.index = pd.to_datetime(times)
|
|
220
|
+
df.index.name = self._extract_cell_value(self.dimNames[0, 0]).item()
|
|
221
|
+
|
|
222
|
+
return df
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def convert_to_object(props, class_name, byte_order):
|
|
226
|
+
"""Converts the object to a Python compatible object"""
|
|
227
|
+
|
|
228
|
+
if class_name == "datetime":
|
|
229
|
+
obj = MatDatetime(props)
|
|
230
|
+
|
|
231
|
+
elif class_name == "duration":
|
|
232
|
+
obj = MatDuration(props)
|
|
233
|
+
|
|
234
|
+
elif class_name == "string":
|
|
235
|
+
obj = MatString(props, byte_order)
|
|
236
|
+
|
|
237
|
+
# elif class_name == "table":
|
|
238
|
+
# obj = MatTable(props[0, 0], defaults)
|
|
239
|
+
|
|
240
|
+
# elif class_name == "timetable":
|
|
241
|
+
# if "any" in props.dtype.names:
|
|
242
|
+
# obj = MatTimetable(props[0, 0], defaults)
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
# For all other classes, return raw data
|
|
246
|
+
obj = props
|
|
247
|
+
|
|
248
|
+
return obj
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def wrap_enumeration_instance(enum_array, shapes):
|
|
252
|
+
"""Wraps enumeration instance data into a dictionary"""
|
|
253
|
+
wrapped_dict = {"_Values": np.empty(shapes, dtype=object)}
|
|
254
|
+
if len(enum_array) == 0:
|
|
255
|
+
wrapped_dict["_Values"] = np.array([], dtype=object)
|
|
256
|
+
else:
|
|
257
|
+
enum_props = [item.get("_Props", np.array([]))[0, 0] for item in enum_array]
|
|
258
|
+
wrapped_dict["_Values"] = np.array(enum_props).reshape(shapes, order="F")
|
|
259
|
+
return wrapped_dict
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.io import loadmat
|
|
5
|
+
from scipy.io.matlab._mio5 import MatFile5Reader
|
|
6
|
+
from scipy.io.matlab._mio5_params import OPAQUE_DTYPE
|
|
7
|
+
|
|
8
|
+
from .subsystem import SubsystemReader
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_matfile_version(ssStream):
|
|
12
|
+
"""Reads subsystem MAT-file version and endianness
|
|
13
|
+
Inputs
|
|
14
|
+
1. ssStream (BytesIO): Subsystem data stream
|
|
15
|
+
Returns:
|
|
16
|
+
1. v_major (int): Major version
|
|
17
|
+
2. v_minor (int): Minor version
|
|
18
|
+
3. byte_order (str): Endianness
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
ssStream.seek(0)
|
|
22
|
+
data = ssStream.read(4)
|
|
23
|
+
maj_ind = int(data[2] == b"I"[0])
|
|
24
|
+
v_major = int(data[maj_ind])
|
|
25
|
+
v_minor = int(data[1 - maj_ind])
|
|
26
|
+
byte_order = "<" if data[2] == b"I"[0] else ">"
|
|
27
|
+
if v_major in (1, 2):
|
|
28
|
+
return v_major, v_minor, byte_order
|
|
29
|
+
|
|
30
|
+
# Unsure if Subsystem and MAT-file versions can be different
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"Unknown subsystem data type, version {}, {}".format(v_major, v_minor)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_subsystem(ssdata, **kwargs):
|
|
37
|
+
"""Reads subsystem data as a MAT-file stream
|
|
38
|
+
Inputs
|
|
39
|
+
1. ssdata (numpy.ndarray): Subsystem data from "__function_workspace__"
|
|
40
|
+
2. kwargs: Additional arguments for scipy.io.loadmat
|
|
41
|
+
Returns:
|
|
42
|
+
1. subsystem data (numpy.ndarray): Parsed subsystem data
|
|
43
|
+
2. byte_order (str)
|
|
44
|
+
"""
|
|
45
|
+
ssStream = BytesIO(ssdata)
|
|
46
|
+
|
|
47
|
+
mjv, mnv, byte_order = get_matfile_version(ssStream)
|
|
48
|
+
if mjv != 1:
|
|
49
|
+
raise NotImplementedError(f"Subsystem version {mjv}.{mnv} not supported")
|
|
50
|
+
|
|
51
|
+
kwargs.pop("byte_order", None)
|
|
52
|
+
kwargs.pop("variable_names", None)
|
|
53
|
+
|
|
54
|
+
ssStream.seek(8) # Skip subsystem header
|
|
55
|
+
MR = MatFile5Reader(ssStream, byte_order=byte_order, **kwargs)
|
|
56
|
+
MR.initialize_read()
|
|
57
|
+
hdr, _ = MR.read_var_header()
|
|
58
|
+
try:
|
|
59
|
+
res = MR.read_var_array(hdr, process=False)
|
|
60
|
+
except Exception as err:
|
|
61
|
+
raise ValueError(f"Error reading subsystem data: {err}")
|
|
62
|
+
|
|
63
|
+
return res, byte_order
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def remove_unsupported_args(kwargs):
|
|
67
|
+
"""Removes unsupported arguments for scipy.io.loadmat"""
|
|
68
|
+
kwargs.pop("simplify_cells", None)
|
|
69
|
+
kwargs.pop("squeeze_me", None)
|
|
70
|
+
kwargs.pop("struct_as_record", None)
|
|
71
|
+
kwargs.pop("uint16_codec", None)
|
|
72
|
+
kwargs.pop("chars_as_strings", False)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_function_workspace(file_path, mdict=None, spmatrix=True, **kwargs):
|
|
76
|
+
"""Reads function workspace from MAT-file
|
|
77
|
+
Inputs
|
|
78
|
+
1. file_path (str): Path to MAT-file
|
|
79
|
+
2. mdict (dict): Dictionary to store loaded variables
|
|
80
|
+
3. spmatrix (bool): Whether to load sparse matrices
|
|
81
|
+
4. kwargs: Additional arguments for scipy.io.loadmat
|
|
82
|
+
Returns:
|
|
83
|
+
1. matfile_dict (dict): Dictionary of loaded variables
|
|
84
|
+
2. ssdata: Subsystem data from "__function_workspace__"
|
|
85
|
+
"""
|
|
86
|
+
matfile_dict = loadmat(file_path, mdict=mdict, spmatrix=spmatrix, **kwargs)
|
|
87
|
+
ssdata = matfile_dict.pop("__function_workspace__", None)
|
|
88
|
+
return matfile_dict, ssdata
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def find_opaque_dtype(arr, subsystem, path=()):
|
|
92
|
+
"""Recursively finds and replaces mxOPAQUE_CLASS objects in a numpy array
|
|
93
|
+
with the corresponding MCOS object.
|
|
94
|
+
|
|
95
|
+
This is a hacky solution to find mxOPAQUE_CLASS arrays inside struct arrays or cell arrays.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
if not isinstance(arr, np.ndarray):
|
|
99
|
+
return arr
|
|
100
|
+
|
|
101
|
+
if arr.dtype == OPAQUE_DTYPE:
|
|
102
|
+
type_system = arr[0]["_TypeSystem"]
|
|
103
|
+
metadata = arr[0]["_Metadata"]
|
|
104
|
+
return subsystem.read_mcos_object(metadata, type_system)
|
|
105
|
+
|
|
106
|
+
elif arr.dtype == object:
|
|
107
|
+
# Iterate through cell arrays
|
|
108
|
+
for idx in np.ndindex(arr.shape):
|
|
109
|
+
cell_item = arr[idx]
|
|
110
|
+
if cell_item.dtype == OPAQUE_DTYPE:
|
|
111
|
+
type_system = cell_item[0]["_TypeSystem"]
|
|
112
|
+
metadata = cell_item[0]["_Metadata"]
|
|
113
|
+
arr[idx] = subsystem.read_mcos_object(metadata, type_system)
|
|
114
|
+
else:
|
|
115
|
+
find_opaque_dtype(cell_item, subsystem, path + (idx,))
|
|
116
|
+
|
|
117
|
+
elif arr.dtype.names:
|
|
118
|
+
# Iterate though struct array
|
|
119
|
+
for idx in np.ndindex(arr.shape):
|
|
120
|
+
for name in arr.dtype.names:
|
|
121
|
+
field_val = arr[idx][name]
|
|
122
|
+
if field_val.dtype == OPAQUE_DTYPE:
|
|
123
|
+
type_system = field_val[0]["_TypeSystem"]
|
|
124
|
+
metadata = field_val[0]["_Metadata"]
|
|
125
|
+
arr[idx][name] = subsystem.read_mcos_object(metadata, type_system)
|
|
126
|
+
else:
|
|
127
|
+
find_opaque_dtype(field_val, subsystem, path + (idx, name))
|
|
128
|
+
|
|
129
|
+
return arr
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def load_from_mat(file_path, mdict=None, raw_data=False, *, spmatrix=True, **kwargs):
|
|
133
|
+
"""Loads variables from MAT-file
|
|
134
|
+
Calls scipy.io.loadmat to read the MAT-file and then processes the
|
|
135
|
+
"__function_workspace__" variable to extract subsystem data.
|
|
136
|
+
Inputs
|
|
137
|
+
1. file_path (str): Path to MAT-file
|
|
138
|
+
2. mdict (dict): Dictionary to store loaded variables
|
|
139
|
+
3. raw_data (bool): Whether to return raw data for objects
|
|
140
|
+
4. spmatrix (bool): Additional arguments for scipy.io.loadmat
|
|
141
|
+
5. kwargs: Additional arguments for scipy.io.loadmat
|
|
142
|
+
Returns:
|
|
143
|
+
1. mdict (dict): Dictionary of loaded variables
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
remove_unsupported_args(kwargs)
|
|
147
|
+
|
|
148
|
+
matfile_dict, ssdata = get_function_workspace(file_path, mdict, spmatrix, **kwargs)
|
|
149
|
+
if ssdata is None:
|
|
150
|
+
# No subsystem data in file
|
|
151
|
+
if mdict is not None:
|
|
152
|
+
mdict.update(matfile_dict)
|
|
153
|
+
return mdict
|
|
154
|
+
return matfile_dict
|
|
155
|
+
|
|
156
|
+
ss_array, byte_order = read_subsystem(ssdata, **kwargs)
|
|
157
|
+
subsystem = SubsystemReader(ss_array, byte_order, raw_data)
|
|
158
|
+
|
|
159
|
+
for var, data in matfile_dict.items():
|
|
160
|
+
if not isinstance(data, np.ndarray):
|
|
161
|
+
continue
|
|
162
|
+
matfile_dict[var] = find_opaque_dtype(data, subsystem)
|
|
163
|
+
|
|
164
|
+
# Update mdict if present
|
|
165
|
+
if mdict is not None:
|
|
166
|
+
mdict.update(matfile_dict)
|
|
167
|
+
else:
|
|
168
|
+
mdict = matfile_dict
|
|
169
|
+
|
|
170
|
+
return mdict
|