#include <RcppArmadillo.h>

#include <Rcpp.h>
// [[Rcpp::plugins(cpp11)]]

using namespace Rcpp;
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::depends(RcppParallel)]]

#include <RcppParallel.h>

using namespace RcppParallel;

// GreyScale function

// Define greyscale function
int greyscale(const int a, const int b, const int c) 
{    
  int output;
  output = round((0.30 * a) + (0.59 * b) + (0.11 * c));
  return output;
}

// Declare worker object for parallel computing of greyscale
struct GreyScale : public Worker
{
  // Source matrices
  const RMatrix<int> red;
  const RMatrix<int> green;
  const RMatrix<int> blue;
  // Destination matrix
  RMatrix<int> grey;
  // Initialize with source and destination
  GreyScale(const IntegerMatrix red, const IntegerMatrix green, const IntegerMatrix blue, IntegerMatrix grey) 
    : red(red), green(green), blue(blue), grey(grey) {}
  // Apply greyscale() to the corresponding elements of the rgb matrices
  void operator()(std::size_t begin, std::size_t end) {
    for (std::size_t i = begin; i < end; i++) {
      grey[i] = greyscale(red[i], green[i], blue[i]); } }
};

// Define parallel computing greyscale_par function 

// [[Rcpp::export]]
IntegerMatrix greyscale_par(const IntegerMatrix red, const IntegerMatrix green, const IntegerMatrix blue) 
{
  // Allocate the output matrix
  IntegerMatrix grey(red.nrow(), red.ncol());
  // GreyScale function (pass input and output matrices)
  GreyScale greyscale(red, green, blue, grey);
  // Call parallelFor to do the work
  parallelFor(0, red.size(), greyscale);
  // Return the output matrix
  return grey;
}


// Define Skeletonization function

// [[Rcpp::export]]
IntegerMatrix skeletonization(const IntegerMatrix x) 
{
  // Define objects
  int nrow = x.nrow(), ncol = x.ncol(), n = x.size();
  IntegerMatrix binary_matrix(nrow, ncol);
  std::unordered_set<int> even_border; 
  std::unordered_set<int> odd_border;
  int diagonal = nrow + 1;
  int anti_diagonal = nrow - 1;
  int c;
  int index;
  // Binarization & set partition
  for (int j = 1; j < (ncol - 1); j++) {
    c = (j * nrow);
    for (int i = 1; i < anti_diagonal; i++) {
      // Binarize if positive
      if (x(i, j) > 0) { index = c + i; binary_matrix[index] = 1;
      // Determine if on the border (4n connectivity)
      if ((x[index - 1] == 0) || (x[index + 1] == 0) || (x[index - nrow] == 0) || (x[index + nrow] == 0)) {
        // Allocate to appropriate set
        if (((i + j) % 2) == 0) { even_border.emplace(index); } else { odd_border.emplace(index); } } } } }
  // Perform skeletonization
  // Define objects
  std::unordered_set<int> even_remove; 
  std::unordered_set<int> odd_remove;
  int condition = 1;
  int p2, p3, p4, p5, p6, p7, p8, p9;
  int temp;
  // Perform iterations
  while (condition == 1) {  
    // Sub iteration 1
    // Identify elements to be removed
    for (auto const & p : even_border) {
      // Obtain linear index
      index = p;
      // Test condition 1
      p2 = binary_matrix[index - 1];
      p4 = binary_matrix[index + nrow];
      p6 = binary_matrix[index + 1];
      if ((p2 == 1) && (p4 == 1) && (p6 == 1)) { continue; }
      // Test condition 2
      p8 = binary_matrix[index - nrow];
      if ((p4 == 1) && (p6 == 1) && (p8 == 1)) { continue; }
      // Test condition 3
      p3 = binary_matrix[index + anti_diagonal];
      p5 = binary_matrix[index + diagonal];
      p7 = binary_matrix[index - anti_diagonal];
      p9 = binary_matrix[index - diagonal];
      temp = (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9);
      if ((temp < 2) || (temp > 7)) { continue; } 
      // Test condition 4
      temp = 0;
      if ((p2 == 0) && ((p3 == 1) || (p4 == 1))) { temp = 1; }
      if ((p4 == 0) && ((p5 == 1) || (p6 == 1))) { if (temp == 0) { temp = 1; } else { continue; } }
      if ((p6 == 0) && ((p7 == 1) || (p8 == 1))) { if (temp == 0) { temp = 1; } else { continue; } }
      if ((p8 == 0) && ((p9 == 1) || (p2 == 1))) { if (temp == 0) { temp = 1; } else { continue; } }
      // Store position for removal if conditions are true
      if (temp == 1) { even_remove.emplace(index); 
        if (p2 == 1) { odd_border.emplace(index - 1); }
        if (p4 == 1) { odd_border.emplace(index + nrow); }
        if (p6 == 1) { odd_border.emplace(index + 1); }
        if (p8 == 1) { odd_border.emplace(index - nrow); } } }
    // Remove identified elements (if any)
    if (even_remove.size() != 0) { 
      for (auto const & u : even_remove) { 
        binary_matrix[u] = 0; even_border.erase(u); }
      even_remove.clear(); } else { condition = 0; }
      // Sub iteration 2
      // Identify elements to be removed
      for (auto const & p : odd_border) {
        // Obtain linear index
        index = p;
        // Test condition 1
        p2 = binary_matrix[index - 1];
        p4 = binary_matrix[index + nrow];
        p8 = binary_matrix[index - nrow];
        if ((p2 == 1) && (p4 == 1) && (p8 == 1)) { continue; }
        // Test condition 2
        p6 = binary_matrix[index + 1];
        if ((p2 == 1) && (p6 == 1) && (p8 == 1)) { continue; }
        // Test condition 3
        p3 = binary_matrix[index + anti_diagonal];
        p5 = binary_matrix[index + diagonal];
        p7 = binary_matrix[index - anti_diagonal];
        p9 = binary_matrix[index - diagonal];
        temp = (p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9);
        if ((temp < 2) || (temp > 7)) { continue; } 
        // Test condition 4
        temp = 0;
        if ((p2 == 0) && ((p3 == 1) || (p4 == 1))) { temp = 1; }
        if ((p4 == 0) && ((p5 == 1) || (p6 == 1))) { if (temp == 0) { temp = 1; } else { continue; } }
        if ((p6 == 0) && ((p7 == 1) || (p8 == 1))) { if (temp == 0) { temp = 1; } else { continue; } }
        if ((p8 == 0) && ((p9 == 1) || (p2 == 1))) { if (temp == 0) { temp = 1; } else { continue; } }
        // Store position for removal if conditions are true
        if (temp == 1) { odd_remove.emplace(index); 
          if (p2 == 1) { even_border.emplace(index - 1); }
          if (p4 == 1) { even_border.emplace(index + nrow); }
          if (p6 == 1) { even_border.emplace(index + 1); }
          if (p8 == 1) { even_border.emplace(index - nrow); } } }
      // Remove identified elements (if any)
      if (odd_remove.size() != 0) { 
        for (auto const & u : odd_remove) { 
          binary_matrix[u] = 0; odd_border.erase(u); }
        odd_remove.clear();
        condition = 1; } }
  // Assign 255 to remaining elements
  for (int i = 0; i < n; ++i) { 
    if (binary_matrix[i] == 1) { binary_matrix[i] = 255; } }
  // Return output
  return binary_matrix; 
}


// Euclidean distance transform function

// Declare worker object for parallel computing of Euclidean distance (central)
struct Euclidean : public Worker 
{
  // Source vectors
  const RVector<int> skeleton;
  const RVector<int> border_x;
  const RVector<int> border_y;
  const RVector<int> constants;
  // Destination matrix
  RMatrix<int> distance_matrix;
  // Initialize with source and destination
  Euclidean(const IntegerVector skeleton, const IntegerVector border_x, const IntegerVector border_y, const IntegerVector constants, IntegerMatrix distance_matrix)
    : skeleton(skeleton), border_x(border_x), border_y(border_y), constants(constants), distance_matrix(distance_matrix) {}
  // Apply Euclidean distance function to the range of elements requested
  void operator()(std::size_t begin, std::size_t end) {
    for (std::size_t j = begin; j < end; ++j) {
      int index = skeleton[j];
      int x_coord = (index % constants[0]);
      int y_coord = (index / constants[0]);
      double d; 
      double d_min = constants[1];
      for (int i = 0; i < border_x.size(); ++i) {  
        d = sqrt(pow((x_coord - border_x[i]), 2) + pow((y_coord - border_y[i]), 2));
        if (d < d_min) { d_min = d; } } 
      distance_matrix[index] = round(d_min); } }
};


// [[Rcpp::export]]
IntegerMatrix distance(const IntegerMatrix x, const IntegerMatrix y) 
{
  // Define objects
  int nrow = x.nrow(), ncol = x.ncol(), n = x.size();
  int diagonal = nrow + 1;
  int anti_diagonal = nrow - 1;
  int c, index;
  // Identify border and skeleton pixels (central)
  std::vector<int> border_id;
  std::vector<int> skeleton_id;
  for (int j = 1; j < (ncol - 1); j++) {
    c = (j * nrow);
    for (int i = 1; i < (nrow - 1); i++) {
      if (x(i, j) > 0) { 
        index = c + i;
        if ((x[index - 1] == 0) || (x[index + 1] == 0) || (x[index + nrow] == 0) || (x[index - nrow] == 0) ||
            (x[index - diagonal] == 0) || (x[index + diagonal] == 0) || (x[index - anti_diagonal] == 0) || (x[index + anti_diagonal] == 0)) {
          border_id.emplace_back(index); } } 
      if (y(i, j) > 0) { skeleton_id.emplace_back(c + i); } } }
  // Obtain border pixel x and y coordinates
  IntegerVector border_x(border_id.size());
  IntegerVector border_y(border_id.size());
  for (int i = 0; i < border_id.size(); ++i) { index = border_id[i]; border_x[i] = (index % nrow); border_y[i] = (index / nrow); }
  std::vector<int>().swap(border_id);
  IntegerVector skeleton = wrap(skeleton_id);
  std::vector<int>().swap(skeleton_id);
  // Define constants vector
  IntegerVector constants(1);
  constants[0] = nrow;
  constants[1] = n;
  // Allocate the output matrix
  IntegerMatrix distance_matrix(nrow, ncol);
  // Euclidean function (pass input vectors and output matrix)
  Euclidean euclidean(skeleton, border_x, border_y, constants, distance_matrix);
  // Call parallelFor to do the work
  parallelFor(0, skeleton.size(), euclidean);
  // Return output
  return distance_matrix;
}


////////////////////////////////////////////////////////////////////////////////////


// [[Rcpp::export]]
IntegerVector filter_indices(const IntegerMatrix x) {
  // Define objects
  int nrow = x.nrow(); 
  int ncol = x.ncol(); 
  int n = x.size();
  int diagonal = nrow + 1; 
  int anti_diagonal = nrow - 1;
  // Circle detection (Screw removal)
  // Gausian convolution (sigma = 15, radius = 45)  
  // Define objects
  int upper_col = ncol - 45;
  int upper_row = nrow - 45;
  double temp; 
  std::vector<double> Gauss_kernel = { 0.000297,	0.000361,	0.000439,	0.00053,	0.000637,	0.000762,	0.000909,	0.001078,	0.001274,	0.001498,	0.001754,	0.002044,	0.002372,	0.002741,	0.003153,	
                                       0.00361,	0.004116,	0.004671,	0.005278,	0.005938,	0.00665,	0.007415,	0.008231,	0.009096,	0.010008,	0.010962,	0.011954,	0.012978,	0.014027,	0.015094,	
                                       0.01617,	0.017246,	0.018313,	0.019358,	0.020373,	0.021346,	0.022266,	0.023123,	0.023907,	0.024607,	0.025216,	0.025725,	0.026128,	0.02642,	0.026597 };
  std::vector<int> shift_horizontal;
  std::vector<int> shift_vertical;
  shift_horizontal.reserve(45);
  shift_vertical.reserve(45);
  for (int i = 45; i >= 1; i--) { shift_horizontal.emplace_back(i * nrow); }
  for (int i = 45; i >= 1; i--) { shift_vertical.emplace_back(i); }
  std::vector<double> Gauss_temp(n);
  IntegerMatrix matrix_1(nrow, ncol);
  // Central Horizontal Convolution
  for (int j = 45; j < upper_col; ++j) {
    for (int i = (j * nrow); i < ((j + 1) * nrow); ++i) {
      temp = (0.026656 * x[i]);
      for (int k = 0; k < 45; ++k) {
        temp += ((x[i - shift_horizontal[k]] + x[i + shift_horizontal[k]]) * Gauss_kernel[k]); }
      Gauss_temp[i] = temp; } }
  // Central Vertical Convolution
  for (int j = 0; j < ncol; ++j) {
    for (int i = ((j * nrow) + 45); i < (((j + 1) * nrow) - 45); ++i) {
      temp = (0.026656 * Gauss_temp[i]);
      for (int k = 0; k < 45; ++k) {
        temp += ((Gauss_temp[i - shift_vertical[k]] + Gauss_temp[i + shift_vertical[k]]) * Gauss_kernel[k]); }
      matrix_1[i] = std::round(temp); } }
  // Thresholding (>= 160) 
  for (int i = 0; i < n; ++i) {
    if (matrix_1[i] >= 160) {
      matrix_1[i] = 255; 
    } else {
      matrix_1[i] = 0; } }
  std::vector<double>().swap(Gauss_kernel);
  std::vector<double>().swap(Gauss_temp);
  std::vector<int>().swap(shift_horizontal);
  std::vector<int>().swap(shift_vertical);
  // Connected components labelling 
  // Obtain vertical sequences
  upper_col = ncol - 1;
  upper_row = nrow - 1;
  std::vector<int> vertical_seq_start;
  std::vector<int> vertical_seq_end;
  int condition;
  for (int j = 1; j < upper_col; ++j) {
    condition = 0;
    for (int i = ((j * nrow) + 1); i < ((j + 1) * nrow) - 1; ++i) {
      if (matrix_1[i] == 255) {
        if (condition == 0) {
          if (matrix_1[i + 1] == 0) { vertical_seq_start.emplace_back(i); vertical_seq_end.emplace_back(i); 
          } else { vertical_seq_start.emplace_back(i); condition = 1; } } else {
            if (matrix_1[i + 1] == 0) { vertical_seq_end.emplace_back(i); condition = 0; } } } } }
  std::fill(matrix_1.begin(), matrix_1.end(), 0);
  // Connected Components Labeling (starting/ending row/column excluded)
  // Define objects
  std::vector<int> label_list;
  label_list.reserve(vertical_seq_start.size());
  std::map<int, int> label_map;
  std::set<int> label_track;
  int current_label = 1;
  int a, seq_start, seq_end, label, min_label;
  // 1st column labelling
  // Assign current label to vertical sequence 
  for (int i = vertical_seq_start[0]; i <= vertical_seq_end[0]; ++i) { matrix_1[i] = current_label; }
  // Add label starting vertical sequence index in "label_map"
  label_map.emplace(current_label, 0); 
  // Add the label to the "label_list"
  label_list.emplace_back(current_label);  
  // Increment "current_label" by 1
  current_label += 1; 
  // Labelling of the remaining columns
  for (int j = 1; j < vertical_seq_start.size(); ++j) {
    // Obtain adjacent labels (if present)
    seq_start = vertical_seq_start[j];
    seq_end = vertical_seq_end[j];
    for (int i = (seq_start - diagonal); i <= (seq_end - anti_diagonal); ++i) {
      if (matrix_1[i] > 0) { label_track.emplace(matrix_1[i]); } }
    // If no adjacent labels are found
    if (label_track.empty()) { 
      // Assign "current_label" to vertical sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_1[i] = current_label; }
      // Add label starting vertical sequence index in "label_map"	
      label_map.emplace(current_label, j); 
      // Add "current_label" to the "label_list"
      label_list.emplace_back(current_label); 
      // Increment "current_label" by 1 
      current_label += 1; 
      // If 1 adjacent label is found
    } else if (label_track.size() == 1) { 
      // Obtain label value
      label = *label_track.begin();
      // Assign adjacent label  to vertical sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_1[i] = label; }
      // Add label to the "label_list"
      label_list.emplace_back(label);
      // Clear "label_track"
      label_track.clear(); 
      // If > 1 adjacent label is found
    } else {
      // Obtain minimum adjacent label
      min_label = *label_track.begin();
      // Assign minimum adjacent label to current sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_1[i] = min_label; }
      // Add minimum adjacent label to the "label_list"
      label_list.emplace_back(min_label);
      // Remove "min_label" from the "label_track" set
      label_track.erase(label_track.find(min_label));
      // Retrieve label starting vertical sequence index in "label_map"
      a = label_map[*label_track.begin()];
      // If only 1 label remains
      if (label_track.size() == 1) {
        label = *label_track.begin();
        // Obtain corresponding vertical sequence indices 
        for (int k = a; k < j; ++k) { 
          if (label_list[k] == label) {  
            // Assign minimum label to detected previous vertical sequences 
            label_list[k] = min_label;
            for (int i = vertical_seq_start[k]; i <= vertical_seq_end[k]; ++i) { matrix_1[i] = min_label; } } }
        // Erase label from "label_map"
        label_map.erase(label);
      } else {
        // Obtain corresponding vertical sequence indices
        for (int k = a; k < j; ++k) {
          if (std::binary_search(label_track.begin(), label_track.end(), label_list[k])) { 
            // Assign minimum label to detected previous vertical sequences 
            label_list[k] = min_label;
            for (int i = vertical_seq_start[k]; i <= vertical_seq_end[k]; ++i) { matrix_1[i] = min_label; } } } 
        // Erase labels from "label_map"
        for (auto const & u : label_track) { label_map.erase(u); } }
      // Clear "label_track"
      label_track.clear(); } }
  // Particle removal
  // Obtain label frequency map
  std::map<int, int> label_frequencies;
  for (int j = 0; j < label_list.size(); ++j) { 
    for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) { 
      ++label_frequencies[matrix_1[i]]; } }
  // Obtain map of particle centroids
  std::map<int, std::pair<long double, long double>> particle_coordinates;
  int area, seq_length, x_coord, y_coord_start, y_coord_end; 
  double x_sum, y_sum;
  for (auto const & p : label_frequencies) {
    label = p.first;
    area = p.second;
    x_sum = 0;
    y_sum = 0;
    for (int j = label_map[label]; j < label_list.size(); ++j) {
      if (label_list[j] == label) {
        seq_start = vertical_seq_start[j];
        seq_end = vertical_seq_end[j];
        seq_length = (seq_end - seq_start);
        x_coord = (seq_start / nrow);
        y_coord_start = (seq_start % nrow);
        y_coord_end = (seq_end % nrow);  
        if (seq_length == 0) { 
          x_sum += x_coord;
          y_sum += y_coord_start; 
        } else if (seq_length == 1) { 
          x_sum += (x_coord * 2); 
          y_sum += (y_coord_start + y_coord_end);
        } else {
          x_sum += (x_coord * (seq_length + 1));
          y_sum += (((y_coord_end * (y_coord_end + 1)) - ((y_coord_start - 1) * y_coord_start)) / 2); } } }
    x_sum /= area;
    y_sum /= area;
    particle_coordinates.emplace(label, std::make_pair(x_sum, y_sum)); }
  std::map<int, int>().swap(label_map);
  // Identify circular areas above threshold value (> 0.7)
  int x_center, y_center, x_left, x_right, y_up, y_down, temporary, r, r2;
  double overlap;
  double pi = 3.14159265;
  IntegerMatrix matrix_2(nrow, ncol);
  for (auto const & p : particle_coordinates) {
    label = p.first;
    area = label_frequencies[label];
    r = std::round(sqrt(area / pi));
    if ((area >= 10000) && (r >= 150)) {
      x_center = std::round(std::get<0>(p.second));
      y_center = std::round(std::get<1>(p.second));
      r2 = (r * r);
      x_left = x_center - r;
      x_right = x_center + r;
      if (x_left < 0) { x_left = 0; }
      if (x_right > upper_col) { x_right = upper_col; }         
      overlap = 0;
      for (int j = x_left; j <= x_right; ++j) {
        temporary = std::round(sqrt(r2 - std::pow((j - x_center), 2)));
        y_up = y_center - temporary;
        y_down = y_center + temporary;
        if (y_up < 0) { y_up = 0; } 
        if (y_down > upper_row) { y_down = upper_row; }                  
        for (int i = y_up; i <= y_down; ++i) {
          if (matrix_1(i, j) == label) { 
            overlap += 1; } } }
      if ((overlap / area) > 0.7) {
        r = 350;
        r2 = (r * r);
        x_left = x_center - r;
        x_right = x_center + r;
        if (x_left < 0) { x_center = 0; x_left = 0; x_right = 350; }
        if (x_right > upper_col) { x_center = upper_col; x_left = upper_col - 350; x_right = upper_col; }
        for (int j = x_left; j <= x_right; ++j) {
          temporary = std::round(sqrt(r2 - std::pow((j - x_center), 2)));
          y_up = y_center - temporary;
          y_down = y_center + temporary;
          if (y_up < 0) { y_up = 0; } 
          if (y_down > upper_row) { y_down = upper_row; }                         
          for (int i = y_up; i <= y_down; ++i) {
            matrix_2(i, j) = 255; } } } } }
  std::vector<int> temp_vec;
  for (int i = 0; i < n; ++i) {
    if (matrix_2[i] == 255) {
      temp_vec.emplace_back(i); } }
  // Morphological Opening (Disk SE, r = 5)
  // Erosions
  std::copy(x.begin(), x.end(), matrix_1.begin());
  std::copy(x.begin(), x.end(), matrix_2.begin());
  for (int i = 0; i < nrow; ++i) {
    matrix_1[i] = 255;
    matrix_2[i] = 255; }
  for (int i = (n - nrow); i < n; ++i) {
    matrix_1[i] = 255;
    matrix_2[i] = 255; }
  for (int j = 0; j < ncol; ++j) {
    matrix_1(0, j) = 255;
    matrix_2(0, j) = 255;
    matrix_1(upper_row, j) = 255;
    matrix_2(upper_row, j) = 255; }
  // 1st Erosion (3 x 1)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_1[i] = std::min(matrix_2[i], std::min(matrix_2[i - 1], matrix_2[i + 1])); } }
  // 2nd Erosion (3 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_2[i] = std::min(matrix_1[i], std::min(matrix_1[i - anti_diagonal], matrix_1[i + anti_diagonal])); } }
  // 3rd Erosion (1 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_1[i] = std::min(matrix_2[i], std::min(matrix_2[i - nrow], matrix_2[i + nrow])); } }
  // 4th Erosion (3 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_2[i] = std::min(matrix_1[i], std::min(matrix_1[i - diagonal], matrix_1[i + diagonal])); } }
  // 5th Erosion (1 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_1[i] = std::min(matrix_2[i], std::min(matrix_2[i - nrow], matrix_2[i + nrow])); } }
  // 6th Erosion (3 x 1)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_2[i] = std::min(matrix_1[i], std::min(matrix_1[i - 1], matrix_1[i + 1])); } }
  // Dilations
  for (int i = 0; i < nrow; ++i) {
    matrix_1[i] = 0;
    matrix_2[i] = 0; }
  for (int i = (n - nrow); i < n; ++i) {
    matrix_1[i] = 0;
    matrix_2[i] = 0; }
  for (int j = 0; j < ncol; ++j) {
    matrix_1(0, j) = 0;
    matrix_2(0, j) = 0;
    matrix_1(upper_row, j) = 0;
    matrix_2(upper_row, j) = 0; }
  // 1st Dilation (3 x 1)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_1[i] = std::max(matrix_2[i], std::max(matrix_2[i - 1], matrix_2[i + 1])); } }
  // 2nd Dilation (3 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_2[i] = std::max(matrix_1[i], std::max(matrix_1[i - anti_diagonal], matrix_1[i + anti_diagonal])); } }
  // 3rd Dilation (1 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_1[i] = std::max(matrix_2[i], std::max(matrix_2[i - nrow], matrix_2[i + nrow])); } }
  // 4th Dilation (3 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_2[i] = std::max(matrix_1[i], std::max(matrix_1[i - diagonal], matrix_1[i + diagonal])); } }
  // 5th Dilation (1 x 3)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_1[i] = std::max(matrix_2[i], std::max(matrix_2[i - nrow], matrix_2[i + nrow])); } }
  // 6th Dilation (3 x 1)
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      matrix_2[i] = std::max(matrix_1[i], std::max(matrix_1[i - 1], matrix_1[i + 1])); } }
  // Variance filter (r = 2)
  double xx_sum, prev_sum_x, prev_sum_xx, current_sum;
  int q1 = (nrow * 2) + 2;
  int q2 = (nrow * 2) + 1;
  int q3 = (nrow * 2);
  int q4 = (nrow * 2) - 1;
  int q5 = (nrow * 2) - 2;
  int q6 = nrow + 2;
  int q7 = nrow - 2;
  int q8 = (nrow * 2) + 3;
  int q9 = nrow + 3;
  int q10 = nrow - 3;
  int q11 = (nrow * 2) - 3;
  int start;
  upper_col = ncol - 2;
  std::fill(matrix_1.begin(), matrix_1.end(), 0);
  for (int j = 2; j < upper_col; ++j) {
    start = ((j * nrow) + 2);
    x_sum = 0;
    xx_sum = 0;
    temp = x[start - q1]; x_sum += temp; xx_sum += (temp * temp);  
    temp = x[start - q2]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - q3]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - q4]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - q5]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - q6]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - anti_diagonal]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - nrow]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start - diagonal]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start - q7]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start - 2]; x_sum += temp; xx_sum += (temp * temp);  
    temp = x[start - 1]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start + 1]; x_sum += temp; xx_sum += (temp * temp);  
    temp = x[start + 2]; x_sum += temp; xx_sum += (temp * temp); 
    temp = x[start + q7]; x_sum += temp; xx_sum += (temp * temp);      
    temp = x[start + diagonal]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start + nrow]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start + anti_diagonal]; x_sum += temp; xx_sum += (temp * temp);      
    temp = x[start + q6]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start + q5]; x_sum += temp; xx_sum += (temp * temp);      
    temp = x[start + q4]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start + q3]; x_sum += temp; xx_sum += (temp * temp);
    temp = x[start + q2]; x_sum += temp; xx_sum += (temp * temp);      
    temp = x[start + q1]; x_sum += temp; xx_sum += (temp * temp); 
    prev_sum_x = x_sum; 
    prev_sum_xx = xx_sum; 
    temp = ((prev_sum_xx - ((prev_sum_x * prev_sum_x) / 25)) / 25); 
    if (temp > 255) { matrix_1[start] = 255; 
    } else { matrix_1[start] = std::round(temp); }
    for (int i = (start + 1); i < (((j + 1) * nrow) - 2); ++i) {
      x_sum = 0;
      xx_sum = 0;
      temp = x[i - q8]; x_sum += temp; xx_sum += (temp * temp);
      temp = x[i - q9]; x_sum += temp; xx_sum += (temp * temp);
      temp = x[i - 3]; x_sum += temp; xx_sum += (temp * temp);      
      temp = x[i + q10]; x_sum += temp; xx_sum += (temp * temp); 
      temp = x[i + q11]; x_sum += temp; xx_sum += (temp * temp);
      prev_sum_x -= x_sum; 
      prev_sum_xx -= xx_sum; 
      x_sum = 0;
      xx_sum = 0;
      temp = x[i - q5]; x_sum += temp; xx_sum += (temp * temp);
      temp = x[i - q7]; x_sum += temp; xx_sum += (temp * temp);
      temp = x[i + 2]; x_sum += temp; xx_sum += (temp * temp);      
      temp = x[i + q6]; x_sum += temp; xx_sum += (temp * temp); 
      temp = x[i + q1]; x_sum += temp; xx_sum += (temp * temp);
      prev_sum_x += x_sum;
      prev_sum_xx += xx_sum;
      temp = ((prev_sum_xx - ((prev_sum_x * prev_sum_x) / 25)) / 25); 
      if (temp > 255) { matrix_1[i] = 255; 
      } else { matrix_1[i] = std::round(temp); } } } 
  // Max entropy threshold identification (var <= 100)
  // Obtain value histogram 
  std::map<int, int> histogram;
  for (int i = 0; i < n; ++i) {
    if (matrix_1[i] <= 100) {
      histogram[matrix_2[i]]++; } }
  // Normalize frequencies
  std::vector<double> hist_norm(256);
  double frequency_sum = 0;
  for (auto const & p : histogram) { frequency_sum += p.second; }
  for (auto const & p : histogram) { hist_norm[p.first] = (p.second / frequency_sum); }
  std::map<int, int>().swap(histogram);
  // Compute cumulative distribution 
  std::vector<double> cumulative(256);
  cumulative[0] = hist_norm[0];
  for (int i = 1; i < 256; ++i) { cumulative[i] = hist_norm[i] + cumulative[i - 1]; }
  // Compute lower and upper entropy ranges
  std::vector<double> entropy_low(256);
  std::vector<double> entropy_high(256);
  double d, cl, ch;
  // Lower entropy range
  for (int t = 0; t < 256; ++t) {
    cl = cumulative[t];
    if (cl > 0) {
      for (int i = 0; i < t; ++i) {  
        d = hist_norm[i];
        if (d > 0) { d = (d / cl); entropy_low[t] = entropy_low[t] - (d * log(d)); } } }
    // Higher entropy range
    ch = 1 - cl;
    if (ch > 0) {
      for (int i = (t + 1); i < 256; ++i) {  
        d = hist_norm[i];
        if (d > 0) { d = (d / ch); entropy_high[t] = entropy_high[t] - (d * log(d)); } } } }
  std::vector<double>().swap(hist_norm);
  std::vector<double>().swap(cumulative);
  // Identify max entropy threshold value
  double max_entropy = entropy_low[0] + entropy_high[0];
  int max_entropy_threshold = 0;
  for (int i = 1; i < 256; ++i) { 
    d = entropy_low[i] + entropy_high[i]; 
    if (d > max_entropy) { max_entropy = d; max_entropy_threshold = i; } }
  std::vector<double>().swap(entropy_low);
  std::vector<double>().swap(entropy_high);
  // Thresholding (>= upper_threshold)
  for (int i = 0; i < n; ++i) {
    if ((matrix_2[i] >= max_entropy_threshold) && (matrix_1[i] <= 120)) {
      matrix_2[i] = 1; 
    } else {
      matrix_2[i] = 0; } }
  // Particle removal (Size and Circularity thresholds / < 1500, > 0.6)
  // Outer value assignment
  upper_col = ncol - 1;
  for (int i = 0; i < nrow; ++i) {
    matrix_2[i] = 0; } 
  for (int j = 1; j < upper_col; ++j) {
    matrix_2(0, j) = 0;  
    matrix_2(upper_row, j) = 0; } 
  for (int i = (n - nrow); i < n; ++i) {
    matrix_2[i] = 0; } 
  // Circle removal
  for (auto const & p : temp_vec) {
    matrix_2[p] = 0; }
  // Identify binary
  // Obtain vertical sequences
  vertical_seq_start.clear();
  vertical_seq_end.clear();
  for (int j = 1; j < upper_col; ++j) {
    condition = 0;
    for (int i = ((j * nrow) + 1); i < ((j + 1) * nrow) - 1; ++i) {
      if (matrix_2[i] == 1) {
        if (condition == 0) {
          if (matrix_2[i + 1] == 0) { vertical_seq_start.emplace_back(i); vertical_seq_end.emplace_back(i); 
          } else { vertical_seq_start.emplace_back(i); condition = 1; } } else {
            if (matrix_2[i + 1] == 0) { vertical_seq_end.emplace_back(i); condition = 0; } } } } }
  // Connected Components Labeling (starting/ending row/column excluded)
  // Define objects
  label_list.clear();
  label_list.reserve(vertical_seq_start.size());
  std::map<int, std::pair<int, int>> label_map_2;
  label_track.clear();
  std::fill(matrix_2.begin(), matrix_2.end(), 0);
  current_label = 1;
  // 1st column labelling
  // Assign current label to vertical sequence 
  for (int i = vertical_seq_start[0]; i <= vertical_seq_end[0]; ++i) { matrix_2[i] = current_label; }
  // Add label starting vertical sequence index in "label_map"
  label_map_2.emplace(current_label, std::make_pair(0, 0)); 
  // Add the label to the "label_list"
  label_list.emplace_back(current_label);  
  // Increment "current_label" by 1
  current_label += 1; 
  // Labelling of the remaining columns
  for (int j = 1; j < vertical_seq_start.size(); ++j) {
    // Obtain adjacent labels (if present)
    seq_start = vertical_seq_start[j];
    seq_end = vertical_seq_end[j];
    for (int i = (seq_start - diagonal); i <= (seq_end - anti_diagonal); ++i) {
      if (matrix_2[i] > 0) { label_track.emplace(matrix_2[i]); } }
    // If no adjacent labels are found
    if (label_track.empty()) { 
      // Assign "current_label" to vertical sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_2[i] = current_label; }
      // Add label starting vertical sequence index in "label_map"	
      label_map_2.emplace(current_label, std::make_pair(j, j)); 
      // Add "current_label" to the "label_list"
      label_list.emplace_back(current_label); 
      // Increment "current_label" by 1 
      current_label += 1; 
      // If 1 adjacent label is found
    } else if (label_track.size() == 1) { 
      // Obtain label value
      label = *label_track.begin();
      // Assign adjacent label  to vertical sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_2[i] = label; }
      // Add label to the "label_list"
      label_list.emplace_back(label);
      // Update ending index in "label_map"
      std::get<1>(label_map_2[label]) = j;
      // Clear "label_track"
      label_track.clear(); 
      // If > 1 adjacent label is found
    } else {
      // Obtain minimum adjacent label
      min_label = *label_track.begin();
      // Assign minimum adjacent label to current sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_2[i] = min_label; }
      // Add minimum adjacent label to the "label_list"
      label_list.emplace_back(min_label);
      // Remove "min_label" from the "label_track" set
      label_track.erase(label_track.find(min_label));
      // Update ending index in "label_map"
      std::get<1>(label_map_2[min_label]) = j;
      // Retrieve label starting vertical sequence index in "label_map"
      temp = std::get<0>(label_map_2[*label_track.begin()]);
      // If only 1 label remains
      if (label_track.size() == 1) {
        label = *label_track.begin();
        // Obtain corresponding vertical sequence indices 
        for (int k = temp; k < j; ++k) { 
          if (label_list[k] == label) {  
            // Assign minimum label to detected previous vertical sequences 
            label_list[k] = min_label;
            for (int i = vertical_seq_start[k]; i <= vertical_seq_end[k]; ++i) { matrix_2[i] = min_label; } } }
        // Erase label from "label_map"
        label_map_2.erase(label);     
      } else {
        // Obtain corresponding vertical sequence indices
        for (int k = temp; k < j; ++k) {
          if (std::binary_search(label_track.begin(), label_track.end(), label_list[k])) { 
            // Assign minimum label to detected previous vertical sequences 
            label_list[k] = min_label;
            for (int i = vertical_seq_start[k]; i <= vertical_seq_end[k]; ++i) { matrix_2[i] = min_label; } } } 
        // Erase labels from "label_map"
        for (auto const & u : label_track) { label_map_2.erase(u); } }
      // Clear "label_track"
      label_track.clear(); } }
  // Particle removal
  // Obtain label frequency map
  label_frequencies.clear();
  for (int j = 0; j < label_list.size(); ++j) { 
    for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) { 
      ++label_frequencies[matrix_2[i]]; } } 
  // Obtain map of particle centroids
  std::vector<int> temp_vec2 = temp_vec;
  temp_vec.clear();
  temp_vec.reserve(label_frequencies.size());
  particle_coordinates.clear();
  double threshold_count;
  for (auto const & p : label_frequencies) {
    label = p.first;
    area = p.second;
    x_sum = 0;
    y_sum = 0;
    threshold_count = 0;
    for (int j = std::get<0>(label_map_2[label]); j <= std::get<1>(label_map_2[label]); ++j) {
      if (label_list[j] == label) {
        seq_start = vertical_seq_start[j];
        seq_end = vertical_seq_end[j];
        seq_length = (seq_end - seq_start);
        x_coord = (seq_start / nrow);
        y_coord_start = (seq_start % nrow);
        y_coord_end = (seq_end % nrow);  
        if (seq_length == 0) { 
          x_sum += x_coord;
          y_sum += y_coord_start; 
        } else if (seq_length == 1) { 
          x_sum += (x_coord * 2); 
          y_sum += (y_coord_start + y_coord_end);
        } else {
          x_sum += (x_coord * (seq_length + 1));
          y_sum += (((y_coord_end * (y_coord_end + 1)) - ((y_coord_start - 1) * y_coord_start)) / 2); } 
        for (int i = seq_start; i <= seq_end; ++i) {
          if (x[i] >= 200) {
            threshold_count += 1; } } } }
    x_sum /= area;
    y_sum /= area;
    particle_coordinates.emplace(label, std::make_pair(x_sum, y_sum));
    temp_vec.emplace_back(threshold_count); }
  // Remove particles above size, circularity and brightness thresholds
  double overlap_2;
  int increment = 0;
  label_track.clear();
  for (auto const & p : particle_coordinates) {
    label = p.first;
    area = label_frequencies[label];
    overlap_2 = (temp_vec[increment] / area);  
    increment += 1;
    r = std::round(sqrt(area / pi));
    r2 = (r * r);
    x_center = std::round(std::get<0>(p.second));
    y_center = std::round(std::get<1>(p.second));
    overlap = 0;
    if ((x_center > r) && (x_center < (ncol - r)) && (y_center > r) && (y_center < (nrow - r))) {
      x_left = x_center - r;
      x_right = x_center + r; 
      for (int j = x_left; j <= x_right; ++j) {
        temporary = std::round(sqrt(r2 - std::pow((j - x_center), 2)));
        for (int i = (y_center - temporary); i <= (y_center + temporary); ++i) {
          if (matrix_2(i, j) == label) { 
            overlap += 1; } } }
      if ((area < 1500) || ((overlap / area) > 0.6) || ((overlap_2 > 0.3) && (area < 10000))) {
        label_track.emplace(label);
        for (int j = std::get<0>(label_map_2[label]); j <= std::get<1>(label_map_2[label]); ++j) {
          if (label_list[j] == label) {
            for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) {
              matrix_2[i] = 0; } } } } } } 
  // Region growth
  // Define objects
  int iteration_limit = 300;
  int width = iteration_limit;
  upper_col = ncol - width;
  upper_row = nrow - width;
  for (int j = 0; j < width; ++j) {
    for (int i = 0; i < nrow; ++i) {
      matrix_2(i, j) = 0; } }
  for (int j = width; j < upper_col; ++j) {
    for (int i = 0; i < width; ++i) {
      matrix_2(i, j) = 0; } 
    for (int i = upper_row; i < nrow; ++i) {
      matrix_2(i, j) = 0; } }
  for (int j = upper_col; j < ncol; ++j) {
    for (int i = 0; i < nrow; ++i) {
      matrix_2(i, j) = 0; } }
  for (int j = width; j < upper_col; ++j) {
    for (int i = width; i < upper_row; ++i) {
      if (matrix_2(i, j) > 0) { 
        matrix_2(i, j) = 1; 
      } else if ((matrix_2(i, j) == 0) && (matrix_1(i, j) <= 120) && (x(i, j) >= 90)) {
        matrix_2(i, j) = (-1); } } }
  std::unordered_set<int> border;
  std::unordered_set<int> border_new;
  // Border pixel identification
  int iteration;
  for (int j = width; j < upper_col; ++j) {
    for (int i = ((j * nrow) + width); i < (((j + 1) * nrow) - width); ++i) {
      if (matrix_2[i] == 1) {
        temporary = i - 1; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i + 1; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i - nrow; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i + nrow; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i - diagonal; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i + diagonal; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i - anti_diagonal; if (matrix_2[temporary] == (-1)) { border.emplace(i); }
        temporary = i + anti_diagonal; if (matrix_2[temporary] == (-1)) { border.emplace(i); } } } }
  // Border growth
  if (border.size() > 0) { 
    condition = 1;
    iteration = 0;
    while (condition == 1) {
      iteration += 1;
      for (auto const & u : border) {
        temporary = u - 1; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u + 1; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u - nrow; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u + nrow; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u - diagonal; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u + diagonal; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u - anti_diagonal; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); }
        temporary = u + anti_diagonal; if (matrix_2[temporary] == (-1)) { border_new.emplace(temporary); } }
      if ((border_new.size() == 0) || (iteration == iteration_limit)) { 
        condition = 0; 
      } else {
        for (auto const & t : border_new) { 
          matrix_2[t] = 1; }
        border = border_new;
        border_new.clear(); } } }
  // Border expansion (r = 15)
  upper_col = ncol - 15;
  border.clear();
  border_new.clear();
  for (int j = 15; j < upper_col; ++j) { 
    for (int i = ((j * nrow) + 15); i < (((j + 1) * nrow) - 15); ++i) { 
      if ((matrix_2[i] == 1) && ((matrix_2[i - anti_diagonal] != 1) || (matrix_2[i - nrow] != 1) || (matrix_2[i - diagonal] != 1) || (matrix_2[i - 1] != 1) || 
          (matrix_2[i + 1] != 1) || (matrix_2[i + diagonal] != 1) || (matrix_2[i + nrow] != 1) || (matrix_2[i + anti_diagonal] != 1))) {
        border.emplace(i); } } }
  if (border.size() > 0) { 
    condition = 1;
    iteration = 0;
    while (condition == 1) {
      iteration += 1;
      for (auto const & u : border) {
        temporary = u - 1; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u + 1; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u - nrow; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u + nrow; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u - diagonal; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u + diagonal; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u - anti_diagonal; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); }
        temporary = u + anti_diagonal; if ((matrix_2[temporary] != 1) && (x[temporary] >= 70)) { border_new.emplace(temporary); } }
      if ((border_new.size() == 0) || (iteration == 15)) { 
        condition = 0; 
      } else {
        for (auto const & t : border_new) { 
          matrix_2[t] = 1; }
        border = border_new;
        border_new.clear(); } } }
  // Obtain (initial) foreground indices
  for (auto const & p : temp_vec2) {
    matrix_2[p] = 0; }
  temp_vec.clear();
  for (int i = 0; i < n; ++i) {
    if (matrix_2[i] == 1) {
      temp_vec.emplace_back(i); } } 
  // Return output
  return wrap(temp_vec);
}


// Declare worker object for parallel computing of Frangi filtter
struct Frangi_par : public Worker
{
  // Source matrices
  const RMatrix<double> dxx_matrix;
  const RMatrix<double> dxy_matrix;
  const RMatrix<double> dyy_matrix;
  // Source vectors
  const RVector<int> threshold_index;
  const RVector<double> constants;
  const RVector<double> vertical_xx;
  const RVector<double> vertical_xy;
  const RVector<double> vertical_yy;
  // Destination matrix
  RMatrix<double> Frangi_matrix;
  // Initialize with source and destination
  Frangi_par(const NumericMatrix dxx_matrix, const NumericMatrix dxy_matrix, const NumericMatrix dyy_matrix, const IntegerVector threshold_index, const NumericVector constants, const NumericVector vertical_xx, const NumericVector vertical_xy, const NumericVector vertical_yy, NumericMatrix Frangi_matrix) 
    : dxx_matrix(dxx_matrix), dxy_matrix(dxy_matrix), dyy_matrix(dyy_matrix), threshold_index(threshold_index), constants(constants), vertical_xx(vertical_xx), vertical_xy(vertical_xy), vertical_yy(vertical_yy), Frangi_matrix(Frangi_matrix) {}
  // Compute Frangi filter for each of the elements of the derivative matrices
  void operator()(std::size_t begin, std::size_t end) {
    double radius = constants[0];
    double parameter_1 = constants[1];
    double parameter_2 = constants[2];
    double dxx, dxy, dyy, r, s, temp, t, lambda_1, lambda_2, Frangi_temp; 
    int increment;
    for (std::size_t i = begin; i < end; i++) {
      // Vertical Gaussian kernel
      dxx = 0;
      dxy = 0;
      dyy = 0;
      increment = 0;
      for (int t = (threshold_index[i] - radius); t <= (threshold_index[i] + radius); ++t) { 
        dxx += (dxx_matrix[t] * vertical_xx[increment]);
        dxy += (dxy_matrix[t] * vertical_xy[increment]);
        dyy += (dyy_matrix[t] * vertical_yy[increment]);
        increment += 1; }
      // Eigenpair computation
      r = ((dxx + dyy) * 0.5);
      s = ((dxx - dyy) * 0.5);
      temp = (s * s) + (dxy * dxy);
      if (temp >= 0) { 
        t = sqrt(temp);
        lambda_1 = r + t;
        lambda_2 = r - t;
        // Eigenpair sorting (|lambda_2| > |lambda_1|)
        if (std::abs(lambda_1) > std::abs(lambda_2)) {
          std::swap(lambda_1, lambda_2); }
        // Frangi vesselness computation
        Frangi_temp = (exp(-(std::pow((lambda_1 / lambda_2), 2) / parameter_1))) * (1 - exp(-(((lambda_1 * lambda_1) + (lambda_2 * lambda_2)) / parameter_2)));
        if (Frangi_temp > Frangi_matrix[threshold_index[i]]) { 
          Frangi_matrix[threshold_index[i]] = Frangi_temp; } } } }         
};

// Define parallel computing frangi_par function 

// [[Rcpp::export]]
IntegerMatrix frangi_filter_par(const IntegerMatrix x, const IntegerVector threshold_index, const int sigma) {
  // Define objects
  int ncol = x.ncol();
  int nrow = x.nrow();
  int n = x.size();
  int sigma_2 = std::pow(sigma, 2);
  int sigma_4 = std::pow(sigma, 4);
  int sigma_6 = std::pow(sigma, 6);
  int radius = std::ceil(3 * sigma);
  int radius_length = (radius * 2) + 1;
  double pi2 = 6.28318530718;
  double xx_factor = (1 / (pi2 * sigma_4));
  double xy_factor = (1 / (pi2 * sigma_6));
  double exp_factor = (2 * sigma_2);
  NumericVector constants(3);
  constants[0] = radius;
  constants[1] = (2 * std::pow(0.8, 2));
  constants[2] = (2 * std::pow(25, 2));
  // Compute 2nd order Gaussian Filters
  // Compute filters
  std::vector<int> coordinates;
  coordinates.reserve(radius_length);
  std::vector<int> coordinates_2;
  coordinates_2.reserve(radius_length);
  for (int i = -radius; i <= radius; ++i) { 
    coordinates.emplace_back(i);
    coordinates_2.emplace_back(i * i); }     
  arma::mat D2Gauss_xx(radius_length, radius_length);
  arma::mat D2Gauss_xy(radius_length, radius_length);
  double temp;
  for (int j = 0; j < radius_length; ++j) {
    for (int i = 0; i < radius_length; ++i) {
      temp = std::exp(-((coordinates_2[i] + coordinates_2[j]) / exp_factor));
      D2Gauss_xx(i, j) = ((xx_factor * ((coordinates_2[i] / sigma_2) - 1)) * temp);
      D2Gauss_xy(i, j) = ((xy_factor * (coordinates[i] * coordinates[j])) * temp); } }
  arma::mat D2Gauss_yy = trans(D2Gauss_xx);
  // Compute seperable filters
  arma::mat U, V; 
  arma::vec S;
  arma::svd(U, S, V, D2Gauss_xx, "dc");
  arma::vec horizontal_xx = U.col(0);
  arma::vec vertical_xx_temp = V.col(0);
  double scale_factor = sqrt(S[0]);
  for (int j = 0; j < radius_length; ++j) { horizontal_xx[j] *= scale_factor; }
  for (int i = 0; i < radius_length; ++i) { vertical_xx_temp[i] *= scale_factor; }  
  arma::svd(U, S, V, D2Gauss_xy, "dc");
  arma::vec horizontal_xy = U.col(0);
  arma::vec vertical_xy_temp = V.col(0);
  scale_factor = sqrt(S[0]);
  for (int j = 0; j < radius_length; ++j) { horizontal_xy[j] *= scale_factor; }
  for (int i = 0; i < radius_length; ++i) { vertical_xy_temp[i] *= scale_factor; }  
  arma::svd(U, S, V, D2Gauss_yy, "dc");
  arma::vec horizontal_yy = U.col(0);
  arma::vec vertical_yy_temp = V.col(0);
  scale_factor = sqrt(S[0]);
  for (int j = 0; j < radius_length; ++j) { horizontal_yy[j] *= scale_factor; }
  for (int i = 0; i < radius_length; ++i) { vertical_yy_temp[i] *= scale_factor; }
  NumericVector vertical_xx = Rcpp::as<Rcpp::NumericVector>(wrap(vertical_xx_temp));
  NumericVector vertical_xy = Rcpp::as<Rcpp::NumericVector>(wrap(vertical_xy_temp));
  NumericVector vertical_yy = Rcpp::as<Rcpp::NumericVector>(wrap(vertical_yy_temp));
  // Mark indicator_matrix
  int threshold_index_size = threshold_index.size();
  std::vector<int> indicator_vec(n);
  for (int i = 0; i < threshold_index_size; ++i) { indicator_vec[threshold_index[i]] = 1; }
  for (int i = 0; i < n; ++i) {
    if (indicator_vec[i] == 1) {
      if (indicator_vec[i - 1] != 1) { 
        for (int j = (i - radius); j < i; ++j) {
          if (indicator_vec[j] == 0) { indicator_vec[j] = 2; } } }
      if (indicator_vec[i + 1] != 1) {
        for (int j = (i + 1); j <= (i + radius); ++j) {
          if (indicator_vec[j] == 0) { indicator_vec[j] = 2; } } } } }
  // Convolution 
  std::vector<int> shift;
  shift.reserve(radius_length);
  for (int i = -(radius * nrow); i <= (radius * nrow); i += nrow) { shift.emplace_back(i); }
  // Horizontal filters
  NumericMatrix dxx_matrix(nrow, ncol);
  NumericMatrix dxy_matrix(nrow, ncol);
  NumericMatrix dyy_matrix(nrow, ncol);
  double dxx_temp, dxy_temp, dyy_temp;
  for (int i = 0; i < n; ++i) {
    if (indicator_vec[i] != 0) {
      dxx_temp = 0;
      dxy_temp = 0;
      dyy_temp = 0;
      for (int t = 0; t < radius_length; ++t) {
        temp = x[i + shift[t]];
        dxx_temp += (temp * horizontal_xx[t]);          
        dxy_temp += (temp * horizontal_xy[t]); 
        dyy_temp += (temp * horizontal_yy[t]); }
      dxx_matrix[i] = dxx_temp;
      dxy_matrix[i] = dxy_temp;
      dyy_matrix[i] = dyy_temp; } }
  std::vector<int>().swap(indicator_vec);
  NumericMatrix Frangi_matrix(nrow, ncol);
  // Vertical filter & Frangi filter computation
  // Frangi_par function (pass input and output matrices)
  Frangi_par frangi_par(dxx_matrix, dxy_matrix, dyy_matrix, threshold_index, constants, vertical_xx, vertical_xy, vertical_yy, Frangi_matrix);
  // Call parallelFor to do the work
  parallelFor(0, threshold_index_size, frangi_par); 
  // 8 bit conversion
  double scale_normalization = ((double)255 / max(Frangi_matrix));  
  for (int i = 0; i < threshold_index_size; ++i) {
    temp = (Frangi_matrix[threshold_index[i]] * scale_normalization);
    Frangi_matrix[threshold_index[i]] = std::round(temp); }
  // Return output 
  return as<IntegerMatrix>(Frangi_matrix);
}


// Define root binarization function

// [[Rcpp::export]]
IntegerMatrix root_binarization(const IntegerMatrix x) {
  // Define objects
  int ncol = x.ncol();
  int nrow = x.nrow();
  int n = x.size();
  int diagonal = nrow + 1; 
  int anti_diagonal = nrow - 1;
  int upper_col = ncol - 1;
  int upper_row = nrow - 1;
  // Obtain histogram of values 
  std::map<int, int> histogram;
  for (int i = 0; i < n; ++i) {
    histogram[x[i]]++; } 
  std::vector<int> data(256);
  for (auto const & p : histogram) {
    data[p.first] = p.second; }
  // Compute (ImageJ) IsoData threshold value
  int k = 256;
  int maxValue = k - 1;
  int count0 = data[0];
  data[0] = 0;
  int countMax = data[maxValue];
  data[maxValue] = 0;
  int min = 0;
  int max = maxValue;
  int level;
  double result, sum1, sum2, sum3, sum4;
  while ((data[min] == 0) && (min < maxValue)) {
    min++; }
  while ((data[max] == 0) && (max > 0)) {
    max--; }
  int index = min;
  int inc = std::max((max / 40), 1);
  int threshold;
  if (min >= max) {
    level = (k / 2);
    threshold = level; 
  } else {
    do {
      sum1 = 0;
      sum2 = 0;
      sum3 = 0;
      sum4 = 0;
      for (int i = min; i <= index; i++) {
        sum1 += (i * data[i]);
        sum2 += data[i]; }
      for (int i = (index + 1); i <= max; i++) {
        sum3 += (i * data[i]);
        sum4 += data[i]; }			
      result = (((sum1 / sum2) + (sum3 / sum4)) / 2);
      index++;
    } while (((index + 1) <= result) && (index < (max - 1)));
    threshold = std::round(result); }
  std::vector<int>().swap(data);
  // Apply threshold
  IntegerMatrix matrix_1(nrow, ncol);
  for (int i = 0; i < n; ++i) {
    if (x[i] >= threshold) {
      matrix_1[i] = 255; } }
  // Particle removal (Size and Circularity thresholds / < 5000, > 0.6)
  // Outer value assignment
  for (int i = 0; i < nrow; ++i) {
    matrix_1[i] = 0; } 
  for (int j = 1; j < upper_col; ++j) {
    matrix_1(0, j) = 0;  
    matrix_1(upper_row, j) = 0; } 
  for (int i = (n - nrow); i < n; ++i) {
    matrix_1[i] = 0; } 
  // Identify binary
  // Obtain vertical sequences
  std::vector<int> vertical_seq_start;
  std::vector<int> vertical_seq_end;
  int condition;
  for (int j = 1; j < upper_col; ++j) {
    condition = 0;
    for (int i = ((j * nrow) + 1); i < ((j + 1) * nrow) - 1; ++i) {
      if (matrix_1[i] == 255) {
        if (condition == 0) {
          if (matrix_1[i + 1] == 0) { vertical_seq_start.emplace_back(i); vertical_seq_end.emplace_back(i); 
          } else { vertical_seq_start.emplace_back(i); condition = 1; } } else {
            if (matrix_1[i + 1] == 0) { vertical_seq_end.emplace_back(i); condition = 0; } } } } }
  std::fill(matrix_1.begin(), matrix_1.end(), 0);
  // Connected Components Labeling (starting/ending row/column excluded)
  // Define objects
  std::vector<int> label_list;
  label_list.reserve(vertical_seq_start.size());
  std::map<int, std::pair<int, int>> label_map;
  std::set<int> label_track;
  int current_label = 1;
  int a, seq_start, seq_end, label, min_label;
  // 1st column labelling
  // Assign current label to vertical sequence 
  for (int i = vertical_seq_start[0]; i <= vertical_seq_end[0]; ++i) { matrix_1[i] = current_label; }
  // Add label starting vertical sequence index in "label_map"
  label_map.emplace(current_label, std::make_pair(0, 0)); 
  // Add the label to the "label_list"
  label_list.emplace_back(current_label);  
  // Increment "current_label" by 1
  current_label += 1; 
  // Labelling of the remaining columns
  for (int j = 1; j < vertical_seq_start.size(); ++j) {
    // Obtain adjacent labels (if present)
    seq_start = vertical_seq_start[j];
    seq_end = vertical_seq_end[j];
    for (int i = (seq_start - diagonal); i <= (seq_end - anti_diagonal); ++i) {
      if (matrix_1[i] > 0) { label_track.emplace(matrix_1[i]); } }
    // If no adjacent labels are found
    if (label_track.empty()) { 
      // Assign "current_label" to vertical sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_1[i] = current_label; }
      // Add label starting vertical sequence index in "label_map"	
      label_map.emplace(current_label, std::make_pair(j, j)); 
      // Add "current_label" to the "label_list"
      label_list.emplace_back(current_label); 
      // Increment "current_label" by 1 
      current_label += 1; 
      // If 1 adjacent label is found
    } else if (label_track.size() == 1) { 
      // Obtain label value
      label = *label_track.begin();
      // Assign adjacent label  to vertical sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_1[i] = label; }
      // Add label to the "label_list"
      label_list.emplace_back(label);
      // Update ending index in "label_map"
      std::get<1>(label_map[label]) = j;
      // Clear "label_track"
      label_track.clear(); 
      // If > 1 adjacent label is found
    } else {
      // Obtain minimum adjacent label
      min_label = *label_track.begin();
      // Assign minimum adjacent label to current sequence
      for (int i = seq_start; i <= seq_end; ++i) { matrix_1[i] = min_label; }
      // Add minimum adjacent label to the "label_list"
      label_list.emplace_back(min_label);
      // Remove "min_label" from the "label_track" set
      label_track.erase(label_track.find(min_label));
      // Update ending index in "label_map"
      std::get<1>(label_map[min_label]) = j;
      // Retrieve label starting vertical sequence index in "label_map"
      a = std::get<0>(label_map[*label_track.begin()]);
      // If only 1 label remains
      if (label_track.size() == 1) {
        label = *label_track.begin();
        // Obtain corresponding vertical sequence indices 
        for (int k = a; k < j; ++k) { 
          if (label_list[k] == label) {  
            // Assign minimum label to detected previous vertical sequences 
            label_list[k] = min_label;
            for (int i = vertical_seq_start[k]; i <= vertical_seq_end[k]; ++i) { matrix_1[i] = min_label; } } }
        // Erase label from "label_map"
        label_map.erase(label);     
      } else {
        // Obtain corresponding vertical sequence indices
        for (int k = a; k < j; ++k) {
          if (std::binary_search(label_track.begin(), label_track.end(), label_list[k])) { 
            // Assign minimum label to detected previous vertical sequences 
            label_list[k] = min_label;
            for (int i = vertical_seq_start[k]; i <= vertical_seq_end[k]; ++i) { matrix_1[i] = min_label; } } } 
        // Erase labels from "label_map"
        for (auto const & u : label_track) { label_map.erase(u); } }
      // Clear "label_track"
      label_track.clear(); } }
  // Particle removal
  // Obtain label frequency map
  std::map<int, int> label_frequencies;
  for (int j = 0; j < label_list.size(); ++j) { 
    for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) { 
      ++label_frequencies[matrix_1[i]]; } } 
  // Obtain map of particle centroids
  std::map<int, std::pair<long double, long double>> particle_coordinates;
  int area, seq_length, x_coord, y_coord_start, y_coord_end; 
  long double x_sum, y_sum;
  for (auto const & p : label_frequencies) {
    label = p.first;
    area = p.second;
    x_sum = 0;
    y_sum = 0;
    for (int j = std::get<0>(label_map[label]); j <= std::get<1>(label_map[label]); ++j) {
      if (label_list[j] == label) {
        seq_start = vertical_seq_start[j];
        seq_end = vertical_seq_end[j];
        seq_length = (seq_end - seq_start);
        x_coord = (seq_start / nrow);
        y_coord_start = (seq_start % nrow);
        y_coord_end = (seq_end % nrow);  
        if (seq_length == 0) { 
          x_sum += x_coord;
          y_sum += y_coord_start; 
        } else if (seq_length == 1) { 
          x_sum += (x_coord * 2); 
          y_sum += (y_coord_start + y_coord_end);
        } else {
          x_sum += (x_coord * (seq_length + 1));
          y_sum += (((y_coord_end * (y_coord_end + 1)) - ((y_coord_start - 1) * y_coord_start)) / 2); } } } 
    x_sum /= area;
    y_sum /= area;
    particle_coordinates.emplace(label, std::make_pair(x_sum, y_sum)); }
  // Remove particles above size and circularity thresholds
  double pi = 3.14159;
  double overlap;
  int r, r2, x_center, y_center, x_left, x_right, temporary;
  label_track.clear();
  for (auto const & p : particle_coordinates) {
    label = p.first;
    area = label_frequencies[label];
    r = std::round(sqrt(area / pi));
    r2 = (r * r);
    x_center = std::round(std::get<0>(p.second));
    y_center = std::round(std::get<1>(p.second));
    overlap = 0;
    if ((x_center > r) && (x_center < (ncol - r)) && (y_center > r) && (y_center < (nrow - r))) {
      x_left = x_center - r;
      x_right = x_center + r; 
      for (int j = x_left; j <= x_right; ++j) {
        temporary = std::round(sqrt(r2 - std::pow((j - x_center), 2)));
        for (int i = (y_center - temporary); i <= (y_center + temporary); ++i) {
          if (matrix_1(i, j) == label) { 
            overlap += 1; } } }
      if ((area < 5000) || ((overlap / area) > 0.6)) {
        label_track.emplace(label);
        for (int j = std::get<0>(label_map[label]); j <= std::get<1>(label_map[label]); ++j) {
          if (label_list[j] == label) {
            for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) {
              matrix_1[i] = 0; } } } } } }
  for (auto const & p : label_track) {
    label_map.erase(p); }
  std::set<int>().swap(label_track);
  // Identify particle lower threshold (IQR rule)
  double p_sum, p_count, temp;
  int tolerance = 20;
  int threshold_2 = threshold - tolerance;
  std::map<int, double> particle_IQR;
  for (auto const & p: label_map) {
    p_sum = 0;
    p_count = 0;
    for (int j = std::get<0>(p.second); j <= std::get<1>(p.second); ++j) {
      if (label_list[j] == p.first) {
        for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) {
          p_sum += x[i]; }
        p_count += (vertical_seq_end[j] - vertical_seq_start[j]) + 1; } }
    temp = (p_sum / p_count) - 20;
    if (temp <= threshold_2) { temp = threshold_2; }
    particle_IQR.emplace(p.first, temp); }
  // Assign (-1) to candidates
  for (int i = 0; i < n; ++i) {
    if ((x[i] > 0) && (matrix_1[i] == 0)) { 
      matrix_1[i] = (-1); } }
  std::unordered_set<int> border;
  std::unordered_set<int> border_new;
  std::unordered_set<int> restore;
  double lower;
  int c;
  for (auto const & p: particle_IQR) {
    label = p.first;
    lower = p.second;
    condition = 1;
    // Border pixel identification
    for (int j = std::get<0>(label_map[label]); j <= std::get<1>(label_map[label]); ++j) {
      if (label_list[j] == label) {
        seq_start = vertical_seq_start[j];
        seq_end = vertical_seq_end[j];
        c = seq_start - diagonal; if (matrix_1[c] == (-1)) { border.emplace(c); }
        c = seq_start - 1; if (matrix_1[c] == (-1)) { border.emplace(c); }
        c = seq_start + anti_diagonal; if (matrix_1[c] == (-1)) { border.emplace(c); }
        for (int i = vertical_seq_start[j]; i <= vertical_seq_end[j]; ++i) {
          c = i - nrow; if (matrix_1[c] == (-1)) { border.emplace(c); }
          c = i + nrow; if (matrix_1[c] == (-1)) { border.emplace(c); } }
        c = seq_end - anti_diagonal; if (matrix_1[c] == (-1)) { border.emplace(c); }
        c = seq_end + 1; if (matrix_1[c] == (-1)) { border.emplace(c); }
        c = seq_end + diagonal; if (matrix_1[c] == (-1)) { border.emplace(c); } } }
    // Region expansion
    if (border.size() == 0) { condition = 0; }
    while (condition == 1) {
      for (auto const & u : border) {
        if ((matrix_1[u] == (-1))) { if (x[u] >= lower) { matrix_1[u] = label; border_new.emplace(u); } else { matrix_1[u] = (-2); restore.emplace(u); } }
        c = u - 1; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u + 1; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u - nrow; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u + nrow; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u - diagonal; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u + diagonal; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u - anti_diagonal; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } }
        c = u + anti_diagonal; if ((matrix_1[c] == (-1))) { if (x[c] >= lower) { matrix_1[c] = label; border_new.emplace(c); } else { matrix_1[u] = (-2); restore.emplace(c); } } }
      if (border_new.size() == 0) { 
        condition = 0;
        border.clear();
        for (auto const & r : restore) { matrix_1[r] = (-1); }
        restore.clear(); 
      } else { 
        border = border_new;
        border_new.clear(); } } }
  // Binarize labelled areas
  for (int i = 0; i < n; ++i) { 
    if (matrix_1[i] < 0) { matrix_1[i] = 0; 
    } else if (matrix_1[i] > 0) { matrix_1[i] = 255; } }
  // Return output
  return matrix_1;
}


// Define root hair enhancement function

// [[Rcpp::export]]
IntegerMatrix root_hair_enhance(const IntegerMatrix x, const IntegerMatrix binary_matrix, const int roothair_pixel_radius = 100) {
  // Define objects
  int nrow = binary_matrix.nrow(); 
  int ncol = binary_matrix.ncol(); 
  int n = binary_matrix.size();
  int diagonal = nrow + 1;
  int anti_diagonal = nrow - 1;
  IntegerMatrix matrix_1(nrow, ncol);
  int condition, iteration, count, temporary;
  int zone_radius = roothair_pixel_radius;
  int sigma;
  // Border expansion (r = zone_radius)
  std::unordered_set<int> border;
  std::unordered_set<int> border_new;
  for (int j = zone_radius; j < (ncol - zone_radius); ++j) { 
    for (int i = ((j * nrow) + zone_radius); i < (((j + 1) * nrow) - zone_radius); ++i) { 
      if (binary_matrix[i] == 255) {
        matrix_1[i] = 1;
          if ((binary_matrix[i - anti_diagonal] != 255) || (binary_matrix[i - nrow] != 255) || (binary_matrix[i - diagonal] != 255) || (binary_matrix[i - 1] != 255) || 
          (binary_matrix[i + 1] != 255) || (binary_matrix[i + diagonal] != 255) || (binary_matrix[i + nrow] != 255) || (binary_matrix[i + anti_diagonal] != 255)) {
            border.emplace(i); } } } } 
  if (border.size() > 0) { 
    condition = 1;
    iteration = 0;
    count = border.size();
    while (condition == 1) {
      iteration += 1;
      for (auto const & u : border) {
        temporary = u - 1; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u + 1; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u - nrow; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u + nrow; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u - diagonal; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u + diagonal; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u - anti_diagonal; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); }
        temporary = u + anti_diagonal; if ((matrix_1[temporary] != 1) && (x[temporary] >= 70) && (x[temporary] <= 200)) { border_new.emplace(temporary); } }
      if ((border_new.size() == 0) || (iteration == zone_radius)) { 
        condition = 0; 
      } else {
        count += border_new.size();
        for (auto const & t : border_new) { 
          matrix_1[t] = 1; }
        border = border_new;
        border_new.clear(); } } }
  // Obtain foreground indices
  std::vector<int> temp_vec;
  temp_vec.reserve(count);
  for (int i = 0; i < n; ++i) {
    if (matrix_1[i] == 1) {
      temp_vec.emplace_back(i); } } 
  // Obtain max responce of frangi filters with sigma 2 to 10 (step of 2)
  matrix_1 = frangi_filter_par(x, wrap(temp_vec), sigma = 2);
  IntegerMatrix matrix_2 = frangi_filter_par(x, wrap(temp_vec), sigma = 4);
  for (int i = 0; i < n; ++i) {
    matrix_1[i] = std::max(matrix_1[i], matrix_2[i]); }
  matrix_2 = frangi_filter_par(x, wrap(temp_vec), sigma = 6);
  for (int i = 0; i < n; ++i) {
    matrix_1[i] = std::max(matrix_1[i], matrix_2[i]); }
  matrix_2 = frangi_filter_par(x, wrap(temp_vec), sigma = 8);
  for (int i = 0; i < n; ++i) {
    matrix_1[i] = std::max(matrix_1[i], matrix_2[i]); }
  matrix_2 = frangi_filter_par(x, wrap(temp_vec), sigma = 10);
  for (int i = 0; i < n; ++i) {
    matrix_1[i] = std::max(matrix_1[i], matrix_2[i]); }
  // Return output
  return (matrix_1);
}


// Define root outline function

// [[Rcpp::export]]
List root_outline(const IntegerMatrix x, const IntegerMatrix y, const IntegerMatrix vessels_matrix, const int dpi = 1200, const int roothair_pixel_radius = 100) 
{
  // Define objects
  int nrow = x.nrow(); 
  int ncol = x.ncol(); 
  int n = x.size();
  int upper_col = ncol - 1;
  int upper_row = nrow - 1;
  int diagonal = nrow + 1;
  int anti_diagonal = nrow - 1;
  int index;
  // Skeletonization
  IntegerMatrix skeleton = skeletonization(x);
  // Skeleton length parameters computation
  int x_min = ncol;
  int x_max = (-1);
  int y_min = nrow;
  int y_max = (-1);
  double root_length = 0;
  for (int j = 1; j < upper_col; ++j) {
    for (int i = 1; i < upper_row; ++i) {
      if (skeleton(i, j) > 0) {
        if (j < x_min) { x_min = j; }
        if (j > x_max) { x_max = j; }
        if (i < y_min) { y_min = i; }
        if (i > y_max) { y_max = i; }                         
        root_length += 1; } } }
  double root_length_cm_conversion = ((double)2.54 / (double)dpi);
  double root_length_x = ((double)(x_max - x_min) * root_length_cm_conversion);
  double root_length_y = ((double)(y_max - y_min) * root_length_cm_conversion);
  double root_length_total = ((double)root_length * root_length_cm_conversion);
  // Euclidean distance map
  IntegerMatrix distance_matrix = distance(x, skeleton);
  // Skeleton labelling
  IntegerMatrix skeleton_label(nrow, ncol);
  std::set<int> end_point; 
  std::set<int> branch_point;
  int temp; 
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      if (skeleton[i] == 255) {
        temp = 0;
        if (skeleton[i - anti_diagonal] == 255) { temp += 1; }
        if (skeleton[i - nrow] == 255) { temp += 1; }
        if (skeleton[i - diagonal] == 255) { temp += 1; }
        if (skeleton[i - 1] == 255) { temp += 1; }
        if (skeleton[i + 1] == 255) { temp += 1; }
        if (skeleton[i + diagonal] == 255) { temp += 1; }
        if (skeleton[i + nrow] == 255) { temp += 1; }
        if (skeleton[i + anti_diagonal] == 255) { temp += 1; }
        if (temp == 1) { skeleton_label[i] = 1; end_point.emplace(i); 
        } else if (temp == 2) { skeleton_label[i] = 2; 
        } else if (temp > 2) { skeleton_label[i] = 3; branch_point.emplace(i); } } } }
  // Segment labelling
  // Branch labelling
  int condition, next, next_temp, vec_size;
  int label = 3;
  double m, c, d;
  int dx, dy, x_temp, y_temp, x_length, y_length;
  int k0, k1, k2, k3;
  std::vector<int> temp_vec;
  std::vector<int> x_coord;
  std::vector<int> y_coord;
  std::vector<std::pair<int, int>> low_coord;
  std::vector<std::pair<int, int>> high_coord;
  std::fill(skeleton.begin(), skeleton.end(), 0);
  double alpha = 0.5;
  arma::mat X(4, 4);
  X.col(0) = arma::vec( { (-alpha), (2 * alpha), (-alpha), 0 } );  
  X.col(1) = arma::vec( { (2 - alpha), (alpha - 3), 0, 1 } );  
  X.col(2) = arma::vec( { (alpha - 2), (3 - (2 * alpha)), alpha, 0 } );  
  X.col(3) = arma::vec( { alpha, (-alpha), 0, 0 } ); 
  double step;
  arma::mat X_x; 
  arma::mat X_y;
  arma::mat U;
  arma::mat C;
  // Outer skeleton segments
  // Branch removal (n < 25)
  int branch_size_threshold = 25;
  std::vector<int> skel_1;
  if (end_point.size() > 0) {
    for (auto const & p : end_point) { 
      skeleton_label[p] = label;
      condition = 1;
      label += 1;
      next = p;
      next_temp = p;
      temp_vec.clear();
      temp_vec.emplace_back(next);
      low_coord.clear();
      high_coord.clear();
      while (condition == 1) {
        temp = next - anti_diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next - nrow; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next - diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next - 1; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next + 1; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next + diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next + nrow; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        temp = next + anti_diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
        if (next == next_temp) { condition = 0; } else { next = next_temp; temp_vec.emplace_back(next); } }
      temp = next - anti_diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next - nrow; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next - diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next - 1; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next + 1; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next + diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next + nrow; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
      temp = next + anti_diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }
      if (next != next_temp) { temp_vec.emplace_back(next_temp); } 
      vec_size = temp_vec.size();
      if (vec_size < branch_size_threshold) {
        for (auto const & p : temp_vec) {
          skeleton_label[p] = 0; } 
      } else {  
        x_coord.resize(vec_size);
        y_coord.resize(vec_size);
        for (int i = 0; i < vec_size; ++i) {
          x_coord[i] = (temp_vec[i] / nrow);
          y_coord[i] = (temp_vec[i] % nrow); }
        d = 0;
        for (int i = 0; i < vec_size; ++i) {
          d += distance_matrix[temp_vec[i]]; }
        d /= vec_size;
        for (int i = 3; i < (vec_size - 3); ++i) {  
          skel_1.emplace_back(temp_vec[i]);              
          if (x_coord[i - 3] == x_coord[i + 3]) { 
            dx = d; 
            dy = 0; 
          } else {
            m = ((double)(y_coord[i - 3] - y_coord[i + 3])) / ((double)(x_coord[i - 3] - x_coord[i + 3]));
            dy = std::round(sqrt((d * d) / ((m * m) + 1)));
            dx = std::round(-(dy * m)); }
          y_temp = (y_coord[i] + dy);
          x_temp = (x_coord[i] + dx); 
          if ((x_temp >= 0) && (x_temp < ncol) && (y_temp >= 0) && (y_temp < nrow)) {
            low_coord.emplace_back(std::make_pair(y_temp, x_temp)); }
          y_temp = (y_coord[i] - dy);
          x_temp = (x_coord[i] - dx);
          if ((x_temp >= 0) && (x_temp < ncol) && (y_temp >= 0) && (y_temp < nrow)) {
            high_coord.emplace_back(std::make_pair(y_temp, x_temp)); } } 
        // Draw lower segment
        for (int i = 1; i < (low_coord.size() - 2); i++) {            
          X_x = arma::colvec( { low_coord[i - 1].first, low_coord[i].first, low_coord[i + 1].first, low_coord[i + 2].first } );
          X_y = arma::colvec( { low_coord[i - 1].second, low_coord[i].second, low_coord[i + 1].second, low_coord[i + 2].second } );
          x_length = std::abs(low_coord[i].first - low_coord[i + 1].first);
          y_length = std::abs(low_coord[i].second - low_coord[i + 1].second);
          if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
          } else { step = ((double)1 / (y_length + 15)); } 
          for (double u = 0; u <= 1; u += step) {     
            U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
            C = (U * X);
            skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; } }        
        // Draw upper segment
        for (int i = 1; i < (high_coord.size() - 2); i++) {            
          X_x = arma::colvec( { high_coord[i - 1].first, high_coord[i].first, high_coord[i + 1].first, high_coord[i + 2].first } );
          X_y = arma::colvec( { high_coord[i - 1].second, high_coord[i].second, high_coord[i + 1].second, high_coord[i + 2].second } );
          x_length = std::abs(high_coord[i].first - high_coord[i + 1].first);
          y_length = std::abs(high_coord[i].second - high_coord[i + 1].second);
          if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
          } else { step = ((double)1 / (y_length + 15)); } 
          for (double u = 0; u <= 1; u += step) {     
            U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
            C = (U * X);
            skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; } } 
        // Draw endpoint connecting segments
        X_x = arma::colvec( { low_coord[1].first, low_coord[0].first, high_coord[0].first, high_coord[1].first } );
        X_y = arma::colvec( { low_coord[1].second, low_coord[0].second, high_coord[0].second, high_coord[1].second } );
        x_length = std::abs(low_coord[0].first - low_coord[1].first);
        y_length = std::abs(low_coord[0].second - low_coord[1].second);
        if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
        } else { step = ((double)1 / (y_length + 15)); } 
        for (double u = 0; u <= 1; u += step) {     
          U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
          C = (U * X);
          skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; }  
        k0 = low_coord.size() - 2;
        k1 = low_coord.size() - 1;
        k2 = high_coord.size() - 1;
        k3 = high_coord.size() - 2;
        X_x = arma::colvec( { low_coord[k0].first, low_coord[k1].first, high_coord[k2].first, high_coord[k3].first } );
        X_y = arma::colvec( { low_coord[k0].second, low_coord[k1].second, high_coord[k2].second, high_coord[k3].second } );
        x_length = std::abs(low_coord[k1].first - high_coord[k2].first);
        y_length = std::abs(low_coord[k1].second - high_coord[k2].second);
        if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
        } else { step = ((double)1 / (y_length + 15)); } 
        for (double u = 0; u <= 1; u += step) {     
          U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
          C = (U * X);
          skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; } } } }
  // Central skeleton segments
  std::vector<int> branch_point_remove;
  std::vector<int> temp_vec2;
  int condition_2;
  if (branch_point.size() > 0) { condition_2 = 1; }
  while (condition_2 == 1) {
    branch_point_remove.clear();
    for (auto const & p : branch_point) {
      temp_vec2.clear();
      temp = p - anti_diagonal; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); }  
      temp = p - nrow; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); } 
      temp = p - diagonal; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); }  
      temp = p - 1; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); }   
      temp = p + 1; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); } 
      temp = p + diagonal; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); }   
      temp = p + nrow; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); }  
      temp = p + anti_diagonal; if (skeleton_label[temp] == 2) { temp_vec2.emplace_back(temp); } 
      if (temp_vec2.size() == 0) {
        branch_point_remove.emplace_back(p);
      } else {
        for (auto const & u : temp_vec2) {
          skeleton_label[u] = label;
          condition = 1;
          label += 1;
          next = u;
          next_temp = u;
          temp_vec.clear();
          temp_vec.emplace_back(next);
          low_coord.clear();
          high_coord.clear();
          while (condition == 1) {
            temp = next - anti_diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next - nrow; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next - diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next - 1; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next + 1; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next + diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next + nrow; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            temp = next + anti_diagonal; if (skeleton_label[temp] == 2) { skeleton_label[temp] = label; next_temp = temp; }  
            if (next == next_temp) { condition = 0; } else { next = next_temp; temp_vec.emplace_back(next); } }
          temp = next - anti_diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next - nrow; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next - diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next - 1; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next + 1; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next + diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next + nrow; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }  
          temp = next + anti_diagonal; if (skeleton_label[temp] == 1) { skeleton_label[temp] = label; next_temp = temp; }
          if (next != next_temp) { temp_vec.emplace_back(next_temp); }  
          vec_size = temp_vec.size();
          if (vec_size >= 8) {
            x_coord.resize(vec_size);
            y_coord.resize(vec_size);
            for (int i = 0; i < vec_size; ++i) {
              x_coord[i] = (temp_vec[i] / nrow);
              y_coord[i] = (temp_vec[i] % nrow); }
            d = 0;
            for (int i = 0; i < vec_size; ++i) {
              d += distance_matrix[temp_vec[i]]; }
            d /= vec_size;
            for (int i = 3; i < (vec_size - 3); ++i) { 
              skel_1.emplace_back(temp_vec[i]);                
              if (x_coord[i - 3] == x_coord[i + 3]) { 
                dx = d; 
                dy = 0; 
              } else {
                m = ((double)(y_coord[i - 3] - y_coord[i + 3])) / ((double)(x_coord[i - 3] - x_coord[i + 3]));
                dy = std::round(sqrt((d * d) / ((m * m) + 1)));
                dx = std::round(-(dy * m)); }
              y_temp = (y_coord[i] + dy);
              x_temp = (x_coord[i] + dx); 
              if ((x_temp >= 0) && (x_temp < ncol) && (y_temp >= 0) && (y_temp < nrow)) {
                low_coord.emplace_back(std::make_pair(y_temp, x_temp)); }
              y_temp = (y_coord[i] - dy);
              x_temp = (x_coord[i] - dx);
              if ((x_temp >= 0) && (x_temp < ncol) && (y_temp >= 0) && (y_temp < nrow)) {
                high_coord.emplace_back(std::make_pair(y_temp, x_temp)); } } 
            // Draw lower segment
            for (int i = 1; i < (low_coord.size() - 2); i++) {            
              X_x = arma::colvec( { low_coord[i - 1].first, low_coord[i].first, low_coord[i + 1].first, low_coord[i + 2].first } );
              X_y = arma::colvec( { low_coord[i - 1].second, low_coord[i].second, low_coord[i + 1].second, low_coord[i + 2].second } );
              x_length = std::abs(low_coord[i].first - low_coord[i + 1].first);
              y_length = std::abs(low_coord[i].second - low_coord[i + 1].second);
              if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
              } else { step = ((double)1 / (y_length + 15)); } 
              for (double u = 0; u <= 1; u += step) {     
                U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
                C = (U * X);
                skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; } }        
            // Draw upper segment
            for (int i = 1; i < (high_coord.size() - 2); i++) {            
              X_x = arma::colvec( { high_coord[i - 1].first, high_coord[i].first, high_coord[i + 1].first, high_coord[i + 2].first } );
              X_y = arma::colvec( { high_coord[i - 1].second, high_coord[i].second, high_coord[i + 1].second, high_coord[i + 2].second } );
              x_length = std::abs(high_coord[i].first - high_coord[i + 1].first);
              y_length = std::abs(high_coord[i].second - high_coord[i + 1].second);
              if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
              } else { step = ((double)1 / (y_length + 15)); } 
              for (double u = 0; u <= 1; u += step) {     
                U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
                C = (U * X);
                skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; } } 
            // Draw endpoint connecting segments
            X_x = arma::colvec( { low_coord[1].first, low_coord[0].first, high_coord[0].first, high_coord[1].first } );
            X_y = arma::colvec( { low_coord[1].second, low_coord[0].second, high_coord[0].second, high_coord[1].second } );
            x_length = std::abs(low_coord[0].first - low_coord[1].first);
            y_length = std::abs(low_coord[0].second - low_coord[1].second);
            if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
            } else { step = ((double)1 / (y_length + 15)); } 
            for (double u = 0; u <= 1; u += step) {     
              U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
              C = (U * X);
              skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; }  
            k0 = low_coord.size() - 2;
            k1 = low_coord.size() - 1;
            k2 = high_coord.size() - 1;
            k3 = high_coord.size() - 2;
            X_x = arma::colvec( { low_coord[k0].first, low_coord[k1].first, high_coord[k2].first, high_coord[k3].first } );
            X_y = arma::colvec( { low_coord[k0].second, low_coord[k1].second, high_coord[k2].second, high_coord[k3].second } );
            x_length = std::abs(low_coord[k1].first - high_coord[k2].first);
            y_length = std::abs(low_coord[k1].second - high_coord[k2].second);
            if (x_length >= y_length) { step = ((double)1 / (x_length + 15)); 
            } else { step = ((double)1 / (y_length + 15)); } 
            for (double u = 0; u <= 1; u += step) {     
              U = arma::rowvec( { std::pow(u, 3), std::pow(u, 2), u, 1 } );
              C = (U * X);
              skeleton(((int) arma::mat(C * X_x)[0]), ((int) arma::mat(C * X_y)[0])) = label; } } }
        branch_point_remove.emplace_back(p); } }                    
    if (branch_point_remove.size() > 0) {      
      for (auto const & t : branch_point_remove) {
        branch_point.erase(t); } 
    } else {
      condition_2 = 0; } }
  // Assign binary values
  for (int i = 0; i < n; ++i) {
    if (skeleton[i] > 0) {
      skeleton[i] = 255; } }
  // Dilation (Square SE, r = 2)
  // Outer matrix value assignment
  upper_col = ncol - 2;
  int upper_row2 = (nrow - 2);
  int nrow2 = (nrow * 2);
  std::copy(skeleton.begin(), skeleton.end(), skeleton_label.begin());
  for (int i = 0; i < nrow2; ++i) {
    skeleton[i] = 0;
    skeleton_label[i] = 0; }
  for (int i = (n - nrow2); i < n; ++i) {
    skeleton[i] = 0;
    skeleton_label[i] = 0; }
  for (int j = 2; j < upper_col; ++j) {
    skeleton(0, j) = 0;
    skeleton(1, j) = 0;
    skeleton_label(0, j) = 0;
    skeleton_label(1, j) = 0;
    skeleton(upper_row2, j) = 0;
    skeleton(upper_row, j) = 0;
    skeleton_label(upper_row2, j) = 0;
    skeleton_label(upper_row, j) = 0; }
  // 1st Dilation (5 x 1)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton_label[i] = std::max({skeleton[i - 2], skeleton[i - 1], skeleton[i], skeleton[i + 1], skeleton[i + 2]}); } }
  // 2nd Dilation (1 x 5)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton[i] = std::max({skeleton_label[i - nrow2], skeleton_label[i - nrow], skeleton_label[i], skeleton_label[i + nrow], skeleton_label[i + nrow2]}); } }
  // Hole filling
  std::vector<int> skel_2;
  condition = 1;
  int count = 1;
  int iter_limit = 20;
  while (condition == 1) {
    for (auto const & p : skel_1) {
      temp = p - anti_diagonal; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p - nrow; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p - diagonal; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p - 1; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p + 1; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p + diagonal; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p + nrow; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); }
      temp = p + anti_diagonal; if (skeleton[temp] == 0) { skeleton[temp] = 255; skel_2.emplace_back(temp); } }
    skel_1 = skel_2;
    skel_2.clear();
    count += 1; 
    if ((skel_1.size() == 0) || (count == iter_limit)) { condition = 0; } }
  // Morphological Closing (Disk SE, r = 4)
  // Dilation 
  // Outer matrix value assignment
  upper_col = ncol - 2;
  std::copy(skeleton.begin(), skeleton.end(), skeleton_label.begin());
  for (int i = 0; i < nrow2; ++i) {
    skeleton[i] = 0;
    skeleton_label[i] = 0; }
  for (int i = (n - nrow2); i < n; ++i) {
    skeleton[i] = 0;
    skeleton_label[i] = 0; }
  for (int j = 2; j < upper_col; ++j) {
    skeleton(0, j) = 0;
    skeleton(1, j) = 0;
    skeleton_label(0, j) = 0;
    skeleton_label(1, j) = 0;
    skeleton(upper_row2, j) = 0;
    skeleton(upper_row, j) = 0;
    skeleton_label(upper_row2, j) = 0;
    skeleton_label(upper_row, j) = 0; }
  // 1st Dilation (3 x 1)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton_label[i] = std::max({skeleton[i - 1], skeleton[i], skeleton[i + 1]}); } }
  // 2nd Dilation (3 x 3)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton[i] = std::max({skeleton_label[i - anti_diagonal], skeleton_label[i], skeleton_label[i + anti_diagonal]}); } }
  // 3rd Dilation (1 x 3)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton_label[i] = std::max({skeleton[i - nrow], skeleton[i], skeleton[i + nrow]}); } }
  // 4th Dilation (3 x 3)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton[i] = std::max({skeleton_label[i - diagonal], skeleton_label[i], skeleton_label[i + diagonal]}); } }
  // Erosions
  // Outer matrix value assignment
  std::copy(skeleton.begin(), skeleton.end(), skeleton_label.begin());
  for (int i = 0; i < nrow2; ++i) {
    skeleton[i] = 255;
    skeleton_label[i] = 255; }
  for (int i = (n - nrow2); i < n; ++i) {
    skeleton[i] = 255;
    skeleton_label[i] = 255; }
  for (int j = 2; j < upper_col; ++j) {
    skeleton(0, j) = 255;
    skeleton(1, j) = 255;
    skeleton_label(0, j) = 255;
    skeleton_label(1, j) = 255;
    skeleton(upper_row2, j) = 255;
    skeleton(upper_row, j) = 255;
    skeleton_label(upper_row2, j) = 255;
    skeleton_label(upper_row, j) = 255; }
  // 1st Erosion (3 x 1)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton_label[i] = std::min({skeleton[i - 1], skeleton[i], skeleton[i + 1]}); } }
  // 2nd Erosion (3 x 3)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton[i] = std::min({skeleton_label[i - anti_diagonal], skeleton_label[i], skeleton_label[i + anti_diagonal]}); } }
  // 3rd Erosion (1 x 3)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton_label[i] = std::min({skeleton[i - nrow], skeleton[i], skeleton[i + nrow]}); } }
  // 4th Erosion (3 x 3)
  for (int j = 2; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 2); i < (((j + 1) * nrow) - 2); ++i) {
      skeleton[i] = std::min({skeleton_label[i - diagonal], skeleton_label[i], skeleton_label[i + diagonal]}); } }
  // Outer matrix value restoration
  for (int i = 0; i < nrow2; ++i) {
    skeleton[i] = 0; }
  for (int i = (n - nrow2); i < n; ++i) {
    skeleton[i] = 0; }
  for (int j = 2; j < upper_col; ++j) {
    skeleton(0, j) = 0;
    skeleton(1, j) = 0;
    skeleton(upper_row2, j) = 0;
    skeleton(upper_row, j) = 0; }
  // Root hair zone (0.2 inch radius)
  // Identify border indices
  std::set<int> border_1;
  for (int j = 1; j < upper_col; ++j) {
    for (int i = ((j * nrow) + 1); i < (((j + 1) * nrow) - 1); ++i) {
      if ((skeleton[i] > 0) && ((skeleton[i - anti_diagonal] == 0) || (skeleton[i - nrow] == 0) || (skeleton[i - diagonal] == 0) || 
          (skeleton[i - 1] == 0) || (skeleton[i + 1] == 0) || (skeleton[i + diagonal] == 0) || (skeleton[i + nrow] == 0) || 
          (skeleton[i + anti_diagonal] == 0))) {
        border_1.emplace(i); } } }
  // Grow borders (r = 30)
  std::set<int> border_2;
  int cycle = 1;
  condition = 1;
  while (condition == 1) {
    border_2.clear(); 
    for (auto const & p : border_1) {
      temp = p - anti_diagonal; if (skeleton[temp] == 0) { border_2.emplace(temp); } 
      temp = p - nrow; if (skeleton[temp] == 0) { border_2.emplace(temp); } 
      temp = p - diagonal; if (skeleton[temp] == 0) { border_2.emplace(temp); }  
      temp = p - 1; if (skeleton[temp] == 0) { border_2.emplace(temp); } 
      temp = p + 1; if (skeleton[temp] == 0) { border_2.emplace(temp); } 
      temp = p + diagonal; if (skeleton[temp] == 0) { border_2.emplace(temp); } 
      temp = p + nrow; if (skeleton[temp] == 0) { border_2.emplace(temp); }        
      temp = p + anti_diagonal; if (skeleton[temp] == 0) { border_2.emplace(temp); } } 
    if ((border_2.size() == 0) || (cycle == roothair_pixel_radius)) {
      condition = 0; 
    } else {  
      for (auto const & u : border_2) {
        skeleton[u] = (-1); }
      border_1 = border_2;
      cycle += 1; } }
  // Restore outer matrix values
  for (int i = 0; i < nrow; ++i) {
    skeleton[i] = 0; }
  for (int i = (n - nrow); i < n; ++i) {
    skeleton[i] = 0; }
  for (int j = 0; j < ncol; ++j) {
    skeleton(0, j) = 0;
    skeleton(upper_row, j) = 0; }
  // Compute 70th quartile of the greyscale matrix
  temp_vec.resize(n);
    for (int i = 0; i < n; i++)
    {
        temp_vec[i] = y[i];
    }
  int Q70 = (70 / 100) * n; 
  std::nth_element(temp_vec.begin(), temp_vec.begin() + Q70, temp_vec.end());
  int upper_threshold = temp_vec[Q70];
  // Root hair parameters computation
  int root_area = 0;
  int rh_area = 0;
  for (int j = 1; j < upper_col; ++j) {
    for (int i = 1; i < upper_row; ++i) {
      if (skeleton(i, j) > 0) {
        root_area += 1;
      } else if (skeleton(i, j) < 0) {
        if ((y(i, j) >= upper_threshold) && (vessels_matrix(i, j) >= 15) && (vessels_matrix(i, j) <= 55)) {
          rh_area += 1;  
          skeleton(i, j) = 127;         
        } else {
          skeleton(i, j) = 0; } } } } 
  double rh_root_ratio = ((double)rh_area / (double)root_area);
  // Return output
  return List::create(skeleton, x_min, x_max, y_min, y_max, root_length_x, root_length_y, root_length_total, root_area, rh_area, rh_root_ratio);
}


// Define binary accuracy evaluation function

// [[Rcpp::export]]
NumericVector eval_binary(const IntegerMatrix x, const IntegerMatrix binary_matrix) {
  // Define objects
  int nrow = x.nrow(); 
  int ncol = x.ncol(); 
  int n = x.size();
  double TP_count = 0;
  double FN_count = 0;
  double FP_count = 0;
  double TN_count = 0;
  IntegerMatrix matrix_1(nrow, ncol);
  for (int i = 0; i < n; ++i) {
    if (x[i] == 255) {
      matrix_1[i] = 255; } }
  for (int i = 0; i < n; ++i) 
  {
    if (binary_matrix[i] == 0)
    {
      if (matrix_1[i] == 0)
      {
      TN_count += 1;
      } else if (matrix_1[i] == 255) {
      FP_count += 1;
      }
    } else if (binary_matrix[i] == 255) {
      if (matrix_1[i] == 0)
      {
      FN_count += 1;
      } else if (matrix_1[i] == 255) {
      TP_count += 1;
      }
    }
  }
  double TP_percentage = (TP_count / (double)(n - 1)) * 100.0;
  double FN_percentage = (FN_count / (double)(n - 1)) * 100.0;
  double FP_percentage = (FP_count / (double)(n - 1)) * 100.0;
  double TN_percentage = (TN_count / (double)(n - 1)) * 100.0;
  double MCC = ((TP_percentage * TN_percentage) - (FP_percentage * FN_percentage)) / sqrt((TP_percentage + FP_percentage) * (TP_percentage + FN_percentage) * (TN_percentage + FP_percentage) * (TN_percentage + FN_percentage));
  NumericVector output = NumericVector::create(TP_percentage, FN_percentage, FP_percentage, TN_percentage, MCC);
// Return output
return(output);
}

