annofabcli 1.112.0__py3-none-any.whl → 1.113.0__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.
- annofabcli/annotation/change_annotation_attributes.py +1 -1
- annofabcli/annotation/change_annotation_attributes_per_annotation.py +1 -1
- annofabcli/annotation/change_annotation_properties.py +1 -1
- annofabcli/annotation/copy_annotation.py +1 -1
- annofabcli/annotation/create_classification_annotation.py +1 -1
- annofabcli/annotation/delete_annotation.py +1 -1
- annofabcli/annotation/dump_annotation.py +1 -1
- annofabcli/annotation/import_annotation.py +1 -1
- annofabcli/annotation/list_annotation.py +1 -1
- annofabcli/annotation/list_annotation_count.py +1 -1
- annofabcli/annotation/merge_segmentation.py +1 -1
- annofabcli/annotation/remove_segmentation_overlap.py +1 -1
- annofabcli/annotation/restore_annotation.py +1 -1
- annofabcli/annotation_specs/list_annotation_specs_history.py +1 -1
- annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +1 -1
- annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +26 -26
- annofabcli/annotation_zip/list_polygon_annotation.py +390 -0
- annofabcli/annotation_zip/list_polyline_annotation.py +402 -0
- annofabcli/annotation_zip/list_range_annotation.py +1 -1
- annofabcli/annotation_zip/list_single_point_annotation.py +14 -22
- annofabcli/annotation_zip/subcommand_annotation_zip.py +4 -0
- annofabcli/common/cli.py +1 -0
- annofabcli/filesystem/draw_annotation.py +1 -1
- annofabcli/filesystem/mask_user_info.py +1 -1
- annofabcli/filesystem/merge_annotation.py +1 -1
- annofabcli/input_data/delete_input_data.py +1 -1
- annofabcli/input_data/list_all_input_data.py +1 -1
- annofabcli/input_data/list_all_input_data_merged_task.py +1 -1
- annofabcli/input_data/list_input_data.py +1 -1
- annofabcli/input_data/put_input_data.py +1 -1
- annofabcli/input_data/put_input_data_with_zip.py +1 -1
- annofabcli/instruction/copy_instruction.py +1 -1
- annofabcli/instruction/download_instruction.py +1 -1
- annofabcli/instruction/list_instruction_history.py +1 -1
- annofabcli/instruction/upload_instruction.py +1 -1
- annofabcli/job/delete_job.py +1 -1
- annofabcli/job/list_job.py +1 -1
- annofabcli/job/list_last_job.py +1 -1
- annofabcli/my_account/get_my_account.py +1 -1
- annofabcli/organization/list_organization.py +1 -1
- annofabcli/organization_member/list_organization_member.py +1 -1
- annofabcli/project/change_organization_of_project.py +1 -1
- annofabcli/project/change_project_status.py +1 -1
- annofabcli/project/copy_project.py +1 -1
- annofabcli/project/create_project.py +1 -1
- annofabcli/project/list_project.py +1 -1
- annofabcli/project/put_project.py +1 -1
- annofabcli/project/update_configuration.py +1 -1
- annofabcli/project_member/change_project_members.py +1 -1
- annofabcli/project_member/copy_project_members.py +1 -1
- annofabcli/project_member/drop_project_members.py +1 -1
- annofabcli/project_member/list_users.py +1 -1
- annofabcli/project_member/put_project_members.py +1 -1
- annofabcli/statistics/list_worktime.py +1 -1
- annofabcli/supplementary/delete_supplementary_data.py +1 -1
- annofabcli/supplementary/list_supplementary_data.py +1 -1
- annofabcli/supplementary/put_supplementary_data.py +1 -1
- annofabcli/task/delete_tasks.py +1 -1
- annofabcli/task/list_all_tasks.py +1 -1
- annofabcli/task/list_all_tasks_added_task_history.py +2 -1
- annofabcli/task/list_tasks.py +1 -1
- annofabcli/task/list_tasks_added_task_history.py +1 -1
- annofabcli/task/put_tasks.py +1 -1
- annofabcli/task/put_tasks_by_count.py +1 -1
- annofabcli/task_history/list_all_task_history.py +1 -1
- annofabcli/task_history/list_task_history.py +1 -1
- annofabcli/task_history_event/list_all_task_history_event.py +1 -1
- annofabcli/task_history_event/list_worktime.py +2 -1
- {annofabcli-1.112.0.dist-info → annofabcli-1.113.0.dist-info}/METADATA +1 -1
- {annofabcli-1.112.0.dist-info → annofabcli-1.113.0.dist-info}/RECORD +73 -71
- {annofabcli-1.112.0.dist-info → annofabcli-1.113.0.dist-info}/WHEEL +0 -0
- {annofabcli-1.112.0.dist-info → annofabcli-1.113.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.112.0.dist-info → annofabcli-1.113.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,7 +14,7 @@ import annofabapi
|
|
|
14
14
|
from annofabapi.dataclass.task import Task
|
|
15
15
|
from annofabapi.models import ProjectMemberRole, TaskStatus
|
|
16
16
|
|
|
17
|
-
import annofabcli
|
|
17
|
+
import annofabcli.common.cli
|
|
18
18
|
from annofabcli.annotation.annotation_query import (
|
|
19
19
|
AnnotationQueryForAPI,
|
|
20
20
|
AnnotationQueryForCLI,
|
|
@@ -13,7 +13,7 @@ from annofabapi.models import ProjectMemberRole
|
|
|
13
13
|
from annofabapi.pydantic_models.task_status import TaskStatus
|
|
14
14
|
from pydantic import BaseModel
|
|
15
15
|
|
|
16
|
-
import annofabcli
|
|
16
|
+
import annofabcli.common.cli
|
|
17
17
|
from annofabcli.annotation.annotation_query import convert_attributes_from_cli_to_additional_data_list_v2
|
|
18
18
|
from annofabcli.annotation.dump_annotation import DumpAnnotationMain
|
|
19
19
|
from annofabcli.common.cli import (
|
|
@@ -15,7 +15,7 @@ from annofabapi.models import ProjectMemberRole, SingleAnnotation, TaskStatus
|
|
|
15
15
|
from annofabapi.utils import can_put_annotation, str_now
|
|
16
16
|
from dataclasses_json import DataClassJsonMixin
|
|
17
17
|
|
|
18
|
-
import annofabcli
|
|
18
|
+
import annofabcli.common.cli
|
|
19
19
|
from annofabcli.annotation.annotation_query import AnnotationQueryForAPI, AnnotationQueryForCLI
|
|
20
20
|
from annofabcli.annotation.dump_annotation import DumpAnnotationMain
|
|
21
21
|
from annofabcli.common.cli import (
|
|
@@ -13,7 +13,7 @@ import annofabapi
|
|
|
13
13
|
from annofabapi.models import ProjectMemberRole
|
|
14
14
|
from annofabapi.utils import can_put_annotation
|
|
15
15
|
|
|
16
|
-
import annofabcli
|
|
16
|
+
import annofabcli.common.cli
|
|
17
17
|
from annofabcli.common.cli import (
|
|
18
18
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
19
19
|
PARALLELISM_CHOICES,
|
|
@@ -11,7 +11,7 @@ from annofabapi.models import DefaultAnnotationType, ProjectMemberRole, TaskStat
|
|
|
11
11
|
from annofabapi.util.annotation_specs import AnnotationSpecsAccessor
|
|
12
12
|
from annofabapi.utils import can_put_annotation
|
|
13
13
|
|
|
14
|
-
import annofabcli
|
|
14
|
+
import annofabcli.common.cli
|
|
15
15
|
from annofabcli.common.cli import (
|
|
16
16
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
17
17
|
PARALLELISM_CHOICES,
|
|
@@ -15,7 +15,7 @@ import requests
|
|
|
15
15
|
from annofabapi.dataclass.task import Task
|
|
16
16
|
from annofabapi.models import ProjectMemberRole, TaskStatus
|
|
17
17
|
|
|
18
|
-
import annofabcli
|
|
18
|
+
import annofabcli.common.cli
|
|
19
19
|
from annofabcli.annotation.annotation_query import AnnotationQueryForAPI, AnnotationQueryForCLI
|
|
20
20
|
from annofabcli.annotation.dump_annotation import DumpAnnotationMain
|
|
21
21
|
from annofabcli.common.cli import (
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
import annofabapi
|
|
12
12
|
from annofabapi.models import AnnotationDataHoldingType
|
|
13
13
|
|
|
14
|
-
import annofabcli
|
|
14
|
+
import annofabcli.common.cli
|
|
15
15
|
from annofabcli.common.cli import PARALLELISM_CHOICES, ArgumentParser, CommandLine, build_annofabapi_resource_and_login
|
|
16
16
|
from annofabcli.common.facade import AnnofabApiFacade
|
|
17
17
|
|
|
@@ -32,7 +32,7 @@ from annofabapi.util.annotation_specs import AnnotationSpecsAccessor, get_choice
|
|
|
32
32
|
from annofabapi.utils import can_put_annotation
|
|
33
33
|
from dataclasses_json import DataClassJsonMixin
|
|
34
34
|
|
|
35
|
-
import annofabcli
|
|
35
|
+
import annofabcli.common.cli
|
|
36
36
|
from annofabcli.common.cli import (
|
|
37
37
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
38
38
|
PARALLELISM_CHOICES,
|
|
@@ -9,7 +9,7 @@ import annofabapi
|
|
|
9
9
|
import pandas
|
|
10
10
|
from annofabapi.models import SingleAnnotation
|
|
11
11
|
|
|
12
|
-
import annofabcli
|
|
12
|
+
import annofabcli.common.cli
|
|
13
13
|
from annofabcli.annotation.annotation_query import AnnotationQueryForAPI, AnnotationQueryForCLI
|
|
14
14
|
from annofabcli.common.cli import (
|
|
15
15
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
@@ -9,7 +9,7 @@ import annofabapi
|
|
|
9
9
|
import pandas
|
|
10
10
|
from annofabapi.models import SingleAnnotation
|
|
11
11
|
|
|
12
|
-
import annofabcli
|
|
12
|
+
import annofabcli.common.cli
|
|
13
13
|
from annofabcli.annotation.annotation_query import AnnotationQueryForCLI
|
|
14
14
|
from annofabcli.annotation.list_annotation import ListAnnotationMain
|
|
15
15
|
from annofabcli.common.cli import (
|
|
@@ -19,7 +19,7 @@ from annofabapi.segmentation import read_binary_image, write_binary_image
|
|
|
19
19
|
from annofabapi.util.annotation_specs import AnnotationSpecsAccessor
|
|
20
20
|
from annofabapi.utils import can_put_annotation
|
|
21
21
|
|
|
22
|
-
import annofabcli
|
|
22
|
+
import annofabcli.common.cli
|
|
23
23
|
from annofabcli.common.cli import (
|
|
24
24
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
25
25
|
PARALLELISM_CHOICES,
|
|
@@ -17,7 +17,7 @@ from annofabapi.pydantic_models.task_status import TaskStatus
|
|
|
17
17
|
from annofabapi.segmentation import read_binary_image, write_binary_image
|
|
18
18
|
from annofabapi.utils import can_put_annotation
|
|
19
19
|
|
|
20
|
-
import annofabcli
|
|
20
|
+
import annofabcli.common.cli
|
|
21
21
|
from annofabcli.common.cli import (
|
|
22
22
|
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
23
23
|
PARALLELISM_CHOICES,
|
|
@@ -4,7 +4,7 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
import annofabapi
|
|
6
6
|
|
|
7
|
-
import annofabcli
|
|
7
|
+
import annofabcli.common.cli
|
|
8
8
|
from annofabcli.common.cli import ArgumentParser, CommandLine, build_annofabapi_resource_and_login
|
|
9
9
|
from annofabcli.common.enums import FormatArgument
|
|
10
10
|
from annofabcli.common.facade import AnnofabApiFacade
|
|
@@ -46,8 +46,13 @@ class AnnotationBoundingBoxInfo(DataClassJsonMixin):
|
|
|
46
46
|
annotation_id: str
|
|
47
47
|
left_top: dict[str, int]
|
|
48
48
|
right_bottom: dict[str, int]
|
|
49
|
+
center: dict[str, float]
|
|
50
|
+
"""バウンディングボックスの中心座標"""
|
|
49
51
|
width: int
|
|
50
52
|
height: int
|
|
53
|
+
area: int
|
|
54
|
+
"""バウンディングボックスの面積"""
|
|
55
|
+
attributes: dict[str, str | int | bool]
|
|
51
56
|
|
|
52
57
|
|
|
53
58
|
def get_annotation_bounding_box_info_list(simple_annotation: dict[str, Any], *, target_label_names: Collection[str] | None = None) -> list[AnnotationBoundingBoxInfo]:
|
|
@@ -64,6 +69,9 @@ def get_annotation_bounding_box_info_list(simple_annotation: dict[str, Any], *,
|
|
|
64
69
|
right_bottom = detail["data"]["right_bottom"]
|
|
65
70
|
width = abs(right_bottom["x"] - left_top["x"])
|
|
66
71
|
height = abs(right_bottom["y"] - left_top["y"])
|
|
72
|
+
center_x = (left_top["x"] + right_bottom["x"]) / 2
|
|
73
|
+
center_y = (left_top["y"] + right_bottom["y"]) / 2
|
|
74
|
+
area = width * height
|
|
67
75
|
|
|
68
76
|
result.append(
|
|
69
77
|
AnnotationBoundingBoxInfo(
|
|
@@ -78,9 +86,12 @@ def get_annotation_bounding_box_info_list(simple_annotation: dict[str, Any], *,
|
|
|
78
86
|
annotation_id=detail["annotation_id"],
|
|
79
87
|
left_top=left_top,
|
|
80
88
|
right_bottom=right_bottom,
|
|
89
|
+
center={"x": center_x, "y": center_y},
|
|
81
90
|
width=width,
|
|
82
91
|
height=height,
|
|
92
|
+
area=area,
|
|
83
93
|
updated_datetime=simple_annotation["updated_datetime"],
|
|
94
|
+
attributes=detail["attributes"],
|
|
84
95
|
)
|
|
85
96
|
)
|
|
86
97
|
|
|
@@ -114,7 +125,7 @@ def get_annotation_bounding_box_info_list_from_annotation_path(
|
|
|
114
125
|
def create_df(
|
|
115
126
|
annotation_bbox_list: list[AnnotationBoundingBoxInfo],
|
|
116
127
|
) -> pandas.DataFrame:
|
|
117
|
-
|
|
128
|
+
base_columns = [
|
|
118
129
|
"project_id",
|
|
119
130
|
"task_id",
|
|
120
131
|
"task_status",
|
|
@@ -129,33 +140,22 @@ def create_df(
|
|
|
129
140
|
"left_top.y",
|
|
130
141
|
"right_bottom.x",
|
|
131
142
|
"right_bottom.y",
|
|
143
|
+
"center.x",
|
|
144
|
+
"center.y",
|
|
132
145
|
"width",
|
|
133
146
|
"height",
|
|
147
|
+
"area",
|
|
134
148
|
]
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"updated_datetime": e.updated_datetime,
|
|
146
|
-
"label": e.label,
|
|
147
|
-
"annotation_id": e.annotation_id,
|
|
148
|
-
"left_top.x": e.left_top["x"],
|
|
149
|
-
"left_top.y": e.left_top["y"],
|
|
150
|
-
"right_bottom.x": e.right_bottom["x"],
|
|
151
|
-
"right_bottom.y": e.right_bottom["y"],
|
|
152
|
-
"width": e.width,
|
|
153
|
-
"height": e.height,
|
|
154
|
-
}
|
|
155
|
-
for e in annotation_bbox_list
|
|
156
|
-
],
|
|
157
|
-
columns=columns,
|
|
158
|
-
)
|
|
149
|
+
|
|
150
|
+
if not annotation_bbox_list:
|
|
151
|
+
# 空のリストの場合は、base_columnsのみで空のDataFrameを返す
|
|
152
|
+
return pandas.DataFrame(columns=base_columns)
|
|
153
|
+
|
|
154
|
+
tmp_annotation_bbox_list = [e.to_dict(encode_json=True) for e in annotation_bbox_list]
|
|
155
|
+
df = pandas.json_normalize(tmp_annotation_bbox_list)
|
|
156
|
+
|
|
157
|
+
attribute_columns = sorted(col for col in df.columns if col.startswith("attributes."))
|
|
158
|
+
columns = base_columns + attribute_columns
|
|
159
159
|
|
|
160
160
|
return df[columns]
|
|
161
161
|
|
|
@@ -301,7 +301,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
301
301
|
parser.add_argument(
|
|
302
302
|
"--label_name",
|
|
303
303
|
type=str,
|
|
304
|
-
nargs="
|
|
304
|
+
nargs="+",
|
|
305
305
|
help="指定したラベル名のバウンディングボックスアノテーションのみを対象にします。複数指定できます。",
|
|
306
306
|
)
|
|
307
307
|
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
from collections.abc import Collection
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import pandas
|
|
10
|
+
from annofabapi.models import InputDataType, ProjectMemberRole
|
|
11
|
+
from pydantic import BaseModel, ConfigDict
|
|
12
|
+
from shapely.errors import ShapelyError
|
|
13
|
+
from shapely.geometry import Polygon
|
|
14
|
+
|
|
15
|
+
import annofabcli.common.cli
|
|
16
|
+
from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
|
|
17
|
+
from annofabcli.common.cli import COMMAND_LINE_ERROR_STATUS_CODE, ArgumentParser, CommandLine, build_annofabapi_resource_and_login, get_list_from_args
|
|
18
|
+
from annofabcli.common.download import DownloadingFile
|
|
19
|
+
from annofabcli.common.enums import FormatArgument
|
|
20
|
+
from annofabcli.common.facade import (
|
|
21
|
+
AnnofabApiFacade,
|
|
22
|
+
TaskQuery,
|
|
23
|
+
match_annotation_with_task_query,
|
|
24
|
+
)
|
|
25
|
+
from annofabcli.common.utils import print_csv, print_json
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AnnotationPolygonInfo(BaseModel):
|
|
31
|
+
"""
|
|
32
|
+
ポリゴンアノテーションの情報
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
model_config = ConfigDict(frozen=True)
|
|
36
|
+
|
|
37
|
+
project_id: str
|
|
38
|
+
task_id: str
|
|
39
|
+
task_status: str
|
|
40
|
+
task_phase: str
|
|
41
|
+
task_phase_stage: int
|
|
42
|
+
|
|
43
|
+
input_data_id: str
|
|
44
|
+
input_data_name: str
|
|
45
|
+
|
|
46
|
+
updated_datetime: str | None
|
|
47
|
+
"""アノテーションJSONに格納されているアノテーションの更新日時"""
|
|
48
|
+
|
|
49
|
+
label: str
|
|
50
|
+
annotation_id: str
|
|
51
|
+
point_count: int
|
|
52
|
+
area: float | None
|
|
53
|
+
"""ポリゴンの面積。2点のポリラインの場合はNone"""
|
|
54
|
+
centroid: dict[str, float] | None
|
|
55
|
+
"""ポリゴンの重心座標。2点のポリラインの場合はNone"""
|
|
56
|
+
bounding_box_width: float | None
|
|
57
|
+
"""外接矩形の幅。2点のポリラインの場合はNone"""
|
|
58
|
+
bounding_box_height: float | None
|
|
59
|
+
"""外接矩形の高さ。2点のポリラインの場合はNone"""
|
|
60
|
+
attributes: dict[str, Any]
|
|
61
|
+
points: list[dict[str, int]]
|
|
62
|
+
"""ポリゴンの頂点リスト。各頂点は整数座標 {"x": int, "y": int} の形式。
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def calculate_polygon_properties(points: list[dict[str, int]]) -> tuple[float | None, dict[str, float] | None, float | None, float | None]:
|
|
67
|
+
"""
|
|
68
|
+
ポリゴンの面積、重心、外接矩形のサイズを計算する。
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
points: ポリゴンの頂点リスト。各頂点は整数座標 {"x": int, "y": int} の形式。
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
(面積, 重心, 外接矩形の幅, 外接矩形の高さ) のタプル。
|
|
75
|
+
2点以下の場合はポリラインなので、(None, None, None, None) を返す。
|
|
76
|
+
無効なポリゴン(自己交差など)の場合も (None, None, None, None) を返す。
|
|
77
|
+
"""
|
|
78
|
+
if len(points) < 3:
|
|
79
|
+
# 2点以下の場合はポリラインなので、NA扱い
|
|
80
|
+
return None, None, None, None
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
# shapelyのPolygonオブジェクトを作成
|
|
84
|
+
coords = [(p["x"], p["y"]) for p in points]
|
|
85
|
+
polygon = Polygon(coords)
|
|
86
|
+
|
|
87
|
+
# 面積を計算
|
|
88
|
+
area = polygon.area
|
|
89
|
+
|
|
90
|
+
# 重心を計算
|
|
91
|
+
centroid = polygon.centroid
|
|
92
|
+
centroid_dict = {"x": centroid.x, "y": centroid.y}
|
|
93
|
+
|
|
94
|
+
# 外接矩形を取得
|
|
95
|
+
minx, miny, maxx, maxy = polygon.bounds
|
|
96
|
+
bbox_width = maxx - minx
|
|
97
|
+
bbox_height = maxy - miny
|
|
98
|
+
except (ValueError, ShapelyError):
|
|
99
|
+
# 無効なポリゴン(例:自己交差など)の場合はNA扱い
|
|
100
|
+
return None, None, None, None
|
|
101
|
+
else:
|
|
102
|
+
return area, centroid_dict, bbox_width, bbox_height
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_annotation_polygon_info_list(simple_annotation: dict[str, Any], *, target_label_names: Collection[str] | None = None) -> list[AnnotationPolygonInfo]:
|
|
106
|
+
result = []
|
|
107
|
+
target_label_names_set = set(target_label_names) if target_label_names is not None else None
|
|
108
|
+
for detail in simple_annotation["details"]:
|
|
109
|
+
if detail["data"]["_type"] == "Points":
|
|
110
|
+
label = detail["label"]
|
|
111
|
+
# ラベル名によるフィルタリング
|
|
112
|
+
if target_label_names_set is not None and label not in target_label_names_set:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
points = detail["data"]["points"]
|
|
116
|
+
point_count = len(points)
|
|
117
|
+
|
|
118
|
+
# ポリゴンのプロパティを計算
|
|
119
|
+
area, centroid, bbox_width, bbox_height = calculate_polygon_properties(points)
|
|
120
|
+
|
|
121
|
+
result.append(
|
|
122
|
+
AnnotationPolygonInfo(
|
|
123
|
+
project_id=simple_annotation["project_id"],
|
|
124
|
+
task_id=simple_annotation["task_id"],
|
|
125
|
+
task_phase=simple_annotation["task_phase"],
|
|
126
|
+
task_phase_stage=simple_annotation["task_phase_stage"],
|
|
127
|
+
task_status=simple_annotation["task_status"],
|
|
128
|
+
input_data_id=simple_annotation["input_data_id"],
|
|
129
|
+
input_data_name=simple_annotation["input_data_name"],
|
|
130
|
+
label=label,
|
|
131
|
+
annotation_id=detail["annotation_id"],
|
|
132
|
+
point_count=point_count,
|
|
133
|
+
area=area,
|
|
134
|
+
centroid=centroid,
|
|
135
|
+
bounding_box_width=bbox_width,
|
|
136
|
+
bounding_box_height=bbox_height,
|
|
137
|
+
attributes=detail["attributes"],
|
|
138
|
+
points=points,
|
|
139
|
+
updated_datetime=simple_annotation["updated_datetime"],
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_annotation_polygon_info_list_from_annotation_path(
|
|
147
|
+
annotation_path: Path,
|
|
148
|
+
*,
|
|
149
|
+
target_task_ids: Collection[str] | None = None,
|
|
150
|
+
task_query: TaskQuery | None = None,
|
|
151
|
+
target_label_names: Collection[str] | None = None,
|
|
152
|
+
) -> list[AnnotationPolygonInfo]:
|
|
153
|
+
annotation_polygon_list = []
|
|
154
|
+
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
155
|
+
iter_parser = lazy_parse_simple_annotation_by_input_data(annotation_path)
|
|
156
|
+
logger.info(f"アノテーションZIPまたはディレクトリ'{annotation_path}'を読み込みます。")
|
|
157
|
+
for index, parser in enumerate(iter_parser):
|
|
158
|
+
if (index + 1) % 10000 == 0:
|
|
159
|
+
logger.info(f"{index + 1} 件目のJSONを読み込み中")
|
|
160
|
+
if target_task_ids is not None and parser.task_id not in target_task_ids:
|
|
161
|
+
continue
|
|
162
|
+
dict_simple_annotation = parser.load_json()
|
|
163
|
+
if task_query is not None and not match_annotation_with_task_query(dict_simple_annotation, task_query):
|
|
164
|
+
continue
|
|
165
|
+
sub_annotation_polygon_list = get_annotation_polygon_info_list(dict_simple_annotation, target_label_names=target_label_names)
|
|
166
|
+
annotation_polygon_list.extend(sub_annotation_polygon_list)
|
|
167
|
+
return annotation_polygon_list
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def create_df(
|
|
171
|
+
annotation_polygon_list: list[AnnotationPolygonInfo],
|
|
172
|
+
) -> pandas.DataFrame:
|
|
173
|
+
"""
|
|
174
|
+
CSV出力用のDataFrameを作成する。
|
|
175
|
+
|
|
176
|
+
Notes:
|
|
177
|
+
points列は含めない。CSVに含めると列の長さが非常に大きくなるため。
|
|
178
|
+
attributes列は、キーごとに別々の列(attributes.<key>の形式)として出力する。
|
|
179
|
+
pandas.json_normalizeを使用してネストした辞書を自動的に展開する。
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
# 基本列の定義
|
|
183
|
+
base_columns = [
|
|
184
|
+
"project_id",
|
|
185
|
+
"task_id",
|
|
186
|
+
"task_status",
|
|
187
|
+
"task_phase",
|
|
188
|
+
"task_phase_stage",
|
|
189
|
+
"input_data_id",
|
|
190
|
+
"input_data_name",
|
|
191
|
+
"updated_datetime",
|
|
192
|
+
"label",
|
|
193
|
+
"annotation_id",
|
|
194
|
+
"point_count",
|
|
195
|
+
"area",
|
|
196
|
+
"centroid.x",
|
|
197
|
+
"centroid.y",
|
|
198
|
+
"bounding_box_width",
|
|
199
|
+
"bounding_box_height",
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
if len(annotation_polygon_list) == 0:
|
|
203
|
+
# 件数が0件のときも列ヘッダを出力する
|
|
204
|
+
return pandas.DataFrame(columns=base_columns)
|
|
205
|
+
|
|
206
|
+
# pandas.json_normalizeを使用してネストした辞書を展開
|
|
207
|
+
# centroid(辞書)とattributes(辞書)が自動的に展開される
|
|
208
|
+
df = pandas.json_normalize([e.model_dump() for e in annotation_polygon_list])
|
|
209
|
+
|
|
210
|
+
# attributes列を抽出してソート
|
|
211
|
+
attributes_columns = sorted([col for col in df.columns if col.startswith("attributes.")])
|
|
212
|
+
# 列の順序を設定
|
|
213
|
+
columns = base_columns + attributes_columns
|
|
214
|
+
|
|
215
|
+
return df[columns]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def print_annotation_polygon(
|
|
219
|
+
annotation_path: Path,
|
|
220
|
+
output_file: Path,
|
|
221
|
+
output_format: FormatArgument,
|
|
222
|
+
*,
|
|
223
|
+
target_task_ids: Collection[str] | None = None,
|
|
224
|
+
task_query: TaskQuery | None = None,
|
|
225
|
+
target_label_names: Collection[str] | None = None,
|
|
226
|
+
) -> None:
|
|
227
|
+
annotation_polygon_list = get_annotation_polygon_info_list_from_annotation_path(
|
|
228
|
+
annotation_path,
|
|
229
|
+
target_task_ids=target_task_ids,
|
|
230
|
+
task_query=task_query,
|
|
231
|
+
target_label_names=target_label_names,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
logger.info(f"{len(annotation_polygon_list)} 件のポリゴンアノテーションの情報を出力します。 :: output='{output_file}'")
|
|
235
|
+
|
|
236
|
+
if output_format == FormatArgument.CSV:
|
|
237
|
+
df = create_df(annotation_polygon_list)
|
|
238
|
+
print_csv(df, output_file)
|
|
239
|
+
|
|
240
|
+
elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
|
|
241
|
+
json_is_pretty = output_format == FormatArgument.PRETTY_JSON
|
|
242
|
+
# Pydantic BaseModelを使用したJSON処理
|
|
243
|
+
print_json(
|
|
244
|
+
[e.model_dump() for e in annotation_polygon_list],
|
|
245
|
+
is_pretty=json_is_pretty,
|
|
246
|
+
output=output_file,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
else:
|
|
250
|
+
raise ValueError(f"出力形式 '{output_format}' はサポートされていません。")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class ListAnnotationPolygon(CommandLine):
|
|
254
|
+
COMMON_MESSAGE = "annofabcli annotation_zip list_polygon_annotation: error:"
|
|
255
|
+
|
|
256
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
257
|
+
if args.project_id is None and args.annotation is None:
|
|
258
|
+
print( # noqa: T201
|
|
259
|
+
f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
|
|
260
|
+
file=sys.stderr,
|
|
261
|
+
)
|
|
262
|
+
return False
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
def main(self) -> None:
|
|
266
|
+
args = self.args
|
|
267
|
+
|
|
268
|
+
if not self.validate(args):
|
|
269
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
270
|
+
|
|
271
|
+
project_id: str | None = args.project_id
|
|
272
|
+
if project_id is not None:
|
|
273
|
+
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
274
|
+
project, _ = self.service.api.get_project(project_id)
|
|
275
|
+
if project["input_data_type"] != InputDataType.IMAGE.value:
|
|
276
|
+
print(f"project_id='{project_id}'であるプロジェクトは画像プロジェクトでないので、終了します", file=sys.stderr) # noqa: T201
|
|
277
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
278
|
+
|
|
279
|
+
annotation_path = Path(args.annotation) if args.annotation is not None else None
|
|
280
|
+
|
|
281
|
+
task_id_list = get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
282
|
+
task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
|
|
283
|
+
label_name_list = get_list_from_args(args.label_name) if args.label_name is not None else None
|
|
284
|
+
|
|
285
|
+
output_file: Path = args.output
|
|
286
|
+
output_format = FormatArgument(args.format)
|
|
287
|
+
|
|
288
|
+
downloading_obj = DownloadingFile(self.service)
|
|
289
|
+
|
|
290
|
+
def download_and_print_annotation_polygon(project_id: str, temp_dir: Path, *, is_latest: bool) -> None:
|
|
291
|
+
local_annotation_path = temp_dir / f"{project_id}__annotation.zip"
|
|
292
|
+
downloading_obj.download_annotation_zip(
|
|
293
|
+
project_id,
|
|
294
|
+
dest_path=local_annotation_path,
|
|
295
|
+
is_latest=is_latest,
|
|
296
|
+
)
|
|
297
|
+
print_annotation_polygon(
|
|
298
|
+
local_annotation_path,
|
|
299
|
+
output_file,
|
|
300
|
+
output_format,
|
|
301
|
+
target_task_ids=task_id_list,
|
|
302
|
+
task_query=task_query,
|
|
303
|
+
target_label_names=label_name_list,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if project_id is not None:
|
|
307
|
+
if args.temp_dir is not None:
|
|
308
|
+
download_and_print_annotation_polygon(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest)
|
|
309
|
+
else:
|
|
310
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
311
|
+
download_and_print_annotation_polygon(
|
|
312
|
+
project_id=project_id,
|
|
313
|
+
temp_dir=Path(str_temp_dir),
|
|
314
|
+
is_latest=args.latest,
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
assert annotation_path is not None
|
|
318
|
+
print_annotation_polygon(
|
|
319
|
+
annotation_path,
|
|
320
|
+
output_file,
|
|
321
|
+
output_format,
|
|
322
|
+
target_task_ids=task_id_list,
|
|
323
|
+
task_query=task_query,
|
|
324
|
+
target_label_names=label_name_list,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
329
|
+
argument_parser = ArgumentParser(parser)
|
|
330
|
+
|
|
331
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
332
|
+
group.add_argument(
|
|
333
|
+
"--annotation",
|
|
334
|
+
type=str,
|
|
335
|
+
help="アノテーションzip、またはzipを展開したディレクトリを指定します。",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
group.add_argument("-p", "--project_id", type=str, help="project_id。アノテーションZIPをダウンロードします。")
|
|
339
|
+
|
|
340
|
+
argument_parser.add_format(
|
|
341
|
+
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
|
342
|
+
default=FormatArgument.CSV,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
argument_parser.add_output()
|
|
346
|
+
|
|
347
|
+
parser.add_argument(
|
|
348
|
+
"-tq",
|
|
349
|
+
"--task_query",
|
|
350
|
+
type=str,
|
|
351
|
+
help="集計対象タスクを絞り込むためのクエリ条件をJSON形式で指定します。使用できるキーは task_id, status, phase, phase_stage です。"
|
|
352
|
+
" ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
|
|
353
|
+
)
|
|
354
|
+
argument_parser.add_task_id(required=False)
|
|
355
|
+
|
|
356
|
+
parser.add_argument(
|
|
357
|
+
"--label_name",
|
|
358
|
+
type=str,
|
|
359
|
+
nargs="+",
|
|
360
|
+
help="指定したラベル名のポリゴンアノテーションのみを対象にします。複数指定できます。",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
parser.add_argument(
|
|
364
|
+
"--latest",
|
|
365
|
+
action="store_true",
|
|
366
|
+
help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
parser.add_argument(
|
|
370
|
+
"--temp_dir",
|
|
371
|
+
type=Path,
|
|
372
|
+
help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
parser.set_defaults(subcommand_func=main)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def main(args: argparse.Namespace) -> None:
|
|
379
|
+
service = build_annofabapi_resource_and_login(args)
|
|
380
|
+
facade = AnnofabApiFacade(service)
|
|
381
|
+
ListAnnotationPolygon(service, facade, args).main()
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
385
|
+
subcommand_name = "list_polygon_annotation"
|
|
386
|
+
subcommand_help = "アノテーションZIPからポリゴンアノテーションの座標情報と属性情報を出力します。"
|
|
387
|
+
epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
388
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
|
|
389
|
+
parse_args(parser)
|
|
390
|
+
return parser
|