annofabcli 1.110.0__py3-none-any.whl → 1.111.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.
@@ -0,0 +1,317 @@
1
+ import argparse
2
+ import logging
3
+ import sys
4
+ import tempfile
5
+ from collections.abc import Collection
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any, Optional
9
+
10
+ import pandas
11
+ from annofabapi.models import InputDataType, ProjectMemberRole
12
+ from dataclasses_json import DataClassJsonMixin
13
+
14
+ import annofabcli
15
+ import annofabcli.common.cli
16
+ from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
17
+ from annofabcli.common.cli import COMMAND_LINE_ERROR_STATUS_CODE, ArgumentParser, CommandLine, build_annofabapi_resource_and_login, get_list_from_args
18
+ from annofabcli.common.download import DownloadingFile
19
+ from annofabcli.common.enums import FormatArgument
20
+ from annofabcli.common.facade import (
21
+ AnnofabApiFacade,
22
+ TaskQuery,
23
+ match_annotation_with_task_query,
24
+ )
25
+ from annofabcli.common.utils import print_csv, print_json
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class AnnotationSinglePointInfo(DataClassJsonMixin):
32
+ project_id: str
33
+ task_id: str
34
+ task_status: str
35
+ task_phase: str
36
+ task_phase_stage: int
37
+
38
+ input_data_id: str
39
+ input_data_name: str
40
+
41
+ updated_datetime: Optional[str]
42
+ """アノテーションJSONに格納されているアノテーションの更新日時"""
43
+
44
+ label: str
45
+ annotation_id: str
46
+ point: dict[str, int]
47
+
48
+
49
+ def get_annotation_single_point_info_list(simple_annotation: dict[str, Any], *, target_label_names: Optional[Collection[str]] = None) -> list[AnnotationSinglePointInfo]:
50
+ result = []
51
+ target_label_names_set = set(target_label_names) if target_label_names is not None else None
52
+ for detail in simple_annotation["details"]:
53
+ if detail["data"]["_type"] == "SinglePoint":
54
+ label = detail["label"]
55
+ # ラベル名によるフィルタリング
56
+ if target_label_names_set is not None and label not in target_label_names_set:
57
+ continue
58
+
59
+ point = detail["data"]["point"]
60
+
61
+ result.append(
62
+ AnnotationSinglePointInfo(
63
+ project_id=simple_annotation["project_id"],
64
+ task_id=simple_annotation["task_id"],
65
+ task_phase=simple_annotation["task_phase"],
66
+ task_phase_stage=simple_annotation["task_phase_stage"],
67
+ task_status=simple_annotation["task_status"],
68
+ input_data_id=simple_annotation["input_data_id"],
69
+ input_data_name=simple_annotation["input_data_name"],
70
+ label=label,
71
+ annotation_id=detail["annotation_id"],
72
+ point=point,
73
+ updated_datetime=simple_annotation["updated_datetime"],
74
+ )
75
+ )
76
+
77
+ return result
78
+
79
+
80
+ def get_annotation_single_point_info_list_from_annotation_path(
81
+ annotation_path: Path,
82
+ *,
83
+ target_task_ids: Optional[Collection[str]] = None,
84
+ task_query: Optional[TaskQuery] = None,
85
+ target_label_names: Optional[Collection[str]] = None,
86
+ ) -> list[AnnotationSinglePointInfo]:
87
+ annotation_point_list = []
88
+ target_task_ids = set(target_task_ids) if target_task_ids is not None else None
89
+ iter_parser = lazy_parse_simple_annotation_by_input_data(annotation_path)
90
+ logger.info(f"アノテーションZIPまたはディレクトリ'{annotation_path}'を読み込みます。")
91
+ for index, parser in enumerate(iter_parser):
92
+ if (index + 1) % 10000 == 0:
93
+ logger.info(f"{index + 1} 件目のJSONを読み込み中")
94
+ if target_task_ids is not None and parser.task_id not in target_task_ids:
95
+ continue
96
+ dict_simple_annotation = parser.load_json()
97
+ if task_query is not None and not match_annotation_with_task_query(dict_simple_annotation, task_query):
98
+ continue
99
+ sub_annotation_point_list = get_annotation_single_point_info_list(dict_simple_annotation, target_label_names=target_label_names)
100
+ annotation_point_list.extend(sub_annotation_point_list)
101
+ return annotation_point_list
102
+
103
+
104
+ def create_df(
105
+ annotation_point_list: list[AnnotationSinglePointInfo],
106
+ ) -> pandas.DataFrame:
107
+ columns = [
108
+ "project_id",
109
+ "task_id",
110
+ "task_status",
111
+ "task_phase",
112
+ "task_phase_stage",
113
+ "input_data_id",
114
+ "input_data_name",
115
+ "updated_datetime",
116
+ "label",
117
+ "annotation_id",
118
+ "point.x",
119
+ "point.y",
120
+ ]
121
+ df = pandas.DataFrame(
122
+ [
123
+ {
124
+ "project_id": e.project_id,
125
+ "task_id": e.task_id,
126
+ "task_status": e.task_status,
127
+ "task_phase": e.task_phase,
128
+ "task_phase_stage": e.task_phase_stage,
129
+ "input_data_id": e.input_data_id,
130
+ "input_data_name": e.input_data_name,
131
+ "updated_datetime": e.updated_datetime,
132
+ "label": e.label,
133
+ "annotation_id": e.annotation_id,
134
+ "point.x": e.point["x"],
135
+ "point.y": e.point["y"],
136
+ }
137
+ for e in annotation_point_list
138
+ ],
139
+ columns=columns,
140
+ )
141
+
142
+ return df[columns]
143
+
144
+
145
+ def print_annotation_single_point(
146
+ annotation_path: Path,
147
+ output_file: Path,
148
+ output_format: FormatArgument,
149
+ *,
150
+ target_task_ids: Optional[Collection[str]] = None,
151
+ task_query: Optional[TaskQuery] = None,
152
+ target_label_names: Optional[Collection[str]] = None,
153
+ ) -> None:
154
+ annotation_point_list = get_annotation_single_point_info_list_from_annotation_path(
155
+ annotation_path,
156
+ target_task_ids=target_task_ids,
157
+ task_query=task_query,
158
+ target_label_names=target_label_names,
159
+ )
160
+
161
+ logger.info(f"{len(annotation_point_list)} 件の点アノテーションの情報を出力します。 :: output='{output_file}'")
162
+
163
+ if output_format == FormatArgument.CSV:
164
+ df = create_df(annotation_point_list)
165
+ print_csv(df, output_file)
166
+
167
+ elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
168
+ json_is_pretty = output_format == FormatArgument.PRETTY_JSON
169
+ # DataClassJsonMixinを使用したtoJSON処理
170
+ print_json(
171
+ [e.to_dict(encode_json=True) for e in annotation_point_list],
172
+ is_pretty=json_is_pretty,
173
+ output=output_file,
174
+ )
175
+
176
+ else:
177
+ raise ValueError(f"出力形式 '{output_format}' はサポートされていません。")
178
+
179
+
180
+ class ListAnnotationSinglePoint(CommandLine):
181
+ COMMON_MESSAGE = "annofabcli annotation_zip list_single_point_annotation: error:"
182
+
183
+ def validate(self, args: argparse.Namespace) -> bool:
184
+ if args.project_id is None and args.annotation is None:
185
+ print( # noqa: T201
186
+ f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
187
+ file=sys.stderr,
188
+ )
189
+ return False
190
+ return True
191
+
192
+ def main(self) -> None:
193
+ args = self.args
194
+
195
+ if not self.validate(args):
196
+ sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
197
+
198
+ project_id: Optional[str] = args.project_id
199
+ if project_id is not None:
200
+ super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
201
+ project, _ = self.service.api.get_project(project_id)
202
+ if project["input_data_type"] != InputDataType.IMAGE.value:
203
+ print(f"project_id='{project_id}'であるプロジェクトは画像プロジェクトでないので、終了します", file=sys.stderr) # noqa: T201
204
+ sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
205
+
206
+ annotation_path = Path(args.annotation) if args.annotation is not None else None
207
+
208
+ task_id_list = get_list_from_args(args.task_id) if args.task_id is not None else None
209
+ task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
210
+ label_name_list = get_list_from_args(args.label_name) if args.label_name is not None else None
211
+
212
+ output_file: Path = args.output
213
+ output_format = FormatArgument(args.format)
214
+
215
+ downloading_obj = DownloadingFile(self.service)
216
+
217
+ def download_and_print_annotation_point(project_id: str, temp_dir: Path, *, is_latest: bool) -> None:
218
+ local_annotation_path = temp_dir / f"{project_id}__annotation.zip"
219
+ downloading_obj.download_annotation_zip(
220
+ project_id,
221
+ dest_path=local_annotation_path,
222
+ is_latest=is_latest,
223
+ )
224
+ print_annotation_single_point(
225
+ local_annotation_path,
226
+ output_file,
227
+ output_format,
228
+ target_task_ids=task_id_list,
229
+ task_query=task_query,
230
+ target_label_names=label_name_list,
231
+ )
232
+
233
+ if project_id is not None:
234
+ if args.temp_dir is not None:
235
+ download_and_print_annotation_point(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest)
236
+ else:
237
+ with tempfile.TemporaryDirectory() as str_temp_dir:
238
+ download_and_print_annotation_point(
239
+ project_id=project_id,
240
+ temp_dir=Path(str_temp_dir),
241
+ is_latest=args.latest,
242
+ )
243
+ else:
244
+ assert annotation_path is not None
245
+ print_annotation_single_point(
246
+ annotation_path,
247
+ output_file,
248
+ output_format,
249
+ target_task_ids=task_id_list,
250
+ task_query=task_query,
251
+ target_label_names=label_name_list,
252
+ )
253
+
254
+
255
+ def parse_args(parser: argparse.ArgumentParser) -> None:
256
+ argument_parser = ArgumentParser(parser)
257
+
258
+ group = parser.add_mutually_exclusive_group(required=True)
259
+ group.add_argument(
260
+ "--annotation",
261
+ type=str,
262
+ help="アノテーションzip、またはzipを展開したディレクトリを指定します。",
263
+ )
264
+
265
+ group.add_argument("-p", "--project_id", type=str, help="project_id。アノテーションZIPをダウンロードします。")
266
+
267
+ argument_parser.add_format(
268
+ choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
269
+ default=FormatArgument.CSV,
270
+ )
271
+
272
+ argument_parser.add_output()
273
+
274
+ parser.add_argument(
275
+ "-tq",
276
+ "--task_query",
277
+ type=str,
278
+ help="集計対象タスクを絞り込むためのクエリ条件をJSON形式で指定します。使用できるキーは task_id, status, phase, phase_stage です。"
279
+ " ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
280
+ )
281
+ argument_parser.add_task_id(required=False)
282
+
283
+ parser.add_argument(
284
+ "--label_name",
285
+ type=str,
286
+ nargs="*",
287
+ help="指定したラベル名の点アノテーションのみを対象にします。複数指定できます。",
288
+ )
289
+
290
+ parser.add_argument(
291
+ "--latest",
292
+ action="store_true",
293
+ help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
294
+ )
295
+
296
+ parser.add_argument(
297
+ "--temp_dir",
298
+ type=Path,
299
+ help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
300
+ )
301
+
302
+ parser.set_defaults(subcommand_func=main)
303
+
304
+
305
+ def main(args: argparse.Namespace) -> None:
306
+ service = build_annofabapi_resource_and_login(args)
307
+ facade = AnnofabApiFacade(service)
308
+ ListAnnotationSinglePoint(service, facade, args).main()
309
+
310
+
311
+ def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
312
+ subcommand_name = "list_single_point_annotation"
313
+ subcommand_help = "アノテーションZIPから点アノテーションの座標情報を出力します。"
314
+ epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
315
+ parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
316
+ parse_args(parser)
317
+ return parser
@@ -6,6 +6,7 @@ from typing import Optional
6
6
  import annofabcli
7
7
  from annofabcli.annotation_zip.list_annotation_bounding_box_2d import add_parser as add_parser_list_annotation_bounding_box_2d
8
8
  from annofabcli.annotation_zip.list_range_annotation import add_parser as add_parser_list_range_annotation
9
+ from annofabcli.annotation_zip.list_single_point_annotation import add_parser as add_parser_list_single_point_annotation
9
10
 
10
11
 
11
12
  def parse_args(parser: argparse.ArgumentParser) -> None:
@@ -19,6 +20,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
19
20
  # サブコマンドの定義
20
21
  add_parser_list_annotation_bounding_box_2d(subparsers)
21
22
  add_parser_list_range_annotation(subparsers)
23
+ add_parser_list_single_point_annotation(subparsers)
22
24
  # 作成中のためコメントアウト
23
25
  # add_parser_validate_annotation(subparsers)
24
26
 
@@ -286,7 +286,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
286
286
  "--api",
287
287
  type=str,
288
288
  choices=[e.value for e in ApiWithCreatingTask],
289
- help="タスク作成に使うWebAPIを指定できます。 ``--csv`` or ``--json`` を指定したときのみ有効なオプションです。\n未指定の場合は、作成するタスク数に応じて、適切なWebAPIを選択します。\n",
289
+ help="タスク作成に使うWebAPIを指定できます。未指定の場合は、作成するタスク数に応じて適切なWebAPIを選択します。",
290
290
  )
291
291
 
292
292
  parser.add_argument(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: annofabcli
3
- Version: 1.110.0
3
+ Version: 1.111.0
4
4
  Summary: Utility Command Line Interface for AnnoFab
5
5
  Author: Kurusugawa Computer Inc.
6
6
  License: MIT
@@ -37,7 +37,8 @@ annofabcli/annotation_specs/subcommand_annotation_specs.py,sha256=yqSjV1S3CSB0sF
37
37
  annofabcli/annotation_zip/__init__.py,sha256=wAeKgkUYvgzdGFS3iJ3h0WogWbg9i-0c4xSg-mzbBpY,64
38
38
  annofabcli/annotation_zip/list_annotation_bounding_box_2d.py,sha256=306qSxOXzXSU1AaaveWSToyBCCt3hGbTuWqBaS5wn44,13363
39
39
  annofabcli/annotation_zip/list_range_annotation.py,sha256=70U3vvb-M5jxQlUlbqX-DSenp8oxXja3tj1gzq-7_DU,12282
40
- annofabcli/annotation_zip/subcommand_annotation_zip.py,sha256=SBpdjNhp4s-u4DBjpl5_OR91GbW5hUAi3kt5dhNbyA4,1397
40
+ annofabcli/annotation_zip/list_single_point_annotation.py,sha256=qW5i4pozl9wx2BPue9rbQX8zvdyTVRfaeQzjhOf-OUU,12575
41
+ annofabcli/annotation_zip/subcommand_annotation_zip.py,sha256=xU8WGXRhSh3gjOpOMD2qLMeN3rtPnE7glSsKBLIBXjE,1574
41
42
  annofabcli/annotation_zip/validate_annotation.py,sha256=Kf1p312CINbZZTbniVAphQrcrMmr5WGQZja30kc7ewc,16645
42
43
  annofabcli/comment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
44
  annofabcli/comment/delete_comment.py,sha256=HZG1BOEKEbi3ISqM-2KZS5iq6aI16jDFBRLFpT-30e4,11385
@@ -205,7 +206,7 @@ annofabcli/task/list_all_tasks.py,sha256=FpFzosCZs2EZNSQMHzWse4z_zjZeiVF5BFmbwb9
205
206
  annofabcli/task/list_all_tasks_added_task_history.py,sha256=fkdiuo64iS7xxvIfGKzSiUPPEMiCVnJjjcAtMxe2Ngs,9551
206
207
  annofabcli/task/list_tasks.py,sha256=cmfqbP2Sr1dlpvpJLJscKq31BUdXVaajr_agiecKWXg,10051
207
208
  annofabcli/task/list_tasks_added_task_history.py,sha256=JI3Rxn9MwXN07Cu2wDfaIXAzm3MlNm1kIh1SZHbxZr4,22488
208
- annofabcli/task/put_tasks.py,sha256=HADm-sP8zs523MlDThlwrGIVIEtR4Md2KlpJbWhCCTk,13745
209
+ annofabcli/task/put_tasks.py,sha256=tqgGtaYkAwTznLhcvl9eZNS2U6eqsoqSbL67jBqyfJk,13653
209
210
  annofabcli/task/put_tasks_by_count.py,sha256=kIjvWtgZ53Smcm8N-raZedQCubx1TWoQT-BYyioYDrA,5999
210
211
  annofabcli/task/reject_tasks.py,sha256=2mRxIsP63N2xcVXQ0xeFqF0VkuUQP6C_bsFEqG-qNf0,22027
211
212
  annofabcli/task/subcommand_task.py,sha256=L_5Dwe58eblrtOrUYxjJAvkSmu6savRUxIqGjsFq-R4,2436
@@ -220,8 +221,8 @@ annofabcli/task_history_event/download_task_history_event_json.py,sha256=hQLVbQ0
220
221
  annofabcli/task_history_event/list_all_task_history_event.py,sha256=EeKMyPUxGwYCFtWQHHW954ZserGm8lUqrwNnV1iX9X4,6830
221
222
  annofabcli/task_history_event/list_worktime.py,sha256=Y7Pu5DP7scPf7HPt6CTiTvB1_5_Nfi1bStUIaCpkhII,15507
222
223
  annofabcli/task_history_event/subcommand_task_history_event.py,sha256=mJVJoT4RXk4HWnY7-Nrsl4If-gtaIIEXd2z7eFZwM2I,1260
223
- annofabcli-1.110.0.dist-info/METADATA,sha256=IkiHLkw9nFBKWbtVvoy_aedwGZ7Xl0d-gFTF4xDNnSE,5134
224
- annofabcli-1.110.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
225
- annofabcli-1.110.0.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
226
- annofabcli-1.110.0.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
227
- annofabcli-1.110.0.dist-info/RECORD,,
224
+ annofabcli-1.111.0.dist-info/METADATA,sha256=8icfwwpFiUk3CLmu_RvCrllfkQnP93ITWhkKwEY_adg,5134
225
+ annofabcli-1.111.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
226
+ annofabcli-1.111.0.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
227
+ annofabcli-1.111.0.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
228
+ annofabcli-1.111.0.dist-info/RECORD,,