31
31
executor = ThreadPoolExecutor (max_workers = 5 )
32
32
merged_meta = None
33
33
TASK_ID = None
34
- cancel_deletion = False # flag to cancel deletion of the source items
34
+ cancel_deletion = False # flag to cancel deletion of the source items
35
35
36
36
if sly .is_development ():
37
37
api .app .workflow .enable ()
@@ -393,6 +393,22 @@ def clone_images_with_annotations(
393
393
image_infos = [info for info in image_infos if info .name not in existing ]
394
394
if progress_cb is not None :
395
395
progress_cb (len_before - len (image_infos ))
396
+ src_existing = set ()
397
+ if options [JSONKEYS .CONFLICT_RESOLUTION_MODE ] in [
398
+ JSONKEYS .CONFLICT_SKIP ,
399
+ JSONKEYS .CONFLICT_REPLACE ,
400
+ ]:
401
+ len_before = len (image_infos )
402
+ non_duplicate = []
403
+ for image_info in image_infos :
404
+ if image_info .name not in src_existing :
405
+ non_duplicate .append (image_info )
406
+ src_existing .add (image_info .name )
407
+ image_infos = non_duplicate
408
+ if progress_cb is not None :
409
+ progress_cb (len_before - len (image_infos ))
410
+ if len (image_infos ) != len_before :
411
+ logger .info ("Some images were skipped due to name conflicts within source images." )
396
412
397
413
if len (image_infos ) == 0 :
398
414
return []
@@ -407,22 +423,29 @@ def _copy_imgs(
407
423
return infos , uploaded
408
424
409
425
def _copy_anns (src : List [sly .ImageInfo ], dst : List [sly .ImageInfo ]):
410
- try :
411
- api .annotation .copy_batch_by_ids (
412
- [i .id for i in src ],
413
- [i .id for i in dst ],
414
- save_source_date = options [JSONKEYS .PRESERVE_SRC_DATE ],
415
- )
416
- except Exception as e :
417
- if "Some users are not members of the destination group" in str (e ):
418
- raise ValueError (
419
- "Unable to copy annotations. Annotation creator is not a member of the destination team."
420
- ) from e
421
- else :
422
- raise e
426
+ by_dataset = defaultdict (list )
427
+ for src_info , dst_info in zip (src , dst ):
428
+ by_dataset [src_info .dataset_id ].append ((src_info , dst_info ))
429
+ for pairs in by_dataset .values ():
430
+ src_ids = [info [0 ].id for info in pairs ]
431
+ dst_ids = [info [1 ].id for info in pairs ]
432
+ try :
433
+ api .annotation .copy_batch_by_ids (
434
+ src_ids ,
435
+ dst_ids ,
436
+ save_source_date = options [JSONKEYS .PRESERVE_SRC_DATE ],
437
+ )
438
+ except Exception as e :
439
+ if "Some users are not members of the destination group" in str (e ):
440
+ raise ValueError (
441
+ "Unable to copy annotations. Annotation creator is not a member of the destination team."
442
+ ) from e
443
+ else :
444
+ raise e
423
445
424
446
return src , dst
425
447
448
+ reserved_names = set (existing .keys ())
426
449
to_rename = {} # {new_name: old_name}
427
450
upload_images_tasks = []
428
451
for src_image_infos_batch in sly .batched (image_infos , UPLOAD_IMAGES_BATCH_SIZE ):
@@ -434,12 +457,32 @@ def _copy_anns(src: List[sly.ImageInfo], dst: List[sly.ImageInfo]):
434
457
JSONKEYS .CONFLICT_REPLACE ,
435
458
]:
436
459
for i , name in enumerate (names ):
437
- if name in existing :
438
- names [i ] = (
439
- "." .join (name .split ("." )[:- 1 ]) + "_" + now + "." + name .split ("." )[- 1 ]
440
- )
460
+ j = 0
461
+ if name in reserved_names :
462
+ new_name = name
463
+ while new_name in reserved_names :
464
+ if j == 0 :
465
+ new_name = (
466
+ "." .join (name .split ("." )[:- 1 ])
467
+ + "_"
468
+ + now
469
+ + "."
470
+ + name .split ("." )[- 1 ]
471
+ )
472
+ else :
473
+ new_name = (
474
+ "." .join (name .split ("." )[:- 1 ])
475
+ + "_"
476
+ + now
477
+ + f"_{ j } "
478
+ + "."
479
+ + name .split ("." )[- 1 ]
480
+ )
481
+ j += 1
482
+ names [i ] = new_name
441
483
if options [JSONKEYS .CONFLICT_RESOLUTION_MODE ] == JSONKEYS .CONFLICT_REPLACE :
442
484
to_rename [names [i ]] = name
485
+ reserved_names .add (new_name )
443
486
upload_images_tasks .append (
444
487
executor .submit (
445
488
_copy_imgs ,
@@ -645,21 +688,20 @@ def _copy_anns(
645
688
sf_idx_to_remove .reverse ()
646
689
for idx in sf_idx_to_remove :
647
690
ann .spatial_figures .pop (idx )
648
- run_in_executor (
649
- api .volume .figure .download_sf_geometries , mask_ids , mask_paths )
691
+ run_in_executor (api .volume .figure .download_sf_geometries , mask_ids , mask_paths )
650
692
tasks .append (
651
693
executor .submit (
652
694
api .volume .annotation .append , dst_info .id , ann , key_id_map , volume_info = dst_info
653
695
)
654
696
)
655
-
697
+
656
698
for task in as_completed (tasks ):
657
699
task .result ()
658
700
progress_masks = tqdm (total = len (mask_paths ), desc = "Uploading Mask 3D geometries" )
659
701
for file in mask_paths :
660
- with open (file , 'rb' ) as f :
702
+ with open (file , "rb" ) as f :
661
703
key = UUID (os .path .basename (f .name ))
662
- api .volume .figure .upload_sf_geometries ([key ] , {key :f .read ()}, key_id_map )
704
+ api .volume .figure .upload_sf_geometries ([key ], {key : f .read ()}, key_id_map )
663
705
progress_masks .update (1 )
664
706
progress_masks .close ()
665
707
if set_csm_warning :
@@ -1057,7 +1099,9 @@ def _create_rec(
1057
1099
dataset_info , created_info , conflict_resolution_result = conflict_resolution_result
1058
1100
)
1059
1101
if dataset_info .custom_data :
1060
- run_in_executor (api .dataset .update , created_id , custom_data = dataset_info .custom_data )
1102
+ run_in_executor (
1103
+ api .dataset .update , created_id , custom_data = dataset_info .custom_data
1104
+ )
1061
1105
logger .info (
1062
1106
"Created Dataset" ,
1063
1107
extra = {
@@ -1328,7 +1372,9 @@ def replace_dataset(src_dataset_info: sly.DatasetInfo, dst_dataset_info: sly.Dat
1328
1372
"""Remove src_dataset_info and change name of dst_dataset_info to src_dataset_info.name"""
1329
1373
api .dataset .update (src_dataset_info .id , name = src_dataset_info .name + "__to_remove" )
1330
1374
api .dataset .remove (src_dataset_info .id )
1331
- return api .dataset .update (dst_dataset_info .id , name = src_dataset_info .name , custom_data = src_dataset_info .custom_data )
1375
+ return api .dataset .update (
1376
+ dst_dataset_info .id , name = src_dataset_info .name , custom_data = src_dataset_info .custom_data
1377
+ )
1332
1378
1333
1379
1334
1380
def run_in_executor (func , * args , ** kwargs ):
@@ -1370,7 +1416,7 @@ def copy_project_with_replace(
1370
1416
parent_id = dst_dataset_id ,
1371
1417
created_at = src_project_info .created_at if perserve_date else None ,
1372
1418
updated_at = src_project_info .updated_at if perserve_date else None ,
1373
- created_by = src_project_info .created_by_id if perserve_date else None ,
1419
+ created_by = src_project_info .created_by_id if perserve_date else None ,
1374
1420
)
1375
1421
existing_datasets = find_children_in_tree (datasets_tree , parent_id = dst_dataset_id )
1376
1422
created_datasets .append (
@@ -1669,9 +1715,12 @@ def move_project(
1669
1715
"No datasets created. Skipping deletion" , extra = {"project_id" : src_project_info .id }
1670
1716
)
1671
1717
return []
1672
-
1718
+
1673
1719
if cancel_deletion :
1674
- logger .info ("The source project will not be removed because some of its entities cannot be moved." , extra = {"project_id" : src_project_info .id })
1720
+ logger .info (
1721
+ "The source project will not be removed because some of its entities cannot be moved." ,
1722
+ extra = {"project_id" : src_project_info .id },
1723
+ )
1675
1724
else :
1676
1725
logger .info ("Removing source project" , extra = {"project_id" : src_project_info .id })
1677
1726
run_in_executor (api .project .remove , src_project_info .id )
@@ -1749,9 +1798,12 @@ def move_datasets_tree(
1749
1798
if len (datasets_to_remove ) == 0 :
1750
1799
logger .info ("No datasets to remove" , extra = {"dataset_id" : dst_dataset_id })
1751
1800
return creted_datasets
1752
-
1801
+
1753
1802
if cancel_deletion :
1754
- logger .info ("The source datasets will not be removed because some of its entities cannot be moved." , extra = {"dataset_id" : dst_dataset_id })
1803
+ logger .info (
1804
+ "The source datasets will not be removed because some of its entities cannot be moved." ,
1805
+ extra = {"dataset_id" : dst_dataset_id },
1806
+ )
1755
1807
else :
1756
1808
logger .info (
1757
1809
"Removing source datasets" ,
@@ -1829,9 +1881,12 @@ def move_items_to_dataset(
1829
1881
options = options ,
1830
1882
progress_cb = progress_cb ,
1831
1883
src_infos = item_infos ,
1832
- )
1884
+ )
1833
1885
if cancel_deletion or len (created_item_infos ) < len (item_infos ):
1834
- logger .info ("Some items were not moved. Skipping deletion of source items" , extra = {"dataset_id" : dst_dataset_id })
1886
+ logger .info (
1887
+ "Some items were not moved. Skipping deletion of source items" ,
1888
+ extra = {"dataset_id" : dst_dataset_id },
1889
+ )
1835
1890
else :
1836
1891
delete_items (item_infos )
1837
1892
cancel_deletion = False
@@ -2265,7 +2320,9 @@ def transfer_from_dataset(
2265
2320
f"Dataset created with ID: { target_dataset .id } and name '{ target_dataset .name } '"
2266
2321
)
2267
2322
if src_dataset .custom_data :
2268
- run_in_executor (api .dataset .update , target_dataset .id , custom_data = src_dataset .custom_data )
2323
+ run_in_executor (
2324
+ api .dataset .update , target_dataset .id , custom_data = src_dataset .custom_data
2325
+ )
2269
2326
logger .info (f"Dataset custom data has been updated" )
2270
2327
else :
2271
2328
raise NotImplementedError (
0 commit comments