Skip to content

Commit

Permalink
[annotation import] 標準プラグインを使った3次元プロジェクトにもアノテーションをインポートできるようにする (#907)
Browse files Browse the repository at this point in the history
* 暫定対応をコミット

* poetry update

* 不要なクラスを削除

* fillnaする列を限定させる

* [statistics visualize] temporaryディレクトリを削除するようにする

* poetry update

* 3次元アノテーションのimport

* [comment list] JSON構造を出力する

* format

* ログの修正

* ログメッセージの修正
  • Loading branch information
yuji38kwmt authored Nov 30, 2022
1 parent 12e1734 commit 0974a0b
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 148 deletions.
46 changes: 29 additions & 17 deletions annofabcli/annotation/import_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
lazy_parse_simple_annotation_dir_by_task,
lazy_parse_simple_annotation_zip_by_task,
)
from annofabapi.plugin import ThreeDimensionAnnotationType
from annofabapi.utils import can_put_annotation, str_now
from dataclasses_json import DataClassJsonMixin
from more_itertools import first_true
Expand Down Expand Up @@ -102,7 +103,7 @@ def get_label_info_from_label_name(self, label_name: str) -> Optional[LabelV1]:
if label_name_en is not None and label_name_en == label_name:
return label

logger.warning(f"アノテーション仕様に label_name={label_name} のラベルが存在しません。")
logger.warning(f"アノテーション仕様に label_name='{label_name}' のラベルが存在しません。")
return None

def _get_additional_data_from_attribute_name(
Expand Down Expand Up @@ -140,33 +141,44 @@ def _get_3dpc_segment_data_uri(cls, annotation_data: Dict[str, Any]) -> str:

@classmethod
def _is_3dpc_segment_label(cls, label_info: Dict[str, Any]) -> bool:
if label_info["annotation_type"] != DefaultAnnotationType.CUSTOM.value:
return False

metadata = label_info["metadata"]
if metadata.get("type") == "SEGMENT":
"""
3次元のセグメントかどうか
"""
# 理想はプラグイン情報を見て、3次元アノテーションの種類かどうか判定した方がよい
# が、当分は文字列判定だけでも十分なので、文字列で判定する
if label_info["annotation_type"] in {
ThreeDimensionAnnotationType.INSTANCE_SEGMENT.value,
ThreeDimensionAnnotationType.SEMANTIC_SEGMENT.value,
}:
return True

# 標準の3次元アノテーション仕様用のプラグインを利用していない場合
# TODO すべての3次元プロジェクトが、標準の3次元アノテーション仕様用のプラグインを利用するようになったら、この処理を削除する
if label_info["annotation_type"] == DefaultAnnotationType.CUSTOM.value:
metadata = label_info["metadata"]
if metadata.get("type") == "SEGMENT":
return True

return False

@classmethod
def _get_data_holding_type_from_data(cls, label_info: Dict[str, Any]) -> AnnotationDataHoldingType:

annotation_type = label_info["annotation_type"]
if annotation_type in [DefaultAnnotationType.SEGMENTATION.value, DefaultAnnotationType.SEGMENTATION_V2.value]:
return AnnotationDataHoldingType.OUTER

# TODO: 3dpc editorに依存したコード。annofab側でSimple Annotationのフォーマットが改善されたら、このコードを削除する
if annotation_type == DefaultAnnotationType.CUSTOM.value:
if cls._is_3dpc_segment_label(label_info):
return AnnotationDataHoldingType.OUTER
if cls._is_3dpc_segment_label(label_info):
return AnnotationDataHoldingType.OUTER

return AnnotationDataHoldingType.INNER

def _to_additional_data_list(self, attributes: Dict[str, Any], label_info: LabelV1) -> List[AdditionalDataV1]:
additional_data_list: List[AdditionalDataV1] = []
for key, value in attributes.items():
specs_additional_data = self._get_additional_data_from_attribute_name(key, label_info)
if specs_additional_data is None:
logger.warning(f"アノテーション仕様に attribute_name={key} が存在しません。")
logger.warning(f"アノテーション仕様に attribute_name='{key}' が存在しません。")
continue

additional_data = AdditionalDataV1(
Expand All @@ -191,7 +203,7 @@ def _to_additional_data_list(self, attributes: Dict[str, Any], label_info: Label
elif additional_data_type in [AdditionalDataDefinitionType.CHOICE, AdditionalDataDefinitionType.SELECT]:
additional_data.choice = self._get_choice_id_from_name(value, specs_additional_data["choices"])
else:
logger.warning(f"additional_data_type={additional_data_type}が不正です。")
logger.warning(f"additional_data_type='{additional_data_type}'が不正です。")
continue

additional_data_list.append(additional_data)
Expand Down Expand Up @@ -275,7 +287,7 @@ def parser_to_request_body(
except Exception:
logger.warning(
f"{parser.task_id}/{parser.input_data_id} :: アノテーションをrequest_bodyに変換するのに失敗しました。 :: "
f"annotation_id={detail.annotation_id}, label={detail.label}",
f"annotation_id='{detail.annotation_id}', label='{detail.label}'",
exc_info=True,
)
continue
Expand Down Expand Up @@ -322,7 +334,7 @@ def parser_to_request_body_with_merge(
except Exception:
logger.warning(
f"{parser.task_id}/{parser.input_data_id} :: アノテーションをrequest_bodyに変換するのに失敗しました。 :: "
f"annotation_id={detail.annotation_id}, label={detail.label}",
f"annotation_id='{detail.annotation_id}', label='{detail.label}'",
exc_info=True,
)
continue
Expand Down Expand Up @@ -399,7 +411,7 @@ def put_annotation_for_task(self, task_parser: SimpleAnnotationParserByTask) ->
success_count += 1
except Exception: # pylint: disable=broad-except
logger.warning(
f"task_id={parser.task_id}, input_data_id={parser.input_data_id} の" f"アノテーションのインポートに失敗しました。",
f"task_id='{parser.task_id}', input_data_id='{parser.input_data_id}' の" f"アノテーションのインポートに失敗しました。",
exc_info=True,
)

Expand Down Expand Up @@ -476,7 +488,7 @@ def execute_task_wrapper(
try:
return self.execute_task(task_parser, task_index=task_index)
except Exception: # pylint: disable=broad-except
logger.warning(f"task_id={task_parser.task_id} のアノテーションのインポートに失敗しました。", exc_info=True)
logger.warning(f"task_id='{task_parser.task_id}' のアノテーションのインポートに失敗しました。", exc_info=True)
return False

def main(
Expand Down Expand Up @@ -514,7 +526,7 @@ def get_iter_task_parser_from_task_ids(
if result:
success_count += 1
except Exception:
logger.warning(f"task_id={task_parser.task_id} のアノテーションのインポートに失敗しました。", exc_info=True)
logger.warning(f"task_id='{task_parser.task_id}' のアノテーションのインポートに失敗しました。", exc_info=True)
continue
finally:
task_count += 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def main(
)
continue

if not self.confirm_processing(f"属性ID='{attribute_id}'を'{attribute_name_en}'に変更しますか?"):
if not self.confirm_processing(f"属性ID='{attribute_id}'を'{attribute_name_en}'に変更したアノテーション仕様のJSONを出力しますか?"):
continue

attribute["additional_data_definition_id"] = attribute_name_en
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ def main(

choice_list = attribute["choices"]

if not self.confirm_processing(f"属性英語名='{attribute_name_en}'の{len(choice_list)}個の選択肢IDを、選択肢英語名に変更しますか?"):
if not self.confirm_processing(
f"属性英語名='{attribute_name_en}'の{len(choice_list)}個の選択肢IDを、" "選択肢英語名に変更したアノテーション仕様のJSONを出力しますか?"
):
continue

replaced_choice_id_count = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def main(self, annotation_specs: dict[str, Any], *, target_label_names: Optional
)
continue

if not self.confirm_processing(f"label_id='{label_id}'を'{label_name_en}'に変更しますか?"):
if not self.confirm_processing(f"label_id='{label_id}'を'{label_name_en}'に変更したアノテーション仕様のJSONを出力しますか?"):
continue

# ラベル内のlabel_idを変更する
Expand Down
4 changes: 3 additions & 1 deletion annofabcli/comment/list_all_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any, Collection, Optional

import annofabapi
import pandas
from annofabapi.models import CommentType

import annofabcli
Expand Down Expand Up @@ -85,7 +86,8 @@ def main(self):
)

logger.info(f"コメントの件数: {len(comment_list)}")
self.print_according_to_format(comment_list)
df = pandas.json_normalize(comment_list)
self.print_according_to_format(df)


def parse_args(parser: argparse.ArgumentParser):
Expand Down
4 changes: 3 additions & 1 deletion annofabcli/comment/list_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
from typing import List, Optional

import pandas
import requests
from annofabapi.models import Comment, CommentType

Expand Down Expand Up @@ -53,7 +54,8 @@ def list_comments(

visualize = AddProps(self.service, project_id)
all_comments = [visualize.add_properties_to_comment(e) for e in all_comments]
self.print_according_to_format(all_comments)
df = pandas.json_normalize(all_comments)
self.print_according_to_format(df)

def main(self):
args = self.args
Expand Down
Empty file.
22 changes: 22 additions & 0 deletions annofabcli/common/annofab/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import annofabapi
from annofabapi.plugin import EditorPluginId, ExtendSpecsPluginId


class Project:
"""
Annofabプロジェクトに対応するクラス
"""

def __init__(self, service: annofabapi.Resource, project_id: str) -> None:
project, _ = service.api.get_project(project_id)
self.project = project

def is_3d_annotation_editor_available(self):
"""標準の3次元アノテーションエディタを利用できるか"""
configuration = self.project["configuration"]
return configuration["plugin_id"] == EditorPluginId.THREE_DIMENSION

def is_3d_annotation_type_available(self):
"""3次元のアノテーション種類を利用できるか"""
configuration = self.project["configuration"]
return configuration["extended_specs_plugin_id"] == ExtendSpecsPluginId.THREE_DIMENSION
17 changes: 0 additions & 17 deletions annofabcli/common/dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from dataclasses import dataclass
from typing import Any, Dict

from dataclasses_json import DataClassJsonMixin

Expand All @@ -15,19 +14,3 @@ class WaitOptions(DataClassJsonMixin):

max_tries: int
"""最大ジョブに何回アクセスするか"""


@dataclass
class SimpleAnnotationDetail4Import(DataClassJsonMixin):
"""
アノテーションインポート用の ``SimpleAnnotationDetail`` クラス。
"""

label: str
"""アノテーション仕様のラベル名(英語)です。 """

data: Dict[str, Any]
""""""

attributes: Dict[str, Any]
"""キーに属性の名前、値に各属性の値が入った辞書構造です。 """
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,16 @@ def _get_continuous_date_dataframe(self) -> pandas.DataFrame:
df = df.merge(self.df, how="left", on=["date", "user_id"], suffixes=(None, "_tmp"))[self.columns]

# 作業時間関係の列を0で埋める
df.fillna(0, inplace=True)
df.fillna(
{
"actual_worktime_hour": 0,
"monitored_worktime_hour": 0,
"monitored_annotation_worktime_hour": 0,
"monitored_inspection_worktime_hour": 0,
"monitored_acceptance_worktime_hour": 0,
},
inplace=True,
)

return df

Expand Down
70 changes: 35 additions & 35 deletions annofabcli/statistics/visualize_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,44 +436,44 @@ def main(self):
output_only_text=args.output_only_text,
)

if len(project_id_list) == 1:
main_obj.visualize_statistics(project_id_list[0], root_output_dir)
if args.merge:
logger.warning(f"出力した統計情報は1件以下なので、`merge`ディレクトリを出力しません。")
if len(project_id_list) == 1:
main_obj.visualize_statistics(project_id_list[0], root_output_dir)
if args.merge:
logger.warning(f"出力した統計情報は1件以下なので、`merge`ディレクトリを出力しません。")

else:
# project_idが複数指定された場合は、project_titleのディレクトリに統計情報を出力する
output_project_dir_list = main_obj.visualize_statistics_for_project_list(
project_id_list=project_id_list,
root_output_dir=root_output_dir,
parallelism=args.parallelism,
)

if args.merge:
merge_visualization_dir(
project_dir_list=[ProjectDir(e) for e in output_project_dir_list],
output_project_dir=ProjectDir(root_output_dir / "merge"),
user_id_list=user_id_list,
minimal_output=args.minimal,
)

if len(output_project_dir_list) > 0:
project_dir_list = [ProjectDir(e) for e in output_project_dir_list]
project_performance = ProjectPerformance.from_project_dirs(project_dir_list)
project_performance.to_csv(root_output_dir / "プロジェクトごとの生産性と品質.csv")

project_actual_worktime = ProjectWorktimePerMonth.from_project_dirs(
project_dir_list, WorktimeColumn.ACTUAL_WORKTIME_HOUR
)
project_actual_worktime.to_csv(root_output_dir / "プロジェクごとの毎月の実績作業時間.csv")

project_monitored_worktime = ProjectWorktimePerMonth.from_project_dirs(
project_dir_list, WorktimeColumn.MONITORED_WORKTIME_HOUR
else:
# project_idが複数指定された場合は、project_titleのディレクトリに統計情報を出力する
output_project_dir_list = main_obj.visualize_statistics_for_project_list(
project_id_list=project_id_list,
root_output_dir=root_output_dir,
parallelism=args.parallelism,
)
project_monitored_worktime.to_csv(root_output_dir / "プロジェクごとの毎月の計測作業時間.csv")

else:
logger.warning(f"出力した統計情報は0件なので、`プロジェクトごとの生産性と品質.csv`を出力しません。")
if args.merge:
merge_visualization_dir(
project_dir_list=[ProjectDir(e) for e in output_project_dir_list],
output_project_dir=ProjectDir(root_output_dir / "merge"),
user_id_list=user_id_list,
minimal_output=args.minimal,
)

if len(output_project_dir_list) > 0:
project_dir_list = [ProjectDir(e) for e in output_project_dir_list]
project_performance = ProjectPerformance.from_project_dirs(project_dir_list)
project_performance.to_csv(root_output_dir / "プロジェクトごとの生産性と品質.csv")

project_actual_worktime = ProjectWorktimePerMonth.from_project_dirs(
project_dir_list, WorktimeColumn.ACTUAL_WORKTIME_HOUR
)
project_actual_worktime.to_csv(root_output_dir / "プロジェクごとの毎月の実績作業時間.csv")

project_monitored_worktime = ProjectWorktimePerMonth.from_project_dirs(
project_dir_list, WorktimeColumn.MONITORED_WORKTIME_HOUR
)
project_monitored_worktime.to_csv(root_output_dir / "プロジェクごとの毎月の計測作業時間.csv")

else:
logger.warning(f"出力した統計情報は0件なので、`プロジェクトごとの生産性と品質.csv`を出力しません。")


def main(args):
Expand Down
Loading

0 comments on commit 0974a0b

Please sign in to comment.