Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
48ab052
docs(build): build the site without the compiled extension and fail C…
Oct 15, 2026
ab8a89e
docs(readme): slim the README and add the entry pages
Oct 15, 2026
31633fe
docs(cli): generate the command-line reference and correct the usage …
Oct 15, 2026
3941234
docs(io): describe the supported formats, the output and the shared c…
Oct 15, 2026
4e2bb64
docs(scale): document large images, volumes and GPU acceleration
Oct 15, 2026
66df924
docs(guide): add IBSI mode, validation and troubleshooting pages
Oct 15, 2026
8b95bf2
docs(features): generate the feature list, the groups and the family …
Oct 15, 2026
1ec703c
test(docs): run every documentation example as part of the test suite
Oct 15, 2026
9d764c4
docs(features): rewrite the 2D intensity, histogram and image quality…
Oct 15, 2026
bcc7334
docs(features): rewrite the 2D morphology, erosion, fractal and radia…
Oct 15, 2026
a22308d
docs(features): rewrite the moments, Zernike and Gabor pages
Oct 15, 2026
463a80b
docs(features): rewrite the GLCM, GLRLM and GLSZM pages
Oct 15, 2026
ac1948d
docs(features): rewrite the GLDM, GLDZM, NGLDM and NGTDM pages
Oct 15, 2026
a23e988
docs(features): add the 3D intensity, shape and texture pages
Oct 15, 2026
1ab5e04
docs(api): rewrite the Python docstrings in numpydoc style and check …
Oct 15, 2026
b141226
docs(devguide): rewrite the developer guide and correct the project n…
Oct 15, 2026
5384205
test(docs): accept the platform-dependent results of two overflow exa…
Oct 15, 2026
47c2036
test(docs): expect the unsigned wrap of the GLSZM overflow example on…
Oct 15, 2026
8dfe1b0
chore(docs): find CLI defaults by symbol and stop link checks blockin…
Oct 16, 2026
26d3205
docs: correct the pages, scripts and registry rows named in review
Oct 16, 2026
ff8f931
docs: link the known defects to their issues
Oct 16, 2026
c6f8475
docs: answer the second review of the pages, scripts and registry
Oct 19, 2026
062fa01
style(docs): put the 3D ball assert message on its own line
Oct 20, 2026
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
76 changes: 76 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Build Documentation

on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'README.md'
- 'src/nyx/**'
- 'tests/vetting/oracle_coverage.csv'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: gen_cli_reference.py --check reads environment.cpp, environment_basic.h, dirs_and_files.cpp, main_nyxus.cpp, features/*.cpp etc. (every file named in an evidence/default_at), but the filter lists only cli_option_constants.h, featureset.*, env_features.cpp and features/*.h. A PR that changes a default in environment.cpp does not run this workflow; the drift is then caught only by the pytest wrapper, and the docs build and the staleness check of cli_reference.rst never run for it. Add src/nyx/*.cpp/*.h (or src/nyx/**) to both filters.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed Both path filters now use src/nyx/** in place of the hand-picked files, so a PR that changes any cited source file runs the docs workflow. Done in 8dfe1b0.

- '.readthedocs.yml'
- '.github/workflows/docs.yml'
pull_request:
paths:
- 'docs/**'
- 'README.md'
- 'src/nyx/**'
- 'tests/vetting/oracle_coverage.csv'
- '.readthedocs.yml'
- '.github/workflows/docs.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

jobs:
Build_Docs:
name: Build documentation (warnings are errors)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: docs/requirements.txt

- name: Install documentation dependencies
run: python -m pip install -r docs/requirements.txt

- name: Check the command-line reference against the CLI
run: python docs/tools/gen_cli_reference.py --check

- name: Check the feature reference against the code
run: python docs/tools/gen_feature_reference.py --check

- name: Build HTML
run: python -m sphinx -b html -W --keep-going docs/source docs/build/html

# External sites rate-limit and time out from CI runners now and then, so a broken link is
# reported here without failing the workflow.
Check_Links:
name: Check external links (does not block)
runs-on: ubuntu-latest
continue-on-error: true

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: docs/requirements.txt

- name: Install documentation dependencies
run: python -m pip install -r docs/requirements.txt

- name: Check links
run: python -m sphinx -b linkcheck -W --keep-going docs/source docs/build/linkcheck

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: linkcheck -W fails the job on any transient 403/429/timeout. The pages link to ncbi.nlm.nih.gov, imagej.net, github.com/PolusAI (many anchors) and others, all of which rate-limit or time out from GitHub runners now and then, so an unrelated PR gets a red check. Make the link check a separate non-required job (or continue-on-error, or a weekly schedule: trigger), and set linkcheck_retries/linkcheck_timeout and linkcheck_ignore for the rate-limited hosts in conf.py, which sets none of them. Also add permissions: contents: read (the other release workflows set permissions; this one gets the default token scope) and pip caching (cache: pip on setup-python).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed The link check is now its own Check_Links job with continue-on-error: true, so a rate-limited host no longer fails the workflow, and conf.py sets linkcheck_retries, linkcheck_timeout and linkcheck_rate_limit_timeout instead of an ignore list. The workflow also gets permissions: contents: read and pip caching. Done in 8dfe1b0.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,6 @@ src/nyx/python/nyxus/_version.py
# default result filename stem (Environment::nyxus_result_fname); the extension follows whichever
# writer ran, so pytest leaves .arrow and .parquet behind on every run of tests/python/.
NyxusFeatures.*

# Sphinx autosummary output
docs/source/References/stubs/
27 changes: 9 additions & 18 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required

version: 2

build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "mambaforge-4.10"

conda:
environment: environment.yml

# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
python: "3.12"

submodules:
include: all

# Optionally declare the Python requirements required to build your docs
# The docs need only the Python sources: docs/source/conf.py mocks the
# compiled backend, so the C++ extension is not built here.
python:
install:
- method: pip
path: .
- requirements: docs/requirements.txt

sphinx:
configuration: docs/source/conf.py
fail_on_warning: true
24 changes: 24 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
cff-version: 1.2.0
message: "If you use Nyxus in your work, please cite the software as below."
type: software
title: Nyxus
abstract: >-
Scalable library for calculating features from intensity-label image data:
intensity, texture, morphology and image quality features for every region of
interest in 2D images and 3D volumes, from the command line or from Python.
authors:
- given-names: Andriy
family-names: Kharchenko
affiliation: Axle Informatics
version: 0.11.0 # x-release-please-version
license: MIT
repository-code: "https://github.com/PolusAI/nyxus"
url: "https://nyxus.readthedocs.io"
keywords:
- image analysis
- feature extraction
- radiomics
- texture features
- morphology
- microscopy
- medical imaging
97 changes: 59 additions & 38 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@ Guidance for Claude Code (and humans) working in this repository.

Nyxus is a scalable C++/Python library that computes engineered features
(intensity, texture, morphology, moments, digital-filter) from segmented and
whole-slide/whole-volume images. It computes 450+ 2D features and a large 3D
feature set at the ROI or whole-image level, and is designed to operate at any
scale by assembling ROIs that span multiple image tiles and files.
whole-slide/whole-volume images. It computes 758 features in all, 539 for 2D
images, 213 for 3D volumes and 6 image quality features, at the ROI or
whole-image level (`docs/tools/gen_feature_reference.py --check` keeps these
counts equal to the code), and is
designed to operate at any scale by assembling ROIs that span multiple image
tiles and files.

- **Python API**: classes `Nyxus` (2D), `Nyxus3D` (3D volumes), `Nested`
(hierarchical parent/child ROIs), `ImageQuality`. Results come back as a
pandas DataFrame; Arrow IPC and Parquet output are also supported.
- **CLI**: the `nyxus` executable (built with `-DBUILD_CLI=ON`).
- **IO**: 2D from OME-TIFF, OME-Zarr, DICOM; 3D from NIFTI (compressed and
uncompressed); in-memory NumPy arrays via the Python API.
- **IO**: TIFF/OME-TIFF and OME-Zarr as 2D images or 3D volumes (OME files up
to 5D, X/Y/Z/C/T); DICOM 2D; NIFTI (compressed and uncompressed); in-memory
NumPy arrays via the Python API.
- Upstream is [PolusAI/nyxus](https://github.com/PolusAI/nyxus). C++20,
CMake build system. Version is derived from git tags via `setuptools_scm`.
CMake build system. Releases are cut by release-please
(`release-please-config.json`, `.release-please-manifest.json`) from the
Conventional Commit subjects on `main`; on each release the release and
wheel builds run `ci-utils/sync_version.py` to stamp the version into
`.version`, `src/nyx/version.h` (`PROJECT_VER`, printed by the CLI) and
`plugin.json` of the build.
The Python package version is derived from the release's git tag via
`setuptools_scm`.

## Repository layout

Expand All @@ -26,7 +37,7 @@ CMakeLists.txt top-level build; feature/IO/GPU options
pyproject.toml Python packaging (package-dir = src/nyx/python)
environment.yml conda deps
ci-utils/envs/ conda dep lists: conda_cpp.txt, conda_py.txt, conda_gpu.txt, ...
ci-utils/install_prereq_windows.bat / install_prereq_linux.sh non-conda dep builders
ci-utils/install_prereq_win.bat / install_prereq_linux.sh non-conda dep builders
.github/workflows/ CI: build_and_test_{windows,ubuntu,mac}.yml + wheel/publish jobs
docs/ Sphinx docs (Read the Docs); docs/source/featurelist.rst
tests/python/ pytest suite (this is what CI runs)
Expand All @@ -51,7 +62,7 @@ src/nyx/ all C++ sources
1:1 code-correspondence check, cyclic-dependency resolution, user-selection
compilation, `init_feature_classes()`.
- **`feature_mgr_init.cpp`** — the concrete registration list
(`register_feature(new GLCMFeature()); ...` for every 2D/3D feature).
(`register_feature(new GLCMFeature()); ...` for every 2D, 3D and image-quality feature class).
**Start here to see what is actually wired in.**
- `env_features.cpp`, `environment*.cpp/h`, `feature_settings.h` — parsing the
requested feature set / groups (e.g. `*ALL*`, `*ALL_GLCM*`, `*WHOLESLIDE*`,
Expand All @@ -76,18 +87,20 @@ src/nyx/ all C++ sources
- **Analysis pipeline** (three phases, declared in `globals.h`, dispatched from
`main_nyxus.cpp`): dataset drivers `workflow_2d_segmented.cpp`,
`workflow_2d_whole.cpp`, `workflow_3d_segmented.cpp`, `workflow_3d_whole.cpp`,
`workflow_pythonapi.cpp`; then `phase1.cpp` (gather ROI metrics, decide
trivial vs oversized), `phase2_2d.cpp` / `phase2_25d.cpp` / `phase2_3d.cpp`
(feature calc), `phase3.cpp` (output). ROIs over the memory limit go through
`processNontrivialRois()` + each feature's streaming
`osized_scan_whole_image()` / `osized_add_online_pixel()`; in-RAM ROIs go
`workflow_pythonapi.cpp`; then `phase1.cpp` (gather ROI metrics; the
workflow files then sort ROIs into trivial and oversized), `phase2_2d.cpp` / `phase2_25d.cpp` / `phase2_3d.cpp`
(feature calc), `phase3.cpp` / `phase3_3d.cpp` (oversized ROIs, out of core);
output in `output_2_csv.cpp`, `output_2_buffer.cpp`, `output_2_apache.cpp`.
ROIs over the memory limit go through `processNontrivialRois()` (`phase3.cpp`)
and `processNontrivialRois_3D()` (`phase3_3d.cpp`), which call each feature's
`osized_scan_whole_image()`; in-RAM ROIs go
through `processTrivialRois*()`. See also `reduce_trivial_rois.cpp`,
`pixel_feed.cpp`, `features_calc_workflow.cpp`.
- **Image loading**: `image_loader*.cpp/h`, `raw_image_loader.*`, and format
backends `raw_tiff.h`, `raw_omezarr.h`, `raw_dicom.h`, `raw_nifti.*`,
`grayscale_tiff.h`, `abs_tile_loader.h`; NIFTI support under `io/nifti/`.
- **GPU (CUDA)**: `src/nyx/gpu/*.cu/.cuh` — GPU kernels for the compute-heavy
features (Gabor, erosion, geometric moments). Guarded by `-DUSEGPU=ON` /
features (Gabor, erosion, geometric moments, GLSZM). Guarded by `-DUSEGPU=ON` /
`USE_GPU`. Enabled at runtime with `--useGpu=true` (CLI) or the constructor.
- **Arrow/Parquet output**: `arrow_helpers.cpp`, `arrow_output_stream.*`
(guarded by `USE_ARROW`).
Expand Down Expand Up @@ -119,8 +132,8 @@ A C++20 compiler + CMake ≥ 3.20 are required. Key CMake options:

Deps are easiest via conda (`ci-utils/envs/conda_cpp.txt` + `conda_py.txt`); set
`NYXUS_DEP_DIR=$CONDA_PREFIX` (or `$CONDA_PREFIX/Library` on Windows) so CMake
finds them. The README "Building from source" section has full Linux/Windows and
Docker recipes.
finds them. The "Building from source" section of the installation guide
(`docs/source/installation.rst`) has the full recipes.

### Windows (MSVC + optional CUDA)

Expand All @@ -135,14 +148,19 @@ build cannot link them. Full-feature + CUDA build (paths below are placeholders:
```
2. Configure with the **Ninja** generator so `nvcc` can use `cl` as its host
compiler (no CUDA MSBuild integration needed): run the Visual Studio
`vcvars64.bat` first, then `cmake -G Ninja -DCMAKE_CXX_COMPILER=cl
-DBUILD_CLI=ON -DALLEXTRAS=ON -DUSEGPU=ON -DCMAKE_PREFIX_PATH=$ENV\Library
-DNYXUS_DEP_DIR=$ENV\Library ..` then `cmake --build . --config Release`.
`vcvars64.bat` first, then `cmake -S . -B build-cli -G Ninja
-DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release -DBUILD_CLI=ON
-DALLEXTRAS=ON -DUSEGPU=ON -DCMAKE_PREFIX_PATH=$ENV\Library` then
`cmake --build build-cli`. With MSVC, CMake presets `Debug` for Ninja and
`--config` has no effect there, so `-DCMAKE_BUILD_TYPE=Release` is required.
(`NYXUS_DEP_DIR` is read only as an environment variable, not as a `-D`
cache variable.)
3. To run, put `$ENV\Library\bin` (dependency DLLs) and the CUDA toolkit `bin`
on PATH.

**Fast Python-extension build** for running the test suite (tiff-only CPU):
same as above but `-DBUILD_LIB=ON -DALLEXTRAS=OFF -DUSEGPU=OFF
same as above (keep `-DCMAKE_BUILD_TYPE=Release`, which
`CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE` below relies on) but `-DBUILD_LIB=ON -DALLEXTRAS=OFF -DUSEGPU=OFF
-DPYTHON_EXECUTABLE=$ENV\python.exe -Dpybind11_DIR=$ENV\Lib\site-packages\pybind11\share\cmake\pybind11`
and `--target backend`, with `-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE` pointed at
`src\nyx\python\nyxus` so the `.pyd` lands in the package. With `ALLEXTRAS=OFF`
Expand Down Expand Up @@ -172,15 +190,17 @@ test can hide such failures. Requires an importable `nyxus.backend` (`.pyd` on
`serial`, `skip_ci`. Key files: `test_nyxus.py` (main API) plus the oracle /
regression suites listed under "Feature validation" below.

**C++ (GoogleTest, `tests/`)** — target `runAllTests` (enabled from the
top-level `CMakeLists.txt`; gtest fetched via `tests/CMakeLists.txt` +
`CMakeLists.txt.gtest`). `tests/test_all.cc` is the single gtest `main` and
**C++ (GoogleTest, `tests/`)** — target `runAllTests` (enabled with
`-DRUN_GTEST=ON` in the top-level `CMakeLists.txt`, which fetches gtest at
configure time from `tests/CMakeLists.txt.gtest` and adds `tests/`).
`tests/test_all.cc` is the single gtest `main` and
`#include`s the many `test_*.h` headers (they are intentionally **not** listed
in CMake — see the note in `tests/CMakeLists.txt`). Headers are grouped by
feature and by oracle: `test_<feat>_ibsi.h` (IBSI conformance),
`test_<feat>_pyradiomics.h`, `test_<feat>_regression.h`,
`test_<feat>_mechanics.h`, and `test_3d_<feat>*.h`; fixtures in `test_data.h`.
Images live in `tests/data/`.
in CMake — see the note in `tests/CMakeLists.txt`). Headers are named
`test_<dim>_<family>_<kind-or-oracle>.h` (see "Feature validation" below):
e.g. `test_2d_glcm_ibsi.h` (IBSI conformance), `test_2d_glcm_pyradiomics.h`,
`test_3d_glcm_regression.h`, `test_2d_glcm_mechanics.h`; a family's shared
fixtures are in `test_<dim>_<family>_common.h`, the general ones in
`test_data.h`. Images live in `tests/data/`.

## Feature validation ("oracle") framework

Expand All @@ -199,9 +219,9 @@ governing documents are:
authoring checklist for adding a vetted feature. Any new or changed test must
follow it — taxonomy, naming, tolerances, and registry update included.
- `tests/vetting/oracle_coverage.csv` — the single source of truth: one row per
assertion (`feature × config_recipe × oracle`) with its `outcome`
(`vetted` / `regression` / `invariant` / `not_tested` / `not_implemented` /
`dropped_invalid`), tolerance, backing test, and benchmark. A feature counts
assertion (`feature × config_recipe × oracle`) with its `status`
(`vetted` / `regression` / `invariant` / `untested`; only a `vetted` row
names an `oracle`), tolerance, backing test, and benchmark. A feature counts
as vetted iff it has ≥1 `vetted` oracle row.
- `tests/vetting/config_recipes.md` — the exact Nyxus + tool settings that make
a feature directly comparable to a reference (referenced by id from the
Expand All @@ -210,7 +230,7 @@ governing documents are:
supporting guides.
- Allowed oracle tokens (SPEC §4): pyradiomics, radiomicsj, skimage, mirp,
matlab, cellprofiler, mitk, feature2djava, wndcharm, imea, imagej, fraclac,
pydicom, ibsi, analytic. Oracle goldens are generated **offline** by a
pydicom, opencv, ibsi, analytic. Oracle goldens are generated **offline** by a
checked-in generator (`tests/vetting/oracles/gen_<family>_<oracle>.*`) and
pinned with full provenance — **reference tools are never CI runtime
dependencies**.
Expand All @@ -232,9 +252,9 @@ tolerance loose enough to pass a known-bad value is itself a test bug.

- **Branches** are cut from and named after their base branch as
`main-<topic>` (not `fix/` or `feat/`).
- **Annotate changed source lines**: when fixing a defect, add a brief comment
on the changed line(s) explaining the defect and the fix, so the reason
survives in the code.
- **Comment changed source lines** with what the code does and why it does it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: This replaces the rule that a defect fix gets a brief comment on the changed line(s) saying what was wrong and how it was fixed ("so the reason survives in the code") with the opposite: the history of a change "belongs in the commit message, not in the code". CONTRIBUTING.md:206 adds the same rule for contributors. That is a change to contributor policy, not a correction of a doc that disagreed with the code, and the description lists it under "corrected where they disagreed with the code". It should be its own PR, or at least be called out in the description, so maintainers can decide it knowingly. As written, #485/#486-style fixes that carry defect annotations would now be against the project's own notes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diverted The rule change stays in this PR, but the description now lists it under its own heading as a policy change rather than a correction, and says the two paragraphs can be dropped without affecting anything else. That leaves the decision to the maintainers, knowingly.

that way. The history of a change (the defect and the fix) belongs in the
commit message, not in the code.
- Match the density, naming, and idiom of the surrounding code.
- **Run `pytest tests/python/` (the full directory) locally before proposing a
push** — it mirrors CI and catches cross-test pollution.
Expand Down Expand Up @@ -263,6 +283,7 @@ tolerance loose enough to pass a known-bad value is itself a test bug.
## Docs

Feature reference: `docs/source/featurelist.rst`. Rendered docs at
https://nyxus.readthedocs.io. The README has extensive Python/CLI usage
examples (directory/file/NumPy featurization, whole-slide, 3D, nested ROIs,
Hounsfield/CT handling, anisotropy, Arrow/Parquet output).
https://nyxus.readthedocs.io. Usage examples (directory/file/NumPy
featurization, whole-slide, 3D, nested ROIs, Hounsfield/CT handling,
anisotropy, Arrow/Parquet output) are in the documentation, starting at
`docs/source/cmdline_and_examples.rst`; the README has only a short quick start.
Loading
Loading