├── .gitignore
├── LICENSE
├── README.md
├── easyturk
├── __init__.py
├── easyturk.py
├── evaluate.py
├── interface.py
├── render.py
└── templates
│ ├── annotate_bbox.html
│ ├── easyturk.html
│ ├── evaluation
│ ├── easyturk.html
│ ├── home.html
│ └── verify_relationship.html
│ ├── javascript
│ ├── bbox-drawer.js
│ ├── bleu.js
│ └── iou.js
│ ├── verify_bbox.html
│ ├── verify_caption.html
│ ├── verify_question_answer.html
│ ├── verify_relationship.html
│ └── write_caption.html
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | env/
2 | *.pyc
3 | rendered_template.html
4 | easyturk/rendered_template.html
5 | easyturk/data
6 | easyturk/scripts
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | COPYRIGHT
2 |
3 | Copyright (c) 2019, Ranjay Krishna.
4 | All rights reserved.
5 |
6 | Each contributor holds copyright over their respective contributions.
7 | The project versioning (Git) records all such contribution source information.
8 |
9 | LICENSE
10 |
11 | The MIT License (MIT)
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | SOFTWARE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EasyTurk: A Wrapper for Custom AMT Tasks
2 |
3 | EasyTurk is an easy to use wrapper to launch and get responses for custom Amazon Mechanical Turk tasks.
4 |
5 | If you are using this repository for your research, please use the following citation:
6 |
7 | ```
8 | @misc{krishna2019easyturk,
9 | author = {Krishna, Ranjay},
10 | title = {EasyTurk: A Wrapper for Custom AMT Tasks},
11 | year = {2019},
12 | publisher = {GitHub},
13 | journal = {GitHub repository},
14 | howpublished = {\url{https://github.com/ranjaykrishna/easyturk}}
15 | }
16 | ```
17 |
18 | #### Why should you use EasyTurk?
19 |
20 | EasyTurk is meant for Amazon Mechanical Turk requestors who want to programmatically create, launch and retrieve Amazon Mechanical Turk microtasks. EasyTurk allows you to build custom html/javascript tasks and control your tasks using an easy python wrapper.
21 |
22 |
23 | #### Dependencies
24 |
25 | EasyTurk requires jinja2 to compile your custom html and javascript tasks. It requires python2.7 or higher to use the API. It also requires that you have boto3 (AMT's python interface) installed.
26 |
27 |
28 | ## Installing EasyTurk
29 |
30 | First let's install the code.
31 | ```
32 | # Clone the repository.
33 | git clone git@github.com:ranjaykrishna/easyturk.git
34 | cd easyturk
35 |
36 | # Create a virtual environment.
37 | # You might want to use -p python3 option
38 | # if you want to use EasyTurk with python 3.
39 | virtualenv env
40 |
41 | # Activate your environment.
42 | source env/bin/activate
43 |
44 | # Install all the requirements.
45 | pip install -r requirements
46 | ```
47 |
48 |
49 | Now, let's install your AMT access keys.
50 | ```
51 | aws configure --profile mturk
52 | ```
53 | This will prompt you to enter your access key and secret key for your AMT account.
54 | If you don’t have an AWS account already, visit [this website](https://aws.amazon.com) and create an account.
55 | If you don’t have an MTurk Requester account already, visit [this website](https://requester.mturk.com) and create a new account.
56 |
57 |
58 | #### Easy check to make sure everything is in place.
59 |
60 | Run python in your terminal. Let's see if we can access your account information.
61 | ```
62 | from easyturk import EasyTurk
63 | et = EasyTurk(sandbox=False)
64 | print(et.get_account_balance())
65 | ```
66 | This should print out how much balance you have left in your account. If you do not see the amount, then there setup was not successful.
67 |
68 | #### Example tasks that you can use out of the box:
69 |
70 | 1. Image captioning: `easyturk/templates/write_caption.html` contains a image captioning task where workers are asked to write captions for each image.
71 | 2. Verification: `easyturk/templates/verify_caption.html` contains a verification task that asks users to verify that a certain caption is correct for a given image.
72 | 3. Bounding box: `easyturk/templates/annotate_bbox.html` contains a bounding box task that asks users to annotate multiple objects per image.
73 | 3. Verify bounding box: `easyturk/templates/verify_bbox.html` contains a bounding box task that asks users verify whether a bounding box has been annotated correctly.
74 | 4. Verify visual relationships: `easyturk/templates/verify_relationship.html` contains a task that asks users to verify whether a visual relationship has been annotated correctly.
75 |
76 | In the remainer of this tutorial, we will explain how you can launch the captioning task and how you can create your own tasks. The same workflow can be used for the other tasks as well.
77 |
78 |
79 | ## Launching an example image-captioning task on AMT.
80 |
81 | In `easyturk/templates/write_caption.html`, there is an example image-captioning task I have already created for you. You can use that task as a reference to create your own task later. For now, let's try and see if we can render the task, launch it to AMT, then retrieve the results, and approve the assignment.
82 |
83 | #### Step 1: Render the task.
84 | The following command should render the template you built. This should allow you to locally open the task in your browser and check to make sure the functionality works. Of course, when you press the submit button, nothing will happen as you are running the task locally. But once we launch this task on AMT, the submit button will pull the worker's responses to your task and send them to AMT. We will later be able to retrieve those results.
85 | ```
86 | python easyturk/render.py --template write_caption.html --output rendered_template.html
87 | open rendered_template.html
88 | ```
89 |
90 | #### Step 2: Launching the task.
91 | The following block of code will launch the task to AMT. We start by creating a list of image urls we want to caption. The example task expects the data to be in a list of objects, where each element in the list is a dictionary containing a `url` field. Once a worker finishes the task, the task will return a similar list, except each element will contain a `caption` field. Run the following in your python interpretor:
92 | ```
93 | from easyturk import interface
94 | data = [
95 | {
96 | 'url': 'http://visualgenome.org/static/images/collect_sentences/dogs-playing.jpg',
97 | },
98 | {
99 | 'url': 'http://visualgenome.org/static/images/collect_sentences/computer.png',
100 | }
101 | ]
102 | hit_ids = interface.launch_caption(data, reward=1, tasks_per_hit=10)
103 | print(hit_ids)
104 | ```
105 | The above code will launch one HIT that will pay a reward of $1 and caption the two images in the list. Once the HIT is launched, it will return and print out the HITId. This HITId will be used to later retrieve and approve worker's responses. So make sure to NOT lose it. It's good practice to save your HITIds in a database or logfile. But if you do lose it, you can always get it back by queries for all the active HITs you have on AMT.
106 |
107 | `launch_caption` is a custom launch script that sets the title, description, keywords, tasks_per_hit fields. When you later write your own HIT, I recommend create a custom launch function like this one. You can see the source code for the function in `easyturk/interface.py`.
108 |
109 |
110 | #### Step 3: Tracking your task's progress.
111 | You can query for your HIT's progress with the following:
112 | ```
113 | from easyturk import EasyTurk
114 | et = EasyTurk(sandbox=False)
115 | progress = et.show_hit_progress(hit_ids)
116 | print(progress[hit_ids[0]])
117 | ```
118 | The above will print out the progress made for the first HIT in the list. It should print something like:
119 | ```
120 | {'completed': 0, 'max_assignment': 1}
121 | ```
122 |
123 |
124 | #### Step 4: Retrieving worker responses.
125 | You can retrieve the work done by workers for the submitted assignments to a HIT using the following code:
126 | ```
127 | from easyturk import interface
128 |
129 | # approve is set to False because right now we are only fetching the results. You can set it to True to auto-approve the assignments.
130 | results = interface.fetch_completed_hits(hit_ids, approve=False)
131 | print(results[hit_ids[0]])
132 | ```
133 | The above code will parse out the responses made by the worker and show you something like the following:
134 | ```
135 | [
136 | {
137 | 'url': 'http://visualgenome.org/static/images/collect_sentences/dogs-playing.jpg',
138 | 'caption': 'Two dogs playing with a stick.'
139 | },
140 | {
141 | 'url': 'http://visualgenome.org/static/images/collect_sentences/computer.png',
142 | 'caption': 'A cluttered room',
143 | }
144 | ]
145 | ```
146 |
147 | #### Step 5: Approving their work.
148 | If you are happy with the work, you can approve and pay your workers by issuing the following command:
149 | ```
150 | from easyturk import EasyTurk
151 | et = EasyTurk(sandbox=False)
152 | for hit_id in hit_ids:
153 | et.approve_hit(hit_id)
154 | ```
155 |
156 | ## Designing your own AMT task.
157 | The best way to learn to create your own tasks is to mimic the high level interface of `easyturk/templates/write_caption.html`. The main contraint to adhere to is making sure that you are using the API provided by `easyturk/templates/easyturk.html`. Currently, EasyTurk assumes that all your tasks you design will reside in one single HTML files in the `easyturk/templates/` directory
158 |
159 |
160 | #### Importing EasyTurk into your custom task.
161 | Simply add the following line before your custom javascript to use the EasyTurk API:
162 | ```
163 | {% include "easyturk.html" %}
164 | ```
165 |
166 | #### Loading the data from EasyTurk.
167 | The task contains a `DEFAULT_INPUT` variable that should be set when designing your task such that it follows the input schema you expect. In our example captioning task, that variable is a random list of image urls. It allows us to render and test the functionality of the task locally before launching it. When launched, make sure to call:
168 | ```
169 | var input = easyturk.getInput(DEFAULT_INPUT);
170 | ```
171 | This API call will update the input variable with the actual input for the given task. So, each worker will see a specific input, based on the `data` variable you set in Step 2 above.
172 |
173 | #### Enabling the task.
174 | The following API call allows workers to preview the HIT without being able to submit it. Make sure to include it so that when the HIT is accepted by a worker, the task will be enabled.
175 | ```
176 | if (!easyturk.isPreview()){
177 | enableTask();
178 | }
179 | ```
180 |
181 | #### Setup submission and submit.
182 | To make sure that the task is submittable, keep the `enableTask()` function and make sure it includes the `easyturk.setupSubmit()` call and the `easyturk.setOutput(output)` calls within. Any python object you set the `output` variable to will be the response you get back from the worker.
183 |
184 | #### Overall.
185 | When making your custom task, make sure to import EasyTurk's APIs. Get the input from EasyTurk, enable the hit, setup submission and then set the output that you want returned to you. It's easy!
186 |
187 |
188 | #### More customization.
189 | There is a lot more you can do. Check out the fully documented code in `easyturk.py`. There are more complicated workflows you can create by using those functions. Have fun.
190 |
191 |
192 | ## Contributing to this repository.
193 |
194 | This wrapper is by no means a complete list of functionality offered by AMT. If you feel inclined to contribute and enable more functionality, please send me a message over email (firstnamelastname [at] gmail [dot] com) or twitter ([@RanjayKrishna](https://twitter.com/RanjayKrishna)). Feel free to also send me pull requests.
195 |
--------------------------------------------------------------------------------
/easyturk/__init__.py:
--------------------------------------------------------------------------------
1 | from easyturk import EasyTurk
2 | import interface
3 |
--------------------------------------------------------------------------------
/easyturk/easyturk.py:
--------------------------------------------------------------------------------
1 | """A bunch of api calls functions that we used to talk to MTurk.
2 | """
3 |
4 | from boto3 import Session
5 | from datetime import datetime
6 | from jinja2 import Environment
7 | from jinja2 import FileSystemLoader
8 |
9 | import json
10 | import os
11 | import xml
12 |
13 |
14 | class EasyTurk(object):
15 | """Class that contains all the api calls to interface with MTurk.
16 | """
17 |
18 | def __init__(self, sandbox=True):
19 | """Constructor for EasyTurk.
20 |
21 | Args:
22 | sandbox: Whether we are launching on sandbox.
23 | """
24 | environments = {
25 | "production": {
26 | "endpoint": "https://mturk-requester.us-east-1.amazonaws.com",
27 | "preview": "https://www.mturk.com/mturk/preview"
28 | },
29 | "sandbox": {
30 | "endpoint": "https://mturk-requester-sandbox.us-east-1.amazonaws.com",
31 | "preview": "https://workersandbox.mturk.com/mturk/preview"
32 | },
33 | }
34 | self.sandbox = sandbox
35 | env = (environments['sandbox'] if sandbox
36 | else environments["production"])
37 | self.session = Session(profile_name='mturk')
38 | self.mtc = self.session.client(
39 | service_name='mturk',
40 | region_name='us-east-1',
41 | endpoint_url=env['endpoint'],
42 | )
43 |
44 | def create_html_question(self, html, frame_height):
45 | head = (""
47 | "")
48 | tail = ""
49 | xml = head + str(frame_height) + tail
50 | return xml.format(html)
51 |
52 | def get_jinja_env(self):
53 | """Get a jinja2 Environment object that we can use to find templates.
54 | """
55 | dir_location = os.path.dirname(os.path.abspath(__file__))
56 | templates = os.path.join(dir_location, 'templates')
57 | return Environment(loader=FileSystemLoader(templates))
58 |
59 | def get_account_balance(self):
60 | """Retrieves the account balance.
61 |
62 | Returns:
63 | available account balance.
64 | """
65 | return self.mtc.get_account_balance()['AvailableBalance']
66 |
67 | def launch_hit(self, template_location, input_data, reward=0,
68 | frame_height=9000, title=None, description=None,
69 | keywords=None, duration=900, max_assignments=1,
70 | country='US', hits_approved=10000, lifetime=604800,
71 | percent_approved=95):
72 | """Launches a HIT.
73 |
74 | Make sure that none of the arguments are None.
75 |
76 | Returns:
77 | A hit_id.
78 | """
79 | if self.sandbox:
80 | percent_approved = 0
81 | hits_approved = 0
82 | hit_properties = {'Title': title,
83 | 'Description': description,
84 | 'Keywords': keywords,
85 | 'MaxAssignments': max_assignments,
86 | 'LifetimeInSeconds': lifetime,
87 | 'AssignmentDurationInSeconds': duration,
88 | 'QualificationRequirements': [
89 | {
90 | 'QualificationTypeId': '00000000000000000040',
91 | 'Comparator': 'GreaterThanOrEqualTo',
92 | 'IntegerValues': [hits_approved]
93 | },
94 | {
95 | 'QualificationTypeId': '00000000000000000071',
96 | 'Comparator': 'EqualTo',
97 | 'LocaleValues': [
98 | {'Country': country},
99 | ],
100 | },
101 | {
102 | 'QualificationTypeId': '000000000000000000L0',
103 | 'Comparator': 'GreaterThanOrEqualTo',
104 | 'IntegerValues': [percent_approved],
105 | }
106 | ],
107 | 'Reward': str(reward)}
108 |
109 | # Setup HTML Question.
110 | env = self.get_jinja_env()
111 | template = env.get_template(template_location)
112 | template_params = {'input': json.dumps(input_data)}
113 | html = template.render(template_params)
114 | html_question = self.create_html_question(html, frame_height)
115 |
116 | hit_properties['Question'] = html_question
117 |
118 | hit = self.mtc.create_hit(**hit_properties)
119 | return hit
120 |
121 | def _parse_response_from_assignment(self, assignment):
122 | """Parses out the worker's response from the assignment received.
123 |
124 | Args:
125 | assignment: A dictionary describing the assignment from boto.
126 |
127 | Returns:
128 | A Python object or list of the worker's response.
129 | """
130 | try:
131 | output_tree = xml.etree.ElementTree.fromstring(assignment['Answer'])
132 | output = json.loads(output_tree[0][1].text)
133 | return output
134 | except ValueError as e:
135 | print(e)
136 | return None
137 |
138 | def get_results(self, hit_id, reject_on_fail=False):
139 | """Retrives the output of a hit if it has finished.
140 |
141 | Args:
142 | hit_id: The hit id of the HIT.
143 | reject_on_fail: If the hit returns unparsable answers,
144 | then reject it.
145 |
146 | Returns:
147 | A list of dictionaries with the following fields:
148 | - assignment_id
149 | - hit_id
150 | - worker_id
151 | - output
152 | - submit_time
153 | The number of dictionaries is equal to the number of assignments.
154 | """
155 | status = ['Approved', 'Submitted', 'Rejected']
156 | try:
157 | assignments = self.mtc.list_assignments_for_hit(
158 | HITId=hit_id, AssignmentStatuses=status)
159 | except Exception:
160 | return []
161 | results = []
162 | for a in assignments['Assignments']:
163 | output = self._parse_response_from_assignment(a)
164 | if output is not None:
165 | results.append({
166 | 'assignment_id': a['AssignmentId'],
167 | 'hit_id': hit_id,
168 | 'worker_id': a['WorkerId'],
169 | 'output': output,
170 | 'submit_time': a['SubmitTime'],
171 | })
172 | elif reject_on_fail:
173 | self.mtc.reject_assignment(
174 | AssignmentId=a['AssignmentId'],
175 | RequesterFeedback='Invalid results')
176 | return results
177 |
178 | def delete_hit(self, hit_id):
179 | """Disables a hit.
180 |
181 | Args:
182 | hit_id: The hit id to disable.
183 |
184 | Returns:
185 | A boolean indicating success.
186 | """
187 | try:
188 | self.mtc.delete_hit(HITId=hit_id)
189 | return True
190 | except Exception:
191 | try:
192 | self.mtc.update_expiration_for_hit(
193 | HITId=hit_id,
194 | ExpireAt=datetime.now())
195 | self.mtc.delete_hit(HITId=hit_id)
196 | return True
197 | except Exception as e:
198 | print e
199 | return False
200 |
201 | def approve_hit(self, hit_id, reject_on_fail=False,
202 | override_rejection=False):
203 | """Approves a hit so that the worker can get paid.
204 |
205 | Args:
206 | hit_id: The hit id to disable.
207 | reject_on_fail: If the hit returns unparsable answers,
208 | then reject it.
209 | override_rejection: overrides a previous rejection if it exists.
210 |
211 | Returns:
212 | Tuple of approved and rejected assignment ids.
213 | """
214 | status = ['Approved', 'Submitted', 'Rejected']
215 | try:
216 | assignments = self.mtc.list_assignments_for_hit(
217 | HITId=hit_id, AssignmentStatuses=status)
218 | except:
219 | return [], []
220 | approve_ids = []
221 | reject_ids = []
222 | for a in assignments['Assignments']:
223 | if a['AssignmentStatus'] == 'Submitted':
224 | output = self._parse_response_from_assignment(a)
225 | if output is not None:
226 | approve_ids.append(a['AssignmentId'])
227 | elif reject_on_fail:
228 | reject_ids.append(a['AssignmentId'])
229 |
230 | for assignment_id in approve_ids:
231 | self.mtc.approve_assignment(
232 | AssignmentId=assignment_id,
233 | RequesterFeedback='Good job',
234 | OverrideRejection=override_rejection)
235 | for assignment_id in reject_ids:
236 | self.mtc.reject_assignment(
237 | AssignmentId=assignment_id, RequesterFeedback='Invalid results')
238 | return approve_ids, reject_ids
239 |
240 | def reject_assignment(self, assignment_id):
241 | """Reject an assignment so that the worker can get paid.
242 |
243 | Args:
244 | assignment_id: An assignment id.
245 |
246 | Returns:
247 | A boolean indicating success.
248 | """
249 | a = self.mtc.get_assignment(AssignmentId=assignment_id)['Assignment']
250 | if a['AssignmentStatus'] == 'Submitted':
251 | self.mtc.reject_assignment(
252 | AssignmentId=assignment_id,
253 | RequesterFeedback='Invalid results')
254 | return True
255 | return False
256 |
257 | def approve_assignment(self, assignment_id, reject_on_fail=False,
258 | override_rejection=False):
259 | """Approves an assignment so that the worker can get paid.
260 |
261 | Args:
262 | assignment_id: An assignment id.
263 | reject_on_fail: If the hit returns unparsable answers,
264 | then reject it.
265 | override_rejection: overrides a previous rejection if it exists.
266 |
267 | Returns:
268 | A boolean indicating success.
269 | """
270 | a = self.mtc.get_assignment(AssignmentId=assignment_id)['Assignment']
271 | if a['AssignmentStatus'] == 'Submitted':
272 | output = self._parse_response_from_assignment(a)
273 | if output is not None:
274 | self.mtc.approve_assignment(
275 | AssignmentId=assignment_id,
276 | RequesterFeedback='Good job',
277 | OverrideRejection=override_rejection)
278 | return True
279 | elif reject_on_fail:
280 | self.mtc.reject_assignment(
281 | AssignmentId=assignment_id,
282 | RequesterFeedback='Invalid results')
283 | return False
284 | return False
285 |
286 | def show_hit_progress(self, hit_ids):
287 | """Show the progress of the hits.
288 |
289 | Args:
290 | hit_ids: A list of HIT ids.
291 |
292 | Returns:
293 | A dictionary from hit_id to a dictionary of completed
294 | and maximum assignments.
295 | """
296 | output = {}
297 | for hit_id in hit_ids:
298 | hit = self.mtc.get_hit(HITId=hit_id)
299 | assignments = self.mtc.list_assignments_for_hit(
300 | HITId=hit_id, AssignmentStatuses=['Submitted'])
301 | completed = len(assignments['Assignments'])
302 | max_assignments = hit['HIT']['MaxAssignments']
303 | output[hit_id] = {'completed': completed,
304 | 'max_assignments': max_assignments}
305 | return output
306 |
307 | def list_hits(self):
308 | """Lists the HITs that have already been launched.
309 |
310 | Returns:
311 | A list of HITs.
312 | """
313 | hits = self.mtc.list_hits()['HITs']
314 | return hits
315 |
--------------------------------------------------------------------------------
/easyturk/evaluate.py:
--------------------------------------------------------------------------------
1 | """Script to render a completed EasyTurk task.
2 | """
3 |
4 | from flask import Flask
5 | from flask import render_template
6 | from flask import request
7 |
8 | import json
9 | import os
10 |
11 | from easyturk import EasyTurk
12 |
13 |
14 | # Global server variables.
15 | app = Flask(__name__)
16 |
17 |
18 | def get_e_filename(filename):
19 | """Modifies the filename to include _e_ in front.
20 |
21 | Args:
22 | filename: A filename (ex, directory/filename.json)
23 |
24 | Returns:
25 | A string directory/_e_filename.json
26 | """
27 | dirname = os.path.dirname(filename)
28 | index = len(dirname) + 1
29 | dirname = filename[0:index]
30 | filename = filename[index:]
31 | return dirname + '_e_' + filename
32 |
33 |
34 | def convert(results):
35 | """Converts the results to include metadata for navigation.
36 |
37 | Args:
38 | results: A dictionary mapping from hit_id to hit data.
39 |
40 | Returns:
41 | A dictionary containing:
42 | - hits: All hits ordered by workers.
43 | - workers: Mapping from worker_id to indices into hits.
44 | """
45 | # Organize all the data by worker id.
46 | worker_map = {}
47 | for hit_id, hit in results.items():
48 | for assignment in hit:
49 | worker_id = assignment['worker_id']
50 | if worker_id not in worker_map:
51 | worker_map[worker_id] = []
52 | worker_map[worker_id].append(assignment)
53 |
54 | # Create the hits and worker index mapping structures.
55 | hits = []
56 | worker_indices = {}
57 | worker_ids = []
58 | for worker_id, assignments in worker_map.items():
59 | worker_indices[worker_id] = range(
60 | len(hits), len(hits) + len(assignments))
61 | hits.extend(assignments)
62 | worker_ids.append(worker_id)
63 | output = {'hits': hits,
64 | 'workers': worker_indices,
65 | 'worker_ids': worker_ids}
66 | return output
67 |
68 |
69 | @app.route('/task')
70 | def task():
71 | """Visualizes the results received for a given task.
72 | """
73 | # Compile the template.
74 | task = request.args['task']
75 | results_file = request.args['results']
76 | eresults_file = get_e_filename(results_file)
77 | if os.path.exists(eresults_file):
78 | results = json.load(open(eresults_file))
79 | else:
80 | results = json.load(open(results_file))
81 | results = convert(results)
82 | json.dump(results, open(eresults_file, 'w'))
83 | return render_template(
84 | 'evaluation/' + task,
85 | results=results,
86 | task=results_file,
87 | eresults_file=eresults_file)
88 |
89 |
90 | @app.route('/interface', methods=['POST'])
91 | def interface():
92 | """Endpoint that rejects and approves work.
93 | """
94 | et = EasyTurk(sandbox=False)
95 | if request.method != 'POST':
96 | return 'Fail'
97 | assignment_ids = json.loads(request.form['assignment_ids'])
98 | approve = json.loads(request.form['approve'])
99 | for assignment_id in assignment_ids:
100 | if approve:
101 | et.approve_assignment(assignment_id)
102 | else:
103 | et.reject_assignment(assignment_id)
104 | eresults_file = request.form['eresults_file']
105 | results = json.load(open(eresults_file))
106 | for hit in results['hits']:
107 | if hit['assignment_id'] in assignment_ids:
108 | hit['approve'] = approve
109 | json.dump(results, open(eresults_file, 'w'))
110 | return 'Succcess'
111 |
112 |
113 | @app.route('/')
114 | def home():
115 | return render_template('evaluation/home.html')
116 |
117 |
118 | if __name__=='__main__':
119 | app.run()
120 |
--------------------------------------------------------------------------------
/easyturk/interface.py:
--------------------------------------------------------------------------------
1 | """Functions to launch, retrieve, and parse specific EasyTurk tasks.
2 | """
3 |
4 | from easyturk import EasyTurk
5 |
6 |
7 | def launch_verify_question_answer(data, reward=1.00, tasks_per_hit=50, sandbox=False):
8 | """Launches HITs to ask workers to verify bounding boxes.
9 |
10 | Args:
11 | data: List containing image urls, questions and answers, for the task.
12 | reward: A postive valued dollar amount per task.
13 | tasks_per_hit: Number of images per hit.
14 | sandbox: Whether to interact on sandbox or production.
15 |
16 | Returns:
17 | A list of hit ids that have been launched.
18 | """
19 | et = EasyTurk(sandbox=sandbox)
20 | template = 'verify_question_answer.html'
21 | hit_ids = []
22 | i = 0
23 | while i < len(data):
24 | hit = et.launch_hit(
25 | template, data[i:i+tasks_per_hit], reward=reward,
26 | title='Verify the answer to a question about an picture',
27 | description=('Verify whether an answer to a question about a picture is correct.'),
28 | keywords='image, text, picture, answer, question, relationship')
29 | hit_id = hit['HIT']['HITId']
30 | hit_ids.append(hit_id)
31 | i += tasks_per_hit
32 | return hit_ids
33 |
34 |
35 | def launch_verify_relationship(data, reward=1.00, tasks_per_hit=30, sandbox=False):
36 | """Launches HITs to ask workers to verify bounding boxes.
37 |
38 | Args:
39 | data: List containing image urls, relationships, for the task.
40 | reward: A postive valued dollar amount per task.
41 | tasks_per_hit: Number of images per hit.
42 | sandbox: Whether to interact on sandbox or production.
43 |
44 | Returns:
45 | A list of hit ids that have been launched.
46 | """
47 | et = EasyTurk(sandbox=sandbox)
48 | template = 'verify_relationship.html'
49 | hit_ids = []
50 | i = 0
51 | while i < len(data):
52 | hit = et.launch_hit(
53 | template, data[i:i+tasks_per_hit], reward=reward,
54 | title='Verify relationships between objects in pictures',
55 | description=('Verify whether the relationships are correctly identified in pictures.'),
56 | keywords='image, text, picture, object, bounding box, relationship')
57 | hit_id = hit['HIT']['HITId']
58 | hit_ids.append(hit_id)
59 | i += tasks_per_hit
60 | return hit_ids
61 |
62 |
63 | def launch_verify_bbox(data, reward=1.00, tasks_per_hit=30, sandbox=False):
64 | """Launches HITs to ask workers to verify bounding boxes.
65 |
66 | Args:
67 | data: List containing image urls, objects, for the task.
68 | reward: A postive valued dollar amount per task.
69 | tasks_per_hit: Number of images per hit.
70 | sandbox: Whether to interact on sandbox or production.
71 |
72 | Returns:
73 | A list of hit ids that have been launched.
74 | """
75 | et = EasyTurk(sandbox=sandbox)
76 | template = 'verify_bbox.html'
77 | hit_ids = []
78 | i = 0
79 | while i < len(data):
80 | hit = et.launch_hit(
81 | template, data[i:i+tasks_per_hit], reward=reward,
82 | title='Verify objects in pictures',
83 | description=('Verify whether objects are correctly identified in pictures.'),
84 | keywords='image, text, picture, object, bounding box')
85 | hit_id = hit['HIT']['HITId']
86 | hit_ids.append(hit_id)
87 | i += tasks_per_hit
88 | return hit_ids
89 |
90 |
91 | def launch_caption(data, reward=1.00, tasks_per_hit=10, sandbox=False):
92 | """Launches HITs to ask workers to caption images.
93 |
94 | Args:
95 | data: List containing image urls for the task.
96 | reward: A postive valued dollar amount per task.
97 | tasks_per_hit: Number of images per hit.
98 | sandbox: Whether to interact on sandbox or production.
99 |
100 | Returns:
101 | A list of hit ids that have been launched.
102 | """
103 | et = EasyTurk(sandbox=sandbox)
104 | template = 'write_caption.html'
105 | hit_ids = []
106 | i = 0
107 | while i < len(data):
108 | hit = et.launch_hit(
109 | template, data[i:i+tasks_per_hit], reward=reward,
110 | title='Caption some pictures',
111 | description=('Write captions about the contents of images.'),
112 | keywords='image, caption, text')
113 | hit_id = hit['HIT']['HITId']
114 | hit_ids.append(hit_id)
115 | i += tasks_per_hit
116 | return hit_ids
117 |
118 |
119 | def fetch_completed_hits(hit_ids, approve=True, sandbox=False):
120 | """Grabs the results for the hit ids.
121 |
122 | Args:
123 | hit_ids: A list of hit ids to fetch.
124 | approve: Whether to approve the hits that have been submitted.
125 | sandbox: Whether to interact on sandbox or production.
126 |
127 | Returns:
128 | A dictionary from hit_id to the result, if that hit_id has
129 | been submitted.
130 | """
131 | et = EasyTurk(sandbox=sandbox)
132 | output = {}
133 | for hit_id in hit_ids:
134 | results = et.get_results(hit_id, reject_on_fail=False)
135 | if len(results) > 0:
136 | output[hit_id] = results
137 | if approve:
138 | for assignment in results:
139 | assignment_id = assignment['assignment_id']
140 | et.approve_assignment(assignment_id)
141 | return output
142 |
--------------------------------------------------------------------------------
/easyturk/render.py:
--------------------------------------------------------------------------------
1 | """Script to render a given EasyTurk task.
2 | """
3 |
4 | import argparse
5 |
6 | from easyturk import EasyTurk
7 |
8 |
9 | if __name__ == '__main__':
10 | parser = argparse.ArgumentParser()
11 | parser.add_argument('--template', required=True)
12 | parser.add_argument('--output', required=True)
13 | args = parser.parse_args()
14 |
15 | # Compile the template.
16 | et = EasyTurk()
17 | env = et.get_jinja_env()
18 | template = env.get_template(args.template)
19 |
20 | html = template.render({'input': ''})
21 |
22 | # Save to output.
23 | with open(args.output, 'w') as f:
24 | f.write(html)
25 |
--------------------------------------------------------------------------------
/easyturk/templates/annotate_bbox.html:
--------------------------------------------------------------------------------
1 |
2 |