annofabcli 1.111.1__py3-none-any.whl → 1.112.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. annofabcli/__main__.py +1 -2
  2. annofabcli/annotation/annotation_query.py +10 -10
  3. annofabcli/annotation/change_annotation_attributes.py +9 -9
  4. annofabcli/annotation/change_annotation_attributes_per_annotation.py +3 -4
  5. annofabcli/annotation/change_annotation_properties.py +13 -13
  6. annofabcli/annotation/copy_annotation.py +5 -5
  7. annofabcli/annotation/create_classification_annotation.py +6 -6
  8. annofabcli/annotation/delete_annotation.py +8 -8
  9. annofabcli/annotation/download_annotation_zip.py +1 -3
  10. annofabcli/annotation/dump_annotation.py +7 -7
  11. annofabcli/annotation/import_annotation.py +12 -12
  12. annofabcli/annotation/list_annotation.py +8 -8
  13. annofabcli/annotation/list_annotation_count.py +1 -2
  14. annofabcli/annotation/merge_segmentation.py +5 -5
  15. annofabcli/annotation/remove_segmentation_overlap.py +4 -4
  16. annofabcli/annotation/restore_annotation.py +6 -6
  17. annofabcli/annotation/subcommand_annotation.py +1 -2
  18. annofabcli/annotation_specs/add_attribute_restriction.py +4 -5
  19. annofabcli/annotation_specs/attribute_restriction.py +8 -8
  20. annofabcli/annotation_specs/export_annotation_specs.py +4 -5
  21. annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -4
  22. annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +3 -4
  23. annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +3 -4
  24. annofabcli/annotation_specs/list_annotation_specs_attribute.py +9 -10
  25. annofabcli/annotation_specs/list_annotation_specs_choice.py +9 -10
  26. annofabcli/annotation_specs/list_annotation_specs_history.py +2 -2
  27. annofabcli/annotation_specs/list_annotation_specs_label.py +8 -9
  28. annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +10 -11
  29. annofabcli/annotation_specs/list_attribute_restriction.py +2 -4
  30. annofabcli/annotation_specs/list_label_color.py +2 -3
  31. annofabcli/annotation_specs/put_label_color.py +3 -4
  32. annofabcli/annotation_specs/subcommand_annotation_specs.py +1 -3
  33. annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +365 -0
  34. annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +11 -12
  35. annofabcli/annotation_zip/list_range_annotation.py +24 -14
  36. annofabcli/annotation_zip/list_single_point_annotation.py +11 -12
  37. annofabcli/annotation_zip/subcommand_annotation_zip.py +3 -2
  38. annofabcli/annotation_zip/validate_annotation.py +8 -7
  39. annofabcli/comment/delete_comment.py +4 -6
  40. annofabcli/comment/download_comment_json.py +4 -6
  41. annofabcli/comment/list_all_comment.py +5 -6
  42. annofabcli/comment/list_comment.py +3 -4
  43. annofabcli/comment/put_comment.py +9 -10
  44. annofabcli/comment/put_comment_simply.py +5 -6
  45. annofabcli/comment/put_inspection_comment.py +1 -3
  46. annofabcli/comment/put_inspection_comment_simply.py +1 -3
  47. annofabcli/comment/put_onhold_comment.py +1 -3
  48. annofabcli/comment/put_onhold_comment_simply.py +1 -3
  49. annofabcli/comment/subcommand_comment.py +1 -3
  50. annofabcli/common/bokeh.py +4 -4
  51. annofabcli/common/cli.py +17 -17
  52. annofabcli/common/download.py +28 -29
  53. annofabcli/common/facade.py +37 -38
  54. annofabcli/common/image.py +14 -14
  55. annofabcli/common/utils.py +8 -8
  56. annofabcli/common/visualize.py +13 -13
  57. annofabcli/experimental/list_out_of_range_annotation_for_movie.py +3 -4
  58. annofabcli/experimental/subcommand_experimental.py +1 -3
  59. annofabcli/filesystem/draw_annotation.py +26 -26
  60. annofabcli/filesystem/filter_annotation.py +9 -10
  61. annofabcli/filesystem/mask_user_info.py +14 -14
  62. annofabcli/filesystem/merge_annotation.py +8 -8
  63. annofabcli/filesystem/subcommand_filesystem.py +1 -3
  64. annofabcli/input_data/copy_input_data.py +8 -9
  65. annofabcli/input_data/delete_input_data.py +2 -2
  66. annofabcli/input_data/delete_metadata_key_of_input_data.py +3 -5
  67. annofabcli/input_data/download_input_data_json.py +4 -6
  68. annofabcli/input_data/list_all_input_data.py +8 -8
  69. annofabcli/input_data/list_all_input_data_merged_task.py +4 -4
  70. annofabcli/input_data/list_input_data.py +4 -4
  71. annofabcli/input_data/put_input_data.py +5 -5
  72. annofabcli/input_data/put_input_data_with_zip.py +2 -3
  73. annofabcli/input_data/subcommand_input_data.py +1 -3
  74. annofabcli/input_data/update_input_data.py +6 -8
  75. annofabcli/input_data/update_metadata_of_input_data.py +3 -5
  76. annofabcli/instruction/copy_instruction.py +4 -5
  77. annofabcli/instruction/download_instruction.py +4 -5
  78. annofabcli/instruction/list_instruction_history.py +2 -2
  79. annofabcli/instruction/subcommand_instruction.py +1 -3
  80. annofabcli/instruction/upload_instruction.py +2 -3
  81. annofabcli/job/delete_job.py +1 -2
  82. annofabcli/job/list_job.py +4 -4
  83. annofabcli/job/list_last_job.py +3 -3
  84. annofabcli/job/subcommand_job.py +1 -3
  85. annofabcli/job/wait_job.py +4 -5
  86. annofabcli/my_account/get_my_account.py +1 -2
  87. annofabcli/my_account/subcommand_my_account.py +1 -3
  88. annofabcli/organization/list_organization.py +1 -2
  89. annofabcli/organization/subcommand_organization.py +1 -3
  90. annofabcli/organization_member/change_organization_member.py +3 -4
  91. annofabcli/organization_member/delete_organization_member.py +3 -4
  92. annofabcli/organization_member/invite_organization_member.py +1 -3
  93. annofabcli/organization_member/list_organization_member.py +2 -2
  94. annofabcli/organization_member/subcommand_organization_member.py +1 -3
  95. annofabcli/project/change_organization_of_project.py +3 -3
  96. annofabcli/project/change_project_status.py +3 -3
  97. annofabcli/project/copy_project.py +4 -4
  98. annofabcli/project/create_project.py +7 -7
  99. annofabcli/project/diff_projects.py +4 -5
  100. annofabcli/project/list_project.py +4 -4
  101. annofabcli/project/put_project.py +1 -2
  102. annofabcli/project/subcommand_project.py +1 -2
  103. annofabcli/project/update_configuration.py +3 -3
  104. annofabcli/project/update_project.py +6 -8
  105. annofabcli/project_member/change_project_members.py +7 -7
  106. annofabcli/project_member/copy_project_members.py +3 -3
  107. annofabcli/project_member/drop_project_members.py +1 -2
  108. annofabcli/project_member/invite_project_members.py +1 -3
  109. annofabcli/project_member/list_users.py +1 -2
  110. annofabcli/project_member/put_project_members.py +5 -5
  111. annofabcli/project_member/subcommand_project_member.py +1 -3
  112. annofabcli/stat_visualization/mask_visualization_dir.py +8 -9
  113. annofabcli/stat_visualization/merge_visualization_dir.py +6 -7
  114. annofabcli/stat_visualization/subcommand_stat_visualization.py +1 -2
  115. annofabcli/stat_visualization/summarize_whole_performance_csv.py +1 -2
  116. annofabcli/stat_visualization/write_graph.py +2 -3
  117. annofabcli/stat_visualization/write_performance_rating_csv.py +20 -27
  118. annofabcli/statistics/histogram.py +5 -6
  119. annofabcli/statistics/linegraph.py +13 -14
  120. annofabcli/statistics/list_annotation_area.py +38 -13
  121. annofabcli/statistics/list_annotation_attribute.py +9 -10
  122. annofabcli/statistics/list_annotation_attribute_filled_count.py +30 -31
  123. annofabcli/statistics/list_annotation_count.py +57 -58
  124. annofabcli/statistics/list_annotation_duration.py +33 -34
  125. annofabcli/statistics/list_video_duration.py +4 -5
  126. annofabcli/statistics/list_worktime.py +3 -3
  127. annofabcli/statistics/scatter.py +9 -8
  128. annofabcli/statistics/subcommand_statistics.py +1 -4
  129. annofabcli/statistics/summarize_task_count.py +4 -6
  130. annofabcli/statistics/summarize_task_count_by_task_id_group.py +2 -4
  131. annofabcli/statistics/summarize_task_count_by_user.py +1 -3
  132. annofabcli/statistics/visualization/dataframe/annotation_count.py +5 -4
  133. annofabcli/statistics/visualization/dataframe/annotation_duration.py +6 -7
  134. annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +15 -17
  135. annofabcli/statistics/visualization/dataframe/productivity_per_date.py +17 -19
  136. annofabcli/statistics/visualization/dataframe/project_performance.py +3 -12
  137. annofabcli/statistics/visualization/dataframe/task.py +11 -12
  138. annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +9 -10
  139. annofabcli/statistics/visualization/dataframe/user_performance.py +21 -19
  140. annofabcli/statistics/visualization/dataframe/whole_performance.py +3 -4
  141. annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +12 -14
  142. annofabcli/statistics/visualization/dataframe/worktime_per_date.py +11 -13
  143. annofabcli/statistics/visualization/filtering_query.py +7 -7
  144. annofabcli/statistics/visualization/project_dir.py +27 -14
  145. annofabcli/statistics/visualization/visualization_source_files.py +49 -0
  146. annofabcli/statistics/visualize_annotation_count.py +22 -23
  147. annofabcli/statistics/visualize_annotation_duration.py +21 -22
  148. annofabcli/statistics/visualize_statistics.py +126 -69
  149. annofabcli/statistics/visualize_video_duration.py +18 -20
  150. annofabcli/supplementary/delete_supplementary_data.py +4 -4
  151. annofabcli/supplementary/list_supplementary_data.py +3 -3
  152. annofabcli/supplementary/put_supplementary_data.py +8 -8
  153. annofabcli/supplementary/subcommand_supplementary.py +1 -3
  154. annofabcli/task/cancel_acceptance.py +16 -17
  155. annofabcli/task/change_operator.py +10 -12
  156. annofabcli/task/change_status_to_break.py +7 -9
  157. annofabcli/task/change_status_to_on_hold.py +10 -12
  158. annofabcli/task/complete_tasks.py +17 -18
  159. annofabcli/task/copy_tasks.py +3 -5
  160. annofabcli/task/delete_metadata_key_of_task.py +4 -6
  161. annofabcli/task/delete_tasks.py +6 -6
  162. annofabcli/task/download_task_json.py +4 -6
  163. annofabcli/task/list_all_tasks.py +7 -7
  164. annofabcli/task/list_all_tasks_added_task_history.py +12 -12
  165. annofabcli/task/list_tasks.py +6 -6
  166. annofabcli/task/list_tasks_added_task_history.py +9 -9
  167. annofabcli/task/put_tasks.py +4 -5
  168. annofabcli/task/put_tasks_by_count.py +1 -2
  169. annofabcli/task/reject_tasks.py +18 -20
  170. annofabcli/task/subcommand_task.py +1 -3
  171. annofabcli/task/update_metadata_of_task.py +5 -6
  172. annofabcli/task_history/download_task_history_json.py +4 -6
  173. annofabcli/task_history/list_all_task_history.py +5 -6
  174. annofabcli/task_history/list_task_history.py +3 -4
  175. annofabcli/task_history/subcommand_task_history.py +1 -3
  176. annofabcli/task_history_event/download_task_history_event_json.py +4 -6
  177. annofabcli/task_history_event/list_all_task_history_event.py +6 -6
  178. annofabcli/task_history_event/list_worktime.py +15 -15
  179. annofabcli/task_history_event/subcommand_task_history_event.py +1 -2
  180. {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/METADATA +9 -15
  181. annofabcli-1.112.0.dist-info/RECORD +229 -0
  182. {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/WHEEL +1 -1
  183. annofabcli-1.111.1.dist-info/RECORD +0 -228
  184. {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/entry_points.txt +0 -0
  185. {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,13 +6,14 @@ import json
6
6
  import logging.handlers
7
7
  import sys
8
8
  import tempfile
9
+ from collections.abc import Callable
9
10
  from multiprocessing import Pool
10
11
  from pathlib import Path
11
- from typing import Any, Callable, Optional
12
+ from typing import Any
12
13
 
13
14
  import annofabapi
14
15
  import pandas
15
- from annofabapi.models import InputDataType, ProjectMemberRole, TaskPhase
16
+ from annofabapi.models import ProjectMemberRole, TaskPhase
16
17
 
17
18
  import annofabcli
18
19
  from annofabcli.common.cli import (
@@ -74,14 +75,15 @@ class WriteCsvGraph:
74
75
  project_dir: ProjectDir,
75
76
  actual_worktime: ActualWorktime,
76
77
  *,
77
- annotation_count: Optional[AnnotationCount] = None,
78
- input_data_count: Optional[InputDataCount] = None,
79
- custom_production_volume: Optional[CustomProductionVolume] = None,
78
+ annotation_count: AnnotationCount | None = None,
79
+ input_data_count: InputDataCount | None = None,
80
+ custom_production_volume: CustomProductionVolume | None = None,
80
81
  minimal_output: bool = False,
81
82
  output_only_text: bool = False,
82
- production_volume_include_labels: Optional[list[str]] = None,
83
- production_volume_exclude_labels: Optional[list[str]] = None,
83
+ production_volume_include_labels: list[str] | None = None,
84
+ production_volume_exclude_labels: list[str] | None = None,
84
85
  include_annotation_duration_seconds: bool = False,
86
+ include_video_duration_minutes: bool = False,
85
87
  ) -> None:
86
88
  self.service = service
87
89
  self.project_id = project_id
@@ -98,10 +100,11 @@ class WriteCsvGraph:
98
100
  self.production_volume_include_labels = production_volume_include_labels
99
101
  self.production_volume_exclude_labels = production_volume_exclude_labels
100
102
  self.include_annotation_duration_seconds = include_annotation_duration_seconds
103
+ self.include_video_duration_minutes = include_video_duration_minutes
101
104
 
102
- self.task: Optional[Task] = None
103
- self.worktime_per_date: Optional[WorktimePerDate] = None
104
- self.task_worktime_obj: Optional[TaskWorktimeByPhaseUser] = None
105
+ self.task: Task | None = None
106
+ self.worktime_per_date: WorktimePerDate | None = None
107
+ self.task_worktime_obj: TaskWorktimeByPhaseUser | None = None
105
108
 
106
109
  def _catch_exception(self, function: Callable[..., Any]) -> Callable[..., Any]:
107
110
  """
@@ -119,14 +122,18 @@ class WriteCsvGraph:
119
122
 
120
123
  def _get_task(self) -> Task:
121
124
  if self.task is None:
125
+ custom_production_volume = self._prepare_custom_production_volume()
126
+
122
127
  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
- )
128
+ if self.visualize_source_files.annotation_zip_path.exists():
129
+ annotation_count = AnnotationCount.from_annotation_zip(
130
+ self.visualize_source_files.annotation_zip_path,
131
+ project_id=self.project_id,
132
+ include_labels=self.production_volume_include_labels,
133
+ exclude_labels=self.production_volume_exclude_labels,
134
+ )
135
+ else:
136
+ annotation_count = AnnotationCount.empty()
130
137
  else:
131
138
  annotation_count = self.annotation_count
132
139
 
@@ -137,36 +144,6 @@ class WriteCsvGraph:
137
144
  new_tasks = filter_tasks(tasks, self.task_completion_criteria, self.filtering_query, task_histories=task_histories)
138
145
  logger.debug(f"project_id='{self.project_id}' :: 集計対象タスクは {len(new_tasks)} / {len(tasks)} 件です。")
139
146
 
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
147
  self.task = Task.from_api_content(
171
148
  tasks=new_tasks,
172
149
  task_histories=task_histories,
@@ -180,6 +157,80 @@ class WriteCsvGraph:
180
157
 
181
158
  return self.task
182
159
 
160
+ def _prepare_custom_production_volume(self) -> CustomProductionVolume | None:
161
+ """カスタム生産量の準備を行う"""
162
+ custom_production_volume = self.custom_production_volume
163
+
164
+ # annotation_duration_secondsを生産量に含める場合、アノテーション時間を計算
165
+ if self.include_annotation_duration_seconds:
166
+ custom_production_volume = self._add_annotation_duration(custom_production_volume)
167
+
168
+ # 動画プロジェクトの場合、動画の長さ(分)を生産量に含める
169
+ if self.include_video_duration_minutes:
170
+ custom_production_volume = self._add_video_duration(custom_production_volume)
171
+
172
+ return custom_production_volume
173
+
174
+ def _add_annotation_duration(self, custom_production_volume: CustomProductionVolume | None) -> CustomProductionVolume:
175
+ """区間アノテーションの長さを生産量に追加する"""
176
+ logger.debug(f"project_id='{self.project_id}' :: 区間アノテーションの長さ('annotation_duration_minute')を計算します。")
177
+ annotation_duration_obj = AnnotationDuration.from_annotation_zip(
178
+ self.visualize_source_files.annotation_zip_path,
179
+ project_id=self.project_id,
180
+ include_labels=self.production_volume_include_labels,
181
+ exclude_labels=self.production_volume_exclude_labels,
182
+ )
183
+ annotation_duration_column = ProductionVolumeColumn(value="annotation_duration_minute", name="区間アノテーションの長さ(分)")
184
+
185
+ if custom_production_volume is not None:
186
+ # 既存のCustomProductionVolumeのデータと結合
187
+ if not custom_production_volume.is_empty():
188
+ annotation_duration_df = pandas.merge(custom_production_volume.df, annotation_duration_obj.df, on=["project_id", "task_id"], how="outer")
189
+ else:
190
+ annotation_duration_df = annotation_duration_obj.df
191
+
192
+ # annotation_duration_minuteを含む新しいProductionVolumeColumnリストを作成
193
+ new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
194
+ if annotation_duration_column not in new_production_volume_list:
195
+ new_production_volume_list.append(annotation_duration_column)
196
+
197
+ return CustomProductionVolume(annotation_duration_df, custom_production_volume_list=new_production_volume_list)
198
+ else:
199
+ # CustomProductionVolumeが存在しない場合、新規作成
200
+ return CustomProductionVolume(annotation_duration_obj.df, custom_production_volume_list=[annotation_duration_column])
201
+
202
+ def _add_video_duration(self, custom_production_volume: CustomProductionVolume | None) -> CustomProductionVolume:
203
+ """動画の長さ(分)を生産量に追加する"""
204
+ logger.debug(f"project_id='{self.project_id}' :: 動画の長さ('video_duration_minute')を計算します。")
205
+ video_duration_by_task_id = self.visualize_source_files.get_video_duration_minutes_by_task_id()
206
+
207
+ # DataFrameの作成
208
+ 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()]
209
+ if len(video_duration_data) == 0:
210
+ video_duration_df = pandas.DataFrame(columns=["project_id", "task_id", "video_duration_minute"])
211
+ else:
212
+ video_duration_df = pandas.DataFrame(video_duration_data)
213
+
214
+ video_duration_df = video_duration_df.astype({"project_id": "string", "task_id": "string", "video_duration_minute": "float64"})
215
+ video_duration_column = ProductionVolumeColumn(value="video_duration_minute", name="動画の長さ(分)")
216
+
217
+ if custom_production_volume is not None:
218
+ # 既存のCustomProductionVolumeのデータと結合
219
+ if not custom_production_volume.is_empty():
220
+ merged_df = pandas.merge(custom_production_volume.df, video_duration_df, on=["project_id", "task_id"], how="outer")
221
+ else:
222
+ merged_df = video_duration_df
223
+
224
+ # video_duration_minuteを含む新しいProductionVolumeColumnリストを作成
225
+ new_production_volume_list = list(custom_production_volume.custom_production_volume_list)
226
+ if video_duration_column not in new_production_volume_list:
227
+ new_production_volume_list.append(video_duration_column)
228
+
229
+ return CustomProductionVolume(merged_df, custom_production_volume_list=new_production_volume_list)
230
+ else:
231
+ # CustomProductionVolumeが存在しない場合、新規作成
232
+ return CustomProductionVolume(video_duration_df, custom_production_volume_list=[video_duration_column])
233
+
183
234
  def _get_task_worktime_obj(self) -> TaskWorktimeByPhaseUser:
184
235
  if self.task_worktime_obj is None:
185
236
  task_history = TaskHistory.from_api_content(self.visualize_source_files.read_task_histories_json())
@@ -247,7 +298,7 @@ class WriteCsvGraph:
247
298
  if not self.output_only_text:
248
299
  self.project_dir.write_user_performance_scatter_plot(user_performance)
249
300
 
250
- def write_cumulative_linegraph_by_user(self, user_id_list: Optional[list[str]] = None) -> None:
301
+ def write_cumulative_linegraph_by_user(self, user_id_list: list[str] | None = None) -> None:
251
302
  """ユーザごとの累積折れ線グラフをプロットする。"""
252
303
  task_worktime_obj = self._get_task_worktime_obj()
253
304
  annotator_obj = AnnotatorCumulativeProductivity.from_df_wrapper(task_worktime_obj)
@@ -259,7 +310,7 @@ class WriteCsvGraph:
259
310
  self.project_dir.write_cumulative_line_graph(inspector_obj, phase=TaskPhase.INSPECTION, user_id_list=user_id_list, minimal_output=self.minimal_output)
260
311
  self.project_dir.write_cumulative_line_graph(acceptor_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=user_id_list, minimal_output=self.minimal_output)
261
312
 
262
- def write_worktime_per_date(self, user_id_list: Optional[list[str]] = None) -> None:
313
+ def write_worktime_per_date(self, user_id_list: list[str] | None = None) -> None:
263
314
  """日ごとの作業時間情報を出力する。"""
264
315
  worktime_per_date_obj = self._get_worktime_per_date()
265
316
 
@@ -278,7 +329,7 @@ class WriteCsvGraph:
278
329
  self.project_dir.write_whole_productivity_line_graph_per_date(productivity_per_completed_date_obj)
279
330
  self.project_dir.write_whole_productivity_line_graph_per_annotation_started_date(productivity_per_started_date_obj)
280
331
 
281
- def write_user_productivity_per_date(self, user_id_list: Optional[list[str]] = None) -> None:
332
+ def write_user_productivity_per_date(self, user_id_list: list[str] | None = None) -> None:
282
333
  """ユーザごとの日ごとの生産性情報を出力する。"""
283
334
  task_worktime_obj = self._get_task_worktime_obj()
284
335
 
@@ -312,14 +363,14 @@ class VisualizingStatisticsMain:
312
363
  # その他
313
364
  download_latest: bool = False,
314
365
  is_get_task_histories_one_of_each: bool = False,
315
- actual_worktime: Optional[ActualWorktime] = None,
316
- annotation_count: Optional[AnnotationCount] = None,
317
- input_data_count: Optional[InputDataCount] = None,
318
- custom_production_volume: Optional[CustomProductionVolume] = None,
319
- user_ids: Optional[list[str]] = None,
366
+ actual_worktime: ActualWorktime | None = None,
367
+ annotation_count: AnnotationCount | None = None,
368
+ input_data_count: InputDataCount | None = None,
369
+ custom_production_volume: CustomProductionVolume | None = None,
370
+ user_ids: list[str] | None = None,
320
371
  not_download_visualization_source_files: bool = False,
321
- production_volume_include_labels: Optional[list[str]] = None,
322
- production_volume_exclude_labels: Optional[list[str]] = None,
372
+ production_volume_include_labels: list[str] | None = None,
373
+ production_volume_exclude_labels: list[str] | None = None,
323
374
  ) -> None:
324
375
  self.service = service
325
376
  self.facade = AnnofabApiFacade(service)
@@ -350,6 +401,8 @@ class VisualizingStatisticsMain:
350
401
  measurement_datetime=annofabapi.utils.str_now(),
351
402
  task_completion_criteria=self.task_completion_criteria,
352
403
  query=self.filtering_query,
404
+ production_volume_include_labels=self.production_volume_include_labels,
405
+ production_volume_exclude_labels=self.production_volume_exclude_labels,
353
406
  )
354
407
  return project_summary
355
408
 
@@ -370,6 +423,9 @@ class VisualizingStatisticsMain:
370
423
  # 動画プロジェクトの場合、annotation_duration_secondを生産量に含める
371
424
  custom_production_volume = self.custom_production_volume
372
425
 
426
+ # 動画プロジェクトかどうかを判定
427
+ is_video_project = project_info.input_data_type == "movie"
428
+
373
429
  project_dir = ProjectDir(
374
430
  output_project_dir,
375
431
  self.task_completion_criteria,
@@ -429,7 +485,8 @@ class VisualizingStatisticsMain:
429
485
  output_only_text=self.output_only_text,
430
486
  production_volume_include_labels=self.production_volume_include_labels,
431
487
  production_volume_exclude_labels=self.production_volume_exclude_labels,
432
- include_annotation_duration_seconds=(project_info.input_data_type == InputDataType.MOVIE.value),
488
+ include_annotation_duration_seconds=is_video_project,
489
+ include_video_duration_minutes=is_video_project,
433
490
  )
434
491
 
435
492
  write_obj._catch_exception(write_obj.write_user_performance)() # noqa: SLF001
@@ -447,7 +504,7 @@ class VisualizingStatisticsMain:
447
504
  self,
448
505
  project_id: str,
449
506
  root_output_dir: Path,
450
- ) -> Optional[Path]:
507
+ ) -> Path | None:
451
508
  try:
452
509
  output_project_dir = root_output_dir / project_id
453
510
  self.visualize_statistics(
@@ -464,7 +521,7 @@ class VisualizingStatisticsMain:
464
521
  project_id_list: list[str],
465
522
  root_output_dir: Path,
466
523
  *,
467
- parallelism: Optional[int] = None,
524
+ parallelism: int | None = None,
468
525
  ) -> list[Path]:
469
526
  output_project_dir_list: list[Path] = []
470
527
 
@@ -527,11 +584,11 @@ class VisualizeStatistics(CommandLine):
527
584
  temp_dir: Path,
528
585
  filtering_query: FilteringQuery,
529
586
  task_completion_criteria: TaskCompletionCriteria,
530
- user_id_list: Optional[list[str]],
587
+ user_id_list: list[str] | None,
531
588
  actual_worktime: ActualWorktime,
532
- annotation_count: Optional[AnnotationCount],
533
- input_data_count: Optional[InputDataCount],
534
- custom_production_volume: Optional[CustomProductionVolume],
589
+ annotation_count: AnnotationCount | None,
590
+ input_data_count: InputDataCount | None,
591
+ custom_production_volume: CustomProductionVolume | None,
535
592
  download_latest: bool, # noqa: FBT001
536
593
  is_get_task_histories_one_of_each: bool, # noqa: FBT001
537
594
  minimal_output: bool, # noqa: FBT001
@@ -539,9 +596,9 @@ class VisualizeStatistics(CommandLine):
539
596
  not_download_visualization_source_files: bool, # noqa: FBT001
540
597
  project_id_list: list[str],
541
598
  root_output_dir: Path,
542
- parallelism: Optional[int],
543
- production_volume_include_labels: Optional[list[str]] = None,
544
- production_volume_exclude_labels: Optional[list[str]] = None,
599
+ parallelism: int | None,
600
+ production_volume_include_labels: list[str] | None = None,
601
+ production_volume_exclude_labels: list[str] | None = None,
545
602
  ) -> None:
546
603
  main_obj = VisualizingStatisticsMain(
547
604
  service=self.service,
@@ -596,7 +653,7 @@ class VisualizeStatistics(CommandLine):
596
653
  task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
597
654
 
598
655
  dict_task_query = annofabcli.common.cli.get_json_from_args(args.task_query)
599
- task_query: Optional[TaskQuery] = None
656
+ task_query: TaskQuery | None = None
600
657
  if dict_task_query is not None:
601
658
  task_query = TaskQuery.from_dict(dict_task_query)
602
659
  logger.warning("引数 '--task_query' は非推奨です。代わりに '--task_completion_criteria' を指定してください。")
@@ -852,7 +909,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
852
909
  parser.set_defaults(subcommand_func=main)
853
910
 
854
911
 
855
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
912
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
856
913
  subcommand_name = "visualize"
857
914
  subcommand_help = "生産性に関するCSVファイルやグラフを出力します。"
858
915
  description = "生産性に関するCSVファイルやグラフを出力します。"
@@ -11,7 +11,6 @@ from collections.abc import Collection, Sequence
11
11
  from enum import Enum
12
12
  from functools import partial
13
13
  from pathlib import Path
14
- from typing import Optional, Union
15
14
 
16
15
  import bokeh
17
16
  import numpy
@@ -22,7 +21,6 @@ from bokeh.models.annotations.labels import Title
22
21
  from bokeh.models.widgets.markups import PreText
23
22
  from bokeh.plotting import ColumnDataSource, figure
24
23
 
25
- import annofabcli
26
24
  import annofabcli.common.cli
27
25
  from annofabcli.common.cli import (
28
26
  COMMAND_LINE_ERROR_STATUS_CODE,
@@ -49,9 +47,9 @@ def plot_video_duration(
49
47
  output_file: Path,
50
48
  *,
51
49
  time_unit: TimeUnit,
52
- bin_width: Optional[float] = None,
53
- project_id: Optional[str] = None,
54
- project_title: Optional[str] = None,
50
+ bin_width: float | None = None,
51
+ project_id: str | None = None,
52
+ project_title: str | None = None,
55
53
  ) -> None:
56
54
  """
57
55
  ラベルごとの区間アノテーションの長さのヒストグラムを出力します。
@@ -66,7 +64,7 @@ def plot_video_duration(
66
64
 
67
65
  def create_figure(
68
66
  durations: Sequence[float],
69
- bins: Union[int, numpy.ndarray],
67
+ bins: int | numpy.ndarray,
70
68
  histogram_range: tuple[float, float],
71
69
  title: str,
72
70
  x_axis_label: str,
@@ -75,10 +73,10 @@ def plot_video_duration(
75
73
  hist, bin_edges = numpy.histogram(durations, bins=bins, range=histogram_range)
76
74
 
77
75
  df_histogram = pandas.DataFrame({"frequency": hist, "left": bin_edges[:-1], "right": bin_edges[1:]})
78
- df_histogram["interval"] = [f"{left:.1f} to {right:.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"])]
76
+ df_histogram["interval"] = [f"{left:.1f} to {right:.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"], strict=False)]
79
77
 
80
78
  source = ColumnDataSource(df_histogram)
81
- fig = figure(
79
+ fig = figure( # type: ignore[call-arg]
82
80
  width=400,
83
81
  height=300,
84
82
  x_axis_label=x_axis_label,
@@ -108,7 +106,7 @@ def plot_video_duration(
108
106
  if bins_sequence[-1] == max_duration:
109
107
  bins_sequence = numpy.append(bins_sequence, bins_sequence[-1] + bin_width)
110
108
 
111
- bins: Union[int, numpy.ndarray] = bins_sequence
109
+ bins: int | numpy.ndarray = bins_sequence
112
110
  else:
113
111
  bins = BIN_COUNT
114
112
 
@@ -142,10 +140,10 @@ def get_video_duration_list(
142
140
  input_data_json: Path,
143
141
  task_json: Path,
144
142
  *,
145
- input_data_ids: Optional[Collection[str]] = None,
143
+ input_data_ids: Collection[str] | None = None,
146
144
  from_datetime: datetime.datetime | None = None,
147
145
  to_datetime: datetime.datetime | None = None,
148
- task_ids: Optional[Collection[str]] = None,
146
+ task_ids: Collection[str] | None = None,
149
147
  ) -> list[float]:
150
148
  """
151
149
  入力データである動画の長さ(単位は秒)の一覧を取得します。
@@ -214,13 +212,13 @@ class VisualizeVideoDuration(CommandLine):
214
212
  output_html: Path,
215
213
  *,
216
214
  time_unit: TimeUnit,
217
- project_id: Optional[str] = None,
218
- project_title: Optional[str] = None,
219
- bin_width: Optional[float] = None,
220
- input_data_ids: Optional[Collection[str]] = None,
221
- task_ids: Optional[Collection[str]] = None,
222
- from_date: Optional[str] = None,
223
- to_date: Optional[str] = None,
215
+ project_id: str | None = None,
216
+ project_title: str | None = None,
217
+ bin_width: float | None = None,
218
+ input_data_ids: Collection[str] | None = None,
219
+ task_ids: Collection[str] | None = None,
220
+ from_date: str | None = None,
221
+ to_date: str | None = None,
224
222
  ) -> None:
225
223
  tz_info = datetime.datetime.now().astimezone().tzinfo
226
224
  from_datetime = datetime.datetime.fromisoformat(from_date).astimezone(tz_info) if from_date is not None else None
@@ -250,7 +248,7 @@ class VisualizeVideoDuration(CommandLine):
250
248
  if not self.validate(args):
251
249
  sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
252
250
 
253
- project_id: Optional[str] = args.project_id
251
+ project_id: str | None = args.project_id
254
252
  if project_id is not None:
255
253
  super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
256
254
  project, _ = self.service.api.get_project(project_id)
@@ -392,7 +390,7 @@ def main(args: argparse.Namespace) -> None:
392
390
  VisualizeVideoDuration(service, facade, args).main()
393
391
 
394
392
 
395
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
393
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
396
394
  subcommand_name = "visualize_video_duration"
397
395
  subcommand_help = "動画の長さをヒストグラムで可視化します。"
398
396
  epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
@@ -3,7 +3,7 @@ import logging
3
3
  import sys
4
4
  from collections import defaultdict
5
5
  from pathlib import Path
6
- from typing import Any, Optional
6
+ from typing import Any
7
7
 
8
8
  import annofabapi
9
9
  import pandas
@@ -36,7 +36,7 @@ def get_input_data_supplementary_data_dict_from_csv(csv_path: Path) -> InputData
36
36
  dtype={"input_data_id": "string", "supplementary_data_id": "string"},
37
37
  )
38
38
  input_data_dict: InputDataSupplementaryDataDict = defaultdict(list)
39
- for input_data_id, supplementary_data_id in zip(df["input_data_id"], df["supplementary_data_id"]):
39
+ for input_data_id, supplementary_data_id in zip(df["input_data_id"], df["supplementary_data_id"], strict=False):
40
40
  input_data_dict[input_data_id].append(supplementary_data_id)
41
41
  return input_data_dict
42
42
 
@@ -70,7 +70,7 @@ class DeleteSupplementaryDataMain(CommandLineWithConfirm):
70
70
 
71
71
  """
72
72
 
73
- def _get_supplementary_data_list(supplementary_data_id: str) -> Optional[dict[str, Any]]:
73
+ def _get_supplementary_data_list(supplementary_data_id: str) -> dict[str, Any] | None:
74
74
  return first_true(supplementary_data_list, pred=lambda e: e["supplementary_data_id"] == supplementary_data_id)
75
75
 
76
76
  input_data = self.service.wrapper.get_input_data_or_none(project_id, input_data_id)
@@ -275,7 +275,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
275
275
  parser.set_defaults(subcommand_func=main)
276
276
 
277
277
 
278
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
278
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
279
279
  subcommand_name = "delete"
280
280
  subcommand_help = "補助情報を削除します。"
281
281
  description = "補助情報を削除します。"
@@ -7,7 +7,7 @@ import logging
7
7
  import multiprocessing
8
8
  import tempfile
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  import annofabapi
13
13
  from annofabapi.models import SupplementaryData
@@ -72,7 +72,7 @@ class ListSupplementaryDataMain:
72
72
  logger.warning(f"input_data_id='{input_data_index}': 補助情報の取得に失敗しました。", exc_info=True)
73
73
  return []
74
74
 
75
- def get_all_supplementary_data_list(self, input_data_id_list: list[str], *, parallelism: Optional[int] = None) -> list[SupplementaryData]:
75
+ def get_all_supplementary_data_list(self, input_data_id_list: list[str], *, parallelism: int | None = None) -> list[SupplementaryData]:
76
76
  """
77
77
  補助情報一覧を取得する。
78
78
  """
@@ -168,7 +168,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
168
168
  parser.set_defaults(subcommand_func=main)
169
169
 
170
170
 
171
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
171
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
172
172
  subcommand_name = "list"
173
173
  subcommand_help = "補助情報一覧を出力します。"
174
174
  description = "補助情報一覧を出力します。"
@@ -7,7 +7,7 @@ from dataclasses import dataclass
7
7
  from functools import partial
8
8
  from multiprocessing import Pool
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  import annofabapi
13
13
  import pandas
@@ -49,9 +49,9 @@ class CliSupplementaryData(DataClassJsonMixin):
49
49
  input_data_id: str
50
50
  supplementary_data_name: str
51
51
  supplementary_data_path: str
52
- supplementary_data_id: Optional[str] = None
53
- supplementary_data_type: Optional[str] = None
54
- supplementary_data_number: Optional[int] = None
52
+ supplementary_data_id: str | None = None
53
+ supplementary_data_type: str | None = None
54
+ supplementary_data_number: int | None = None
55
55
 
56
56
 
57
57
  @dataclass
@@ -64,9 +64,9 @@ class SupplementaryDataForPut:
64
64
  supplementary_data_id: str
65
65
  supplementary_data_name: str
66
66
  supplementary_data_path: str
67
- supplementary_data_type: Optional[str]
67
+ supplementary_data_type: str | None
68
68
  supplementary_data_number: int
69
- last_updated_datetime: Optional[str]
69
+ last_updated_datetime: str | None
70
70
 
71
71
 
72
72
  class SubPutSupplementaryData:
@@ -249,7 +249,7 @@ class PutSupplementaryData(CommandLine):
249
249
  supplementary_data_list: list[CliSupplementaryData],
250
250
  *,
251
251
  overwrite: bool = False,
252
- parallelism: Optional[int] = None,
252
+ parallelism: int | None = None,
253
253
  ) -> None:
254
254
  """
255
255
  補助情報を一括で登録する。
@@ -412,7 +412,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
412
412
  parser.set_defaults(subcommand_func=main)
413
413
 
414
414
 
415
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
415
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
416
416
  subcommand_name = "put"
417
417
  subcommand_help = "補助情報を登録します。"
418
418
  description = "補助情報を登録します。"
@@ -1,7 +1,5 @@
1
1
  import argparse
2
- from typing import Optional
3
2
 
4
- import annofabcli
5
3
  import annofabcli.common.cli
6
4
  import annofabcli.supplementary.delete_supplementary_data
7
5
  import annofabcli.supplementary.list_supplementary_data
@@ -17,7 +15,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
17
15
  annofabcli.supplementary.put_supplementary_data.add_parser(subparsers)
18
16
 
19
17
 
20
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
18
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
21
19
  subcommand_name = "supplementary"
22
20
  subcommand_help = "補助情報関係のサブコマンド"
23
21
  description = "補助情報関係のサブコマンド"