1414
1515import glob
1616import os
17+ import re
1718import shlex
1819import subprocess
1920from typing import Any
3435
3536__all__ = ["nnUNetV2Runner" ]
3637
38+ DATASET_ID_FORMAT = r"Dataset[0-9]{3}|[0-9]+" # regex format for a valid nnUnet dataset name
39+
3740
3841class nnUNetV2Runner : # noqa: N801
3942 """
@@ -195,6 +198,13 @@ def __init__(
195198
196199 # dataset_name_or_id has to be a string
197200 self .dataset_name_or_id = str (self .input_info .pop ("dataset_name_or_id" , 1 ))
201+ self .dataset_name : str | None = None
202+
203+ # ensure the dataset name is a single identifier/number, this prevents code injection when composing commands
204+ if re .fullmatch (DATASET_ID_FORMAT , self .dataset_name_or_id ) is None :
205+ raise ValueError (
206+ f"Value for dataset_name_or_id `{ self .dataset_name_or_id } ` not a valid dataset name or ID."
207+ )
198208
199209 try :
200210 from nnunetv2 .utilities .dataset_name_id_conversion import maybe_convert_to_dataset_name
@@ -239,7 +249,7 @@ def convert_dataset(self):
239249
240250 from nnunetv2 .utilities .dataset_name_id_conversion import maybe_convert_to_dataset_name
241251
242- self .dataset_name = maybe_convert_to_dataset_name (int ( self .dataset_name_or_id ) )
252+ self .dataset_name = maybe_convert_to_dataset_name (self .dataset_name_or_id )
243253
244254 datalist_json = ConfigParser .load_config_file (self .input_info .pop ("datalist" ))
245255
@@ -548,7 +558,7 @@ def train_single_model_command(
548558 Raises:
549559 ValueError: If gpu_id is an empty tuple or list.
550560 """
551- env = os .environ .copy ()
561+ env : dict [ str , str ] = os .environ .copy ()
552562 device_setting : str = "0"
553563 num_gpus = 1
554564 if isinstance (gpu_id , str ):
@@ -574,22 +584,25 @@ def train_single_model_command(
574584
575585 cmd = [
576586 "nnUNetv2_train" ,
577- f" { self .dataset_name_or_id } " ,
578- f" { config } " ,
579- f" { fold } " ,
587+ self .dataset_name_or_id ,
588+ config ,
589+ fold ,
580590 "-tr" ,
581- f" { self .trainer_class_name } " ,
591+ self .trainer_class_name ,
582592 "-num_gpus" ,
583- f" { num_gpus } " ,
593+ num_gpus ,
584594 ]
595+
585596 if self .export_validation_probabilities :
586597 cmd .append ("--npz" )
598+
587599 for _key , _value in kwargs .items ():
588- if _key == "p" or _key == "pretrained_weights" :
589- cmd .extend ([f"-{ _key } " , f"{ _value } " ])
590- else :
591- cmd .extend ([f"--{ _key } " , f"{ _value } " ])
592- return cmd , env
600+ prefix = "-" if _key in {"p" , "pretrained_weights" } else "--"
601+ cmd += [f"{ prefix } { _key } " , str (_value )]
602+
603+ cmd_str : list [str ] = [str (c ) for c in cmd ]
604+
605+ return cmd_str , env
593606
594607 def train (
595608 self ,
@@ -641,7 +654,14 @@ def train_parallel_cmd(
641654 None (all available GPUs).
642655 kwargs: this optional parameter allows you to specify additional arguments defined in the
643656 ``train_single_model`` method.
657+
658+ Raises:
659+ ValueError: self.dataset_name must have a value, ie. when using an existing dataset or after creating one.
644660 """
661+
662+ if self .dataset_name is None :
663+ raise ValueError (f"A valid dataset name must be given in { self .dataset_name = } ." )
664+
645665 # unpack compressed files
646666 folder_names = []
647667 for root , _ , files in os .walk (os .path .join (self .nnunet_preprocessed , self .dataset_name )):
@@ -696,7 +716,14 @@ def train_parallel(
696716 None (all available GPUs).
697717 kwargs: this optional parameter allows you to specify additional arguments defined in the
698718 ``train_single_model`` method.
719+
720+ Raises:
721+ ValueError: self.dataset_name must have a value, ie. when using an existing dataset or after creating one.
699722 """
723+
724+ if self .dataset_name is None :
725+ raise ValueError (f"A valid dataset name must be given in { self .dataset_name = } ." )
726+
700727 all_cmds = self .train_parallel_cmd (configs = configs , gpu_id_for_all = gpu_id_for_all , ** kwargs )
701728 for s , cmds in enumerate (all_cmds ):
702729 for gpu_id , gpu_cmd in cmds .items ():
@@ -908,7 +935,14 @@ def predict_ensemble_postprocessing(
908935 run_postprocessing: whether to conduct post-processing
909936 kwargs: this optional parameter allows you to specify additional arguments defined in the
910937 ``predict`` method.
938+
939+ Raises:
940+ ValueError: self.dataset_name must have a value, ie. when using an existing dataset or after creating one.
911941 """
942+
943+ if self .dataset_name is None :
944+ raise ValueError (f"A valid dataset name must be given in { self .dataset_name = } ." )
945+
912946 from nnunetv2 .ensembling .ensemble import ensemble_folders
913947 from nnunetv2 .postprocessing .remove_connected_components import apply_postprocessing_to_folder
914948 from nnunetv2 .utilities .file_path_utilities import get_output_folder
0 commit comments