Skip to content

Commit 55d9691

Browse files
slicepastepre-commit-ci[bot]KumoLiuericspodsurajpaib
authored andcommitted
Make MetaTensor optional printed in DataStats and DataStatsd Project-MONAI#5905 (Project-MONAI#7814)
Fixes Project-MONAI#5905 ### Description We simply add one argument for DataStats and DataStatsd to make MetaTensor optional printed. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Wei_Chuan, Chiang <jamesis1356@gmail.com> Signed-off-by: YunLiu <55491388+KumoLiu@users.noreply.github.com> Signed-off-by: Suraj Pai <b.pai@maastrichtuniversity.nl> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: YunLiu <55491388+KumoLiu@users.noreply.github.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: Suraj Pai <b.pai@maastrichtuniversity.nl> Co-authored-by: Ben Murray <ben.murray@gmail.com>
1 parent 45ac06c commit 55d9691

File tree

4 files changed

+123
-7
lines changed

4 files changed

+123
-7
lines changed

monai/transforms/utility/array.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ def __init__(
656656
data_shape: bool = True,
657657
value_range: bool = True,
658658
data_value: bool = False,
659+
meta_info: bool = False,
659660
additional_info: Callable | None = None,
660661
name: str = "DataStats",
661662
) -> None:
@@ -667,6 +668,7 @@ def __init__(
667668
value_range: whether to show the value range of input data.
668669
data_value: whether to show the raw value of input data.
669670
a typical example is to print some properties of Nifti image: affine, pixdim, etc.
671+
meta_info: whether to show the data of MetaTensor.
670672
additional_info: user can define callable function to extract additional info from input data.
671673
name: identifier of `logging.logger` to use, defaulting to "DataStats".
672674
@@ -681,6 +683,7 @@ def __init__(
681683
self.data_shape = data_shape
682684
self.value_range = value_range
683685
self.data_value = data_value
686+
self.meta_info = meta_info
684687
if additional_info is not None and not callable(additional_info):
685688
raise TypeError(f"additional_info must be None or callable but is {type(additional_info).__name__}.")
686689
self.additional_info = additional_info
@@ -707,6 +710,7 @@ def __call__(
707710
data_shape: bool | None = None,
708711
value_range: bool | None = None,
709712
data_value: bool | None = None,
713+
meta_info: bool | None = None,
710714
additional_info: Callable | None = None,
711715
) -> NdarrayOrTensor:
712716
"""
@@ -727,6 +731,9 @@ def __call__(
727731
lines.append(f"Value range: (not a PyTorch or Numpy array, type: {type(img)})")
728732
if self.data_value if data_value is None else data_value:
729733
lines.append(f"Value: {img}")
734+
if self.meta_info if meta_info is None else meta_info:
735+
metadata = getattr(img, "meta", "(input is not a MetaTensor)")
736+
lines.append(f"Meta info: {repr(metadata)}")
730737
additional_info = self.additional_info if additional_info is None else additional_info
731738
if additional_info is not None:
732739
lines.append(f"Additional info: {additional_info(img)}")

monai/transforms/utility/dictionary.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,7 @@ def __init__(
793793
data_shape: Sequence[bool] | bool = True,
794794
value_range: Sequence[bool] | bool = True,
795795
data_value: Sequence[bool] | bool = False,
796+
meta_info: Sequence[bool] | bool = False,
796797
additional_info: Sequence[Callable] | Callable | None = None,
797798
name: str = "DataStats",
798799
allow_missing_keys: bool = False,
@@ -812,6 +813,8 @@ def __init__(
812813
data_value: whether to show the raw value of input data.
813814
it also can be a sequence of bool, each element corresponds to a key in ``keys``.
814815
a typical example is to print some properties of Nifti image: affine, pixdim, etc.
816+
meta_info: whether to show the data of MetaTensor.
817+
it also can be a sequence of bool, each element corresponds to a key in ``keys``.
815818
additional_info: user can define callable function to extract
816819
additional info from input data. it also can be a sequence of string, each element
817820
corresponds to a key in ``keys``.
@@ -825,15 +828,34 @@ def __init__(
825828
self.data_shape = ensure_tuple_rep(data_shape, len(self.keys))
826829
self.value_range = ensure_tuple_rep(value_range, len(self.keys))
827830
self.data_value = ensure_tuple_rep(data_value, len(self.keys))
831+
self.meta_info = ensure_tuple_rep(meta_info, len(self.keys))
828832
self.additional_info = ensure_tuple_rep(additional_info, len(self.keys))
829833
self.printer = DataStats(name=name)
830834

831835
def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]:
832836
d = dict(data)
833-
for key, prefix, data_type, data_shape, value_range, data_value, additional_info in self.key_iterator(
834-
d, self.prefix, self.data_type, self.data_shape, self.value_range, self.data_value, self.additional_info
837+
for (
838+
key,
839+
prefix,
840+
data_type,
841+
data_shape,
842+
value_range,
843+
data_value,
844+
meta_info,
845+
additional_info,
846+
) in self.key_iterator(
847+
d,
848+
self.prefix,
849+
self.data_type,
850+
self.data_shape,
851+
self.value_range,
852+
self.data_value,
853+
self.meta_info,
854+
self.additional_info,
835855
):
836-
d[key] = self.printer(d[key], prefix, data_type, data_shape, value_range, data_value, additional_info)
856+
d[key] = self.printer(
857+
d[key], prefix, data_type, data_shape, value_range, data_value, meta_info, additional_info
858+
)
837859
return d
838860

839861

tests/test_data_stats.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import torch
2424
from parameterized import parameterized
2525

26+
from monai.data.meta_tensor import MetaTensor
2627
from monai.transforms import DataStats
2728
from monai.utils.misc import select_optimal_device
2829

@@ -131,20 +132,55 @@
131132
]
132133

133134
TEST_CASE_8 = [
135+
{
136+
"prefix": "test data",
137+
"data_type": True,
138+
"data_shape": True,
139+
"value_range": True,
140+
"data_value": True,
141+
"additional_info": np.mean,
142+
"name": "DataStats",
143+
},
134144
np.array([[0, 1], [1, 2]]),
135145
"test data statistics:\nType: <class 'numpy.ndarray'> int64\nShape: (2, 2)\nValue range: (0, 2)\n"
136146
"Value: [[0 1]\n [1 2]]\nAdditional info: 1.0\n",
137147
]
138148

149+
TEST_CASE_9 = [
150+
np.array([[0, 1], [1, 2]]),
151+
"test data statistics:\nType: <class 'numpy.ndarray'> int64\nShape: (2, 2)\nValue range: (0, 2)\n"
152+
"Value: [[0 1]\n [1 2]]\n"
153+
"Meta info: '(input is not a MetaTensor)'\n"
154+
"Additional info: 1.0\n",
155+
]
156+
157+
TEST_CASE_10 = [
158+
MetaTensor(
159+
torch.tensor([[0, 1], [1, 2]]),
160+
affine=torch.as_tensor([[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], dtype=torch.float64),
161+
meta={"some": "info"},
162+
),
163+
"test data statistics:\nType: <class 'monai.data.meta_tensor.MetaTensor'> torch.int64\n"
164+
"Shape: torch.Size([2, 2])\nValue range: (0, 2)\n"
165+
"Value: tensor([[0, 1],\n [1, 2]])\n"
166+
"Meta info: {'some': 'info', affine: tensor([[2., 0., 0., 0.],\n"
167+
" [0., 2., 0., 0.],\n"
168+
" [0., 0., 2., 0.],\n"
169+
" [0., 0., 0., 1.]], dtype=torch.float64), space: RAS}\n"
170+
"Additional info: 1.0\n",
171+
]
172+
139173

140174
class TestDataStats(unittest.TestCase):
141175

142-
@parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7])
176+
@parameterized.expand(
177+
[TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7, TEST_CASE_8]
178+
)
143179
def test_value(self, input_param, input_data, expected_print):
144180
transform = DataStats(**input_param)
145181
_ = transform(input_data)
146182

147-
@parameterized.expand([TEST_CASE_8])
183+
@parameterized.expand([TEST_CASE_9, TEST_CASE_10])
148184
def test_file(self, input_data, expected_print):
149185
with tempfile.TemporaryDirectory() as tempdir:
150186
filename = os.path.join(tempdir, "test_data_stats.log")
@@ -159,6 +195,7 @@ def test_file(self, input_data, expected_print):
159195
"data_shape": True,
160196
"value_range": True,
161197
"data_value": True,
198+
"meta_info": True,
162199
"additional_info": np.mean,
163200
"name": name,
164201
}

tests/test_data_statsd.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import torch
2222
from parameterized import parameterized
2323

24+
from monai.data.meta_tensor import MetaTensor
2425
from monai.transforms import DataStatsd
2526
from monai.utils.misc import select_optimal_device
2627

@@ -151,22 +152,70 @@
151152
]
152153

153154
TEST_CASE_9 = [
155+
{
156+
"keys": "img",
157+
"prefix": "test data",
158+
"data_shape": True,
159+
"value_range": True,
160+
"data_value": True,
161+
"meta_info": False,
162+
"additional_info": np.mean,
163+
"name": "DataStats",
164+
},
154165
{"img": np.array([[0, 1], [1, 2]])},
155166
"test data statistics:\nType: <class 'numpy.ndarray'> int64\nShape: (2, 2)\nValue range: (0, 2)\n"
156167
"Value: [[0 1]\n [1 2]]\nAdditional info: 1.0\n",
157168
]
158169

170+
TEST_CASE_10 = [
171+
{"img": np.array([[0, 1], [1, 2]])},
172+
"test data statistics:\nType: <class 'numpy.ndarray'> int64\nShape: (2, 2)\nValue range: (0, 2)\n"
173+
"Value: [[0 1]\n [1 2]]\n"
174+
"Meta info: '(input is not a MetaTensor)'\n"
175+
"Additional info: 1.0\n",
176+
]
177+
178+
TEST_CASE_11 = [
179+
{
180+
"img": (
181+
MetaTensor(
182+
torch.tensor([[0, 1], [1, 2]]),
183+
affine=torch.as_tensor([[2, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], dtype=torch.float64),
184+
meta={"some": "info"},
185+
)
186+
)
187+
},
188+
"test data statistics:\nType: <class 'monai.data.meta_tensor.MetaTensor'> torch.int64\n"
189+
"Shape: torch.Size([2, 2])\nValue range: (0, 2)\n"
190+
"Value: tensor([[0, 1],\n [1, 2]])\n"
191+
"Meta info: {'some': 'info', affine: tensor([[2., 0., 0., 0.],\n"
192+
" [0., 2., 0., 0.],\n"
193+
" [0., 0., 2., 0.],\n"
194+
" [0., 0., 0., 1.]], dtype=torch.float64), space: RAS}\n"
195+
"Additional info: 1.0\n",
196+
]
197+
159198

160199
class TestDataStatsd(unittest.TestCase):
161200

162201
@parameterized.expand(
163-
[TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7, TEST_CASE_8]
202+
[
203+
TEST_CASE_1,
204+
TEST_CASE_2,
205+
TEST_CASE_3,
206+
TEST_CASE_4,
207+
TEST_CASE_5,
208+
TEST_CASE_6,
209+
TEST_CASE_7,
210+
TEST_CASE_8,
211+
TEST_CASE_9,
212+
]
164213
)
165214
def test_value(self, input_param, input_data, expected_print):
166215
transform = DataStatsd(**input_param)
167216
_ = transform(input_data)
168217

169-
@parameterized.expand([TEST_CASE_9])
218+
@parameterized.expand([TEST_CASE_10, TEST_CASE_11])
170219
def test_file(self, input_data, expected_print):
171220
with tempfile.TemporaryDirectory() as tempdir:
172221
filename = os.path.join(tempdir, "test_stats.log")
@@ -181,6 +230,7 @@ def test_file(self, input_data, expected_print):
181230
"data_shape": True,
182231
"value_range": True,
183232
"data_value": True,
233+
"meta_info": True,
184234
"additional_info": np.mean,
185235
"name": name,
186236
}

0 commit comments

Comments
 (0)