diff --git a/demos/shape/demoCrop.m b/demos/shape/demoCrop.m
new file mode 100644
index 0000000..ddf99f0
--- /dev/null
+++ b/demos/shape/demoCrop.m
@@ -0,0 +1,55 @@
+% Demonstration of image crop functions
+%
+% output = demoCrop(input)
+%
+% Example
+% demoCrop
+%
+% See also
+%
+
+% ------
+% Author: David Legland
+% e-mail: david.legland@inrae.fr
+% INRAE - BIA Research Unit - BIBS Platform (Nantes)
+% Created: 2022-06-24, using Matlab 9.12.0.1884302 (R2022a)
+% Copyright 2022 INRAE.
+
+%% Input data
+
+% read sample image
+% (provided within the @image/sampleFiles directory)
+img = Image.read('wheatGrainSlice.tif');
+
+figure; show(img);
+
+
+%% Boxes
+
+% need to segment image to obtain the region of the grain
+seg = img > 160;
+seg2 = areaOpening(killBorders(seg), 10);
+
+% compute boxes
+box = regionBoundingBox(seg2);
+obox = regionOrientedBox(seg2);
+
+% display boxes over image
+% (requires MatGeom toolbox)
+hold on;
+drawBox(box, 'color', 'g', 'linewidth', 2);
+drawOrientedBox(obox, 'color', 'm', 'linewidth', 2);
+
+
+%% Crop
+
+resCrop = crop(img, box);
+figure; show(resCrop)
+title('Crop Box');
+write(resCrop, 'wheatGrainCrop.tif');
+
+resCrop2 = cropOrientedBox(img, obox);
+figure; show(resCrop2)
+title('Crop Oriented Box');
+write(resCrop, 'wheatGrainCropOriented.tif');
+
diff --git a/demos/shape/html/demoCrop.html b/demos/shape/html/demoCrop.html
new file mode 100644
index 0000000..477a7c3
--- /dev/null
+++ b/demos/shape/html/demoCrop.html
@@ -0,0 +1,172 @@
+
+
+
+ Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Input data
+
+img = Image.read('wheatGrainSlice.tif');
+
+figure; show(img);
+
Boxes
+seg = img > 160;
+seg2 = areaOpening(killBorders(seg), 10);
+
+
+box = regionBoundingBox(seg2);
+obox = regionOrientedBox(seg2);
+
+
+
+hold on;
+drawBox(box, 'color', 'g', 'linewidth', 2);
+drawOrientedBox(obox, 'color', 'm', 'linewidth', 2);
+
Crop
resCrop = crop(img, box);
+figure; show(resCrop)
+title('Crop Box');
+write(resCrop, 'wheatGrainCrop.tif');
+
+resCrop2 = cropOrientedBox(img, obox);
+figure; show(resCrop2)
+title('Crop Oriented Box');
+write(resCrop, 'wheatGrainCropOriented.tif');
+
\ No newline at end of file
diff --git a/demos/shape/html/demoCrop.png b/demos/shape/html/demoCrop.png
new file mode 100644
index 0000000..d9fc51b
Binary files /dev/null and b/demos/shape/html/demoCrop.png differ
diff --git a/demos/shape/html/demoCrop_01.png b/demos/shape/html/demoCrop_01.png
new file mode 100644
index 0000000..071ac19
Binary files /dev/null and b/demos/shape/html/demoCrop_01.png differ
diff --git a/demos/shape/html/demoCrop_02.png b/demos/shape/html/demoCrop_02.png
new file mode 100644
index 0000000..a6ddb06
Binary files /dev/null and b/demos/shape/html/demoCrop_02.png differ
diff --git a/demos/shape/html/demoCrop_03.png b/demos/shape/html/demoCrop_03.png
new file mode 100644
index 0000000..6815eef
Binary files /dev/null and b/demos/shape/html/demoCrop_03.png differ
diff --git a/demos/shape/html/demoCrop_04.png b/demos/shape/html/demoCrop_04.png
new file mode 100644
index 0000000..2ebc27d
Binary files /dev/null and b/demos/shape/html/demoCrop_04.png differ
diff --git a/src/@Image/Image.m b/src/@Image/Image.m
index 159b486..cf64457 100644
--- a/src/@Image/Image.m
+++ b/src/@Image/Image.m
@@ -108,14 +108,20 @@
methods(Access = protected)
se = defaultStructuringElement(obj, varargin)
+ conn = defaultConnectivity(obj);
name = createNewName(obj, pattern)
end
+%% Private utility methods
+methods(Static, Access = protected)
+ weights = directionWeights3d13(delta);
+end
+
%% Constructor declaration
methods
function obj = Image(varargin)
- %IMAGE Constructor for Image object.
+ % Constructor for Image object.
%
% Syntax
% IMG = Image(MAT);
diff --git a/src/@Image/areaOpening.m b/src/@Image/areaOpening.m
index 70605dc..bae03a0 100644
--- a/src/@Image/areaOpening.m
+++ b/src/@Image/areaOpening.m
@@ -47,11 +47,10 @@
elseif isBinaryImage(obj)
% if image is binary compute labeling
- % first determines connectivity to use
- conn = 4;
- if ndims(obj) == 3
- conn = 6;
- end
+ % choose default connectivity depending on dimension
+ conn = defaultConnectivity(obj);
+
+ % case of connectivity specified by user
if ~isempty(varargin)
conn = varargin{1};
end
diff --git a/src/@Image/catChannels.m b/src/@Image/catChannels.m
index 2579f6c..387f3da 100644
--- a/src/@Image/catChannels.m
+++ b/src/@Image/catChannels.m
@@ -7,16 +7,16 @@
% % 'manual' creation of a color image
% img = Image.read('cameraman.tif');
% res = catChannels(img, img, invert(img));
-% res.type = 'color';
+% res.Type = 'color';
% show(res);
%
% See also
-% splitChannels, cat, catFrames
+% splitChannels, createRGB, cat, catFrames
%
% ------
% Author: David Legland
-% e-mail: david.legland@inra.fr
+% e-mail: david.legland@inrae.fr
% Created: 2011-11-22, using Matlab 7.9.0.529 (R2009b)
% Copyright 2011 INRA - Cepia Software Platform.
@@ -31,7 +31,7 @@
end
res = Image(...
- 'data', data, ...
- 'parent', obj, ...
- 'type', 'vector', ...
- 'name', name);
+ 'Data', data, ...
+ 'Parent', obj, ...
+ 'Type', 'vector', ...
+ 'Name', name);
diff --git a/src/@Image/chamferDistanceMap.m b/src/@Image/chamferDistanceMap.m
new file mode 100644
index 0000000..ad2f85a
--- /dev/null
+++ b/src/@Image/chamferDistanceMap.m
@@ -0,0 +1,203 @@
+function map = chamferDistanceMap(obj, varargin)
+% Distance map of a binary image computed using chamfer mask.
+%
+% DISTMAP = chamferDistanceMap(IMG)
+% DISTMAP = chamferDistanceMap(IMG, WEIGHTS)
+% Computes the distance map of the input image using chamfer weights. The
+% aim of this function is similar to that of the "distanceMap" one, with
+% the following specificities:
+% * possibility to use 5-by-5 chamfer masks
+% * possibility to compute distance maps for label images with touching
+% regions.
+%
+% Example
+% chamferDistanceMap
+%
+% See also
+% distanceMap, geodesicDistanceMap
+%
+
+% ------
+% Author: David Legland
+% e-mail: david.legland@inrae.fr
+% INRAE - BIA Research Unit - BIBS Platform (Nantes)
+% Created: 2021-11-18, using Matlab 9.10.0.1684407 (R2021a) Update 3
+% Copyright 2021 INRAE.
+
+%% Process input arguments
+
+% default weights for orthogonal or diagonal
+weights = [5 7 11];
+
+normalize = true;
+
+% extract user-specified weights
+if ~isempty(varargin)
+ weights = varargin{1};
+ varargin(1) = [];
+end
+
+% extract verbosity option
+verbose = false;
+if length(varargin) > 1
+ varName = varargin{1};
+ if ~ischar(varName)
+ error('Require options as name-value pairs');
+ end
+
+ if strcmpi(varName, 'normalize')
+ normalize = varargin{2};
+ elseif strcmpi(varName, 'verbose')
+ verbose = varargin{2};
+ else
+ error(['unknown option: ' varName]);
+ end
+end
+
+
+%% Initialisations
+
+% determines type of output from type of weights
+outputType = class(weights);
+
+% small check up to avoid degenerate cases
+w1 = weights(1);
+w2 = weights(2);
+if w2 < w1
+ w2 = 2 * w1;
+end
+
+% shifts in directions i and j for (1) forward and (2) backward iterations
+if length(weights) == 2
+ nShifts = 4;
+ di1 = [-1 -1 -1 0];
+ dj1 = [-1 0 1 -1];
+ di2 = [+1 +1 +1 0];
+ dj2 = [-1 0 1 +1];
+ ws = [w2 w1 w2 w1];
+
+elseif length(weights) == 3
+ nShifts = 8;
+ w3 = weights(3);
+ di1 = [-2 -2 -1 -1 -1 -1 -1 0];
+ dj1 = [-1 +1 -2 -1 0 1 +2 -1];
+ di2 = [+2 +2 +1 +1 +1 +1 +1 0];
+ dj2 = [-1 +1 +2 +1 0 -1 -2 +1];
+ ws = [w3 w3 w3 w2 w1 w2 w3 w1];
+end
+
+% allocate memory for result
+dist = ones(size(obj.Data), outputType);
+
+% init result: either max value, or 0 for marker pixels
+if isinteger(w1)
+ dist(:) = intmax(outputType);
+else
+ dist(:) = inf;
+end
+dist(obj.Data == 0) = 0;
+
+% size of image
+[D1, D2] = size(obj.Data);
+
+
+%% Forward iteration
+
+if verbose
+ disp('Forward iteration %d');
+end
+
+for i = 1:D1
+ for j = 1:D2
+ % computes only for pixels within a region
+ if obj.Data(i, j) == 0
+ continue;
+ end
+
+ % compute minimal propagated distance
+ newVal = dist(i, j);
+ for k = 1:nShifts
+ % coordinate of neighbor
+ i2 = i + di1(k);
+ j2 = j + dj1(k);
+
+ % check bounds
+ if i2 < 1 || i2 > D1 || j2 < 1 || j2 > D2
+ continue;
+ end
+
+ % compute new value
+ if obj.Data(i2, j2) == obj.Data(i, j)
+ % neighbor in same region
+ % -> add offset weight to neighbor distance
+ newVal = min(newVal, dist(i2, j2) + ws(k));
+ else
+ % neighbor in another region
+ % -> initialize with the offset weight
+ newVal = min(newVal, ws(k));
+ end
+ end
+
+ % if distance was changed, update result
+ dist(i,j) = newVal;
+ end
+
+end % iteration on lines
+
+
+
+%% Backward iteration
+
+if verbose
+ disp('Backward iteration');
+end
+
+for i = D1:-1:1
+ for j = D2:-1:1
+ % computes only for foreground pixels
+ if obj.Data(i, j) == 0
+ continue;
+ end
+
+ % compute minimal propagated distance
+ newVal = dist(i, j);
+ for k = 1:nShifts
+ % coordinate of neighbor
+ i2 = i + di2(k);
+ j2 = j + dj2(k);
+
+ % check bounds
+ if i2 < 1 || i2 > D1 || j2 < 1 || j2 > D2
+ continue;
+ end
+
+ % compute new value
+ if obj.Data(i2, j2) == obj.Data(i, j)
+ % neighbor in same region
+ % -> add offset weight to neighbor distance
+ newVal = min(newVal, dist(i2, j2) + ws(k));
+ else
+ % neighbor in another region
+ % -> initialize with the offset weight
+ newVal = min(newVal, ws(k));
+ end
+ end
+
+ % if distance was changed, update result
+ dist(i,j) = newVal;
+ end
+
+end % line iteration
+
+if normalize
+ dist(obj.Data>0) = dist(obj.Data>0) / w1;
+end
+
+newName = createNewName(obj, '%s-distMap');
+
+% create new image
+map = Image('Data', dist, ...
+ 'Parent', obj, ...
+ 'Name', newName, ...
+ 'Type', 'intensity', ...
+ 'ChannelNames', {'distance'});
diff --git a/src/@Image/clone.m b/src/@Image/clone.m
index 89c9dbc..0325ba8 100644
--- a/src/@Image/clone.m
+++ b/src/@Image/clone.m
@@ -1,17 +1,27 @@
function res = clone(obj)
% Create a deep-copy of an Image object.
%
-% output = clone(input)
+% RES = clone(IMG)
+% Return a new Image, initialized with same data values as in IMG.
+%
%
% Example
-% clone
+% img = Image.read('rice.png');
+% img2 = clone(img);
+% all(size(img) == size(img2))
+% ans =
+% 1
+% strcmp(class(img.Data), class(img2.Data))
+% ans =
+% 1
%
% See also
+% read, zeros, ones
%
% ------
% Author: David Legland
-% e-mail: david.legland@inra.fr
+% e-mail: david.legland@inrae.fr
% Created: 2011-12-15, using Matlab 7.9.0.529 (R2009b)
% Copyright 2011 INRA - Cepia Software Platform.
diff --git a/src/@Image/colorCloud.m b/src/@Image/colorCloud.m
new file mode 100644
index 0000000..f8cb10d
--- /dev/null
+++ b/src/@Image/colorCloud.m
@@ -0,0 +1,46 @@
+function varargout = colorCloud(obj, varargin)
+% Display image colors into the 3D space of specified gamut.
+%
+% colorCloud(IMG)
+% colorCloud(IMG, GAMUT)
+% See the "colorcloud" function help for options.
+%
+% Works also for 3D images.
+%
+% Example
+% % Read and display a color image
+% img = Image.read('peppers.png');
+% figure; show(img);
+% % Display color cloud in a new figure
+% colorCloud(img)
+%
+% See also
+% show, isColorImage, colorcloud
+%
+
+% ------
+% Author: David Legland
+% e-mail: david.legland@inrae.fr
+% INRAE - BIA Research Unit - BIBS Platform (Nantes)
+% Created: 2020-02-18, using Matlab 9.7.0.1247435 (R2019b) Update 2
+% Copyright 2020 INRAE.
+
+% check input
+if ~isColorImage(obj)
+ error('requires a color image as input');
+end
+
+% format data
+if obj.DataSize(3) == 1
+ rgb = permute(obj.Data(:,:,1,:,1), [2 1 4 3 5]);
+else
+ nSlices = obj.DataSize(3);
+ rgb = zeros([0 3]);
+ for iz = 1:nSlices
+ rgb = [rgb ; permute(obj.Data(:,:,iz,:,1), [2 1 4 3 5])]; %#ok