diff --git a/qutil/matlab.py b/qutil/matlab.py index cab5dc432d3f913b763f7e94e02c4bc9c0fbb9c9..b60530bcfe7cfacabb674cb66c0d82b6a53d11d2 100644 --- a/qutil/matlab.py +++ b/qutil/matlab.py @@ -2,6 +2,7 @@ import itertools import warnings import os +import tempfile from typing import Tuple, Sequence import numpy @@ -12,16 +13,78 @@ import qutil.caching try: import matlab.engine + MatlabEngine = matlab.engine.matlabengine.MatlabEngine except ImportError: warnings.warn("Matlab engine interface not installed. " "Some functionality requires using MATLAB directly.\n" - "Navigate to 'C:\Program Files\MATLAB\R2020b\extern\engines\python' and call 'python setup.py install'") + r"Navigate to 'C:\Program Files\MATLAB\R2020b\extern\engines\python' " + "and call 'python setup.py install'") matlab = None + MatlabEngine = None __all_ = ['load_special_measure_scan', 'cached_load_mat_file', 'special_measure_to_dataframe', 'load_special_measure_with_matlab_engine'] +# matlab class to equivalent numpy data type +CLASS_TO_DTYPE = {'double': numpy.float64, + 'single': numpy.float32, + 'uint8': numpy.uint8, + 'int8': numpy.int8, + 'uint16': numpy.uint16, + 'int16': numpy.int16, + 'uint32': numpy.uint32, + 'int32': numpy.int32, + 'uint64': numpy.uint64, + 'int64': numpy.int64} + + +def _read_array_via_tempfile(engine: MatlabEngine, path: str) -> numpy.ndarray: + """This function writes an array to a file in binary format and reads it again in python. This is only faster than + directly converting the matlab array to a numpy array for very large arrays (and probably depends on your disk speed + ). + + This is currently unused code in the module and only here so it doesnt get lost. + """ + raise NotImplementedError("TODO: check order of dimensions") + + elem_class = engine.eval(f'class({path})') + dtype = CLASS_TO_DTYPE[elem_class] + shape = tuple(map(int, engine.eval(f'num2cell(size({path}))'))) + + py_file_id, file_name = tempfile.mkstemp(suffix='.bin') + os.close(py_file_id) + + try: + mat_file_id = engine.fopen(file_name, 'w') + if mat_file_id < 0: + raise FileNotFoundError("MATLAB could not open tempfile") + try: + engine.eval(f'fwrite({mat_file_id}, {path}(:), class({path}))') + finally: + engine.fclose(mat_file_id) + + arr = numpy.fromfile(file_name, dtype=dtype) + + finally: + os.remove(file_name) + + return arr.reshape(shape) + + +def _get_numeric_array(engine, path) -> numpy.array: + """Conversion from matlab.double to numpy.array is very slow as matlab.double does not implement the buffer + protocol. This function chooses the method automatically from the array size. + + This is currently unused code in the module and only here so it doesnt get lost. + """ + MAX_DIRECT_READ = 10 ** 7 + if engine.eval(f'numel({path})') < MAX_DIRECT_READ: + return numpy.array(engine.eval(path)) + else: + return _read_array_via_tempfile(engine, path) + + class ModuleEngineWrapper: """The purpose of this class is to be a default argument for engine requiring functions different from None. This is the least stupid default interface I came up with (Simon)."""