diff --git a/monai/utils/__init__.py b/monai/utils/__init__.py index 8eccac8f70..c5419cb9af 100644 --- a/monai/utils/__init__.py +++ b/monai/utils/__init__.py @@ -78,6 +78,7 @@ set_determinism, star_zip_with, str2bool, + str2list, zip_with, ) from .module import ( diff --git a/monai/utils/misc.py b/monai/utils/misc.py index d4bea4d27c..1071f37840 100644 --- a/monai/utils/misc.py +++ b/monai/utils/misc.py @@ -47,6 +47,7 @@ "MAX_SEED", "copy_to_device", "str2bool", + "str2list", "MONAIEnvVars", "ImageMetaKey", "is_module_ver_at_least", @@ -363,21 +364,29 @@ def copy_to_device( return obj -def str2bool(value: str, default: bool = False, raise_exc: bool = True) -> bool: +def str2bool(value: Union[str, bool], default: bool = False, raise_exc: bool = True) -> bool: """ Convert a string to a boolean. Case insensitive. True: yes, true, t, y, 1. False: no, false, f, n, 0. Args: - value: string to be converted to a boolean. + value: string to be converted to a boolean. If value is a bool already, simply return it. raise_exc: if value not in tuples of expected true or false inputs, - should we raise an exception? If not, return `None`. + should we raise an exception? If not, return `default`. Raises ValueError: value not in tuples of expected true or false inputs and `raise_exc` is `True`. + Useful with argparse, for example: + parser.add_argument("--convert", default=False, type=str2bool) + python mycode.py --convert=True """ + + if isinstance(value, bool): + return value + true_set = ("yes", "true", "t", "y", "1") false_set = ("no", "false", "f", "n", "0") + if isinstance(value, str): value = value.lower() if value in true_set: @@ -390,6 +399,38 @@ def str2bool(value: str, default: bool = False, raise_exc: bool = True) -> bool: return default +def str2list(value: Optional[Union[str, list]], raise_exc: bool = True) -> Optional[list]: + """ + Convert a string to a list. Useful with argparse commandline arguments: + parser.add_argument("--blocks", default=[1,2,3], type=str2list) + python mycode.py --blocks=1,2,2,4 + + Args: + value: string (comma separated) to be converted to a list + raise_exc: if not possible to convert to a list, raise an exception + Raises + ValueError: value not a string or list or not possible to convert + """ + + if value is None: + return None + elif isinstance(value, list): + return value + elif isinstance(value, str): + v = value.split(",") + for i in range(len(v)): + try: + a = literal_eval(v[i].strip()) # attempt to convert + v[i] = a + except Exception: + pass + return v + elif raise_exc: + raise ValueError(f'Unable to convert "{value}", expected a comma-separated str, e.g. 1,2,3') + + return None + + class MONAIEnvVars: """ Environment variables used by MONAI. diff --git a/tests/test_str2bool.py b/tests/test_str2bool.py index a7132aa97e..e1d9ca1ee3 100644 --- a/tests/test_str2bool.py +++ b/tests/test_str2bool.py @@ -16,9 +16,9 @@ class TestStr2Bool(unittest.TestCase): def test_str_2_bool(self): - for i in ("yes", "true", "t", "y", "1"): + for i in ("yes", "true", "t", "y", "1", True): self.assertTrue(str2bool(i)) - for i in ("no", "false", "f", "n", "0"): + for i in ("no", "false", "f", "n", "0", False): self.assertFalse(str2bool(i)) for bad_value in ("test", 0, 1, 2, None): self.assertFalse(str2bool(bad_value, default=False, raise_exc=False)) diff --git a/tests/test_str2list.py b/tests/test_str2list.py new file mode 100644 index 0000000000..95a4dcaef0 --- /dev/null +++ b/tests/test_str2list.py @@ -0,0 +1,30 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from monai.utils.misc import str2list + + +class TestStr2List(unittest.TestCase): + def test_str_2_list(self): + for i in ("1,2,3", "1, 2, 3", "1,2e-0,3.0", [1, 2, 3]): + self.assertEqual(str2list(i), [1, 2, 3]) + for i in ("1,2,3", "1,2,3,4.3", [1, 2, 3, 4.001]): + self.assertNotEqual(str2list(i), [1, 2, 3, 4]) + for bad_value in ((1, 3), int): + self.assertIsNone(str2list(bad_value, raise_exc=False)) + with self.assertRaises(ValueError): + self.assertIsNone(str2list(bad_value)) + + +if __name__ == "__main__": + unittest.main()