Skip to content

Commit 60a9693

Browse files
committed
Merge branch 'dev' into merging-into-metatensor
Signed-off-by: Wenqi Li <[email protected]>
2 parents 86521d5 + af0e0e9 commit 60a9693

File tree

14 files changed

+224
-124
lines changed

14 files changed

+224
-124
lines changed

docs/source/config_syntax.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ Content:
1212

1313
- [A basic example](#a-basic-example)
1414
- [Syntax examples explained](#syntax-examples-explained)
15-
- [`@` to interpolate with Python objects](#1--to-interpolate-with-python-objects)
16-
- [`$` to evaluate as Python expressions](#2--to-evaluate-as-python-expressions)
17-
- [`%` to textually replace configuration elements](#3--to-textually-replace-configuration-elements)
18-
- [`_target_` (`_disabled_` and `_requires_`) to instantiate a Python object](#4-instantiate-a-python-object)
15+
- [`@` to reference Python objects in configurations](#to-reference-python-objects-in-configurations)
16+
- [`$` to evaluate as Python expressions](#to-evaluate-as-python-expressions)
17+
- [`%` to textually replace configuration elements](#to-textually-replace-configuration-elements)
18+
- [`_target_` (`_disabled_` and `_requires_`) to instantiate a Python object](#instantiate-a-python-object)
1919
- [The command line interface](#the-command-line-interface)
2020
- [Recommendations](#recommendations)
2121

@@ -73,22 +73,22 @@ For more details on the `ConfigParser` API, please see https://docs.monai.io/en/
7373

7474
A few characters and keywords are interpreted beyond the plain texts, here are examples of the syntax:
7575

76-
### 1. `@` to interpolate with Python objects
76+
### To reference Python objects in configurations
7777

7878
```json
7979
"@preprocessing#transforms#keys"
8080
```
8181

82-
_Description:_ A reference to another configuration value defined at `preprocessing#transforms#keys`.
82+
_Description:_ `@` character indicates a reference to another configuration value defined at `preprocessing#transforms#keys`.
8383
where `#` indicates a sub-structure of this configuration file.
8484

8585
```json
8686
"@preprocessing#1"
8787
```
8888

89-
_Description:_ `1` is interpreted as an integer, which is used to index (zero-based indexing) the `preprocessing` sub-structure.
89+
_Description:_ `1` is referencing as an integer, which is used to index (zero-based indexing) the `preprocessing` sub-structure.
9090

91-
### 2. `$` to evaluate as Python expressions
91+
### To evaluate as Python expressions
9292

9393
```json
9494
"$print(42)"
@@ -110,16 +110,16 @@ _Description:_ `$` followed by an import statement is handled slightly different
110110
Python expressions. The imported module `resnet18` will be available as a global variable
111111
to the other configuration sections. This is to simplify the use of external modules in the configuration.
112112

113-
### 3. `%` to textually replace configuration elements
113+
### To textually replace configuration elements
114114

115115
```json
116116
"%demo_config.json#demo_net#in_channels"
117117
```
118118

119-
_Description:_ A macro to replace the current configuration element with the texts at `demo_net#in_channels` in the
119+
_Description:_ `%` character indicates a macro to replace the current configuration element with the texts at `demo_net#in_channels` in the
120120
`demo_config.json` file. The replacement is done before instantiating or evaluating the components.
121121

122-
### 4. instantiate a Python object
122+
### Instantiate a Python object
123123

124124
```json
125125
{
@@ -164,6 +164,7 @@ python -m monai.bundle COMMANDS
164164
where `COMMANDS` is one of the following: `run`, `verify_metadata`, `ckpt_export`, ...
165165
(please see `python -m monai.bundle --help` for a list of available options).
166166

167+
The CLI supports flexible use cases, such as overriding configs at runtime and predefining arguments in a file.
167168
To display a usage page for a command, for example `run`:
168169
```bash
169170
python -m monai.bundle run -- --help
@@ -182,3 +183,5 @@ Details on the CLI argument parsing is provided in the
182183
simple structures with sparse uses of expressions or references are preferred.
183184
- For `$import <module>` in the configuration, please make sure there are instructions for the users to install
184185
the `<module>` if it is not a (optional) dependency of MONAI.
186+
- As "#" and "$" might be interpreted differently by the `shell` or `CLI` tools, may need to add escape characters
187+
or quotes for them in the command line, like: `"\$torch.device('cuda:1')"`, `"'train_part#trainer'"`.

monai/apps/detection/transforms/array.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,9 @@ class RotateBox90(Rotate90):
535535
def __init__(self, k: int = 1, spatial_axes: Tuple[int, int] = (0, 1)) -> None:
536536
super().__init__(k, spatial_axes)
537537

538-
def __call__(self, boxes: NdarrayOrTensor, spatial_size: Union[Sequence[int], int]) -> NdarrayOrTensor: # type: ignore
538+
def __call__( # type: ignore
539+
self, boxes: NdarrayOrTensor, spatial_size: Union[Sequence[int], int]
540+
) -> NdarrayOrTensor:
539541
"""
540542
Args:
541543
img: channel first array, must have shape: (num_channels, H[, W, ..., ]),

monai/apps/detection/transforms/dictionary.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def __init__(
248248
self.converter_to_image_coordinate = AffineBox()
249249
self.affine_lps_to_ras = affine_lps_to_ras
250250

251-
def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]:
251+
def extract_affine(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Tuple[NdarrayOrTensor, NdarrayOrTensor]:
252252
d = dict(data)
253253

254254
meta_key = self.image_meta_key
@@ -269,6 +269,12 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, N
269269
affine_t, *_ = convert_data_type(affine, torch.Tensor)
270270
# torch.inverse should not run in half precision
271271
inv_affine_t = torch.inverse(affine_t.to(COMPUTE_DTYPE))
272+
return affine, inv_affine_t
273+
274+
def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]:
275+
d = dict(data)
276+
277+
affine, inv_affine_t = self.extract_affine(data)
272278

273279
for key in self.key_iterator(d):
274280
self.push_transform(d, key, extra_info={"affine": affine})
@@ -285,6 +291,54 @@ def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, Nd
285291
return d
286292

287293

294+
class AffineBoxToWorldCoordinated(AffineBoxToImageCoordinated):
295+
"""
296+
Dictionary-based transform that converts box in image coordinate to world coordinate.
297+
298+
Args:
299+
box_keys: Keys to pick box data for transformation. The box mode is assumed to be ``StandardMode``.
300+
box_ref_image_keys: The single key that represents the reference image to which ``box_keys`` are attached.
301+
remove_empty: whether to remove the boxes that are actually empty
302+
allow_missing_keys: don't raise exception if key is missing.
303+
image_meta_key: explicitly indicate the key of the corresponding metadata dictionary.
304+
for example, for data with key `image`, the metadata by default is in `image_meta_dict`.
305+
the metadata is a dictionary object which contains: filename, affine, original_shape, etc.
306+
it is a string, map to the `box_ref_image_key`.
307+
if None, will try to construct meta_keys by `box_ref_image_key_{meta_key_postfix}`.
308+
image_meta_key_postfix: if image_meta_keys=None, use `box_ref_image_key_{postfix}` to fetch the metadata according
309+
to the key data, default is `meta_dict`, the metadata is a dictionary object.
310+
For example, to handle key `image`, read/write affine matrices from the
311+
metadata `image_meta_dict` dictionary's `affine` field.
312+
affine_lps_to_ras: default ``False``. Yet if 1) the image is read by ITKReader,
313+
and 2) the ITKReader has affine_lps_to_ras=True, and 3) the box is in world coordinate,
314+
then set ``affine_lps_to_ras=True``.
315+
"""
316+
317+
def __init__(
318+
self,
319+
box_keys: KeysCollection,
320+
box_ref_image_keys: str,
321+
allow_missing_keys: bool = False,
322+
image_meta_key: Union[str, None] = None,
323+
image_meta_key_postfix: Union[str, None] = DEFAULT_POST_FIX,
324+
affine_lps_to_ras=False,
325+
) -> None:
326+
super().__init__(
327+
box_keys, box_ref_image_keys, allow_missing_keys, image_meta_key, image_meta_key_postfix, affine_lps_to_ras
328+
)
329+
self.converter_to_world_coordinate = AffineBox()
330+
331+
def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]:
332+
d = dict(data)
333+
334+
affine, inv_affine_t = self.extract_affine(data)
335+
336+
for key in self.key_iterator(d):
337+
self.push_transform(d, key, extra_info={"affine": inv_affine_t})
338+
d[key] = self.converter_to_world_coordinate(d[key], affine=affine)
339+
return d
340+
341+
288342
class ZoomBoxd(MapTransform, InvertibleTransform):
289343
"""
290344
Dictionary-based transform that zooms input boxes and images with the given zoom scale.

monai/data/grid_dataset.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -187,23 +187,19 @@ def __init__(
187187
) -> None:
188188
super().__init__(data=data, transform=None)
189189
self.patch_iter = patch_iter
190-
self.transform = transform
190+
self.patch_transform = transform
191191
self.with_coordinates = with_coordinates
192192

193193
def __iter__(self):
194194
for image in super().__iter__():
195-
if not self.with_coordinates:
196-
for patch, *_ in self.patch_iter(image): # patch_iter to yield at least 1 item: patch
197-
out_patch = (
198-
patch if self.transform is None else apply_transform(self.transform, patch, map_items=False)
199-
)
195+
for patch, *others in self.patch_iter(image):
196+
out_patch = patch
197+
if self.patch_transform is not None:
198+
out_patch = apply_transform(self.patch_transform, patch, map_items=False)
199+
if self.with_coordinates and len(others) > 0: # patch_iter to yield at least 2 items: patch, coords
200+
yield out_patch, others[0]
201+
else:
200202
yield out_patch
201-
else:
202-
for patch, slices, *_ in self.patch_iter(image): # patch_iter to yield at least 2 items: patch, coords
203-
out_patch = (
204-
patch if self.transform is None else apply_transform(self.transform, patch, map_items=False)
205-
)
206-
yield out_patch, slices
207203

208204

209205
class PatchDataset(Dataset):

monai/data/iterable_dataset.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union
1313

14-
import numpy as np
1514
from torch.utils.data import IterableDataset as _TorchIterableDataset
1615
from torch.utils.data import get_worker_info
1716

@@ -115,9 +114,6 @@ def _get_item():
115114
def randomize(self, size: int) -> None:
116115
self._idx = self.R.randint(size)
117116

118-
def set_random_state(self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None):
119-
raise NotImplementedError(f"`set_random_state` is not available in {self.__class__.__name__}.")
120-
121117

122118
class CSVIterableDataset(IterableDataset):
123119
"""

monai/inferers/inferer.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ class SliceInferer(SlidingWindowInferer):
278278
def __init__(self, spatial_dim: int = 0, *args, **kwargs) -> None:
279279
self.spatial_dim = spatial_dim
280280
super().__init__(*args, **kwargs)
281+
self.orig_roi_size = ensure_tuple(self.roi_size)
281282

282283
def __call__(
283284
self,
@@ -298,11 +299,13 @@ def __call__(
298299

299300
# Check if ``roi_size`` tuple is 2D and ``inputs`` tensor is 3D
300301
self.roi_size = ensure_tuple(self.roi_size)
301-
if len(self.roi_size) == 2 and len(inputs.shape[2:]) == 3:
302-
self.roi_size = list(self.roi_size)
302+
if len(self.orig_roi_size) == 2 and len(inputs.shape[2:]) == 3:
303+
self.roi_size = list(self.orig_roi_size)
303304
self.roi_size.insert(self.spatial_dim, 1)
304305
else:
305-
raise RuntimeError("Currently, only 2D `roi_size` with 3D `inputs` tensor is supported.")
306+
raise RuntimeError(
307+
f"Currently, only 2D `roi_size` ({self.orig_roi_size}) with 3D `inputs` tensor (shape={inputs.shape}) is supported."
308+
)
306309

307310
return super().__call__(inputs=inputs, network=lambda x: self.network_wrapper(network, x, *args, **kwargs))
308311

monai/losses/giou_loss.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ def forward(self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
5050
raise ValueError(f"ground truth has different shape ({target.shape}) from input ({input.shape})")
5151

5252
box_dtype = input.dtype
53-
giou: torch.Tensor = box_pair_giou(target.to(dtype=COMPUTE_DTYPE), input.to(dtype=COMPUTE_DTYPE)) # type: ignore
53+
giou: torch.Tensor = box_pair_giou( # type: ignore
54+
target.to(dtype=COMPUTE_DTYPE), input.to(dtype=COMPUTE_DTYPE)
55+
)
5456
loss: torch.Tensor = 1.0 - giou
5557
if self.reduction == LossReduction.MEAN.value:
5658
loss = loss.mean()

monai/networks/nets/swin_unetr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ def forward(self, x, mask):
474474
q = q * self.scale
475475
attn = q @ k.transpose(-2, -1)
476476
relative_position_bias = self.relative_position_bias_table[
477-
self.relative_position_index[:n, :n].reshape(-1)
477+
self.relative_position_index.clone()[:n, :n].reshape(-1)
478478
].reshape(n, n, -1)
479479
relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous()
480480
attn = attn + relative_position_bias.unsqueeze(0)

0 commit comments

Comments
 (0)