annofabcli 1.97.0__py3-none-any.whl → 1.99.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/__version__.py +1 -1
- annofabcli/annotation/merge_segmentation.py +390 -0
- annofabcli/annotation/remove_segmentation_overlap.py +343 -0
- annofabcli/annotation/subcommand_annotation.py +4 -0
- annofabcli/annotation_specs/put_label_color.py +5 -0
- annofabcli/comment/delete_comment.py +3 -0
- annofabcli/comment/put_inspection_comment.py +3 -0
- annofabcli/comment/put_onhold_comment.py +3 -0
- annofabcli/input_data/change_input_data_name.py +4 -1
- annofabcli/input_data/put_input_data.py +4 -0
- annofabcli/statistics/list_annotation_attribute_filled_count.py +684 -0
- annofabcli/statistics/list_annotation_count.py +80 -37
- annofabcli/statistics/list_annotation_duration.py +18 -18
- annofabcli/statistics/subcommand_statistics.py +3 -0
- annofabcli/statistics/visualization/dataframe/task.py +26 -7
- annofabcli/task/list_tasks_added_task_history.py +229 -161
- annofabcli/task/put_tasks.py +9 -4
- {annofabcli-1.97.0.dist-info → annofabcli-1.99.0.dist-info}/METADATA +2 -2
- {annofabcli-1.97.0.dist-info → annofabcli-1.99.0.dist-info}/RECORD +22 -19
- {annofabcli-1.97.0.dist-info → annofabcli-1.99.0.dist-info}/LICENSE +0 -0
- {annofabcli-1.97.0.dist-info → annofabcli-1.99.0.dist-info}/WHEEL +0 -0
- {annofabcli-1.97.0.dist-info → annofabcli-1.99.0.dist-info}/entry_points.txt +0 -0
annofabcli/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.99.0" # `poetry-dynamic-versioning`を使ってGitHubのバージョンタグを取得している。変更不要
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import copy
|
|
5
|
+
import logging
|
|
6
|
+
import multiprocessing
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
from collections.abc import Collection
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
import annofabapi
|
|
14
|
+
import numpy
|
|
15
|
+
from annofabapi.pydantic_models.default_annotation_type import DefaultAnnotationType
|
|
16
|
+
from annofabapi.pydantic_models.task_status import TaskStatus
|
|
17
|
+
from annofabapi.segmentation import read_binary_image, write_binary_image
|
|
18
|
+
from annofabapi.util.annotation_specs import AnnotationSpecsAccessor
|
|
19
|
+
from annofabapi.utils import can_put_annotation
|
|
20
|
+
|
|
21
|
+
import annofabcli
|
|
22
|
+
from annofabcli.common.cli import (
|
|
23
|
+
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
24
|
+
PARALLELISM_CHOICES,
|
|
25
|
+
ArgumentParser,
|
|
26
|
+
CommandLine,
|
|
27
|
+
CommandLineWithConfirm,
|
|
28
|
+
build_annofabapi_resource_and_login,
|
|
29
|
+
)
|
|
30
|
+
from annofabcli.common.facade import AnnofabApiFacade
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def merge_binary_image_array(binary_image_array_list: list[numpy.ndarray]) -> numpy.ndarray:
|
|
36
|
+
"""
|
|
37
|
+
塗りつぶし画像を読み込んだboolのndarrayのlistから、1個のndarrayを作成します。
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
binary_image_array_list: 塗りつぶし画像を読み込んだboolのndarrayのlist
|
|
41
|
+
"""
|
|
42
|
+
if len(binary_image_array_list) == 0:
|
|
43
|
+
raise ValueError("'binary_image_array_list' must not be empty.")
|
|
44
|
+
|
|
45
|
+
merged_array = numpy.zeros_like(binary_image_array_list[0], dtype=bool)
|
|
46
|
+
for binary_image_array in binary_image_array_list:
|
|
47
|
+
merged_array = numpy.logical_or(merged_array, binary_image_array)
|
|
48
|
+
return merged_array
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MergeSegmentationMain(CommandLineWithConfirm):
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
annofab_service: annofabapi.Resource,
|
|
55
|
+
*,
|
|
56
|
+
project_id: str,
|
|
57
|
+
label_ids: Collection[str],
|
|
58
|
+
label_names: Collection[str],
|
|
59
|
+
all_yes: bool,
|
|
60
|
+
is_force: bool,
|
|
61
|
+
) -> None:
|
|
62
|
+
self.annofab_service = annofab_service
|
|
63
|
+
self.project_id = project_id
|
|
64
|
+
self.is_force = is_force
|
|
65
|
+
self.label_ids = label_ids
|
|
66
|
+
self.label_names = label_names
|
|
67
|
+
|
|
68
|
+
super().__init__(all_yes)
|
|
69
|
+
|
|
70
|
+
def write_merged_segmentation_file(self, details: list[dict[str, Any]], output_dir: Path) -> tuple[list[str], list[str]]:
|
|
71
|
+
"""
|
|
72
|
+
`getEditorAnnotation` APIで取得した`details`から、指定したラベルに対応する塗りつぶしアノテーションを1個にまとめて、
|
|
73
|
+
`output_dir`に出力します。
|
|
74
|
+
塗りつぶし画像のファイル名は`${annotation_id}.png`です。
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
details: `getEditorAnnotation` APIで取得した`details`
|
|
78
|
+
label_ids: 更新対象のアノテーションに対応するラベルIDのcollection
|
|
79
|
+
output_dir: 塗りつぶし画像の出力先のディレクトリ。
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
tuple[0]: 更新対象の塗りつぶしアノテーションのannotation_idのlist(最前面のアノテーション)
|
|
83
|
+
tuple[1]: 削除対象の塗りつぶしアノテーションのannotation_idのlist
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def func(label_id: str) -> tuple[Optional[str], list[str]]:
|
|
87
|
+
updated_annotation_id = None
|
|
88
|
+
deleted_annotation_id_list = []
|
|
89
|
+
binary_image_array_list = []
|
|
90
|
+
segmentation_details = [e for e in details if e["label_id"] == label_id]
|
|
91
|
+
if len(segmentation_details) <= 1:
|
|
92
|
+
return None, []
|
|
93
|
+
|
|
94
|
+
updated_annotation_id = segmentation_details[0]["annotation_id"]
|
|
95
|
+
deleted_annotation_id_list = [e["annotation_id"] for e in segmentation_details[1:]]
|
|
96
|
+
for detail in segmentation_details:
|
|
97
|
+
segmentation_response = self.annofab_service.wrapper.execute_http_get(detail["body"]["url"], stream=True)
|
|
98
|
+
segmentation_response.raw.decode_content = True
|
|
99
|
+
binary_image_array_list.append(read_binary_image(segmentation_response.raw))
|
|
100
|
+
|
|
101
|
+
merged_binary_image_array = merge_binary_image_array(binary_image_array_list)
|
|
102
|
+
output_file_path = output_dir / f"{updated_annotation_id}.png"
|
|
103
|
+
write_binary_image(merged_binary_image_array, output_file_path)
|
|
104
|
+
return updated_annotation_id, deleted_annotation_id_list
|
|
105
|
+
|
|
106
|
+
updated_annotation_id_list = []
|
|
107
|
+
deleted_annotation_id_list = []
|
|
108
|
+
for label_id in self.label_ids:
|
|
109
|
+
updated_annotation_id, sub_deleted_annotation_id_list = func(label_id)
|
|
110
|
+
if updated_annotation_id is not None:
|
|
111
|
+
updated_annotation_id_list.append(updated_annotation_id)
|
|
112
|
+
|
|
113
|
+
deleted_annotation_id_list.extend(sub_deleted_annotation_id_list)
|
|
114
|
+
|
|
115
|
+
return updated_annotation_id_list, deleted_annotation_id_list
|
|
116
|
+
|
|
117
|
+
def merge_segmentation_annotation(self, task_id: str, input_data_id: str, log_message_prefix: str = "") -> bool:
|
|
118
|
+
"""
|
|
119
|
+
label_idに対応する複数の塗りつぶしアノテーションを1つにまとめます。
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
project_id: プロジェクトID
|
|
123
|
+
task_id: タスクID
|
|
124
|
+
annotation_id_list: 更新する塗りつぶし画像のannotation_idのlist
|
|
125
|
+
"""
|
|
126
|
+
old_annotation, _ = self.annofab_service.api.get_editor_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"})
|
|
127
|
+
old_details = old_annotation["details"]
|
|
128
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
129
|
+
temp_dir_path = Path(temp_dir)
|
|
130
|
+
updated_annotation_id_list, deleted_annotation_id_list = self.write_merged_segmentation_file(old_details, temp_dir_path)
|
|
131
|
+
if len(updated_annotation_id_list) == 0:
|
|
132
|
+
assert len(deleted_annotation_id_list) == 0
|
|
133
|
+
logger.debug(
|
|
134
|
+
f"{log_message_prefix}更新対象の塗りつぶしアノテーションはなかった"
|
|
135
|
+
"(1個のラベルに塗りつぶしアノテーションは複数なかった)ので、スキップします。 :: "
|
|
136
|
+
f"task_id='{task_id}', input_data_id='{input_data_id}'"
|
|
137
|
+
)
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
logger.debug(
|
|
141
|
+
f"{log_message_prefix}{len(updated_annotation_id_list)} 件の塗りつぶしアノテーションを更新して、"
|
|
142
|
+
f"{len(deleted_annotation_id_list)} 件の塗りつぶしアノテーションを削除します。 :: "
|
|
143
|
+
f"task_id='{task_id}', input_data_id='{input_data_id}', "
|
|
144
|
+
f"更新対象のannotation_id_list={updated_annotation_id_list}, "
|
|
145
|
+
f"削除対象のannotation_id_list={deleted_annotation_id_list}"
|
|
146
|
+
)
|
|
147
|
+
new_details = []
|
|
148
|
+
for detail in old_details:
|
|
149
|
+
annotation_id = detail["annotation_id"]
|
|
150
|
+
if annotation_id in deleted_annotation_id_list:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
new_detail = copy.deepcopy(detail)
|
|
154
|
+
new_detail["_type"] = "Update"
|
|
155
|
+
if annotation_id in updated_annotation_id_list:
|
|
156
|
+
with (temp_dir_path / f"{annotation_id}.png").open("rb") as f:
|
|
157
|
+
s3_path = self.annofab_service.wrapper.upload_data_to_s3(self.project_id, data=f, content_type="image/png")
|
|
158
|
+
|
|
159
|
+
new_detail["body"]["path"] = s3_path
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
# 更新しない場合は、`body`をNoneにする
|
|
163
|
+
new_detail["body"] = None
|
|
164
|
+
|
|
165
|
+
new_details.append(new_detail)
|
|
166
|
+
|
|
167
|
+
request_body = {
|
|
168
|
+
"project_id": self.project_id,
|
|
169
|
+
"task_id": task_id,
|
|
170
|
+
"input_data_id": input_data_id,
|
|
171
|
+
"details": new_details,
|
|
172
|
+
"format_version": "2.0.0",
|
|
173
|
+
"updated_datetime": old_annotation["updated_datetime"],
|
|
174
|
+
}
|
|
175
|
+
self.annofab_service.api.put_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"}, request_body=request_body)
|
|
176
|
+
logger.debug(
|
|
177
|
+
f"{log_message_prefix}{len(updated_annotation_id_list)} 件の塗りつぶしアノテーションを更新して、"
|
|
178
|
+
f"{len(deleted_annotation_id_list)} 件の塗りつぶしアノテーションを削除しました。 :: "
|
|
179
|
+
f"task_id='{task_id}', input_data_id='{input_data_id}'"
|
|
180
|
+
)
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
def merge_segmentation_annotation_for_task(self, task_id: str, *, task_index: Optional[int] = None) -> int:
|
|
184
|
+
"""
|
|
185
|
+
1個のタスクに対して、label_idに対応する複数の塗りつぶしアノテーションを1つにまとめます。
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
アノテーションを更新した入力データ数(フレーム数)
|
|
189
|
+
"""
|
|
190
|
+
log_message_prefix = f"{task_index + 1} 件目 :: " if task_index is not None else ""
|
|
191
|
+
|
|
192
|
+
task = self.annofab_service.wrapper.get_task_or_none(project_id=self.project_id, task_id=task_id)
|
|
193
|
+
if task is None:
|
|
194
|
+
logger.warning(f"{log_message_prefix}task_id='{task_id}'であるタスクは存在しません。")
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
if task["status"] in {TaskStatus.WORKING.value, TaskStatus.COMPLETE.value}:
|
|
198
|
+
logger.debug(
|
|
199
|
+
f"{log_message_prefix}task_id='{task_id}'のタスクの状態は「作業中」または「完了」であるため、"
|
|
200
|
+
f"アノテーションの更新をスキップします。 :: status='{task['status']}'"
|
|
201
|
+
)
|
|
202
|
+
return 0
|
|
203
|
+
|
|
204
|
+
if not self.confirm_processing(
|
|
205
|
+
f"task_id='{task_id}'の次のラベル名に対応する複数の塗りつぶしアノテーションを1つにまとめますか? :: {self.label_names}"
|
|
206
|
+
):
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
# 担当者割り当て変更チェック
|
|
210
|
+
changed_operator = False
|
|
211
|
+
original_operator_account_id = task["account_id"]
|
|
212
|
+
if not can_put_annotation(task, self.annofab_service.api.account_id):
|
|
213
|
+
if self.is_force:
|
|
214
|
+
logger.debug(f"{log_message_prefix}task_id='{task_id}' のタスクの担当者を自分自身に変更します。")
|
|
215
|
+
changed_operator = True
|
|
216
|
+
task = self.annofab_service.wrapper.change_task_operator(
|
|
217
|
+
self.project_id,
|
|
218
|
+
task_id,
|
|
219
|
+
self.annofab_service.api.account_id,
|
|
220
|
+
last_updated_datetime=task["updated_datetime"],
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
logger.debug(
|
|
224
|
+
f"{log_message_prefix}task_id='{task_id}' のタスクは、過去に誰かに割り当てられたタスクで、"
|
|
225
|
+
f"現在の担当者が自分自身でないため、アノテーションの更新をスキップします。"
|
|
226
|
+
f"担当者を自分自身に変更してアノテーションを更新する場合は、コマンドライン引数 '--force' を指定してください。"
|
|
227
|
+
)
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
success_input_data_count = 0
|
|
231
|
+
for input_data_id in task["input_data_id_list"]:
|
|
232
|
+
try:
|
|
233
|
+
result = self.merge_segmentation_annotation(task_id, input_data_id, log_message_prefix=log_message_prefix)
|
|
234
|
+
if result:
|
|
235
|
+
success_input_data_count += 1
|
|
236
|
+
except Exception:
|
|
237
|
+
logger.warning(
|
|
238
|
+
f"{log_message_prefix}task_id='{task_id}', input_data_id='{input_data_id}'のアノテーションの更新に失敗しました。", exc_info=True
|
|
239
|
+
)
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
# 担当者を元に戻す
|
|
243
|
+
if changed_operator:
|
|
244
|
+
logger.debug(
|
|
245
|
+
f"{log_message_prefix}task_id='{task_id}' のタスクの担当者を、元の担当者(account_id='{original_operator_account_id}')に変更します。"
|
|
246
|
+
)
|
|
247
|
+
self.annofab_service.wrapper.change_task_operator(
|
|
248
|
+
self.project_id,
|
|
249
|
+
task_id,
|
|
250
|
+
original_operator_account_id,
|
|
251
|
+
last_updated_datetime=task["updated_datetime"],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return success_input_data_count
|
|
255
|
+
|
|
256
|
+
def update_segmentation_annotation_for_task_wrapper(self, tpl: tuple[int, str]) -> int:
|
|
257
|
+
try:
|
|
258
|
+
task_index, task_id = tpl
|
|
259
|
+
return self.merge_segmentation_annotation_for_task(task_id, task_index=task_index)
|
|
260
|
+
except Exception:
|
|
261
|
+
logger.warning(f"task_id='{task_id}' のアノテーションの更新に失敗しました。", exc_info=True)
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
def main(
|
|
265
|
+
self,
|
|
266
|
+
task_ids: Collection[str],
|
|
267
|
+
parallelism: Optional[int] = None,
|
|
268
|
+
) -> None:
|
|
269
|
+
logger.info(f"{len(task_ids)} 件のタスクに対して、複数の塗りつぶしアノテーションを1個にまとめます。")
|
|
270
|
+
success_input_data_count = 0
|
|
271
|
+
if parallelism is not None:
|
|
272
|
+
with multiprocessing.Pool(parallelism) as pool:
|
|
273
|
+
result_count_list = pool.map(self.update_segmentation_annotation_for_task_wrapper, enumerate(task_ids))
|
|
274
|
+
success_input_data_count = sum(result_count_list)
|
|
275
|
+
|
|
276
|
+
else:
|
|
277
|
+
for task_index, task_id in enumerate(task_ids):
|
|
278
|
+
try:
|
|
279
|
+
result = self.merge_segmentation_annotation_for_task(task_id, task_index=task_index)
|
|
280
|
+
success_input_data_count += result
|
|
281
|
+
except Exception:
|
|
282
|
+
logger.warning(f"task_id='{task_id}' のアノテーションの更新に失敗しました。", exc_info=True)
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
logger.info(f"{len(task_ids)} 件のタスクに含まれる入力データ {success_input_data_count} 件の塗りつぶしアノテーションを更新しました。")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class MergeSegmentation(CommandLine):
|
|
289
|
+
COMMON_MESSAGE = "annofabcli annotation merge_segmentation: error:"
|
|
290
|
+
|
|
291
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
292
|
+
if args.parallelism is not None and not args.yes:
|
|
293
|
+
print( # noqa: T201
|
|
294
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
295
|
+
file=sys.stderr,
|
|
296
|
+
)
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
def main(self) -> None:
|
|
302
|
+
args = self.args
|
|
303
|
+
if not self.validate(args):
|
|
304
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
305
|
+
|
|
306
|
+
project_id = args.project_id
|
|
307
|
+
task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id)
|
|
308
|
+
label_name_list = annofabcli.common.cli.get_list_from_args(args.label_name)
|
|
309
|
+
|
|
310
|
+
annotation_specs, _ = self.service.api.get_annotation_specs(project_id, query_params={"v": "3"})
|
|
311
|
+
accessor = AnnotationSpecsAccessor(annotation_specs)
|
|
312
|
+
label_id_list = []
|
|
313
|
+
invalid_label_name_list = []
|
|
314
|
+
for label_name in label_name_list:
|
|
315
|
+
try:
|
|
316
|
+
label = accessor.get_label(label_name=label_name)
|
|
317
|
+
if label["annotation_type"] not in {DefaultAnnotationType.SEGMENTATION.value, DefaultAnnotationType.SEGMENTATION_V2.value}:
|
|
318
|
+
invalid_label_name_list.append(label_name)
|
|
319
|
+
|
|
320
|
+
except ValueError:
|
|
321
|
+
invalid_label_name_list.append(label_name)
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
label_id_list.append(label["label_id"])
|
|
325
|
+
|
|
326
|
+
if len(invalid_label_name_list) > 0:
|
|
327
|
+
print( # noqa: T201
|
|
328
|
+
f"{self.COMMON_MESSAGE} --label_name: 次のラベル名(英語)はアノテーション仕様に存在しないか、"
|
|
329
|
+
f"アノテーションの種類が「塗りつぶし」ではありません。 :: {invalid_label_name_list}",
|
|
330
|
+
file=sys.stderr,
|
|
331
|
+
)
|
|
332
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
333
|
+
|
|
334
|
+
main_obj = MergeSegmentationMain(
|
|
335
|
+
self.service,
|
|
336
|
+
project_id=project_id,
|
|
337
|
+
label_ids=label_id_list,
|
|
338
|
+
label_names=label_name_list,
|
|
339
|
+
all_yes=self.all_yes,
|
|
340
|
+
is_force=args.force,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
main_obj.main(task_id_list, parallelism=args.parallelism)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main(args: argparse.Namespace) -> None:
|
|
347
|
+
service = build_annofabapi_resource_and_login(args)
|
|
348
|
+
facade = AnnofabApiFacade(service)
|
|
349
|
+
MergeSegmentation(service, facade, args).main()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
353
|
+
argument_parser = ArgumentParser(parser)
|
|
354
|
+
argument_parser.add_project_id()
|
|
355
|
+
argument_parser.add_task_id()
|
|
356
|
+
|
|
357
|
+
parser.add_argument(
|
|
358
|
+
"--label_name",
|
|
359
|
+
type=str,
|
|
360
|
+
nargs="+",
|
|
361
|
+
required=True,
|
|
362
|
+
help="変更対象のアノテーションのラベル名(英語)を指定します。",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
parser.add_argument(
|
|
366
|
+
"--force",
|
|
367
|
+
action="store_true",
|
|
368
|
+
help="過去に担当者を割り当てられていて、かつ現在の担当者が自分自身でない場合、タスクの担当者を一時的に自分自身に変更してからアノテーションをコピーします。",
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
parser.add_argument(
|
|
372
|
+
"--parallelism",
|
|
373
|
+
type=int,
|
|
374
|
+
choices=PARALLELISM_CHOICES,
|
|
375
|
+
help="並列度。指定しない場合は、逐次的に処理します。指定した場合は、``--yes`` も指定してください。",
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
parser.set_defaults(subcommand_func=main)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
382
|
+
subcommand_name = "merge_segmentation"
|
|
383
|
+
subcommand_help = "複数の塗りつぶしアノテーションを1つにまとめます。"
|
|
384
|
+
description = (
|
|
385
|
+
"複数の塗りつぶしアノテーションを1つにまとめます。"
|
|
386
|
+
"ラベルの種類を「塗りつぶし(インスタンスセグメンテーション)」から「塗りつぶしv2(セマンティックセグメンテーション)」に変更する場合などに有用です。"
|
|
387
|
+
)
|
|
388
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
|
|
389
|
+
parse_args(parser)
|
|
390
|
+
return parser
|