Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ci-utils/envs/conda_cpp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ xsimd >=13,<14
cmake
dcmtk >=3.6.9
fmjpeg2koj >=1.0.3
libarrow
libparquet
libarrow <=23.0.0
libparquet <=23.0.0
8 changes: 4 additions & 4 deletions ci-utils/install_prereq_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#

BUILD_Z5_DEP=1
BULD_DCMTK_DEP=1
BUILD_DCMTK_DEP=1
BUILD_ARROW_DEP=0
BUILD_BOOST_DEP=1

Expand All @@ -24,7 +24,7 @@ done

if [[ "${min_build,,}" == "yes" ]]; then
BUILD_Z5_DEP=0
BULD_DCMTK_DEP=0
BUILD_DCMTK_DEP=0
BUILD_ARROW_DEP=0
BUILD_BOOST_DEP=0
fi
Expand Down Expand Up @@ -152,7 +152,7 @@ cmake -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ -DCMAKE_PREFIX_PATH=.
make install -j4
cd ../../

if [[ $BULD_DCMTK_DEP -eq 1 ]]; then
if [[ $BUILD_DCMTK_DEP -eq 1 ]]; then
curl -L https://github.com/glennrp/libpng/archive/refs/tags/v1.6.53.zip -o v1.6.53.zip
unzip v1.6.53.zip
cd libpng-1.6.53
Expand Down Expand Up @@ -202,7 +202,7 @@ fi
make install -j4
cd ../../

if [[ $BULD_DCMTK_DEP -eq 1 ]]; then
if [[ $BUILD_DCMTK_DEP -eq 1 ]]; then
curl -L https://github.com/DCMTK/dcmtk/archive/refs/tags/DCMTK-3.6.9.zip -o DCMTK-3.6.9.zip
unzip DCMTK-3.6.9.zip
cd dcmtk-DCMTK-3.6.9/CMake
Expand Down
1 change: 1 addition & 0 deletions src/nyx/cli_option_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#define clo_XYRESOLUTION "--pixelsPerCentimeter" // pixels per centimeter
#define clo_PXLDIST "--pixelDistance" // used in neighbor features
#define clo_COARSEGRAYDEPTH "--coarseGrayDepth" // Environment :: raw_coarse_grayscale_depth
#define clo_BINNINGORIGIN "--binningOrigin" // Environment :: "zero" (default) or "min" (PyRadiomics-style)
#define clo_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 clo_TEMPDIR "--tempDir" // Optional. Used in processing non-trivial features. Default - system temp directory
#define clo_IBSICOMPLIANCE "--ibsi" // skip binning for grey level and grey tone features
Expand Down
1 change: 1 addition & 0 deletions src/nyx/env_features.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ void Environment::compile_feature_settings()
s[(int)NyxSetting::USEGPU].bval = using_gpu();
s[(int)NyxSetting::VERBOSLVL].ival = get_verbosity_level();
s[(int)NyxSetting::IBSI].bval = ibsi_compliance;
s[(int)NyxSetting::BINNING_ORIGIN].ival = static_cast<int>(get_binning_origin()); // propagate binning origin to per-feature settings
}
}

Expand Down
37 changes: 34 additions & 3 deletions src/nyx/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ void Environment::show_cmdline_help()
<< "\t\t\tDefault: 5 \n"
<< "\t\t" << OPT << clo_COARSEGRAYDEPTH << "=<custom number of grayscale levels> \n"
<< "\t\t\tDefault: 64 \n"
<< "\t\t" << OPT << clo_BINNINGORIGIN << "=<zero|min> Origin of intensity binning range \n"
<< "\t\t\t'zero' bins from [0, max] (default), 'min' bins from [min, max] (PyRadiomics-compatible) \n"
<< "\t\t" << OPT << clo_GLCMANGLES << "=<one or more comma separated rotation angles from set {0, 45, 90, and 135}> \n"
<< "\t\t\tDefault: 0,45,90,135 \n"
<< "\t\t" << OPT << clo_VERBOSITY << "=<levels of verbosity 0 (silence), 1 (minimum output), 2 (1 + timing), 3 (2 + roi metrics + more timing), 4 (3 + diagnostic information)> \n"
Expand Down Expand Up @@ -441,6 +443,7 @@ bool Environment::parse_cmdline(int argc, char** argv)
find_string_argument(i, clo_GLCMOFFSET, glcmOptions.rawOffs) ||
find_string_argument(i, clo_PXLDIST, pixel_distance) ||
find_string_argument(i, clo_COARSEGRAYDEPTH, raw_coarse_grayscale_depth) ||
find_string_argument(i, clo_BINNINGORIGIN, raw_binning_origin) ||
find_string_argument(i, clo_VERBOSITY, rawVerbosity) ||
find_string_argument(i, clo_IBSICOMPLIANCE, raw_ibsi_compliance) ||
find_string_argument(i, clo_RAMLIMIT, rawRamLimit) ||
Expand Down Expand Up @@ -663,9 +666,27 @@ bool Environment::parse_cmdline(int argc, char** argv)
if (!raw_coarse_grayscale_depth.empty())
{
// string -> integer
if (sscanf(raw_coarse_grayscale_depth.c_str(), "%d", &coarse_grayscale_depth) != 1)
// Reject zero and negative values — grey depth must be at least 1
// for valid binning. Previously only parse failure was checked,
// which allowed depth=0 to slip through and be misinterpreted
// as IBSI mode.
if (sscanf(raw_coarse_grayscale_depth.c_str(), "%d", &coarse_grayscale_depth) != 1 || coarse_grayscale_depth < 1)
{
std::cerr << "Error: " << clo_COARSEGRAYDEPTH << "=" << raw_coarse_grayscale_depth << ": expecting an integer constant\n";
std::cerr << "Error: " << clo_COARSEGRAYDEPTH << "=" << raw_coarse_grayscale_depth << ": expecting a positive integer constant\n";
return false;
}
}

// parse BINNINGORIGIN
if (!raw_binning_origin.empty())
{
if (raw_binning_origin == "min")
binning_origin_ = BinningOrigin::min_based;
else if (raw_binning_origin == "zero")
binning_origin_ = BinningOrigin::zero;
else
{
std::cerr << "Error: " << clo_BINNINGORIGIN << "=" << raw_binning_origin << ": expecting 'zero' or 'min'\n";
return false;
}
}
Expand Down Expand Up @@ -915,11 +936,21 @@ int Environment::get_coarse_gray_depth()
return coarse_grayscale_depth;
}

void Environment::set_coarse_gray_depth(unsigned int new_depth)
void Environment::set_coarse_gray_depth(int new_depth)
{
coarse_grayscale_depth = new_depth;
}

BinningOrigin Environment::get_binning_origin() const
{
return binning_origin_;
}

void Environment::set_binning_origin(BinningOrigin bo)
{
binning_origin_ = bo;
}

bool Environment::set_ram_limit(size_t megabytes) {

// Megabytes to bytes
Expand Down
9 changes: 8 additions & 1 deletion src/nyx/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ class Environment: public BasicEnvironment
int get_floating_point_precision();

int get_coarse_gray_depth();
void set_coarse_gray_depth(unsigned int new_depth);
// Signed int to match sscanf %d target type and enable validation
// of negative values in parse_cmdline.
void set_coarse_gray_depth(int new_depth);

BinningOrigin get_binning_origin() const;
void set_binning_origin(BinningOrigin bo);

// implementation of SKIPROI
bool roi_is_blacklisted (const std::string& fname, int roi_label);
Expand Down Expand Up @@ -242,6 +247,8 @@ class Environment: public BasicEnvironment

int coarse_grayscale_depth; //= 64;
std::string raw_coarse_grayscale_depth; //= "";
std::string raw_binning_origin; //= "" (default "zero"; alternative "min")
BinningOrigin binning_origin_ = BinningOrigin::zero;

// data members implementing RAMLIMIT
std::string rawRamLimit; //= "";
Expand Down
10 changes: 10 additions & 0 deletions src/nyx/feature_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

#include <vector>

// Binning origin strategy for texture features
enum class BinningOrigin : int
{
zero = 0, // bins span [0, max] (default Nyxus/MATLAB behavior)
min_based = 1 // bins span [min, max] (PyRadiomics-compatible behavior)
};

// feature settings
union FeatureSetting
{
Expand Down Expand Up @@ -40,6 +47,8 @@ enum class NyxSetting : int
GLRLM_GREYDEPTH,
// GLSZM
GLSZM_GREYDEPTH,
// Binning origin
BINNING_ORIGIN,
//
__COUNT__
};
Expand All @@ -65,5 +74,6 @@ enum class NyxSetting : int
#define STNGS_GLSZM_GREYDEPTH(obj) (obj[(int)NyxSetting::GLSZM_GREYDEPTH].ival)
#define STNGS_NGTDM_GREYDEPTH(obj) (obj[(int)NyxSetting::NGTDM_GREYDEPTH].ival)
#define STNGS_NGTDM_RADIUS(obj) (obj[(int)NyxSetting::NGTDM_RADIUS].ival)
#define STNGS_BINNING_ORIGIN(obj) (static_cast<BinningOrigin>(obj[(int)NyxSetting::BINNING_ORIGIN].ival))


46 changes: 30 additions & 16 deletions src/nyx/features/3d_glcm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,18 @@ void D3_GLCM_feature::calculate (LR& r, const Fsettings& s)
SimpleCube<PixIntens> D;
D.allocate (w,h,d);

auto greyBinningInfo = STNGS_GLCM_GREYDEPTH(s); // former Nyxus::theEnvironment.get_coarse_gray_depth()
if (STNGS_IBSI(s)) // former Nyxus::theEnvironment.ibsi_compliance
// Use GLCM-specific grey depth if set via metaparams, otherwise fall back to global.
// GLCM_GREYDEPTH defaults to 0 when not explicitly configured (e.g. no metaparams
// set), so fall back to the global GREYDEPTH to avoid treating it as IBSI mode.
auto greyBinningInfo = STNGS_GLCM_GREYDEPTH(s);
if (greyBinningInfo == 0)
greyBinningInfo = s[(int)NyxSetting::GREYDEPTH].ival;
auto binOrigin = STNGS_BINNING_ORIGIN(s); // zero-based (Nyxus/MATLAB) or min-based (PyRadiomics)
bool ibsi = STNGS_IBSI(s);
if (ibsi)
greyBinningInfo = 0;

bin_intensities_3d (D, r.aux_image_cube, r.aux_min, r.aux_max, greyBinningInfo);
bin_intensities_3d (D, r.aux_image_cube, r.aux_min, r.aux_max, greyBinningInfo, binOrigin);

// calculate features for all the 13 directions
for (const ShiftToNeighbor & sh : shifts)
Expand All @@ -64,8 +71,9 @@ void D3_GLCM_feature::calculate (LR& r, const Fsettings& s)
D,
r.aux_min,
r.aux_max,
STNGS_GLCM_GREYDEPTH(s),
STNGS_IBSI(s),
greyBinningInfo,
ibsi,
binOrigin,
STNGS_NAN(s));
}
}
Expand Down Expand Up @@ -176,9 +184,9 @@ void D3_GLCM_feature::save_value(std::vector<std::vector<double>>& fvals)
//xxxx deprecated in PyR xxxx fvals[(int)Feature3D::GLCM_VARIANCE_AVE][0] = calc_ave(fvals_variance);
}

void D3_GLCM_feature::extract_texture_features_at_angle(int dx, int dy, int dz, const SimpleCube<PixIntens>& binned_greys, PixIntens min_val, PixIntens max_val, int n_greys, bool ibsi, double soft_nan)
void D3_GLCM_feature::extract_texture_features_at_angle(int dx, int dy, int dz, const SimpleCube<PixIntens>& binned_greys, PixIntens min_val, PixIntens max_val, int n_greys, bool ibsi, BinningOrigin binOrigin, double soft_nan)
{
calculateCoocMatAtAngle (P_matrix, dx, dy, dz, binned_greys, min_val, max_val, n_greys, ibsi);
calculateCoocMatAtAngle (P_matrix, dx, dy, dz, binned_greys, min_val, max_val, n_greys, ibsi, binOrigin);

// Blank cooc-matrix? -- no point to use it, assign each feature value '0' and return.
if (sum_p == 0)
Expand Down Expand Up @@ -270,7 +278,8 @@ void D3_GLCM_feature::calculateCoocMatAtAngle(
PixIntens grays_min_val,
PixIntens grays_max_val,
int n_greys,
bool ibsi)
bool ibsi,
BinningOrigin binOrigin)
{
int w = D.width(),
h = D.height(),
Expand All @@ -281,8 +290,11 @@ void D3_GLCM_feature::calculateCoocMatAtAngle(
if (ibsi)
greyInfo = 0;

// allocate the cooc and intensities matrices
if (radiomics_grey_binning(greyInfo))
// Allocate the cooc and intensities matrices.
// '&& !ibsi' guard: IBSI mode uses n_levels=0 and its own branch below,
// regardless of binOrigin. Without the guard, IBSI + min_based would
// incorrectly enter the radiomics path.
if (binOrigin == BinningOrigin::min_based && !ibsi)
{
// unique intensities
std::unordered_set<PixIntens> U(D.begin(), D.end());
Expand All @@ -294,7 +306,7 @@ void D3_GLCM_feature::calculateCoocMatAtAngle(
GLCM.allocate((int)I.size(), (int)I.size());
}
else
if (matlab_grey_binning(greyInfo))
if (binOrigin == BinningOrigin::zero && !ibsi) // zero-based (MATLAB) binning; !ibsi excludes IBSI which uses its own branch
{
auto n_matlab_levels = greyInfo;
I.resize(n_matlab_levels);
Expand Down Expand Up @@ -334,16 +346,17 @@ void D3_GLCM_feature::calculateCoocMatAtAngle(
lvl_a = D.zyx(zslice + dz, row + dy, col + dx);

// Skip 0-intensity pixels (usually out of mask pixels)
if (ibsi_grey_binning(greyInfo))
if (ibsi)
if (lvl_a == 0 || lvl_b == 0)
continue;

// 0-based grey tone indices, hence '-1'
int a = lvl_a,
b = lvl_b;

// raw intensities need to be modified for different grey binning paradigms (Matlab, PyRadiomics, IBSI)
if (radiomics_grey_binning(greyInfo))
// Raw intensities need to be modified for different grey binning paradigms.
// '&& !ibsi' ensures IBSI mode falls through to the else (matlab/IBSI) branch.
if ((binOrigin == BinningOrigin::min_based && !ibsi))
{
// skip zeroes
if (a == 0 || b == 0)
Expand All @@ -364,8 +377,9 @@ void D3_GLCM_feature::calculateCoocMatAtAngle(

(GLCM.xy(a, b))++;

// Radiomics GLCM is symmetric, Matlab one is not
if (D3_GLCM_feature::symmetric_glcm || radiomics_grey_binning(greyInfo) || ibsi_grey_binning(greyInfo))
// Radiomics and IBSI GLCMs are symmetric; Matlab is not.
// Equivalent to: symmetric_glcm || radiomics || ibsi
if (D3_GLCM_feature::symmetric_glcm || (binOrigin == BinningOrigin::min_based && !ibsi) || ibsi)
(GLCM.xy(b, a))++;
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/nyx/features/3d_glcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class D3_GLCM_feature : public FeatureMethod, public TextureFeature
PixIntens max_val,
bool normalize);

void extract_texture_features_at_angle (int dx, int dy, int dz, const SimpleCube<PixIntens> & grays, PixIntens min_val, PixIntens max_val, int n_greys, bool ibsi, double soft_nan);
void extract_texture_features_at_angle (int dx, int dy, int dz, const SimpleCube<PixIntens> & grays, PixIntens min_val, PixIntens max_val, int n_greys, bool ibsi, BinningOrigin binOrigin, double soft_nan);

void calculateCoocMatAtAngle(
// out
Expand All @@ -157,7 +157,8 @@ class D3_GLCM_feature : public FeatureMethod, public TextureFeature
PixIntens min_val,
PixIntens max_val,
int n_greys,
bool ibsi);
bool ibsi,
BinningOrigin binOrigin);

void calculatePxpmy();
void calculate_by_row_mean();
Expand Down
17 changes: 12 additions & 5 deletions src/nyx/features/3d_gldm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,15 @@ void D3_GLDM_feature::calculate (LR& r, const Fsettings& s)
D.allocate(w, h, d);

auto greyInfo = STNGS_GLDM_GREYDEPTH(s); // former Nyxus::theEnvironment.get_coarse_gray_depth()
if (STNGS_IBSI(s)) // former Nyxus::theEnvironment.ibsi_compliance
auto binOrigin = STNGS_BINNING_ORIGIN(s); // zero-based (Nyxus/MATLAB) or min-based (PyRadiomics)
bool ibsi = STNGS_IBSI(s);
if (ibsi)
greyInfo = 0;

bin_intensities_3d (D, r.aux_image_cube, r.aux_min, r.aux_max, greyInfo);
bin_intensities_3d (D, r.aux_image_cube, r.aux_min, r.aux_max, greyInfo, binOrigin);

// allocate intensities matrix
if (ibsi_grey_binning(greyInfo))
if (ibsi)
{
auto n_ibsi_levels = *std::max_element(D.begin(), D.end());

Expand All @@ -113,8 +115,13 @@ void D3_GLDM_feature::calculate (LR& r, const Fsettings& s)
std::sort(I.begin(), I.end());
}

// zero (backround) intensity at given grey binning method
PixIntens zeroI = matlab_grey_binning(greyInfo) ? 1 : 0;
// Determine the background intensity value. In zero-based (MATLAB) binning,
// intensity 0 gets mapped to bin 1, so background becomes 1. In all other
// modes (min-based radiomics, IBSI) background stays at 0. The three-part
// condition: greyInfo>0 confirms binning is active, BinningOrigin::zero
// selects MATLAB mode, and !ibsi excludes IBSI which handles background
// via its own 0-intensity skip logic.
PixIntens zeroI = (greyInfo > 0 && binOrigin == BinningOrigin::zero && !ibsi) ? 1 : 0;

// Gather zones
for (int zslice = 0; zslice < d; zslice++)
Expand Down
10 changes: 6 additions & 4 deletions src/nyx/features/3d_gldzm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,17 @@ void D3_GLDZM_feature::prepare_GLDZM_matrix_kit (SimpleMatrix<unsigned int>& GLD
auto greyInfo_localFeature = D3_GLDZM_feature::n_levels;
if (greyInfo_localFeature != 0 && greyInfo != greyInfo_localFeature)
greyInfo = greyInfo_localFeature;
if (STNGS_IBSI(s)) // former Nyxus::theEnvironment.ibsi_compliance
auto binOrigin = STNGS_BINNING_ORIGIN(s); // zero-based (Nyxus/MATLAB) or min-based (PyRadiomics)
bool ibsi = STNGS_IBSI(s);
if (ibsi)
greyInfo = 0;

auto& imR = r.aux_image_cube;
bin_intensities_3d (D, imR, r.aux_min, r.aux_max, greyInfo);
bin_intensities_3d (D, imR, r.aux_min, r.aux_max, greyInfo, binOrigin);

// allocate intensities matrix
std::vector<PixIntens> I;
if (ibsi_grey_binning(greyInfo))
if (ibsi)
{
auto n_ibsi_levels = *std::max_element(D.begin(), D.end());
I.resize(n_ibsi_levels);
Expand Down Expand Up @@ -106,7 +108,7 @@ void D3_GLDZM_feature::prepare_GLDZM_matrix_kit (SimpleMatrix<unsigned int>& GLD
continue;

// Skip 0-intensity pixels (usually out of mask pixels)
if (ibsi_grey_binning(greyInfo))
if (ibsi)
if (inten == 0)
continue;

Expand Down
8 changes: 5 additions & 3 deletions src/nyx/features/3d_glrlm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,17 @@ void D3_GLRLM_feature::calculate (LR& r, const Fsettings& s)
G.allocate (w,h,d);

auto greyInfo = STNGS_GLRLM_GREYDEPTH(s);
if (STNGS_IBSI(s))
auto binOrigin = STNGS_BINNING_ORIGIN(s); // zero-based (Nyxus/MATLAB) or min-based (PyRadiomics)
bool ibsi = STNGS_IBSI(s);
if (ibsi)
greyInfo = 0;

bin_intensities_3d (G, r.aux_image_cube, r.aux_min, r.aux_max, greyInfo);
bin_intensities_3d (G, r.aux_image_cube, r.aux_min, r.aux_max, greyInfo, binOrigin);

// sorted intensities

std::vector <PixIntens> I;
if (ibsi_grey_binning(greyInfo))
if (ibsi)
{
auto n_ibsi_levels = *std::max_element (G.begin(), G.end());
I.resize (n_ibsi_levels);
Expand Down
Loading
Loading