Repository navigation
Fix sdist include csrc - #1
Closed
vikasreddy11 wants to merge 29 commits into
Closed
vikasreddy11 wants to merge 29 commits into
vikasreddy11 wants to merge 29 commits into
Conversation
### Description Addresses missing deprecation warnings about the changing default value of `hash_type` in Project-MONAI#9088. The solution in Project-MONAI#9124 isn't ideal since the warning comes up in too many scenarios, the objective is to raise the warning when no value is passed explicitly to let users know to change things, and at the same time not breaking backwards compatibility. The behaviour defaults to using md5 as the `hash_type`, perhaps it's more secure for this to be sha256 despite compatibility breaking? There's also one minor MLFlow test fix. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
…#9107) Closes Project-MONAI#7169. ### Summary MONAI had no way to convert a 3D segmentation into an explicit surface mesh without dropping out to raw `skimage`. This adds a thin post-transform wrapping `skimage.measure.marching_cubes`: ### Benchmarks (sphere, skimage CPU kernel) | vol | step | verts / faces | time | |---|---|---|---| | 32³ | 1 | 1.2k / 2.4k | ~1ms | | 64³ | 1 | 4.8k / 9.6k | ~3ms | | 64³ | 5 | 192 / 380 | <1ms | | 128³ | 1 | 19k / 39k | ~17ms | | 128³ | 2 | 4.8k / 9.6k | ~3ms | | 128³ | 5 | 774 / 1.5k | ~1ms | ### Tests - New `tests/transforms/test_marching_cubes.py`: 13 passed (skimage parity, torch/MetaTensor inputs, spacing incl. pixdim, multi-channel, dict wrapper, error paths). - Neighbors `keep_largest_connected_component[d]` + `remove_small_objects`: 316 passed. Signed-off-by: Soumya Snigdha Kundu <soumyawork15@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…eMetric aggregate score (Project-MONAI#9117) Fixes Project-MONAI#9116 ### Description `compute_embedding_collapse` builds `aggregate` from a fixed `primary` key set that does not contain any `per_class_rank_<cls>` key, so per-class rank — the only indicator that can see collapse confined to one class — is computed, returned, and then excluded from every reduction. Under `reduction="max"`, documented as the worst-case score for safety-critical use, a class can be fully collapsed (`per_class_rank_<cls> == 1.0`) while `aggregate` reports a healthy embedding space, because the global indicators are all genuinely healthy. This folds the worst per-class score into the reduction pool as a single term. Under `max` the result is the worst case across every indicator, including per-class. Under `mean`, reducing to one term first keeps one vote per indicator, so the average does not drift as the number of classes grows. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [x] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [x] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: DucAnhValentinoNguyen <ducanhdollar98@gmail.com> Signed-off-by: Duc-Anh Valentino Nguyen <61320780+DucAnhValentinoNguyen@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…ct-MONAI#9134) ### Description `DiceMetric` and `MeanIoU` disagree on what `ignore_index` means, even though their docstrings carry the same wording: > Voxels with this label are excluded from the score `DiceHelper` builds a spatial mask with `create_ignore_mask()`, so every voxel belonging to the ignored class drops out of *all* class scores. `compute_iou()` instead zeroes the ignored channel in `y_pred` and `y`. Zeroing a channel does not remove those voxels from the other channels, so a voxel of the ignored class that the model assigned to some other class still counts as a false positive for that class. ```python import torch from monai.metrics import compute_dice, compute_iou # 4 voxels, 3 one-hot classes; voxel 1 belongs to the ignored class 1 y = torch.tensor([[[1., 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0]]]) # a perfect prediction except the ignored voxel is called class 0 y_pred = torch.tensor([[[1., 1, 0, 1], [0, 0, 0, 0], [0, 0, 1, 0]]]) compute_dice(y_pred, y, ignore_index=1) # [[1.0, nan, 1.0]] compute_iou(y_pred, y, ignore_index=1) # [[0.667, nan, 1.0]] ``` Class 0 is scored as perfect by Dice and penalised by IoU, for the same input and the same setting. Since `ignore_index` exists so that padding and unlabelled regions do not affect the score, the Dice behaviour is the intended one, and it is what `create_ignore_mask()` documents. `compute_iou()` now uses `create_ignore_mask()` too. This also removes the separate valid-class-index branch, since `create_ignore_mask()` already distinguishes a valid class index from a sentinel value. ### Tests The existing tests in `tests/metrics/test_ignore_index_metrics.py` are invariance checks that both behaviours happened to satisfy, and there were no `ignore_index` tests in `test_compute_meaniou.py` at all, which is how the difference went unnoticed when the feature landed in Project-MONAI#8757. Added `test_ignored_voxels_excluded_from_other_classes`, which pins the case above and asserts that `MeanIoU` and `DiceMetric` agree. It fails before this change. `pytest tests/metrics` passes: 410 passed, 39 skipped. The one failure in that directory, `test_cumulative_average_dist.py::DistributedCumulativeAverage::test_value`, also fails on an unmodified checkout here and is unrelated. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. I ran `pytest tests/metrics` directly rather than `runtests.sh`, which does not work on Windows (Project-MONAI#5857). Happy to run anything else you would like to see. --------- Signed-off-by: Siddhardha Nanda <99672439+SID-6921@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…t-MONAI#8855) Fixes Project-MONAI#8733 ### Description This PR adds support for connected component-based Surface Distance metric calculation to the existing SurfaceDistanceMetric and its helper function. ### Changes * Added per_component: bool = False to both SurfaceDistanceMetric class and its helper function * Extended the compute_average_surface_distance that calculates surface distance scores for each connected component individually * Added input shape validation requiring 4D or 5D binary segmentation with 2 channels (background + foreground) when per_component=True * Added appropriate testcases and docstrings. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [x] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [x] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [x] In-line docstrings updated. - [x] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Vijay Vignesh Prasad Rao <vijayvigneshp02@gmail.com> Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Fixes Project-MONAI#9162. ### Description MMAR support is deprecated in favor of MONAI Bundles. For security reasons, `load_from_mmar` now raises `DeprecatedError` before downloading or loading a model. The old loading code is removed, while the public imports and signature remain available to give callers a clear error. The three MMAR functions and `RemoteMMARKeys` are deprecated at `1.7.0` and scheduled for removal in `1.9.0`. Lookup and download still work until removal. The loader is disabled immediately, including on development versions before `1.7.0`. The skipped MMAR suite is replaced with 41 offline tests covering loader rejection, public exports, lookup/download behavior, and deprecation warnings and removal behavior across the `1.7.0` and `1.9.0` release boundaries. A private lookup helper prevents duplicate deprecation warnings for string-ID downloads, with a regression test checking that only `download_mmar` warns. Class constants and `MODEL_DESC` remain readable without warnings; the deprecation note explains that they will be removed with the MMAR module in `1.9.0`, and the tests cover attribute access. The loader is tested with warnings ignored and its decorator unwrapped. ### Validation On Windows with Python 3.12.7: - python -m unittest tests.apps.test_mmar_download tests.utils.test_deprecated -v: 72 passed, no skips. - Black 26.10.0, isort 9.0.2, and Ruff 0.16.10 passed on the MMAR package and changed test file. - Pyrefly 1.3.2 reported zero errors in the changed source files. - A targeted Sphinx build of the MMAR section and API docstrings passed with warnings treated as errors. - git diff --check passed. The documentation check uses a small Sphinx project with a stub for the existing bundle link. Full-suite, full-documentation, and coverage results are not claimed. CI is rerunning for the review update. ### Types of changes - [x] Breaking change: MMAR model loading is disabled for security reasons. - [x] Regression tests added. - [x] Docstrings and API documentation updated. - [ ] Full integration tests passed locally. - [ ] Full quick unit/distributed test suites passed locally. - [ ] Full make html documentation build passed. Prepared with Astra 6 assistance and reviewed with Claude and by myself, Also deepseek was also evaluvating the issue . Both are credited in the commit trailers. --------- Signed-off-by: Muhammad Talha <muhammadtalha223@gmail.com>
…S and SegResNetDS2 (Project-MONAI#9131) ### Description Add missing documentation for the `upsample_mode` parameter in both `SegResNetDS` and `SegResNetDS2` docstrings. The parameter is present in both `__init__` signatures (default `"deconv"`) but was not documented in the class docstrings. This PR adds the missing entries, describing the supported values (`"deconv"`, `"deconvgroup"`, `"nontrainable"`, `"pixelshuffle"`) and linking to `monai.utils.enums.UpsampleMode`. No functional changes. ### Types of changes - Non-breaking change (docstring only) ### Checklist - [x] I have read the [CONTRIBUTING](https://github.com/Project-MONAI/MONAI/blob/dev/CONTRIBUTING.md) guidelines. - [x] My code follows the style guidelines of this project. - [x] I have performed a self-review of my own code. - [x] I have made corresponding changes to the documentation. - [x] My changes generate no new warnings. Signed-off-by: Talhax55z <muhammadtalha223@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…ject-MONAI#9128) Follow-up to Project-MONAI#8797. While working on the anisotropic analysis in Project-MONAI#8797, I noticed that `shape_factor()` and `is_valid_shape()` in `SegResNetDS` had no direct unit test coverage for the anisotropic path. This PR adds tests for: - isotropic mode across 2D and 3D - anisotropic mode with `resolution` set - valid and invalid inputs to `is_valid_shape()` - mixed validity (one dimension invalid, others valid) - the `ValueError` raised by the forward pass on invalid shapes No runtime code changed. This is a test-only contribution. --------- Signed-off-by: Talhax55z <muhammadtalha223@gmail.com> Signed-off-by: Muhammad Talha <muhammadtalha223@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
…fusionMatrixMetric (Project-MONAI#9105) Closes Project-MONAI#4422. ### What Adds the likelihood-ratio family of diagnostic-testing metrics to `ConfusionMatrixMetric`, completing the diagnostic-odds block of the confusion-matrix reference the module already follows (the wikipedia table cited in the docstring): - **positive likelihood ratio** — `LR+ = tpr / fpr` (aliases: `plr`, `lr+`) - **negative likelihood ratio** — `LR- = fnr / tnr` (aliases: `nlr`, `lr-`) - **diagnostic odds ratio** — `DOR = (tp * tn) / (fp * fn)` (aliases: `dor`), included as the third member of the same family since Project-MONAI#4422 asks for the likelihood-ratio view of the confusion matrix and `DOR = LR+ / LR-` All three work through the existing `metric_name` machinery of `ConfusionMatrixMetric` / `compute_confusion_matrix_metric`, so they compose with `compute_sample`, every `MetricReduction` mode, `get_not_nans`, `include_background` and multi-class (one-vs-rest per channel) inputs with no new API surface. ### Conventions - Zero denominators yield `NaN`, following the module's existing convention for ratio-based metrics: `LR+` is undefined when `fpr == 0` (perfect specificity), `LR-` when `tnr == 0`, `DOR` when `fp * fn == 0`, and an empty class (all counts zero) is `NaN` for all three. - `DOR == LR+ / LR-` holds wherever all three are defined (covered by an algebraic-consistency test). - The conventions are documented in the class docstring next to the metric-name list. ### Testing - The three metrics join the file's canonical exhaustive table (per-metric `mean` / `mean_batch` on the standard multi-class inputs), including entries where NaN-aware reductions matter. - Edge cases on hand-computed confusion matrices: `LR+ = 3.6 / 1.0 / 0.0 / NaN`, `LR-` incl. the zero-specificity case, `DOR = 14.0 / 0.0 / NaN`, an all-zero (empty class) row, and the classification API with `include_background` both True and False. - All reduction modes with undefined entries present on the multi-class NaN data: `sum`, `sum_batch`, `mean_channel`, each with the expected `not_nans` counts. - Alias resolution (case/spacing/underscore variants) and `NotImplementedError` on unknown names. - `pytest tests/metrics/test_compute_confusion_matrix.py` — 63 passed; `black` (line length 120) clean. ### Review focus 1. `DOR` inclusion scope — it is on the same wikipedia table and mathematically `LR+/LR-`, but I'm happy to split it out if preferred. 2. The zero-denominator → `NaN` convention matches how `tpr`/`ppv` etc. already behave; `inf` (the limit) was deliberately not used to stay consistent. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: LiQing <325196192+qinxwew@users.noreply.github.com> Co-authored-by: LiQing <325196192+qinxwew@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…-MONAI#9185) Fixes Project-MONAI#9184. ### Description `EnsureChannelFirst` with `channel_dim="no_channel"` adds a channel dim, but the cached `MetaTensor.spatial_ndim` was computed before that and clamped to `ndim - 1`. For a `(H, W, D)` input with a 4x4 affine it stayed at 2. Crops then built a 2D translation from `peek_pending_rank()` and the offset on the third axis was lost. After adding the channel dim, I now recompute `spatial_ndim` from the affine (only for the no-channel case and a non-batched affine). Inputs where an existing channel dim is moved are not touched. I added tests to `test_ensure_channel_firstd.py` that check `spatial_ndim` for 3D and 2D channel-less input, and that `SpatialCropd` gets the full origin shift afterwards. They fail on `dev` without the change. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. I ran the meta tensor, EnsureChannelFirst, crop, spacing, load image and utility transform tests locally, plus black, isort and ruff on the changed files. --------- Signed-off-by: Sebastian Penhouet <7819068+Spenhouet@users.noreply.github.com> Co-authored-by: Sebastian Penhouet <7819068+Spenhouet@users.noreply.github.com>
### Description Removes docstring entries for parameters that these classes don't accept. Passing them, as the docs suggest, raises a `TypeError`: - `GenerateInstanceBorderd`, `GenerateWatershedMarkersd`: `allow_missing_keys` (these are plain `Transform`s, not `MapTransform`s) - `AffineBoxToImageCoordinated`, `AffineBoxToWorldCoordinated`: `remove_empty` - `LabelQualityScore`: `spatial_map` (only `VarianceMetric` takes it) Documentation only; no behaviour change. Follow-up to Project-MONAI#9149. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] In-line docstrings updated. Signed-off-by: Caitlin Leonard <caitlinsimona@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…tmpdir GC on zarr v3 (Project-MONAI#8816) ## Description Fixes Project-MONAI#8476 ### Root Cause In `ZarrAvgMerger.__init__`, when both `value_store` and `count_store` are `None` and zarr v3 is detected, a single `self.tmpdir` attribute was assigned **twice** sequentially: ```python # Before (buggy) if value_store is None: self.tmpdir = TemporaryDirectory() # ← assigned here self.value_store = zarr.storage.LocalStore(self.tmpdir.name) if count_store is None: self.tmpdir = TemporaryDirectory() # ← OVERWRITES the above! self.count_store = zarr.storage.LocalStore(self.tmpdir.name) ``` When `self.tmpdir` is overwritten by the second assignment, Python's reference count for the first `TemporaryDirectory` drops to zero and it is **immediately garbage-collected** — deleting the directory that `value_store` was pointing to. This causes the zarr `ValueError` seen in the issue. ### Fix Introduce two separate attributes `self.value_tmpdir` and `self.count_tmpdir` so both `TemporaryDirectory` objects remain alive for the full lifetime of the `ZarrAvgMerger` instance: ```python # After (fixed) self.value_tmpdir: TemporaryDirectory | None = None self.count_tmpdir: TemporaryDirectory | None = None if value_store is None: self.value_tmpdir = TemporaryDirectory() self.value_store = zarr.storage.LocalStore(self.value_tmpdir.name) if count_store is None: self.count_tmpdir = TemporaryDirectory() self.count_store = zarr.storage.LocalStore(self.count_tmpdir.name) ``` Also cleaned up the old `self.tmpdir: TemporaryDirectory | None` declaration which is no longer needed. ### Testing The existing `tests/inferers/test_zarr_avg_merger.py` test suite covers this path (the failing `test_zarr_avg_merger_patches_13` and `test_zarr_avg_merger_patches_14` cases from the issue). ### Checklist - [x] New/modified code has proper docstrings - [x] No new dependencies introduced - [x] Backward compatible — no API changes Signed-off-by: Zeeshan Modi <92383127+Zeesejo@users.noreply.github.com>
…MONAI#9033) ### Description `HausdorffDistanceMetric(percentile=0)` returned the full Hausdorff distance instead of the 0th-percentile (minimum) surface distance. The guard in `_compute_percentile_hausdorff_distance` was a truthiness test, `if not percentile:`, so the valid input `0` was treated as "percentile unset" and short-circuited to `surface_distance.max()`. `percentile` is documented as "an optional float number between 0 and 100", and the 0th percentile of the surface distances is the minimum — so `percentile=0` must not be treated as unset. The fix: - `percentile=None` → `.max()` (unchanged: the default, full Hausdorff distance) - `percentile == 0` → `.min()` (the 0th-percentile minimum) - everything else → the existing `torch.quantile` branch `percentile == 0` is routed through `.min()` rather than the quantile branch on purpose: `torch.quantile` returns `NaN` for an all-inf distance tensor (empty prediction or ground truth), which would have regressed those cases from `inf` to `NaN`. Tests: two new percentile-0 cases in the spherical-segmentation test matrix (plain and with spacing, red on the old code), plus a parameterized unit test of `_compute_percentile_hausdorff_distance` covering `None`/`0`/quantile paths, the all-inf guard, the empty-tensor path, and out-of-range `ValueError`s. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Soumya Snigdha Kundu <soumya_snigdha.kundu@kcl.ac.uk> Signed-off-by: Soumya Snigdha Kundu <soumyawork15@gmail.com>
Fixes Project-MONAI#9133 ### Problem and cause Importing MONAI eagerly loaded the nvImageCodec plugin, producing warnings about missing optional libraries. Excluding the plugin from eager loading fixed warnings during `import monai`, but creating `LoadImage()` still triggered them because its default readers initialized the plugin. ### Solution - Excluded `nvimgcodec_pydicom_plugin` from eager submodule loading. - Moved plugin setup to first use through `verify_suffix()` or `read()`. - Check whether a file is DICOM before initializing the optional plugin. - Changed the message about unavailable nvImageCodec to debug level. Both changes are needed: the exclusion prevents warnings during `import monai`, while lazy setup prevents them during `LoadImage()` initialization. ### Reproduction environment Python 3.12, WSL2, CPU PyTorch, and `nvidia-nvimgcodec-cu13` installed without the nvTIFF and nvJPEG2000 wheels. ### Testing - `pytest tests/data/test_init_reader.py tests/data/test_nvimgcodec_pydicom_reader.py -q` — 28 passed, 4 skipped, 7 subtests passed. - `./runtests.sh --ruff` — passed. - Full integration and quick-test suites have not been run. ### Types of changes - [x] Non-breaking change. - [ ] Breaking change. - [x] New tests added to cover the changes. - [ ] Integration tests passed locally. - [ ] Quick tests passed locally. - [x] In-line docstrings updated. - [ ] Documentation updated and `make html` tested in `docs/`. --------- Signed-off-by: Vikas Reddy <vikasreddy1090@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…oject-MONAI#9195) Fixes Project-MONAI#9206. Related to Project-MONAI#9134 (merged PR that fixed the same inconsistency for `MeanIoU`); this PR extends spatial exclusion to confusion-matrix counts. Also addresses the [single-channel false-positive review finding](Project-MONAI#9195 (comment)). ## Summary `ConfusionMatrixMetric(ignore_index=...)` must exclude ignored ground-truth voxels from **every evaluated class**, not just zero the ignored class's channel. Otherwise, predictions at ignored locations can introduce false positives and inflate true negatives. The fix builds a spatial validity mask from the original, binarized target before background removal or channel zeroing. It applies that mask to the positive counts, true negatives, and total valid-voxel count. Public signatures and the `[B, C, 4]` output order `[TP, FP, TN, FN]` are unchanged. ## Reproduced defect Consider ground-truth class assignments `[0, 1, 2, 0]`, predictions `[0, 0, 2, 0]`, and `ignore_index=1`. The only incorrect prediction is at an ignored voxel; all valid voxels are correct. These IDs illustrate the one-hot inputs below, rather than label-map inputs passed to this API. | Class 0 | Unmodified upstream | Correct spatial exclusion | |---|---:|---:| | `[TP, FP, TN, FN]` | `[2, 1, 1, 0]` | `[2, 0, 1, 0]` | | Precision | `0.6666667` | `1.0` | The original branch set `mask = None` for a valid ignored channel. Zeroing that channel did not exclude its locations from other channels. Simply zeroing all predictions and targets is also insufficient: those locations would then look like true negatives. The negative counts and valid-voxel total must use the same spatial mask. ## Binary membership and boolean inputs Every channel in this API represents binarized membership, including a single-channel target. Therefore, valid ignored class indices use: ```python mask = (original_y[:, ignore_index : ignore_index + 1] == 0).float() ``` This handles representation pitfalls without changing the shared helper: - **Single channel:** `ignore_index=0` excludes target values of `1` (membership in channel 0), not values of `0`. For `y=[1, 1, 0]`, only the last location remains valid. - **Boolean multi-channel targets:** the previous PR head delegated to a helper that evaluated `1.0 - bool_tensor`, raising `RuntimeError`. Direct comparison supports boolean targets and avoids a whole-target floating-point conversion. - **Single-channel false positives:** zeroing the only channel would turn a valid false positive into a true negative with `include_background=True`. Channel zeroing is restricted to multi-channel inputs; the spatial mask alone handles a single channel. For `y=[1, 1, 0]` and all-one predictions, both background settings now yield `[0, 1, 0, 0]` and specificity `0.0`. Sentinel values and `ignore_index=None` retain the existing helper path. Multi-channel background handling, multi-channel ignored-class zeroing, and undefined-score behavior are preserved. No arbitrary integer label-map support or new dependency is introduced. ## Minimal reproduction ```python import torch from monai.metrics import get_confusion_matrix # Shape [batch, channel, spatial]; boolean one-hot ground truth. y = torch.tensor([[[1, 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0]]], dtype=torch.bool) pred = torch.tensor([[[1, 1, 0, 1], [0, 0, 0, 0], [0, 0, 1, 0]]], dtype=torch.float32) counts = get_confusion_matrix(pred, y, ignore_index=1) assert counts[0, 0].tolist() == [2.0, 0.0, 1.0, 0.0] ``` ## Validation Fresh local validation on Windows, Python 3.14.1, PyTorch 2.12.1+cpu, and NumPy 2.3.5. The latest single-channel correction is published as [`fe6736c`](Project-MONAI@fe6736c), following the boolean-mask fix in [`3d74777`](Project-MONAI@3d74777): | Stage | Executed result | |---|---| | Original upstream module at `bbebca6f17cabf98ea5fb8ec5776eeacd10aa60d` | Four expected regression failures; four controls passed | | Boolean regressions on previous PR head `f65b69b12568cd0008889309ea96c17c38593581` | All 18 cases raised the reproduced boolean-subtraction error | | Single-channel regression on the boolean-mask follow-up | Five expected assertion failures, including specificity `1.0` instead of `0.0` | | Updated targeted repository suite | 141 tests: **132 passed, nine optional SciPy skips**, zero failures/errors | | Updated standalone reproduction/control suite | **Eight passed** | | Boolean cases after the fix | **18 passed**, included in the 141-test repository total | | Single-channel cases after the fix | **16 passed**, included in the 141-test repository total | | Ruff / Black / isort on changed Python files | All passed | The 18 added boolean cases cover both background settings, all three ignored class indices, and float32/int64/boolean predictions. They assert independently specified counts and verify that neither input is mutated. The public single-channel specificity regression now covers both background settings rather than only `False`. Other regressions cover single-channel dtypes, all-valid/all-ignored masks, sentinel controls, public metric aggregation, and errors on valid voxels. ```text python -m unittest tests.metrics.test_compute_confusion_matrix tests.metrics.test_ignore_index_metrics python -m unittest tests.metrics.test_ignore_index_metrics -k multichannel_boolean ``` The red-stage upstream check loads the exact unmodified upstream module into memory; it does not edit a checkout. Local tests use isolated test-only dependencies and restricted numerical-library thread counts. GPU, distributed, full integration, documentation-build, and performance validation are not claimed for this follow-up. Final audit on 2026-10-20 at `fe6736c6ea563421d3595cae2402d6f035b624d0` reran the targeted suite (132 passed, nine optional SciPy skips), the eight standalone reproduction/control tests, and Ruff/Black/isort successfully; the disposable sandbox was removed. DCO, pre-commit.ci, CodeRabbit, and the three DeepSource checks also passed on this head. These checks do not establish full integration/GPU/distributed validation. ## Infographic  The original illustration summarizes the initial multi-channel correction validated at `e42d7c4cec5574bca811e8423167dca1ccf34379`. The exact original image is rehosted outside the code diff because its previous attachment URL is unavailable. It is not a report of subsequent single-channel and boolean-input validation; current results are documented above. ## Change checklist - [x] Non-breaking fix; public signatures and count layout unchanged. - [x] Regression tests added and executed. - [x] Relevant inline documentation updated. - [ ] Full integration/GPU/distributed suite executed locally. - [ ] Documentation built locally. **AI assistance disclosure:** the original implementation and description used OpenAI Codex. This follow-up's analysis, tests, fix, and description were prepared with Letta Code; a separate agent reviewed the code. The original infographic was generated with OpenAI image generation; its historical results are distinguished from the current validation above. The submitting author remains responsible for review under MONAI's tool-assisted contribution policy. --------- Signed-off-by: JoaoMarcos44 <87440198+JoaoMarcos44@users.noreply.github.com> Signed-off-by: joaomarcos <joaomarcosdias444@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: Letta Code <noreply@letta.com>
…DynUNet (Project-MONAI#9127) <!-- worktree: .claude/worktrees/dynunet-deep-supervision-list | branch: feat/dynunet-deep-supervision-list --> # feat(networks): opt-in native-resolution deep supervision output for DynUNet ## Summary `DynUNet` is documented as an nnU-Net reimplementation, but its deep supervision differs from the reference in three ways (raised in discussion Project-MONAI#3149): supervision heads are always upsampled with nearest-neighbour interpolation and stacked on dim 1, head count is capped by `deep_supr_num`, and the network applies no per-level weighting. nnU-Net keeps each head at its native resolution, downsamples the target instead, supervises every decoder stage, and weights levels with a normalized `1/2**level` schedule (deepest dropped). This change does two things: 1. **Documents the divergence** in the `DynUNet` docstring — an explicit "architecture only, full pipeline in `monai.apps.nnunet`" pointer plus a differences paragraph covering interpolation, head count, weighting, and bias — and fixes the stale tutorials link (`tree/master/` → `tree/main/`; `main` is the tutorials repo's default branch). 2. **Adds an opt-in `deep_supr_output="stack"|"list"` argument.** `"list"` returns `[final, head_1, ..., head_k]` with each head at its native resolution, highest first, which is exactly the layout `monai.losses.DeepSupervisionLoss` expects — so users can reproduce nnU-Net-style supervision (or any custom loss) without hand-rolling hooks. The default `"stack"` is untouched: outputs are bit-identical to before for a fixed seed. ## Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [x] In-line docstrings updated. - [x] Documentation updated, tested `make html` command in the `docs/` folder. ## Changes ### `monai/networks/nets/dynunet.py` Docstring: divergence + ownership paragraphs after the paper citations, and the link fix: ```diff `Optimized U-Net for Brain Tumor Segmentation <https://arxiv.org/pdf/2110.03352.pdf>`_. + This is the network architecture only; for the full nnU-Net pipeline (planning, preprocessing, training, + ensembling) see :py:mod:`monai.apps.nnunet`. + + Differences from the reference nnU-Net network: deep supervision heads are optional and limited by + ``deep_supr_num`` (nnU-Net supervises every decoder stage); by default their low-resolution logits are + upsampled with nearest-neighbour interpolation and stacked, whereas nnU-Net keeps each output at its native + resolution and downsamples the target instead (``deep_supr_output="list"`` gives that behaviour, to be used + with :py:class:`monai.losses.DeepSupervisionLoss`); no per-level loss weighting is built in (nnU-Net uses a + normalized ``1/2**level`` schedule with the deepest level dropped; here weighting is left to the loss); + convolutions have no bias, which is immaterial before affine instance norm. ``` ```diff - https://github.com/Project-MONAI/tutorials/tree/master/modules/dynunet_pipeline. + https://github.com/Project-MONAI/tutorials/tree/main/modules/dynunet_pipeline. ``` New argument (appended last, so positional callers and existing checkpoints are unaffected), validated loudly, plus an `Args` entry: ```diff res_block: bool = False, trans_bias: bool = False, + deep_supr_output: str = "stack", ): ... self.deep_supervision = deep_supervision self.deep_supr_num = deep_supr_num + if deep_supr_output not in ("stack", "list"): + raise ValueError(f"deep_supr_output should be 'stack' or 'list', got {deep_supr_output!r}.") + self.deep_supr_output = deep_supr_output ``` `forward` gains the list branch; the stack path is unchanged. The union return type is declared explicitly (which is also what TorchScript keys on), and the first assignment is annotated so the declared return stays typed (`mypy warn_return_any`): ```diff - def forward(self, x): + def forward(self, x: torch.Tensor) -> torch.Tensor | list[torch.Tensor]: + out: torch.Tensor = self.skip_layers(x) out = self.output_block(out) if self.training and self.deep_supervision: + if self.deep_supr_output == "list": + return [out] + list(self.heads) out_all = [out] for feature_map in self.heads: out_all.append(interpolate(feature_map, out.shape[2:])) ``` `list(self.heads)` copies per call, so replicas (e.g. DataParallel) never alias the returned list. ### `tests/networks/nets/test_dynunet.py` - `TEST_CASE_DEEP_SUPERVISION_LIST`: a 24-case grid (`spatial_dims` × `res_block` × `deep_supr_num` × three stride families) asserting the exact shape of every element of the list. Expected head resolutions are derived from the cumulative products of the encoder's actual downsample strides (`strides[1:-1]`) — not `2**level`, which is wrong for anisotropic strides that contain 1s (e.g. `(1, 2, 1, 2, 1)` puts the first two heads at `//2, //2`). - `test_with_deep_supervision_loss`: list output → `DeepSupervisionLoss(DiceCELoss(...))` → finite scalar, `backward()` succeeds, and every supervision head's parameters received a gradient. - `test_eval_returns_single_tensor`: eval mode still returns one `Tensor`. - `test_invalid_deep_supr_output`: invalid value raises `ValueError` naming the value. - `test_list_output_ignored_without_deep_supervision`: `"list"` with `deep_supervision=False` still returns a single `Tensor` (the argument is scoped to deep supervision, as documented). ## Testing - **Unit:** `python -m unittest tests.networks.nets.test_dynunet` → 58 tests OK (1 pre-existing CUDA-only NVFuser skip). `python -m unittest tests.losses.test_ds_loss tests.networks.nets.test_segresnet_ds` → 94 OK (covers the loss-side integration and the list-returning TorchScript precedent; both untouched). - **Lint/format:** `./runtests.sh --ruff`, `./runtests.sh --copyright` (1370 files), `black --check` / `isort --check` on the changed files → pass. - **Type check:** `pyrefly check` and `mypy` on the changed files → clean; the reported error set is byte-identical to the base branch. - **Docs:** `make html` in `docs/` → build succeeded (new docstring renders; `:py:class:`/ `:py:mod:` cross-references resolve). - **Behaviour:** default `"stack"` output is bit-identical to base for a fixed seed; `"list"` yields native-resolution heads (e.g. 32/16/8³ for strides `[1, 2, 2, 2, 1]`) that are *not* nearest-upsampled blocks; one Adam step through `DeepSupervisionLoss` completes with gradients on all heads; eval returns a single tensor; an invalid value fails loudly. ## Notes for reviewers - **Tool assistance:** This contribution was prepared with assistance from OpenAI Codex and reviewed by the submitting author, who takes responsibility for the submitted code and description. - **Argument shape:** `deep_supr_output` is a string mode (leaves room for future formats, matches `upsample_mode`-style APIs). Happy to switch to a boolean (`deep_supr_native_output: bool`) if preferred — called out as an open question during scoping. - **TorchScript:** the default (deep-supervision-off) path scripts as before and is covered by the existing `test_script`. Scripting a `deep_supervision=True` DynUNet already fails on the base branch inside `DynUNetSkipLayer` (`Optional[List[Tensor]]` vs `_set_item`, needs local-variable refinement) — reproduced identically with and without this change, so no script test was added for the list mode: no DS-enabled DynUNet can be scripted today either way. That pre-existing bug deserves its own issue. - **DataParallel:** list gather is the standard path but was not exercised locally (no multi-GPU). Signed-off-by: Soumya Snigdha Kundu <soumyawork15@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
IntensityRemap is supposed to keep an images intensities in the same [min, max] range but it rescaled with * max + min so the output ended up in [min, min + max] instead. For example a CT scan in [-1000, 3000] came out in [-1000, 2000] and a image in [10, 20] came out in [10, 30]. The fix is one line, multiply by (max - min) instead and a second commit makes RandIntensityRemap use its own seeded generator instead of numpys global one so setting the seed actually makes the output repeatable. These are the first tests these two transforms have ever had, without the fixes 6 of 9 fail and with them all 9 pass. --------- Signed-off-by: devteamaegis <devteam.aegis@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…gression tests (Project-MONAI#9191) Related to Project-MONAI#9166. ### Description The current dev branch already includes the image writer spatial-rank fix. This PR handles the remaining rank-one case in spatial_resample: when spatial_size is None, use the input spatial size instead of passing None to the size validation predicate. Regression tests cover: - Writing channel-less 2D and 3D NIfTI images without an original affine or output spatial shape, checking shape, voxel values, and affine. - Eager and lazy rank-one resampling with spatial_size=None and -1. ### Types of changes - [x] Non-breaking bug fix. - [x] New tests added to cover the changes. ### Validation Ran the following related test modules: python -B -m unittest tests.data.test_nifti_rw tests.transforms.test_spatial_resample tests.transforms.spatial.test_spatial_resampled tests.transforms.test_resample_to_match tests.transforms.test_resample_to_matchd Result: 195 tests run, OK (6 skipped). Full integration and quick test suites were not run. AI assistance: OpenAI Codex assisted with implementation, testing, and preparation of this description. --------- Signed-off-by: moonbirdyang <kirito168888@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…9171) ### Description `DiceHelper`/`DiceMetric`'s `per_component=True` path never received the spatial mask `create_ignore_mask` builds from `ignore_index` — `compute_cc_dice` has no mask parameter at all, so combining `per_component=True` with `ignore_index` silently computed TP/FP/FN over the "ignored" voxels as if nothing were ignored. The regular per-channel path already applies this mask correctly; only the per-component path was missing it. Fix threads the mask into `compute_cc_dice`: ignored voxels are forced to agree as background in both `y` and `y_pred` before the connected-component assignment and histogram, so they never contribute to any component's score — consistent with how the non-per_component path already handles masking. Reachable via the public API: `DiceMetric(per_component=True, ignore_index=N)(y_pred, y)`. No existing test exercised this combination (checked `tests/metrics/test_compute_meandice.py` — separate `per_component` and `ignore_index` cases, never together). ### Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] New tests added to cover the changes - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage` - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests` (ran the targeted test file directly via pytest instead: `pytest tests/metrics/test_compute_meandice.py`, 22/22 passed; did not run the full repo test runner) - [ ] In-line docstrings updated - [ ] Documentation updated --------- Signed-off-by: Siddhardha Nanda <99672439+SID-6921@users.noreply.github.com> Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
…AI#9237) ### Description `get_network_from_nnunet_plans()` and `ModelnnUNetWrapper` (used by `get_nnunet_monai_predictor()`) build the network from a bundle's `plans.json`. nnUNet resolves `network_class_name`, and every keyword argument listed in `_kw_requires_import`, by dotted name with `pydoc.locate` and then calls them. Nothing restricted which names a plans file could reference. This PR adds a check that runs before nnUNet is called: - `network_class_name` and each imported keyword argument must sit under an allowed module prefix, by default `DEFAULT_NNUNET_NETWORK_MODULES = ("dynamic_network_architectures", "torch.nn")`. The prefix is checked before anything is imported. - If the name can be imported, it must be a `torch.nn.Module` subclass. Names that can't be imported are left to nnUNet's existing fallback, which only searches inside `dynamic_network_architectures`. - A new `allowed_network_modules` argument on `get_network_from_nnunet_plans()`, `get_nnunet_monai_predictor()` and `ModelnnUNetWrapper` lets users load a trusted custom architecture. Standard nnUNet plans (`PlainConvUNet`, `ResidualEncoderUNet`, with `torch.nn` conv, norm, dropout and nonlinearity ops) load as before. ### Tests - New `tests/apps/nnunet/test_nnunet_bundle_network_spec.py`: - unit tests for the check: accepted defaults, rejected classes and imported arguments, names under an allowed prefix that are not `Module` classes, and custom allowed modules; - with `nnunetv2` installed, end-to-end tests of `get_network_from_nnunet_plans()` that build a real `PlainConvUNet` and check that a disallowed class or imported argument is rejected without being called. - Run locally with nnunetv2 2.8.1 / torch 2.15 CPU: 10 passed. Without nnunetv2, the end-to-end class is skipped. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New tests added to cover the changes. - [x] Documentation updated. --------- Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com>
…9233) ### Description `algo_from_json()` used to build a small `ConfigParser` config from the file's `_target_` and `_state_.template_path`, then call `get_parsed_content()`. As a result, a saved state string was treated as config syntax (`$` expressions, `@` references, `%` macros) instead of as a plain value. This change: - has `_reject_non_algo_target()` return the resolved `Algo` subclass, and - has `algo_from_json()` construct that class directly with the saved `template_path`, so values read from the file are always passed as literal data. Behaviour for normal files does not change. `load_state_dict()` and the `template_path` / `output_path` overrides that run afterwards are untouched. ### Tests - New `TestAlgoFromJsonStateLiterals` in `tests/auto3dseg/test_json_serialization.py`. It checks that state values starting with `$`, `@` or `%` reach the constructor verbatim and are never evaluated, and that a plain state round-trips. - `tests/auto3dseg/` passes locally (21 passed). ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New tests added to cover the changes. --------- Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
The NNI trialCommand was built by concatenating paths into one string and reached the platform shell unquoted, so a work dir containing spaces or metacharacters was not passed through as written. `_nni_trial_command()` now builds it from an argument list and quotes each path for the shell NNI uses (POSIX quoting; on Windows double quotes for `cmd.exe`, rejecting what it interprets even inside quotes). The printed trialCommand hint quotes the object path the same way, falling back to the unquoted path with a warning when `cmd.exe` cannot carry it.
…NAI#9236) ### Description `BundleAlgo.export_to_disk(output_path, algo_name)` joined `algo_name` to `output_path` and then removed and recreated that directory. The name was never checked, so a name containing a path separator, `..`, or an absolute path, or an existing entry that is a symlink, could resolve somewhere other than a child of `output_path`. The Algo's `name` attribute, used as the template sub-directory to copy from, was not checked either. This PR: - requires `algo_name` to be a single directory name (no separators, no drive component, no NUL, not empty, `.` or `..`); - applies the same check to a non-empty Algo `name` (an empty name still copies the whole `template_path`, as before); - checks that the template folder exists, then resolves the destination with `realpath` and requires it to be inside `output_path`, before anything is removed or copied. All built-in callers (`BundleGen.generate`, `NNIGen`, `OptunaGen`) already pass single directory names, so their behaviour does not change. ### Tests - New `tests/auto3dseg/test_export_to_disk.py`. It covers a normal export, re-exporting over an existing directory, rejected names (`../x`, absolute paths, `a/b`, `.`, `..`, empty), a symlinked destination that points outside `output_path`, and a rejected Algo `name`. In every rejected case it also checks that the neighbouring directory is untouched. 9 of these fail on `dev`. - `tests/auto3dseg/` passes locally (24 passed). ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New tests added to cover the changes. --------- Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
Fixes Project-MONAI#4798. ### Description Adds ConvNeXt (`A ConvNet for the 2020s`) as a classification backbone. MONAI has no ConvNeXt today, even though `MedNeXt` derives from it — MedNeXt is a segmentation architecture, so the modern convolutional *backbone* is still missing alongside `resnet`/`densenet`/`senet`/`efficientnet`. This follows the direction @wyli gave on the issue: > I think from monai's perspective we can add value to it by supporting both 2d and 3d, and making use of existing monai network layers/blocks whenever possible. **Supports 1D, 2D and 3D.** The reference implementation is 2D-only; every layer here is built through MONAI's dimension-agnostic factories, so volumetric inputs work with the same code path. The reference applies the pointwise convolutions as `Linear` layers on a permuted channels-last tensor; using 1x1 convolutions instead is mathematically equivalent and keeps the block agnostic to the number of spatial dimensions. **Reuses existing MONAI layers** rather than re-implementing them: - `Conv` / `Pool` factories for all dimension-agnostic ops - `DropPath` (`monai.networks.layers`) for stochastic depth — it is already shape-agnostic, and the `DropPath(p) if p > 0 else nn.Identity()` guard and `torch.linspace` schedule follow `swin_unetr.py` - `get_act_layer` for the `act` argument - `trunc_normal_` for the reference's `std=0.02` initialisation The structure follows `densenet.py` (the `features` / `class_layers` split, `spatial_dims, in_channels, out_channels` ordering, `OrderedDict` module naming, real subclasses for variants, alias block). ### `LayerNormNd` ConvNeXt normalises over the channel dimension of a channels-first feature map. `torch.nn.LayerNorm` normalises over the *trailing* dimensions, so it expects channels-last and cannot be used directly, and MONAI has no channels-first equivalent (`Norm.LAYER` is a plain `nn.LayerNorm` alias). So this PR adds `LayerNormNd`, which normalises dimension 1 of a `(batch, channel, *spatial)` tensor for any number of spatial dimensions. It computes the statistics explicitly over `dim=1` instead of permuting to channels-last, which keeps it uniform across ranks and TorchScript-friendly. A test asserts it equals `nn.LayerNorm` applied to the equivalent permuted tensor. I've kept it in `convnext.py` to keep this PR's surface small — happy to promote it to `monai/networks/layers/` as a public layer if you'd prefer, since nothing else in MONAI currently offers this. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New feature (non-breaking change which adds functionality). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [x] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Docs *are* updated (`docs/source/networks.rst`, following the `DenseNet` entry pattern — base class with `:members:`, variants without), but I haven't run `make html` locally, and I'm on Windows so I ran the test files directly with `unittest` and the linters individually rather than through `runtests.sh`. Details below — happy to run anything else you'd like to see. ### Testing New file `tests/networks/nets/test_convnext.py`, 26 tests, all passing (`python -m unittest tests.networks.nets.test_convnext`): - **Faithfulness to the reference.** The default variants match the parameter counts published by [facebookresearch/ConvNeXt](https://github.com/facebookresearch/ConvNeXt) exactly for ImageNet-1k (Tiny 28,589,128 / Small 50,223,688 / Base 88,591,464; Large 197,767,336 and XLarge 350,196,968 also verified locally). This checks the architecture is correct rather than merely shape-correct. - **Shapes** for 1D, 2D (non-cubic input) and 3D, over all five variants, plus a case with stochastic depth and layer scale disabled. - **`LayerNormNd` equivalence** to `nn.LayerNorm` on the permuted tensor, for 2D and 3D (max difference ~3e-07). - **TorchScript** via `test_script_save` for every case, including both the layer-scale and no-layer-scale paths. - **Drop path schedule** ascends linearly from 0 to `drop_path_rate` across the whole network. - **Ill-args** raise `ValueError`. Also ran `tests/networks/nets/test_densenet.py` and `test_mednext.py` (56 tests) to confirm the `nets/__init__.py` addition doesn't regress anything. Checks run on Windows with torch 2.13.0+cpu: `ruff check`, `black --check`, `isort --check-only` all clean on the changed files; `mypy monai/networks/nets/convnext.py` reports no errors for this file. (I ran the test files directly with `unittest` rather than the full `./runtests.sh`, which expects a POSIX shell.) ### Notes - **No `pretrained` support in this PR.** The reference 2D weights could be mapped onto this implementation in a follow-up (the 1x1-conv vs `Linear` pointwise weights differ only by a reshape), but that felt like a separate change; `MedNeXt` likewise ships without pretrained weights. - Every spatial dimension of the input should be divisible by 32 (patchify stem of 4 plus three downsamplings), which is documented in the class docstring. --------- Signed-off-by: VenkateswarluNagineni <venkates2002@tamu.edu> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
### Description Adds a root-level `AGENTS.md`, as requested in Project-MONAI#9199, scoped to just the repo-wide rules an AI coding assistant needs on every session: PRs target `dev`, every commit needs DCO sign-off, `./runtests.sh --autofix` is the formatter, optional dependencies go through `monai.utils.optional_import`, a new public API needs a `docs/source/*.rst` entry, and a slow test goes in `tests/min_tests.py`. It cross-references the AI/tool-assisted contribution policy merged in Project-MONAI#9106 rather than duplicating it — this file is about *how the codebase works*, not *whether AI assistance is allowed*. Per the discussion on Project-MONAI#9199, nested package-level `AGENTS.md` files (for `monai/transforms`, `monai/networks`, etc.) are left for a follow-up PR rather than bundled into this one. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change. - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally. - [ ] Quick tests passed locally. - [ ] In-line docstrings updated. - [ ] Documentation updated. (Docs-only change — no code paths touched, so the usual test/docs checkboxes above don't apply.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: arxh80 <archie.mullins1807@gmail.com> Signed-off-by: arxh80 <archie.mullins8@gmail.com> Co-authored-by: arxh80 <archie.mullins1807@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Rafael Garcia-Dias <rafaelagd@gmail.com>
### Description
The docstrings in `monai/networks/nets/quicknat.py` listed argument
names that don't match the function signatures, so copying them from the
docs raises a `TypeError`:
- `ConvConcatDenseBlock`: `in_channles` -> `in_channels`, `seLayer` ->
`se_layer`
- `Quicknat`: `stride_convolution` -> `stride_conv`, `droup_out` ->
`drop_out`
Also fixes a typo ("paramters"). Documentation only; no behaviour
change.
### Types of changes
- [x] Non-breaking change (fix or new feature that would not break
existing functionality).
- [x] In-line docstrings updated.
---------
Signed-off-by: Caitlin Leonard <157518754+caitlin-leonard@users.noreply.github.com>
## Summary Add a regression test for the public `NibabelWriter` path reported in Project-MONAI#9166. The reported `TypeError` is already resolved on `dev` by the general `spatial_resample` fix in Project-MONAI#9080. This PR adds direct coverage for the original writer-level scenario so the regression cannot return without failing this test. The new test writes a 2D channel-less array with `spatial_ndim=None`, `original_affine=None`, and no `spatial_shape`, then verifies the saved data and affine. ## Validation - On `e28a2c07^`, the new test fails with the reported `TypeError: '>=' not supported between instances of 'NoneType' and 'int'`. - On current `dev`, the new test passes. - `python -m pytest tests/data/test_nifti_rw.py -q`: 42 passed - `python -m pytest tests/transforms/test_spatial_resample.py -q`: 76 passed - `python -m pytest tests/data/test_image_rw.py -q`: 2 passed, 28 skipped in the local minimal environment - `ruff`, `black --check`, `isort --check-only`, and `git diff --check`: passed ## AI disclosure This contribution was prepared with Codex assistance and reviewed by the submitter, who will respond to review comments personally. Signed-off-by: peng xiang <78131130+px12811@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
) Related to Project-MONAI#9112. ### Description Fixes lazy resampling with the compiled extension (`BUILD_MONAI=1`) when `align_corners=True`, following @ericspod's confirmation that the native `grid_sample` path is the reference. In `spatial_resample`, the compiled branch builds `Affine(affine=xform, normalized=True, align_corners=True)` from a matrix that is already in voxel space. `AffineGrid` and the compiled `Resample` then apply the `align_corners=True` scalings (`d_out/(d_out-1)` and `(d_in-1)/d_in`) on top of it, distorting the grid passed to `grid_pull`. For non-integer `mode`, the matrix is now pre-compensated so those scalings cancel. Integer `mode` is unchanged. Verification (`BUILD_MONAI=1` unless noted): - `test_affine` + `test_affined`: 132 failed -> 0 failed. - Wider set (spacing, resize, zoom, rotate, rand_* transforms): 10 failed -> 0 failed. Default (non-compiled) run: 0 failed, unchanged. - Eager `Spacing` vs a plain `F.grid_sample(align_corners=True)` reference on three shapes: compiled was 0.2 to 0.5 off before, ~2e-7 after (native ~3e-8). - Reproduces on CPU too, so not GPU-specific. Test changes: - Added `test_lazy_rotate_align_corners_compiled` (skipped unless the extension is compiled). It fails without the fix and passes with it. - `_TEMPLATE_5_COMPILED` in `test_spacing.py` changed from 0.75 to 1.0. All sampled input coordinates for that case lie inside the image and the input is all ones, so 1.0 is the geometrically correct value. The native template (0.9553) looks like a unit-size-axis artifact and is untouched. Note: `BUILD_MONAI=1` must be set at runtime, not only at build time, for these tests to exercise the compiled path. Tested locally on RTX 3050 / WSL2 / PyTorch 2.14.1+cu132 (outside CI's range); not run on PyTorch 2.8 to 2.11 or through the full `./runtests.sh`. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Vikas Reddy <vikasreddy1090@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
Signed-off-by: Vikas Reddy <vikasreddy1090@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related to Project-MONAI#9112.
Description
The source distribution built from the current
MANIFEST.incontains none of the 38 C++/CUDA source files undermonai/csrc.get_extensions()insetup.pyreturns an empty list when it finds no sources, so runningBUILD_MONAI=1 python setup.py build_exton that sdist finishes without an error, without buildingmonai._C, and without producingmonai/_C.so.This adds one line to
MANIFEST.in:Verification (RTX 3050, CUDA 13.2, PyTorch 2.14.1, Python 3.12, setuptools 84.2.1):
monai/csrcin the sdist.BUILD_MONAI=1 python setup.py build_ext --inplaceon the extracted sdist compiles nothing and produces nomonai/_C.so.monai/_C.so.Not covered: the
BuildBackendWarningmessages aboutmonai.csrcandmonai._extensions.gmmseen in the Project-MONAI#9112 CI logs. I could not reproduce them locally ondev, with or without this change, so I don't know whether they are related.Types of changes
./runtests.sh -f -u --net --coverage../runtests.sh --quick --unittests --disttests.make htmlcommand in thedocs/folder.