anatools 5.1.28__py3-none-any.whl → 6.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. anatools/__init__.py +1 -1
  2. anatools/anaclient/anaclient.py +16 -15
  3. anatools/anaclient/api/api.py +7 -2
  4. anatools/anaclient/api/datasets.py +21 -3
  5. anatools/anaclient/api/handlers.py +1 -1
  6. anatools/anaclient/channels.py +50 -25
  7. anatools/anaclient/datasets.py +94 -7
  8. anatools/anaclient/helpers.py +11 -10
  9. anatools/anaclient/services.py +46 -20
  10. anatools/anaclient/volumes.py +19 -18
  11. anatools/annotations/annotations.py +39 -18
  12. anatools/annotations/draw.py +34 -18
  13. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anadeploy +4 -0
  14. anatools-6.0.1.data/scripts/renderedai +4166 -0
  15. {anatools-5.1.28.dist-info → anatools-6.0.1.dist-info}/METADATA +1 -1
  16. {anatools-5.1.28.dist-info → anatools-6.0.1.dist-info}/RECORD +27 -26
  17. {anatools-5.1.28.dist-info → anatools-6.0.1.dist-info}/WHEEL +1 -1
  18. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/ana +0 -0
  19. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anamount +0 -0
  20. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anaprofile +0 -0
  21. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anarules +0 -0
  22. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anaserver +0 -0
  23. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anatransfer +0 -0
  24. {anatools-5.1.28.data → anatools-6.0.1.data}/scripts/anautils +0 -0
  25. {anatools-5.1.28.dist-info → anatools-6.0.1.dist-info}/entry_points.txt +0 -0
  26. {anatools-5.1.28.dist-info → anatools-6.0.1.dist-info}/licenses/LICENSE +0 -0
  27. {anatools-5.1.28.dist-info → anatools-6.0.1.dist-info}/top_level.txt +0 -0
@@ -174,15 +174,40 @@ def build_service(self, servicefile):
174
174
  disk_usage = shutil.disk_usage('/')
175
175
  gb_free = disk_usage.free / (1024**3) # Convert bytes to GB
176
176
  if self.verbose == 'debug': print(f"Disk space left: {gb_free:.3f}GB")
177
- if gb_free < 20: print_color(f'\nWarning: Low disk space detected! Only {gb_free:.1f}GB available.', 'ffff00')
177
+ if gb_free < 20 and self.interactive: print_color(f'\nWarning: Low disk space detected! Only {gb_free:.1f}GB available.', 'ffff00')
178
178
  docker_space_cmd = "docker system df --format json"
179
179
  result = subprocess.run(docker_space_cmd, shell=True, capture_output=True, text=True)
180
180
  if result.returncode == 0:
181
- docker_stats = json.loads(result.stdout)
182
- total_docker_size = sum(item.get('Size', 0) for item in docker_stats if 'Size' in item)
183
- docker_gb = total_docker_size / (1024**3) # Convert bytes to GB
181
+ # Parse Docker output which may be NDJSON (newline-delimited) or concatenated JSON objects
182
+ # Use JSONDecoder.raw_decode() to handle both formats robustly
183
+ import re
184
+ def parse_docker_json(data):
185
+ results = []
186
+ decoder = json.JSONDecoder()
187
+ idx = 0
188
+ data = data.strip()
189
+ while idx < len(data):
190
+ obj, end_idx = decoder.raw_decode(data, idx)
191
+ results.append(obj)
192
+ idx = end_idx
193
+ # Skip any whitespace between objects
194
+ while idx < len(data) and data[idx].isspace():
195
+ idx += 1
196
+ return results
197
+ docker_stats = parse_docker_json(result.stdout)
198
+ # Parse human-readable size strings (e.g., "85.19GB", "2.087MB", "0B") to GB
199
+ def parse_size_to_gb(size_str):
200
+ if not size_str or size_str == '0B':
201
+ return 0.0
202
+ match = re.match(r'^([\d.]+)([KMGT]?B)$', size_str)
203
+ if not match:
204
+ return 0.0
205
+ value, unit = float(match.group(1)), match.group(2)
206
+ multipliers = {'B': 1/(1024**3), 'KB': 1/(1024**2), 'MB': 1/1024, 'GB': 1, 'TB': 1024}
207
+ return value * multipliers.get(unit, 0)
208
+ docker_gb = sum(parse_size_to_gb(item.get('Size', '0B')) for item in docker_stats)
184
209
  if self.verbose == 'debug': print(f"Docker space used: {docker_gb:.3f}GB")
185
- if docker_gb > 20:
210
+ if docker_gb > 20 and self.interactive:
186
211
  print_color(f'\nWarning: Docker is using {docker_gb:.1f}GB of disk space!', 'ffff00')
187
212
  print_color('Consider running "docker system prune -a" to free up space.', 'ffff00')
188
213
  print_color('This will remove:', 'ffff00')
@@ -191,10 +216,11 @@ def build_service(self, servicefile):
191
216
  print_color(' - All dangling images', 'ffff00')
192
217
  print_color(' - All dangling build cache', 'ffff00')
193
218
  except Exception as e:
219
+ if self.verbose == 'debug': print(f"Docker space check failed: {e}")
194
220
  pass
195
221
 
196
222
  # check for service Dockerfile
197
- print('Building Service Image...', end='', flush=True)
223
+ if self.interactive: print('Building Service Image...', end='', flush=True)
198
224
  if not os.path.isfile(servicefile): raise Exception(f'No service file {servicefile} found.')
199
225
  servicedir, servicefile = os.path.split(servicefile)
200
226
  imgtime = int(time.time())
@@ -207,12 +233,12 @@ def build_service(self, servicefile):
207
233
  try:
208
234
  start = time.time()
209
235
  streamer = dockerclient.build(
210
- path=servicedir,
211
- dockerfile=os.path.join(servicedir, '.devcontainer/Dockerfile'),
212
- tag=image,
213
- rm=True,
214
- decode=True,
215
- platform='linux/amd64',
236
+ path=servicedir,
237
+ dockerfile=os.path.join(servicedir, '.devcontainer/Dockerfile'),
238
+ tag=image,
239
+ rm=True,
240
+ decode=True,
241
+ platform='linux/amd64',
216
242
  nocache=False,
217
243
  pull=False)
218
244
  logfilepath = os.path.join(tempfile.gettempdir(), 'dockerbuild.log')
@@ -225,7 +251,7 @@ def build_service(self, servicefile):
225
251
  if 'error' in output: print_color(f'{output["error"]}', 'ff0000')
226
252
  if 'stream' in output: logfile.write(output['stream'])
227
253
  if 'error' in output: logfile.write(output["error"])
228
- print(f'\rBuilding Service Image... [{time.time()-start:.3f}s]', end='', flush=True)
254
+ if self.interactive: print(f'\rBuilding Service Image... [{time.time()-start:.3f}s]', end='', flush=True)
229
255
  except StopIteration:
230
256
  time.sleep(1)
231
257
  try:
@@ -238,7 +264,7 @@ def build_service(self, servicefile):
238
264
  logfile.close()
239
265
  raise Exception(f'Error encountered while building Docker image. Please check logfile {logfilepath}.')
240
266
  if self.verbose != 'debug' and os.path.exists(logfilepath): os.remove(logfilepath)
241
- print(f"\rBuilding Service Image...done. [{time.time()-start:.3f}s]", flush=True)
267
+ if self.interactive: print(f"\rBuilding Service Image...done. [{time.time()-start:.3f}s]", flush=True)
242
268
  return image
243
269
 
244
270
 
@@ -296,13 +322,13 @@ def deploy_service(self, serviceId=None, servicefile=None):
296
322
  progressDetail = line['progressDetail']
297
323
  if progressDetail['total'] >= largest:
298
324
  largest = progressDetail['total']
299
- print(f"\rPushing Service Image... [{time.time()-start:.3f}s, {min(100,round((progressDetail['current']/progressDetail['total']) * 100))}%]", end='', flush=True)
300
- if 'error' in line:
325
+ if self.interactive: print(f"\rPushing Service Image... [{time.time()-start:.3f}s, {min(100,round((progressDetail['current']/progressDetail['total']) * 100))}%]", end='', flush=True)
326
+ if 'error' in line:
301
327
  if 'HTTP 403' in line['error']: raise Exception('You do not have permission to push to this repository.')
302
328
  else: raise Exception(line['error'])
303
329
  logfile.close()
304
330
  if self.verbose != 'debug' and os.path.exists(logfilepath): os.remove(logfilepath)
305
- print(f"\rPushing Service Image...done. [{time.time()-start:.3f}s] ", flush=True)
331
+ if self.interactive: print(f"\rPushing Service Image...done. [{time.time()-start:.3f}s] ", flush=True)
306
332
 
307
333
  # cleanup docker and update services
308
334
  dockerclient.images.remove(reponame)
@@ -331,13 +357,13 @@ def get_service_deployment(self, deploymentId, stream=False):
331
357
  if deploymentId is None: raise Exception('DeploymentId must be specified.')
332
358
  if stream:
333
359
  data = self.ana_api.getServiceDeployment(deploymentId=deploymentId)
334
- print(f"\r\tStep {data['status']['step']} - {data['status']['message']}", end='', flush=True)
360
+ if self.interactive: print(f"\r\tStep {data['status']['step']} - {data['status']['message']}", end='', flush=True)
335
361
  while (data['status']['state'] not in ['Service Deployment Complete','Service Deployment Failed']):
336
362
  time.sleep(10)
337
- print(f"\r\tStep {data['status']['step']} - {data['status']['message']}", end='', flush=True)
363
+ if self.interactive: print(f"\r\tStep {data['status']['step']} - {data['status']['message']}", end='', flush=True)
338
364
  if self.check_logout(): return
339
365
  data = self.ana_api.getServiceDeployment(deploymentId=deploymentId)
340
- print(f"\r\tStep {data['status']['step']} - {data['status']['message']}", flush=True)
366
+ if self.interactive: print(f"\r\tStep {data['status']['step']} - {data['status']['message']}", flush=True)
341
367
  return data
342
368
  else: return self.ana_api.getServiceDeployment(deploymentId=deploymentId)
343
369
 
@@ -274,8 +274,8 @@ def download_volume_data(self, volumeId, files=[], localDir=None, recursive=True
274
274
  if self.interactive:
275
275
  print(f"\x1b[1K\rdownload: {response[index]['key']} to {filename}", flush=True)
276
276
  except:
277
- traceback.print_exc()
278
- print(f"\x1b[1K\rdownload: failed to download {response[index]['key']}", flush=True)
277
+ if self.verbose: traceback.print_exc()
278
+ if self.interactive: print(f"\x1b[1K\rdownload: failed to download {response[index]['key']}", flush=True)
279
279
  return
280
280
 
281
281
 
@@ -330,7 +330,8 @@ def upload_volume_data(self, volumeId, files=None, localDir=None, destinationDir
330
330
  if sync == True:
331
331
  file_hash = generate_etag(filepath)
332
332
  source_hashes.append(file + file_hash)
333
- else: print(f"Could not find {filepath}.")
333
+ else:
334
+ if self.interactive: print(f"Could not find {filepath}.")
334
335
  else:
335
336
  for root, dirs, files in os.walk(localDir):
336
337
  for file in files:
@@ -371,37 +372,37 @@ def upload_volume_data(self, volumeId, files=None, localDir=None, destinationDir
371
372
  delete_files.append(destination_file)
372
373
 
373
374
  if (len(delete_files)):
374
- print(f"The following files will be deleted:", end='\n', flush=True)
375
- for file in delete_files:
376
- print(f" {file}", end='\n', flush=True)
377
- answer = input("Delete these files [Y/n]: ")
378
- if answer.lower() == "y":
379
- self.refresh_token()
380
- self.ana_api.deleteVolumeData(volumeId=volumeId, keys=delete_files)
375
+ if self.interactive:
376
+ print(f"The following files will be deleted:", end='\n', flush=True)
377
+ for file in delete_files:
378
+ print(f" {file}", end='\n', flush=True)
379
+ answer = input("Delete these files [Y/n]: ")
380
+ if answer.lower() == "y":
381
+ self.refresh_token()
382
+ self.ana_api.deleteVolumeData(volumeId=volumeId, keys=delete_files)
381
383
 
382
384
  for index, file in enumerate(source_files):
383
385
  destination_key = (destinationDir or '') + file
384
- print(f"\x1b[1K\rUploading {file} to the volume [{index+1} / {len(source_files)}]", end='\n' if self.verbose else '', flush=True)
386
+ if self.interactive: print(f"\x1b[1K\rUploading {file} to the volume [{index+1} / {len(source_files)}]", end='\n' if self.verbose else '', flush=True)
385
387
  if (sync == True and (source_hashes[index] in destination_hashes)):
386
- print(f"\x1b[1K\rsync: {file}'s hash exists", end='\n' if self.verbose else '', flush=True)
388
+ if self.interactive: print(f"\x1b[1K\rsync: {file}'s hash exists", end='\n' if self.verbose else '', flush=True)
387
389
  elif sync == False or (source_hashes[index] not in destination_hashes):
388
390
  try:
389
391
  self.refresh_token()
390
392
  filepath = os.path.join(localDir, file)
391
393
  filesize = os.path.getsize(filepath)
392
394
  fileinfo = self.ana_api.uploadVolumeData(volumeId=volumeId, key=destination_key, size=filesize)
393
- # print(f"\x1b[1K\rupload: {file} to the volume. [{index+1} / {len(source_files)}]", end='\n' if self.verbose else '', flush=True)
394
- parts = multipart_upload_file(filepath, int(fileinfo["partSize"]), fileinfo["urls"], f"Uploading {file} to the volume [{index+1} / {len(source_files)}]")
395
+ parts = multipart_upload_file(filepath, int(fileinfo["partSize"]), fileinfo["urls"], f"Uploading {file} to the volume [{index+1} / {len(source_files)}]", interactive=self.interactive)
395
396
  self.refresh_token()
396
397
  finalize_success = self.ana_api.uploadVolumeDataFinalizer(uploadId=fileinfo['uploadId'], key=fileinfo['key'], parts=parts)
397
398
  if not finalize_success:
398
399
  faileduploads.append(file)
399
400
  except:
400
- traceback.print_exc()
401
+ if self.verbose: traceback.print_exc()
401
402
  faileduploads.append(file)
402
- print(f"\x1b[1K\rupload: {file} failed", end='\n' if self.verbose else '', flush=True)
403
- print("\x1b[1K\rUploading files completed.", flush=True)
404
- if len(faileduploads): print('The following files failed to upload:', faileduploads, flush=True)
403
+ if self.interactive: print(f"\x1b[1K\rupload: {file} failed", end='\n' if self.verbose else '', flush=True)
404
+ if self.interactive: print("\x1b[1K\rUploading files completed.", flush=True)
405
+ if len(faileduploads) and self.interactive: print('The following files failed to upload:', faileduploads, flush=True)
405
406
  return
406
407
 
407
408
 
@@ -14,15 +14,15 @@ class annotations:
14
14
  Examples of mapfiles are in the example channel at /ana/channels/example/mapfiles/.
15
15
  """
16
16
 
17
- def bounding_box_2d(self, image_path, out_dir, object_ids=None, object_types=None, line_thickness=1, colors=None):
17
+ def bounding_box_2d(self, image_path, out_dir, object_ids=None, object_types=None, line_thickness=1, colors=None, quiet=False):
18
18
  """
19
- Generates images annotated with 2d bounding boxes for datasets downloaded from the Platform. Optional filter on
19
+ Generates images annotated with 2d bounding boxes for datasets downloaded from the Platform. Optional filter on
20
20
  object_ids or object_types (must choose a single filter).
21
-
21
+
22
22
  Parameters
23
23
  ----------
24
24
  image_path : str
25
- Path to of specific image file to draw the boxes for.
25
+ Path to of specific image file to draw the boxes for.
26
26
  out_dir : str
27
27
  File path to directory where the image should be saved to.
28
28
  object_ids : list[int]
@@ -30,22 +30,29 @@ class annotations:
30
30
  object_types: list[str]
31
31
  Filter for the object types to annotate. If not provided, all object types will get annotated. Choose either id or type filter.
32
32
  line_thickness: int
33
- Desired line thickness for box outline.
34
- colors: dict
33
+ Desired line thickness for box outline.
34
+ colors: dict
35
35
  Dictionary of colors to use for each object type where each color is a tuple of 3 integers (R, G, B). For example: colors={'Car': (255, 0, 0)}
36
+ quiet: bool
37
+ If True, suppress print output. Useful for CLI/automation.
38
+
39
+ Returns
40
+ -------
41
+ str or None
42
+ Path to the saved annotated image, or None if an error occurred.
36
43
  """
37
- draw(image_path, out_dir, draw_type='box_2d', object_ids=object_ids, object_types=object_types, line_thickness=line_thickness, colors=colors)
44
+ return draw(image_path, out_dir, draw_type='box_2d', object_ids=object_ids, object_types=object_types, line_thickness=line_thickness, colors=colors, quiet=quiet)
38
45
 
39
46
 
40
- def bounding_box_3d(self, image_path, out_dir, object_ids=None, object_types=None, line_thickness=1, colors=None):
47
+ def bounding_box_3d(self, image_path, out_dir, object_ids=None, object_types=None, line_thickness=1, colors=None, quiet=False):
41
48
  """
42
- Generates images annotated with 3d bounding boxes for datasets downloaded from the Platform. Optional filter on
49
+ Generates images annotated with 3d bounding boxes for datasets downloaded from the Platform. Optional filter on
43
50
  object_ids or object_types (must choose a single filter).
44
-
51
+
45
52
  Parameters
46
53
  ----------
47
54
  image_path : str
48
- Path to of specific image file to draw the boxes for.
55
+ Path to of specific image file to draw the boxes for.
49
56
  out_dir : str
50
57
  File path to directory where the image should be saved to.
51
58
  object_ids : list[int]
@@ -53,22 +60,29 @@ class annotations:
53
60
  object_types: list[str]
54
61
  Filter for the object types to annotate. If not provided, all object types will get annotated. Choose either id or type filter.
55
62
  line_thickness: int
56
- Desired line thickness for box outline.
63
+ Desired line thickness for box outline.
57
64
  colors: dict
58
65
  Dictionary of colors to use for each object type where each color is a tuple of 3 integers (R, G, B). For example: colors={'Car': (255, 0, 0)}
66
+ quiet: bool
67
+ If True, suppress print output. Useful for CLI/automation.
68
+
69
+ Returns
70
+ -------
71
+ str or None
72
+ Path to the saved annotated image, or None if an error occurred.
59
73
  """
60
- draw(image_path, out_dir, draw_type='box_3d', object_ids=object_ids, object_types=object_types, line_thickness=line_thickness, colors=colors)
74
+ return draw(image_path, out_dir, draw_type='box_3d', object_ids=object_ids, object_types=object_types, line_thickness=line_thickness, colors=colors, quiet=quiet)
61
75
 
62
76
 
63
- def segmentation(self, image_path, out_dir, object_ids=None, object_types=None, line_thickness=1, colors=None):
77
+ def segmentation(self, image_path, out_dir, object_ids=None, object_types=None, line_thickness=1, colors=None, quiet=False):
64
78
  """
65
- Generates images annotated with outlines around objects for datasets downloaded from the Platform. Optional filter on
79
+ Generates images annotated with outlines around objects for datasets downloaded from the Platform. Optional filter on
66
80
  object_ids or object_types (must choose a single filter).
67
-
81
+
68
82
  Parameters
69
83
  ----------
70
84
  image_path : str
71
- Path to of specific image file to draw the boxes for.
85
+ Path to of specific image file to draw the boxes for.
72
86
  out_dir : str
73
87
  File path to directory where the image should be saved to.
74
88
  object_ids : list[int]
@@ -79,8 +93,15 @@ class annotations:
79
93
  Desired line thickness for object segmentation outline.
80
94
  colors: dict
81
95
  Dictionary of colors to use for each object type where each color is a tuple of 3 integers (R, G, B). For example: colors={'Car': (255, 0, 0)}
96
+ quiet: bool
97
+ If True, suppress print output. Useful for CLI/automation.
98
+
99
+ Returns
100
+ -------
101
+ str or None
102
+ Path to the saved annotated image, or None if an error occurred.
82
103
  """
83
- draw(image_path, out_dir, draw_type='segmentation', object_ids=object_ids, object_types=object_types, line_thickness=line_thickness, colors=colors)
104
+ return draw(image_path, out_dir, draw_type='segmentation', object_ids=object_ids, object_types=object_types, line_thickness=line_thickness, colors=colors, quiet=quiet)
84
105
 
85
106
 
86
107
  def dump_classification(self, datadir, outdir, mapfile=None):
@@ -4,14 +4,14 @@ import json
4
4
  import os
5
5
 
6
6
 
7
- def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=None, line_thickness=1, colors=None):
7
+ def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=None, line_thickness=1, colors=None, quiet=False):
8
8
  """
9
9
  This function handles the io and draws the right type of annotation on the image.
10
10
 
11
11
  Parameters
12
12
  ----------
13
13
  image_path : str
14
- Path to of specific image file to draw the boxes for.
14
+ Path to of specific image file to draw the boxes for.
15
15
  out_dir : str
16
16
  File path to directory where the image should be saved to.
17
17
  draw_type : str
@@ -24,15 +24,24 @@ def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=
24
24
  Desired line thickness for box outline.
25
25
  colors: dict
26
26
  Dictionary of colors to use for each object type. Must provide a color for each object type specified in the filter.
27
+ quiet: bool
28
+ If True, suppress print output. Useful for CLI/automation.
29
+
30
+ Returns
31
+ -------
32
+ str or None
33
+ Path to the saved annotated image, or None if an error occurred.
27
34
  """
28
35
 
29
36
  if not os.path.exists(image_path):
30
- print('Incorrect path to images: ' + image_path)
31
- return
37
+ if not quiet:
38
+ print('Incorrect path to images: ' + image_path)
39
+ return None
32
40
 
33
41
  if object_types and object_ids:
34
- print('Provide either object_types OR object_ids. ')
35
- return
42
+ if not quiet:
43
+ print('Provide either object_types OR object_ids. ')
44
+ return None
36
45
 
37
46
  if not os.path.exists(out_dir):
38
47
  os.makedirs(out_dir)
@@ -47,8 +56,8 @@ def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=
47
56
  file.close()
48
57
 
49
58
  annotation_ids = [data['id'] for data in annotations['annotations']]
50
- if object_ids is not None and not check_lists(annotation_ids, object_ids, 'object_ids'):
51
- return
59
+ if object_ids is not None and not check_lists(annotation_ids, object_ids, 'object_ids', quiet=quiet):
60
+ return None
52
61
 
53
62
 
54
63
  metadata_file = root_dir+'/metadata/'+image_name+'-metadata.json'
@@ -62,8 +71,8 @@ def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=
62
71
  if object_ids:
63
72
  object_data = [data for data in metadata['objects'] if data['id'] in object_ids]
64
73
  elif object_types is not None:
65
- if not check_lists(metadata_types, object_types, 'object_types'):
66
- return
74
+ if not check_lists(metadata_types, object_types, 'object_types', quiet=quiet):
75
+ return None
67
76
  object_data = [data for data in metadata['objects'] if data['type'] in object_types]
68
77
  else:
69
78
  object_data = [data for data in metadata['objects']]
@@ -74,14 +83,15 @@ def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=
74
83
  generate_color = [int(val) for val in np.random.randint(0, 255, 3)]
75
84
  type_colors[type] = tuple(generate_color)
76
85
  else: type_colors = colors
77
-
86
+
78
87
 
79
88
  draw_img = Image.open(image_path)
80
89
  for object in annotations['annotations']:
81
90
  if any(object['id'] == d['id'] for d in object_data):
82
91
  color_info = [data for data in object_data if object['id'] == data['id']]
83
92
  if color_info[0]['type'] not in type_colors:
84
- print(f'Color for {color_info[0]["type"]} not found in colors dictionary. Skipping object.')
93
+ if not quiet:
94
+ print(f'Color for {color_info[0]["type"]} not found in colors dictionary. Skipping object.')
85
95
  continue
86
96
  if draw_type == 'box_2d':
87
97
  draw_img = box_2d(object['bbox'], draw_img, line_thickness, type_colors[color_info[0]['type']])
@@ -90,11 +100,14 @@ def draw(image_path, out_dir, draw_type='box_2d', object_ids=None, object_types=
90
100
  elif draw_type == 'segmentation':
91
101
  draw_img = segmentation(object['segmentation'], draw_img, line_thickness, type_colors[color_info[0]['type']])
92
102
  else:
93
- print('Provide either box_2d, box_3d, or segmentation')
103
+ if not quiet:
104
+ print('Provide either box_2d, box_3d, or segmentation')
94
105
 
95
106
  outimg = out_dir+'/'+image_name+'-annotated-'+draw_type+'.'+image_ext
96
107
  draw_img.save(outimg)
97
- print(f'Image saved to {outimg}')
108
+ if not quiet:
109
+ print(f'Image saved to {outimg}')
110
+ return outimg
98
111
 
99
112
 
100
113
  def box_2d(coordinates, bbox_img, width, outline):
@@ -193,7 +206,7 @@ def segmentation(coordinates, draw_img, width, color):
193
206
  return draw_img
194
207
 
195
208
 
196
- def check_lists(actual, expected, name_to_check):
209
+ def check_lists(actual, expected, name_to_check, quiet=False):
197
210
  """
198
211
  Helper function that checks if one list is in another for validation on draw inputs.
199
212
 
@@ -205,16 +218,19 @@ def check_lists(actual, expected, name_to_check):
205
218
  The expected list that is provided from the user.
206
219
  name_to_check: str
207
220
  Name of parameter to check (either object_id or object_type).
221
+ quiet: bool
222
+ If True, suppress print output.
208
223
 
209
224
  Returns
210
225
  -------
211
226
  bool
212
227
  True if lists are matching, False otherwise.
213
228
  """
214
-
229
+
215
230
  if not set(expected).issubset(set(actual)):
216
231
  out_of_bounds_check= list(set(expected) - set(actual))
217
- print(f'Provided {name_to_check} list has the following out of bounds: {out_of_bounds_check}. Please rerun with valid list. \nHere are all the {name_to_check} that can get annotated: ')
218
- print(list(set(actual)))
232
+ if not quiet:
233
+ print(f'Provided {name_to_check} list has the following out of bounds: {out_of_bounds_check}. Please rerun with valid list. \nHere are all the {name_to_check} that can get annotated: ')
234
+ print(list(set(actual)))
219
235
  return False
220
236
  return True
@@ -332,6 +332,7 @@ try:
332
332
  while resp not in [str(i) for i in range(len(channels)+1)]:
333
333
  resp = input(f'Invalid input, please enter a number between 0 and {len(channels)}: ')
334
334
  remotechannel = channels[int(resp)-1]
335
+ print_color(f"Selected channel: {remotechannel['name']}", color='brand')
335
336
  if volumes != remotechannel['volumes']:
336
337
  client.edit_channel(channelId=remotechannel['channelId'], volumes=volumes)
337
338
  remotechannel = client.get_channels(channelId=remotechannel['channelId'])[0]
@@ -346,6 +347,7 @@ try:
346
347
  if channels == False or len(channels) == 0: raise Exception(f"Channel {args.channelId} not found")
347
348
  remotechannel = channels[0]
348
349
  starttime = time.time()
350
+ client.interactive = True
349
351
  deploymentId = client.deploy_channel(channelId=remotechannel['channelId'], channelfile=args.channel)
350
352
  print('Registering Channel Image...', flush=True)
351
353
  registerstart = time.time()
@@ -493,6 +495,7 @@ try:
493
495
  while resp not in [str(i) for i in range(len(services)+1)]:
494
496
  resp = input(f'Invalid input, please enter a number between 0 and {len(services)}: ')
495
497
  remoteservice = services[int(resp)-1]
498
+ print_color(f"Selected service: {remoteservice['name']}", color='brand')
496
499
  if volumes != remoteservice['volumes']:
497
500
  client.edit_service(serviceId=remoteservice['serviceId'], volumes=volumes)
498
501
  remoteservice = client.get_services(serviceId=remoteservice['serviceId'])[0]
@@ -510,6 +513,7 @@ try:
510
513
  client.edit_service(serviceId=remoteservice['serviceId'], volumes=volumes)
511
514
  remoteservice = client.get_services(serviceId=remoteservice['serviceId'])[0]
512
515
  starttime = time.time()
516
+ client.interactive = True
513
517
  deploymentId = client.deploy_service(serviceId=remoteservice['serviceId'], servicefile=args.service)
514
518
  print('Registering Service Image...', flush=True)
515
519
  registerstart = time.time()