diff --git a/ShoulderCase/@Scapula/Scapula.m b/ShoulderCase/@Scapula/Scapula.m index 51886c7..e7040aa 100644 --- a/ShoulderCase/@Scapula/Scapula.m +++ b/ShoulderCase/@Scapula/Scapula.m @@ -1,126 +1,122 @@ classdef (Abstract) Scapula < handle %SCAPULA Summary of this class goes here % This class defines the scapula. Landmarks are used to define its % coordinate system. It includes the glenoid object. properties angulusInferior % Landmark Angulus Inferior read from amira angulusInferiorLocal % Same as above, expressed the scapular coordinate system rather than the CT coordinate system trigonumSpinae % Landmark Trigonum Spinae Scapulae (the midpoint of the triangular surface on the medial border of the scapula in line with the scaoular spine read from amira processusCoracoideus % Landmark Processus Coracoideus (most lateral point) read from amira acromioClavicular % Landmark Acromio-clavicular joint (most lateral part on acromion)read from amira angulusAcromialis % Landmark Angulus Acromialis (most laterodorsal point) read from amira spinoGlenoidNotch % Landmark Spino-glenoid notch read from amira pillar % 5 landmarks on the pillar groove % 5 landmarks on the scapula (supraspinatus) groove coordSys % Scapular coordinate system plane % Plane class segmentation % either none 'N', manual 'M' or automatic 'A' surface % surface points and triangles of the scapula glenoid % Glenoid class acromion % Acromion class comment % any comment end properties (Hidden = true) shoulder end methods (Abstract) load(obj); createGlenoid(obj); end methods (Access = protected) function obj = Scapula(shoulder) %SCAPULA Construct an instance of this class % Constructor of the scapula object, sets all properties to % zero. obj.shoulder = shoulder; obj.angulusInferior = []; obj.angulusInferiorLocal = []; obj.trigonumSpinae = []; obj.processusCoracoideus = []; obj.acromioClavicular = []; obj.angulusAcromialis = []; obj.spinoGlenoidNotch = []; obj.pillar = []; obj.groove = []; obj.coordSys = CoordinateSystemScapula(); obj.plane = Plane(); obj.segmentation = 'N'; obj.surface = []; obj.createGlenoid(); obj.acromion = Acromion(obj); obj.acromion.scapula = obj; obj.comment = ''; end end methods function output = coordSysSet(obj) try obj.setCoordinateSystemWithLandmarks(); catch ME warning(ME.message); output = 0; end % To enable retro compatibility with scapula.coordSysSet former % implementation, the two following commands are called here obj.setShoulderSideWithCoordinateSystem; obj.angulusInferiorLocal = obj.coordSys.express(obj.angulusInferior); output = 1; end function measurePlane(obj) % Scapular plane is fitted on 3 points (angulusInferior, % trigonumSpinae, most laretal scapular goove landmark). inferior = obj.angulusInferior; medial = obj.trigonumSpinae; mostLateralGrooveIndex = findLongest3DVector(medial-obj.groove); mostLateralGroovePoint = obj.groove(mostLateralGrooveIndex(1),:); - % trying alternative: the lateral point is spinoGlenoidNotch - mostLateralGroovePoint = obj.spinoGlenoidNotch; - % trying alternative: the lateral point is the mean of groove points - % mostLateralGroovePoint = mean(obj.groove); obj.plane.fit([inferior; medial; mostLateralGroovePoint]); anterior = obj.processusCoracoideus; posterior = obj.angulusAcromialis; obj.plane.normal = orientVectorToward(obj.plane.normal,(anterior-posterior)); end function setShoulderSideWithCoordinateSystem(obj) % The scapula coordinate system is right-handed for the right shoulder only if obj.coordSys.isRightHanded obj.shoulder.side = 'R'; return elseif obj.coordSys.isLeftHanded obj.shoulder.side = 'L'; return end warning(['Couldn''t find the shoulder side by evaluating the scapula',... 'coordinate system.']); end end end diff --git a/ShoulderCase/@Scapula/setCoordinateSystemWithLandmarks.m b/ShoulderCase/@Scapula/setCoordinateSystemWithLandmarks.m index d1e4723..dd6cf85 100644 --- a/ShoulderCase/@Scapula/setCoordinateSystemWithLandmarks.m +++ b/ShoulderCase/@Scapula/setCoordinateSystemWithLandmarks.m @@ -1,60 +1,58 @@ function setCoordinateSystemWithLandmarks(obj) % Calculate the (EPFL) scapular coordinate system % The EPFL scapular coordinate system is defined for a right scapula see % paper (10.1302/0301-620X.96B4.32641). The X axis is % antero-posterior, the Y axis is infero-superior, the Z axis % is meddio-lateral. This system is right-handed. % This orientation corresponds approximatively to the ISB % recommendadtion (doi:10.1016/j.jbiomech.2004.05.042). % For left scapula we keep the x axis as postero-anterior and % get a left-handed coordinate system. if (length(obj.groove) < 2) error(['Can''t set scapular coordinate system with actual',... ' obj.groove']); return; end % The four following methods have to be called in this specific order setPAAxis(obj); setMLAxis(obj); setISAxis(obj); setOrigin(obj); end function setPAAxis(obj) % Set the postero-anterior axis to be the scapula plane normal obj.coordSys.PA = obj.plane.normal; end function setMLAxis(obj) % Set the media-lateral axis to be the line fitted to the projection of % the groove points on the scapula plane lateral = obj.spinoGlenoidNotch; medial = obj.trigonumSpinae; groovePointsProjection = obj.plane.projectOnPlane(obj.groove); + grooveAxis = fitLine(groovePointsProjection); - % trying alternative: add spinoGlenoidNotch and trigonumSpinae - grooveAxis = fitLine([mean(groovePointsProjection);lateral;medial]); grooveAxis = (grooveAxis/norm(grooveAxis))'; obj.coordSys.ML = orientVectorToward(grooveAxis,(lateral-medial)); end function setISAxis(obj) % Set the infero-superior axis to be orthogonal with the two other axes superior = obj.spinoGlenoidNotch; inferior = obj.angulusInferior; obj.coordSys.IS = cross(obj.coordSys.PA,obj.coordSys.ML); obj.coordSys.IS = orientVectorToward(obj.coordSys.IS,(superior-inferior)); end function setOrigin(obj) % Set the origin of the coordinate system to the spino-glenoid notch % projected on the scapular axis spinoGlenoid = obj.spinoGlenoidNotch; + grooveMeanProjection = mean(obj.plane.projectOnPlane(obj.groove)); - % trying alternative: add spinoGelnoidNotch and trigonumSpinae - grooveMeanProjection = mean(obj.plane.projectOnPlane([mean(obj.groove);obj.trigonumSpinae;obj.spinoGlenoidNotch])); obj.coordSys.origin = grooveMeanProjection + ... dot((spinoGlenoid - grooveMeanProjection),obj.coordSys.ML)*obj.coordSys.ML; end diff --git a/ShoulderCase/@ShoulderCase/ShoulderCase.m b/ShoulderCase/@ShoulderCase/ShoulderCase.m index 0a6baea..0b82305 100755 --- a/ShoulderCase/@ShoulderCase/ShoulderCase.m +++ b/ShoulderCase/@ShoulderCase/ShoulderCase.m @@ -1,212 +1,212 @@ 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) diagnosis = []; treatment = []; outcome = []; patient = []; shoulderManual = []; shoulderAuto = []; study = []; comment = []; end % properties (Constant, Hidden = true) % dataPath = '/Volumes/shoulder/dataDev'; % path to data folder from package folder % % This should be done differently. Check where we are, and where we % % should go. % end properties (Hidden = true) dataPath = []; dataCTPath = []; dataAmiraPath = []; dataMatlabPath = []; dataDicomPath = []; id4C = []; % SCaseId with P/N followed by 3 digits --> 4 char end - methods (Access = public)%?ShoulderCaseLoader) + 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(4); % path attribution obj.dataCTPath = dataCTPath; % Initialsation obj.patient = Patient(obj); obj.shoulderManual = ShoulderManual(obj); obj.shoulderAuto = ShoulderAuto(obj); obj.propagateDataPath; end end methods function propagateDataPath(obj) % Update current dataPath % Propagate the path to objects in properties obj.dataAmiraPath = fullfile(obj.dataCTPath,'amira'); obj.dataMatlabPath = fullfile(obj.dataCTPath,'matlab'); obj.dataDicomPath = fullfile(obj.dataCTPath,'dicom'); obj.shoulderManual.propagateDataPath; obj.shoulderAuto.propagateDataPath; 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 = [dir '/' filename '.mat']; try SCase = obj; save(filename, 'SCase'); outputArg = 1; catch fprintf('Error creating SCase matlab file \n'); % Should be in log outputArg = -1; end end function outputArg = appendToCSV(obj,filename) % Save SCase to csv file logFid = fopen('log/measureSCase.log', 'a'); dataDir = ConfigFileExtractor.getVariable('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 end end