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 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/#section/AnnotationQuery )に渡すdictに変換する。
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/#section/AnnotationQuery に対応しています。
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
- is_force: bool,
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.is_force = is_force
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
- アノテーションの属性を変更するAPI ``change_annotation_attributes`` を実行したか否か
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.is_force: # noqa: SIM102
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}'のアノテーションの属性の変更に失敗しました。", exc_info=True)
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
- result_bool_list = pool.map(func, enumerate(task_id_list))
223
- success_count = len([e for e in result_bool_list if e])
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}'のアノテーションの属性の変更に失敗しました。", exc_info=True)
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.force: # noqa: SIM102
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 --force : '--force' 引数を利用するにはプロジェクトのオーナーロールを持つユーザーで実行する必要があります。",
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, is_force=args.force, all_yes=args.yes)
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
- "--force",
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
- "間違えてアノテーション属性を変更したときに復元できるようにするため、 ``--backup`` でバックアップ用のディレクトリを指定することを推奨します。"
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} のアノテーションZIPをダウンロードします。")
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をダウンロードしました。output={output_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) -> bool:
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 False
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 False
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 False
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
- return True
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
- success_count = 0
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
- if self.put_annotation_for_input_data(parser):
460
- success_count += 1
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 success_count
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
- result_count = self.put_annotation_for_task(task_parser)
519
- logger.info(f"{logger_prefix}タスク'{task_parser.task_id}'の入力データ {result_count} 個に対してアノテーションをインポートしました。")
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 result_count > 0
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'を指定するときは、必ず '--yes' を指定してください。",
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'を指定するときは、必ず '--yes' を指定してください。",
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'を指定するときは、必ず '--yes' を指定してください。",
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="使用するプロセス数(並列度)を指定してください。指定する場合は必ず '--yes' を指定してください。指定しない場合は、逐次的に処理します。",
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'を指定するときは、必ず '--yes' を指定してください。",
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'を指定するときは、必ず '--yes' を指定してください。",
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'を指定するときは、必ず '--yes' を指定してください。",
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'を指定するときは、必ず '--yes' を指定してください。",
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(f"{log_message_prefix}input_data_id='{input_data.input_data_id}' がすでに存在するのでスキップします。")
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} に、{len(input_data_list)} 件の入力データを登録します。")
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} に、{count_put_input_data} / {len(input_data_list)} 件の入力データを登録しました。")
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'を指定するときは、必ず '--yes' を指定してください。",
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.add_argument(
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
- parser.add_argument(
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],
@@ -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'を指定するときは、必ず '--yes' を指定してください。",
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.106.7
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
- [![Build Status](https://app.travis-ci.com/kurusugawa-computer/annofab-cli.svg?branch=main)](https://app.travis-ci.com/kurusugawa-computer/annofab-cli)
41
40
  [![PyPI version](https://badge.fury.io/py/annofabcli.svg)](https://badge.fury.io/py/annofabcli)
42
41
  [![Python Versions](https://img.shields.io/pypi/pyversions/annofabcli.svg)](https://pypi.org/project/annofabcli/)
43
42
  [![Documentation Status](https://readthedocs.org/projects/annofab-cli/badge/?version=latest)](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=YfuJE9E43xSo6iHTxVuQPHCz2eBaJS07QnVU42-0znQ,5293
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=VwfPWpLOpVa2SeEJ264LmCKkBGDJvpX8o7GbWIrDE0o,15712
6
- annofabcli/annotation/change_annotation_attributes.py,sha256=Zjqax-sb7ujJnTUVv4io0GFmZzfqx0rpN2N1W86uVTs,17092
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=P_ZpdqIaSFEmB8jjpdykcRhh2tVlHxSlXFrYreJjShE,3282
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=w0iSTmkIY8tz3cTUy2FJ6LCVpVUtKzcD7ej2cznot4A,34533
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=F0_p1k0Yzx0YVOuLYOS48OWutsN5ysOu3TRa3yhRdTw,17071
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=NpQVrXrIPI3vjAyOZ-5Mm-Pae_uCHu8BXVEA06QzKTU,11399
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=TL8o_K9LCLxqMEVPs9wNK_X4jzEiJwjr327lziQbeAM,3803
44
- annofabcli/comment/put_inspection_comment_simply.py,sha256=WuySt-BV4oPETBIgLwpCIH-hh8XRetIHPb6Gtnpgs7A,6805
45
- annofabcli/comment/put_onhold_comment.py,sha256=u1DZyUOK6dgCHN46BP-GujCUBgQ-zY-maYAUaW4_I7E,3463
46
- annofabcli/comment/put_onhold_comment_simply.py,sha256=7A0MWk98_Iv4CdQPN2McgU2cQkIej1f1JiRt5dsq6Z0,3272
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=3rr5sWGZvlureGyZ4Fga3cfKQOwirLa_cnE7yhybZk0,18600
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=RC6Pkhh_ps8fMZuDSQqhk2RdMpJ_z5TA9ax42tuJRFM,12567
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=66C1NQq46d6HenLpQCnkTjiXedL4iVtCu6qUimosVyU,8742
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.106.7.dist-info/METADATA,sha256=3zYEhS25YYDmpQ1FV9qAyccpPSLvajgcXFrydflKzUE,5286
213
- annofabcli-1.106.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
214
- annofabcli-1.106.7.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
215
- annofabcli-1.106.7.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
216
- annofabcli-1.106.7.dist-info/RECORD,,
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,,