annofabcli 1.102.1__py3-none-any.whl → 1.103.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 (134) hide show
  1. annofabcli/annotation/annotation_query.py +9 -29
  2. annofabcli/annotation/change_annotation_attributes.py +6 -14
  3. annofabcli/annotation/change_annotation_properties.py +5 -12
  4. annofabcli/annotation/copy_annotation.py +4 -10
  5. annofabcli/annotation/delete_annotation.py +10 -26
  6. annofabcli/annotation/dump_annotation.py +1 -4
  7. annofabcli/annotation/import_annotation.py +16 -40
  8. annofabcli/annotation/list_annotation.py +1 -4
  9. annofabcli/annotation/merge_segmentation.py +5 -15
  10. annofabcli/annotation/remove_segmentation_overlap.py +8 -29
  11. annofabcli/annotation/restore_annotation.py +3 -9
  12. annofabcli/annotation_specs/add_attribute_restriction.py +2 -8
  13. annofabcli/annotation_specs/attribute_restriction.py +2 -10
  14. annofabcli/annotation_specs/export_annotation_specs.py +1 -3
  15. annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -10
  16. annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +4 -10
  17. annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +1 -3
  18. annofabcli/annotation_specs/list_annotation_specs_attribute.py +7 -18
  19. annofabcli/annotation_specs/list_annotation_specs_choice.py +3 -8
  20. annofabcli/annotation_specs/list_annotation_specs_history.py +0 -1
  21. annofabcli/annotation_specs/list_annotation_specs_label.py +3 -8
  22. annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +4 -9
  23. annofabcli/annotation_specs/list_attribute_restriction.py +3 -9
  24. annofabcli/annotation_specs/put_label_color.py +1 -6
  25. annofabcli/comment/delete_comment.py +3 -9
  26. annofabcli/comment/list_all_comment.py +2 -4
  27. annofabcli/comment/list_comment.py +1 -4
  28. annofabcli/comment/put_comment.py +4 -13
  29. annofabcli/comment/put_comment_simply.py +2 -6
  30. annofabcli/comment/put_inspection_comment.py +2 -6
  31. annofabcli/comment/put_inspection_comment_simply.py +3 -6
  32. annofabcli/comment/put_onhold_comment.py +2 -6
  33. annofabcli/comment/put_onhold_comment_simply.py +2 -4
  34. annofabcli/common/cli.py +5 -43
  35. annofabcli/common/download.py +8 -25
  36. annofabcli/common/image.py +5 -9
  37. annofabcli/common/utils.py +1 -3
  38. annofabcli/common/visualize.py +2 -4
  39. annofabcli/filesystem/draw_annotation.py +8 -20
  40. annofabcli/filesystem/filter_annotation.py +7 -24
  41. annofabcli/filesystem/mask_user_info.py +3 -6
  42. annofabcli/filesystem/merge_annotation.py +2 -6
  43. annofabcli/input_data/change_input_data_name.py +3 -7
  44. annofabcli/input_data/copy_input_data.py +6 -14
  45. annofabcli/input_data/delete_input_data.py +7 -24
  46. annofabcli/input_data/delete_metadata_key_of_input_data.py +5 -16
  47. annofabcli/input_data/list_all_input_data.py +5 -14
  48. annofabcli/input_data/list_all_input_data_merged_task.py +8 -23
  49. annofabcli/input_data/list_input_data.py +5 -16
  50. annofabcli/input_data/put_input_data.py +7 -19
  51. annofabcli/input_data/update_metadata_of_input_data.py +6 -14
  52. annofabcli/instruction/list_instruction_history.py +0 -1
  53. annofabcli/instruction/upload_instruction.py +1 -4
  54. annofabcli/job/list_job.py +1 -2
  55. annofabcli/job/list_last_job.py +1 -3
  56. annofabcli/organization/list_organization.py +0 -1
  57. annofabcli/organization_member/change_organization_member.py +1 -3
  58. annofabcli/organization_member/delete_organization_member.py +2 -6
  59. annofabcli/organization_member/invite_organization_member.py +1 -3
  60. annofabcli/organization_member/list_organization_member.py +0 -1
  61. annofabcli/project/change_organization_of_project.py +257 -0
  62. annofabcli/project/change_project_status.py +2 -2
  63. annofabcli/project/copy_project.py +2 -7
  64. annofabcli/project/diff_projects.py +4 -16
  65. annofabcli/project/list_project.py +0 -1
  66. annofabcli/project/put_project.py +2 -6
  67. annofabcli/project/subcommand_project.py +2 -0
  68. annofabcli/project_member/change_project_members.py +2 -2
  69. annofabcli/project_member/copy_project_members.py +2 -7
  70. annofabcli/project_member/drop_project_members.py +1 -3
  71. annofabcli/project_member/invite_project_members.py +1 -3
  72. annofabcli/project_member/list_users.py +0 -1
  73. annofabcli/project_member/put_project_members.py +4 -12
  74. annofabcli/stat_visualization/mask_visualization_dir.py +6 -16
  75. annofabcli/stat_visualization/merge_visualization_dir.py +6 -18
  76. annofabcli/stat_visualization/summarize_whole_performance_csv.py +3 -7
  77. annofabcli/stat_visualization/write_graph.py +5 -15
  78. annofabcli/stat_visualization/write_performance_rating_csv.py +4 -12
  79. annofabcli/statistics/list_annotation_area.py +3 -7
  80. annofabcli/statistics/list_annotation_attribute.py +6 -15
  81. annofabcli/statistics/list_annotation_attribute_filled_count.py +9 -23
  82. annofabcli/statistics/list_annotation_count.py +18 -44
  83. annofabcli/statistics/list_annotation_duration.py +14 -40
  84. annofabcli/statistics/list_video_duration.py +2 -3
  85. annofabcli/statistics/list_worktime.py +0 -1
  86. annofabcli/statistics/scatter.py +3 -9
  87. annofabcli/statistics/summarize_task_count.py +7 -12
  88. annofabcli/statistics/summarize_task_count_by_task_id_group.py +3 -11
  89. annofabcli/statistics/summarize_task_count_by_user.py +1 -5
  90. annofabcli/statistics/visualization/dataframe/annotation_count.py +1 -3
  91. annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +3 -9
  92. annofabcli/statistics/visualization/dataframe/productivity_per_date.py +11 -23
  93. annofabcli/statistics/visualization/dataframe/project_performance.py +1 -3
  94. annofabcli/statistics/visualization/dataframe/task.py +2 -5
  95. annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +6 -20
  96. annofabcli/statistics/visualization/dataframe/user_performance.py +29 -88
  97. annofabcli/statistics/visualization/dataframe/whole_performance.py +4 -10
  98. annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +17 -49
  99. annofabcli/statistics/visualization/dataframe/worktime_per_date.py +3 -9
  100. annofabcli/statistics/visualization/filtering_query.py +2 -6
  101. annofabcli/statistics/visualization/project_dir.py +9 -26
  102. annofabcli/statistics/visualization/visualization_source_files.py +3 -10
  103. annofabcli/statistics/visualize_annotation_count.py +7 -21
  104. annofabcli/statistics/visualize_annotation_duration.py +7 -17
  105. annofabcli/statistics/visualize_statistics.py +17 -52
  106. annofabcli/statistics/visualize_video_duration.py +8 -19
  107. annofabcli/supplementary/delete_supplementary_data.py +7 -23
  108. annofabcli/supplementary/list_supplementary_data.py +1 -1
  109. annofabcli/supplementary/put_supplementary_data.py +5 -15
  110. annofabcli/task/cancel_acceptance.py +3 -4
  111. annofabcli/task/change_operator.py +3 -11
  112. annofabcli/task/change_status_to_break.py +1 -1
  113. annofabcli/task/change_status_to_on_hold.py +5 -18
  114. annofabcli/task/complete_tasks.py +8 -25
  115. annofabcli/task/copy_tasks.py +2 -3
  116. annofabcli/task/delete_metadata_key_of_task.py +2 -6
  117. annofabcli/task/delete_tasks.py +7 -25
  118. annofabcli/task/list_all_tasks.py +2 -4
  119. annofabcli/task/list_tasks.py +2 -6
  120. annofabcli/task/list_tasks_added_task_history.py +7 -21
  121. annofabcli/task/put_tasks.py +2 -3
  122. annofabcli/task/put_tasks_by_count.py +3 -7
  123. annofabcli/task/reject_tasks.py +7 -19
  124. annofabcli/task/update_metadata_of_task.py +1 -1
  125. annofabcli/task_history/list_all_task_history.py +2 -5
  126. annofabcli/task_history/list_task_history.py +0 -1
  127. annofabcli/task_history_event/list_all_task_history_event.py +4 -11
  128. annofabcli/task_history_event/list_worktime.py +4 -14
  129. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/METADATA +1 -1
  130. annofabcli-1.103.0.dist-info/RECORD +215 -0
  131. annofabcli-1.102.1.dist-info/RECORD +0 -214
  132. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/WHEEL +0 -0
  133. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/entry_points.txt +0 -0
  134. {annofabcli-1.102.1.dist-info → annofabcli-1.103.0.dist-info}/licenses/LICENSE +0 -0
@@ -66,15 +66,9 @@ class WritingGraph:
66
66
  inspector_per_date_obj = InspectorProductivityPerDate.from_df_wrapper(task_worktime_list)
67
67
  acceptor_per_date_obj = AcceptorProductivityPerDate.from_df_wrapper(task_worktime_list)
68
68
 
69
- self.output_project_dir.write_performance_line_graph_per_date(
70
- annotator_per_date_obj, phase=TaskPhase.ANNOTATION, user_id_list=self.user_id_list
71
- )
72
- self.output_project_dir.write_performance_line_graph_per_date(
73
- inspector_per_date_obj, phase=TaskPhase.INSPECTION, user_id_list=self.user_id_list
74
- )
75
- self.output_project_dir.write_performance_line_graph_per_date(
76
- acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=self.user_id_list
77
- )
69
+ self.output_project_dir.write_performance_line_graph_per_date(annotator_per_date_obj, phase=TaskPhase.ANNOTATION, user_id_list=self.user_id_list)
70
+ self.output_project_dir.write_performance_line_graph_per_date(inspector_per_date_obj, phase=TaskPhase.INSPECTION, user_id_list=self.user_id_list)
71
+ self.output_project_dir.write_performance_line_graph_per_date(acceptor_per_date_obj, phase=TaskPhase.ACCEPTANCE, user_id_list=self.user_id_list)
78
72
 
79
73
  def main(self) -> None:
80
74
  try:
@@ -103,9 +97,7 @@ class WritingGraph:
103
97
  logger.warning("'日毎の生産量と生産性.csv'から生成できるグラフの出力に失敗しました。", exc_info=True)
104
98
 
105
99
  try:
106
- self.output_project_dir.write_whole_productivity_line_graph_per_annotation_started_date(
107
- self.project_dir.read_whole_productivity_per_first_annotation_started_date()
108
- )
100
+ self.output_project_dir.write_whole_productivity_line_graph_per_annotation_started_date(self.project_dir.read_whole_productivity_per_first_annotation_started_date())
109
101
  except Exception:
110
102
  logger.warning("'教師付者_教師付開始日list.csv'から生成できるグラフの出力に失敗しました。", exc_info=True)
111
103
 
@@ -130,9 +122,7 @@ def create_custom_production_volume_list(cli_value: str) -> list[ProductionVolum
130
122
  def main(args: argparse.Namespace) -> None:
131
123
  user_id_list = get_list_from_args(args.user_id) if args.user_id is not None else None
132
124
 
133
- custom_production_volume_list = (
134
- create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
135
- )
125
+ custom_production_volume_list = create_custom_production_volume_list(args.custom_production_volume) if args.custom_production_volume is not None else None
136
126
 
137
127
  task_completion_criteria = TaskCompletionCriteria(args.task_completion_criteria)
138
128
  input_project_dir = ProjectDir(args.dir, task_completion_criteria, custom_production_volume_list=custom_production_volume_list)
@@ -171,12 +171,8 @@ class CollectingPerformanceInfo:
171
171
  quality_indicator_by_directory: Optional[QualityIndicatorByDirectory] = None,
172
172
  threshold_infos_by_directory: Optional[ThresholdInfoSettings] = None,
173
173
  ) -> None:
174
- self.quality_indicator = (
175
- quality_indicator if quality_indicator is not None else QualityIndicator("pointed_out_inspection_comment_count/annotation_count")
176
- )
177
- self.productivity_indicator = (
178
- productivity_indicator if productivity_indicator is not None else ProductivityIndicator("actual_worktime_hour/annotation_count")
179
- )
174
+ self.quality_indicator = quality_indicator if quality_indicator is not None else QualityIndicator("pointed_out_inspection_comment_count/annotation_count")
175
+ self.productivity_indicator = productivity_indicator if productivity_indicator is not None else ProductivityIndicator("actual_worktime_hour/annotation_count")
180
176
  self.threshold_info = threshold_info if threshold_info is not None else ThresholdInfo()
181
177
  self.threshold_infos_by_directory = threshold_infos_by_directory if threshold_infos_by_directory is not None else {}
182
178
  self.productivity_indicator_by_directory = productivity_indicator_by_directory if productivity_indicator_by_directory is not None else {}
@@ -324,9 +320,7 @@ class CollectingPerformanceInfo:
324
320
  if not p_project_dir.is_dir():
325
321
  continue
326
322
 
327
- custom_production_volume_list = (
328
- custom_production_volume_list_by_directory.get(p_project_dir.name) if custom_production_volume_list_by_directory is not None else None
329
- )
323
+ custom_production_volume_list = custom_production_volume_list_by_directory.get(p_project_dir.name) if custom_production_volume_list_by_directory is not None else None
330
324
  project_title = p_project_dir.name
331
325
  project_dir = ProjectDir(
332
326
  p_project_dir,
@@ -593,9 +587,7 @@ class WritePerformanceRatingCsv(CommandLineWithoutWebapi):
593
587
  user_id_list = get_list_from_args(args.user_id) if args.user_id is not None else None
594
588
  df_user = create_user_df(target_dir)
595
589
  custom_production_volume_by_directory = (
596
- create_custom_production_volume_by_directory(args.custom_production_volume_by_directory)
597
- if args.custom_production_volume_by_directory is not None
598
- else None
590
+ create_custom_production_volume_by_directory(args.custom_production_volume_by_directory) if args.custom_production_volume_by_directory is not None else None
599
591
  )
600
592
  result = CollectingPerformanceInfo(
601
593
  productivity_indicator=ProductivityIndicator(args.productivity_indicator),
@@ -238,14 +238,10 @@ class ListAnnotationArea(CommandLine):
238
238
 
239
239
  if project_id is not None:
240
240
  if args.temp_dir is not None:
241
- download_and_print_annotation_area(
242
- project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path
243
- )
241
+ download_and_print_annotation_area(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path)
244
242
  else:
245
243
  with tempfile.TemporaryDirectory() as str_temp_dir:
246
- download_and_print_annotation_area(
247
- project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path
248
- )
244
+ download_and_print_annotation_area(project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path)
249
245
  else:
250
246
  assert annotation_path is not None
251
247
  print_annotation_area(
@@ -293,7 +289,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
293
289
  parser.add_argument(
294
290
  "--latest",
295
291
  action="store_true",
296
- help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。", # noqa: E501
292
+ help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
297
293
  )
298
294
 
299
295
  parser.add_argument(
@@ -69,9 +69,7 @@ class AnnotationAttribute(pydantic.BaseModel):
69
69
  attributes: dict[str, Union[str, int, bool]]
70
70
 
71
71
 
72
- def get_annotation_attribute_list_from_annotation_json(
73
- simple_annotation: dict[str, Any], *, target_labels: Collection[str] | None = None
74
- ) -> list[AnnotationAttribute]:
72
+ def get_annotation_attribute_list_from_annotation_json(simple_annotation: dict[str, Any], *, target_labels: Collection[str] | None = None) -> list[AnnotationAttribute]:
75
73
  """
76
74
  1個のアノテーションJSONに対して、アノテーションの属性情報を取得します。
77
75
 
@@ -207,9 +205,7 @@ class ListAnnotationAttribute(CommandLine):
207
205
 
208
206
  downloading_obj = DownloadingFile(self.service)
209
207
 
210
- def download_and_print_annotation_attribute_list(
211
- project_id: str, temp_dir: Path, *, is_latest: bool, annotation_path: Optional[Path]
212
- ) -> None:
208
+ def download_and_print_annotation_attribute_list(project_id: str, temp_dir: Path, *, is_latest: bool, annotation_path: Optional[Path]) -> None:
213
209
  if annotation_path is None:
214
210
  annotation_path = temp_dir / f"{project_id}__annotation.zip"
215
211
  downloading_obj.download_annotation_zip(
@@ -225,16 +221,12 @@ class ListAnnotationAttribute(CommandLine):
225
221
 
226
222
  if project_id is not None:
227
223
  if args.temp_dir is not None:
228
- download_and_print_annotation_attribute_list(
229
- project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path
230
- )
224
+ download_and_print_annotation_attribute_list(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path)
231
225
  else:
232
226
  # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
233
227
  # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
234
228
  with tempfile.TemporaryDirectory() as str_temp_dir:
235
- download_and_print_annotation_attribute_list(
236
- project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path
237
- )
229
+ download_and_print_annotation_attribute_list(project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path)
238
230
  else:
239
231
  assert annotation_path is not None
240
232
  annotation_attribute_list = get_annotation_attribute_list_from_annotation_zipdir_path(
@@ -276,14 +268,13 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
276
268
  type=str,
277
269
  nargs="+",
278
270
  required=False,
279
- help="出力対象のアノテーションのラベル名(英語)を指定します。指定しない場合はラベル名で絞り込みません。"
280
- " ``file://`` を先頭に付けると、ラベル名の一覧が記載されたファイルを指定できます。",
271
+ help="出力対象のアノテーションのラベル名(英語)を指定します。指定しない場合はラベル名で絞り込みません。 ``file://`` を先頭に付けると、ラベル名の一覧が記載されたファイルを指定できます。",
281
272
  )
282
273
 
283
274
  parser.add_argument(
284
275
  "--latest",
285
276
  action="store_true",
286
- help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。", # noqa: E501
277
+ help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
287
278
  )
288
279
 
289
280
  parser.add_argument(
@@ -316,14 +316,12 @@ class AnnotationCountCsvByAttribute:
316
316
  Args:
317
317
  selective_attribute_value_max_count: 選択肢系の属性の値の個数の上限。これを超えた場合は、非選択肢系属性(トラッキングIDやアノテーションリンクなど)とみなす
318
318
 
319
- """ # noqa: E501
319
+ """
320
320
 
321
321
  def __init__(self, selective_attribute_value_max_count: int = 20) -> None:
322
322
  self.selective_attribute_value_max_count = selective_attribute_value_max_count
323
323
 
324
- def _value_columns(
325
- self, annotation_count_list: Collection[HasAnnotationAttributeCounts], *, prior_attribute_columns: Optional[list[tuple[str, str, str]]]
326
- ) -> list[tuple[str, str, str]]:
324
+ def _value_columns(self, annotation_count_list: Collection[HasAnnotationAttributeCounts], *, prior_attribute_columns: Optional[list[tuple[str, str, str]]]) -> list[tuple[str, str, str]]:
327
325
  """
328
326
  CSVの数値列を取得します。
329
327
  """
@@ -441,9 +439,7 @@ def get_frame_no_map(task_json_path: Path) -> dict[tuple[str, str], int]:
441
439
 
442
440
 
443
441
  def get_attribute_columns(attribute_names: list[tuple[str, str]]) -> list[tuple[str, str, str]]:
444
- attribute_columns = [
445
- (label_name, attribute_name, value_type) for label_name, attribute_name in attribute_names for value_type in ["filled", "empty"]
446
- ]
442
+ attribute_columns = [(label_name, attribute_name, value_type) for label_name, attribute_name in attribute_names for value_type in ["filled", "empty"]]
447
443
  return attribute_columns
448
444
 
449
445
 
@@ -451,9 +447,7 @@ class ListAnnotationAttributeFilledCountMain:
451
447
  def __init__(self, service: annofabapi.Resource) -> None:
452
448
  self.service = service
453
449
 
454
- def print_annotation_count_csv_by_input_data(
455
- self, annotation_count_list: list[AnnotationCountByInputData], output_file: Path, *, attribute_names: Optional[list[tuple[str, str]]]
456
- ) -> None:
450
+ def print_annotation_count_csv_by_input_data(self, annotation_count_list: list[AnnotationCountByInputData], output_file: Path, *, attribute_names: Optional[list[tuple[str, str]]]) -> None:
457
451
  attribute_columns: Optional[list[tuple[str, str, str]]] = None
458
452
  if attribute_names is not None:
459
453
  attribute_columns = get_attribute_columns(attribute_names)
@@ -461,9 +455,7 @@ class ListAnnotationAttributeFilledCountMain:
461
455
  df = AnnotationCountCsvByAttribute().create_df_by_input_data(annotation_count_list, prior_attribute_columns=attribute_columns)
462
456
  print_csv(df, output_file)
463
457
 
464
- def print_annotation_count_csv_by_task(
465
- self, annotation_count_list: list[AnnotationCountByTask], output_file: Path, *, attribute_names: Optional[list[tuple[str, str]]]
466
- ) -> None:
458
+ def print_annotation_count_csv_by_task(self, annotation_count_list: list[AnnotationCountByTask], output_file: Path, *, attribute_names: Optional[list[tuple[str, str]]]) -> None:
467
459
  attribute_columns: Optional[list[tuple[str, str, str]]] = None
468
460
  if attribute_names is not None:
469
461
  attribute_columns = get_attribute_columns(attribute_names)
@@ -493,9 +485,7 @@ class ListAnnotationAttributeFilledCountMain:
493
485
 
494
486
  frame_no_map = get_frame_no_map(task_json_path) if task_json_path is not None else None
495
487
 
496
- annotation_count_list_by_input_data = ListAnnotationCounterByInputData(
497
- frame_no_map=frame_no_map, target_attribute_names=target_attribute_names
498
- ).get_annotation_count_list(
488
+ annotation_count_list_by_input_data = ListAnnotationCounterByInputData(frame_no_map=frame_no_map, target_attribute_names=target_attribute_names).get_annotation_count_list(
499
489
  annotation_path,
500
490
  target_task_ids=target_task_ids,
501
491
  task_query=task_query,
@@ -504,9 +494,7 @@ class ListAnnotationAttributeFilledCountMain:
504
494
  if group_by == GroupBy.INPUT_DATA_ID:
505
495
  logger.info(f"{len(annotation_count_list_by_input_data)} 件の入力データに含まれるアノテーション数の情報を出力します。")
506
496
  if output_format == FormatArgument.CSV:
507
- self.print_annotation_count_csv_by_input_data(
508
- annotation_count_list_by_input_data, output_file=output_file, attribute_names=target_attribute_names
509
- )
497
+ self.print_annotation_count_csv_by_input_data(annotation_count_list_by_input_data, output_file=output_file, attribute_names=target_attribute_names)
510
498
 
511
499
  elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
512
500
  json_is_pretty = output_format == FormatArgument.PRETTY_JSON
@@ -520,9 +508,7 @@ class ListAnnotationAttributeFilledCountMain:
520
508
  annotation_count_list_by_task = convert_annotation_count_list_by_input_data_to_by_task(annotation_count_list_by_input_data)
521
509
  logger.info(f"{len(annotation_count_list_by_task)} 件のタスクに含まれるアノテーション数の情報を出力します。")
522
510
  if output_format == FormatArgument.CSV:
523
- self.print_annotation_count_csv_by_task(
524
- annotation_count_list_by_task, output_file=output_file, attribute_names=target_attribute_names
525
- )
511
+ self.print_annotation_count_csv_by_task(annotation_count_list_by_task, output_file=output_file, attribute_names=target_attribute_names)
526
512
 
527
513
  elif output_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
528
514
  json_is_pretty = output_format == FormatArgument.PRETTY_JSON
@@ -669,7 +655,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
669
655
  parser.add_argument(
670
656
  "--latest",
671
657
  action="store_true",
672
- help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。", # noqa: E501
658
+ help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
673
659
  )
674
660
 
675
661
  parser.set_defaults(subcommand_func=main)
@@ -219,13 +219,9 @@ class ListAnnotationCounterByInputData:
219
219
 
220
220
  annotation_count_by_label = collections.Counter([e["label"] for e in details])
221
221
  if self.target_labels is not None:
222
- annotation_count_by_label = collections.Counter(
223
- {label: count for label, count in annotation_count_by_label.items() if label in self.target_labels}
224
- )
222
+ annotation_count_by_label = collections.Counter({label: count for label, count in annotation_count_by_label.items() if label in self.target_labels})
225
223
  if self.non_target_labels is not None:
226
- annotation_count_by_label = collections.Counter(
227
- {label: count for label, count in annotation_count_by_label.items() if label not in self.non_target_labels}
228
- )
224
+ annotation_count_by_label = collections.Counter({label: count for label, count in annotation_count_by_label.items() if label not in self.non_target_labels})
229
225
 
230
226
  attributes_list: list[AttributeValueKey] = []
231
227
  for detail in details:
@@ -413,10 +409,9 @@ class AttributeCountCsv:
413
409
  Args:
414
410
  selective_attribute_value_max_count: 選択肢系の属性の値の個数の上限。これを超えた場合は、非選択肢系属性(トラッキングIDやアノテーションリンクなど)とみなす
415
411
 
416
- """ # noqa: E501
412
+ """
417
413
 
418
- def __init__(self, selective_attribute_value_max_count: int = 20, csv_format: Optional[dict[str, Any]] = None) -> None:
419
- self.csv_format = csv_format
414
+ def __init__(self, selective_attribute_value_max_count: int = 20) -> None:
420
415
  self.selective_attribute_value_max_count = selective_attribute_value_max_count
421
416
 
422
417
  def _only_selective_attribute(self, columns: list[AttributeValueKey]) -> list[AttributeValueKey]:
@@ -429,23 +424,13 @@ class AttributeCountCsv:
429
424
  for label, attribute_name, _ in columns:
430
425
  attribute_name_list.append((label, attribute_name))
431
426
 
432
- non_selective_attribute_names = {
433
- key for key, value in collections.Counter(attribute_name_list).items() if value > self.selective_attribute_value_max_count
434
- }
427
+ non_selective_attribute_names = {key for key, value in collections.Counter(attribute_name_list).items() if value > self.selective_attribute_value_max_count}
435
428
  if len(non_selective_attribute_names) > 0:
436
- logger.debug(
437
- f"以下の属性は値の個数が{self.selective_attribute_value_max_count}を超えていたため、集計しません。 :: {non_selective_attribute_names}"
438
- )
429
+ logger.debug(f"以下の属性は値の個数が{self.selective_attribute_value_max_count}を超えていたため、集計しません。 :: {non_selective_attribute_names}")
439
430
 
440
- return [
441
- (label, attribute_name, attribute_value)
442
- for (label, attribute_name, attribute_value) in columns
443
- if (label, attribute_name) not in non_selective_attribute_names
444
- ]
431
+ return [(label, attribute_name, attribute_value) for (label, attribute_name, attribute_value) in columns if (label, attribute_name) not in non_selective_attribute_names]
445
432
 
446
- def _value_columns(
447
- self, counter_list: Collection[AnnotationCounter], prior_attribute_columns: Optional[list[AttributeValueKey]]
448
- ) -> list[AttributeValueKey]:
433
+ def _value_columns(self, counter_list: Collection[AnnotationCounter], prior_attribute_columns: Optional[list[AttributeValueKey]]) -> list[AttributeValueKey]:
449
434
  all_attr_key_set = {attr_key for c in counter_list for attr_key in c.annotation_count_by_attribute}
450
435
  if prior_attribute_columns is not None:
451
436
  remaining_columns = sorted(all_attr_key_set - set(prior_attribute_columns))
@@ -511,7 +496,7 @@ class AttributeCountCsv:
511
496
  # `task_id`列など`basic_columns`も`fillna`対象だが、nanではないはずので問題ない
512
497
  df.fillna(0, inplace=True)
513
498
 
514
- print_csv(df, output=str(output_file), to_csv_kwargs=self.csv_format)
499
+ print_csv(df, output=str(output_file))
515
500
 
516
501
  def print_csv_by_input_data(
517
502
  self,
@@ -555,7 +540,7 @@ class AttributeCountCsv:
555
540
  value_columns = self._value_columns(counter_list, prior_attribute_columns)
556
541
  df = df.fillna(dict.fromkeys(value_columns, 0))
557
542
 
558
- print_csv(df, output=str(output_file), to_csv_kwargs=self.csv_format)
543
+ print_csv(df, output=str(output_file))
559
544
 
560
545
 
561
546
  class LabelCountCsv:
@@ -565,9 +550,6 @@ class LabelCountCsv:
565
550
 
566
551
  """
567
552
 
568
- def __init__(self, csv_format: Optional[dict[str, Any]] = None) -> None:
569
- self.csv_format = csv_format
570
-
571
553
  def _value_columns(self, counter_list: Collection[AnnotationCounter], prior_label_columns: Optional[list[str]]) -> list[str]:
572
554
  all_attr_key_set = {attr_key for c in counter_list for attr_key in c.annotation_count_by_label}
573
555
  if prior_label_columns is not None:
@@ -615,7 +597,7 @@ class LabelCountCsv:
615
597
  # NaNを0に変換する
616
598
  # `basic_columns`は必ずnanではないので、すべての列に対してfillnaを実行しても問題ないはず
617
599
  df.fillna(0, inplace=True)
618
- print_csv(df, output=str(output_file), to_csv_kwargs=self.csv_format)
600
+ print_csv(df, output=str(output_file))
619
601
 
620
602
  def print_csv_by_input_data(
621
603
  self,
@@ -658,7 +640,7 @@ class LabelCountCsv:
658
640
  value_columns = self._value_columns(counter_list, prior_label_columns)
659
641
  df = df.fillna(dict.fromkeys(value_columns, 0))
660
642
 
661
- print_csv(df, output=str(output_file), to_csv_kwargs=self.csv_format)
643
+ print_csv(df, output=str(output_file))
662
644
 
663
645
 
664
646
  class AnnotationSpecs:
@@ -693,9 +675,7 @@ class AnnotationSpecs:
693
675
  result = [to_label_name(label) for label in self._labels_v1]
694
676
  duplicated_labels = [key for key, value in collections.Counter(result).items() if value > 1]
695
677
  if len(duplicated_labels) > 0:
696
- logger.warning(
697
- f"アノテーション仕様のラベル英語名が重複しています。アノテーション個数が正しく算出できない可能性があります。:: {duplicated_labels}"
698
- )
678
+ logger.warning(f"アノテーション仕様のラベル英語名が重複しています。アノテーション個数が正しく算出できない可能性があります。:: {duplicated_labels}")
699
679
  return result
700
680
 
701
681
  def attribute_name_keys(
@@ -734,9 +714,7 @@ class AnnotationSpecs:
734
714
 
735
715
  duplicated_attribute_names = [key for key, value in collections.Counter(result).items() if value > 1]
736
716
  if len(duplicated_attribute_names) > 0:
737
- logger.warning(
738
- f"アノテーション仕様の属性情報(ラベル英語名、属性英語名)が重複しています。アノテーション個数が正しく算出できない可能性があります。:: {duplicated_attribute_names}" # noqa: E501
739
- )
717
+ logger.warning(f"アノテーション仕様の属性情報(ラベル英語名、属性英語名)が重複しています。アノテーション個数が正しく算出できない可能性があります。:: {duplicated_attribute_names}")
740
718
 
741
719
  return result
742
720
 
@@ -776,7 +754,7 @@ class AnnotationSpecs:
776
754
  duplicated_attributes = [key for key, value in collections.Counter(target_attribute_value_keys).items() if value > 1]
777
755
  if len(duplicated_attributes) > 0:
778
756
  logger.warning(
779
- f"アノテーション仕様の属性情報(ラベル英語名、属性英語名、選択肢英語名)が重複しています。アノテーション個数が正しく算出できない可能性があります。:: {duplicated_attributes}" # noqa: E501
757
+ f"アノテーション仕様の属性情報(ラベル英語名、属性英語名、選択肢英語名)が重複しています。アノテーション個数が正しく算出できない可能性があります。:: {duplicated_attributes}"
780
758
  )
781
759
 
782
760
  return target_attribute_value_keys
@@ -874,9 +852,7 @@ class ListAnnotationCountMain:
874
852
  non_selective_attribute_name_keys = annotation_specs.non_selective_attribute_name_keys()
875
853
 
876
854
  frame_no_map = self.get_frame_no_map(task_json_path) if task_json_path is not None else None
877
- counter_by_input_data = ListAnnotationCounterByInputData(
878
- non_target_attribute_names=non_selective_attribute_name_keys, frame_no_map=frame_no_map
879
- )
855
+ counter_by_input_data = ListAnnotationCounterByInputData(non_target_attribute_names=non_selective_attribute_name_keys, frame_no_map=frame_no_map)
880
856
  counter_list_by_input_data = counter_by_input_data.get_annotation_counter_list(
881
857
  annotation_path,
882
858
  target_task_ids=target_task_ids,
@@ -957,9 +933,7 @@ class ListAnnotationCountMain:
957
933
  non_selective_attribute_name_keys = None
958
934
 
959
935
  frame_no_map = self.get_frame_no_map(task_json_path) if task_json_path is not None else None
960
- counter_list_by_input_data = ListAnnotationCounterByInputData(
961
- non_target_attribute_names=non_selective_attribute_name_keys, frame_no_map=frame_no_map
962
- ).get_annotation_counter_list(
936
+ counter_list_by_input_data = ListAnnotationCounterByInputData(non_target_attribute_names=non_selective_attribute_name_keys, frame_no_map=frame_no_map).get_annotation_counter_list(
963
937
  annotation_path,
964
938
  target_task_ids=target_task_ids,
965
939
  task_query=task_query,
@@ -1198,7 +1172,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
1198
1172
  parser.add_argument(
1199
1173
  "--latest",
1200
1174
  action="store_true",
1201
- help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。", # noqa: E501
1175
+ help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
1202
1176
  )
1203
1177
 
1204
1178
  parser.set_defaults(subcommand_func=main)
@@ -203,14 +203,10 @@ class ListAnnotationDurationByInputData:
203
203
  annotation_duration_by_label[detail["label"]] += calculate_annotation_duration_second(detail)
204
204
 
205
205
  if self.target_labels is not None:
206
- annotation_duration_by_label = {
207
- label: duration for label, duration in annotation_duration_by_label.items() if label in self.target_labels
208
- }
206
+ annotation_duration_by_label = {label: duration for label, duration in annotation_duration_by_label.items() if label in self.target_labels}
209
207
 
210
208
  if self.non_target_labels is not None:
211
- annotation_duration_by_label = {
212
- label: duration for label, duration in annotation_duration_by_label.items() if label not in self.non_target_labels
213
- }
209
+ annotation_duration_by_label = {label: duration for label, duration in annotation_duration_by_label.items() if label not in self.non_target_labels}
214
210
 
215
211
  annotation_duration_by_attribute: dict[AttributeValueKey, float] = defaultdict(float)
216
212
  for detail in range_details:
@@ -307,7 +303,7 @@ class AnnotationDurationCsvByAttribute:
307
303
  Args:
308
304
  selective_attribute_value_max_count: 選択肢系の属性の値の個数の上限。これを超えた場合は、非選択肢系属性(トラッキングIDやアノテーションリンクなど)とみなす
309
305
 
310
- """ # noqa: E501
306
+ """
311
307
 
312
308
  def __init__(self, selective_attribute_value_max_count: int = 20) -> None:
313
309
  self.selective_attribute_value_max_count = selective_attribute_value_max_count
@@ -322,23 +318,13 @@ class AnnotationDurationCsvByAttribute:
322
318
  for label, attribute_name, _ in columns:
323
319
  attribute_name_list.append((label, attribute_name))
324
320
 
325
- non_selective_attribute_names = {
326
- key for key, value in collections.Counter(attribute_name_list).items() if value > self.selective_attribute_value_max_count
327
- }
321
+ non_selective_attribute_names = {key for key, value in collections.Counter(attribute_name_list).items() if value > self.selective_attribute_value_max_count}
328
322
  if len(non_selective_attribute_names) > 0:
329
- logger.debug(
330
- f"以下の属性は値の個数が{self.selective_attribute_value_max_count}を超えていたため、集計しません。 :: {non_selective_attribute_names}"
331
- )
323
+ logger.debug(f"以下の属性は値の個数が{self.selective_attribute_value_max_count}を超えていたため、集計しません。 :: {non_selective_attribute_names}")
332
324
 
333
- return [
334
- (label, attribute_name, attribute_value)
335
- for (label, attribute_name, attribute_value) in columns
336
- if (label, attribute_name) not in non_selective_attribute_names
337
- ]
325
+ return [(label, attribute_name, attribute_value) for (label, attribute_name, attribute_value) in columns if (label, attribute_name) not in non_selective_attribute_names]
338
326
 
339
- def _value_columns(
340
- self, annotation_duration_list: Collection[AnnotationDuration], prior_attribute_columns: Optional[list[AttributeValueKey]]
341
- ) -> list[AttributeValueKey]:
327
+ def _value_columns(self, annotation_duration_list: Collection[AnnotationDuration], prior_attribute_columns: Optional[list[AttributeValueKey]]) -> list[AttributeValueKey]:
342
328
  all_attr_key_set = {attr_key for c in annotation_duration_list for attr_key in c.annotation_duration_second_by_attribute}
343
329
  if prior_attribute_columns is not None:
344
330
  remaining_columns = sorted(all_attr_key_set - set(prior_attribute_columns))
@@ -482,9 +468,7 @@ class ListAnnotationDurationMain:
482
468
  def __init__(self, service: annofabapi.Resource) -> None:
483
469
  self.service = service
484
470
 
485
- def print_annotation_duration_csv(
486
- self, annotation_duration_list: list[AnnotationDuration], csv_type: CsvType, output_file: Path, *, annotation_specs: Optional[AnnotationSpecs]
487
- ) -> None:
471
+ def print_annotation_duration_csv(self, annotation_duration_list: list[AnnotationDuration], csv_type: CsvType, output_file: Path, *, annotation_specs: Optional[AnnotationSpecs]) -> None:
488
472
  if csv_type == CsvType.LABEL:
489
473
  # ラベル名の列順が、アノテーション仕様にあるラベル名の順番に対応するようにする。
490
474
  label_columns: Optional[list[str]] = None
@@ -522,9 +506,7 @@ class ListAnnotationDurationMain:
522
506
  annotation_specs = AnnotationSpecs(self.service, project_id, annotation_type=DefaultAnnotationType.RANGE.value)
523
507
  non_selective_attribute_name_keys = annotation_specs.non_selective_attribute_name_keys()
524
508
 
525
- annotation_duration_list = ListAnnotationDurationByInputData(
526
- non_target_attribute_names=non_selective_attribute_name_keys
527
- ).get_annotation_duration_list(
509
+ annotation_duration_list = ListAnnotationDurationByInputData(non_target_attribute_names=non_selective_attribute_name_keys).get_annotation_duration_list(
528
510
  annotation_path,
529
511
  input_data_json_path=input_data_json_path,
530
512
  target_task_ids=target_task_ids,
@@ -535,9 +517,7 @@ class ListAnnotationDurationMain:
535
517
 
536
518
  if arg_format == FormatArgument.CSV:
537
519
  assert csv_type is not None
538
- self.print_annotation_duration_csv(
539
- annotation_duration_list, output_file=output_file, csv_type=csv_type, annotation_specs=annotation_specs
540
- )
520
+ self.print_annotation_duration_csv(annotation_duration_list, output_file=output_file, csv_type=csv_type, annotation_specs=annotation_specs)
541
521
 
542
522
  elif arg_format in [FormatArgument.PRETTY_JSON, FormatArgument.JSON]:
543
523
  json_is_pretty = arg_format == FormatArgument.PRETTY_JSON
@@ -577,9 +557,7 @@ class ListAnnotationDuration(CommandLine):
577
557
  super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
578
558
  project, _ = self.service.api.get_project(project_id)
579
559
  if project["input_data_type"] != InputDataType.MOVIE.value:
580
- logger.warning(
581
- f"project_id='{project_id}'であるプロジェクトは、動画プロジェクトでないので、出力される区間アノテーションの長さはすべて0秒になります。"
582
- )
560
+ logger.warning(f"project_id='{project_id}'であるプロジェクトは、動画プロジェクトでないので、出力される区間アノテーションの長さはすべて0秒になります。")
583
561
 
584
562
  annotation_path = Path(args.annotation) if args.annotation is not None else None
585
563
 
@@ -622,16 +600,12 @@ class ListAnnotationDuration(CommandLine):
622
600
 
623
601
  if project_id is not None:
624
602
  if args.temp_dir is not None:
625
- download_and_print_annotation_duration(
626
- project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path
627
- )
603
+ download_and_print_annotation_duration(project_id=project_id, temp_dir=args.temp_dir, is_latest=args.latest, annotation_path=annotation_path)
628
604
  else:
629
605
  # `NamedTemporaryFile`を使わない理由: Windowsで`PermissionError`が発生するため
630
606
  # https://qiita.com/yuji38kwmt/items/c6f50e1fc03dafdcdda0 参考
631
607
  with tempfile.TemporaryDirectory() as str_temp_dir:
632
- download_and_print_annotation_duration(
633
- project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path
634
- )
608
+ download_and_print_annotation_duration(project_id=project_id, temp_dir=Path(str_temp_dir), is_latest=args.latest, annotation_path=annotation_path)
635
609
  else:
636
610
  assert annotation_path is not None
637
611
  main_obj.print_annotation_duration(
@@ -694,7 +668,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
694
668
  parser.add_argument(
695
669
  "--latest",
696
670
  action="store_true",
697
- help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。", # noqa: E501
671
+ help="``--annotation`` を指定しないとき、最新のアノテーションzipを参照します。このオプションを指定すると、アノテーションzipを更新するのに数分待ちます。",
698
672
  )
699
673
 
700
674
  parser.add_argument(
@@ -71,7 +71,7 @@ class ListVideoDuration(CommandLine):
71
71
  def validate(self, args: argparse.Namespace) -> bool:
72
72
  if args.project_id is None and (args.input_data_json is None or args.task_json is None):
73
73
  print( # noqa: T201
74
- f"{self.COMMON_MESSAGE} argument --project_id: '--input_data_json'または'--task_json'が未指定のときは、'--project_id' を指定してください。", # noqa: E501
74
+ f"{self.COMMON_MESSAGE} argument --project_id: '--input_data_json'または'--task_json'が未指定のときは、'--project_id' を指定してください。",
75
75
  file=sys.stderr,
76
76
  )
77
77
  return False
@@ -171,8 +171,7 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
171
171
  "--input_data_json",
172
172
  type=Path,
173
173
  required=False,
174
- help="入力データ情報が記載されたJSONファイルのパスを指定します。\n"
175
- "JSONファイルは ``$ annofabcli input_data download`` コマンドで取得できます。",
174
+ help="入力データ情報が記載されたJSONファイルのパスを指定します。\nJSONファイルは ``$ annofabcli input_data download`` コマンドで取得できます。",
176
175
  )
177
176
 
178
177
  parser.add_argument(
@@ -156,7 +156,6 @@ def parse_args(parser: argparse.ArgumentParser) -> None:
156
156
  )
157
157
 
158
158
  argument_parser.add_output()
159
- argument_parser.add_csv_format()
160
159
 
161
160
  parser.set_defaults(subcommand_func=main)
162
161
 
@@ -142,15 +142,11 @@ class ScatterGraph:
142
142
  if legend_label == "":
143
143
  legend_label = "none"
144
144
 
145
- self._scatter_glyphs[legend_label] = self.figure.scatter(
146
- x=x_column_name, y=y_column_name, source=source, legend_label=legend_label, color=color, muted_alpha=0.2, size=6
147
- )
145
+ self._scatter_glyphs[legend_label] = self.figure.scatter(x=x_column_name, y=y_column_name, source=source, legend_label=legend_label, color=color, muted_alpha=0.2, size=6)
148
146
 
149
147
  # 1点ごとに`text`で名前を表示している理由:
150
148
  # `add_multi_choice_widget_for_searching_user`関数で追加したMultiChoice Widgetで、名前の表示スタイルを変更するため
151
- for x, y, username, user_id in zip(
152
- source.data[x_column_name], source.data[y_column_name], source.data[username_column_name], source.data[user_id_column_name]
153
- ):
149
+ for x, y, username, user_id in zip(source.data[x_column_name], source.data[y_column_name], source.data[username_column_name], source.data[user_id_column_name]):
154
150
  self.text_glyphs[user_id] = self.figure.text(
155
151
  x=x,
156
152
  y=y,
@@ -204,9 +200,7 @@ class ScatterGraph:
204
200
 
205
201
  # 1点ごとに`text`で名前を表示している理由:
206
202
  # `add_multi_choice_widget_for_searching_user`関数で追加したMultiChoice Widgetで、名前の表示スタイルを変更するため
207
- for x, y, username, user_id in zip(
208
- source.data[x_column_name], source.data[y_column_name], source.data[username_column_name], source.data[user_id_column_name]
209
- ):
203
+ for x, y, username, user_id in zip(source.data[x_column_name], source.data[y_column_name], source.data[username_column_name], source.data[user_id_column_name]):
210
204
  self.text_glyphs[user_id] = self.figure.text(
211
205
  x=x,
212
206
  y=y,