annofabcli 1.111.2__py3-none-any.whl → 1.113.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 (186) hide show
  1. annofabcli/__main__.py +1 -2
  2. annofabcli/annotation/annotation_query.py +10 -10
  3. annofabcli/annotation/change_annotation_attributes.py +10 -10
  4. annofabcli/annotation/change_annotation_attributes_per_annotation.py +4 -5
  5. annofabcli/annotation/change_annotation_properties.py +14 -14
  6. annofabcli/annotation/copy_annotation.py +6 -6
  7. annofabcli/annotation/create_classification_annotation.py +7 -7
  8. annofabcli/annotation/delete_annotation.py +9 -9
  9. annofabcli/annotation/download_annotation_zip.py +1 -3
  10. annofabcli/annotation/dump_annotation.py +8 -8
  11. annofabcli/annotation/import_annotation.py +13 -13
  12. annofabcli/annotation/list_annotation.py +9 -9
  13. annofabcli/annotation/list_annotation_count.py +2 -3
  14. annofabcli/annotation/merge_segmentation.py +6 -6
  15. annofabcli/annotation/remove_segmentation_overlap.py +5 -5
  16. annofabcli/annotation/restore_annotation.py +7 -7
  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 +3 -3
  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 +37 -38
  35. annofabcli/annotation_zip/list_polygon_annotation.py +390 -0
  36. annofabcli/annotation_zip/list_polyline_annotation.py +402 -0
  37. annofabcli/annotation_zip/list_range_annotation.py +25 -15
  38. annofabcli/annotation_zip/list_single_point_annotation.py +25 -34
  39. annofabcli/annotation_zip/subcommand_annotation_zip.py +7 -2
  40. annofabcli/annotation_zip/validate_annotation.py +8 -7
  41. annofabcli/comment/delete_comment.py +4 -6
  42. annofabcli/comment/download_comment_json.py +4 -6
  43. annofabcli/comment/list_all_comment.py +5 -6
  44. annofabcli/comment/list_comment.py +3 -4
  45. annofabcli/comment/put_comment.py +9 -10
  46. annofabcli/comment/put_comment_simply.py +5 -6
  47. annofabcli/comment/put_inspection_comment.py +1 -3
  48. annofabcli/comment/put_inspection_comment_simply.py +1 -3
  49. annofabcli/comment/put_onhold_comment.py +1 -3
  50. annofabcli/comment/put_onhold_comment_simply.py +1 -3
  51. annofabcli/comment/subcommand_comment.py +1 -3
  52. annofabcli/common/bokeh.py +4 -4
  53. annofabcli/common/cli.py +18 -17
  54. annofabcli/common/download.py +28 -29
  55. annofabcli/common/facade.py +37 -38
  56. annofabcli/common/image.py +14 -14
  57. annofabcli/common/utils.py +8 -8
  58. annofabcli/common/visualize.py +13 -13
  59. annofabcli/experimental/list_out_of_range_annotation_for_movie.py +3 -4
  60. annofabcli/experimental/subcommand_experimental.py +1 -3
  61. annofabcli/filesystem/draw_annotation.py +27 -27
  62. annofabcli/filesystem/filter_annotation.py +9 -10
  63. annofabcli/filesystem/mask_user_info.py +15 -15
  64. annofabcli/filesystem/merge_annotation.py +9 -9
  65. annofabcli/filesystem/subcommand_filesystem.py +1 -3
  66. annofabcli/input_data/copy_input_data.py +8 -9
  67. annofabcli/input_data/delete_input_data.py +3 -3
  68. annofabcli/input_data/delete_metadata_key_of_input_data.py +3 -5
  69. annofabcli/input_data/download_input_data_json.py +4 -6
  70. annofabcli/input_data/list_all_input_data.py +9 -9
  71. annofabcli/input_data/list_all_input_data_merged_task.py +5 -5
  72. annofabcli/input_data/list_input_data.py +5 -5
  73. annofabcli/input_data/put_input_data.py +6 -6
  74. annofabcli/input_data/put_input_data_with_zip.py +3 -4
  75. annofabcli/input_data/subcommand_input_data.py +1 -3
  76. annofabcli/input_data/update_input_data.py +6 -8
  77. annofabcli/input_data/update_metadata_of_input_data.py +3 -5
  78. annofabcli/instruction/copy_instruction.py +5 -6
  79. annofabcli/instruction/download_instruction.py +5 -6
  80. annofabcli/instruction/list_instruction_history.py +3 -3
  81. annofabcli/instruction/subcommand_instruction.py +1 -3
  82. annofabcli/instruction/upload_instruction.py +3 -4
  83. annofabcli/job/delete_job.py +2 -3
  84. annofabcli/job/list_job.py +5 -5
  85. annofabcli/job/list_last_job.py +4 -4
  86. annofabcli/job/subcommand_job.py +1 -3
  87. annofabcli/job/wait_job.py +4 -5
  88. annofabcli/my_account/get_my_account.py +2 -3
  89. annofabcli/my_account/subcommand_my_account.py +1 -3
  90. annofabcli/organization/list_organization.py +2 -3
  91. annofabcli/organization/subcommand_organization.py +1 -3
  92. annofabcli/organization_member/change_organization_member.py +3 -4
  93. annofabcli/organization_member/delete_organization_member.py +3 -4
  94. annofabcli/organization_member/invite_organization_member.py +1 -3
  95. annofabcli/organization_member/list_organization_member.py +3 -3
  96. annofabcli/organization_member/subcommand_organization_member.py +1 -3
  97. annofabcli/project/change_organization_of_project.py +4 -4
  98. annofabcli/project/change_project_status.py +4 -4
  99. annofabcli/project/copy_project.py +5 -5
  100. annofabcli/project/create_project.py +8 -8
  101. annofabcli/project/diff_projects.py +4 -5
  102. annofabcli/project/list_project.py +5 -5
  103. annofabcli/project/put_project.py +2 -3
  104. annofabcli/project/subcommand_project.py +1 -2
  105. annofabcli/project/update_configuration.py +4 -4
  106. annofabcli/project/update_project.py +6 -8
  107. annofabcli/project_member/change_project_members.py +8 -8
  108. annofabcli/project_member/copy_project_members.py +4 -4
  109. annofabcli/project_member/drop_project_members.py +2 -3
  110. annofabcli/project_member/invite_project_members.py +1 -3
  111. annofabcli/project_member/list_users.py +2 -3
  112. annofabcli/project_member/put_project_members.py +6 -6
  113. annofabcli/project_member/subcommand_project_member.py +1 -3
  114. annofabcli/stat_visualization/mask_visualization_dir.py +8 -9
  115. annofabcli/stat_visualization/merge_visualization_dir.py +6 -7
  116. annofabcli/stat_visualization/subcommand_stat_visualization.py +1 -2
  117. annofabcli/stat_visualization/summarize_whole_performance_csv.py +1 -2
  118. annofabcli/stat_visualization/write_graph.py +2 -3
  119. annofabcli/stat_visualization/write_performance_rating_csv.py +20 -27
  120. annofabcli/statistics/histogram.py +5 -6
  121. annofabcli/statistics/linegraph.py +13 -14
  122. annofabcli/statistics/list_annotation_area.py +38 -13
  123. annofabcli/statistics/list_annotation_attribute.py +9 -10
  124. annofabcli/statistics/list_annotation_attribute_filled_count.py +30 -31
  125. annofabcli/statistics/list_annotation_count.py +57 -58
  126. annofabcli/statistics/list_annotation_duration.py +33 -34
  127. annofabcli/statistics/list_video_duration.py +4 -5
  128. annofabcli/statistics/list_worktime.py +4 -4
  129. annofabcli/statistics/scatter.py +9 -8
  130. annofabcli/statistics/subcommand_statistics.py +1 -4
  131. annofabcli/statistics/summarize_task_count.py +4 -6
  132. annofabcli/statistics/summarize_task_count_by_task_id_group.py +2 -4
  133. annofabcli/statistics/summarize_task_count_by_user.py +1 -3
  134. annofabcli/statistics/visualization/dataframe/annotation_count.py +5 -4
  135. annofabcli/statistics/visualization/dataframe/annotation_duration.py +2 -3
  136. annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +15 -17
  137. annofabcli/statistics/visualization/dataframe/productivity_per_date.py +17 -19
  138. annofabcli/statistics/visualization/dataframe/project_performance.py +3 -12
  139. annofabcli/statistics/visualization/dataframe/task.py +11 -12
  140. annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +9 -10
  141. annofabcli/statistics/visualization/dataframe/user_performance.py +21 -19
  142. annofabcli/statistics/visualization/dataframe/whole_performance.py +3 -4
  143. annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +12 -14
  144. annofabcli/statistics/visualization/dataframe/worktime_per_date.py +11 -13
  145. annofabcli/statistics/visualization/filtering_query.py +7 -7
  146. annofabcli/statistics/visualization/project_dir.py +27 -14
  147. annofabcli/statistics/visualize_annotation_count.py +22 -23
  148. annofabcli/statistics/visualize_annotation_duration.py +21 -22
  149. annofabcli/statistics/visualize_statistics.py +36 -33
  150. annofabcli/statistics/visualize_video_duration.py +18 -20
  151. annofabcli/supplementary/delete_supplementary_data.py +5 -5
  152. annofabcli/supplementary/list_supplementary_data.py +4 -4
  153. annofabcli/supplementary/put_supplementary_data.py +9 -9
  154. annofabcli/supplementary/subcommand_supplementary.py +1 -3
  155. annofabcli/task/cancel_acceptance.py +16 -17
  156. annofabcli/task/change_operator.py +10 -12
  157. annofabcli/task/change_status_to_break.py +7 -9
  158. annofabcli/task/change_status_to_on_hold.py +10 -12
  159. annofabcli/task/complete_tasks.py +17 -18
  160. annofabcli/task/copy_tasks.py +3 -5
  161. annofabcli/task/delete_metadata_key_of_task.py +4 -6
  162. annofabcli/task/delete_tasks.py +7 -7
  163. annofabcli/task/download_task_json.py +4 -6
  164. annofabcli/task/list_all_tasks.py +8 -8
  165. annofabcli/task/list_all_tasks_added_task_history.py +14 -13
  166. annofabcli/task/list_tasks.py +7 -7
  167. annofabcli/task/list_tasks_added_task_history.py +10 -10
  168. annofabcli/task/put_tasks.py +5 -6
  169. annofabcli/task/put_tasks_by_count.py +2 -3
  170. annofabcli/task/reject_tasks.py +18 -20
  171. annofabcli/task/subcommand_task.py +1 -3
  172. annofabcli/task/update_metadata_of_task.py +5 -6
  173. annofabcli/task_history/download_task_history_json.py +4 -6
  174. annofabcli/task_history/list_all_task_history.py +6 -7
  175. annofabcli/task_history/list_task_history.py +4 -5
  176. annofabcli/task_history/subcommand_task_history.py +1 -3
  177. annofabcli/task_history_event/download_task_history_event_json.py +4 -6
  178. annofabcli/task_history_event/list_all_task_history_event.py +7 -7
  179. annofabcli/task_history_event/list_worktime.py +17 -16
  180. annofabcli/task_history_event/subcommand_task_history_event.py +1 -2
  181. {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/METADATA +9 -15
  182. annofabcli-1.113.0.dist-info/RECORD +231 -0
  183. {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/WHEEL +1 -1
  184. annofabcli-1.111.2.dist-info/RECORD +0 -228
  185. {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/entry_points.txt +0 -0
  186. {annofabcli-1.111.2.dist-info → annofabcli-1.113.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,7 +2,7 @@ import argparse
2
2
  import logging
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
- from typing import Any, Optional
5
+ from typing import Any
6
6
 
7
7
  import more_itertools
8
8
  import pandas
@@ -10,7 +10,7 @@ import requests
10
10
  from annofabapi.models import ProjectMemberRole, ProjectMemberStatus
11
11
  from dataclasses_json import DataClassJsonMixin
12
12
 
13
- import annofabcli
13
+ import annofabcli.common.cli
14
14
  from annofabcli.common.cli import ArgumentParser, CommandLine, build_annofabapi_resource_and_login
15
15
  from annofabcli.common.facade import AnnofabApiFacade
16
16
 
@@ -25,8 +25,8 @@ class Member(DataClassJsonMixin):
25
25
 
26
26
  user_id: str
27
27
  member_role: ProjectMemberRole
28
- sampling_inspection_rate: Optional[int] = None
29
- sampling_acceptance_rate: Optional[int] = None
28
+ sampling_inspection_rate: int | None = None
29
+ sampling_acceptance_rate: int | None = None
30
30
 
31
31
 
32
32
  class PutProjectMembers(CommandLine):
@@ -35,7 +35,7 @@ class PutProjectMembers(CommandLine):
35
35
  """
36
36
 
37
37
  @staticmethod
38
- def find_member(members: list[dict[str, Any]], user_id: str) -> Optional[dict[str, Any]]:
38
+ def find_member(members: list[dict[str, Any]], user_id: str) -> dict[str, Any] | None:
39
39
  member = more_itertools.first_true(members, default=None, pred=lambda e: e["user_id"] == user_id)
40
40
  return member
41
41
 
@@ -181,7 +181,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
181
181
  parser.set_defaults(subcommand_func=main)
182
182
 
183
183
 
184
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
184
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
185
185
  subcommand_name = "put"
186
186
  subcommand_help = "プロジェクトメンバを登録します。"
187
187
  epilog = "オーナーロールを持つユーザで実行してください。"
@@ -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.project_member.change_project_members
7
5
  import annofabcli.project_member.copy_project_members
@@ -23,7 +21,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
23
21
  annofabcli.project_member.put_project_members.add_parser(subparsers)
24
22
 
25
23
 
26
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
24
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
27
25
  subcommand_name = "project_member"
28
26
  subcommand_help = "プロジェクトメンバ関係のサブコマンド"
29
27
  description = "プロジェクトメンバ関係のサブコマンド"
@@ -5,7 +5,6 @@ import json
5
5
  import logging
6
6
  from dataclasses import dataclass
7
7
  from pathlib import Path
8
- from typing import Optional
9
8
 
10
9
  import pandas
11
10
  from annofabapi.models import TaskPhase
@@ -54,8 +53,8 @@ class ReplacementDict:
54
53
  def create_replacement_dict(
55
54
  df_user: pandas.DataFrame,
56
55
  *,
57
- not_masked_biography_set: Optional[set[str]],
58
- not_masked_user_id_set: Optional[set[str]],
56
+ not_masked_biography_set: set[str] | None,
57
+ not_masked_user_id_set: set[str] | None,
59
58
  ) -> ReplacementDict:
60
59
  """
61
60
  ユーザー情報を置換するためのインスタンスを生成します。
@@ -76,8 +75,8 @@ def create_replacement_dict(
76
75
 
77
76
  df2 = df_user.set_index("user_id")
78
77
  df3 = df2.loc[replacement_dict_for_user_id.keys()]
79
- replacement_dict_for_username = dict(zip(df3["username"], replacement_dict_for_user_id.values()))
80
- replacement_dict_for_account_id = dict(zip(df3["account_id"], replacement_dict_for_user_id.values()))
78
+ replacement_dict_for_username = dict(zip(df3["username"], replacement_dict_for_user_id.values(), strict=False))
79
+ replacement_dict_for_account_id = dict(zip(df3["account_id"], replacement_dict_for_user_id.values(), strict=False))
81
80
 
82
81
  replacement_dict_by_biography = create_replacement_dict_by_biography(df_user, not_masked_biography_set=not_masked_biography_set)
83
82
 
@@ -93,7 +92,7 @@ def write_line_graph(
93
92
  task_worktime_by_phase_user: TaskWorktimeByPhaseUser,
94
93
  output_project_dir: ProjectDir,
95
94
  *,
96
- user_id_list: Optional[list[str]] = None,
95
+ user_id_list: list[str] | None = None,
97
96
  minimal_output: bool = False,
98
97
  ) -> None:
99
98
  output_project_dir.write_cumulative_line_graph(
@@ -150,8 +149,8 @@ def mask_visualization_dir(
150
149
  project_dir: ProjectDir,
151
150
  output_project_dir: ProjectDir,
152
151
  *,
153
- not_masked_biography_set: Optional[set[str]] = None,
154
- not_masked_user_id_set: Optional[set[str]] = None,
152
+ not_masked_biography_set: set[str] | None = None,
153
+ not_masked_user_id_set: set[str] | None = None,
155
154
  minimal_output: bool = False,
156
155
  ) -> None:
157
156
  worktime_per_date = project_dir.read_worktime_per_date_user()
@@ -292,7 +291,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
292
291
  parser.set_defaults(subcommand_func=main)
293
292
 
294
293
 
295
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
294
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
296
295
  subcommand_name = "mask_user_info"
297
296
  subcommand_help = "`annofabcli statistics visualize` コマンドの出力結果のユーザ情報をマスクします。"
298
297
  description = "`annofabcli statistics visualize` コマンドの出力結果のユーザ情報をマスクします。マスク対象のファイルのみ出力します。"
@@ -5,7 +5,6 @@ import json
5
5
  import logging
6
6
  import sys
7
7
  from pathlib import Path
8
- from typing import Optional
9
8
 
10
9
  from annofabapi.models import TaskPhase
11
10
 
@@ -47,7 +46,7 @@ class WritingVisualizationFile:
47
46
  output_project_dir: ProjectDir,
48
47
  task_completion_criteria: TaskCompletionCriteria,
49
48
  *,
50
- user_id_list: Optional[list[str]] = None,
49
+ user_id_list: list[str] | None = None,
51
50
  minimal_output: bool = False,
52
51
  ) -> None:
53
52
  self.output_project_dir = output_project_dir
@@ -138,12 +137,12 @@ class WritingVisualizationFile:
138
137
 
139
138
 
140
139
  class MergingVisualizationFile:
141
- def __init__(self, project_dir_list: list[ProjectDir], *, custom_production_volume_list: Optional[list[ProductionVolumeColumn]] = None) -> None:
140
+ def __init__(self, project_dir_list: list[ProjectDir], *, custom_production_volume_list: list[ProductionVolumeColumn] | None = None) -> None:
142
141
  self.project_dir_list = project_dir_list
143
142
  self.custom_production_volume_list = custom_production_volume_list
144
143
 
145
144
  def merge_worktime_per_date(self) -> WorktimePerDate:
146
- merged_obj: Optional[WorktimePerDate] = None
145
+ merged_obj: WorktimePerDate | None = None
147
146
  for project_dir in self.project_dir_list:
148
147
  tmp_obj = project_dir.read_worktime_per_date_user()
149
148
 
@@ -191,8 +190,8 @@ def merge_visualization_dir( # pylint: disable=too-many-statements
191
190
  task_completion_criteria: TaskCompletionCriteria,
192
191
  output_project_dir: ProjectDir,
193
192
  *,
194
- custom_production_volume_list: Optional[list[ProductionVolumeColumn]] = None,
195
- user_id_list: Optional[list[str]] = None,
193
+ custom_production_volume_list: list[ProductionVolumeColumn] | None = None,
194
+ user_id_list: list[str] | None = None,
196
195
  minimal_output: bool = False,
197
196
  ) -> None:
198
197
  merging_obj = MergingVisualizationFile(project_dir_list, custom_production_volume_list=custom_production_volume_list)
@@ -316,7 +315,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
316
315
  parser.set_defaults(subcommand_func=main)
317
316
 
318
317
 
319
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
318
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
320
319
  subcommand_name = "merge"
321
320
  subcommand_help = "``annofabcli statistics visualize`` コマンドの出力結果をマージします。"
322
321
  description = "``annofabcli statistics visualize`` コマンドの出力結果をマージします。"
@@ -1,5 +1,4 @@
1
1
  import argparse
2
- from typing import Optional
3
2
 
4
3
  import annofabcli.stat_visualization.mask_visualization_dir
5
4
  import annofabcli.stat_visualization.merge_visualization_dir
@@ -19,7 +18,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
19
18
  annofabcli.stat_visualization.write_performance_rating_csv.add_parser(subparsers)
20
19
 
21
20
 
22
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
21
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
23
22
  subcommand_name = "stat_visualization"
24
23
  subcommand_help = "`annofabcli statistics visualization` コマンドの出力結果を加工するサブコマンド(アルファ版)"
25
24
  description = "`annofabcli statistics visualization` コマンドの出力結果を加工するサブコマンド(アルファ版)"
@@ -4,7 +4,6 @@ import argparse
4
4
  import json
5
5
  import logging
6
6
  from pathlib import Path
7
- from typing import Optional
8
7
 
9
8
  import annofabcli
10
9
  from annofabcli.common.cli import (
@@ -64,7 +63,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
64
63
  parser.set_defaults(subcommand_func=main)
65
64
 
66
65
 
67
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
66
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
68
67
  subcommand_name = "summarize_whole_performance_csv"
69
68
  subcommand_help = "``annofabcli statistics visualize`` コマンドの出力結果であるプロジェクトディレクトリから、プロジェクトごとの生産性や品質の一覧を出力します。。"
70
69
  parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help)
@@ -4,7 +4,6 @@ import argparse
4
4
  import json
5
5
  import logging
6
6
  from pathlib import Path
7
- from typing import Optional
8
7
 
9
8
  from annofabapi.models import TaskPhase
10
9
 
@@ -33,7 +32,7 @@ class WritingGraph:
33
32
  project_dir: ProjectDir,
34
33
  output_project_dir: ProjectDir,
35
34
  *,
36
- user_id_list: Optional[list[str]] = None,
35
+ user_id_list: list[str] | None = None,
37
36
  minimal_output: bool = False,
38
37
  ) -> None:
39
38
  self.project_dir = project_dir
@@ -185,7 +184,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
185
184
  parser.set_defaults(subcommand_func=main)
186
185
 
187
186
 
188
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
187
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
189
188
  subcommand_name = "write_graph"
190
189
  subcommand_help = "`annofabcli statistics visualize` コマンドの出力結果であるプロジェクトのディレクトリから、グラフを出力します。"
191
190
  parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help)
@@ -7,14 +7,13 @@ from collections.abc import Collection
7
7
  from dataclasses import dataclass
8
8
  from enum import Enum
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any, TypeAlias
11
11
 
12
12
  import numpy
13
13
  import pandas
14
14
  from annofabapi.models import TaskPhase
15
15
  from dataclasses_json import DataClassJsonMixin
16
16
  from pandas.api.typing import NAType
17
- from typing_extensions import TypeAlias
18
17
 
19
18
  import annofabcli
20
19
  from annofabcli.common.cli import CommandLineWithoutWebapi, get_json_from_args, get_list_from_args
@@ -54,9 +53,9 @@ class ResultDataframe:
54
53
  class ThresholdInfo(DataClassJsonMixin):
55
54
  """閾値の情報"""
56
55
 
57
- threshold_worktime: Optional[float] = None
56
+ threshold_worktime: float | None = None
58
57
  """作業時間の閾値。指定した時間以下の作業者は除外する。"""
59
- threshold_task_count: Optional[int] = None
58
+ threshold_task_count: int | None = None
60
59
  """作業したタスク数の閾値。作業したタスク数が指定した数以下作業者は除外する。"""
61
60
 
62
61
 
@@ -164,12 +163,12 @@ class CollectingPerformanceInfo:
164
163
  def __init__(
165
164
  self,
166
165
  *,
167
- productivity_indicator: Optional[ProductivityIndicator] = None,
168
- quality_indicator: Optional[QualityIndicator] = None,
169
- threshold_info: Optional[ThresholdInfo] = None,
170
- productivity_indicator_by_directory: Optional[ProductivityIndicatorByDirectory] = None,
171
- quality_indicator_by_directory: Optional[QualityIndicatorByDirectory] = None,
172
- threshold_infos_by_directory: Optional[ThresholdInfoSettings] = None,
166
+ productivity_indicator: ProductivityIndicator | None = None,
167
+ quality_indicator: QualityIndicator | None = None,
168
+ threshold_info: ThresholdInfo | None = None,
169
+ productivity_indicator_by_directory: ProductivityIndicatorByDirectory | None = None,
170
+ quality_indicator_by_directory: QualityIndicatorByDirectory | None = None,
171
+ threshold_infos_by_directory: ThresholdInfoSettings | None = None,
173
172
  ) -> None:
174
173
  self.quality_indicator = quality_indicator if quality_indicator is not None else QualityIndicator("pointed_out_inspection_comment_count/annotation_count")
175
174
  self.productivity_indicator = productivity_indicator if productivity_indicator is not None else ProductivityIndicator("actual_worktime_hour/annotation_count")
@@ -311,7 +310,7 @@ class CollectingPerformanceInfo:
311
310
  self,
312
311
  df_user: pandas.DataFrame,
313
312
  target_dir: Path,
314
- custom_production_volume_list_by_directory: Optional[dict[str, list[ProductionVolumeColumn]]],
313
+ custom_production_volume_list_by_directory: dict[str, list[ProductionVolumeColumn]] | None,
315
314
  ) -> ResultDataframe:
316
315
  """対象ディレクトリから、評価対象の指標になる情報を取得します。"""
317
316
  df_annotation_productivity = df_user
@@ -332,13 +331,7 @@ class CollectingPerformanceInfo:
332
331
  )
333
332
  project_dir_list.append(project_dir)
334
333
 
335
- try:
336
- project_info = project_dir.read_project_info()
337
- project_title = project_info.project_title
338
- except Exception:
339
- # 複数のプロジェクトをマージして生産性情報を出力した場合は、`project_info.json`は存在しないので、このブロックに入る
340
- logger.info(f"'{project_dir}'からプロジェクト情報を読み込むのに失敗しました。project_titleは空文字にします。", exc_info=True)
341
- project_title = ""
334
+ project_title = project_dir.get_project_title()
342
335
 
343
336
  try:
344
337
  user_performance = project_dir.read_user_performance()
@@ -395,7 +388,7 @@ def to_rank(series: pandas.Series) -> pandas.Series:
395
388
  return pandas.Series([numpy.nan] * len(series))
396
389
 
397
390
 
398
- def to_deviation(series: pandas.Series, threshold_deviation_user_count: Optional[int] = None) -> pandas.Series:
391
+ def to_deviation(series: pandas.Series, threshold_deviation_user_count: int | None = None) -> pandas.Series:
399
392
  if series.count() == 0 or (threshold_deviation_user_count is not None and series.count() <= threshold_deviation_user_count):
400
393
  return pandas.Series([numpy.nan] * len(series))
401
394
  else:
@@ -407,7 +400,7 @@ def to_deviation(series: pandas.Series, threshold_deviation_user_count: Optional
407
400
  return series.map(lambda x: 50 if not numpy.isnan(x) else numpy.nan)
408
401
 
409
402
 
410
- def create_rank_df(df: pandas.DataFrame, *, user_ids: Optional[Collection[str]] = None) -> pandas.DataFrame:
403
+ def create_rank_df(df: pandas.DataFrame, *, user_ids: Collection[str] | None = None) -> pandas.DataFrame:
411
404
  df_rank = df.copy()
412
405
  for col in df.columns[3:]:
413
406
  df_rank[col] = to_rank(df[col])
@@ -421,8 +414,8 @@ def create_rank_df(df: pandas.DataFrame, *, user_ids: Optional[Collection[str]]
421
414
  def create_deviation_df(
422
415
  df: pandas.DataFrame,
423
416
  *,
424
- threshold_deviation_user_count: Optional[int] = None,
425
- user_ids: Optional[Collection[str]] = None,
417
+ threshold_deviation_user_count: int | None = None,
418
+ user_ids: Collection[str] | None = None,
426
419
  ) -> pandas.DataFrame:
427
420
  """偏差値が格納されたDataFrameを返す。
428
421
 
@@ -505,7 +498,7 @@ def create_custom_production_volume_by_directory(cli_value: str) -> dict[str, li
505
498
 
506
499
 
507
500
  class WritingCsv:
508
- def __init__(self, threshold_deviation_user_count: Optional[int] = None, user_ids: Optional[Collection[str]] = None) -> None:
501
+ def __init__(self, threshold_deviation_user_count: int | None = None, user_ids: Collection[str] | None = None) -> None:
509
502
  self.threshold_deviation_user_count = threshold_deviation_user_count
510
503
  self.user_ids = user_ids
511
504
 
@@ -535,7 +528,7 @@ class WritingCsv:
535
528
 
536
529
 
537
530
  def create_productivity_indicator_by_directory(
538
- value: Optional[str],
531
+ value: str | None,
539
532
  ) -> ProductivityIndicatorByDirectory:
540
533
  """
541
534
  コマンドライン引数`--productivity_indicator_by_directory`から渡された文字列を、ProductivityIndicatorByDirectoryに変換する。
@@ -551,7 +544,7 @@ def create_productivity_indicator_by_directory(
551
544
 
552
545
 
553
546
  def create_quality_indicator_by_directory(
554
- value: Optional[str],
547
+ value: str | None,
555
548
  ) -> QualityIndicatorByDirectory:
556
549
  """
557
550
  コマンドライン引数`--quality_indicator_by_directory`から渡された文字列を、ProductivityIndicatorByDirectoryに変換する。
@@ -566,7 +559,7 @@ def create_quality_indicator_by_directory(
566
559
  return result
567
560
 
568
561
 
569
- def create_threshold_infos_per_project(value: Optional[str]) -> ThresholdInfoSettings:
562
+ def create_threshold_infos_per_project(value: str | None) -> ThresholdInfoSettings:
570
563
  if value is None:
571
564
  return {}
572
565
 
@@ -729,7 +722,7 @@ def main(args: argparse.Namespace) -> None:
729
722
  WritePerformanceRatingCsv(args).main()
730
723
 
731
724
 
732
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
725
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
733
726
  subcommand_name = "write_performance_rating_csv"
734
727
  subcommand_help = "プロジェクトごとユーザごとにパフォーマンスを評価できる複数のCSVを出力します。"
735
728
  description = "プロジェクトごとユーザごとにパフォーマンスを評価できる複数のCSVを出力します。"
@@ -1,5 +1,4 @@
1
1
  import logging
2
- from typing import Optional
3
2
 
4
3
  import numpy
5
4
  import pandas
@@ -45,8 +44,8 @@ def create_histogram_figure(
45
44
  *,
46
45
  x_axis_label: str,
47
46
  y_axis_label: str,
48
- title: Optional[str] = None,
49
- sub_title: Optional[str] = None,
47
+ title: str | None = None,
48
+ sub_title: str | None = None,
50
49
  width: int = 400,
51
50
  height: int = 300,
52
51
  ) -> figure:
@@ -64,11 +63,11 @@ def create_histogram_figure(
64
63
  height: グラフの高さ
65
64
  """
66
65
  df_histogram = pandas.DataFrame({"frequency": hist, "left": bin_edges[:-1], "right": bin_edges[1:]})
67
- df_histogram["interval"] = [f"{left:.1f} to {right:.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"])]
68
- df_histogram["width"] = [f"{(right - left):.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"])]
66
+ df_histogram["interval"] = [f"{left:.1f} to {right:.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"], strict=False)]
67
+ df_histogram["width"] = [f"{(right - left):.1f}" for left, right in zip(df_histogram["left"], df_histogram["right"], strict=False)]
69
68
 
70
69
  source = ColumnDataSource(df_histogram)
71
- fig = figure(
70
+ fig = figure( # type: ignore[call-arg]
72
71
  width=width,
73
72
  height=height,
74
73
  x_axis_label=x_axis_label,
@@ -7,10 +7,9 @@ from __future__ import annotations
7
7
  import copy
8
8
  import logging
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  import bokeh
13
- import bokeh.layouts
14
13
  import bokeh.palettes
15
14
  import pandas
16
15
  from bokeh.models import CrosshairTool, CustomJS, DataRange1d, HoverTool, LinearAxis
@@ -50,7 +49,7 @@ class LineGraph:
50
49
  """マーカーのサイズ"""
51
50
 
52
51
  @staticmethod
53
- def _create_hover_tool(tool_tip_items: Optional[list[str]] = None) -> HoverTool:
52
+ def _create_hover_tool(tool_tip_items: list[str] | None = None) -> HoverTool:
54
53
  """
55
54
  HoverTool用のオブジェクトを生成する。
56
55
  """
@@ -69,10 +68,10 @@ class LineGraph:
69
68
  y_axis_label: str,
70
69
  width: int = 1200,
71
70
  height: int = 1000,
72
- tooltip_columns: Optional[list[str]] = None,
71
+ tooltip_columns: list[str] | None = None,
73
72
  **figure_kwargs, # noqa: ANN003
74
73
  ) -> None:
75
- fig = figure(
74
+ fig = figure( # type: ignore[call-arg]
76
75
  title=title,
77
76
  x_axis_label=x_axis_label,
78
77
  y_axis_label=y_axis_label,
@@ -101,8 +100,8 @@ class LineGraph:
101
100
  self,
102
101
  axis_label: str,
103
102
  *,
104
- secondary_y_axis_range: Optional[DataRange1d] = None,
105
- primary_y_axis_range: Optional[DataRange1d] = None,
103
+ secondary_y_axis_range: DataRange1d | None = None,
104
+ primary_y_axis_range: DataRange1d | None = None,
106
105
  ):
107
106
  """
108
107
  第2のY軸を追加する。
@@ -124,12 +123,12 @@ class LineGraph:
124
123
  "right",
125
124
  )
126
125
  if secondary_y_axis_range is not None:
127
- self.figure.extra_y_ranges = {self._SECONDARY_Y_RANGE_NAME: secondary_y_axis_range} # type: ignore[assignment]
126
+ self.figure.extra_y_ranges = {self._SECONDARY_Y_RANGE_NAME: secondary_y_axis_range}
128
127
  else:
129
- self.figure.extra_y_ranges = {self._SECONDARY_Y_RANGE_NAME: DataRange1d()} # type: ignore[assignment]
128
+ self.figure.extra_y_ranges = {self._SECONDARY_Y_RANGE_NAME: DataRange1d()}
130
129
 
131
130
  if primary_y_axis_range is not None:
132
- self.figure.y_range = primary_y_axis_range # type: ignore[assignment]
131
+ self.figure.y_range = primary_y_axis_range
133
132
 
134
133
  self.exists_secondary_y_axis = True
135
134
 
@@ -140,7 +139,7 @@ class LineGraph:
140
139
  y_column: str,
141
140
  *,
142
141
  legend_label: str,
143
- color: Optional[Any] = None, # noqa: ANN401
142
+ color: Any | None = None, # noqa: ANN401
144
143
  is_secondary_y_axis: bool = False,
145
144
  **kwargs, # noqa: ANN003
146
145
  ) -> tuple[GlyphRenderer, GlyphRenderer]:
@@ -189,10 +188,10 @@ class LineGraph:
189
188
  y_column: str,
190
189
  *,
191
190
  legend_label: str,
192
- color: Optional[Any] = None, # noqa: ANN401
191
+ color: Any | None = None, # noqa: ANN401
193
192
  is_secondary_y_axis: bool = False,
194
193
  **kwargs, # noqa: ANN003
195
- ) -> tuple[GlyphRenderer, GlyphRenderer]:
194
+ ) -> GlyphRenderer:
196
195
  """
197
196
  移動平均用の折れ線を追加する
198
197
  """
@@ -317,7 +316,7 @@ class LineGraph:
317
316
  """
318
317
  code = code % (self.DEFAULT_LINE_WIDTH)
319
318
  options = [(username, f"{user_id}:{username}") for user_id, username in users]
320
- multi_choice = MultiChoice(options=options, title="Find User:", width=300)
319
+ multi_choice = MultiChoice(options=options, title="Find User:", width=300) # type: ignore[arg-type]
321
320
  multi_choice.js_on_change(
322
321
  "value",
323
322
  CustomJS(code=code, args=args),
@@ -8,7 +8,7 @@ import zipfile
8
8
  from collections.abc import Collection, Iterator
9
9
  from dataclasses import dataclass
10
10
  from pathlib import Path
11
- from typing import Any, Optional
11
+ from typing import Any
12
12
 
13
13
  import numpy
14
14
  import pandas
@@ -20,8 +20,8 @@ from annofabapi.parser import (
20
20
  )
21
21
  from annofabapi.segmentation import read_binary_image
22
22
  from dataclasses_json import DataClassJsonMixin
23
+ from shapely.geometry import Polygon
23
24
 
24
- import annofabcli
25
25
  import annofabcli.common.cli
26
26
  from annofabcli.common.cli import (
27
27
  COMMAND_LINE_ERROR_STATUS_CODE,
@@ -64,7 +64,7 @@ class AnnotationAreaInfo(DataClassJsonMixin):
64
64
  input_data_id: str
65
65
  input_data_name: str
66
66
 
67
- updated_datetime: Optional[str]
67
+ updated_datetime: str | None
68
68
  """アノテーションJSONに格納されているアノテーションの更新日時"""
69
69
 
70
70
  label: str
@@ -83,6 +83,30 @@ def calculate_segmentation_area(outer_file: Path) -> int:
83
83
  return int(numpy.count_nonzero(nd_array))
84
84
 
85
85
 
86
+ def calculate_polygon_area(points: list[dict[str, int]]) -> int:
87
+ """
88
+ Shapelyを使ってポリゴンの面積を計算する
89
+
90
+ Args:
91
+ points: ポリゴンの頂点座標のリスト [{"x": int, "y": int}, ...]
92
+
93
+ Returns:
94
+ int: ポリゴンの面積(ピクセル単位)
95
+ """
96
+ if len(points) < 3:
97
+ # Annofabはポリゴンとポリラインの区別がないため、点が3個未満の場合はあり得る
98
+ # したがって、assertなどではなく、面積0を返す
99
+ return 0
100
+
101
+ # Shapelyのポリゴンオブジェクトを作成
102
+ coords = [(point["x"], point["y"]) for point in points]
103
+ polygon = Polygon(coords)
104
+
105
+ # 面積を計算(自己交差がある場合も適切に処理される)
106
+ area = polygon.area
107
+ return round(area)
108
+
109
+
86
110
  def get_annotation_area_info_list(parser: SimpleAnnotationParser, simple_annotation: dict[str, Any]) -> list[AnnotationAreaInfo]:
87
111
  result = []
88
112
  for detail in simple_annotation["details"]:
@@ -90,7 +114,8 @@ def get_annotation_area_info_list(parser: SimpleAnnotationParser, simple_annotat
90
114
  annotation_area = calculate_segmentation_area(parser.open_outer_file(detail["data"]["data_uri"]))
91
115
  elif detail["data"]["_type"] == "BoundingBox":
92
116
  annotation_area = calculate_bounding_box_area(detail["data"])
93
-
117
+ elif detail["data"]["_type"] == "Points":
118
+ annotation_area = calculate_polygon_area(detail["data"]["points"])
94
119
  else:
95
120
  continue
96
121
 
@@ -116,8 +141,8 @@ def get_annotation_area_info_list(parser: SimpleAnnotationParser, simple_annotat
116
141
  def get_annotation_area_info_list_from_annotation_path(
117
142
  annotation_path: Path,
118
143
  *,
119
- target_task_ids: Optional[Collection[str]] = None,
120
- task_query: Optional[TaskQuery] = None,
144
+ target_task_ids: Collection[str] | None = None,
145
+ task_query: TaskQuery | None = None,
121
146
  ) -> list[AnnotationAreaInfo]:
122
147
  annotation_area_list = []
123
148
  target_task_ids = set(target_task_ids) if target_task_ids is not None else None
@@ -166,8 +191,8 @@ def print_annotation_area(
166
191
  output_file: Path,
167
192
  output_format: FormatArgument,
168
193
  *,
169
- target_task_ids: Optional[Collection[str]] = None,
170
- task_query: Optional[TaskQuery] = None,
194
+ target_task_ids: Collection[str] | None = None,
195
+ task_query: TaskQuery | None = None,
171
196
  ) -> None:
172
197
  annotation_area_list = get_annotation_area_info_list_from_annotation_path(
173
198
  annotation_path,
@@ -175,7 +200,7 @@ def print_annotation_area(
175
200
  task_query=task_query,
176
201
  )
177
202
 
178
- logger.info(f"{len(annotation_area_list)} 件のタスクに含まれる塗りつぶしアノテーションのピクセル数情報を出力します。")
203
+ logger.info(f"{len(annotation_area_list)} 件のタスクに含まれる塗りつぶし、矩形、ポリゴンアノテーションの面積情報を出力します。")
179
204
 
180
205
  if output_format == FormatArgument.CSV:
181
206
  df = create_df(annotation_area_list)
@@ -211,7 +236,7 @@ class ListAnnotationArea(CommandLine):
211
236
  if not self.validate(args):
212
237
  sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
213
238
 
214
- project_id: Optional[str] = args.project_id
239
+ project_id: str | None = args.project_id
215
240
  if project_id is not None:
216
241
  super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
217
242
  project, _ = self.service.api.get_project(project_id)
@@ -228,7 +253,7 @@ class ListAnnotationArea(CommandLine):
228
253
 
229
254
  downloading_obj = DownloadingFile(self.service)
230
255
 
231
- def download_and_print_annotation_area(project_id: str, temp_dir: Path, *, is_latest: bool, annotation_path: Optional[Path]) -> None:
256
+ def download_and_print_annotation_area(project_id: str, temp_dir: Path, *, is_latest: bool, annotation_path: Path | None) -> None:
232
257
  if annotation_path is None:
233
258
  annotation_path = downloading_obj.download_annotation_zip_to_dir(
234
259
  project_id,
@@ -309,9 +334,9 @@ def main(args: argparse.Namespace) -> None:
309
334
  ListAnnotationArea(service, facade, args).main()
310
335
 
311
336
 
312
- def add_parser(subparsers: Optional[argparse._SubParsersAction] = None) -> argparse.ArgumentParser:
337
+ def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
313
338
  subcommand_name = "list_annotation_area"
314
- subcommand_help = "塗りつぶしアノテーションまたは矩形アノテーションの面積を出力します。"
339
+ subcommand_help = "塗りつぶし、矩形、ポリゴンアノテーションの面積を出力します。"
315
340
  epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
316
341
  parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog)
317
342
  parse_args(parser)