annofabcli 1.111.2__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/__main__.py +1 -2
- annofabcli/annotation/annotation_query.py +10 -10
- annofabcli/annotation/change_annotation_attributes.py +10 -10
- annofabcli/annotation/change_annotation_attributes_per_annotation.py +4 -5
- annofabcli/annotation/change_annotation_properties.py +14 -14
- annofabcli/annotation/copy_annotation.py +6 -6
- annofabcli/annotation/create_classification_annotation.py +7 -7
- annofabcli/annotation/delete_annotation.py +9 -9
- annofabcli/annotation/download_annotation_zip.py +1 -3
- annofabcli/annotation/dump_annotation.py +8 -8
- annofabcli/annotation/import_annotation.py +13 -13
- annofabcli/annotation/list_annotation.py +9 -9
- annofabcli/annotation/list_annotation_count.py +2 -3
- annofabcli/annotation/merge_segmentation.py +6 -6
- annofabcli/annotation/remove_segmentation_overlap.py +5 -5
- annofabcli/annotation/restore_annotation.py +7 -7
- annofabcli/annotation/subcommand_annotation.py +1 -2
- annofabcli/annotation_specs/add_attribute_restriction.py +4 -5
- annofabcli/annotation_specs/attribute_restriction.py +8 -8
- annofabcli/annotation_specs/export_annotation_specs.py +4 -5
- annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -4
- annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +3 -4
- annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +3 -4
- annofabcli/annotation_specs/list_annotation_specs_attribute.py +9 -10
- annofabcli/annotation_specs/list_annotation_specs_choice.py +9 -10
- annofabcli/annotation_specs/list_annotation_specs_history.py +3 -3
- annofabcli/annotation_specs/list_annotation_specs_label.py +8 -9
- annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +10 -11
- annofabcli/annotation_specs/list_attribute_restriction.py +2 -4
- annofabcli/annotation_specs/list_label_color.py +2 -3
- annofabcli/annotation_specs/put_label_color.py +3 -4
- annofabcli/annotation_specs/subcommand_annotation_specs.py +1 -3
- annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +365 -0
- annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +37 -38
- 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 +25 -15
- annofabcli/annotation_zip/list_single_point_annotation.py +25 -34
- annofabcli/annotation_zip/subcommand_annotation_zip.py +7 -2
- annofabcli/annotation_zip/validate_annotation.py +8 -7
- annofabcli/comment/delete_comment.py +4 -6
- annofabcli/comment/download_comment_json.py +4 -6
- annofabcli/comment/list_all_comment.py +5 -6
- annofabcli/comment/list_comment.py +3 -4
- annofabcli/comment/put_comment.py +9 -10
- annofabcli/comment/put_comment_simply.py +5 -6
- annofabcli/comment/put_inspection_comment.py +1 -3
- annofabcli/comment/put_inspection_comment_simply.py +1 -3
- annofabcli/comment/put_onhold_comment.py +1 -3
- annofabcli/comment/put_onhold_comment_simply.py +1 -3
- annofabcli/comment/subcommand_comment.py +1 -3
- annofabcli/common/bokeh.py +4 -4
- annofabcli/common/cli.py +18 -17
- annofabcli/common/download.py +28 -29
- annofabcli/common/facade.py +37 -38
- annofabcli/common/image.py +14 -14
- annofabcli/common/utils.py +8 -8
- annofabcli/common/visualize.py +13 -13
- annofabcli/experimental/list_out_of_range_annotation_for_movie.py +3 -4
- annofabcli/experimental/subcommand_experimental.py +1 -3
- annofabcli/filesystem/draw_annotation.py +27 -27
- annofabcli/filesystem/filter_annotation.py +9 -10
- annofabcli/filesystem/mask_user_info.py +15 -15
- annofabcli/filesystem/merge_annotation.py +9 -9
- annofabcli/filesystem/subcommand_filesystem.py +1 -3
- annofabcli/input_data/copy_input_data.py +8 -9
- annofabcli/input_data/delete_input_data.py +3 -3
- annofabcli/input_data/delete_metadata_key_of_input_data.py +3 -5
- annofabcli/input_data/download_input_data_json.py +4 -6
- annofabcli/input_data/list_all_input_data.py +9 -9
- annofabcli/input_data/list_all_input_data_merged_task.py +5 -5
- annofabcli/input_data/list_input_data.py +5 -5
- annofabcli/input_data/put_input_data.py +6 -6
- annofabcli/input_data/put_input_data_with_zip.py +3 -4
- annofabcli/input_data/subcommand_input_data.py +1 -3
- annofabcli/input_data/update_input_data.py +6 -8
- annofabcli/input_data/update_metadata_of_input_data.py +3 -5
- annofabcli/instruction/copy_instruction.py +5 -6
- annofabcli/instruction/download_instruction.py +5 -6
- annofabcli/instruction/list_instruction_history.py +3 -3
- annofabcli/instruction/subcommand_instruction.py +1 -3
- annofabcli/instruction/upload_instruction.py +3 -4
- annofabcli/job/delete_job.py +2 -3
- annofabcli/job/list_job.py +5 -5
- annofabcli/job/list_last_job.py +4 -4
- annofabcli/job/subcommand_job.py +1 -3
- annofabcli/job/wait_job.py +4 -5
- annofabcli/my_account/get_my_account.py +2 -3
- annofabcli/my_account/subcommand_my_account.py +1 -3
- annofabcli/organization/list_organization.py +2 -3
- annofabcli/organization/subcommand_organization.py +1 -3
- annofabcli/organization_member/change_organization_member.py +3 -4
- annofabcli/organization_member/delete_organization_member.py +3 -4
- annofabcli/organization_member/invite_organization_member.py +1 -3
- annofabcli/organization_member/list_organization_member.py +3 -3
- annofabcli/organization_member/subcommand_organization_member.py +1 -3
- annofabcli/project/change_organization_of_project.py +4 -4
- annofabcli/project/change_project_status.py +4 -4
- annofabcli/project/copy_project.py +5 -5
- annofabcli/project/create_project.py +8 -8
- annofabcli/project/diff_projects.py +4 -5
- annofabcli/project/list_project.py +5 -5
- annofabcli/project/put_project.py +2 -3
- annofabcli/project/subcommand_project.py +1 -2
- annofabcli/project/update_configuration.py +4 -4
- annofabcli/project/update_project.py +6 -8
- annofabcli/project_member/change_project_members.py +8 -8
- annofabcli/project_member/copy_project_members.py +4 -4
- annofabcli/project_member/drop_project_members.py +2 -3
- annofabcli/project_member/invite_project_members.py +1 -3
- annofabcli/project_member/list_users.py +2 -3
- annofabcli/project_member/put_project_members.py +6 -6
- annofabcli/project_member/subcommand_project_member.py +1 -3
- annofabcli/stat_visualization/mask_visualization_dir.py +8 -9
- annofabcli/stat_visualization/merge_visualization_dir.py +6 -7
- annofabcli/stat_visualization/subcommand_stat_visualization.py +1 -2
- annofabcli/stat_visualization/summarize_whole_performance_csv.py +1 -2
- annofabcli/stat_visualization/write_graph.py +2 -3
- annofabcli/stat_visualization/write_performance_rating_csv.py +20 -27
- annofabcli/statistics/histogram.py +5 -6
- annofabcli/statistics/linegraph.py +13 -14
- annofabcli/statistics/list_annotation_area.py +38 -13
- annofabcli/statistics/list_annotation_attribute.py +9 -10
- annofabcli/statistics/list_annotation_attribute_filled_count.py +30 -31
- annofabcli/statistics/list_annotation_count.py +57 -58
- annofabcli/statistics/list_annotation_duration.py +33 -34
- annofabcli/statistics/list_video_duration.py +4 -5
- annofabcli/statistics/list_worktime.py +4 -4
- annofabcli/statistics/scatter.py +9 -8
- annofabcli/statistics/subcommand_statistics.py +1 -4
- annofabcli/statistics/summarize_task_count.py +4 -6
- annofabcli/statistics/summarize_task_count_by_task_id_group.py +2 -4
- annofabcli/statistics/summarize_task_count_by_user.py +1 -3
- annofabcli/statistics/visualization/dataframe/annotation_count.py +5 -4
- annofabcli/statistics/visualization/dataframe/annotation_duration.py +2 -3
- annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +15 -17
- annofabcli/statistics/visualization/dataframe/productivity_per_date.py +17 -19
- annofabcli/statistics/visualization/dataframe/project_performance.py +3 -12
- annofabcli/statistics/visualization/dataframe/task.py +11 -12
- annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +9 -10
- annofabcli/statistics/visualization/dataframe/user_performance.py +21 -19
- annofabcli/statistics/visualization/dataframe/whole_performance.py +3 -4
- annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +12 -14
- annofabcli/statistics/visualization/dataframe/worktime_per_date.py +11 -13
- annofabcli/statistics/visualization/filtering_query.py +7 -7
- annofabcli/statistics/visualization/project_dir.py +27 -14
- annofabcli/statistics/visualize_annotation_count.py +22 -23
- annofabcli/statistics/visualize_annotation_duration.py +21 -22
- annofabcli/statistics/visualize_statistics.py +36 -33
- annofabcli/statistics/visualize_video_duration.py +18 -20
- annofabcli/supplementary/delete_supplementary_data.py +5 -5
- annofabcli/supplementary/list_supplementary_data.py +4 -4
- annofabcli/supplementary/put_supplementary_data.py +9 -9
- annofabcli/supplementary/subcommand_supplementary.py +1 -3
- annofabcli/task/cancel_acceptance.py +16 -17
- annofabcli/task/change_operator.py +10 -12
- annofabcli/task/change_status_to_break.py +7 -9
- annofabcli/task/change_status_to_on_hold.py +10 -12
- annofabcli/task/complete_tasks.py +17 -18
- annofabcli/task/copy_tasks.py +3 -5
- annofabcli/task/delete_metadata_key_of_task.py +4 -6
- annofabcli/task/delete_tasks.py +7 -7
- annofabcli/task/download_task_json.py +4 -6
- annofabcli/task/list_all_tasks.py +8 -8
- annofabcli/task/list_all_tasks_added_task_history.py +14 -13
- annofabcli/task/list_tasks.py +7 -7
- annofabcli/task/list_tasks_added_task_history.py +10 -10
- annofabcli/task/put_tasks.py +5 -6
- annofabcli/task/put_tasks_by_count.py +2 -3
- annofabcli/task/reject_tasks.py +18 -20
- annofabcli/task/subcommand_task.py +1 -3
- annofabcli/task/update_metadata_of_task.py +5 -6
- annofabcli/task_history/download_task_history_json.py +4 -6
- annofabcli/task_history/list_all_task_history.py +6 -7
- annofabcli/task_history/list_task_history.py +4 -5
- annofabcli/task_history/subcommand_task_history.py +1 -3
- annofabcli/task_history_event/download_task_history_event_json.py +4 -6
- annofabcli/task_history_event/list_all_task_history_event.py +7 -7
- annofabcli/task_history_event/list_worktime.py +17 -16
- annofabcli/task_history_event/subcommand_task_history_event.py +1 -2
- {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/METADATA +9 -15
- annofabcli-1.113.0.dist-info/RECORD +231 -0
- {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/WHEEL +1 -1
- annofabcli-1.111.2.dist-info/RECORD +0 -228
- {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import math
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from collections.abc import Collection
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import pandas
|
|
11
|
+
from annofabapi.models import InputDataType, ProjectMemberRole
|
|
12
|
+
from pydantic import BaseModel, ConfigDict
|
|
13
|
+
|
|
14
|
+
import annofabcli.common.cli
|
|
15
|
+
from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
|
|
16
|
+
from annofabcli.common.cli import COMMAND_LINE_ERROR_STATUS_CODE, ArgumentParser, CommandLine, build_annofabapi_resource_and_login, get_list_from_args
|
|
17
|
+
from annofabcli.common.download import DownloadingFile
|
|
18
|
+
from annofabcli.common.enums import FormatArgument
|
|
19
|
+
from annofabcli.common.facade import (
|
|
20
|
+
AnnofabApiFacade,
|
|
21
|
+
TaskQuery,
|
|
22
|
+
match_annotation_with_task_query,
|
|
23
|
+
)
|
|
24
|
+
from annofabcli.common.utils import print_csv, print_json
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AnnotationPolylineInfo(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
ポリラインアノテーションの情報
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(frozen=True)
|
|
35
|
+
|
|
36
|
+
project_id: str
|
|
37
|
+
task_id: str
|
|
38
|
+
task_status: str
|
|
39
|
+
task_phase: str
|
|
40
|
+
task_phase_stage: int
|
|
41
|
+
|
|
42
|
+
input_data_id: str
|
|
43
|
+
input_data_name: str
|
|
44
|
+
|
|
45
|
+
updated_datetime: str | None
|
|
46
|
+
"""アノテーションJSONに格納されているアノテーションの更新日時"""
|
|
47
|
+
|
|
48
|
+
label: str
|
|
49
|
+
annotation_id: str
|
|
50
|
+
point_count: int
|
|
51
|
+
length: float
|
|
52
|
+
"""ポリラインの総長(各線分の長さの合計)"""
|
|
53
|
+
start_point: dict[str, float]
|
|
54
|
+
"""始点の座標"""
|
|
55
|
+
end_point: dict[str, float]
|
|
56
|
+
"""終点の座標"""
|
|
57
|
+
midpoint: dict[str, float]
|
|
58
|
+
"""中点(全頂点の座標平均)"""
|
|
59
|
+
bounding_box_width: float
|
|
60
|
+
"""外接矩形の幅"""
|
|
61
|
+
bounding_box_height: float
|
|
62
|
+
"""外接矩形の高さ"""
|
|
63
|
+
attributes: dict[str, Any]
|
|
64
|
+
points: list[dict[str, int]]
|
|
65
|
+
"""ポリラインの頂点リスト。各頂点は整数座標 {"x": int, "y": int} の形式。
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def calculate_polyline_properties(points: list[dict[str, int]]) -> tuple[float, dict[str, float], dict[str, float], dict[str, float], float, float]:
|
|
70
|
+
"""
|
|
71
|
+
ポリラインの長さ、始点、終点、中点、外接矩形のサイズを計算する。
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
points: ポリラインの頂点リスト。各頂点は整数座標 {"x": int, "y": int} の形式。2点以上が必須。
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
(長さ, 始点, 終点, 中点, 外接矩形の幅, 外接矩形の高さ) のタプル。
|
|
78
|
+
"""
|
|
79
|
+
# 始点と終点
|
|
80
|
+
start_point = {"x": float(points[0]["x"]), "y": float(points[0]["y"])}
|
|
81
|
+
end_point = {"x": float(points[-1]["x"]), "y": float(points[-1]["y"])}
|
|
82
|
+
|
|
83
|
+
# 中点(全頂点の座標平均)
|
|
84
|
+
sum_x = sum(p["x"] for p in points)
|
|
85
|
+
sum_y = sum(p["y"] for p in points)
|
|
86
|
+
midpoint = {"x": sum_x / len(points), "y": sum_y / len(points)}
|
|
87
|
+
|
|
88
|
+
# 線の長さを計算
|
|
89
|
+
total_length = 0.0
|
|
90
|
+
for i in range(len(points) - 1):
|
|
91
|
+
p1 = points[i]
|
|
92
|
+
p2 = points[i + 1]
|
|
93
|
+
dx = p2["x"] - p1["x"]
|
|
94
|
+
dy = p2["y"] - p1["y"]
|
|
95
|
+
segment_length = math.hypot(dx, dy)
|
|
96
|
+
total_length += segment_length
|
|
97
|
+
|
|
98
|
+
# 外接矩形を計算
|
|
99
|
+
x_coords = [p["x"] for p in points]
|
|
100
|
+
y_coords = [p["y"] for p in points]
|
|
101
|
+
min_x = min(x_coords)
|
|
102
|
+
max_x = max(x_coords)
|
|
103
|
+
min_y = min(y_coords)
|
|
104
|
+
max_y = max(y_coords)
|
|
105
|
+
bbox_width = float(max_x - min_x)
|
|
106
|
+
bbox_height = float(max_y - min_y)
|
|
107
|
+
|
|
108
|
+
return total_length, start_point, end_point, midpoint, bbox_width, bbox_height
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_annotation_polyline_info_list(simple_annotation: dict[str, Any], *, target_label_names: Collection[str] | None = None) -> list[AnnotationPolylineInfo]:
|
|
112
|
+
result = []
|
|
113
|
+
target_label_names_set = set(target_label_names) if target_label_names is not None else None
|
|
114
|
+
for detail in simple_annotation["details"]:
|
|
115
|
+
if detail["data"]["_type"] == "Points":
|
|
116
|
+
label = detail["label"]
|
|
117
|
+
# ラベル名によるフィルタリング
|
|
118
|
+
if target_label_names_set is not None and label not in target_label_names_set:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
points = detail["data"]["points"]
|
|
122
|
+
point_count = len(points)
|
|
123
|
+
|
|
124
|
+
# ポリラインのプロパティを計算
|
|
125
|
+
length, start_point, end_point, midpoint, bbox_width, bbox_height = calculate_polyline_properties(points)
|
|
126
|
+
|
|
127
|
+
result.append(
|
|
128
|
+
AnnotationPolylineInfo(
|
|
129
|
+
project_id=simple_annotation["project_id"],
|
|
130
|
+
task_id=simple_annotation["task_id"],
|
|
131
|
+
task_phase=simple_annotation["task_phase"],
|
|
132
|
+
task_phase_stage=simple_annotation["task_phase_stage"],
|
|
133
|
+
task_status=simple_annotation["task_status"],
|
|
134
|
+
input_data_id=simple_annotation["input_data_id"],
|
|
135
|
+
input_data_name=simple_annotation["input_data_name"],
|
|
136
|
+
label=label,
|
|
137
|
+
annotation_id=detail["annotation_id"],
|
|
138
|
+
point_count=point_count,
|
|
139
|
+
length=length,
|
|
140
|
+
start_point=start_point,
|
|
141
|
+
end_point=end_point,
|
|
142
|
+
midpoint=midpoint,
|
|
143
|
+
bounding_box_width=bbox_width,
|
|
144
|
+
bounding_box_height=bbox_height,
|
|
145
|
+
attributes=detail["attributes"],
|
|
146
|
+
points=points,
|
|
147
|
+
updated_datetime=simple_annotation["updated_datetime"],
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_annotation_polyline_info_list_from_annotation_path(
|
|
155
|
+
annotation_path: Path,
|
|
156
|
+
*,
|
|
157
|
+
target_task_ids: Collection[str] | None = None,
|
|
158
|
+
task_query: TaskQuery | None = None,
|
|
159
|
+
target_label_names: Collection[str] | None = None,
|
|
160
|
+
) -> list[AnnotationPolylineInfo]:
|
|
161
|
+
annotation_polyline_list = []
|
|
162
|
+
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
163
|
+
iter_parser = lazy_parse_simple_annotation_by_input_data(annotation_path)
|
|
164
|
+
logger.info(f"アノテーションZIPまたはディレクトリ'{annotation_path}'を読み込みます。")
|
|
165
|
+
for index, parser in enumerate(iter_parser):
|
|
166
|
+
if (index + 1) % 10000 == 0:
|
|
167
|
+
logger.info(f"{index + 1} 件目のJSONを読み込み中")
|
|
168
|
+
if target_task_ids is not None and parser.task_id not in target_task_ids:
|
|
169
|
+
continue
|
|
170
|
+
dict_simple_annotation = parser.load_json()
|
|
171
|
+
if task_query is not None and not match_annotation_with_task_query(dict_simple_annotation, task_query):
|
|
172
|
+
continue
|
|
173
|
+
sub_annotation_polyline_list = get_annotation_polyline_info_list(dict_simple_annotation, target_label_names=target_label_names)
|
|
174
|
+
annotation_polyline_list.extend(sub_annotation_polyline_list)
|
|
175
|
+
return annotation_polyline_list
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def create_df(
|
|
179
|
+
annotation_polyline_list: list[AnnotationPolylineInfo],
|
|
180
|
+
) -> pandas.DataFrame:
|
|
181
|
+
"""
|
|
182
|
+
CSV出力用のDataFrameを作成する。
|
|
183
|
+
|
|
184
|
+
Notes:
|
|
185
|
+
points列は含めない。CSVに含めると列の長さが非常に大きくなるため。
|
|
186
|
+
attributes列は、キーごとに別々の列(attributes.<key>の形式)として出力する。
|
|
187
|
+
pandas.json_normalizeを使用してネストした辞書を自動的に展開する。
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
# 基本列の定義
|
|
191
|
+
base_columns = [
|
|
192
|
+
"project_id",
|
|
193
|
+
"task_id",
|
|
194
|
+
"task_status",
|
|
195
|
+
"task_phase",
|
|
196
|
+
"task_phase_stage",
|
|
197
|
+
"input_data_id",
|
|
198
|
+
"input_data_name",
|
|
199
|
+
"updated_datetime",
|
|
200
|
+
"label",
|
|
201
|
+
"annotation_id",
|
|
202
|
+
"point_count",
|
|
203
|
+
"length",
|
|
204
|
+
"start_point.x",
|
|
205
|
+
"start_point.y",
|
|
206
|
+
"end_point.x",
|
|
207
|
+
"end_point.y",
|
|
208
|
+
"midpoint.x",
|
|
209
|
+
"midpoint.y",
|
|
210
|
+
"bounding_box_width",
|
|
211
|
+
"bounding_box_height",
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
if len(annotation_polyline_list) == 0:
|
|
215
|
+
# 件数が0件のときも列ヘッダを出力する
|
|
216
|
+
return pandas.DataFrame(columns=base_columns)
|
|
217
|
+
|
|
218
|
+
# pandas.json_normalizeを使用してネストした辞書を展開
|
|
219
|
+
# start_point, end_point, midpoint(辞書)とattributes(辞書)が自動的に展開される
|
|
220
|
+
df = pandas.json_normalize([e.model_dump() for e in annotation_polyline_list])
|
|
221
|
+
|
|
222
|
+
# attributes列を抽出してソート
|
|
223
|
+
attributes_columns = sorted([col for col in df.columns if col.startswith("attributes.")])
|
|
224
|
+
# 列の順序を設定
|
|
225
|
+
columns = base_columns + attributes_columns
|
|
226
|
+
|
|
227
|
+
return df[columns]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def print_annotation_polyline(
|
|
231
|
+
annotation_path: Path,
|
|
232
|
+
output_file: Path,
|
|
233
|
+
output_format: FormatArgument,
|
|
234
|
+
*,
|
|
235
|
+
target_task_ids: Collection[str] | None = None,
|
|
236
|
+
task_query: TaskQuery | None = None,
|
|
237
|
+
target_label_names: Collection[str] | None = None,
|
|
238
|
+
) -> None:
|
|
239
|
+
annotation_polyline_list = get_annotation_polyline_info_list_from_annotation_path(
|
|
240
|
+
annotation_path,
|
|
241
|
+
target_task_ids=target_task_ids,
|
|
242
|
+
task_query=task_query,
|
|
243
|
+
target_label_names=target_label_names,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
logger.info(f"{len(annotation_polyline_list)} 件のポリラインアノテーションの情報を出力します。 :: output='{output_file}'")
|
|
247
|
+
|
|
248
|
+
if output_format == FormatArgument.CSV:
|
|
249
|
+
df = create_df(annotation_polyline_list)
|
|
250
|
+
print_csv(df, output_file)
|
|
251
|
+
|
|
252
|
+
elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
|
|
253
|
+
json_is_pretty = output_format == FormatArgument.PRETTY_JSON
|
|
254
|
+
# Pydantic BaseModelを使用したJSON処理
|
|
255
|
+
print_json(
|
|
256
|
+
[e.model_dump() for e in annotation_polyline_list],
|
|
257
|
+
is_pretty=json_is_pretty,
|
|
258
|
+
output=output_file,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
raise ValueError(f"出力形式 '{output_format}' はサポートされていません。")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class ListAnnotationPolyline(CommandLine):
|
|
266
|
+
COMMON_MESSAGE = "annofabcli annotation_zip list_polyline_annotation: error:"
|
|
267
|
+
|
|
268
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
269
|
+
if args.project_id is None and args.annotation is None:
|
|
270
|
+
print( # noqa: T201
|
|
271
|
+
f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
|
|
272
|
+
file=sys.stderr,
|
|
273
|
+
)
|
|
274
|
+
return False
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
def main(self) -> None:
|
|
278
|
+
args = self.args
|
|
279
|
+
|
|
280
|
+
if not self.validate(args):
|
|
281
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
282
|
+
|
|
283
|
+
project_id: str | None = args.project_id
|
|
284
|
+
if project_id is not None:
|
|
285
|
+
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
286
|
+
project, _ = self.service.api.get_project(project_id)
|
|
287
|
+
if project["input_data_type"] != InputDataType.IMAGE.value:
|
|
288
|
+
print(f"project_id='{project_id}'であるプロジェクトは画像プロジェクトでないので、終了します", file=sys.stderr) # noqa: T201
|
|
289
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
290
|
+
|
|
291
|
+
annotation_path = Path(args.annotation) if args.annotation is not None else None
|
|
292
|
+
|
|
293
|
+
task_id_list = get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
294
|
+
task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
|
|
295
|
+
label_name_list = get_list_from_args(args.label_name) if args.label_name is not None else None
|
|
296
|
+
|
|
297
|
+
output_file: Path = args.output
|
|
298
|
+
output_format = FormatArgument(args.format)
|
|
299
|
+
|
|
300
|
+
downloading_obj = DownloadingFile(self.service)
|
|
301
|
+
|
|
302
|
+
def download_and_print_annotation_polyline(project_id: str, temp_dir: Path, *, is_latest: bool) -> None:
|
|
303
|
+
local_annotation_path = temp_dir / f"{project_id}__annotation.zip"
|
|
304
|
+
downloading_obj.download_annotation_zip(
|
|
305
|
+
project_id,
|
|
306
|
+
dest_path=local_annotation_path,
|
|
307
|
+
is_latest=is_latest,
|
|
308
|
+
)
|
|
309
|
+
print_annotation_polyline(
|
|
310
|
+
local_annotation_path,
|
|
311
|
+
output_file,
|
|
312
|
+
output_format,
|
|
313
|
+
target_task_ids=task_id_list,
|
|
314
|
+
task_query=task_query,
|
|
315
|
+
target_label_names=label_name_list,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if project_id is not None:
|
|
319
|
+
if args.temp_dir is not None:
|
|
320
|
+
download_and_print_annotation_polyline(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest)
|
|
321
|
+
else:
|
|
322
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
323
|
+
download_and_print_annotation_polyline(
|
|
324
|
+
project_id=project_id,
|
|
325
|
+
temp_dir=Path(str_temp_dir),
|
|
326
|
+
is_latest=args.latest,
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
assert annotation_path is not None
|
|
330
|
+
print_annotation_polyline(
|
|
331
|
+
annotation_path,
|
|
332
|
+
output_file,
|
|
333
|
+
output_format,
|
|
334
|
+
target_task_ids=task_id_list,
|
|
335
|
+
task_query=task_query,
|
|
336
|
+
target_label_names=label_name_list,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
341
|
+
argument_parser = ArgumentParser(parser)
|
|
342
|
+
|
|
343
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
344
|
+
group.add_argument(
|
|
345
|
+
"--annotation",
|
|
346
|
+
type=str,
|
|
347
|
+
help="アノテーションzip、またはzipを展開したディレクトリを指定します。",
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
group.add_argument("-p", "--project_id", type=str, help="project_id。アノテーションZIPをダウンロードします。")
|
|
351
|
+
|
|
352
|
+
argument_parser.add_format(
|
|
353
|
+
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
|
354
|
+
default=FormatArgument.CSV,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
argument_parser.add_output()
|
|
358
|
+
|
|
359
|
+
parser.add_argument(
|
|
360
|
+
"-tq",
|
|
361
|
+
"--task_query",
|
|
362
|
+
type=str,
|
|
363
|
+
help="集計対象タスクを絞り込むためのクエリ条件をJSON形式で指定します。使用できるキーは task_id, status, phase, phase_stage です。"
|
|
364
|
+
" ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
|
|
365
|
+
)
|
|
366
|
+
argument_parser.add_task_id(required=False)
|
|
367
|
+
|
|
368
|
+
parser.add_argument(
|
|
369
|
+
"--label_name",
|
|
370
|
+
type=str,
|
|
371
|
+
nargs="+",
|
|
372
|
+
help="指定したラベル名のポリラインアノテーションのみを対象にします。複数指定できます。",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
parser.add_argument(
|
|
376
|
+
"--latest",
|
|
377
|
+
action="store_true",
|
|
378
|
+
help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
parser.add_argument(
|
|
382
|
+
"--temp_dir",
|
|
383
|
+
type=Path,
|
|
384
|
+
help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
parser.set_defaults(subcommand_func=main)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def main(args: argparse.Namespace) -> None:
|
|
391
|
+
service = build_annofabapi_resource_and_login(args)
|
|
392
|
+
facade = AnnofabApiFacade(service)
|
|
393
|
+
ListAnnotationPolyline(service, facade, args).main()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
397
|
+
subcommand_name = "list_polyline_annotation"
|
|
398
|
+
subcommand_help = "アノテーションZIPからポリラインアノテーションの座標情報と属性情報を出力します。"
|
|
399
|
+
epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
400
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
|
|
401
|
+
parse_args(parser)
|
|
402
|
+
return parser
|
|
@@ -7,13 +7,12 @@ import tempfile
|
|
|
7
7
|
from collections.abc import Collection
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
import pandas
|
|
13
13
|
from annofabapi.models import InputDataType, ProjectMemberRole
|
|
14
14
|
from dataclasses_json import DataClassJsonMixin
|
|
15
15
|
|
|
16
|
-
import annofabcli
|
|
17
16
|
import annofabcli.common.cli
|
|
18
17
|
from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
|
|
19
18
|
from annofabcli.common.cli import COMMAND_LINE_ERROR_STATUS_CODE, ArgumentParser, CommandLine, build_annofabapi_resource_and_login, get_list_from_args
|
|
@@ -40,7 +39,7 @@ class RangeAnnotationInfo(DataClassJsonMixin):
|
|
|
40
39
|
input_data_id: str
|
|
41
40
|
input_data_name: str
|
|
42
41
|
|
|
43
|
-
updated_datetime:
|
|
42
|
+
updated_datetime: str | None
|
|
44
43
|
"""アノテーションJSONに格納されているアノテーションの更新日時"""
|
|
45
44
|
|
|
46
45
|
label: str
|
|
@@ -48,9 +47,10 @@ class RangeAnnotationInfo(DataClassJsonMixin):
|
|
|
48
47
|
begin_second: float
|
|
49
48
|
end_second: float
|
|
50
49
|
duration_second: float
|
|
50
|
+
attributes: dict[str, str | int | bool]
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def get_range_annotation_info_list(simple_annotation: dict[str, Any], *, target_label_names:
|
|
53
|
+
def get_range_annotation_info_list(simple_annotation: dict[str, Any], *, target_label_names: Collection[str] | None = None) -> list[RangeAnnotationInfo]:
|
|
54
54
|
result = []
|
|
55
55
|
target_label_names_set = set(target_label_names) if target_label_names is not None else None
|
|
56
56
|
for detail in simple_annotation["details"]:
|
|
@@ -81,6 +81,7 @@ def get_range_annotation_info_list(simple_annotation: dict[str, Any], *, target_
|
|
|
81
81
|
end_second=end_second,
|
|
82
82
|
duration_second=duration_second,
|
|
83
83
|
updated_datetime=simple_annotation["updated_datetime"],
|
|
84
|
+
attributes=detail["attributes"],
|
|
84
85
|
)
|
|
85
86
|
)
|
|
86
87
|
|
|
@@ -90,9 +91,9 @@ def get_range_annotation_info_list(simple_annotation: dict[str, Any], *, target_
|
|
|
90
91
|
def get_range_annotation_info_list_from_annotation_path(
|
|
91
92
|
annotation_path: Path,
|
|
92
93
|
*,
|
|
93
|
-
target_task_ids:
|
|
94
|
-
task_query:
|
|
95
|
-
target_label_names:
|
|
94
|
+
target_task_ids: Collection[str] | None = None,
|
|
95
|
+
task_query: TaskQuery | None = None,
|
|
96
|
+
target_label_names: Collection[str] | None = None,
|
|
96
97
|
) -> list[RangeAnnotationInfo]:
|
|
97
98
|
range_annotation_list = []
|
|
98
99
|
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
@@ -114,7 +115,7 @@ def get_range_annotation_info_list_from_annotation_path(
|
|
|
114
115
|
def create_df(
|
|
115
116
|
range_annotation_list: list[RangeAnnotationInfo],
|
|
116
117
|
) -> pandas.DataFrame:
|
|
117
|
-
|
|
118
|
+
base_columns = [
|
|
118
119
|
"project_id",
|
|
119
120
|
"task_id",
|
|
120
121
|
"task_status",
|
|
@@ -129,7 +130,16 @@ def create_df(
|
|
|
129
130
|
"end_second",
|
|
130
131
|
"duration_second",
|
|
131
132
|
]
|
|
132
|
-
|
|
133
|
+
|
|
134
|
+
if not range_annotation_list:
|
|
135
|
+
# 空のリストの場合は、base_columnsのみで空のDataFrameを返す
|
|
136
|
+
return pandas.DataFrame(columns=base_columns)
|
|
137
|
+
|
|
138
|
+
tmp_range_annotation_list = [e.to_dict(encode_json=True) for e in range_annotation_list]
|
|
139
|
+
df = pandas.json_normalize(tmp_range_annotation_list)
|
|
140
|
+
|
|
141
|
+
attribute_columns = sorted(col for col in df.columns if col.startswith("attributes."))
|
|
142
|
+
columns = base_columns + attribute_columns
|
|
133
143
|
|
|
134
144
|
return df[columns]
|
|
135
145
|
|
|
@@ -139,9 +149,9 @@ def print_range_annotation(
|
|
|
139
149
|
output_file: Path,
|
|
140
150
|
output_format: FormatArgument,
|
|
141
151
|
*,
|
|
142
|
-
target_task_ids:
|
|
143
|
-
task_query:
|
|
144
|
-
target_label_names:
|
|
152
|
+
target_task_ids: Collection[str] | None = None,
|
|
153
|
+
task_query: TaskQuery | None = None,
|
|
154
|
+
target_label_names: Collection[str] | None = None,
|
|
145
155
|
) -> None:
|
|
146
156
|
range_annotation_list = get_range_annotation_info_list_from_annotation_path(
|
|
147
157
|
annotation_path,
|
|
@@ -187,7 +197,7 @@ class ListRangeAnnotation(CommandLine):
|
|
|
187
197
|
if not self.validate(args):
|
|
188
198
|
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
189
199
|
|
|
190
|
-
project_id:
|
|
200
|
+
project_id: str | None = args.project_id
|
|
191
201
|
if project_id is not None:
|
|
192
202
|
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
193
203
|
project, _ = self.service.api.get_project(project_id)
|
|
@@ -274,7 +284,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
274
284
|
parser.add_argument(
|
|
275
285
|
"--label_name",
|
|
276
286
|
type=str,
|
|
277
|
-
nargs="
|
|
287
|
+
nargs="+",
|
|
278
288
|
help="指定したラベル名の区間アノテーションのみを対象にします。複数指定できます。",
|
|
279
289
|
)
|
|
280
290
|
|
|
@@ -299,7 +309,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
299
309
|
ListRangeAnnotation(service, facade, args).main()
|
|
300
310
|
|
|
301
311
|
|
|
302
|
-
def add_parser(subparsers:
|
|
312
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
303
313
|
subcommand_name = "list_range_annotation"
|
|
304
314
|
subcommand_help = "アノテーションZIPから動画プロジェクトの区間アノテーションの情報を出力します。"
|
|
305
315
|
epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
@@ -5,13 +5,12 @@ import tempfile
|
|
|
5
5
|
from collections.abc import Collection
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
import pandas
|
|
11
11
|
from annofabapi.models import InputDataType, ProjectMemberRole
|
|
12
12
|
from dataclasses_json import DataClassJsonMixin
|
|
13
13
|
|
|
14
|
-
import annofabcli
|
|
15
14
|
import annofabcli.common.cli
|
|
16
15
|
from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
|
|
17
16
|
from annofabcli.common.cli import COMMAND_LINE_ERROR_STATUS_CODE, ArgumentParser, CommandLine, build_annofabapi_resource_and_login, get_list_from_args
|
|
@@ -38,15 +37,16 @@ class AnnotationSinglePointInfo(DataClassJsonMixin):
|
|
|
38
37
|
input_data_id: str
|
|
39
38
|
input_data_name: str
|
|
40
39
|
|
|
41
|
-
updated_datetime:
|
|
40
|
+
updated_datetime: str | None
|
|
42
41
|
"""アノテーションJSONに格納されているアノテーションの更新日時"""
|
|
43
42
|
|
|
44
43
|
label: str
|
|
45
44
|
annotation_id: str
|
|
46
45
|
point: dict[str, int]
|
|
46
|
+
attributes: dict[str, str | int | bool]
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def get_annotation_single_point_info_list(simple_annotation: dict[str, Any], *, target_label_names:
|
|
49
|
+
def get_annotation_single_point_info_list(simple_annotation: dict[str, Any], *, target_label_names: Collection[str] | None = None) -> list[AnnotationSinglePointInfo]:
|
|
50
50
|
result = []
|
|
51
51
|
target_label_names_set = set(target_label_names) if target_label_names is not None else None
|
|
52
52
|
for detail in simple_annotation["details"]:
|
|
@@ -71,6 +71,7 @@ def get_annotation_single_point_info_list(simple_annotation: dict[str, Any], *,
|
|
|
71
71
|
annotation_id=detail["annotation_id"],
|
|
72
72
|
point=point,
|
|
73
73
|
updated_datetime=simple_annotation["updated_datetime"],
|
|
74
|
+
attributes=detail["attributes"],
|
|
74
75
|
)
|
|
75
76
|
)
|
|
76
77
|
|
|
@@ -80,9 +81,9 @@ def get_annotation_single_point_info_list(simple_annotation: dict[str, Any], *,
|
|
|
80
81
|
def get_annotation_single_point_info_list_from_annotation_path(
|
|
81
82
|
annotation_path: Path,
|
|
82
83
|
*,
|
|
83
|
-
target_task_ids:
|
|
84
|
-
task_query:
|
|
85
|
-
target_label_names:
|
|
84
|
+
target_task_ids: Collection[str] | None = None,
|
|
85
|
+
task_query: TaskQuery | None = None,
|
|
86
|
+
target_label_names: Collection[str] | None = None,
|
|
86
87
|
) -> list[AnnotationSinglePointInfo]:
|
|
87
88
|
annotation_point_list = []
|
|
88
89
|
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
@@ -104,7 +105,7 @@ def get_annotation_single_point_info_list_from_annotation_path(
|
|
|
104
105
|
def create_df(
|
|
105
106
|
annotation_point_list: list[AnnotationSinglePointInfo],
|
|
106
107
|
) -> pandas.DataFrame:
|
|
107
|
-
|
|
108
|
+
base_columns = [
|
|
108
109
|
"project_id",
|
|
109
110
|
"task_id",
|
|
110
111
|
"task_status",
|
|
@@ -118,26 +119,16 @@ def create_df(
|
|
|
118
119
|
"point.x",
|
|
119
120
|
"point.y",
|
|
120
121
|
]
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"updated_datetime": e.updated_datetime,
|
|
132
|
-
"label": e.label,
|
|
133
|
-
"annotation_id": e.annotation_id,
|
|
134
|
-
"point.x": e.point["x"],
|
|
135
|
-
"point.y": e.point["y"],
|
|
136
|
-
}
|
|
137
|
-
for e in annotation_point_list
|
|
138
|
-
],
|
|
139
|
-
columns=columns,
|
|
140
|
-
)
|
|
122
|
+
|
|
123
|
+
if not annotation_point_list:
|
|
124
|
+
# 空のリストの場合は、base_columnsのみで空のDataFrameを返す
|
|
125
|
+
return pandas.DataFrame(columns=base_columns)
|
|
126
|
+
|
|
127
|
+
tmp_annotation_point_list = [e.to_dict(encode_json=True) for e in annotation_point_list]
|
|
128
|
+
df = pandas.json_normalize(tmp_annotation_point_list)
|
|
129
|
+
|
|
130
|
+
attribute_columns = sorted(col for col in df.columns if col.startswith("attributes."))
|
|
131
|
+
columns = base_columns + attribute_columns
|
|
141
132
|
|
|
142
133
|
return df[columns]
|
|
143
134
|
|
|
@@ -147,9 +138,9 @@ def print_annotation_single_point(
|
|
|
147
138
|
output_file: Path,
|
|
148
139
|
output_format: FormatArgument,
|
|
149
140
|
*,
|
|
150
|
-
target_task_ids:
|
|
151
|
-
task_query:
|
|
152
|
-
target_label_names:
|
|
141
|
+
target_task_ids: Collection[str] | None = None,
|
|
142
|
+
task_query: TaskQuery | None = None,
|
|
143
|
+
target_label_names: Collection[str] | None = None,
|
|
153
144
|
) -> None:
|
|
154
145
|
annotation_point_list = get_annotation_single_point_info_list_from_annotation_path(
|
|
155
146
|
annotation_path,
|
|
@@ -195,7 +186,7 @@ class ListAnnotationSinglePoint(CommandLine):
|
|
|
195
186
|
if not self.validate(args):
|
|
196
187
|
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
197
188
|
|
|
198
|
-
project_id:
|
|
189
|
+
project_id: str | None = args.project_id
|
|
199
190
|
if project_id is not None:
|
|
200
191
|
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
201
192
|
project, _ = self.service.api.get_project(project_id)
|
|
@@ -283,7 +274,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
283
274
|
parser.add_argument(
|
|
284
275
|
"--label_name",
|
|
285
276
|
type=str,
|
|
286
|
-
nargs="
|
|
277
|
+
nargs="+",
|
|
287
278
|
help="指定したラベル名の点アノテーションのみを対象にします。複数指定できます。",
|
|
288
279
|
)
|
|
289
280
|
|
|
@@ -308,7 +299,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
308
299
|
ListAnnotationSinglePoint(service, facade, args).main()
|
|
309
300
|
|
|
310
301
|
|
|
311
|
-
def add_parser(subparsers:
|
|
302
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
312
303
|
subcommand_name = "list_single_point_annotation"
|
|
313
304
|
subcommand_help = "アノテーションZIPから点アノテーションの座標情報を出力します。"
|
|
314
305
|
epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|