Skip to content

Commit

Permalink
Adds unit tests and modifies customFileClass to use wrapper instead
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenNneji committed Sep 13, 2023
1 parent 9b24061 commit 501c6fa
Show file tree
Hide file tree
Showing 32 changed files with 255 additions and 292 deletions.
13 changes: 2 additions & 11 deletions API/parseClassToStructs.m
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,6 @@
layersDetails = {0};
end

% When there are custom files, we need to strip the file extension
% from the filename if it's present
for i = 1:length(customFiles)
thisCustomFileCell = customFiles{i};
[~,name,~] = fileparts(thisCustomFileCell{1});
thisCustomFileCell{1} = name;
customFiles{i} = thisCustomFileCell;
end

% Pull out all the cell arrays (except priors) into one array
problemDefCells{1} = inputStruct.contrastRepeatSLDs;
problemDefCells{2} = inputStruct.allData;
Expand All @@ -131,7 +122,7 @@
problemDefCells{11} = inputStruct.nbairNames;
problemDefCells{12} = inputStruct.nbsubNames;
problemDefCells{13} = inputStruct.resolParNames;
problemDefCells{14} = customFiles';
problemDefCells{14} = customFiles;
problemDefCells{15} = cellstr(inputStruct.backgroundTypes');
problemDefCells{16} = cellstr(inputStruct.resolutionTypes');
problemDefCells{17} = inputStruct.allOilChiData;
Expand Down Expand Up @@ -181,7 +172,7 @@

% Also the custom files array..
if isempty(problemDefCells{14})
problemDefCells{14} = {{'','',''}};
problemDefCells{14} = {''};
end


Expand Down
61 changes: 42 additions & 19 deletions API/projectClass/customFileClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

% A container class for holding custom files for either
% models, backgrounds or resolutions.
properties (SetAccess = private, Hidden = true)
wrappers = {}
end

properties(Access = private, Constant, Hidden)
invalidLanguageMessage = sprintf('Language must be a supportedLanguages enum or one of the following strings (%s)', ...
strjoin(supportedLanguages.values(), ', '))
strjoin(supportedLanguages.values(), ', '))
end

methods
Expand All @@ -27,25 +30,34 @@
end
end

function delete(obj)
% Destroys the wrappers
for i=1:length(obj.wrappers)
delete(obj.wrappers{i});
end
obj.wrappers = {};
end

function obj = addCustomFile(obj, varargin)
% Add an entry to the file table.
% A custom file entry can be added with no parameters, just the
% name of the custom file entry, the name of the entry
% alongside a filename, or can be fully defined by specifying
% the name of the custom file entry, filename, language, and
% file path
% file path. For MATLAB, the provided path must be in the
% matlab path
%
% customFiles.addCustomFile()
% customFiles.addCustomFile('New Row')
% customFiles.addCustomFile('New Row', 'file.m')
% customFiles.addCustomFile('New Row', 'file.m', 'matlab', 'pwd')
% customFiles.addCustomFile('New Row', 'file.py', 'python', 'C:/stuff')
if isempty(varargin)

% Nothing supplied - add empty data row
nameVal = obj.autoNameCounter();
newName = sprintf('New custom file %d', nameVal);

newRow = {newName, '', supportedLanguages.Matlab.value, 'pwd'};
newRow = {newName, '', supportedLanguages.Matlab.value, ''};

else

Expand All @@ -62,7 +74,7 @@
throw(invalidType('Single input is expected to be a custom object name'));
end

newRow = {newName, '', supportedLanguages.Matlab.value, 'pwd'};
newRow = {newName, '', supportedLanguages.Matlab.value, ''};

case 2

Expand All @@ -71,7 +83,7 @@
newName = char(inputs{1});
newFile = char(inputs{2});

newRow = {newName, newFile, supportedLanguages.Matlab.value, 'pwd'};
newRow = {newName, newFile, supportedLanguages.Matlab.value, ''};

case 4

Expand All @@ -93,8 +105,7 @@

% Check language is valid, then add the new entry
newRow{3} = validateOption(newRow{3}, 'supportedLanguages', obj.invalidLanguageMessage).value;
obj.addRow(newRow{:});

obj.addRow(newRow{:});
end

function obj = setCustomFile(obj, row, varargin)
Expand Down Expand Up @@ -198,7 +209,7 @@ function displayTable(obj)

thisFilePath = thisRow{1,4}{:};
if isempty(thisFilePath)
thisFilePath = 'pwd';
thisFilePath = "pwd";
else
thisFilePath = char(thisFilePath);
if length(thisFilePath) > 10
Expand All @@ -218,31 +229,43 @@ function displayTable(obj)
% Convert the custom files class to a struct
%
% customFiles.toStruct()
numberOfFiles = obj.rowCount;

fileStruct.files = {};
numberOfFiles = obj.rowCount;
if numberOfFiles > 0
filesList = cell(numberOfFiles, 1);
for i = 1:numberOfFiles
thisRow = obj.varTable{i,:};
thisFile = thisRow{2};
thisType = thisRow{3};
thisPath = thisRow{4};

if strcmpi(thisPath,'pwd')
thisPath = pwd;
thisPath = thisRow{4};
[~, functionName, ~] = fileparts(thisFile);
libpath = fullfile(thisPath, thisFile);
wrapper = [];
if i <= length(obj.wrappers)
wrapper = obj.wrappers{i};
end
filesList{i} = {thisFile, thisType, thisPath};
if isempty(wrapper) || ~strcmp(wrapper.libPath, libpath) || ~strcmp(wrapper.functionName, functionName)
if strcmpi(thisType, supportedLanguages.Python.value)
wrapper = pythonWrapper(libpath, functionName);
thisFile = wrapper.getHandle();
elseif strcmpi(thisType, supportedLanguages.Cpp.value)
wrapper = dyLibWrapper(libpath, functionName);
thisFile = wrapper.getHandle();
end
obj.wrappers{i} = wrapper;
else
thisFile = wrapper.getHandle();
end
[~, handle, ~] = fileparts(thisFile);
filesList{i} = handle;
end
fileStruct.files = filesList;
else
fileStruct.files = {};
end
end

end

methods(Access = protected)

function obj = setCustomLanguage(obj, row, language)
% Check whether a specified language is supported, and set the
% file entry if so.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
classdef dyLibWrapper < handle
% A custom function wrapper for c++ dynamic library. The library should be a .dll
% (Windows), .so (Linux), or .dylib (MacOS).

properties (SetAccess = private)
functionName
libPath
end

properties (Access = private, Hidden = true)
objectHandle; % Handle to the underlying C++ class instance
mexHandle; % Handle to the mex function
libPath;
functionName;
objectHandle % Handle to the underlying C++ class instance
mexHandle % Handle to the mex function
end

methods
Expand All @@ -23,7 +25,6 @@
obj.functionName = functionName;
end


function delete(obj)
% Destroys the callback class instance in wrapperMex
if ~isempty(obj.objectHandle)
Expand All @@ -41,6 +42,5 @@ function disp(obj)
% Displays libarary path and function name
fprintf('Dynamic library wrapper for %s function in %s\n', obj.functionName, obj.libPath);
end

end
end
end
5 changes: 5 additions & 0 deletions API/projectClass/projectClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@
obj.contrasts = contrastsClass();
end

function delete(obj)
% Destroys the wrappers
delete(obj.customFile);
end

function domainsObj = toDomainsClass(obj)
% Alias of the converter routine from projectClass to
% domainsClass.
Expand Down
59 changes: 59 additions & 0 deletions API/projectClass/pythonWrapper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
classdef pythonWrapper < handle
% A custom function wrapper for a python file.

properties (SetAccess = private)
functionName
libPath
end

properties (SetAccess = private, Hidden = true)
tempFolder
tempName
filename
end

methods
function obj = pythonWrapper(libPath, functionName)
% Creates a pythonWrapper object. It creates a temporary function which runs
% the python code using the python executable connected to MATLAB pyenv. The
% arguments should be full or relative path of the python file with extension and
% the name of the function to call in the python file
%
% wrapper = pythonWrapper('customBilayer.py', 'customBilayer');
% wrapper = pythonWrapper('D:/MATLAB/customBilayer.py', 'customDomains');
obj.tempFolder = tempname();
obj.libPath = libPath;
obj.functionName = functionName;

fnstr = ['function [output,sub_rough] = %s(varargin)\n' ...
' [output, sub_rough] = pyRunner("%s", "%s", varargin);\n' ...
'end\n'];

mkdir(obj.tempFolder);
filename = sprintf('%s.m', tempname(obj.tempFolder));
fid = fopen(filename, 'w+');
[~, obj.tempName, ~] = fileparts(filename);
fprintf(fid, fnstr, obj.tempName, obj.libPath, obj.functionName);
fclose(fid);
addpath(obj.tempFolder);
end

function delete(obj)
% Removes the temporary runner file
rmpath(obj.tempFolder);
if exist(obj.tempFolder, 'dir')
rmdir(obj.tempFolder, 's');
end
end

function handle = getHandle(obj)
% Gets the name of the temporary runner file
handle = obj.tempName;
end

function disp(obj)
% Displays library path and function name
fprintf('Python wrapper for %s function in %s\n', obj.functionName, obj.libPath);
end
end
end
4 changes: 2 additions & 2 deletions compile/customWrapper/classHandle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ class CallbackInterface
};


template<class base> inline const char *convertPtr2String(base *ptr)
template<class base> inline std::string convertPtr2String(base *ptr)
{
auto handle = reinterpret_cast<uint64_t>(new ClassHandle<base>(ptr));
return std::to_string(handle).c_str();
return std::to_string(handle);
}

template<class base> inline ClassHandle<base> *convertString2HandlePtr(const char* in)
Expand Down
2 changes: 2 additions & 0 deletions compile/customWrapper/library.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ class Library: public CallbackInterface
this->functionName = strdup(functionName);
init(libName);
};

~Library(void){};

Library& operator=(Library* other) noexcept
{

if (other) {
this->library = std::move(other->library);
this->functionName = other->functionName;
Expand Down
39 changes: 39 additions & 0 deletions compile/customWrapper/pyRunner.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function [output,sub_rough] = pyRunner(filename, functionName, args)
% Calls a Python custom function from the base Matlab
% workspace. The arguments should be full or relative path of the
% python file with extension, the name of the function to call in the
% python file and a cell array of argument for the reflectivity calculation
%
% [o, s] = pythonWrapper.runner('layers.py', 'layer', {params, bulk_in, bulk_out, contrast});
% [o, s] = pythonWrapper.runner('layers.py', 'layer', {params, bulk_in, bulk_out, contrast, domain});

[filepath, name, ~] = fileparts(filename);
insert(py.sys.path, int32(0), filepath)

module = py.importlib.import_module(name);
py.importlib.reload(module);

% Call the python function
if (numel(args) == 4)
out = module.(functionName)(args{1}, args{2}, args{3}, int32(args{4} - 1));
else
out = module.(functionName)(args{1}, args{2}, args{3}, int32(args{4} - 1), int32(args{5} - 1));
end

output = out{1};
if isa(output, "py.numpy.ndarray")
% convert python (Numpy) ndarray to matlab
temp = zeros(double(output.shape));
list = output.tolist();
for i=1:size(temp, 1)
temp(i, :) = double(list{i});
end
output = temp;
end
output = double(output);
sub_rough = double(out{2});

% remove the module from sys modules to ensure reload works
py.sys.modules().pop(name);
py.sys.path().remove(filepath);
end
Loading

0 comments on commit 501c6fa

Please sign in to comment.