annofabcli 1.110.0__py3-none-any.whl → 1.111.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/annotation/import_annotation.py +9 -7
- annofabcli/annotation_zip/list_single_point_annotation.py +317 -0
- annofabcli/annotation_zip/subcommand_annotation_zip.py +2 -0
- annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +10 -1
- annofabcli/statistics/visualization/model.py +8 -0
- annofabcli/statistics/visualization/project_dir.py +1 -1
- annofabcli/statistics/visualize_statistics.py +3 -1
- annofabcli/task/put_tasks.py +1 -1
- {annofabcli-1.110.0.dist-info → annofabcli-1.111.1.dist-info}/METADATA +1 -1
- {annofabcli-1.110.0.dist-info → annofabcli-1.111.1.dist-info}/RECORD +13 -12
- {annofabcli-1.110.0.dist-info → annofabcli-1.111.1.dist-info}/WHEEL +0 -0
- {annofabcli-1.110.0.dist-info → annofabcli-1.111.1.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.110.0.dist-info → annofabcli-1.111.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -254,8 +254,8 @@ class AnnotationConverter:
|
|
|
254
254
|
for attribute_name, attribute_value in attributes.items():
|
|
255
255
|
try:
|
|
256
256
|
specs_additional_data = self.annotation_specs_accessor.get_attribute(attribute_name=attribute_name, label=label)
|
|
257
|
-
except ValueError:
|
|
258
|
-
logger.warning(f"アノテーション仕様に属性名(英語)が'{attribute_name}'である属性情報が存在しないか、複数存在します。 :: {log_message_suffix}")
|
|
257
|
+
except ValueError as e:
|
|
258
|
+
logger.warning(f"アノテーション仕様に属性名(英語)が'{attribute_name}'である属性情報が存在しないか、複数存在します。 :: {log_message_suffix} :: error={e}")
|
|
259
259
|
if self.is_strict:
|
|
260
260
|
raise
|
|
261
261
|
continue
|
|
@@ -298,12 +298,11 @@ class AnnotationConverter:
|
|
|
298
298
|
ValueError: 存在しないラベル名が指定された場合(`self.is_strict`がFalseでもraiseされる9
|
|
299
299
|
|
|
300
300
|
"""
|
|
301
|
-
log_message_suffix = f"task_id='{parser.task_id}', input_data_id='{parser.input_data_id}', label_name='{detail.label}', annotation_id='{detail.annotation_id}'"
|
|
302
301
|
|
|
303
302
|
try:
|
|
304
303
|
label_info = self.annotation_specs_accessor.get_label(label_name=detail.label)
|
|
305
|
-
except ValueError:
|
|
306
|
-
logger.warning(f"アノテーション仕様にラベル名(英語)が'{detail.label}'であるラベル情報が存在しないか、または複数存在します。 :: {log_message_suffix}")
|
|
304
|
+
except ValueError as e:
|
|
305
|
+
logger.warning(f"アノテーション仕様にラベル名(英語)が'{detail.label}'であるラベル情報が存在しないか、または複数存在します。 :: {log_message_suffix} :: error={e}")
|
|
307
306
|
raise
|
|
308
307
|
|
|
309
308
|
if detail.attributes is not None:
|
|
@@ -361,9 +360,12 @@ class AnnotationConverter:
|
|
|
361
360
|
old_dict_detail[old_detail["annotation_id"]] = old_detail
|
|
362
361
|
|
|
363
362
|
new_request_details: list[dict[str, Any]] = []
|
|
364
|
-
for detail in details:
|
|
363
|
+
for detail_index, detail in enumerate(details):
|
|
365
364
|
try:
|
|
366
|
-
|
|
365
|
+
# detail_indexを出力する理由: annotation_idはNoneだとどれが問題なのか分からないため
|
|
366
|
+
log_message_suffix = (
|
|
367
|
+
f"task_id='{parser.task_id}', input_data_id='{parser.input_data_id}', label_name='{detail.label}', annotation_id='{detail.annotation_id}', detail_index={detail_index}"
|
|
368
|
+
)
|
|
367
369
|
|
|
368
370
|
request_detail = self.convert_annotation_detail(parser, detail, log_message_suffix=log_message_suffix)
|
|
369
371
|
except Exception as e:
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
from collections.abc import Collection
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
import pandas
|
|
11
|
+
from annofabapi.models import InputDataType, ProjectMemberRole
|
|
12
|
+
from dataclasses_json import DataClassJsonMixin
|
|
13
|
+
|
|
14
|
+
import annofabcli
|
|
15
|
+
import annofabcli.common.cli
|
|
16
|
+
from annofabcli.common.annofab.annotation_zip import lazy_parse_simple_annotation_by_input_data
|
|
17
|
+
from annofabcli.common.cli import COMMAND_LINE_ERROR_STATUS_CODE, ArgumentParser, CommandLine, build_annofabapi_resource_and_login, get_list_from_args
|
|
18
|
+
from annofabcli.common.download import DownloadingFile
|
|
19
|
+
from annofabcli.common.enums import FormatArgument
|
|
20
|
+
from annofabcli.common.facade import (
|
|
21
|
+
AnnofabApiFacade,
|
|
22
|
+
TaskQuery,
|
|
23
|
+
match_annotation_with_task_query,
|
|
24
|
+
)
|
|
25
|
+
from annofabcli.common.utils import print_csv, print_json
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class AnnotationSinglePointInfo(DataClassJsonMixin):
|
|
32
|
+
project_id: str
|
|
33
|
+
task_id: str
|
|
34
|
+
task_status: str
|
|
35
|
+
task_phase: str
|
|
36
|
+
task_phase_stage: int
|
|
37
|
+
|
|
38
|
+
input_data_id: str
|
|
39
|
+
input_data_name: str
|
|
40
|
+
|
|
41
|
+
updated_datetime: Optional[str]
|
|
42
|
+
"""アノテーションJSONに格納されているアノテーションの更新日時"""
|
|
43
|
+
|
|
44
|
+
label: str
|
|
45
|
+
annotation_id: str
|
|
46
|
+
point: dict[str, int]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_annotation_single_point_info_list(simple_annotation: dict[str, Any], *, target_label_names: Optional[Collection[str]] = None) -> list[AnnotationSinglePointInfo]:
|
|
50
|
+
result = []
|
|
51
|
+
target_label_names_set = set(target_label_names) if target_label_names is not None else None
|
|
52
|
+
for detail in simple_annotation["details"]:
|
|
53
|
+
if detail["data"]["_type"] == "SinglePoint":
|
|
54
|
+
label = detail["label"]
|
|
55
|
+
# ラベル名によるフィルタリング
|
|
56
|
+
if target_label_names_set is not None and label not in target_label_names_set:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
point = detail["data"]["point"]
|
|
60
|
+
|
|
61
|
+
result.append(
|
|
62
|
+
AnnotationSinglePointInfo(
|
|
63
|
+
project_id=simple_annotation["project_id"],
|
|
64
|
+
task_id=simple_annotation["task_id"],
|
|
65
|
+
task_phase=simple_annotation["task_phase"],
|
|
66
|
+
task_phase_stage=simple_annotation["task_phase_stage"],
|
|
67
|
+
task_status=simple_annotation["task_status"],
|
|
68
|
+
input_data_id=simple_annotation["input_data_id"],
|
|
69
|
+
input_data_name=simple_annotation["input_data_name"],
|
|
70
|
+
label=label,
|
|
71
|
+
annotation_id=detail["annotation_id"],
|
|
72
|
+
point=point,
|
|
73
|
+
updated_datetime=simple_annotation["updated_datetime"],
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_annotation_single_point_info_list_from_annotation_path(
|
|
81
|
+
annotation_path: Path,
|
|
82
|
+
*,
|
|
83
|
+
target_task_ids: Optional[Collection[str]] = None,
|
|
84
|
+
task_query: Optional[TaskQuery] = None,
|
|
85
|
+
target_label_names: Optional[Collection[str]] = None,
|
|
86
|
+
) -> list[AnnotationSinglePointInfo]:
|
|
87
|
+
annotation_point_list = []
|
|
88
|
+
target_task_ids = set(target_task_ids) if target_task_ids is not None else None
|
|
89
|
+
iter_parser = lazy_parse_simple_annotation_by_input_data(annotation_path)
|
|
90
|
+
logger.info(f"アノテーションZIPまたはディレクトリ'{annotation_path}'を読み込みます。")
|
|
91
|
+
for index, parser in enumerate(iter_parser):
|
|
92
|
+
if (index + 1) % 10000 == 0:
|
|
93
|
+
logger.info(f"{index + 1} 件目のJSONを読み込み中")
|
|
94
|
+
if target_task_ids is not None and parser.task_id not in target_task_ids:
|
|
95
|
+
continue
|
|
96
|
+
dict_simple_annotation = parser.load_json()
|
|
97
|
+
if task_query is not None and not match_annotation_with_task_query(dict_simple_annotation, task_query):
|
|
98
|
+
continue
|
|
99
|
+
sub_annotation_point_list = get_annotation_single_point_info_list(dict_simple_annotation, target_label_names=target_label_names)
|
|
100
|
+
annotation_point_list.extend(sub_annotation_point_list)
|
|
101
|
+
return annotation_point_list
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def create_df(
|
|
105
|
+
annotation_point_list: list[AnnotationSinglePointInfo],
|
|
106
|
+
) -> pandas.DataFrame:
|
|
107
|
+
columns = [
|
|
108
|
+
"project_id",
|
|
109
|
+
"task_id",
|
|
110
|
+
"task_status",
|
|
111
|
+
"task_phase",
|
|
112
|
+
"task_phase_stage",
|
|
113
|
+
"input_data_id",
|
|
114
|
+
"input_data_name",
|
|
115
|
+
"updated_datetime",
|
|
116
|
+
"label",
|
|
117
|
+
"annotation_id",
|
|
118
|
+
"point.x",
|
|
119
|
+
"point.y",
|
|
120
|
+
]
|
|
121
|
+
df = pandas.DataFrame(
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
"project_id": e.project_id,
|
|
125
|
+
"task_id": e.task_id,
|
|
126
|
+
"task_status": e.task_status,
|
|
127
|
+
"task_phase": e.task_phase,
|
|
128
|
+
"task_phase_stage": e.task_phase_stage,
|
|
129
|
+
"input_data_id": e.input_data_id,
|
|
130
|
+
"input_data_name": e.input_data_name,
|
|
131
|
+
"updated_datetime": e.updated_datetime,
|
|
132
|
+
"label": e.label,
|
|
133
|
+
"annotation_id": e.annotation_id,
|
|
134
|
+
"point.x": e.point["x"],
|
|
135
|
+
"point.y": e.point["y"],
|
|
136
|
+
}
|
|
137
|
+
for e in annotation_point_list
|
|
138
|
+
],
|
|
139
|
+
columns=columns,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return df[columns]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def print_annotation_single_point(
|
|
146
|
+
annotation_path: Path,
|
|
147
|
+
output_file: Path,
|
|
148
|
+
output_format: FormatArgument,
|
|
149
|
+
*,
|
|
150
|
+
target_task_ids: Optional[Collection[str]] = None,
|
|
151
|
+
task_query: Optional[TaskQuery] = None,
|
|
152
|
+
target_label_names: Optional[Collection[str]] = None,
|
|
153
|
+
) -> None:
|
|
154
|
+
annotation_point_list = get_annotation_single_point_info_list_from_annotation_path(
|
|
155
|
+
annotation_path,
|
|
156
|
+
target_task_ids=target_task_ids,
|
|
157
|
+
task_query=task_query,
|
|
158
|
+
target_label_names=target_label_names,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
logger.info(f"{len(annotation_point_list)} 件の点アノテーションの情報を出力します。 :: output='{output_file}'")
|
|
162
|
+
|
|
163
|
+
if output_format == FormatArgument.CSV:
|
|
164
|
+
df = create_df(annotation_point_list)
|
|
165
|
+
print_csv(df, output_file)
|
|
166
|
+
|
|
167
|
+
elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
|
|
168
|
+
json_is_pretty = output_format == FormatArgument.PRETTY_JSON
|
|
169
|
+
# DataClassJsonMixinを使用したtoJSON処理
|
|
170
|
+
print_json(
|
|
171
|
+
[e.to_dict(encode_json=True) for e in annotation_point_list],
|
|
172
|
+
is_pretty=json_is_pretty,
|
|
173
|
+
output=output_file,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError(f"出力形式 '{output_format}' はサポートされていません。")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ListAnnotationSinglePoint(CommandLine):
|
|
181
|
+
COMMON_MESSAGE = "annofabcli annotation_zip list_single_point_annotation: error:"
|
|
182
|
+
|
|
183
|
+
def validate(self, args: argparse.Namespace) -> bool:
|
|
184
|
+
if args.project_id is None and args.annotation is None:
|
|
185
|
+
print( # noqa: T201
|
|
186
|
+
f"{self.COMMON_MESSAGE} argument --project_id: '--annotation'が未指定のときは、'--project_id' を指定してください。",
|
|
187
|
+
file=sys.stderr,
|
|
188
|
+
)
|
|
189
|
+
return False
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
def main(self) -> None:
|
|
193
|
+
args = self.args
|
|
194
|
+
|
|
195
|
+
if not self.validate(args):
|
|
196
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
197
|
+
|
|
198
|
+
project_id: Optional[str] = args.project_id
|
|
199
|
+
if project_id is not None:
|
|
200
|
+
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
201
|
+
project, _ = self.service.api.get_project(project_id)
|
|
202
|
+
if project["input_data_type"] != InputDataType.IMAGE.value:
|
|
203
|
+
print(f"project_id='{project_id}'であるプロジェクトは画像プロジェクトでないので、終了します", file=sys.stderr) # noqa: T201
|
|
204
|
+
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
205
|
+
|
|
206
|
+
annotation_path = Path(args.annotation) if args.annotation is not None else None
|
|
207
|
+
|
|
208
|
+
task_id_list = get_list_from_args(args.task_id) if args.task_id is not None else None
|
|
209
|
+
task_query = TaskQuery.from_dict(annofabcli.common.cli.get_json_from_args(args.task_query)) if args.task_query is not None else None
|
|
210
|
+
label_name_list = get_list_from_args(args.label_name) if args.label_name is not None else None
|
|
211
|
+
|
|
212
|
+
output_file: Path = args.output
|
|
213
|
+
output_format = FormatArgument(args.format)
|
|
214
|
+
|
|
215
|
+
downloading_obj = DownloadingFile(self.service)
|
|
216
|
+
|
|
217
|
+
def download_and_print_annotation_point(project_id: str, temp_dir: Path, *, is_latest: bool) -> None:
|
|
218
|
+
local_annotation_path = temp_dir / f"{project_id}__annotation.zip"
|
|
219
|
+
downloading_obj.download_annotation_zip(
|
|
220
|
+
project_id,
|
|
221
|
+
dest_path=local_annotation_path,
|
|
222
|
+
is_latest=is_latest,
|
|
223
|
+
)
|
|
224
|
+
print_annotation_single_point(
|
|
225
|
+
local_annotation_path,
|
|
226
|
+
output_file,
|
|
227
|
+
output_format,
|
|
228
|
+
target_task_ids=task_id_list,
|
|
229
|
+
task_query=task_query,
|
|
230
|
+
target_label_names=label_name_list,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if project_id is not None:
|
|
234
|
+
if args.temp_dir is not None:
|
|
235
|
+
download_and_print_annotation_point(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest)
|
|
236
|
+
else:
|
|
237
|
+
with tempfile.TemporaryDirectory() as str_temp_dir:
|
|
238
|
+
download_and_print_annotation_point(
|
|
239
|
+
project_id=project_id,
|
|
240
|
+
temp_dir=Path(str_temp_dir),
|
|
241
|
+
is_latest=args.latest,
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
assert annotation_path is not None
|
|
245
|
+
print_annotation_single_point(
|
|
246
|
+
annotation_path,
|
|
247
|
+
output_file,
|
|
248
|
+
output_format,
|
|
249
|
+
target_task_ids=task_id_list,
|
|
250
|
+
task_query=task_query,
|
|
251
|
+
target_label_names=label_name_list,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
256
|
+
argument_parser = ArgumentParser(parser)
|
|
257
|
+
|
|
258
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
259
|
+
group.add_argument(
|
|
260
|
+
"--annotation",
|
|
261
|
+
type=str,
|
|
262
|
+
help="アノテーションzip、またはzipを展開したディレクトリを指定します。",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
group.add_argument("-p", "--project_id", type=str, help="project_id。アノテーションZIPをダウンロードします。")
|
|
266
|
+
|
|
267
|
+
argument_parser.add_format(
|
|
268
|
+
choices=[FormatArgument.CSV, FormatArgument.JSON, FormatArgument.PRETTY_JSON],
|
|
269
|
+
default=FormatArgument.CSV,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
argument_parser.add_output()
|
|
273
|
+
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
"-tq",
|
|
276
|
+
"--task_query",
|
|
277
|
+
type=str,
|
|
278
|
+
help="集計対象タスクを絞り込むためのクエリ条件をJSON形式で指定します。使用できるキーは task_id, status, phase, phase_stage です。"
|
|
279
|
+
" ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
|
|
280
|
+
)
|
|
281
|
+
argument_parser.add_task_id(required=False)
|
|
282
|
+
|
|
283
|
+
parser.add_argument(
|
|
284
|
+
"--label_name",
|
|
285
|
+
type=str,
|
|
286
|
+
nargs="*",
|
|
287
|
+
help="指定したラベル名の点アノテーションのみを対象にします。複数指定できます。",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
parser.add_argument(
|
|
291
|
+
"--latest",
|
|
292
|
+
action="store_true",
|
|
293
|
+
help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
parser.add_argument(
|
|
297
|
+
"--temp_dir",
|
|
298
|
+
type=Path,
|
|
299
|
+
help="指定したディレクトリに、アノテーションZIPなどの一時ファイルをダウンロードします。",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
parser.set_defaults(subcommand_func=main)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def main(args: argparse.Namespace) -> None:
|
|
306
|
+
service = build_annofabapi_resource_and_login(args)
|
|
307
|
+
facade = AnnofabApiFacade(service)
|
|
308
|
+
ListAnnotationSinglePoint(service, facade, args).main()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
|
|
312
|
+
subcommand_name = "list_single_point_annotation"
|
|
313
|
+
subcommand_help = "アノテーションZIPから点アノテーションの座標情報を出力します。"
|
|
314
|
+
epilog = "アノテーションZIPをダウンロードする場合は、オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|
|
315
|
+
parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
|
|
316
|
+
parse_args(parser)
|
|
317
|
+
return parser
|
|
@@ -6,6 +6,7 @@ from typing import Optional
|
|
|
6
6
|
import annofabcli
|
|
7
7
|
from annofabcli.annotation_zip.list_annotation_bounding_box_2d import add_parser as add_parser_list_annotation_bounding_box_2d
|
|
8
8
|
from annofabcli.annotation_zip.list_range_annotation import add_parser as add_parser_list_range_annotation
|
|
9
|
+
from annofabcli.annotation_zip.list_single_point_annotation import add_parser as add_parser_list_single_point_annotation
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
@@ -19,6 +20,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
19
20
|
# サブコマンドの定義
|
|
20
21
|
add_parser_list_annotation_bounding_box_2d(subparsers)
|
|
21
22
|
add_parser_list_range_annotation(subparsers)
|
|
23
|
+
add_parser_list_single_point_annotation(subparsers)
|
|
22
24
|
# 作成中のためコメントアウト
|
|
23
25
|
# add_parser_validate_annotation(subparsers)
|
|
24
26
|
|
|
@@ -141,7 +141,7 @@ class WholeProductivityPerCompletedDate:
|
|
|
141
141
|
return pandas.DataFrame(index=[e.strftime("%Y-%m-%d") for e in pandas.date_range(start_date, end_date)])
|
|
142
142
|
|
|
143
143
|
@classmethod
|
|
144
|
-
def from_df_wrapper(cls, task: Task, worktime_per_date: WorktimePerDate, task_completion_criteria: TaskCompletionCriteria) -> WholeProductivityPerCompletedDate:
|
|
144
|
+
def from_df_wrapper(cls, task: Task, worktime_per_date: WorktimePerDate, task_completion_criteria: TaskCompletionCriteria) -> WholeProductivityPerCompletedDate: # noqa: PLR0912
|
|
145
145
|
"""
|
|
146
146
|
完了日毎の全体の生産量、生産性を算出する。
|
|
147
147
|
|
|
@@ -172,6 +172,8 @@ class WholeProductivityPerCompletedDate:
|
|
|
172
172
|
datetime_column = "first_acceptance_reached_datetime"
|
|
173
173
|
elif task_completion_criteria == TaskCompletionCriteria.INSPECTION_REACHED:
|
|
174
174
|
datetime_column = "first_inspection_reached_datetime"
|
|
175
|
+
elif task_completion_criteria == TaskCompletionCriteria.ANNOTATION_STARTED:
|
|
176
|
+
datetime_column = "first_annotation_started_datetime"
|
|
175
177
|
else:
|
|
176
178
|
assert_noreturn(task_completion_criteria)
|
|
177
179
|
|
|
@@ -280,6 +282,8 @@ class WholeProductivityPerCompletedDate:
|
|
|
280
282
|
str_task = "受入フェーズに到達した"
|
|
281
283
|
elif self.task_completion_criteria == TaskCompletionCriteria.INSPECTION_REACHED:
|
|
282
284
|
str_task = "検査フェーズに到達した"
|
|
285
|
+
elif self.task_completion_criteria == TaskCompletionCriteria.ANNOTATION_STARTED:
|
|
286
|
+
str_task = "教師付フェーズが着手された"
|
|
283
287
|
else:
|
|
284
288
|
assert_noreturn(self.task_completion_criteria)
|
|
285
289
|
|
|
@@ -884,6 +888,9 @@ class WholeProductivityPerFirstAnnotationStartedDate:
|
|
|
884
888
|
df_sub_task = df_task[df_task["phase"] == TaskPhase.ACCEPTANCE.value]
|
|
885
889
|
elif task_completion_criteria == TaskCompletionCriteria.INSPECTION_REACHED:
|
|
886
890
|
df_sub_task = df_task[df_task["phase"].isin([TaskPhase.INSPECTION.value, TaskPhase.ACCEPTANCE.value])]
|
|
891
|
+
elif task_completion_criteria == TaskCompletionCriteria.ANNOTATION_STARTED:
|
|
892
|
+
# 教師付フェーズが着手されたタスク(first_annotation_started_datetimeがNoneでない)を抽出する
|
|
893
|
+
df_sub_task = df_task[df_task["first_annotation_started_datetime"].notna()]
|
|
887
894
|
else:
|
|
888
895
|
assert_noreturn(task_completion_criteria)
|
|
889
896
|
|
|
@@ -1013,6 +1020,8 @@ class WholeProductivityPerFirstAnnotationStartedDate:
|
|
|
1013
1020
|
str_task = "受入フェーズ"
|
|
1014
1021
|
elif self.task_completion_criteria == TaskCompletionCriteria.INSPECTION_REACHED:
|
|
1015
1022
|
str_task = "検査フェーズまたは受入フェーズ"
|
|
1023
|
+
elif self.task_completion_criteria == TaskCompletionCriteria.ANNOTATION_STARTED:
|
|
1024
|
+
str_task = "教師付フェーズが着手されたタスク"
|
|
1016
1025
|
else:
|
|
1017
1026
|
assert_noreturn(self.task_completion_criteria)
|
|
1018
1027
|
|
|
@@ -40,6 +40,8 @@ class TaskCompletionCriteria(Enum):
|
|
|
40
40
|
"""タスクが受入フェーズに到達したら「タスクの完了」とみなす"""
|
|
41
41
|
INSPECTION_REACHED = "inspection_reached"
|
|
42
42
|
"""タスクが検査フェーズに到達したら「タスクの完了」とみなす"""
|
|
43
|
+
ANNOTATION_STARTED = "annotation_started"
|
|
44
|
+
"""教師付フェーズが着手されたら「タスクの完了」とみなす"""
|
|
43
45
|
|
|
44
46
|
def is_task_completed(self, task: dict[str, Any]) -> bool:
|
|
45
47
|
"""指定したタスクが、タスクの完了条件に合致するかどうかを判定します。
|
|
@@ -48,6 +50,7 @@ class TaskCompletionCriteria(Enum):
|
|
|
48
50
|
task: タスク情報。以下のキーを参照します。
|
|
49
51
|
* phase
|
|
50
52
|
* status
|
|
53
|
+
* work_time_span (ANNOTATION_STARTEDの場合のみ)
|
|
51
54
|
|
|
52
55
|
Returns:
|
|
53
56
|
タスクの完了条件に合致する場合はTrue、そうでない場合はFalse
|
|
@@ -62,5 +65,10 @@ class TaskCompletionCriteria(Enum):
|
|
|
62
65
|
# 受入フェーズも含む理由:検査フェーズに到達したタスクを「完了」とみなすならば、検査フェーズより後段フェーズである受入フェーズも「完了」とみなせるため
|
|
63
66
|
return task["phase"] in {TaskPhase.INSPECTION.value, TaskPhase.ACCEPTANCE.value}
|
|
64
67
|
|
|
68
|
+
elif self == TaskCompletionCriteria.ANNOTATION_STARTED:
|
|
69
|
+
# 教師付フェーズが着手されたタスクを「完了」とみなす
|
|
70
|
+
# work_time_span > 0 であれば教師付フェーズが着手されたとみなす
|
|
71
|
+
return task["work_time_span"] > 0
|
|
72
|
+
|
|
65
73
|
else:
|
|
66
74
|
assert_noreturn(self)
|
|
@@ -42,7 +42,7 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
42
42
|
- acceptance_completed: 受入フェーズの完了状態
|
|
43
43
|
- acceptance_reached: 受入フェーズに到達
|
|
44
44
|
- inspection_reached: 検査フェーズまたは受入フェーズに到達
|
|
45
|
-
- annotation_started:
|
|
45
|
+
- annotation_started: 教師付フェーズが着手された
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
48
|
FILENAME_WHOLE_PERFORMANCE = "全体の生産性と品質.csv"
|
|
@@ -710,7 +710,9 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
710
710
|
default=TaskCompletionCriteria.ACCEPTANCE_COMPLETED.value,
|
|
711
711
|
help="タスクの完了条件を指定します。\n"
|
|
712
712
|
"* ``acceptance_completed``: タスクが受入フェーズの完了状態であれば「タスクの完了」とみなす\n"
|
|
713
|
-
"* ``acceptance_reached``: タスクが受入フェーズに到達したら「タスクの完了」とみなす\n"
|
|
713
|
+
"* ``acceptance_reached``: タスクが受入フェーズに到達したら「タスクの完了」とみなす\n"
|
|
714
|
+
"* ``inspection_reached``: タスクが検査フェーズに到達したら「タスクの完了」とみなす\n"
|
|
715
|
+
"* ``annotation_started``: 教師付フェーズが着手されたら「タスクの完了」とみなす\n",
|
|
714
716
|
)
|
|
715
717
|
|
|
716
718
|
parser.add_argument(
|
annofabcli/task/put_tasks.py
CHANGED
|
@@ -286,7 +286,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
286
286
|
"--api",
|
|
287
287
|
type=str,
|
|
288
288
|
choices=[e.value for e in ApiWithCreatingTask],
|
|
289
|
-
help="タスク作成に使うWebAPI
|
|
289
|
+
help="タスク作成に使うWebAPIを指定できます。未指定の場合は、作成するタスク数に応じて適切なWebAPIを選択します。",
|
|
290
290
|
)
|
|
291
291
|
|
|
292
292
|
parser.add_argument(
|
|
@@ -11,7 +11,7 @@ annofabcli/annotation/create_classification_annotation.py,sha256=Ex9kSVNHZu71UbL
|
|
|
11
11
|
annofabcli/annotation/delete_annotation.py,sha256=bU87ov7NhYFPe-Cldn3nBSK_CY9A9x3mSLN1Ame-cKk,25144
|
|
12
12
|
annofabcli/annotation/download_annotation_zip.py,sha256=SMtfxt6NKkpHGRDoKRCjPqpQB5DBpQ-PD2_3ts51h0Q,3314
|
|
13
13
|
annofabcli/annotation/dump_annotation.py,sha256=Q-p6f5XBs7khDgrfY5Q3CGLBMKEerJWO_CQ8_73UXVM,9972
|
|
14
|
-
annofabcli/annotation/import_annotation.py,sha256=
|
|
14
|
+
annofabcli/annotation/import_annotation.py,sha256=M0osVbY06Xs2DXkQLwCKx7WmK9cPc9EFuvHbAKMKv-0,35898
|
|
15
15
|
annofabcli/annotation/list_annotation.py,sha256=uKcOuGC7lzd6vVbzizkiZtYdXJ7EzY0iifuiqKl2wQM,10707
|
|
16
16
|
annofabcli/annotation/list_annotation_count.py,sha256=T9fbaoxWeDJIVgW_YgHRldbwrVZWiE-57lfJrDQrj80,6474
|
|
17
17
|
annofabcli/annotation/merge_segmentation.py,sha256=kIsCeXtJxzd6nobQPpi0fscaRDlTx3tg1qpy5PDfSJI,18107
|
|
@@ -37,7 +37,8 @@ annofabcli/annotation_specs/subcommand_annotation_specs.py,sha256=yqSjV1S3CSB0sF
|
|
|
37
37
|
annofabcli/annotation_zip/__init__.py,sha256=wAeKgkUYvgzdGFS3iJ3h0WogWbg9i-0c4xSg-mzbBpY,64
|
|
38
38
|
annofabcli/annotation_zip/list_annotation_bounding_box_2d.py,sha256=306qSxOXzXSU1AaaveWSToyBCCt3hGbTuWqBaS5wn44,13363
|
|
39
39
|
annofabcli/annotation_zip/list_range_annotation.py,sha256=70U3vvb-M5jxQlUlbqX-DSenp8oxXja3tj1gzq-7_DU,12282
|
|
40
|
-
annofabcli/annotation_zip/
|
|
40
|
+
annofabcli/annotation_zip/list_single_point_annotation.py,sha256=qW5i4pozl9wx2BPue9rbQX8zvdyTVRfaeQzjhOf-OUU,12575
|
|
41
|
+
annofabcli/annotation_zip/subcommand_annotation_zip.py,sha256=xU8WGXRhSh3gjOpOMD2qLMeN3rtPnE7glSsKBLIBXjE,1574
|
|
41
42
|
annofabcli/annotation_zip/validate_annotation.py,sha256=Kf1p312CINbZZTbniVAphQrcrMmr5WGQZja30kc7ewc,16645
|
|
42
43
|
annofabcli/comment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
44
|
annofabcli/comment/delete_comment.py,sha256=HZG1BOEKEbi3ISqM-2KZS5iq6aI16jDFBRLFpT-30e4,11385
|
|
@@ -161,12 +162,12 @@ annofabcli/statistics/summarize_task_count_by_task_id_group.py,sha256=K4FFED1pza
|
|
|
161
162
|
annofabcli/statistics/summarize_task_count_by_user.py,sha256=KUGVUwn_KO4RSA2twAz60nX1OQE1pd5TL5gBmL1bgMA,7421
|
|
162
163
|
annofabcli/statistics/visualize_annotation_count.py,sha256=7OATuGa2okq80unuTe-X30CBVkrlMLDN5Y-Q_5mB6eI,22138
|
|
163
164
|
annofabcli/statistics/visualize_annotation_duration.py,sha256=9JH9MirhOyCmjcChFJMtfnFIV2k4sucP9PAwNKMcbtE,21022
|
|
164
|
-
annofabcli/statistics/visualize_statistics.py,sha256=
|
|
165
|
+
annofabcli/statistics/visualize_statistics.py,sha256=njhjbHBhhQ5ksJmOWrcLYh2pLHZdn0uAk8dCufjtfAI,44281
|
|
165
166
|
annofabcli/statistics/visualize_video_duration.py,sha256=yY18H0-boNy4-MQWUM_xBTMzxDUQ51TrVtz8mbsB_dI,16623
|
|
166
167
|
annofabcli/statistics/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
167
168
|
annofabcli/statistics/visualization/filtering_query.py,sha256=kqGOa1YdQ62kTLlkiIHFiYNju4Pg9rXpsC4Cph-QjLo,4355
|
|
168
|
-
annofabcli/statistics/visualization/model.py,sha256=
|
|
169
|
-
annofabcli/statistics/visualization/project_dir.py,sha256
|
|
169
|
+
annofabcli/statistics/visualization/model.py,sha256=LXFuelx9_6n1mU418Mw5FOCBQTlnNBlOijZ7oUIfQ_Q,2928
|
|
170
|
+
annofabcli/statistics/visualization/project_dir.py,sha256=g1Jb9-CW32BcuwS3Z2dTgJ9bo3_Edlc7wWtrKhLzFcc,24300
|
|
170
171
|
annofabcli/statistics/visualization/visualization_source_files.py,sha256=SFY7WXUtjECB8l7zP-exawocrTiZ0UI7Z5sjgq4J_g4,8641
|
|
171
172
|
annofabcli/statistics/visualization/dataframe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
172
173
|
annofabcli/statistics/visualization/dataframe/actual_worktime.py,sha256=7nsHlvN5cDzXIw-u_MSAZf4nlSSY56IlunSmnODXTbY,1916
|
|
@@ -184,7 +185,7 @@ annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py,sha
|
|
|
184
185
|
annofabcli/statistics/visualization/dataframe/user.py,sha256=EHn7nlf6D6UX-gsVXy8m_3QaCsHsUhr0iy2rbNozOgc,1707
|
|
185
186
|
annofabcli/statistics/visualization/dataframe/user_performance.py,sha256=X0jXV0wY8AZC8hoV62sJd9GRThqTf5RsaVciLeCvGlk,56628
|
|
186
187
|
annofabcli/statistics/visualization/dataframe/whole_performance.py,sha256=kw5Cww0fwDfwriTtxiT0l3wr7YAMA9eGjvp84G9MN9I,12449
|
|
187
|
-
annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py,sha256=
|
|
188
|
+
annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py,sha256=J-QW2Mvt-hGTpDUGGJKFvo0REzIWwrbDAzcMf9iqx_U,54480
|
|
188
189
|
annofabcli/statistics/visualization/dataframe/worktime_per_date.py,sha256=wt0paPy2cVVzLUJhbV12-6bInikbQoxRFnOHY7BcI9o,21342
|
|
189
190
|
annofabcli/supplementary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
190
191
|
annofabcli/supplementary/delete_supplementary_data.py,sha256=dA4n61SSkTbDKDqjVldHIEAxbPQXyrQciwI4RDuC35U,13537
|
|
@@ -205,7 +206,7 @@ annofabcli/task/list_all_tasks.py,sha256=FpFzosCZs2EZNSQMHzWse4z_zjZeiVF5BFmbwb9
|
|
|
205
206
|
annofabcli/task/list_all_tasks_added_task_history.py,sha256=fkdiuo64iS7xxvIfGKzSiUPPEMiCVnJjjcAtMxe2Ngs,9551
|
|
206
207
|
annofabcli/task/list_tasks.py,sha256=cmfqbP2Sr1dlpvpJLJscKq31BUdXVaajr_agiecKWXg,10051
|
|
207
208
|
annofabcli/task/list_tasks_added_task_history.py,sha256=JI3Rxn9MwXN07Cu2wDfaIXAzm3MlNm1kIh1SZHbxZr4,22488
|
|
208
|
-
annofabcli/task/put_tasks.py,sha256=
|
|
209
|
+
annofabcli/task/put_tasks.py,sha256=tqgGtaYkAwTznLhcvl9eZNS2U6eqsoqSbL67jBqyfJk,13653
|
|
209
210
|
annofabcli/task/put_tasks_by_count.py,sha256=kIjvWtgZ53Smcm8N-raZedQCubx1TWoQT-BYyioYDrA,5999
|
|
210
211
|
annofabcli/task/reject_tasks.py,sha256=2mRxIsP63N2xcVXQ0xeFqF0VkuUQP6C_bsFEqG-qNf0,22027
|
|
211
212
|
annofabcli/task/subcommand_task.py,sha256=L_5Dwe58eblrtOrUYxjJAvkSmu6savRUxIqGjsFq-R4,2436
|
|
@@ -220,8 +221,8 @@ annofabcli/task_history_event/download_task_history_event_json.py,sha256=hQLVbQ0
|
|
|
220
221
|
annofabcli/task_history_event/list_all_task_history_event.py,sha256=EeKMyPUxGwYCFtWQHHW954ZserGm8lUqrwNnV1iX9X4,6830
|
|
221
222
|
annofabcli/task_history_event/list_worktime.py,sha256=Y7Pu5DP7scPf7HPt6CTiTvB1_5_Nfi1bStUIaCpkhII,15507
|
|
222
223
|
annofabcli/task_history_event/subcommand_task_history_event.py,sha256=mJVJoT4RXk4HWnY7-Nrsl4If-gtaIIEXd2z7eFZwM2I,1260
|
|
223
|
-
annofabcli-1.
|
|
224
|
-
annofabcli-1.
|
|
225
|
-
annofabcli-1.
|
|
226
|
-
annofabcli-1.
|
|
227
|
-
annofabcli-1.
|
|
224
|
+
annofabcli-1.111.1.dist-info/METADATA,sha256=_p6y1IWSFtptJzWmxklxObVfFZSZEEYeai7Xrl5fC-A,5134
|
|
225
|
+
annofabcli-1.111.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
226
|
+
annofabcli-1.111.1.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
|
|
227
|
+
annofabcli-1.111.1.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
|
|
228
|
+
annofabcli-1.111.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|