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
|
@@ -0,0 +1,343 @@
|
|
|
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.task_status import TaskStatus
|
|
16
|
+
from annofabapi.segmentation import read_binary_image, write_binary_image
|
|
17
|
+
from annofabapi.utils import can_put_annotation
|
|
18
|
+
|
|
19
|
+
import annofabcli
|
|
20
|
+
from annofabcli.common.cli import (
|
|
21
|
+
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
22
|
+
PARALLELISM_CHOICES,
|
|
23
|
+
ArgumentParser,
|
|
24
|
+
CommandLine,
|
|
25
|
+
CommandLineWithConfirm,
|
|
26
|
+
build_annofabapi_resource_and_login,
|
|
27
|
+
)
|
|
28
|
+
from annofabcli.common.facade import AnnofabApiFacade
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def remove_overlap_of_binary_image_array(
|
|
34
|
+
binary_image_array_by_annotation: dict[str, numpy.ndarray], annotation_id_list: list[str]
|
|
35
|
+
) -> dict[str, numpy.ndarray]:
|
|
36
|
+
"""
|
|
37
|
+
塗りつぶし画像の重なりを除去したbool配列をdictで返します。
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
binary_image_array_by_annotation: annotation_idをkeyとし、塗りつぶし画像のbool配列をvalueとするdict
|
|
41
|
+
annotation_id_list: 塗りつぶし画像のannotation_idのlist。背面から前面の順に格納されている
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
重なりを除去した塗りつぶし画像のbool配列が格納されているdict。keyはannotation_id
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
assert set(binary_image_array_by_annotation.keys()) == set(annotation_id_list)
|
|
48
|
+
|
|
49
|
+
whole_2d_array = None # 複数の塗りつぶしアノテーションを1枚に重ね合わせた状態。各要素はannotation_id
|
|
50
|
+
|
|
51
|
+
# 背面から塗りつぶしアノテーションのbool配列を重ねていく
|
|
52
|
+
for annotation_id in annotation_id_list:
|
|
53
|
+
input_binary_image_array = binary_image_array_by_annotation[annotation_id]
|
|
54
|
+
if whole_2d_array is None:
|
|
55
|
+
whole_2d_array = numpy.full(input_binary_image_array.shape, "", dtype=str)
|
|
56
|
+
|
|
57
|
+
whole_2d_array = numpy.where(input_binary_image_array, annotation_id, whole_2d_array)
|
|
58
|
+
|
|
59
|
+
output_binary_image_array_by_annotation = {}
|
|
60
|
+
for annotation_id in annotation_id_list:
|
|
61
|
+
output_binary_image_array: numpy.ndarray = whole_2d_array == annotation_id # type: ignore[assignment]
|
|
62
|
+
output_binary_image_array_by_annotation[annotation_id] = output_binary_image_array
|
|
63
|
+
|
|
64
|
+
return output_binary_image_array_by_annotation
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RemoveSegmentationOverlapMain(CommandLineWithConfirm):
|
|
68
|
+
def __init__(self, annofab_service: annofabapi.Resource, *, project_id: str, all_yes: bool, is_force: bool) -> None:
|
|
69
|
+
self.annofab_service = annofab_service
|
|
70
|
+
self.project_id = project_id
|
|
71
|
+
self.is_force = is_force
|
|
72
|
+
super().__init__(all_yes)
|
|
73
|
+
|
|
74
|
+
def remove_segmentation_overlap_and_save(self, details: list[dict[str, Any]], output_dir: Path) -> list[str]:
|
|
75
|
+
"""
|
|
76
|
+
`getEditorAnnotation` APIで取得した`details`から、塗りつぶし画像の重なりの除去が必要な場合に、
|
|
77
|
+
重なりを除去した塗りつぶし画像を`output_dir`に出力します。
|
|
78
|
+
塗りつぶし画像のファイル名は`${annotation_id}.png`です。
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
details: `getEditorAnnotation` APIで取得した`details`
|
|
82
|
+
output_dir: 塗りつぶし画像の出力先のディレクトリ。
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
重なりの除去が必要な塗りつぶし画像のannotation_idのlist
|
|
86
|
+
"""
|
|
87
|
+
input_binary_image_array_by_annotation = {}
|
|
88
|
+
segmentation_annotation_id_list = []
|
|
89
|
+
|
|
90
|
+
for detail in details:
|
|
91
|
+
if detail["body"]["_type"] != "Outer":
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
segmentation_response = self.annofab_service.wrapper.execute_http_get(detail["body"]["url"], stream=True)
|
|
95
|
+
segmentation_response.raw.decode_content = True
|
|
96
|
+
input_binary_image_array_by_annotation[detail["annotation_id"]] = read_binary_image(segmentation_response.raw)
|
|
97
|
+
segmentation_annotation_id_list.append(detail["annotation_id"])
|
|
98
|
+
|
|
99
|
+
# reversedを使っている理由:
|
|
100
|
+
# `details`には、前面から背面の順にアノテーションが格納されているため、
|
|
101
|
+
output_binary_image_array_by_annotation = remove_overlap_of_binary_image_array(
|
|
102
|
+
input_binary_image_array_by_annotation, list(reversed(segmentation_annotation_id_list))
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
updated_annotation_id_list = []
|
|
106
|
+
for annotation_id, output_binary_image_array in output_binary_image_array_by_annotation.items():
|
|
107
|
+
input_binary_image_array = input_binary_image_array_by_annotation[annotation_id]
|
|
108
|
+
if not numpy.array_equal(input_binary_image_array, output_binary_image_array):
|
|
109
|
+
output_file_path = output_dir / f"{annotation_id}.png"
|
|
110
|
+
write_binary_image(output_binary_image_array, output_file_path)
|
|
111
|
+
updated_annotation_id_list.append(annotation_id)
|
|
112
|
+
|
|
113
|
+
return updated_annotation_id_list
|
|
114
|
+
|
|
115
|
+
def update_segmentation_annotation(self, task_id: str, input_data_id: str, log_message_prefix: str = "") -> bool:
|
|
116
|
+
"""
|
|
117
|
+
塗りつぶしアノテーションの重なりがあれば、`putAnnotation` APIを使用して重なりを除去します。
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
project_id: プロジェクトID
|
|
121
|
+
task_id: タスクID
|
|
122
|
+
annotation_id_list: 更新する塗りつぶし画像のannotation_idのlist
|
|
123
|
+
"""
|
|
124
|
+
old_annotation, _ = self.annofab_service.api.get_editor_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"})
|
|
125
|
+
old_details = old_annotation["details"]
|
|
126
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
127
|
+
temp_dir_path = Path(temp_dir)
|
|
128
|
+
updated_annotation_id_list = self.remove_segmentation_overlap_and_save(old_details, temp_dir_path)
|
|
129
|
+
if len(updated_annotation_id_list) == 0:
|
|
130
|
+
logger.debug(
|
|
131
|
+
f"{log_message_prefix}塗りつぶしアノテーションの重なりはなかったので、スキップします。 :: "
|
|
132
|
+
f"task_id='{task_id}', input_data_id='{input_data_id}'"
|
|
133
|
+
)
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
logger.debug(
|
|
137
|
+
f"{log_message_prefix}{len(updated_annotation_id_list)} 件の塗りつぶしアノテーションを更新します。 :: "
|
|
138
|
+
f"task_id='{task_id}', input_data_id='{input_data_id}', annotation_id_list={updated_annotation_id_list}"
|
|
139
|
+
)
|
|
140
|
+
new_details = []
|
|
141
|
+
for detail in old_details:
|
|
142
|
+
annotation_id = detail["annotation_id"]
|
|
143
|
+
new_detail = copy.deepcopy(detail)
|
|
144
|
+
new_detail["_type"] = "Update"
|
|
145
|
+
if annotation_id in updated_annotation_id_list:
|
|
146
|
+
with (temp_dir_path / f"{annotation_id}.png").open("rb") as f:
|
|
147
|
+
s3_path = self.annofab_service.wrapper.upload_data_to_s3(self.project_id, data=f, content_type="image/png")
|
|
148
|
+
|
|
149
|
+
new_detail["body"]["path"] = s3_path
|
|
150
|
+
|
|
151
|
+
else:
|
|
152
|
+
# 更新しない場合は、`body`をNoneにする
|
|
153
|
+
new_detail["body"] = None
|
|
154
|
+
|
|
155
|
+
new_details.append(new_detail)
|
|
156
|
+
|
|
157
|
+
request_body = {
|
|
158
|
+
"project_id": self.project_id,
|
|
159
|
+
"task_id": task_id,
|
|
160
|
+
"input_data_id": input_data_id,
|
|
161
|
+
"details": new_details,
|
|
162
|
+
"format_version": "2.0.0",
|
|
163
|
+
"updated_datetime": old_annotation["updated_datetime"],
|
|
164
|
+
}
|
|
165
|
+
self.annofab_service.api.put_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"}, request_body=request_body)
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"{log_message_prefix}{len(updated_annotation_id_list)} 件の塗りつぶしアノテーションを更新しました。 :: "
|
|
168
|
+
f"task_id='{task_id}', input_data_id='{input_data_id}'"
|
|
169
|
+
)
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
def update_segmentation_annotation_for_task(self, task_id: str, *, task_index: Optional[int] = None) -> int:
|
|
173
|
+
"""
|
|
174
|
+
1個のタスクに対して、塗りつぶしアノテーションの重なりを除去します。
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
アノテーションを更新した入力データ数(フレーム数)
|
|
178
|
+
"""
|
|
179
|
+
log_message_prefix = f"{task_index + 1} 件目 :: " if task_index is not None else ""
|
|
180
|
+
|
|
181
|
+
task = self.annofab_service.wrapper.get_task_or_none(project_id=self.project_id, task_id=task_id)
|
|
182
|
+
if task is None:
|
|
183
|
+
logger.warning(f"{log_message_prefix}task_id='{task_id}'であるタスクは存在しません。")
|
|
184
|
+
return 0
|
|
185
|
+
|
|
186
|
+
if task["status"] in {TaskStatus.WORKING.value, TaskStatus.COMPLETE.value}:
|
|
187
|
+
logger.debug(
|
|
188
|
+
f"{log_message_prefix}task_id='{task_id}'のタスクの状態は「作業中」または「完了」であるため、"
|
|
189
|
+
f"アノテーションの更新をスキップします。 :: status='{task['status']}'"
|
|
190
|
+
)
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
if not self.confirm_processing(f"task_id='{task_id}'の塗りつぶしアノテーションの重なりを除去しますか?"):
|
|
194
|
+
return 0
|
|
195
|
+
|
|
196
|
+
# 担当者割り当て変更チェック
|
|
197
|
+
changed_operator = False
|
|
198
|
+
original_operator_account_id = task["account_id"]
|
|
199
|
+
if not can_put_annotation(task, self.annofab_service.api.account_id):
|
|
200
|
+
if self.is_force:
|
|
201
|
+
logger.debug(f"{log_message_prefix}task_id='{task_id}' のタスクの担当者を自分自身に変更します。")
|
|
202
|
+
changed_operator = True
|
|
203
|
+
task = self.annofab_service.wrapper.change_task_operator(
|
|
204
|
+
self.project_id,
|
|
205
|
+
task_id,
|
|
206
|
+
self.annofab_service.api.account_id,
|
|
207
|
+
last_updated_datetime=task["updated_datetime"],
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
logger.debug(
|
|
211
|
+
f"{log_message_prefix}task_id='{task_id}' のタスクは、過去に誰かに割り当てられたタスクで、"
|
|
212
|
+
f"現在の担当者が自分自身でないため、アノテーションの更新をスキップします。"
|
|
213
|
+
f"担当者を自分自身に変更してアノテーションを更新する場合は、コマンドライン引数 '--force' を指定してください。"
|
|
214
|
+
)
|
|
215
|
+
return 0
|
|
216
|
+
|
|
217
|
+
success_input_data_count = 0
|
|
218
|
+
for input_data_id in task["input_data_id_list"]:
|
|
219
|
+
try:
|
|
220
|
+
result = self.update_segmentation_annotation(task_id, input_data_id, log_message_prefix=log_message_prefix)
|
|
221
|
+
if result:
|
|
222
|
+
success_input_data_count += 1
|
|
223
|
+
except Exception:
|
|
224
|
+
logger.warning(
|
|
225
|
+
f"{log_message_prefix}task_id='{task_id}', input_data_id='{input_data_id}'のアノテーションの更新に失敗しました。", exc_info=True
|
|
226
|
+
)
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
# 担当者を元に戻す
|
|
230
|
+
if changed_operator:
|
|
231
|
+
logger.debug(
|
|
232
|
+
f"{log_message_prefix}task_id='{task_id}' のタスクの担当者を、元の担当者(account_id='{original_operator_account_id}')に変更します。"
|
|
233
|
+
)
|
|
234
|
+
self.annofab_service.wrapper.change_task_operator(
|
|
235
|
+
self.project_id,
|
|
236
|
+
task_id,
|
|
237
|
+
original_operator_account_id,
|
|
238
|
+
last_updated_datetime=task["updated_datetime"],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return success_input_data_count
|
|
242
|
+
|
|
243
|
+
def update_segmentation_annotation_for_task_wrapper(self, tpl: tuple[int, str]) -> int:
|
|
244
|
+
try:
|
|
245
|
+
task_index, task_id = tpl
|
|
246
|
+
return self.update_segmentation_annotation_for_task(task_id, task_index=task_index)
|
|
247
|
+
except Exception:
|
|
248
|
+
logger.warning(f"task_id='{task_id}' のアノテーションの更新に失敗しました。", exc_info=True)
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
def main(
|
|
252
|
+
self,
|
|
253
|
+
task_ids: Collection[str],
|
|
254
|
+
parallelism: Optional[int] = None,
|
|
255
|
+
) -> None:
|
|
256
|
+
logger.info(f"{len(task_ids)} 件のタスクの塗りつぶしアノテーションの重なりを除去します。")
|
|
257
|
+
success_input_data_count = 0
|
|
258
|
+
if parallelism is not None:
|
|
259
|
+
with multiprocessing.Pool(parallelism) as pool:
|
|
260
|
+
result_count_list = pool.map(self.update_segmentation_annotation_for_task_wrapper, enumerate(task_ids))
|
|
261
|
+
success_input_data_count = sum(result_count_list)
|
|
262
|
+
|
|
263
|
+
else:
|
|
264
|
+
for task_index, task_id in enumerate(task_ids):
|
|
265
|
+
try:
|
|
266
|
+
result = self.update_segmentation_annotation_for_task(task_id, task_index=task_index)
|
|
267
|
+
success_input_data_count += result
|
|
268
|
+
except Exception:
|
|
269
|
+
logger.warning(f"task_id='{task_id}' のアノテーションの更新に失敗しました。", exc_info=True)
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
logger.info(f"{len(task_ids)} 件のタスクに含まれる入力データ {success_input_data_count} 件の塗りつぶしアノテーションを更新しました。")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class RemoveSegmentationOverlap(CommandLine):
|
|
276
|
+
COMMON_MESSAGE = "annofabcli annotation remove_segmentation_overlap: error:"
|
|
277
|
+
|
|
278
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
279
|
+
if args.parallelism is not None and not args.yes:
|
|
280
|
+
print( # noqa: T201
|
|
281
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
282
|
+
file=sys.stderr,
|
|
283
|
+
)
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
def main(self) -> None:
|
|
289
|
+
args = self.args
|
|
290
|
+
if not self.validate(args):
|
|
291
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
292
|
+
|
|
293
|
+
project_id = args.project_id
|
|
294
|
+
task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id)
|
|
295
|
+
|
|
296
|
+
main_obj = RemoveSegmentationOverlapMain(
|
|
297
|
+
self.service,
|
|
298
|
+
project_id=project_id,
|
|
299
|
+
all_yes=self.all_yes,
|
|
300
|
+
is_force=args.force,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
main_obj.main(task_id_list, parallelism=args.parallelism)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def main(args: argparse.Namespace) -> None:
|
|
307
|
+
service = build_annofabapi_resource_and_login(args)
|
|
308
|
+
facade = AnnofabApiFacade(service)
|
|
309
|
+
RemoveSegmentationOverlap(service, facade, args).main()
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
313
|
+
argument_parser = ArgumentParser(parser)
|
|
314
|
+
argument_parser.add_project_id()
|
|
315
|
+
argument_parser.add_task_id()
|
|
316
|
+
|
|
317
|
+
parser.add_argument(
|
|
318
|
+
"--force",
|
|
319
|
+
action="store_true",
|
|
320
|
+
help="過去に担当者を割り当てられていて、かつ現在の担当者が自分自身でない場合、タスクの担当者を一時的に自分自身に変更してからアノテーションをコピーします。",
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
parser.add_argument(
|
|
324
|
+
"--parallelism",
|
|
325
|
+
type=int,
|
|
326
|
+
choices=PARALLELISM_CHOICES,
|
|
327
|
+
help="並列度。指定しない場合は、逐次的に処理します。指定した場合は、``--yes`` も指定してください。",
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
parser.set_defaults(subcommand_func=main)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
334
|
+
subcommand_name = "remove_segmentation_overlap"
|
|
335
|
+
subcommand_help = "塗りつぶしアノテーションの重なりを除去します。"
|
|
336
|
+
description = (
|
|
337
|
+
"塗りつぶしアノテーションの重なりを除去します。"
|
|
338
|
+
"Annofabでインスタンスセグメンテーションは重ねることができてしまいます。"
|
|
339
|
+
"この重なりをなくしたいときに有用です。"
|
|
340
|
+
)
|
|
341
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
|
|
342
|
+
parse_args(parser)
|
|
343
|
+
return parser
|
|
@@ -10,6 +10,8 @@ import annofabcli.annotation.dump_annotation
|
|
|
10
10
|
import annofabcli.annotation.import_annotation
|
|
11
11
|
import annofabcli.annotation.list_annotation
|
|
12
12
|
import annofabcli.annotation.list_annotation_count
|
|
13
|
+
import annofabcli.annotation.merge_segmentation
|
|
14
|
+
import annofabcli.annotation.remove_segmentation_overlap
|
|
13
15
|
import annofabcli.annotation.restore_annotation
|
|
14
16
|
import annofabcli.common.cli
|
|
15
17
|
|
|
@@ -27,6 +29,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
27
29
|
annofabcli.annotation.import_annotation.add_parser(subparsers)
|
|
28
30
|
annofabcli.annotation.list_annotation.add_parser(subparsers)
|
|
29
31
|
annofabcli.annotation.list_annotation_count.add_parser(subparsers)
|
|
32
|
+
annofabcli.annotation.merge_segmentation.add_parser(subparsers)
|
|
33
|
+
annofabcli.annotation.remove_segmentation_overlap.add_parser(subparsers)
|
|
30
34
|
annofabcli.annotation.restore_annotation.add_parser(subparsers)
|
|
31
35
|
|
|
32
36
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import logging
|
|
5
|
+
import sys
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import Any, Optional
|
|
7
8
|
|
|
@@ -10,6 +11,7 @@ import annofabapi
|
|
|
10
11
|
import annofabcli
|
|
11
12
|
import annofabcli.common.cli
|
|
12
13
|
from annofabcli.common.cli import (
|
|
14
|
+
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
13
15
|
ArgumentParser,
|
|
14
16
|
CommandLine,
|
|
15
17
|
CommandLineWithConfirm,
|
|
@@ -100,6 +102,9 @@ class PutLabelColor(CommandLine):
|
|
|
100
102
|
args = self.args
|
|
101
103
|
label_color = get_json_from_args(args.json)
|
|
102
104
|
|
|
105
|
+
if not isinstance(label_color, dict):
|
|
106
|
+
print("annofabcli annotation_specs put_label_color: error: JSON形式が不正です。オブジェクトを指定してください。", file=sys.stderr) # noqa: T201
|
|
107
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
103
108
|
main_obj = PuttingLabelColorMain(service=self.service, project_id=args.project_id, all_yes=args.yes)
|
|
104
109
|
main_obj.main(label_color, comment=args.comment)
|
|
105
110
|
|
|
@@ -225,6 +225,9 @@ class DeleteComment(CommandLine):
|
|
|
225
225
|
super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
|
|
226
226
|
|
|
227
227
|
dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
|
|
228
|
+
if not isinstance(dict_comments, dict):
|
|
229
|
+
print(f"{self.COMMON_MESSAGE} argument --json: JSON形式が不正です。オブジェクトを指定してください。", file=sys.stderr) # noqa: T201
|
|
230
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
228
231
|
main_obj = DeleteCommentMain(self.service, project_id=args.project_id, all_yes=self.all_yes)
|
|
229
232
|
main_obj.delete_comments_for_task_list(
|
|
230
233
|
comment_ids_for_task_list=dict_comments,
|
|
@@ -44,6 +44,9 @@ class PutInspectionComment(CommandLine):
|
|
|
44
44
|
super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
|
|
45
45
|
|
|
46
46
|
dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
|
|
47
|
+
if not isinstance(dict_comments, dict):
|
|
48
|
+
print(f"{self.COMMON_MESSAGE} argument --json: JSON形式が不正です。オブジェクトを指定してください。", file=sys.stderr) # noqa: T201
|
|
49
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
47
50
|
comments_for_task_list = convert_cli_comments(dict_comments, comment_type=CommentType.INSPECTION)
|
|
48
51
|
main_obj = PutCommentMain(self.service, project_id=args.project_id, comment_type=CommentType.INSPECTION, all_yes=self.all_yes)
|
|
49
52
|
main_obj.add_comments_for_task_list(
|
|
@@ -42,6 +42,9 @@ class PutInspectionComment(CommandLine):
|
|
|
42
42
|
super().validate_project(args.project_id)
|
|
43
43
|
|
|
44
44
|
dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
|
|
45
|
+
if not isinstance(dict_comments, dict):
|
|
46
|
+
print(f"{self.COMMON_MESSAGE} argument --json: JSON形式が不正です。オブジェクトを指定してください。", file=sys.stderr) # noqa: T201
|
|
47
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
45
48
|
comments_for_task_list = convert_cli_comments(
|
|
46
49
|
dict_comments,
|
|
47
50
|
comment_type=CommentType.ONHOLD,
|
|
@@ -177,7 +177,10 @@ class ChangeInputDataName(CommandLine):
|
|
|
177
177
|
changed_input_data_list = create_changed_input_data_list_from_csv(args.csv)
|
|
178
178
|
|
|
179
179
|
elif args.json is not None:
|
|
180
|
-
input_data_dict_list
|
|
180
|
+
input_data_dict_list = get_json_from_args(args.json)
|
|
181
|
+
if not isinstance(input_data_dict_list, list):
|
|
182
|
+
print("annofabcli input_data change_name: error: JSON形式が不正です。オブジェクトの配列を指定してください。", file=sys.stderr) # noqa: T201
|
|
183
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
181
184
|
changed_input_data_list = create_changed_input_data_list_from_dict(input_data_dict_list)
|
|
182
185
|
else:
|
|
183
186
|
raise RuntimeError("'--csv'または'--json'のいずれかを指定してください。")
|
|
@@ -356,6 +356,10 @@ class PutInputData(CommandLine):
|
|
|
356
356
|
|
|
357
357
|
elif args.json is not None:
|
|
358
358
|
input_data_dict_list = get_json_from_args(args.json)
|
|
359
|
+
if not isinstance(input_data_dict_list, list):
|
|
360
|
+
print(f"{self.COMMON_MESSAGE} argument --json: JSON形式が不正です。オブジェクトの配列を指定してください。", file=sys.stderr) # noqa: T201
|
|
361
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
362
|
+
|
|
359
363
|
input_data_list = self.get_input_data_list_from_dict(input_data_dict_list, allow_duplicated_input_data=args.allow_duplicated_input_data)
|
|
360
364
|
self.put_input_data_list(project_id, input_data_list=input_data_list, overwrite=args.overwrite, parallelism=args.parallelism)
|
|
361
365
|
|