annofabcli 1.106.7__py3-none-any.whl → 1.107.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 +2 -0
- annofabcli/annotation/annotation_query.py +2 -2
- annofabcli/annotation/change_annotation_attributes.py +35 -30
- annofabcli/annotation/download_annotation_zip.py +2 -2
- annofabcli/annotation/import_annotation.py +37 -15
- annofabcli/annotation/restore_annotation.py +1 -1
- annofabcli/annotation_zip/__init__.py +1 -0
- annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +324 -0
- annofabcli/annotation_zip/subcommand_annotation_zip.py +35 -0
- annofabcli/comment/delete_comment.py +2 -2
- annofabcli/comment/put_inspection_comment.py +1 -1
- annofabcli/comment/put_inspection_comment_simply.py +1 -1
- annofabcli/comment/put_onhold_comment.py +1 -1
- annofabcli/comment/put_onhold_comment_simply.py +1 -1
- annofabcli/common/annofab/annotation_zip.py +37 -0
- annofabcli/input_data/put_input_data.py +11 -8
- annofabcli/statistics/list_annotation_area.py +3 -8
- annofabcli/task/copy_tasks.py +1 -1
- {annofabcli-1.106.7.dist-info → annofabcli-1.107.0.dist-info}/METADATA +1 -2
- {annofabcli-1.106.7.dist-info → annofabcli-1.107.0.dist-info}/RECORD +23 -19
- {annofabcli-1.106.7.dist-info → annofabcli-1.107.0.dist-info}/WHEEL +0 -0
- {annofabcli-1.106.7.dist-info → annofabcli-1.107.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.106.7.dist-info → annofabcli-1.107.0.dist-info}/licenses/LICENSE +0 -0
annofabcli/__main__.py
CHANGED
|
@@ -10,6 +10,7 @@ import pandas
|
|
|
10
10
|
|
|
11
11
|
import annofabcli.annotation.subcommand_annotation
|
|
12
12
|
import annofabcli.annotation_specs.subcommand_annotation_specs
|
|
13
|
+
import annofabcli.annotation_zip.subcommand_annotation_zip
|
|
13
14
|
import annofabcli.comment.subcommand_comment
|
|
14
15
|
import annofabcli.common.cli
|
|
15
16
|
import annofabcli.experimental.subcommand_experimental
|
|
@@ -107,6 +108,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
107
108
|
|
|
108
109
|
annofabcli.annotation.subcommand_annotation.add_parser(subparsers)
|
|
109
110
|
annofabcli.annotation_specs.subcommand_annotation_specs.add_parser(subparsers)
|
|
111
|
+
annofabcli.annotation_zip.subcommand_annotation_zip.add_parser(subparsers)
|
|
110
112
|
annofabcli.comment.subcommand_comment.add_parser(subparsers)
|
|
111
113
|
annofabcli.input_data.subcommand_input_data.add_parser(subparsers)
|
|
112
114
|
annofabcli.instruction.subcommand_instruction.add_parser(subparsers)
|
|
@@ -270,7 +270,7 @@ class AnnotationQueryForCLI(DataClassJsonMixin):
|
|
|
270
270
|
|
|
271
271
|
def to_query_for_api(self, annotation_specs: dict[str, Any]) -> AnnotationQueryForAPI:
|
|
272
272
|
"""
|
|
273
|
-
WebAPIのquery_params( https://annofab.com/docs/api/#
|
|
273
|
+
WebAPIのquery_params( https://annofab.com/docs/api/#tag/x-data-types/AnnotationQuery )に渡すdictに変換する。
|
|
274
274
|
|
|
275
275
|
Args:
|
|
276
276
|
annotation_specs: アノテーション仕様(V2,V3版)
|
|
@@ -306,7 +306,7 @@ class AnnotationQueryForCLI(DataClassJsonMixin):
|
|
|
306
306
|
class AnnotationQueryForAPI(DataClassJsonMixin):
|
|
307
307
|
"""
|
|
308
308
|
WebAPIでアノテーションを絞り込むためのクエリ。
|
|
309
|
-
https://annofab.com/docs/api/#
|
|
309
|
+
https://annofab.com/docs/api/#tag/x-data-types/AnnotationQuery に対応しています。
|
|
310
310
|
"""
|
|
311
311
|
|
|
312
312
|
label_id: Optional[str] = None
|
|
@@ -46,7 +46,7 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
46
46
|
service: annofabapi.Resource,
|
|
47
47
|
*,
|
|
48
48
|
project_id: str,
|
|
49
|
-
|
|
49
|
+
include_completed: bool,
|
|
50
50
|
all_yes: bool,
|
|
51
51
|
) -> None:
|
|
52
52
|
self.service = service
|
|
@@ -54,7 +54,7 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
54
54
|
CommandLineWithConfirm.__init__(self, all_yes)
|
|
55
55
|
|
|
56
56
|
self.project_id = project_id
|
|
57
|
-
self.
|
|
57
|
+
self.include_completed = include_completed
|
|
58
58
|
|
|
59
59
|
self.dump_annotation_obj = DumpAnnotationMain(service, project_id)
|
|
60
60
|
|
|
@@ -114,9 +114,9 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
114
114
|
*,
|
|
115
115
|
backup_dir: Optional[Path] = None,
|
|
116
116
|
task_index: Optional[int] = None,
|
|
117
|
-
) -> bool:
|
|
117
|
+
) -> tuple[bool, int]:
|
|
118
118
|
"""
|
|
119
|
-
|
|
119
|
+
タスクに対してアノテーション属性値を変更する。
|
|
120
120
|
|
|
121
121
|
Args:
|
|
122
122
|
project_id:
|
|
@@ -127,23 +127,24 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
127
127
|
backup_dir: アノテーションをバックアップとして保存するディレクトリ。指定しない場合は、バックアップを取得しない。
|
|
128
128
|
|
|
129
129
|
Returns:
|
|
130
|
-
|
|
130
|
+
tuple[0]: 成功した場合はTrue、失敗した場合はFalse
|
|
131
|
+
tuple[1]: 変更したアノテーションの個数
|
|
131
132
|
"""
|
|
132
133
|
logger_prefix = f"{task_index + 1!s} 件目: " if task_index is not None else ""
|
|
133
134
|
dict_task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
|
|
134
135
|
if dict_task is None:
|
|
135
136
|
logger.warning(f"task_id = '{task_id}' は存在しません。")
|
|
136
|
-
return False
|
|
137
|
+
return False, 0
|
|
137
138
|
|
|
138
139
|
task: Task = Task.from_dict(dict_task)
|
|
139
140
|
if task.status == TaskStatus.WORKING:
|
|
140
141
|
logger.warning(f"task_id='{task_id}': タスクが作業中状態のため、スキップします。")
|
|
141
|
-
return False
|
|
142
|
+
return False, 0
|
|
142
143
|
|
|
143
|
-
if not self.
|
|
144
|
+
if not self.include_completed: # noqa: SIM102
|
|
144
145
|
if task.status == TaskStatus.COMPLETE:
|
|
145
|
-
logger.warning(f"task_id='{task_id}':
|
|
146
|
-
return False
|
|
146
|
+
logger.warning(f"task_id='{task_id}': タスクが完了状態のため、スキップします。完了状態のタスクのアノテーション属性値を変更するには、 ``--include_completed`` を指定してください。")
|
|
147
|
+
return False, 0
|
|
147
148
|
|
|
148
149
|
annotation_list = self.get_annotation_list_for_task(task_id, annotation_query)
|
|
149
150
|
logger.info(
|
|
@@ -151,17 +152,17 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
151
152
|
)
|
|
152
153
|
if len(annotation_list) == 0:
|
|
153
154
|
logger.info(f"{logger_prefix}task_id='{task_id}'には変更対象のアノテーションが存在しないので、スキップします。")
|
|
154
|
-
return False
|
|
155
|
+
return False, 0
|
|
155
156
|
|
|
156
|
-
if not self.confirm_processing(f"task_id='{task_id}'
|
|
157
|
-
return False
|
|
157
|
+
if not self.confirm_processing(f"task_id='{task_id}' のアノテーション属性値を変更しますか?"):
|
|
158
|
+
return False, 0
|
|
158
159
|
|
|
159
160
|
if backup_dir is not None:
|
|
160
161
|
self.dump_annotation_obj.dump_annotation_for_task(task_id, output_dir=backup_dir)
|
|
161
162
|
|
|
162
163
|
self.change_annotation_attributes(annotation_list, additional_data_list)
|
|
163
164
|
logger.info(f"{logger_prefix}task_id='{task_id}': {len(annotation_list)} 個のアノテーションの属性値を変更しました。")
|
|
164
|
-
return True
|
|
165
|
+
return True, len(annotation_list)
|
|
165
166
|
|
|
166
167
|
def change_attributes_for_task_wrapper(
|
|
167
168
|
self,
|
|
@@ -170,7 +171,7 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
170
171
|
additional_data_list: list[dict[str, Any]],
|
|
171
172
|
*,
|
|
172
173
|
backup_dir: Optional[Path] = None,
|
|
173
|
-
) -> bool:
|
|
174
|
+
) -> tuple[bool, int]:
|
|
174
175
|
task_index, task_id = tpl
|
|
175
176
|
try:
|
|
176
177
|
return self.change_attributes_for_task(
|
|
@@ -181,8 +182,8 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
181
182
|
task_index=task_index,
|
|
182
183
|
)
|
|
183
184
|
except Exception: # pylint: disable=broad-except
|
|
184
|
-
logger.warning(f"タスク'{task_id}'
|
|
185
|
-
return False
|
|
185
|
+
logger.warning(f"タスク'{task_id}'のアノテーションの属性値の変更に失敗しました。", exc_info=True)
|
|
186
|
+
return False, 0
|
|
186
187
|
|
|
187
188
|
def change_annotation_attributes_for_task_list(
|
|
188
189
|
self,
|
|
@@ -209,8 +210,9 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
209
210
|
|
|
210
211
|
if backup_dir is not None:
|
|
211
212
|
backup_dir.mkdir(exist_ok=True, parents=True)
|
|
212
|
-
|
|
213
213
|
success_count = 0
|
|
214
|
+
# 変更したアノテーションの個数
|
|
215
|
+
changed_annotation_count = 0
|
|
214
216
|
if parallelism is not None:
|
|
215
217
|
func = functools.partial(
|
|
216
218
|
self.change_attributes_for_task_wrapper,
|
|
@@ -219,26 +221,28 @@ class ChangeAnnotationAttributesMain(CommandLineWithConfirm):
|
|
|
219
221
|
backup_dir=backup_dir,
|
|
220
222
|
)
|
|
221
223
|
with multiprocessing.Pool(parallelism) as pool:
|
|
222
|
-
|
|
223
|
-
success_count = len([e for e in
|
|
224
|
+
result_tuple_list = pool.map(func, enumerate(task_id_list))
|
|
225
|
+
success_count = len([e for e in result_tuple_list if e[0]])
|
|
226
|
+
changed_annotation_count = sum(e[1] for e in result_tuple_list)
|
|
224
227
|
|
|
225
228
|
else:
|
|
226
229
|
for task_index, task_id in enumerate(task_id_list):
|
|
227
230
|
try:
|
|
228
|
-
result = self.change_attributes_for_task(
|
|
231
|
+
result, sub_changed_annotation_count = self.change_attributes_for_task(
|
|
229
232
|
task_id,
|
|
230
233
|
annotation_query=annotation_query,
|
|
231
234
|
additional_data_list=additional_data_list,
|
|
232
235
|
backup_dir=backup_dir,
|
|
233
236
|
task_index=task_index,
|
|
234
237
|
)
|
|
238
|
+
changed_annotation_count += sub_changed_annotation_count
|
|
235
239
|
if result:
|
|
236
240
|
success_count += 1
|
|
237
241
|
except Exception:
|
|
238
|
-
logger.warning(f"タスク'{task_id}'
|
|
242
|
+
logger.warning(f"タスク'{task_id}'のアノテーションの属性値の変更に失敗しました。", exc_info=True)
|
|
239
243
|
continue
|
|
240
244
|
|
|
241
|
-
logger.info(f"{success_count} / {len(task_id_list)}
|
|
245
|
+
logger.info(f"{success_count} / {len(task_id_list)} 件のタスクに対して {changed_annotation_count} 件のアノテーションの属性値を変更しました。")
|
|
242
246
|
|
|
243
247
|
|
|
244
248
|
class ChangeAttributesOfAnnotation(CommandLine):
|
|
@@ -310,15 +314,15 @@ class ChangeAttributesOfAnnotation(CommandLine):
|
|
|
310
314
|
backup_dir = Path(args.backup)
|
|
311
315
|
|
|
312
316
|
super().validate_project(project_id, [ProjectMemberRole.OWNER, ProjectMemberRole.ACCEPTER])
|
|
313
|
-
if args.
|
|
317
|
+
if args.include_completed: # noqa: SIM102
|
|
314
318
|
if not self.facade.contains_any_project_member_role(project_id, [ProjectMemberRole.OWNER]):
|
|
315
319
|
print( # noqa: T201
|
|
316
|
-
f"{self.COMMON_MESSAGE} argument --
|
|
320
|
+
f"{self.COMMON_MESSAGE} argument --include_completed : '--include_completed' 引数を利用するにはプロジェクトのオーナーロールを持つユーザーで実行する必要があります。",
|
|
317
321
|
file=sys.stderr,
|
|
318
322
|
)
|
|
319
323
|
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
320
324
|
|
|
321
|
-
main_obj = ChangeAnnotationAttributesMain(self.service, project_id=project_id,
|
|
325
|
+
main_obj = ChangeAnnotationAttributesMain(self.service, project_id=project_id, include_completed=args.include_completed, all_yes=args.yes)
|
|
322
326
|
main_obj.change_annotation_attributes_for_task_list(
|
|
323
327
|
task_id_list,
|
|
324
328
|
annotation_query=annotation_query,
|
|
@@ -358,7 +362,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
358
362
|
)
|
|
359
363
|
|
|
360
364
|
parser.add_argument(
|
|
361
|
-
"--
|
|
365
|
+
"--include_completed",
|
|
362
366
|
action="store_true",
|
|
363
367
|
help="完了状態のタスクのアノテーション属性も変更します。ただし、オーナーロールを持つユーザーでしか実行できません。",
|
|
364
368
|
)
|
|
@@ -381,10 +385,11 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
381
385
|
|
|
382
386
|
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
383
387
|
subcommand_name = "change_attributes"
|
|
384
|
-
subcommand_help = "
|
|
388
|
+
subcommand_help = "アノテーションの属性値を変更します。"
|
|
385
389
|
description = (
|
|
386
|
-
"
|
|
387
|
-
"
|
|
390
|
+
"アノテーションの属性値を一括で変更します。ただし、作業中状態のタスクに含まれるアノテーションは変更できません。"
|
|
391
|
+
"完了状態のタスクに含まれるアノテーションは、デフォルトでは変更できません。"
|
|
392
|
+
"間違えてアノテーション属性値を変更したときに復元できるようにするため、 ``--backup`` でバックアップ用のディレクトリを指定することを推奨します。"
|
|
388
393
|
)
|
|
389
394
|
epilog = "オーナロールまたはチェッカーロールを持つユーザで実行してください。"
|
|
390
395
|
|
|
@@ -19,7 +19,7 @@ class DownloadingAnnotationZip(CommandLine):
|
|
|
19
19
|
super().validate_project(project_id, [ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
20
20
|
|
|
21
21
|
project_title = self.facade.get_project_title(project_id)
|
|
22
|
-
logger.info(f"{project_title}
|
|
22
|
+
logger.info(f"プロジェクト'{project_title}'のアノテーションZIPをダウンロードします。")
|
|
23
23
|
|
|
24
24
|
obj = DownloadingFile(self.service)
|
|
25
25
|
obj.download_annotation_zip(
|
|
@@ -28,7 +28,7 @@ class DownloadingAnnotationZip(CommandLine):
|
|
|
28
28
|
is_latest=is_latest,
|
|
29
29
|
should_download_full_annotation=should_download_full_annotation,
|
|
30
30
|
)
|
|
31
|
-
logger.info(f"アノテーションZIP
|
|
31
|
+
logger.info(f"アノテーションZIPをダウンロードして、'{output_zip}'に保存しました。")
|
|
32
32
|
|
|
33
33
|
def main(self) -> None:
|
|
34
34
|
args = self.args
|
|
@@ -419,19 +419,25 @@ class ImportAnnotationMain(CommandLineWithConfirm):
|
|
|
419
419
|
self.is_overwrite = is_overwrite
|
|
420
420
|
self.converter = converter
|
|
421
421
|
|
|
422
|
-
def put_annotation_for_input_data(self, parser: SimpleAnnotationParser) ->
|
|
422
|
+
def put_annotation_for_input_data(self, parser: SimpleAnnotationParser) -> int:
|
|
423
|
+
"""
|
|
424
|
+
1個の入力データに対してアノテーションを登録します。
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
登録したアノテーションの個数
|
|
428
|
+
"""
|
|
423
429
|
task_id = parser.task_id
|
|
424
430
|
input_data_id = parser.input_data_id
|
|
425
431
|
|
|
426
432
|
simple_annotation: ImportedSimpleAnnotation = ImportedSimpleAnnotation.from_dict(parser.load_json())
|
|
427
433
|
if len(simple_annotation.details) == 0:
|
|
428
434
|
logger.debug(f"task_id='{task_id}', input_data_id='{input_data_id}' :: インポート元にアノテーションデータがないため、アノテーションの登録をスキップします。")
|
|
429
|
-
return
|
|
435
|
+
return 0
|
|
430
436
|
|
|
431
437
|
input_data = self.service.wrapper.get_input_data_or_none(self.project_id, input_data_id)
|
|
432
438
|
if input_data is None:
|
|
433
439
|
logger.warning(f"input_data_id='{input_data_id}'という入力データは存在しません。 :: task_id='{task_id}'")
|
|
434
|
-
return
|
|
440
|
+
return 0
|
|
435
441
|
|
|
436
442
|
old_annotation, _ = self.service.api.get_editor_annotation(self.project_id, task_id, input_data_id, query_params={"v": "2"})
|
|
437
443
|
if len(old_annotation["details"]) > 0: # noqa: SIM102
|
|
@@ -441,30 +447,44 @@ class ImportAnnotationMain(CommandLineWithConfirm):
|
|
|
441
447
|
f"インポート先のタスク内の入力データに既にアノテーションが存在するため、アノテーションの登録をスキップします。"
|
|
442
448
|
f"アノテーションをインポートする場合は、`--overwrite` または '--merge' を指定してください。"
|
|
443
449
|
)
|
|
444
|
-
return
|
|
450
|
+
return 0
|
|
445
451
|
|
|
446
|
-
logger.info(f"task_id='{task_id}', input_data_id='{input_data_id}' :: {len(simple_annotation.details)} 件のアノテーションを登録します。")
|
|
447
452
|
if self.is_merge:
|
|
448
453
|
request_body = self.converter.convert_annotation_details(parser, simple_annotation.details, old_details=old_annotation["details"], updated_datetime=old_annotation["updated_datetime"])
|
|
449
454
|
else:
|
|
450
455
|
request_body = self.converter.convert_annotation_details(parser, simple_annotation.details, old_details=[], updated_datetime=old_annotation["updated_datetime"])
|
|
451
456
|
|
|
457
|
+
if len(request_body["details"]) == 0:
|
|
458
|
+
logger.warning(f"task_id='{task_id}', input_data_id='{input_data_id}' :: 登録できるアノテーション数が0/{len(simple_annotation.details)}件なので、アノテーションの登録をスキップします。")
|
|
459
|
+
return 0
|
|
452
460
|
self.service.api.put_annotation(self.project_id, task_id, input_data_id, request_body=request_body, query_params={"v": "2"})
|
|
453
|
-
|
|
461
|
+
success_annotation_count = len(request_body["details"])
|
|
462
|
+
logger.debug(f"task_id='{task_id}', input_data_id='{input_data_id}' :: {success_annotation_count}/{len(simple_annotation.details)} 件のアノテーションを登録しました。")
|
|
463
|
+
return success_annotation_count
|
|
454
464
|
|
|
455
|
-
def put_annotation_for_task(self, task_parser: SimpleAnnotationParserByTask) -> int:
|
|
456
|
-
|
|
465
|
+
def put_annotation_for_task(self, task_parser: SimpleAnnotationParserByTask) -> tuple[int, int]:
|
|
466
|
+
"""
|
|
467
|
+
1個のタスクに対して、アノテーションを登録します。
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
tuple[0]: アノテーションを登録した入力データの個数
|
|
471
|
+
tuple[1]: 登録したアノテーションの個数
|
|
472
|
+
"""
|
|
473
|
+
success_input_data_count = 0
|
|
474
|
+
success_annotation_count = 0
|
|
457
475
|
for parser in task_parser.lazy_parse():
|
|
458
476
|
try:
|
|
459
|
-
|
|
460
|
-
|
|
477
|
+
tmp_success_annotation_count = self.put_annotation_for_input_data(parser)
|
|
478
|
+
if tmp_success_annotation_count > 0:
|
|
479
|
+
success_input_data_count += 1
|
|
480
|
+
success_annotation_count += tmp_success_annotation_count
|
|
461
481
|
except Exception: # pylint: disable=broad-except
|
|
462
482
|
logger.warning(
|
|
463
483
|
f"task_id='{parser.task_id}', input_data_id='{parser.input_data_id}' のアノテーションのインポートに失敗しました。",
|
|
464
484
|
exc_info=True,
|
|
465
485
|
)
|
|
466
486
|
|
|
467
|
-
return
|
|
487
|
+
return success_input_data_count, success_annotation_count
|
|
468
488
|
|
|
469
489
|
def execute_task(self, task_parser: SimpleAnnotationParserByTask, task_index: Optional[int] = None) -> bool:
|
|
470
490
|
"""
|
|
@@ -515,8 +535,10 @@ class ImportAnnotationMain(CommandLineWithConfirm):
|
|
|
515
535
|
)
|
|
516
536
|
return False
|
|
517
537
|
|
|
518
|
-
|
|
519
|
-
logger.info(
|
|
538
|
+
success_input_data_count, success_annotation_count = self.put_annotation_for_task(task_parser)
|
|
539
|
+
logger.info(
|
|
540
|
+
f"{logger_prefix}task_id='{task_parser.task_id}'のタスクに含まれる入力データ {success_input_data_count} 個に対して、アノテーション{success_annotation_count}個をインポートしました。"
|
|
541
|
+
)
|
|
520
542
|
|
|
521
543
|
if changed_operator:
|
|
522
544
|
logger.debug(f"タスク'{task_id}' の担当者を元に戻します。")
|
|
@@ -527,7 +549,7 @@ class ImportAnnotationMain(CommandLineWithConfirm):
|
|
|
527
549
|
last_updated_datetime=task["updated_datetime"],
|
|
528
550
|
)
|
|
529
551
|
|
|
530
|
-
return
|
|
552
|
+
return success_input_data_count > 0
|
|
531
553
|
|
|
532
554
|
def execute_task_wrapper(
|
|
533
555
|
self,
|
|
@@ -617,7 +639,7 @@ class ImportAnnotation(CommandLine):
|
|
|
617
639
|
|
|
618
640
|
if args.parallelism is not None and not args.yes:
|
|
619
641
|
print( # noqa: T201
|
|
620
|
-
f"{COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
642
|
+
f"{COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
621
643
|
file=sys.stderr,
|
|
622
644
|
)
|
|
623
645
|
return False
|
|
@@ -305,7 +305,7 @@ class RestoreAnnotation(CommandLine):
|
|
|
305
305
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
306
306
|
if args.parallelism is not None and not args.yes:
|
|
307
307
|
print( # noqa: T201
|
|
308
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
308
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
309
309
|
file=sys.stderr,
|
|
310
310
|
)
|
|
311
311
|
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""アノテーションZIPに関するサブパッケージ"""
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
from collections.abc import Collection
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
import pandas
|
|
13
|
+
from annofabapi.models import InputDataType, ProjectMemberRole
|
|
14
|
+
from dataclasses_json import DataClassJsonMixin
|
|
15
|
+
|
|
16
|
+
import annofabcli
|
|
17
|
+
import annofabcli.common.cli
|
|
18
|
+
from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
|
|
19
|
+
from annofabcli.common.cli import (
|
|
20
|
+
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
21
|
+
ArgumentParser,
|
|
22
|
+
CommandLine,
|
|
23
|
+
build_annofabapi_resource_and_login,
|
|
24
|
+
)
|
|
25
|
+
from annofabcli.common.download import DownloadingFile
|
|
26
|
+
from annofabcli.common.enums import FormatArgument
|
|
27
|
+
from annofabcli.common.facade import (
|
|
28
|
+
AnnofabApiFacade,
|
|
29
|
+
TaskQuery,
|
|
30
|
+
match_annotation_with_task_query,
|
|
31
|
+
)
|
|
32
|
+
from annofabcli.common.utils import print_csv, print_json
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class AnnotationBoundingBoxInfo(DataClassJsonMixin):
|
|
39
|
+
project_id: str
|
|
40
|
+
task_id: str
|
|
41
|
+
task_status: str
|
|
42
|
+
task_phase: str
|
|
43
|
+
task_phase_stage: int
|
|
44
|
+
|
|
45
|
+
input_data_id: str
|
|
46
|
+
input_data_name: str
|
|
47
|
+
|
|
48
|
+
updated_datetime: Optional[str]
|
|
49
|
+
"""アノテーションJSONに格納されているアノテーションの更新日時"""
|
|
50
|
+
|
|
51
|
+
label: str
|
|
52
|
+
annotation_id: str
|
|
53
|
+
left_top: dict[str, int]
|
|
54
|
+
right_bottom: dict[str, int]
|
|
55
|
+
width: int
|
|
56
|
+
height: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_annotation_bounding_box_info_list(simple_annotation: dict[str, Any]) -> list[AnnotationBoundingBoxInfo]:
|
|
60
|
+
result = []
|
|
61
|
+
for detail in simple_annotation["details"]:
|
|
62
|
+
if detail["data"]["_type"] == "BoundingBox":
|
|
63
|
+
left_top = detail["data"]["left_top"]
|
|
64
|
+
right_bottom = detail["data"]["right_bottom"]
|
|
65
|
+
width = abs(right_bottom["x"] - left_top["x"])
|
|
66
|
+
height = abs(right_bottom["y"] - left_top["y"])
|
|
67
|
+
|
|
68
|
+
result.append(
|
|
69
|
+
AnnotationBoundingBoxInfo(
|
|
70
|
+
project_id=simple_annotation["project_id"],
|
|
71
|
+
task_id=simple_annotation["task_id"],
|
|
72
|
+
task_phase=simple_annotation["task_phase"],
|
|
73
|
+
task_phase_stage=simple_annotation["task_phase_stage"],
|
|
74
|
+
task_status=simple_annotation["task_status"],
|
|
75
|
+
input_data_id=simple_annotation["input_data_id"],
|
|
76
|
+
input_data_name=simple_annotation["input_data_name"],
|
|
77
|
+
label=detail["label"],
|
|
78
|
+
annotation_id=detail["annotation_id"],
|
|
79
|
+
left_top=left_top,
|
|
80
|
+
right_bottom=right_bottom,
|
|
81
|
+
width=width,
|
|
82
|
+
height=height,
|
|
83
|
+
updated_datetime=simple_annotation["updated_datetime"],
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_annotation_bounding_box_info_list_from_annotation_path(
|
|
91
|
+
annotation_path: Path,
|
|
92
|
+
*,
|
|
93
|
+
target_task_ids: Optional[Collection[str]] = None,
|
|
94
|
+
task_query: Optional[TaskQuery] = None,
|
|
95
|
+
) -> list[AnnotationBoundingBoxInfo]:
|
|
96
|
+
annotation_bbox_list = []
|
|
97
|
+
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
98
|
+
iter_parser = lazy_parse_simple_annotation_by_input_data(annotation_path)
|
|
99
|
+
logger.info(f"アノテーションZIPまたはディレクトリ'{annotation_path}'を読み込みます。")
|
|
100
|
+
for index, parser in enumerate(iter_parser):
|
|
101
|
+
if (index + 1) % 10000 == 0:
|
|
102
|
+
logger.info(f"{index + 1} 件目のJSONを読み込み中")
|
|
103
|
+
if target_task_ids is not None and parser.task_id not in target_task_ids:
|
|
104
|
+
continue
|
|
105
|
+
dict_simple_annotation = parser.load_json()
|
|
106
|
+
if task_query is not None and not match_annotation_with_task_query(dict_simple_annotation, task_query):
|
|
107
|
+
continue
|
|
108
|
+
sub_annotation_bbox_list = get_annotation_bounding_box_info_list(dict_simple_annotation)
|
|
109
|
+
annotation_bbox_list.extend(sub_annotation_bbox_list)
|
|
110
|
+
return annotation_bbox_list
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def create_df(
|
|
114
|
+
annotation_bbox_list: list[AnnotationBoundingBoxInfo],
|
|
115
|
+
) -> pandas.DataFrame:
|
|
116
|
+
columns = [
|
|
117
|
+
"project_id",
|
|
118
|
+
"task_id",
|
|
119
|
+
"task_status",
|
|
120
|
+
"task_phase",
|
|
121
|
+
"task_phase_stage",
|
|
122
|
+
"input_data_id",
|
|
123
|
+
"input_data_name",
|
|
124
|
+
"updated_datetime",
|
|
125
|
+
"label",
|
|
126
|
+
"annotation_id",
|
|
127
|
+
"left_top.x",
|
|
128
|
+
"left_top.y",
|
|
129
|
+
"right_bottom.x",
|
|
130
|
+
"right_bottom.y",
|
|
131
|
+
"width",
|
|
132
|
+
"height",
|
|
133
|
+
]
|
|
134
|
+
if len(annotation_bbox_list) > 0:
|
|
135
|
+
df = pandas.DataFrame(
|
|
136
|
+
[
|
|
137
|
+
{
|
|
138
|
+
"project_id": e.project_id,
|
|
139
|
+
"task_id": e.task_id,
|
|
140
|
+
"task_status": e.task_status,
|
|
141
|
+
"task_phase": e.task_phase,
|
|
142
|
+
"task_phase_stage": e.task_phase_stage,
|
|
143
|
+
"input_data_id": e.input_data_id,
|
|
144
|
+
"input_data_name": e.input_data_name,
|
|
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
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
df = pandas.DataFrame(columns=columns)
|
|
160
|
+
|
|
161
|
+
return df[columns]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def print_annotation_bounding_box(
|
|
165
|
+
annotation_path: Path,
|
|
166
|
+
output_file: Path,
|
|
167
|
+
output_format: FormatArgument,
|
|
168
|
+
*,
|
|
169
|
+
target_task_ids: Optional[Collection[str]] = None,
|
|
170
|
+
task_query: Optional[TaskQuery] = None,
|
|
171
|
+
) -> None:
|
|
172
|
+
annotation_bbox_list = get_annotation_bounding_box_info_list_from_annotation_path(
|
|
173
|
+
annotation_path,
|
|
174
|
+
target_task_ids=target_task_ids,
|
|
175
|
+
task_query=task_query,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
logger.info(f"{len(annotation_bbox_list)} 件のバウンディングボックスアノテーションの情報を出力します。 :: output='{output_file}'")
|
|
179
|
+
|
|
180
|
+
if output_format == FormatArgument.CSV:
|
|
181
|
+
df = create_df(annotation_bbox_list)
|
|
182
|
+
print_csv(df, output_file)
|
|
183
|
+
|
|
184
|
+
elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
|
|
185
|
+
json_is_pretty = output_format == FormatArgument.PRETTY_JSON
|
|
186
|
+
# DataClassJsonMixinを使用したtoJSON処理
|
|
187
|
+
print_json(
|
|
188
|
+
[e.to_dict(encode_json=True) for e in annotation_bbox_list],
|
|
189
|
+
is_pretty=json_is_pretty,
|
|
190
|
+
output=output_file,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
else:
|
|
194
|
+
raise ValueError(f"出力形式 '{output_format}' はサポートされていません。")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ListAnnotationBoundingBox2d(CommandLine):
|
|
198
|
+
COMMON_MESSAGE = "annofabcli annotation_zip list_annotation_bounding_box_2d: error:"
|
|
199
|
+
|
|
200
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
201
|
+
if args.project_id is None and args.annotation is None:
|
|
202
|
+
print( # noqa: T201
|
|
203
|
+
f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
|
|
204
|
+
file=sys.stderr,
|
|
205
|
+
)
|
|
206
|
+
return False
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
def main(self) -> None:
|
|
210
|
+
args = self.args
|
|
211
|
+
|
|
212
|
+
if not self.validate(args):
|
|
213
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
214
|
+
|
|
215
|
+
project_id: Optional[str] = args.project_id
|
|
216
|
+
if project_id is not None:
|
|
217
|
+
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
218
|
+
project, _ = self.service.api.get_project(project_id)
|
|
219
|
+
if project["input_data_type"] != InputDataType.IMAGE.value:
|
|
220
|
+
print(f"project_id='{project_id}'であるプロジェクトは画像プロジェクトでないので、終了します", file=sys.stderr) # noqa: T201
|
|
221
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
222
|
+
|
|
223
|
+
annotation_path = Path(args.annotation) if args.annotation is not None else None
|
|
224
|
+
|
|
225
|
+
task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
226
|
+
task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
|
|
227
|
+
|
|
228
|
+
output_file: Path = args.output
|
|
229
|
+
output_format = FormatArgument(args.format)
|
|
230
|
+
|
|
231
|
+
downloading_obj = DownloadingFile(self.service)
|
|
232
|
+
|
|
233
|
+
def download_and_print_annotation_bbox(project_id: str, temp_dir: Path, *, is_latest: bool) -> None:
|
|
234
|
+
annotation_path = temp_dir / f"{project_id}__annotation.zip"
|
|
235
|
+
downloading_obj.download_annotation_zip(
|
|
236
|
+
project_id,
|
|
237
|
+
dest_path=annotation_path,
|
|
238
|
+
is_latest=is_latest,
|
|
239
|
+
)
|
|
240
|
+
print_annotation_bounding_box(
|
|
241
|
+
annotation_path,
|
|
242
|
+
output_file,
|
|
243
|
+
output_format,
|
|
244
|
+
target_task_ids=task_id_list,
|
|
245
|
+
task_query=task_query,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if project_id is not None:
|
|
249
|
+
if args.temp_dir is not None:
|
|
250
|
+
download_and_print_annotation_bbox(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest)
|
|
251
|
+
else:
|
|
252
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
253
|
+
download_and_print_annotation_bbox(
|
|
254
|
+
project_id=project_id,
|
|
255
|
+
temp_dir=Path(str_temp_dir),
|
|
256
|
+
is_latest=args.latest,
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
assert annotation_path is not None
|
|
260
|
+
print_annotation_bounding_box(
|
|
261
|
+
annotation_path,
|
|
262
|
+
output_file,
|
|
263
|
+
output_format,
|
|
264
|
+
target_task_ids=task_id_list,
|
|
265
|
+
task_query=task_query,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
270
|
+
argument_parser = ArgumentParser(parser)
|
|
271
|
+
|
|
272
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
273
|
+
group.add_argument(
|
|
274
|
+
"--annotation",
|
|
275
|
+
type=str,
|
|
276
|
+
help="アノテーションzip、またはzipを展開したディレクトリを指定します。",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
group.add_argument("-p", "--project_id", type=str, help="project_id。アノテーションZIPをダウンロードします。")
|
|
280
|
+
|
|
281
|
+
argument_parser.add_format(
|
|
282
|
+
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
|
283
|
+
default=FormatArgument.CSV,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
argument_parser.add_output()
|
|
287
|
+
|
|
288
|
+
parser.add_argument(
|
|
289
|
+
"-tq",
|
|
290
|
+
"--task_query",
|
|
291
|
+
type=str,
|
|
292
|
+
help="集計対象タスクを絞り込むためのクエリ条件をJSON形式で指定します。使用できるキーは task_id, status, phase, phase_stage です。"
|
|
293
|
+
" ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
|
|
294
|
+
)
|
|
295
|
+
argument_parser.add_task_id(required=False)
|
|
296
|
+
|
|
297
|
+
parser.add_argument(
|
|
298
|
+
"--latest",
|
|
299
|
+
action="store_true",
|
|
300
|
+
help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
parser.add_argument(
|
|
304
|
+
"--temp_dir",
|
|
305
|
+
type=Path,
|
|
306
|
+
help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
parser.set_defaults(subcommand_func=main)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def main(args: argparse.Namespace) -> None:
|
|
313
|
+
service = build_annofabapi_resource_and_login(args)
|
|
314
|
+
facade = AnnofabApiFacade(service)
|
|
315
|
+
ListAnnotationBoundingBox2d(service, facade, args).main()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
319
|
+
subcommand_name = "list_annotation_bounding_box_2d"
|
|
320
|
+
subcommand_help = "アノテーションZIPからバウンディングボックス(矩形)アノテーションの座標情報を出力します。"
|
|
321
|
+
epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
322
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
|
|
323
|
+
parse_args(parser)
|
|
324
|
+
return parser
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""サブコマンド annotation_zip"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import annofabcli
|
|
7
|
+
from annofabcli.annotation_zip.list_annotation_bounding_box_2d import add_parser as add_parser_list_annotation_bounding_box_2d
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
11
|
+
"""
|
|
12
|
+
サブコマンドの引数を定義する
|
|
13
|
+
Args:
|
|
14
|
+
parser: パーサー
|
|
15
|
+
"""
|
|
16
|
+
subparsers = parser.add_subparsers(dest="subcommand_name")
|
|
17
|
+
|
|
18
|
+
# サブコマンドの定義
|
|
19
|
+
add_parser_list_annotation_bounding_box_2d(subparsers)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
23
|
+
"""
|
|
24
|
+
annotation_zipサブコマンドを追加する。
|
|
25
|
+
Args:
|
|
26
|
+
subparsers: 親パーサーのサブパーサー
|
|
27
|
+
Returns:
|
|
28
|
+
annotation_zipサブコマンドのパーサー
|
|
29
|
+
"""
|
|
30
|
+
subcommand_name = "annotation_zip"
|
|
31
|
+
subcommand_help = "アノテーションZIPに対する操作を行うサブコマンド"
|
|
32
|
+
|
|
33
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, is_subcommand=False)
|
|
34
|
+
parse_args(parser)
|
|
35
|
+
return parser
|
|
@@ -206,7 +206,7 @@ class DeleteComment(CommandLine):
|
|
|
206
206
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
207
207
|
if args.parallelism is not None and not args.yes:
|
|
208
208
|
print( # noqa: T201
|
|
209
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
209
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
210
210
|
file=sys.stderr,
|
|
211
211
|
)
|
|
212
212
|
return False
|
|
@@ -254,7 +254,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
254
254
|
"--parallelism",
|
|
255
255
|
type=int,
|
|
256
256
|
choices=PARALLELISM_CHOICES,
|
|
257
|
-
help="
|
|
257
|
+
help="使用するプロセス数(並列度)を指定してください。指定する場合は'--yes' を指定してください。指定しない場合は、逐次的に処理します。",
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
parser.set_defaults(subcommand_func=main)
|
|
@@ -29,7 +29,7 @@ class PutInspectionComment(CommandLine):
|
|
|
29
29
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
30
30
|
if args.parallelism is not None and not args.yes:
|
|
31
31
|
print( # noqa: T201
|
|
32
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
32
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
33
33
|
file=sys.stderr,
|
|
34
34
|
)
|
|
35
35
|
return False
|
|
@@ -29,7 +29,7 @@ class PutInspectionCommentSimply(CommandLine):
|
|
|
29
29
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
30
30
|
if args.parallelism is not None and not args.yes:
|
|
31
31
|
print( # noqa: T201
|
|
32
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
32
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
33
33
|
file=sys.stderr,
|
|
34
34
|
)
|
|
35
35
|
return False
|
|
@@ -27,7 +27,7 @@ class PutInspectionComment(CommandLine):
|
|
|
27
27
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
28
28
|
if args.parallelism is not None and not args.yes:
|
|
29
29
|
print( # noqa: T201
|
|
30
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
30
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
31
31
|
file=sys.stderr,
|
|
32
32
|
)
|
|
33
33
|
return False
|
|
@@ -27,7 +27,7 @@ class PutOnholdCommentSimply(CommandLine):
|
|
|
27
27
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
28
28
|
if args.parallelism is not None and not args.yes:
|
|
29
29
|
print( # noqa: T201
|
|
30
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
30
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
31
31
|
file=sys.stderr,
|
|
32
32
|
)
|
|
33
33
|
return False
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AnnofabのアノテーションZIPまたはそれを展開したディレクトリに関するモジュール
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import zipfile
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from annofabapi.parser import (
|
|
10
|
+
SimpleAnnotationParser,
|
|
11
|
+
lazy_parse_simple_annotation_dir,
|
|
12
|
+
lazy_parse_simple_annotation_zip,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lazy_parse_simple_annotation_by_input_data(annotation_path: Path) -> Iterator[SimpleAnnotationParser]:
|
|
17
|
+
"""
|
|
18
|
+
アノテーションZIPを入力データごとに読み込むparserのイテレータを返します。
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
アノテーションZIPまたは、それを展開したディレクトリのパス
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
SimpleAnnotationParserのイテレータ
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ValueError: 指定されたパスが存在しない、またはzipファイルやディレクトリではない場合
|
|
28
|
+
"""
|
|
29
|
+
if not annotation_path.exists():
|
|
30
|
+
raise ValueError(f"'{annotation_path}' は存在しません。")
|
|
31
|
+
|
|
32
|
+
if annotation_path.is_dir():
|
|
33
|
+
return lazy_parse_simple_annotation_dir(annotation_path)
|
|
34
|
+
elif zipfile.is_zipfile(str(annotation_path)):
|
|
35
|
+
return lazy_parse_simple_annotation_zip(annotation_path)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(f"'{annotation_path}'は、zipファイルまたはディレクトリではありません。")
|
|
@@ -196,16 +196,19 @@ class SubPutInputData:
|
|
|
196
196
|
|
|
197
197
|
if dict_input_data is not None:
|
|
198
198
|
if overwrite:
|
|
199
|
-
logger.debug(f"{log_message_prefix}input_data_id='{input_data.input_data_id}'
|
|
199
|
+
logger.debug(f"{log_message_prefix}input_data_id='{input_data.input_data_id}'の入力データはすでに存在します。")
|
|
200
200
|
last_updated_datetime = dict_input_data["updated_datetime"]
|
|
201
201
|
else:
|
|
202
|
-
logger.debug(
|
|
202
|
+
logger.debug(
|
|
203
|
+
f"{log_message_prefix}input_data_id='{input_data.input_data_id}'の入力データがすでに存在するので入力データの登録をスキップします。"
|
|
204
|
+
"入力データを上書きして登録する場合は、引数に '--overwrite' を指定してください。"
|
|
205
|
+
)
|
|
203
206
|
return False
|
|
204
207
|
|
|
205
208
|
file_path = get_file_scheme_path(input_data.input_data_path)
|
|
206
209
|
if file_path is not None: # noqa: SIM102
|
|
207
210
|
if not Path(file_path).exists():
|
|
208
|
-
logger.warning(f"{input_data.input_data_path}
|
|
211
|
+
logger.warning(f"input_data_path='{input_data.input_data_path}'にファイルは存在しません。入力データの登録をスキップします。")
|
|
209
212
|
return False
|
|
210
213
|
|
|
211
214
|
if not self.confirm_put_input_data(input_data, already_exists=last_updated_datetime is not None):
|
|
@@ -219,13 +222,13 @@ class SubPutInputData:
|
|
|
219
222
|
|
|
220
223
|
except requests.exceptions.HTTPError:
|
|
221
224
|
logger.warning(
|
|
222
|
-
f"{log_message_prefix}入力データの登録に失敗しました。input_data_id='{input_data.input_data_id}', input_data_name='{input_data.input_data_name}'",
|
|
225
|
+
f"{log_message_prefix}入力データの登録に失敗しました。 :: input_data_id='{input_data.input_data_id}', input_data_name='{input_data.input_data_name}'",
|
|
223
226
|
exc_info=True,
|
|
224
227
|
)
|
|
225
228
|
return False
|
|
226
229
|
except CheckSumError:
|
|
227
230
|
logger.warning(
|
|
228
|
-
f"{log_message_prefix}入力データを登録しましたが、データが破損している可能性があります。input_data_id='{input_data.input_data_id}', input_data_name='{input_data.input_data_name}'",
|
|
231
|
+
f"{log_message_prefix}入力データを登録しましたが、データが破損している可能性があります。 :: input_data_id='{input_data.input_data_id}', input_data_name='{input_data.input_data_name}'",
|
|
229
232
|
exc_info=True,
|
|
230
233
|
)
|
|
231
234
|
return False
|
|
@@ -257,7 +260,7 @@ class PutInputData(CommandLine):
|
|
|
257
260
|
"""
|
|
258
261
|
|
|
259
262
|
project_title = self.facade.get_project_title(project_id)
|
|
260
|
-
logger.info(f"{project_title}
|
|
263
|
+
logger.info(f"プロジェクト'{project_title}'に、{len(input_data_list)} 件の入力データを登録します。")
|
|
261
264
|
|
|
262
265
|
count_put_input_data = 0
|
|
263
266
|
|
|
@@ -274,7 +277,7 @@ class PutInputData(CommandLine):
|
|
|
274
277
|
if result:
|
|
275
278
|
count_put_input_data += 1
|
|
276
279
|
|
|
277
|
-
logger.info(f"{project_title}
|
|
280
|
+
logger.info(f"プロジェクト'{project_title}'に、{count_put_input_data} / {len(input_data_list)} 件の入力データを登録しました。")
|
|
278
281
|
|
|
279
282
|
@staticmethod
|
|
280
283
|
def get_input_data_list_from_df(df: pandas.DataFrame) -> list[CsvInputData]:
|
|
@@ -317,7 +320,7 @@ class PutInputData(CommandLine):
|
|
|
317
320
|
if args.csv is not None or args.json is not None: # noqa: SIM102
|
|
318
321
|
if args.parallelism is not None and not args.yes:
|
|
319
322
|
print( # noqa: T201
|
|
320
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
323
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
321
324
|
file=sys.stderr,
|
|
322
325
|
)
|
|
323
326
|
return False
|
|
@@ -264,19 +264,14 @@ class ListAnnotationArea(CommandLine):
|
|
|
264
264
|
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
265
265
|
argument_parser = ArgumentParser(parser)
|
|
266
266
|
|
|
267
|
-
parser.
|
|
267
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
268
|
+
group.add_argument(
|
|
268
269
|
"--annotation",
|
|
269
270
|
type=str,
|
|
270
271
|
help="アノテーションzip、またはzipを展開したディレクトリを指定します。指定しない場合はAnnofabからダウンロードします。",
|
|
271
272
|
)
|
|
272
273
|
|
|
273
|
-
|
|
274
|
-
"-p",
|
|
275
|
-
"--project_id",
|
|
276
|
-
type=str,
|
|
277
|
-
help="project_id。``--annotation`` が未指定のときは必須です。\n"
|
|
278
|
-
"``--annotation`` が指定されているときに ``--project_id`` を指定すると、アノテーション仕様を参照して、集計対象の属性やCSV列順が決まります。",
|
|
279
|
-
)
|
|
274
|
+
group.add_argument("-p", "--project_id", type=str, help="project_id。``--annotation`` が未指定のときは必須です。\n")
|
|
280
275
|
|
|
281
276
|
argument_parser.add_format(
|
|
282
277
|
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
annofabcli/task/copy_tasks.py
CHANGED
|
@@ -157,7 +157,7 @@ class CopyTasks(CommandLine):
|
|
|
157
157
|
def validate(self, args: argparse.Namespace) -> bool:
|
|
158
158
|
if args.parallelism is not None and not args.yes:
|
|
159
159
|
print( # noqa: T201
|
|
160
|
-
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'
|
|
160
|
+
f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、'--yes' を指定してください。",
|
|
161
161
|
file=sys.stderr,
|
|
162
162
|
)
|
|
163
163
|
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: annofabcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.107.0
|
|
4
4
|
Summary: Utility Command Line Interface for AnnoFab
|
|
5
5
|
Author: Kurusugawa Computer Inc.
|
|
6
6
|
License: MIT
|
|
@@ -37,7 +37,6 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
[Annofab](https://annofab.com/)のCLI(Command Line Interface)ツールです。
|
|
38
38
|
「タスクの一括差し戻し」や、「タスク一覧出力」など、Annofabの画面で実施するには時間がかかる操作を、コマンドとして提供しています。
|
|
39
39
|
|
|
40
|
-
[](https://app.travis-ci.com/kurusugawa-computer/annofab-cli)
|
|
41
40
|
[](https://badge.fury.io/py/annofabcli)
|
|
42
41
|
[](https://pypi.org/project/annofabcli/)
|
|
43
42
|
[](https://annofab-cli.readthedocs.io/ja/latest/?badge=latest)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
annofabcli/__init__.py,sha256=fdBtxy5rOI8zi26jf0hmXS5KTBjQIsm2b9ZUSAIR558,319
|
|
2
|
-
annofabcli/__main__.py,sha256=
|
|
2
|
+
annofabcli/__main__.py,sha256=HSKErJxFZARMWsH6GadYeyzcH5YBcZVDsy06wYGTo5c,5431
|
|
3
3
|
annofabcli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
annofabcli/annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
annofabcli/annotation/annotation_query.py,sha256=
|
|
6
|
-
annofabcli/annotation/change_annotation_attributes.py,sha256=
|
|
5
|
+
annofabcli/annotation/annotation_query.py,sha256=jd69b_QBw8fy0i_bk-3MGNarS_W0XjCDJa1T4GSZjvk,15730
|
|
6
|
+
annofabcli/annotation/change_annotation_attributes.py,sha256=pCHT12ba5jsT9kqslUXqfy2QriRT8yiAzgHDuL1JkNE,17884
|
|
7
7
|
annofabcli/annotation/change_annotation_attributes_per_annotation.py,sha256=g_SqH1xzAdA8D1Hy1aoOPlpF86nc7F1PENeUX8XftDs,15529
|
|
8
8
|
annofabcli/annotation/change_annotation_properties.py,sha256=Kp_LZ5sSoVmmjGE80ABVO3InxsXBIxiFFvVcIJNsOMk,18309
|
|
9
9
|
annofabcli/annotation/copy_annotation.py,sha256=Pih2k3vvpgfT3Ovb3gZw2L_8fK_ws_wKR7ARYG5hG_8,18407
|
|
10
10
|
annofabcli/annotation/delete_annotation.py,sha256=hQApNrx2Ci1bBWk0dRGA0oJkIgDHwl6Jy0-33gYF6jo,22989
|
|
11
|
-
annofabcli/annotation/download_annotation_zip.py,sha256=
|
|
11
|
+
annofabcli/annotation/download_annotation_zip.py,sha256=SMtfxt6NKkpHGRDoKRCjPqpQB5DBpQ-PD2_3ts51h0Q,3314
|
|
12
12
|
annofabcli/annotation/dump_annotation.py,sha256=Q-p6f5XBs7khDgrfY5Q3CGLBMKEerJWO_CQ8_73UXVM,9972
|
|
13
|
-
annofabcli/annotation/import_annotation.py,sha256=
|
|
13
|
+
annofabcli/annotation/import_annotation.py,sha256=cFLpWOSJHDCbLXJV607ywPlitHR6JemB4C9b8RWJxhw,35785
|
|
14
14
|
annofabcli/annotation/list_annotation.py,sha256=uKcOuGC7lzd6vVbzizkiZtYdXJ7EzY0iifuiqKl2wQM,10707
|
|
15
15
|
annofabcli/annotation/list_annotation_count.py,sha256=T9fbaoxWeDJIVgW_YgHRldbwrVZWiE-57lfJrDQrj80,6474
|
|
16
16
|
annofabcli/annotation/merge_segmentation.py,sha256=kIsCeXtJxzd6nobQPpi0fscaRDlTx3tg1qpy5PDfSJI,18107
|
|
17
17
|
annofabcli/annotation/remove_segmentation_overlap.py,sha256=JeeBY3PUcBqLYiNItEH8Ae9zhEy6cf4LldNr_4yBdjY,15991
|
|
18
|
-
annofabcli/annotation/restore_annotation.py,sha256=
|
|
18
|
+
annofabcli/annotation/restore_annotation.py,sha256=4rrMYbC29HAsCxgcdOVDfZGcJgorbKDro6ewvDBjxwo,17064
|
|
19
19
|
annofabcli/annotation/subcommand_annotation.py,sha256=PfiU4G_Col3quCXKbZpqWoUXGi5p2uB1XI9BKMwaq1I,2288
|
|
20
20
|
annofabcli/annotation_specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
annofabcli/annotation_specs/add_attribute_restriction.py,sha256=jVA7ltharabR94bf71QuW0x0yVgu6-A7PHn7Dq3kUfg,6825
|
|
@@ -33,17 +33,20 @@ annofabcli/annotation_specs/list_attribute_restriction.py,sha256=8yVt8UvB3fS6qMx
|
|
|
33
33
|
annofabcli/annotation_specs/list_label_color.py,sha256=huHBAb-LVLe-tj5ZALiBkFgZlZSTvi2ZDsPYgJPDZ2Q,2356
|
|
34
34
|
annofabcli/annotation_specs/put_label_color.py,sha256=L6kX6bqy80IQp8iZg4QqnRqzE8KPApgTyydPy_VG0dk,5990
|
|
35
35
|
annofabcli/annotation_specs/subcommand_annotation_specs.py,sha256=yqSjV1S3CSB0sFqelLBNkPy9RrVyGNRnSWCnaLTjUsc,2692
|
|
36
|
+
annofabcli/annotation_zip/__init__.py,sha256=wAeKgkUYvgzdGFS3iJ3h0WogWbg9i-0c4xSg-mzbBpY,64
|
|
37
|
+
annofabcli/annotation_zip/list_annotation_bounding_box_2d.py,sha256=fPpc1Ekk-9f3_yKSn0PD7l6JPlheOExFtbCY3_n89iY,12520
|
|
38
|
+
annofabcli/annotation_zip/subcommand_annotation_zip.py,sha256=Cx07h2hJW6o9arkAsOnTsNmaY3G20PTkGlB6jzldx6Q,1146
|
|
36
39
|
annofabcli/comment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
annofabcli/comment/delete_comment.py,sha256=
|
|
40
|
+
annofabcli/comment/delete_comment.py,sha256=HZG1BOEKEbi3ISqM-2KZS5iq6aI16jDFBRLFpT-30e4,11385
|
|
38
41
|
annofabcli/comment/download_comment_json.py,sha256=YfqUnMgvLgVFV7FJPIXwirREkQ2E63fXeCaF4hfwk8c,2338
|
|
39
42
|
annofabcli/comment/list_all_comment.py,sha256=zMZSmVe8P89WDBZE5PZn5SIv5j2OTbnP1kS-HnCxPCI,6483
|
|
40
43
|
annofabcli/comment/list_comment.py,sha256=ikXOtfI9W88w7YC1GQQ1ryq2BatHelIRb8Pa5-fKbSs,6187
|
|
41
44
|
annofabcli/comment/put_comment.py,sha256=7q_GQW7nKUqzm9tf7mjhiPHfd0lBFLFvvdyF5Cn9854,12025
|
|
42
45
|
annofabcli/comment/put_comment_simply.py,sha256=OwanjmQy5nqqIw-i0Gt9APdEdftM-EuZW4pRLYbIwQM,8171
|
|
43
|
-
annofabcli/comment/put_inspection_comment.py,sha256=
|
|
44
|
-
annofabcli/comment/put_inspection_comment_simply.py,sha256=
|
|
45
|
-
annofabcli/comment/put_onhold_comment.py,sha256=
|
|
46
|
-
annofabcli/comment/put_onhold_comment_simply.py,sha256=
|
|
46
|
+
annofabcli/comment/put_inspection_comment.py,sha256=3fVn3flI6kpyDboDx9YUWqwxLfKzuHWmNpXk7mfWZ5A,3796
|
|
47
|
+
annofabcli/comment/put_inspection_comment_simply.py,sha256=Vxd9yeC5eS9VlZedOHTsSZem26mjK1zYG2rv026peJ8,6798
|
|
48
|
+
annofabcli/comment/put_onhold_comment.py,sha256=8hrK6foOtWx3iQQyWUTN9PmgpCYsleBy3Z2yLeDYqmU,3456
|
|
49
|
+
annofabcli/comment/put_onhold_comment_simply.py,sha256=1_0l1A-zUn9Su2b87fTKznTBPIxvTMf3yNpZMI4LKzA,3265
|
|
47
50
|
annofabcli/comment/subcommand_comment.py,sha256=gd8w8ArXM1Tq9VUduDgn8m4G6HwevRWJ36VBtGHg-5I,1537
|
|
48
51
|
annofabcli/comment/utils.py,sha256=aUj7U6MtGh64F3Ko83y4NKPKyWAqcg-c1XLqjkmIpSk,350
|
|
49
52
|
annofabcli/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -62,6 +65,7 @@ annofabcli/common/utils.py,sha256=Eb4DS5j_EVdZW_YnsvIgjTbgWcjHd560plck2_WgrCI,95
|
|
|
62
65
|
annofabcli/common/visualize.py,sha256=mBPCA5vfYNbE3I1m5oHRpo-E1hsNH11KswILtYRCOKQ,13650
|
|
63
66
|
annofabcli/common/annofab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
67
|
annofabcli/common/annofab/annotation_specs.py,sha256=h-YSnuK0riCLoJMmV-KDP3JS-_JMqx0feSvPAB3xCSI,765
|
|
68
|
+
annofabcli/common/annofab/annotation_zip.py,sha256=FoKpusFfF2GoGcLLrSjfCW9AeqQXWyWXMj2iM_Nu0PU,1333
|
|
65
69
|
annofabcli/common/annofab/project.py,sha256=BK96o9OO961yfUvcjuHG1DeJ2bMMEYTS5ujtJCvD_4I,891
|
|
66
70
|
annofabcli/data/logging.yaml,sha256=kBKE59V13MtDwscHw0ecOegaU9JghkhbIABRpNis_WA,704
|
|
67
71
|
annofabcli/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -82,7 +86,7 @@ annofabcli/input_data/download_input_data_json.py,sha256=vxGoeM3ZEggQbWiWsrDK0_G
|
|
|
82
86
|
annofabcli/input_data/list_all_input_data.py,sha256=C0u2ZmB52NIgn8-S-F6GT6GFS39gLsl5cCNa_oiNKtk,9754
|
|
83
87
|
annofabcli/input_data/list_all_input_data_merged_task.py,sha256=xs1uT9_ToQNA2luLxTOuPO_AUi5WRf1uMCdoBFZIMaU,12655
|
|
84
88
|
annofabcli/input_data/list_input_data.py,sha256=Jz5tRDPBLE8CyPV85jmOGd2-aHELyAE4MGi7WVjZaDM,11335
|
|
85
|
-
annofabcli/input_data/put_input_data.py,sha256=
|
|
89
|
+
annofabcli/input_data/put_input_data.py,sha256=D2H5mW7fOQMlJmtKLHIT2S9jc_uHdPshI4oBRXsD3To,18955
|
|
86
90
|
annofabcli/input_data/put_input_data_with_zip.py,sha256=SA4aMAwMBFgc9Lh0zmRCbmkXG4AMrcBqd5zeTSdr8lc,5566
|
|
87
91
|
annofabcli/input_data/subcommand_input_data.py,sha256=X8EoxsF6PMiKrvk_r7PIe2D0WZuaPlgLJRuTiljPIdM,2048
|
|
88
92
|
annofabcli/input_data/update_metadata_of_input_data.py,sha256=E4E-FZaQBmhmhMhddehhtVLxS26TsehZzHRI6Ccr8pc,11460
|
|
@@ -137,7 +141,7 @@ annofabcli/stat_visualization/write_performance_rating_csv.py,sha256=-eC_Nv9ohrL
|
|
|
137
141
|
annofabcli/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
138
142
|
annofabcli/statistics/histogram.py,sha256=CvzDxT2cKLSnBGSqkZE6p92PayGxYYja1YyB24M4ALU,3245
|
|
139
143
|
annofabcli/statistics/linegraph.py,sha256=0kr7jVBNMiM2ECYhv3Ry5RitElKerSl9ZKxbKzfiplI,12494
|
|
140
|
-
annofabcli/statistics/list_annotation_area.py,sha256=
|
|
144
|
+
annofabcli/statistics/list_annotation_area.py,sha256=ChBWz-LcGkdNV7J_qg4JeE7b7WdKyNgyfRjoW3IJfvw,12390
|
|
141
145
|
annofabcli/statistics/list_annotation_attribute.py,sha256=lmvT3b1mVX4PRGWrBUfZafCn3c_hb_hx3WKXnHGcyyk,12754
|
|
142
146
|
annofabcli/statistics/list_annotation_attribute_filled_count.py,sha256=UF8wB-kfUnHCIPykb2Z2slyEcp9aHrzcbFbOs_3Z8-A,29341
|
|
143
147
|
annofabcli/statistics/list_annotation_count.py,sha256=4uTRR63Db-WDGRQt3AC8mPaRfFgbG5d27A036GNG3D0,53151
|
|
@@ -186,7 +190,7 @@ annofabcli/task/change_operator.py,sha256=q6pMd1SdsTRgMHS0705dnosTSHprTpYgXtNd0r
|
|
|
186
190
|
annofabcli/task/change_status_to_break.py,sha256=hwdFTFW-zV0VxuinoBB5n6mvHJ7g9ChjrSOXZcNk88w,8621
|
|
187
191
|
annofabcli/task/change_status_to_on_hold.py,sha256=vWRyk6IK3HcgTWDIbbhXzsrtuoa7OlXCf8CvUpFp_Uw,12981
|
|
188
192
|
annofabcli/task/complete_tasks.py,sha256=ssg_Z7ADRQRXvXgK2k5TEmvbRjrJQ33cXeb8kG8Y3jc,24917
|
|
189
|
-
annofabcli/task/copy_tasks.py,sha256=
|
|
193
|
+
annofabcli/task/copy_tasks.py,sha256=EcaCaSzqddlSKSUYqDOD53-r1GFpIl38KL3FriSfMeI,8735
|
|
190
194
|
annofabcli/task/delete_metadata_key_of_task.py,sha256=Cjqe3fWKeRzVxxlrGyt3TS-x1riD55LnNXLIS9JPoTw,8029
|
|
191
195
|
annofabcli/task/delete_tasks.py,sha256=7T5eNCMW06ABekNGLwhTitDK5qn0tiPKrEXyJXyQNvs,13098
|
|
192
196
|
annofabcli/task/download_task_json.py,sha256=Ocjecmdf2WV_Sq3u1InfMLIsT3XSw0ojyJmJbhv2sgg,2803
|
|
@@ -209,8 +213,8 @@ annofabcli/task_history_event/download_task_history_event_json.py,sha256=hQLVbQ0
|
|
|
209
213
|
annofabcli/task_history_event/list_all_task_history_event.py,sha256=EeKMyPUxGwYCFtWQHHW954ZserGm8lUqrwNnV1iX9X4,6830
|
|
210
214
|
annofabcli/task_history_event/list_worktime.py,sha256=Y7Pu5DP7scPf7HPt6CTiTvB1_5_Nfi1bStUIaCpkhII,15507
|
|
211
215
|
annofabcli/task_history_event/subcommand_task_history_event.py,sha256=mJVJoT4RXk4HWnY7-Nrsl4If-gtaIIEXd2z7eFZwM2I,1260
|
|
212
|
-
annofabcli-1.
|
|
213
|
-
annofabcli-1.
|
|
214
|
-
annofabcli-1.
|
|
215
|
-
annofabcli-1.
|
|
216
|
-
annofabcli-1.
|
|
216
|
+
annofabcli-1.107.0.dist-info/METADATA,sha256=naOfvMmFCACfxPn3uScdATo_vyMGHigfNxqlMgUrl6g,5134
|
|
217
|
+
annofabcli-1.107.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
218
|
+
annofabcli-1.107.0.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
|
|
219
|
+
annofabcli-1.107.0.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
|
|
220
|
+
annofabcli-1.107.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|