diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fb23cf9..43d065f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ set(SOURCE src/nyx/features/glcm.cpp src/nyx/features/glcm_nontriv.cpp src/nyx/features/gldm.cpp + src/nyx/features/gldzm.cpp src/nyx/features/glrlm.cpp src/nyx/features/glszm.cpp src/nyx/features/hexagonality_polygonality.cpp diff --git a/src/nyx/environment.h b/src/nyx/environment.h index 43e380b8..06bdfa13 100644 --- a/src/nyx/environment.h +++ b/src/nyx/environment.h @@ -29,7 +29,7 @@ #define ONLINESTATSTHRESH "--onlineStatsThresh" // Environment :: onlineStatsThreshold -- Example: --onlineStatsThresh=150 #define XYRESOLUTION "--pixelsPerCentimeter" // pixels per centimeter #define PXLDIST "--pixelDistance" // used in neighbor features -#define COARSEGRAYDEPTH "--coarseGrayDepth" // Default - 8 +#define COARSEGRAYDEPTH "--coarseGrayDepth" // Environment :: raw_coarse_grayscale_depth #define RAMLIMIT "--ramLimit" // Optional. Limit for treating ROIs as non-trivial and for setting the batch size of trivial ROIs. Default - amount of available system RAM #define TEMPDIR "--tempDir" // Optional. Used in processing non-trivial features. Default - system temp directory #define IBSICOMPLIANCE "--ibsi" // skip binning for grey level and grey tone features diff --git a/src/nyx/feature_mgr.cpp b/src/nyx/feature_mgr.cpp index 3d9727e9..80df9b1f 100644 --- a/src/nyx/feature_mgr.cpp +++ b/src/nyx/feature_mgr.cpp @@ -77,7 +77,7 @@ bool FeatureManager::check_11_correspondence() else // error - no providers { success = false; - std::cout << "Error: feature " << theFeatureSet.findFeatureNameByCode((AvailableFeatures)i_fcode) << " (code " << i_fcode << ") is not provided by any feature method \n"; + std::cout << "Error: feature " << theFeatureSet.findFeatureNameByCode((AvailableFeatures)i_fcode) << " (code " << i_fcode << ") is not provided by any feature method. Check constructor of class FeatureManager\n"; } } @@ -199,7 +199,7 @@ void FeatureManager::build_user_requested_set() FeatureMethod* fm = get_feature_method_by_code (fc); if (fm == nullptr) - throw (std::runtime_error("Feature " + std::to_string(fc) + " is not provided by any feature method")); + throw (std::runtime_error("Feature " + std::to_string(fc) + " is not provided by any feature method. Check constructor of class FeatureManager")); // first, save feature methods of fm's dependencies for (auto depend_fc : fm->dependencies) @@ -207,7 +207,7 @@ void FeatureManager::build_user_requested_set() FeatureMethod* depend_fm = get_feature_method_by_code (depend_fc); if (depend_fm == nullptr) - throw (std::runtime_error("Feature " + std::to_string(depend_fc) + " is not provided by any feature method")); + throw (std::runtime_error("Feature " + std::to_string(depend_fc) + " is not provided by any feature method. Check constructor of class FeatureManager")); // save this fm if it's not yet saved if (std::find(user_requested_features.begin(), user_requested_features.end(), depend_fm) == user_requested_features.end()) diff --git a/src/nyx/feature_mgr_init.cpp b/src/nyx/feature_mgr_init.cpp index 5af6bf4f..8f0ee8c3 100644 --- a/src/nyx/feature_mgr_init.cpp +++ b/src/nyx/feature_mgr_init.cpp @@ -14,6 +14,7 @@ #include "features/geodetic_len_thickness.h" #include "features/glcm.h" #include "features/glrlm.h" +#include "features/gldzm.h" #include "features/glszm.h" #include "features/gldm.h" #include "features/hexagonality_polygonality.h" @@ -48,7 +49,8 @@ FeatureManager::FeatureManager() register_feature (new FractalDimensionFeature()); register_feature (new GLCMFeature()); register_feature (new GLRLMFeature()); - register_feature (new GLSZMFeature()); + register_feature (new GLDZMFeature()); + register_feature (new GLSZMFeature()); register_feature (new GLDMFeature()); register_feature (new NGTDMFeature()); register_feature (new ImageMomentsFeature()); diff --git a/src/nyx/features/gldm.cpp b/src/nyx/features/gldm.cpp index 73033f16..5224b766 100644 --- a/src/nyx/features/gldm.cpp +++ b/src/nyx/features/gldm.cpp @@ -48,8 +48,8 @@ void GLDMFeature::calculate(LR& r) bool disableGrayBinning = Environment::ibsi_compliance || nGrays >= piRange; // Gather zones - for (int row = 0; row < D.height(); row++) - for (int col = 0; col < D.width(); col++) + for (int row = 0; row < D.get_height(); row++) + for (int col = 0; col < D.get_width(); col++) { // Find a non-blank pixel PixIntens pi = Nyxus::to_grayscale (D.yx(row, col), r.aux_min, piRange, nGrays, disableGrayBinning); diff --git a/src/nyx/features/gldzm.cpp b/src/nyx/features/gldzm.cpp new file mode 100644 index 00000000..93a6dcf3 --- /dev/null +++ b/src/nyx/features/gldzm.cpp @@ -0,0 +1,549 @@ +#include +#include "gldzm.h" +#include "../environment.h" + +GLDZMFeature::GLDZMFeature() : FeatureMethod("GLDZMFeature") +{ + provide_features({ + GLDZM_SDE, + GLDZM_LDE, + GLDZM_LGLE, + GLDZM_HGLE, + GLDZM_SDLGLE, + GLDZM_SDHGLE, + GLDZM_LDLGLE, + GLDZM_LDHGLE, + GLDZM_GLNU, + GLDZM_GLNUN, + GLDZM_ZDNU, + GLDZM_ZDNUN, + GLDZM_ZP, + GLDZM_GLM, + GLDZM_GLV, + GLDZM_ZDM, + GLDZM_ZDV, + GLDZM_ZDE }); +} + +void GLDZMFeature::clear_buffers() +{ + f_SDE = + f_LDE = + f_LGLE = + f_HGLE = + f_SDLGLE = + f_SDHGLE = + f_LDLGLE = + f_LDHGLE = + f_GLNU = + f_GLNUN = + f_ZDNU = + f_ZDNUN = + f_ZP = + f_GLM = + f_GLV = + f_ZDM = + f_ZDV = + f_ZDE = + f_GLE = 0; +} + +void GLDZMFeature::calculate (LR& r) +{ + clear_buffers(); + + //==== Check if the ROI is degenerate (equal intensity) + if (r.aux_min == r.aux_max) + return; + + //==== Compose the distance matrix + + // -- Zones (intensity clusters) + using ACluster = std::pair; // + std::vector Z; + + // -- Unique intensities + std::unordered_set U; + + // -- We need a copy of ROI's image matrix for + // (1) making a binned (coarser) pixel intensity image and + // (2) zone finding alsorithm's ability to leave marks in recognized zones + auto M = r.aux_image_matrix; + pixData& D = M.WriteablePixels(); + + // -- Squeeze the copy's intensity range for getting more prominent zones + PixIntens piRange = r.aux_max - 0; // Not 'r.aux_max - r.aux_min' because unlike in 'LR::raw_pixels' the min intensity in an image matrix ==0 + unsigned int nGrays = theEnvironment.get_coarse_gray_depth(); + for (size_t i = 0; i < D.size(); i++) + { + // raw intensity + unsigned int Ir = D[i]; + // ignore blank pixels + if (Ir == 0) + continue; + // binned intensity + unsigned int Ib = Nyxus::to_grayscale (Ir, 0, piRange, nGrays, Environment::ibsi_compliance); + D[i] = Ib; + // update the set of unique intensities + U.insert(Ib); + } + + //==== Find zones + constexpr int huge = INT_MAX; // Value greater than any pixel's distance to ROI border + const int VISITED = -1; + for (int row = 0; row < M.height; row++) + for (int col = 0; col < M.width; col++) + { + // Find a non-blank pixel + auto inten = D.yx(row, col); + if (inten == 0 || int(inten) == VISITED) + continue; + + // Found a gray pixel. Find same-intensity neighbourhood of it. + std::vector> history; + + int x = col, + y = row; + int zoneSize = 1; // '1' because we already have a pixel. Hopefully it's a part of a zone + D.yx(y, x) = VISITED; + + // Keep track of this pixel, hopefully 1st pixel of the cluster + history.push_back ({x,y}); + int zoneMetric = dist2border (D, x, y); //dist2closestRoiBorder (D, x, y); + + // Scan the neighborhood of pixel (x,y) + for (;;) + { + // East + int _x = x + 1, + _y = y; + if (D.safe(_y,_x) && D.yx(_y,_x) != VISITED && D.yx(_y,_x) == inten) + { + D.yx(y, x + 1) = VISITED; + + // Update zone's metric + int dist2roi = dist2border (D, _x, _y); //dist2closestRoiBorder (D, _x, _y); + zoneMetric = std::min (zoneMetric, dist2roi); + + // Update zone size + zoneSize++; + + // Remember this pixel + history.push_back ({_x,_y}); + // Advance + x = x + 1; + // Proceed + continue; + } + + // South + _x = x; + _y = y + 1; + if (D.safe(_y,_x) && D.yx(_y,_x) != VISITED && D.yx(_y,_x) == inten) + { + D.yx(_y,_x) = VISITED; + + // Update zone's metric + int dist2roi = dist2border (D, _x, _y); //dist2closestRoiBorder(D, _x, _y); + zoneMetric = std::min(zoneMetric, dist2roi); + + // Update zone size + zoneSize++; + + history.push_back ({_x,_y}); + y = y + 1; + continue; + } + + // We are done exploring this cluster + break; + } + + // Done scanning a cluster. Perform 3 actions: + // --1 + U.insert(inten); + + // --2 Create a zone + ACluster clu = { inten, zoneMetric}; + Z.push_back(clu); + } + + //==== Fill the zonal metric matrix + + // -- number of discrete intensity values in the image + int Ng = (int)U.size(); + + // -- max zone distance to ROI or image border + int Nd = 0; + for (auto& z : Z) + Nd = std::max (Nd, z.second); + + // -- Set to vector to be able to know each intensity's index + std::vector I (U.begin(), U.end()); + std::sort (I.begin(), I.end()); // Optional + + // -- Zone intensity -to- zone distance matrix + SimpleMatrix P; + P.allocate (Nd, Ng); // Ng rows, Nd columns + P.fill(0); + + // -- fill it + int i = 0; + for (auto& z : Z) + { + // row. Gray tones are sparse so we need to find indices of tones in 'Z' and use them as rows of P-matrix + auto iter = std::find (I.begin(), I.end(), z.first); + int row = (int)(iter - I.begin()); + // col (a distance). Distances are dense \in [1,Nd] + int col = z.second - 1; // 0-based => -1 + auto& k = P.yx (row, col); + k++; + } + + //==== Calculate vectors of totals by intensity (Mx) and by distance (Md) + std::vector Mx, Md; + calc_row_and_column_sum_vectors (Mx, Md, P, Ng, Nd); + + //==== Calculate features, set variables f_GLE, f_GML, f_GLV, etc + calc_features (Mx, Md, P, r.aux_area); +} + +template int GLDZMFeature::dist2border (Imgmatrx& I, const int x, const int y) +{ + // scan left + int dist2l = 0; + for (int x0 = x - 1; x0 >= 0; x0--) + if (I.yx(y, x0) == 0 || x0 == 0) // we're at the ROI border or left margin (x0==0) + { + dist2l = x - x0; + break; + } + // scan right + int dist2r = 0, w = I.get_width(); + for (int x0 = x + 1; x0 < w; x0++) + if (I.yx(y, x0) == 0 || x0 == w - 1) // we're at the ROI border or right margin (x0==w-1) + { + dist2r = x0 - x; + break; + } + // scan up + int dist2t = 0; + for (int y0 = y - 1; y0 >= 0; y0--) + if (I.yx(y0, x) == 0 || y0 == 0) // we're at the ROI border or top margin (y0==0) + { + dist2t = y - y0; + break; + } + // scan down + int dist2b = 0, h = I.get_height(); + for (int y0 = y + 1; y0 < h; y0++) + if (I.yx(y0, x) == 0 || y0 == h - 1) // we're at the ROI border or bottom margin (y0==h-1) + { + dist2b = y0 - y; + break; + } + // result + int retval = std::min(std::min(std::min(dist2l, dist2r), dist2t), dist2b); + if (retval == 0) + retval = 1; // Requirement of GLDZM: pixel on the border is within distance 1 from ROI border + return retval; +} + +template void GLDZMFeature::calc_row_and_column_sum_vectors (std::vector & Mx, std::vector& Md, Imgmatrx& P, const int Ng, const int Nd) +{ + // Sum distances of each grey levels + Mx.resize (Ng); + for (int gray_i = 0; gray_i < Ng; gray_i++) + { + double sumD = 0; + for (int d = 1; d <= Nd; d++) + sumD += P.yx (gray_i, d-1); + Mx[gray_i] = sumD; + } + + // Sum grey levels of each distance + Md.resize (Nd); + for (int d = 1; d <= Nd; d++) + { + double sumG = 0; + for (int gray_i = 0; gray_i < Ng; gray_i++) + sumG += P.yx (gray_i, d-1); + Md[d-1] = sumG; + } +} + +template void GLDZMFeature::calc_features (const std::vector& Mx, const std::vector& Md, Imgmatrx& P, unsigned int roi_area) +{ + int Nd = Md.size(); + for (int d_ = 1; d_ <= Nd; d_++) + { + double d = (double) d_; + double m = Md [d_-1]; + f_SDE += m / d / d; // Small Distance Emphasis = \frac{1}{N_s} \sum_d \frac{m_d}{d^2} + f_LDE += d * d * m; // Large Distance Emphasis = \frac{1}{N_s} \sum_d d^2 m_d + f_ZDNU += m * m; // Zone Distance Non-Uniformity = \frac{1}{N_s} \sum_d m_d^2 + // Zone Distance Non-Uniformity Normalized = \frac{1}{N_s^2} \sum_d m_d^2 + } + + double Ns = 0; + for (auto levelSumOfDists : Mx) + Ns += levelSumOfDists; + + f_SDE /= (double)Ns; + f_LDE /= (double)Ns; + f_ZDNU /= (double)Ns; + f_ZDNUN = f_ZDNU / (double)Ns; + + int Ng = Mx.size(); + for (int g = 0; g < Ng; g++) + { + double tmp = (double)g + 1; + double x = Mx[g]; + f_LGLE += x / (tmp * tmp); // Low Grey Level Emphasis = \frac{1}{N_s} \sum_x \frac{m_x}{x^2} + f_HGLE += (tmp * tmp) * x; // High Grey Level Emphasis = \frac{1}{N_s} \sum_x x^2 m_x + f_GLNU += x * x; // Grey Level Non-Uniformity = \frac{1}{N_s} \sum_x m_x^2 + } + f_LGLE /= (double)Ns; + f_HGLE /= (double)Ns; + f_GLNU /= (double)Ns; + f_GLNUN = f_GLNU / (double)Ns; // Grey Level Non-Uniformity Normalized = \frac{1}{N_s^2} \sum_x m_x^2 + + for (int g = 0; g < Ng; g++) + for (int d = 1; d <= Nd; d++) + { + double g_ = (double)g + 1, d_ = d; + double p = P.yx(g, d - 1); + f_SDLGLE += p / g_ / g_ / d_ / d_; // Small Distance Low Grey Level Emphasis = \frac{1}{N_s} \sum_x \sum_d \frac{ m_{x,d}}{x^2 d^2} + f_SDHGLE += g_ * g_ * p / d_ / d_; // Small Distance High Grey Level Emphasis = \frac{1}{N_s} \sum_x \sum_d \frac{x^2 m_{x,d}}{d^2} + f_LDLGLE += d_ * d_ * p / g_ / g_; // Large Distance Low Grey Level Emphasis = \frac{1}{N_s} \sum_x \sum_d \frac{d^2 m_{x,d}}{x^2} + f_LDHGLE += g_ * g_ * d_ * d_ * p; // Large Distance High Grey Level Emphasis = \frac{1}{N_s} \sum_x \sum_d \x^2 d^2 m_{x,d} + f_GLM += g_ * p; // Grey Level Mean = \mu_x = \sum_x \sum_d x p_{x,d} + f_ZDM += d_ * p; // Zone Distance Mean = \mu_d = \sum_x \sum_d d p_{x,d} + f_ZDE += p * log2(p + EPS); // Zone Distance Entropy = - \sum_x \sum_d p_{x,d} \textup{log}_2 ( p_{x,d} ) + } + f_SDLGLE /= (double)Ns; + f_SDHGLE /= (double)Ns; + f_LDLGLE /= (double)Ns; + f_LDHGLE /= (double)Ns; + f_ZP = (double)Ns / (double)roi_area; // Zone Percentage = \frac{N_s}{N_v} + f_GLE = f_ZDE; + + for (int g = 0; g < Ng; g++) + for (int d = 1; d <= Nd; d++) + { + // Grey Level Variance = \sum_x \sum_d \left(x - \mu_x \right)^2 p_{x,d} + double p = P.yx(g, d - 1) / (double)Ns, + x = (double)g, + dif = x - f_GLM; + f_GLV += dif * dif * p; + // Zone Distance Variance} = \sum_x \sum_d \left(d - \mu_d \right)^2 p_{x,d} + double d_ = (double)d; + dif = d - f_ZDM; + f_ZDV += dif * dif * p; + } +} + +void GLDZMFeature::save_value (std::vector>& fvals) +{ + fvals[GLDZM_SDE][0] = f_SDE; + fvals[GLDZM_LDE][0] = f_LDE; + fvals[GLDZM_LGLE][0] = f_LGLE; + fvals[GLDZM_HGLE][0] = f_HGLE; + fvals[GLDZM_SDLGLE][0] = f_SDLGLE; + fvals[GLDZM_SDHGLE][0] = f_SDHGLE; + fvals[GLDZM_LDLGLE][0] = f_LDLGLE; + fvals[GLDZM_LDHGLE][0] = f_LDHGLE; + fvals[GLDZM_GLNU][0] = f_GLNU; + fvals[GLDZM_GLNUN][0] = f_GLNUN; + fvals[GLDZM_ZDNU][0] = f_ZDNU; + fvals[GLDZM_ZDNUN][0] = f_ZDNUN; + fvals[GLDZM_ZP][0] = f_ZP; + fvals[GLDZM_GLM][0] = f_GLM; + fvals[GLDZM_GLV][0] = f_GLV; + fvals[GLDZM_ZDM][0] = f_ZDM; + fvals[GLDZM_ZDV][0] = f_ZDV; + fvals[GLDZM_ZDE][0] = f_ZDE; +} + +void GLDZMFeature::parallel_process_1_batch(size_t start, size_t end, std::vector* ptrLabels, std::unordered_map * ptrLabelData) +{ + // Iterate ROIs of this batch + for (auto i = start; i < end; i++) + { + // Get ahold of ROI's cached data + int lab = (*ptrLabels)[i]; + LR& r = (*ptrLabelData)[lab]; + + // Calculate feature of this ROI + GLDZMFeature f; + f.calculate (r); + f.save_value (r.fvals); + } +} + +void GLDZMFeature::osized_add_online_pixel(size_t x, size_t y, uint32_t intensity) {} + +void GLDZMFeature::osized_calculate (LR& r, ImageLoader&) +{ + clear_buffers(); + + //==== Check if the ROI is degenerate (equal intensity) + if (r.aux_min == r.aux_max) + return; + + //==== Compose the distance matrix + + // -- Zones (intensity clusters) + OutOfRamPixelCloud Z_int, Z_dist; + Z_int.init (r.label, "GLDZMFeature-osized_calculate-Z_int"); + Z_dist.init (r.label, "GLDZMFeature-osized_calculate-Z_dist"); + + // -- Unique intensities + std::unordered_set U; + + // -- Create an image matrix for this ROI for + // (1) making a binned (coarser) pixel intensity image and + // (2) zone finding alsorithm's ability to leave marks in recognized zones + WriteImageMatrix_nontriv D("GLDZMFeature-osized_calculate-M", r.label); + D.allocate_from_cloud(r.raw_pixels_NT, r.aabb, false); + + // -- Squeeze pixels' intensity range for getting more prominent zones + PixIntens piRange = r.aux_max - 0; // reflecting the fact that the original image's pixel intensity range is [0-r.aux_max] where 0 represents off-ROI pixels + unsigned int nGrays = theEnvironment.get_coarse_gray_depth(); + for (size_t i = 0; i < D.size(); i++) + { + // raw intensity + unsigned int Ir = D[i]; + // ignore blank pixels + if (Ir == 0) + continue; + // binned intensity + unsigned int Ib = Nyxus::to_grayscale (Ir, 0, piRange, nGrays, Environment::ibsi_compliance); + D.set_at (i, Ib); + // update the set of unique intensities + U.insert(Ib); + } + + // -- Find zones + constexpr int huge = INT_MAX; // Value greater than any pixel's distance to ROI border + const int VISITED = -1; + // Helpful temps + auto height = D.get_height(), + width = D.get_width(); + for (int row = 0; row < height; row++) + for (int col = 0; col < width; col++) + { + // Find a non-blank pixel + auto inten = D.yx(row, col); + if (inten == 0 || int(inten) == VISITED) + continue; + + // Found a gray pixel. Find same-intensity neighbourhood of it. + std::vector> history; + + int x = col, + y = row; + int zoneSize = 1; // '1' because we already have a pixel. Hopefully it's a part of a zone + D.set_at(y, x, VISITED); + + // Keep track of this pixel, hopefully 1st pixel of the cluster + history.push_back({ x,y }); + int zoneMetric = dist2border (D, x, y); + + // Scan the neighborhood of pixel (x,y) + for (;;) + { + // East + int _x = x + 1, + _y = y; + if (D.safe(_y, _x) && D.yx(_y, _x) != VISITED && D.yx(_y, _x) == inten) + { + D.set_at(y, x + 1, VISITED); + + // Update zone's metric + int dist2roi = dist2border (D, _x, _y); + zoneMetric = std::min(zoneMetric, dist2roi); + + // Update zone size + zoneSize++; + + // Remember this pixel + history.push_back({ _x,_y }); + // Advance + x = x + 1; + // Proceed + continue; + } + + // South + _x = x; + _y = y + 1; + if (D.safe(_y, _x) && D.yx(_y, _x) != VISITED && D.yx(_y, _x) == inten) + { + D.set_at(_y, _x, VISITED); + + // Update zone's metric + int dist2roi = dist2border (D, _x, _y); + zoneMetric = std::min(zoneMetric, dist2roi); + + // Update zone size + zoneSize++; + + history.push_back({ _x,_y }); + y = y + 1; + continue; + } + + // We are done exploring this cluster + break; + } + + // Done scanning a cluster. Perform 3 actions: + // --1 + U.insert(inten); + + // --2 Create a zone + Z_int.add_pixel(Pixel2(-1, -1, (StatsInt)inten)); // using just intensity field of triplet Pixel2 + Z_dist.add_pixel(Pixel2(-1, -1, (StatsInt)zoneMetric)); + } + + //==== Fill the zonal metric matrix + + // -- number of discrete intensity values in the image + int Ng = (int)U.size(); + + // -- max zone distance to ROI or image border + int Nd = 0; + for (auto p : Z_dist) + Nd = std::max (Nd, (int)p.inten); + + // --Set to vector to be able to know each intensity's index + std::vector I (U.begin(), U.end()); + std::sort (I.begin(), I.end()); // Optional + + // -- Zone intensity -to- zone distance matrix + WriteImageMatrix_nontriv P ("GLDZMFeature-osized_calculate-P", r.label); + P.allocate (Nd, Ng, 0); // Ng rows, Nd columns + + // -- fill it + for (int i=0; i -1 + auto k = P.yx (row, col); + P.set_at (row, col, k+1); + } + + //==== Calculate vectors of totals by intensity (Mx) and by distance (Md) + std::vector Mx, Md; + calc_row_and_column_sum_vectors (Mx, Md, P, Ng, Nd); + + //==== Calculate features, set variables f_GLE, f_GML, f_GLV, etc + calc_features (Mx, Md, P, r.aux_area); +} + diff --git a/src/nyx/features/gldzm.h b/src/nyx/features/gldzm.h new file mode 100644 index 00000000..325729a3 --- /dev/null +++ b/src/nyx/features/gldzm.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../feature_method.h" + +/// @brief Grey Level Distance Zone (GLDZM) features +/// +/// Grey Level Dsitance Zone (GLDZM) quantifies distances zones of same intensity to the ROI border + +class GLDZMFeature : public FeatureMethod +{ +public: + GLDZMFeature(); + + void calculate(LR& r); + void osized_add_online_pixel(size_t x, size_t y, uint32_t intensity); + void osized_calculate(LR& r, ImageLoader& imloader); + void save_value(std::vector>& feature_vals); + static void parallel_process_1_batch(size_t start, size_t end, std::vector* ptrLabels, std::unordered_map * ptrLabelData); + + // Support of manual reduce + static bool required(const FeatureSet& fs) + { + return fs.anyEnabled({ + GLDZM_SDE, + GLDZM_LDE, + GLDZM_LGLE, + GLDZM_HGLE, + GLDZM_SDLGLE, + GLDZM_SDHGLE, + GLDZM_LDLGLE, + GLDZM_LDHGLE, + GLDZM_GLNU, + GLDZM_GLNUN, + GLDZM_ZDNU, + GLDZM_ZDNUN, + GLDZM_ZP, + GLDZM_GLM, + GLDZM_GLV, + GLDZM_ZDM, + GLDZM_ZDV, + GLDZM_ZDE + }); + } + +private: + + void clear_buffers(); + template int dist2border (Imgmatrx & I, const int x, const int y); + template void calc_row_and_column_sum_vectors (std::vector& Mx, std::vector& Md, Imgmatrx & P, const int Ng, const int Nd); + template void calc_features (const std::vector& Mx, const std::vector& Md, Imgmatrx& P, unsigned int roi_area); + + const double EPS = 2.2e-16; + + // Variables caching feature values between calculate() and save_value(). + double f_SDE, // Small Distance Emphasis + f_LDE, // Large Distance Emphasis + f_LGLE, // Low Grey Level Emphasis + f_HGLE, // High GreyLevel Emphasis + f_SDLGLE, // Small Distance Low Grey Level Emphasis + f_SDHGLE, // Small Distance High GreyLevel Emphasis + f_LDLGLE, // Large Distance Low Grey Level Emphasis + f_LDHGLE, // Large Distance High Grey Level Emphasis + f_GLNU, // Grey Level Non Uniformity + f_GLNUN, // Grey Level Non Uniformity Normalized + f_ZDNU, // Zone Distance Non Uniformity + f_ZDNUN, // Zone Distance Non Uniformity Normalized + f_ZP, // Zone Percentage + f_GLM, // Grey Level Mean + f_GLV, // Grey Level Variance + f_ZDM, // Zone Distance Mean + f_ZDV, // Zone Distance Variance + f_ZDE, // Zone Distance Entropy + f_GLE; // Grey Level Entropy +}; \ No newline at end of file diff --git a/src/nyx/features/glszm.cpp b/src/nyx/features/glszm.cpp index c2f11d9d..33b28d42 100644 --- a/src/nyx/features/glszm.cpp +++ b/src/nyx/features/glszm.cpp @@ -44,6 +44,7 @@ void GLSZMFeature::osized_calculate (LR& r, ImageLoader&) //==== While scanning clusters, learn unique intensities std::unordered_set U; + // Width of the intensity - zone area matrix int maxZoneArea = 0; // Create an image matrix for this ROI @@ -56,7 +57,7 @@ void GLSZMFeature::osized_calculate (LR& r, ImageLoader&) // Copy the image matrix WriteImageMatrix_nontriv D ("GLSZMFeature-osized_calculate-D", r.label); - D.allocate (M.get_width(), M.get_height(), 0); + D.allocate (width, height, 0); D.copy (M); // Squeeze the intensity range @@ -213,6 +214,7 @@ void GLSZMFeature::calculate(LR& r) //==== While scanning clusters, learn unique intensities std::unordered_set U; + // Width of the intensity - zone area matrix int maxZoneArea = 0; // Copy the image matrix diff --git a/src/nyx/features/image_matrix.cpp b/src/nyx/features/image_matrix.cpp index 2654b1c2..fa5469e6 100644 --- a/src/nyx/features/image_matrix.cpp +++ b/src/nyx/features/image_matrix.cpp @@ -9,7 +9,7 @@ void ImageMatrix::print (const std::string& head, const std::string& tail, std::vector special_points) { const int Wd = 6; // data - const int Wi = 5; // index + const int Wi = 5; // index readOnlyPixels D = ReadablePixels(); @@ -42,7 +42,7 @@ void ImageMatrix::print (const std::string& head, const std::string& tail, std:: y = std::get<1>(p); int loc_x = x - original_aabb.get_xmin(), loc_y = y - original_aabb.get_ymin(); - if (col == loc_x && row == loc_y) + if (col == x && row == y) // Global variant: (col == loc_x && row == loc_y) { haveSpecPix = true; std::string txt = std::get<2>(p); @@ -58,7 +58,12 @@ void ImageMatrix::print (const std::string& head, const std::string& tail, std:: if (I == 0) std::cout << std::setw(Wd) << '.'; else - std::cout << std::setw(Wd) << I; + { + // Sometimes a pixel's intensity has a special value within an algorithm, e.g. in GLDM's zone finding. + // Let's display such intensity values as negative for easier reading + signed int Isig = (signed int)I; + std::cout << std::setw(Wd) << Isig; + } } std::cout << "\n"; } diff --git a/src/nyx/features/image_matrix.h b/src/nyx/features/image_matrix.h index b015cf74..1a458f72 100644 --- a/src/nyx/features/image_matrix.h +++ b/src/nyx/features/image_matrix.h @@ -82,6 +82,16 @@ class SimpleMatrix : public std::vector return val; } + inline T yx (int y, int x) const + { + return xy (x,y); + } + + inline T& yx (int y, int x) + { + return xy (x,y); + } + // y - strided index, x - nonstrided; 1-based x and y inline T matlab (int y, int x) const { @@ -184,8 +194,8 @@ class pixData : public std::vector return true; } - int width() const { return W; } - int height() const { return H; } + int get_width() const { return W; } + int get_height() const { return H; } private: int W, H; diff --git a/src/nyx/features/image_matrix_nontriv.cpp b/src/nyx/features/image_matrix_nontriv.cpp index 7ef0e935..5dd46a5b 100644 --- a/src/nyx/features/image_matrix_nontriv.cpp +++ b/src/nyx/features/image_matrix_nontriv.cpp @@ -29,6 +29,12 @@ OutOfRamPixelCloud::~OutOfRamPixelCloud() } } +void OutOfRamPixelCloud::check_io_ok() const +{ + if (!pF) + throw (std::runtime_error("ERROR in add_pixel() - file might not be open with init()")); +} + void OutOfRamPixelCloud::init (unsigned int _roi_label, std::string name) { n_items = 0; @@ -69,6 +75,8 @@ void OutOfRamPixelCloud::clear() void OutOfRamPixelCloud::add_pixel(const Pixel2& p) { + check_io_ok(); + fwrite((const void*) &(p.x), sizeof(p.x), 1, pF); fwrite((const void*)&(p.y), sizeof(p.y), 1, pF); fwrite((const void*)&(p.inten), sizeof(p.inten), 1, pF); @@ -83,6 +91,8 @@ size_t OutOfRamPixelCloud::size() const Pixel2 OutOfRamPixelCloud::get_at(size_t idx) const { + check_io_ok(); + size_t offs = idx * item_size; fseek(pF, offs, SEEK_SET); Pixel2 px; diff --git a/src/nyx/features/image_matrix_nontriv.h b/src/nyx/features/image_matrix_nontriv.h index 9dd1c8fb..ded76d90 100644 --- a/src/nyx/features/image_matrix_nontriv.h +++ b/src/nyx/features/image_matrix_nontriv.h @@ -68,6 +68,7 @@ class OutOfRamPixelCloud std::string filepath; FILE* pF = nullptr; size_t item_size = sizeof(Pixel2::x) + sizeof(Pixel2::y) + sizeof(Pixel2::inten); + void check_io_ok() const; }; /// @brief Read-only out of memory pixel matrix browsable via ImageLoader @@ -176,6 +177,7 @@ class WriteImageMatrix_nontriv AABB original_aabb; }; + /// @brief Padded image matrix class Power2PaddedImageMatrix_NT : public WriteImageMatrix_nontriv { diff --git a/src/nyx/features/ngtdm.cpp b/src/nyx/features/ngtdm.cpp index 0ed9fd8d..c10ac7b6 100644 --- a/src/nyx/features/ngtdm.cpp +++ b/src/nyx/features/ngtdm.cpp @@ -59,8 +59,8 @@ void NGTDMFeature::calculate (LR& r) // Gather zones unsigned int nGrays = theEnvironment.get_coarse_gray_depth(); - for (int row = 0; row < D.height(); row++) - for (int col = 0; col < D.width(); col++) + for (int row = 0; row < D.get_height(); row++) + for (int col = 0; col < D.get_width(); col++) { // Find a non-blank pixel diff --git a/src/nyx/featureset.cpp b/src/nyx/featureset.cpp index 1471ce6b..66c15700 100644 --- a/src/nyx/featureset.cpp +++ b/src/nyx/featureset.cpp @@ -88,7 +88,7 @@ namespace Nyxus {"EDGE_STDDEV_INTENSITY",PERIMETER}, {"EDGE_MAX_INTENSITY",PERIMETER}, {"EDGE_MIN_INTENSITY",PERIMETER}, - {"EDGE_INTEGRATED_INTENSITY", EDGE_INTEGRATED_INTENSITY}, + {"EDGE_INTEGRATED_INTENSITY", EDGE_INTEGRATED_INTENSITY}, {"CIRCULARITY", CIRCULARITY}, @@ -208,6 +208,25 @@ namespace Nyxus {"GLRLM_LRLGLE", GLRLM_LRLGLE}, {"GLRLM_LRHGLE", GLRLM_LRHGLE}, + {"GLDZM_SDE", GLDZM_SDE}, + {"GLDZM_LDE", GLDZM_LDE}, + {"GLDZM_LGLE", GLDZM_LGLE}, + {"GLDZM_HGLE", GLDZM_HGLE}, + {"GLDZM_SDLGLE", GLDZM_SDLGLE}, + {"GLDZM_SDHGLE", GLDZM_SDHGLE}, + {"GLDZM_LDLGLE", GLDZM_LDLGLE}, + {"GLDZM_LDHGLE", GLDZM_LDHGLE}, + {"GLDZM_GLNU", GLDZM_GLNU}, + {"GLDZM_GLNUN", GLDZM_GLNUN}, + {"GLDZM_ZDNU", GLDZM_ZDNU}, + {"GLDZM_ZDNUN", GLDZM_ZDNUN}, + {"GLDZM_ZP", GLDZM_ZP}, + {"GLDZM_GLM", GLDZM_GLM}, + {"GLDZM_GLV", GLDZM_GLV}, + {"GLDZM_ZDM", GLDZM_ZDM}, + {"GLDZM_ZDV", GLDZM_ZDV}, + {"GLDZM_ZDE", GLDZM_ZDE}, + {"GLSZM_SAE", GLSZM_SAE}, {"GLSZM_LAE", GLSZM_LAE}, {"GLSZM_GLN", GLSZM_GLN }, diff --git a/src/nyx/featureset.h b/src/nyx/featureset.h index 652be991..66d453b2 100644 --- a/src/nyx/featureset.h +++ b/src/nyx/featureset.h @@ -201,6 +201,26 @@ namespace Nyxus GLRLM_LRLGLE, // Long Run Low Gray Level Emphasis GLRLM_LRHGLE, // Long Run High Gray Level Emphasis + // GLDZM: + GLDZM_SDE, // Small Distance Emphasis + GLDZM_LDE, // Large Distance Emphasis + GLDZM_LGLE, // Low Grey Level Emphasis + GLDZM_HGLE, // High GreyLevel Emphasis + GLDZM_SDLGLE, // Small Distance Low Grey Level Emphasis + GLDZM_SDHGLE, // Small Distance High GreyLevel Emphasis + GLDZM_LDLGLE, // Large Distance Low Grey Level Emphasis + GLDZM_LDHGLE, // Large Distance High Grey Level Emphasis + GLDZM_GLNU, // Grey Level Non Uniformity + GLDZM_GLNUN, // Grey Level Non Uniformity Normalized + GLDZM_ZDNU, // Zone Distance Non Uniformity + GLDZM_ZDNUN, // Zone Distance Non Uniformity Normalized + GLDZM_ZP, // Zone Percentage + GLDZM_GLM, // Grey Level Mean + GLDZM_GLV, // Grey Level Variance + GLDZM_ZDM, // Zone Distance Mean + GLDZM_ZDV, // Zone Distance Variance + GLDZM_ZDE, // Zone Distance Entropy + // GLSZM: GLSZM_SAE, // Small Area Emphasis GLSZM_LAE, // Large Area Emphasis diff --git a/src/nyx/helpers/helpers.h b/src/nyx/helpers/helpers.h index 13b3dafe..95143cef 100644 --- a/src/nyx/helpers/helpers.h +++ b/src/nyx/helpers/helpers.h @@ -274,9 +274,11 @@ namespace Nyxus /// @return Squeezed intensity within range [0,255] inline unsigned int to_grayscale (unsigned int i, unsigned int min_i, unsigned int i_range, unsigned int n_levels, bool disable_binning=false) { - if (disable_binning) return i; + if (disable_binning) + return i; - unsigned int new_pi = (unsigned int) ((double(i-min_i) / double(i_range) * double(n_levels))) ; + double pi = ((double(i-min_i) / double(i_range) * double(n_levels))); + unsigned int new_pi = (unsigned int)pi; return new_pi; } diff --git a/src/nyx/phase3.cpp b/src/nyx/phase3.cpp index 9bb1c723..0cd0682f 100644 --- a/src/nyx/phase3.cpp +++ b/src/nyx/phase3.cpp @@ -20,7 +20,11 @@ namespace Nyxus /// bool processNontrivialRois (const std::vector& nontrivRoiLabels, const std::string& intens_fpath, const std::string& label_fpath, int num_FL_threads) { - for (auto lab : nontrivRoiLabels) + // Sort labels for reproducibility with function's trivial counterpart. Nontrivial part of the workflow isn't time-critical anyway + auto L = nontrivRoiLabels; + std::sort (L.begin(), L.end()); + + for (auto lab : L) { LR& r = roiData[lab]; diff --git a/src/nyx/reduce_trivial_rois.cpp b/src/nyx/reduce_trivial_rois.cpp index 31d56a3a..a4119f88 100644 --- a/src/nyx/reduce_trivial_rois.cpp +++ b/src/nyx/reduce_trivial_rois.cpp @@ -26,6 +26,7 @@ #include "features/geodetic_len_thickness.h" #include "features/glcm.h" #include "features/glrlm.h" +#include "features/gldzm.h" #include "features/glszm.h" #include "features/gldm.h" #include "features/hexagonality_polygonality.h" @@ -190,6 +191,13 @@ namespace Nyxus runParallel(GLRLMFeature::parallel_process_1_batch, n_reduce_threads, workPerThread, jobSize, &PendingRoisLabels, &roiData); } + //==== GLDZM + if (GLDZMFeature::required(theFeatureSet)) + { + STOPWATCH("Texture/GLDZM/DZ/#bbbbbb", "\t="); + runParallel(GLDZMFeature::parallel_process_1_batch, n_reduce_threads, workPerThread, jobSize, &PendingRoisLabels, &roiData); + } + //==== GLSZM if (GLSZMFeature::required(theFeatureSet)) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 50f964aa..ff5bee30 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -63,6 +63,7 @@ set(TEST_SRC ../src/nyx/features/glcm.cpp ../src/nyx/features/glcm_nontriv.cpp ../src/nyx/features/gldm.cpp + ../src/nyx/features/gldzm.cpp ../src/nyx/features/glrlm.cpp ../src/nyx/features/glszm.cpp ../src/nyx/features/hexagonality_polygonality.cpp