& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
12 | #endif
13 |
14 | // simil_A
15 | double simil_A(arma::rowvec spx_vec1, arma::rowvec spx_vec2, int wL, int wA, int wB);
16 | RcppExport SEXP _SuperpixelImageSegmentation_simil_A(SEXP spx_vec1SEXP, SEXP spx_vec2SEXP, SEXP wLSEXP, SEXP wASEXP, SEXP wBSEXP) {
17 | BEGIN_RCPP
18 | Rcpp::RObject rcpp_result_gen;
19 | Rcpp::RNGScope rcpp_rngScope_gen;
20 | Rcpp::traits::input_parameter< arma::rowvec >::type spx_vec1(spx_vec1SEXP);
21 | Rcpp::traits::input_parameter< arma::rowvec >::type spx_vec2(spx_vec2SEXP);
22 | Rcpp::traits::input_parameter< int >::type wL(wLSEXP);
23 | Rcpp::traits::input_parameter< int >::type wA(wASEXP);
24 | Rcpp::traits::input_parameter< int >::type wB(wBSEXP);
25 | rcpp_result_gen = Rcpp::wrap(simil_A(spx_vec1, spx_vec2, wL, wA, wB));
26 | return rcpp_result_gen;
27 | END_RCPP
28 | }
29 | // apply_rcpp
30 | arma::mat apply_rcpp(arma::cube& input);
31 | RcppExport SEXP _SuperpixelImageSegmentation_apply_rcpp(SEXP inputSEXP) {
32 | BEGIN_RCPP
33 | Rcpp::RObject rcpp_result_gen;
34 | Rcpp::RNGScope rcpp_rngScope_gen;
35 | Rcpp::traits::input_parameter< arma::cube& >::type input(inputSEXP);
36 | rcpp_result_gen = Rcpp::wrap(apply_rcpp(input));
37 | return rcpp_result_gen;
38 | END_RCPP
39 | }
40 | // NAs_matrix
41 | arma::uvec NAs_matrix(arma::mat& x);
42 | RcppExport SEXP _SuperpixelImageSegmentation_NAs_matrix(SEXP xSEXP) {
43 | BEGIN_RCPP
44 | Rcpp::RObject rcpp_result_gen;
45 | Rcpp::RNGScope rcpp_rngScope_gen;
46 | Rcpp::traits::input_parameter< arma::mat& >::type x(xSEXP);
47 | rcpp_result_gen = Rcpp::wrap(NAs_matrix(x));
48 | return rcpp_result_gen;
49 | END_RCPP
50 | }
51 | // is_mt_finite
52 | bool is_mt_finite(arma::mat x);
53 | RcppExport SEXP _SuperpixelImageSegmentation_is_mt_finite(SEXP xSEXP) {
54 | BEGIN_RCPP
55 | Rcpp::RObject rcpp_result_gen;
56 | Rcpp::RNGScope rcpp_rngScope_gen;
57 | Rcpp::traits::input_parameter< arma::mat >::type x(xSEXP);
58 | rcpp_result_gen = Rcpp::wrap(is_mt_finite(x));
59 | return rcpp_result_gen;
60 | END_RCPP
61 | }
62 | // image_segmentation
63 | Rcpp::List image_segmentation(arma::cube input_image, std::string method, int num_superpixel, std::string kmeans_method, bool AP_data, bool use_median, int minib_kmeans_batch, double minib_kmeans_init_fraction, int kmeans_num_init, int kmeans_max_iters, std::string kmeans_initializer, std::string colour_type, double compactness_factor, bool adjust_centroids_and_return_masks, bool return_labels_2_dimensionsional, bool sim_normalize, int sim_wL, int sim_wA, int sim_wB, int sim_color_radius, int ap_maxits, int ap_convits, double ap_dampfact, bool ap_details, double ap_nonoise, bool ap_time, bool verbose);
64 | RcppExport SEXP _SuperpixelImageSegmentation_image_segmentation(SEXP input_imageSEXP, SEXP methodSEXP, SEXP num_superpixelSEXP, SEXP kmeans_methodSEXP, SEXP AP_dataSEXP, SEXP use_medianSEXP, SEXP minib_kmeans_batchSEXP, SEXP minib_kmeans_init_fractionSEXP, SEXP kmeans_num_initSEXP, SEXP kmeans_max_itersSEXP, SEXP kmeans_initializerSEXP, SEXP colour_typeSEXP, SEXP compactness_factorSEXP, SEXP adjust_centroids_and_return_masksSEXP, SEXP return_labels_2_dimensionsionalSEXP, SEXP sim_normalizeSEXP, SEXP sim_wLSEXP, SEXP sim_wASEXP, SEXP sim_wBSEXP, SEXP sim_color_radiusSEXP, SEXP ap_maxitsSEXP, SEXP ap_convitsSEXP, SEXP ap_dampfactSEXP, SEXP ap_detailsSEXP, SEXP ap_nonoiseSEXP, SEXP ap_timeSEXP, SEXP verboseSEXP) {
65 | BEGIN_RCPP
66 | Rcpp::RObject rcpp_result_gen;
67 | Rcpp::RNGScope rcpp_rngScope_gen;
68 | Rcpp::traits::input_parameter< arma::cube >::type input_image(input_imageSEXP);
69 | Rcpp::traits::input_parameter< std::string >::type method(methodSEXP);
70 | Rcpp::traits::input_parameter< int >::type num_superpixel(num_superpixelSEXP);
71 | Rcpp::traits::input_parameter< std::string >::type kmeans_method(kmeans_methodSEXP);
72 | Rcpp::traits::input_parameter< bool >::type AP_data(AP_dataSEXP);
73 | Rcpp::traits::input_parameter< bool >::type use_median(use_medianSEXP);
74 | Rcpp::traits::input_parameter< int >::type minib_kmeans_batch(minib_kmeans_batchSEXP);
75 | Rcpp::traits::input_parameter< double >::type minib_kmeans_init_fraction(minib_kmeans_init_fractionSEXP);
76 | Rcpp::traits::input_parameter< int >::type kmeans_num_init(kmeans_num_initSEXP);
77 | Rcpp::traits::input_parameter< int >::type kmeans_max_iters(kmeans_max_itersSEXP);
78 | Rcpp::traits::input_parameter< std::string >::type kmeans_initializer(kmeans_initializerSEXP);
79 | Rcpp::traits::input_parameter< std::string >::type colour_type(colour_typeSEXP);
80 | Rcpp::traits::input_parameter< double >::type compactness_factor(compactness_factorSEXP);
81 | Rcpp::traits::input_parameter< bool >::type adjust_centroids_and_return_masks(adjust_centroids_and_return_masksSEXP);
82 | Rcpp::traits::input_parameter< bool >::type return_labels_2_dimensionsional(return_labels_2_dimensionsionalSEXP);
83 | Rcpp::traits::input_parameter< bool >::type sim_normalize(sim_normalizeSEXP);
84 | Rcpp::traits::input_parameter< int >::type sim_wL(sim_wLSEXP);
85 | Rcpp::traits::input_parameter< int >::type sim_wA(sim_wASEXP);
86 | Rcpp::traits::input_parameter< int >::type sim_wB(sim_wBSEXP);
87 | Rcpp::traits::input_parameter< int >::type sim_color_radius(sim_color_radiusSEXP);
88 | Rcpp::traits::input_parameter< int >::type ap_maxits(ap_maxitsSEXP);
89 | Rcpp::traits::input_parameter< int >::type ap_convits(ap_convitsSEXP);
90 | Rcpp::traits::input_parameter< double >::type ap_dampfact(ap_dampfactSEXP);
91 | Rcpp::traits::input_parameter< bool >::type ap_details(ap_detailsSEXP);
92 | Rcpp::traits::input_parameter< double >::type ap_nonoise(ap_nonoiseSEXP);
93 | Rcpp::traits::input_parameter< bool >::type ap_time(ap_timeSEXP);
94 | Rcpp::traits::input_parameter< bool >::type verbose(verboseSEXP);
95 | rcpp_result_gen = Rcpp::wrap(image_segmentation(input_image, method, num_superpixel, kmeans_method, AP_data, use_median, minib_kmeans_batch, minib_kmeans_init_fraction, kmeans_num_init, kmeans_max_iters, kmeans_initializer, colour_type, compactness_factor, adjust_centroids_and_return_masks, return_labels_2_dimensionsional, sim_normalize, sim_wL, sim_wA, sim_wB, sim_color_radius, ap_maxits, ap_convits, ap_dampfact, ap_details, ap_nonoise, ap_time, verbose));
96 | return rcpp_result_gen;
97 | END_RCPP
98 | }
99 |
--------------------------------------------------------------------------------
/man/Image_Segmentation.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/px_utils.R
3 | \docType{class}
4 | \name{Image_Segmentation}
5 | \alias{Image_Segmentation}
6 | \title{Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering}
7 | \usage{
8 | # init <- Image_Segmentation$new()
9 | }
10 | \description{
11 | Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering
12 |
13 | Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering
14 | }
15 | \details{
16 | \emph{sim_wL}, \emph{sim_wA}, \emph{sim_wB} are the weights of the three channels. They keep balance so as to be consistent with human perception.
17 |
18 | The quantity \emph{colorradius} adjusts the number of clusters, and if its value is low, the number of targets would increase, which leads to more detailed segmentation results.
19 |
20 | If the \emph{adjust_centroids_and_return_masks} parameter is set to FALSE then the output \emph{kmeans_image_data} will be an RGB image, otherwise it will be a black-and-white image.
21 |
22 | \emph{colour_type} parameter: RGB (Red-Green-Blue), LAB (Lightness, A-colour-dimension, B-colour-dimension) or HSV (Hue, Saturation, Value) colour.
23 |
24 | Higher resolution images give better results.
25 |
26 | The \emph{affinity propagation} algorithm is used here with default parameter values.
27 |
28 | By setting the \emph{sim_normalize} parameter to TRUE, the affinity propagation algorithm requires less iterations to complete. However, the \emph{colorradius} parameter does not have an
29 | effect if the similarity matrix is normalized.
30 |
31 | Regarding the \emph{use_median} parameter in the Rcpp I use the following steps: \emph{1st.} I compute the superpixels and extract the labels, \emph{2nd.} each superpixel label consists of multiple pixels and for these superpixels I have
32 | to compute a dissimilarity matrix therefore each superpixel must correspond to a single value, \emph{3rd.} to come to this single value for each superpixel the R user has the option to either use the 'mean' or the 'median of multiple
33 | image pixels (per superpixel)
34 |
35 |
36 | ---------------kmeans initializers----------------------
37 |
38 | \strong{optimal_init} : this initializer adds rows of the data incrementally, while checking that they do not already exist in the centroid-matrix
39 |
40 | \strong{quantile_init} : initialization of centroids by using the cummulative distance between observations and by removing potential duplicates
41 |
42 | \strong{kmeans++} : kmeans++ initialization. Reference : http://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf AND http://stackoverflow.com/questions/5466323/how-exactly-does-k-means-work
43 |
44 | \strong{random} : random selection of data rows as initial centroids
45 | }
46 | \section{Methods}{
47 |
48 |
49 | \describe{
50 | \item{\code{Image_Segmentation$new()}}{}
51 |
52 | \item{\code{--------------}}{}
53 |
54 | \item{\code{spixel_segmentation()}}{}
55 |
56 | \item{\code{--------------}}{}
57 |
58 | \item{\code{spixel_masks_show()}}{}
59 |
60 | \item{\code{--------------}}{}
61 |
62 | \item{\code{spixel_clusters_show()}}{}
63 |
64 | \item{\code{--------------}}{}
65 |
66 | }
67 | }
68 |
69 | \examples{
70 |
71 | library(SuperpixelImageSegmentation)
72 |
73 | path = system.file("images", "BSR_bsds500_image.jpg", package = "SuperpixelImageSegmentation")
74 |
75 | im = OpenImageR::readImage(path)
76 |
77 | init = Image_Segmentation$new()
78 |
79 | num_spix = 10 # for illustration purposes
80 | # num_spix = 600 # recommended number of superpixels
81 |
82 | spx = init$spixel_segmentation(input_image = im,
83 | superpixel = num_spix,
84 | AP_data = TRUE,
85 | use_median = TRUE,
86 | return_labels_2_dimensionsional = TRUE,
87 | sim_color_radius = 10)
88 |
89 |
90 | #...........................
91 | # plot the superpixel labels
92 | #...........................
93 |
94 | plt = init$spixel_clusters_show(spix_labels = spx$spix_labels,
95 | color_palette = grDevices::rainbow,
96 | parameter_list_png = NULL)
97 |
98 | # plt
99 |
100 |
101 | #....................................................
102 | # create a binary image for a specified cluster label
103 | #....................................................
104 |
105 | pix_values = spx$spix_labels
106 |
107 | target_cluster = 3 # determine clusters visually ('plt' variable)
108 |
109 | pix_values[pix_values != target_cluster] = 0 # set all other values to 0 (background)
110 | pix_values[pix_values == target_cluster] = 1 # set the target_cluster to 1 (binary image)
111 |
112 | # OpenImageR::imageShow(pix_values)
113 |
114 | }
115 | \references{
116 | https://www.ijsr.net/archive/v4i4/SUB152869.pdf , "Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering", Bao Zhou, 2013, International Journal of Science and Research (IJSR)
117 | }
118 | \section{Methods}{
119 | \subsection{Public methods}{
120 | \itemize{
121 | \item \href{#method-Image_Segmentation-new}{\code{Image_Segmentation$new()}}
122 | \item \href{#method-Image_Segmentation-spixel_segmentation}{\code{Image_Segmentation$spixel_segmentation()}}
123 | \item \href{#method-Image_Segmentation-spixel_masks_show}{\code{Image_Segmentation$spixel_masks_show()}}
124 | \item \href{#method-Image_Segmentation-spixel_clusters_show}{\code{Image_Segmentation$spixel_clusters_show()}}
125 | \item \href{#method-Image_Segmentation-clone}{\code{Image_Segmentation$clone()}}
126 | }
127 | }
128 | \if{html}{\out{
}}
129 | \if{html}{\out{}}
130 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-new}{}}}
131 | \subsection{Method \code{new()}}{
132 | \subsection{Usage}{
133 | \if{html}{\out{}}\preformatted{Image_Segmentation$new()}\if{html}{\out{
}}
134 | }
135 |
136 | }
137 | \if{html}{\out{
}}
138 | \if{html}{\out{}}
139 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-spixel_segmentation}{}}}
140 | \subsection{Method \code{spixel_segmentation()}}{
141 | \subsection{Usage}{
142 | \if{html}{\out{}}\preformatted{Image_Segmentation$spixel_segmentation(
143 | input_image,
144 | method = "slic",
145 | superpixel = 200,
146 | kmeans_method = "",
147 | AP_data = FALSE,
148 | use_median = TRUE,
149 | minib_kmeans_batch = 10,
150 | minib_kmeans_init_fraction = 0.5,
151 | kmeans_num_init = 3,
152 | kmeans_max_iters = 100,
153 | kmeans_initializer = "kmeans++",
154 | colour_type = "RGB",
155 | compactness_factor = 20,
156 | adjust_centroids_and_return_masks = FALSE,
157 | return_labels_2_dimensionsional = FALSE,
158 | sim_normalize = FALSE,
159 | sim_wL = 3,
160 | sim_wA = 10,
161 | sim_wB = 10,
162 | sim_color_radius = 20,
163 | ap_maxits = 1000,
164 | ap_convits = 100,
165 | ap_dampfact = 0.9,
166 | ap_nonoise = 0,
167 | verbose = FALSE
168 | )}\if{html}{\out{
}}
169 | }
170 |
171 | \subsection{Arguments}{
172 | \if{html}{\out{}}
173 | \describe{
174 | \item{\code{input_image}}{a 3-dimensional input image (the range of the pixel values should be preferably in the range 0 to 255)}
175 |
176 | \item{\code{method}}{a character string specifying the superpixel method. It can be either "slic" or "slico"}
177 |
178 | \item{\code{superpixel}}{a numeric value specifying the number of superpixels}
179 |
180 | \item{\code{kmeans_method}}{a character string specifying the kmeans method. If not empty ("") then it can be either "kmeans" or "mini_batch_kmeans"}
181 |
182 | \item{\code{AP_data}}{a boolean. If TRUE then the affinity propagation image data will be computed and returned}
183 |
184 | \item{\code{use_median}}{a boolean. If TRUE then the median will be used rather than the mean value for the inner computations (see the details section for more information)}
185 |
186 | \item{\code{minib_kmeans_batch}}{the size of the mini batches}
187 |
188 | \item{\code{minib_kmeans_init_fraction}}{percentage of data to use for the initialization centroids (applies if initializer is \emph{kmeans++} or \emph{optimal_init}). Should be a float number between 0.0 and 1.0.}
189 |
190 | \item{\code{kmeans_num_init}}{number of times the algorithm will be run with different centroid seeds}
191 |
192 | \item{\code{kmeans_max_iters}}{the maximum number of clustering iterations}
193 |
194 | \item{\code{kmeans_initializer}}{the method of initialization. One of, \emph{optimal_init}, \emph{quantile_init}, \emph{kmeans++} and \emph{random}. See details for more information}
195 |
196 | \item{\code{colour_type}}{a character string specifying the colour type. It can be one of "RGB", "LAB" or "HSV"}
197 |
198 | \item{\code{compactness_factor}}{a numeric value specifying the compactness parameter in case that \emph{method} is "slic"}
199 |
200 | \item{\code{adjust_centroids_and_return_masks}}{a boolean. If TRUE and the \emph{kmeans_method} parameter is NOT empty ("") then the centroids will be adjusted and image-masks will be returned. This will allow me to plot the masks using the \emph{spixel_masks_show} method.}
201 |
202 | \item{\code{return_labels_2_dimensionsional}}{a boolean. If TRUE then a matrix of labels based on the output superpixels in combination with the Affinity Propagation clusters will be returned}
203 |
204 | \item{\code{sim_normalize}}{a boolean. If TRUE then the constructed similarity matrix will be normalised to have unit p-norm (see the armadillo documentation for more details)}
205 |
206 | \item{\code{sim_wL}}{a numeric value specifying the weight for the \emph{"L"} channel of the image (see the details section for more information)}
207 |
208 | \item{\code{sim_wA}}{a numeric value specifying the weight for the \emph{"A"} channel of the image (see the details section for more information)}
209 |
210 | \item{\code{sim_wB}}{a numeric value specifying the weight for the \emph{"B"} channel of the image (see the details section for more information)}
211 |
212 | \item{\code{sim_color_radius}}{a numeric value specifying the \emph{colorradius} (see the details section for more information)}
213 |
214 | \item{\code{ap_maxits}}{a numeric value specifying the maximum number of iterations for the Affinity Propagation Clustering (defaults to 1000)}
215 |
216 | \item{\code{ap_convits}}{a numeric value. If the estimated exemplars stay fixed for convits iterations, the affinity propagation algorithm terminates early (defaults to 100)}
217 |
218 | \item{\code{ap_dampfact}}{a float number specifying the update equation damping level in [0.5, 1). Higher values correspond to heavy damping, which may be needed if oscillations occur in the Affinity Propagation Clustering (defaults to 0.9)}
219 |
220 | \item{\code{ap_nonoise}}{a float number. The affinity propagation algorithm adds a small amount of noise to \emph{data} to prevent degenerate cases; this disables that.}
221 |
222 | \item{\code{verbose}}{a boolean. If TRUE then information will be printed in the console (spixel_masks_show method)}
223 | }
224 | \if{html}{\out{
}}
225 | }
226 | }
227 | \if{html}{\out{
}}
228 | \if{html}{\out{}}
229 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-spixel_masks_show}{}}}
230 | \subsection{Method \code{spixel_masks_show()}}{
231 | \subsection{Usage}{
232 | \if{html}{\out{}}\preformatted{Image_Segmentation$spixel_masks_show(
233 | delay_display_seconds = 3,
234 | display_all = FALSE,
235 | margin_btw_plots = 0.15,
236 | verbose = FALSE
237 | )}\if{html}{\out{
}}
238 | }
239 |
240 | \subsection{Arguments}{
241 | \if{html}{\out{}}
242 | \describe{
243 | \item{\code{delay_display_seconds}}{a numeric value specifying the seconds to delay the display of the next image (It displays the images consecutively). This parameter applies only if the \emph{display_all} is set to FALSE (spixel_masks_show method)}
244 |
245 | \item{\code{display_all}}{a boolean. If TRUE then all images will be displayed in a grid (spixel_masks_show method)}
246 |
247 | \item{\code{margin_btw_plots}}{a float number specifying the margins between the plots if the \emph{display_all} parameter is set to TRUE (spixel_masks_show method)}
248 |
249 | \item{\code{verbose}}{a boolean. If TRUE then information will be printed in the console (spixel_masks_show method)}
250 | }
251 | \if{html}{\out{
}}
252 | }
253 | }
254 | \if{html}{\out{
}}
255 | \if{html}{\out{}}
256 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-spixel_clusters_show}{}}}
257 | \subsection{Method \code{spixel_clusters_show()}}{
258 | \subsection{Usage}{
259 | \if{html}{\out{}}\preformatted{Image_Segmentation$spixel_clusters_show(
260 | spix_labels,
261 | color_palette = grDevices::rainbow,
262 | parameter_list_png = NULL
263 | )}\if{html}{\out{
}}
264 | }
265 |
266 | \subsection{Arguments}{
267 | \if{html}{\out{}}
268 | \describe{
269 | \item{\code{spix_labels}}{a matrix. I can retrieve the "spix_labels" parameter by setting the "return_labels_2_dimensionsional" parameter to TRUE in the "spixel_segmentation" method (spixel_clusters_show method)}
270 |
271 | \item{\code{color_palette}}{one of the color palettes. Use ?grDevices::topo.colors to see the available color palettes}
272 |
273 | \item{\code{parameter_list_png}}{either NULL or a list of parameters passed to the ?grDevices::png function, such as list(filename = 'img.png', width = 100, height = 100, units = "px", pointsize = 12, bg = "white", type = "quartz")}
274 | }
275 | \if{html}{\out{
}}
276 | }
277 | }
278 | \if{html}{\out{
}}
279 | \if{html}{\out{}}
280 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-clone}{}}}
281 | \subsection{Method \code{clone()}}{
282 | The objects of this class are cloneable with this method.
283 | \subsection{Usage}{
284 | \if{html}{\out{}}\preformatted{Image_Segmentation$clone(deep = FALSE)}\if{html}{\out{
}}
285 | }
286 |
287 | \subsection{Arguments}{
288 | \if{html}{\out{}}
289 | \describe{
290 | \item{\code{deep}}{Whether to make a deep clone.}
291 | }
292 | \if{html}{\out{
}}
293 | }
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/src/superPx_AP_Kmeans_Segment.cpp:
--------------------------------------------------------------------------------
1 | # include
2 | # include
3 | # include
4 | # include
5 | // [[Rcpp::depends("RcppArmadillo")]]
6 | // [[Rcpp::depends(ClusterR)]]
7 | // [[Rcpp::depends(OpenImageR)]]
8 |
9 |
10 | # include
11 | # include
12 | # include
13 | # include
14 | # include
15 |
16 |
17 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18 | // similarity function [ negative euclidean -- which gives the best results in the article ]
19 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20 |
21 | // [[Rcpp::export]]
22 | double simil_A(arma::rowvec spx_vec1, arma::rowvec spx_vec2, int wL = 3, int wA = 10, int wB = 10) {
23 |
24 | double value = -( wL * std::pow((spx_vec1(0) - spx_vec2(0)), 2.0) + wA * std::pow((spx_vec1(1) - spx_vec2(1)), 2.0) + wB * std::pow((spx_vec1(2) - spx_vec2(2)), 2.0) );
25 |
26 | return value;
27 | }
28 |
29 |
30 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31 | // apply(im, 3, as.vector) in rcpp
32 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33 |
34 | // [[Rcpp::export]]
35 | arma::mat apply_rcpp(arma::cube &input) {
36 |
37 | arma::mat res(input.n_rows * input.n_cols, 3);
38 |
39 | for (unsigned int i = 0; i < (input.n_rows * input.n_cols); i++) {
40 | res(i,0) = input.slice(0)(i);
41 | res(i,1) = input.slice(1)(i);
42 | res(i,2) = input.slice(2)(i);
43 | }
44 |
45 | return res;
46 | }
47 |
48 |
49 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50 | // find which rows of the kmeans-centroids include NA's
51 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52 |
53 | // [[Rcpp::export]]
54 | arma::uvec NAs_matrix(arma::mat &x) {
55 |
56 | arma::uvec all_rows;
57 | int item = 0;
58 |
59 | if (x.has_nan()) {
60 | for (unsigned int j = 0; j < x.n_cols; j++) {
61 | arma::uvec find_row = arma::find_nonfinite(x.col(j));
62 | for (unsigned int k = 0; k < find_row.n_elem; k++) {
63 | item++;
64 | all_rows.resize(item);
65 | all_rows(item-1) = find_row(k);
66 | }
67 | }
68 | all_rows = arma::unique(all_rows);
69 | }
70 | return all_rows;
71 | }
72 |
73 |
74 | //------------------------------
75 | // check if the matrix is finite
76 | //------------------------------
77 |
78 | // [[Rcpp::export]]
79 | bool is_mt_finite(arma::mat x) {
80 |
81 | clustR::ClustHeader clsh;
82 |
83 | return clsh.check_NaN_Inf(x);
84 | }
85 |
86 |
87 | //----------------------------
88 | // image segmentation function [ superpixels, affinity propagation, kmeans ]
89 | //----------------------------
90 |
91 | // [[Rcpp::export]]
92 | Rcpp::List image_segmentation(arma::cube input_image,
93 | std::string method = "slic",
94 | int num_superpixel = 200,
95 | std::string kmeans_method = "",
96 | bool AP_data = false,
97 | bool use_median = true,
98 | int minib_kmeans_batch = 10,
99 | double minib_kmeans_init_fraction = 0.5,
100 | int kmeans_num_init = 3,
101 | int kmeans_max_iters = 100,
102 | std::string kmeans_initializer = "kmeans++",
103 | std::string colour_type = "RGB",
104 | double compactness_factor = 20,
105 | bool adjust_centroids_and_return_masks = false,
106 | bool return_labels_2_dimensionsional = false,
107 | bool sim_normalize = false,
108 | int sim_wL = 3,
109 | int sim_wA = 10,
110 | int sim_wB = 10,
111 | int sim_color_radius = 20,
112 | int ap_maxits = 1000,
113 | int ap_convits = 100,
114 | double ap_dampfact = 0.9,
115 | bool ap_details = false,
116 | double ap_nonoise = 0.0,
117 | bool ap_time = false,
118 | bool verbose = false) {
119 |
120 | Affinity_Propagation afpr;
121 | clustR::ClustHeader clsh;
122 |
123 | if (kmeans_method == "" && !AP_data) { Rcpp::stop("Either the 'kmeans_method' should be set to 'kmeans' / 'mini_batch_kmeans' or the 'AP' parameter should be set to TRUE!"); }
124 | if (adjust_centroids_and_return_masks && kmeans_method == "") { Rcpp::Rcout << "WARNING: Set the 'kmeans_method' parameter to either 'kmeans' or 'mini_batch_kmeans' to receive image-masks data!" << std::endl; }
125 | if (verbose) { Rcpp::Rcout << "Sequential computation starts ..." << std::endl; }
126 |
127 | oimageR::Utility_functions UTLF;
128 | Rcpp::List lst_obj = UTLF.interface_superpixels(input_image, method, num_superpixel, compactness_factor, true, true, true, "", false);
129 |
130 | if (verbose) { Rcpp::Rcout << "The super-pixel " << method << " method as pre-processing step was used / completed!" << std::endl; }
131 |
132 | arma::cube im_3d_obj = Rcpp::as(lst_obj["slic_data"]);
133 | arma::mat labels_obj = Rcpp::as(lst_obj["labels"]);
134 | arma::cube lab_obj = Rcpp::as(lst_obj["lab_data"]);
135 |
136 | arma::colvec unq_labels = arma::unique(arma::vectorise(labels_obj));
137 | int ITEMS = unq_labels.n_elem;
138 |
139 | arma::mat lst_3d_vecs(ITEMS, 3, arma::fill::zeros);
140 | int inner_iters = 3;
141 |
142 | for (int i = 0; i < ITEMS; i++) {
143 | arma::uvec idx = arma::find(labels_obj == unq_labels(i));
144 | if (idx.n_elem > 0) { // it is possible that the unique number of labels is less than the ITEMS
145 | arma::rowvec inner_vec(3, arma::fill::zeros);
146 | for (int k = 0; k < inner_iters; k++) {
147 | arma::mat in_mt = lab_obj.slice(k); // use 'rgb_2lab' as input, as the similarity function requires 'Lab' input data
148 | if (use_median) {
149 | inner_vec(k) = arma::median(arma::vectorise(in_mt(idx)));
150 | }
151 | else {
152 | inner_vec(k) = arma::mean(arma::vectorise(in_mt(idx)));
153 | }
154 | }
155 | lst_3d_vecs.row(unq_labels(i)) = inner_vec; // keep track of the 'unq_labels' because after calculating the distance matrix I have to retrieve the initial labels
156 | }
157 | }
158 |
159 | arma::rowvec vec_avg((ITEMS * ITEMS) - ITEMS); // number of elements of the similarity matrix excluding the main diagonal
160 |
161 | arma::mat simil_mt(ITEMS,ITEMS, arma::fill::zeros);
162 | for (int i = 0; i < ITEMS; i++) {
163 | for (int j = 0; j < ITEMS; j++) {
164 | if (i != j) { // calculate the distance only for the off-diagonal items (items not in the main diagonal)
165 | double tmp_dist = simil_A(lst_3d_vecs.row(i), lst_3d_vecs.row(j), sim_wL, sim_wA, sim_wB); // same note for 'unq_labels' as before
166 | vec_avg = tmp_dist; // use the similarities both to construct the similarity matrix and to extract the preference value
167 | simil_mt(i,j) = tmp_dist;
168 | }
169 | }
170 | }
171 |
172 | double norm_avg = 0; // recalculate the preference value based on the center-scaled data
173 | if (sim_normalize) { // normalize the similarity matrix
174 | simil_mt = arma::normalise(simil_mt);
175 | if (!use_median) {
176 | int norm_items = 0;
177 | for (unsigned int f = 0; f < simil_mt.n_rows; f++) {
178 | for (unsigned int ff = 0; ff < simil_mt.n_cols; ff++) {
179 | if (f != ff) {
180 | norm_avg += simil_mt(f,ff);
181 | norm_items++;
182 | }
183 | }
184 | }
185 | norm_avg /= norm_items;
186 | }
187 | }
188 |
189 | double avg_off_diag = 0; // set the preference value taking into account the 'colorradius' [ 'use_median' option too, in contrary to the mean value of the article ]
190 | if (use_median) {
191 | avg_off_diag = arma::median(arma::vectorise(simil_mt));
192 | }
193 | else {
194 | if (sim_normalize) {
195 | avg_off_diag = norm_avg;
196 | }
197 | else {
198 | avg_off_diag = arma::mean(vec_avg); // the mean value is based on the off-diagonal elements only
199 | }
200 | }
201 | double set_preference_value = sim_color_radius * avg_off_diag;
202 |
203 | if (verbose) { Rcpp::Rcout << "The similarity matrix based on super-pixels was computed!" << std::endl; }
204 |
205 |
206 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207 | // affinity propagation with default values [ use affinity propagation on super-pixels because it does not scale well in high dimensions ]
208 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
209 |
210 | std::vector preference;
211 | preference.push_back(set_preference_value);
212 |
213 | Rcpp::List afpr_res = afpr.affinity_propagation(simil_mt, preference, ap_maxits, ap_convits, ap_dampfact, ap_details, ap_nonoise, 2.2204e-16, ap_time); // the affinity propagation algorithm returns clusters for the superpixels, thus it's not possible to 'adjust_centroids_and_return_masks' as is the case in kmeans
214 |
215 | int affinty_num_clusters = Rcpp::as(afpr_res["K"]); // number of clusters
216 | std::string aff_str_num = std::to_string(affinty_num_clusters);
217 | int AP_iterations = Rcpp::as(afpr_res["iterations"]);
218 | std::string str_iterations = std::to_string(AP_iterations);
219 | std::vector exemplars = Rcpp::as >(afpr_res["exemplars"]); // exemplars
220 |
221 | std::unordered_map > clusters; // workaround to get the clusters and their indices as an unordered_map because it can't be done directly using Rcpp::as(clust_nams[n]);
226 | std::vector vec_in = Rcpp::as >(tmp_clusts[n]);
227 | int val_in = std::stoi(str_in);
228 | clusters[val_in] = vec_in;
229 | }
230 |
231 | if (verbose) { Rcpp::Rcout << "It took " + str_iterations + " iterations for affinity propagation to complete!" << std::endl; }
232 | if (verbose) { Rcpp::Rcout << aff_str_num + " clusters were chosen based on super-pixels and affinity propagation!" << std::endl; }
233 |
234 | arma::cube hsv_tmp;
235 | if (colour_type == "HSV") {
236 | hsv_tmp = UTLF.RGB_to_HSV(input_image);
237 | }
238 |
239 | arma::cube ap_new_im;
240 |
241 | if (AP_data) {
242 |
243 | ap_new_im.set_size(input_image.n_rows, input_image.n_cols, input_image.n_slices);
244 | std::vector ap_keys; // obtain the clusters
245 | int CLUSTS = clusters.size();
246 | ap_keys.reserve(CLUSTS);
247 |
248 | for(auto kv : clusters) {
249 | ap_keys.push_back(kv.first);
250 | }
251 | std::unordered_map > idx_clusters;
252 | for (unsigned int d = 0; d < ap_keys.size(); d++) {
253 | std::vector sub_clust = clusters[ap_keys[d]];
254 | for (unsigned int dd = 0; dd < sub_clust.size(); dd++) {
255 | arma::uvec idx = arma::find(labels_obj == sub_clust[dd]);
256 | if (idx.size() > 0) {
257 | for (unsigned int ddd = 0; ddd < idx.size(); ddd++) {
258 | idx_clusters[ap_keys[d]].push_back(idx[ddd]); // assign the indices of each sub-cluster
259 | }
260 | }
261 | }
262 | }
263 | for (int f = 0; f < CLUSTS; f++) {
264 | arma::uvec tmp_cl_idx = arma::conv_to::from(idx_clusters[ap_keys[f]]);
265 | for (unsigned int ff = 0; ff < input_image.n_slices; ff++) {
266 | double avg_clust_idx; // return the transformed image based on the specified colour-type
267 | if (colour_type == "RGB") {
268 | if (use_median) {
269 | avg_clust_idx = arma::median(arma::vectorise(input_image.slice(ff)(tmp_cl_idx)));
270 | }
271 | else {
272 | avg_clust_idx = arma::mean(arma::vectorise(input_image.slice(ff)(tmp_cl_idx)));
273 | }
274 | }
275 | else if (colour_type == "LAB") {
276 | if (use_median) {
277 | avg_clust_idx = arma::median(arma::vectorise(lab_obj.slice(ff)(tmp_cl_idx)));
278 | }
279 | else {
280 | avg_clust_idx = arma::mean(arma::vectorise(lab_obj.slice(ff)(tmp_cl_idx)));
281 | }
282 | }
283 | else if (colour_type == "HSV") {
284 | if (use_median) {
285 | avg_clust_idx = arma::median(arma::vectorise(hsv_tmp.slice(ff)(tmp_cl_idx)));
286 | }
287 | else {
288 | avg_clust_idx = arma::mean(arma::vectorise(hsv_tmp.slice(ff)(tmp_cl_idx)));
289 | }
290 | }
291 | else {
292 | Rcpp::stop("Invalid 'colour_type' parameter!");
293 | }
294 | ap_new_im.slice(ff)(tmp_cl_idx).fill(avg_clust_idx); // use the subset of cluster-indices to assign the average value
295 | }
296 | }
297 |
298 | if (verbose) { Rcpp::Rcout << "Image data based on Affinity Propagation clustering ('AP_image_data') will be returned!" << std::endl; }
299 | }
300 |
301 |
302 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
303 | // process the initial data for vector quantization
304 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
305 |
306 | if (input_image.empty()) { Rcpp::stop("the initial 3-dimensional data is an empty object!"); }
307 |
308 | arma::mat im2;
309 |
310 | if (colour_type == "RGB") {
311 | im2 = apply_rcpp(input_image);
312 | }
313 | else if (colour_type == "LAB") {
314 | im2 = apply_rcpp(lab_obj);
315 | }
316 | else if (colour_type == "HSV") {
317 | im2 = apply_rcpp(hsv_tmp);
318 | }
319 | else {
320 | Rcpp::stop("Invalid 'colour_type' parameter!");
321 | }
322 |
323 |
324 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
325 | // use the affinity-propagation number of clusters as
326 | // input to the k-means or mini-batch-kmeans algorithm
327 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
328 |
329 | Rcpp::List kmeans_obj;
330 | std::string which_km;
331 | arma::mat getcent;
332 | arma::rowvec getclust;
333 | arma::cube new_im;
334 | arma::field masks_lst;
335 |
336 | if (kmeans_method != "") {
337 |
338 | if (kmeans_method == "mini_batch_kmeans") {
339 |
340 | kmeans_obj = clsh.mini_batch_kmeans(im2, affinty_num_clusters, minib_kmeans_batch, kmeans_max_iters, kmeans_num_init, minib_kmeans_init_fraction,
341 | kmeans_initializer, minib_kmeans_batch, false, R_NilValue, 1e-4, 0.5, 1);
342 |
343 | getcent = Rcpp::as(kmeans_obj[0]);
344 | Rcpp::List preds_lst = clsh.Predict_mini_batch_kmeans(im2, getcent, false, 1.0e-6);
345 | getclust = Rcpp::as(preds_lst[0]);
346 | which_km = "mini-batch-kmeans";
347 | }
348 | else if (kmeans_method == "kmeans") {
349 | kmeans_obj = clsh.KMEANS_rcpp(im2, affinty_num_clusters, kmeans_num_init, kmeans_max_iters,
350 | kmeans_initializer, false, false, R_NilValue, 1e-4, 1.0e-6, 0.5, 1);
351 |
352 | getcent = Rcpp::as(kmeans_obj["centers"]);
353 | getclust = Rcpp::as(kmeans_obj["clusters"]);
354 | which_km = "kmeans";
355 | }
356 | else {
357 | Rcpp::stop("Invalid method for the 'kmeans_method' parameter!");
358 | }
359 | if (verbose) { Rcpp::Rcout << "The " + which_km + " algorithm based on the number of affinity-propagation-clusters was completed!" << std::endl; }
360 |
361 |
362 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
363 | // use centroids and clusters to create the output-kmeans-image
364 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
365 |
366 | arma::uvec idx_NA = NAs_matrix(getcent);
367 | if (!idx_NA.empty()) {
368 | // arma::uvec all_idx = arma::regspace(0, 1, getcent.n_rows-1); // create vector of indices
369 | // arma::uvec keep_dat = afpr.matlab_setdiff(all_idx, idx_NA); // find which indices do not have NA's
370 | // getcent = getcent.rows(keep_dat); // overwrite initial centroids by keeping those who do not include NA's. NOT A GOOD APPROACH because getcent.n_rows != getclust.max() and this raises an error in "new_im.slice(0)(f) = getcent(getclust(f), 0);"
371 | getcent.rows(idx_NA).fill(0); // better fill the centroid-rows including NA's with 0's rather than overwritting the initial centroids matrix (see comment previous line)
372 | std::string tmp_str = std::to_string(idx_NA.n_elem);
373 | Rcpp::Rcout << "WARNING: " + tmp_str + " row(s) of the output centroids include missing values (NA's)!. The missing values will be replaced / filled with 0's! To overcome this warning an option would be to experiment with a different 'kmeans_initializer'!" << std::endl;
374 | }
375 |
376 | if (adjust_centroids_and_return_masks) { // here I solely adjust the centroids
377 | if (verbose) { Rcpp::Rcout << "NOTE: The 'KMeans_image_data' will be returned in black & white colour due to the fact that the 'adjust_centroids_and_return_masks' parameter was set to TRUE!" << std::endl; }
378 | int true_clusters = getcent.n_rows; // it is possible that there are NA's in the centroids
379 | getcent.set_size(true_clusters, 3); // n_cols = 3 because I have an RGB image
380 | for (int i = 0; i < true_clusters; i++) {
381 | arma::rowvec tmp_row(3);
382 | tmp_row.fill(i);
383 | getcent.row(i) = tmp_row; // in case that 'adjust_centroids_and_return_masks' is set to TRUE the KMeans-centroids will be overwritten. Better quality of the 'KMeans_image_data' output can be obtained if 'adjust_centroids_and_return_masks' is set to FALSE.
384 | }
385 | }
386 |
387 | new_im.set_size(input_image.n_rows, input_image.n_cols, input_image.n_slices); // see for a similar way to create the cluster-masks in R : https://github.com/mlampros/ClusterR/issues/14#issuecomment-457692420
388 | new_im.fill(0);
389 | for (unsigned int f = 0; f < getclust.n_elem; f++) {
390 | new_im.slice(0)(f) = getcent(getclust(f), 0); // each observation is associated with the nearby centroid
391 | new_im.slice(1)(f) = getcent(getclust(f), 1);
392 | new_im.slice(2)(f) = getcent(getclust(f), 2);
393 | }
394 |
395 | if (verbose) { Rcpp::Rcout << "Pre-processing of the " + which_km + "-output-image is completed!" << std::endl; }
396 |
397 |
398 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
399 | // display segments of the 'new_im' image
400 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
401 |
402 | masks_lst.set_size(affinty_num_clusters, 1);
403 |
404 | if (adjust_centroids_and_return_masks) { // adjust the centroids and return the masks
405 | for (int IM = 0; IM < affinty_num_clusters; IM++) {
406 | arma::Cube mask_3d(new_im.n_rows, new_im.n_cols, 3);
407 | for (int f = 0; f < 3; f++) {
408 | arma::Mat mask1(new_im.n_rows, new_im.n_cols, arma::fill::zeros);
409 | arma::uvec tmp_uvec = arma::find(new_im.slice(f) == IM);
410 | mask1(tmp_uvec).fill(1);
411 | mask_3d.slice(f) = mask1;
412 | }
413 |
414 | arma::cube tmp_show;
415 |
416 | if (colour_type == "RGB") {
417 | tmp_show = input_image % mask_3d;
418 | }
419 | else if (colour_type == "LAB") {
420 | tmp_show = lab_obj % mask_3d;
421 | }
422 | else if (colour_type == "HSV") {
423 | tmp_show = hsv_tmp % mask_3d;
424 | }
425 | else {
426 | Rcpp::stop("Invalid 'colour_type' parameter!");
427 | }
428 | masks_lst(IM,0) = tmp_show;
429 | }
430 | if (verbose) { Rcpp::Rcout << "The centroids were adjusted and the image-masks will be returned!" << std::endl; }
431 | }
432 | }
433 |
434 | Rcpp::List tmp_lst = Rcpp::List::create(Rcpp::Named("KMeans_image_data") = new_im,
435 | Rcpp::Named("KMeans_clusters") = getclust,
436 | Rcpp::Named("masks") = masks_lst,
437 | Rcpp::Named("centr") = getcent,
438 | Rcpp::Named("AP_image_data") = ap_new_im);
439 |
440 | if (return_labels_2_dimensionsional) {
441 | tmp_lst["spix_labels"] = labels_obj;
442 | tmp_lst["AP_clusters"] = clusters;
443 | }
444 |
445 | return tmp_lst;
446 | }
447 |
448 |
--------------------------------------------------------------------------------
/R/px_utils.R:
--------------------------------------------------------------------------------
1 |
2 |
3 | #' Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering
4 | #'
5 | #'
6 | #' @param input_image a 3-dimensional input image (the range of the pixel values should be preferably in the range 0 to 255)
7 | #' @param method a character string specifying the superpixel method. It can be either "slic" or "slico"
8 | #' @param superpixel a numeric value specifying the number of superpixels
9 | #' @param kmeans_method a character string specifying the kmeans method. If not empty ("") then it can be either "kmeans" or "mini_batch_kmeans"
10 | #' @param AP_data a boolean. If TRUE then the affinity propagation image data will be computed and returned
11 | #' @param use_median a boolean. If TRUE then the median will be used rather than the mean value for the inner computations (see the details section for more information)
12 | #' @param minib_kmeans_batch the size of the mini batches
13 | #' @param kmeans_num_init number of times the algorithm will be run with different centroid seeds
14 | #' @param kmeans_max_iters the maximum number of clustering iterations
15 | #' @param minib_kmeans_init_fraction percentage of data to use for the initialization centroids (applies if initializer is \emph{kmeans++} or \emph{optimal_init}). Should be a float number between 0.0 and 1.0.
16 | #' @param kmeans_initializer the method of initialization. One of, \emph{optimal_init}, \emph{quantile_init}, \emph{kmeans++} and \emph{random}. See details for more information
17 | #' @param colour_type a character string specifying the colour type. It can be one of "RGB", "LAB" or "HSV"
18 | #' @param compactness_factor a numeric value specifying the compactness parameter in case that \emph{method} is "slic"
19 | #' @param adjust_centroids_and_return_masks a boolean. If TRUE and the \emph{kmeans_method} parameter is NOT empty ("") then the centroids will be adjusted and image-masks will be returned. This will allow me to plot the masks using the \emph{spixel_masks_show} method.
20 | #' @param return_labels_2_dimensionsional a boolean. If TRUE then a matrix of labels based on the output superpixels in combination with the Affinity Propagation clusters will be returned
21 | #' @param sim_normalize a boolean. If TRUE then the constructed similarity matrix will be normalised to have unit p-norm (see the armadillo documentation for more details)
22 | #' @param sim_wL a numeric value specifying the weight for the \emph{"L"} channel of the image (see the details section for more information)
23 | #' @param sim_wA a numeric value specifying the weight for the \emph{"A"} channel of the image (see the details section for more information)
24 | #' @param sim_wB a numeric value specifying the weight for the \emph{"B"} channel of the image (see the details section for more information)
25 | #' @param sim_color_radius a numeric value specifying the \emph{colorradius} (see the details section for more information)
26 | #' @param ap_maxits a numeric value specifying the maximum number of iterations for the Affinity Propagation Clustering (defaults to 1000)
27 | #' @param ap_convits a numeric value. If the estimated exemplars stay fixed for convits iterations, the affinity propagation algorithm terminates early (defaults to 100)
28 | #' @param ap_dampfact a float number specifying the update equation damping level in [0.5, 1). Higher values correspond to heavy damping, which may be needed if oscillations occur in the Affinity Propagation Clustering (defaults to 0.9)
29 | #' @param ap_nonoise a float number. The affinity propagation algorithm adds a small amount of noise to \emph{data} to prevent degenerate cases; this disables that.
30 | #' @param delay_display_seconds a numeric value specifying the seconds to delay the display of the next image (It displays the images consecutively). This parameter applies only if the \emph{display_all} is set to FALSE (spixel_masks_show method)
31 | #' @param display_all a boolean. If TRUE then all images will be displayed in a grid (spixel_masks_show method)
32 | #' @param margin_btw_plots a float number specifying the margins between the plots if the \emph{display_all} parameter is set to TRUE (spixel_masks_show method)
33 | #' @param verbose a boolean. If TRUE then information will be printed in the console (spixel_masks_show method)
34 | #' @param spix_labels a matrix. I can retrieve the "spix_labels" parameter by setting the "return_labels_2_dimensionsional" parameter to TRUE in the "spixel_segmentation" method (spixel_clusters_show method)
35 | #' @param color_palette one of the color palettes. Use ?grDevices::topo.colors to see the available color palettes
36 | #' @param parameter_list_png either NULL or a list of parameters passed to the ?grDevices::png function, such as list(filename = 'img.png', width = 100, height = 100, units = "px", pointsize = 12, bg = "white", type = "quartz")
37 | #'
38 | #' @export
39 | #' @references
40 | #'
41 | #' https://www.ijsr.net/archive/v4i4/SUB152869.pdf , "Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering", Bao Zhou, 2013, International Journal of Science and Research (IJSR)
42 | #'
43 | #' @details
44 | #'
45 | #' \emph{sim_wL}, \emph{sim_wA}, \emph{sim_wB} are the weights of the three channels. They keep balance so as to be consistent with human perception.
46 | #'
47 | #' The quantity \emph{colorradius} adjusts the number of clusters, and if its value is low, the number of targets would increase, which leads to more detailed segmentation results.
48 | #'
49 | #' If the \emph{adjust_centroids_and_return_masks} parameter is set to FALSE then the output \emph{kmeans_image_data} will be an RGB image, otherwise it will be a black-and-white image.
50 | #'
51 | #' \emph{colour_type} parameter: RGB (Red-Green-Blue), LAB (Lightness, A-colour-dimension, B-colour-dimension) or HSV (Hue, Saturation, Value) colour.
52 | #'
53 | #' Higher resolution images give better results.
54 | #'
55 | #' The \emph{affinity propagation} algorithm is used here with default parameter values.
56 | #'
57 | #' By setting the \emph{sim_normalize} parameter to TRUE, the affinity propagation algorithm requires less iterations to complete. However, the \emph{colorradius} parameter does not have an
58 | #' effect if the similarity matrix is normalized.
59 | #'
60 | #' Regarding the \emph{use_median} parameter in the Rcpp I use the following steps: \emph{1st.} I compute the superpixels and extract the labels, \emph{2nd.} each superpixel label consists of multiple pixels and for these superpixels I have
61 | #' to compute a dissimilarity matrix therefore each superpixel must correspond to a single value, \emph{3rd.} to come to this single value for each superpixel the R user has the option to either use the 'mean' or the 'median of multiple
62 | #' image pixels (per superpixel)
63 | #'
64 | #'
65 | #' ---------------kmeans initializers----------------------
66 | #'
67 | #' \strong{optimal_init} : this initializer adds rows of the data incrementally, while checking that they do not already exist in the centroid-matrix
68 | #'
69 | #' \strong{quantile_init} : initialization of centroids by using the cummulative distance between observations and by removing potential duplicates
70 | #'
71 | #' \strong{kmeans++} : kmeans++ initialization. Reference : http://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf AND http://stackoverflow.com/questions/5466323/how-exactly-does-k-means-work
72 | #'
73 | #' \strong{random} : random selection of data rows as initial centroids
74 | #'
75 | #' @docType class
76 | #' @importFrom R6 R6Class
77 | #' @importFrom OpenImageR NormalizeObject rotateFixed imageShow readImage
78 | #' @importFrom grDevices png rainbow dev.off
79 | #' @importFrom lattice levelplot
80 | #'
81 | #' @section Methods:
82 | #'
83 | #' \describe{
84 | #' \item{\code{Image_Segmentation$new()}}{}
85 | #'
86 | #' \item{\code{--------------}}{}
87 | #'
88 | #' \item{\code{spixel_segmentation()}}{}
89 | #'
90 | #' \item{\code{--------------}}{}
91 | #'
92 | #' \item{\code{spixel_masks_show()}}{}
93 | #'
94 | #' \item{\code{--------------}}{}
95 | #'
96 | #' \item{\code{spixel_clusters_show()}}{}
97 | #'
98 | #' \item{\code{--------------}}{}
99 | #'
100 | #' }
101 | #' @usage # init <- Image_Segmentation$new()
102 | #' @examples
103 | #'
104 | #' library(SuperpixelImageSegmentation)
105 | #'
106 | #' path = system.file("images", "BSR_bsds500_image.jpg", package = "SuperpixelImageSegmentation")
107 | #'
108 | #' im = OpenImageR::readImage(path)
109 | #'
110 | #' init = Image_Segmentation$new()
111 | #'
112 | #' num_spix = 10 # for illustration purposes
113 | #' # num_spix = 600 # recommended number of superpixels
114 | #'
115 | #' spx = init$spixel_segmentation(input_image = im,
116 | #' superpixel = num_spix,
117 | #' AP_data = TRUE,
118 | #' use_median = TRUE,
119 | #' return_labels_2_dimensionsional = TRUE,
120 | #' sim_color_radius = 10)
121 | #'
122 | #'
123 | #' #...........................
124 | #' # plot the superpixel labels
125 | #' #...........................
126 | #'
127 | #' plt = init$spixel_clusters_show(spix_labels = spx$spix_labels,
128 | #' color_palette = grDevices::rainbow,
129 | #' parameter_list_png = NULL)
130 | #'
131 | #' # plt
132 | #'
133 | #'
134 | #' #....................................................
135 | #' # create a binary image for a specified cluster label
136 | #' #....................................................
137 | #'
138 | #' pix_values = spx$spix_labels
139 | #'
140 | #' target_cluster = 3 # determine clusters visually ('plt' variable)
141 | #'
142 | #' pix_values[pix_values != target_cluster] = 0 # set all other values to 0 (background)
143 | #' pix_values[pix_values == target_cluster] = 1 # set the target_cluster to 1 (binary image)
144 | #'
145 | #' # OpenImageR::imageShow(pix_values)
146 | #'
147 |
148 | Image_Segmentation <- R6::R6Class("Image_Segmentation",
149 |
150 | lock_objects = FALSE,
151 |
152 | public = list(
153 |
154 | initialize = function() {
155 |
156 | },
157 |
158 |
159 | #.................................................................................
160 | # image segmentation using superpixels, Affinity propagation and Kmeans clustering
161 | #.................................................................................
162 |
163 | spixel_segmentation = function(input_image,
164 | method = "slic",
165 | superpixel = 200,
166 | kmeans_method = "",
167 | AP_data = FALSE,
168 | use_median = TRUE,
169 | minib_kmeans_batch = 10,
170 | minib_kmeans_init_fraction = 0.5,
171 | kmeans_num_init = 3,
172 | kmeans_max_iters = 100,
173 | kmeans_initializer = "kmeans++",
174 | colour_type = "RGB",
175 | compactness_factor = 20,
176 | adjust_centroids_and_return_masks = FALSE,
177 | return_labels_2_dimensionsional = FALSE,
178 | sim_normalize = FALSE,
179 | sim_wL = 3,
180 | sim_wA = 10,
181 | sim_wB = 10,
182 | sim_color_radius = 20,
183 | ap_maxits = 1000,
184 | ap_convits = 100,
185 | ap_dampfact = 0.9,
186 | ap_nonoise = 0.0,
187 | verbose = FALSE) {
188 |
189 | if (!kmeans_initializer %in% c('kmeans++', 'random', 'optimal_init', 'quantile_init')) stop("available initializer methods are 'kmeans++', 'random', 'optimal_init' and 'quantile_init'", call. = F)
190 |
191 | if (verbose) {
192 | t_start = proc.time()
193 | }
194 | if (adjust_centroids_and_return_masks) {
195 | private$masks_flag = T
196 | }
197 | private$lst_obj = image_segmentation(input_image = input_image,
198 | method = method,
199 | num_superpixel = superpixel,
200 | kmeans_method = kmeans_method,
201 | AP_data = AP_data,
202 | use_median = use_median,
203 | minib_kmeans_batch = minib_kmeans_batch,
204 | minib_kmeans_init_fraction = minib_kmeans_init_fraction,
205 | kmeans_num_init = kmeans_num_init,
206 | kmeans_max_iters = kmeans_max_iters,
207 | kmeans_initializer = kmeans_initializer,
208 | colour_type = colour_type,
209 | compactness_factor = compactness_factor,
210 | adjust_centroids_and_return_masks = adjust_centroids_and_return_masks,
211 | return_labels_2_dimensionsional = return_labels_2_dimensionsional,
212 | sim_normalize = sim_normalize,
213 | sim_wL = sim_wL,
214 | sim_wA = sim_wA,
215 | sim_wB = sim_wB,
216 | sim_color_radius = sim_color_radius,
217 | ap_maxits = ap_maxits,
218 | ap_convits = ap_convits,
219 | ap_dampfact = ap_dampfact,
220 | ap_details = verbose, # use 'verbose' also for the affinity propagation 'details'
221 | ap_nonoise = ap_nonoise,
222 | ap_time = verbose, # use 'verbose' also for the affinity propagation 'time' (this might also give an indication if the affinity propagation algorithm is a bottleneck for specific use cases - especially if the dissimilarity matrix becomes too big due to the specified number of super-pixels)
223 | verbose = verbose)
224 |
225 | if (return_labels_2_dimensionsional) {
226 | lbs_out = matrix(0, nrow = nrow(private$lst_obj$spix_labels), ncol = ncol(private$lst_obj$spix_labels)) # initialize a new matrix because the pixel values of the 'spix_labels' matrix might overlap with the cluster numbers (iterations: 'i')
227 | for (i in 1:length(private$lst_obj$AP_clusters)) {
228 | idx = which(private$lst_obj$spix_labels %in% private$lst_obj$AP_clusters[[i]]) # more efficient than using c++
229 | lbs_out[idx] = i
230 | }
231 |
232 | private$lst_obj$spix_labels = NULL # remove the 'spix_labels' object
233 | private$lst_obj$AP_clusters = NULL # remove the 'AP-clusters' object
234 | private$lst_obj$spix_labels = lbs_out # assign the initialized and populated matrix to the output list
235 | }
236 |
237 | if (verbose) {
238 | t_end = proc.time()
239 | time_total = as.numeric((t_end - t_start)['elapsed'])
240 | time_ = private$elapsed_time(time_total)
241 | cat(time_, "\n")
242 | }
243 |
244 | return(private$lst_obj)
245 | },
246 |
247 |
248 | #.................................................................
249 | # plot the image segmentation masks (based on the output clusters)
250 | #.................................................................
251 |
252 | spixel_masks_show = function(delay_display_seconds = 3,
253 | display_all = FALSE,
254 | margin_btw_plots = 0.15,
255 | verbose = FALSE) {
256 |
257 | if (is.null(private$lst_obj)) { stop("First run the 'spixel_segmentation' method with the 'adjust_centroids_and_return_masks' parameter set to TRUE!", call. = F) }
258 | if (!private$masks_flag) { stop("The 'adjust_centroids_and_return_masks' parameter of the 'spixel_segmentation' method should be set to TRUE!", call. = F) }
259 |
260 | if (display_all) {
261 |
262 | grid_r_c = private$calc_grid_rows_cols(length(private$lst_obj$masks))
263 | par(mfrow = c(grid_r_c$rows, grid_r_c$cols), mar = c(margin_btw_plots, margin_btw_plots, margin_btw_plots, margin_btw_plots))
264 |
265 | for (plt in 1:length(private$lst_obj$masks)) {
266 | tmp_im = OpenImageR::NormalizeObject(private$lst_obj$masks[[plt]])
267 | has_nan = unlist(lapply(1:dim(tmp_im)[3], function(x) is_mt_finite(tmp_im[,,x])))
268 | if (sum(has_nan) != 3) {
269 | cat("WARNING: The image-mask", plt, "includes non-finite data and won't be plotted!", "\n")
270 | }
271 | else {
272 | tmp_im = grDevices::as.raster(tmp_im)
273 | graphics::plot(tmp_im)
274 | }
275 | }
276 | }
277 | else {
278 | for (i in 1:length(private$lst_obj$masks)) {
279 | has_nan = unlist(lapply(1:dim(private$lst_obj$masks[[i]])[3], function(x) is_mt_finite(private$lst_obj$masks[[i]][,,x])))
280 | if (verbose) { cat("Image:", i, "\n") }
281 | if (sum(as.vector(private$lst_obj$masks[[i]])) == 0) {
282 | cat("WARNING: The image mask", i, "is an all-0's-image!", "\n")
283 | }
284 | else if (sum(has_nan) != 3) {
285 | cat("WARNING: The image-mask", i, "includes non-finite data and won't be plotted!", "\n")
286 | }
287 | else {
288 | OpenImageR::imageShow(private$lst_obj$masks[[i]])
289 | Sys.sleep(delay_display_seconds)
290 | }
291 | }
292 | }
293 | },
294 |
295 |
296 | #......................................................................................................
297 | # plot 2-dimensional superpixel clusters along with a legend so that I'll be in place to distinguish
298 | # between the cluster labels
299 | #
300 | # I can retrieve the "spix_labels" parameter by setting the "return_labels_2_dimensionsional" parameter
301 | # to TRUE in the "spixel_segmentation" method of this R6 class
302 | #......................................................................................................
303 |
304 | spixel_clusters_show = function(spix_labels,
305 | color_palette = grDevices::rainbow,
306 | parameter_list_png = NULL) {
307 |
308 | if (!inherits(spix_labels, 'matrix')) stop("The 'spix_labels' parameter must be of type matrix (set the 'return_labels_2_dimensionsional' parameter to TRUE in the 'spixel_segmentation' method)!", call. = F)
309 |
310 | LEN_UNQ = length(unique(as.vector(spix_labels)))
311 | spix_labels = OpenImageR::rotateFixed(spix_labels, angle = 90) # rotate the output labels to visualize the image
312 |
313 | if (!is.null(parameter_list_png)) {
314 | do.call(grDevices::png, parameter_list_png)
315 | }
316 |
317 | lat_plt = lattice::levelplot(spix_labels,
318 | xlab = NULL,
319 | ylab = NULL,
320 | cuts = LEN_UNQ - 1,
321 | col.regions = do.call(color_palette, list(n = LEN_UNQ)), # use a color-palette with default parameters
322 | useRaster = TRUE) # for the 'lattice::levelplot()' see: https://stackoverflow.com/a/15188726/8302386 + using 'useRaster' the plot is returned faster
323 |
324 | if (!is.null(parameter_list_png)) {
325 | print(lat_plt)
326 | grDevices::dev.off()
327 | }
328 |
329 | return(lat_plt)
330 | }
331 |
332 | ),
333 |
334 | private = list(
335 |
336 | lst_obj = NULL,
337 | masks_flag = F,
338 |
339 | #.....................................
340 | # divisors calculation
341 | # https://stackoverflow.com/a/19465753
342 | #.....................................
343 |
344 | divisors = function(x){
345 | y <- seq_len(x)
346 | y[ x%%y == 0 ]
347 | },
348 |
349 |
350 | #.................................................................................
351 | # calculate the grid-rows and grid-cols (for specific number of sub-matrices < 53)
352 | #.................................................................................
353 |
354 | calc_grid_rows_cols = function(num_masks) {
355 |
356 | if (length(num_masks) > 52) stop("Plotting of grid of images using the 'calc_grid_rows_cols' function is restricted to 52 objects!", call. = F)
357 |
358 | rows = sqrt(num_masks)
359 | cols = 0
360 | DIVS = private$divisors(num_masks)
361 | LEN = length(DIVS)
362 | if (LEN > 2 && (num_masks %% rows) != 0) {
363 | rows = DIVS[LEN/2]
364 | cols = DIVS[(LEN/2)+1]
365 | }
366 | else {
367 | if ((num_masks %% rows) == 0) { # square int's
368 | cols = rows
369 | }
370 | else {
371 | if ((num_masks %% rows) == 0) {
372 | rows = round(rows)
373 | cols = num_masks / rows
374 | }
375 | else {
376 | rows = floor(rows)
377 | cols = ceiling(num_masks / rows)
378 | }
379 | }
380 | if (((rows * cols) - num_masks) > 1) { # neither square nor divisors [ primes ]
381 | DIF = ((rows * cols) - num_masks)
382 | rows = rows - DIF + 1
383 | if ((num_masks %% rows) == 0) {
384 | cols = rows
385 | }
386 | else {
387 | if ((num_masks %% rows) == 0) {
388 | rows = round(rows)
389 | cols = num_masks / rows
390 | }
391 | else {
392 | rows = floor(rows)
393 | cols = ceiling(num_masks / rows)
394 | }
395 | }
396 | }
397 | }
398 | return(list(rows = rows, cols = cols))
399 | },
400 |
401 |
402 | #..........................................
403 | # elapsed time in hours & minutes & seconds
404 | #..........................................
405 |
406 | elapsed_time = function(secs) {
407 | tmp_hours = as.integer((secs / 60) / 60)
408 | tmp_hours_minutes = (secs / 60) %% 60
409 | tmp_seconds = secs %% 60
410 | res_out = paste(c("Elapsed time: ", tmp_hours, " hours and ", as.integer(tmp_hours_minutes), " minutes and ", as.integer(tmp_seconds), " seconds."), collapse = "")
411 | return(res_out)
412 | }
413 | )
414 | )
415 |
--------------------------------------------------------------------------------