annofabcli 1.111.1__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.
@@ -23,7 +23,7 @@ class AnnotationDuration:
23
23
  return [
24
24
  "project_id",
25
25
  "task_id",
26
- "annotation_duration_second",
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
- "annotation_duration_second": "float64",
117
+ "annotation_duration_minute": "float64",
118
118
  }
119
119
 
120
120
  df = pandas.DataFrame(columns=cls.columns()).astype(df_dtype)
@@ -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 InputDataType, ProjectMemberRole, TaskPhase
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
- # アノテーションZIPからアノテーション数を取得
124
- annotation_count = AnnotationCount.from_annotation_zip(
125
- self.visualize_source_files.annotation_zip_path,
126
- project_id=self.project_id,
127
- include_labels=self.production_volume_include_labels,
128
- exclude_labels=self.production_volume_exclude_labels,
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=(project_info.input_data_type == InputDataType.MOVIE.value),
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: annofabcli
3
- Version: 1.111.1
3
+ Version: 1.111.2
4
4
  Summary: Utility Command Line Interface for AnnoFab
5
5
  Author: Kurusugawa Computer Inc.
6
6
  License: MIT
@@ -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=njhjbHBhhQ5ksJmOWrcLYh2pLHZdn0uAk8dCufjtfAI,44281
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
169
  annofabcli/statistics/visualization/model.py,sha256=LXFuelx9_6n1mU418Mw5FOCBQTlnNBlOijZ7oUIfQ_Q,2928
170
170
  annofabcli/statistics/visualization/project_dir.py,sha256=g1Jb9-CW32BcuwS3Z2dTgJ9bo3_Edlc7wWtrKhLzFcc,24300
171
- annofabcli/statistics/visualization/visualization_source_files.py,sha256=SFY7WXUtjECB8l7zP-exawocrTiZ0UI7Z5sjgq4J_g4,8641
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=vKxlBsDY04Q0MEUSR3Iav1GKMx1MM6BEdnFlWq2Et68,4378
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
@@ -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.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,,
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,,