diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index e371dac76e4..a39ebba0d90 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - opt: ["codeformat", "mypy"] # "pytype" omitted for being essentially deprecated, see #8865 + opt: ["codeformat", "pyrefly"] steps: - name: Clean unused tools run: | @@ -80,8 +80,7 @@ jobs: run: | # clean up temporary files $(pwd)/runtests.sh --build --clean - # Github actions have multiple cores, so parallelize pytype - $(pwd)/runtests.sh --build --${{ matrix.opt }} -j $(nproc --all) + $(pwd)/runtests.sh --build --${{ matrix.opt }} min-dep: # Test with minumum dependencies installed for different OS, Python, and PyTorch combinations runs-on: ${{ matrix.os }} diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 8ff912f5281..1f4a77f34f4 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -214,7 +214,7 @@ jobs: python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" python -c 'import torch; print(torch.rand(5,3, device=torch.device("cuda:0")))' ngc --version - BUILD_MONAI=1 ./runtests.sh --build --coverage --unittests --disttests # unit tests with pytype checks, coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --pyrefly --unittests --disttests # unit tests with pyrefly checks, coverage report BUILD_MONAI=1 ./runtests.sh --build --coverage --net # integration tests with coverage report coverage xml --ignore-errors if pgrep python; then pkill python; fi diff --git a/.github/workflows/weekly-preview.yml b/.github/workflows/weekly-preview.yml index 7e7349ec931..4f74ff6bd8e 100644 --- a/.github/workflows/weekly-preview.yml +++ b/.github/workflows/weekly-preview.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - opt: ["codeformat", "mypy"] + opt: ["codeformat", "pyrefly"] steps: - name: Clean unused tools run: | diff --git a/.gitignore b/.gitignore index 76c6ab0d124..528dbabc6f3 100644 --- a/.gitignore +++ b/.gitignore @@ -110,12 +110,12 @@ venv.bak/ # pytype cache .pytype/ -# mypy -.mypy_cache/ +# pyrefly cache +.pyrefly_cache/ + examples/scd_lvsegs.npz temp/ .idea/ -.dmypy.json *~ diff --git a/.plans/PR_reviews/8940/comments.md b/.plans/PR_reviews/8940/comments.md new file mode 100644 index 00000000000..952739a406b --- /dev/null +++ b/.plans/PR_reviews/8940/comments.md @@ -0,0 +1,15 @@ +# PR Review Comments (Copy/Paste Ready) + +## Comment 1 +- **File:** `tests/data/test_persistentdataset.py` +- **Line:** `216` +- **Severity:** Minor +- **Comment:** Leftover commented-out line from refactoring. The old `os.path.join` approach is replaced by `Path` usage on line 217, so this comment is dead code. +- **Suggested change:** Remove line 216. + +## Comment 2 +- **File:** `monai/data/dataset.py` +- **Line:** `387` +- **Severity:** Minor +- **Comment:** The local variable is named `data_item_md5` but the hash function now uses sha256 (changed in `utils.py`). This name is misleading — a future reader might assume md5 is still in use. Same issue on line 1621 for `CacheNTransDataset`. +- **Suggested change:** Rename `data_item_md5` to `data_item_hash` in both locations (lines 387-389 and 1621-1623). diff --git a/.plans/PR_reviews/8940/report.md b/.plans/PR_reviews/8940/report.md new file mode 100644 index 00000000000..2dc93ad9390 --- /dev/null +++ b/.plans/PR_reviews/8940/report.md @@ -0,0 +1,51 @@ +# PR Review Report — #8940 + +## 1) PR Summary +Allows `PersistentDataset` to cache `MetaTensor` objects with `weights_only=True` by leveraging MONAI's existing `torch.serialization.add_safe_globals([MetaTensor, ...])` registration. Switches cache-key hashing from md5 to sha256. Addresses GHSA-636w-j999-g7x5. + +## 2) Template Compliance +- [x] Description matches implemented changes +- [x] Linked issue(s) are relevant — security advisory linked +- [x] Checklist claims match actual changes — new tests added, docstrings updated +- [ ] Type of change label is accurate — marked "Non-breaking" but see notes below + +### Notes +- Marked "Non-breaking change" but the hash-algorithm change (md5 → sha256) invalidates all existing cache files. Old cache files won't be found (hash mismatch), forcing full recomputation and leaving orphaned .pt files on disk. Functionally the API is non-breaking, but users with large pre-built caches will experience an unexpected performance regression. Consider calling this out in a migration note or release changelog. + +## 3) Findings by Severity + +### Critical +- None. + +### Major +- **Title:** Hash algorithm change invalidates all existing persistent caches +- **Severity:** Major +- **Evidence:** `monai/data/utils.py:1366-1383` (both `json_hashing` and `pickle_hashing`) +- **Why it matters:** Cache filenames are derived from hash output. After upgrading, none of the existing cache files will match the new sha256 hashes. All data will be recomputed, and old .pt files become orphaned on disk. For users with large cached datasets, this is a significant performance regression and disk-waste concern with no warning. +- **Suggested fix:** Document prominently in the PR description / changelog. Consider adding a one-time migration helper or a deprecation cycle (accept both hash formats for one release). At minimum, warn users to manually clear their cache directories after upgrading. + +### Minor +- **Title:** Dead code comment in new test +- **Severity:** Minor +- **Evidence:** `tests/data/test_persistentdataset.py:216` +- **Why it matters:** The commented-out line `# cache_dir = os.path.join(os.path.join(tempdir, "cache"), "data")` is leftover from refactoring to `Path`. It's noise. +- **Suggested fix:** Remove the commented-out line. + +- **Title:** Misleading variable name `data_item_md5` persists +- **Severity:** Minor +- **Evidence:** `monai/data/dataset.py:387-389` +- **Why it matters:** After the sha256 switch, the variable name `data_item_md5` is misleading. While pre-existing to this PR, the algorithm change makes this name actively confusing for future readers. +- **Suggested fix:** Rename to `data_item_hash` or similar. Same for the duplicate at line 1621. + +## 4) Testing Assessment +- **Existing tests:** `test_track_meta_and_weights_only` updated — TEST_CASE_5 now expects `MetaTensor` instead of `ValueError`. Covers the new valid combination. +- **Missing tests:** No test for cache-key collision across hash algorithm change (would require a migration scenario). No test verifying old md5-named cache files are handled gracefully (they're silently ignored — is that the intended behavior?). +- **Confidence:** Medium — core logic (MetaTensor + weights_only) is well tested. Cache migration behavior is untested. + +## 5) Needs Author Clarification (if any) +- Was the silent invalidation of all existing cache files intentional, or should there be a fallback/compatibility path? The PR description calls this "non-breaking" but the behavioral impact on cached datasets is material. + +## 6) Verdict +**Verdict:** Approve with comments + +The core change is sound: removing the artificial `track_meta=True` + `weights_only=True` restriction is correct since MetaTensor is registered as a safe global. Tests are thorough and cover both happy path and unsafe-rejection scenarios. The hash algorithm switch to sha256 is a security improvement. Main concern is the undocumented cache-invalidation impact — this should be communicated to users in release notes. diff --git a/.plans/PR_reviews/feat-ignore-index-support/comments.md b/.plans/PR_reviews/feat-ignore-index-support/comments.md new file mode 100644 index 00000000000..48222d1871e --- /dev/null +++ b/.plans/PR_reviews/feat-ignore-index-support/comments.md @@ -0,0 +1,36 @@ +# PR Review Comments (Copy/Paste Ready) + +## Comment 1 +- **File:** `monai/metrics/meandice.py` +- **Line:** `437` +- **Severity:** Major +- **Comment:** The `and not self.per_component` condition was removed from the `first_ch` assignment. In `per_component=True` + `include_background=True` mode, the old code correctly skipped channel 0 (background has no connected components to analyze). The new code includes it, which changes Dice scores for per_component users. +- **Suggested change:** Restore the condition: `first_ch = 0 if self.include_background and not self.per_component else 1` + +## Comment 2 +- **File:** `monai/metrics/__init__.py` +- **Line:** `45` +- **Severity:** Major +- **Comment:** `create_ignore_mask` is added to `__all__` in `monai/metrics/utils.py` but not imported here. Since losses modules import it from `monai.metrics.utils`, it should be publicly available. External code that needs the same masking logic has no supported import path. +- **Suggested change:** Add `create_ignore_mask` to the import from `.utils` on this line. + +## Comment 3 +- **File:** `monai/losses/utils.py` +- **Line:** `76` +- **Severity:** Minor +- **Comment:** The docstring `"""Apply ignore_index masking to loss inputs."""` is too minimal for a utility function used by four loss classes. Please document the parameters, return type, and contract (e.g., what happens when mask and ignore_index are both None vs one set). +- **Suggested change:** Add Google-style Args/Returns docstring. + +## Comment 4 +- **File:** `tests/losses/test_ignore_index_losses.py` +- **Line:** `101` +- **Severity:** Minor +- **Comment:** `to_onehot_y=True` is passed explicitly here, but some test case kwargs in `SENTINEL_ONEHOT_TEST_CASES` already include it. This could cause a `TypeError` if a loss class rejects duplicate keyword arguments in the future. +- **Suggested change:** Remove the explicit `to_onehot_y=True` and rely on the kwargs dict, or ensure kwargs never carry `to_onehot_y`. + +## Comment 5 +- **File:** `monai/losses/unified_focal_loss.py` +- **Line:** `250` +- **Severity:** Minor +- **Comment:** The shape validation logic in `AsymmetricUnifiedFocalLoss.forward` grew from ~5 lines to ~30+ lines of conditionals. This is now significantly harder to audit for correctness. Consider extracting the validation into a private `_validate_and_prepare_inputs` helper to keep `forward` focused on the loss computation. +- **Suggested change:** Extract shape validation to a private method; add focused tests for the new branches (binary-to-2-channel, sentinel ignore_index before one_hot, mismatch scenarios). diff --git a/.plans/PR_reviews/feat-ignore-index-support/report.md b/.plans/PR_reviews/feat-ignore-index-support/report.md new file mode 100644 index 00000000000..c46e8ae1c3d --- /dev/null +++ b/.plans/PR_reviews/feat-ignore-index-support/report.md @@ -0,0 +1,82 @@ +# PR Review Report — `feat-ignore-index-support` + +## 1) PR Summary +Adds an `ignore_index` parameter to segmentation losses (DiceLoss, FocalLoss, TverskyLoss, AsymmetricUnifiedFocalLoss) and metrics (DiceMetric, MeanIoU, GeneralizedDiceScore, HausdorffDistanceMetric, SurfaceDiceMetric, SurfaceDistanceMetric, ConfusionMatrixMetric). A centralized `create_ignore_mask` helper in `monai/metrics/utils.py` generates spatial masks for label-encoded and one-hot targets, while `mask_loss_inputs` in `monai/losses/utils.py` applies them. + +**Note:** No PR has been opened — this review is against branch `feat-ignore-index-support` vs `upstream/dev`. + +## 2) Template Compliance +- [x] No PR opened yet — template compliance N/A (review is pre-submission) + +### Notes +- No PR body to verify claims against. + +## 3) Findings by Severity + +### Critical +- None. + +### Major + +- **Title:** `DiceHelper.__call__` `first_ch` logic change alters per_component behavior +- **Severity:** Major +- **Evidence:** `monai/metrics/meandice.py:437` — line changed from `first_ch = 0 if self.include_background and not self.per_component else 1` to `first_ch = 0 if self.include_background else 1`. +- **Why it matters:** In `per_component=True` + `include_background=True` mode, the old code intentionally skipped channel 0 (background) and only computed Dice on the foreground channel. The new code includes channel 0, which is semantically wrong for per_component mode (the background channel has no connected components to analyze) and changes output values for existing users. +- **Suggested fix:** Restore the `and not self.per_component` condition: `first_ch = 0 if self.include_background and not self.per_component else 1`. + +- **Title:** Cross-package internal import: losses importing from `monai.metrics.utils` +- **Severity:** Major +- **Evidence:** Four loss files import `create_ignore_mask` from `monai.metrics.utils`: `monai/losses/dice.py:26`, `monai/losses/focal_loss.py:22`, `monai/losses/tversky.py:21`, `monai/losses/unified_focal_loss.py:20`. Additionally, `monai/losses/utils.py:17` imports from the same metrics module. +- **Why it matters:** Losses and metrics are sibling packages. Having losses depend on metrics internals at the module level creates a soft import cycle (metrics already import from losses for `LossMetric`). This is architecturally fragile and against the principle that utility layers should not depend on their peers. +- **Suggested fix:** Move `create_ignore_mask` to a shared utility module (e.g., `monai/utils/` or a new `monai/losses/utils.py` copy) or accept the cycle but document it clearly. + +- **Title:** `create_ignore_mask` not publicly exported from `monai.metrics` package +- **Severity:** Major +- **Evidence:** `monai/metrics/utils.py:49` adds `"create_ignore_mask"` to `__all__`, but `monai/metrics/__init__.py` does not import it (line 45 imports other utils but omits `create_ignore_mask`). The loss modules import it via the internal path `monai.metrics.utils.create_ignore_mask`, bypassing the package's public API. +- **Why it matters:** Users cannot `from monai.metrics import create_ignore_mask`. The function is effectively private yet used as a cross-package dependency. This inconsistency means external code that needs the same masking logic has no supported import path. +- **Suggested fix:** Add `create_ignore_mask` to the `monai/metrics/__init__.py` imports (line 45), or move it to a shared location and export from there. + +### Minor + +- **Title:** `mask_loss_inputs` docstring is too minimal +- **Severity:** Minor +- **Evidence:** `monai/losses/utils.py:76` — the docstring is a single line: `"""Apply ignore_index masking to loss inputs."""` +- **Why it matters:** This is a public utility function used by multiple loss classes. It should document its parameters, return type, and contract. +- **Suggested fix:** Add full Args/Returns docstring following Google style used throughout the codebase. + +- **Title:** Duplicate `to_onehot_y=True` in test parameterization +- **Severity:** Minor +- **Evidence:** `tests/losses/test_ignore_index_losses.py:98` — `SENTINEL_ONEHOT_TEST_CASES` already includes `kwargs` that may contain `to_onehot_y`, but line 101 adds `to_onehot_y=True` again. +- **Why it matters:** This could cause subtle test failures if a future loss class rejects duplicate keyword arguments. Currently benign for `dict.update()` but fragile. +- **Suggested fix:** Remove the redundant explicit `to_onehot_y=True` from line 101 and rely on kwargs. + +- **Title:** `AsymmetricUnifiedFocalLoss.forward` shape validation rewrite is complex +- **Severity:** Minor +- **Evidence:** `monai/losses/unified_focal_loss.py:250-286` — the shape validation logic has been extensively rewritten, adding ~30 lines of conditional checks for `to_onehot_y`, `ignore_index`, binary-to-2-channel conversion, and sentinel value handling. +- **Why it matters:** The original code had a simple `torch.max(y_true) != self.num_classes - 1` check. The new logic has many branching paths that are harder to reason about. A regression in the shape validation path could silently pass incorrect inputs. +- **Suggested fix:** Consider extracting the shape validation into a private helper method. Add test cases specifically for the new shape validation branches. + +- **Title:** `get_surface_distance` type narrowing on seg_pred introduces dtype coupling +- **Severity:** Minor +- **Evidence:** `monai/metrics/utils.py:345-349` — new `if isinstance(seg_pred, torch.Tensor)` / `else` branch replaces a single generic `dis[seg_pred]` call. +- **Why it matters:** The old code relied on duck-typing (indexing worked for both torch and numpy). The new code bakes in a torch/numpy split. For cupy inputs this may break since they're not handled by the else branch. +- **Suggested fix:** Test with cupy inputs or add a cupy branch using `cupy.asarray(seg_pred).astype(bool)`. + +## 4) Testing Assessment +- **Existing tests:** Two new test files (`test_ignore_index_losses.py`, `test_ignore_index_metrics.py`) cover ignore_index consistency, no-ignore behavior, class-index masking, and sentinel one-hot masking. Good coverage of the ignore_index feature paths. +- **Missing tests:** No tests for: + - `DiceHelper` per_component mode with `ignore_index` + - Shape validation edge cases in `AsymmetricUnifiedFocalLoss` + - `get_edge_surface_distance` with `mask` and `warn_empty` parameters + - `use_subvoxels=True` path with the new areas handling in `get_edge_surface_distance` + - `compute_hausdorff_distance` with `ignore_index` matching a class index (NaN path) +- **Confidence:** Medium — the ignore_index core path is well tested, but the per_component regression and `get_edge_surface_distance` changes lack coverage. + +## 5) Needs Author Clarification (if any) +- Was the removal of `and not self.per_component` from the `first_ch` assignment in `DiceHelper.__call__` intentional? If not, this is a regression. +- Is the losses → metrics import direction acceptable to maintainers, or should `create_ignore_mask` be extracted to a shared utility module? + +## 6) Verdict +**Verdict:** Request Changes + +The `first_ch` logic change in `DiceHelper.__call__` is likely a regression that silently alters Dice scores in per_component mode. The cross-package import direction and missing public export of `create_ignore_mask` should be resolved before merge. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ad171abb1b..a8db8db0a43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ Please note that, as per PyTorch, MONAI uses American English spelling. This mea ### Preparing pull requests To ensure the code quality, MONAI relies on several linting tools ([black](https://github.com/psf/black), [isort](https://github.com/timothycrosley/isort), [ruff](https://github.com/astral-sh/ruff)), -static type analysis tools ([mypy](https://github.com/python/mypy), [pytype](https://github.com/google/pytype)), as well as a set of unit/integration tests. +static type analysis tools ([pyrefly](https://github.com/facebook/pyrefly)), as well as a set of unit/integration tests. This section highlights all the necessary preparation steps required before sending a pull request. To collaborate efficiently, please read through this section and follow them. diff --git a/monai/apps/auto3dseg/auto_runner.py b/monai/apps/auto3dseg/auto_runner.py index 8421893f514..ad09c24e609 100644 --- a/monai/apps/auto3dseg/auto_runner.py +++ b/monai/apps/auto3dseg/auto_runner.py @@ -405,6 +405,7 @@ def inspect_datalist_folds(self, datalist_filename: str) -> int: datalist = ConfigParser.load_config_file(datalist_filename) if "training" not in datalist: + # pyrefly: ignore [unnecessary-type-conversion] raise ValueError("Datalist files has no training key:" + str(datalist_filename)) fold_list = [int(d["fold"]) for d in datalist["training"] if "fold" in d] @@ -790,6 +791,7 @@ def _train_algo_in_nni(self, history: list[dict[str, Any]]) -> None: nni_config_filename = os.path.abspath(os.path.join(self.work_dir, f"{name}_nni_config.yaml")) ConfigParser.export_config_file(nni_config, nni_config_filename, fmt="yaml", default_flow_style=None) + # pyrefly: ignore [redundant-cast] max_trial = min(self.hpo_tasks, cast(int, default_nni_config["maxTrialNumber"])) cmd = "nnictl create --config " + nni_config_filename + " --port 8088" @@ -805,6 +807,7 @@ def _train_algo_in_nni(self, history: list[dict[str, Any]]) -> None: n_trainings = len(import_bundle_algo_history(self.work_dir, only_trained=True)) cmd = "nnictl stop --all" + # pyrefly: ignore [bad-argument-type] run_cmd(cmd.split(), check=True) logger.info(f"NNI completes HPO on {name}") last_total_tasks = n_trainings diff --git a/monai/apps/auto3dseg/bundle_gen.py b/monai/apps/auto3dseg/bundle_gen.py index 75da66d43c2..a210832831e 100644 --- a/monai/apps/auto3dseg/bundle_gen.py +++ b/monai/apps/auto3dseg/bundle_gen.py @@ -344,6 +344,7 @@ def infer(self, image_file): config_dir = os.path.join(self.output_path, "configs") configs_path = [os.path.join(config_dir, f) for f in os.listdir(config_dir)] + # pyrefly: ignore [implicit-import] spec = importlib.util.spec_from_file_location("InferClass", infer_py) infer_class = importlib.util.module_from_spec(spec) # type: ignore sys.modules["InferClass"] = infer_class diff --git a/monai/apps/auto3dseg/ensemble_builder.py b/monai/apps/auto3dseg/ensemble_builder.py index eaf1f14c7ff..cab9995eca8 100644 --- a/monai/apps/auto3dseg/ensemble_builder.py +++ b/monai/apps/auto3dseg/ensemble_builder.py @@ -337,6 +337,7 @@ def __init__(self, history: Sequence[dict[str, Any]], data_src_cfg_name: str | N self.ensemble: AlgoEnsemble self.data_src_cfg = ConfigParser(globals=False) + # pyrefly: ignore [unnecessary-type-conversion] if data_src_cfg_name is not None and os.path.exists(str(data_src_cfg_name)): self.data_src_cfg.read_config(data_src_cfg_name) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index d2f89d2eea7..d15b2bec3cf 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -434,6 +434,7 @@ def _randomize(self, d, key_label): else: logger.info(f"Not slice IDs for label: {key_label}") sid = None + # pyrefly: ignore [unsupported-operation] self.sid[key_label] = sid def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.ndarray]: @@ -561,6 +562,7 @@ def __init__( self.guidance: dict[str, list[list[int]]] = {} def randomize(self, data=None): + # pyrefly: ignore [unsupported-operation] probability = data[self.probability] self._will_interact = self.R.choice([True, False], p=[probability, 1.0 - probability]) @@ -885,6 +887,7 @@ def _randomize(self, d, key_label): else: logger.info(f"Not slice IDs for label: {key_label}") sid = None + # pyrefly: ignore [unsupported-operation] self.sid[key_label] = sid def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.ndarray]: diff --git a/monai/apps/deepgrow/dataset.py b/monai/apps/deepgrow/dataset.py index e597188e745..0d1c11b1193 100644 --- a/monai/apps/deepgrow/dataset.py +++ b/monai/apps/deepgrow/dataset.py @@ -175,6 +175,7 @@ def _save_data_2d(vol_idx, vol_image, vol_label, dataset_dir, relative_path): continue # For all Labels + # pyrefly: ignore [missing-attribute] unique_labels = np.unique(label.flatten()) unique_labels = unique_labels[unique_labels != 0] unique_labels_count = max(unique_labels_count, len(unique_labels)) diff --git a/monai/apps/deepgrow/transforms.py b/monai/apps/deepgrow/transforms.py index d92a79a16a5..624eed342da 100644 --- a/monai/apps/deepgrow/transforms.py +++ b/monai/apps/deepgrow/transforms.py @@ -288,6 +288,7 @@ def __init__(self, guidance: str = "guidance", discrepancy: str = "discrepancy", self._will_interact = None def randomize(self, data=None): + # pyrefly: ignore [unsupported-operation] probability = data[self.probability] self._will_interact = self.R.choice([True, False], p=[probability, 1.0 - probability]) diff --git a/monai/apps/detection/networks/retinanet_detector.py b/monai/apps/detection/networks/retinanet_detector.py index 95b29b8285c..9b9bf269113 100644 --- a/monai/apps/detection/networks/retinanet_detector.py +++ b/monai/apps/detection/networks/retinanet_detector.py @@ -525,6 +525,7 @@ def forward( ) # 4. Generate anchors and store it in self.anchors: List[Tensor] + # pyrefly: ignore [bad-argument-type] self.generate_anchors(images, head_outputs) # num_anchor_locs_per_level: List[int], list of HW or HWD for each level num_anchor_locs_per_level = [x.shape[2:].numel() for x in head_outputs[self.cls_key]] @@ -535,6 +536,7 @@ def forward( # reshape to Tensor sized(B, sum(HWA), self.num_classes) for self.cls_key # or (B, sum(HWA), 2* self.spatial_dims) for self.box_reg_key # A = self.num_anchors_per_loc + # pyrefly: ignore [bad-argument-type] head_outputs[key] = self._reshape_maps(head_outputs[key]) # 6(1). If during training, return losses diff --git a/monai/apps/detection/transforms/array.py b/monai/apps/detection/transforms/array.py index 301a636b6cc..635506c08ab 100644 --- a/monai/apps/detection/transforms/array.py +++ b/monai/apps/detection/transforms/array.py @@ -257,10 +257,14 @@ def __call__(self, boxes: NdarrayTensor, src_spatial_size: Sequence[int] | int | diff = od - zd half = abs(diff) // 2 if diff > 0: # need padding (half, diff - half) + # pyrefly: ignore [bad-index, unsupported-operation] zoomed_boxes[:, axis] = zoomed_boxes[:, axis] + half + # pyrefly: ignore [bad-index, unsupported-operation] zoomed_boxes[:, axis + spatial_dims] = zoomed_boxes[:, axis + spatial_dims] + half elif diff < 0: # need slicing (half, half + od) + # pyrefly: ignore [bad-index, unsupported-operation] zoomed_boxes[:, axis] = zoomed_boxes[:, axis] - half + # pyrefly: ignore [bad-index, unsupported-operation] zoomed_boxes[:, axis + spatial_dims] = zoomed_boxes[:, axis + spatial_dims] - half return zoomed_boxes diff --git a/monai/apps/detection/transforms/box_ops.py b/monai/apps/detection/transforms/box_ops.py index fa714daad12..54bbc8fd31b 100644 --- a/monai/apps/detection/transforms/box_ops.py +++ b/monai/apps/detection/transforms/box_ops.py @@ -186,7 +186,9 @@ def flip_boxes( _flip_boxes: NdarrayTensor = boxes.clone() if isinstance(boxes, torch.Tensor) else deepcopy(boxes) # type: ignore[assignment] for axis in flip_axes: + # pyrefly: ignore [bad-index, unsupported-operation] _flip_boxes[:, axis + spatial_dims] = spatial_size[axis] - boxes[:, axis] - TO_REMOVE + # pyrefly: ignore [bad-index, unsupported-operation] _flip_boxes[:, axis] = spatial_size[axis] - boxes[:, axis + spatial_dims] - TO_REMOVE return _flip_boxes diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index fcb6b842d78..81226cc3bd8 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -1200,6 +1200,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, cropper = SpatialCrop(roi_center=tuple(center), roi_size=self.spatial_size) crop_start = [max(s.start, 0) for s in cropper.slices] crop_end = [min(s.stop, image_size_a) for s, image_size_a in zip(cropper.slices, image_size)] + # pyrefly: ignore [unnecessary-type-conversion] crop_slices = [slice(int(s), int(e)) for s, e in zip(crop_start, crop_end)] # crop images diff --git a/monai/apps/detection/utils/hard_negative_sampler.py b/monai/apps/detection/utils/hard_negative_sampler.py index 4c8dcf5d458..3911b207a25 100644 --- a/monai/apps/detection/utils/hard_negative_sampler.py +++ b/monai/apps/detection/utils/hard_negative_sampler.py @@ -273,6 +273,7 @@ def get_num_neg(self, negative: torch.Tensor, num_pos: int) -> int: number of negative samples """ # always assume at least one pos sample was sampled + # pyrefly: ignore [unnecessary-type-conversion] num_neg = int(max(1, num_pos) * abs(1 - 1.0 / float(self.positive_fraction))) # protect against not enough negative examples and sample at least self.min_neg if possible num_neg = min(negative.numel(), max(num_neg, self.min_neg)) diff --git a/monai/apps/generation/maisi/networks/autoencoderkl_maisi.py b/monai/apps/generation/maisi/networks/autoencoderkl_maisi.py index 39f84592246..90ffff6c340 100644 --- a/monai/apps/generation/maisi/networks/autoencoderkl_maisi.py +++ b/monai/apps/generation/maisi/networks/autoencoderkl_maisi.py @@ -246,7 +246,9 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: # update padding length if necessary padding = 3 + # pyrefly: ignore [unsupported-operation] if padding % self.stride > 0: + # pyrefly: ignore [unsupported-operation] padding = (padding // self.stride + 1) * self.stride if self.print_info: logger.info(f"Padding size: {padding}") diff --git a/monai/apps/nnunet/nnunetv2_runner.py b/monai/apps/nnunet/nnunetv2_runner.py index 5d5c82801ac..2fb45a269a4 100644 --- a/monai/apps/nnunet/nnunetv2_runner.py +++ b/monai/apps/nnunet/nnunetv2_runner.py @@ -31,6 +31,7 @@ tqdm, has_tqdm = optional_import("tqdm", name="tqdm") nib, _ = optional_import("nibabel") +# pyrefly: ignore [implicit-import] logger = monai.apps.utils.get_logger(__name__) __all__ = ["nnUNetV2Runner"] @@ -274,6 +275,7 @@ def convert_dataset(self): modality = [modality] create_new_dataset_json( + # pyrefly: ignore [bad-argument-type] modality=modality, num_foreground_classes=num_foreground_classes, num_input_channels=num_input_channels, diff --git a/monai/apps/nnunet/utils.py b/monai/apps/nnunet/utils.py index c5102357f99..fec75370a01 100644 --- a/monai/apps/nnunet/utils.py +++ b/monai/apps/nnunet/utils.py @@ -23,6 +23,7 @@ tqdm, has_tqdm = optional_import("tqdm", name="tqdm") nib, _ = optional_import("nibabel") +# pyrefly: ignore [implicit-import] logger = monai.apps.utils.get_logger(__name__) __all__ = ["analyze_data", "create_new_data_copy", "create_new_dataset_json", "NNUNETMode"] @@ -43,6 +44,7 @@ def analyze_data(datalist_json: dict, data_dir: str) -> tuple[int, int]: datalist_json: original data list .json (required by most monai tutorials). data_dir: raw data directory. """ + # pyrefly: ignore [implicit-import] img = monai.transforms.LoadImage(image_only=True, ensure_channel_first=True, simple_keys=True)( os.path.join(data_dir, datalist_json["training"][0]["image"]) ) @@ -51,6 +53,7 @@ def analyze_data(datalist_json: dict, data_dir: str) -> tuple[int, int]: num_foreground_classes = 0 for _i in range(len(datalist_json["training"])): + # pyrefly: ignore [implicit-import] seg = monai.transforms.LoadImage(image_only=True, ensure_channel_first=True, simple_keys=True)( os.path.join(data_dir, datalist_json["training"][_i]["label"]) ) @@ -93,6 +96,7 @@ def create_new_data_copy( _index += 1 # copy image + # pyrefly: ignore [implicit-import] nda = monai.transforms.LoadImage(image_only=True, ensure_channel_first=True, simple_keys=True)( os.path.join(data_dir, orig_img_name) ) @@ -105,6 +109,7 @@ def create_new_data_copy( # copy label if isinstance(datalist_json[_key][_k], dict) and "label" in datalist_json[_key][_k]: + # pyrefly: ignore [implicit-import] nda = monai.transforms.LoadImage(image_only=True, ensure_channel_first=True, simple_keys=True)( os.path.join(data_dir, datalist_json[_key][_k]["label"]) ) diff --git a/monai/apps/nuclick/transforms.py b/monai/apps/nuclick/transforms.py index 6b6542308a7..0fb4457de24 100644 --- a/monai/apps/nuclick/transforms.py +++ b/monai/apps/nuclick/transforms.py @@ -115,7 +115,9 @@ def bbox(self, patch_size, centroid, size): x, y = centroid m, n = size + # pyrefly: ignore [unnecessary-type-conversion] x_start = int(max(x - patch_size / 2, 0)) + # pyrefly: ignore [unnecessary-type-conversion] y_start = int(max(y - patch_size / 2, 0)) x_end = x_start + patch_size y_end = y_start + patch_size diff --git a/monai/apps/pathology/transforms/post/dictionary.py b/monai/apps/pathology/transforms/post/dictionary.py index 1e2540daee0..b1dde44ec1f 100644 --- a/monai/apps/pathology/transforms/post/dictionary.py +++ b/monai/apps/pathology/transforms/post/dictionary.py @@ -403,6 +403,7 @@ def __call__(self, data): d = dict(data) for key in self.key_iterator(d): offset = d[self.offset_key] if self.offset_key else None + # pyrefly: ignore [bad-argument-type] centroid = self.converter(d[key], offset) key_to_add = f"{key}_{self.centroid_key_postfix}" if key_to_add in d: diff --git a/monai/apps/tcia/utils.py b/monai/apps/tcia/utils.py index f023cdbc876..05f7074b8cb 100644 --- a/monai/apps/tcia/utils.py +++ b/monai/apps/tcia/utils.py @@ -100,6 +100,7 @@ def download_tcia_series_instance( query_name = "getImageWithMD5Hash" if check_md5 else "getImage" download_url = f"{BASE_URL}{query_name}?SeriesInstanceUID={series_uid}" + # pyrefly: ignore [implicit-import] monai.apps.utils.download_and_extract( url=download_url, filepath=os.path.join(download_dir, f"{series_uid}.zip"), @@ -111,6 +112,7 @@ def download_tcia_series_instance( raise ValueError("pandas package is necessary, please install it.") hashes_df = pd.read_csv(os.path.join(output_dir, hashes_filename)) for dcm, md5hash in hashes_df.values: + # pyrefly: ignore [implicit-import] monai.apps.utils.check_hash(filepath=os.path.join(output_dir, dcm), val=md5hash, hash_type="md5") diff --git a/monai/apps/vista3d/inferer.py b/monai/apps/vista3d/inferer.py index 23fdb66d02c..18daadb8491 100644 --- a/monai/apps/vista3d/inferer.py +++ b/monai/apps/vista3d/inferer.py @@ -89,8 +89,11 @@ def point_based_window_inferer( unravel_slice = ( slice(None), slice(None), + # pyrefly: ignore [unnecessary-type-conversion] slice(int(lx), int(rx)), + # pyrefly: ignore [unnecessary-type-conversion] slice(int(ly), int(ry)), + # pyrefly: ignore [unnecessary-type-conversion] slice(int(lz), int(rz)), ) batch_image = image[unravel_slice] @@ -151,6 +154,7 @@ def _get_window_idx_c(p: int, roi: int, s: int) -> tuple[int, int]: elif p + roi // 2 > s: left, right = s - roi, s else: + # pyrefly: ignore [unnecessary-type-conversion] left, right = int(p) - roi // 2, int(p) + roi // 2 return left, right diff --git a/monai/apps/vista3d/transforms.py b/monai/apps/vista3d/transforms.py index 7860e3db406..e332f12b5df 100644 --- a/monai/apps/vista3d/transforms.py +++ b/monai/apps/vista3d/transforms.py @@ -46,7 +46,9 @@ def _convert_name_to_index(name_to_index_mapping: dict, label_prompt: list | Non for l in label_prompt: if isinstance(l, (int, str)): converted_label_prompt.append( - name_to_index_mapping.get(l.lower(), int(l) if l.isdigit() else 0) if isinstance(l, str) else int(l) + name_to_index_mapping.get(l.lower(), int(l) if l.isdigit() else 0) + if isinstance(l, str) + else int(l) # pyrefly: ignore [unnecessary-type-conversion] ) else: converted_label_prompt.append(l) @@ -206,8 +208,8 @@ def __init__( self.dataset_key = dataset_key for name, mapping in label_mappings.items(): self.mappers[name] = MapLabelValue( - orig_labels=[int(pair[0]) for pair in mapping], - target_labels=[int(pair[1]) for pair in mapping], + orig_labels=[int(pair[0]) for pair in mapping], # pyrefly: ignore [unnecessary-type-conversion] + target_labels=[int(pair[1]) for pair in mapping], # pyrefly: ignore [unnecessary-type-conversion] dtype=dtype, ) diff --git a/monai/auto3dseg/operations.py b/monai/auto3dseg/operations.py index 404a6d326ee..7b64a04a6cd 100644 --- a/monai/auto3dseg/operations.py +++ b/monai/auto3dseg/operations.py @@ -149,4 +149,5 @@ def evaluate(self, data: Any, **kwargs: Any) -> dict: Args: data: input data """ + # pyrefly: ignore [missing-attribute] return {k: v(data[k], **kwargs).tolist() for k, v in self.data.items() if (callable(v) and k in data)} diff --git a/monai/auto3dseg/seg_summarizer.py b/monai/auto3dseg/seg_summarizer.py index 14a10635df2..8fdd9652455 100644 --- a/monai/auto3dseg/seg_summarizer.py +++ b/monai/auto3dseg/seg_summarizer.py @@ -208,6 +208,7 @@ def summarize(self, data: list[dict]) -> dict[str, dict]: for analyzer in self.summary_analyzers: if callable(analyzer): + # pyrefly: ignore [missing-attribute] report.update({analyzer.stats_name: analyzer(data)}) return report diff --git a/monai/bundle/reference_resolver.py b/monai/bundle/reference_resolver.py index b55c62174b7..27f34ebd5eb 100644 --- a/monai/bundle/reference_resolver.py +++ b/monai/bundle/reference_resolver.py @@ -254,6 +254,7 @@ def iter_subconfigs(cls, id: str, config: Any) -> Iterator[tuple[str, str, Any]] """ for k, v in config.items() if isinstance(config, dict) else enumerate(config): sub_id = f"{id}{cls.sep}{k}" if id != "" else f"{k}" + # pyrefly: ignore [invalid-yield] yield k, sub_id, v @classmethod diff --git a/monai/bundle/scripts.py b/monai/bundle/scripts.py index ab02cd552e3..376d9da6b2e 100644 --- a/monai/bundle/scripts.py +++ b/monai/bundle/scripts.py @@ -590,6 +590,7 @@ def download( _download_from_monaihosting( download_path=bundle_dir_, filename=name_, version=version_, progress=progress_ ) + # pyrefly: ignore [implicit-import] except urllib.error.HTTPError: # if also cannot download from ngc monaihosting, download according to bundle_info _download_from_bundle_info( @@ -1952,8 +1953,10 @@ def create_workflow( _args, workflow_name=ConfigWorkflow, config_file=None ) # the default workflow name is "ConfigWorkflow" if isinstance(workflow_name, str): + # pyrefly: ignore [unnecessary-type-conversion] workflow_class, has_built_in = optional_import("monai.bundle", name=str(workflow_name)) # search built-in if not has_built_in: + # pyrefly: ignore [unnecessary-type-conversion] workflow_class = locate(str(workflow_name)) # search dotted path if workflow_class is None: raise ValueError(f"cannot locate specified workflow class: {workflow_name}.") @@ -1966,6 +1969,7 @@ def create_workflow( ) if config_file is not None: + # pyrefly: ignore [unexpected-keyword] workflow_ = workflow_class(config_file=config_file, **_args) else: workflow_ = workflow_class(**_args) diff --git a/monai/bundle/workflows.py b/monai/bundle/workflows.py index 5b95441d51f..0367047b58b 100644 --- a/monai/bundle/workflows.py +++ b/monai/bundle/workflows.py @@ -224,6 +224,7 @@ def add_property(self, name: str, required: str, desc: str | None = None) -> Non desc: descriptions for the property. """ if self.properties is None: + # pyrefly: ignore [bad-assignment] self.properties = {} if name in self.properties: logger.warning(f"property '{name}' already exists in the properties list, overriding it.") @@ -329,6 +330,7 @@ def _get_property(self, name: str, property: dict) -> Any: elif name in self._props_vals: value = self._props_vals[name] elif name in self.parser.config[self.parser.meta_key]: # type: ignore[index] + # pyrefly: ignore [missing-attribute] id = self.properties.get(name, None).get(BundlePropertyConfig.ID, None) value = self.parser[id] else: @@ -621,6 +623,7 @@ def _check_optional_id(self, name: str, property: dict) -> bool: else: ref = self.parser.get(ref_id, None) # for reference IDs that not refer to a property directly but using expressions, skip the check + # pyrefly: ignore [unsupported-operation] if ref is not None and not ref.startswith(EXPR_KEY) and ref != ID_REF_KEY + id: return False return True diff --git a/monai/data/box_utils.py b/monai/data/box_utils.py index 2f4a1426a98..fa0bba7f085 100644 --- a/monai/data/box_utils.py +++ b/monai/data/box_utils.py @@ -447,6 +447,7 @@ def get_spatial_dims( raise ValueError("At least one of the inputs needs to be non-empty.") if len(spatial_dims_list) == 1: + # pyrefly: ignore [unnecessary-type-conversion] spatial_dims = int(spatial_dims_list[0]) spatial_dims = look_up_option(spatial_dims, supported=[2, 3]) return int(spatial_dims) diff --git a/monai/data/dataset.py b/monai/data/dataset.py index f07699594e3..89b94b7797e 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -844,6 +844,7 @@ def __init__( self.hash_func = hash_func self.num_workers = num_workers if self.num_workers is not None: + # pyrefly: ignore [unnecessary-type-conversion] self.num_workers = max(int(self.num_workers), 1) self.runtime_cache = runtime_cache self.cache_num = 0 @@ -863,6 +864,7 @@ def set_data(self, data: Sequence) -> None: self.data = data def _compute_cache_num(data_len: int): + # pyrefly: ignore [unnecessary-type-conversion] self.cache_num = min(int(self.set_num), int(data_len * self.set_rate), data_len) if self.hash_as_key: @@ -1082,6 +1084,7 @@ def __init__( self.num_replace_workers: int | None = num_replace_workers if self.num_replace_workers is not None: + # pyrefly: ignore [unnecessary-type-conversion] self.num_replace_workers = max(int(self.num_replace_workers), 1) self._total_num: int = len(data) @@ -1648,6 +1651,7 @@ def _cachecheck(self, item_transformed): item_k = kvikio_numpy.fromfile( f"{hashfile}-{k}-{i}", dtype=meta_i_k["dtype"], like=cp.empty(()) ) + # pyrefly: ignore [missing-attribute] item_k = convert_to_tensor(item[i].reshape(meta_i_k["shape"]), device=f"cuda:{self.device}") item[i].update({k: item_k, f"{k}_meta_dict": meta_i_k}) return item diff --git a/monai/data/folder_layout.py b/monai/data/folder_layout.py index 4403855030a..a3bf99a1784 100644 --- a/monai/data/folder_layout.py +++ b/monai/data/folder_layout.py @@ -20,6 +20,7 @@ __all__ = ["FolderLayoutBase", "FolderLayout", "default_name_formatter"] +# pyrefly: ignore [implicit-import] def default_name_formatter(metadict: dict, saver: monai.transforms.Transform) -> dict: """Returns a kwargs dict for :py:meth:`FolderLayout.filename`, according to the input metadata and SaveImage transform.""" diff --git a/monai/data/grid_dataset.py b/monai/data/grid_dataset.py index 689138179ae..3c41683ce9a 100644 --- a/monai/data/grid_dataset.py +++ b/monai/data/grid_dataset.py @@ -142,6 +142,7 @@ def __call__( self, data: Mapping[Hashable, NdarrayTensor] ) -> Generator[tuple[Mapping[Hashable, NdarrayTensor], np.ndarray], None, None]: d = dict(data) + # pyrefly: ignore [missing-attribute] original_spatial_shape = d[first(self.keys)].shape[1:] for patch in zip(*[self.patch_iter(d[key]) for key in self.keys]): @@ -247,6 +248,7 @@ def __init__( self.hash_func = hash_func self.num_workers = num_workers if self.num_workers is not None: + # pyrefly: ignore [unnecessary-type-conversion] self.num_workers = max(int(self.num_workers), 1) self._cache: list | ListProxy = [] self._cache_other: list | ListProxy = [] @@ -275,6 +277,7 @@ def set_data(self, data: Sequence) -> None: # only compute cache for the unique items of dataset, and record the last index for duplicated items mapping = {self.hash_func(v): i for i, v in enumerate(self.data)} + # pyrefly: ignore [unnecessary-type-conversion] self.cache_num = min(int(self.set_num), int(len(mapping) * self.set_rate), len(mapping)) self._hash_keys = list(mapping)[: self.cache_num] indices = list(mapping.values())[: self.cache_num] @@ -420,6 +423,7 @@ def __init__( self.patch_func = patch_func if samples_per_image <= 0: raise ValueError("sampler_per_image must be a positive integer.") + # pyrefly: ignore [unnecessary-type-conversion] self.samples_per_image = int(samples_per_image) self.patch_transform = transform diff --git a/monai/data/image_reader.py b/monai/data/image_reader.py index 6859dca62f4..4c3445497bc 100644 --- a/monai/data/image_reader.py +++ b/monai/data/image_reader.py @@ -22,7 +22,7 @@ from collections.abc import Callable, Iterable, Iterator, Sequence from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING, Any, TypeAlias +from typing import TYPE_CHECKING, Any, TypeAlias # pyrefly: ignore [missing-module-attribute] import numpy as np from torch.utils.data._utils.collate import np_str_obj_array_pattern diff --git a/monai/data/wsi_datasets.py b/monai/data/wsi_datasets.py index 2ee8c9d3634..0fc9a79ac24 100644 --- a/monai/data/wsi_datasets.py +++ b/monai/data/wsi_datasets.py @@ -250,8 +250,10 @@ def __init__( self.offset_limits = None elif isinstance(offset_limits, tuple): if isinstance(offset_limits[0], int): + # pyrefly: ignore [bad-assignment] self.offset_limits = (offset_limits, offset_limits) elif isinstance(offset_limits[0], tuple): + # pyrefly: ignore [bad-assignment] self.offset_limits = offset_limits else: raise ValueError( @@ -304,6 +306,7 @@ def _evaluate_patch_locations(self, sample): ) ) # convert locations to mask_location + # pyrefly: ignore [unnecessary-type-conversion] mask_locations = np.round((patch_locations + patch_size_0 // 2) / float(mask_ratio)) # fill out samples with location and metadata @@ -402,6 +405,7 @@ def _evaluate_patch_locations(self, sample): mask_ratio = self.wsi_reader.get_downsample_ratio(wsi_obj, self.mask_level) patch_ratio = self.wsi_reader.get_downsample_ratio(wsi_obj, patch_level) patch_size_0 = np.array([p * patch_ratio for p in patch_size]) # patch size at level 0 + # pyrefly: ignore [unnecessary-type-conversion] patch_locations = np.round((mask_locations + 0.5) * float(mask_ratio) - patch_size_0 // 2).astype(int) # fill out samples with location and metadata diff --git a/monai/data/wsi_reader.py b/monai/data/wsi_reader.py index b377234d10d..8a465c11979 100644 --- a/monai/data/wsi_reader.py +++ b/monai/data/wsi_reader.py @@ -319,6 +319,7 @@ def _get_metadata( } return metadata + # pyrefly: ignore [bad-override] def get_data( self, wsi, diff --git a/monai/engines/evaluator.py b/monai/engines/evaluator.py index 62d5f838477..2748ca34508 100644 --- a/monai/engines/evaluator.py +++ b/monai/engines/evaluator.py @@ -489,11 +489,13 @@ def _iteration(self, engine: EnsembleEvaluator, batchdata: dict[str, torch.Tenso if engine.amp: with torch.autocast("cuda", **engine.amp_kwargs): if isinstance(engine.state.output, dict): + # pyrefly: ignore [no-matching-overload] engine.state.output.update( {engine.pred_keys[idx]: engine.inferer(inputs, network, *args, **kwargs)} ) else: if isinstance(engine.state.output, dict): + # pyrefly: ignore [no-matching-overload] engine.state.output.update( {engine.pred_keys[idx]: engine.inferer(inputs, network, *args, **kwargs)} ) diff --git a/monai/engines/trainer.py b/monai/engines/trainer.py index 921d54a59cb..1f0c75620fa 100644 --- a/monai/engines/trainer.py +++ b/monai/engines/trainer.py @@ -774,4 +774,5 @@ def _compute_discriminator_loss() -> None: engine.state.output[AdversarialKeys.DISCRIMINATOR_LOSS].backward() engine.state.d_optimizer.step() + # pyrefly: ignore [bad-return] return engine.state.output diff --git a/monai/fl/client/monai_algo.py b/monai/fl/client/monai_algo.py index 6e9a6fd1fe2..2c2fb872274 100644 --- a/monai/fl/client/monai_algo.py +++ b/monai/fl/client/monai_algo.py @@ -251,6 +251,7 @@ def _get_data_key_stats(self, data, data_key, hist_bins, hist_range, output_path dataroot=self.workflow.dataset_dir, # type: ignore hist_bins=hist_bins, hist_range=hist_range, + # pyrefly: ignore [bad-argument-type] output_path=output_path, histogram_only=self.histogram_only, ) diff --git a/monai/handlers/mlflow_handler.py b/monai/handlers/mlflow_handler.py index 3078d89f97c..2ea54cc06ab 100644 --- a/monai/handlers/mlflow_handler.py +++ b/monai/handlers/mlflow_handler.py @@ -234,6 +234,7 @@ def start(self, engine: Engine) -> None: self._log_params(attrs) if self.dataset_logger: + # pyrefly: ignore [bad-argument-type] self.dataset_logger(self.dataset_dict) else: self._default_dataset_log(self.dataset_dict) @@ -257,6 +258,7 @@ def _set_experiment(self): else: raise e + # pyrefly: ignore [missing-attribute] if experiment.lifecycle_stage != mlflow.entities.LifecycleStage.ACTIVE: raise ValueError(f"Cannot set a deleted experiment '{self.experiment_name}' as the active experiment") self.experiment = experiment diff --git a/monai/handlers/utils.py b/monai/handlers/utils.py index 02975039b38..afbd484026a 100644 --- a/monai/handlers/utils.py +++ b/monai/handlers/utils.py @@ -124,6 +124,7 @@ class mean median max 5percentile 95percentile notnans if class_labels is None: class_labels = ["class" + str(i) for i in range(v.shape[1])] else: + # pyrefly: ignore [unnecessary-type-conversion] class_labels = [str(i) for i in class_labels] # ensure to have a list of str class_labels += ["mean"] diff --git a/monai/inferers/inferer.py b/monai/inferers/inferer.py index ee94b1ebdbe..9e4b09d36c8 100644 --- a/monai/inferers/inferer.py +++ b/monai/inferers/inferer.py @@ -661,6 +661,7 @@ def __call__( **kwargs, ) except RuntimeError as e: + # pyrefly: ignore [unnecessary-type-conversion] if not gpu_stitching and not buffered_stitching or "OutOfMemoryError" not in str(type(e).__name__): raise e @@ -841,6 +842,7 @@ def network_wrapper( if isinstance(out, Mapping): for k in out.keys(): + # pyrefly: ignore [unsupported-operation] out[k] = out[k].unsqueeze(dim=self.spatial_dim + 2) return out diff --git a/monai/inferers/utils.py b/monai/inferers/utils.py index de53108d1d1..51872f77823 100644 --- a/monai/inferers/utils.py +++ b/monai/inferers/utils.py @@ -413,6 +413,7 @@ def _get_scan_interval( scan_interval = [] for i, o in zip(range(num_spatial_dims), overlap): if roi_size[i] == image_size[i]: + # pyrefly: ignore [unnecessary-type-conversion] scan_interval.append(int(roi_size[i])) else: interval = int(roi_size[i] * (1 - o)) diff --git a/monai/losses/dice.py b/monai/losses/dice.py index 2c4010176a4..f1d0a8710ed 100644 --- a/monai/losses/dice.py +++ b/monai/losses/dice.py @@ -119,7 +119,9 @@ def __init__( self.other_act = other_act self.squared_pred = squared_pred self.jaccard = jaccard + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_nr = float(smooth_nr) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_dr = float(smooth_dr) self.batch = batch weight = torch.as_tensor(weight) if weight is not None else None @@ -378,7 +380,9 @@ def __init__( self.w_type = look_up_option(w_type, Weight) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_nr = float(smooth_nr) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_dr = float(smooth_dr) self.batch = batch self.soft_label = soft_label @@ -555,7 +559,9 @@ def __init__( self.m = self.m / torch.max(self.m) self.alpha_mode = weighting_mode self.num_classes = self.m.size(0) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_nr = float(smooth_nr) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_dr = float(smooth_dr) def forward(self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor: diff --git a/monai/losses/image_dissimilarity.py b/monai/losses/image_dissimilarity.py index 195ac32b1ff..7cfbbb910b7 100644 --- a/monai/losses/image_dissimilarity.py +++ b/monai/losses/image_dissimilarity.py @@ -116,7 +116,9 @@ def __init__( self.register_buffer("kernel", _kernel(self.kernel_size), persistent=False) self.register_buffer("kernel_vol", self.get_kernel_vol(), persistent=False) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_nr = float(smooth_nr) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_dr = float(smooth_dr) def get_kernel_vol(self) -> torch.Tensor: @@ -241,7 +243,10 @@ def __init__( if self.kernel_type == "gaussian": self.register_buffer("preterm", 1 / (2 * sigma**2), persistent=False) self.register_buffer("bin_centers", bin_centers[None, None, ...], persistent=False) + + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_nr = float(smooth_nr) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_dr = float(smooth_dr) def parzen_windowing( diff --git a/monai/losses/multi_scale.py b/monai/losses/multi_scale.py index 206bc83bc66..965930b046e 100644 --- a/monai/losses/multi_scale.py +++ b/monai/losses/multi_scale.py @@ -27,6 +27,7 @@ def make_gaussian_kernel(sigma: int) -> torch.Tensor: def make_cauchy_kernel(sigma: int) -> torch.Tensor: if sigma <= 0: raise ValueError(f"expecting positive sigma, got sigma={sigma}") + # pyrefly: ignore [unnecessary-type-conversion] tail = int(sigma * 5) k = torch.tensor([((x / sigma) ** 2 + 1) for x in range(-tail, tail + 1)]) k = torch.reciprocal(k) diff --git a/monai/losses/tversky.py b/monai/losses/tversky.py index 5db4025be0f..a3c180052d4 100644 --- a/monai/losses/tversky.py +++ b/monai/losses/tversky.py @@ -97,7 +97,9 @@ def __init__( self.other_act = other_act self.alpha = alpha self.beta = beta + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_nr = float(smooth_nr) + # pyrefly: ignore [unnecessary-type-conversion] self.smooth_dr = float(smooth_dr) self.batch = batch self.soft_label = soft_label diff --git a/monai/metrics/utils.py b/monai/metrics/utils.py index 443be953b07..a5927a0a5be 100644 --- a/monai/metrics/utils.py +++ b/monai/metrics/utils.py @@ -217,6 +217,7 @@ def get_mask_edges( or_vol = seg_pred | seg_gt if not or_vol.any(): pred, gt = lib.zeros(seg_pred.shape, dtype=bool), lib.zeros(seg_gt.shape, dtype=bool) + # pyrefly: ignore [bad-return] return (pred, gt) if spacing is None else (pred, gt, pred, gt) channel_first = [seg_pred[None], seg_gt[None], or_vol[None]] if spacing is None and not use_cucim: # cpu only erosion diff --git a/monai/networks/blocks/attention_utils.py b/monai/networks/blocks/attention_utils.py index a8dfcd7df32..a1a46a7a052 100644 --- a/monai/networks/blocks/attention_utils.py +++ b/monai/networks/blocks/attention_utils.py @@ -28,6 +28,7 @@ def get_rel_pos(q_size: int, k_size: int, rel_pos: torch.Tensor) -> torch.Tensor Extracted positional embeddings according to relative positions. """ rel_pos_resized: torch.Tensor = torch.Tensor() + # pyrefly: ignore [unnecessary-type-conversion] max_rel_dist = int(2 * max(q_size, k_size) - 1) # Interpolate rel pos if needed. if rel_pos.shape[0] != max_rel_dist: diff --git a/monai/networks/blocks/dints_block.py b/monai/networks/blocks/dints_block.py index d3ac7cf04b3..baee1ca12ea 100644 --- a/monai/networks/blocks/dints_block.py +++ b/monai/networks/blocks/dints_block.py @@ -169,6 +169,7 @@ def __init__( super().__init__() self._in_channel = in_channel self._out_channel = out_channel + # pyrefly: ignore [unnecessary-type-conversion] self._p3dmode = int(mode) conv_type = Conv[Conv.CONV, 3] diff --git a/monai/networks/blocks/localnet_block.py b/monai/networks/blocks/localnet_block.py index b8b3802bb9a..443254eae95 100644 --- a/monai/networks/blocks/localnet_block.py +++ b/monai/networks/blocks/localnet_block.py @@ -243,6 +243,7 @@ def __init__( def additive_upsampling(self, x, mid) -> torch.Tensor: x = F.interpolate(x, mid.shape[2:], mode=self.mode, align_corners=self.align_corners) # [(batch, out_channels, ...), (batch, out_channels, ...)] + # pyrefly: ignore [unnecessary-type-conversion] x = x.split(split_size=int(self.out_channels), dim=1) # (batch, out_channels, ...) out: torch.Tensor = torch.sum(torch.stack(x, dim=-1), dim=-1) diff --git a/monai/networks/blocks/squeeze_and_excitation.py b/monai/networks/blocks/squeeze_and_excitation.py index 665e9020fff..44a74bd5af4 100644 --- a/monai/networks/blocks/squeeze_and_excitation.py +++ b/monai/networks/blocks/squeeze_and_excitation.py @@ -58,6 +58,7 @@ def __init__( pool_type = Pool[Pool.ADAPTIVEAVG, spatial_dims] self.avg_pool = pool_type(1) # spatial size (1, 1, ...) + # pyrefly: ignore [unnecessary-type-conversion] channels = int(in_channels // r) if channels <= 0: raise ValueError(f"r must be positive and smaller than in_channels, got r={r} in_channels={in_channels}.") diff --git a/monai/networks/layers/spatial_transforms.py b/monai/networks/layers/spatial_transforms.py index 6ce9979a801..8d29e8aaa44 100644 --- a/monai/networks/layers/spatial_transforms.py +++ b/monai/networks/layers/spatial_transforms.py @@ -127,6 +127,7 @@ def grid_pull( ] out: torch.Tensor out = _GridPull.apply(input, grid, interpolation, bound, extrapolate) + # pyrefly: ignore [implicit-import] if isinstance(input, monai.data.MetaTensor): out = convert_to_dst_type(out, dst=input)[0] return out @@ -232,6 +233,7 @@ def grid_push( shape = tuple(input.shape[2:]) out: torch.Tensor = _GridPush.apply(input, grid, shape, interpolation, bound, extrapolate) + # pyrefly: ignore [implicit-import] if isinstance(input, monai.data.MetaTensor): out = convert_to_dst_type(out, dst=input)[0] return out @@ -332,6 +334,7 @@ def grid_count(grid: torch.Tensor, shape=None, interpolation="linear", bound="ze shape = tuple(grid.shape[2:]) out: torch.Tensor = _GridCount.apply(grid, shape, interpolation, bound, extrapolate) + # pyrefly: ignore [implicit-import] if isinstance(input, monai.data.MetaTensor): out = convert_to_dst_type(out, dst=input)[0] return out @@ -431,6 +434,7 @@ def grid_grad(input: torch.Tensor, grid: torch.Tensor, interpolation="linear", b ] out: torch.Tensor = _GridGrad.apply(input, grid, interpolation, bound, extrapolate) + # pyrefly: ignore [implicit-import] if isinstance(input, monai.data.MetaTensor): out = convert_to_dst_type(out, dst=input)[0] return out diff --git a/monai/networks/nets/efficientnet.py b/monai/networks/nets/efficientnet.py index e9b7675144c..03311115c33 100644 --- a/monai/networks/nets/efficientnet.py +++ b/monai/networks/nets/efficientnet.py @@ -913,6 +913,7 @@ def _round_repeats(repeats: int, depth_coefficient: float | None) -> int: return repeats # follow the formula transferred from official TensorFlow impl. + # pyrefly: ignore [unnecessary-type-conversion] return int(math.ceil(depth_coefficient * repeats)) @@ -938,6 +939,7 @@ def _calculate_output_image_size(input_image_size: list[int], stride: int | tupl stride = stride[0] # return output image size + # pyrefly: ignore [unnecessary-type-conversion] return [int(math.ceil(im_sz / stride)) for im_sz in input_image_size] diff --git a/monai/networks/nets/flexible_unet.py b/monai/networks/nets/flexible_unet.py index c27b0fc17b9..bc244ba6e7a 100644 --- a/monai/networks/nets/flexible_unet.py +++ b/monai/networks/nets/flexible_unet.py @@ -61,9 +61,13 @@ def register_class(self, name: type[Any] | str): "or implement all interfaces specified by it." ) + # pyrefly: ignore [missing-attribute] name_string_list = name.get_encoder_names() + # pyrefly: ignore [missing-attribute] feature_number_list = name.num_outputs() + # pyrefly: ignore [missing-attribute] feature_channel_list = name.num_channels_per_output() + # pyrefly: ignore [missing-attribute] parameter_list = name.get_encoder_parameters() assert len(name_string_list) == len(feature_number_list) == len(feature_channel_list) == len(parameter_list) diff --git a/monai/networks/nets/milmodel.py b/monai/networks/nets/milmodel.py index a31f1051106..59fb0e514ad 100644 --- a/monai/networks/nets/milmodel.py +++ b/monai/networks/nets/milmodel.py @@ -66,6 +66,7 @@ def __init__( raise ValueError("Number of classes must be positive: " + str(num_classes)) if mil_mode.lower() not in ["mean", "max", "att", "att_trans", "att_trans_pyramid"]: + # pyrefly: ignore [unnecessary-type-conversion] raise ValueError("Unsupported mil_mode: " + str(mil_mode)) self.mil_mode = mil_mode.lower() @@ -97,6 +98,7 @@ def hook(module, input, output): # assume torchvision model string is provided torch_model = getattr(models, backbone, None) if torch_model is None: + # pyrefly: ignore [unnecessary-type-conversion] raise ValueError("Unknown torch vision model" + str(backbone)) net = torch_model(weights="DEFAULT" if pretrained else None) @@ -105,6 +107,7 @@ def hook(module, input, output): net.fc = torch.nn.Identity() # remove final linear layer else: raise ValueError( + # pyrefly: ignore [unnecessary-type-conversion] "Unable to detect FC layer for the torchvision model " + str(backbone), ". Please initialize the backbone model manually.", ) @@ -121,6 +124,7 @@ def hook(module, input, output): raise ValueError("Unsupported backbone") if backbone is not None and mil_mode not in ["mean", "max", "att", "att_trans"]: + # pyrefly: ignore [unnecessary-type-conversion] raise ValueError("Custom backbone is not supported for the mode:" + str(mil_mode)) if self.mil_mode in ["mean", "max"]: @@ -160,10 +164,12 @@ def hook(module, input, output): ] ) self.transformer = transformer_list + # pyrefly: ignore [unsupported-operation] nfc = nfc + 256 self.attention = nn.Sequential(nn.Linear(nfc, 2048), nn.Tanh(), nn.Linear(2048, 1)) else: + # pyrefly: ignore [unnecessary-type-conversion] raise ValueError("Unsupported mil_mode: " + str(mil_mode)) self.myfc = nn.Linear(nfc, num_classes) @@ -220,6 +226,7 @@ def calc_head(self, x: torch.Tensor) -> torch.Tensor: x = self.myfc(x) else: + # pyrefly: ignore [unnecessary-type-conversion] raise ValueError("Wrong model mode" + str(self.mil_mode)) return x diff --git a/monai/networks/nets/transchex.py b/monai/networks/nets/transchex.py index 6c40cae2aa3..dfe71dd4bad 100644 --- a/monai/networks/nets/transchex.py +++ b/monai/networks/nets/transchex.py @@ -74,6 +74,7 @@ def from_pretrained( return load_tf_weights_in_bert(model, weights_path) old_keys = [] new_keys = [] + # pyrefly: ignore [missing-attribute] for key in state_dict.keys(): new_key = None if "gamma" in key: @@ -84,11 +85,13 @@ def from_pretrained( old_keys.append(key) new_keys.append(new_key) for old_key, new_key in zip(old_keys, new_keys): + # pyrefly: ignore [missing-attribute, unsupported-operation] state_dict[new_key] = state_dict.pop(old_key) missing_keys: list = [] unexpected_keys: list = [] error_msgs: list = [] metadata = getattr(state_dict, "_metadata", None) + # pyrefly: ignore [missing-attribute] state_dict = state_dict.copy() if metadata is not None: state_dict._metadata = metadata diff --git a/monai/networks/nets/vista3d.py b/monai/networks/nets/vista3d.py index 232b8c1c11a..2fe62825b33 100644 --- a/monai/networks/nets/vista3d.py +++ b/monai/networks/nets/vista3d.py @@ -240,6 +240,7 @@ def connected_components_combine( mapping_index: [B]. thred: the threshold to convert logits to binary. """ + # pyrefly: ignore [implicit-import] logits = logits.as_tensor() if isinstance(logits, monai.data.MetaTensor) else logits _logits = logits[mapping_index] inside = [] @@ -297,6 +298,7 @@ def gaussian_combine( 1, keepdims=True ) weight[weight < 0] = 0 + # pyrefly: ignore [implicit-import] logits = logits.as_tensor() if isinstance(logits, monai.data.MetaTensor) else logits logits[mapping_index] *= weight logits[mapping_index] += (1 - weight) * point_logits diff --git a/monai/networks/nets/vqvae.py b/monai/networks/nets/vqvae.py index 43ba48585c2..690bf48de08 100644 --- a/monai/networks/nets/vqvae.py +++ b/monai/networks/nets/vqvae.py @@ -361,18 +361,22 @@ def __init__( else: downsample_parameters_tuple = downsample_parameters + # pyrefly: ignore [not-iterable] if not all(all(isinstance(value, int) for value in sub_item) for sub_item in downsample_parameters_tuple): raise ValueError("`downsample_parameters` should be a single tuple of integer or a tuple of tuples.") # check if downsample_parameters is a tuple of ints or a tuple of tuples of ints + # pyrefly: ignore [not-iterable] if not all(all(isinstance(value, int) for value in sub_item) for sub_item in upsample_parameters_tuple): raise ValueError("`upsample_parameters` should be a single tuple of integer or a tuple of tuples.") for parameter in downsample_parameters_tuple: + # pyrefly: ignore [bad-argument-type] if len(parameter) != 4: raise ValueError("`downsample_parameters` should be a tuple of tuples with 4 integers.") for parameter in upsample_parameters_tuple: + # pyrefly: ignore [bad-argument-type] if len(parameter) != 5: raise ValueError("`upsample_parameters` should be a tuple of tuples with 5 integers.") @@ -396,6 +400,7 @@ def __init__( channels=channels, num_res_layers=num_res_layers, num_res_channels=num_res_channels, + # pyrefly: ignore [bad-argument-type] downsample_parameters=downsample_parameters_tuple, dropout=dropout, act=act, @@ -408,6 +413,7 @@ def __init__( channels=channels, num_res_layers=num_res_layers, num_res_channels=num_res_channels, + # pyrefly: ignore [bad-argument-type] upsample_parameters=upsample_parameters_tuple, dropout=dropout, act=act, diff --git a/monai/networks/utils.py b/monai/networks/utils.py index f56c39dcd18..61d63544b08 100644 --- a/monai/networks/utils.py +++ b/monai/networks/utils.py @@ -601,6 +601,7 @@ def copy_model_state( dst_dict[dst_key] = val updated_keys.append(dst_key) for s in mapping if mapping else {}: + # pyrefly: ignore [unsupported-operation] dst_key = f"{dst_prefix}{mapping[s]}" if dst_key in dst_dict and dst_key not in to_skip: if dst_dict[dst_key].shape != src_dict[s].shape: diff --git a/monai/optimizers/lr_scheduler.py b/monai/optimizers/lr_scheduler.py index 96c1412feac..07c4c0f309f 100644 --- a/monai/optimizers/lr_scheduler.py +++ b/monai/optimizers/lr_scheduler.py @@ -97,9 +97,11 @@ def __init__( def lr_lambda(self, step): if step < self.warmup_steps: + # pyrefly: ignore [unnecessary-type-conversion] f = float(step) / float(max(1.0, self.warmup_steps)) return self.warmup_multiplier + (1 - self.warmup_multiplier) * f progress = float(step - self.warmup_steps) / float(max(1, self.t_total - self.warmup_steps)) + # pyrefly: ignore [unnecessary-type-conversion] return max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(self.cycles) * 2.0 * progress))) def get_lr(self): diff --git a/monai/optimizers/novograd.py b/monai/optimizers/novograd.py index 9ca612fc564..5d3c5504e17 100644 --- a/monai/optimizers/novograd.py +++ b/monai/optimizers/novograd.py @@ -112,6 +112,7 @@ def step(self, closure: Callable[[], T] | None = None) -> T | None: # type: ign norm = torch.sum(torch.pow(grad, 2)) if exp_avg_sq == 0: + # pyrefly: ignore [missing-attribute] exp_avg_sq.copy_(norm) else: exp_avg_sq.mul_(beta2).add_(norm, alpha=1 - beta2) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 6c3fc104c9f..9f9ade6a7ce 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -532,10 +532,12 @@ def __call__(self, data, start=0, end=None, threading=False, lazy: bool | None = ) # if the data is a mapping (dictionary), append the OneOf transform to the end + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): self.push_transform(data, extra_info={"index": index}) elif isinstance(data, Mapping): for key in data: # dictionary not change size during iteration + # pyrefly: ignore [implicit-import] if isinstance(data[key], monai.data.MetaTensor): self.push_transform(data[key], extra_info={"index": index}) return data @@ -545,10 +547,12 @@ def inverse(self, data): return data index = None + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): index = self.pop_transform(data)[TraceKeys.EXTRA_INFO]["index"] elif isinstance(data, Mapping): for key in data: + # pyrefly: ignore [implicit-import] if isinstance(data[key], monai.data.MetaTensor): index = self.pop_transform(data, key)[TraceKeys.EXTRA_INFO]["index"] else: @@ -627,10 +631,12 @@ def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None ) # if the data is a mapping (dictionary), append the RandomOrder transform to the end + # pyrefly: ignore [implicit-import] if isinstance(input_, monai.data.MetaTensor): self.push_transform(input_, extra_info={"applied_order": applied_order}) elif isinstance(input_, Mapping): for key in input_: # dictionary not change size during iteration + # pyrefly: ignore [implicit-import] if isinstance(input_[key], monai.data.MetaTensor): self.push_transform(input_[key], extra_info={"applied_order": applied_order}) return input_ @@ -640,10 +646,12 @@ def inverse(self, data): return data applied_order = None + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): applied_order = self.pop_transform(data)[TraceKeys.EXTRA_INFO]["applied_order"] elif isinstance(data, Mapping): for key in data: + # pyrefly: ignore [implicit-import] if isinstance(data[key], monai.data.MetaTensor): applied_order = self.pop_transform(data, key)[TraceKeys.EXTRA_INFO]["applied_order"] else: @@ -790,10 +798,12 @@ def __call__(self, data, start=0, end=None, threading=False, lazy: bool | None = threading=threading, log_stats=self.log_stats, ) + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): self.push_transform(data, extra_info={"applied_order": applied_order}) elif isinstance(data, Mapping): for key in data: # dictionary not change size during iteration + # pyrefly: ignore [implicit-import] if isinstance(data[key], monai.data.MetaTensor) or self.trace_key(key) in data: self.push_transform(data, key, extra_info={"applied_order": applied_order}) @@ -805,10 +815,12 @@ def inverse(self, data): return data applied_order = None + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): applied_order = self.pop_transform(data)[TraceKeys.EXTRA_INFO]["applied_order"] elif isinstance(data, Mapping): for key in data: + # pyrefly: ignore [implicit-import] if isinstance(data[key], monai.data.MetaTensor) or self.trace_key(key) in data: applied_order = self.pop_transform(data, key)[TraceKeys.EXTRA_INFO]["applied_order"] else: diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 4b18c74b2d4..73b0efc8532 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -231,8 +231,10 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, in pad_width = [] for i, sp_i in enumerate(spatial_size): width = max(sp_i - spatial_shape[i], 0) + # pyrefly: ignore [unnecessary-type-conversion] pad_width.append((int(width // 2), int(width - (width // 2)))) else: + # pyrefly: ignore [unnecessary-type-conversion] pad_width = [(0, int(max(sp_i - spatial_shape[i], 0))) for i, sp_i in enumerate(spatial_size)] return tuple([(0, 0)] + pad_width) # type: ignore @@ -280,12 +282,15 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, in spatial_border = tuple(max(0, b) for b in spatial_border) if len(spatial_border) == 1: + # pyrefly: ignore [unnecessary-type-conversion] data_pad_width = [(int(spatial_border[0]), int(spatial_border[0])) for _ in spatial_shape] elif len(spatial_border) == len(spatial_shape): + # pyrefly: ignore [unnecessary-type-conversion] data_pad_width = [(int(sp), int(sp)) for sp in spatial_border[: len(spatial_shape)]] elif len(spatial_border) == len(spatial_shape) * 2: data_pad_width = [ - (int(spatial_border[2 * i]), int(spatial_border[2 * i + 1])) for i in range(len(spatial_shape)) + (int(spatial_border[2 * i]), int(spatial_border[2 * i + 1])) + for i in range(len(spatial_shape)) # pyrefly: ignore [unnecessary-type-conversion] ] else: raise ValueError( @@ -980,6 +985,7 @@ def __init__( ): LazyTransform.__init__(self, lazy) self.spatial_size = ensure_tuple(spatial_size) + # pyrefly: ignore [unnecessary-type-conversion] self.num_samples = int(num_samples) self.weight_map = weight_map self.centers: list[np.ndarray] = [] @@ -1130,6 +1136,7 @@ def __init__( self.bg_indices = bg_indices self.allow_smaller = allow_smaller + # pyrefly: ignore [bad-override] def randomize( self, label: torch.Tensor | None = None, @@ -1319,6 +1326,7 @@ def __init__( self.warn = warn self.max_samples_per_class = max_samples_per_class + # pyrefly: ignore [bad-override] def randomize( self, label: torch.Tensor | None = None, diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 7c82fe065ba..d089cea457b 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -19,7 +19,7 @@ from collections.abc import Callable, Hashable, Mapping, Sequence from copy import deepcopy -from typing import Any, TypeAlias, cast +from typing import Any, TypeAlias, cast # pyrefly: ignore [missing-module-attribute] import numpy as np import torch @@ -1104,6 +1104,7 @@ def set_random_state( self.cropper.set_random_state(seed, state) return self + # pyrefly: ignore [bad-override] def randomize( self, label: torch.Tensor | None = None, @@ -1266,6 +1267,7 @@ def set_random_state( self.cropper.set_random_state(seed, state) return self + # pyrefly: ignore [bad-override] def randomize( self, label: torch.Tensor, indices: list[NdarrayOrTensor] | None = None, image: torch.Tensor | None = None ) -> None: diff --git a/monai/transforms/io/dictionary.py b/monai/transforms/io/dictionary.py index 4927450c7d0..f4108a9a281 100644 --- a/monai/transforms/io/dictionary.py +++ b/monai/transforms/io/dictionary.py @@ -279,6 +279,7 @@ def __init__( output_format: str = "", writer: type[image_writer.ImageWriter] | str | None = None, output_name_formatter: Callable[[dict, Transform], dict] | None = None, + # pyrefly: ignore [implicit-import] folder_layout: monai.data.FolderLayoutBase | None = None, savepath_in_metadict: bool = False, ) -> None: diff --git a/monai/transforms/lazy/utils.py b/monai/transforms/lazy/utils.py index 75f1e3529d0..30b31cd2690 100644 --- a/monai/transforms/lazy/utils.py +++ b/monai/transforms/lazy/utils.py @@ -178,6 +178,7 @@ def resample(data: torch.Tensor, matrix: NdarrayOrTensor, kwargs: dict | None = """ if not Affine.is_affine_shaped(matrix): raise NotImplementedError(f"Calling the dense grid resample API directly not implemented, {matrix.shape}.") + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor) and data.pending_operations: warnings.warn("data.pending_operations is not empty, the resampling output may be incorrect.") kwargs = kwargs or {} @@ -191,7 +192,9 @@ def resample(data: torch.Tensor, matrix: NdarrayOrTensor, kwargs: dict | None = "align_corners": kwargs.get(LazyAttr.ALIGN_CORNERS, False), } ndim = len(matrix) - 1 + # pyrefly: ignore [implicit-import] img = convert_to_tensor(data=data, track_meta=monai.data.get_track_meta()) + # pyrefly: ignore [implicit-import] init_affine = monai.data.to_affine_nd(ndim, img.affine) spatial_size = kwargs.get(LazyAttr.SHAPE, None) out_spatial_size = img.peek_pending_shape() if spatial_size is None else spatial_size @@ -228,11 +231,13 @@ def resample(data: torch.Tensor, matrix: NdarrayOrTensor, kwargs: dict | None = img.affine = call_kwargs["dst_affine"] img = img.to(torch.float32) # consistent with monai.transforms.spatial.functional.spatial_resample return img + # pyrefly: ignore [bad-argument-type, implicit-import] img = monai.transforms.crop_or_pad_nd(img, matrix_np, out_spatial_size, mode=call_kwargs["padding_mode"]) img = img.to(torch.float32) # consistent with monai.transforms.spatial.functional.spatial_resample img.affine = call_kwargs["dst_affine"] return img + # pyrefly: ignore [bad-argument-type, implicit-import] resampler = monai.transforms.SpatialResample(**init_kwargs) resampler.lazy = False # resampler is a lazytransform with resampler.trace_transform(False): # don't track this transform in `img` diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 40f95d47d61..1ce285a9a39 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -93,8 +93,10 @@ def _apply_transform( data = apply_pending_transforms_in_order(transform, data, lazy, overrides, logger_name) if isinstance(data, tuple) and unpack_parameters: + # pyrefly: ignore [not-callable] return transform(*data, lazy=lazy) if isinstance(transform, LazyTrait) else transform(*data) + # pyrefly: ignore [not-callable] return transform(data, lazy=lazy) if isinstance(transform, LazyTrait) else transform(data) @@ -159,8 +161,10 @@ def apply_transform( if log_stats is not False and not isinstance(transform, transforms.compose.Compose): # log the input data information of exact transform in the transform chain if isinstance(log_stats, str): + # pyrefly: ignore [implicit-import] datastats = transforms.utility.array.DataStats(data_shape=False, value_range=False, name=log_stats) else: + # pyrefly: ignore [implicit-import] datastats = transforms.utility.array.DataStats(data_shape=False, value_range=False) logger = logging.getLogger(datastats._logger_name) logger.error(f"\n=== Transform input info -- {type(transform).__name__} ===") diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py index 3deb2f84962..4edf2ce45f4 100644 --- a/monai/transforms/utility/dictionary.py +++ b/monai/transforms/utility/dictionary.py @@ -759,6 +759,7 @@ def __call__(self, data): sub_keys = d[key].keys() if self.sub_keys is None else self.sub_keys # move all the sub-keys to the top level + # pyrefly: ignore [not-iterable] for sk in sub_keys: # set the top-level key for the sub-key sk_top = f"{self.prefix}_{sk}" if self.prefix else sk diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 2ca94617f31..a9ce5db1877 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -186,6 +186,7 @@ def rand_choice(prob: float = 0.5) -> bool: """ Returns True if a randomly chosen number is less than or equal to `prob`, by default this is a 50/50 chance. """ + # pyrefly: ignore [unnecessary-type-conversion] return bool(random.random() <= prob) @@ -202,6 +203,7 @@ def in_bounds(x: float, y: float, margin: float, maxx: float, maxy: float) -> bo """ Returns True if (x,y) is within the rectangle (margin, margin, maxx-margin, maxy-margin). """ + # pyrefly: ignore [unnecessary-type-conversion] return bool(margin <= x < (maxx - margin) and margin <= y < (maxy - margin)) @@ -369,6 +371,7 @@ def check_non_lazy_pending_ops( name: an optional name to be included in the error message. raise_error: whether to raise an error, default to False, a warning message will be issued instead. """ + # pyrefly: ignore [implicit-import] if isinstance(input_array, monai.data.MetaTensor) and input_array.pending_operations: msg = ( "The input image is a MetaTensor and has pending operations,\n" @@ -427,6 +430,7 @@ def map_and_generate_sampling_centers( if label_spatial_shape is not None: _shape = label_spatial_shape + # pyrefly: ignore [implicit-import] elif isinstance(label, monai.data.MetaTensor): _shape = label.peek_pending_shape() else: @@ -531,6 +535,7 @@ def map_classes_to_indices( if img_flat is not None: label_flat = img_flat & label_flat # no need to save the indices in GPU, otherwise, still need to move to CPU at runtime when crop by indices + # pyrefly: ignore [implicit-import] output_type = torch.Tensor if isinstance(label, monai.data.MetaTensor) else None cls_indices: NdarrayOrTensor = convert_data_type( nonzero(label_flat), output_type=output_type, device=torch.device("cpu") @@ -633,6 +638,7 @@ def correct_crop_centers( valid_centers = [] for c, v_s, v_e in zip(centers, valid_start, valid_end): center_i = min(max(c, v_s), v_e - 1) + # pyrefly: ignore [unnecessary-type-conversion] valid_centers.append(int(center_i)) return ensure_tuple(valid_centers) @@ -800,6 +806,7 @@ def _create_grid_numpy( compute a `spatial_size` mesh with the numpy API. """ spacing = spacing or tuple(1.0 for _ in spatial_size) + # pyrefly: ignore [unnecessary-type-conversion] ranges = [np.linspace(-(d - 1.0) / 2.0 * s, (d - 1.0) / 2.0 * s, int(d)) for d, s in zip(spatial_size, spacing)] coords = np.asarray(np.meshgrid(*ranges, indexing="ij"), dtype=get_equivalent_dtype(dtype, np.ndarray)) if not homogeneous: @@ -822,6 +829,7 @@ def _create_grid_torch( torch.linspace( -(d - 1.0) / 2.0 * s, (d - 1.0) / 2.0 * s, + # pyrefly: ignore [unnecessary-type-conversion] int(d), device=device, dtype=get_equivalent_dtype(dtype, torch.Tensor), @@ -1100,6 +1108,7 @@ def create_translate( backend: APIs to use, ``numpy`` or ``torch``. """ _backend = look_up_option(backend, TransformBackends) + # pyrefly: ignore [unnecessary-type-conversion] spatial_dims = int(spatial_dims) if _backend == TransformBackends.NUMPY: return _create_translate(spatial_dims=spatial_dims, shift=shift, eye_func=np.eye, array_func=np.asarray) @@ -1284,10 +1293,14 @@ def keep_merge_components_with_points( features_neg, _ = label(img_neg_, connectivity=3, return_num=True) outs = np.zeros_like(img_pos_) + # pyrefly: ignore [missing-attribute] for bs in range(point_coords.shape[0]): + # pyrefly: ignore [bad-index] for i, p in enumerate(point_coords[bs]): + # pyrefly: ignore [bad-index] if point_labels[bs, i] in pos_val: features = features_pos + # pyrefly: ignore [bad-index] elif point_labels[bs, i] in neg_val: features = features_neg else: @@ -1422,6 +1435,7 @@ def sample_points_from_label( _point_label = [] for id in label_set: if id in unique_labels: + # pyrefly: ignore [unnecessary-type-conversion] plabels = labels == int(id) nlabels = ~plabels _plabels = get_largest_connected_component_mask(erode(plabels.unsqueeze(0).unsqueeze(0))[0, 0]) @@ -1495,8 +1509,11 @@ def remove_small_objects( raise RuntimeError("Skimage required.") if by_measure: + # pyrefly: ignore [missing-attribute] sr = len(img.shape[1:]) + # pyrefly: ignore [implicit-import] if isinstance(img, monai.data.MetaTensor): + # pyrefly: ignore [missing-attribute] _pixdim = img.pixdim elif pixdim is not None: _pixdim = ensure_tuple_rep(pixdim, sr) @@ -1845,6 +1862,7 @@ def reset_ops_id(data): """find MetaTensors in list or dict `data` and (in-place) set ``TraceKeys.ID`` to ``Tracekeys.NONE``.""" if isinstance(data, (list, tuple)): return [reset_ops_id(d) for d in data] + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): data.applied_operations = reset_ops_id(data.applied_operations) return data @@ -2011,6 +2029,7 @@ def get_transform_backends(): """ backends = {} unique_transforms = [] + # pyrefly: ignore [implicit-import] for n, obj in getmembers(monai.transforms): # skip aliases if obj in unique_transforms: @@ -2202,15 +2221,20 @@ def sync_meta_info(key, data_dict, t: bool = True): # update meta dicts meta_dict_key = PostFix.meta(key) if meta_dict_key not in d: + # pyrefly: ignore [implicit-import] d[meta_dict_key] = monai.data.MetaTensor.get_default_meta() + # pyrefly: ignore [implicit-import] if not isinstance(d[key], monai.data.MetaTensor): + # pyrefly: ignore [implicit-import] d[key] = monai.data.MetaTensor(data_dict[key]) d[key].meta = d[meta_dict_key] d[meta_dict_key].update(d[key].meta) # prefer metatensor's data # update xform info + # pyrefly: ignore [implicit-import] xform_key = monai.transforms.TraceableTransform.trace_key(key) if xform_key not in d: + # pyrefly: ignore [implicit-import] d[xform_key] = monai.data.MetaTensor.get_default_applied_operations() from_meta, from_dict = d[key].applied_operations, d[xform_key] if not from_meta: # avoid [] @@ -2467,6 +2491,7 @@ def has_status_keys(data: torch.Tensor, status_key: Any, default_message: str = _, reasons = has_status_keys(d, status_key, default_message) if reasons is not None: status_key_occurrences.extend(reasons) + # pyrefly: ignore [implicit-import] elif isinstance(data, monai.data.MetaTensor): for op in data.applied_operations: status_key_occurrences.extend(check_applied_operations(op, status_key, default_message)) diff --git a/monai/transforms/utils_pytorch_numpy_unification.py b/monai/transforms/utils_pytorch_numpy_unification.py index 1bc9c206d84..db6ebc26e61 100644 --- a/monai/transforms/utils_pytorch_numpy_unification.py +++ b/monai/transforms/utils_pytorch_numpy_unification.py @@ -478,6 +478,7 @@ def max(x: NdarrayTensor, dim: int | tuple | None = None, **kwargs) -> NdarrayTe else: ret = torch.max(x, int(dim), **kwargs) # type: ignore + # pyrefly: ignore [bad-index] return ret[0] if isinstance(ret, tuple) else ret @@ -544,6 +545,7 @@ def min(x: NdarrayTensor, dim: int | tuple | None = None, **kwargs) -> NdarrayTe else: ret = torch.min(x, int(dim), **kwargs) # type: ignore + # pyrefly: ignore [bad-index] return ret[0] if isinstance(ret, tuple) else ret diff --git a/monai/utils/dist.py b/monai/utils/dist.py index 47da2bee6ed..9c321e464e2 100644 --- a/monai/utils/dist.py +++ b/monai/utils/dist.py @@ -197,5 +197,6 @@ def __init__(self, rank: int | None = None, filter_fn: Callable = lambda rank: r ) self.rank = 0 + # pyrefly: ignore [bad-override] def filter(self, *_args): return self.filter_fn(self.rank) diff --git a/monai/utils/enums.py b/monai/utils/enums.py index be00b27d73e..7be796e6b87 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -390,6 +390,7 @@ def orig_meta(key: str | None = None) -> str: @staticmethod def transforms(key: str | None = None) -> str: + # pyrefly: ignore [unsupported-operation] return PostFix._get_str(key, TraceKeys.KEY_SUFFIX[1:]) diff --git a/monai/utils/misc.py b/monai/utils/misc.py index ed48d4b37d7..01839225d93 100644 --- a/monai/utils/misc.py +++ b/monai/utils/misc.py @@ -324,6 +324,7 @@ def progress_bar(index: int, count: int, desc: str | None = None, bar_len: int = newline: whether to print in a new line for every index. """ end = "\r" if not newline else "\r\n" + # pyrefly: ignore [unnecessary-type-conversion] filled_len = int(bar_len * index // count) bar = f"{desc} " if desc is not None else "" bar += "[" + "=" * filled_len + " " * (bar_len - filled_len) + "]" @@ -365,6 +366,7 @@ def set_determinism( seed_ = torch.default_generator.seed() % MAX_SEED torch.manual_seed(seed_) else: + # pyrefly: ignore [unnecessary-type-conversion] seed = int(seed) % MAX_SEED torch.manual_seed(seed) @@ -416,6 +418,7 @@ def _parse_var(s): d[key] = literal_eval(value) except ValueError: try: + # pyrefly: ignore [unnecessary-type-conversion] d[key] = bool(_strtobool(str(value))) except ValueError: d[key] = value @@ -919,11 +922,13 @@ def is_sqrt(num: Sequence[int] | int) -> bool: def unsqueeze_right(arr: NT, ndim: int) -> NT: """Append 1-sized dimensions to `arr` to create a result with `ndim` dimensions.""" + # pyrefly: ignore [bad-index, missing-attribute] return arr[(...,) + (None,) * (ndim - arr.ndim)] def unsqueeze_left(arr: NT, ndim: int) -> NT: """Prepend 1-sized dimensions to `arr` to create a result with `ndim` dimensions.""" + # pyrefly: ignore [bad-index, missing-attribute] return arr[(None,) * (ndim - arr.ndim)] diff --git a/monai/utils/module.py b/monai/utils/module.py index a2569b19fed..b1598cd1eda 100644 --- a/monai/utils/module.py +++ b/monai/utils/module.py @@ -545,6 +545,7 @@ def version_leq(lhs: str, rhs: str) -> bool: """ + # pyrefly: ignore [unnecessary-type-conversion] lhs, rhs = str(lhs), str(rhs) pkging, has_ver = optional_import("packaging.version") if has_ver: @@ -572,6 +573,7 @@ def version_geq(lhs: str, rhs: str) -> bool: rhs: version name to compare with `lhs`, return True if earlier or equal to `lhs`. """ + # pyrefly: ignore [unnecessary-type-conversion] lhs, rhs = str(lhs), str(rhs) pkging, has_ver = optional_import("packaging.version") @@ -623,6 +625,7 @@ def pytorch_after(major: int, minor: int, patch: int = 0, current_ver_string: st c_major, c_minor = get_torch_version_tuple() c_patch = "0" c_mn = int(c_major), int(c_minor) + # pyrefly: ignore [unnecessary-type-conversion] mn = int(major), int(minor) if c_mn != mn: return c_mn > mn @@ -634,6 +637,7 @@ def pytorch_after(major: int, minor: int, patch: int = 0, current_ver_string: st c_p = int(p_reg.group()) except (AttributeError, TypeError, ValueError): is_prerelease = True + # pyrefly: ignore [unnecessary-type-conversion] patch = int(patch) if c_p != patch: return c_p > patch @@ -679,5 +683,6 @@ def compute_capabilities_after(major: int, minor: int = 0, current_ver_string: s parts += ["0"] c_major, c_minor = parts[:2] c_mn = int(c_major), int(c_minor) + # pyrefly: ignore [unnecessary-type-conversion] mn = int(major), int(minor) return c_mn > mn diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index b5dfb580c5a..b36c0d13b0f 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -46,6 +46,7 @@ def get_numpy_dtype_from_string(dtype: str) -> np.dtype: """Get a numpy dtype (e.g., `np.float32`) from its string (e.g., `"float32"`).""" + # pyrefly: ignore [unnecessary-type-conversion] return np.empty([], dtype=str(dtype).split(".")[-1]).dtype @@ -149,8 +150,11 @@ def _convert_tensor(tensor: Any, **kwargs: Any) -> Any: # if input data is not Tensor, convert it to Tensor first tensor = torch.as_tensor(tensor, **kwargs) + # pyrefly: ignore [implicit-import] if track_meta and not isinstance(tensor, monai.data.MetaTensor): + # pyrefly: ignore [implicit-import] return monai.data.MetaTensor(tensor) + # pyrefly: ignore [implicit-import] if not track_meta and isinstance(tensor, monai.data.MetaTensor): return tensor.as_tensor() return tensor @@ -320,7 +324,9 @@ def convert_data_type( """ orig_type: type + # pyrefly: ignore [implicit-import] if isinstance(data, monai.data.MetaTensor): + # pyrefly: ignore [implicit-import] orig_type = monai.data.MetaTensor elif isinstance(data, torch.Tensor): orig_type = torch.Tensor @@ -333,11 +339,14 @@ def convert_data_type( orig_device = data.device if isinstance(data, torch.Tensor) else None + # pyrefly: ignore [bad-assignment] output_type = output_type or orig_type dtype_ = get_equivalent_dtype(dtype, output_type) data_: NdarrayTensor + # pyrefly: ignore [bad-argument-type] if issubclass(output_type, torch.Tensor): + # pyrefly: ignore [implicit-import] track_meta = issubclass(output_type, monai.data.MetaTensor) data_ = convert_to_tensor( data, dtype=dtype_, device=device, wrap_sequence=wrap_sequence, track_meta=track_meta, safe=safe @@ -386,8 +395,11 @@ def convert_to_dst_type( copy_meta = False output_type: Any + # pyrefly: ignore [implicit-import] if isinstance(dst, monai.data.MetaTensor): + # pyrefly: ignore [implicit-import] output_type = monai.data.MetaTensor + # pyrefly: ignore [implicit-import] if not isinstance(src, monai.data.MetaTensor): copy_meta = True # converting a non-meta tensor to a meta tensor, probably take the metadata as well. elif isinstance(dst, torch.Tensor): @@ -400,6 +412,7 @@ def convert_to_dst_type( output, _type, _device = convert_data_type( data=src, output_type=output_type, device=device, dtype=dtype, wrap_sequence=wrap_sequence, safe=safe ) + # pyrefly: ignore [implicit-import] if copy_meta and isinstance(output, monai.data.MetaTensor): output.copy_meta_from(dst) return output, _type, _device diff --git a/monai/visualize/img2tensorboard.py b/monai/visualize/img2tensorboard.py index 30fd4560433..a46b725113d 100644 --- a/monai/visualize/img2tensorboard.py +++ b/monai/visualize/img2tensorboard.py @@ -172,6 +172,7 @@ def plot_2d_or_3d_image( max_frames: if plot 3D RGB image as video in TensorBoardX, set the FPS to `max_frames`. tag: tag of the plotted image on TensorBoard. """ + # pyrefly: ignore [bad-index] data_index = data[index] # as the `d` data has no batch dim, reduce the spatial dim index if positive frame_dim = frame_dim - 1 if frame_dim > 0 else frame_dim diff --git a/monai/visualize/visualizer.py b/monai/visualize/visualizer.py index 023e4444062..1f7c7e3eda0 100644 --- a/monai/visualize/visualizer.py +++ b/monai/visualize/visualizer.py @@ -11,7 +11,7 @@ from __future__ import annotations -from collections.abc import Callable, Sized +from collections.abc import Callable, Sequence import torch import torch.nn.functional as F @@ -21,7 +21,9 @@ __all__ = ["default_upsampler"] -def default_upsampler(spatial_size: Sized, align_corners: bool = False) -> Callable[[torch.Tensor], torch.Tensor]: +def default_upsampler( + spatial_size: Sequence[int], align_corners: bool = False +) -> Callable[[torch.Tensor], torch.Tensor]: """ A linear interpolation method for upsampling the feature map. The output of this function is a callable `func`, @@ -32,6 +34,6 @@ def up(x): linear_mode = [InterpolateMode.LINEAR, InterpolateMode.BILINEAR, InterpolateMode.TRILINEAR] interp_mode = linear_mode[len(spatial_size) - 1] smode = str(interp_mode.value) - return F.interpolate(x, size=spatial_size, mode=smode, align_corners=align_corners) # type: ignore + return F.interpolate(x, size=spatial_size, mode=smode, align_corners=align_corners) return up diff --git a/pyproject.toml b/pyproject.toml index 325622b66a7..51d5f2fe3e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,11 +20,9 @@ exclude = ''' \.eggs | \.git | \.hg - | \.mypy_cache | \.tox | \.venv | venv - | \.pytype | _build | buck-out | build @@ -85,34 +83,55 @@ extend-ignore = [ [tool.ruff.lint.mccabe] max-complexity = 50 # todo lower this treshold when yesqa id replaced with Ruff's RUF100 -[tool.pytype] -# Space-separated list of files or directories to exclude. -exclude = ["versioneer.py", "_version.py"] -# Space-separated list of files or directories to process. -inputs = ["monai"] -# Keep going past errors to analyze as many files as possible. -keep_going = true -# Run N jobs in parallel. -jobs = 8 -# All pytype output goes here. -output = ".pytype" -# Paths to source code directories, separated by ':'. -pythonpath = "." -# Check attribute values against their annotations. -check_attribute_types = true -# Check container mutations against their annotations. -check_container_types = true -# Check parameter defaults and assignments against their annotations. -check_parameter_types = true -# Check variable values against their annotations. -check_variable_types = true -# Comma or space separated list of error names to ignore. -disable = ["pyi-error"] -# Report errors. -report_errors = true -# Experimental: Infer precise return types even for invalid function calls. -precise_return = true -# Experimental: solve unknown types to label with structural types. -protocols = true -# Experimental: Only load submodules that are explicitly imported. -strict_import = false +[tool.pyrefly] +# Check only the monai package +project-includes = ["monai/"] + +# Exclude auto-generated and vendored files +project-excludes = [ + "**/venv/**", + "**/.venv/**", + "versioneer.py", + "monai/_version.py", +] + +# Match CI environment +python-version = "3.9" +python-platform = "linux" + +# "legacy" preset provides a smooth migration from previous type checkers +preset = "legacy" + +# Check unannotated defs (previously enforced in mypy config) +check-unannotated-defs = true + +[tool.pyrefly.errors] +# Ignore missing imports +missing-import = "ignore" + +# Suppress unused-ignore warnings +unused-ignore = "ignore" + +# Suppress implicit-import globally (MONAI style uses lazy imports) +implicit-import = "ignore" + +# Downgrade errors in unannotated/dynamic code to warnings +# (pre-existing issues, not new — will fix incrementally) +bad-assignment = "warn" +bad-return = "warn" +bad-argument-type = "warn" +invalid-annotation = "ignore" +not-iterable = "warn" +not-callable = "warn" +bad-index = "warn" + +# Pre-existing errors not flagged by previous type checkers +# Suppress for a smooth migration; fix incrementally +missing-attribute = "ignore" +bad-override = "ignore" +no-matching-overload = "ignore" +unsupported-operation = "ignore" +missing-module-attribute = "ignore" +not-a-type = "ignore" +invalid-yield = "ignore" +deprecated = "ignore" diff --git a/requirements-dev.txt b/requirements-dev.txt index b2c36f8de62..c46648006bf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ ruff>=0.14.11,<0.15 pybind11 setuptools<71 # pkg_resources removed in setuptools>=71; needed by MetricsReloaded setup.py types-setuptools -mypy>=1.5.0, <1.12.0 +pyrefly>=1.0.0 ninja torchio torchvision diff --git a/runtests.sh b/runtests.sh index 431e9298c35..d48bc96f417 100755 --- a/runtests.sh +++ b/runtests.sh @@ -49,7 +49,7 @@ doRuffFix=false doClangFormat=false doCopyRight=false doPytypeFormat=false -doMypyFormat=false +doPyreflyFormat=false doCleanup=false doDistTests=false doPrecommit=false @@ -61,7 +61,7 @@ PY_EXE=${MONAI_PY_EXE:-$(which python)} function print_usage { echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--pylint] [--ruff]" - echo " [--clangformat] [--precommit] [--pytype] [-j number] [--mypy]" + echo " [--clangformat] [--precommit] [--pytype] [-j number] [--pyrefly]" echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--build] [--list_tests]" echo " [--dryrun] [--copyright] [--clean] [--help] [--version] [--path] [--formatfix]" echo "" @@ -87,9 +87,9 @@ function print_usage { echo " --precommit : perform source code format check and fix using \"pre-commit\"" echo "" echo "Python type check options:" - echo " --pytype : perform \"pytype\" static type checks" - echo " -j, --jobs : number of parallel jobs to run \"pytype\" (default $NUM_PARALLEL)" - echo " --mypy : perform \"mypy\" static type checks" + echo " --pytype : perform \"pytype\" static type checks (deprecated, may be removed in future)" + echo " -j, --jobs : number of parallel jobs to run \"pytype\" (default $NUM_PARALLEL) (deprecated)" + echo " --pyrefly : perform \"pyrefly\" static type checks" echo "" echo "MONAI unit testing options:" echo " -u, --unittests : perform unit testing" @@ -196,8 +196,8 @@ function clean_py { find ${TO_CLEAN} -depth -maxdepth 1 -type d -name "monai.egg-info" -exec rm -r "{}" + find ${TO_CLEAN} -depth -maxdepth 1 -type d -name "build" -exec rm -r "{}" + find ${TO_CLEAN} -depth -maxdepth 1 -type d -name "dist" -exec rm -r "{}" + - find ${TO_CLEAN} -depth -maxdepth 1 -type d -name ".mypy_cache" -exec rm -r "{}" + find ${TO_CLEAN} -depth -maxdepth 1 -type d -name ".pytype" -exec rm -r "{}" + + find ${TO_CLEAN} -depth -maxdepth 1 -type d -name ".pyrefly_cache" -exec rm -r "{}" + find ${TO_CLEAN} -depth -maxdepth 1 -type d -name ".coverage" -exec rm -r "{}" + find ${TO_CLEAN} -depth -maxdepth 1 -type d -name "__pycache__" -exec rm -r "{}" + } @@ -271,6 +271,7 @@ do doIsortFormat=true # doPylintFormat=true # https://github.com/Project-MONAI/MONAI/issues/7094 doRuffFormat=true + doPyreflyFormat=true doCopyRight=true ;; --disttests) @@ -314,10 +315,11 @@ do doPrecommit=true ;; --pytype) + echo "${yellow}WARNING: --pytype is deprecated and may be removed in a future release.${noColor}" doPytypeFormat=true ;; - --mypy) - doMypyFormat=true + --pyrefly) + doPyreflyFormat=true ;; -j|--jobs) NUM_PARALLEL=$2 @@ -611,7 +613,9 @@ fi if [ $doPytypeFormat = true ] then set +e # disable exit on failure so that diagnostics can be given on failure + echo "${yellow}WARNING: pytype is deprecated and may be removed in a future release.${noColor}" echo "${separator}${blue}pytype${noColor}" + # ensure that the necessary packages for code format testing are installed if ! is_pip_installed pytype then @@ -639,26 +643,27 @@ then fi -if [ $doMypyFormat = true ] +if [ $doPyreflyFormat = true ] then set +e # disable exit on failure so that diagnostics can be given on failure - echo "${separator}${blue}mypy${noColor}" + echo "${separator}${blue}pyrefly${noColor}" # ensure that the necessary packages for code format testing are installed - if ! is_pip_installed mypy + if ! is_pip_installed pyrefly then install_deps fi - ${cmdPrefix}"${PY_EXE}" -m mypy --version - ${cmdPrefix}"${PY_EXE}" -m mypy "$homedir" + ${cmdPrefix}"${PY_EXE}" -m pyrefly --version + # Run without file arguments to respect project-includes/excludes from pyproject.toml + ${cmdPrefix}"${PY_EXE}" -m pyrefly check - mypy_status=$? - if [ ${mypy_status} -ne 0 ] + pyrefly_status=$? + if [ ${pyrefly_status} -ne 0 ] then - : # mypy output already follows format - exit ${mypy_status} + echo "${red}failed!${noColor}" + exit ${pyrefly_status} else - : # mypy output already follows format + echo "${green}passed!${noColor}" fi set -e # enable exit on failure fi diff --git a/setup.cfg b/setup.cfg index d987141d0b8..8c085da8ec6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -199,56 +199,6 @@ versionfile_build = monai/_version.py tag_prefix = parentdir_prefix = -[mypy] -# Suppresses error messages about imports that cannot be resolved. -ignore_missing_imports = True -# Changes the treatment of arguments with a default value of None by not implicitly making their type Optional. -no_implicit_optional = True -# Warns about casting an expression to its inferred type. -warn_redundant_casts = True -# No error on unneeded # type: ignore comments. -warn_unused_ignores = False -# Shows a warning when returning a value with type Any from a function declared with a non-Any return type. -warn_return_any = True -# Prohibit equality checks, identity checks, and container checks between non-overlapping types. -strict_equality = True -# Shows column numbers in error messages. -show_column_numbers = True -# Shows error codes in error messages. -show_error_codes = True -# Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location markers. -pretty = False -# Warns about per-module sections in the config file that do not match any files processed when invoking mypy. -warn_unused_configs = True -# Make arguments prepended via Concatenate be truly positional-only. -extra_checks = True -# Allows variables to be redefined with an arbitrary type, -# as long as the redefinition is in the same block and nesting level as the original definition. -# allow_redefinition = True - -exclude = venv/ - -[mypy-versioneer] -# Ignores all non-fatal errors. -ignore_errors = True - -[mypy-monai._version] -# Ignores all non-fatal errors. -ignore_errors = True - -[mypy-monai.eggs] -# Ignores all non-fatal errors. -ignore_errors = True - -[mypy-monai.*] -# Also check the body of functions with no types in their type signature. -check_untyped_defs = True -# Warns about usage of untyped decorators. -disallow_untyped_decorators = True - -[mypy-monai.visualize.*,monai.utils.*,monai.optimizers.*,monai.losses.*,monai.inferers.*,monai.config.*,monai._extensions.*,monai.fl.*,monai.engines.*,monai.handlers.*,monai.auto3dseg.*,monai.bundle.*,monai.metrics.*,monai.apps.*] -disallow_incomplete_defs = True - [coverage:run] concurrency = multiprocessing source = . diff --git a/tests/apps/test_download_url_yandex.py b/tests/apps/test_download_url_yandex.py index b29119bf074..54d39b06ff7 100644 --- a/tests/apps/test_download_url_yandex.py +++ b/tests/apps/test_download_url_yandex.py @@ -18,6 +18,10 @@ from monai.apps.utils import download_url +YANDEX_MODEL_URL = ( + "https://cloud-api.yandex.net/v1/disk/public/resources/download?" + "public_key=https%3A%2F%2Fdisk.yandex.ru%2Fd%2Fxs0gzlj2_irgWA" +) YANDEX_MODEL_FLAWED_URL = ( "https://cloud-api.yandex.net/v1/disk/public/resources/download?" "public_key=https%3A%2F%2Fdisk.yandex.ru%2Fd%2Fxs0gzlj2_irgWA-url-with-error" @@ -26,6 +30,11 @@ class TestDownloadUrlYandex(unittest.TestCase): + @unittest.skip("data source unstable") + def test_verify(self): + with tempfile.TemporaryDirectory() as tempdir: + download_url(url=YANDEX_MODEL_URL, filepath=os.path.join(tempdir, "model.pt")) + def test_verify_error(self): with tempfile.TemporaryDirectory() as tempdir: with self.assertRaises(HTTPError): diff --git a/tests/losses/test_dice_loss.py b/tests/losses/test_dice_loss.py index d8fb5e11957..66c038783a6 100644 --- a/tests/losses/test_dice_loss.py +++ b/tests/losses/test_dice_loss.py @@ -104,6 +104,11 @@ }, 1.534853, ], + [ # shape: (1, 1, 2, 2), (1, 1, 2, 2) + {"include_background": True, "sigmoid": True, "smooth_nr": 1e-6, "smooth_dr": 1e-6}, + {"input": torch.tensor([[[[1.0, -1.0], [-1.0, 1.0]]]]), "target": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]])}, + 0.307576, + ], [ # shape: (1, 1, 2, 2), (1, 1, 2, 2) {"include_background": True, "sigmoid": True, "squared_pred": True}, {"input": torch.tensor([[[[1.0, -1.0], [-1.0, 1.0]]]]), "target": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]])}, diff --git a/tests/losses/test_generalized_dice_loss.py b/tests/losses/test_generalized_dice_loss.py index 7d60b99932e..8549e874822 100644 --- a/tests/losses/test_generalized_dice_loss.py +++ b/tests/losses/test_generalized_dice_loss.py @@ -112,6 +112,11 @@ }, 0.0, ], + [ # shape: (1, 1, 2, 2), (1, 1, 2, 2) + {"include_background": True, "sigmoid": True, "smooth_nr": 1e-6, "smooth_dr": 1e-6}, + {"input": torch.tensor([[[[1.0, -1.0], [-1.0, 1.0]]]]), "target": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]])}, + 0.307576, + ], [ # shape: (1, 2, 4), (1, 1, 4) { "include_background": True,