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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,13 @@ dmypy.json
# Cython debug symbols
cython_debug/
.DS_Store

# CMake build artifacts (when cmake runs in the repo root)
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile

# Test output files
*.arrow
*.parquet
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@ set(SOURCE
src/nyx/slideprops.cpp
src/nyx/strpat.cpp
src/nyx/workflow_2d_segmented.cpp
src/nyx/workflow_2d_fmaps.cpp
src/nyx/workflow_2d_whole.cpp
src/nyx/workflow_3d_fmaps.cpp
src/nyx/workflow_3d_segmented.cpp
src/nyx/workflow_3d_whole.cpp
src/nyx/workflow_pythonapi.cpp
Expand Down
74 changes: 61 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,54 @@ f = nyx.featurize_directory (intensity_dir=dir, label_dir=dir)
```


### Feature maps (sliding kernel) mode

Feature maps mode computes features at every position of a sliding kernel across each ROI, producing spatial feature maps instead of a single feature vector per ROI. This is useful for generating spatially resolved feature representations for downstream analysis or machine learning.

#### 2D feature maps

```python
from nyxus import Nyxus, save_fmaps_to_tiff

nyx = Nyxus(["*ALL_INTENSITY*"], fmaps=True, fmaps_radius=2) # 5x5 kernel
results = nyx.featurize_directory("/path/to/intensities", "/path/to/labels")

# results is a list of dicts, one per parent ROI:
# [
# {
# "parent_roi_label": 1,
# "intensity_image": "img1.tif",
# "mask_image": "seg1.tif",
# "origin_x": 10, "origin_y": 20,
# "features": {
# "MEAN": numpy.array(shape=(map_h, map_w)),
# "STDDEV": numpy.array(shape=(map_h, map_w)),
# ...
# }
# },
# ...
# ]

# Save feature maps as TIFF stacks (requires tifffile)
save_fmaps_to_tiff(results, "output/tiff/")
```

#### 3D feature maps

```python
from nyxus import Nyxus3D, save_fmaps_to_nifti

nyx = Nyxus3D(["*3D_ALL_INTENSITY*"], fmaps=True, fmaps_radius=1) # 3x3x3 kernel
results = nyx.featurize_directory("/path/to/volumes", "/path/to/masks")

# Each feature map is a 3D numpy array shaped (map_d, map_h, map_w)

# Save as NIfTI volumes (requires nibabel)
save_fmaps_to_nifti(results, "output/nifti/", voxel_size=(0.5, 0.5, 1.0))
```

Note: Feature maps mode returns numpy arrays rather than DataFrames, since the output is inherently image-shaped. Arrow/Parquet output is not supported in this mode.

## Further steps

For more information on all of the available options and features, check out [the documentation](#).
Expand Down Expand Up @@ -271,19 +319,19 @@ print(nyx.get_params())
will print the dictionary

```bash
{'coarse_gray_depth': 256,
'features': ['*ALL*'],
'gabor_f0': 0.1,
'gabor_freqs': [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0],
'gabor_gamma': 0.1,
'gabor_kersize': 16,
'gabor_sig2lam': 0.8,
'gabor_theta': 45.0,
'gabor_thold': 0.025,
'ibsi': 0,
'n_loader_threads': 1,
'n_feature_calc_threads': 4,
'neighbor_distance': 5,
{'coarse_gray_depth': 256,
'features': ['*ALL*'],
'gabor_f0': 0.1,
'gabor_freqs': [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0],
'gabor_gamma': 0.1,
'gabor_kersize': 16,
'gabor_sig2lam': 0.8,
'gabor_theta': 45.0,
'gabor_thold': 0.025,
'ibsi': 0,
'n_loader_threads': 1,
'n_feature_calc_threads': 4,
'neighbor_distance': 5,
'pixels_per_micron': 1.0}
```

Expand Down
6 changes: 4 additions & 2 deletions ci-utils/envs/conda_cpp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ xsimd >=13,<14
cmake
dcmtk >=3.6.9
fmjpeg2koj >=1.0.3
libarrow
libparquet
# Pin to <=23.0.0 — newer versions introduce breaking API changes
# that cause build failures.
libarrow <=23.0.0
libparquet <=23.0.0
13 changes: 11 additions & 2 deletions docs/source/References/Classes.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
Nyxus Classes
================
.. autosummary::
:toctree: stubs
:toctree: stubs

nyxus.Nyxus
nyxus.Nested
nyxus.Nyxus3D
nyxus.Nested

Feature Map I/O
================
.. autosummary::
:toctree: stubs

nyxus.save_fmaps_to_tiff
nyxus.save_fmaps_to_nifti
38 changes: 24 additions & 14 deletions docs/source/cmdline_and_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,19 @@ should adhere to columns "WIPP I/O role" and "WIPP type".
- path
* - --arrowOutputType
- (Optional) Type of Arrow file to write the feature results to. Current options are 'arrow' for Arrow IPC or 'parquet' for Parquet
- string
- string
- output
- enum
* - --fmaps
- (Optional) Enable feature maps mode. When enabled, a sliding kernel is moved across each ROI and features are computed at every position, producing spatial feature maps instead of a single feature vector per ROI. Acceptable values: true, false. Default: '--fmaps=false'. Not compatible with Arrow/Parquet output.
- string constant
- input
- enum
* - --fmapsRadius
- (Optional) Radius of the sliding kernel in feature maps mode. The kernel size is (2 * radius + 1). For example, '--fmapsRadius=2' produces a 5x5 kernel (2D) or 5x5x5 kernel (3D). Default: '--fmapsRadius=2'
- integer
- input
- integer

Examples
========
Expand Down Expand Up @@ -378,19 +388,19 @@ will print the dictionary

.. code-block:: bash

{'coarse_gray_depth': 256,
'features': ['*ALL*'],
'gabor_f0': 0.1,
'gabor_freqs': [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0],
'gabor_gamma': 0.1,
'gabor_kersize': 16,
'gabor_sig2lam': 0.8,
'gabor_theta': 45.0,
'gabor_thold': 0.025,
'ibsi': 0,
'n_loader_threads': 1,
'n_feature_calc_threads': 4,
'neighbor_distance': 5,
{'coarse_gray_depth': 256,
'features': ['*ALL*'],
'gabor_f0': 0.1,
'gabor_freqs': [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0],
'gabor_gamma': 0.1,
'gabor_kersize': 16,
'gabor_sig2lam': 0.8,
'gabor_theta': 45.0,
'gabor_thold': 0.025,
'ibsi': 0,
'n_loader_threads': 1,
'n_feature_calc_threads': 4,
'neighbor_distance': 5,
'pixels_per_micron': 1.0}

There is also the option to pass arguments to this function to only receive a subset of parameter values. The arguments should be
Expand Down
4 changes: 4 additions & 0 deletions src/nyx/cli_option_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
#define clo_ANISO_Y "--anisoy"
#define clo_ANISO_Z "--anisoz"

// Feature maps
#define clo_FMAPS "--fmaps" // Enable feature maps mode. "true" or "false"
#define clo_FMAPS_RADIUS "--fmapsRadius" // Kernel radius for feature maps. Integer >= 1. Example: "2" (produces 5x5 kernel)

// Result options
#define clo_NOVAL "--noval" // -> raw_noval
#define clo_TINYVAL "--tinyval" // -> raw_tiny
Expand Down
24 changes: 24 additions & 0 deletions src/nyx/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ bool Environment::parse_cmdline(int argc, char** argv)
|| find_string_argument(i, clo_RESULTFNAME, nyxus_result_fname)
|| find_string_argument(i, clo_CLI_DIM, raw_dim)

|| find_string_argument(i, clo_FMAPS, raw_fmaps)
|| find_string_argument(i, clo_FMAPS_RADIUS, raw_fmaps_radius)

#ifdef CHECKTIMING
|| find_string_argument(i, clo_EXCLUSIVETIMING, rawExclusiveTiming)
#endif
Expand Down Expand Up @@ -900,6 +903,27 @@ bool Environment::parse_cmdline(int argc, char** argv)
ibsi_compliance = false;
}

//==== Parse feature maps options
// --fmaps: enable/disable sliding-kernel feature extraction
if (!raw_fmaps.empty())
{
std::string tmp = raw_fmaps;
std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower);
fmaps_mode = (tmp == "true" || tmp == "1" || tmp == "on");
}

// --fmaps_radius: kernel half-width (full kernel size = 2*radius+1)
if (!raw_fmaps_radius.empty())
{
int r = 0;
if (sscanf(raw_fmaps_radius.c_str(), "%d", &r) != 1 || r < 1)
{
std::cerr << "Error: " << clo_FMAPS_RADIUS << "=" << raw_fmaps_radius << ": expecting an integer >= 1\n";
return false;
}
fmaps_kernel_radius = r;
}

// Success
return true;
}
Expand Down
15 changes: 15 additions & 0 deletions src/nyx/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@ class Environment: public BasicEnvironment
ResultOptions resultOptions;
std::tuple<bool, std::optional<std::string>> parse_result_options_4cli ();

// Feature maps (fmaps) options — sliding-kernel feature extraction mode
bool fmaps_mode = false; ///< When true, features are computed per kernel position (spatial maps)
int fmaps_kernel_radius = 2; ///< Half-width of the kernel; full kernel side = 2*radius+1
std::string raw_fmaps; ///< Raw CLI string for --fmaps flag (parsed in process_input)
std::string raw_fmaps_radius; ///< Raw CLI string for --fmaps_radius (parsed in process_input)

/// @brief Returns the kernel side length: 2*radius+1
int fmaps_kernel_size() const { return 2 * fmaps_kernel_radius + 1; }

/// @brief Returns true if fmaps mode conflicts with the current save option.
bool fmaps_prevents_arrow() const
{
return fmaps_mode && (saveOption == Nyxus::SaveOption::saveArrowIPC || saveOption == Nyxus::SaveOption::saveParquet);
}

// feature settings
Fsettings fsett_PixelIntensity,
fsett_BasicMorphology,
Expand Down
Loading
Loading