Skip to content

Commit bf72099

Browse files
authored
Merge branch 'dev' into fix-he-stain-order
2 parents 433eaad + e72145c commit bf72099

File tree

14 files changed

+222
-24
lines changed

14 files changed

+222
-24
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242

4343
# Initializes the CodeQL tools for scanning.
4444
- name: Initialize CodeQL
45-
uses: github/codeql-action/init@v3
45+
uses: github/codeql-action/init@v4
4646
with:
4747
languages: ${{ matrix.language }}
4848
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -72,4 +72,4 @@ jobs:
7272
BUILD_MONAI=1 ./runtests.sh --build
7373
7474
- name: Perform CodeQL Analysis
75-
uses: github/codeql-action/analyze@v3
75+
uses: github/codeql-action/analyze@v4

.github/workflows/docker.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
python setup.py build
3838
cat build/lib/monai/_version.py
3939
- name: Upload version
40-
uses: actions/upload-artifact@v4
40+
uses: actions/upload-artifact@v5
4141
with:
4242
name: _version.py
4343
path: build/lib/monai/_version.py
@@ -57,7 +57,7 @@ jobs:
5757
with:
5858
ref: dev
5959
- name: Download version
60-
uses: actions/download-artifact@v5
60+
uses: actions/download-artifact@v6
6161
with:
6262
name: _version.py
6363
- name: docker_build

.github/workflows/integration.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
run: ./runtests.sh --build --net
7575

7676
- name: Add reaction
77-
uses: peter-evans/create-or-update-comment@v4
77+
uses: peter-evans/create-or-update-comment@v5
7878
with:
7979
token: ${{ secrets.PR_MAINTAIN }}
8080
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
@@ -154,7 +154,7 @@ jobs:
154154
python -m tests.test_integration_gpu_customization
155155
156156
- name: Add reaction
157-
uses: peter-evans/create-or-update-comment@v4
157+
uses: peter-evans/create-or-update-comment@v5
158158
with:
159159
token: ${{ secrets.PR_MAINTAIN }}
160160
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}

.github/workflows/pythonapp-min.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ jobs:
8888
run: |
8989
which python
9090
python -m pip install --user --upgrade pip setuptools wheel
91+
python -m pip install --user more-itertools>=8.0
9192
- name: cache weekly timestamp
9293
id: pip-cache
9394
run: |

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666

6767
- if: matrix.python-version == '3.9' && startsWith(github.ref, 'refs/tags/')
6868
name: Upload artifacts
69-
uses: actions/upload-artifact@v4
69+
uses: actions/upload-artifact@v5
7070
with:
7171
name: dist
7272
path: dist/
@@ -109,7 +109,7 @@ jobs:
109109
python setup.py build
110110
cat build/lib/monai/_version.py
111111
- name: Upload version
112-
uses: actions/upload-artifact@v4
112+
uses: actions/upload-artifact@v5
113113
with:
114114
name: _version.py
115115
path: build/lib/monai/_version.py
@@ -127,7 +127,7 @@ jobs:
127127
steps:
128128
- uses: actions/checkout@v5
129129
- name: Download version
130-
uses: actions/download-artifact@v5
130+
uses: actions/download-artifact@v6
131131
with:
132132
name: _version.py
133133
- name: Set tag

docs/source/transforms.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ Generic Interfaces
3737
.. autoclass:: MultiSampleTrait
3838
:members:
3939

40+
`ReduceTrait`
41+
^^^^^^^^^^^^^^^^^^
42+
.. autoclass:: ReduceTrait
43+
:members:
44+
4045
`Randomizable`
4146
^^^^^^^^^^^^^^
4247
.. autoclass:: Randomizable
@@ -1252,6 +1257,12 @@ Utility
12521257
:members:
12531258
:special-members: __call__
12541259

1260+
`FlattenSequence`
1261+
""""""""""""""""""""""""
1262+
.. autoclass:: FlattenSequence
1263+
:members:
1264+
:special-members: __call__
1265+
12551266
Dictionary Transforms
12561267
---------------------
12571268

@@ -2337,6 +2348,12 @@ Utility (Dict)
23372348
:members:
23382349
:special-members: __call__
23392350

2351+
`FlattenSequenced`
2352+
"""""""""""""""""""""""""
2353+
.. autoclass:: FlattenSequenced
2354+
:members:
2355+
:special-members: __call__
2356+
23402357

23412358
MetaTensor
23422359
^^^^^^^^^^

monai/data/box_utils.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,10 @@ def box_iou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOrTensor
826826
boxes2: bounding boxes, Mx4 or Mx6 torch tensor or ndarray. The box mode is assumed to be ``StandardMode``
827827
828828
Returns:
829-
IoU, with size of (N,M) and same data type as ``boxes1``
829+
An array/tensor matching the container type of ``boxes1`` (NumPy ndarray or Torch tensor), always
830+
floating-point with size ``(N, M)``:
831+
- if ``boxes1`` has a floating-point dtype, the same dtype is used.
832+
- if ``boxes1`` has an integer dtype, the result is returned as ``torch.float32``.
830833
831834
"""
832835

@@ -842,16 +845,18 @@ def box_iou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOrTensor
842845

843846
inter, union = _box_inter_union(boxes1_t, boxes2_t, compute_dtype=COMPUTE_DTYPE)
844847

845-
# compute IoU and convert back to original box_dtype
848+
# compute IoU and convert back to original box_dtype or torch.float32
846849
iou_t = inter / (union + torch.finfo(COMPUTE_DTYPE).eps) # (N,M)
850+
if not box_dtype.is_floating_point:
851+
box_dtype = COMPUTE_DTYPE
847852
iou_t = iou_t.to(dtype=box_dtype)
848853

849854
# check if NaN or Inf
850855
if torch.isnan(iou_t).any() or torch.isinf(iou_t).any():
851856
raise ValueError("Box IoU is NaN or Inf.")
852857

853858
# convert tensor back to numpy if needed
854-
iou, *_ = convert_to_dst_type(src=iou_t, dst=boxes1)
859+
iou, *_ = convert_to_dst_type(src=iou_t, dst=boxes1, dtype=box_dtype)
855860
return iou
856861

857862

@@ -867,7 +872,10 @@ def box_giou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOrTenso
867872
boxes2: bounding boxes, Mx4 or Mx6 torch tensor or ndarray. The box mode is assumed to be ``StandardMode``
868873
869874
Returns:
870-
GIoU, with size of (N,M) and same data type as ``boxes1``
875+
An array/tensor matching the container type of ``boxes1`` (NumPy ndarray or Torch tensor), always
876+
floating-point with size ``(N, M)``:
877+
- if ``boxes1`` has a floating-point dtype, the same dtype is used.
878+
- if ``boxes1`` has an integer dtype, the result is returned as ``torch.float32``.
871879
872880
Reference:
873881
https://giou.stanford.edu/GIoU.pdf
@@ -904,12 +912,15 @@ def box_giou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOrTenso
904912

905913
# GIoU
906914
giou_t = iou - (enclosure - union) / (enclosure + torch.finfo(COMPUTE_DTYPE).eps)
915+
if not box_dtype.is_floating_point:
916+
box_dtype = COMPUTE_DTYPE
907917
giou_t = giou_t.to(dtype=box_dtype)
918+
908919
if torch.isnan(giou_t).any() or torch.isinf(giou_t).any():
909920
raise ValueError("Box GIoU is NaN or Inf.")
910921

911922
# convert tensor back to numpy if needed
912-
giou, *_ = convert_to_dst_type(src=giou_t, dst=boxes1)
923+
giou, *_ = convert_to_dst_type(src=giou_t, dst=boxes1, dtype=box_dtype)
913924
return giou
914925

915926

@@ -925,7 +936,10 @@ def box_pair_giou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOr
925936
boxes2: bounding boxes, same shape with boxes1. The box mode is assumed to be ``StandardMode``
926937
927938
Returns:
928-
paired GIoU, with size of (N,) and same data type as ``boxes1``
939+
An array/tensor matching the container type of ``boxes1`` (NumPy ndarray or Torch tensor), always
940+
floating-point with size ``(N, )``:
941+
- if ``boxes1`` has a floating-point dtype, the same dtype is used.
942+
- if ``boxes1`` has an integer dtype, the result is returned as ``torch.float32``.
929943
930944
Reference:
931945
https://giou.stanford.edu/GIoU.pdf
@@ -982,12 +996,15 @@ def box_pair_giou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOr
982996
enclosure = torch.prod(wh, dim=-1, keepdim=False) # (N,)
983997

984998
giou_t: torch.Tensor = iou - (enclosure - union) / (enclosure + torch.finfo(COMPUTE_DTYPE).eps) # type: ignore
999+
if not box_dtype.is_floating_point:
1000+
box_dtype = COMPUTE_DTYPE
9851001
giou_t = giou_t.to(dtype=box_dtype) # (N,spatial_dims)
1002+
9861003
if torch.isnan(giou_t).any() or torch.isinf(giou_t).any():
9871004
raise ValueError("Box GIoU is NaN or Inf.")
9881005

9891006
# convert tensor back to numpy if needed
990-
giou, *_ = convert_to_dst_type(src=giou_t, dst=boxes1)
1007+
giou, *_ = convert_to_dst_type(src=giou_t, dst=boxes1, dtype=box_dtype)
9911008
return giou
9921009

9931010

monai/transforms/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@
506506
ZoomDict,
507507
)
508508
from .spatial.functional import spatial_resample
509-
from .traits import LazyTrait, MultiSampleTrait, RandomizableTrait, ThreadUnsafe
509+
from .traits import LazyTrait, MultiSampleTrait, RandomizableTrait, ReduceTrait, ThreadUnsafe
510510
from .transform import LazyTransform, MapTransform, Randomizable, RandomizableTransform, Transform, apply_transform
511511
from .utility.array import (
512512
AddCoordinateChannels,
@@ -521,6 +521,7 @@
521521
EnsureChannelFirst,
522522
EnsureType,
523523
FgBgToIndices,
524+
FlattenSequence,
524525
Identity,
525526
ImageFilter,
526527
IntensityStats,
@@ -593,6 +594,9 @@
593594
FgBgToIndicesd,
594595
FgBgToIndicesD,
595596
FgBgToIndicesDict,
597+
FlattenSequenced,
598+
FlattenSequenceD,
599+
FlattenSequenceDict,
596600
FlattenSubKeysd,
597601
FlattenSubKeysD,
598602
FlattenSubKeysDict,

monai/transforms/traits.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from __future__ import annotations
1616

17-
__all__ = ["LazyTrait", "InvertibleTrait", "RandomizableTrait", "MultiSampleTrait", "ThreadUnsafe"]
17+
__all__ = ["LazyTrait", "InvertibleTrait", "RandomizableTrait", "MultiSampleTrait", "ThreadUnsafe", "ReduceTrait"]
1818

1919
from typing import Any
2020

@@ -99,3 +99,14 @@ class ThreadUnsafe:
9999
"""
100100

101101
pass
102+
103+
104+
class ReduceTrait:
105+
"""
106+
An interface to indicate that the transform has the capability to reduce multiple samples
107+
into a single sample.
108+
This interface can be extended from by people adapting transforms to the MONAI framework as well
109+
as by implementors of MONAI transforms.
110+
"""
111+
112+
pass

monai/transforms/transform.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from monai import config, transforms
2626
from monai.config import KeysCollection
2727
from monai.data.meta_tensor import MetaTensor
28-
from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe
28+
from monai.transforms.traits import LazyTrait, RandomizableTrait, ReduceTrait, ThreadUnsafe
2929
from monai.utils import MAX_SEED, ensure_tuple, first
3030
from monai.utils.enums import TransformBackends
3131
from monai.utils.misc import MONAIEnvVars
@@ -142,7 +142,7 @@ def apply_transform(
142142
"""
143143
try:
144144
map_items_ = int(map_items) if isinstance(map_items, bool) else map_items
145-
if isinstance(data, (list, tuple)) and map_items_ > 0:
145+
if isinstance(data, (list, tuple)) and map_items_ > 0 and not isinstance(transform, ReduceTrait):
146146
return [
147147
apply_transform(transform, item, map_items_ - 1, unpack_items, log_stats, lazy, overrides)
148148
for item in data
@@ -482,8 +482,7 @@ def key_iterator(self, data: Mapping[Hashable, Any], *extra_iterables: Iterable
482482
yield (key,) + tuple(_ex_iters) if extra_iterables else key
483483
elif not self.allow_missing_keys:
484484
raise KeyError(
485-
f"Key `{key}` of transform `{self.__class__.__name__}` was missing in the data"
486-
" and allow_missing_keys==False."
485+
f"Key `{key}` of transform `{self.__class__.__name__}` was missing in the data and allow_missing_keys==False."
487486
)
488487

489488
def first_key(self, data: dict[Hashable, Any]):

0 commit comments

Comments
 (0)