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
@@ -8,12 +8,10 @@ from __future__ import annotations
8
8
  import datetime
9
9
  import logging
10
10
  from pathlib import Path
11
- from typing import Any, Optional
11
+ from typing import Any
12
12
 
13
13
  import annofabapi
14
- import bokeh
15
14
  import bokeh.layouts
16
- import bokeh.palettes
17
15
  import pandas
18
16
  from bokeh.models.ui import UIElement
19
17
  from bokeh.plotting import ColumnDataSource
@@ -250,9 +248,9 @@ class WorktimePerDate:
250
248
  project_id: str,
251
249
  actual_worktime: ActualWorktime,
252
250
  *,
253
- task_history_event_json: Optional[Path] = None,
254
- start_date: Optional[str] = None,
255
- end_date: Optional[str] = None,
251
+ task_history_event_json: Path | None = None,
252
+ start_date: str | None = None,
253
+ end_date: str | None = None,
256
254
  ) -> WorktimePerDate:
257
255
  """
258
256
 
@@ -371,8 +369,8 @@ class WorktimePerDate:
371
369
  self,
372
370
  output_file: Path,
373
371
  *,
374
- target_user_id_list: Optional[list[str]] = None,
375
- metadata: Optional[dict[str, Any]] = None,
372
+ target_user_id_list: list[str] | None = None,
373
+ metadata: dict[str, Any] | None = None,
376
374
  ) -> None:
377
375
  """
378
376
  作業時間の累積値をプロットする。
@@ -468,7 +466,7 @@ class WorktimePerDate:
468
466
  username = df_subset.iloc[0]["username"]
469
467
 
470
468
  line_count += 1
471
- for line_graph, (x_column, y_column) in zip(line_graph_list, columns_list):
469
+ for line_graph, (x_column, y_column) in zip(line_graph_list, columns_list, strict=False):
472
470
  line_graph.add_line(
473
471
  source=source,
474
472
  x_column=x_column,
@@ -510,10 +508,10 @@ class WorktimePerDate:
510
508
 
511
509
  def mask_user_info(
512
510
  self,
513
- to_replace_for_user_id: Optional[dict[str, str]] = None,
514
- to_replace_for_username: Optional[dict[str, str]] = None,
515
- to_replace_for_account_id: Optional[dict[str, str]] = None,
516
- to_replace_for_biography: Optional[dict[str, str]] = None,
511
+ to_replace_for_user_id: dict[str, str] | None = None,
512
+ to_replace_for_username: dict[str, str] | None = None,
513
+ to_replace_for_account_id: dict[str, str] | None = None,
514
+ to_replace_for_biography: dict[str, str] | None = None,
517
515
  ) -> WorktimePerDate:
518
516
  """
519
517
  引数から渡された情報を元に、インスタンス変数`df`内のユーザー情報をマスクして、新しいインスタンスを返します。
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  from collections.abc import Collection
5
5
  from dataclasses import dataclass
6
- from typing import Any, Optional
6
+ from typing import Any
7
7
 
8
8
  from annofabapi.dataclass.task import Task as DcTask
9
9
  from annofabapi.models import Task
@@ -21,13 +21,13 @@ class FilteringQuery:
21
21
  絞り込み条件
22
22
  """
23
23
 
24
- task_query: Optional[TaskQuery] = None
25
- start_date: Optional[str] = None
26
- end_date: Optional[str] = None
27
- ignored_task_ids: Optional[Collection[str]] = None
24
+ task_query: TaskQuery | None = None
25
+ start_date: str | None = None
26
+ end_date: str | None = None
27
+ ignored_task_ids: Collection[str] | None = None
28
28
 
29
29
 
30
- def _get_first_annotation_started_datetime(sub_task_history_list: list[dict[str, Any]]) -> Optional[str]:
30
+ def _get_first_annotation_started_datetime(sub_task_history_list: list[dict[str, Any]]) -> str | None:
31
31
  """
32
32
  1個のタスクのタスク履歴一覧から、最初に教師付フェーズを作業した日時を取得します。
33
33
  """
@@ -40,7 +40,7 @@ def _get_first_annotation_started_datetime(sub_task_history_list: list[dict[str,
40
40
  return task_history_list_with_annotation_phase[0]["started_datetime"]
41
41
 
42
42
 
43
- def filter_task_histories(task_histories: dict[str, list[dict[str, Any]]], *, start_date: Optional[str] = None, end_date: Optional[str] = None) -> dict[str, list[dict[str, Any]]]:
43
+ def filter_task_histories(task_histories: dict[str, list[dict[str, Any]]], *, start_date: str | None = None, end_date: str | None = None) -> dict[str, list[dict[str, Any]]]:
44
44
  """
45
45
  タスク履歴を絞り込みます。
46
46
 
@@ -4,7 +4,7 @@ import json
4
4
  import logging
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  from annofabapi.models import TaskPhase
10
10
  from dataclasses_json import DataClassJsonMixin
@@ -36,13 +36,9 @@ class ProjectDir(DataClassJsonMixin):
36
36
 
37
37
  Args:
38
38
  project_dir: ``annofabcli statistics visualize``コマンドによって出力されたプロジェクトディレクトリ
39
- metadata:
40
-
41
- タスクの完了条件(task_completion_criteria)は以下のいずれかです。
42
- - acceptance_completed: 受入フェーズの完了状態
43
- - acceptance_reached: 受入フェーズに到達
44
- - inspection_reached: 検査フェーズまたは受入フェーズに到達
45
- - annotation_started: 教師付フェーズが着手された
39
+ task_completion_criteria: タスクの完了条件
40
+ metadata: プロジェクトIDや絞り込み条件などの情報が含まれるメタデータ。この情報はグラフに埋め込まれます。
41
+ custom_production_volume_list: 独自の生産量に関する列情報のリスト
46
42
  """
47
43
 
48
44
  FILENAME_WHOLE_PERFORMANCE = "全体の生産性と品質.csv"
@@ -61,8 +57,8 @@ class ProjectDir(DataClassJsonMixin):
61
57
  project_dir: Path,
62
58
  task_completion_criteria: TaskCompletionCriteria,
63
59
  *,
64
- metadata: Optional[dict[str, Any]] = None,
65
- custom_production_volume_list: Optional[list[ProductionVolumeColumn]] = None,
60
+ metadata: dict[str, Any] | None = None,
61
+ custom_production_volume_list: list[ProductionVolumeColumn] | None = None,
66
62
  ) -> None:
67
63
  self.project_dir = project_dir
68
64
  self.task_completion_criteria = task_completion_criteria
@@ -130,7 +126,7 @@ class ProjectDir(DataClassJsonMixin):
130
126
  obj: AbstractPhaseCumulativeProductivity,
131
127
  phase: TaskPhase,
132
128
  *,
133
- user_id_list: Optional[list[str]] = None,
129
+ user_id_list: list[str] | None = None,
134
130
  minimal_output: bool = False,
135
131
  ) -> None:
136
132
  """
@@ -179,7 +175,7 @@ class ProjectDir(DataClassJsonMixin):
179
175
  phase_name = self.get_phase_name_for_filename(phase)
180
176
  obj.to_csv(self.project_dir / Path(f"{phase_name}者_{phase_name}開始日list.csv"))
181
177
 
182
- def write_performance_line_graph_per_date(self, obj: AbstractPhaseProductivityPerDate, phase: TaskPhase, *, user_id_list: Optional[list[str]] = None) -> None:
178
+ def write_performance_line_graph_per_date(self, obj: AbstractPhaseProductivityPerDate, phase: TaskPhase, *, user_id_list: list[str] | None = None) -> None:
183
179
  """
184
180
  指定したフェーズの開始日ごとの作業時間や生産性情報を、折れ線グラフとして出力します。
185
181
  """
@@ -396,7 +392,7 @@ class ProjectDir(DataClassJsonMixin):
396
392
  """`ユーザ_日付list-作業時間.csvを書き込む"""
397
393
  obj.to_csv(self.project_dir / self.FILENAME_WORKTIME_PER_DATE_USER)
398
394
 
399
- def write_worktime_line_graph(self, obj: WorktimePerDate, user_id_list: Optional[list[str]] = None) -> None:
395
+ def write_worktime_line_graph(self, obj: WorktimePerDate, user_id_list: list[str] | None = None) -> None:
400
396
  """横軸が日付、縦軸がユーザごとの作業時間である折れ線グラフを出力します。"""
401
397
  obj.plot_cumulatively(self.project_dir / "line-graph/累積折れ線-横軸_日-縦軸_作業時間.html", target_user_id_list=user_id_list, metadata=self.metadata)
402
398
 
@@ -429,7 +425,7 @@ class ProjectDir(DataClassJsonMixin):
429
425
  """
430
426
  print_json(obj.to_dict(encode_json=True), output=self.project_dir / self.FILENAME_MERGE_INFO, is_pretty=True)
431
427
 
432
- def read_metadata(self) -> Optional[dict[str, Any]]:
428
+ def read_metadata(self) -> dict[str, Any] | None:
433
429
  """
434
430
  `project_info`または`merge_info`の内容をメタデータとして読み込む。
435
431
  どちらも存在しない場合はNoneを返す。
@@ -448,6 +444,17 @@ class ProjectDir(DataClassJsonMixin):
448
444
 
449
445
  return None
450
446
 
447
+ def get_project_title(self) -> str:
448
+ """
449
+ `project_info`に記載されているプジェクトのタイトルを返します。
450
+ ただし`self.is_merged()`がTrueの場合は、`project_info`が存在しないので空文字を返します。
451
+ """
452
+ if self.is_merged():
453
+ return ""
454
+
455
+ project_info = self.read_project_info()
456
+ return project_info.project_title
457
+
451
458
 
452
459
  @dataclass
453
460
  class ProjectInfo(DataClassJsonMixin):
@@ -464,6 +471,12 @@ class ProjectInfo(DataClassJsonMixin):
464
471
  query: FilteringQuery
465
472
  """集計対象を絞り込むためのクエリ"""
466
473
 
474
+ production_volume_include_labels: list[str] | None = None
475
+ """生産量に含める「アノテーション仕様のラベル名(英語)」のリスト。Noneの場合、すべてのラベルを含む。"""
476
+
477
+ production_volume_exclude_labels: list[str] | None = None
478
+ """生産量から除外する「アノテーション仕様のラベル名(英語)」のリスト。Noneの場合、除外しない。"""
479
+
467
480
 
468
481
  @dataclass
469
482
  class MergingInfo(DataClassJsonMixin):
@@ -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,
@@ -10,7 +10,7 @@ from collections import defaultdict
10
10
  from collections.abc import Collection, Sequence
11
11
  from functools import partial
12
12
  from pathlib import Path
13
- from typing import Any, Optional
13
+ from typing import Any
14
14
 
15
15
  import bokeh
16
16
  import numpy
@@ -20,7 +20,6 @@ from bokeh.models import LayoutDOM
20
20
  from bokeh.models.widgets.markups import Div
21
21
  from bokeh.plotting import figure
22
22
 
23
- import annofabcli
24
23
  import annofabcli.common.cli
25
24
  from annofabcli.common.bokeh import convert_1d_figure_list_to_2d, create_pretext_from_metadata
26
25
  from annofabcli.common.cli import (
@@ -56,11 +55,11 @@ def _get_y_axis_label(group_by: GroupBy) -> str:
56
55
  raise RuntimeError(f"group_by='{group_by}'が対象外です。")
57
56
 
58
57
 
59
- def convert_to_2d_figure_list(figures_dict: dict[tuple[str, str], list[figure]], *, ncols: int = 4) -> list[list[Optional[LayoutDOM]]]:
58
+ def convert_to_2d_figure_list(figures_dict: dict[tuple[str, str], list[figure]], *, ncols: int = 4) -> list[list[LayoutDOM | None]]:
60
59
  """
61
60
  grid layout用に2次元のfigureリストに変換する。
62
61
  """
63
- row_list: list[list[Optional[LayoutDOM]]] = []
62
+ row_list: list[list[LayoutDOM | None]] = []
64
63
 
65
64
  for (label_name, attribute_name), figure_list in figures_dict.items():
66
65
  row_list.append([Div(text=f"<h3>ラベル名='{label_name}', 属性名='{attribute_name}'</h3>")])
@@ -68,7 +67,7 @@ def convert_to_2d_figure_list(figures_dict: dict[tuple[str, str], list[figure]],
68
67
  for i in range(math.ceil(len(figure_list) / ncols)):
69
68
  start = i * ncols
70
69
  end = (i + 1) * ncols
71
- row: list[Optional[LayoutDOM]] = []
70
+ row: list[LayoutDOM | None] = []
72
71
  row.extend(figure_list[start:end])
73
72
  if len(row) < ncols:
74
73
  row.extend([None] * (ncols - len(row)))
@@ -101,11 +100,11 @@ def plot_label_histogram(
101
100
  group_by: GroupBy,
102
101
  output_file: Path,
103
102
  *,
104
- prior_keys: Optional[list[str]] = None,
105
- bin_width: Optional[int] = None,
103
+ prior_keys: list[str] | None = None,
104
+ bin_width: int | None = None,
106
105
  exclude_empty_value: bool = False,
107
106
  arrange_bin_edge: bool = False,
108
- metadata: Optional[dict[str, Any]] = None,
107
+ metadata: dict[str, Any] | None = None,
109
108
  ) -> None:
110
109
  """
111
110
  ラベルごとのアノテーション数のヒストグラムを出力する。
@@ -152,7 +151,7 @@ def plot_label_histogram(
152
151
 
153
152
  max_annotation_count = df.max(numeric_only=True).max()
154
153
 
155
- figure_list_2d: list[list[Optional[LayoutDOM]]] = [
154
+ figure_list_2d: list[list[LayoutDOM | None]] = [
156
155
  [
157
156
  Div(text="<h3>アノテーション数の分布(ラベル名ごと)</h3>"),
158
157
  ]
@@ -203,11 +202,11 @@ def plot_attribute_histogram( # noqa: PLR0915
203
202
  group_by: GroupBy,
204
203
  output_file: Path,
205
204
  *,
206
- prior_keys: Optional[list[AttributeValueKey]] = None,
207
- bin_width: Optional[int] = None,
205
+ prior_keys: list[AttributeValueKey] | None = None,
206
+ bin_width: int | None = None,
208
207
  exclude_empty_value: bool = False,
209
208
  arrange_bin_edge: bool = False,
210
- metadata: Optional[dict[str, Any]] = None,
209
+ metadata: dict[str, Any] | None = None,
211
210
  ) -> None:
212
211
  """
213
212
  属性値ごとのアノテーション数のヒストグラムを出力する。
@@ -256,7 +255,7 @@ def plot_attribute_histogram( # noqa: PLR0915
256
255
 
257
256
  max_annotation_count = df.max(numeric_only=True).max()
258
257
 
259
- figure_list_2d: list[list[Optional[LayoutDOM]]] = [
258
+ figure_list_2d: list[list[LayoutDOM | None]] = [
260
259
  [
261
260
  Div(text="<h3>アノテーション数の分布(属性値ごと)</h3>"),
262
261
  ]
@@ -322,10 +321,10 @@ class VisualizeAnnotationCount(CommandLine):
322
321
  annotation_path: Path,
323
322
  output_dir: Path,
324
323
  *,
325
- bin_width: Optional[int] = None,
326
- project_id: Optional[str] = None,
327
- target_task_ids: Optional[Collection[str]] = None,
328
- task_query: Optional[TaskQuery] = None,
324
+ bin_width: int | None = None,
325
+ project_id: str | None = None,
326
+ target_task_ids: Collection[str] | None = None,
327
+ task_query: TaskQuery | None = None,
329
328
  exclude_empty_value: bool = False,
330
329
  arrange_bin_edge: bool = False,
331
330
  ) -> None:
@@ -333,8 +332,8 @@ class VisualizeAnnotationCount(CommandLine):
333
332
  attributes_count_html = output_dir / "attributes_count.html"
334
333
 
335
334
  # 集計対象の属性を、選択肢系の属性にする
336
- annotation_specs: Optional[AnnotationSpecs] = None
337
- non_selective_attribute_name_keys: Optional[list[AttributeNameKey]] = None
335
+ annotation_specs: AnnotationSpecs | None = None
336
+ non_selective_attribute_name_keys: list[AttributeNameKey] | None = None
338
337
  if project_id is not None:
339
338
  annotation_specs = AnnotationSpecs(self.service, project_id)
340
339
  non_selective_attribute_name_keys = annotation_specs.non_selective_attribute_name_keys()
@@ -361,8 +360,8 @@ class VisualizeAnnotationCount(CommandLine):
361
360
  else:
362
361
  raise RuntimeError(f"group_by='{group_by}'が対象外です。")
363
362
 
364
- label_keys: Optional[list[str]] = None
365
- attribute_value_keys: Optional[list[AttributeValueKey]] = None
363
+ label_keys: list[str] | None = None
364
+ attribute_value_keys: list[AttributeValueKey] | None = None
366
365
  if annotation_specs is not None:
367
366
  label_keys = annotation_specs.label_keys()
368
367
  attribute_value_keys = annotation_specs.selective_attribute_value_keys()
@@ -406,7 +405,7 @@ class VisualizeAnnotationCount(CommandLine):
406
405
  if not self.validate(args):
407
406
  sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
408
407
 
409
- project_id: Optional[str] = args.project_id
408
+ project_id: str | None = args.project_id
410
409
  if project_id is not None:
411
410
  super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
412
411
 
@@ -534,7 +533,7 @@ def main(args: argparse.Namespace) -> None:
534
533
  VisualizeAnnotationCount(service, facade, args).main()
535
534
 
536
535
 
537
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
536
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
538
537
  subcommand_name = "visualize_annotation_count"
539
538
  subcommand_help = "各ラベル、各属性値のアノテーション数をヒストグラムで可視化します。"
540
539
  description = "各ラベル、各属性値のアノテーション数をヒストグラムで可視化したファイルを出力します。"
@@ -9,7 +9,7 @@ from collections.abc import Collection, Sequence
9
9
  from enum import Enum
10
10
  from functools import partial
11
11
  from pathlib import Path
12
- from typing import Any, Optional
12
+ from typing import Any
13
13
 
14
14
  import bokeh
15
15
  import numpy
@@ -19,7 +19,6 @@ from bokeh.models import LayoutDOM
19
19
  from bokeh.models.widgets.markups import Div
20
20
  from bokeh.plotting import figure
21
21
 
22
- import annofabcli
23
22
  import annofabcli.common.cli
24
23
  from annofabcli.common.bokeh import convert_1d_figure_list_to_2d, create_pretext_from_metadata
25
24
  from annofabcli.common.cli import (
@@ -56,11 +55,11 @@ def plot_annotation_duration_histogram_by_label( # noqa: PLR0915
56
55
  output_file: Path,
57
56
  *,
58
57
  time_unit: TimeUnit,
59
- bin_width: Optional[float] = None,
60
- prior_keys: Optional[list[str]] = None,
58
+ bin_width: float | None = None,
59
+ prior_keys: list[str] | None = None,
61
60
  exclude_empty_value: bool = False,
62
61
  arrange_bin_edge: bool = False,
63
- metadata: Optional[dict[str, Any]] = None,
62
+ metadata: dict[str, Any] | None = None,
64
63
  ) -> None:
65
64
  """
66
65
  ラベルごとの区間アノテーションの長さのヒストグラムを出力します。
@@ -88,7 +87,7 @@ def plot_annotation_duration_histogram_by_label( # noqa: PLR0915
88
87
  df = df / 60
89
88
  return df
90
89
 
91
- def get_histogram_range(df: pandas.DataFrame) -> Optional[tuple[float, float]]:
90
+ def get_histogram_range(df: pandas.DataFrame) -> tuple[float, float] | None:
92
91
  if arrange_bin_edge:
93
92
  return (
94
93
  df.min(numeric_only=True).min(),
@@ -101,7 +100,7 @@ def plot_annotation_duration_histogram_by_label( # noqa: PLR0915
101
100
 
102
101
  max_duration = df.max(numeric_only=True).max()
103
102
 
104
- figure_list_2d: list[list[Optional[LayoutDOM]]] = [
103
+ figure_list_2d: list[list[LayoutDOM | None]] = [
105
104
  [
106
105
  Div(text="<h3>区間アノテーションの長さの分布(ラベル名ごと)</h3>"),
107
106
  ]
@@ -165,11 +164,11 @@ def plot_annotation_duration_histogram_by_attribute( # noqa: PLR0915
165
164
  output_file: Path,
166
165
  *,
167
166
  time_unit: TimeUnit,
168
- bin_width: Optional[float] = None,
169
- prior_keys: Optional[list[AttributeValueKey]] = None,
167
+ bin_width: float | None = None,
168
+ prior_keys: list[AttributeValueKey] | None = None,
170
169
  exclude_empty_value: bool = False,
171
170
  arrange_bin_edge: bool = False,
172
- metadata: Optional[dict[str, Any]] = None,
171
+ metadata: dict[str, Any] | None = None,
173
172
  ) -> None:
174
173
  """
175
174
  属性値ごとの区間アノテーションの長さのヒストグラムを出力します。
@@ -198,7 +197,7 @@ def plot_annotation_duration_histogram_by_attribute( # noqa: PLR0915
198
197
  df = df / 60
199
198
  return df
200
199
 
201
- def get_histogram_range(df: pandas.DataFrame) -> Optional[tuple[float, float]]:
200
+ def get_histogram_range(df: pandas.DataFrame) -> tuple[float, float] | None:
202
201
  if arrange_bin_edge:
203
202
  return (
204
203
  df.min(numeric_only=True).min(),
@@ -224,7 +223,7 @@ def plot_annotation_duration_histogram_by_attribute( # noqa: PLR0915
224
223
  max_duration = df.max(numeric_only=True).max()
225
224
  x_axis_label = "区間アノテーションの長さ[分]" if time_unit == TimeUnit.MINUTE else "区間アノテーションの長さ[秒]"
226
225
 
227
- figure_list_2d: list[list[Optional[LayoutDOM]]] = [
226
+ figure_list_2d: list[list[LayoutDOM | None]] = [
228
227
  [
229
228
  Div(text="<h3>区間アノテーションの長さの分布(属性値ごと)</h3>"),
230
229
  ]
@@ -290,10 +289,10 @@ class VisualizeAnnotationDuration(CommandLine):
290
289
  output_dir: Path,
291
290
  time_unit: TimeUnit,
292
291
  *,
293
- bin_width: Optional[int] = None,
294
- project_id: Optional[str] = None,
295
- target_task_ids: Optional[Collection[str]] = None,
296
- task_query: Optional[TaskQuery] = None,
292
+ bin_width: int | None = None,
293
+ project_id: str | None = None,
294
+ target_task_ids: Collection[str] | None = None,
295
+ task_query: TaskQuery | None = None,
297
296
  exclude_empty_value: bool = False,
298
297
  arrange_bin_edge: bool = False,
299
298
  ) -> None:
@@ -301,8 +300,8 @@ class VisualizeAnnotationDuration(CommandLine):
301
300
  duration_by_attribute_html = output_dir / "annotation_duration_by_attribute.html"
302
301
 
303
302
  # 集計対象の属性を、選択肢系の属性にする
304
- annotation_specs: Optional[AnnotationSpecs] = None
305
- non_selective_attribute_name_keys: Optional[list[AttributeNameKey]] = None
303
+ annotation_specs: AnnotationSpecs | None = None
304
+ non_selective_attribute_name_keys: list[AttributeNameKey] | None = None
306
305
  if project_id is not None:
307
306
  annotation_specs = AnnotationSpecs(self.service, project_id, annotation_type=DefaultAnnotationType.RANGE.value)
308
307
  non_selective_attribute_name_keys = annotation_specs.non_selective_attribute_name_keys()
@@ -315,8 +314,8 @@ class VisualizeAnnotationDuration(CommandLine):
315
314
  task_query=task_query,
316
315
  )
317
316
 
318
- label_keys: Optional[list[str]] = None
319
- attribute_value_keys: Optional[list[AttributeValueKey]] = None
317
+ label_keys: list[str] | None = None
318
+ attribute_value_keys: list[AttributeValueKey] | None = None
320
319
  if annotation_specs is not None:
321
320
  label_keys = annotation_specs.label_keys()
322
321
  attribute_value_keys = annotation_specs.selective_attribute_value_keys()
@@ -359,7 +358,7 @@ class VisualizeAnnotationDuration(CommandLine):
359
358
  if not self.validate(args):
360
359
  sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
361
360
 
362
- project_id: Optional[str] = args.project_id
361
+ project_id: str | None = args.project_id
363
362
  if project_id is not None:
364
363
  super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
365
364
  project, _ = self.service.api.get_project(project_id)
@@ -487,7 +486,7 @@ def main(args: argparse.Namespace) -> None:
487
486
  VisualizeAnnotationDuration(service, facade, args).main()
488
487
 
489
488
 
490
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
489
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
491
490
  subcommand_name = "visualize_annotation_duration"
492
491
  subcommand_help = "ラベルごとまたは属性値ごとに区間アノテーションの長さをヒストグラムで可視化したファイルを出力します。"
493
492
  epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"