Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
opt: ["codeformat", "pytype", "mypy"]
opt: ["codeformat", "pyrefly"]
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
Expand All @@ -52,8 +52,7 @@ jobs:
run: |
# clean up temporary files
$(pwd)/runtests.sh --build --clean
# Github actions have 2 cores, so parallelize pytype
$(pwd)/runtests.sh --build --${{ matrix.opt }} -j 2
$(pwd)/runtests.sh --build --${{ matrix.opt }}

quick-py3: # full dependencies installed tests for different OS
runs-on: ${{ matrix.os }}
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/weekly-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
opt: ["codeformat", "pytype", "mypy"]
opt: ["codeformat", "pyrefly"]
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
Expand All @@ -35,8 +35,7 @@ jobs:
run: |
# clean up temporary files
$(pwd)/runtests.sh --build --clean
# Github actions have 2 cores, so parallelize pytype
$(pwd)/runtests.sh --build --${{ matrix.opt }} -j 2
$(pwd)/runtests.sh --build --${{ matrix.opt }}

packaging:
if: github.repository == 'Project-MONAI/MONAI'
Expand Down
8 changes: 2 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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

*~

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ([flake8 and its plugins](https://gitlab.com/pycqa/flake8), [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.
Expand Down
4 changes: 4 additions & 0 deletions monai/apps/auto3dseg/auto_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,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]
Expand Down Expand Up @@ -570,6 +571,7 @@ def set_device_info(
self.device_setting["CUDA_VISIBLE_DEVICES"] = ",".join([str(x) for x in cuda_visible_devices])
self.device_setting["n_devices"] = len(cuda_visible_devices)
else:
# pyrefly: ignore [deprecated]
logger.warn(f"Wrong format of cuda_visible_devices {cuda_visible_devices}, devices not set")

if num_nodes is None:
Expand Down Expand Up @@ -781,6 +783,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"

Expand All @@ -796,6 +799,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
Expand Down
1 change: 1 addition & 0 deletions monai/apps/auto3dseg/bundle_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions monai/apps/auto3dseg/ensemble_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions monai/apps/deepedit/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,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]:
Expand Down Expand Up @@ -518,6 +519,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])

Expand Down Expand Up @@ -842,6 +844,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]:
Expand Down
1 change: 1 addition & 0 deletions monai/apps/deepgrow/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions monai/apps/deepgrow/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
2 changes: 2 additions & 0 deletions monai/apps/detection/networks/retinanet_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,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]]
Expand All @@ -536,6 +537,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
Expand Down
4 changes: 4 additions & 0 deletions monai/apps/detection/transforms/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions monai/apps/detection/transforms/box_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions monai/apps/detection/transforms/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,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
Expand Down
1 change: 1 addition & 0 deletions monai/apps/detection/utils/hard_negative_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions monai/apps/generation/maisi/networks/autoencoderkl_maisi.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,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}")
Expand Down
2 changes: 2 additions & 0 deletions monai/apps/nnunet/nnunetv2_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,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"]
Expand Down Expand Up @@ -263,6 +264,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,
Expand Down
5 changes: 5 additions & 0 deletions monai/apps/nnunet/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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"])
)
Expand All @@ -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"])
)
Expand Down Expand Up @@ -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)
)
Expand All @@ -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"])
)
Expand Down
2 changes: 2 additions & 0 deletions monai/apps/nuclick/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions monai/apps/pathology/transforms/post/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions monai/apps/tcia/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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")


Expand Down
4 changes: 4 additions & 0 deletions monai/apps/vista3d/inferer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -147,6 +150,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

Expand Down
3 changes: 3 additions & 0 deletions monai/apps/vista3d/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ 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(
# pyrefly: ignore [unnecessary-type-conversion]
name_to_index_mapping.get(l.lower(), int(l) if l.isdigit() else 0) if isinstance(l, str) else int(l)
)
else:
Expand Down Expand Up @@ -208,7 +209,9 @@ def __init__(
self.dataset_key = dataset_key
for name, mapping in label_mappings.items():
self.mappers[name] = MapLabelValue(
# pyrefly: ignore [unnecessary-type-conversion]
orig_labels=[int(pair[0]) for pair in mapping],
# pyrefly: ignore [unnecessary-type-conversion]
target_labels=[int(pair[1]) for pair in mapping],
dtype=dtype,
)
Expand Down
1 change: 1 addition & 0 deletions monai/auto3dseg/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
1 change: 1 addition & 0 deletions monai/auto3dseg/seg_summarizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions monai/bundle/reference_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions monai/bundle/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ def download(
_download_from_monaihosting(
download_path=bundle_dir_, filename=name_, version=version_, progress=progress_
)
# pyrefly: ignore [implicit-import]
except urllib.error.HTTPError:
# for monaihosting bundles, if cannot download from default host, download according to bundle_info
_download_from_bundle_info(
Expand Down Expand Up @@ -1989,8 +1990,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}.")
Expand Down
Loading