annofabcli 1.102.1__py3-none-any.whl → 1.104.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 (136) hide show
  1. annofabcli/__main__.py +1 -1
  2. annofabcli/annotation/annotation_query.py +9 -29
  3. annofabcli/annotation/change_annotation_attributes.py +6 -14
  4. annofabcli/annotation/change_annotation_properties.py +5 -12
  5. annofabcli/annotation/copy_annotation.py +4 -10
  6. annofabcli/annotation/delete_annotation.py +10 -26
  7. annofabcli/annotation/dump_annotation.py +1 -4
  8. annofabcli/annotation/import_annotation.py +15 -39
  9. annofabcli/annotation/list_annotation.py +1 -4
  10. annofabcli/annotation/merge_segmentation.py +5 -15
  11. annofabcli/annotation/remove_segmentation_overlap.py +8 -29
  12. annofabcli/annotation/restore_annotation.py +3 -9
  13. annofabcli/annotation_specs/add_attribute_restriction.py +2 -8
  14. annofabcli/annotation_specs/attribute_restriction.py +2 -10
  15. annofabcli/annotation_specs/export_annotation_specs.py +1 -3
  16. annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -10
  17. annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +4 -10
  18. annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +1 -3
  19. annofabcli/annotation_specs/list_annotation_specs_attribute.py +7 -18
  20. annofabcli/annotation_specs/list_annotation_specs_choice.py +3 -8
  21. annofabcli/annotation_specs/list_annotation_specs_history.py +0 -1
  22. annofabcli/annotation_specs/list_annotation_specs_label.py +3 -8
  23. annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +4 -9
  24. annofabcli/annotation_specs/list_attribute_restriction.py +3 -9
  25. annofabcli/annotation_specs/put_label_color.py +1 -6
  26. annofabcli/comment/delete_comment.py +3 -9
  27. annofabcli/comment/list_all_comment.py +15 -5
  28. annofabcli/comment/list_comment.py +46 -7
  29. annofabcli/comment/put_comment.py +4 -13
  30. annofabcli/comment/put_comment_simply.py +2 -6
  31. annofabcli/comment/put_inspection_comment.py +2 -6
  32. annofabcli/comment/put_inspection_comment_simply.py +3 -6
  33. annofabcli/comment/put_onhold_comment.py +2 -6
  34. annofabcli/comment/put_onhold_comment_simply.py +2 -4
  35. annofabcli/common/cli.py +5 -43
  36. annofabcli/common/download.py +8 -25
  37. annofabcli/common/image.py +3 -7
  38. annofabcli/common/utils.py +2 -4
  39. annofabcli/common/visualize.py +2 -4
  40. annofabcli/filesystem/draw_annotation.py +6 -18
  41. annofabcli/filesystem/filter_annotation.py +7 -24
  42. annofabcli/filesystem/mask_user_info.py +2 -5
  43. annofabcli/filesystem/merge_annotation.py +2 -6
  44. annofabcli/input_data/change_input_data_name.py +3 -7
  45. annofabcli/input_data/copy_input_data.py +6 -14
  46. annofabcli/input_data/delete_input_data.py +7 -24
  47. annofabcli/input_data/delete_metadata_key_of_input_data.py +5 -16
  48. annofabcli/input_data/list_all_input_data.py +5 -14
  49. annofabcli/input_data/list_all_input_data_merged_task.py +8 -23
  50. annofabcli/input_data/list_input_data.py +5 -16
  51. annofabcli/input_data/put_input_data.py +7 -19
  52. annofabcli/input_data/update_metadata_of_input_data.py +6 -14
  53. annofabcli/instruction/list_instruction_history.py +0 -1
  54. annofabcli/instruction/upload_instruction.py +4 -7
  55. annofabcli/job/list_job.py +2 -3
  56. annofabcli/job/list_last_job.py +1 -3
  57. annofabcli/organization/list_organization.py +0 -1
  58. annofabcli/organization_member/change_organization_member.py +1 -3
  59. annofabcli/organization_member/delete_organization_member.py +2 -6
  60. annofabcli/organization_member/invite_organization_member.py +1 -3
  61. annofabcli/organization_member/list_organization_member.py +0 -1
  62. annofabcli/project/change_organization_of_project.py +257 -0
  63. annofabcli/project/change_project_status.py +2 -2
  64. annofabcli/project/copy_project.py +2 -7
  65. annofabcli/project/diff_projects.py +4 -16
  66. annofabcli/project/list_project.py +0 -1
  67. annofabcli/project/put_project.py +2 -6
  68. annofabcli/project/subcommand_project.py +2 -0
  69. annofabcli/project_member/change_project_members.py +1 -1
  70. annofabcli/project_member/copy_project_members.py +2 -7
  71. annofabcli/project_member/drop_project_members.py +1 -3
  72. annofabcli/project_member/invite_project_members.py +2 -4
  73. annofabcli/project_member/list_users.py +0 -1
  74. annofabcli/project_member/put_project_members.py +4 -12
  75. annofabcli/stat_visualization/mask_visualization_dir.py +6 -16
  76. annofabcli/stat_visualization/merge_visualization_dir.py +7 -19
  77. annofabcli/stat_visualization/summarize_whole_performance_csv.py +3 -7
  78. annofabcli/stat_visualization/write_graph.py +5 -15
  79. annofabcli/stat_visualization/write_performance_rating_csv.py +4 -12
  80. annofabcli/statistics/list_annotation_area.py +3 -7
  81. annofabcli/statistics/list_annotation_attribute.py +6 -15
  82. annofabcli/statistics/list_annotation_attribute_filled_count.py +9 -23
  83. annofabcli/statistics/list_annotation_count.py +18 -44
  84. annofabcli/statistics/list_annotation_duration.py +14 -40
  85. annofabcli/statistics/list_video_duration.py +2 -3
  86. annofabcli/statistics/list_worktime.py +0 -1
  87. annofabcli/statistics/scatter.py +3 -9
  88. annofabcli/statistics/summarize_task_count.py +7 -12
  89. annofabcli/statistics/summarize_task_count_by_task_id_group.py +3 -11
  90. annofabcli/statistics/summarize_task_count_by_user.py +1 -5
  91. annofabcli/statistics/visualization/dataframe/annotation_count.py +2 -4
  92. annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +6 -12
  93. annofabcli/statistics/visualization/dataframe/productivity_per_date.py +10 -22
  94. annofabcli/statistics/visualization/dataframe/project_performance.py +1 -3
  95. annofabcli/statistics/visualization/dataframe/task.py +2 -5
  96. annofabcli/statistics/visualization/dataframe/task_history.py +1 -1
  97. annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +6 -20
  98. annofabcli/statistics/visualization/dataframe/user_performance.py +29 -88
  99. annofabcli/statistics/visualization/dataframe/whole_performance.py +6 -12
  100. annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +17 -49
  101. annofabcli/statistics/visualization/dataframe/worktime_per_date.py +4 -10
  102. annofabcli/statistics/visualization/filtering_query.py +2 -6
  103. annofabcli/statistics/visualization/project_dir.py +9 -26
  104. annofabcli/statistics/visualization/visualization_source_files.py +3 -10
  105. annofabcli/statistics/visualize_annotation_count.py +9 -23
  106. annofabcli/statistics/visualize_annotation_duration.py +5 -15
  107. annofabcli/statistics/visualize_statistics.py +18 -53
  108. annofabcli/statistics/visualize_video_duration.py +8 -19
  109. annofabcli/supplementary/delete_supplementary_data.py +7 -23
  110. annofabcli/supplementary/list_supplementary_data.py +1 -1
  111. annofabcli/supplementary/put_supplementary_data.py +5 -15
  112. annofabcli/task/cancel_acceptance.py +3 -4
  113. annofabcli/task/change_operator.py +3 -11
  114. annofabcli/task/change_status_to_break.py +1 -1
  115. annofabcli/task/change_status_to_on_hold.py +5 -18
  116. annofabcli/task/complete_tasks.py +8 -25
  117. annofabcli/task/copy_tasks.py +2 -3
  118. annofabcli/task/delete_metadata_key_of_task.py +2 -6
  119. annofabcli/task/delete_tasks.py +8 -26
  120. annofabcli/task/list_all_tasks.py +2 -4
  121. annofabcli/task/list_tasks.py +3 -7
  122. annofabcli/task/list_tasks_added_task_history.py +7 -21
  123. annofabcli/task/put_tasks.py +2 -3
  124. annofabcli/task/put_tasks_by_count.py +3 -7
  125. annofabcli/task/reject_tasks.py +7 -19
  126. annofabcli/task/update_metadata_of_task.py +2 -2
  127. annofabcli/task_history/list_all_task_history.py +2 -5
  128. annofabcli/task_history/list_task_history.py +0 -1
  129. annofabcli/task_history_event/list_all_task_history_event.py +4 -11
  130. annofabcli/task_history_event/list_worktime.py +4 -14
  131. {annofabcli-1.102.1.dist-info → annofabcli-1.104.0.dist-info}/METADATA +1 -1
  132. annofabcli-1.104.0.dist-info/RECORD +215 -0
  133. annofabcli-1.102.1.dist-info/RECORD +0 -214
  134. {annofabcli-1.102.1.dist-info → annofabcli-1.104.0.dist-info}/WHEEL +0 -0
  135. {annofabcli-1.102.1.dist-info → annofabcli-1.104.0.dist-info}/entry_points.txt +0 -0
  136. {annofabcli-1.102.1.dist-info → annofabcli-1.104.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import logging
5
+ from collections import Counter
5
6
  from typing import Any, Optional
6
7
 
7
8
  import pandas
@@ -19,14 +20,48 @@ from annofabcli.common.visualize import AddProps
19
20
  logger = logging.getLogger(__name__)
20
21
 
21
22
 
23
+ def create_reply_counter(comments: list[dict[str, Any]]) -> Counter[tuple[str, str, str]]:
24
+ """
25
+ 返信コメントの回数を取得するcounterを生成します。
26
+
27
+ """
28
+ root_comment_id_counter = Counter((c["task_id"], c["input_data_id"], c["comment_node"]["root_comment_id"]) for c in comments if c["comment_node"]["_type"] == "Reply")
29
+ return root_comment_id_counter
30
+
31
+
32
+ def create_empty_df_comment() -> pandas.DataFrame:
33
+ return pandas.DataFrame(
34
+ columns=[
35
+ "project_id",
36
+ "task_id",
37
+ "input_data_id",
38
+ "comment_id",
39
+ "phase",
40
+ "phase_stage",
41
+ "comment_type",
42
+ "account_id",
43
+ "user_id",
44
+ "username",
45
+ "phrases",
46
+ "comment",
47
+ "created_datetime",
48
+ "updated_datetime",
49
+ "reply_count",
50
+ ]
51
+ )
52
+
53
+
22
54
  class ListingComments(CommandLine):
23
- def get_comments(self, project_id: str, task_id: str, input_data_id: str): # noqa: ANN201
55
+ def get_comments(self, project_id: str, task_id: str, input_data_id: str) -> list[dict[str, Any]]:
24
56
  comments, _ = self.service.api.get_comments(project_id, task_id, input_data_id, query_params={"v": "2"})
57
+ # 返信回数を算出する
58
+ reply_counter = create_reply_counter(comments)
59
+ for c in comments:
60
+ key = (c["task_id"], c["input_data_id"], c["comment_id"])
61
+ c["reply_count"] = reply_counter.get(key, 0)
25
62
  return comments
26
63
 
27
- def get_comment_list(
28
- self, project_id: str, task_id_list: list[str], *, comment_type: Optional[CommentType], exclude_reply: bool
29
- ) -> list[dict[str, Any]]:
64
+ def get_comment_list(self, project_id: str, task_id_list: list[str], *, comment_type: Optional[CommentType], exclude_reply: bool) -> list[dict[str, Any]]:
30
65
  all_comments: list[Comment] = []
31
66
 
32
67
  for task_id in task_id_list:
@@ -51,10 +86,11 @@ class ListingComments(CommandLine):
51
86
  all_comments.extend(comments)
52
87
 
53
88
  except requests.HTTPError:
54
- logger.warning(f"タスク task_id = {task_id} のコメントを取得できませんでした。", exc_info=True)
89
+ logger.warning(f"task_id='{task_id}'のタスクのコメントの取得に失敗しました。", exc_info=True)
55
90
 
56
91
  visualize = AddProps(self.service, project_id)
57
92
  all_comments = [visualize.add_properties_to_comment(e) for e in all_comments]
93
+
58
94
  return all_comments
59
95
 
60
96
  def main(self) -> None:
@@ -68,7 +104,11 @@ class ListingComments(CommandLine):
68
104
 
69
105
  output_format = FormatArgument(args.format)
70
106
  if output_format == FormatArgument.CSV:
71
- df = pandas.json_normalize(comment_list)
107
+ if len(comment_list) > 0:
108
+ df = pandas.json_normalize(comment_list)
109
+ else:
110
+ df = create_empty_df_comment()
111
+
72
112
  print_csv(df, output=args.output)
73
113
  else:
74
114
  print_according_to_format(comment_list, output_format, output=args.output)
@@ -107,7 +147,6 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
107
147
  default=FormatArgument.CSV,
108
148
  )
109
149
  argument_parser.add_output()
110
- argument_parser.add_csv_format()
111
150
 
112
151
  parser.set_defaults(subcommand_func=main)
113
152
 
@@ -131,9 +131,7 @@ class PutCommentMain(CommandLineWithConfirm):
131
131
  return False
132
132
 
133
133
  if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
134
- logger.warning(
135
- f"task_id='{task_id}' :: タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。 :: task_status='{task['status']}'" # noqa: E501
136
- )
134
+ logger.warning(f"task_id='{task_id}' :: タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。 :: task_status='{task['status']}'")
137
135
  return False
138
136
  return True
139
137
 
@@ -184,9 +182,7 @@ class PutCommentMain(CommandLineWithConfirm):
184
182
  request_body = self._create_request_body(task=changed_task, input_data_id=input_data_id, comments=comments)
185
183
  self.service.api.batch_update_comments(self.project_id, task_id, input_data_id, request_body=request_body)
186
184
  added_comments_count += 1
187
- logger.debug(
188
- f"{logging_prefix} :: task_id='{task_id}', input_data_id='{input_data_id}' :: {len(comments)}件のコメントを付与しました。"
189
- )
185
+ logger.debug(f"{logging_prefix} :: task_id='{task_id}', input_data_id='{input_data_id}' :: {len(comments)}件のコメントを付与しました。")
190
186
  except Exception: # pylint: disable=broad-except
191
187
  logger.warning(
192
188
  f"{logging_prefix} :: task_id='{task_id}', input_data_id='{input_data_id}' :: コメントの付与に失敗しました。",
@@ -214,10 +210,7 @@ class PutCommentMain(CommandLineWithConfirm):
214
210
  parallelism: Optional[int] = None,
215
211
  ) -> None:
216
212
  comments_count = sum(len(e) for e in comments_for_task_list.values())
217
- logger.info(
218
- f"{self.comment_type_name}を付与するタスク数: {len(comments_for_task_list)}, "
219
- f"{self.comment_type_name}を付与する入力データ数: {comments_count}"
220
- )
213
+ logger.info(f"{self.comment_type_name}を付与するタスク数: {len(comments_for_task_list)}, {self.comment_type_name}を付与する入力データ数: {comments_count}")
221
214
 
222
215
  if parallelism is not None:
223
216
  with multiprocessing.Pool(parallelism) as pool:
@@ -294,8 +287,6 @@ def convert_cli_comments(dict_comments: dict[str, Any], *, comment_type: Comment
294
287
 
295
288
  result = {}
296
289
  for task_id, comments_for_task in dict_comments.items():
297
- sub_result = {
298
- input_data_id: [func_convert(e) for e in comments] for input_data_id, comments in comments_for_task.items() if len(comments) > 0
299
- }
290
+ sub_result = {input_data_id: [func_convert(e) for e in comments] for input_data_id, comments in comments_for_task.items() if len(comments) > 0}
300
291
  result.update({task_id: sub_result})
301
292
  return result
@@ -95,9 +95,7 @@ class PutCommentSimplyMain(CommandLineWithConfirm):
95
95
  return False
96
96
 
97
97
  if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
98
- logger.warning(
99
- f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。(task_status='{task['status']}')" # noqa: E501
100
- )
98
+ logger.warning(f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。(task_status='{task['status']}')")
101
99
  return False
102
100
  return True
103
101
 
@@ -147,9 +145,7 @@ class PutCommentSimplyMain(CommandLineWithConfirm):
147
145
  logger.debug(f"{logging_prefix} :: task_id='{task_id}' のタスクにコメントを付与しました。")
148
146
  return True # noqa: TRY300
149
147
  except Exception: # pylint: disable=broad-except
150
- logger.warning(
151
- f"{logging_prefix} :: task_id='{task_id}', input_data_id='{input_data_id}' :: コメントの付与に失敗しました。", exc_info=True
152
- )
148
+ logger.warning(f"{logging_prefix} :: task_id='{task_id}', input_data_id='{input_data_id}' :: コメントの付与に失敗しました。", exc_info=True)
153
149
  return False
154
150
  finally:
155
151
  self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
@@ -70,18 +70,14 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
70
70
  parser.add_argument(
71
71
  "--json",
72
72
  type=str,
73
- help=(
74
- "付与する検査コメントの内容をJSON形式で指定してください。"
75
- "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n"
76
- f"(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"
77
- ),
73
+ help=(f"付与する検査コメントの内容をJSON形式で指定してください。``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"),
78
74
  )
79
75
 
80
76
  parser.add_argument(
81
77
  "--parallelism",
82
78
  type=int,
83
79
  choices=PARALLELISM_CHOICES,
84
- help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。", # noqa: E501
80
+ help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。",
85
81
  )
86
82
 
87
83
  parser.set_defaults(subcommand_func=main)
@@ -94,10 +94,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
94
94
  type=str,
95
95
  nargs="+",
96
96
  required=True,
97
- help=(
98
- "検査コメントを付与するタスクのtask_idを指定してください。\n"
99
- "``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"
100
- ),
97
+ help=("検査コメントを付与するタスクのtask_idを指定してください。\n``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"),
101
98
  )
102
99
 
103
100
  parser.add_argument(
@@ -125,14 +122,14 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
125
122
  "--custom_project_type",
126
123
  type=str,
127
124
  choices=[e.value for e in CustomProjectType],
128
- help="[BETA] ビルトインのエディタプラグインを使用していないカスタムプロジェクトの種類を指定します。カスタムプロジェクトに対して、検査コメントの位置を指定しない場合は必須です。\n", # noqa: E501
125
+ help="[BETA] ビルトインのエディタプラグインを使用していないカスタムプロジェクトの種類を指定します。カスタムプロジェクトに対して、検査コメントの位置を指定しない場合は必須です。\n",
129
126
  )
130
127
 
131
128
  parser.add_argument(
132
129
  "--parallelism",
133
130
  type=int,
134
131
  choices=PARALLELISM_CHOICES,
135
- help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。", # noqa: E501
132
+ help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。",
136
133
  )
137
134
 
138
135
  parser.set_defaults(subcommand_func=main)
@@ -71,18 +71,14 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
71
71
  parser.add_argument(
72
72
  "--json",
73
73
  type=str,
74
- help=(
75
- "付与する保留コメントの情報をJSON形式で指定してください。"
76
- "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n"
77
- f"(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"
78
- ),
74
+ help=(f"付与する保留コメントの情報をJSON形式で指定してください。``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n(ex) ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"),
79
75
  )
80
76
 
81
77
  parser.add_argument(
82
78
  "--parallelism",
83
79
  type=int,
84
80
  choices=PARALLELISM_CHOICES,
85
- help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。", # noqa: E501
81
+ help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。",
86
82
  )
87
83
 
88
84
  parser.set_defaults(subcommand_func=main)
@@ -67,9 +67,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
67
67
  type=str,
68
68
  nargs="+",
69
69
  required=True,
70
- help=(
71
- "コメントを付与するタスクのtask_idを指定してください。\n``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"
72
- ),
70
+ help=("コメントを付与するタスクのtask_idを指定してください。\n``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"),
73
71
  )
74
72
 
75
73
  parser.add_argument(
@@ -83,7 +81,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
83
81
  "--parallelism",
84
82
  type=int,
85
83
  choices=PARALLELISM_CHOICES,
86
- help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。", # noqa: E501
84
+ help="使用するプロセス数(並列度)を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。",
87
85
  )
88
86
 
89
87
  parser.set_defaults(subcommand_func=main)
annofabcli/common/cli.py CHANGED
@@ -25,7 +25,6 @@ from annofabcli.common.exceptions import AnnofabCliException, AuthenticationErro
25
25
  from annofabcli.common.facade import AnnofabApiFacade
26
26
  from annofabcli.common.typing import InputDataSize
27
27
  from annofabcli.common.utils import (
28
- DEFAULT_CSV_FORMAT,
29
28
  get_file_scheme_path,
30
29
  print_according_to_format,
31
30
  print_csv,
@@ -130,9 +129,7 @@ def add_parser(
130
129
 
131
130
  group.add_argument("--disable_log", action="store_true", help="ログを無効にします。")
132
131
 
133
- group.add_argument(
134
- "--debug", action="store_true", help="HTTPリクエストの内容やレスポンスのステータスコードなど、デバッグ用のログが出力されます。"
135
- )
132
+ group.add_argument("--debug", action="store_true", help="HTTPリクエストの内容やレスポンスのステータスコードなど、デバッグ用のログが出力されます。")
136
133
 
137
134
  return parent_parser
138
135
 
@@ -150,7 +147,7 @@ def add_parser(
150
147
  )
151
148
  parser.set_defaults(command_help=parser.print_help)
152
149
 
153
- # 引数グループに"global optional group"がある場合は、"--help"オプションをデフォルトの"optional"グループから、"global optional arguments"グループに移動する # noqa: E501
150
+ # 引数グループに"global optional group"がある場合は、"--help"オプションをデフォルトの"optional"グループから、"global optional arguments"グループに移動する
154
151
  # https://ja.stackoverflow.com/a/57313/19524
155
152
  global_optional_argument_group = first_true(parser._action_groups, pred=lambda e: e.title == GLOBAL_OPTIONAL_ARGUMENTS_TITLE) # noqa: SLF001
156
153
  if global_optional_argument_group is not None:
@@ -188,20 +185,6 @@ def get_list_from_args(str_list: Optional[list[str]] = None) -> list[str]:
188
185
  return str_list
189
186
 
190
187
 
191
- def get_csv_format_from_args(target: Optional[str] = None) -> dict[str, Any]:
192
- """
193
- コマンドライン引数の値から csv_format を取得する。
194
- Default: {"encoding": "utf_8_sig", "index": False}
195
-
196
- """
197
- csv_format = DEFAULT_CSV_FORMAT.copy()
198
- if target is not None:
199
- arg_csv_format = get_json_from_args(target)
200
- csv_format.update(arg_csv_format)
201
-
202
- return csv_format
203
-
204
-
205
188
  def get_json_from_args(target: Optional[str] = None) -> Any: # noqa: ANN401
206
189
  """
207
190
  JSON形式をPythonオブジェクトに変換する。
@@ -420,9 +403,7 @@ class ArgumentParser:
420
403
  '--input_data_id` 引数を追加
421
404
  """
422
405
  if help_message is None:
423
- help_message = (
424
- "対象の入力データのinput_data_idを指定します。 ``file://`` を先頭に付けると、input_data_idの一覧が記載されたファイルを指定できます。"
425
- )
406
+ help_message = "対象の入力データのinput_data_idを指定します。 ``file://`` を先頭に付けると、input_data_idの一覧が記載されたファイルを指定できます。"
426
407
 
427
408
  self.parser.add_argument("-i", "--input_data_id", type=str, required=required, nargs="+", help=help_message)
428
409
 
@@ -435,19 +416,6 @@ class ArgumentParser:
435
416
 
436
417
  self.parser.add_argument("-f", "--format", type=str, choices=[e.value for e in choices], default=default.value, help=help_message)
437
418
 
438
- def add_csv_format(self, help_message: Optional[str] = None) -> None:
439
- """
440
- '--csv_format` 引数を追加
441
- """
442
- if help_message is None:
443
- help_message = (
444
- "CSVのフォーマットをJSON形式で指定します。 ``--format`` が ``csv`` でないときは、このオプションは無視されます。\n"
445
- "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n"
446
- "指定した値は ``pandas.DataFrame.to_csv`` の引数として渡されます。"
447
- )
448
-
449
- self.parser.add_argument("--csv_format", type=str, help=help_message)
450
-
451
419
  def add_output(self, *, required: bool = False, help_message: Optional[str] = None) -> None:
452
420
  """
453
421
  '--output` 引数を追加
@@ -510,9 +478,6 @@ class CommandLineWithoutWebapi:
510
478
  #: 出力先
511
479
  output: Optional[str] = None
512
480
 
513
- #: CSVのフォーマット
514
- csv_format: Optional[dict[str, Any]] = None
515
-
516
481
  #: 出力フォーマット
517
482
  str_format: Optional[str] = None
518
483
 
@@ -530,9 +495,6 @@ class CommandLineWithoutWebapi:
530
495
  if hasattr(args, "query"):
531
496
  self.query = args.query
532
497
 
533
- if hasattr(args, "csv_format"):
534
- self.csv_format = get_csv_format_from_args(args.csv_format)
535
-
536
498
  if hasattr(args, "output"):
537
499
  self.output = args.output
538
500
 
@@ -590,10 +552,10 @@ class CommandLineWithoutWebapi:
590
552
  return True
591
553
 
592
554
  def print_csv(self, df: pandas.DataFrame) -> None:
593
- print_csv(df, output=self.output, to_csv_kwargs=self.csv_format)
555
+ print_csv(df, output=self.output)
594
556
 
595
557
  def print_according_to_format(self, target: Any) -> None: # noqa: ANN401
596
- print_according_to_format(target, format=FormatArgument(self.str_format), output=self.output, csv_format=self.csv_format)
558
+ print_according_to_format(target, format=FormatArgument(self.str_format), output=self.output)
597
559
 
598
560
 
599
561
  class PrettyHelpFormatter(argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter):
@@ -110,10 +110,7 @@ class DownloadingFile:
110
110
  except requests.HTTPError as e:
111
111
  # すでにジョブが進行中の場合は、無視する
112
112
  if e.response.status_code == requests.codes.conflict:
113
- logger.warning(
114
- f"別のバックグラウンドジョブが既に実行されているので、アノテーションZIPの更新処理を実行できません。"
115
- f" :: error_message: {_get_annofab_error_message(e)}"
116
- )
113
+ logger.warning(f"別のバックグラウンドジョブが既に実行されているので、アノテーションZIPの更新処理を実行できません。 :: error_message: {_get_annofab_error_message(e)}")
117
114
  else:
118
115
  raise e # noqa: TRY201
119
116
 
@@ -160,9 +157,7 @@ class DownloadingFile:
160
157
  except requests.HTTPError as e:
161
158
  # すでにジョブが進行中の場合は、無視する
162
159
  if e.response.status_code == requests.codes.conflict:
163
- logger.warning(
164
- f"別のバックグラウンドジョブが既に実行されているので、更新処理を無視します。 :: error_message: {_get_annofab_error_message(e)}"
165
- )
160
+ logger.warning(f"別のバックグラウンドジョブが既に実行されているので、更新処理を無視します。 :: error_message: {_get_annofab_error_message(e)}")
166
161
  else:
167
162
  raise e # noqa: TRY201
168
163
 
@@ -179,9 +174,7 @@ class DownloadingFile:
179
174
  partial_func = partial(self.download_task_json, project_id, dest_path, is_latest=is_latest, wait_options=wait_options)
180
175
  await loop.run_in_executor(None, partial_func)
181
176
 
182
- def download_task_json(
183
- self, project_id: str, dest_path: Union[str, Path], *, is_latest: bool = False, wait_options: Optional[WaitOptions] = None
184
- ) -> None:
177
+ def download_task_json(self, project_id: str, dest_path: Union[str, Path], *, is_latest: bool = False, wait_options: Optional[WaitOptions] = None) -> None:
185
178
  if is_latest:
186
179
  self.wait_until_updated_task_json(project_id, wait_options)
187
180
  self.service.wrapper.download_project_tasks_url(project_id, dest_path)
@@ -205,9 +198,7 @@ class DownloadingFile:
205
198
  except requests.HTTPError as e:
206
199
  # すでにジョブが進行中の場合は、無視する
207
200
  if e.response.status_code == requests.codes.conflict:
208
- logger.warning(
209
- f"別のバックグラウンドジョブが既に実行されているので、更新処理を無視します。 :: error_message={_get_annofab_error_message(e)}"
210
- )
201
+ logger.warning(f"別のバックグラウンドジョブが既に実行されているので、更新処理を無視します。 :: error_message={_get_annofab_error_message(e)}")
211
202
  else:
212
203
  raise e # noqa: TRY201
213
204
 
@@ -237,9 +228,7 @@ class DownloadingFile:
237
228
  self.service.wrapper.download_project_task_histories_url(project_id, dest_path)
238
229
  except requests.HTTPError as e:
239
230
  if e.response.status_code == requests.codes.not_found:
240
- raise DownloadingFileNotFoundError(
241
- f"project_id='{project_id}'のプロジェクトに、タスク履歴全件ファイルが存在しないため、ダウンロードできませんでした。"
242
- ) from e
231
+ raise DownloadingFileNotFoundError(f"project_id='{project_id}'のプロジェクトに、タスク履歴全件ファイルが存在しないため、ダウンロードできませんでした。") from e
243
232
  raise e # noqa: TRY201
244
233
 
245
234
  def download_task_history_event_json(self, project_id: str, dest_path: Union[str, Path]) -> None:
@@ -257,9 +246,7 @@ class DownloadingFile:
257
246
  self.service.wrapper.download_project_task_history_events_url(project_id, dest_path)
258
247
  except requests.HTTPError as e:
259
248
  if e.response.status_code == requests.codes.not_found:
260
- raise DownloadingFileNotFoundError(
261
- f"project_id='{project_id}'のプロジェクトに、タスク履歴イベント全件ファイルが存在しないため、ダウンロードできませんでした。"
262
- ) from e
249
+ raise DownloadingFileNotFoundError(f"project_id='{project_id}'のプロジェクトに、タスク履歴イベント全件ファイルが存在しないため、ダウンロードできませんでした。") from e
263
250
  raise e # noqa: TRY201
264
251
 
265
252
  async def download_task_history_event_json_with_async(self, project_id: str, dest_path: Union[str, Path]) -> None:
@@ -294,9 +281,7 @@ class DownloadingFile:
294
281
  self.service.wrapper.download_project_inspections_url(project_id, dest_path)
295
282
  except requests.HTTPError as e:
296
283
  if e.response.status_code == requests.codes.not_found:
297
- raise DownloadingFileNotFoundError(
298
- f"project_id='{project_id}'のプロジェクトに、検査コメント全件ファイルが存在しないため、ダウンロードできませんでした。"
299
- ) from e
284
+ raise DownloadingFileNotFoundError(f"project_id='{project_id}'のプロジェクトに、検査コメント全件ファイルが存在しないため、ダウンロードできませんでした。") from e
300
285
  raise e # noqa: TRY201
301
286
 
302
287
  async def download_comment_json_with_async(self, project_id: str, dest_path: Union[str, Path]) -> None:
@@ -321,7 +306,5 @@ class DownloadingFile:
321
306
  self.service.wrapper.download_project_comments_url(project_id, dest_path)
322
307
  except requests.HTTPError as e:
323
308
  if e.response.status_code == requests.codes.not_found:
324
- raise DownloadingFileNotFoundError(
325
- f"project_id='{project_id}'のプロジェクトに、コメント全件ファイルが存在しないため、ダウンロードできませんでした。"
326
- ) from e
309
+ raise DownloadingFileNotFoundError(f"project_id='{project_id}'のプロジェクトに、コメント全件ファイルが存在しないため、ダウンロードできませんでした。") from e
327
310
  raise e # noqa: TRY201
@@ -225,9 +225,7 @@ def write_annotation_grayscale_image(
225
225
  annotation_list = list(reversed(simple_annotation["details"]))
226
226
 
227
227
  # 描画対象のアノテーションを絞り込む
228
- annotation_list = [
229
- e for e in annotation_list if e["data"] is not None and e["data"]["_type"] in ["BoundingBox", "Points", "SegmentationV2", "Segmentation"]
230
- ]
228
+ annotation_list = [e for e in annotation_list if e["data"] is not None and e["data"]["_type"] in ["BoundingBox", "Points", "SegmentationV2", "Segmentation"]]
231
229
 
232
230
  if len(annotation_list) > 255:
233
231
  # channel depthは8bitなので、255個のアノテーションまでしか描画できない
@@ -297,7 +295,7 @@ def write_annotation_images_from_path(
297
295
  Returns:
298
296
  True: アノテーション情報の画像化に成功した。False: アノテーション情報の画像化に失敗した。
299
297
 
300
- """ # noqa: E501
298
+ """
301
299
 
302
300
  def _get_image_size(input_data_id: str) -> Optional[InputDataSize]:
303
301
  def _get_image_size_from_system_metadata(arg_input_data: dict[str, Any]): # noqa: ANN202
@@ -306,9 +304,7 @@ def write_annotation_images_from_path(
306
304
  if original_resolution is not None and (original_resolution.get("width") is not None and original_resolution.get("height") is not None):
307
305
  return original_resolution.get("width"), original_resolution.get("height")
308
306
  else:
309
- logger.warning(
310
- f"input_data_id='{input_data_id}'のプロパティ`system_metadata.original_resolution`には画像サイズが設定されていません。"
311
- )
307
+ logger.warning(f"input_data_id='{input_data_id}'のプロパティ`system_metadata.original_resolution`には画像サイズが設定されていません。")
312
308
  return None
313
309
 
314
310
  if image_size is not None:
@@ -110,7 +110,6 @@ def print_according_to_format(
110
110
  target: Any, # noqa: ANN401
111
111
  format: FormatArgument, # noqa: A002
112
112
  output: Optional[Union[str, Path]] = None,
113
- csv_format: Optional[dict[str, Any]] = None,
114
113
  ) -> None:
115
114
  """
116
115
  コマンドライン引数 ``--format`` の値にしたがって、内容を出力する。
@@ -119,7 +118,6 @@ def print_according_to_format(
119
118
  target: 出力する内容
120
119
  format: 出力フォーマット
121
120
  output: 出力先(オプション)
122
- csv_format: CSVのフォーマット(オプション)
123
121
 
124
122
 
125
123
  """
@@ -132,7 +130,7 @@ def print_according_to_format(
132
130
 
133
131
  elif format == FormatArgument.CSV:
134
132
  df = pandas.DataFrame(target)
135
- print_csv(df, output=output, to_csv_kwargs=csv_format)
133
+ print_csv(df, output=output)
136
134
 
137
135
  elif format == FormatArgument.TASK_ID_LIST:
138
136
  task_id_list = [e["task_id"] for e in target]
@@ -223,7 +221,7 @@ def get_cache_dir() -> Path:
223
221
 
224
222
  """
225
223
  cache_home_dir = os.environ.get("XDG_CACHE_HOME")
226
- if cache_home_dir is None: # noqa: SIM108
224
+ if cache_home_dir is None:
227
225
  cache_home_dir_path = Path.home() / ".cache"
228
226
  else:
229
227
  cache_home_dir_path = Path(cache_home_dir)
@@ -47,9 +47,7 @@ class AddProps:
47
47
  アノテーション仕様に関する情報をインスタンス変数に格納します。
48
48
  """
49
49
  annotation_specs, _ = self.service.api.get_annotation_specs(self.project_id, query_params={"v": "2"})
50
- self._specs_labels = convert_annotation_specs_labels_v2_to_v1(
51
- labels_v2=annotation_specs["labels"], additionals_v2=annotation_specs["additionals"]
52
- )
50
+ self._specs_labels = convert_annotation_specs_labels_v2_to_v1(labels_v2=annotation_specs["labels"], additionals_v2=annotation_specs["additionals"])
53
51
  self._specs_inspection_phrases = annotation_specs["inspection_phrases"]
54
52
 
55
53
  @property
@@ -345,7 +343,7 @@ class AddProps:
345
343
  task["number_of_rejections_by_inspection"] = get_number_of_rejections(histories, TaskPhase.INSPECTION)
346
344
  task["number_of_rejections_by_acceptance"] = get_number_of_rejections(histories, TaskPhase.ACCEPTANCE)
347
345
 
348
- # number_of_rejectionsは非推奨なプロパティで、number_of_rejections_by_inspection/number_of_rejections_by_acceptanceと矛盾する場合があるので、削除する # noqa: E501
346
+ # number_of_rejectionsは非推奨なプロパティで、number_of_rejections_by_inspection/number_of_rejections_by_acceptanceと矛盾する場合があるので、削除する
349
347
  task.pop("number_of_rejections", None)
350
348
 
351
349
  task["input_data_count"] = len(task["input_data_id_list"])
@@ -266,18 +266,14 @@ def draw_annotation_all( # noqa: ANN201, PLR0913
266
266
 
267
267
  try:
268
268
  drawing.main(parser, image_file=image_file, output_file=output_file, image_size=default_image_size)
269
- logger.debug(
270
- f"{success_count + 1}件目: {output_file!s} を出力しました。image_file={image_file}, アノテーションJSON={parser.json_file_path}"
271
- )
269
+ logger.debug(f"{success_count + 1}件目: {output_file!s} を出力しました。image_file={image_file}, アノテーションJSON={parser.json_file_path}")
272
270
  success_count += 1
273
271
  except Exception: # pylint: disable=broad-except
274
272
  logger.warning(f"{parser.json_file_path} のアノテーションの描画に失敗しました。", exc_info=True)
275
273
 
276
274
  logger.info(f"{success_count} / {total_count} 件、アノテーションを描画しました。")
277
275
 
278
- new_label_color_dict = {
279
- label_name: ImageColor.getrgb(color) if isinstance(color, str) else color for label_name, color in drawing.label_color_dict.items()
280
- }
276
+ new_label_color_dict = {label_name: ImageColor.getrgb(color) if isinstance(color, str) else color for label_name, color in drawing.label_color_dict.items()}
281
277
  logger.info(f"label_color={json.dumps(new_label_color_dict, ensure_ascii=False)}")
282
278
 
283
279
 
@@ -367,9 +363,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
367
363
  help="Annofabからダウンロードしたアノテーションzip、またはzipを展開したディレクトリを指定してください。",
368
364
  )
369
365
 
370
- parser.add_argument(
371
- "--image_dir", type=Path, help="画像が存在するディレクトリを指定してください。\n'--input_data_id_csv'を指定したときは必須です。"
372
- )
366
+ parser.add_argument("--image_dir", type=Path, help="画像が存在するディレクトリを指定してください。\n'--input_data_id_csv'を指定したときは必須です。")
373
367
 
374
368
  parser.add_argument(
375
369
  "--input_data_id_csv",
@@ -380,17 +374,13 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
380
374
  "詳細は https://annofab-cli.readthedocs.io/ja/latest/command_reference/filesystem/draw_annotation.html を参照してください。",
381
375
  )
382
376
 
383
- parser.add_argument(
384
- "--default_image_size", type=str, help="デフォルトの画像サイズ。 ``--input_data_id_csv`` を指定しないときは必須です。\n(例) 1280x720"
385
- )
377
+ parser.add_argument("--default_image_size", type=str, help="デフォルトの画像サイズ。 ``--input_data_id_csv`` を指定しないときは必須です。\n(例) 1280x720")
386
378
 
387
379
  LABEL_COLOR_SAMPLE = {"dog": [255, 128, 64], "cat": "blue"} # noqa: N806
388
380
  parser.add_argument(
389
381
  "--label_color",
390
382
  type=str,
391
- help="label_nameとRGBの関係をJSON形式で指定します。\n"
392
- f"(例) ``{json.dumps(LABEL_COLOR_SAMPLE)}``\n"
393
- "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
383
+ help=f"label_nameとRGBの関係をJSON形式で指定します。\n(例) ``{json.dumps(LABEL_COLOR_SAMPLE)}``\n``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
394
384
  )
395
385
 
396
386
  parser.add_argument(
@@ -425,9 +415,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
425
415
  parser.add_argument(
426
416
  "--drawing_options",
427
417
  type=str,
428
- help="描画オプションをJSON形式で指定します。\n"
429
- f"(例) ``{json.dumps(DRAWING_OPTIONS_SAMPLE)}``\n\n"
430
- "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
418
+ help=f"描画オプションをJSON形式で指定します。\n(例) ``{json.dumps(DRAWING_OPTIONS_SAMPLE)}``\n\n``file://`` を先頭に付けると、JSON形式のファイルを指定できます。",
431
419
  )
432
420
 
433
421
  argument_parser.add_task_id(