diff --git a/api/algorithms/algorithm_AverageSmallestAndLargest.m b/api/algorithms/algorithm_AverageSmallestAndLargest.m index cd8dcd2..fd5e1f4 100644 --- a/api/algorithms/algorithm_AverageSmallestAndLargest.m +++ b/api/algorithms/algorithm_AverageSmallestAndLargest.m @@ -18,10 +18,6 @@ % averageSeg (matrix [height, width]): % The average segmentation. % -% THROWS: -% TDSFT:algorithms: -% if the input is empty. -% % DESCRIPTION: % Get the average segmentation between the smallest and the largest. % Can be passed as input the smallest and the largest segmentation or an array of @@ -29,22 +25,9 @@ % The average segmentation is obtained by taking the 1-pixel line in the middle of % the area between the two segmentations. function averageSeg = algorithm_AverageSmallestAndLargest(varargin) - disp('Getting the average segmentation between the smallest and the largest...'); - if nargin == 1 segmentations = varargin{1}; - % Check if the input is empty, if it is the case throw an exception - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - - % If there is only one segmentation, return it - if length(segmentations) == 1 - averageSeg = segmentations{1}; - return; - end - try smallest = algorithm_Smallest(segmentations); largest = algorithm_Largest(segmentations); @@ -58,7 +41,7 @@ throw(MException('TDSFT:algorithms', 'Wrong number of parameters')); end - % Overlap the two smallest and the largest segmentations + % Overlap the smallest and the largest segmentations averageSeg = smallest + largest; % Fill the holes and get the area between the two segmentations diff --git a/api/algorithms/algorithm_AverageTargetFromInput.m b/api/algorithms/algorithm_AverageTargetFromInput.m index 9c4b77a..ca430fe 100644 --- a/api/algorithms/algorithm_AverageTargetFromInput.m +++ b/api/algorithms/algorithm_AverageTargetFromInput.m @@ -5,7 +5,7 @@ % PARAMETERS: % segmentations (Cell array: [1, raters], Cells: matrix [height, width]): % array containing the segmentations to fuse. -% startSegmentation (int): +% target (int): % the index of the segmentation to be used as main segmentation. % % OUTPUT: @@ -14,8 +14,6 @@ % % THROWS: % TDSFT:algorithms: -% the segmentations array is empty. -% TDSFT:algorithms: % the start segmentation index is greater than the number of segmentations. % % DESCRIPTION: @@ -24,20 +22,13 @@ % with value 1 in the other segmentations. Then it is computed the average pixel: % - if the segmentations are 2, the average pixel is the middle point between the two pixels; % - if the segmentations are more than 2, the average pixel is the centroid of the pixels. -function averageSeg = algorithm_AverageTargetFromInput(segmentations, startSegmentation) - s = strcat("Getting average segmentation targetting the segmentation ", num2str(startSegmentation{1}), "..."); - disp(s); - - % Check if the input is empty, if it is the case throw an exception - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - +function averageSeg = algorithm_AverageTargetFromInput(segmentations, target) try - if startSegmentation{1} > length(segmentations) || startSegmentation{1} < 1 + % Check if the target segmentation is in the range. + if target > length(segmentations) || target < 1 throw(MException('TDSFT:algorithms', 'Wrong start segmentation index')); end - averageSeg = getAverageSegmentation(segmentations, startSegmentation{1}); + averageSeg = getAverageSegmentation(segmentations, target); catch ME rethrow(ME); end diff --git a/api/algorithms/algorithm_AverageTargetLargest.m b/api/algorithms/algorithm_AverageTargetLargest.m index 0f0c808..f3edc4d 100644 --- a/api/algorithms/algorithm_AverageTargetLargest.m +++ b/api/algorithms/algorithm_AverageTargetLargest.m @@ -8,7 +8,7 @@ % % OUTPUT: % averageSeg (matrix [height, width]): -% The average segmentation. +% the average segmentation. % % DESCRIPTION: % The average segmentation is computed by averaging the segmentations as follows: @@ -17,13 +17,6 @@ % - if the segmentations are 2, the average pixel is the middle point between the two pixels; % - if the segmentations are more than 2, the average pixel is the centroid of the pixels. function averageSeg = algorithm_AverageTargetLargest(segmentations) - disp('Getting average segmentation targetting the largest...'); - - % Check if the input is empty, if it is the case throw an exception - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - try largest = algorithm_Largest(segmentations); averageSeg = getAverageSegmentation(segmentations, largest); diff --git a/api/algorithms/algorithm_AverageTargetSmallest.m b/api/algorithms/algorithm_AverageTargetSmallest.m index 874d7bb..a604aa2 100644 --- a/api/algorithms/algorithm_AverageTargetSmallest.m +++ b/api/algorithms/algorithm_AverageTargetSmallest.m @@ -17,16 +17,9 @@ % - if the segmentations are 2, the average pixel is the middle point between the two pixels; % - if the segmentations are more than 2, the average pixel is the centroid of the pixels. function averageSeg = algorithm_AverageTargetSmallest(segmentations) - disp('Getting average segmentation targetting the smallest......'); - - % Check if the input is empty, if it is the case throw an exception - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - try - largest = algorithm_Smallest(segmentations); - averageSeg = getAverageSegmentation(segmentations, largest); + smallest = algorithm_Smallest(segmentations); + averageSeg = getAverageSegmentation(segmentations, smallest); catch ME rethrow(ME); end diff --git a/api/algorithms/algorithm_Largest.m b/api/algorithms/algorithm_Largest.m index 0d3542d..ec8bce1 100644 --- a/api/algorithms/algorithm_Largest.m +++ b/api/algorithms/algorithm_Largest.m @@ -3,17 +3,10 @@ % NAME: TDSFT (version 1.0) % % PARAMETERS: -% The function accepts two different input parameters: -% 1) segmentations (Cell array: [1, raters], Cells: matrix [height, width]): -% array containing the segmentations to fuse. -% -% 2) segmentations (Matrix [height, width]): -% the segmentations are already overlapped in a matrix. -% (The algorithm need NO dense segmentations, so to overlap the segmentations no fill holes is needed). -% -% THROWS: -% TDSFT:algorithms: -% throwed if the input is empty. +% segmentations (Cell array: [1, raters], Cells: matrix [height, width] || Matrix [height, width]): +% - array: array containing the segmentations to fuse; +% - matrix: the segmentations are already overlapped in a matrix. +% (The algorithm need NO dense segmentations, so to overlap the segmentations no fill holes is needed) % % OUTPUT: % largestSegmentation (Matrix [height, width]): @@ -22,30 +15,13 @@ % DESCRIPTION: % Fuse all the segmentations together overlapping them and getting the largest possible segmentation. % The largest segmentation is the smallest segmentation possible which contains each input segmentation. -% To obtain it the segmentations are overlapped and then is returned the perimeter of the area covered by them. +% To obtain it the segmentations are overlapped and then is returned the perimeter of the total area covered by them. function largestSegmentation = algorithm_Largest(segmentations) - disp('Getting the largest...'); - if iscell(segmentations) - - % Check if the input is empty - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - - % If there is only one segmentation, return it - if length(segmentations) == 1 - largestSegmentation = segmentations{1}; - return; - end - - try - overlap = overlapSegmentations(segmentations); - catch ME - rethrow(ME); - end + overlap = overlapSegmentations(segmentations); else overlap = segmentations; end - largestSegmentation = getLargestSegmentation(overlap); + + [~, largestSegmentation] = getLargestArea(overlap); end \ No newline at end of file diff --git a/api/algorithms/algorithm_Middle.m b/api/algorithms/algorithm_Middle.m index b82fdb5..48db20b 100644 --- a/api/algorithms/algorithm_Middle.m +++ b/api/algorithms/algorithm_Middle.m @@ -14,8 +14,6 @@ % % THROWS: % TDSFT:algorithms: -% throwed if the input is empty. -% TDSFT:algorithms: % throwed if the number of segmentations is even and the algorithm is not specified. % TDSFT:algorithms: % throwed if the algorithm choosed if the number of segmentations is even is not available. @@ -29,81 +27,56 @@ % Discard outliers until the middle segmentation is reached. To do that iterate over the segmentations and % discard the largest and the smallest segmentation at each iteration. % If the number of segmentations is even specify a method for the last two segmentations left. +% The available methods are: +% - 'LargestSegmentation': use the largest segmentation. +% - 'SmallestSegmentation': use the smallest segmentation. +% - 'AverageSmallestAndLargest': use the average between the smallest and the largest segmentation. function middleSegmentation = algorithm_Middle(segmentations, algorithm) - disp('Getting the middle...'); - - % check if the input is empty - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - - % check if the number of segmentations is even and the algorithm is specified + % Check if the number of segmentations is even and the algorithm is specified if mod(length(segmentations), 2) == 0 && nargin < 2 throw(MException('TDSFT:algorithms', 'If the number of segmentations is even, the algorithm must be specified')); end - % if there is only one segmentation, return it - if length(segmentations) == 1 - middleSegmentation = segmentations{1}; - return; - end - - try - overlap = overlapSegmentations(segmentations); - catch ME - rethrow(ME); - end + overlap = overlapSegmentations(segmentations); nSeg = length(segmentations); % number of segmentations totIt = floor( (nSeg - 1) / 2 ); % total number of iterations - % preallocate the filled segmentations array - filledSegmentations = cell(1, length(segmentations)); - - % Get the filled segmentations needed to get the smallest segmentation - for i=1:length(segmentations) - filledSegmentations{i} = imfill(segmentations{i}, 'holes'); - end - - try - overlapFilled = overlapSegmentations(filledSegmentations); - catch ME - rethrow(ME); - end + % Fill segmentations and overlap. + filledSegmentations = getFilledSegmentations(segmentations); + overlapFilled = overlapSegmentations(filledSegmentations); % At each iteration discard the largest and the smallest segmentation for i=1:totIt - largest = uint8( getLargestSegmentation(overlap) ); - smallest = uint8( getSmallestSegmentation(overlapFilled, nSeg) ); + largest = uint8( algorithm_Largest(overlap) ); + smallest = uint8( algorithm_Smallest(overlapFilled, nSeg) ); overlap = overlap - largest; overlap = overlap - smallest; - filledLargest = imfill(largest, 'holes'); - filledSmallest = imfill(smallest, 'holes'); + filledLargest = imfill(largest, "holes"); + filledSmallest = imfill(smallest, "holes"); overlapFilled = overlapFilled - filledLargest; overlapFilled = overlapFilled - filledSmallest; nSeg = nSeg - 2; end - % compute result + % If the number of segmentations is even, use the specified algorithm for the last two segmentations left. if isequal(nSeg, 2) - % if the number of segmentations is even, use the specified algorithm for the last two segmentations left - algorithm = fromSpacedToFullName(algorithm); + algorithm = fromSpacedToFullNameAlgorithm(algorithm); if strcmp(algorithm, 'algorithm_Largest') - middleSegmentation = getLargestSegmentation(overlap); + middleSegmentation = algorithm_Largest(overlap); elseif strcmp(algorithm, 'algorithm_Smallest') - middleSegmentation = getSmallestSegmentation(overlapFilled, nSeg); + middleSegmentation = algorithm_Smallest(overlapFilled, nSeg); elseif strcmp(algorithm, 'algorithm_AverageSmallestAndLargest') - smallest = getSmallestSegmentation(overlapFilled, nSeg); - largest = getLargestSegmentation(overlap); + smallest = algorithm_Smallest(overlapFilled, nSeg); + largest = algorithm_Largest(overlap); middleSegmentation = algorithm_AverageSmallestAndLargest(smallest, largest); else throw(MException('TDSFT:algorithms', 'Algorithm not available')); end else - % if the number of segmentations is odd, return the segmentation left middleSegmentation = imbinarize(overlap); end diff --git a/api/algorithms/algorithm_STAPLE.m b/api/algorithms/algorithm_STAPLE.m index 651621e..8e6ee39 100644 --- a/api/algorithms/algorithm_STAPLE.m +++ b/api/algorithms/algorithm_STAPLE.m @@ -10,10 +10,6 @@ % gtSegmentation (Matrix [height, width]): % the ground truth segmentation computed with STAPLE algorithm. % -% THROWS: -% staple:emptyInput (Exception): -% throwed if the input is empty. -% % DESCRIPTION: % Use STAPLE algorithm to get the ground truth segmentation. % @@ -23,37 +19,21 @@ % an algorithm for the validation of image segmentation." % Medical Imaging, IEEE Transactions on 23.7 (2004): 903-921. function gtSegmentation = algorithm_STAPLE(segmentations) - disp('Executing STAPLE...'); - - % check if the input is empty - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - - % if there is only one segmentation, return it - if length(segmentations) == 1 - gtSegmentation = segmentations{1}; - return; - end + % Convert to the right format for STAPLE. + % (See STAPLE file for more details) - % convert to the right format for STAPLE - % See STAPLE file for more details - - % get segmentations dimensions - [height, width] = size(segmentations{1}); + % Get segmentations dimensions. + imageDims = size(segmentations{1}); nSeg = length(segmentations); - % preallocate the array - stapleParam = zeros(height*width, nSeg); + % Preallocate the array. + stapleParam = zeros(imageDims(1) * imageDims(2), nSeg); for i=1:length(segmentations) seg = segmentations{i}; stapleParam(:, i) = seg(:); end [W, ~, ~] = STAPLE(stapleParam); - % get segmentations dimensions - imageDims = size(segmentations{1}); - - % reshape to get the ground truth segmentation + % Reshape to get the ground truth segmentation. gtSegmentation = reshape((W >= .5), imageDims); end \ No newline at end of file diff --git a/api/algorithms/algorithm_Smallest.m b/api/algorithms/algorithm_Smallest.m index 5262050..a5c6c82 100644 --- a/api/algorithms/algorithm_Smallest.m +++ b/api/algorithms/algorithm_Smallest.m @@ -20,52 +20,27 @@ % % THROWS: % TDSFT:algorithms: -% throwed if the input is empty. -% TDSFT:algorithms: % throwed if the input param number is wrong. % % DESCRIPTION: % Fuse all the segmentations together overlapping them if needed and getting the smallest segmentation possible. % The smallest segmentation is the perimeter of the area covered by every segmentation (the common area between every segmentation). function smallestSegmentation = algorithm_Smallest(varargin) - disp('Getting the smallest...'); - % if the segmentations are not overlapped, overlap them if nargin == 1 segmentations = varargin{1}; - - % Check if the input is empty - if isempty(segmentations) - throw(MException('TDSFT:algorithms', 'Segmentations array empty')); - end - - % If there is only one segmentation, return it nSeg = length(segmentations); - if nSeg == 1 - smallestSegmentation = segmentations{1}; - return; - end - % preallocate the filledSegmentations array - filledSegmentations = cell(1, nSeg); - - % overlap the filled segmentations - for i=1:length(segmentations) - filledSegmentations{i} = imfill(segmentations{i}, 'holes'); - end - - try - overlap = overlapSegmentations(filledSegmentations); - catch ME - rethrow(ME); - end + % Fill and overlap the segmentations. + filledSegmentations = getFilledSegmentations(segmentations); + overlap = overlapSegmentations(filledSegmentations); - elseif varargin == 2 + elseif nargin == 2 overlap = varargin{1}; nSeg = varargin{2}; else throw(MException('TDSFT:algorithms', 'Wrong number of input arguments')); end - smallestSegmentation = getSmallestSegmentation(overlap, nSeg); + [~, smallestSegmentation] = getCommonArea(overlap, nSeg); end \ No newline at end of file diff --git a/api/utils/classes/ImagesStoringMethods.m b/api/utils/classes/ImagesStoringMethods.m index d8b9d87..f5d6fdf 100644 --- a/api/utils/classes/ImagesStoringMethods.m +++ b/api/utils/classes/ImagesStoringMethods.m @@ -3,20 +3,22 @@ % NAME: TDSFT (version 1.0) % % DESCRIPTION: Enumeration class for image storing methods. -% It is used to be able to use pretty names for +% Used to be able to use pretty names for % the different storing methods. classdef ImagesStoringMethods properties - type + string end methods % Constructor % % Parameters: - % m: string representing the type of the image - function storing_method = ImagesStoringMethods(m) - storing_method.type = m; + % s: string representing the type of the image + % Return: + % the image storing method + function storing_method = ImagesStoringMethods(s) + storing_method.string = s; end end diff --git a/api/utils/classes/OnePixelLineTypes.m b/api/utils/classes/OnePixelLineTypes.m new file mode 100644 index 0000000..ba5d025 --- /dev/null +++ b/api/utils/classes/OnePixelLineTypes.m @@ -0,0 +1,32 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% DESCRIPTION: Enumeration class for one pixel line types. +% Used to specify which one pixel line the user +% wants to use if the segmentation is of more than one pixel. +classdef OnePixelLineTypes + properties + string + end + + methods + % Constructor + % + % Parameters: + % s: string representing the type of the image + % Return: + % the one pixel line type + function line_type = OnePixelLineTypes(s) + line_type.string = s; + end + end + + % Enumeration values with their corresponding string + enumeration + EXTERNAL ('external') + MIDDLE ('middle') + INTERNAL ('internal') + end +end + diff --git a/api/utils/functions/getCentroid.m b/api/utils/functions/getCentroid.m index 028236a..cfbc7c7 100644 --- a/api/utils/functions/getCentroid.m +++ b/api/utils/functions/getCentroid.m @@ -1,4 +1,4 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzo.drudi5@studio.unibo.it) +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) % DATE: April 20, 2023 % NAME: TDSFT (version 1.0) % @@ -26,19 +26,19 @@ throw (MException("TDSFT:algorithms", "The points array is empty")); end - % Remove duplicates + % Remove duplicates. points = unique(points, "rows"); if size(points, 1) == 1 row = points(1, 1); col = points(1, 2); - % not using (a+b)/2 because it could overflow + % Not using (a+b)/2 because it could overflow. elseif size(points, 1) == 2 [row, col] = getMiddlePoint(points); else pgon = polyshape(points, "KeepCollinearPoints", true); - % Check if the points are collinear + % Check if the points are collinear. if isequal(area(pgon), 0) [row, col] = getMiddlePoint(points); else diff --git a/api/utils/functions/getMiddlePoint.m b/api/utils/functions/getMiddlePoint.m index bcbeb57..4820c61 100644 --- a/api/utils/functions/getMiddlePoint.m +++ b/api/utils/functions/getMiddlePoint.m @@ -1,4 +1,4 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzo.drudi5@studio.unibo.it) +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) % DATE: April 27, 2023 % NAME: TDSFT (version 1.0) % @@ -7,9 +7,9 @@ % the collinear points from which the middle point is computed % % OUTPUT: -% row: +% row (int): % the row (y coordinate) of the middle point -% col: +% col (int): % the col (x coordinate) of the middle point % % THROWS: @@ -26,7 +26,7 @@ throw(MException("TDSFT:algorithms", "Insert at least 2 points")); end - % If there are more than 2 points search for the extreme points + % If there are more than 2 points search for the extreme points. if length(points) > 2 extremePoints = zeros(2,2); [~, minIdx] = min(points(:,1)); diff --git a/api/utils/functions/segmentations/closingLineAlgorithms/closing_ChanVese.m b/api/utils/functions/segmentations/closingLineAlgorithms/closing_ChanVese.m new file mode 100644 index 0000000..1f44862 --- /dev/null +++ b/api/utils/functions/segmentations/closingLineAlgorithms/closing_ChanVese.m @@ -0,0 +1,37 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation which needs to be closed. +% inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% res (Matrix: [height, width]): +% the resulting segmentation after the closing process. + +% DESCRIPTION: +% Use Chan-Vese algorithm to close a result of the fusion process which is not closed. +% Use the input segmentations to get the nearest mask to the border. +% +% The Chan-Vese algorithm is a level set method which differ from classial snake and active contour methods +% because it does not require a stopping edge-function.It is a model which can detect contours both with or +% without gradient, for instance objects with very smooth boundaries or even with discontinuous boundaries +% +% REFERENCES: +% T. F. Chan and L. A. Vese, +% "Active contours without edges," +% in IEEE Transactions on Image Processing, +% vol. 10, no. 2, pp. 266-277, Feb. 2001, doi: 10.1109/83.902291. +function res = closing_ChanVese(fusionResult, inputSegmentations) + mask = getSegmentationsMask(inputSegmentations); + nIterations = 3000; + smoothfactor = 0.6; + incrementFactor = 25; + res = activecontour(fusionResult .* incrementFactor, mask, nIterations, "chan-vese", "SmoothFactor", smoothfactor); + res = imfill(res, "holes"); + res = bwperim(res); +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/closingLineAlgorithms/closing_GeodesicActiveContour.m b/api/utils/functions/segmentations/closingLineAlgorithms/closing_GeodesicActiveContour.m new file mode 100644 index 0000000..4c28719 --- /dev/null +++ b/api/utils/functions/segmentations/closingLineAlgorithms/closing_GeodesicActiveContour.m @@ -0,0 +1,34 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation which needs to be closed. +% inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% res (Matrix: [height, width]): +% the resulting segmentation after the closing process. + +% DESCRIPTION: +% Use Geodesic Active Contour algorithm to close a result of the fusion process which is not closed. +% Use the input segmentations to get the nearest mask to the border. +% +% This technique is based on active contours evolving in time according to intrinsic geometric measures of the image. +% The evolving contours naturally split and merge, allowing the simultaneous detection of several objects and both interior +% and exterior boundaries. The proposed approach is based on the relation between active contours and the computation of geodesics +% or minimal distance curves. +% +% REFERENCES: +% Caselles, V., Kimmel, R. & Sapiro, G. Geodesic Active Contours. +% International Journal of Computer Vision 22, 61–79 (1997). +function res = closing_GeodesicActiveContour(fusionResult, inputSegmentations) + mask = getSegmentationsMask(inputSegmentations); + incrementFactor = 25; + res = activecontour(fusionResult .* incrementFactor, mask, "edge"); + res = imfill(res, "holes"); + res = bwperim(res); +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/closingLineAlgorithms/closing_NoClosing.m b/api/utils/functions/segmentations/closingLineAlgorithms/closing_NoClosing.m new file mode 100644 index 0000000..de2e712 --- /dev/null +++ b/api/utils/functions/segmentations/closingLineAlgorithms/closing_NoClosing.m @@ -0,0 +1,18 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 10, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% fusionResult (Matrix [height, width]): +% the result of the fusion process. +% +% OUTPUT: +% fusionResult (Matrix: [height, width]): +% same as input. + +% DESCRIPTION: +% Simple function to permit to see the result of the fusion process +% without applying the closing operation. +function fusionResult = closing_NoClosing(fusionResult, ~) + % Do nothing +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/closingLineAlgorithms/closing_ShapePreserving.m b/api/utils/functions/segmentations/closingLineAlgorithms/closing_ShapePreserving.m new file mode 100644 index 0000000..af76606 --- /dev/null +++ b/api/utils/functions/segmentations/closingLineAlgorithms/closing_ShapePreserving.m @@ -0,0 +1,60 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% fusionResult (Matrix [height, width]): +% the result of the fusion process. +% It is a not-close segmentation which needs to be closed. +% inputSegmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% the segmentations used for the fusion process. +% +% OUTPUT: +% res (Matrix: [height, width]): +% the resulting segmentation after the closing process. + +% DESCRIPTION: +% Use the Shape-Preserving Piecewise Cubic Hermite interpolation (PCHIP). +% It gets the polar coordinates of the outline pixels of the fusion result +% and then it interpolates them with the PCHIP method. +% +% REFERENCES: +% ] Fritsch, F. N. and R. E. Carlson, +% "Monotone Piecewise Cubic Interpolation.", +% SIAM Journal on Numerical Analysis. Vol. 17, 1980, pp.238–246. +function res = closing_ShapePreserving(fusionResult, inputSegmentations) + % get the centroid of the largest segmentation + largest = algorithm_Largest(inputSegmentations); + cn = regionprops(largest, "Centroid").Centroid; + s = size(fusionResult); + + % find locations of outline pixels + [idxy, idxx] = find(fusionResult); % in cart coord + [idxth, idxr] = cart2pol(idxx-cn(1),idxy-cn(2)); % in polar coord (theta, rho) + + % sort pixel locations by angular position with reference to largest segmentation center center + [idxth, sortmap] = sort(idxth,"ascend"); + idxr = idxr(sortmap); + + % query points for interpolation + nQueryPoints = 100000; + newth = linspace(-pi, pi, nQueryPoints).'; + + % Shape-Preserving Piecewise Cubic Hermite interpolation (PCHIP) + newr = interp1(idxth,idxr,newth,"pchip"); + + % remove NaNs, interpolation method produce NaNs for + % query points outside the domain + nanp = ~isnan(newr); + newr = newr(nanp); + newth = newth(nanp); + + % construct output image + [newx, newy] = pol2cart(newth,newr); + res = false(s); + res(sub2ind(s,round(newy+cn(2)),round(newx+cn(1)))) = true; + + % fill holes and get perimeter to remove interior pixels + res = imfill(res, 'holes'); + res = bwperim(res); +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/computeFusion.m b/api/utils/functions/segmentations/multiple/computeFusion.m index e8d829d..6d6c3e9 100644 --- a/api/utils/functions/segmentations/multiple/computeFusion.m +++ b/api/utils/functions/segmentations/multiple/computeFusion.m @@ -3,29 +3,54 @@ % NAME: TDSFT (version 1.0) % % PARAMETERS: -% segmentations (Cell array: [1, nSeg], Cells: matrix [height, width]): -% the segmentations to be fused. -% algorithm (string): -% the algorithm to be used for the fusion process. -% varargin (Cell array: [1, nVarargin]): -% the additional parameters of the algorithm. -% (Some algorithms require additional parameters) +% segmentations (Cell array: [1, nSeg], Cells: matrix [height, width]): +% the segmentations to be fused. +% fusionAlgorithm (string): +% the algorithm to be used for the fusion process. +% (The algorithm must be in the folder "algorithms") +% closeLineAlgoirthm (string): +% the method to be used to close the resulting segmentation if it +% is not already a close line. +% (The algorithm must be in the folder "close-algorithms") +% varargin (Cell array: [1, nVarargin]): +% the additional parameters of the algorithm. +% (Some algorithms require additional parameters) % % OUTPUT: -% resSeg (Matrix [height, width]): -% the resulting segmentation of the fusion process. +% resSeg (Matrix [height, width]): +% the resulting segmentation of the fusion process. +% +% THROWS: +% TDSFT:algorithms: +% if the number of input segmentations is less than 2. % % DESCRIPTION: -% Fuse the segmentations using the specified algorithm. -function resSeg = computeFusion(segmentations, algorithm, varargin) +% Fuse the segmentations using the specified algorithm. +function resSeg = computeFusion(segmentations, fusionAlgorithm, closeLineAlgorithm, varargin) + printName = erase(fusionAlgorithm, 'algorithm_'); + printName = fromCamelCaseToSpacedString(printName); + fprintf('Computing %s...\n', printName); + + % Throw an exception if the number of segmentations is less than 2. + if length(segmentations) < 2 + throw(MException('TDSFT:algorithms', 'The number of segmentations must be at least 2')); + end + try - fun = str2func(algorithm); - if nargin > 2 - resSeg = fun(segmentations, varargin); + fun = str2func(fusionAlgorithm); + if nargin > 3 + resSeg = fun(segmentations, varargin{:}); else resSeg = fun(segmentations); end - + + % Check if the resulting segmentation is already a close line. + % If not, use the specified method to close it + segFill = imfill(resSeg, 'holes'); + if isequal(segFill, resSeg) + fun = str2func(closeLineAlgorithm); + resSeg = fun(resSeg, segmentations); + end catch ME rethrow(ME); end diff --git a/api/utils/functions/segmentations/multiple/getAverageSegmentation.m b/api/utils/functions/segmentations/multiple/getAverageSegmentation.m index 9ac1d57..259c4c4 100644 --- a/api/utils/functions/segmentations/multiple/getAverageSegmentation.m +++ b/api/utils/functions/segmentations/multiple/getAverageSegmentation.m @@ -3,93 +3,94 @@ % NAME: TDSFT (version 1.0) % % PARAMETERS: -% segmentations (Cell array: [1, raters], Cells: matrix [height, width]): -% array containing the segmentations to fuse. -% startSegmentation (Integer || Matrix: [height, width]): -% the segmentation to start the fusion with. -% It can be: -% - an integer: it is the index of the segmentation to start the fusion with -% (the segmentation from which to start is one of input); -% - a matrix: it is the segmentation to start the fusion with. +% segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing the segmentations to fuse. +% target (Integer || Matrix: [height, width]): +% the target segmentation from which the average is computed. +% It can be: +% - integer: the index of one of the input segmentations; +% - matrix: the segmentation to start the fusion with. % % OUTPUT: -% averageSeg(Matrix: [height, width]): -% the average segmentation. +% averageSeg (Matrix: [height, width]): +% the average segmentation. % % THROWS: -% TDSFT:algorithms: -% if the input array `segmentations` is empty. +% TDSFT:algorithms: +% if the input array 'segmentations' is empty. % % DESCRIPTION: -% The average segmentation is computed by averaging the segmentations as follows: -% For each pixel with value 1 of the start segmentation is searched the closest pixel -% with value 1 in the other segmentations. Then it is computed the average pixel: -% - if the segmentations are 2, the average pixel is the middle point between the two pixels; -% - if the segmentations are more than 2, the average pixel is the centroid of the pixels. -function averageSeg = getAverageSegmentation(segmentations, startSegmentation) +% The average segmentation is computed by averaging the segmentations as follows: +% For each pixel with value 1 of the target segmentation is searched the closest pixel +% with value 1 in the other segmentations. Then it is computed the average pixel: +% - if the segmentations are 2, the average pixel is the middle point between the two pixels; +% - if the segmentations are more than 2, the average pixel is the centroid of the pixels. +function averageSeg = getAverageSegmentation(segmentations, target) if isempty(segmentations) throw(MException("TDSFT:algorithms", "Segmentations array empty")); end + % Number of input segmentations. nSeg = length(segmentations); - % If there is only one segmentation, return it + + % If there is only one segmentation, return it. if nSeg == 1 averageSeg = segmentations{1}; return; end - - % Get the main segmentation - % If startSegmentation is a number, get the segmentation from the array `segmentations` - if isnumeric(startSegmentation) - mainSeg = segmentations{startSegmentation}; + + % Number of segmentations to consider in the average process. + % If startSegmentation is a number, the number of segmentations is the length of the input array. + % Else, the number of segmentations is the length of the input array + 1. + arrayLength = nSeg; + + % Get the main segmentation. + % If startSegmentation is a number, get the segmentation from the array 'segmentations'. + if isnumeric(target) + mainSeg = segmentations{target}; else - mainSeg = startSegmentation; + mainSeg = target; + arrayLength = arrayLength + 1; end - % Get the size of the main segmentation + % Get the size of the main segmentation. [height, width] = size(mainSeg); - % Create the output segmentation + % Preallocate the output segmentation matrix. averageSeg = zeros(height, width); - % For each pixel of the main segmentation + % For each pixel of the main segmentation. try for i = 1:height for j = 1:width - - % If the pixel is a 1 then search the nearest pixel in the other segmentations + % If the pixel value is 1 then, for every other segmentation, search for the nearest pixel. if mainSeg(i, j) == 1 - % Create an array containing the nearest pixels of the other segmentations - % If the main segmentation is not inside `segmentations`, the length of the array - % is the number of segmentations + 1 - arrayLength = nSeg; - if ~isnumeric(startSegmentation) - arrayLength = arrayLength + 1; - end + % Array containing the pixel himself and the nearest pixels of the other segmentations. nearestPixels = zeros(arrayLength, 2); - + % Current index of the nearestPixels array. idx = 1; - - % Add the pixel of the main segmentation to the array + % Add the pixel of the main segmentation to the array. nearestPixels(idx, :) = [i, j]; idx = idx + 1; for k = 1:nSeg - % If the segmentation is the start segmentation, skip it - if isnumeric(startSegmentation) && k == startSegmentation + % If the segmentation is the start segmentation, skip it. + if isnumeric(target) && k == target continue; end % The index of the array is the index of the segmentation + 1 because the first - % element is the main segmentation pixel - nearestPixels(idx, :) = getNearestNonZeroPixel(segmentations{k}, i, j); + % element is the main segmentation pixel. + [row, col] = getNearestNonZeroPixel(segmentations{k}, i, j); + nearestPixels(idx, :) = [row, col]; idx = idx + 1; end - % Get the centroid of the points + + % Get the centroid of the points. [row, col] = getCentroid(nearestPixels); - % Set the pixel to 1 + % Set the pixel to 1. averageSeg(row, col) = 1; end end diff --git a/api/utils/functions/segmentations/multiple/getCommonArea.m b/api/utils/functions/segmentations/multiple/getCommonArea.m index 882c7cf..7d79313 100644 --- a/api/utils/functions/segmentations/multiple/getCommonArea.m +++ b/api/utils/functions/segmentations/multiple/getCommonArea.m @@ -1,23 +1,27 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzo.drudi5@studio.unibo.it) +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) % DATE: April 11, 2023 % NAME: TDSFT (version 1.0) % % PARAMETERS: -% overlap: the overlap between the binary segmentations -% nSeg: the number of segmentations +% overlap(Matrix: [height, width]: +% the overlap between the binary filled segmentations. +% nSeg(int): +% the number of segmentations. % % OUTPUT: -% seg: the segmentation of the common area -% area: the common area between all the segmentations +% seg((Matrix: [height, width]): +% the segmentation of the common area. +% area: +% the common area between all the segmentations. % % DESCRIPTION: -% Get the common area between all the segmentations. -% The common area is the area selected by all the segmentations. -% The common area is computed getting the pixel where value == number of segmentations. -function [seg, area] = getCommonArea(overlap, nSeg) +% Get the common area between all the segmentations. +% The common area is the area covered by all the segmentations. +function [area, perimeter] = getCommonArea(overlap, nSeg) area = overlap; [m, n] = size(overlap); + % If the pixel is covered by all the segmentations, it is set to 1. for i = 1:m for j = 1:n if area(i,j) == nSeg @@ -28,5 +32,5 @@ end end - seg = bwperim(area); + perimeter = bwperim(area); end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/getFilledSegmentations.m b/api/utils/functions/segmentations/multiple/getFilledSegmentations.m new file mode 100644 index 0000000..4c98e52 --- /dev/null +++ b/api/utils/functions/segmentations/multiple/getFilledSegmentations.m @@ -0,0 +1,21 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% segmentations(Cell array: [1, nSeg] (Cells: matrix [height, width]): +% the segmentations to fill. +% +% OUTPUT: +% filledSegmentations(Cell array: [1, nSeg] (Cells: matrix [height, width]): +% the filled input segmentations. +% +% DESCRIPTION: +% Fill (make dense) all the input segmentations. +function filledSegmentations = getFilledSegmentations(segmentations) + % Preallocate the output. + filledSegmentations = cell(1, length(segmentations)); + for i = 1:length(segmentations) + filledSegmentations{i} = imfill(segmentations{i}, "holes"); + end +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/getLargestArea.m b/api/utils/functions/segmentations/multiple/getLargestArea.m new file mode 100644 index 0000000..29b3111 --- /dev/null +++ b/api/utils/functions/segmentations/multiple/getLargestArea.m @@ -0,0 +1,26 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% overlappedSegmentations(Matrix: [height, width]: +% the sum (overlap) of all the segmentations. +% +% OUTPUT: +% largestSegmentation(Matrix: [height, width]): +% the perimeter of the total area covered by the segmentations. +% largestArea(Matrix: [height, width]): +% the total area covered by the segmentations. +% +% DESCRIPTION: +% Return the largest possible area (and its perimeter) as a matrix height * width. +% The largest area is the total area covered by the segmentations. +function [area, perimeter] = getLargestArea(overlappedSegmentations) + + % Binarize the overlapped segmentations. + overlappedSegmentations = imbinarize(overlappedSegmentations); + + % Fill the resulting segmentation and get the perimeter. + area = imfill(overlappedSegmentations, "holes"); + perimeter = bwperim(area); +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/getLargestSegmentation.m b/api/utils/functions/segmentations/multiple/getLargestSegmentation.m deleted file mode 100644 index 824596b..0000000 --- a/api/utils/functions/segmentations/multiple/getLargestSegmentation.m +++ /dev/null @@ -1,23 +0,0 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) -% DATE: April 12, 2023 -% NAME: TDSFT (version 1.0) -% -% PARAMETERS: -% overlappedSegmentations(Matrix: [height, width]: -% the sum (overlap) of all the segmentations. -% -% OUTPUT: -% largestSegmentation(Matrix: [height, width]): -% the largest segmentation. -% -% DESCRIPTION: -% Return the largest possible segmentation as a matrix height * width. -% The largest segmentation is the smallest segmentation possible which contains each input segmentation. -function largestSegmentation = getLargestSegmentation(overlappedSegmentations) - % binarize the overlapped segmentations - overlappedSegmentations = imbinarize(overlappedSegmentations); - - % fill the resulting segmentation and get the perimeter - filledOverlap = imfill(overlappedSegmentations, "holes"); - largestSegmentation = bwperim(filledOverlap); -end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/getSegmentationsMask.m b/api/utils/functions/segmentations/multiple/getSegmentationsMask.m new file mode 100644 index 0000000..f0137cc --- /dev/null +++ b/api/utils/functions/segmentations/multiple/getSegmentationsMask.m @@ -0,0 +1,21 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 8, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% segmentations (Cell array: [1, raters], Cells: matrix [height, width]): +% array containing of which compute the mask. +% +% OUTPUT: +% mask(Matrix: [height, width]): +% the mask of the segmentations. +% +% +% DESCRIPTION: +% Mask of the segmentations used by the close line algorithms. +% In order to obtain faster and more accurate segmentation results, is important to specify an initial contour position +% that is close to the desired object boundaries. For that reason we use as mask the largest segmentation area. +function mask = getSegmentationsMask(segmentations) + mask = algorithm_Largest(segmentations); + mask = imfill(mask, "holes"); +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/getSmallestSegmentation.m b/api/utils/functions/segmentations/multiple/getSmallestSegmentation.m deleted file mode 100644 index d189387..0000000 --- a/api/utils/functions/segmentations/multiple/getSmallestSegmentation.m +++ /dev/null @@ -1,19 +0,0 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) -% DATE: April 12, 2023 -% NAME: TDSFT (version 1.0) -% -% PARAMETERS: -% overlappedSegmentations (Matrix [height, width]): -% the sum (overlap) of all the segmentations. -% nSeg (int): -% the number of segmentations -% -% OUTPUT: -% smallestSegmentation (Matrix [height, width]): -% the smallest segmentation. -% -% DESCRIPTION: -% The smallest segmentation is the perimeter of the area covered by every segmentation (the common area between every segmentation). -function smallestSegmentation = getSmallestSegmentation(overlappedSegmentations, nSeg) - [smallestSegmentation, ~] = getCommonArea(overlappedSegmentations, nSeg); -end \ No newline at end of file diff --git a/api/utils/functions/segmentations/multiple/overlapSegmentations.m b/api/utils/functions/segmentations/multiple/overlapSegmentations.m index 156c419..001963a 100644 --- a/api/utils/functions/segmentations/multiple/overlapSegmentations.m +++ b/api/utils/functions/segmentations/multiple/overlapSegmentations.m @@ -3,36 +3,36 @@ % NAME: TDSFT (version 1.0) % % PARAMETERS: -% segmentations (Cell array: [1, nSeg] (Cells: matrix [height, width]): -% the segmentations to overlap. +% segmentations (Cell array: [1, nSeg] (Cells: matrix [height, width]): +% the segmentations to overlap. % % OUTPUT: -% overlap (Matrix [height, width]): -% the overlapped segmentations. +% overlap (Matrix [height, width]): +% the overlapped segmentations. % % THROWS: -% TDSFT:algorithms: -% if the input is empty. +% TDSFT:algorithms: +% if the input is empty. % % DESCRIPTION: -% Overlap (sum) the input segmentations and return the resulting matrix. +% Overlap (sum) the input segmentations and return the resulting matrix. function overlap = overlapSegmentations(segmentations) - % check if the input is empty + % Check if the input is empty. if isempty(segmentations) throw(MException("TDSFT:algorithms", "Segmentations array empty")); end - % if there is only one segmentation, return it + % If there is only one segmentation, return it. if length(segmentations) == 1 overlap = segmentations{1}; return; end - % initialize the overlap + % Initialize the overlap. [height, width] = size(segmentations{1}); - overlap = zeros(height, width, 'uint8'); + overlap = zeros(height, width, "uint8"); - % overlap all the segmentations + % Overlap all the segmentations. for i=1:length(segmentations) seg = uint8(segmentations{i}); overlap = overlap + seg; diff --git a/api/utils/functions/segmentations/single/getBinaryImageBackground.m b/api/utils/functions/segmentations/single/getBinaryImageBackground.m new file mode 100644 index 0000000..284b7fe --- /dev/null +++ b/api/utils/functions/segmentations/single/getBinaryImageBackground.m @@ -0,0 +1,27 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 12, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% img (Matrix [height, width]): +% a black and white image. +% +% OUTPUT: +% bg (int): +% get the background of a black and white image. +% 1 if the background is white, 0 otherwise. +% +% DESCRIPTION: +% Returns the background color of a binary black and white image. +function bg = getBinaryImageBackground(img) + [h, w] = size(img); + + % Get the sum of the first and last row and column. + firstRow = sum( img(1, :) ); + lastRow = sum( img(h, :) ); + firstCol = sum( img(:, 1) ); + lastCol = sum( img(:, w) ); + + % Since objects can't touch the border, if they are all 0, the background is white. + bg = firstRow || lastRow || firstCol || lastCol; +end \ No newline at end of file diff --git a/api/utils/functions/segmentations/single/getNearestNonZeroPixel.m b/api/utils/functions/segmentations/single/getNearestNonZeroPixel.m index ad5ed08..df12b88 100644 --- a/api/utils/functions/segmentations/single/getNearestNonZeroPixel.m +++ b/api/utils/functions/segmentations/single/getNearestNonZeroPixel.m @@ -1,4 +1,4 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzo.drudi5@studio.unibo.it) +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) % DATE: April 23, 2023 % NAME: TDSFT (version 1.0) % @@ -17,33 +17,39 @@ % the col (y coordinate) of the nearest non-zero pixel. % % THROWS: -% TDSFT:algorithms: -% if the pixel index is out of bounds. -% TDSFT:algorithms: -% if no non-zero pixel is found. +% TDSFT:algorithms: +% if the pixel index is out of bounds. +% TDSFT:algorithms: +% if no non-zero pixel is found. % % DESCRIPTION: -% Get the nearest non-zero pixel from the given pixel index. +% Get the nearest non-zero pixel from the given pixel index. function [row, col] = getNearestNonZeroPixel(segmentation, startRow, startCol) [height, width] = size(segmentation); + %Check if the pixel index is out of bounds. if startRow < 1 || startRow > height || startCol < 1 || startCol > width throw(MException("TDSFT:algorithms", "Pixel index out of bounds")); end - % If the pixel is already non-zero, return it + % If the pixel is already non-zero, return it. if segmentation(startRow, startCol) ~= 0 row = startRow; col = startCol; return; end + % Use the bwdist function to get the nearest non-zero pixel. [~, nearestPixelArray] = bwdist(segmentation); + + % Get the linear index of the nearest non-zero pixel. nearestPixelLinearIndex = nearestPixelArray(startRow, startCol); + % Check if no non-zero pixel is found. if nearestPixelLinearIndex == 0 throw(MException("TDSFT:algorithms", "No non-zero pixel found")); end + % Convert the linear index to the row and col index. [row, col] = ind2sub([height, width], nearestPixelLinearIndex); end \ No newline at end of file diff --git a/api/utils/functions/segmentations/single/getOnePixelSegmentation.m b/api/utils/functions/segmentations/single/getOnePixelSegmentation.m index a6bbf50..73b8907 100644 --- a/api/utils/functions/segmentations/single/getOnePixelSegmentation.m +++ b/api/utils/functions/segmentations/single/getOnePixelSegmentation.m @@ -5,46 +5,33 @@ % PARAMETERS: % seg (Matrix [height, width]): % black and white segmentation. -% internal (boolean): -% if true (1), is used the internal line (when the segmentation is of more than one pixel). -% middle (boolean): -% if true (1), is used the middle line (when the segmentation is of more than one pixel). +% line (string): +% the line to use if the segmentation is of more than one pixel. +% (see onePixelSegmentation.m for more details) % % OUTPUT: % opSeg (Matrix [height, width]): % the one-pixel segmentation. -% -% THROWS: -% MException:processImage -% if internal and middle are both true (1). % % DESCRIPTION: -% Gets the one-pixel segmentation. Often the line of the segmentation is not one pixel, but more. +% Gets the one-pixel segmentation. Often the line of the segmentation is not of one pixel, but of more. % If the segmentation is of more than one pixel, it is possible to get the external, internal or middle line. -% The external line is the default one. -% ATTENTION: Cannot be used both internal and middle line. -function opSeg = getOnePixelSegmentation(seg, internal, middle) - % Check the input - if internal && middle - throw(MException("TDSFT:processImage", "Cannot be used both internal and middle line")); - end - - % Fill the holes +% (see OnePixelLineTypes.m for more details) +function opSeg = getOnePixelSegmentation(seg, line) + % Fill the holes. segFill = imfill(seg, "holes"); - % Get the internal perimeter - if internal - extSeg = bwperim(segFill); - opSeg = bwperim(seg) - extSeg; - - % Get the middle perimeter - elseif middle - opSeg = bwskel(seg); - opSeg = imfill(opSeg, "holes"); - opSeg = bwperim(opSeg); - - % Get the external perimeter (default) - else - opSeg = bwperim(segFill); + % Get the one-pixel segmentation using the specified line type. + switch line + case "internal" + extSeg = bwperim(segFill); + opSeg = bwperim(seg) - extSeg; + case "middle" + opSeg = bwskel(seg); + opSeg = imfill(opSeg, "holes"); + opSeg = bwperim(opSeg); + % Default is external line. + otherwise + opSeg = bwperim(segFill); end end \ No newline at end of file diff --git a/api/utils/functions/segmentations/single/imTo8bit.m b/api/utils/functions/segmentations/single/imTo8bit.m index e0111b9..2c87cc8 100644 --- a/api/utils/functions/segmentations/single/imTo8bit.m +++ b/api/utils/functions/segmentations/single/imTo8bit.m @@ -15,25 +15,30 @@ % if the image is not converted to 8 bit. % % DESCRIPTION: -% Converts an image to 8-bit format. +% Converts an image to 8-bit format. +% Supported formats: 12-bit, 16-bit, 32-bit, 64-bit. function cImg = imTo8bit(img) - if isa(img, ImagesStoringMethods.INT_16.type) + if isa(img, ImagesStoringMethods.INT_16.string) + % 16-bit if max(img(:)) > 2^12 cImg = 255.*double(img)./(2^16-1); + % 12-bit else cImg = 255.*double(img)./(2^12-1); end - elseif isa(img, ImagesStoringMethods.INT_32.type) + % 32-bit + elseif isa(img, ImagesStoringMethods.INT_32.string) cImg = 255.*double(img)./(2^32-1); - elseif isa(img, ImagesStoringMethods.INT_64.type) + % 64-bit + elseif isa(img, ImagesStoringMethods.INT_64.string) cImg = 255.*double(img)./(2^64-1); else cImg = img; end - % check if the storing method of the converted image is `uint8` + % check if the storing method of the converted image is 'uint8' % else throws an error - if ~isa(cImg, ImagesStoringMethods.INT_8.type) + if ~isa(cImg, ImagesStoringMethods.INT_8.string) throw(MException("TDSFT:processImage", "Image not converted to 8 bit")); end end \ No newline at end of file diff --git a/api/utils/functions/segmentations/single/isSegmentationClosed.m b/api/utils/functions/segmentations/single/isSegmentationClosed.m index 5e8777b..0ef4258 100644 --- a/api/utils/functions/segmentations/single/isSegmentationClosed.m +++ b/api/utils/functions/segmentations/single/isSegmentationClosed.m @@ -6,8 +6,12 @@ % img (Matrix [height, width]): % black and white image. % flag (boolean): -% true if a dense object is present and you want more tests, false otherwise. -% It adds more check to the segmentation to be able to recognize open lines and close dense lines. +% true if a dense object is present and more tests are required, false otherwise. +% It adds more check to the segmentation to be able to better recognize open lines and close dense lines. +% Be careful, it is difficult to recognize a dense object from an open line of more than one pixel. +% Since an open line of more than one pixel is, by definition, a dense object we use some statistics to +% recognize a dense object from an open line of more than one pixel. +% So if you are not sure that the object is dense, set dense to false. % % OUTPUT: % check: @@ -18,11 +22,17 @@ % Checks if the segmentation is closed or not. Returns true if it is closed. function check = isSegmentationClosed(img, flag) imgFill = imfill(img, "holes"); - dif1 = imgFill - img; - if sum(sum(dif1)) ~= 0 + + % If the image can be filled it means the object in the image is not dense + % and there is a hole to fill so the segmentation is closed. + if ~isequal(img, imgFill) check = true; else - % Check if it is an open segmentation of 1 pixel + % Here there are two options: + % 1) The object in the image is a dense object. + % 2) The object in the image is an open line of more than one pixel. + + % Check if it is an open segmentation of 1 pixel. perim = bwperim(img); check = ~isequal(perim, img); @@ -30,7 +40,7 @@ return; end - % If it is a dense object make an easy empirical test. + % If dense option is true do a statistical test to check to recognize a dense object from an open line of more than one pixel. % ATTENTION: it is not a perfect check since it is not possible to separate a dense object (close dense line) % from a line of more than one pixel. fillCells = nnz(imgFill); diff --git a/api/utils/functions/segmentations/single/isWhiteBackground.m b/api/utils/functions/segmentations/single/isWhiteBackground.m deleted file mode 100644 index af813b9..0000000 --- a/api/utils/functions/segmentations/single/isWhiteBackground.m +++ /dev/null @@ -1,25 +0,0 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) -% DATE: April 12, 2023 -% NAME: TDSFT (version 1.0) -% -% PARAMETERS: -% img (Matrix [height, width]): -% black and white image. -% -% OUTPUT: -% check: -% true (1) => white -% false (0) => black) -% -% DESCRIPTION: -% Returns the background of a black and white image. -function check = isWhiteBackground(img) - [h, w] = size(img); - - firstRow = sum( img(1, :) ); - lastRow = sum( img(h, :) ); - firstCol = sum( img(:, 1) ); - lastCol = sum( img(:, w) ); - - check = firstRow || lastRow || firstCol || lastCol; -end \ No newline at end of file diff --git a/api/utils/functions/segmentations/single/processImage.m b/api/utils/functions/segmentations/single/processImage.m index 45a8e4f..f0f5fa3 100644 --- a/api/utils/functions/segmentations/single/processImage.m +++ b/api/utils/functions/segmentations/single/processImage.m @@ -7,11 +7,15 @@ % the image to convert. % dense (boolean): % true (1) if a dense object is present and more test are wanted/neeeded, false (0) otherwise. -% It adds more check to the segmentation to be able to recognize open lines and close dense lines. -% internal (boolean): -% if true (1), the internal line is required (when segmentation of more than one pixel). -% middle (boolean): -% if true (1), the middle line is required (when segmentation of more than one pixel). +% It adds more check to the segmentation to be able to better recognize open lines and close dense lines. +% Be careful, it is difficult to recognize a dense object from an open line of more than one pixel. +% Since an open line of more than one pixel is, by definition, a dense object we use some statistics to +% recognize a dense object from an open line of more than one pixel. +% So if you are not sure that the object is dense, set dense to false. +% If dense is true only external line can be used since dense object have no middle or internal line! +% line (string): +% the line to use if the segmentation is more than one pixel (external, middle or internal). +% (see onePixelLineTypes.m for more details) % % OUTPUT: % bw (Matrix [height, width]): @@ -24,20 +28,31 @@ % % THROWS: % TDSFT:processImage: +% throwed if the segmentation dense is true and line is not external. +% TDSFT:processImage: % throwed if the segmentation is an open line. % TDSFT:processImage: % throwed if the segmentation is empty. % % DESCRIPTION: -% - Converts the image to 8 bit; -% - Convertes the image to black white; -% - Get the segmentation (perimeter) of the object contained in the image; -% - CANNOT BE TRUE BOTH DENSE AND INTERNAL (or MIDDLE), if the object is dense there is no internal line. -% - CANNOT BE TRUE BOTH INTERNAL AND MIDDLE, only one option for the segmentation of more than one pixel is allowed. -% - The default option for line segmentation is the external line. -function [bw, seg, wrn] = processImage(img, dense, internal, middle) - % Check the channels of the image - % If the image has more than one channel but is not an rgb image, use only the first channel +% - Converts the image to 8-bit; +% - Convertes the image to black and white; +% - Convert to the required configuration (black background and white segmentation); +% - If there are more than one object, select the largest one; +% - Get the one pixel segmentation of the object contained in the image using the specified line; + +% ( If dense is true only external line can be used. If the object is dense there is no middle +% or internal line!. +function [bw, seg, wrn] = processImage(img, dense, line) + %Ccheck the parameters. + % If dense is true only external line can be used. + if dense && ~strcmp(line, "external") + throw(MException("TDSFT:processImage", "Dense object can only have external line")); + end + + % Check the channels of the image. + % If the image has more than one channel but is not an rgb image, use only the first channel. + % Use the wrn variable to warn the user about that. if size(img,3) ~= 1 && size(img,3) ~= 3 img = img(:,:,1); wrn = true; @@ -46,44 +61,45 @@ end try - % Convert to 8 bit image + % Convert to 8 bit image. cImg = imTo8bit(img); - % Convert to grayscale + % Convert to grayscale. grayImg = im2gray(cImg); - % Convert to bw + % Convert to bw. bw = imbinarize(grayImg); - % Convert to black background if needed - if isWhiteBackground(bw) + % Convert to black background if needed. + % We want the background to be black and the segmentation to be white. + if getBinaryImageBackground(bw) bw = ~bw; end - % If there are more than one object, select the largest one + % If there are more than one object, select the largest one. [~, numBlobs] = bwlabel(bw); if numBlobs > 1 bw = bwareafilt(bw, 1); end - % Check line closing + % Check if the segmentation is an open line. if ~isSegmentationClosed(bw, dense) throw(MException("TDSFT:processImage", "Not closed segmentation uploaded")); end - % Check if the segmentation is empty + % Check if the segmentation is empty. if ~sum(bw(:)) throw(MException("TDSFT:processImage", "Empty segmentation uploaded")); end - % Check if the segmentation is already single pixel + % Check if the segmentation is already of one pixel. if isequal(bw, bwperim(bw)) seg = bw; return; end - seg = getOnePixelSegmentation(bw, internal, middle); - + % Get the one pixel segmentation. + seg = getOnePixelSegmentation(bw, line); catch ME rethrow(ME); end diff --git a/api/utils/functions/strings/getAlgorithmFullName.m b/api/utils/functions/strings/fromCamelCaseToFullNameAlgorithm.m similarity index 52% rename from api/utils/functions/strings/getAlgorithmFullName.m rename to api/utils/functions/strings/fromCamelCaseToFullNameAlgorithm.m index 5e4d331..00628ea 100644 --- a/api/utils/functions/strings/getAlgorithmFullName.m +++ b/api/utils/functions/strings/fromCamelCaseToFullNameAlgorithm.m @@ -4,16 +4,18 @@ % % PARAMETERS: % algorithm (string): -% algorithm name. +% an algorithm name (already in camelcase). +% (e.g. 'AverageTargetLargest') % % OUTPUT: % fullName (string): % fullName of the algorithm. +% (e.g. 'algorithm_AverageTargetLargest') % % DESCRIPTION: -% Get the full name of the specified algorithm. +% Get the full name of the camel case in. % Fullname is needed in the standalone version to recognize algorithm files. -% Example: 'algorithm_' + 'TDSFT' = 'algorithm_TDSFT'. -function fullName = getAlgorithmFullName(algorithm) +% Example: 'algorithm_' + 'AverageTargetLargest' = 'algorithm_AverageTargetLargest'. +function fullName = fromCamelCaseToFullNameAlgorithm(algorithm) fullName = strcat("algorithm_", algorithm); end \ No newline at end of file diff --git a/api/utils/functions/strings/fromCamelCaseToSpacedString.m b/api/utils/functions/strings/fromCamelCaseToSpacedString.m index 5e55233..795392f 100644 --- a/api/utils/functions/strings/fromCamelCaseToSpacedString.m +++ b/api/utils/functions/strings/fromCamelCaseToSpacedString.m @@ -5,10 +5,12 @@ % PARAMETERS: % s (string): % the camel case string. +% (e.g. 'ThisIsACamelCaseString'). % % OUTPUT: % result (string): % the spaced string. +% (e.g. 'This Is A Spaced String'). % % DESCRIPTION: % Convert from camelcase string to spaced string. diff --git a/api/utils/functions/strings/fromSpacedStringToCamelCase.m b/api/utils/functions/strings/fromSpacedToCamelCaseString.m similarity index 55% rename from api/utils/functions/strings/fromSpacedStringToCamelCase.m rename to api/utils/functions/strings/fromSpacedToCamelCaseString.m index 3da42a0..ea5dfa6 100644 --- a/api/utils/functions/strings/fromSpacedStringToCamelCase.m +++ b/api/utils/functions/strings/fromSpacedToCamelCaseString.m @@ -3,16 +3,18 @@ % NAME: TDSFT (version 1.0) % % PARAMETERS: -% s (string): -% the spaced string. +% s (string): +% string with spaces. +% (e.g. 'This Is A String With Spaces') % % OUTPUT: -% result (string): -% the camel case string. +% result (string): +% the string where spaces are replaced by camel case separation. +% (e.g. 'ThisIsACamelCaseString'). % % DESCRIPTION: % Convert from spaced string to camelcase string. % Example: 'This Is The Example String' = 'ThisIsTheExampleString'. -function result = fromSpacedStringToCamelCase(s) +function result = fromSpacedToCamelCaseString(s) result = regexprep(s, "([a-z]) ([A-Z])", "$1$2"); end \ No newline at end of file diff --git a/api/utils/functions/strings/fromSpacedToClosingAlgorithmFullName.m b/api/utils/functions/strings/fromSpacedToClosingAlgorithmFullName.m new file mode 100644 index 0000000..4e00772 --- /dev/null +++ b/api/utils/functions/strings/fromSpacedToClosingAlgorithmFullName.m @@ -0,0 +1,21 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: May 13, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% s (string): +% the spaced string. +% (e.g. 'Chan Vese'). +% +% OUTPUT: +% result (string): +% the camelcase fullname. +% (e.g. 'closing_ChanVese'). +% +% DESCRIPTION: +% Convert from spaced string to closing algorithm fullname. +% Example: 'Chan Vese' = 'closing_ChanVese'. +function result = fromSpacedToClosingAlgorithmFullName(s) + result = fromSpacedToCamelCaseString(s); + result = strcat("closing_", result); +end \ No newline at end of file diff --git a/api/utils/functions/strings/fromSpacedToFullName.m b/api/utils/functions/strings/fromSpacedToFullName.m deleted file mode 100644 index b05a0a6..0000000 --- a/api/utils/functions/strings/fromSpacedToFullName.m +++ /dev/null @@ -1,19 +0,0 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) -% DATE: April 20, 2023 -% NAME: TDSFT (version 1.0) -% -% PARAMETERS: -% algorithm (string): -% algorithm spaced name. -% -% OUTPUT: -% fullName (string): -% fullName of the algorithm. -% -% DESCRIPTION: -% Get the full name of the specified algorithm. -% Example: 'Largest Segmentation' = 'algorithm_LargestSegmentation'. -function fullName = fromSpacedToFullName(algorithm) - fullName = fromSpacedStringToCamelCase(algorithm); - fullName = getAlgorithmFullName(fullName); -end \ No newline at end of file diff --git a/api/utils/functions/strings/fromSpacedToFullNameAlgorithm.m b/api/utils/functions/strings/fromSpacedToFullNameAlgorithm.m new file mode 100644 index 0000000..a501478 --- /dev/null +++ b/api/utils/functions/strings/fromSpacedToFullNameAlgorithm.m @@ -0,0 +1,21 @@ +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: April 20, 2023 +% NAME: TDSFT (version 1.0) +% +% PARAMETERS: +% algorithm (string): +% an algorithm spaced name. +% (e.g. 'Average Target Largest') +% +% OUTPUT: +% fullName (string): +% fullName of the input algorithm. +% (e.g. 'algorithm_AverageTargetLargest') +% +% DESCRIPTION: +% Get, from a spaced algorithm name, its fullname. +% Example: 'Average Target Largest' = 'algorithm_AverageTargetLargest'. +function fullName = fromSpacedToFullNameAlgorithm(algorithm) + fullName = fromSpacedToCamelCaseString(algorithm); + fullName = fromCamelCaseToFullNameAlgorithm(fullName); +end \ No newline at end of file diff --git a/api/utils/functions/strings/removeFileExtension.m b/api/utils/functions/strings/removeFileExtension.m index 3c5ddfb..ceb3554 100644 --- a/api/utils/functions/strings/removeFileExtension.m +++ b/api/utils/functions/strings/removeFileExtension.m @@ -4,14 +4,14 @@ % % PARAMETERS: % filename (string): -% the filename. +% a filename. % % OUTPUT: % result (string): % the filename without the extension. % % DESCRIPTION: -% Get the filename without extension. +% Returns the filename without the extension. % Example: 'filename.m' = 'filename'. function result = removeFileExtension(filename) expression = ".*(?=\.)"; diff --git a/app/app_advancedFeature.mlapp b/app/app_advancedFeature.mlapp deleted file mode 100644 index 7b34caf..0000000 Binary files a/app/app_advancedFeature.mlapp and /dev/null differ diff --git a/app/app_advancedFeatures.mlapp b/app/app_advancedFeatures.mlapp new file mode 100644 index 0000000..468b8b1 Binary files /dev/null and b/app/app_advancedFeatures.mlapp differ diff --git a/app/app_image.mlapp b/app/app_image.mlapp index dd5c317..7754bbf 100644 Binary files a/app/app_image.mlapp and b/app/app_image.mlapp differ diff --git a/app/app_input.mlapp b/app/app_input.mlapp index a2434de..be12ca8 100644 Binary files a/app/app_input.mlapp and b/app/app_input.mlapp differ diff --git a/app/app_main.mlapp b/app/app_main.mlapp index 2835790..6831d27 100644 Binary files a/app/app_main.mlapp and b/app/app_main.mlapp differ diff --git a/app/app_result.mlapp b/app/app_result.mlapp index b7d5bd3..11ec73a 100644 Binary files a/app/app_result.mlapp and b/app/app_result.mlapp differ diff --git a/runStartup.m b/runStartup.m index 357b871..6407476 100644 --- a/runStartup.m +++ b/runStartup.m @@ -1,5 +1,5 @@ -% AUTHOR: Lorenzo Drudi (E-mail: lorenzo.drudi5@studio.unibo.it) -% DATE: March 17, 2022 +% AUTHOR: Lorenzo Drudi (E-mail: lorenzodrudi11@gmail.com) +% DATE: March 17, 2023 % NAME: TDSFT (version 1.0) % % DESCRIPTION: This function is used to add the folders of the project to the search path