annofabcli 1.111.1__py3-none-any.whl → 1.112.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annofabcli/__main__.py +1 -2
- annofabcli/annotation/annotation_query.py +10 -10
- annofabcli/annotation/change_annotation_attributes.py +9 -9
- annofabcli/annotation/change_annotation_attributes_per_annotation.py +3 -4
- annofabcli/annotation/change_annotation_properties.py +13 -13
- annofabcli/annotation/copy_annotation.py +5 -5
- annofabcli/annotation/create_classification_annotation.py +6 -6
- annofabcli/annotation/delete_annotation.py +8 -8
- annofabcli/annotation/download_annotation_zip.py +1 -3
- annofabcli/annotation/dump_annotation.py +7 -7
- annofabcli/annotation/import_annotation.py +12 -12
- annofabcli/annotation/list_annotation.py +8 -8
- annofabcli/annotation/list_annotation_count.py +1 -2
- annofabcli/annotation/merge_segmentation.py +5 -5
- annofabcli/annotation/remove_segmentation_overlap.py +4 -4
- annofabcli/annotation/restore_annotation.py +6 -6
- annofabcli/annotation/subcommand_annotation.py +1 -2
- annofabcli/annotation_specs/add_attribute_restriction.py +4 -5
- annofabcli/annotation_specs/attribute_restriction.py +8 -8
- annofabcli/annotation_specs/export_annotation_specs.py +4 -5
- annofabcli/annotation_specs/get_annotation_specs_with_attribute_id_replaced.py +3 -4
- annofabcli/annotation_specs/get_annotation_specs_with_choice_id_replaced.py +3 -4
- annofabcli/annotation_specs/get_annotation_specs_with_label_id_replaced.py +3 -4
- annofabcli/annotation_specs/list_annotation_specs_attribute.py +9 -10
- annofabcli/annotation_specs/list_annotation_specs_choice.py +9 -10
- annofabcli/annotation_specs/list_annotation_specs_history.py +2 -2
- annofabcli/annotation_specs/list_annotation_specs_label.py +8 -9
- annofabcli/annotation_specs/list_annotation_specs_label_attribute.py +10 -11
- annofabcli/annotation_specs/list_attribute_restriction.py +2 -4
- annofabcli/annotation_specs/list_label_color.py +2 -3
- annofabcli/annotation_specs/put_label_color.py +3 -4
- annofabcli/annotation_specs/subcommand_annotation_specs.py +1 -3
- annofabcli/annotation_zip/list_annotation_3d_bounding_box.py +365 -0
- annofabcli/annotation_zip/list_annotation_bounding_box_2d.py +11 -12
- annofabcli/annotation_zip/list_range_annotation.py +24 -14
- annofabcli/annotation_zip/list_single_point_annotation.py +11 -12
- annofabcli/annotation_zip/subcommand_annotation_zip.py +3 -2
- annofabcli/annotation_zip/validate_annotation.py +8 -7
- annofabcli/comment/delete_comment.py +4 -6
- annofabcli/comment/download_comment_json.py +4 -6
- annofabcli/comment/list_all_comment.py +5 -6
- annofabcli/comment/list_comment.py +3 -4
- annofabcli/comment/put_comment.py +9 -10
- annofabcli/comment/put_comment_simply.py +5 -6
- annofabcli/comment/put_inspection_comment.py +1 -3
- annofabcli/comment/put_inspection_comment_simply.py +1 -3
- annofabcli/comment/put_onhold_comment.py +1 -3
- annofabcli/comment/put_onhold_comment_simply.py +1 -3
- annofabcli/comment/subcommand_comment.py +1 -3
- annofabcli/common/bokeh.py +4 -4
- annofabcli/common/cli.py +17 -17
- annofabcli/common/download.py +28 -29
- annofabcli/common/facade.py +37 -38
- annofabcli/common/image.py +14 -14
- annofabcli/common/utils.py +8 -8
- annofabcli/common/visualize.py +13 -13
- annofabcli/experimental/list_out_of_range_annotation_for_movie.py +3 -4
- annofabcli/experimental/subcommand_experimental.py +1 -3
- annofabcli/filesystem/draw_annotation.py +26 -26
- annofabcli/filesystem/filter_annotation.py +9 -10
- annofabcli/filesystem/mask_user_info.py +14 -14
- annofabcli/filesystem/merge_annotation.py +8 -8
- annofabcli/filesystem/subcommand_filesystem.py +1 -3
- annofabcli/input_data/copy_input_data.py +8 -9
- annofabcli/input_data/delete_input_data.py +2 -2
- annofabcli/input_data/delete_metadata_key_of_input_data.py +3 -5
- annofabcli/input_data/download_input_data_json.py +4 -6
- annofabcli/input_data/list_all_input_data.py +8 -8
- annofabcli/input_data/list_all_input_data_merged_task.py +4 -4
- annofabcli/input_data/list_input_data.py +4 -4
- annofabcli/input_data/put_input_data.py +5 -5
- annofabcli/input_data/put_input_data_with_zip.py +2 -3
- annofabcli/input_data/subcommand_input_data.py +1 -3
- annofabcli/input_data/update_input_data.py +6 -8
- annofabcli/input_data/update_metadata_of_input_data.py +3 -5
- annofabcli/instruction/copy_instruction.py +4 -5
- annofabcli/instruction/download_instruction.py +4 -5
- annofabcli/instruction/list_instruction_history.py +2 -2
- annofabcli/instruction/subcommand_instruction.py +1 -3
- annofabcli/instruction/upload_instruction.py +2 -3
- annofabcli/job/delete_job.py +1 -2
- annofabcli/job/list_job.py +4 -4
- annofabcli/job/list_last_job.py +3 -3
- annofabcli/job/subcommand_job.py +1 -3
- annofabcli/job/wait_job.py +4 -5
- annofabcli/my_account/get_my_account.py +1 -2
- annofabcli/my_account/subcommand_my_account.py +1 -3
- annofabcli/organization/list_organization.py +1 -2
- annofabcli/organization/subcommand_organization.py +1 -3
- annofabcli/organization_member/change_organization_member.py +3 -4
- annofabcli/organization_member/delete_organization_member.py +3 -4
- annofabcli/organization_member/invite_organization_member.py +1 -3
- annofabcli/organization_member/list_organization_member.py +2 -2
- annofabcli/organization_member/subcommand_organization_member.py +1 -3
- annofabcli/project/change_organization_of_project.py +3 -3
- annofabcli/project/change_project_status.py +3 -3
- annofabcli/project/copy_project.py +4 -4
- annofabcli/project/create_project.py +7 -7
- annofabcli/project/diff_projects.py +4 -5
- annofabcli/project/list_project.py +4 -4
- annofabcli/project/put_project.py +1 -2
- annofabcli/project/subcommand_project.py +1 -2
- annofabcli/project/update_configuration.py +3 -3
- annofabcli/project/update_project.py +6 -8
- annofabcli/project_member/change_project_members.py +7 -7
- annofabcli/project_member/copy_project_members.py +3 -3
- annofabcli/project_member/drop_project_members.py +1 -2
- annofabcli/project_member/invite_project_members.py +1 -3
- annofabcli/project_member/list_users.py +1 -2
- annofabcli/project_member/put_project_members.py +5 -5
- annofabcli/project_member/subcommand_project_member.py +1 -3
- annofabcli/stat_visualization/mask_visualization_dir.py +8 -9
- annofabcli/stat_visualization/merge_visualization_dir.py +6 -7
- annofabcli/stat_visualization/subcommand_stat_visualization.py +1 -2
- annofabcli/stat_visualization/summarize_whole_performance_csv.py +1 -2
- annofabcli/stat_visualization/write_graph.py +2 -3
- annofabcli/stat_visualization/write_performance_rating_csv.py +20 -27
- annofabcli/statistics/histogram.py +5 -6
- annofabcli/statistics/linegraph.py +13 -14
- annofabcli/statistics/list_annotation_area.py +38 -13
- annofabcli/statistics/list_annotation_attribute.py +9 -10
- annofabcli/statistics/list_annotation_attribute_filled_count.py +30 -31
- annofabcli/statistics/list_annotation_count.py +57 -58
- annofabcli/statistics/list_annotation_duration.py +33 -34
- annofabcli/statistics/list_video_duration.py +4 -5
- annofabcli/statistics/list_worktime.py +3 -3
- annofabcli/statistics/scatter.py +9 -8
- annofabcli/statistics/subcommand_statistics.py +1 -4
- annofabcli/statistics/summarize_task_count.py +4 -6
- annofabcli/statistics/summarize_task_count_by_task_id_group.py +2 -4
- annofabcli/statistics/summarize_task_count_by_user.py +1 -3
- annofabcli/statistics/visualization/dataframe/annotation_count.py +5 -4
- annofabcli/statistics/visualization/dataframe/annotation_duration.py +6 -7
- annofabcli/statistics/visualization/dataframe/cumulative_productivity.py +15 -17
- annofabcli/statistics/visualization/dataframe/productivity_per_date.py +17 -19
- annofabcli/statistics/visualization/dataframe/project_performance.py +3 -12
- annofabcli/statistics/visualization/dataframe/task.py +11 -12
- annofabcli/statistics/visualization/dataframe/task_worktime_by_phase_user.py +9 -10
- annofabcli/statistics/visualization/dataframe/user_performance.py +21 -19
- annofabcli/statistics/visualization/dataframe/whole_performance.py +3 -4
- annofabcli/statistics/visualization/dataframe/whole_productivity_per_date.py +12 -14
- annofabcli/statistics/visualization/dataframe/worktime_per_date.py +11 -13
- annofabcli/statistics/visualization/filtering_query.py +7 -7
- annofabcli/statistics/visualization/project_dir.py +27 -14
- annofabcli/statistics/visualization/visualization_source_files.py +49 -0
- annofabcli/statistics/visualize_annotation_count.py +22 -23
- annofabcli/statistics/visualize_annotation_duration.py +21 -22
- annofabcli/statistics/visualize_statistics.py +126 -69
- annofabcli/statistics/visualize_video_duration.py +18 -20
- annofabcli/supplementary/delete_supplementary_data.py +4 -4
- annofabcli/supplementary/list_supplementary_data.py +3 -3
- annofabcli/supplementary/put_supplementary_data.py +8 -8
- annofabcli/supplementary/subcommand_supplementary.py +1 -3
- annofabcli/task/cancel_acceptance.py +16 -17
- annofabcli/task/change_operator.py +10 -12
- annofabcli/task/change_status_to_break.py +7 -9
- annofabcli/task/change_status_to_on_hold.py +10 -12
- annofabcli/task/complete_tasks.py +17 -18
- annofabcli/task/copy_tasks.py +3 -5
- annofabcli/task/delete_metadata_key_of_task.py +4 -6
- annofabcli/task/delete_tasks.py +6 -6
- annofabcli/task/download_task_json.py +4 -6
- annofabcli/task/list_all_tasks.py +7 -7
- annofabcli/task/list_all_tasks_added_task_history.py +12 -12
- annofabcli/task/list_tasks.py +6 -6
- annofabcli/task/list_tasks_added_task_history.py +9 -9
- annofabcli/task/put_tasks.py +4 -5
- annofabcli/task/put_tasks_by_count.py +1 -2
- annofabcli/task/reject_tasks.py +18 -20
- annofabcli/task/subcommand_task.py +1 -3
- annofabcli/task/update_metadata_of_task.py +5 -6
- annofabcli/task_history/download_task_history_json.py +4 -6
- annofabcli/task_history/list_all_task_history.py +5 -6
- annofabcli/task_history/list_task_history.py +3 -4
- annofabcli/task_history/subcommand_task_history.py +1 -3
- annofabcli/task_history_event/download_task_history_event_json.py +4 -6
- annofabcli/task_history_event/list_all_task_history_event.py +6 -6
- annofabcli/task_history_event/list_worktime.py +15 -15
- annofabcli/task_history_event/subcommand_task_history_event.py +1 -2
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/METADATA +9 -15
- annofabcli-1.112.0.dist-info/RECORD +229 -0
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/WHEEL +1 -1
- annofabcli-1.111.1.dist-info/RECORD +0 -228
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/entry_points.txt +0 -0
- {annofabcli-1.111.1.dist-info → annofabcli-1.112.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,12 +8,10 @@ from __future__ import annotations
|
|
|
8
8
|
import datetime
|
|
9
9
|
import logging
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any
|
|
12
12
|
|
|
13
13
|
import annofabapi
|
|
14
|
-
import bokeh
|
|
15
14
|
import bokeh.layouts
|
|
16
|
-
import bokeh.palettes
|
|
17
15
|
import pandas
|
|
18
16
|
from bokeh.models.ui import UIElement
|
|
19
17
|
from bokeh.plotting import ColumnDataSource
|
|
@@ -250,9 +248,9 @@ class WorktimePerDate:
|
|
|
250
248
|
project_id: str,
|
|
251
249
|
actual_worktime: ActualWorktime,
|
|
252
250
|
*,
|
|
253
|
-
task_history_event_json:
|
|
254
|
-
start_date:
|
|
255
|
-
end_date:
|
|
251
|
+
task_history_event_json: Path | None = None,
|
|
252
|
+
start_date: str | None = None,
|
|
253
|
+
end_date: str | None = None,
|
|
256
254
|
) -> WorktimePerDate:
|
|
257
255
|
"""
|
|
258
256
|
|
|
@@ -371,8 +369,8 @@ class WorktimePerDate:
|
|
|
371
369
|
self,
|
|
372
370
|
output_file: Path,
|
|
373
371
|
*,
|
|
374
|
-
target_user_id_list:
|
|
375
|
-
metadata:
|
|
372
|
+
target_user_id_list: list[str] | None = None,
|
|
373
|
+
metadata: dict[str, Any] | None = None,
|
|
376
374
|
) -> None:
|
|
377
375
|
"""
|
|
378
376
|
作業時間の累積値をプロットする。
|
|
@@ -468,7 +466,7 @@ class WorktimePerDate:
|
|
|
468
466
|
username = df_subset.iloc[0]["username"]
|
|
469
467
|
|
|
470
468
|
line_count += 1
|
|
471
|
-
for line_graph, (x_column, y_column) in zip(line_graph_list, columns_list):
|
|
469
|
+
for line_graph, (x_column, y_column) in zip(line_graph_list, columns_list, strict=False):
|
|
472
470
|
line_graph.add_line(
|
|
473
471
|
source=source,
|
|
474
472
|
x_column=x_column,
|
|
@@ -510,10 +508,10 @@ class WorktimePerDate:
|
|
|
510
508
|
|
|
511
509
|
def mask_user_info(
|
|
512
510
|
self,
|
|
513
|
-
to_replace_for_user_id:
|
|
514
|
-
to_replace_for_username:
|
|
515
|
-
to_replace_for_account_id:
|
|
516
|
-
to_replace_for_biography:
|
|
511
|
+
to_replace_for_user_id: dict[str, str] | None = None,
|
|
512
|
+
to_replace_for_username: dict[str, str] | None = None,
|
|
513
|
+
to_replace_for_account_id: dict[str, str] | None = None,
|
|
514
|
+
to_replace_for_biography: dict[str, str] | None = None,
|
|
517
515
|
) -> WorktimePerDate:
|
|
518
516
|
"""
|
|
519
517
|
引数から渡された情報を元に、インスタンス変数`df`内のユーザー情報をマスクして、新しいインスタンスを返します。
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
from collections.abc import Collection
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
from annofabapi.dataclass.task import Task as DcTask
|
|
9
9
|
from annofabapi.models import Task
|
|
@@ -21,13 +21,13 @@ class FilteringQuery:
|
|
|
21
21
|
絞り込み条件
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
task_query:
|
|
25
|
-
start_date:
|
|
26
|
-
end_date:
|
|
27
|
-
ignored_task_ids:
|
|
24
|
+
task_query: TaskQuery | None = None
|
|
25
|
+
start_date: str | None = None
|
|
26
|
+
end_date: str | None = None
|
|
27
|
+
ignored_task_ids: Collection[str] | None = None
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def _get_first_annotation_started_datetime(sub_task_history_list: list[dict[str, Any]]) ->
|
|
30
|
+
def _get_first_annotation_started_datetime(sub_task_history_list: list[dict[str, Any]]) -> str | None:
|
|
31
31
|
"""
|
|
32
32
|
1個のタスクのタスク履歴一覧から、最初に教師付フェーズを作業した日時を取得します。
|
|
33
33
|
"""
|
|
@@ -40,7 +40,7 @@ def _get_first_annotation_started_datetime(sub_task_history_list: list[dict[str,
|
|
|
40
40
|
return task_history_list_with_annotation_phase[0]["started_datetime"]
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def filter_task_histories(task_histories: dict[str, list[dict[str, Any]]], *, start_date:
|
|
43
|
+
def filter_task_histories(task_histories: dict[str, list[dict[str, Any]]], *, start_date: str | None = None, end_date: str | None = None) -> dict[str, list[dict[str, Any]]]:
|
|
44
44
|
"""
|
|
45
45
|
タスク履歴を絞り込みます。
|
|
46
46
|
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from annofabapi.models import TaskPhase
|
|
10
10
|
from dataclasses_json import DataClassJsonMixin
|
|
@@ -36,13 +36,9 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
36
36
|
|
|
37
37
|
Args:
|
|
38
38
|
project_dir: ``annofabcli statistics visualize``コマンドによって出力されたプロジェクトディレクトリ
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- acceptance_completed: 受入フェーズの完了状態
|
|
43
|
-
- acceptance_reached: 受入フェーズに到達
|
|
44
|
-
- inspection_reached: 検査フェーズまたは受入フェーズに到達
|
|
45
|
-
- annotation_started: 教師付フェーズが着手された
|
|
39
|
+
task_completion_criteria: タスクの完了条件
|
|
40
|
+
metadata: プロジェクトIDや絞り込み条件などの情報が含まれるメタデータ。この情報はグラフに埋め込まれます。
|
|
41
|
+
custom_production_volume_list: 独自の生産量に関する列情報のリスト
|
|
46
42
|
"""
|
|
47
43
|
|
|
48
44
|
FILENAME_WHOLE_PERFORMANCE = "全体の生産性と品質.csv"
|
|
@@ -61,8 +57,8 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
61
57
|
project_dir: Path,
|
|
62
58
|
task_completion_criteria: TaskCompletionCriteria,
|
|
63
59
|
*,
|
|
64
|
-
metadata:
|
|
65
|
-
custom_production_volume_list:
|
|
60
|
+
metadata: dict[str, Any] | None = None,
|
|
61
|
+
custom_production_volume_list: list[ProductionVolumeColumn] | None = None,
|
|
66
62
|
) -> None:
|
|
67
63
|
self.project_dir = project_dir
|
|
68
64
|
self.task_completion_criteria = task_completion_criteria
|
|
@@ -130,7 +126,7 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
130
126
|
obj: AbstractPhaseCumulativeProductivity,
|
|
131
127
|
phase: TaskPhase,
|
|
132
128
|
*,
|
|
133
|
-
user_id_list:
|
|
129
|
+
user_id_list: list[str] | None = None,
|
|
134
130
|
minimal_output: bool = False,
|
|
135
131
|
) -> None:
|
|
136
132
|
"""
|
|
@@ -179,7 +175,7 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
179
175
|
phase_name = self.get_phase_name_for_filename(phase)
|
|
180
176
|
obj.to_csv(self.project_dir / Path(f"{phase_name}者_{phase_name}開始日list.csv"))
|
|
181
177
|
|
|
182
|
-
def write_performance_line_graph_per_date(self, obj: AbstractPhaseProductivityPerDate, phase: TaskPhase, *, user_id_list:
|
|
178
|
+
def write_performance_line_graph_per_date(self, obj: AbstractPhaseProductivityPerDate, phase: TaskPhase, *, user_id_list: list[str] | None = None) -> None:
|
|
183
179
|
"""
|
|
184
180
|
指定したフェーズの開始日ごとの作業時間や生産性情報を、折れ線グラフとして出力します。
|
|
185
181
|
"""
|
|
@@ -396,7 +392,7 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
396
392
|
"""`ユーザ_日付list-作業時間.csvを書き込む"""
|
|
397
393
|
obj.to_csv(self.project_dir / self.FILENAME_WORKTIME_PER_DATE_USER)
|
|
398
394
|
|
|
399
|
-
def write_worktime_line_graph(self, obj: WorktimePerDate, user_id_list:
|
|
395
|
+
def write_worktime_line_graph(self, obj: WorktimePerDate, user_id_list: list[str] | None = None) -> None:
|
|
400
396
|
"""横軸が日付、縦軸がユーザごとの作業時間である折れ線グラフを出力します。"""
|
|
401
397
|
obj.plot_cumulatively(self.project_dir / "line-graph/累積折れ線-横軸_日-縦軸_作業時間.html", target_user_id_list=user_id_list, metadata=self.metadata)
|
|
402
398
|
|
|
@@ -429,7 +425,7 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
429
425
|
"""
|
|
430
426
|
print_json(obj.to_dict(encode_json=True), output=self.project_dir / self.FILENAME_MERGE_INFO, is_pretty=True)
|
|
431
427
|
|
|
432
|
-
def read_metadata(self) ->
|
|
428
|
+
def read_metadata(self) -> dict[str, Any] | None:
|
|
433
429
|
"""
|
|
434
430
|
`project_info`または`merge_info`の内容をメタデータとして読み込む。
|
|
435
431
|
どちらも存在しない場合はNoneを返す。
|
|
@@ -448,6 +444,17 @@ class ProjectDir(DataClassJsonMixin):
|
|
|
448
444
|
|
|
449
445
|
return None
|
|
450
446
|
|
|
447
|
+
def get_project_title(self) -> str:
|
|
448
|
+
"""
|
|
449
|
+
`project_info`に記載されているプジェクトのタイトルを返します。
|
|
450
|
+
ただし`self.is_merged()`がTrueの場合は、`project_info`が存在しないので空文字を返します。
|
|
451
|
+
"""
|
|
452
|
+
if self.is_merged():
|
|
453
|
+
return ""
|
|
454
|
+
|
|
455
|
+
project_info = self.read_project_info()
|
|
456
|
+
return project_info.project_title
|
|
457
|
+
|
|
451
458
|
|
|
452
459
|
@dataclass
|
|
453
460
|
class ProjectInfo(DataClassJsonMixin):
|
|
@@ -464,6 +471,12 @@ class ProjectInfo(DataClassJsonMixin):
|
|
|
464
471
|
query: FilteringQuery
|
|
465
472
|
"""集計対象を絞り込むためのクエリ"""
|
|
466
473
|
|
|
474
|
+
production_volume_include_labels: list[str] | None = None
|
|
475
|
+
"""生産量に含める「アノテーション仕様のラベル名(英語)」のリスト。Noneの場合、すべてのラベルを含む。"""
|
|
476
|
+
|
|
477
|
+
production_volume_exclude_labels: list[str] | None = None
|
|
478
|
+
"""生産量から除外する「アノテーション仕様のラベル名(英語)」のリスト。Noneの場合、除外しない。"""
|
|
479
|
+
|
|
467
480
|
|
|
468
481
|
@dataclass
|
|
469
482
|
class MergingInfo(DataClassJsonMixin):
|
|
@@ -39,6 +39,7 @@ class VisualizationSourceFiles:
|
|
|
39
39
|
self.task_history_json_path = target_dir / f"{self.project_id}__task-history.json"
|
|
40
40
|
self.task_history_event_json_path = target_dir / f"{self.project_id}__task-history-event.json"
|
|
41
41
|
self.annotation_zip_path = target_dir / f"{self.project_id}__annotation.zip"
|
|
42
|
+
self.input_data_json_path = target_dir / f"{self.project_id}__input_data.json"
|
|
42
43
|
|
|
43
44
|
self.logging_prefix = f"project_id='{project_id}'"
|
|
44
45
|
|
|
@@ -99,6 +100,52 @@ class VisualizationSourceFiles:
|
|
|
99
100
|
logger.debug(f"{self.logging_prefix}: '{self.comment_json_path}'を読み込みました。{len(comment_list)}件のコメントが含まれています。")
|
|
100
101
|
return comment_list
|
|
101
102
|
|
|
103
|
+
def read_input_data_json(self) -> list[dict[str, Any]]:
|
|
104
|
+
"""
|
|
105
|
+
入力データ全件ファイルを読み込みます。
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
全入力データの一覧
|
|
109
|
+
"""
|
|
110
|
+
with self.input_data_json_path.open(encoding="utf-8") as f:
|
|
111
|
+
input_data_list = json.load(f)
|
|
112
|
+
|
|
113
|
+
logger.debug(f"{self.logging_prefix}: '{self.input_data_json_path}'を読み込みました。{len(input_data_list)}件の入力データが含まれています。")
|
|
114
|
+
return input_data_list
|
|
115
|
+
|
|
116
|
+
def get_video_duration_minutes_by_task_id(self) -> dict[str, float]:
|
|
117
|
+
"""
|
|
118
|
+
動画プロジェクトの場合、タスクIDごとの動画の長さ(分単位)を取得します。
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
key: task_id, value: 動画の長さ(分)
|
|
122
|
+
"""
|
|
123
|
+
tasks = self.read_tasks_json()
|
|
124
|
+
input_data_list = self.read_input_data_json()
|
|
125
|
+
|
|
126
|
+
# 入力データIDをキーとした辞書を作成
|
|
127
|
+
dict_input_data_by_id = {input_data["input_data_id"]: input_data for input_data in input_data_list}
|
|
128
|
+
|
|
129
|
+
result = {}
|
|
130
|
+
for task in tasks:
|
|
131
|
+
task_id = task["task_id"]
|
|
132
|
+
input_data_id_list = task["input_data_id_list"]
|
|
133
|
+
assert len(input_data_id_list) == 1, f"task_id='{task_id}'には複数の入力データが含まれています。"
|
|
134
|
+
input_data_id = input_data_id_list[0]
|
|
135
|
+
input_data = dict_input_data_by_id.get(input_data_id)
|
|
136
|
+
|
|
137
|
+
if input_data is None:
|
|
138
|
+
logger.warning(f"task_id='{task_id}' :: タスクに含まれている入力データ(input_data_id='{input_data_id}')は、見つかりません。")
|
|
139
|
+
result[task_id] = 0.0
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
video_duration_second = input_data["system_metadata"]["input_duration"]
|
|
143
|
+
|
|
144
|
+
# 秒から分に変換
|
|
145
|
+
result[task_id] = video_duration_second / 60.0
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
102
149
|
def write_files(self, *, is_latest: bool = False, should_get_task_histories_one_of_each: bool = False, should_download_annotation_zip: bool = True) -> None:
|
|
103
150
|
"""
|
|
104
151
|
可視化に必要なファイルを作成します。
|
|
@@ -117,6 +164,8 @@ class VisualizationSourceFiles:
|
|
|
117
164
|
|
|
118
165
|
downloading_obj.download_task_json(self.project_id, dest_path=self.task_json_path, is_latest=is_latest, wait_options=wait_options)
|
|
119
166
|
|
|
167
|
+
downloading_obj.download_input_data_json(self.project_id, dest_path=self.input_data_json_path, is_latest=is_latest, wait_options=wait_options)
|
|
168
|
+
|
|
120
169
|
if should_download_annotation_zip:
|
|
121
170
|
downloading_obj.download_annotation_zip(
|
|
122
171
|
self.project_id,
|
|
@@ -10,7 +10,7 @@ from collections import defaultdict
|
|
|
10
10
|
from collections.abc import Collection, Sequence
|
|
11
11
|
from functools import partial
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Any
|
|
14
14
|
|
|
15
15
|
import bokeh
|
|
16
16
|
import numpy
|
|
@@ -20,7 +20,6 @@ from bokeh.models import LayoutDOM
|
|
|
20
20
|
from bokeh.models.widgets.markups import Div
|
|
21
21
|
from bokeh.plotting import figure
|
|
22
22
|
|
|
23
|
-
import annofabcli
|
|
24
23
|
import annofabcli.common.cli
|
|
25
24
|
from annofabcli.common.bokeh import convert_1d_figure_list_to_2d, create_pretext_from_metadata
|
|
26
25
|
from annofabcli.common.cli import (
|
|
@@ -56,11 +55,11 @@ def _get_y_axis_label(group_by: GroupBy) -> str:
|
|
|
56
55
|
raise RuntimeError(f"group_by='{group_by}'が対象外です。")
|
|
57
56
|
|
|
58
57
|
|
|
59
|
-
def convert_to_2d_figure_list(figures_dict: dict[tuple[str, str], list[figure]], *, ncols: int = 4) -> list[list[
|
|
58
|
+
def convert_to_2d_figure_list(figures_dict: dict[tuple[str, str], list[figure]], *, ncols: int = 4) -> list[list[LayoutDOM | None]]:
|
|
60
59
|
"""
|
|
61
60
|
grid layout用に2次元のfigureリストに変換する。
|
|
62
61
|
"""
|
|
63
|
-
row_list: list[list[
|
|
62
|
+
row_list: list[list[LayoutDOM | None]] = []
|
|
64
63
|
|
|
65
64
|
for (label_name, attribute_name), figure_list in figures_dict.items():
|
|
66
65
|
row_list.append([Div(text=f"<h3>ラベル名='{label_name}', 属性名='{attribute_name}'</h3>")])
|
|
@@ -68,7 +67,7 @@ def convert_to_2d_figure_list(figures_dict: dict[tuple[str, str], list[figure]],
|
|
|
68
67
|
for i in range(math.ceil(len(figure_list) / ncols)):
|
|
69
68
|
start = i * ncols
|
|
70
69
|
end = (i + 1) * ncols
|
|
71
|
-
row: list[
|
|
70
|
+
row: list[LayoutDOM | None] = []
|
|
72
71
|
row.extend(figure_list[start:end])
|
|
73
72
|
if len(row) < ncols:
|
|
74
73
|
row.extend([None] * (ncols - len(row)))
|
|
@@ -101,11 +100,11 @@ def plot_label_histogram(
|
|
|
101
100
|
group_by: GroupBy,
|
|
102
101
|
output_file: Path,
|
|
103
102
|
*,
|
|
104
|
-
prior_keys:
|
|
105
|
-
bin_width:
|
|
103
|
+
prior_keys: list[str] | None = None,
|
|
104
|
+
bin_width: int | None = None,
|
|
106
105
|
exclude_empty_value: bool = False,
|
|
107
106
|
arrange_bin_edge: bool = False,
|
|
108
|
-
metadata:
|
|
107
|
+
metadata: dict[str, Any] | None = None,
|
|
109
108
|
) -> None:
|
|
110
109
|
"""
|
|
111
110
|
ラベルごとのアノテーション数のヒストグラムを出力する。
|
|
@@ -152,7 +151,7 @@ def plot_label_histogram(
|
|
|
152
151
|
|
|
153
152
|
max_annotation_count = df.max(numeric_only=True).max()
|
|
154
153
|
|
|
155
|
-
figure_list_2d: list[list[
|
|
154
|
+
figure_list_2d: list[list[LayoutDOM | None]] = [
|
|
156
155
|
[
|
|
157
156
|
Div(text="<h3>アノテーション数の分布(ラベル名ごと)</h3>"),
|
|
158
157
|
]
|
|
@@ -203,11 +202,11 @@ def plot_attribute_histogram( # noqa: PLR0915
|
|
|
203
202
|
group_by: GroupBy,
|
|
204
203
|
output_file: Path,
|
|
205
204
|
*,
|
|
206
|
-
prior_keys:
|
|
207
|
-
bin_width:
|
|
205
|
+
prior_keys: list[AttributeValueKey] | None = None,
|
|
206
|
+
bin_width: int | None = None,
|
|
208
207
|
exclude_empty_value: bool = False,
|
|
209
208
|
arrange_bin_edge: bool = False,
|
|
210
|
-
metadata:
|
|
209
|
+
metadata: dict[str, Any] | None = None,
|
|
211
210
|
) -> None:
|
|
212
211
|
"""
|
|
213
212
|
属性値ごとのアノテーション数のヒストグラムを出力する。
|
|
@@ -256,7 +255,7 @@ def plot_attribute_histogram( # noqa: PLR0915
|
|
|
256
255
|
|
|
257
256
|
max_annotation_count = df.max(numeric_only=True).max()
|
|
258
257
|
|
|
259
|
-
figure_list_2d: list[list[
|
|
258
|
+
figure_list_2d: list[list[LayoutDOM | None]] = [
|
|
260
259
|
[
|
|
261
260
|
Div(text="<h3>アノテーション数の分布(属性値ごと)</h3>"),
|
|
262
261
|
]
|
|
@@ -322,10 +321,10 @@ class VisualizeAnnotationCount(CommandLine):
|
|
|
322
321
|
annotation_path: Path,
|
|
323
322
|
output_dir: Path,
|
|
324
323
|
*,
|
|
325
|
-
bin_width:
|
|
326
|
-
project_id:
|
|
327
|
-
target_task_ids:
|
|
328
|
-
task_query:
|
|
324
|
+
bin_width: int | None = None,
|
|
325
|
+
project_id: str | None = None,
|
|
326
|
+
target_task_ids: Collection[str] | None = None,
|
|
327
|
+
task_query: TaskQuery | None = None,
|
|
329
328
|
exclude_empty_value: bool = False,
|
|
330
329
|
arrange_bin_edge: bool = False,
|
|
331
330
|
) -> None:
|
|
@@ -333,8 +332,8 @@ class VisualizeAnnotationCount(CommandLine):
|
|
|
333
332
|
attributes_count_html = output_dir / "attributes_count.html"
|
|
334
333
|
|
|
335
334
|
# 集計対象の属性を、選択肢系の属性にする
|
|
336
|
-
annotation_specs:
|
|
337
|
-
non_selective_attribute_name_keys:
|
|
335
|
+
annotation_specs: AnnotationSpecs | None = None
|
|
336
|
+
non_selective_attribute_name_keys: list[AttributeNameKey] | None = None
|
|
338
337
|
if project_id is not None:
|
|
339
338
|
annotation_specs = AnnotationSpecs(self.service, project_id)
|
|
340
339
|
non_selective_attribute_name_keys = annotation_specs.non_selective_attribute_name_keys()
|
|
@@ -361,8 +360,8 @@ class VisualizeAnnotationCount(CommandLine):
|
|
|
361
360
|
else:
|
|
362
361
|
raise RuntimeError(f"group_by='{group_by}'が対象外です。")
|
|
363
362
|
|
|
364
|
-
label_keys:
|
|
365
|
-
attribute_value_keys:
|
|
363
|
+
label_keys: list[str] | None = None
|
|
364
|
+
attribute_value_keys: list[AttributeValueKey] | None = None
|
|
366
365
|
if annotation_specs is not None:
|
|
367
366
|
label_keys = annotation_specs.label_keys()
|
|
368
367
|
attribute_value_keys = annotation_specs.selective_attribute_value_keys()
|
|
@@ -406,7 +405,7 @@ class VisualizeAnnotationCount(CommandLine):
|
|
|
406
405
|
if not self.validate(args):
|
|
407
406
|
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
408
407
|
|
|
409
|
-
project_id:
|
|
408
|
+
project_id: str | None = args.project_id
|
|
410
409
|
if project_id is not None:
|
|
411
410
|
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
412
411
|
|
|
@@ -534,7 +533,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
534
533
|
VisualizeAnnotationCount(service, facade, args).main()
|
|
535
534
|
|
|
536
535
|
|
|
537
|
-
def add_parser(subparsers:
|
|
536
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
538
537
|
subcommand_name = "visualize_annotation_count"
|
|
539
538
|
subcommand_help = "各ラベル、各属性値のアノテーション数をヒストグラムで可視化します。"
|
|
540
539
|
description = "各ラベル、各属性値のアノテーション数をヒストグラムで可視化したファイルを出力します。"
|
|
@@ -9,7 +9,7 @@ from collections.abc import Collection, Sequence
|
|
|
9
9
|
from enum import Enum
|
|
10
10
|
from functools import partial
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Any
|
|
12
|
+
from typing import Any
|
|
13
13
|
|
|
14
14
|
import bokeh
|
|
15
15
|
import numpy
|
|
@@ -19,7 +19,6 @@ from bokeh.models import LayoutDOM
|
|
|
19
19
|
from bokeh.models.widgets.markups import Div
|
|
20
20
|
from bokeh.plotting import figure
|
|
21
21
|
|
|
22
|
-
import annofabcli
|
|
23
22
|
import annofabcli.common.cli
|
|
24
23
|
from annofabcli.common.bokeh import convert_1d_figure_list_to_2d, create_pretext_from_metadata
|
|
25
24
|
from annofabcli.common.cli import (
|
|
@@ -56,11 +55,11 @@ def plot_annotation_duration_histogram_by_label( # noqa: PLR0915
|
|
|
56
55
|
output_file: Path,
|
|
57
56
|
*,
|
|
58
57
|
time_unit: TimeUnit,
|
|
59
|
-
bin_width:
|
|
60
|
-
prior_keys:
|
|
58
|
+
bin_width: float | None = None,
|
|
59
|
+
prior_keys: list[str] | None = None,
|
|
61
60
|
exclude_empty_value: bool = False,
|
|
62
61
|
arrange_bin_edge: bool = False,
|
|
63
|
-
metadata:
|
|
62
|
+
metadata: dict[str, Any] | None = None,
|
|
64
63
|
) -> None:
|
|
65
64
|
"""
|
|
66
65
|
ラベルごとの区間アノテーションの長さのヒストグラムを出力します。
|
|
@@ -88,7 +87,7 @@ def plot_annotation_duration_histogram_by_label( # noqa: PLR0915
|
|
|
88
87
|
df = df / 60
|
|
89
88
|
return df
|
|
90
89
|
|
|
91
|
-
def get_histogram_range(df: pandas.DataFrame) ->
|
|
90
|
+
def get_histogram_range(df: pandas.DataFrame) -> tuple[float, float] | None:
|
|
92
91
|
if arrange_bin_edge:
|
|
93
92
|
return (
|
|
94
93
|
df.min(numeric_only=True).min(),
|
|
@@ -101,7 +100,7 @@ def plot_annotation_duration_histogram_by_label( # noqa: PLR0915
|
|
|
101
100
|
|
|
102
101
|
max_duration = df.max(numeric_only=True).max()
|
|
103
102
|
|
|
104
|
-
figure_list_2d: list[list[
|
|
103
|
+
figure_list_2d: list[list[LayoutDOM | None]] = [
|
|
105
104
|
[
|
|
106
105
|
Div(text="<h3>区間アノテーションの長さの分布(ラベル名ごと)</h3>"),
|
|
107
106
|
]
|
|
@@ -165,11 +164,11 @@ def plot_annotation_duration_histogram_by_attribute( # noqa: PLR0915
|
|
|
165
164
|
output_file: Path,
|
|
166
165
|
*,
|
|
167
166
|
time_unit: TimeUnit,
|
|
168
|
-
bin_width:
|
|
169
|
-
prior_keys:
|
|
167
|
+
bin_width: float | None = None,
|
|
168
|
+
prior_keys: list[AttributeValueKey] | None = None,
|
|
170
169
|
exclude_empty_value: bool = False,
|
|
171
170
|
arrange_bin_edge: bool = False,
|
|
172
|
-
metadata:
|
|
171
|
+
metadata: dict[str, Any] | None = None,
|
|
173
172
|
) -> None:
|
|
174
173
|
"""
|
|
175
174
|
属性値ごとの区間アノテーションの長さのヒストグラムを出力します。
|
|
@@ -198,7 +197,7 @@ def plot_annotation_duration_histogram_by_attribute( # noqa: PLR0915
|
|
|
198
197
|
df = df / 60
|
|
199
198
|
return df
|
|
200
199
|
|
|
201
|
-
def get_histogram_range(df: pandas.DataFrame) ->
|
|
200
|
+
def get_histogram_range(df: pandas.DataFrame) -> tuple[float, float] | None:
|
|
202
201
|
if arrange_bin_edge:
|
|
203
202
|
return (
|
|
204
203
|
df.min(numeric_only=True).min(),
|
|
@@ -224,7 +223,7 @@ def plot_annotation_duration_histogram_by_attribute( # noqa: PLR0915
|
|
|
224
223
|
max_duration = df.max(numeric_only=True).max()
|
|
225
224
|
x_axis_label = "区間アノテーションの長さ[分]" if time_unit == TimeUnit.MINUTE else "区間アノテーションの長さ[秒]"
|
|
226
225
|
|
|
227
|
-
figure_list_2d: list[list[
|
|
226
|
+
figure_list_2d: list[list[LayoutDOM | None]] = [
|
|
228
227
|
[
|
|
229
228
|
Div(text="<h3>区間アノテーションの長さの分布(属性値ごと)</h3>"),
|
|
230
229
|
]
|
|
@@ -290,10 +289,10 @@ class VisualizeAnnotationDuration(CommandLine):
|
|
|
290
289
|
output_dir: Path,
|
|
291
290
|
time_unit: TimeUnit,
|
|
292
291
|
*,
|
|
293
|
-
bin_width:
|
|
294
|
-
project_id:
|
|
295
|
-
target_task_ids:
|
|
296
|
-
task_query:
|
|
292
|
+
bin_width: int | None = None,
|
|
293
|
+
project_id: str | None = None,
|
|
294
|
+
target_task_ids: Collection[str] | None = None,
|
|
295
|
+
task_query: TaskQuery | None = None,
|
|
297
296
|
exclude_empty_value: bool = False,
|
|
298
297
|
arrange_bin_edge: bool = False,
|
|
299
298
|
) -> None:
|
|
@@ -301,8 +300,8 @@ class VisualizeAnnotationDuration(CommandLine):
|
|
|
301
300
|
duration_by_attribute_html = output_dir / "annotation_duration_by_attribute.html"
|
|
302
301
|
|
|
303
302
|
# 集計対象の属性を、選択肢系の属性にする
|
|
304
|
-
annotation_specs:
|
|
305
|
-
non_selective_attribute_name_keys:
|
|
303
|
+
annotation_specs: AnnotationSpecs | None = None
|
|
304
|
+
non_selective_attribute_name_keys: list[AttributeNameKey] | None = None
|
|
306
305
|
if project_id is not None:
|
|
307
306
|
annotation_specs = AnnotationSpecs(self.service, project_id, annotation_type=DefaultAnnotationType.RANGE.value)
|
|
308
307
|
non_selective_attribute_name_keys = annotation_specs.non_selective_attribute_name_keys()
|
|
@@ -315,8 +314,8 @@ class VisualizeAnnotationDuration(CommandLine):
|
|
|
315
314
|
task_query=task_query,
|
|
316
315
|
)
|
|
317
316
|
|
|
318
|
-
label_keys:
|
|
319
|
-
attribute_value_keys:
|
|
317
|
+
label_keys: list[str] | None = None
|
|
318
|
+
attribute_value_keys: list[AttributeValueKey] | None = None
|
|
320
319
|
if annotation_specs is not None:
|
|
321
320
|
label_keys = annotation_specs.label_keys()
|
|
322
321
|
attribute_value_keys = annotation_specs.selective_attribute_value_keys()
|
|
@@ -359,7 +358,7 @@ class VisualizeAnnotationDuration(CommandLine):
|
|
|
359
358
|
if not self.validate(args):
|
|
360
359
|
sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
|
|
361
360
|
|
|
362
|
-
project_id:
|
|
361
|
+
project_id: str | None = args.project_id
|
|
363
362
|
if project_id is not None:
|
|
364
363
|
super().validate_project(project_id, project_member_roles=[ProjectMemberRole.OWNER, ProjectMemberRole.TRAINING_DATA_USER])
|
|
365
364
|
project, _ = self.service.api.get_project(project_id)
|
|
@@ -487,7 +486,7 @@ def main(args: argparse.Namespace) -> None:
|
|
|
487
486
|
VisualizeAnnotationDuration(service, facade, args).main()
|
|
488
487
|
|
|
489
488
|
|
|
490
|
-
def add_parser(subparsers:
|
|
489
|
+
def add_parser(subparsers: argparse._SubParsersAction | None = None) -> argparse.ArgumentParser:
|
|
491
490
|
subcommand_name = "visualize_annotation_duration"
|
|
492
491
|
subcommand_help = "ラベルごとまたは属性値ごとに区間アノテーションの長さをヒストグラムで可視化したファイルを出力します。"
|
|
493
492
|
epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
|