diff --git a/ShoulderCase/@ShoulderCase/ShoulderCase.m b/ShoulderCase/@ShoulderCase/ShoulderCase.m index b4985ec..a6201f4 100755 --- a/ShoulderCase/@ShoulderCase/ShoulderCase.m +++ b/ShoulderCase/@ShoulderCase/ShoulderCase.m @@ -1,243 +1,251 @@ classdef ShoulderCase < handle % Properties and methods associated to the SoulderCase object. % Author: Alexandre Terrier, EPFL-LBO % Matthieu Boubat, EPFL-LBO % Creation date: 2018-07-01 % Revision date: 2020-07-23 % TODO: % Need method to load properties (from amira, matlab, excel, SQL) % Need method to save properties (in matlab, excel, SQL) % Remove datapath from constants % properties id = []; % id of the shoulder case, as it appears in the database (Pnnn) id4C = []; % SCaseId with P/N followed by 3 digits --> 4 char diagnosis = []; treatment = []; outcome = []; patient = []; shoulders = []; study = []; comment = []; dataCTPath = []; end methods (Access = ?ShoulderCaseLoader) function obj = ShoulderCase(SCaseID,dataCTPath) % SCaseID validation rawSCaseID = SCaseIDParser(SCaseID); assert(rawSCaseID.isValidID,'The input argument is not a valid SCaseID.') obj.id = SCaseID; obj.id4C = rawSCaseID.getIDWithNumberOfDigits(3); % path attribution obj.dataCTPath = dataCTPath; % Folder creation if not(isfolder(obj.dataMatlabPath)) mkdir(obj.dataMatlabPath); end % Initialisation obj.patient = Patient(obj); obj.shoulders.left.auto = Shoulder(obj, "L", "auto"); obj.shoulders.left.manual = Shoulder(obj, "L", "manual"); obj.shoulders.right.auto = Shoulder(obj, "R", "auto"); obj.shoulders.right.manual = Shoulder(obj, "R", "manual"); end end methods function output = dataPath(obj) output = fileparts(obj.dataCTPath); end function output = dataAmiraPath(obj) output = fullfile(obj.dataCTPath, "amira"); end function output = dataMatlabPath(obj) output = fullfile(obj.dataCTPath, "matlab"); end function output = dataDicomPath(obj) output = fullfile(obj.dataCTPath, "dicom"); end function output = dataSlicerPath(obj) output = fullfile(obj.dataCTPath, "slicer"); end function outputArg = output(obj, varargin) % Below is copy/past from previous version % This function is used to output the variable Results, used % by scapula_measurePKG the modified funcion scapula_measure. % inputArg could be as in scapula_calculation % 'density', 'References', 'obliqueSlice', 'display' Result.SCase_id = obj.id; % 1 Result.glenoid_Radius = obj.shoulderManual.scapula.glenoid.radius; % 2 Result.glenoid_radiusRMSE = obj.shoulderManual.scapula.glenoid.fittedSphere.RMSE; % 3 % 3 %Result.glenoidSphericity = ' '; %Result.glenoid_biconcave = ' '; Result.glenoid_depth = obj.shoulderManual.scapula.glenoid.depth; % 4 Result.glenoid_width = obj.shoulderManual.scapula.glenoid.width; % 5 Result.glenoid_height = obj.shoulderManual.scapula.glenoid.height; % 6 Result.glenoid_center_PA = obj.shoulderManual.scapula.glenoid.centerLocal(1); % 7 Result.glenoid_center_IS = obj.shoulderManual.scapula.glenoid.centerLocal(2); % 8 Result.glenoid_center_ML = obj.shoulderManual.scapula.glenoid.centerLocal(3); % 9 Result.glenoid_version_ampl = obj.shoulderManual.scapula.glenoid.versionAmplitude; % 10 Result.glenoid_version_orient = obj.shoulderManual.scapula.glenoid.versionOrientation; % 11 Result.glenoid_Version = obj.shoulderManual.scapula.glenoid.version; % 12 Result.glenoid_Inclination = obj.shoulderManual.scapula.glenoid.inclination; % 13 Result.humerus_joint_radius = ' '; % 14 Result.humeral_head_radius = obj.shoulderManual.humerus.radius; % 15 % Result.humerus_GHsublux_2D = ' '; % Result.humerus_SHsublux_2D = ' '; Result.humerus_GHsubluxation_ampl = obj.shoulderManual.humerus.GHSAmpl; % 16 Result.humerus_GHsubluxation_orient = obj.shoulderManual.humerus.GHSOrient; % 17 Result.humerus_SHsubluxation_ampl = obj.shoulderManual.humerus.SHSAmpl; % 18 Result.humerus_SHsubluxation_orient = obj.shoulderManual.humerus.SHSOrient; % 19 Result.scapula_CSA = obj.shoulderManual.scapula.acromion.criticalShoulderAngle; % radCSA; % 5 Lines below should be updated Result.scapula_CTangle = 0; %CTorientation; %20 Result.scapula_CTangleVersion = 0; % WearPlaneAngle; %21 Result.scapula_CTangleSHS = 0; % SHSPlaneAngle; % 22 Result.scapula_CTangleGHS = 0; % GHSPlaneAngle; % 23 Result.scapula_PlaneRMSE = 0; %PlaneRMSE;%24 Result.scapula_AI = obj.shoulderManual.scapula.acromion.acromionIndex; outputArg = Result; end function outputArg = saveMatlab(obj) % Save SCase to matlab file dir = obj.dataMatlabPath; % Create dir if not exist try if ~exist(dir, 'dir') % create directory if it does not exist mkdir(dir); end catch fprintf('Error creating the matlab directory \n'); % Should be in log end % Save SCase in matlab directoty, in a file named SCaseCNNN.m filename = 'SCase'; filename = fullfile(obj.dataMatlabPath, filename + ".mat"); dataCTPath = obj.dataCTPath; try SCase = obj; % Delete CT path wich must be set when loading/creating the % SCase to avoid messing with paths on different systems. SCase.dataCTPath = []; save(filename, 'SCase'); outputArg = 1; catch fprintf('Error creating SCase matlab file \n'); % Should be in log outputArg = -1; end obj.dataCTPath = dataCTPath; end function outputArg = appendToCSV(obj,filename) % Save SCase to csv file logFid = fopen('log/measureSCase.log', 'a'); dataDir = getConfig().dataDir; xlsDir = [dataDir '/Excel/xlsFromMatlab']; fid = fopen([xlsDir '/' filename],'a'); fprintf(fid,[... obj.id ','... % SCase_id obj.shoulderManual.side ','... % shoulder_side num2str(obj.shoulderManual.scapula.glenoid.radius) ','... % glenoid_radius num2str(obj.shoulderManual.scapula.glenoid.fittedSphere.RMSE) ','... % glenoid_sphereRMSE num2str(obj.shoulderManual.scapula.glenoid.depth) ','... % glenoid_depth num2str(obj.shoulderManual.scapula.glenoid.width) ','... % glenoid_width num2str(obj.shoulderManual.scapula.glenoid.height) ','... % glenoid_height num2str(obj.shoulderManual.scapula.glenoid.centerLocal.x) ','... % glenoid_centerPA num2str(obj.shoulderManual.scapula.glenoid.centerLocal.y) ','... % glenoid_centerIS num2str(obj.shoulderManual.scapula.glenoid.centerLocal.z) ','... % glenoid_centerML num2str(obj.shoulderManual.scapula.glenoid.versionAmplitude) ','... % glenoid_versionAmpl num2str(obj.shoulderManual.scapula.glenoid.versionOrientation) ','... % glenoid_versionOrientation num2str(obj.shoulderManual.scapula.glenoid.version) ','... % glenoid_version num2str(obj.shoulderManual.scapula.glenoid.inclination) ','... % glenoid_inclination num2str(obj.shoulderManual.humerus.jointRadius) ','... % humerus_jointRadius num2str(obj.shoulderManual.humerus.radius) ','... % humerus_headRadius num2str(obj.shoulderManual.humerus.GHSAmpl) ','... % humerus_GHSAmpl num2str(obj.shoulderManual.humerus.GHSOrient) ','... % humerus_GHSOrient num2str(obj.shoulderManual.humerus.SHSAmpl) ','... % humerus_SHSAmpl num2str(obj.shoulderManual.humerus.SHSOrient) ','... % humerus_SHSOrient num2str(obj.shoulderManual.humerus.SHSAngle) ','... % humerus_SHSAgle num2str(obj.shoulderManual.humerus.SHSPA) ','... % humerus_SHSPA num2str(obj.shoulderManual.humerus.SHSIS) ','... % humerus_SHSIS num2str(obj.shoulderManual.scapula.acromion.AI) ','... % acromion_AI num2str(obj.shoulderManual.scapula.acromion.CSA) ','... % acromion_CSA num2str(obj.shoulderManual.scapula.acromion.PSA) ','... % acromion_PSA num2str(obj.shoulderManual.scapula.acromion.AAA) '\n'... % acromion_AAA ]); fclose(fid); fclose(logFid); outputArg = 1; end function outputArg = saveExcel(~) % Save SCase to Excel file outputArg = 1; end function outputArg = saveSQL(~) % Save SCase to MySQL database outputArg = 1; end function output = getTableOfData(obj, varargin) % This method export all of the measurements and data of a % SCase to a table, which then can be used to export all of the % measurements as csv or excel file (see exportSCaseData function) metadataSCase = getTabulatedProperties(obj); metadataPatient = getTabulatedProperties(obj.patient,... "prefix", "patient"); - % Auto shoulder - landmarksAcquisition = table("auto",... - 'VariableName', "landmarks_acquisition"); - autoData = getTabulatedProperties(obj.shoulderAuto,... + % Right Auto shoulder + rightAutoData = getTabulatedProperties(obj.shoulders.right.auto,... "recursive", true,... "parentObjects", {obj}); - autoTable = [metadataSCase landmarksAcquisition metadataPatient,... - autoData]; + rightAutoTable = [metadataSCase metadataPatient rightAutoData]; - % Manual shoulder - landmarksAcquisition = table("manual",... - 'VariableName', "landmarks_acquisition"); - manualData = getTabulatedProperties(obj.shoulderManual,... + % Right Manual shoulder + rightManualData = getTabulatedProperties(obj.shoulders.right.manual,... "recursive", true,... "parentObjects", {obj}); - manualTable = [metadataSCase landmarksAcquisition metadataPatient,... - manualData]; + rightManualTable = [metadataSCase metadataPatient rightManualData]; - output = outerjoin(autoTable, manualTable, "MergeKeys", true); + % Left Auto shoulder + leftAutoData = getTabulatedProperties(obj.shoulders.left.auto,... + "recursive", true,... + "parentObjects", {obj}); + leftAutoTable = [metadataSCase metadataPatient leftAutoData]; + + % Left Manual shoulder + leftManualData = getTabulatedProperties(obj.shoulders.left.manual,... + "recursive", true,... + "parentObjects", {obj}); + leftManualTable = [metadataSCase metadataPatient leftManualData]; + + output = outerjoin(rightAutoTable, rightManualTable, "MergeKeys", true); + output = outerjoin(output, leftAutoTable, "MergeKeys", true); + output = outerjoin(output, leftManualTable, "MergeKeys", true); end end end \ No newline at end of file diff --git a/ShoulderCase/getTabulatedProperties.m b/ShoulderCase/getTabulatedProperties.m index 137e43c..e14a7e1 100644 --- a/ShoulderCase/getTabulatedProperties.m +++ b/ShoulderCase/getTabulatedProperties.m @@ -1,140 +1,143 @@ function output = getTabulatedProperties(object, varargin) % Return a table with object properties as variables. % Properties are converted to one table variable if they are scalar string, % numeric, or char array. They are converted to multiple variables if they are % 1xn array of string, numeric, or char array. The corresponding variables have % a "_n" suffix. If the property is a numerical 1x3 array the suffixes are % "_x", "_y", "_z". % % Recursion can be applied on other types of properties if the arguments % ("recursive", true) are given. % % To avoid recursion looping on itself specify the parent objects with the % arguments ("parentObjects", object). Recursion automatically add the new % parents object for each deeper level in nested structures. % % Give table's variable names a prefix and a suffix with the arguments % ("prefix", "text_written_before") and ("prefix", "text_written_after") output = []; parameters = inputParser; isScalarString = @(arg) isstring(arg) & isscalar(arg); isBoolean = @(arg) or(isequal(arg, true), isequal(arg, false)); addOptional(parameters, "recursive", false, isBoolean); addOptional(parameters, "parentObjects", {}); addOptional(parameters, "prefix", "", isScalarString); addOptional(parameters, "suffix", "", isScalarString); parse(parameters, varargin{:}); parameters = parameters.Results; parameters.parentObjects = [parameters.parentObjects, {object}]; properties = string(fields(object))'; for property = properties data = []; % Avoid infinite loop recursion if isParent(object.(property), parameters.parentObjects) continue end % Recursion - if not(isstring(object.(property)) || isnumeric(object.(property)) || ischar(object.(property)) ) + if not(isstring(object.(property))... + | isnumeric(object.(property))... + | ischar(object.(property))... + | islogical(object.(property)) ) if parameters.recursive output = [output getTabulatedProperties(object.(property),... "recursive", true,... "parentObjects", parameters.parentObjects,... "prefix", join([parameters.prefix property], "_"))]; end continue end % Format char to string if ischar(object.(property)) object.(property) = string(object.(property)); end % Scalar values are straightforward if isscalar(object.(property)) data.(fieldnameToColumnname(property, parameters.prefix, parameters.suffix)) = ... nanIfEmpty(object.(property)); end % Value is probably a single point coordinates if isequal(size(object.(property)), [1 3]) & isnumeric(object.(property)) data.(fieldnameToColumnname(property, parameters.prefix, parameters.suffix)) = true; point = object.(property); data.(fieldnameToColumnname(property, parameters.prefix, "x_" + parameters.suffix)) = ... nanIfEmpty(point(1)); data.(fieldnameToColumnname(property, parameters.prefix, "y_" + parameters.suffix)) = ... nanIfEmpty(point(2)); data.(fieldnameToColumnname(property, parameters.prefix, "z_" + parameters.suffix)) = ... nanIfEmpty(point(3)); end % Value is a one dimensional horizontal array if (size(object.(property), 1) == 1 &... size(object.(property), 2) > 1 &... size(object.(property), 2) ~= 3) data.(fieldnameToColumnname(property, parameters.prefix, parameters.suffix)) = true; array = object.(property); for i = 1:length(array) data.(fieldnameToColumnname(property, parameters.prefix, sprintf("%d_", i) + parameters.suffix)) = ... nanIfEmpty(array(i)); end end if not(isempty(data)) output = [output struct2table(data)]; end end end function output = fieldnameToColumnname(fieldname, prefix, suffix) % Tries to extract words from a string based on the capitalized letters in the % string. Capitalized concatenated words is a standard way of naming variables, % we want to extract a string containing these words modified to lower case % and separated by the "_" character. For example: % FooBARFooBAR -> foo_bar_foo_bar % where the words are "foo" and "bar". columnname = fieldname; % FooBARFooBAR -> FooBARFoo_bar columnname = (regexprep(columnname, "[A-Z]{2,}$", "_${lower($0)}")); % FooBARFoo_bar -> FooBARF#oo_bar columnname = (regexprep(columnname, "[A-Z]{2,}", "$0#")); % FooBARF#oo_bar -> FooBAR_f#oo_bar columnname = (regexprep(columnname, "[A-Z]#", "_${lower($0)}")); % FooBAR_f#oo_bar -> FooBAR_foo_bar columnname = (regexprep(columnname, "#", "")); % FooBAR_foo_bar -> _foo_bar_foo_bar columnname = (regexprep(columnname, "[A-Z]{1,}", "_${lower($0)}")); % _foo_bar_foo_bar -> foo_bar_foo_bar columnname = (regexprep(columnname, "^_", "")); columnname = join([prefix columnname suffix], "_"); columnname = strip(columnname, "_"); output = columnname; end function output = nanIfEmpty(value) if isempty(value) | isequal(value, "") output = nan; else output = value; end end function output = isParent(parsedProperty, parentList) output = false; for i = 1:length(parentList) if isequal(parentList{i}, parsedProperty) output = true; return end end end \ No newline at end of file