annofabcli 1.106.8__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
@@ -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.8
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
5
+ annofabcli/annotation/annotation_query.py,sha256=jd69b_QBw8fy0i_bk-3MGNarS_W0XjCDJa1T4GSZjvk,15730
6
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.8.dist-info/METADATA,sha256=rSWCLpho30p_wsQdKax1vujJoZ3Zlj03RAGDGcth2s8,5286
213
- annofabcli-1.106.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
214
- annofabcli-1.106.8.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
215
- annofabcli-1.106.8.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
216
- annofabcli-1.106.8.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,,