supervisely 6.73.277__py3-none-any.whl → 6.73.279__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.
Potentially problematic release.
This version of supervisely might be problematic. Click here for more details.
- supervisely/__init__.py +2 -0
- supervisely/annotation/annotation.py +138 -1
- supervisely/convert/__init__.py +12 -56
- supervisely/convert/base_converter.py +12 -1
- supervisely/convert/image/__init__.py +22 -0
- supervisely/convert/image/coco/coco_helper.py +494 -2
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +417 -11
- supervisely/convert/image/yolo/yolo_helper.py +339 -4
- supervisely/convert/pointcloud/__init__.py +8 -0
- supervisely/convert/pointcloud_episodes/__init__.py +9 -0
- supervisely/convert/video/__init__.py +3 -0
- supervisely/convert/volume/__init__.py +3 -0
- supervisely/nn/training/gui/gui.py +25 -1
- supervisely/nn/training/gui/hyperparameters_selector.py +11 -1
- supervisely/nn/training/gui/model_selector.py +8 -2
- supervisely/nn/training/gui/training_artifacts.py +1 -1
- supervisely/nn/training/train_app.py +151 -46
- supervisely/project/project.py +311 -2
- {supervisely-6.73.277.dist-info → supervisely-6.73.279.dist-info}/METADATA +1 -1
- {supervisely-6.73.277.dist-info → supervisely-6.73.279.dist-info}/RECORD +24 -24
- {supervisely-6.73.277.dist-info → supervisely-6.73.279.dist-info}/LICENSE +0 -0
- {supervisely-6.73.277.dist-info → supervisely-6.73.279.dist-info}/WHEEL +0 -0
- {supervisely-6.73.277.dist-info → supervisely-6.73.279.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.277.dist-info → supervisely-6.73.279.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import shutil
|
|
2
3
|
import sys
|
|
3
4
|
import uuid
|
|
4
5
|
from copy import deepcopy
|
|
5
|
-
from
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
|
|
6
9
|
|
|
7
10
|
import cv2
|
|
8
11
|
import numpy as np
|
|
9
12
|
|
|
13
|
+
from supervisely._utils import generate_free_name
|
|
14
|
+
from supervisely.api.image_api import ImageApi
|
|
15
|
+
from supervisely.imaging.image import read as sly_image
|
|
16
|
+
from supervisely.io.fs import get_file_name_with_ext
|
|
17
|
+
from supervisely.io.json import dump_json_file, load_json_file
|
|
18
|
+
from supervisely.project.project import Dataset, OpenMode, Project
|
|
19
|
+
from supervisely.project.project_meta import ProjectMeta
|
|
20
|
+
from supervisely.task.progress import tqdm_sly
|
|
21
|
+
|
|
22
|
+
COCO_INSTANCES_FILE = "coco_instances.json"
|
|
23
|
+
COCO_CAPTIONS_FILE = "coco_captions.json"
|
|
24
|
+
|
|
10
25
|
|
|
11
26
|
class HiddenCocoPrints:
|
|
12
27
|
def __enter__(self):
|
|
@@ -321,5 +336,482 @@ def create_custom_geometry_config(num_keypoints=None, cat_labels=None, cat_edges
|
|
|
321
336
|
for edge in cat_edges:
|
|
322
337
|
template.add_edge(src=cat_labels[edge[0] - 1], dst=cat_labels[edge[1] - 1])
|
|
323
338
|
else:
|
|
324
|
-
logger.
|
|
339
|
+
logger.warning("Edges can not be mapped without skeleton, please check your annotation")
|
|
325
340
|
return template
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _get_graph_info(idx, obj_class):
|
|
344
|
+
data = {"supercategory": obj_class.name, "id": idx, "name": obj_class.name}
|
|
345
|
+
kp = {i: n["label"] for i, n in obj_class.geometry_config["nodes"].items()}
|
|
346
|
+
keys = {k: j for j, k in enumerate(list(kp.keys()), 1)}
|
|
347
|
+
edges = obj_class.geometry_config["edges"]
|
|
348
|
+
sk = [[keys[e["src"]], keys[e["dst"]]] for e in edges]
|
|
349
|
+
data["keypoints"] = list(kp.values())
|
|
350
|
+
data["skeleton"] = sk
|
|
351
|
+
return data
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def get_categories_from_meta(meta: ProjectMeta):
|
|
355
|
+
cat = lambda idx, c: {"supercategory": c.name, "id": idx, "name": c.name}
|
|
356
|
+
return [
|
|
357
|
+
cat(idx, c) if c.geometry_type != GraphNodes else _get_graph_info(idx, c)
|
|
358
|
+
for idx, c in enumerate(meta.obj_classes)
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def sly_ann_to_coco(
|
|
363
|
+
ann: Annotation,
|
|
364
|
+
coco_image_id: int,
|
|
365
|
+
class_mapping: Dict[str, int],
|
|
366
|
+
coco_ann: Optional[Union[Dict, List]] = None,
|
|
367
|
+
last_label_id: Optional[int] = None,
|
|
368
|
+
coco_captions: Optional[Union[Dict, List]] = None,
|
|
369
|
+
last_caption_id: Optional[int] = None,
|
|
370
|
+
) -> Tuple[List, List]:
|
|
371
|
+
"""
|
|
372
|
+
Convert Supervisely annotation to COCO format annotation ("annotations" field).
|
|
373
|
+
|
|
374
|
+
:param coco_image_id: Image id in COCO format.
|
|
375
|
+
:type coco_image_id: int
|
|
376
|
+
:param class_mapping: Dictionary that maps class names to class ids.
|
|
377
|
+
:type class_mapping: Dict[str, int]
|
|
378
|
+
:param coco_ann: COCO annotation in dictionary or list format to append new annotations.
|
|
379
|
+
:type coco_ann: Union[Dict, List], optional
|
|
380
|
+
:param last_label_id: Last label id in COCO format to continue counting.
|
|
381
|
+
:type last_label_id: int, optional
|
|
382
|
+
:param coco_captions: COCO captions in dictionary or list format to append new captions.
|
|
383
|
+
:type coco_captions: Union[Dict, List], optional
|
|
384
|
+
:return: Tuple with list of COCO objects and list of COCO captions.
|
|
385
|
+
:rtype: :class:`tuple`
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
:Usage example:
|
|
389
|
+
|
|
390
|
+
.. code-block:: python
|
|
391
|
+
|
|
392
|
+
import supervisely as sly
|
|
393
|
+
from supervisely.convert.image.coco.coco_helper import sly_ann_to_coco
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
coco_instances = dict(
|
|
397
|
+
info=dict(
|
|
398
|
+
description="COCO dataset converted from Supervisely",
|
|
399
|
+
url="None",
|
|
400
|
+
version=str(1.0),
|
|
401
|
+
year=2025,
|
|
402
|
+
contributor="Supervisely",
|
|
403
|
+
date_created="2025-01-01 00:00:00",
|
|
404
|
+
),
|
|
405
|
+
licenses=[dict(url="None", id=0, name="None")],
|
|
406
|
+
images=[],
|
|
407
|
+
annotations=[],
|
|
408
|
+
categories=get_categories_from_meta(meta), # [{"supercategory": "lemon", "id": 0, "name": "lemon"}, ...]
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
ann = sly.Annotation.from_json(ann_json, meta)
|
|
412
|
+
image_id = 11
|
|
413
|
+
label_id = 222
|
|
414
|
+
class_mapping = {obj_cls.name: idx for idx, obj_cls in enumerate(meta.obj_classes)}
|
|
415
|
+
|
|
416
|
+
curr_coco_ann, _ = sly_ann_to_coco(ann, image_id, class_mapping, coco_instances, label_id)
|
|
417
|
+
# or
|
|
418
|
+
# curr_coco_ann, _ = sly_ann_to_coco(ann, image_id, class_mapping, label_id=label_id)
|
|
419
|
+
# coco_instances["annotations"].extend(curr_coco_ann)
|
|
420
|
+
|
|
421
|
+
label_id += len(curr_coco_ann)
|
|
422
|
+
image_id += 1
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
coco_obj_template = lambda x, y, z: {
|
|
426
|
+
"id": x,
|
|
427
|
+
"segmentation": [],
|
|
428
|
+
"area": 0,
|
|
429
|
+
"iscrowd": 0,
|
|
430
|
+
"image_id": y,
|
|
431
|
+
"bbox": [],
|
|
432
|
+
"category_id": z,
|
|
433
|
+
}
|
|
434
|
+
label_id = last_label_id + 1 if last_label_id is not None else 1
|
|
435
|
+
if isinstance(coco_ann, dict):
|
|
436
|
+
coco_ann = coco_ann["annotations"]
|
|
437
|
+
if isinstance(coco_ann, list):
|
|
438
|
+
label_id = len(coco_ann) + 1
|
|
439
|
+
|
|
440
|
+
last_caption_id = last_caption_id + 1 if last_caption_id is not None else 1
|
|
441
|
+
if isinstance(coco_captions, dict):
|
|
442
|
+
coco_captions = coco_captions["annotations"]
|
|
443
|
+
if isinstance(coco_captions, list):
|
|
444
|
+
last_caption_id = len(coco_captions) + 1
|
|
445
|
+
|
|
446
|
+
def _update_inst_results(label_id, coco_ann, coco_obj, res):
|
|
447
|
+
label_id += 1
|
|
448
|
+
if isinstance(coco_ann, list):
|
|
449
|
+
coco_ann.append(coco_obj)
|
|
450
|
+
res.append(coco_obj)
|
|
451
|
+
return label_id
|
|
452
|
+
|
|
453
|
+
def _get_common_bbox(labels, sly_bbox=False, approx=False):
|
|
454
|
+
bboxes = [l.geometry.to_bbox() for l in labels]
|
|
455
|
+
x = min([bbox.left for bbox in bboxes])
|
|
456
|
+
y = min([bbox.top for bbox in bboxes])
|
|
457
|
+
max_x = max([bbox.right for bbox in bboxes])
|
|
458
|
+
max_y = max([bbox.bottom for bbox in bboxes])
|
|
459
|
+
if approx:
|
|
460
|
+
x, y, max_x, max_y = x - 10, y - 10, max_x + 10, max_y + 10
|
|
461
|
+
if sly_bbox:
|
|
462
|
+
return Rectangle(top=y, left=x, bottom=max_y, right=max_x)
|
|
463
|
+
return [x, y, max_x - x, max_y - y]
|
|
464
|
+
|
|
465
|
+
def _create_keypoints_obj(label, cat_id, label_id, coco_image_id):
|
|
466
|
+
nodes_dict = label.obj_class.geometry_config["nodes"]
|
|
467
|
+
keypoint_uuid_labels = {i: d["label"] for i, d in nodes_dict.items()}
|
|
468
|
+
keypoints = []
|
|
469
|
+
for key in keypoint_uuid_labels.keys():
|
|
470
|
+
if key not in label.geometry.nodes:
|
|
471
|
+
keypoints.extend([0, 0, 0])
|
|
472
|
+
else:
|
|
473
|
+
loc = label.geometry.nodes[key].location
|
|
474
|
+
keypoints.extend([loc.col, loc.row, 2])
|
|
475
|
+
coco_obj = coco_obj_template(label_id, coco_image_id, cat_id)
|
|
476
|
+
coco_obj["keypoints"] = keypoints
|
|
477
|
+
coco_obj["num_keypoints"] = len(keypoint_uuid_labels)
|
|
478
|
+
x, y = keypoints[0::3], keypoints[1::3]
|
|
479
|
+
x0, x1, y0, y1 = (np.min(x), np.max(x), np.min(y), np.max(y))
|
|
480
|
+
x0, x1, y0, y1 = int(x0), int(x1), int(y0), int(y1)
|
|
481
|
+
coco_obj["area"] = int((x1 - x0) * (y1 - y0))
|
|
482
|
+
coco_obj["bbox"] = [x0, y0, x1 - x0, y1 - y0]
|
|
483
|
+
return coco_obj
|
|
484
|
+
|
|
485
|
+
def _update_caption_results(caption_id, coco_captions, caption, res):
|
|
486
|
+
caption_id += 1
|
|
487
|
+
if isinstance(coco_captions, list):
|
|
488
|
+
coco_captions.append(caption)
|
|
489
|
+
res.append(caption)
|
|
490
|
+
return caption_id
|
|
491
|
+
|
|
492
|
+
res_inst = [] # result list of COCO objects
|
|
493
|
+
|
|
494
|
+
for binding_key, labels in ann.get_bindings().items():
|
|
495
|
+
if binding_key is None:
|
|
496
|
+
polygons = [l for l in labels if l.obj_class.geometry_type == Polygon]
|
|
497
|
+
masks = [l for l in labels if l.obj_class.geometry_type == Bitmap]
|
|
498
|
+
bboxes = [l for l in labels if l.obj_class.geometry_type == Rectangle]
|
|
499
|
+
graphs = [l for l in labels if l.obj_class.geometry_type == GraphNodes]
|
|
500
|
+
if len(masks) > 0:
|
|
501
|
+
for l in masks:
|
|
502
|
+
polygon_cls = l.obj_class.clone(geometry_type=Polygon)
|
|
503
|
+
polygons.extend(l.convert(polygon_cls))
|
|
504
|
+
for label in polygons + bboxes:
|
|
505
|
+
cat_id = class_mapping[label.obj_class.name]
|
|
506
|
+
coco_obj = coco_obj_template(label_id, coco_image_id, cat_id)
|
|
507
|
+
coco_obj["bbox"] = _get_common_bbox([label])
|
|
508
|
+
coco_obj["area"] = label.geometry.area
|
|
509
|
+
if label.obj_class.geometry_type == Polygon:
|
|
510
|
+
poly = label.geometry.to_json()["points"]["exterior"]
|
|
511
|
+
poly = np.array(poly).flatten().astype(float).tolist()
|
|
512
|
+
coco_obj["segmentation"] = [poly]
|
|
513
|
+
|
|
514
|
+
label_id = _update_inst_results(label_id, coco_ann, coco_obj, res_inst)
|
|
515
|
+
|
|
516
|
+
for label in graphs:
|
|
517
|
+
cat_id = class_mapping[label.obj_class.name]
|
|
518
|
+
new_obj = _create_keypoints_obj(label, cat_id, label_id, coco_image_id)
|
|
519
|
+
label_id = _update_inst_results(label_id, coco_ann, new_obj, res_inst)
|
|
520
|
+
|
|
521
|
+
continue
|
|
522
|
+
|
|
523
|
+
bboxes = [l for l in labels if l.obj_class.geometry_type == Rectangle]
|
|
524
|
+
polygons = [l for l in labels if l.obj_class.geometry_type == Polygon]
|
|
525
|
+
masks = [l for l in labels if l.obj_class.geometry_type == Bitmap]
|
|
526
|
+
graphs = [l for l in labels if l.obj_class.geometry_type == GraphNodes]
|
|
527
|
+
|
|
528
|
+
if len(masks) > 0: # convert Bitmap to Polygon
|
|
529
|
+
for l in masks:
|
|
530
|
+
polygon_cls = l.obj_class.clone(geometry_type=Polygon)
|
|
531
|
+
polygons.extend(l.convert(polygon_cls))
|
|
532
|
+
|
|
533
|
+
matched_bbox = False
|
|
534
|
+
if len(polygons) > 0: # process polygons
|
|
535
|
+
cat_id = class_mapping[polygons[0].obj_class.name]
|
|
536
|
+
coco_obj = coco_obj_template(label_id, coco_image_id, cat_id)
|
|
537
|
+
if len(bboxes) > 0:
|
|
538
|
+
found = _get_common_bbox(bboxes, sly_bbox=True, approx=True)
|
|
539
|
+
new = _get_common_bbox(polygons, sly_bbox=True)
|
|
540
|
+
matched_bbox = found.contains(new)
|
|
541
|
+
|
|
542
|
+
polys = [l.geometry.to_json()["points"]["exterior"] for l in polygons]
|
|
543
|
+
polys = [np.array(p).flatten().astype(float).tolist() for p in polys]
|
|
544
|
+
coco_obj["segmentation"] = polys
|
|
545
|
+
coco_obj["area"] = sum([l.geometry.area for l in polygons])
|
|
546
|
+
coco_obj["bbox"] = _get_common_bbox(bboxes if matched_bbox else polygons)
|
|
547
|
+
label_id = _update_inst_results(label_id, coco_ann, coco_obj, res_inst)
|
|
548
|
+
|
|
549
|
+
if len(graphs) > 0:
|
|
550
|
+
if len(graphs) > 1:
|
|
551
|
+
logger.warning(
|
|
552
|
+
"Multiple Keypoints in one binding key are not supported. "
|
|
553
|
+
"Only the first graph will be converted."
|
|
554
|
+
)
|
|
555
|
+
cat_id = class_mapping[graphs[0].obj_class.name]
|
|
556
|
+
coco_obj = _create_keypoints_obj(graphs[0], cat_id, label_id, coco_image_id)
|
|
557
|
+
label_id = _update_inst_results(label_id, coco_ann, coco_obj, res_inst)
|
|
558
|
+
|
|
559
|
+
if len(bboxes) > 0 and not matched_bbox: # process bboxes separately
|
|
560
|
+
for label in bboxes:
|
|
561
|
+
cat_id = class_mapping[label.obj_class.name]
|
|
562
|
+
coco_obj = coco_obj_template(label_id, coco_image_id, cat_id)
|
|
563
|
+
coco_obj["bbox"] = _get_common_bbox([label])
|
|
564
|
+
coco_obj["area"] = label.geometry.area
|
|
565
|
+
|
|
566
|
+
label_id = _update_inst_results(label_id, coco_ann, coco_obj, res_inst)
|
|
567
|
+
|
|
568
|
+
is_caption = lambda t: t.meta.name == "caption" and t.meta.value_type == TagValueType.ANY_STRING
|
|
569
|
+
caption_tags = [tag for tag in ann.img_tags if is_caption(tag)]
|
|
570
|
+
|
|
571
|
+
res_captions = [] # result list of COCO captions
|
|
572
|
+
for tag in caption_tags:
|
|
573
|
+
caption = {"image_id": coco_image_id, "id": caption_id, "caption": tag.value}
|
|
574
|
+
caption_id = _update_caption_results(caption_id, coco_captions, caption, res_captions)
|
|
575
|
+
|
|
576
|
+
return res_inst, res_captions
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def has_caption_tag(meta: ProjectMeta) -> bool:
|
|
580
|
+
tag = meta.get_tag_meta("caption")
|
|
581
|
+
return tag is not None and tag.value_type == TagValueType.ANY_STRING
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def create_coco_ann_template(meta: ProjectMeta) -> Dict[str, Any]:
|
|
585
|
+
now = datetime.now()
|
|
586
|
+
coco_ann = dict(
|
|
587
|
+
info=dict(
|
|
588
|
+
description="COCO dataset converted from Supervisely",
|
|
589
|
+
url="None",
|
|
590
|
+
version=str(1.0),
|
|
591
|
+
year=now.year,
|
|
592
|
+
contributor="Supervisely",
|
|
593
|
+
date_created=now.strftime("%Y-%m-%d %H:%M:%S"),
|
|
594
|
+
),
|
|
595
|
+
licenses=[dict(url="None", id=0, name="None")],
|
|
596
|
+
images=[],
|
|
597
|
+
annotations=[],
|
|
598
|
+
categories=[],
|
|
599
|
+
)
|
|
600
|
+
coco_ann["categories"] = get_categories_from_meta(meta)
|
|
601
|
+
return coco_ann
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def sly_ds_to_coco(
|
|
605
|
+
dataset: Dataset,
|
|
606
|
+
meta: ProjectMeta,
|
|
607
|
+
return_type: Literal["path", "dict"] = "path",
|
|
608
|
+
dest_dir: Optional[str] = None,
|
|
609
|
+
copy_images: bool = False,
|
|
610
|
+
with_captions: bool = False,
|
|
611
|
+
log_progress: bool = False,
|
|
612
|
+
progress_cb: Optional[Callable] = None,
|
|
613
|
+
) -> Union[str, Tuple[str, str], Dict, Tuple[Dict, Dict]]:
|
|
614
|
+
"""
|
|
615
|
+
Convert Supervisely dataset to COCO format.
|
|
616
|
+
|
|
617
|
+
Note: Depending on the `return_type` and `with_captions` parameters, the function returns different values.
|
|
618
|
+
|
|
619
|
+
:param dataset: Supervisely dataset.
|
|
620
|
+
:type dataset: :class:`Dataset<supervisely.project.dataset.Dataset>`
|
|
621
|
+
:param meta: Project meta information.
|
|
622
|
+
:type meta: :class:`ProjectMeta<supervisely.project.project_meta.ProjectMeta>`
|
|
623
|
+
:param return_type: Type of return value.
|
|
624
|
+
If 'path', returns paths to COCO dataset files.
|
|
625
|
+
If 'dict', returns COCO dataset dictionaries.
|
|
626
|
+
:param dest_dir: Destination path to save COCO dataset.
|
|
627
|
+
:type dest_dir: :class:`str`, optional
|
|
628
|
+
:param copy_images: If True, copies images to the destination directory.
|
|
629
|
+
:type copy_images: :class:`bool`, optional
|
|
630
|
+
:param with_captions: If True, returns COCO captions.
|
|
631
|
+
:type with_captions: :class:`bool`, optional
|
|
632
|
+
:param log_progress: If True, logs the progress of the conversion.
|
|
633
|
+
:type log_progress: :class:`bool`, optional
|
|
634
|
+
:param progress_cb: Callback function to track the progress of the conversion.
|
|
635
|
+
:type progress_cb: :class:`Callable`, optional
|
|
636
|
+
:return:
|
|
637
|
+
If return_type is 'path', returns paths to COCO dataset file or file (instances or instances and captions).
|
|
638
|
+
If return_type is 'dict', returns COCO dataset dictionary or dictionaries (instances or instances and captions).
|
|
639
|
+
:rtype: :class:`tuple`
|
|
640
|
+
|
|
641
|
+
:Usage example:
|
|
642
|
+
|
|
643
|
+
.. code-block:: python
|
|
644
|
+
|
|
645
|
+
import supervisely as sly
|
|
646
|
+
from supervisely.convert.image.coco.coco_helper import sly_ds_to_coco
|
|
647
|
+
|
|
648
|
+
project_path = "/home/admin/work/supervisely/projects/lemons_annotated"
|
|
649
|
+
project = sly.Project(project_path, sly.OpenMode.READ)
|
|
650
|
+
|
|
651
|
+
for ds in project.datasets:
|
|
652
|
+
dest_dir = "/home/admin/work/supervisely/projects/lemons_annotated/ds1"
|
|
653
|
+
coco_json, coco_captions, coco_json_path, coco_captions_path = sly_ds_to_coco(ds, project.meta, save=True, dest_dir=dest_dir)
|
|
654
|
+
"""
|
|
655
|
+
dest_dir = Path(dataset.path).parent / "coco" if dest_dir is None else Path(dest_dir)
|
|
656
|
+
save_json = return_type == "path"
|
|
657
|
+
if save_json is True:
|
|
658
|
+
annotations_dir = dest_dir / "annotations"
|
|
659
|
+
annotations_dir.mkdir(parents=True, exist_ok=True)
|
|
660
|
+
if copy_images is True:
|
|
661
|
+
images_dir = dest_dir / "images"
|
|
662
|
+
images_dir.mkdir(parents=True, exist_ok=True)
|
|
663
|
+
|
|
664
|
+
if progress_cb is not None:
|
|
665
|
+
log_progress = False
|
|
666
|
+
|
|
667
|
+
if log_progress:
|
|
668
|
+
progress_cb = tqdm_sly(
|
|
669
|
+
desc=f"Converting dataset '{dataset.short_name}' to COCO format",
|
|
670
|
+
total=len(dataset),
|
|
671
|
+
).update
|
|
672
|
+
|
|
673
|
+
coco_ann = create_coco_ann_template(meta)
|
|
674
|
+
coco_captions = create_coco_ann_template(meta) if with_captions else None
|
|
675
|
+
|
|
676
|
+
image_coco = lambda info, idx: dict(
|
|
677
|
+
license="None",
|
|
678
|
+
file_name=info.name,
|
|
679
|
+
url="None",
|
|
680
|
+
height=info.height,
|
|
681
|
+
width=info.width,
|
|
682
|
+
date_captured=info.created_at,
|
|
683
|
+
id=idx,
|
|
684
|
+
sly_id=info.id,
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
class_mapping = {cls.name: idx for idx, cls in enumerate(meta.obj_classes)}
|
|
688
|
+
label_id = 0
|
|
689
|
+
caption_id = 0
|
|
690
|
+
for image_idx, name in enumerate(dataset.get_items_names(), 1):
|
|
691
|
+
img_path = dataset.get_img_path(name)
|
|
692
|
+
img_name = get_file_name_with_ext(img_path)
|
|
693
|
+
ann_path = dataset.get_ann_path(name)
|
|
694
|
+
img_info_path = dataset.get_img_info_path(name)
|
|
695
|
+
|
|
696
|
+
if copy_images:
|
|
697
|
+
dst_img_path = images_dir / img_name
|
|
698
|
+
shutil.copy(img_path, dst_img_path)
|
|
699
|
+
|
|
700
|
+
if os.path.exists(img_info_path):
|
|
701
|
+
image_info_json = load_json_file(img_info_path)
|
|
702
|
+
else:
|
|
703
|
+
img = sly_image(img_path, remove_alpha_channel=False)
|
|
704
|
+
now = datetime.now()
|
|
705
|
+
image_info_json = {
|
|
706
|
+
"id": None,
|
|
707
|
+
"name": img_name,
|
|
708
|
+
"height": img.shape[0],
|
|
709
|
+
"width": img.shape[1],
|
|
710
|
+
"created_at": now.strftime("%Y-%m-%d %H:%M:%S"),
|
|
711
|
+
}
|
|
712
|
+
image_info = ImageApi._convert_json_info(ImageApi(None), image_info_json)
|
|
713
|
+
|
|
714
|
+
coco_ann["images"].append(image_coco(image_info, image_idx))
|
|
715
|
+
if with_captions is True:
|
|
716
|
+
coco_captions["images"].append(image_coco(image_info, image_idx)) # pylint: disable=unsubscriptable-object
|
|
717
|
+
|
|
718
|
+
ann = Annotation.load_json_file(ann_path, meta)
|
|
719
|
+
if ann.img_size is None or ann.img_size == (0, 0) or ann.img_size == (None, None):
|
|
720
|
+
img = sly_image(img_path)
|
|
721
|
+
ann = ann.clone(img_size=[img.shape[0], img.shape[1]])
|
|
722
|
+
insts, captions = ann.to_coco(
|
|
723
|
+
image_idx, class_mapping, coco_ann, label_id, coco_captions, caption_id
|
|
724
|
+
)
|
|
725
|
+
label_id += len(insts)
|
|
726
|
+
caption_id += len(captions)
|
|
727
|
+
|
|
728
|
+
if progress_cb is not None:
|
|
729
|
+
progress_cb(1)
|
|
730
|
+
|
|
731
|
+
ann_path = None
|
|
732
|
+
captions_path = None
|
|
733
|
+
if save_json is True:
|
|
734
|
+
logger.info("Saving COCO annotations to disk...")
|
|
735
|
+
ann_path = str(annotations_dir / COCO_INSTANCES_FILE)
|
|
736
|
+
dump_json_file(coco_ann, ann_path)
|
|
737
|
+
logger.info(f"Saved COCO instances to '{ann_path}'")
|
|
738
|
+
|
|
739
|
+
if with_captions is True:
|
|
740
|
+
captions_path = str(annotations_dir / COCO_CAPTIONS_FILE)
|
|
741
|
+
dump_json_file(coco_captions, captions_path)
|
|
742
|
+
logger.info(f"Saved COCO captions to '{captions_path}'")
|
|
743
|
+
|
|
744
|
+
return ann_path, captions_path
|
|
745
|
+
return ann_path
|
|
746
|
+
if with_captions:
|
|
747
|
+
return coco_ann, coco_captions
|
|
748
|
+
return coco_ann
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def sly_project_to_coco(
|
|
752
|
+
project: Union[Project, str],
|
|
753
|
+
dest_dir: Optional[str] = None,
|
|
754
|
+
copy_images: bool = False,
|
|
755
|
+
with_captions: bool = False,
|
|
756
|
+
log_progress: bool = True,
|
|
757
|
+
progress_cb: Optional[Callable] = None,
|
|
758
|
+
) -> None:
|
|
759
|
+
"""
|
|
760
|
+
Convert Supervisely project to COCO format.
|
|
761
|
+
|
|
762
|
+
:param project: Supervisely project.
|
|
763
|
+
:type project: :class:`Project<supervisely.project.project.Project>` or :class:`str`
|
|
764
|
+
:param dest_dir: Destination directory.
|
|
765
|
+
:type dest_dir: :class:`str`, optional
|
|
766
|
+
:param copy_images: Copy images to destination directory.
|
|
767
|
+
:type copy_images: :class:`bool`, optional
|
|
768
|
+
:param with_captions: Return COCO captions.
|
|
769
|
+
:type with_captions: :class:`bool`, optional
|
|
770
|
+
:param log_progress: Show uploading progress bar.
|
|
771
|
+
:type log_progress: :class:`bool`
|
|
772
|
+
:param progress_cb: Function for tracking conversion progress (for all items in the project).
|
|
773
|
+
:type progress_cb: callable, optional
|
|
774
|
+
:return: None
|
|
775
|
+
:rtype: NoneType
|
|
776
|
+
|
|
777
|
+
:Usage example:
|
|
778
|
+
|
|
779
|
+
.. code-block:: python
|
|
780
|
+
|
|
781
|
+
import supervisely as sly
|
|
782
|
+
|
|
783
|
+
# Local folder with Project
|
|
784
|
+
project_directory = "/home/admin/work/supervisely/source/project"
|
|
785
|
+
|
|
786
|
+
# Convert Project to COCO format
|
|
787
|
+
sly.Project(project_directory).to_coco(log_progress=True)
|
|
788
|
+
"""
|
|
789
|
+
if isinstance(project, str):
|
|
790
|
+
project = Project(project, mode=OpenMode.READ)
|
|
791
|
+
|
|
792
|
+
dest_dir = Path(dest_dir) if dest_dir is not None else Path(project.directory).parent / "coco"
|
|
793
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
794
|
+
|
|
795
|
+
if progress_cb is not None:
|
|
796
|
+
log_progress = False
|
|
797
|
+
|
|
798
|
+
if log_progress:
|
|
799
|
+
progress_cb = tqdm_sly(
|
|
800
|
+
desc="Converting Supervisely project to COCO format", total=project.total_items
|
|
801
|
+
).update
|
|
802
|
+
|
|
803
|
+
used_ds_names = set()
|
|
804
|
+
for ds in project.datasets:
|
|
805
|
+
ds: Dataset
|
|
806
|
+
coco_dir = generate_free_name(used_ds_names, ds.short_name, extend_used_names=True)
|
|
807
|
+
ds.to_coco(
|
|
808
|
+
meta=project.meta,
|
|
809
|
+
return_type="path",
|
|
810
|
+
dest_dir=dest_dir / coco_dir,
|
|
811
|
+
copy_images=copy_images,
|
|
812
|
+
with_captions=with_captions,
|
|
813
|
+
log_progress=log_progress,
|
|
814
|
+
progress_cb=progress_cb,
|
|
815
|
+
)
|
|
816
|
+
logger.info(f"Dataset '{ds.short_name}' has been converted to COCO format.")
|
|
817
|
+
logger.info(f"Project '{project.name}' has been converted to COCO format.")
|