annofabcli 1.114.4__py3-none-any.whl → 1.114.6__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/annotation_zip/list_annotation_3d_bounding_box.py +2 -2
- annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +2 -2
- annofabcli/annotation_zip/list_polygon_annotation.py +2 -2
- annofabcli/annotation_zip/list_polyline_annotation.py +2 -2
- annofabcli/annotation_zip/list_range_annotation.py +2 -2
- annofabcli/annotation_zip/list_single_point_annotation.py +2 -2
- annofabcli/comment/delete_comment.py +2 -2
- annofabcli/comment/put_comment.py +145 -19
- annofabcli/comment/put_comment_simply.py +7 -4
- annofabcli/comment/put_inspection_comment.py +5 -1
- annofabcli/comment/put_onhold_comment.py +5 -1
- annofabcli/input_data/list_all_input_data_merged_task.py +3 -3
- annofabcli/statistics/list_annotation_count.py +525 -162
- annofabcli/statistics/list_annotation_duration.py +1 -1
- annofabcli/statistics/list_video_duration.py +1 -1
- {annofabcli-1.114.4.dist-info → annofabcli-1.114.6.dist-info}/METADATA +1 -1
- {annofabcli-1.114.4.dist-info → annofabcli-1.114.6.dist-info}/RECORD +20 -20
- {annofabcli-1.114.4.dist-info → annofabcli-1.114.6.dist-info}/WHEEL +0 -0
- {annofabcli-1.114.4.dist-info → annofabcli-1.114.6.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.114.4.dist-info → annofabcli-1.114.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,7 +7,6 @@ import collections
|
|
|
7
7
|
import copy
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
-
import sys
|
|
11
10
|
import tempfile
|
|
12
11
|
import zipfile
|
|
13
12
|
from collections import Counter, defaultdict
|
|
@@ -34,7 +33,6 @@ from dataclasses_json import DataClassJsonMixin, config
|
|
|
34
33
|
|
|
35
34
|
import annofabcli.common.cli
|
|
36
35
|
from annofabcli.common.cli import (
|
|
37
|
-
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
38
36
|
ArgumentParser,
|
|
39
37
|
CommandLine,
|
|
40
38
|
build_annofabapi_resource_and_login,
|
|
@@ -103,6 +101,15 @@ def encode_annotation_count_by_attribute(
|
|
|
103
101
|
|
|
104
102
|
@dataclass(frozen=True)
|
|
105
103
|
class AnnotationCounter(abc.ABC):
|
|
104
|
+
"""
|
|
105
|
+
アノテーションの集計情報を保持する抽象基底クラス。
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
annotation_count: アノテーションの総数
|
|
109
|
+
annotation_count_by_label: ラベルごとのアノテーション数
|
|
110
|
+
annotation_count_by_attribute: 属性値ごとのアノテーション数
|
|
111
|
+
"""
|
|
112
|
+
|
|
106
113
|
annotation_count: int
|
|
107
114
|
annotation_count_by_label: Counter[str]
|
|
108
115
|
annotation_count_by_attribute: Counter[AttributeValueKey] = field(
|
|
@@ -114,6 +121,18 @@ class AnnotationCounter(abc.ABC):
|
|
|
114
121
|
|
|
115
122
|
@dataclass(frozen=True)
|
|
116
123
|
class AnnotationCounterByTask(AnnotationCounter, DataClassJsonMixin):
|
|
124
|
+
"""
|
|
125
|
+
タスク単位でのアノテーション集計情報。
|
|
126
|
+
|
|
127
|
+
Attributes:
|
|
128
|
+
project_id: プロジェクトID
|
|
129
|
+
task_id: タスクID
|
|
130
|
+
task_status: タスクのステータス
|
|
131
|
+
task_phase: タスクのフェーズ
|
|
132
|
+
task_phase_stage: タスクフェーズのステージ番号
|
|
133
|
+
input_data_count: タスクに含まれる入力データの数
|
|
134
|
+
"""
|
|
135
|
+
|
|
117
136
|
project_id: str
|
|
118
137
|
task_id: str
|
|
119
138
|
task_status: TaskStatus
|
|
@@ -124,6 +143,21 @@ class AnnotationCounterByTask(AnnotationCounter, DataClassJsonMixin):
|
|
|
124
143
|
|
|
125
144
|
@dataclass(frozen=True)
|
|
126
145
|
class AnnotationCounterByInputData(AnnotationCounter, DataClassJsonMixin):
|
|
146
|
+
"""
|
|
147
|
+
入力データ単位でのアノテーション集計情報。
|
|
148
|
+
|
|
149
|
+
Attributes:
|
|
150
|
+
project_id: プロジェクトID
|
|
151
|
+
task_id: タスクID
|
|
152
|
+
task_status: タスクのステータス
|
|
153
|
+
task_phase: タスクのフェーズ
|
|
154
|
+
task_phase_stage: タスクフェーズのステージ番号
|
|
155
|
+
input_data_id: 入力データID
|
|
156
|
+
input_data_name: 入力データ名
|
|
157
|
+
updated_datetime: アノテーションJSONに格納されているアノテーションの更新日時
|
|
158
|
+
frame_no: フレーム番号。アノテーションJSONには含まれていない情報なので、Optionalにする
|
|
159
|
+
"""
|
|
160
|
+
|
|
127
161
|
project_id: str
|
|
128
162
|
task_id: str
|
|
129
163
|
task_status: TaskStatus
|
|
@@ -139,6 +173,18 @@ class AnnotationCounterByInputData(AnnotationCounter, DataClassJsonMixin):
|
|
|
139
173
|
|
|
140
174
|
|
|
141
175
|
def lazy_parse_simple_annotation_by_input_data(annotation_path: Path) -> Iterator[SimpleAnnotationParser]:
|
|
176
|
+
"""
|
|
177
|
+
アノテーションzipまたはディレクトリから、入力データ単位でアノテーションを遅延パースするイテレータを返します。
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
SimpleAnnotationParserのイテレータ
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
RuntimeError: 指定されたパスが存在しない場合、またはzipファイルでもディレクトリでもない場合
|
|
187
|
+
"""
|
|
142
188
|
if not annotation_path.exists():
|
|
143
189
|
raise RuntimeError(f"'{annotation_path}' は存在しません。")
|
|
144
190
|
|
|
@@ -151,6 +197,18 @@ def lazy_parse_simple_annotation_by_input_data(annotation_path: Path) -> Iterato
|
|
|
151
197
|
|
|
152
198
|
|
|
153
199
|
def lazy_parse_simple_annotation_by_task(annotation_path: Path) -> Iterator[SimpleAnnotationParserByTask]:
|
|
200
|
+
"""
|
|
201
|
+
アノテーションzipまたはディレクトリから、タスク単位でアノテーションを遅延パースするイテレータを返します。
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
SimpleAnnotationParserByTaskのイテレータ
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
RuntimeError: 指定されたパスが存在しない場合、またはzipファイルでもディレクトリでもない場合
|
|
211
|
+
"""
|
|
154
212
|
if not annotation_path.exists():
|
|
155
213
|
raise RuntimeError(f"'{annotation_path}' は存在しません。")
|
|
156
214
|
|
|
@@ -166,12 +224,17 @@ class ListAnnotationCounterByInputData:
|
|
|
166
224
|
"""入力データ単位で、ラベルごと/属性ごとのアノテーション数を集計情報を取得するメソッドの集まり。
|
|
167
225
|
|
|
168
226
|
Args:
|
|
169
|
-
target_labels: 集計対象のラベル(label_name_en
|
|
170
|
-
target_attribute_names:
|
|
171
|
-
non_target_labels:
|
|
172
|
-
non_target_attribute_names:
|
|
227
|
+
target_labels: 集計対象のラベル(label_name_en)。`non_target_labels`との同時指定不可。
|
|
228
|
+
target_attribute_names: 集計対象の属性名。`non_target_attribute_names`との同時指定不可。
|
|
229
|
+
non_target_labels: 集計対象外のラベル。`target_labels`との同時指定不可。
|
|
230
|
+
non_target_attribute_names: 集計対象外の属性名のキー。`target_attribute_names`との同時指定不可。
|
|
231
|
+
target_attribute_names_only: 集計対象の属性名(属性名のみでフィルタリング)。ラベル名に関係なく、指定された属性名のみが対象になります。
|
|
173
232
|
frame_no_map: key:task_id,input_data_idのtuple, value:フレーム番号
|
|
174
233
|
|
|
234
|
+
Raises:
|
|
235
|
+
ValueError: `target_labels`と`non_target_labels`の両方が指定された場合、
|
|
236
|
+
または`target_attribute_names`と`non_target_attribute_names`の両方が指定された場合に発生します。
|
|
237
|
+
|
|
175
238
|
"""
|
|
176
239
|
|
|
177
240
|
def __init__(
|
|
@@ -181,12 +244,19 @@ class ListAnnotationCounterByInputData:
|
|
|
181
244
|
non_target_labels: Collection[str] | None = None,
|
|
182
245
|
target_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
183
246
|
non_target_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
247
|
+
target_attribute_names_only: Collection[str] | None = None,
|
|
184
248
|
frame_no_map: dict[tuple[str, str], int] | None = None,
|
|
185
249
|
) -> None:
|
|
250
|
+
if target_labels is not None and non_target_labels is not None:
|
|
251
|
+
raise ValueError("`target_labels`と`non_target_labels`の両方が指定されています。")
|
|
252
|
+
if target_attribute_names is not None and non_target_attribute_names is not None:
|
|
253
|
+
raise ValueError("`target_attribute_names`と`non_target_attribute_names`の両方が指定されています。")
|
|
254
|
+
|
|
186
255
|
self.target_labels = set(target_labels) if target_labels is not None else None
|
|
187
256
|
self.target_attribute_names = set(target_attribute_names) if target_attribute_names is not None else None
|
|
188
257
|
self.non_target_labels = set(non_target_labels) if non_target_labels is not None else None
|
|
189
258
|
self.non_target_attribute_names = set(non_target_attribute_names) if non_target_attribute_names is not None else None
|
|
259
|
+
self.target_attribute_names_only = set(target_attribute_names_only) if target_attribute_names_only is not None else None
|
|
190
260
|
self.frame_no_map = frame_no_map
|
|
191
261
|
|
|
192
262
|
def get_annotation_counter(
|
|
@@ -241,6 +311,14 @@ class ListAnnotationCounterByInputData:
|
|
|
241
311
|
if (label, attribute_name) in self.target_attribute_names
|
|
242
312
|
}
|
|
243
313
|
)
|
|
314
|
+
if self.target_attribute_names_only is not None:
|
|
315
|
+
annotation_count_by_attribute = collections.Counter(
|
|
316
|
+
{
|
|
317
|
+
(label, attribute_name, attribute_value): count
|
|
318
|
+
for (label, attribute_name, attribute_value), count in annotation_count_by_attribute.items()
|
|
319
|
+
if attribute_name in self.target_attribute_names_only
|
|
320
|
+
}
|
|
321
|
+
)
|
|
244
322
|
if self.non_target_attribute_names is not None:
|
|
245
323
|
annotation_count_by_attribute = collections.Counter(
|
|
246
324
|
{
|
|
@@ -309,7 +387,20 @@ class ListAnnotationCounterByInputData:
|
|
|
309
387
|
|
|
310
388
|
|
|
311
389
|
class ListAnnotationCounterByTask:
|
|
312
|
-
"""タスク単位で、ラベルごと/属性ごとのアノテーション数を集計情報を取得するメソッドの集まり。
|
|
390
|
+
"""タスク単位で、ラベルごと/属性ごとのアノテーション数を集計情報を取得するメソッドの集まり。
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
target_labels: 集計対象のラベル(label_name_en)。`non_target_labels`との同時指定不可。
|
|
394
|
+
target_attribute_names: 集計対象の属性名。`non_target_attribute_names`との同時指定不可。
|
|
395
|
+
non_target_labels: 集計対象外のラベル。`target_labels`との同時指定不可。
|
|
396
|
+
non_target_attribute_names: 集計対象外の属性名のキー。`target_attribute_names`との同時指定不可。
|
|
397
|
+
target_attribute_names_only: 集計対象の属性名(属性名のみでフィルタリング)。ラベル名に関係なく、指定された属性名のみが対象になります。
|
|
398
|
+
|
|
399
|
+
Raises:
|
|
400
|
+
ValueError: `target_labels`と`non_target_labels`の両方が指定された場合、
|
|
401
|
+
または`target_attribute_names`と`non_target_attribute_names`の両方が指定された場合に発生します。
|
|
402
|
+
|
|
403
|
+
"""
|
|
313
404
|
|
|
314
405
|
def __init__(
|
|
315
406
|
self,
|
|
@@ -318,12 +409,19 @@ class ListAnnotationCounterByTask:
|
|
|
318
409
|
non_target_labels: Collection[str] | None = None,
|
|
319
410
|
target_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
320
411
|
non_target_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
412
|
+
target_attribute_names_only: Collection[str] | None = None,
|
|
321
413
|
) -> None:
|
|
414
|
+
if target_labels is not None and non_target_labels is not None:
|
|
415
|
+
raise ValueError("`target_labels`と`non_target_labels`の両方が指定されています。")
|
|
416
|
+
if target_attribute_names is not None and non_target_attribute_names is not None:
|
|
417
|
+
raise ValueError("`target_attribute_names`と`non_target_attribute_names`の両方が指定されています。")
|
|
418
|
+
|
|
322
419
|
self.counter_by_input_data = ListAnnotationCounterByInputData(
|
|
323
420
|
target_labels=target_labels,
|
|
324
421
|
non_target_labels=non_target_labels,
|
|
325
422
|
target_attribute_names=target_attribute_names,
|
|
326
423
|
non_target_attribute_names=non_target_attribute_names,
|
|
424
|
+
target_attribute_names_only=target_attribute_names_only,
|
|
327
425
|
)
|
|
328
426
|
|
|
329
427
|
def get_annotation_counter(self, task_parser: SimpleAnnotationParserByTask) -> AnnotationCounterByTask:
|
|
@@ -411,41 +509,31 @@ class ListAnnotationCounterByTask:
|
|
|
411
509
|
class AttributeCountCsv:
|
|
412
510
|
"""
|
|
413
511
|
属性値ごとのアノテーション数を記載するCSV。
|
|
414
|
-
|
|
415
|
-
Args:
|
|
416
|
-
selective_attribute_value_max_count: 選択肢系の属性の値の個数の上限。これを超えた場合は、非選択肢系属性(トラッキングIDやアノテーションリンクなど)とみなす
|
|
417
|
-
|
|
418
512
|
"""
|
|
419
513
|
|
|
420
|
-
def
|
|
421
|
-
self
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
選択肢系の属性に対応する列のみ抽出する。
|
|
426
|
-
属性値の個数が多い場合、非選択肢系の属性(トラッキングIDやアノテーションリンクなど)の可能性があるため、それらを除外する。
|
|
427
|
-
CSVの列数を増やしすぎないための対策。
|
|
514
|
+
def _value_columns(
|
|
515
|
+
self,
|
|
516
|
+
counter_list: Collection[AnnotationCounter],
|
|
517
|
+
prior_attribute_columns: list[AttributeValueKey] | None,
|
|
518
|
+
) -> list[AttributeValueKey]:
|
|
428
519
|
"""
|
|
429
|
-
|
|
430
|
-
for label, attribute_name, _ in columns:
|
|
431
|
-
attribute_name_list.append((label, attribute_name))
|
|
520
|
+
CSV出力のための属性値列の順序を決定します。
|
|
432
521
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return [(label, attribute_name, attribute_value) for (label, attribute_name, attribute_value) in columns if (label, attribute_name) not in non_selective_attribute_names]
|
|
522
|
+
Args:
|
|
523
|
+
counter_list: アノテーション集計情報のコレクション
|
|
524
|
+
prior_attribute_columns: 優先的に配置する属性値のキーのリスト。Noneの場合はソート順で配置されます。
|
|
438
525
|
|
|
439
|
-
|
|
526
|
+
Returns:
|
|
527
|
+
属性値のキーのリスト(CSV列の順序)
|
|
528
|
+
"""
|
|
440
529
|
all_attr_key_set = {attr_key for c in counter_list for attr_key in c.annotation_count_by_attribute}
|
|
441
530
|
if prior_attribute_columns is not None:
|
|
442
531
|
remaining_columns = sorted(all_attr_key_set - set(prior_attribute_columns))
|
|
443
|
-
remaining_columns_only_selective_attribute = self._only_selective_attribute(remaining_columns)
|
|
444
532
|
|
|
445
|
-
# `
|
|
446
|
-
# `
|
|
533
|
+
# `remaining_columns`には、属性値が空である列などが格納されている
|
|
534
|
+
# `remaining_columns`を、`value_columns`の関連している位置に挿入する。
|
|
447
535
|
value_columns = copy.deepcopy(prior_attribute_columns)
|
|
448
|
-
for remaining_column in
|
|
536
|
+
for remaining_column in remaining_columns:
|
|
449
537
|
is_inserted = False
|
|
450
538
|
for i in range(len(value_columns) - 1, -1, -1):
|
|
451
539
|
col = value_columns[i]
|
|
@@ -456,11 +544,11 @@ class AttributeCountCsv:
|
|
|
456
544
|
if not is_inserted:
|
|
457
545
|
value_columns.append(remaining_column)
|
|
458
546
|
|
|
459
|
-
assert len(value_columns) == len(prior_attribute_columns) + len(
|
|
547
|
+
assert len(value_columns) == len(prior_attribute_columns) + len(remaining_columns)
|
|
460
548
|
|
|
461
549
|
else:
|
|
462
550
|
remaining_columns = sorted(all_attr_key_set)
|
|
463
|
-
value_columns =
|
|
551
|
+
value_columns = remaining_columns
|
|
464
552
|
|
|
465
553
|
# 重複している場合は、重複要素を取り除く。ただし元の順番は維持する
|
|
466
554
|
value_columns = list(dict.fromkeys(value_columns).keys())
|
|
@@ -472,13 +560,22 @@ class AttributeCountCsv:
|
|
|
472
560
|
output_file: Path,
|
|
473
561
|
prior_attribute_columns: list[AttributeValueKey] | None = None,
|
|
474
562
|
) -> None:
|
|
563
|
+
"""
|
|
564
|
+
タスク単位の属性値ごとアノテーション数をCSVファイルに出力します。
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
counter_list: タスク単位のアノテーション集計情報のリスト
|
|
568
|
+
output_file: 出力先CSVファイルのパス
|
|
569
|
+
prior_attribute_columns: 優先的に配置する属性値のキーのリスト
|
|
570
|
+
"""
|
|
571
|
+
|
|
475
572
|
def get_columns() -> list[AttributeValueKey]:
|
|
476
573
|
basic_columns = [
|
|
477
574
|
("project_id", "", ""),
|
|
478
575
|
("task_id", "", ""),
|
|
479
|
-
("task_status", "", ""),
|
|
480
576
|
("task_phase", "", ""),
|
|
481
577
|
("task_phase_stage", "", ""),
|
|
578
|
+
("task_status", "", ""),
|
|
482
579
|
("input_data_count", "", ""),
|
|
483
580
|
("annotation_count", "", ""),
|
|
484
581
|
]
|
|
@@ -512,13 +609,22 @@ class AttributeCountCsv:
|
|
|
512
609
|
output_file: Path,
|
|
513
610
|
prior_attribute_columns: list[AttributeValueKey] | None = None,
|
|
514
611
|
) -> None:
|
|
612
|
+
"""
|
|
613
|
+
入力データ単位の属性値ごとアノテーション数をCSVファイルに出力します。
|
|
614
|
+
|
|
615
|
+
Args:
|
|
616
|
+
counter_list: 入力データ単位のアノテーション集計情報のリスト
|
|
617
|
+
output_file: 出力先CSVファイルのパス
|
|
618
|
+
prior_attribute_columns: 優先的に配置する属性値のキーのリスト
|
|
619
|
+
"""
|
|
620
|
+
|
|
515
621
|
def get_columns() -> list[AttributeValueKey]:
|
|
516
622
|
basic_columns = [
|
|
517
623
|
("project_id", "", ""),
|
|
518
624
|
("task_id", "", ""),
|
|
519
|
-
("task_status", "", ""),
|
|
520
625
|
("task_phase", "", ""),
|
|
521
626
|
("task_phase_stage", "", ""),
|
|
627
|
+
("task_status", "", ""),
|
|
522
628
|
("input_data_id", "", ""),
|
|
523
629
|
("input_data_name", "", ""),
|
|
524
630
|
("frame_no", "", ""),
|
|
@@ -563,6 +669,16 @@ class LabelCountCsv:
|
|
|
563
669
|
"""
|
|
564
670
|
|
|
565
671
|
def _value_columns(self, counter_list: Collection[AnnotationCounter], prior_label_columns: list[str] | None) -> list[str]:
|
|
672
|
+
"""
|
|
673
|
+
CSV出力のためのラベル列の順序を決定します。
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
counter_list: アノテーション集計情報のコレクション
|
|
677
|
+
prior_label_columns: 優先的に配置するラベル名のリスト。Noneの場合はソート順で配置されます。
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
ラベル名のリスト(CSV列の順序)
|
|
681
|
+
"""
|
|
566
682
|
all_attr_key_set = {attr_key for c in counter_list for attr_key in c.annotation_count_by_label}
|
|
567
683
|
if prior_label_columns is not None:
|
|
568
684
|
remaining_columns = sorted(all_attr_key_set - set(prior_label_columns))
|
|
@@ -579,13 +695,22 @@ class LabelCountCsv:
|
|
|
579
695
|
output_file: Path,
|
|
580
696
|
prior_label_columns: list[str] | None = None,
|
|
581
697
|
) -> None:
|
|
698
|
+
"""
|
|
699
|
+
タスク単位のラベルごとアノテーション数をCSVファイルに出力します。
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
counter_list: タスク単位のアノテーション集計情報のリスト
|
|
703
|
+
output_file: 出力先CSVファイルのパス
|
|
704
|
+
prior_label_columns: 優先的に配置するラベル名のリスト
|
|
705
|
+
"""
|
|
706
|
+
|
|
582
707
|
def get_columns() -> list[str]:
|
|
583
708
|
basic_columns = [
|
|
584
709
|
"project_id",
|
|
585
710
|
"task_id",
|
|
586
|
-
"task_status",
|
|
587
711
|
"task_phase",
|
|
588
712
|
"task_phase_stage",
|
|
713
|
+
"task_status",
|
|
589
714
|
"input_data_count",
|
|
590
715
|
"annotation_count",
|
|
591
716
|
]
|
|
@@ -619,13 +744,22 @@ class LabelCountCsv:
|
|
|
619
744
|
output_file: Path,
|
|
620
745
|
prior_label_columns: list[str] | None = None,
|
|
621
746
|
) -> None:
|
|
747
|
+
"""
|
|
748
|
+
入力データ単位のラベルごとアノテーション数をCSVファイルに出力します。
|
|
749
|
+
|
|
750
|
+
Args:
|
|
751
|
+
counter_list: 入力データ単位のアノテーション集計情報のリスト
|
|
752
|
+
output_file: 出力先CSVファイルのパス
|
|
753
|
+
prior_label_columns: 優先的に配置するラベル名のリスト
|
|
754
|
+
"""
|
|
755
|
+
|
|
622
756
|
def get_columns() -> list[str]:
|
|
623
757
|
basic_columns = [
|
|
624
758
|
"project_id",
|
|
625
759
|
"task_id",
|
|
626
|
-
"task_status",
|
|
627
760
|
"task_phase",
|
|
628
761
|
"task_phase_stage",
|
|
762
|
+
"task_status",
|
|
629
763
|
"input_data_id",
|
|
630
764
|
"input_data_name",
|
|
631
765
|
"frame_no",
|
|
@@ -736,16 +870,15 @@ class AnnotationSpecs:
|
|
|
736
870
|
|
|
737
871
|
return result
|
|
738
872
|
|
|
739
|
-
def
|
|
873
|
+
def get_attribute_value_keys_for_target_attributes(self, target_attribute_names: Collection[AttributeNameKey]) -> list[AttributeValueKey]:
|
|
740
874
|
"""
|
|
741
|
-
|
|
875
|
+
指定された属性名のキーに対応する属性値のキーの一覧を取得します。
|
|
742
876
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
* ラジオボタン
|
|
746
|
-
* チェックボックス
|
|
877
|
+
Args:
|
|
878
|
+
target_attribute_names: 属性値のキーを取得する対象の属性名のキー。
|
|
747
879
|
"""
|
|
748
880
|
target_attribute_value_keys = []
|
|
881
|
+
target_attribute_names_set = set(target_attribute_names)
|
|
749
882
|
for label in self._labels_v1:
|
|
750
883
|
label_name_en = AddProps.get_message(label["label_name"], MessageLocale.EN)
|
|
751
884
|
label_name_en = label_name_en if label_name_en is not None else ""
|
|
@@ -754,20 +887,33 @@ class AnnotationSpecs:
|
|
|
754
887
|
attribute_name_en = AddProps.get_message(attribute["name"], MessageLocale.EN)
|
|
755
888
|
attribute_name_en = attribute_name_en if attribute_name_en is not None else ""
|
|
756
889
|
|
|
757
|
-
if
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
]
|
|
890
|
+
if (label_name_en, attribute_name_en) not in target_attribute_names_set:
|
|
891
|
+
continue
|
|
892
|
+
|
|
893
|
+
attribute_type = AdditionalDataDefinitionType(attribute["type"])
|
|
894
|
+
if attribute_type in [AdditionalDataDefinitionType.CHOICE, AdditionalDataDefinitionType.SELECT]:
|
|
761
895
|
for choice in attribute["choices"]:
|
|
762
896
|
choice_name_en = AddProps.get_message(choice["name"], MessageLocale.EN)
|
|
763
897
|
choice_name_en = choice_name_en if choice_name_en is not None else ""
|
|
764
898
|
target_attribute_value_keys.append((label_name_en, attribute_name_en, choice_name_en))
|
|
765
899
|
|
|
766
|
-
elif
|
|
900
|
+
elif attribute_type == AdditionalDataDefinitionType.FLAG:
|
|
767
901
|
target_attribute_value_keys.extend([(label_name_en, attribute_name_en, "true"), (label_name_en, attribute_name_en, "false")])
|
|
768
902
|
|
|
769
|
-
|
|
770
|
-
|
|
903
|
+
return target_attribute_value_keys
|
|
904
|
+
|
|
905
|
+
def selective_attribute_value_keys(self) -> list[AttributeValueKey]:
|
|
906
|
+
"""
|
|
907
|
+
選択系(ドロップダウン/ラジオボタン/チェックボックス/フラグ)の属性の属性値のキーの一覧。
|
|
908
|
+
|
|
909
|
+
対象となる属性種類:
|
|
910
|
+
* ドロップダウン
|
|
911
|
+
* ラジオボタン
|
|
912
|
+
* チェックボックス
|
|
913
|
+
* フラグ
|
|
914
|
+
"""
|
|
915
|
+
selective_attribute_names = self.selective_attribute_name_keys()
|
|
916
|
+
target_attribute_value_keys = self.get_attribute_value_keys_for_target_attributes(selective_attribute_names)
|
|
771
917
|
|
|
772
918
|
duplicated_attributes = [key for key, value in collections.Counter(target_attribute_value_keys).items() if value > 1]
|
|
773
919
|
if len(duplicated_attributes) > 0:
|
|
@@ -802,6 +948,34 @@ class AnnotationSpecs:
|
|
|
802
948
|
|
|
803
949
|
return target_attributes_columns
|
|
804
950
|
|
|
951
|
+
def get_attribute_name_keys_by_attribute_names(self, attribute_names: Collection[str]) -> tuple[list[AttributeNameKey], list[str]]:
|
|
952
|
+
"""
|
|
953
|
+
指定された属性名を持つ全てのラベルと属性の組み合わせを取得します。
|
|
954
|
+
|
|
955
|
+
Args:
|
|
956
|
+
attribute_names: 検索対象の属性名のリスト
|
|
957
|
+
|
|
958
|
+
Returns:
|
|
959
|
+
(見つかった属性名キーのリスト, 見つからなかった属性名のリスト) のタプル
|
|
960
|
+
"""
|
|
961
|
+
attribute_names_set = set(attribute_names)
|
|
962
|
+
result = []
|
|
963
|
+
found_attribute_names = set()
|
|
964
|
+
for label in self._labels_v1:
|
|
965
|
+
label_name_en = AddProps.get_message(label["label_name"], MessageLocale.EN)
|
|
966
|
+
label_name_en = label_name_en if label_name_en is not None else ""
|
|
967
|
+
|
|
968
|
+
for attribute in label["additional_data_definitions"]:
|
|
969
|
+
attribute_name_en = AddProps.get_message(attribute["name"], MessageLocale.EN)
|
|
970
|
+
attribute_name_en = attribute_name_en if attribute_name_en is not None else ""
|
|
971
|
+
|
|
972
|
+
if attribute_name_en in attribute_names_set:
|
|
973
|
+
result.append((label_name_en, attribute_name_en))
|
|
974
|
+
found_attribute_names.add(attribute_name_en)
|
|
975
|
+
|
|
976
|
+
not_found_attribute_names = list(attribute_names_set - found_attribute_names)
|
|
977
|
+
return result, not_found_attribute_names
|
|
978
|
+
|
|
805
979
|
def non_selective_attribute_name_keys(self) -> list[AttributeNameKey]:
|
|
806
980
|
"""
|
|
807
981
|
非選択系の属性の名前のキー
|
|
@@ -834,11 +1008,27 @@ class AnnotationSpecs:
|
|
|
834
1008
|
|
|
835
1009
|
|
|
836
1010
|
class ListAnnotationCountMain:
|
|
837
|
-
def __init__(self, service: annofabapi.Resource) -> None:
|
|
1011
|
+
def __init__(self, service: annofabapi.Resource, annotation_specs: AnnotationSpecs) -> None:
|
|
1012
|
+
"""
|
|
1013
|
+
Args:
|
|
1014
|
+
service: AnnofabのAPIリソース
|
|
1015
|
+
annotation_specs: アノテーション仕様
|
|
1016
|
+
"""
|
|
838
1017
|
self.service = service
|
|
1018
|
+
self.annotation_specs = annotation_specs
|
|
839
1019
|
|
|
840
1020
|
@staticmethod
|
|
841
1021
|
def get_frame_no_map(task_json_path: Path) -> dict[tuple[str, str], int]:
|
|
1022
|
+
"""
|
|
1023
|
+
タスクJSONファイルから、(task_id, input_data_id)をキーとし、フレーム番号を値とするマップを取得します。
|
|
1024
|
+
フレーム番号は1始まりです。
|
|
1025
|
+
|
|
1026
|
+
Args:
|
|
1027
|
+
task_json_path: タスクJSONファイルのパス
|
|
1028
|
+
|
|
1029
|
+
Returns:
|
|
1030
|
+
key: (task_id, input_data_id)のタプル、value: フレーム番号のdict
|
|
1031
|
+
"""
|
|
842
1032
|
with task_json_path.open(encoding="utf-8") as f:
|
|
843
1033
|
task_list = json.load(f)
|
|
844
1034
|
|
|
@@ -857,20 +1047,52 @@ class ListAnnotationCountMain:
|
|
|
857
1047
|
csv_type: CsvType,
|
|
858
1048
|
output_file: Path,
|
|
859
1049
|
*,
|
|
860
|
-
project_id: str | None = None,
|
|
861
1050
|
task_json_path: Path | None = None,
|
|
862
1051
|
target_task_ids: Collection[str] | None = None,
|
|
863
1052
|
task_query: TaskQuery | None = None,
|
|
1053
|
+
additional_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
1054
|
+
specified_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
864
1055
|
) -> None:
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1056
|
+
"""
|
|
1057
|
+
ラベルごと/属性ごとのアノテーション数を入力データ単位でCSVファイルに出力します。
|
|
1058
|
+
|
|
1059
|
+
Args:
|
|
1060
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
1061
|
+
csv_type: 出力するCSVの種類(ラベルごと/属性ごと)
|
|
1062
|
+
output_file: 出力先のCSVファイルパス
|
|
1063
|
+
task_json_path: タスクJSONファイルのパス。フレーム番号を出力する場合に指定します。
|
|
1064
|
+
target_task_ids: 集計対象のタスクIDのコレクション
|
|
1065
|
+
task_query: 集計対象タスクを絞り込むためのクエリ条件
|
|
1066
|
+
additional_attribute_names: デフォルトの選択系属性に加えて集計対象とする属性名のキー
|
|
1067
|
+
specified_attribute_names: 集計対象とする属性名のキー。指定した場合、これらの属性のみが集計対象になります。
|
|
1068
|
+
|
|
1069
|
+
Raises:
|
|
1070
|
+
ValueError: `additional_attribute_names`と`specified_attribute_names`の両方が指定された場合に発生します。
|
|
1071
|
+
"""
|
|
1072
|
+
if additional_attribute_names is not None and specified_attribute_names is not None:
|
|
1073
|
+
raise ValueError("`additional_attribute_names`と`specified_attribute_names`の両方が指定されています。")
|
|
1074
|
+
|
|
1075
|
+
if specified_attribute_names is not None:
|
|
1076
|
+
# specified_attribute_namesから属性名のみを抽出
|
|
1077
|
+
target_attribute_names_only = list({attr_name for _, attr_name in specified_attribute_names})
|
|
1078
|
+
elif additional_attribute_names is not None:
|
|
1079
|
+
# デフォルトの選択系属性の属性名を取得
|
|
1080
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1081
|
+
default_attribute_names = {attr_name for _, attr_name in default_selective_attributes}
|
|
1082
|
+
# additional_attribute_namesから属性名のみを抽出
|
|
1083
|
+
additional_attr_names = {attr_name for _, attr_name in additional_attribute_names}
|
|
1084
|
+
# 両方を組み合わせる
|
|
1085
|
+
target_attribute_names_only = list(default_attribute_names | additional_attr_names)
|
|
1086
|
+
else:
|
|
1087
|
+
# デフォルトでは選択肢系の属性のみを集計対象とする
|
|
1088
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1089
|
+
target_attribute_names_only = list({attr_name for _, attr_name in default_selective_attributes})
|
|
871
1090
|
|
|
872
1091
|
frame_no_map = self.get_frame_no_map(task_json_path) if task_json_path is not None else None
|
|
873
|
-
counter_by_input_data = ListAnnotationCounterByInputData(
|
|
1092
|
+
counter_by_input_data = ListAnnotationCounterByInputData(
|
|
1093
|
+
target_attribute_names_only=target_attribute_names_only,
|
|
1094
|
+
frame_no_map=frame_no_map,
|
|
1095
|
+
)
|
|
874
1096
|
counter_list_by_input_data = counter_by_input_data.get_annotation_counter_list(
|
|
875
1097
|
annotation_path,
|
|
876
1098
|
target_task_ids=target_task_ids,
|
|
@@ -879,16 +1101,19 @@ class ListAnnotationCountMain:
|
|
|
879
1101
|
|
|
880
1102
|
if csv_type == CsvType.LABEL:
|
|
881
1103
|
# ラベル名の列順が、アノテーション仕様にあるラベル名の順番に対応するようにする。
|
|
882
|
-
label_columns
|
|
883
|
-
if annotation_specs is not None:
|
|
884
|
-
label_columns = annotation_specs.label_keys()
|
|
885
|
-
|
|
1104
|
+
label_columns = self.annotation_specs.label_keys()
|
|
886
1105
|
LabelCountCsv().print_csv_by_input_data(counter_list_by_input_data, output_file, prior_label_columns=label_columns)
|
|
887
1106
|
elif csv_type == CsvType.ATTRIBUTE:
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
attribute_columns = annotation_specs.
|
|
891
|
-
|
|
1107
|
+
if specified_attribute_names is not None:
|
|
1108
|
+
# 指定された属性名に対応する属性値のキーのみを取得
|
|
1109
|
+
attribute_columns = self.annotation_specs.get_attribute_value_keys_for_target_attributes(specified_attribute_names)
|
|
1110
|
+
elif additional_attribute_names is not None:
|
|
1111
|
+
# デフォルトの選択系属性とadditional_attribute_namesを組み合わせた属性値のキーを取得
|
|
1112
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1113
|
+
combined_attributes = list(set(default_selective_attributes) | set(additional_attribute_names))
|
|
1114
|
+
attribute_columns = self.annotation_specs.get_attribute_value_keys_for_target_attributes(combined_attributes)
|
|
1115
|
+
else:
|
|
1116
|
+
attribute_columns = self.annotation_specs.selective_attribute_value_keys()
|
|
892
1117
|
AttributeCountCsv().print_csv_by_input_data(counter_list_by_input_data, output_file, prior_attribute_columns=attribute_columns)
|
|
893
1118
|
|
|
894
1119
|
def print_annotation_counter_csv_by_task(
|
|
@@ -897,18 +1122,48 @@ class ListAnnotationCountMain:
|
|
|
897
1122
|
csv_type: CsvType,
|
|
898
1123
|
output_file: Path,
|
|
899
1124
|
*,
|
|
900
|
-
project_id: str | None = None,
|
|
901
1125
|
target_task_ids: Collection[str] | None = None,
|
|
902
1126
|
task_query: TaskQuery | None = None,
|
|
1127
|
+
additional_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
1128
|
+
specified_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
903
1129
|
) -> None:
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1130
|
+
"""
|
|
1131
|
+
ラベルごと/属性ごとのアノテーション数をタスク単位でCSVファイルに出力します。
|
|
1132
|
+
|
|
1133
|
+
Args:
|
|
1134
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
1135
|
+
csv_type: 出力するCSVの種類(ラベルごと/属性ごと)
|
|
1136
|
+
output_file: 出力先のCSVファイルパス
|
|
1137
|
+
target_task_ids: 集計対象のタスクIDのコレクション
|
|
1138
|
+
task_query: 集計対象タスクを絞り込むためのクエリ条件
|
|
1139
|
+
additional_attribute_names: デフォルトの選択系属性に加えて集計対象とする属性名のキー
|
|
1140
|
+
specified_attribute_names: 集計対象とする属性名のキー。指定した場合、これらの属性のみが集計対象になります。
|
|
1141
|
+
|
|
1142
|
+
Raises:
|
|
1143
|
+
ValueError: `additional_attribute_names`と`specified_attribute_names`の両方が指定された場合に発生します。
|
|
1144
|
+
"""
|
|
1145
|
+
if additional_attribute_names is not None and specified_attribute_names is not None:
|
|
1146
|
+
raise ValueError("`additional_attribute_names`と`specified_attribute_names`の両方が指定されています。")
|
|
1147
|
+
|
|
1148
|
+
if specified_attribute_names is not None:
|
|
1149
|
+
# specified_attribute_namesから属性名のみを抽出
|
|
1150
|
+
target_attribute_names_only = list({attr_name for _, attr_name in specified_attribute_names})
|
|
1151
|
+
elif additional_attribute_names is not None:
|
|
1152
|
+
# デフォルトの選択系属性の属性名を取得
|
|
1153
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1154
|
+
default_attribute_names = {attr_name for _, attr_name in default_selective_attributes}
|
|
1155
|
+
# additional_attribute_namesから属性名のみを抽出
|
|
1156
|
+
additional_attr_names = {attr_name for _, attr_name in additional_attribute_names}
|
|
1157
|
+
# 両方を組み合わせる
|
|
1158
|
+
target_attribute_names_only = list(default_attribute_names | additional_attr_names)
|
|
1159
|
+
else:
|
|
1160
|
+
# デフォルトでは選択肢系の属性のみを集計対象とする
|
|
1161
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1162
|
+
target_attribute_names_only = list({attr_name for _, attr_name in default_selective_attributes})
|
|
1163
|
+
|
|
1164
|
+
counter_list_by_task = ListAnnotationCounterByTask(
|
|
1165
|
+
target_attribute_names_only=target_attribute_names_only,
|
|
1166
|
+
).get_annotation_counter_list(
|
|
912
1167
|
annotation_path,
|
|
913
1168
|
target_task_ids=target_task_ids,
|
|
914
1169
|
task_query=task_query,
|
|
@@ -916,18 +1171,21 @@ class ListAnnotationCountMain:
|
|
|
916
1171
|
|
|
917
1172
|
if csv_type == CsvType.LABEL:
|
|
918
1173
|
# 列順が、アノテーション仕様にあるラベル名の順番に対応するようにする。
|
|
919
|
-
label_columns
|
|
920
|
-
if annotation_specs is not None:
|
|
921
|
-
label_columns = annotation_specs.label_keys()
|
|
922
|
-
|
|
1174
|
+
label_columns = self.annotation_specs.label_keys()
|
|
923
1175
|
LabelCountCsv().print_csv_by_task(counter_list_by_task, output_file, prior_label_columns=label_columns)
|
|
924
1176
|
|
|
925
1177
|
elif csv_type == CsvType.ATTRIBUTE:
|
|
926
1178
|
# 列順が、アノテーション仕様にある属性名と属性値の順番に対応するようにする。
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
attribute_columns = annotation_specs.
|
|
930
|
-
|
|
1179
|
+
if specified_attribute_names is not None:
|
|
1180
|
+
# 指定された属性名に対応する属性値のキーのみを取得
|
|
1181
|
+
attribute_columns = self.annotation_specs.get_attribute_value_keys_for_target_attributes(specified_attribute_names)
|
|
1182
|
+
elif additional_attribute_names is not None:
|
|
1183
|
+
# デフォルトの選択系属性とadditional_attribute_namesを組み合わせた属性値のキーを取得
|
|
1184
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1185
|
+
combined_attributes = list(set(default_selective_attributes) | set(additional_attribute_names))
|
|
1186
|
+
attribute_columns = self.annotation_specs.get_attribute_value_keys_for_target_attributes(combined_attributes)
|
|
1187
|
+
else:
|
|
1188
|
+
attribute_columns = self.annotation_specs.selective_attribute_value_keys()
|
|
931
1189
|
AttributeCountCsv().print_csv_by_task(counter_list_by_task, output_file, prior_attribute_columns=attribute_columns)
|
|
932
1190
|
|
|
933
1191
|
def print_annotation_counter_json_by_input_data(
|
|
@@ -935,23 +1193,54 @@ class ListAnnotationCountMain:
|
|
|
935
1193
|
annotation_path: Path,
|
|
936
1194
|
output_file: Path,
|
|
937
1195
|
*,
|
|
938
|
-
project_id: str | None = None,
|
|
939
1196
|
task_json_path: Path | None = None,
|
|
940
1197
|
target_task_ids: Collection[str] | None = None,
|
|
941
1198
|
task_query: TaskQuery | None = None,
|
|
942
1199
|
json_is_pretty: bool = False,
|
|
1200
|
+
additional_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
1201
|
+
specified_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
943
1202
|
) -> None:
|
|
944
|
-
"""
|
|
1203
|
+
"""
|
|
1204
|
+
ラベルごと/属性ごとのアノテーション数を入力データ単位でJSONファイルに出力します。
|
|
945
1205
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1206
|
+
Args:
|
|
1207
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
1208
|
+
output_file: 出力先のJSONファイルパス
|
|
1209
|
+
task_json_path: タスクJSONファイルのパス。フレーム番号を出力する場合に指定します。
|
|
1210
|
+
target_task_ids: 集計対象のタスクIDのコレクション
|
|
1211
|
+
task_query: 集計対象タスクを絞り込むためのクエリ条件
|
|
1212
|
+
json_is_pretty: Trueの場合、整形されたJSON(pretty print)を出力します
|
|
1213
|
+
additional_attribute_names: デフォルトの選択系属性に加えて集計対象とする属性名のキー
|
|
1214
|
+
specified_attribute_names: 集計対象とする属性名のキー。指定した場合、これらの属性のみが集計対象になります。
|
|
1215
|
+
|
|
1216
|
+
Raises:
|
|
1217
|
+
ValueError: `additional_attribute_names`と`specified_attribute_names`の両方が指定された場合に発生します。
|
|
1218
|
+
"""
|
|
1219
|
+
if additional_attribute_names is not None and specified_attribute_names is not None:
|
|
1220
|
+
raise ValueError("`additional_attribute_names`と`specified_attribute_names`の両方が指定されています。")
|
|
1221
|
+
|
|
1222
|
+
if specified_attribute_names is not None:
|
|
1223
|
+
# specified_attribute_namesから属性名のみを抽出
|
|
1224
|
+
target_attribute_names_only = list({attr_name for _, attr_name in specified_attribute_names})
|
|
1225
|
+
elif additional_attribute_names is not None:
|
|
1226
|
+
# デフォルトの選択系属性の属性名を取得
|
|
1227
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1228
|
+
default_attribute_names = {attr_name for _, attr_name in default_selective_attributes}
|
|
1229
|
+
# additional_attribute_namesから属性名のみを抽出
|
|
1230
|
+
additional_attr_names = {attr_name for _, attr_name in additional_attribute_names}
|
|
1231
|
+
# 両方を組み合わせる
|
|
1232
|
+
target_attribute_names_only = list(default_attribute_names | additional_attr_names)
|
|
950
1233
|
else:
|
|
951
|
-
|
|
1234
|
+
# デフォルトでは選択肢系の属性のみを集計対象とする
|
|
1235
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1236
|
+
target_attribute_names_only = list({attr_name for _, attr_name in default_selective_attributes})
|
|
952
1237
|
|
|
953
1238
|
frame_no_map = self.get_frame_no_map(task_json_path) if task_json_path is not None else None
|
|
954
|
-
|
|
1239
|
+
counter_by_input_data = ListAnnotationCounterByInputData(
|
|
1240
|
+
target_attribute_names_only=target_attribute_names_only,
|
|
1241
|
+
frame_no_map=frame_no_map,
|
|
1242
|
+
)
|
|
1243
|
+
counter_list_by_input_data = counter_by_input_data.get_annotation_counter_list(
|
|
955
1244
|
annotation_path,
|
|
956
1245
|
target_task_ids=target_task_ids,
|
|
957
1246
|
task_query=task_query,
|
|
@@ -968,22 +1257,48 @@ class ListAnnotationCountMain:
|
|
|
968
1257
|
annotation_path: Path,
|
|
969
1258
|
output_file: Path,
|
|
970
1259
|
*,
|
|
971
|
-
project_id: str | None = None,
|
|
972
1260
|
target_task_ids: Collection[str] | None = None,
|
|
973
1261
|
task_query: TaskQuery | None = None,
|
|
974
1262
|
json_is_pretty: bool = False,
|
|
1263
|
+
additional_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
1264
|
+
specified_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
975
1265
|
) -> None:
|
|
976
|
-
"""
|
|
1266
|
+
"""
|
|
1267
|
+
ラベルごと/属性ごとのアノテーション数をタスク単位でJSONファイルに出力します。
|
|
977
1268
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1269
|
+
Args:
|
|
1270
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
1271
|
+
output_file: 出力先のJSONファイルパス
|
|
1272
|
+
target_task_ids: 集計対象のタスクIDのコレクション
|
|
1273
|
+
task_query: 集計対象タスクを絞り込むためのクエリ条件
|
|
1274
|
+
json_is_pretty: Trueの場合、整形されたJSON(pretty print)を出力します
|
|
1275
|
+
additional_attribute_names: デフォルトの選択系属性に加えて集計対象とする属性名のキー
|
|
1276
|
+
specified_attribute_names: 集計対象とする属性名のキー。指定した場合、これらの属性のみが集計対象になります。
|
|
1277
|
+
|
|
1278
|
+
Raises:
|
|
1279
|
+
ValueError: `additional_attribute_names`と`specified_attribute_names`の両方が指定された場合に発生します。
|
|
1280
|
+
"""
|
|
1281
|
+
if additional_attribute_names is not None and specified_attribute_names is not None:
|
|
1282
|
+
raise ValueError("`additional_attribute_names`と`specified_attribute_names`の両方が指定されています。")
|
|
1283
|
+
|
|
1284
|
+
if specified_attribute_names is not None:
|
|
1285
|
+
# specified_attribute_namesから属性名のみを抽出
|
|
1286
|
+
target_attribute_names_only = list({attr_name for _, attr_name in specified_attribute_names})
|
|
1287
|
+
elif additional_attribute_names is not None:
|
|
1288
|
+
# デフォルトの選択系属性の属性名を取得
|
|
1289
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1290
|
+
default_attribute_names = {attr_name for _, attr_name in default_selective_attributes}
|
|
1291
|
+
# additional_attribute_namesから属性名のみを抽出
|
|
1292
|
+
additional_attr_names = {attr_name for _, attr_name in additional_attribute_names}
|
|
1293
|
+
# 両方を組み合わせる
|
|
1294
|
+
target_attribute_names_only = list(default_attribute_names | additional_attr_names)
|
|
982
1295
|
else:
|
|
983
|
-
|
|
1296
|
+
# デフォルトでは選択肢系の属性のみを集計対象とする
|
|
1297
|
+
default_selective_attributes = self.annotation_specs.selective_attribute_name_keys()
|
|
1298
|
+
target_attribute_names_only = list({attr_name for _, attr_name in default_selective_attributes})
|
|
984
1299
|
|
|
985
1300
|
counter_list_by_task = ListAnnotationCounterByTask(
|
|
986
|
-
|
|
1301
|
+
target_attribute_names_only=target_attribute_names_only,
|
|
987
1302
|
).get_annotation_counter_list(
|
|
988
1303
|
annotation_path,
|
|
989
1304
|
target_task_ids=target_task_ids,
|
|
@@ -1003,34 +1318,51 @@ class ListAnnotationCountMain:
|
|
|
1003
1318
|
output_file: Path,
|
|
1004
1319
|
arg_format: OutputFormat,
|
|
1005
1320
|
*,
|
|
1006
|
-
project_id: str | None = None,
|
|
1007
1321
|
task_json_path: Path | None = None,
|
|
1008
1322
|
target_task_ids: Collection[str] | None = None,
|
|
1009
1323
|
task_query: TaskQuery | None = None,
|
|
1010
1324
|
csv_type: CsvType | None = None,
|
|
1325
|
+
additional_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
1326
|
+
specified_attribute_names: Collection[AttributeNameKey] | None = None,
|
|
1011
1327
|
) -> None:
|
|
1012
|
-
"""
|
|
1328
|
+
"""
|
|
1329
|
+
ラベルごと/属性ごとのアノテーション数を出力します。
|
|
1330
|
+
|
|
1331
|
+
Args:
|
|
1332
|
+
annotation_path: アノテーションzipまたはzipを展開したディレクトリのパス
|
|
1333
|
+
group_by: 集計単位(タスク単位または入力データ単位)
|
|
1334
|
+
output_file: 出力先ファイルのパス
|
|
1335
|
+
arg_format: 出力形式(CSV、JSON、整形済みJSON)
|
|
1336
|
+
task_json_path: タスクJSONファイルのパス。フレーム番号を出力する場合に指定します。
|
|
1337
|
+
target_task_ids: 集計対象のタスクIDのコレクション
|
|
1338
|
+
task_query: 集計対象タスクを絞り込むためのクエリ条件
|
|
1339
|
+
csv_type: CSV出力時の種類(ラベルごと/属性ごと)。CSV形式の場合は必須です。
|
|
1340
|
+
additional_attribute_names: デフォルトの選択系属性に加えて集計対象とする属性名のキー
|
|
1341
|
+
specified_attribute_names: 集計対象とする属性名のキー。指定した場合、これらの属性のみが集計対象になります。
|
|
1342
|
+
"""
|
|
1013
1343
|
if arg_format == OutputFormat.CSV:
|
|
1014
1344
|
assert csv_type is not None
|
|
1015
1345
|
if group_by == GroupBy.INPUT_DATA_ID:
|
|
1016
1346
|
self.print_annotation_counter_csv_by_input_data(
|
|
1017
|
-
project_id=project_id,
|
|
1018
1347
|
annotation_path=annotation_path,
|
|
1019
1348
|
task_json_path=task_json_path,
|
|
1020
1349
|
output_file=output_file,
|
|
1350
|
+
csv_type=csv_type,
|
|
1021
1351
|
target_task_ids=target_task_ids,
|
|
1022
1352
|
task_query=task_query,
|
|
1023
|
-
|
|
1353
|
+
additional_attribute_names=additional_attribute_names,
|
|
1354
|
+
specified_attribute_names=specified_attribute_names,
|
|
1024
1355
|
)
|
|
1025
1356
|
|
|
1026
1357
|
elif group_by == GroupBy.TASK_ID:
|
|
1027
1358
|
self.print_annotation_counter_csv_by_task(
|
|
1028
|
-
project_id=project_id,
|
|
1029
1359
|
annotation_path=annotation_path,
|
|
1030
1360
|
output_file=output_file,
|
|
1361
|
+
csv_type=csv_type,
|
|
1031
1362
|
target_task_ids=target_task_ids,
|
|
1032
1363
|
task_query=task_query,
|
|
1033
|
-
|
|
1364
|
+
additional_attribute_names=additional_attribute_names,
|
|
1365
|
+
specified_attribute_names=specified_attribute_names,
|
|
1034
1366
|
)
|
|
1035
1367
|
|
|
1036
1368
|
elif arg_format in [OutputFormat.PRETTY_JSON, OutputFormat.JSON]:
|
|
@@ -1038,23 +1370,25 @@ class ListAnnotationCountMain:
|
|
|
1038
1370
|
|
|
1039
1371
|
if group_by == GroupBy.INPUT_DATA_ID:
|
|
1040
1372
|
self.print_annotation_counter_json_by_input_data(
|
|
1041
|
-
project_id=project_id,
|
|
1042
1373
|
annotation_path=annotation_path,
|
|
1043
1374
|
task_json_path=task_json_path,
|
|
1044
1375
|
output_file=output_file,
|
|
1045
1376
|
target_task_ids=target_task_ids,
|
|
1046
1377
|
task_query=task_query,
|
|
1047
1378
|
json_is_pretty=json_is_pretty,
|
|
1379
|
+
additional_attribute_names=additional_attribute_names,
|
|
1380
|
+
specified_attribute_names=specified_attribute_names,
|
|
1048
1381
|
)
|
|
1049
1382
|
|
|
1050
1383
|
elif group_by == GroupBy.TASK_ID:
|
|
1051
1384
|
self.print_annotation_counter_json_by_task(
|
|
1052
|
-
project_id=project_id,
|
|
1053
1385
|
annotation_path=annotation_path,
|
|
1054
1386
|
output_file=output_file,
|
|
1055
1387
|
target_task_ids=target_task_ids,
|
|
1056
1388
|
task_query=task_query,
|
|
1057
1389
|
json_is_pretty=json_is_pretty,
|
|
1390
|
+
additional_attribute_names=additional_attribute_names,
|
|
1391
|
+
specified_attribute_names=specified_attribute_names,
|
|
1058
1392
|
)
|
|
1059
1393
|
|
|
1060
1394
|
|
|
@@ -1063,55 +1397,61 @@ class ListAnnotationCount(CommandLine):
|
|
|
1063
1397
|
アノテーション数情報を出力する。
|
|
1064
1398
|
"""
|
|
1065
1399
|
|
|
1066
|
-
COMMON_MESSAGE = "annofabcli statistics list_annotation_count: error:"
|
|
1067
|
-
|
|
1068
|
-
def validate(self, args: argparse.Namespace) -> bool:
|
|
1069
|
-
if args.project_id is None and args.annotation is None:
|
|
1070
|
-
print( # noqa: T201
|
|
1071
|
-
f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
|
|
1072
|
-
file=sys.stderr,
|
|
1073
|
-
)
|
|
1074
|
-
return False
|
|
1075
|
-
|
|
1076
|
-
return True
|
|
1077
|
-
|
|
1078
1400
|
def main(self) -> None:
|
|
1401
|
+
"""
|
|
1402
|
+
アノテーション数情報を出力するメイン処理。
|
|
1403
|
+
コマンドライン引数を解析し、アノテーションzip/ディレクトリから集計情報を取得して、
|
|
1404
|
+
指定された形式(CSV/JSON)で出力します。
|
|
1405
|
+
"""
|
|
1079
1406
|
args = self.args
|
|
1080
1407
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1408
|
+
project_id: str = args.project_id
|
|
1409
|
+
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
1083
1410
|
|
|
1084
|
-
|
|
1085
|
-
if project_id is not None:
|
|
1086
|
-
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
1087
|
-
|
|
1088
|
-
annotation_path = Path(args.annotation) if args.annotation is not None else None
|
|
1411
|
+
annotation_path = args.annotation
|
|
1089
1412
|
|
|
1090
1413
|
task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
1091
1414
|
task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
|
|
1092
1415
|
|
|
1416
|
+
# アノテーション仕様を取得
|
|
1417
|
+
annotation_specs = AnnotationSpecs(self.service, project_id)
|
|
1418
|
+
|
|
1419
|
+
additional_attribute_names: list[AttributeNameKey] | None = None
|
|
1420
|
+
specified_attribute_names: list[AttributeNameKey] | None = None
|
|
1421
|
+
|
|
1422
|
+
if args.additional_attribute_name is not None:
|
|
1423
|
+
attribute_name_str_list = annofabcli.common.cli.get_list_from_args(args.additional_attribute_name)
|
|
1424
|
+
# アノテーション仕様から属性名を検索してAttributeNameKeyのリストに変換
|
|
1425
|
+
additional_attribute_names, not_found_names = annotation_specs.get_attribute_name_keys_by_attribute_names(attribute_name_str_list)
|
|
1426
|
+
if len(not_found_names) > 0:
|
|
1427
|
+
logger.warning(f"指定された属性名のうち、アノテーション仕様に見つからなかった属性名があります。 :: {not_found_names}")
|
|
1428
|
+
elif args.attribute_name is not None:
|
|
1429
|
+
attribute_name_str_list = annofabcli.common.cli.get_list_from_args(args.attribute_name)
|
|
1430
|
+
# アノテーション仕様から属性名を検索してAttributeNameKeyのリストに変換
|
|
1431
|
+
specified_attribute_names, not_found_names = annotation_specs.get_attribute_name_keys_by_attribute_names(attribute_name_str_list)
|
|
1432
|
+
if len(not_found_names) > 0:
|
|
1433
|
+
logger.warning(f"指定された属性名のうち、アノテーション仕様に見つからなかった属性名があります。 :: {not_found_names}")
|
|
1434
|
+
|
|
1093
1435
|
group_by = GroupBy(args.group_by)
|
|
1094
1436
|
csv_type = CsvType(args.type)
|
|
1095
1437
|
output_file: Path = args.output
|
|
1096
1438
|
arg_format = OutputFormat(args.format)
|
|
1097
|
-
main_obj = ListAnnotationCountMain(self.service)
|
|
1439
|
+
main_obj = ListAnnotationCountMain(self.service, annotation_specs)
|
|
1098
1440
|
|
|
1099
1441
|
downloading_obj = DownloadingFile(self.service)
|
|
1100
1442
|
|
|
1101
1443
|
def download_and_process_annotation(temp_dir: Path, *, is_latest: bool, annotation_path: Path | None) -> None:
|
|
1102
|
-
#
|
|
1103
|
-
|
|
1444
|
+
# タスク全件ファイルは、group_by=input_data_idの場合のみフレーム番号を参照するのに利用する
|
|
1445
|
+
task_json_path: Path | None = None
|
|
1446
|
+
if group_by == GroupBy.INPUT_DATA_ID:
|
|
1104
1447
|
task_json_path = downloading_obj.download_task_json_to_dir(
|
|
1105
1448
|
project_id,
|
|
1106
1449
|
temp_dir,
|
|
1107
1450
|
is_latest=is_latest,
|
|
1108
1451
|
)
|
|
1109
|
-
else:
|
|
1110
|
-
task_json_path = None
|
|
1111
1452
|
|
|
1112
1453
|
func = partial(
|
|
1113
1454
|
main_obj.print_annotation_counter,
|
|
1114
|
-
project_id=project_id,
|
|
1115
1455
|
task_json_path=task_json_path,
|
|
1116
1456
|
group_by=group_by,
|
|
1117
1457
|
csv_type=csv_type,
|
|
@@ -1119,10 +1459,11 @@ class ListAnnotationCount(CommandLine):
|
|
|
1119
1459
|
output_file=output_file,
|
|
1120
1460
|
target_task_ids=task_id_list,
|
|
1121
1461
|
task_query=task_query,
|
|
1462
|
+
additional_attribute_names=additional_attribute_names,
|
|
1463
|
+
specified_attribute_names=specified_attribute_names,
|
|
1122
1464
|
)
|
|
1123
1465
|
|
|
1124
1466
|
if annotation_path is None:
|
|
1125
|
-
assert project_id is not None
|
|
1126
1467
|
annotation_path = downloading_obj.download_annotation_zip_to_dir(
|
|
1127
1468
|
project_id,
|
|
1128
1469
|
temp_dir,
|
|
@@ -1132,44 +1473,29 @@ class ListAnnotationCount(CommandLine):
|
|
|
1132
1473
|
else:
|
|
1133
1474
|
func(annotation_path=annotation_path)
|
|
1134
1475
|
|
|
1135
|
-
if
|
|
1136
|
-
|
|
1137
|
-
download_and_process_annotation(temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path)
|
|
1138
|
-
else:
|
|
1139
|
-
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
1140
|
-
download_and_process_annotation(temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path)
|
|
1476
|
+
if args.temp_dir is not None:
|
|
1477
|
+
download_and_process_annotation(temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path)
|
|
1141
1478
|
else:
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
func = partial(
|
|
1145
|
-
main_obj.print_annotation_counter,
|
|
1146
|
-
project_id=project_id,
|
|
1147
|
-
task_json_path=None,
|
|
1148
|
-
group_by=group_by,
|
|
1149
|
-
csv_type=csv_type,
|
|
1150
|
-
arg_format=arg_format,
|
|
1151
|
-
output_file=output_file,
|
|
1152
|
-
target_task_ids=task_id_list,
|
|
1153
|
-
task_query=task_query,
|
|
1154
|
-
)
|
|
1155
|
-
func(annotation_path=annotation_path)
|
|
1479
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
1480
|
+
download_and_process_annotation(temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path)
|
|
1156
1481
|
|
|
1157
1482
|
|
|
1158
1483
|
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
1484
|
+
"""
|
|
1485
|
+
コマンドライン引数のパーサーを設定します。
|
|
1486
|
+
|
|
1487
|
+
Args:
|
|
1488
|
+
parser: argparseのArgumentParserインスタンス
|
|
1489
|
+
"""
|
|
1159
1490
|
argument_parser = ArgumentParser(parser)
|
|
1160
1491
|
|
|
1161
1492
|
parser.add_argument(
|
|
1162
1493
|
"--annotation",
|
|
1163
|
-
type=
|
|
1494
|
+
type=Path,
|
|
1164
1495
|
help="アノテーションzip、またはzipを展開したディレクトリを指定します。指定しない場合はAnnofabからダウンロードします。",
|
|
1165
1496
|
)
|
|
1166
1497
|
|
|
1167
|
-
|
|
1168
|
-
"-p",
|
|
1169
|
-
"--project_id",
|
|
1170
|
-
type=str,
|
|
1171
|
-
help="project_id。``--annotation`` が未指定のときは必須です。``--annotation`` が指定されているときに ``--project_id`` を指定すると、アノテーション仕様を参照して、集計対象の属性やCSV列順が決まります。", # noqa: E501
|
|
1172
|
-
)
|
|
1498
|
+
argument_parser.add_project_id()
|
|
1173
1499
|
|
|
1174
1500
|
parser.add_argument(
|
|
1175
1501
|
"--group_by",
|
|
@@ -1218,16 +1544,53 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
1218
1544
|
help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
|
|
1219
1545
|
)
|
|
1220
1546
|
|
|
1547
|
+
# --attribute_nameと--additional_attribute_nameは排他的
|
|
1548
|
+
attribute_group = parser.add_mutually_exclusive_group()
|
|
1549
|
+
attribute_group.add_argument(
|
|
1550
|
+
"--additional_attribute_name",
|
|
1551
|
+
type=str,
|
|
1552
|
+
nargs="+",
|
|
1553
|
+
help="デフォルトで集計される選択肢系の属性(ドロップダウン、ラジオボタン、チェックボックス)に加えて、集計したい属性の英語名を指定します。"
|
|
1554
|
+
"ラベル名に関係なく、デフォルト属性と指定した属性名を持つ属性が集計対象になります。"
|
|
1555
|
+
" ``file://`` を先頭に付けると、属性名が記載されたファイルを指定できます。"
|
|
1556
|
+
" ``--type attribute`` を指定したときのみ有効なオプションです。",
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
attribute_group.add_argument(
|
|
1560
|
+
"--attribute_name",
|
|
1561
|
+
type=str,
|
|
1562
|
+
nargs="+",
|
|
1563
|
+
help="集計対象とする属性の英語名を指定します。指定した属性名のみが集計対象になります(デフォルトの選択肢系属性は含まれません)。"
|
|
1564
|
+
"ラベル名に関係なく、指定した属性名を持つ属性のみが集計対象になります。"
|
|
1565
|
+
" ``file://`` を先頭に付けると、属性名が記載されたファイルを指定できます。"
|
|
1566
|
+
" ``--type attribute`` を指定したときのみ有効なオプションです。",
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1221
1569
|
parser.set_defaults(subcommand_func=main)
|
|
1222
1570
|
|
|
1223
1571
|
|
|
1224
1572
|
def main(args: argparse.Namespace) -> None:
|
|
1573
|
+
"""
|
|
1574
|
+
list_annotation_countコマンドのエントリーポイント。
|
|
1575
|
+
|
|
1576
|
+
Args:
|
|
1577
|
+
args: コマンドライン引数
|
|
1578
|
+
"""
|
|
1225
1579
|
service = build_annofabapi_resource_and_login(args)
|
|
1226
1580
|
facade = AnnofabApiFacade(service)
|
|
1227
1581
|
ListAnnotationCount(service, facade, args).main()
|
|
1228
1582
|
|
|
1229
1583
|
|
|
1230
1584
|
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
1585
|
+
"""
|
|
1586
|
+
list_annotation_countサブコマンドのパーサーを追加します。
|
|
1587
|
+
|
|
1588
|
+
Args:
|
|
1589
|
+
subparsers: 親パーサーのサブパーサーアクション
|
|
1590
|
+
|
|
1591
|
+
Returns:
|
|
1592
|
+
作成されたArgumentParserインスタンス
|
|
1593
|
+
"""
|
|
1231
1594
|
subcommand_name = "list_annotation_count"
|
|
1232
1595
|
subcommand_help = "ラベルごとまたは属性値ごとにアノテーション数を出力します。"
|
|
1233
1596
|
epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|