diff --git a/ShoulderCase/@MaskSubdividerWithSquareGrid/MaskSubdividerWithSquareGrid.m b/ShoulderCase/@MaskSubdividerWithSquareGrid/MaskSubdividerWithSquareGrid.m new file mode 100644 index 0000000..4a5e354 --- /dev/null +++ b/ShoulderCase/@MaskSubdividerWithSquareGrid/MaskSubdividerWithSquareGrid.m @@ -0,0 +1,81 @@ +classdef MaskSubdividerWithSquareGrid < handle + + properties + mask = []; + maskLimit = []; + + xGrid = []; + xResolution = 1; + yGrid = []; + yResolution = 1; + end + + methods + function obj = MaskSubdividerWithSquareGrid(mask) + obj.mask = mask; + [obj.xGrid, obj.yGrid] = meshgrid(1:size(mask, 1), 1:size(mask, 2)); + + [maskPixelY, maskPixelX] = find(mask); + obj.maskLimit.top = min(maskPixelY); + obj.maskLimit.bottom = max(maskPixelY); + obj.maskLimit.left = min(maskPixelX); + obj.maskLimit.right = max(maskPixelX); + end + + function setXResolutionInPixels(obj, resolutionInPixels) + resolutionInPixels = round(resolutionInPixels); + obj.xResolution =... + min(obj.maskLimit.right - obj.maskLimit.left,... + max(1, resolutionInPixels)); + end + + function setXResolutionInMm(obj, resolutionInMm, pixelSizeX) + obj.setXResolutionInPixels(resolutionInMm/pixelSizeX); + end + + function setYResolutionInPixels(obj, resolutionInPixels) + resolutionInPixels = round(resolutionInPixels); + obj.yResolution =... + min(obj.maskLimit.bottom - obj.maskLimit.top,... + max(1, resolutionInPixels)); + end + + function setYResolutionInMm(obj, resolutionInMm, pixelSizeY) + obj.setYResolutionInPixels(resolutionInMm/pixelSizeY); + end + + function output = getDivision(obj, top, bottom, left, right) + division =... + (obj.xGrid >= left)... + & (obj.xGrid <= right)... + & (obj.yGrid >= top)... + & (obj.yGrid <= bottom); + output = division; + end + + function output = getMaskSubdivisions(obj) + maskSubdivisions = []; + for x = obj.maskLimit.left:obj.xResolution:obj.maskLimit.right + for y = obj.maskLimit.top:obj.yResolution:obj.maskLimit.bottom + maskSubdivisions(:,:,end+1) =... + obj.mask... + & obj.getDivision(... + y, (y + obj.yResolution-1), x, (x + obj.xResolution-1)); + end + end + output = obj.removeEmptySubdivisions(maskSubdivisions); + end + + function output = removeEmptySubdivisions(obj, subdivisions) + subdivisionsToRemove = []; + for i = 1:size(subdivisions, 3) + if not(any(subdivisions(:,:,i), "all")) + subdivisionsToRemove(end+1) = i; + end + end + subdivisions(:,:,subdivisionsToRemove) = []; + output = subdivisions; + end + end + +end \ No newline at end of file diff --git a/ShoulderCase/@Muscle/Muscle.m b/ShoulderCase/@Muscle/Muscle.m index 290d0fc..91c2863 100644 --- a/ShoulderCase/@Muscle/Muscle.m +++ b/ShoulderCase/@Muscle/Muscle.m @@ -1,144 +1,145 @@ classdef Muscle < handle % The Muscle class is linked to a segmented muscle file (a mask) % and to the slice it has been segmented out of. % % Then, this class can measured values linked to the PCSA and the % muscle's degeneration. properties container = nan; name = ""; segmentationName = ""; sliceName = ""; PCSA = nan; atrophy = nan; fat = nan; osteochondroma = nan; degeneration = nan; centroid = nan; insertion = nan; forceApplicationPoint = nan; forceVector = nan; end properties (Access = ?RotatorCuff) subdivisions end methods function obj = Muscle(musclesContainer,muscleName) obj.container = musclesContainer; obj.name = muscleName; [~, ~] = mkdir(obj.dataPath); [~, ~] = mkdir(obj.dataMaskPath); end function output = dataPath(obj) output = fullfile(obj.container.dataPath, obj.name); end function output = dataMaskPath(obj) output = fullfile(obj.dataPath, "mask"); end function output = dataContourPath(obj) output = fullfile(obj.dataPath, "contour"); end function setSliceName(obj, value) obj.sliceName = value; end function setSegmentationName(obj, value) obj.segmentationName = value; end function output = loadMask(obj, maskSuffix) output = logical(imread(fullfile(obj.dataMaskPath,... obj.segmentationName+"_"+maskSuffix+".png"))); end function saveMask(obj, mask, maskSuffix) imwrite(mask, fullfile(obj.dataMaskPath,... obj.segmentationName+"_"+maskSuffix+".png")); end function output = loadSlice(obj, sliceSuffix) filePattern = fullfile(obj.container.dataSlicesPath,... obj.sliceName + "_" + sliceSuffix + "*"); matchingFile = dir(filePattern); assert(not(isempty(matchingFile)),... "File matching %s not found", filePattern); assert(length(matchingFile) <= 1,... "Multiple matching files found. Include file extension in given suffix"); if endsWith(matchingFile.name, ".mat") loadedFile = load(fullfile(matchingFile.folder, matchingFile.name)); % loaded Matlab archives are structures whose fields contain the saved % variables. When only one variable is saved we want to return its value % directly and avoid having to retrieve the original variable name. loadedVariables = string(fields(loadedFile)); if isequal(length(loadedVariables), 1) loadedFile = loadedFile.(loadedVariables); end output = loadedFile; elseif endsWith(matchingFile.name, ".png") output = imread(fullfile(matchingFile.folder, matchingFile.name)); end end function output = measureFirst(obj) % Call methods that can be run after morphology() methods has been run % by all ShoulderCase objects. obj.setSliceName(getConfig().rotatorCuffSliceName); obj.setSegmentationName(getConfig().rotatorCuffSegmentationName); - radialSubdivisions = getConfig().numberOfMuscleSubdivisions.radial; - angularSubdivisions = getConfig().numberOfMuscleSubdivisions.angular; + subdivisionsResolutionX = getConfig().muscleSubdivisionsResolutionInMm.x; + subdivisionsResolutionY = getConfig().muscleSubdivisionsResolutionInMm.y; success = Logger.timeLogExecution(... - obj.name+sprintf(" subdivide segmentation %d by %d: ",... - radialSubdivisions, angularSubdivisions),... - @(obj) obj.subdivide(radialSubdivisions, angularSubdivisions), obj); + obj.name+sprintf(" subdivide segmentation %d mm by %d mm: ",... + subdivisionsResolutionX, subdivisionsResolutionY),... + @(obj) obj.subdivide(subdivisionsResolutionX, subdivisionsResolutionY),... + obj); success = success | Logger.timeLogExecution(... obj.name+" measure and save contour: ",... @(obj) obj.measureAndSaveContour(), obj); success = success | Logger.timeLogExecution(... obj.name+" measure and save centroid: ",... @(obj) obj.measureAndSaveCentroid(), obj); success = success | Logger.timeLogExecution(... obj.name+" PCSA: ",... @(obj) obj.measurePCSA(), obj); success = success | Logger.timeLogExecution(... obj.name+" atrophy: ",... @(obj) obj.measureAtrophy(), obj); success = success | Logger.timeLogExecution(... obj.name+" fat infiltration: ",... @(obj) obj.measureFatInfiltration(), obj); success = success | Logger.timeLogExecution(... obj.name+" osteochondroma: ",... @(obj) obj.measureOsteochondroma(), obj); success = success | Logger.timeLogExecution(... obj.name+" degeneration: ",... @(obj) obj.measureDegeneration(), obj); success = success | Logger.timeLogExecution(... obj.name+" insertion: ",... @(obj) obj.measureInsertion(obj.container.shoulder.humerus), obj); success = success | Logger.timeLogExecution(... obj.name+" humeral head contact point: ",... @(obj) obj.measureHumerusContactPoint(), obj); success = success | Logger.timeLogExecution(... obj.name+" force: ",... @(obj) obj.measureForceVector(), obj); % Saving subdivisions resulting from obj.subdivide(10,10) increases the % file size by roughly 15MB (x2000%) for 1 shoulder only. % This caused problems with the MATLAB save/load functions and, even % if saving in the v7.3 MAT-file version solved the problems, subdivisions % don't need to be saved so they are removed here. obj.subdivisions = []; output = success; end end end diff --git a/ShoulderCase/@Muscle/subdivide.m b/ShoulderCase/@Muscle/subdivide.m index 9b3c729..2528342 100644 --- a/ShoulderCase/@Muscle/subdivide.m +++ b/ShoulderCase/@Muscle/subdivide.m @@ -1,12 +1,7 @@ -function subdivide(obj, numberOfRadialDivisions, numberOfAngularDivisions) - if isequal([numberOfAngularDivisions numberOfRadialDivisions], [1 1]) - obj.subdivisions = obj.loadMask("Segmentation"); - return - end - subdivider = MaskSubdivider(obj.loadMask("Segmentation")); - subdivider.setNumberOfRadialDivisions(numberOfRadialDivisions); - subdivider.setNumberOfAngularDivisions(numberOfAngularDivisions); - humeralHeadCenter = obj.container.shoulder.humerus.center; - subdivider.setCenter(obj.getClosestPointInSlice(humeralHeadCenter)); +function subdivide(obj, xResolutionInCm, yResolutionInCm) + subdivider = MaskSubdividerWithSquareGrid(obj.loadMask("Segmentation")); + slicePixelSpacings = obj.loadSlice("PixelSpacings"); + subdivider.setXResolutionInMm(xResolutionInCm, slicePixelSpacings(1)); + subdivider.setYResolutionInMm(yResolutionInCm, slicePixelSpacings(2)); obj.subdivisions = subdivider.getMaskSubdivisions(); end \ No newline at end of file diff --git a/setup.m b/setup.m index 61b62a5..d74da8c 100644 --- a/setup.m +++ b/setup.m @@ -1,45 +1,45 @@ function setup() % to execute once after cloning the repo. createDefaultConfigFile(); addpath(genpath(pwd)); end function createDefaultConfigFile() defaultConfig.maxSCaseIDDigits = 3; defaultConfig.SCaseIDValidTypes = ["N", "P"]; defaultConfig.pythonDir = "/shoulder/methods/python/rcseg"; defaultConfig.dataDir = "/shoulder/dataDev"; defaultConfig.casesToMeasure = ["*"]; defaultConfig.shouldersToMeasure.rightAuto = true; defaultConfig.shouldersToMeasure.rightManual = true; defaultConfig.shouldersToMeasure.leftAuto = true; defaultConfig.shouldersToMeasure.leftManual = true; defaultConfig.standardMeasurementsToRun.loadData = true; defaultConfig.standardMeasurementsToRun.morphology = true; defaultConfig.standardMeasurementsToRun.measureFirst = true; defaultConfig.standardMeasurementsToRun.measureSecond = true; defaultConfig.specialMeasurementsToRun.sliceAndSegment = false; defaultConfig.specialMeasurementsToRun.calcDensity = false; defaultConfig.specialMeasurementsArguments.sliceAndSegment = []; defaultConfig.specialMeasurementsArguments.calcDensity = []; defaultConfig.rotatorCuffSliceName = "rotatorCuffMatthieu"; defaultConfig.rotatorCuffSegmentationName = "autoMatthieu"; - defaultConfig.numberOfMuscleSubdivisions.radial = 10; - defaultConfig.numberOfMuscleSubdivisions.angular = 10; + defaultConfig.muscleSubdivisionsResolutionInMm.x = 5; + defaultConfig.muscleSubdivisionsResolutionInMm.y = 5; defaultConfig.overwriteMeasurements = false; defaultConfig.saveMeasurements = false; defaultConfig.saveAllMeasurementsInOneFile = false; fid = fopen('config.json','w'); fprintf(fid, replace(jsonencode(defaultConfig), ",", ",\n")); fclose(fid); end \ No newline at end of file