annofabcli 1.111.0__py3-none-any.whl → 1.111.2__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/statistics/visualization/dataframe/annotation_duration.py +4 -4
- 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/visualization/visualization_source_files.py +49 -0
- annofabcli/statistics/visualize_statistics.py +96 -40
- {annofabcli-1.111.0.dist-info → annofabcli-1.111.2.dist-info}/METADATA +1 -1
- {annofabcli-1.111.0.dist-info → annofabcli-1.111.2.dist-info}/RECORD +12 -12
- {annofabcli-1.111.0.dist-info → annofabcli-1.111.2.dist-info}/WHEEL +0 -0
- {annofabcli-1.111.0.dist-info → annofabcli-1.111.2.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.111.0.dist-info → annofabcli-1.111.2.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:
|
|
@@ -23,7 +23,7 @@ class AnnotationDuration:
|
|
|
23
23
|
return [
|
|
24
24
|
"project_id",
|
|
25
25
|
"task_id",
|
|
26
|
-
"
|
|
26
|
+
"annotation_duration_minute",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
@classmethod
|
|
@@ -62,7 +62,7 @@ class AnnotationDuration:
|
|
|
62
62
|
"""
|
|
63
63
|
logger.debug(f"アノテーションZIPファイルから区間アノテーションの長さを計算します。 :: project_id='{project_id}', file='{annotation_zip!s}'")
|
|
64
64
|
|
|
65
|
-
result: dict[tuple[str, str], float] = defaultdict(float) # key:(project_id, task_id), value
|
|
65
|
+
result: dict[tuple[str, str], float] = defaultdict(float) # key:(project_id, task_id), value:合計アノテーション時間(分)
|
|
66
66
|
|
|
67
67
|
for index, parser in enumerate(lazy_parse_simple_annotation_zip(annotation_zip)):
|
|
68
68
|
simple_annotation = parser.load_json()
|
|
@@ -83,7 +83,7 @@ class AnnotationDuration:
|
|
|
83
83
|
# 区間アノテーションの場合
|
|
84
84
|
begin = data["begin"]
|
|
85
85
|
end = data["end"]
|
|
86
|
-
total_duration += (end - begin) / 1000.0 #
|
|
86
|
+
total_duration += (end - begin) / 1000.0 / 60.0 # ミリ秒から分に変換
|
|
87
87
|
|
|
88
88
|
result[(project_id, parser.task_id)] += total_duration
|
|
89
89
|
|
|
@@ -114,7 +114,7 @@ class AnnotationDuration:
|
|
|
114
114
|
df_dtype: dict[str, str] = {
|
|
115
115
|
"project_id": "string",
|
|
116
116
|
"task_id": "string",
|
|
117
|
-
"
|
|
117
|
+
"annotation_duration_minute": "float64",
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
df = pandas.DataFrame(columns=cls.columns()).astype(df_dtype)
|
|
@@ -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"
|
|
@@ -39,6 +39,7 @@ class VisualizationSourceFiles:
|
|
|
39
39
|
self.task_history_json_path = target_dir / f"{self.project_id}__task-history.json"
|
|
40
40
|
self.task_history_event_json_path = target_dir / f"{self.project_id}__task-history-event.json"
|
|
41
41
|
self.annotation_zip_path = target_dir / f"{self.project_id}__annotation.zip"
|
|
42
|
+
self.input_data_json_path = target_dir / f"{self.project_id}__input_data.json"
|
|
42
43
|
|
|
43
44
|
self.logging_prefix = f"project_id='{project_id}'"
|
|
44
45
|
|
|
@@ -99,6 +100,52 @@ class VisualizationSourceFiles:
|
|
|
99
100
|
logger.debug(f"{self.logging_prefix}: '{self.comment_json_path}'を読み込みました。{len(comment_list)}件のコメントが含まれています。")
|
|
100
101
|
return comment_list
|
|
101
102
|
|
|
103
|
+
def read_input_data_json(self) -> list[dict[str, Any]]:
|
|
104
|
+
"""
|
|
105
|
+
入力データ全件ファイルを読み込みます。
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
全入力データの一覧
|
|
109
|
+
"""
|
|
110
|
+
with self.input_data_json_path.open(encoding="utf-8") as f:
|
|
111
|
+
input_data_list = json.load(f)
|
|
112
|
+
|
|
113
|
+
logger.debug(f"{self.logging_prefix}: '{self.input_data_json_path}'を読み込みました。{len(input_data_list)}件の入力データが含まれています。")
|
|
114
|
+
return input_data_list
|
|
115
|
+
|
|
116
|
+
def get_video_duration_minutes_by_task_id(self) -> dict[str, float]:
|
|
117
|
+
"""
|
|
118
|
+
動画プロジェクトの場合、タスクIDごとの動画の長さ(分単位)を取得します。
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
key: task_id, value: 動画の長さ(分)
|
|
122
|
+
"""
|
|
123
|
+
tasks = self.read_tasks_json()
|
|
124
|
+
input_data_list = self.read_input_data_json()
|
|
125
|
+
|
|
126
|
+
# 入力データIDをキーとした辞書を作成
|
|
127
|
+
dict_input_data_by_id = {input_data["input_data_id"]: input_data for input_data in input_data_list}
|
|
128
|
+
|
|
129
|
+
result = {}
|
|
130
|
+
for task in tasks:
|
|
131
|
+
task_id = task["task_id"]
|
|
132
|
+
input_data_id_list = task["input_data_id_list"]
|
|
133
|
+
assert len(input_data_id_list) == 1, f"task_id='{task_id}'には複数の入力データが含まれています。"
|
|
134
|
+
input_data_id = input_data_id_list[0]
|
|
135
|
+
input_data = dict_input_data_by_id.get(input_data_id)
|
|
136
|
+
|
|
137
|
+
if input_data is None:
|
|
138
|
+
logger.warning(f"task_id='{task_id}' :: タスクに含まれている入力データ(input_data_id='{input_data_id}')は、見つかりません。")
|
|
139
|
+
result[task_id] = 0.0
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
video_duration_second = input_data["system_metadata"]["input_duration"]
|
|
143
|
+
|
|
144
|
+
# 秒から分に変換
|
|
145
|
+
result[task_id] = video_duration_second / 60.0
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
102
149
|
def write_files(self, *, is_latest: bool = False, should_get_task_histories_one_of_each: bool = False, should_download_annotation_zip: bool = True) -> None:
|
|
103
150
|
"""
|
|
104
151
|
可視化に必要なファイルを作成します。
|
|
@@ -117,6 +164,8 @@ class VisualizationSourceFiles:
|
|
|
117
164
|
|
|
118
165
|
downloading_obj.download_task_json(self.project_id, dest_path=self.task_json_path, is_latest=is_latest, wait_options=wait_options)
|
|
119
166
|
|
|
167
|
+
downloading_obj.download_input_data_json(self.project_id, dest_path=self.input_data_json_path, is_latest=is_latest, wait_options=wait_options)
|
|
168
|
+
|
|
120
169
|
if should_download_annotation_zip:
|
|
121
170
|
downloading_obj.download_annotation_zip(
|
|
122
171
|
self.project_id,
|
|
@@ -12,7 +12,7 @@ from typing import Any, Callable, Optional
|
|
|
12
12
|
|
|
13
13
|
import annofabapi
|
|
14
14
|
import pandas
|
|
15
|
-
from annofabapi.models import
|
|
15
|
+
from annofabapi.models import ProjectMemberRole, TaskPhase
|
|
16
16
|
|
|
17
17
|
import annofabcli
|
|
18
18
|
from annofabcli.common.cli import (
|
|
@@ -82,6 +82,7 @@ class WriteCsvGraph:
|
|
|
82
82
|
production_volume_include_labels: Optional[list[str]] = None,
|
|
83
83
|
production_volume_exclude_labels: Optional[list[str]] = None,
|
|
84
84
|
include_annotation_duration_seconds: bool = False,
|
|
85
|
+
include_video_duration_minutes: bool = False,
|
|
85
86
|
) -> None:
|
|
86
87
|
self.service = service
|
|
87
88
|
self.project_id = project_id
|
|
@@ -98,6 +99,7 @@ class WriteCsvGraph:
|
|
|
98
99
|
self.production_volume_include_labels = production_volume_include_labels
|
|
99
100
|
self.production_volume_exclude_labels = production_volume_exclude_labels
|
|
100
101
|
self.include_annotation_duration_seconds = include_annotation_duration_seconds
|
|
102
|
+
self.include_video_duration_minutes = include_video_duration_minutes
|
|
101
103
|
|
|
102
104
|
self.task: Optional[Task] = None
|
|
103
105
|
self.worktime_per_date: Optional[WorktimePerDate] = None
|
|
@@ -119,14 +121,18 @@ class WriteCsvGraph:
|
|
|
119
121
|
|
|
120
122
|
def _get_task(self) -> Task:
|
|
121
123
|
if self.task is None:
|
|
124
|
+
custom_production_volume = self._prepare_custom_production_volume()
|
|
125
|
+
|
|
122
126
|
if self.annotation_count is None:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
if self.visualize_source_files.annotation_zip_path.exists():
|
|
128
|
+
annotation_count = AnnotationCount.from_annotation_zip(
|
|
129
|
+
self.visualize_source_files.annotation_zip_path,
|
|
130
|
+
project_id=self.project_id,
|
|
131
|
+
include_labels=self.production_volume_include_labels,
|
|
132
|
+
exclude_labels=self.production_volume_exclude_labels,
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
annotation_count = AnnotationCount.empty()
|
|
130
136
|
else:
|
|
131
137
|
annotation_count = self.annotation_count
|
|
132
138
|
|
|
@@ -137,36 +143,6 @@ class WriteCsvGraph:
|
|
|
137
143
|
new_tasks = filter_tasks(tasks, self.task_completion_criteria, self.filtering_query, task_histories=task_histories)
|
|
138
144
|
logger.debug(f"project_id='{self.project_id}' :: 集計対象タスクは {len(new_tasks)} / {len(tasks)} 件です。")
|
|
139
145
|
|
|
140
|
-
# annotation_duration_secondsを生産量に含める場合、アノテーション時間を計算
|
|
141
|
-
custom_production_volume = self.custom_production_volume
|
|
142
|
-
if self.include_annotation_duration_seconds:
|
|
143
|
-
logger.debug(f"project_id='{self.project_id}' :: 区間アノテーションの長さ('annotation_duration_second')を計算します。")
|
|
144
|
-
annotation_duration_obj = AnnotationDuration.from_annotation_zip(
|
|
145
|
-
self.visualize_source_files.annotation_zip_path,
|
|
146
|
-
project_id=self.project_id,
|
|
147
|
-
include_labels=self.production_volume_include_labels,
|
|
148
|
-
exclude_labels=self.production_volume_exclude_labels,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if custom_production_volume is not None:
|
|
152
|
-
# 既存のCustomProductionVolumeのデータと結合
|
|
153
|
-
if not custom_production_volume.is_empty():
|
|
154
|
-
annotation_duration_df = pandas.merge(custom_production_volume.df, annotation_duration_obj.df, on=["project_id", "task_id"], how="outer")
|
|
155
|
-
else:
|
|
156
|
-
annotation_duration_df = annotation_duration_obj.df
|
|
157
|
-
|
|
158
|
-
# annotation_duration_secondを含む新しいProductionVolumeColumnリストを作成
|
|
159
|
-
annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_second", name="区間アノテーションの長さ(秒)")
|
|
160
|
-
new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
|
|
161
|
-
if annotation_duration_column not in new_production_volume_list:
|
|
162
|
-
new_production_volume_list.append(annotation_duration_column)
|
|
163
|
-
|
|
164
|
-
custom_production_volume = CustomProductionVolume(annotation_duration_df, custom_production_volume_list=new_production_volume_list)
|
|
165
|
-
else:
|
|
166
|
-
# CustomProductionVolumeが存在しない場合、新規作成
|
|
167
|
-
annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_second", name="区間アノテーションの長さ(秒)")
|
|
168
|
-
custom_production_volume = CustomProductionVolume(annotation_duration_obj.df, custom_production_volume_list=[annotation_duration_column])
|
|
169
|
-
|
|
170
146
|
self.task = Task.from_api_content(
|
|
171
147
|
tasks=new_tasks,
|
|
172
148
|
task_histories=task_histories,
|
|
@@ -180,6 +156,80 @@ class WriteCsvGraph:
|
|
|
180
156
|
|
|
181
157
|
return self.task
|
|
182
158
|
|
|
159
|
+
def _prepare_custom_production_volume(self) -> Optional[CustomProductionVolume]:
|
|
160
|
+
"""カスタム生産量の準備を行う"""
|
|
161
|
+
custom_production_volume = self.custom_production_volume
|
|
162
|
+
|
|
163
|
+
# annotation_duration_secondsを生産量に含める場合、アノテーション時間を計算
|
|
164
|
+
if self.include_annotation_duration_seconds:
|
|
165
|
+
custom_production_volume = self._add_annotation_duration(custom_production_volume)
|
|
166
|
+
|
|
167
|
+
# 動画プロジェクトの場合、動画の長さ(分)を生産量に含める
|
|
168
|
+
if self.include_video_duration_minutes:
|
|
169
|
+
custom_production_volume = self._add_video_duration(custom_production_volume)
|
|
170
|
+
|
|
171
|
+
return custom_production_volume
|
|
172
|
+
|
|
173
|
+
def _add_annotation_duration(self, custom_production_volume: Optional[CustomProductionVolume]) -> CustomProductionVolume:
|
|
174
|
+
"""区間アノテーションの長さを生産量に追加する"""
|
|
175
|
+
logger.debug(f"project_id='{self.project_id}' :: 区間アノテーションの長さ('annotation_duration_minute')を計算します。")
|
|
176
|
+
annotation_duration_obj = AnnotationDuration.from_annotation_zip(
|
|
177
|
+
self.visualize_source_files.annotation_zip_path,
|
|
178
|
+
project_id=self.project_id,
|
|
179
|
+
include_labels=self.production_volume_include_labels,
|
|
180
|
+
exclude_labels=self.production_volume_exclude_labels,
|
|
181
|
+
)
|
|
182
|
+
annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_minute", name="区間アノテーションの長さ(分)")
|
|
183
|
+
|
|
184
|
+
if custom_production_volume is not None:
|
|
185
|
+
# 既存のCustomProductionVolumeのデータと結合
|
|
186
|
+
if not custom_production_volume.is_empty():
|
|
187
|
+
annotation_duration_df = pandas.merge(custom_production_volume.df, annotation_duration_obj.df, on=["project_id", "task_id"], how="outer")
|
|
188
|
+
else:
|
|
189
|
+
annotation_duration_df = annotation_duration_obj.df
|
|
190
|
+
|
|
191
|
+
# annotation_duration_minuteを含む新しいProductionVolumeColumnリストを作成
|
|
192
|
+
new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
|
|
193
|
+
if annotation_duration_column not in new_production_volume_list:
|
|
194
|
+
new_production_volume_list.append(annotation_duration_column)
|
|
195
|
+
|
|
196
|
+
return CustomProductionVolume(annotation_duration_df, custom_production_volume_list=new_production_volume_list)
|
|
197
|
+
else:
|
|
198
|
+
# CustomProductionVolumeが存在しない場合、新規作成
|
|
199
|
+
return CustomProductionVolume(annotation_duration_obj.df, custom_production_volume_list=[annotation_duration_column])
|
|
200
|
+
|
|
201
|
+
def _add_video_duration(self, custom_production_volume: Optional[CustomProductionVolume]) -> CustomProductionVolume:
|
|
202
|
+
"""動画の長さ(分)を生産量に追加する"""
|
|
203
|
+
logger.debug(f"project_id='{self.project_id}' :: 動画の長さ('video_duration_minute')を計算します。")
|
|
204
|
+
video_duration_by_task_id = self.visualize_source_files.get_video_duration_minutes_by_task_id()
|
|
205
|
+
|
|
206
|
+
# DataFrameの作成
|
|
207
|
+
video_duration_data = [{"project_id": self.project_id, "task_id": task_id, "video_duration_minute": duration} for task_id, duration in video_duration_by_task_id.items()]
|
|
208
|
+
if len(video_duration_data) == 0:
|
|
209
|
+
video_duration_df = pandas.DataFrame(columns=["project_id", "task_id", "video_duration_minute"])
|
|
210
|
+
else:
|
|
211
|
+
video_duration_df = pandas.DataFrame(video_duration_data)
|
|
212
|
+
|
|
213
|
+
video_duration_df = video_duration_df.astype({"project_id": "string", "task_id": "string", "video_duration_minute": "float64"})
|
|
214
|
+
video_duration_column = ProductionVolumeColumn(value="video_duration_minute", name="動画の長さ(分)")
|
|
215
|
+
|
|
216
|
+
if custom_production_volume is not None:
|
|
217
|
+
# 既存のCustomProductionVolumeのデータと結合
|
|
218
|
+
if not custom_production_volume.is_empty():
|
|
219
|
+
merged_df = pandas.merge(custom_production_volume.df, video_duration_df, on=["project_id", "task_id"], how="outer")
|
|
220
|
+
else:
|
|
221
|
+
merged_df = video_duration_df
|
|
222
|
+
|
|
223
|
+
# video_duration_minuteを含む新しいProductionVolumeColumnリストを作成
|
|
224
|
+
new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
|
|
225
|
+
if video_duration_column not in new_production_volume_list:
|
|
226
|
+
new_production_volume_list.append(video_duration_column)
|
|
227
|
+
|
|
228
|
+
return CustomProductionVolume(merged_df, custom_production_volume_list=new_production_volume_list)
|
|
229
|
+
else:
|
|
230
|
+
# CustomProductionVolumeが存在しない場合、新規作成
|
|
231
|
+
return CustomProductionVolume(video_duration_df, custom_production_volume_list=[video_duration_column])
|
|
232
|
+
|
|
183
233
|
def _get_task_worktime_obj(self) -> TaskWorktimeByPhaseUser:
|
|
184
234
|
if self.task_worktime_obj is None:
|
|
185
235
|
task_history = TaskHistory.from_api_content(self.visualize_source_files.read_task_histories_json())
|
|
@@ -370,6 +420,9 @@ class VisualizingStatisticsMain:
|
|
|
370
420
|
# 動画プロジェクトの場合、annotation_duration_secondを生産量に含める
|
|
371
421
|
custom_production_volume = self.custom_production_volume
|
|
372
422
|
|
|
423
|
+
# 動画プロジェクトかどうかを判定
|
|
424
|
+
is_video_project = project_info.input_data_type == "movie"
|
|
425
|
+
|
|
373
426
|
project_dir = ProjectDir(
|
|
374
427
|
output_project_dir,
|
|
375
428
|
self.task_completion_criteria,
|
|
@@ -429,7 +482,8 @@ class VisualizingStatisticsMain:
|
|
|
429
482
|
output_only_text=self.output_only_text,
|
|
430
483
|
production_volume_include_labels=self.production_volume_include_labels,
|
|
431
484
|
production_volume_exclude_labels=self.production_volume_exclude_labels,
|
|
432
|
-
include_annotation_duration_seconds=
|
|
485
|
+
include_annotation_duration_seconds=is_video_project,
|
|
486
|
+
include_video_duration_minutes=is_video_project,
|
|
433
487
|
)
|
|
434
488
|
|
|
435
489
|
write_obj._catch_exception(write_obj.write_user_performance)() # noqa: SLF001
|
|
@@ -710,7 +764,9 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
|
|
|
710
764
|
default=TaskCompletionCriteria.ACCEPTANCE_COMPLETED.value,
|
|
711
765
|
help="タスクの完了条件を指定します。\n"
|
|
712
766
|
"* ``acceptance_completed``: タスクが受入フェーズの完了状態であれば「タスクの完了」とみなす\n"
|
|
713
|
-
"* ``acceptance_reached``: タスクが受入フェーズに到達したら「タスクの完了」とみなす\n"
|
|
767
|
+
"* ``acceptance_reached``: タスクが受入フェーズに到達したら「タスクの完了」とみなす\n"
|
|
768
|
+
"* ``inspection_reached``: タスクが検査フェーズに到達したら「タスクの完了」とみなす\n"
|
|
769
|
+
"* ``annotation_started``: 教師付フェーズが着手されたら「タスクの完了」とみなす\n",
|
|
714
770
|
)
|
|
715
771
|
|
|
716
772
|
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
|
|
@@ -162,17 +162,17 @@ annofabcli/statistics/summarize_task_count_by_task_id_group.py,sha256=K4FFED1pza
|
|
|
162
162
|
annofabcli/statistics/summarize_task_count_by_user.py,sha256=KUGVUwn_KO4RSA2twAz60nX1OQE1pd5TL5gBmL1bgMA,7421
|
|
163
163
|
annofabcli/statistics/visualize_annotation_count.py,sha256=7OATuGa2okq80unuTe-X30CBVkrlMLDN5Y-Q_5mB6eI,22138
|
|
164
164
|
annofabcli/statistics/visualize_annotation_duration.py,sha256=9JH9MirhOyCmjcChFJMtfnFIV2k4sucP9PAwNKMcbtE,21022
|
|
165
|
-
annofabcli/statistics/visualize_statistics.py,sha256=
|
|
165
|
+
annofabcli/statistics/visualize_statistics.py,sha256=aDvoGfzSGXp14Ax5mWcNM7GHxrtDqpdlHw2RVAry_Gg,47190
|
|
166
166
|
annofabcli/statistics/visualize_video_duration.py,sha256=yY18H0-boNy4-MQWUM_xBTMzxDUQ51TrVtz8mbsB_dI,16623
|
|
167
167
|
annofabcli/statistics/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
168
168
|
annofabcli/statistics/visualization/filtering_query.py,sha256=kqGOa1YdQ62kTLlkiIHFiYNju4Pg9rXpsC4Cph-QjLo,4355
|
|
169
|
-
annofabcli/statistics/visualization/model.py,sha256=
|
|
170
|
-
annofabcli/statistics/visualization/project_dir.py,sha256
|
|
171
|
-
annofabcli/statistics/visualization/visualization_source_files.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
|
|
171
|
+
annofabcli/statistics/visualization/visualization_source_files.py,sha256=5-XTBCnO1FWiYYDqOHxdQbYReYz2RKVsAHH_b1svK-4,10825
|
|
172
172
|
annofabcli/statistics/visualization/dataframe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
173
173
|
annofabcli/statistics/visualization/dataframe/actual_worktime.py,sha256=7nsHlvN5cDzXIw-u_MSAZf4nlSSY56IlunSmnODXTbY,1916
|
|
174
174
|
annofabcli/statistics/visualization/dataframe/annotation_count.py,sha256=4b5B-RTtrETdU1lR6R0yQaMFAVxDZvxom9iBEcEDNHc,5265
|
|
175
|
-
annofabcli/statistics/visualization/dataframe/annotation_duration.py,sha256=
|
|
175
|
+
annofabcli/statistics/visualization/dataframe/annotation_duration.py,sha256=zkofZ7_IFJZ3JhQLEu-bgZhO2dEKytVol3WxeOMtu-4,4385
|
|
176
176
|
annofabcli/statistics/visualization/dataframe/cumulative_productivity.py,sha256=Z9gxGCfgQra0M0LAq3bUhGxXnwRqZ8Gt-zIo2DPs6Nc,15800
|
|
177
177
|
annofabcli/statistics/visualization/dataframe/custom_production_volume.py,sha256=5ELLiQJ5sNKdVKmYYVeZW4nedDg1CVGxMDdF5TUUX5c,2142
|
|
178
178
|
annofabcli/statistics/visualization/dataframe/input_data_count.py,sha256=wDRFtoIWw_Gy2bPZ7LBx3eMO3LdUdjbQKS9mncXav6I,1654
|
|
@@ -185,7 +185,7 @@ annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py,sha
|
|
|
185
185
|
annofabcli/statistics/visualization/dataframe/user.py,sha256=EHn7nlf6D6UX-gsVXy8m_3QaCsHsUhr0iy2rbNozOgc,1707
|
|
186
186
|
annofabcli/statistics/visualization/dataframe/user_performance.py,sha256=X0jXV0wY8AZC8hoV62sJd9GRThqTf5RsaVciLeCvGlk,56628
|
|
187
187
|
annofabcli/statistics/visualization/dataframe/whole_performance.py,sha256=kw5Cww0fwDfwriTtxiT0l3wr7YAMA9eGjvp84G9MN9I,12449
|
|
188
|
-
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
|
|
189
189
|
annofabcli/statistics/visualization/dataframe/worktime_per_date.py,sha256=wt0paPy2cVVzLUJhbV12-6bInikbQoxRFnOHY7BcI9o,21342
|
|
190
190
|
annofabcli/supplementary/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
191
191
|
annofabcli/supplementary/delete_supplementary_data.py,sha256=dA4n61SSkTbDKDqjVldHIEAxbPQXyrQciwI4RDuC35U,13537
|
|
@@ -221,8 +221,8 @@ annofabcli/task_history_event/download_task_history_event_json.py,sha256=hQLVbQ0
|
|
|
221
221
|
annofabcli/task_history_event/list_all_task_history_event.py,sha256=EeKMyPUxGwYCFtWQHHW954ZserGm8lUqrwNnV1iX9X4,6830
|
|
222
222
|
annofabcli/task_history_event/list_worktime.py,sha256=Y7Pu5DP7scPf7HPt6CTiTvB1_5_Nfi1bStUIaCpkhII,15507
|
|
223
223
|
annofabcli/task_history_event/subcommand_task_history_event.py,sha256=mJVJoT4RXk4HWnY7-Nrsl4If-gtaIIEXd2z7eFZwM2I,1260
|
|
224
|
-
annofabcli-1.111.
|
|
225
|
-
annofabcli-1.111.
|
|
226
|
-
annofabcli-1.111.
|
|
227
|
-
annofabcli-1.111.
|
|
228
|
-
annofabcli-1.111.
|
|
224
|
+
annofabcli-1.111.2.dist-info/METADATA,sha256=8kC_vmMBPGWNDApBpji4krvAeE_KnXdEA1VTkzg_H9E,5134
|
|
225
|
+
annofabcli-1.111.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
226
|
+
annofabcli-1.111.2.dist-info/entry_points.txt,sha256=C2uSUc-kkLJpoK_mDL5FEMAdorLEMPfwSf8VBMYnIFM,56
|
|
227
|
+
annofabcli-1.111.2.dist-info/licenses/LICENSE,sha256=pcqWYfxFtxBzhvKp3x9MXNM4xciGb2eFewaRhXUNHlo,1081
|
|
228
|
+
annofabcli-1.111.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|