Skip to content

Commit 7b04de4

Browse files
committed
example toolsを追加
1 parent cb7971c commit 7b04de4

File tree

6 files changed

+317
-4
lines changed

6 files changed

+317
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* 「画像を入力データとして登録する」機能など、APIを組み合わせた機能を利用できます。
1616

1717
# Requirements
18-
* python 3.6+
18+
* Python 3.6+
1919

2020
# Install
2121

annofabapi/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99

1010
import dateutil
11+
import dateutil.tz
1112
import requests
1213

1314

examples/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22
annofabapiモジュールを使ったサンプルコードです。
33
pythonコマンドを使ってCLIとして利用できます。
44

5+
# Requirements
6+
* Python 3.6+
7+
* 最新のannofabapiモジュール
8+
59
# 使い方
610

11+
## AnnoFabの認証情報の設定
12+
`.netrc`ファイルにAnnoFabの認証情報を記載してください。
13+
詳しくは[../README.md](../README.md)を参照してください。
14+
715
## Pipenvを使う場合
816

917
```
@@ -41,6 +49,8 @@ $ python invite_user_to_projects.py -h
4149

4250
## invite_user_to_projects.py
4351
複数のプロジェクトに、ユーザを招待します。
52+
各プロジェクトのオーナ権限を持つユーザで実行してください。
53+
※オーナ権限を持たないプロジェクトの場合はスキップします
4454

4555
```
4656
# ORG組織配下のすべてのプロジェクトに、user1をownerロールで割り当てる
@@ -52,7 +62,18 @@ $ python invite_user_to_projects.py --user_id user1 --role owner --project_id pr
5262

5363
## cancel_acceptance.py
5464
受け入れ完了タスクを、受け入れ取り消しにします。
65+
アノテーション仕様を途中で変更したときなどに、利用します。
66+
67+
対象のタスクは、以下のようなtask_idの一覧が記載されたファイルで指定します。
68+
69+
```
70+
task_id1
71+
task_id2
72+
task_id3
73+
...
74+
```
5575

76+
プロジェクトのオーナ権限を持つユーザで実行してください。
5677

5778
```
5879
# prj1プロジェクトのタスクを、受け入れ取り消しにする。再度受け入れを担当させるユーザは未担当
@@ -62,3 +83,12 @@ $ python cancel_acceptance.py --project_id prj1 --task_id_file file
6283
$ python cancel_acceptance.py --project_id prj1 --task_id_file file --user_id user1
6384
```
6485

86+
## reject_tasks_with_adding_comment.py
87+
アノテーション仕様を途中で変更したときなどに、利用します。
88+
89+
プロジェクトのチェッカー以上の権限を持つユーザで実行してください。
90+
91+
```
92+
# prj1プロジェクトのタスクを、差し戻す
93+
$ python cancel_acceptance.py --project_id prj1 --task_id_file file --comment "auto comment at tool"
94+
```

examples/cancel_acceptance.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import annofabapi
1212

13-
from example_utils import read_lines
13+
from example_utils import read_lines, ExamplesWrapper
1414

1515
logging_formatter = '%(levelname)s : %(asctime)s : %(name)s : %(funcName)s : %(message)s'
1616
logging.basicConfig(format=logging_formatter)
@@ -33,8 +33,7 @@ def cancel_acceptance(project_id: str, task_id_list: List[str], acceptor_user_id
3333
task_id_list: 受け入れ取り消しするtask_id_list
3434
acceptor_user_id: 再度受入を担当させたいユーザのuser_id
3535
"""
36-
acceptor_account_id = get_account_id_from_user_id(project_id,
37-
acceptor_user_id) if acceptor_user_id is not None else None
36+
acceptor_account_id = examples_wrapper.get_account_id_from_user_id(project_id, acceptor_user_id)
3837

3938
for task_id in task_id_list:
4039
try:
@@ -88,6 +87,7 @@ def main(args):
8887
logger.info(args)
8988

9089
service = annofabapi.build_from_netrc()
90+
examples_wrapper = ExamplesWrapper(service)
9191

9292
try:
9393
main(args)

examples/example_utils.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,131 @@
11
from typing import Any, Dict, List, Optional, Tuple, Union
22

3+
import annofabapi
34

45
def read_lines(filepath: str) -> List[str]:
56
"""ファイルを行単位で読み込む。改行コードを除く"""
67
with open(filepath) as f:
78
lines = f.readlines()
89
return [e.rstrip('\r\n') for e in lines]
910

11+
class ExamplesWrapper:
12+
"""
13+
Exampleツール用のWrapperクラス
14+
Returns:
15+
16+
"""
17+
def __init__(self, service: annofabapi.Resource):
18+
self.service = service
19+
20+
def get_account_id_last_annotation_phase(self, task_histories: List[Dict[str, Any]]):
21+
"""
22+
タスク履歴の最後のannotation phaseを担当したaccount_idを取得する. なければNoneを返す
23+
Args:
24+
task_histories:
25+
26+
Returns:
27+
28+
29+
"""
30+
annotation_histories = [e for e in task_histories if e["phase"] == "annotation"]
31+
if len(annotation_histories) > 0:
32+
last_history = annotation_histories[-1]
33+
return last_history["account_id"]
34+
else:
35+
return None
36+
37+
def get_account_id_from_user_id(self, project_id: str, user_id: str):
38+
"""
39+
usre_idからaccount_idを取得する
40+
Args:
41+
project_id:
42+
user_id:
43+
44+
Returns:
45+
account_id
46+
47+
"""
48+
member, _ = self.service.api.get_project_member(project_id, user_id)
49+
return member['account_id']
50+
51+
52+
def change_operator_of_task(self, project_id: str, task_id: str, account_id: str) -> Dict[str, Any]:
53+
"""
54+
タスクの担当者を変更する
55+
Args:
56+
self:
57+
project_id:
58+
task_id:
59+
account_id:
60+
61+
Returns:
62+
変更後のtask情報
63+
64+
"""
65+
task, _ = self.service.api.get_task(project_id, task_id)
66+
67+
req = {
68+
"status": "not_started",
69+
"account_id": account_id,
70+
"last_updated_datetime": task["updated_datetime"],
71+
72+
}
73+
return self.service.api.operate_task(project_id, task_id, request_body=req)[0]
74+
75+
def change_to_working_phase(self, project_id: str, task_id: str, account_id: str) -> Dict[str, Any]:
76+
"""
77+
タスクを作業中に変更する
78+
Args:
79+
self:
80+
project_id:
81+
task_id:
82+
account_id:
83+
84+
Returns:
85+
変更後のtask情報
86+
87+
"""
88+
task, _ = self.service.api.get_task(project_id, task_id)
89+
90+
req = {
91+
"status": "working",
92+
"account_id": account_id,
93+
"last_updated_datetime": task["updated_datetime"],
94+
95+
}
96+
return self.service.api.operate_task(project_id, task_id, request_body=req)[0]
97+
98+
99+
def reject_task(self, project_id: str, task_id: str, account_id: str):
100+
"""
101+
タスクを差し戻したあと、最後のannotation phase担当者に割り当てる。
102+
Args:
103+
task_id:
104+
account_id: 差し戻すときのユーザのaccount_id
105+
106+
Returns:
107+
変更あとのtask情報
108+
109+
"""
110+
111+
# タスクを差し戻す
112+
task, _ = self.service.api.get_task(project_id, task_id)
113+
annotator_account_id = self.get_account_id_last_annotation_phase(task["histories_by_phase"])
114+
115+
req_reject = {
116+
"status": "rejected",
117+
"account_id": account_id,
118+
"last_updated_datetime": task["updated_datetime"],
119+
120+
}
121+
rejected_task, _ = self.service.api.operate_task(project_id, task_id, request_body=req_reject)
122+
123+
req_change_operator = {
124+
"status": "not_started",
125+
"account_id": annotator_account_id,
126+
"last_updated_datetime": rejected_task["updated_datetime"],
127+
128+
}
129+
updated_task, _ = self.service.api.operate_task(project_id, task["task_id"], request_body=req_change_operator)
130+
return updated_task
131+
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
検査コメントを付与してタスクを差し戻します。
3+
"""
4+
5+
import argparse
6+
import logging
7+
import time
8+
from typing import Any, Dict, List, Optional, Tuple, Union
9+
10+
import requests
11+
import uuid
12+
import annofabapi
13+
import annofabapi.utils
14+
15+
from example_utils import read_lines, ExamplesWrapper
16+
17+
logging_formatter = '%(levelname)s : %(asctime)s : %(name)s : %(funcName)s : %(message)s'
18+
logging.basicConfig(format=logging_formatter)
19+
logging.getLogger("annofabapi").setLevel(level=logging.DEBUG)
20+
21+
logger = logging.getLogger(__name__)
22+
logger.setLevel(level=logging.DEBUG)
23+
24+
def add_inspection_comment(project_id: str, task: Dict[str, Any], inspection_comment: str, commenter_account_id: str):
25+
"""
26+
先頭画像の左上に検査コメントを付与する
27+
Args:
28+
project_id:
29+
task:
30+
inspection_comment:
31+
commenter_account_id:
32+
33+
Returns:
34+
更新した検査コメントの一覧
35+
36+
"""
37+
first_input_data_id = task["input_data_id_list"][0]
38+
req_inspection = [{
39+
"data": {
40+
"project_id": project_id,
41+
"comment": inspection_comment,
42+
"task_id": task["task_id"],
43+
"input_data_id": first_input_data_id,
44+
"inspection_id": str(uuid.uuid4()),
45+
"phase": task["phase"],
46+
"commenter_account_id": commenter_account_id,
47+
"data": {
48+
"x": 0,
49+
"y": 0,
50+
"_type": "Point"
51+
},
52+
"status": "annotator_action_required",
53+
"created_datetime": annofabapi.utils.str_now()
54+
},
55+
"_type": "Put",
56+
57+
}]
58+
59+
return service.api.batch_update_inspections(project_id, task["task_id"], first_input_data_id, request_body=req_inspection)[0]
60+
61+
62+
63+
def reject_tasks_with_adding_comment(project_id: str, task_id_list: List[str], inspection_comment: str, commenter_user_id: str):
64+
"""
65+
検査コメントを付与して、タスクを差し戻す
66+
Args:
67+
project_id:
68+
task_id_list:
69+
inspection_comment: 検査コメントの中身
70+
commenter_user_id: 検査コメントを付与して、タスクを差し戻すユーザのuser_id
71+
"""
72+
73+
commenter_account_id = examples_wrapper.get_account_id_from_user_id(project_id, commenter_user_id)
74+
75+
for task_id in task_id_list:
76+
task, _ = service.api.get_task(project_id, task_id)
77+
logger.debug(f"task_id = {task_id}, {task['status']}, {task['phase']}")
78+
if task["phase"] == "annotation":
79+
logger.warning(f"task_id = {task_id} はannofation phaseのため、差し戻しできません。")
80+
continue
81+
82+
try:
83+
# 担当者を変更して、作業中にする
84+
examples_wrapper.change_operator_of_task(project_id, task_id, commenter_account_id)
85+
logger.debug(f"task_id = {task_id}, {commenter_user_id}に担当者変更 完了")
86+
87+
examples_wrapper.change_to_working_phase(project_id, task_id, commenter_account_id)
88+
logger.debug(f"task_id = {task_id}, working statusに変更 完了")
89+
except requests.exceptions.HTTPError as e:
90+
logger.error(e)
91+
logger.info(f"task_id = {task_id} の担当者変更 or 作業phaseへの変更に失敗")
92+
continue
93+
94+
# 少し待たないと検査コメントが登録できない場合があるため
95+
time.sleep(3)
96+
try:
97+
# 検査コメントを付与する
98+
add_inspection_comment(project_id, task, inspection_comment, commenter_account_id)
99+
logger.debug(f"task_id = {task_id}, 検査コメントの付与 完了")
100+
except requests.exceptions.HTTPError as e:
101+
logger.error(e)
102+
logger.info(f"task_id = {task_id} 検査コメントの付与に失敗")
103+
continue
104+
105+
try:
106+
# タスクを差し戻す
107+
rejected_task = examples_wrapper.reject_task(project_id, task_id, commenter_account_id)
108+
109+
except requests.exceptions.HTTPError as e:
110+
logger.error(e)
111+
logger.info(f"task_id = {task_id} タスクの差し戻しに失敗")
112+
continue
113+
114+
logger.info(f"task_id = {task_id} の差し戻し完了")
115+
return
116+
117+
118+
119+
120+
def main(args):
121+
task_id_list = read_lines(args.task_id_file)
122+
user_id = service.api.login_user_id
123+
reject_tasks_with_adding_comment(args.project_id, task_id_list, args.comment, user_id)
124+
125+
126+
if __name__ == "__main__":
127+
parser = argparse.ArgumentParser(
128+
description="検査コメントを付与してタスクを差し戻します。検査コメントは先頭の画像の左上(0,0)に付与します。AnnoFab認証情報は`.netrc`に記載すること")
129+
parser.add_argument('--project_id',
130+
metavar='project_id',
131+
type=str,
132+
required=True,
133+
help='対象のプロジェクトのproject_id')
134+
135+
parser.add_argument(
136+
'--task_id_file',
137+
metavar='file',
138+
type=str,
139+
required=True,
140+
help=
141+
'task_idの一覧が記載されたファイル。task_idは改行(LF or CRLF)で区切る。')
142+
143+
parser.add_argument('--comment',
144+
metavar='comment',
145+
type=str,
146+
required=True,
147+
help='差し戻すときに付与する検査コメントの中身')
148+
149+
args = parser.parse_args()
150+
151+
logger.info(args)
152+
153+
service = annofabapi.build_from_netrc()
154+
examples_wrapper = ExamplesWrapper(service)
155+
156+
try:
157+
main(args)
158+
159+
except Exception as e:
160+
logger.exception(e)

0 commit comments

Comments
 (0)