24
24
from monai .deploy .packager .templates import Template
25
25
from monai .deploy .utils .fileutil import checksum
26
26
from monai .deploy .utils .importutil import dist_module_path , dist_requires , get_application
27
+ from monai .deploy .utils .spinner import ProgressSpinner
27
28
28
29
logger = logging .getLogger ("app_packager" )
29
30
30
31
executor_url = "https://globalcdn.nuget.org/packages/monai.deploy.executor.0.1.0-prealpha.0.nupkg"
31
32
32
33
34
+ def verify_base_image (base_image : str ) -> str :
35
+ """Helper function which validates whether valid base image passed to Packager.
36
+ Additionally, this function provides the string identifier of the dockerfile
37
+ template to build MAP
38
+ Args:
39
+ base_image (str): potential base image to build MAP docker image
40
+ Returns:
41
+ str: returns string identifier of the dockerfile template to build MAP
42
+ if valid base image provided, returns empty string otherwise
43
+ """
44
+ valid_prefixes = {"nvcr.io/nvidia/cuda" : "ubuntu" , "nvcr.io/nvidia/pytorch" : "pytorch" }
45
+
46
+ for prefix , template in valid_prefixes .items ():
47
+ if prefix in base_image :
48
+ return template
49
+
50
+ return ""
51
+
52
+
33
53
def initialize_args (args : Namespace ) -> Dict :
34
54
"""Processes and formats input arguements for Packager
35
-
36
55
Args:
37
56
args (Namespace): Input arguements for Packager from CLI
38
-
39
57
Returns:
40
58
Dict: Processed set of input arguements for Packager
41
59
"""
@@ -45,16 +63,49 @@ def initialize_args(args: Namespace) -> Dict:
45
63
processed_args ["application" ] = args .application
46
64
processed_args ["tag" ] = args .tag
47
65
processed_args ["docker_file_name" ] = DefaultValues .DOCKER_FILE_NAME
48
- processed_args ["base_image" ] = args .base if args .base else DefaultValues .BASE_IMAGE
49
66
processed_args ["working_dir" ] = args .working_dir if args .working_dir else DefaultValues .WORK_DIR
50
67
processed_args ["app_dir" ] = "/opt/monai/app"
51
68
processed_args ["executor_dir" ] = "/opt/monai/executor"
52
69
processed_args ["input_dir" ] = args .input if args .input_dir else DefaultValues .INPUT_DIR
53
70
processed_args ["output_dir" ] = args .output if args .output_dir else DefaultValues .OUTPUT_DIR
54
71
processed_args ["models_dir" ] = args .models if args .models_dir else DefaultValues .MODELS_DIR
55
- processed_args ["api-version " ] = DefaultValues . API_VERSION
72
+ processed_args ["no_cache " ] = args . no_cache
56
73
processed_args ["timeout" ] = args .timeout if args .timeout else DefaultValues .TIMEOUT
57
- processed_args ["version" ] = args .version if args .version else DefaultValues .VERSION
74
+ processed_args ["api-version" ] = DefaultValues .API_VERSION
75
+ processed_args ["requirements" ] = ""
76
+
77
+ if args .requirements :
78
+ if not args .requirements .endswith (".txt" ):
79
+ logger .error (
80
+ f"Improper path to requirements.txt provided: { args .requirements } , defaulting to sdk provided values"
81
+ )
82
+ else :
83
+ processed_args ["requirements" ] = args .requirements
84
+
85
+ # Verify proper base image:
86
+ dockerfile_type = ""
87
+
88
+ if args .base :
89
+ dockerfile_type = verify_base_image (args .base )
90
+ if not dockerfile_type :
91
+ logger .error (
92
+ "Provided base image '{}' is not supported \n \
93
+ Please provide a Cuda or Pytorch image from https://ngc.nvidia.com/ (nvcr.io/nvidia)" .format (
94
+ args .base
95
+ )
96
+ )
97
+ sys .exit (1 )
98
+
99
+ processed_args ["dockerfile_type" ] = dockerfile_type if args .base else DefaultValues .DOCKERFILE_TYPE
100
+
101
+ base_image = ""
102
+ if args .base :
103
+ base_image = args .base
104
+ elif os .getenv ("MONAI_BASEIMAGE" ):
105
+ base_image = os .getenv ("MONAI_BASEIMAGE" )
106
+ else :
107
+ base_image = DefaultValues .BASE_IMAGE
108
+ processed_args ["base_image" ] = base_image
58
109
59
110
# Obtain SDK provide application values
60
111
app_obj = get_application (args .application )
@@ -63,12 +114,14 @@ def initialize_args(args: Namespace) -> Dict:
63
114
else :
64
115
raise WrongValueError ("Application from '{}' not found" .format (args .application ))
65
116
117
+ # Use version number if provided through CLI, otherwise use value provided by SDK
118
+ processed_args ["version" ] = args .version if args .version else processed_args ["application_info" ]["app-version" ]
119
+
66
120
return processed_args
67
121
68
122
69
123
def build_image (args : dict , temp_dir : str ):
70
124
"""Creates dockerfile and builds MONAI Application Package (MAP) image
71
-
72
125
Args:
73
126
args (dict): Input arguements for Packager
74
127
temp_dir (str): Temporary directory to build MAP
@@ -77,6 +130,7 @@ def build_image(args: dict, temp_dir: str):
77
130
tag = args ["tag" ]
78
131
docker_file_name = args ["docker_file_name" ]
79
132
base_image = args ["base_image" ]
133
+ dockerfile_type = args ["dockerfile_type" ]
80
134
working_dir = args ["working_dir" ]
81
135
app_dir = args ["app_dir" ]
82
136
executor_dir = args ["executor_dir" ]
@@ -87,6 +141,9 @@ def build_image(args: dict, temp_dir: str):
87
141
models_dir = args ["models_dir" ]
88
142
timeout = args ["timeout" ]
89
143
application_path = args ["application" ]
144
+ local_requirements_file = args ["requirements" ]
145
+ no_cache = args ["no_cache" ]
146
+ app_version = args ["version" ]
90
147
91
148
# Copy application files to temp directory (under 'app' folder)
92
149
target_application_path = os .path .join (temp_dir , "app" )
@@ -96,13 +153,12 @@ def build_image(args: dict, temp_dir: str):
96
153
else :
97
154
shutil .copytree (application_path , target_application_path )
98
155
99
- # Copy monai-deploy- app-sdk module to temp directory (under 'monai-deploy-app-sdk' folder)
156
+ # Copy monai-app-sdk module to temp directory (under 'monai-deploy-app-sdk' folder)
100
157
monai_app_sdk_path = os .path .join (dist_module_path ("monai-deploy-app-sdk" ), "monai" , "deploy" )
101
158
target_monai_app_sdk_path = os .path .join (temp_dir , "monai-deploy-app-sdk" )
102
159
shutil .copytree (monai_app_sdk_path , target_monai_app_sdk_path )
103
160
104
161
# Parse SDK provided values
105
- app_version = args ["application_info" ]["app-version" ]
106
162
sdk_version = args ["application_info" ]["sdk-version" ]
107
163
local_models = args ["application_info" ]["models" ]
108
164
pip_packages = args ["application_info" ]["pip-packages" ]
@@ -115,7 +171,13 @@ def build_image(args: dict, temp_dir: str):
115
171
os .makedirs (pip_folder , exist_ok = True )
116
172
pip_requirements_path = os .path .join (pip_folder , "requirements.txt" )
117
173
with open (pip_requirements_path , "w" ) as requirements_file :
118
- requirements_file .writelines ("\n " .join (pip_packages ))
174
+ # Use local requirements.txt packages if provided, otherwise use sdk provided packages
175
+ if local_requirements_file :
176
+ with open (local_requirements_file , "r" ) as lr :
177
+ for line in lr :
178
+ requirements_file .write (line )
179
+ else :
180
+ requirements_file .writelines ("\n " .join (pip_packages ))
119
181
map_requirements_path = "/tmp/requirements.txt"
120
182
121
183
# Copy model files to temp directory (under 'model' folder)
@@ -153,7 +215,7 @@ def build_image(args: dict, temp_dir: str):
153
215
"timeout" : timeout ,
154
216
"working_dir" : working_dir ,
155
217
}
156
- docker_template_string = Template .get_template ("pytorch" ).format (** template_params )
218
+ docker_template_string = Template .get_template (dockerfile_type ).format (** template_params )
157
219
158
220
# Write out dockerfile
159
221
logger .debug (docker_template_string )
@@ -169,34 +231,25 @@ def build_image(args: dict, temp_dir: str):
169
231
170
232
# Build dockerfile into an MAP image
171
233
docker_build_cmd = ["docker" , "build" , "-f" , docker_file_path , "-t" , tag , temp_dir ]
234
+ if no_cache :
235
+ docker_build_cmd .append ("--no-cache" )
172
236
proc = subprocess .Popen (docker_build_cmd , stdout = subprocess .PIPE )
173
237
174
- def build_spinning_wheel ():
175
- while True :
176
- for cursor in "|/-\\ " :
177
- yield cursor
178
-
179
- spinner = build_spinning_wheel ()
180
-
181
- print ("Building MONAI Application Package... " )
238
+ spinner = ProgressSpinner ("Building MONAI Application Package... " )
239
+ spinner .start ()
182
240
183
241
while proc .poll () is None :
184
- if proc .stdout :
185
- logger .debug (proc .stdout .readline ().decode ("utf-8" ))
186
- sys .stdout .write (next (spinner ))
187
- sys .stdout .flush ()
188
- sys .stdout .write ("\b " )
189
- sys .stdout .write ("\b " )
242
+ logger .debug (proc .stdout .readline ().decode ("utf-8" ))
190
243
244
+ spinner .stop ()
191
245
return_code = proc .returncode
192
246
193
247
if return_code == 0 :
194
- print (f"Successfully built { tag } " )
248
+ logger . info (f"Successfully built { tag } " )
195
249
196
250
197
251
def create_app_manifest (args : Dict , temp_dir : str ):
198
252
"""Creates Application manifest .json file
199
-
200
253
Args:
201
254
args (Dict): Input arguements for Packager
202
255
temp_dir (str): Temporary directory to build MAP
@@ -237,7 +290,6 @@ def create_app_manifest(args: Dict, temp_dir: str):
237
290
238
291
def create_package_manifest (args : Dict , temp_dir : str ):
239
292
"""Creates package manifest .json file
240
-
241
293
Args:
242
294
args (Dict): Input arguements for Packager
243
295
temp_dir (str): Temporary directory to build MAP
@@ -284,7 +336,6 @@ def create_package_manifest(args: Dict, temp_dir: str):
284
336
def package_application (args : Namespace ):
285
337
"""Driver function for invoking all functions for creating and
286
338
building the MONAI Application package image
287
-
288
339
Args:
289
340
args (Namespace): Input arguements for Packager from CLI
290
341
"""
0 commit comments