Skip to content

Commit ed7cfad

Browse files
committed
handle more types
1 parent a0e3601 commit ed7cfad

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed

py2mat.m

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
%% PY2MAT Convert a native Python variable to an equivalent native MATLAB variable.
2+
%
3+
% futher enhanced in 2024 by SciVision
4+
% {{{ code/matlab_py/py2mat.m v 1.3 2023-09-15
5+
% This code accompanies the book _Python for MATLAB Development:
6+
% Extend MATLAB with 300,000+ Modules from the Python Package Index_
7+
% ISBN 978-1-4842-7222-0 | ISBN 978-1-4842-7223-7 (eBook)
8+
% DOI 10.1007/978-1-4842-7223-7
9+
% https://github.com/Apress/python-for-matlab-development
10+
%
11+
% Copyright © 2022-2023 Albert Danial
12+
%
13+
% Contributions by:
14+
% - https://github.com/hcommin (performance enhancements)
15+
%
16+
% MIT License:
17+
% Permission is hereby granted, free of charge, to any person obtaining a copy
18+
% of this software and associated documentation files (the "Software"), to deal
19+
% in the Software without restriction, including without limitation the rights
20+
% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21+
% copies of the Software, and to permit persons to whom the Software is
22+
% furnished to do so, subject to the following conditions:
23+
%
24+
% The above copyright notice and this permission notice shall be included in
25+
% all copies or substantial portions of the Software.
26+
%
27+
% THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28+
% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29+
% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
30+
% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31+
% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32+
% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
33+
% DEALINGS IN THE SOFTWARE.
34+
% }}}
35+
function [x_mat] = py2mat(x_py)
36+
switch class(x_py)
37+
% MATLAB types
38+
case {'logical', 'double', 'datetime'}
39+
x_mat = x_py;
40+
41+
% Python dictionaries
42+
case 'py.dict'
43+
try
44+
% the obvious dict -> struct conversion only works
45+
% if the key string can be a MATLAB variable
46+
x_mat = struct(x_py);
47+
catch EO
48+
% A failure most likely means a Python key is not
49+
% a proper MATLAB variable name. Go through them
50+
% individually
51+
key_index = int64(0);
52+
key_list = cell(py.list(x_py.keys()));
53+
for key = key_list
54+
key_index = key_index + 1;
55+
if class(key) == "cell"
56+
% key could be a Python number
57+
v_name = string(py.str(key{1}));
58+
else
59+
v_name = string(key);
60+
end
61+
if isvarname(v_name)
62+
x_mat.(v_name) = x_py.get(key_list{key_index});
63+
else
64+
x_mat.(matlab.lang.makeValidName(v_name)) = x_py.get(key_list{key_index});
65+
end
66+
end
67+
end
68+
fields = fieldnames(x_mat);
69+
for i = 1:length(fields)
70+
new_var = py2mat(x_mat.(fields{i}));
71+
x_mat = setfield(x_mat,fields{i}, new_var); %#ok<SFLD>
72+
end
73+
74+
% NumPy arrays and typed scalars
75+
case "py.numpy.ndarray"
76+
switch string(x_py.dtype.name)
77+
case "float64"
78+
x_mat = x_py.double;
79+
case "float32"
80+
x_mat = x_py.single;
81+
case "float16"
82+
% doesn't exist in matlab, upcast to float32
83+
x_mat = single(x_py.astype('float32'));
84+
% only reals and logicals can be cast to arrays so
85+
% have to cast the rest to either single or double
86+
case "uint8"
87+
x_mat = uint8(x_py.astype('float32'));
88+
case "int8"
89+
x_mat = int8(x_py.astype('float32'));
90+
case "uint16"
91+
x_mat = uint16(x_py.astype('float32'));
92+
case "int16"
93+
x_mat = int16(x_py.astype('float32'));
94+
case "uint32"
95+
x_mat = uint32(x_py.astype('float32'));
96+
case "int32"
97+
x_mat = int32(x_py.astype('float32'));
98+
case "uint64"
99+
x_mat = uint64(x_py.astype('float64'));
100+
case "int64"
101+
x_mat = int64(x_py.astype('float64'));
102+
case "bool"
103+
x_mat = logical(x_py);
104+
% Complex types require a math operation to coerce the
105+
% real and imaginary components to contiguous arrays.
106+
% Use "+0" for minimal performance impact. Without this,
107+
% complex creation fails with
108+
% Python Error: ValueError: ndarray is not contiguous
109+
case "complex64"
110+
x_mat = complex(single(x_py.real+0), single(x_py.imag+0));
111+
case "complex128"
112+
x_mat = complex(double(x_py.real+0), double(x_py.imag+0));
113+
case "complex256"
114+
fprintf('py2mat: MATLAB does not support quad precision complex\n')
115+
x_mat = [];
116+
return
117+
otherwise
118+
% gets here with custom dtypes
119+
fprintf('py2mat: %s not recognized\n', ...
120+
string(x_py.dtype.name));
121+
x_mat = [];
122+
return
123+
end
124+
125+
% Scipy sparse matrices
126+
case {'py.scipy.sparse.coo.coo_matrix', ...
127+
'py.scipy.sparse.csr.csr_matrix', ...
128+
'py.scipy.sparse.csc.csc_matrix', ...
129+
'py.scipy.sparse.dok.dok_matrix', ...
130+
'py.scipy.sparse.bsr.bsr_matrix', ...
131+
'py.scipy.sparse.dia.dia_matrix', ...
132+
'py.scipy.sparse.lil.lil_matrix', }
133+
ndims = x_py.get_shape();
134+
if length(ndims) ~= 2
135+
fprintf('py2mat: can only convert 2D sparse matrices\n')
136+
x_mat = [];
137+
return
138+
end
139+
nR = int64(ndims{1});
140+
nC = int64(ndims{2});
141+
x_py = x_py.tocoo();
142+
values = py2mat(x_py.data);
143+
if isempty(values)
144+
% gets here if trying to convert a complex256 sparse matrix
145+
return
146+
end
147+
% add 1 to row & col indices to go from 0-based to 1-based
148+
x_mat = sparse(single(x_py.row)+1, single(x_py.col)+1, values, nR, nC);
149+
150+
% Python sets, tuples, and lists
151+
case {'cell', 'py.tuple', 'py.list'}
152+
[nR, nC] = size(x_py);
153+
x_mat = cell(nR,nC);
154+
for r = 1:nR
155+
for c = 1:nC
156+
x_mat{r,c} = py2mat(x_py{r,c});
157+
end
158+
end
159+
160+
% Python strings
161+
case 'py.str'
162+
x_mat = string(x_py);
163+
164+
% Python integers
165+
case 'py.int'
166+
x_mat = x_py.int64;
167+
168+
case 'py.float'
169+
x_mat = x_py.real;
170+
171+
case 'py.complex'
172+
x_mat = complex(x_py.real, x_py.imag);
173+
174+
case 'py.bool'
175+
x_mat = logical(x_py);
176+
177+
case 'py.datetime.datetime'
178+
x_mat = datetime(int64(x_py.year), ...
179+
int64(x_py.month), ...
180+
int64(x_py.day), ...
181+
int64(x_py.hour), ...
182+
int64(x_py.minute),...
183+
int64(x_py.second),...
184+
double(x_py.microsecond)/1000.0);
185+
186+
case 'py.bytes'
187+
x_mat = uint8(x_py);
188+
189+
case 'py.NoneType'
190+
x_mat = '';
191+
192+
case 'py.scipy.optimize.optimize.OptimizeResult'
193+
for k = cell(py.list(x_py.keys))
194+
x_mat.(string(k{1})) = py2mat(x_py.get(k{1}));
195+
end
196+
197+
% punt
198+
otherwise
199+
% return the original item? nothing?
200+
fprintf('py2mat: type "%s" not recognized\n', ...
201+
string(class(x_py)));
202+
x_mat = [];
203+
end
204+
end

0 commit comments

Comments
 (0)