annofabcli 1.95.0__py3-none-any.whl → 1.96.1__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/__version__.py +1 -1
- annofabcli/annotation_specs/export_annotation_specs.py +10 -5
- annofabcli/input_data/put_input_data.py +28 -29
- annofabcli/statistics/list_annotation_attribute.py +310 -0
- annofabcli/statistics/subcommand_statistics.py +2 -0
- annofabcli/supplementary/delete_supplementary_data.py +22 -24
- annofabcli/supplementary/put_supplementary_data.py +52 -29
- annofabcli/task/list_tasks_added_task_history.py +2 -1
- {annofabcli-1.95.0.dist-info → annofabcli-1.96.1.dist-info}/METADATA +1 -1
- {annofabcli-1.95.0.dist-info → annofabcli-1.96.1.dist-info}/RECORD +13 -12
- {annofabcli-1.95.0.dist-info → annofabcli-1.96.1.dist-info}/LICENSE +0 -0
- {annofabcli-1.95.0.dist-info → annofabcli-1.96.1.dist-info}/WHEEL +0 -0
- {annofabcli-1.95.0.dist-info → annofabcli-1.96.1.dist-info}/entry_points.txt +0 -0
annofabcli/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.96.1" # `poetry-dynamic-versioning`を使ってGitHubのバージョンタグを取得している。変更不要
|
|
@@ -20,15 +20,20 @@ from annofabcli.common.utils import print_according_to_format
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class
|
|
23
|
+
class ExportAnnotationSpecs(CommandLine):
|
|
24
24
|
COMMON_MESSAGE = "annofabcli annotation_specs export: error:"
|
|
25
25
|
|
|
26
26
|
def get_history_id_from_before_index(self, project_id: str, before: int) -> Optional[str]:
|
|
27
27
|
histories, _ = self.service.api.get_annotation_specs_histories(project_id)
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
sorted_histories = sorted(histories, key=lambda x: x["updated_datetime"], reverse=True)
|
|
29
|
+
|
|
30
|
+
if before + 1 > len(sorted_histories):
|
|
31
|
+
logger.warning(
|
|
32
|
+
f"アノテーション仕様の履歴は{len(sorted_histories)}個のため、最新より{before}個前のアノテーション仕様は見つかりませんでした。"
|
|
33
|
+
)
|
|
30
34
|
return None
|
|
31
|
-
|
|
35
|
+
|
|
36
|
+
history = sorted_histories[before]
|
|
32
37
|
return history["history_id"]
|
|
33
38
|
|
|
34
39
|
def get_exported_annotation_specs(self, project_id: str, history_id: Optional[str]) -> dict[str, Any]:
|
|
@@ -107,7 +112,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
107
112
|
def main(args: argparse.Namespace) -> None:
|
|
108
113
|
service = build_annofabapi_resource_and_login(args)
|
|
109
114
|
facade = AnnofabApiFacade(service)
|
|
110
|
-
|
|
115
|
+
ExportAnnotationSpecs(service, facade, args).main()
|
|
111
116
|
|
|
112
117
|
|
|
113
118
|
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import logging
|
|
3
|
+
import re
|
|
3
4
|
import sys
|
|
4
|
-
import uuid
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from functools import partial
|
|
7
7
|
from multiprocessing import Pool
|
|
@@ -25,14 +25,11 @@ from annofabcli.common.cli import (
|
|
|
25
25
|
get_json_from_args,
|
|
26
26
|
prompt_yesnoall,
|
|
27
27
|
)
|
|
28
|
-
from annofabcli.common.dataclasses import WaitOptions
|
|
29
28
|
from annofabcli.common.facade import AnnofabApiFacade
|
|
30
29
|
from annofabcli.common.utils import get_file_scheme_path
|
|
31
30
|
|
|
32
31
|
logger = logging.getLogger(__name__)
|
|
33
32
|
|
|
34
|
-
DEFAULT_WAIT_OPTIONS = WaitOptions(interval=60, max_tries=360)
|
|
35
|
-
|
|
36
33
|
|
|
37
34
|
@dataclass
|
|
38
35
|
class CsvInputData(DataClassJsonMixin):
|
|
@@ -43,7 +40,6 @@ class CsvInputData(DataClassJsonMixin):
|
|
|
43
40
|
input_data_name: str
|
|
44
41
|
input_data_path: str
|
|
45
42
|
input_data_id: Optional[str] = None
|
|
46
|
-
sign_required: Optional[bool] = None
|
|
47
43
|
|
|
48
44
|
|
|
49
45
|
@dataclass
|
|
@@ -55,7 +51,14 @@ class InputDataForPut(DataClassJsonMixin):
|
|
|
55
51
|
input_data_name: str
|
|
56
52
|
input_data_path: str
|
|
57
53
|
input_data_id: str
|
|
58
|
-
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def convert_input_data_name_to_input_data_id(input_data_name: str) -> str:
|
|
57
|
+
"""
|
|
58
|
+
入力データ名から、入力データIDを生成します。
|
|
59
|
+
* IDに使えない文字以外は`__`に変換する。
|
|
60
|
+
"""
|
|
61
|
+
return re.sub(r"[^a-zA-Z0-9_.-]", "__", input_data_name)
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
def read_input_data_csv(csv_file: Path) -> pandas.DataFrame:
|
|
@@ -64,7 +67,6 @@ def read_input_data_csv(csv_file: Path) -> pandas.DataFrame:
|
|
|
64
67
|
* input_data_name
|
|
65
68
|
* input_data_path
|
|
66
69
|
* input_data_id
|
|
67
|
-
* sign_required
|
|
68
70
|
|
|
69
71
|
Args:
|
|
70
72
|
csv_file (Path): CSVファイルのパス
|
|
@@ -76,6 +78,12 @@ def read_input_data_csv(csv_file: Path) -> pandas.DataFrame:
|
|
|
76
78
|
str(csv_file),
|
|
77
79
|
sep=",",
|
|
78
80
|
header=None,
|
|
81
|
+
# names引数に"sign_required"を指定している理由:
|
|
82
|
+
# v1.96.0以前では、`sign_required`列を読み込んでいた。したがって、4列のCSVを使っているユーザーは存在する
|
|
83
|
+
# CSVの列数が`names`引数で指定した列数より大きい場合、右側から列名が割り当てられる。
|
|
84
|
+
# (https://qiita.com/yuji38kwmt/items/ac46c3d0ccac109410ba)
|
|
85
|
+
# したがって、"sign_required"がないと4列のCSVは読み込めない。
|
|
86
|
+
# 4列のCSVもしばらくサポートするため、"sign_required"を指定している。
|
|
79
87
|
names=("input_data_name", "input_data_path", "input_data_id", "sign_required"),
|
|
80
88
|
# IDと名前は必ず文字列として読み込むようにする
|
|
81
89
|
dtype={"input_data_id": str, "input_data_name": str},
|
|
@@ -126,8 +134,8 @@ class SubPutInputData:
|
|
|
126
134
|
|
|
127
135
|
file_path = get_file_scheme_path(csv_input_data.input_data_path)
|
|
128
136
|
if file_path is not None:
|
|
129
|
-
request_body.update({"input_data_name": csv_input_data.input_data_name
|
|
130
|
-
logger.debug(f"'{file_path}'を入力データとして登録します。input_data_name={csv_input_data.input_data_name}")
|
|
137
|
+
request_body.update({"input_data_name": csv_input_data.input_data_name})
|
|
138
|
+
logger.debug(f"'{file_path}'を入力データとして登録します。input_data_name='{csv_input_data.input_data_name}'")
|
|
131
139
|
self.service.wrapper.put_input_data_from_file(
|
|
132
140
|
project_id, input_data_id=csv_input_data.input_data_id, file_path=file_path, request_body=request_body
|
|
133
141
|
)
|
|
@@ -137,7 +145,6 @@ class SubPutInputData:
|
|
|
137
145
|
{
|
|
138
146
|
"input_data_name": csv_input_data.input_data_name,
|
|
139
147
|
"input_data_path": csv_input_data.input_data_path,
|
|
140
|
-
"sign_required": csv_input_data.sign_required,
|
|
141
148
|
}
|
|
142
149
|
)
|
|
143
150
|
|
|
@@ -181,12 +188,13 @@ class SubPutInputData:
|
|
|
181
188
|
|
|
182
189
|
return self.confirm_processing(message_for_confirm)
|
|
183
190
|
|
|
184
|
-
def put_input_data_main(self, project_id: str, csv_input_data: CsvInputData, overwrite: bool = False) -> bool:
|
|
191
|
+
def put_input_data_main(self, project_id: str, csv_input_data: CsvInputData, *, overwrite: bool = False) -> bool:
|
|
185
192
|
input_data = InputDataForPut(
|
|
186
193
|
input_data_name=csv_input_data.input_data_name,
|
|
187
194
|
input_data_path=csv_input_data.input_data_path,
|
|
188
|
-
input_data_id=csv_input_data.input_data_id
|
|
189
|
-
|
|
195
|
+
input_data_id=csv_input_data.input_data_id
|
|
196
|
+
if csv_input_data.input_data_id is not None
|
|
197
|
+
else convert_input_data_name_to_input_data_id(csv_input_data.input_data_name),
|
|
190
198
|
)
|
|
191
199
|
|
|
192
200
|
last_updated_datetime = None
|
|
@@ -194,10 +202,10 @@ class SubPutInputData:
|
|
|
194
202
|
|
|
195
203
|
if dict_input_data is not None:
|
|
196
204
|
if overwrite:
|
|
197
|
-
logger.debug(f"input_data_id={input_data.input_data_id} はすでに存在します。")
|
|
205
|
+
logger.debug(f"input_data_id='{input_data.input_data_id}' はすでに存在します。")
|
|
198
206
|
last_updated_datetime = dict_input_data["updated_datetime"]
|
|
199
207
|
else:
|
|
200
|
-
logger.debug(f"input_data_id={input_data.input_data_id} がすでに存在するのでスキップします。")
|
|
208
|
+
logger.debug(f"input_data_id='{input_data.input_data_id}' がすでに存在するのでスキップします。")
|
|
201
209
|
return False
|
|
202
210
|
|
|
203
211
|
file_path = get_file_scheme_path(input_data.input_data_path)
|
|
@@ -278,14 +286,12 @@ class PutInputData(CommandLine):
|
|
|
278
286
|
|
|
279
287
|
@staticmethod
|
|
280
288
|
def get_input_data_list_from_df(df: pandas.DataFrame) -> list[CsvInputData]:
|
|
281
|
-
def create_input_data(e: Any): # noqa:
|
|
289
|
+
def create_input_data(e: Any) -> CsvInputData: # noqa: ANN401
|
|
282
290
|
input_data_id = e.input_data_id if not pandas.isna(e.input_data_id) else None
|
|
283
|
-
sign_required: Optional[bool] = e.sign_required if pandas.notna(e.sign_required) else None
|
|
284
291
|
return CsvInputData(
|
|
285
292
|
input_data_name=e.input_data_name,
|
|
286
293
|
input_data_path=e.input_data_path,
|
|
287
294
|
input_data_id=input_data_id,
|
|
288
|
-
sign_required=sign_required,
|
|
289
295
|
)
|
|
290
296
|
|
|
291
297
|
input_data_list = [create_input_data(e) for e in df.itertuples()]
|
|
@@ -381,7 +387,6 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
381
387
|
" * 1列目: input_data_name (required)\n"
|
|
382
388
|
" * 2列目: input_data_path (required)\n"
|
|
383
389
|
" * 3列目: input_data_id\n"
|
|
384
|
-
" * 4列目: sign_required (bool)\n"
|
|
385
390
|
),
|
|
386
391
|
)
|
|
387
392
|
|
|
@@ -400,25 +405,20 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
400
405
|
parser.add_argument(
|
|
401
406
|
"--overwrite",
|
|
402
407
|
action="store_true",
|
|
403
|
-
help="指定した場合、input_data_idがすでに存在していたら上書きします。指定しなければ、スキップします。"
|
|
404
|
-
" ``--csv`` , ``--json`` を指定したときのみ有効なオプションです。",
|
|
408
|
+
help="指定した場合、input_data_idがすでに存在していたら上書きします。指定しなければ、スキップします。",
|
|
405
409
|
)
|
|
406
410
|
|
|
407
411
|
parser.add_argument(
|
|
408
412
|
"--allow_duplicated_input_data",
|
|
409
413
|
action="store_true",
|
|
410
|
-
help=(
|
|
411
|
-
"``--csv`` , ``--json`` に渡した入力データの重複(input_data_name, input_data_path)を許可します。\n"
|
|
412
|
-
"``--csv`` , ``--json`` を指定したときのみ有効なオプションです。"
|
|
413
|
-
),
|
|
414
|
+
help=("``--csv`` , ``--json`` に渡した入力データの重複(input_data_name, input_data_path)を許可します。\n"),
|
|
414
415
|
)
|
|
415
416
|
|
|
416
417
|
parser.add_argument(
|
|
417
418
|
"--parallelism",
|
|
418
419
|
type=int,
|
|
419
420
|
choices=PARALLELISM_CHOICES,
|
|
420
|
-
help="
|
|
421
|
-
"``--csv`` , ``--json`` を指定したときのみ有効なオプションです。また、必ず ``--yes`` を指定してください。",
|
|
421
|
+
help="並列度。指定しない場合は、逐次的に処理します。指定する場合は、 ``--yes`` も指定してください。",
|
|
422
422
|
)
|
|
423
423
|
|
|
424
424
|
parser.set_defaults(subcommand_func=main)
|
|
@@ -427,9 +427,8 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
427
427
|
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
428
428
|
subcommand_name = "put"
|
|
429
429
|
subcommand_help = "入力データを登録します。"
|
|
430
|
-
description = "CSVに記載された情報から、入力データを登録します。"
|
|
431
430
|
epilog = "オーナロールを持つユーザで実行してください。"
|
|
432
431
|
|
|
433
|
-
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help,
|
|
432
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
|
|
434
433
|
parse_args(parser)
|
|
435
434
|
return parser
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# pylint: disable=too-many-lines
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import zipfile
|
|
9
|
+
from collections.abc import Collection, Iterator
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Literal, Optional, Union
|
|
12
|
+
|
|
13
|
+
import pandas
|
|
14
|
+
import pydantic
|
|
15
|
+
from annofabapi.models import ProjectMemberRole
|
|
16
|
+
from annofabapi.parser import (
|
|
17
|
+
SimpleAnnotationParser,
|
|
18
|
+
lazy_parse_simple_annotation_dir,
|
|
19
|
+
lazy_parse_simple_annotation_zip,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
import annofabcli
|
|
23
|
+
import annofabcli.common.cli
|
|
24
|
+
from annofabcli.common.cli import (
|
|
25
|
+
COMMAND_LINE_ERROR_STATUS_CODE,
|
|
26
|
+
ArgumentParser,
|
|
27
|
+
CommandLine,
|
|
28
|
+
build_annofabapi_resource_and_login,
|
|
29
|
+
)
|
|
30
|
+
from annofabcli.common.download import DownloadingFile
|
|
31
|
+
from annofabcli.common.enums import FormatArgument
|
|
32
|
+
from annofabcli.common.facade import (
|
|
33
|
+
AnnofabApiFacade,
|
|
34
|
+
TaskQuery,
|
|
35
|
+
match_annotation_with_task_query,
|
|
36
|
+
)
|
|
37
|
+
from annofabcli.common.type_util import assert_noreturn
|
|
38
|
+
from annofabcli.common.utils import print_csv, print_json
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def lazy_parse_simple_annotation_by_input_data(annotation_path: Path) -> Iterator[SimpleAnnotationParser]:
|
|
44
|
+
if not annotation_path.exists():
|
|
45
|
+
raise RuntimeError(f"'{annotation_path}' は存在しません。")
|
|
46
|
+
|
|
47
|
+
if annotation_path.is_dir():
|
|
48
|
+
return lazy_parse_simple_annotation_dir(annotation_path)
|
|
49
|
+
elif zipfile.is_zipfile(str(annotation_path)):
|
|
50
|
+
return lazy_parse_simple_annotation_zip(annotation_path)
|
|
51
|
+
else:
|
|
52
|
+
raise RuntimeError(f"'{annotation_path}'は、zipファイルまたはディレクトリではありません。")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AnnotationAttribute(pydantic.BaseModel):
|
|
56
|
+
"""
|
|
57
|
+
入力データまたはタスク単位の区間アノテーションの長さ情報。
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
task_id: str
|
|
61
|
+
task_status: str
|
|
62
|
+
task_phase: str
|
|
63
|
+
task_phase_stage: int
|
|
64
|
+
|
|
65
|
+
input_data_id: str
|
|
66
|
+
input_data_name: str
|
|
67
|
+
annotation_id: str
|
|
68
|
+
label: str
|
|
69
|
+
attributes: dict[str, Union[str, int, bool]]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_annotation_attribute_list_from_annotation_json(
|
|
73
|
+
simple_annotation: dict[str, Any], *, target_labels: Collection[str] | None = None
|
|
74
|
+
) -> list[AnnotationAttribute]:
|
|
75
|
+
"""
|
|
76
|
+
1個のアノテーションJSONに対して、アノテーションの属性情報を取得します。
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
simple_annotation: アノテーションJSONファイルの内容
|
|
80
|
+
target_labels: 絞り込むラベルのcollection
|
|
81
|
+
"""
|
|
82
|
+
details = simple_annotation["details"]
|
|
83
|
+
|
|
84
|
+
result = []
|
|
85
|
+
for detail in details:
|
|
86
|
+
if target_labels is not None: # noqa: SIM102
|
|
87
|
+
if detail["label"] not in target_labels:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
result.append(
|
|
91
|
+
AnnotationAttribute(
|
|
92
|
+
task_id=simple_annotation["task_id"],
|
|
93
|
+
task_status=simple_annotation["task_status"],
|
|
94
|
+
task_phase=simple_annotation["task_phase"],
|
|
95
|
+
task_phase_stage=simple_annotation["task_phase_stage"],
|
|
96
|
+
input_data_id=simple_annotation["input_data_id"],
|
|
97
|
+
input_data_name=simple_annotation["input_data_name"],
|
|
98
|
+
label=detail["label"],
|
|
99
|
+
annotation_id=detail["annotation_id"],
|
|
100
|
+
attributes=detail["attributes"],
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_annotation_attribute_list_from_annotation_zipdir_path(
|
|
107
|
+
annotation_zipdir_path: Path,
|
|
108
|
+
*,
|
|
109
|
+
target_task_ids: Optional[Collection[str]] = None,
|
|
110
|
+
task_query: Optional[TaskQuery] = None,
|
|
111
|
+
target_labels: Collection[str] | None = None,
|
|
112
|
+
) -> list[AnnotationAttribute]:
|
|
113
|
+
"""
|
|
114
|
+
アノテーションzipまたはそれを展開したディレクトリから、アノテーションの属性のlistを取得します。
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
"""
|
|
118
|
+
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
119
|
+
|
|
120
|
+
iter_parser = lazy_parse_simple_annotation_by_input_data(annotation_zipdir_path)
|
|
121
|
+
|
|
122
|
+
result = []
|
|
123
|
+
logger.debug("アノテーションzipまたはディレクトリを読み込み中")
|
|
124
|
+
for index, parser in enumerate(iter_parser):
|
|
125
|
+
if (index + 1) % 1000 == 0:
|
|
126
|
+
logger.debug(f"{index + 1} 件目のJSONを読み込み中")
|
|
127
|
+
|
|
128
|
+
if target_task_ids is not None and parser.task_id not in target_task_ids:
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
simple_annotation_dict = parser.load_json()
|
|
132
|
+
if task_query is not None: # noqa: SIM102
|
|
133
|
+
if not match_annotation_with_task_query(simple_annotation_dict, task_query):
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
sub_result = get_annotation_attribute_list_from_annotation_json(simple_annotation_dict, target_labels=target_labels)
|
|
137
|
+
result.extend(sub_result)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def print_annotation_attribute_list_as_csv(annotation_attribute_list: list, output_file: Optional[Path]) -> None:
|
|
143
|
+
df = pandas.json_normalize(annotation_attribute_list)
|
|
144
|
+
|
|
145
|
+
base_columns = [
|
|
146
|
+
"task_id",
|
|
147
|
+
"task_status",
|
|
148
|
+
"task_phase",
|
|
149
|
+
"task_phase_stage",
|
|
150
|
+
"input_data_id",
|
|
151
|
+
"input_data_name",
|
|
152
|
+
"annotation_id",
|
|
153
|
+
"label",
|
|
154
|
+
]
|
|
155
|
+
attribute_columns = [col for col in df.columns if col.startswith("attributes.")]
|
|
156
|
+
columns = base_columns + attribute_columns
|
|
157
|
+
print_csv(df[columns], output_file)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def print_annotation_attribute_list(
|
|
161
|
+
annotation_attribute_list: list[AnnotationAttribute],
|
|
162
|
+
output_file: Path,
|
|
163
|
+
output_format: Literal[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
|
164
|
+
) -> None:
|
|
165
|
+
tmp_annotation_attribute_list = [e.model_dump() for e in annotation_attribute_list]
|
|
166
|
+
if output_format == FormatArgument.CSV:
|
|
167
|
+
print_annotation_attribute_list_as_csv(tmp_annotation_attribute_list, output_file)
|
|
168
|
+
elif output_format == FormatArgument.JSON:
|
|
169
|
+
print_json(tmp_annotation_attribute_list, output=output_file, is_pretty=False)
|
|
170
|
+
elif output_format == FormatArgument.PRETTY_JSON:
|
|
171
|
+
print_json(tmp_annotation_attribute_list, output=output_file, is_pretty=True)
|
|
172
|
+
else:
|
|
173
|
+
raise assert_noreturn(output_format)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class ListAnnotationAttribute(CommandLine):
|
|
177
|
+
COMMON_MESSAGE = "annofabcli statistics list_annotation_attribute: error:"
|
|
178
|
+
|
|
179
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
180
|
+
if args.project_id is None and args.annotation is None:
|
|
181
|
+
print( # noqa: T201
|
|
182
|
+
f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
|
|
183
|
+
file=sys.stderr,
|
|
184
|
+
)
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
def main(self) -> None:
|
|
190
|
+
args = self.args
|
|
191
|
+
|
|
192
|
+
if not self.validate(args):
|
|
193
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
194
|
+
|
|
195
|
+
project_id: Optional[str] = args.project_id
|
|
196
|
+
if project_id is not None:
|
|
197
|
+
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
198
|
+
|
|
199
|
+
annotation_path = Path(args.annotation) if args.annotation is not None else None
|
|
200
|
+
|
|
201
|
+
task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
202
|
+
label_name_list = annofabcli.common.cli.get_list_from_args(args.label_name) if args.label_name is not None else None
|
|
203
|
+
task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
|
|
204
|
+
|
|
205
|
+
output_file: Path = args.output
|
|
206
|
+
output_format = FormatArgument(args.format)
|
|
207
|
+
|
|
208
|
+
downloading_obj = DownloadingFile(self.service)
|
|
209
|
+
|
|
210
|
+
def download_and_print_annotation_attribute_list(
|
|
211
|
+
project_id: str, temp_dir: Path, *, is_latest: bool, annotation_path: Optional[Path]
|
|
212
|
+
) -> None:
|
|
213
|
+
if annotation_path is None:
|
|
214
|
+
annotation_path = temp_dir / f"{project_id}__annotation.zip"
|
|
215
|
+
downloading_obj.download_annotation_zip(
|
|
216
|
+
project_id,
|
|
217
|
+
dest_path=annotation_path,
|
|
218
|
+
is_latest=is_latest,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
annotation_attribute_list = get_annotation_attribute_list_from_annotation_zipdir_path(
|
|
222
|
+
annotation_zipdir_path=annotation_path, target_task_ids=task_id_list, task_query=task_query, target_labels=label_name_list
|
|
223
|
+
)
|
|
224
|
+
print_annotation_attribute_list(annotation_attribute_list, output_file, output_format) # type: ignore[arg-type]
|
|
225
|
+
|
|
226
|
+
if project_id is not None:
|
|
227
|
+
if args.temp_dir is not None:
|
|
228
|
+
download_and_print_annotation_attribute_list(
|
|
229
|
+
project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
# `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
|
|
233
|
+
# https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
|
|
234
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
235
|
+
download_and_print_annotation_attribute_list(
|
|
236
|
+
project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
assert annotation_path is not None
|
|
240
|
+
annotation_attribute_list = get_annotation_attribute_list_from_annotation_zipdir_path(
|
|
241
|
+
annotation_zipdir_path=annotation_path, target_task_ids=task_id_list, task_query=task_query, target_labels=label_name_list
|
|
242
|
+
)
|
|
243
|
+
print_annotation_attribute_list(annotation_attribute_list, output_file, output_format) # type: ignore[arg-type]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
247
|
+
argument_parser = ArgumentParser(parser)
|
|
248
|
+
|
|
249
|
+
annotation_group = parser.add_mutually_exclusive_group(required=True)
|
|
250
|
+
annotation_group.add_argument(
|
|
251
|
+
"--annotation",
|
|
252
|
+
type=str,
|
|
253
|
+
help="アノテーションzip、またはzipを展開したディレクトリを指定します。指定しない場合はAnnofabからダウンロードします。",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
annotation_group.add_argument("-p", "--project_id", type=str, help="対象プロジェクトの project_id")
|
|
257
|
+
|
|
258
|
+
argument_parser.add_format(
|
|
259
|
+
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
|
260
|
+
default=FormatArgument.CSV,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
argument_parser.add_output()
|
|
264
|
+
|
|
265
|
+
parser.add_argument(
|
|
266
|
+
"-tq",
|
|
267
|
+
"--task_query",
|
|
268
|
+
type=str,
|
|
269
|
+
help="集計対象タスクを絞り込むためのクエリ条件をJSON形式で指定します。使用できるキーは task_id, status, phase, phase_stage です。"
|
|
270
|
+
" ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
|
|
271
|
+
)
|
|
272
|
+
argument_parser.add_task_id(required=False)
|
|
273
|
+
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
"--label_name",
|
|
276
|
+
type=str,
|
|
277
|
+
nargs="+",
|
|
278
|
+
required=False,
|
|
279
|
+
help="出力対象のアノテーションのラベル名(英語)を指定します。指定しない場合はラベル名で絞り込みません。"
|
|
280
|
+
" ``file://`` を先頭に付けると、ラベル名の一覧が記載されたファイルを指定できます。",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
parser.add_argument(
|
|
284
|
+
"--latest",
|
|
285
|
+
action="store_true",
|
|
286
|
+
help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。", # noqa: E501
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
parser.add_argument(
|
|
290
|
+
"--temp_dir",
|
|
291
|
+
type=Path,
|
|
292
|
+
help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
parser.set_defaults(subcommand_func=main)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def main(args: argparse.Namespace) -> None:
|
|
299
|
+
service = build_annofabapi_resource_and_login(args)
|
|
300
|
+
facade = AnnofabApiFacade(service)
|
|
301
|
+
ListAnnotationAttribute(service, facade, args).main()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
305
|
+
subcommand_name = "list_annotation_attribute"
|
|
306
|
+
subcommand_help = "アノテーションZIPを読み込み、アノテーションの属性値の一覧を出力します。"
|
|
307
|
+
epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
308
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
|
|
309
|
+
parse_args(parser)
|
|
310
|
+
return parser
|
|
@@ -4,6 +4,7 @@ from typing import Optional
|
|
|
4
4
|
import annofabcli
|
|
5
5
|
import annofabcli.common.cli
|
|
6
6
|
import annofabcli.stat_visualization.merge_visualization_dir
|
|
7
|
+
import annofabcli.statistics.list_annotation_attribute
|
|
7
8
|
import annofabcli.statistics.list_annotation_count
|
|
8
9
|
import annofabcli.statistics.list_annotation_duration
|
|
9
10
|
import annofabcli.statistics.list_video_duration
|
|
@@ -21,6 +22,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
21
22
|
subparsers = parser.add_subparsers(dest="subcommand_name")
|
|
22
23
|
|
|
23
24
|
# サブコマンドの定義
|
|
25
|
+
annofabcli.statistics.list_annotation_attribute.add_parser(subparsers)
|
|
24
26
|
annofabcli.statistics.list_annotation_count.add_parser(subparsers)
|
|
25
27
|
annofabcli.statistics.list_annotation_duration.add_parser(subparsers)
|
|
26
28
|
annofabcli.statistics.list_video_duration.add_parser(subparsers)
|
|
@@ -92,14 +92,14 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
92
92
|
supplementary_data = _get_supplementary_data_list(supplementary_data_id)
|
|
93
93
|
if supplementary_data is None:
|
|
94
94
|
logger.warning(
|
|
95
|
-
f"input_data_id={input_data_id} の入力データに、"
|
|
96
|
-
f"supplementary_data_id={supplementary_data_id} の補助情報は存在しないのでスキップします。"
|
|
95
|
+
f"input_data_id='{input_data_id}' の入力データに、"
|
|
96
|
+
f"supplementary_data_id='{supplementary_data_id}' の補助情報は存在しないのでスキップします。"
|
|
97
97
|
)
|
|
98
98
|
continue
|
|
99
99
|
|
|
100
100
|
message_for_confirm = (
|
|
101
|
-
f"補助情報 supplementary_data_id={supplementary_data_id}, "
|
|
102
|
-
f"supplementary_data_name={supplementary_data['supplementary_data_name']} を削除しますか?"
|
|
101
|
+
f"補助情報 supplementary_data_id='{supplementary_data_id}', "
|
|
102
|
+
f"supplementary_data_name='{supplementary_data['supplementary_data_name']}' を削除しますか?"
|
|
103
103
|
)
|
|
104
104
|
if not self.confirm_processing(message_for_confirm):
|
|
105
105
|
continue
|
|
@@ -107,17 +107,17 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
107
107
|
try:
|
|
108
108
|
self.service.api.delete_supplementary_data(project_id, input_data_id=input_data_id, supplementary_data_id=supplementary_data_id)
|
|
109
109
|
logger.debug(
|
|
110
|
-
f"補助情報 supplementary_data_id={supplementary_data_id}, "
|
|
111
|
-
f"supplementary_data_name={supplementary_data['supplementary_data_name']} を削除しました。"
|
|
112
|
-
f"(入力データ input_data_id={input_data_id}, "
|
|
113
|
-
f"input_data_name={input_data['input_data_name']} に紐付いている)"
|
|
110
|
+
f"補助情報 supplementary_data_id='{supplementary_data_id}', "
|
|
111
|
+
f"supplementary_data_name='{supplementary_data['supplementary_data_name']}' を削除しました。"
|
|
112
|
+
f"(入力データ input_data_id='{input_data_id}', "
|
|
113
|
+
f"input_data_name='{input_data['input_data_name']}' に紐付いている)"
|
|
114
114
|
)
|
|
115
115
|
deleted_count += 1
|
|
116
|
-
except requests.HTTPError
|
|
117
|
-
logger.warning(e)
|
|
116
|
+
except requests.HTTPError:
|
|
118
117
|
logger.warning(
|
|
119
|
-
f"補助情報 supplementary_data_id={supplementary_data_id}, "
|
|
120
|
-
f"supplementary_data_name={supplementary_data['supplementary_data_name']} の削除に失敗しました。"
|
|
118
|
+
f"補助情報 supplementary_data_id='{supplementary_data_id}', "
|
|
119
|
+
f"supplementary_data_name='{supplementary_data['supplementary_data_name']}' の削除に失敗しました。",
|
|
120
|
+
exc_info=True,
|
|
121
121
|
)
|
|
122
122
|
continue
|
|
123
123
|
return deleted_count
|
|
@@ -128,9 +128,8 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
128
128
|
for input_data_id, supplementary_data_id_list in input_data_dict.items():
|
|
129
129
|
try:
|
|
130
130
|
deleted_count += self.delete_supplementary_data_list_for_input_data(project_id, input_data_id, supplementary_data_id_list)
|
|
131
|
-
except Exception
|
|
132
|
-
logger.warning(
|
|
133
|
-
logger.warning(f"入力データ(input_data_id={input_data_id})配下の補助情報の削除に失敗しました。")
|
|
131
|
+
except Exception: # pylint: disable=broad-except
|
|
132
|
+
logger.warning(f"入力データ(input_data_id='{input_data_id}')配下の補助情報の削除に失敗しました。", exc_info=True)
|
|
134
133
|
|
|
135
134
|
logger.info(f"{deleted_count} / {total_count} 件の補助情報を削除しました。")
|
|
136
135
|
|
|
@@ -155,15 +154,15 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
155
154
|
try:
|
|
156
155
|
self.service.api.delete_supplementary_data(project_id, input_data_id=input_data_id, supplementary_data_id=supplementary_data_id)
|
|
157
156
|
logger.debug(
|
|
158
|
-
f"補助情報を削除しました。input_data_id={input_data_id}, supplementary_data_id={supplementary_data_id}, "
|
|
157
|
+
f"補助情報を削除しました。input_data_id='{input_data_id}', supplementary_data_id='{supplementary_data_id}', "
|
|
159
158
|
f"supplementary_data_name={supplementary_data['supplementary_data_name']}"
|
|
160
159
|
)
|
|
161
160
|
deleted_count += 1
|
|
162
|
-
except requests.HTTPError
|
|
163
|
-
logger.warning(e)
|
|
161
|
+
except requests.HTTPError:
|
|
164
162
|
logger.warning(
|
|
165
|
-
f"補助情報の削除に失敗しました。input_data_id={input_data_id}, supplementary_data_id={supplementary_data_id}, "
|
|
166
|
-
f"supplementary_data_name={supplementary_data['supplementary_data_name']}"
|
|
163
|
+
f"補助情報の削除に失敗しました。input_data_id='{input_data_id}', supplementary_data_id='{supplementary_data_id}', "
|
|
164
|
+
f"supplementary_data_name='{supplementary_data['supplementary_data_name']}'",
|
|
165
|
+
exc_info=True,
|
|
167
166
|
)
|
|
168
167
|
continue
|
|
169
168
|
|
|
@@ -174,7 +173,7 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
174
173
|
for input_data_id in input_data_id_list:
|
|
175
174
|
input_data = self.service.wrapper.get_input_data_or_none(project_id, input_data_id)
|
|
176
175
|
if input_data is None:
|
|
177
|
-
logger.warning(f"input_data_id={input_data_id} の入力データは存在しないので、補助情報の削除をスキップします。")
|
|
176
|
+
logger.warning(f"input_data_id='{input_data_id}' の入力データは存在しないので、補助情報の削除をスキップします。")
|
|
178
177
|
continue
|
|
179
178
|
input_data_name = input_data["input_data_name"]
|
|
180
179
|
|
|
@@ -202,9 +201,8 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
|
|
|
202
201
|
f"input_data_name='{input_data_name}') "
|
|
203
202
|
)
|
|
204
203
|
|
|
205
|
-
except Exception
|
|
206
|
-
logger.warning(
|
|
207
|
-
logger.warning(f"入力データ(input_data_id={input_data_id})配下の補助情報の削除に失敗しました。")
|
|
204
|
+
except Exception: # pylint: disable=broad-except
|
|
205
|
+
logger.warning(f"入力データ(input_data_id='{input_data_id}')配下の補助情報の削除に失敗しました。", exc_info=True)
|
|
208
206
|
|
|
209
207
|
logger.info(f"{len(dict_deleted_count)} / {len(input_data_id_list)} 件の入力データに紐づく補助情報を削除しました。")
|
|
210
208
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
+
import re
|
|
4
5
|
import sys
|
|
5
|
-
import uuid
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from functools import partial
|
|
8
8
|
from multiprocessing import Pool
|
|
@@ -32,6 +32,14 @@ from annofabcli.common.utils import get_file_scheme_path
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def convert_supplementary_data_name_to_supplementary_data_id(supplementary_data_name: str) -> str:
|
|
36
|
+
"""
|
|
37
|
+
補助情報データ名から、補助情報データIDを生成します。
|
|
38
|
+
* IDに使えない文字以外は`__`に変換する。
|
|
39
|
+
"""
|
|
40
|
+
return re.sub(r"[^a-zA-Z0-9_.-]", "__", supplementary_data_name)
|
|
41
|
+
|
|
42
|
+
|
|
35
43
|
@dataclass
|
|
36
44
|
class CsvSupplementaryData(DataClassJsonMixin):
|
|
37
45
|
"""
|
|
@@ -71,13 +79,13 @@ class SubPutSupplementaryData:
|
|
|
71
79
|
all_yes:
|
|
72
80
|
"""
|
|
73
81
|
|
|
74
|
-
def __init__(self, service: annofabapi.Resource, facade: AnnofabApiFacade, all_yes: bool = False) -> None:
|
|
82
|
+
def __init__(self, service: annofabapi.Resource, facade: AnnofabApiFacade, *, all_yes: bool = False) -> None:
|
|
75
83
|
self.service = service
|
|
76
84
|
self.facade = facade
|
|
77
85
|
self.all_yes = all_yes
|
|
78
86
|
self.supplementary_data_cache: dict[str, list[SupplementaryData]] = {}
|
|
79
87
|
|
|
80
|
-
def put_supplementary_data(self, project_id: str, supplementary_data: SupplementaryDataForPut)
|
|
88
|
+
def put_supplementary_data(self, project_id: str, supplementary_data: SupplementaryDataForPut) -> None:
|
|
81
89
|
file_path = get_file_scheme_path(supplementary_data.supplementary_data_path)
|
|
82
90
|
if file_path is not None:
|
|
83
91
|
request_body = {
|
|
@@ -89,7 +97,12 @@ class SubPutSupplementaryData:
|
|
|
89
97
|
if supplementary_data.supplementary_data_type is not None:
|
|
90
98
|
request_body.update({"supplementary_data_type": supplementary_data.supplementary_data_type})
|
|
91
99
|
|
|
92
|
-
logger.debug(
|
|
100
|
+
logger.debug(
|
|
101
|
+
f"'{file_path}'を補助情報として登録します。 :: "
|
|
102
|
+
f"input_data_id='{supplementary_data.input_data_id}', "
|
|
103
|
+
f"supplementary_data_id='{supplementary_data.supplementary_data_id}', "
|
|
104
|
+
f"supplementary_data_name='{supplementary_data.supplementary_data_name}'"
|
|
105
|
+
)
|
|
93
106
|
self.service.wrapper.put_supplementary_data_from_file(
|
|
94
107
|
project_id,
|
|
95
108
|
input_data_id=supplementary_data.input_data_id,
|
|
@@ -141,10 +154,20 @@ class SubPutSupplementaryData:
|
|
|
141
154
|
|
|
142
155
|
return yes
|
|
143
156
|
|
|
144
|
-
def confirm_put_supplementary_data(
|
|
145
|
-
|
|
157
|
+
def confirm_put_supplementary_data(
|
|
158
|
+
self, csv_supplementary_data: CsvSupplementaryData, supplementary_data_id: str, *, already_exists: bool = False
|
|
159
|
+
) -> bool:
|
|
146
160
|
if already_exists:
|
|
147
|
-
message_for_confirm
|
|
161
|
+
message_for_confirm = (
|
|
162
|
+
f"supplementary_data_name='{csv_supplementary_data.supplementary_data_name}', "
|
|
163
|
+
f"supplementary_data_id='{supplementary_data_id}'の補助情報を更新しますか?"
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
message_for_confirm = (
|
|
167
|
+
f"supplementary_data_name='{csv_supplementary_data.supplementary_data_name}', "
|
|
168
|
+
f"supplementary_data_id='{supplementary_data_id}'の補助情報を登録しますか?"
|
|
169
|
+
)
|
|
170
|
+
|
|
148
171
|
return self.confirm_processing(message_for_confirm)
|
|
149
172
|
|
|
150
173
|
def get_supplementary_data_list_cached(self, project_id: str, input_data_id: str) -> list[SupplementaryData]:
|
|
@@ -158,14 +181,14 @@ class SubPutSupplementaryData:
|
|
|
158
181
|
cached_list = self.get_supplementary_data_list_cached(project_id, input_data_id)
|
|
159
182
|
return first_true(cached_list, pred=lambda e: e["supplementary_data_id"] == supplementary_data_id)
|
|
160
183
|
|
|
161
|
-
def
|
|
162
|
-
cached_list = self.get_supplementary_data_list_cached(project_id, input_data_id)
|
|
163
|
-
return first_true(cached_list, pred=lambda e: e["supplementary_data_number"] == supplementary_data_number)
|
|
164
|
-
|
|
165
|
-
def put_supplementary_data_main(self, project_id: str, csv_supplementary_data: CsvSupplementaryData, overwrite: bool = False) -> bool: # noqa: FBT001, FBT002
|
|
184
|
+
def put_supplementary_data_main(self, project_id: str, csv_supplementary_data: CsvSupplementaryData, *, overwrite: bool = False) -> bool:
|
|
166
185
|
last_updated_datetime = None
|
|
167
186
|
input_data_id = csv_supplementary_data.input_data_id
|
|
168
|
-
supplementary_data_id =
|
|
187
|
+
supplementary_data_id = (
|
|
188
|
+
csv_supplementary_data.supplementary_data_id
|
|
189
|
+
if csv_supplementary_data.supplementary_data_id is not None
|
|
190
|
+
else convert_supplementary_data_name_to_supplementary_data_id(csv_supplementary_data.supplementary_data_name)
|
|
191
|
+
)
|
|
169
192
|
supplementary_data_path = csv_supplementary_data.supplementary_data_path
|
|
170
193
|
|
|
171
194
|
# input_data_idの存在確認
|
|
@@ -173,21 +196,20 @@ class SubPutSupplementaryData:
|
|
|
173
196
|
logger.warning(f"input_data_id='{input_data_id}'である入力データは存在しないため、補助情報の登録をスキップします。")
|
|
174
197
|
return False
|
|
175
198
|
|
|
176
|
-
|
|
177
|
-
old_supplementary_data_key = f"supplementary_data_id={supplementary_data_id}"
|
|
178
|
-
old_supplementary_data = self.get_supplementary_data_by_id(project_id, input_data_id, supplementary_data_id)
|
|
179
|
-
else:
|
|
180
|
-
supplementary_data_number = csv_supplementary_data.supplementary_data_number
|
|
181
|
-
old_supplementary_data_key = f"input_data_id={input_data_id}, supplementary_data_number={supplementary_data_number}"
|
|
182
|
-
old_supplementary_data = self.get_supplementary_data_by_number(project_id, input_data_id, supplementary_data_number)
|
|
183
|
-
supplementary_data_id = old_supplementary_data["supplementary_data_id"] if old_supplementary_data is not None else str(uuid.uuid4())
|
|
199
|
+
old_supplementary_data = self.get_supplementary_data_by_id(project_id, input_data_id, supplementary_data_id)
|
|
184
200
|
|
|
185
201
|
if old_supplementary_data is not None:
|
|
186
202
|
if overwrite:
|
|
187
|
-
logger.debug(
|
|
203
|
+
logger.debug(
|
|
204
|
+
f"supplementary_data_id='{supplementary_data_id}'である補助情報がすでに存在します。 :: "
|
|
205
|
+
f"input_data_id='{input_data_id}', supplementary_data_name='{csv_supplementary_data.supplementary_data_name}'"
|
|
206
|
+
)
|
|
188
207
|
last_updated_datetime = old_supplementary_data["updated_datetime"]
|
|
189
208
|
else:
|
|
190
|
-
logger.debug(
|
|
209
|
+
logger.debug(
|
|
210
|
+
f"supplementary_data_id='{supplementary_data_id}'である補助情報がすでに存在するので、補助情報の登録をスキップします。 :: "
|
|
211
|
+
f"input_data_id='{input_data_id}', supplementary_data_name='{csv_supplementary_data.supplementary_data_name}'"
|
|
212
|
+
)
|
|
191
213
|
return False
|
|
192
214
|
|
|
193
215
|
file_path = get_file_scheme_path(supplementary_data_path)
|
|
@@ -197,7 +219,7 @@ class SubPutSupplementaryData:
|
|
|
197
219
|
logger.warning(f"'{supplementary_data_path}' は存在しません。")
|
|
198
220
|
return False
|
|
199
221
|
|
|
200
|
-
if not self.confirm_put_supplementary_data(csv_supplementary_data, already_exists=last_updated_datetime is not None):
|
|
222
|
+
if not self.confirm_put_supplementary_data(csv_supplementary_data, supplementary_data_id, already_exists=last_updated_datetime is not None):
|
|
201
223
|
return False
|
|
202
224
|
|
|
203
225
|
# 補助情報を登録
|
|
@@ -213,8 +235,8 @@ class SubPutSupplementaryData:
|
|
|
213
235
|
try:
|
|
214
236
|
self.put_supplementary_data(project_id, supplementary_data_for_put)
|
|
215
237
|
logger.debug(
|
|
216
|
-
f"補助情報を登録しました。"
|
|
217
|
-
f"input_data_id='{supplementary_data_for_put.input_data_id}',"
|
|
238
|
+
f"補助情報を登録しました。 :: "
|
|
239
|
+
f"input_data_id='{supplementary_data_for_put.input_data_id}', "
|
|
218
240
|
f"supplementary_data_id='{supplementary_data_for_put.supplementary_data_id}', "
|
|
219
241
|
f"supplementary_data_name='{supplementary_data_for_put.supplementary_data_name}'"
|
|
220
242
|
)
|
|
@@ -222,7 +244,7 @@ class SubPutSupplementaryData:
|
|
|
222
244
|
|
|
223
245
|
except requests.exceptions.HTTPError:
|
|
224
246
|
logger.warning(
|
|
225
|
-
f"補助情報の登録に失敗しました。"
|
|
247
|
+
f"補助情報の登録に失敗しました。 ::"
|
|
226
248
|
f"input_data_id='{supplementary_data_for_put.input_data_id}',"
|
|
227
249
|
f"supplementary_data_id='{supplementary_data_for_put.supplementary_data_id}', "
|
|
228
250
|
f"supplementary_data_name='{supplementary_data_for_put.supplementary_data_name}'",
|
|
@@ -240,7 +262,8 @@ class PutSupplementaryData(CommandLine):
|
|
|
240
262
|
self,
|
|
241
263
|
project_id: str,
|
|
242
264
|
supplementary_data_list: list[CsvSupplementaryData],
|
|
243
|
-
|
|
265
|
+
*,
|
|
266
|
+
overwrite: bool = False,
|
|
244
267
|
parallelism: Optional[int] = None,
|
|
245
268
|
) -> None:
|
|
246
269
|
"""
|
|
@@ -280,7 +303,7 @@ class PutSupplementaryData(CommandLine):
|
|
|
280
303
|
|
|
281
304
|
@staticmethod
|
|
282
305
|
def get_supplementary_data_list_from_csv(csv_path: Path) -> list[CsvSupplementaryData]:
|
|
283
|
-
def create_supplementary_data(e: Any): # noqa:
|
|
306
|
+
def create_supplementary_data(e: Any) -> CsvSupplementaryData: # noqa: ANN401
|
|
284
307
|
supplementary_data_id = e.supplementary_data_id if not pandas.isna(e.supplementary_data_id) else None
|
|
285
308
|
supplementary_data_type = e.supplementary_data_type if not pandas.isna(e.supplementary_data_type) else None
|
|
286
309
|
return CsvSupplementaryData(
|
|
@@ -309,9 +309,10 @@ class ListTasksAddedTaskHistoryMain:
|
|
|
309
309
|
task_list = list_task_obj.get_task_list(self.project_id, task_id_list=task_id_list, task_query=task_query)
|
|
310
310
|
|
|
311
311
|
obj = AddingAdditionalInfoToTask(self.service, project_id=self.project_id)
|
|
312
|
+
logger.info(f"{len(task_list)} 件のタスクの履歴情報を取得します。")
|
|
312
313
|
|
|
313
314
|
for index, task in enumerate(task_list):
|
|
314
|
-
if (index + 1) %
|
|
315
|
+
if (index + 1) % 100 == 0:
|
|
315
316
|
logger.debug(f"{index + 1} 件目のタスク履歴情報を取得します。")
|
|
316
317
|
|
|
317
318
|
obj.add_additional_info_to_task(task)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
annofabcli/__init__.py,sha256=NMA7kFxmLlCiILQPHJa9mEuqXxtLALw_dwyXYsvz4VM,71
|
|
2
2
|
annofabcli/__main__.py,sha256=JzfycqVG9ENhWOCxTouZwpHwWTSrI-grLsaMudxjyBM,5283
|
|
3
|
-
annofabcli/__version__.py,sha256=
|
|
3
|
+
annofabcli/__version__.py,sha256=fRMeiNR_DG_Pyx4KmlvY_dwyMYU17Xi-HyO9fioFt3g,132
|
|
4
4
|
annofabcli/annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
annofabcli/annotation/annotation_query.py,sha256=ke3W3RT1-WfFzwt-TXcQwGmghG34vcKJkM_jxgbNKjU,15922
|
|
6
6
|
annofabcli/annotation/change_annotation_attributes.py,sha256=zHXyENZfbMGL_15xiK7Cy4cQ2sV0GjSVmKuPm3sOX7Y,17173
|
|
@@ -15,7 +15,7 @@ annofabcli/annotation/list_annotation_count.py,sha256=T9fbaoxWeDJIVgW_YgHRldbwrV
|
|
|
15
15
|
annofabcli/annotation/restore_annotation.py,sha256=naUEbt48ION9JSijCBR2aQdaoCrRu005tYq0vgUtyp0,14683
|
|
16
16
|
annofabcli/annotation/subcommand_annotation.py,sha256=Xu4SOHLpo_kPZbvE8hQHr9NRduPTESqu7pYC4LGDlXg,1872
|
|
17
17
|
annofabcli/annotation_specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
annofabcli/annotation_specs/export_annotation_specs.py,sha256=
|
|
18
|
+
annofabcli/annotation_specs/export_annotation_specs.py,sha256=eZF5fj2P5U22_5UKgbpsUhZEUvVPMPerMpN4miIcnSA,4821
|
|
19
19
|
annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py,sha256=L0H0M31Amnm3ZEg9JdQe2A1vprsCLPTpj0LUFnP6GDY,8330
|
|
20
20
|
annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py,sha256=mISJAgRTBMvISPk5__0w0MmhTkyQCcGPDin8K97x5Eg,6943
|
|
21
21
|
annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py,sha256=5tlvSdUXAUn8uQ1BXNWxkR2MIKnV6AJoxveiwD21IAo,7114
|
|
@@ -75,7 +75,7 @@ annofabcli/input_data/download_input_data_json.py,sha256=vxGoeM3ZEggQbWiWsrDK0_G
|
|
|
75
75
|
annofabcli/input_data/list_all_input_data.py,sha256=Kq261WCkma5UNKfMDT7O6Z-dzzqp-KP1wL0DvHe5fH8,9879
|
|
76
76
|
annofabcli/input_data/list_all_input_data_merged_task.py,sha256=UaBJW-nMHytmQ4okg69Ew1jhC2vMNnRMgl2lEBabtUI,12850
|
|
77
77
|
annofabcli/input_data/list_input_data.py,sha256=RBxsHyKg1bVIEQUFDkfrq-nJmEdEYNoCjJ2L2GgSfeU,11519
|
|
78
|
-
annofabcli/input_data/put_input_data.py,sha256=
|
|
78
|
+
annofabcli/input_data/put_input_data.py,sha256=x54C-rLJVzr1YF2GlMR0w0HJReOE3E7YKiBeuh0RsTI,17934
|
|
79
79
|
annofabcli/input_data/put_input_data_with_zip.py,sha256=SA4aMAwMBFgc9Lh0zmRCbmkXG4AMrcBqd5zeTSdr8lc,5566
|
|
80
80
|
annofabcli/input_data/subcommand_input_data.py,sha256=X8EoxsF6PMiKrvk_r7PIe2D0WZuaPlgLJRuTiljPIdM,2048
|
|
81
81
|
annofabcli/input_data/update_metadata_of_input_data.py,sha256=txdliirBrtoTabGEyWYLPclZC_DarEpRAY3MTsbViPA,11556
|
|
@@ -130,12 +130,13 @@ annofabcli/stat_visualization/write_performance_rating_csv.py,sha256=TDn7-poyFt2
|
|
|
130
130
|
annofabcli/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
131
131
|
annofabcli/statistics/histogram.py,sha256=CvzDxT2cKLSnBGSqkZE6p92PayGxYYja1YyB24M4ALU,3245
|
|
132
132
|
annofabcli/statistics/linegraph.py,sha256=0kr7jVBNMiM2ECYhv3Ry5RitElKerSl9ZKxbKzfiplI,12494
|
|
133
|
+
annofabcli/statistics/list_annotation_attribute.py,sha256=87jjNCOXJUbWnmswMCLN7GTjGsBfqpFJ6hViWmnj8Y4,12557
|
|
133
134
|
annofabcli/statistics/list_annotation_count.py,sha256=xsqXxRO21MW7Wbs96PEa8gHb3i7bxjQvoMHLB2eT-0g,50360
|
|
134
135
|
annofabcli/statistics/list_annotation_duration.py,sha256=9PCg1IA_g4HoHHSfsMVb2zBMLfLJ3u9Id8Qa9L8Qhko,31923
|
|
135
136
|
annofabcli/statistics/list_video_duration.py,sha256=uNeMteRBX2JG_AWmcgMJj0Jzbq_qF7bvAwr25GmeIiw,9124
|
|
136
137
|
annofabcli/statistics/list_worktime.py,sha256=C7Yu3IOW2EvhkJJv6gY3hNdS9_TOLmT_9LZsB7vLJ1o,6493
|
|
137
138
|
annofabcli/statistics/scatter.py,sha256=IUCwXix9GbZb6V82wjjb5q2eamrT5HQsU_bzDTjAFnM,11011
|
|
138
|
-
annofabcli/statistics/subcommand_statistics.py,sha256=
|
|
139
|
+
annofabcli/statistics/subcommand_statistics.py,sha256=JPixzhJGClQYVH6Tgby3KTD85df-aEJKvLAjfJCv-1E,2259
|
|
139
140
|
annofabcli/statistics/summarize_task_count.py,sha256=8OH6BBRYRjHJkWRTjU0A0OfXa7f3NIRHrxPNFlRt_hM,9707
|
|
140
141
|
annofabcli/statistics/summarize_task_count_by_task_id_group.py,sha256=TSSmcFv615NLcq6uqXmg3ilYqSHl3A5qp90msVQM1gE,8646
|
|
141
142
|
annofabcli/statistics/summarize_task_count_by_user.py,sha256=TRoJXpt2HOVb8QO2YtRejkOAxyK80_NsPt3Vk9es9C8,6948
|
|
@@ -166,9 +167,9 @@ annofabcli/statistics/visualize_annotation_duration.py,sha256=J4Z3Ql1sdiTraMnRaR
|
|
|
166
167
|
annofabcli/statistics/visualize_statistics.py,sha256=3bP9WRALADkAxPePa1Ix49apahi1V_qjAL6Dbrwtxd0,39550
|
|
167
168
|
annofabcli/statistics/visualize_video_duration.py,sha256=A6Q91aWx7GO4F0TGxbiQI0za8BFr7IqiUaYyw3__BgY,16842
|
|
168
169
|
annofabcli/supplementary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
169
|
-
annofabcli/supplementary/delete_supplementary_data.py,sha256=
|
|
170
|
+
annofabcli/supplementary/delete_supplementary_data.py,sha256=QeOohZ6RnsxC9rKSPi0JJqJXmj99ZL17psulTqlEQFU,13738
|
|
170
171
|
annofabcli/supplementary/list_supplementary_data.py,sha256=F4iJnQi_4W7S_d7ahqxWFSFcnfiKYhNuysC28v0QUWA,7649
|
|
171
|
-
annofabcli/supplementary/put_supplementary_data.py,sha256=
|
|
172
|
+
annofabcli/supplementary/put_supplementary_data.py,sha256=FwFRszjcsJt-UrT2RCRAs4zzYea03CbDoiKBUZQW0zc,19581
|
|
172
173
|
annofabcli/supplementary/subcommand_supplementary.py,sha256=F8qfuNQzgW5HV1QKB4h0DWN7-kPVQcoFQwPfW_vjZVk,1079
|
|
173
174
|
annofabcli/task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
174
175
|
annofabcli/task/cancel_acceptance.py,sha256=rHKmAzQE1a25szVWb7dexokeW32V12bkyDIYM_oIl5U,13899
|
|
@@ -183,7 +184,7 @@ annofabcli/task/download_task_json.py,sha256=Ocjecmdf2WV_Sq3u1InfMLIsT3XSw0ojyJm
|
|
|
183
184
|
annofabcli/task/list_all_tasks.py,sha256=F9GpzzgWffF3lUeGrFIvjweq-iEwJ1c-g8usskO_2dE,6506
|
|
184
185
|
annofabcli/task/list_all_tasks_added_task_history.py,sha256=fkdiuo64iS7xxvIfGKzSiUPPEMiCVnJjjcAtMxe2Ngs,9551
|
|
185
186
|
annofabcli/task/list_tasks.py,sha256=O4jjp_zdmurcGNWXFp9JXHJsH4nhlR5e3ok96YnD1SI,10237
|
|
186
|
-
annofabcli/task/list_tasks_added_task_history.py,sha256=
|
|
187
|
+
annofabcli/task/list_tasks_added_task_history.py,sha256=7avkLYFErcdSNxGc8CQXr4FFIF1z9FtQJBSloD-tzBI,19675
|
|
187
188
|
annofabcli/task/put_tasks.py,sha256=hT2xPowJmcNJhjxoAm-MFiKTw_RFcJUYlpeanegVrAU,13400
|
|
188
189
|
annofabcli/task/put_tasks_by_count.py,sha256=MUHfWhqtSAXnB3O36p3bMSSgQ_3Zek9GT5qRvHGx8Lo,6041
|
|
189
190
|
annofabcli/task/reject_tasks.py,sha256=5ByAN6VnKwvU5BT_cfsHwA1jLDl74bonqk3bwtnrkPU,23139
|
|
@@ -199,8 +200,8 @@ annofabcli/task_history_event/download_task_history_event_json.py,sha256=hQLVbQ0
|
|
|
199
200
|
annofabcli/task_history_event/list_all_task_history_event.py,sha256=JQEgwOIXbbTIfeX23AVaoySHViOR9UGm9uoXuhVEBqo,6446
|
|
200
201
|
annofabcli/task_history_event/list_worktime.py,sha256=9jsRYa2C9bva8E1Aqxv9CCKDuCP0MvbiaIyQFTDpjqY,13150
|
|
201
202
|
annofabcli/task_history_event/subcommand_task_history_event.py,sha256=mJVJoT4RXk4HWnY7-Nrsl4If-gtaIIEXd2z7eFZwM2I,1260
|
|
202
|
-
annofabcli-1.
|
|
203
|
-
annofabcli-1.
|
|
204
|
-
annofabcli-1.
|
|
205
|
-
annofabcli-1.
|
|
206
|
-
annofabcli-1.
|
|
203
|
+
annofabcli-1.96.1.dist-info/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
|
|
204
|
+
annofabcli-1.96.1.dist-info/METADATA,sha256=u94b-Jzx5b2TOxFlJeBqtJbcbkkBxEgCtLomabfLhP4,5626
|
|
205
|
+
annofabcli-1.96.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
206
|
+
annofabcli-1.96.1.dist-info/entry_points.txt,sha256=A8vlN9fiMhbYRcdBfSpl7piYzAwvkMhRXIPQUAvQFUo,55
|
|
207
|
+
annofabcli-1.96.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|