annofabcli 1.113.0__py3-none-any.whl → 1.114.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annofabcli/__main__.py +2 -0
- annofabcli/annotation/dump_annotation.py +19 -12
- annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +7 -0
- annofabcli/comment/list_all_comment.py +41 -8
- annofabcli/common/download.py +69 -0
- annofabcli/input_data/list_all_input_data.py +34 -11
- annofabcli/task/list_all_tasks.py +29 -8
- annofabcli/task/list_all_tasks_added_task_history.py +38 -25
- annofabcli/task_count/__init__.py +0 -0
- annofabcli/task_count/list_by_phase.py +447 -0
- annofabcli/task_count/subcommand_task_count.py +21 -0
- annofabcli/task_history/list_all_task_history.py +31 -9
- annofabcli/task_history_event/list_all_task_history_event.py +30 -9
- {annofabcli-1.113.0.dist-info → annofabcli-1.114.0.dist-info}/METADATA +1 -1
- {annofabcli-1.113.0.dist-info → annofabcli-1.114.0.dist-info}/RECORD +18 -15
- {annofabcli-1.113.0.dist-info → annofabcli-1.114.0.dist-info}/WHEEL +0 -0
- {annofabcli-1.113.0.dist-info → annofabcli-1.114.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.113.0.dist-info → annofabcli-1.114.0.dist-info}/licenses/LICENSE +0 -0
annofabcli/__main__.py
CHANGED
|
@@ -26,6 +26,7 @@ import annofabcli.stat_visualization.subcommand_stat_visualization
|
|
|
26
26
|
import annofabcli.statistics.subcommand_statistics
|
|
27
27
|
import annofabcli.supplementary.subcommand_supplementary
|
|
28
28
|
import annofabcli.task.subcommand_task
|
|
29
|
+
import annofabcli.task_count.subcommand_task_count
|
|
29
30
|
import annofabcli.task_history.subcommand_task_history
|
|
30
31
|
import annofabcli.task_history_event.subcommand_task_history_event
|
|
31
32
|
|
|
@@ -121,6 +122,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
121
122
|
annofabcli.stat_visualization.subcommand_stat_visualization.add_parser(subparsers)
|
|
122
123
|
annofabcli.supplementary.subcommand_supplementary.add_parser(subparsers)
|
|
123
124
|
annofabcli.task.subcommand_task.add_parser(subparsers)
|
|
125
|
+
annofabcli.task_count.subcommand_task_count.add_parser(subparsers)
|
|
124
126
|
annofabcli.task_history.subcommand_task_history.add_parser(subparsers)
|
|
125
127
|
annofabcli.task_history_event.subcommand_task_history_event.add_parser(subparsers)
|
|
126
128
|
|
|
@@ -68,7 +68,7 @@ class DumpAnnotationMain:
|
|
|
68
68
|
json_path = task_dir / f"{input_data_id}.json"
|
|
69
69
|
self.dump_editor_annotation(editor_annotation=editor_annotation, json_path=json_path)
|
|
70
70
|
|
|
71
|
-
def dump_annotation_for_task(self, task_id: str, output_dir: Path, *, task_index: int | None = None, task_history_index: int | None = None) -> bool:
|
|
71
|
+
def dump_annotation_for_task(self, task_id: str, output_dir: Path, *, task_index: int | None = None, task_history_index: int | None = None, task_history_id: str | None = None) -> bool:
|
|
72
72
|
"""
|
|
73
73
|
タスク配下のアノテーションをファイルに保存する。
|
|
74
74
|
|
|
@@ -85,23 +85,23 @@ class DumpAnnotationMain:
|
|
|
85
85
|
logger.warning(f"task_id = '{task_id}' のタスクは存在しません。スキップします。")
|
|
86
86
|
return False
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
actual_task_history_id: str | None = task_history_id
|
|
89
89
|
if task_history_index is not None:
|
|
90
90
|
task_histories, _ = self.service.api.get_task_histories(self.project_id, task_id)
|
|
91
91
|
if task_history_index >= len(task_histories):
|
|
92
92
|
logger.warning(f"task_id='{task_id}' :: task_history_index='{task_history_index}'のタスク履歴は存在しません。タスク履歴は{len(task_histories)}件です。スキップします。")
|
|
93
93
|
return False
|
|
94
|
-
|
|
94
|
+
actual_task_history_id = task_histories[task_history_index]["task_history_id"]
|
|
95
95
|
|
|
96
96
|
input_data_id_list = task["input_data_id_list"]
|
|
97
97
|
task_dir = output_dir / task_id
|
|
98
98
|
task_dir.mkdir(exist_ok=True, parents=True)
|
|
99
|
-
logger.debug(f"{logger_prefix}task_id = '{task_id}' のアノテーション情報を '{task_dir}' ディレクトリに保存します。 :: task_history_id='{
|
|
99
|
+
logger.debug(f"{logger_prefix}task_id = '{task_id}' のアノテーション情報を '{task_dir}' ディレクトリに保存します。 :: task_history_id='{actual_task_history_id}'")
|
|
100
100
|
|
|
101
101
|
is_failure = False
|
|
102
102
|
for input_data_id in input_data_id_list:
|
|
103
103
|
try:
|
|
104
|
-
self.dump_annotation_for_input_data(task_id, input_data_id, task_dir=task_dir, task_history_id=
|
|
104
|
+
self.dump_annotation_for_input_data(task_id, input_data_id, task_dir=task_dir, task_history_id=actual_task_history_id)
|
|
105
105
|
except Exception:
|
|
106
106
|
logger.warning(f"タスク'{task_id}', 入力データ'{input_data_id}' のアノテーション情報のダンプに失敗しました。", exc_info=True)
|
|
107
107
|
is_failure = True
|
|
@@ -109,15 +109,15 @@ class DumpAnnotationMain:
|
|
|
109
109
|
|
|
110
110
|
return not is_failure
|
|
111
111
|
|
|
112
|
-
def dump_annotation_for_task_wrapper(self, tpl: tuple[int, str], output_dir: Path, *, task_history_index: int | None = None) -> bool:
|
|
112
|
+
def dump_annotation_for_task_wrapper(self, tpl: tuple[int, str], output_dir: Path, *, task_history_index: int | None = None, task_history_id: str | None = None) -> bool:
|
|
113
113
|
task_index, task_id = tpl
|
|
114
114
|
try:
|
|
115
|
-
return self.dump_annotation_for_task(task_id, output_dir=output_dir, task_index=task_index, task_history_index=task_history_index)
|
|
115
|
+
return self.dump_annotation_for_task(task_id, output_dir=output_dir, task_index=task_index, task_history_index=task_history_index, task_history_id=task_history_id)
|
|
116
116
|
except Exception: # pylint: disable=broad-except
|
|
117
117
|
logger.warning(f"タスク'{task_id}'のアノテーション情報のダンプに失敗しました。", exc_info=True)
|
|
118
118
|
return False
|
|
119
119
|
|
|
120
|
-
def dump_annotation(self, task_id_list: list[str], output_dir: Path, *, task_history_index: int | None = None, parallelism: int | None = None) -> None:
|
|
120
|
+
def dump_annotation(self, task_id_list: list[str], output_dir: Path, *, task_history_index: int | None = None, task_history_id: str | None = None, parallelism: int | None = None) -> None:
|
|
121
121
|
project_title = self.facade.get_project_title(self.project_id)
|
|
122
122
|
logger.info(f"プロジェクト'{project_title}'に対して、タスク{len(task_id_list)} 件のアノテーションをファイルに保存します。")
|
|
123
123
|
|
|
@@ -126,7 +126,7 @@ class DumpAnnotationMain:
|
|
|
126
126
|
success_count = 0
|
|
127
127
|
|
|
128
128
|
if parallelism is not None:
|
|
129
|
-
func = functools.partial(self.dump_annotation_for_task_wrapper, task_history_index=task_history_index, output_dir=output_dir)
|
|
129
|
+
func = functools.partial(self.dump_annotation_for_task_wrapper, task_history_index=task_history_index, task_history_id=task_history_id, output_dir=output_dir)
|
|
130
130
|
with multiprocessing.Pool(parallelism) as pool:
|
|
131
131
|
result_bool_list = pool.map(func, enumerate(task_id_list))
|
|
132
132
|
success_count = len([e for e in result_bool_list if e])
|
|
@@ -134,7 +134,7 @@ class DumpAnnotationMain:
|
|
|
134
134
|
else:
|
|
135
135
|
for task_index, task_id in enumerate(task_id_list):
|
|
136
136
|
try:
|
|
137
|
-
result = self.dump_annotation_for_task(task_id, output_dir=output_dir, task_history_index=task_history_index, task_index=task_index)
|
|
137
|
+
result = self.dump_annotation_for_task(task_id, output_dir=output_dir, task_history_index=task_history_index, task_history_id=task_history_id, task_index=task_index)
|
|
138
138
|
if result:
|
|
139
139
|
success_count += 1
|
|
140
140
|
except Exception:
|
|
@@ -157,7 +157,7 @@ class DumpAnnotation(CommandLine):
|
|
|
157
157
|
super().validate_project(project_id, project_member_roles=None)
|
|
158
158
|
|
|
159
159
|
main_obj = DumpAnnotationMain(self.service, project_id)
|
|
160
|
-
main_obj.dump_annotation(task_id_list, output_dir=output_dir, parallelism=args.parallelism, task_history_index=args.task_history_index)
|
|
160
|
+
main_obj.dump_annotation(task_id_list, output_dir=output_dir, parallelism=args.parallelism, task_history_index=args.task_history_index, task_history_id=args.task_history_id)
|
|
161
161
|
|
|
162
162
|
|
|
163
163
|
def main(args: argparse.Namespace) -> None:
|
|
@@ -174,12 +174,19 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
174
174
|
|
|
175
175
|
parser.add_argument("-o", "--output_dir", type=str, required=True, help="出力先ディレクトリのパス")
|
|
176
176
|
|
|
177
|
-
parser.
|
|
177
|
+
task_history_group = parser.add_mutually_exclusive_group()
|
|
178
|
+
task_history_group.add_argument(
|
|
178
179
|
"--task_history_index",
|
|
179
180
|
type=int,
|
|
180
181
|
help="指定したタスク履歴のインデックス(ゼロ始まり)で付与されたアノテーション情報をダンプします。過去のアノテーション結果をダンプする場合に指定します。"
|
|
181
182
|
"ただし、過去のアノテーションデータは30日間しか保持されません。30日より前に更新されたアノテーションをダンプした場合は、アノテーションが0件の状態でダンプされます。",
|
|
182
183
|
)
|
|
184
|
+
task_history_group.add_argument(
|
|
185
|
+
"--task_history_id",
|
|
186
|
+
type=str,
|
|
187
|
+
help="指定したタスク履歴IDで付与されたアノテーション情報をダンプします。過去のアノテーション結果をダンプする場合に指定します。"
|
|
188
|
+
"ただし、過去のアノテーションデータは30日間しか保持されません。30日より前に更新されたアノテーションをダンプした場合は、アノテーションが0件の状態でダンプされます。",
|
|
189
|
+
)
|
|
183
190
|
|
|
184
191
|
parser.add_argument(
|
|
185
192
|
"--parallelism",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import logging
|
|
3
|
+
import math
|
|
3
4
|
import sys
|
|
4
5
|
import tempfile
|
|
5
6
|
from collections.abc import Collection
|
|
@@ -65,6 +66,9 @@ class Annotation3DBoundingBoxInfo(DataClassJsonMixin):
|
|
|
65
66
|
top_z: float
|
|
66
67
|
"""天面のZ座標(location.z + height/2)。回転は考慮していない。"""
|
|
67
68
|
|
|
69
|
+
horizontal_distance: float
|
|
70
|
+
"""原点(0, 0)からのXY平面上の距離(sqrt(location.x² + location.y²))"""
|
|
71
|
+
|
|
68
72
|
attributes: dict[str, str | int | bool]
|
|
69
73
|
"""属性情報"""
|
|
70
74
|
|
|
@@ -96,6 +100,7 @@ def get_annotation_3d_bounding_box_info_list(simple_annotation: dict[str, Any],
|
|
|
96
100
|
footprint_area = width * depth
|
|
97
101
|
bottom_z = location.z - height / 2
|
|
98
102
|
top_z = location.z + height / 2
|
|
103
|
+
horizontal_distance = math.hypot(location.x, location.y)
|
|
99
104
|
|
|
100
105
|
result.append(
|
|
101
106
|
Annotation3DBoundingBoxInfo(
|
|
@@ -116,6 +121,7 @@ def get_annotation_3d_bounding_box_info_list(simple_annotation: dict[str, Any],
|
|
|
116
121
|
footprint_area=footprint_area,
|
|
117
122
|
bottom_z=bottom_z,
|
|
118
123
|
top_z=top_z,
|
|
124
|
+
horizontal_distance=horizontal_distance,
|
|
119
125
|
attributes=detail["attributes"],
|
|
120
126
|
updated_datetime=simple_annotation["updated_datetime"],
|
|
121
127
|
)
|
|
@@ -177,6 +183,7 @@ def create_df(
|
|
|
177
183
|
"footprint_area",
|
|
178
184
|
"bottom_z",
|
|
179
185
|
"top_z",
|
|
186
|
+
"horizontal_distance",
|
|
180
187
|
]
|
|
181
188
|
|
|
182
189
|
if len(tmp_annotation_bbox_list) == 0:
|
|
@@ -39,21 +39,46 @@ class ListAllCommentMain:
|
|
|
39
39
|
task_ids: Collection[str] | None,
|
|
40
40
|
comment_type: CommentType | None,
|
|
41
41
|
exclude_reply: bool, # noqa: FBT001
|
|
42
|
+
temp_dir: Path | None,
|
|
42
43
|
) -> list[dict[str, Any]]:
|
|
43
44
|
if comment_json is None:
|
|
44
45
|
downloading_obj = DownloadingFile(self.service)
|
|
45
46
|
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
46
47
|
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
47
|
-
|
|
48
|
-
json_path =
|
|
49
|
-
|
|
50
|
-
with
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
if temp_dir is not None:
|
|
49
|
+
json_path = downloading_obj.download_comment_json_to_dir(project_id, temp_dir)
|
|
50
|
+
else:
|
|
51
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
52
|
+
json_path = downloading_obj.download_comment_json_to_dir(project_id, Path(str_temp_dir))
|
|
53
|
+
with json_path.open(encoding="utf-8") as f:
|
|
54
|
+
comment_list = json.load(f)
|
|
55
|
+
# 一時ディレクトリの場合はここでフィルタリング処理まで行う
|
|
56
|
+
if task_ids is not None:
|
|
57
|
+
task_id_set = set(task_ids)
|
|
58
|
+
comment_list = [e for e in comment_list if e["task_id"] in task_id_set]
|
|
59
|
+
|
|
60
|
+
if comment_type is not None:
|
|
61
|
+
comment_list = [e for e in comment_list if e["comment_type"] == comment_type.value]
|
|
62
|
+
|
|
63
|
+
# 返信回数を算出する
|
|
64
|
+
reply_counter = create_reply_counter(comment_list)
|
|
65
|
+
for c in comment_list:
|
|
66
|
+
key = (c["task_id"], c["input_data_id"], c["comment_id"])
|
|
67
|
+
c["reply_count"] = reply_counter.get(key, 0)
|
|
68
|
+
|
|
69
|
+
if exclude_reply:
|
|
70
|
+
# 返信コメントを除外する
|
|
71
|
+
comment_list = [e for e in comment_list if e["comment_node"]["_type"] != "Reply"]
|
|
72
|
+
|
|
73
|
+
visualize = AddProps(self.service, project_id)
|
|
74
|
+
comment_list = [visualize.add_properties_to_comment(e) for e in comment_list]
|
|
75
|
+
|
|
76
|
+
return comment_list
|
|
53
77
|
else:
|
|
54
78
|
json_path = comment_json
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
|
|
80
|
+
with json_path.open(encoding="utf-8") as f:
|
|
81
|
+
comment_list = json.load(f)
|
|
57
82
|
|
|
58
83
|
if task_ids is not None:
|
|
59
84
|
task_id_set = set(task_ids)
|
|
@@ -86,6 +111,7 @@ class ListAllComment(CommandLine):
|
|
|
86
111
|
|
|
87
112
|
task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
88
113
|
comment_type = CommentType(args.comment_type) if args.comment_type is not None else None
|
|
114
|
+
temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
|
|
89
115
|
|
|
90
116
|
main_obj = ListAllCommentMain(self.service)
|
|
91
117
|
comment_list = main_obj.get_all_comment(
|
|
@@ -94,6 +120,7 @@ class ListAllComment(CommandLine):
|
|
|
94
120
|
task_ids=task_id_list,
|
|
95
121
|
comment_type=comment_type,
|
|
96
122
|
exclude_reply=args.exclude_reply,
|
|
123
|
+
temp_dir=temp_dir,
|
|
97
124
|
)
|
|
98
125
|
|
|
99
126
|
logger.info(f"コメントの件数: {len(comment_list)}")
|
|
@@ -133,6 +160,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
133
160
|
|
|
134
161
|
parser.add_argument("--exclude_reply", action="store_true", help="返信コメントを除外します。")
|
|
135
162
|
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
"--temp_dir",
|
|
165
|
+
type=str,
|
|
166
|
+
help="``--comment_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
|
|
167
|
+
)
|
|
168
|
+
|
|
136
169
|
argument_parser.add_format(
|
|
137
170
|
choices=[
|
|
138
171
|
FormatArgument.CSV,
|
annofabcli/common/download.py
CHANGED
|
@@ -404,3 +404,72 @@ class DownloadingFile:
|
|
|
404
404
|
wait_options=wait_options,
|
|
405
405
|
)
|
|
406
406
|
return dest_path
|
|
407
|
+
|
|
408
|
+
def download_task_history_json_to_dir(
|
|
409
|
+
self,
|
|
410
|
+
project_id: str,
|
|
411
|
+
output_dir: Path,
|
|
412
|
+
) -> Path:
|
|
413
|
+
"""
|
|
414
|
+
タスク履歴JSONをoutput_dirに統一された命名規則でダウンロードする。
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
project_id: プロジェクトID
|
|
418
|
+
output_dir: 出力ディレクトリ
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
ダウンロードされたファイルのパス
|
|
422
|
+
"""
|
|
423
|
+
dest_path = output_dir / get_filename(project_id, "task_history", "json")
|
|
424
|
+
|
|
425
|
+
self.download_task_history_json(
|
|
426
|
+
project_id,
|
|
427
|
+
dest_path=str(dest_path),
|
|
428
|
+
)
|
|
429
|
+
return dest_path
|
|
430
|
+
|
|
431
|
+
def download_task_history_event_json_to_dir(
|
|
432
|
+
self,
|
|
433
|
+
project_id: str,
|
|
434
|
+
output_dir: Path,
|
|
435
|
+
) -> Path:
|
|
436
|
+
"""
|
|
437
|
+
タスク履歴イベントJSONをoutput_dirに統一された命名規則でダウンロードする。
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
project_id: プロジェクトID
|
|
441
|
+
output_dir: 出力ディレクトリ
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
ダウンロードされたファイルのパス
|
|
445
|
+
"""
|
|
446
|
+
dest_path = output_dir / get_filename(project_id, "task_history_event", "json")
|
|
447
|
+
|
|
448
|
+
self.download_task_history_event_json(
|
|
449
|
+
project_id,
|
|
450
|
+
dest_path=str(dest_path),
|
|
451
|
+
)
|
|
452
|
+
return dest_path
|
|
453
|
+
|
|
454
|
+
def download_comment_json_to_dir(
|
|
455
|
+
self,
|
|
456
|
+
project_id: str,
|
|
457
|
+
output_dir: Path,
|
|
458
|
+
) -> Path:
|
|
459
|
+
"""
|
|
460
|
+
コメントJSONをoutput_dirに統一された命名規則でダウンロードする。
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
project_id: プロジェクトID
|
|
464
|
+
output_dir: 出力ディレクトリ
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
ダウンロードされたファイルのパス
|
|
468
|
+
"""
|
|
469
|
+
dest_path = output_dir / get_filename(project_id, "comment", "json")
|
|
470
|
+
|
|
471
|
+
self.download_comment_json(
|
|
472
|
+
project_id,
|
|
473
|
+
dest_path=str(dest_path),
|
|
474
|
+
)
|
|
475
|
+
return dest_path
|
|
@@ -71,24 +71,39 @@ class ListInputDataWithJsonMain:
|
|
|
71
71
|
contain_parent_task_id_list: bool = False,
|
|
72
72
|
contain_supplementary_data_count: bool = False,
|
|
73
73
|
is_latest: bool = False,
|
|
74
|
+
temp_dir: Path | None = None,
|
|
74
75
|
) -> list[dict[str, Any]]:
|
|
75
76
|
if input_data_json is None:
|
|
76
77
|
downloading_obj = DownloadingFile(self.service)
|
|
77
78
|
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
78
79
|
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
79
|
-
|
|
80
|
-
json_path =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
if temp_dir is not None:
|
|
81
|
+
json_path = downloading_obj.download_input_data_json_to_dir(project_id, temp_dir, is_latest=is_latest)
|
|
82
|
+
else:
|
|
83
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
84
|
+
json_path = downloading_obj.download_input_data_json_to_dir(project_id, Path(str_temp_dir), is_latest=is_latest)
|
|
85
|
+
with json_path.open(encoding="utf-8") as f:
|
|
86
|
+
input_data_list = json.load(f)
|
|
87
|
+
# 一時ディレクトリの場合はここでフィルタリング処理まで行う
|
|
88
|
+
input_data_id_set = set(input_data_id_list) if input_data_id_list is not None else None
|
|
89
|
+
filtered_input_data_list = [e for e in input_data_list if self.filter_input_data_list(e, input_data_query=input_data_query, input_data_id_set=input_data_id_set)]
|
|
90
|
+
|
|
91
|
+
adding_obj = AddingDetailsToInputData(self.service, project_id)
|
|
92
|
+
if contain_parent_task_id_list:
|
|
93
|
+
adding_obj.add_parent_task_id_list_to_input_data_list(input_data_list)
|
|
94
|
+
|
|
95
|
+
if contain_supplementary_data_count:
|
|
96
|
+
adding_obj.add_supplementary_data_count_to_input_data_list(input_data_list)
|
|
97
|
+
|
|
98
|
+
# 入力データの不要なキーを削除する
|
|
99
|
+
for input_data in input_data_list:
|
|
100
|
+
remove_unnecessary_keys_from_input_data(input_data)
|
|
101
|
+
return filtered_input_data_list
|
|
88
102
|
else:
|
|
89
103
|
json_path = input_data_json
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
|
|
105
|
+
with json_path.open(encoding="utf-8") as f:
|
|
106
|
+
input_data_list = json.load(f)
|
|
92
107
|
|
|
93
108
|
input_data_id_set = set(input_data_id_list) if input_data_id_list is not None else None
|
|
94
109
|
filtered_input_data_list = [e for e in input_data_list if self.filter_input_data_list(e, input_data_query=input_data_query, input_data_id_set=input_data_id_set)]
|
|
@@ -117,6 +132,7 @@ class ListAllInputData(CommandLine):
|
|
|
117
132
|
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.TRAINING_DATA_USER, ProjectMemberRole.OWNER])
|
|
118
133
|
|
|
119
134
|
main_obj = ListInputDataWithJsonMain(self.service)
|
|
135
|
+
temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
|
|
120
136
|
input_data_list = main_obj.get_input_data_list(
|
|
121
137
|
project_id=project_id,
|
|
122
138
|
input_data_json=args.input_data_json,
|
|
@@ -125,6 +141,7 @@ class ListAllInputData(CommandLine):
|
|
|
125
141
|
is_latest=args.latest,
|
|
126
142
|
contain_parent_task_id_list=args.with_parent_task_id_list,
|
|
127
143
|
contain_supplementary_data_count=args.with_supplementary_data_count,
|
|
144
|
+
temp_dir=temp_dir,
|
|
128
145
|
)
|
|
129
146
|
|
|
130
147
|
logger.debug(f"入力データ一覧の件数: {len(input_data_list)}")
|
|
@@ -195,6 +212,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
195
212
|
|
|
196
213
|
parser.add_argument("--with_supplementary_data_count", action="store_true", help="入力データに紐づく補助情報の個数( ``supplementary_data_count`` )も出力します。")
|
|
197
214
|
|
|
215
|
+
parser.add_argument(
|
|
216
|
+
"--temp_dir",
|
|
217
|
+
type=str,
|
|
218
|
+
help="``--input_data_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
|
|
219
|
+
)
|
|
220
|
+
|
|
198
221
|
argument_parser.add_format(
|
|
199
222
|
choices=[
|
|
200
223
|
FormatArgument.CSV,
|
|
@@ -47,21 +47,34 @@ class ListTasksWithJsonMain:
|
|
|
47
47
|
task_id_list: list[str] | None = None,
|
|
48
48
|
task_query: TaskQuery | None = None,
|
|
49
49
|
is_latest: bool = False, # noqa: FBT001, FBT002
|
|
50
|
+
temp_dir: Path | None = None,
|
|
50
51
|
) -> list[dict[str, Any]]:
|
|
51
52
|
if task_json is None:
|
|
52
53
|
downloading_obj = DownloadingFile(self.service)
|
|
53
54
|
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
54
55
|
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
55
|
-
|
|
56
|
-
json_path =
|
|
57
|
-
|
|
58
|
-
with
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
if temp_dir is not None:
|
|
57
|
+
json_path = downloading_obj.download_task_json_to_dir(project_id, temp_dir, is_latest=is_latest)
|
|
58
|
+
else:
|
|
59
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
60
|
+
json_path = downloading_obj.download_task_json_to_dir(project_id, Path(str_temp_dir), is_latest=is_latest)
|
|
61
|
+
with json_path.open(encoding="utf-8") as f:
|
|
62
|
+
task_list = json.load(f)
|
|
63
|
+
# 一時ディレクトリの場合はここでフィルタリング処理まで行う
|
|
64
|
+
if task_query is not None:
|
|
65
|
+
task_query = self.facade.set_account_id_of_task_query(project_id, task_query)
|
|
66
|
+
|
|
67
|
+
logger.debug("出力対象のタスクを抽出しています。")
|
|
68
|
+
task_id_set = set(task_id_list) if task_id_list is not None else None
|
|
69
|
+
filtered_task_list = [e for e in task_list if self.match_task_with_conditions(e, task_query=task_query, task_id_set=task_id_set)]
|
|
70
|
+
|
|
71
|
+
visualize_obj = AddProps(self.service, project_id)
|
|
72
|
+
return [visualize_obj.add_properties_to_task(e) for e in filtered_task_list]
|
|
61
73
|
else:
|
|
62
74
|
json_path = task_json
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
|
|
76
|
+
with json_path.open(encoding="utf-8") as f:
|
|
77
|
+
task_list = json.load(f)
|
|
65
78
|
|
|
66
79
|
if task_query is not None:
|
|
67
80
|
task_query = self.facade.set_account_id_of_task_query(project_id, task_query)
|
|
@@ -85,12 +98,14 @@ class ListTasksWithJson(CommandLine):
|
|
|
85
98
|
super().validate_project(project_id, project_member_roles=None)
|
|
86
99
|
|
|
87
100
|
main_obj = ListTasksWithJsonMain(self.service)
|
|
101
|
+
temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
|
|
88
102
|
task_list = main_obj.get_task_list(
|
|
89
103
|
project_id=project_id,
|
|
90
104
|
task_json=args.task_json,
|
|
91
105
|
task_id_list=task_id_list,
|
|
92
106
|
task_query=task_query,
|
|
93
107
|
is_latest=args.latest,
|
|
108
|
+
temp_dir=temp_dir,
|
|
94
109
|
)
|
|
95
110
|
|
|
96
111
|
logger.debug(f"タスク一覧の件数: {len(task_list)}")
|
|
@@ -133,6 +148,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
133
148
|
"指定しない場合は、コマンドを実行した日の02:00(JST)頃のタスクの一覧が出力されます。",
|
|
134
149
|
)
|
|
135
150
|
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
"--temp_dir",
|
|
153
|
+
type=str,
|
|
154
|
+
help="``--task_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
|
|
155
|
+
)
|
|
156
|
+
|
|
136
157
|
argument_parser.add_format(
|
|
137
158
|
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON, FormatArgument.TASK_ID_LIST],
|
|
138
159
|
default=FormatArgument.CSV,
|
|
@@ -60,31 +60,35 @@ class ListAllTasksAddedTaskHistoryMain:
|
|
|
60
60
|
|
|
61
61
|
return task_list
|
|
62
62
|
|
|
63
|
-
def load_task_list(self, task_json_path: Path | None) -> list[dict[str, Any]]:
|
|
64
|
-
if task_json_path is
|
|
65
|
-
with task_json_path.open(encoding="utf-8") as f:
|
|
66
|
-
return json.load(f)
|
|
67
|
-
|
|
68
|
-
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
69
|
-
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
70
|
-
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
71
|
-
task_json_path = Path(str_temp_dir) / f"{self.project_id}__task.json"
|
|
72
|
-
self.downloading_obj.download_task_json(self.project_id, str(task_json_path))
|
|
73
|
-
with task_json_path.open(encoding="utf-8") as f:
|
|
74
|
-
return json.load(f)
|
|
75
|
-
|
|
76
|
-
def load_task_history_dict(self, task_history_json_path: Path | None) -> TaskHistoryDict:
|
|
77
|
-
if task_history_json_path is not None:
|
|
78
|
-
with task_history_json_path.open(encoding="utf-8") as f:
|
|
79
|
-
return json.load(f)
|
|
80
|
-
else:
|
|
63
|
+
def load_task_list(self, task_json_path: Path | None, temp_dir: Path | None) -> list[dict[str, Any]]:
|
|
64
|
+
if task_json_path is None:
|
|
81
65
|
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
82
66
|
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
with
|
|
87
|
-
|
|
67
|
+
if temp_dir is not None:
|
|
68
|
+
task_json_path = self.downloading_obj.download_task_json_to_dir(self.project_id, temp_dir)
|
|
69
|
+
else:
|
|
70
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
71
|
+
task_json_path = self.downloading_obj.download_task_json_to_dir(self.project_id, Path(str_temp_dir))
|
|
72
|
+
with task_json_path.open(encoding="utf-8") as f:
|
|
73
|
+
return json.load(f)
|
|
74
|
+
|
|
75
|
+
with task_json_path.open(encoding="utf-8") as f:
|
|
76
|
+
return json.load(f)
|
|
77
|
+
|
|
78
|
+
def load_task_history_dict(self, task_history_json_path: Path | None, temp_dir: Path | None) -> TaskHistoryDict:
|
|
79
|
+
if task_history_json_path is None:
|
|
80
|
+
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
81
|
+
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
82
|
+
if temp_dir is not None:
|
|
83
|
+
task_history_json_path = self.downloading_obj.download_task_history_json_to_dir(self.project_id, temp_dir)
|
|
84
|
+
else:
|
|
85
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
86
|
+
task_history_json_path = self.downloading_obj.download_task_history_json_to_dir(self.project_id, Path(str_temp_dir))
|
|
87
|
+
with task_history_json_path.open(encoding="utf-8") as f:
|
|
88
|
+
return json.load(f)
|
|
89
|
+
|
|
90
|
+
with task_history_json_path.open(encoding="utf-8") as f:
|
|
91
|
+
return json.load(f)
|
|
88
92
|
|
|
89
93
|
@staticmethod
|
|
90
94
|
def match_task_with_conditions(
|
|
@@ -120,12 +124,13 @@ class ListAllTasksAddedTaskHistoryMain:
|
|
|
120
124
|
task_history_json_path: Path | None,
|
|
121
125
|
task_id_list: list[str] | None,
|
|
122
126
|
task_query: TaskQuery | None,
|
|
127
|
+
temp_dir: Path | None,
|
|
123
128
|
):
|
|
124
129
|
"""
|
|
125
130
|
タスク履歴情報を加えたタスク一覧を取得する。
|
|
126
131
|
"""
|
|
127
|
-
task_list = self.load_task_list(task_json_path)
|
|
128
|
-
task_history_dict = self.load_task_history_dict(task_history_json_path)
|
|
132
|
+
task_list = self.load_task_list(task_json_path, temp_dir)
|
|
133
|
+
task_history_dict = self.load_task_history_dict(task_history_json_path, temp_dir)
|
|
129
134
|
|
|
130
135
|
filtered_task_list = self.filter_task_list(task_list, task_id_list=task_id_list, task_query=task_query)
|
|
131
136
|
|
|
@@ -163,11 +168,13 @@ class ListAllTasksAddedTaskHistory(CommandLine):
|
|
|
163
168
|
|
|
164
169
|
self.validate_project(project_id, [ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
165
170
|
|
|
171
|
+
temp_dir = Path(args.temp_dir) if args.temp_dir is not None else None
|
|
166
172
|
task_list = ListAllTasksAddedTaskHistoryMain(self.service, project_id).get_task_list_added_task_history(
|
|
167
173
|
task_json_path=args.task_json,
|
|
168
174
|
task_history_json_path=args.task_history_json,
|
|
169
175
|
task_id_list=task_id_list,
|
|
170
176
|
task_query=task_query,
|
|
177
|
+
temp_dir=temp_dir,
|
|
171
178
|
)
|
|
172
179
|
|
|
173
180
|
logger.info(f"タスク一覧の件数: {len(task_list)}")
|
|
@@ -200,6 +207,12 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
200
207
|
"JSONファイルは ``$ annofabcli task_history download`` コマンドで取得できます。",
|
|
201
208
|
)
|
|
202
209
|
|
|
210
|
+
parser.add_argument(
|
|
211
|
+
"--temp_dir",
|
|
212
|
+
type=str,
|
|
213
|
+
help="``--task_json`` と ``--task_history_json`` を指定しなかった場合、ダウンロードしたJSONファイルの保存先ディレクトリを指定できます。指定しない場合は、一時ディレクトリに保存されます。",
|
|
214
|
+
)
|
|
215
|
+
|
|
203
216
|
argument_parser.add_output()
|
|
204
217
|
|
|
205
218
|
argument_parser.add_format(
|
|
File without changes
|