diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index ae3694f276..9412fd2f3e 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: | @@ -259,7 +259,7 @@ jobs: cache: 'pip' - name: Install dependencies run: | - python -m pip install --user --upgrade pip setuptools wheel twine packaging + python -m pip install --user --upgrade pip "setuptools<70" wheel twine packaging # install the latest pytorch for testing # however, "pip install monai*.tar.gz" will build cpp/cuda with an isolated # fresh torch installation according to pyproject.toml @@ -291,8 +291,8 @@ jobs: - name: Install wheel file working-directory: ${{ steps.mktemp.outputs.tmp_dir }} run: | - # install from wheel - python -m pip install monai*.whl --extra-index-url https://download.pytorch.org/whl/cpu + # install from wheel (use --no-build-isolation to keep system setuptools with pkg_resources) + python -m pip install monai*.whl --no-build-isolation --extra-index-url https://download.pytorch.org/whl/cpu python -c 'import monai; monai.config.print_config()' 2>&1 | grep -iv "unknown" python -c 'import monai; print(monai.__file__)' python -m pip uninstall -y monai @@ -302,6 +302,6 @@ jobs: run: | for name in *.tar.gz; do break; done echo $name - python -m pip install ${name}[all] --extra-index-url https://download.pytorch.org/whl/cpu + python -m pip install ${name}[all] --no-build-isolation --extra-index-url https://download.pytorch.org/whl/cpu python -c 'import monai; monai.config.print_config()' 2>&1 | grep -iv "unknown" python -c 'import monai; print(monai.__file__)' diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 3bdfe12715..9f384117e4 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/pythonapp.yml b/.github/workflows/pythonapp.yml new file mode 100644 index 0000000000..0c9b9126d6 --- /dev/null +++ b/.github/workflows/pythonapp.yml @@ -0,0 +1,206 @@ +# Jenkinsfile.monai-premerge +name: premerge + +permissions: + contents: read + +on: + # quick tests for pull requests and the releasing branches + push: + branches: + - dev + - main + - releasing/* + pull_request: + head_ref-ignore: + - dev + +concurrency: + # automatically cancel the previously triggered workflows when there's a newer version + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + # caching of these jobs: + # - docker-py3-pip- (shared) + # - ubuntu py37 pip- + # - os-latest-pip- (shared) + flake8-py3: + runs-on: ubuntu-latest + strategy: + matrix: + opt: ["codeformat", "pyrefly"] + steps: + - uses: actions/checkout@v6 + - name: Set up Python ${{ env.PYTHON_VER1 || '3.10' }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VER1 || '3.10' }} + cache: 'pip' + - name: Install dependencies + run: | + find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; + python -m pip install --upgrade pip wheel + python -m pip install --no-build-isolation -r requirements-dev.txt + - name: Lint and type check + run: | + # clean up temporary files + $(pwd)/runtests.sh --build --clean + $(pwd)/runtests.sh --build --${{ matrix.opt }} + + quick-py3: # full dependencies installed tests for different OS + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macOS-latest, ubuntu-latest] + timeout-minutes: 120 + steps: + - if: runner.os == 'windows' + name: Config pagefile (Windows only) + uses: al-cheb/configure-pagefile-action@v1.4 + with: + minimum-size: 8GB + maximum-size: 16GB + disk-root: "D:" + - uses: actions/checkout@v6 + - name: Set up Python ${{ env.PYTHON_VER1 || '3.10' }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VER1 || '3.10' }} + cache: 'pip' + - name: Prepare pip wheel + run: | + which python + python -m pip install --upgrade pip wheel + - if: runner.os == 'windows' + name: Install torch cpu from pytorch.org (Windows only) + run: | + python -m pip install torch==${{ env.PYTORCH_VER1 || '2.8.0' }} torchvision==${{ env.TORCHVISION_VER1 || '0.23.0' }}+cpu --index-url https://download.pytorch.org/whl/cpu + shell: bash + - if: runner.os == 'Linux' + name: Install itk pre-release (Linux only) + run: | + python -m pip install --pre -U itk + find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; + - name: Install the dependencies + run: | + python -m pip install --user --upgrade pip wheel + python -m pip install torch==${{ env.PYTORCH_VER1 || '2.8.0' }} torchvision==${{ env.TORCHVISION_VER1 || '0.23.0' }} + cat "requirements-dev.txt" + python -m pip install --no-build-isolation -r requirements-dev.txt + python -m pip list + BUILD_MONAI=0 python -m pip install -e . # test no compile installation + shell: bash + - name: Run compiled (${{ runner.os }}) + run: | + python -m pip uninstall -y monai + BUILD_MONAI=1 python -m pip install -e . # compile the cpp extensions + shell: bash + - name: Run quick tests (CPU ${{ runner.os }}) + run: | + python -c 'import torch; print(torch.__version__); print(torch.rand(5,3))' + python -c "import monai; monai.config.print_config()" + python -m unittest -v + env: + QUICKTEST: True + PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python # https://github.com/Project-MONAI/MONAI/issues/4354 + + packaging: + runs-on: ubuntu-latest + env: + QUICKTEST: True + shell: bash + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Set up Python ${{ env.PYTHON_VER1 || '3.10' }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VER1 || '3.10' }} + cache: 'pip' + - name: Install dependencies + run: | + find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; + python -m pip install --user --upgrade pip "setuptools<70" wheel twine packaging + # install the latest pytorch for testing + # however, "pip install monai*.tar.gz" will build cpp/cuda with an isolated + # fresh torch installation according to pyproject.toml + python -m pip install torch==${{ env.PYTORCH_VER1 || '2.8.0' }} torchvision --extra-index-url https://download.pytorch.org/whl/cpu + - name: Check packages + run: | + pip uninstall monai + pip list | grep -iv monai + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + set -e + + # build tar.gz and wheel + python setup.py check -m -s + python setup.py sdist bdist_wheel + python -m twine check dist/* + - run: echo "pwd=$PWD" >> $GITHUB_OUTPUT + id: root + - run: echo "tmp_dir=$(mktemp -d)" >> $GITHUB_OUTPUT + id: mktemp + - name: Move packages + run: | + printf ${{ steps.root.outputs.pwd }} + printf ${{ steps.mktemp.outputs.tmp_dir }} + # move packages to a temp dir + cp dist/monai* "${{ steps.mktemp.outputs.tmp_dir }}" + rm -r build dist monai.egg-info + cd "${{ steps.mktemp.outputs.tmp_dir }}" + ls -al + - name: Install wheel file + working-directory: ${{ steps.mktemp.outputs.tmp_dir }} + run: | + # install from wheel (use --no-build-isolation to keep system setuptools with pkg_resources) + python -m pip install monai*.whl --no-build-isolation + python -c 'import monai; monai.config.print_config()' 2>&1 | grep -iv "unknown" + python -c 'import monai; print(monai.__file__)' + python -m pip uninstall -y monai + rm monai*.whl + - name: Install source archive + working-directory: ${{ steps.mktemp.outputs.tmp_dir }} + run: | + # install from tar.gz (use --no-build-isolation to keep system setuptools with pkg_resources) + name=$(ls *.tar.gz | head -n1) + echo $name + python -m pip install $name[all] --no-build-isolation + python -c 'import monai; monai.config.print_config()' 2>&1 | grep -iv "unknown" + python -c 'import monai; print(monai.__file__)' + - name: Quick test + working-directory: ${{ steps.mktemp.outputs.tmp_dir }} + run: | + # run min tests + cp ${{ steps.root.outputs.pwd }}/requirements*.txt . + cp -r ${{ steps.root.outputs.pwd }}/tests . + ls -al + python -m pip install --no-build-isolation -r requirements-dev.txt + python -m unittest -v + env: + PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python # https://github.com/Project-MONAI/MONAI/issues/4354 + + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Set up Python ${{ env.PYTHON_VER1 || '3.10' }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VER1 || '3.10' }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel + python -m pip install -r docs/requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu + - name: Make html + run: | + cd docs/ + make clean + make html 2>&1 | tee tmp_log + if [[ $(grep -c "ERROR:" tmp_log) != 0 ]]; then echo "found errors"; grep "ERROR:" tmp_log; exit 1; fi + sed '/WARNING.*pip/d' tmp_log > tmp_log1; mv tmp_log1 tmp_log # monai#7133 + if [[ $(grep -c "WARNING:" tmp_log) != 0 ]]; then echo "found warnings"; grep "WARNING:" tmp_log; exit 1; fi + shell: bash diff --git a/.github/workflows/weekly-preview.yml b/.github/workflows/weekly-preview.yml index 6a2d07386f..5c6fabec12 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 76c6ab0d12..9d01fa7050 100644 --- a/.gitignore +++ b/.gitignore @@ -107,15 +107,11 @@ venv.bak/ # mkdocs documentation /site -# pytype cache -.pytype/ - -# mypy -.mypy_cache/ +# pyrefly cache +.pyrefly_cache/ examples/scd_lvsegs.npz temp/ .idea/ -.dmypy.json *~ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ad171abb1..a8db8db0a4 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 8421893f51..ad09c24e60 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 75da66d43c..a210832831 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 eaf1f14c7f..cab9995eca 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 d2f89d2eea..d15b2bec3c 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 e597188e74..0d1c11b119 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 d92a79a16a..624eed342d 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 95b29b8285..9b9bf26911 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 301a636b6c..635506c08a 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 fa714daad1..54bbc8fd31 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 fcb6b842d7..81226cc3bd 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 4c8dcf5d45..3911b207a2 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 39f8459224..90ffff6c34 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 547e73332f..8265d6f915 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 1278eacd56..bd8c3f1637 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 6b6542308a..0fb4457de2 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 1e2540daee..b1dde44ec1 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 f023cdbc87..05f7074b8c 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 23fdb66d02..18daadb849 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 bd7fb19493..400071b7de 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) @@ -208,8 +210,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 404a6d326e..7b64a04a6c 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 14a10635df..8fdd965245 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 b55c62174b..27f34ebd5e 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 ab02cd552e..376d9da6b2 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 5b95441d51..0367047b58 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 b0c41b5d7d..2bdb0d75a5 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 2511ce2219..9664a3eb9a 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -850,6 +850,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 @@ -869,6 +870,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: @@ -1088,6 +1090,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) @@ -1654,6 +1657,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 4403855030..a3bf99a178 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 689138179a..3c41683ce9 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 a85eb95c20..8e05c175f7 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/meta_tensor.py b/monai/data/meta_tensor.py index 12bd76ba60..757cafd9b3 100644 --- a/monai/data/meta_tensor.py +++ b/monai/data/meta_tensor.py @@ -498,12 +498,15 @@ def peek_pending_affine(self): if next_matrix is None: continue res = convert_to_dst_type(res, next_matrix)[0] + # pyrefly: ignore [implicit-import] next_matrix = monai.data.utils.to_affine_nd(r, next_matrix) + # pyrefly: ignore [implicit-import] res = monai.transforms.lazy.utils.combine_transforms(res, next_matrix) return res def peek_pending_rank(self): a = self.pending_operations[-1].get(LazyAttr.AFFINE, None) if self.pending_operations else self.affine + # pyrefly: ignore [unnecessary-type-conversion] return 1 if a is None else int(max(1, len(a) - 1)) def new_empty(self, size, dtype=None, device=None, requires_grad=False): # type: ignore[override] @@ -568,6 +571,7 @@ def ensure_torch_and_prune_meta( remove_extra_metadata(meta) # bc-breaking if pattern is not None: + # pyrefly: ignore [implicit-import] meta = monai.transforms.DeleteItemsd(keys=pattern, sep=sep, use_re=True)(meta) # return the `MetaTensor` diff --git a/monai/data/utils.py b/monai/data/utils.py index d548ed7248..8e9feec9a8 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -722,8 +722,11 @@ def affine_to_spacing(affine: NdarrayTensor, r: int = 3, dtype=float, suppress_z Returns: an `r` dimensional vector of spacing. """ + # pyrefly: ignore [bad-index, missing-attribute] if len(affine.shape) != 2 or affine.shape[0] != affine.shape[1]: + # pyrefly: ignore [missing-attribute] raise ValueError(f"affine must be a square matrix, got {affine.shape}.") + # pyrefly: ignore [bad-index] _affine, *_ = convert_to_dst_type(affine[:r, :r], dst=affine, dtype=dtype) if isinstance(_affine, torch.Tensor): spacing = torch.sqrt(torch.sum(_affine * _affine, dim=0)) @@ -1490,6 +1493,7 @@ def orientation_ras_lps(affine: NdarrayTensor) -> NdarrayTensor: Args: affine: a 2D affine matrix. """ + # pyrefly: ignore [missing-attribute] sr = max(affine.shape[0] - 1, 1) # spatial rank is at least 1 flip_d = [[-1, 1], [-1, -1, 1], [-1, -1, 1, 1]] flip_diag = flip_d[min(sr - 1, 2)] + [1] * (sr - 3) diff --git a/monai/data/wsi_datasets.py b/monai/data/wsi_datasets.py index 2ee8c9d363..0fc9a79ac2 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 b377234d10..8a465c1197 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 62d5f83847..2748ca3450 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 921d54a59c..1f0c75620f 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 6e9a6fd1fe..2c2fb87227 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 3078d89f97..2ea54cc06a 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 02975039b3..afbd484026 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 ee94b1ebdb..9e4b09d36c 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 de53108d1d..51872f7782 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 b4558f930c..440ffb0dc6 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 37c78fae60..91f5250f8e 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: @@ -238,7 +240,10 @@ def __init__( if self.kernel_type == "gaussian": self.preterm = 1 / (2 * sigma**2) 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 206bc83bc6..965930b046 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 154f34c526..834218ddcd 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 3070764e06..4071303b2c 100644 --- a/monai/metrics/utils.py +++ b/monai/metrics/utils.py @@ -216,6 +216,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 a8dfcd7df3..a1a46a7a05 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 d3ac7cf04b..baee1ca12e 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 b8b3802bb9..443254eae9 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 665e9020ff..44a74bd5af 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 6ce9979a80..8d29e8aaa4 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 e9b7675144..03311115c3 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 c27b0fc17b..bc244ba6e7 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 a31f105110..59fb0e514a 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 6c40cae2aa..dfe71dd4ba 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 232b8c1c11..2fe62825b3 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 43ba48585c..690bf48de0 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 f56c39dcd1..61d63544b0 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 96c1412fea..07c4c0f309 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 9ca612fc56..5d3c5504e1 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 6c3fc104c9..9f9ade6a7c 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 b23fbac7d9..fd57d108fa 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( @@ -979,6 +984,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] = [] @@ -1129,6 +1135,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, @@ -1318,6 +1325,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 510ff72938..78bc19494b 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 @@ -1100,6 +1100,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, @@ -1262,6 +1263,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/croppad/functional.py b/monai/transforms/croppad/functional.py index acf42849d3..a4d256232d 100644 --- a/monai/transforms/croppad/functional.py +++ b/monai/transforms/croppad/functional.py @@ -114,6 +114,7 @@ def pad_nd( if any(k in str(err) for k in ("supported", "unexpected keyword", "implemented", "value")): return _np_pad(img, pad_width=to_pad, mode=mode, **kwargs) raise ValueError( + # pyrefly: ignore [missing-attribute] f"{img.shape} {to_pad} {mode} {kwargs} {img.dtype} {img.device if isinstance(img, torch.Tensor) else None}" ) from err @@ -143,6 +144,7 @@ def crop_or_pad_nd(img: torch.Tensor, translation_mat, spatial_size: tuple[int, for s, e, sp in zip(src_start, src_end, img.shape[1:]): do_pad, do_crop = do_pad or s < 0 or e > sp - 1, do_crop or s > 0 or e < sp - 1 to_pad += [(0 if s >= 0 else int(-s), 0 if e < sp - 1 else int(e - sp + 1))] + # pyrefly: ignore [unnecessary-type-conversion] to_crop += [slice(int(max(s, 0)), int(e + 1 + to_pad[-1][0]))] if do_pad: _mode = _convert_pt_pad_mode(mode) @@ -188,6 +190,7 @@ def pad_func( spatial_rank = img.peek_pending_rank() if isinstance(img, MetaTensor) else 3 do_pad = np.asarray(to_pad).any() if do_pad: + # pyrefly: ignore [unnecessary-type-conversion] to_pad_list = [(int(p[0]), int(p[1])) for p in to_pad] if len(to_pad_list) < len(img.shape): to_pad_list += [(0, 0)] * (len(img.shape) - len(to_pad_list)) diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 23a57ae9fb..332f6d6eda 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -113,6 +113,7 @@ def __init__( self.noise: np.ndarray | None = None self.sample_std = sample_std + # pyrefly: ignore [bad-override] def randomize(self, img: NdarrayOrTensor, mean: float | None = None) -> None: super().randomize(None) if not self._do_transform: diff --git a/monai/transforms/io/dictionary.py b/monai/transforms/io/dictionary.py index 4927450c7d..f4108a9a28 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 75f1e3529d..30b31cd269 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/spatial/array.py b/monai/transforms/spatial/array.py index 420c8c8d8e..dd54ca5bc4 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -3376,6 +3376,7 @@ def __call__(self, array: NdarrayOrTensor) -> MetaTensor: # create the patch iterator which sweeps the image row-by-row patch_iterator = iter_patch( array, + # pyrefly: ignore [bad-argument-type] patch_size=(None,) + self.patch_size, # expand to have the channel dim start_pos=(0,) + self.offset, # expand to have the channel dim overlap=self.overlap, diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index d976e27916..b14b4b81e6 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -203,6 +203,7 @@ def spatial_resample( if isinstance(mode, int) or _use_compiled: dst_xform = create_translate(spatial_rank, [float(d - 1) / 2 for d in spatial_size]) xform = xform @ convert_to_dst_type(dst_xform, xform)[0] + # pyrefly: ignore [implicit-import] affine_xform = monai.transforms.Affine( affine=xform, spatial_size=spatial_size, @@ -291,6 +292,7 @@ def flip(img, sp_axes, lazy, transform_info): sp_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] sp_size = convert_to_numpy(sp_size, wrap_sequence=True).tolist() extra_info = {"axes": sp_axes} # track the spatial axes + # pyrefly: ignore [implicit-import] axes = monai.transforms.utils.map_spatial_axes(img.ndim, sp_axes) # use the axes with channel dim rank = img.peek_pending_rank() if isinstance(img, MetaTensor) else torch.tensor(3.0, dtype=torch.double) # axes include the channel dim @@ -632,6 +634,7 @@ def affine_func( "do_resampling": do_resampling, "align_corners": resampler.align_corners, } + # pyrefly: ignore [implicit-import] affine = monai.transforms.Affine.compute_w_affine(rank, affine, img_size, sp_size) meta_info = TraceableTransform.track_transform_meta( img, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 40f95d47d6..1ce285a9a3 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/array.py b/monai/transforms/utility/array.py index ed4b149e6b..16a1f81d1b 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -410,6 +410,7 @@ def __init__( self.dtype = dtype self.device = device self.wrap_sequence = wrap_sequence + # pyrefly: ignore [unnecessary-type-conversion] self.track_meta = get_track_meta() if track_meta is None else bool(track_meta) def __call__(self, img: NdarrayOrTensor): @@ -471,6 +472,7 @@ def __init__( self.dtype = dtype self.device = device self.wrap_sequence = wrap_sequence + # pyrefly: ignore [unnecessary-type-conversion] self.track_meta = get_track_meta() if track_meta is None else bool(track_meta) def __call__(self, data: NdarrayOrTensor, dtype: DtypeLike | torch.dtype = None): diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py index 7dd24a3880..4a50926bf7 100644 --- a/monai/transforms/utility/dictionary.py +++ b/monai/transforms/utility/dictionary.py @@ -752,6 +752,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 86f9d1c3e4..e7d9fec622 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), @@ -1060,6 +1068,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) @@ -1244,10 +1253,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: @@ -1382,6 +1395,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]) @@ -1455,8 +1469,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) @@ -1806,6 +1823,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 @@ -1972,6 +1990,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: @@ -2163,15 +2182,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 [] @@ -2428,6 +2452,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 1bc9c206d8..db6ebc26e6 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 47da2bee6e..9c321e464e 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 be00b27d73..2d08c2cc79 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -324,15 +324,25 @@ class ForwardMode(StrEnum): class TraceKeys(StrEnum): """Extra metadata keys used for traceable transforms.""" + # pyrefly: ignore [invalid-annotation] CLASS_NAME: str = "class" + # pyrefly: ignore [invalid-annotation] ID: str = "id" + # pyrefly: ignore [invalid-annotation] ORIG_SIZE: str = "orig_size" + # pyrefly: ignore [invalid-annotation] EXTRA_INFO: str = "extra_info" + # pyrefly: ignore [invalid-annotation] DO_TRANSFORM: str = "do_transforms" + # pyrefly: ignore [invalid-annotation] KEY_SUFFIX: str = "_transforms" + # pyrefly: ignore [invalid-annotation] NONE: str = "none" + # pyrefly: ignore [invalid-annotation] TRACING: str = "tracing" + # pyrefly: ignore [invalid-annotation] STATUSES: str = "statuses" + # pyrefly: ignore [invalid-annotation] LAZY: str = "lazy" @@ -390,6 +400,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 ed48d4b37d..01839225d9 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 a2569b19fe..b1598cd1ed 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 b5dfb580c5..b36c0d13b0 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 30fd456043..a46b725113 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 023e444406..fe2a6cc08f 100644 --- a/monai/visualize/visualizer.py +++ b/monai/visualize/visualizer.py @@ -32,6 +32,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 325622b66a..006c81bcf5 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 @@ -71,6 +69,7 @@ extend-ignore = [ "B028", # no explicit stacklevel keyword argument found (flake8-bugbear) ] + [tool.ruff.lint.per-file-ignores] "tests/**" = [ "B018", @@ -85,34 +84,53 @@ 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 (matching mypy's scope) +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 matches mypy's laxness for a smooth migration +preset = "legacy" + +# Match mypy's check_untyped_defs=True (set for [mypy-monai.*]) +# and disallow_untyped_decorators=True +check-unannotated-defs = true + +[tool.pyrefly.errors] +# Match mypy's ignore_missing_imports = True +missing-import = "ignore" + +# Match mypy's warn_unused_ignores = False +unused-ignore = "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 = "warn" +not-iterable = "warn" +not-callable = "warn" +bad-index = "warn" + +# Pre-existing errors in the codebase matched to mypy's baseline +# (mypy didn't flag these, so suppress for a smooth migration) +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 08fcdc2b0e..cf5233879a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,7 +19,10 @@ isort>=5.1, <6, !=6.0.0 ruff>=0.14.11,<0.15 pybind11 types-setuptools -mypy>=1.5.0, <1.12.0 +pyrefly>=1.0.0 +ninja +types-setuptools +pyrefly>=1.0.0 ninja torchio torchvision diff --git a/runtests.sh b/runtests.sh index 431e9298c3..baf8aa3129 100755 --- a/runtests.sh +++ b/runtests.sh @@ -48,7 +48,7 @@ doRuffFormat=false doRuffFix=false doClangFormat=false doCopyRight=false -doPytypeFormat=false +doPyreflyFormat=false doMypyFormat=false doCleanup=false doDistTests=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] [--pyrefly]" echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--build] [--list_tests]" echo " [--dryrun] [--copyright] [--clean] [--help] [--version] [--path] [--formatfix]" echo "" @@ -87,9 +87,7 @@ 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 " --pyrefly : perform \"pyrefly\" static type checks" echo "" echo "MONAI unit testing options:" echo " -u, --unittests : perform unit testing" @@ -196,8 +194,7 @@ 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 +268,7 @@ do doIsortFormat=true # doPylintFormat=true # https://github.com/Project-MONAI/MONAI/issues/7094 doRuffFormat=true + doPyreflyFormat=true doCopyRight=true ;; --disttests) @@ -313,8 +311,8 @@ do --precommit) doPrecommit=true ;; - --pytype) - doPytypeFormat=true + --pyrefly) + doPyreflyFormat=true ;; --mypy) doMypyFormat=true @@ -608,32 +606,27 @@ then fi -if [ $doPytypeFormat = true ] +if [ $doPyreflyFormat = true ] then set +e # disable exit on failure so that diagnostics can be given on failure - echo "${separator}${blue}pytype${noColor}" + echo "${separator}${blue}pyrefly${noColor}" + # ensure that the necessary packages for code format testing are installed - if ! is_pip_installed pytype + if ! is_pip_installed pyrefly then install_deps fi - pytype_ver=$(${cmdPrefix}"${PY_EXE}" -m pytype --version) - if [[ "$OSTYPE" == "darwin"* && "$pytype_ver" == "2021."* ]]; then - echo "${red}pytype not working on macOS 2021 (https://github.com/Project-MONAI/MONAI/issues/2391). Please upgrade to 2022*.${noColor}" - exit 1 - else - ${cmdPrefix}"${PY_EXE}" -m pytype --version + ${cmdPrefix}"${PY_EXE}" -m pyrefly --version + # Run without file arguments to respect project-includes/excludes from pyproject.toml + ${cmdPrefix}"${PY_EXE}" -m pyrefly check - ${cmdPrefix}"${PY_EXE}" -m pytype -j ${NUM_PARALLEL} --python-version="$(${PY_EXE} -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")" "$homedir" - - pytype_status=$? - if [ ${pytype_status} -ne 0 ] - then - echo "${red}failed!${noColor}" - exit ${pytype_status} - else - echo "${green}passed!${noColor}" - fi + pyrefly_status=$? + if [ ${pyrefly_status} -ne 0 ] + then + echo "${red}failed!${noColor}" + exit ${pyrefly_status} + else + echo "${green}passed!${noColor}" fi set -e # enable exit on failure fi @@ -641,26 +634,8 @@ fi if [ $doMypyFormat = true ] then - set +e # disable exit on failure so that diagnostics can be given on failure - echo "${separator}${blue}mypy${noColor}" - - # ensure that the necessary packages for code format testing are installed - if ! is_pip_installed mypy - then - install_deps - fi - ${cmdPrefix}"${PY_EXE}" -m mypy --version - ${cmdPrefix}"${PY_EXE}" -m mypy "$homedir" - - mypy_status=$? - if [ ${mypy_status} -ne 0 ] - then - : # mypy output already follows format - exit ${mypy_status} - else - : # mypy output already follows format - fi - set -e # enable exit on failure + echo "${red}Warning: mypy has been replaced by pyrefly. Use --pyrefly instead.${noColor}" + exit 1 fi diff --git a/setup.cfg b/setup.cfg index d987141d0b..8c085da8ec 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/uv.lock b/uv.lock new file mode 100644 index 0000000000..7518fc90bf --- /dev/null +++ b/uv.lock @@ -0,0 +1,3 @@ +version = 1 +revision = 3 +requires-python = ">=3.12"