|
| 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