Custom Interfaces
Prodigy ships with a range of built-in annotation interfaces for annotating text, images and other content. You can also put together fully custom solutions by combining interfaces and adding custom HTML, CSS and JavaScript.
Combining interfaces with blocks New: 1.9
Blocks are a new and exciting feature that let you freely combine different
annotation interfaces. For example, you can add a
text_input
field to the ner_manual
interface to collect
additional free-form comments from the annotators. Or you can add custom
html
blocks before and after the image_manual
UI or a multiple
choice
interface. Here’s an example:
Blocks are defined as a list of dictionaries in the "config"
returned by your
recipe and will be rendered in order.
They all use the data present in the current annotation task, unless you
override certain properties in the block. Each block needs to define at least a
"view_id"
, mapping to one of the existing
annotation interfaces.
recipe.py (excerpt)return {
"dataset": dataset,
"stream": stream,
"view_id": "blocks",
"config": {
"blocks": [
{"view_id": "ner_manual"},
{"view_id": "choice", "text": None},
{"view_id": "text_input", "field_rows": 3},
]
}
}
In the above example, the "text"
property of the second block is overwritten
and set to None
, so it won’t render any text. That’s because both
ner_manual
and choice
render the "text"
of an annotation
task if present, so it would show up twice, once in each block. You typically
also want to define text_input
settings with the block, so you can have
multiple inputs if needed and don’t need to store the presentational settings
with each annotation task.
Aside from the task content, you can also define the following config settings with each block:
html_template | HTML template with variables to render the task content. Setting it in the block allows you to have multiple custom HTML blocks rendering different content. |
labels | Label set used in the ner_manual and image_manual interfaces. Setting it in the block is a bit cleaner and technically allows you to create blocks using both interfaces together with different labels (although not necessarily recommended). |
The annotation task, i.e. what your stream sends out, should contain the content you’re annotating and what you want to save in the database. The config and overrides in the blocks are applied to all tasks, so they should only really include presentational settings, like the HTML template to use, the ID or placeholder of input fields or overrides to prevent the same content from being displayed by several blocks.
Multiple blocks of the same type are only supported for the text_input
and html
interface. For text input blocks, you can use the "field_id"
to assign unique names to your fields and make sure they all write the user
input to different keys. For HTML blocks, you can use different
"html_template"
values that reference different keys of your task.
Example[
{"view_id": "text_input", "field_id": "user_input_a", "field_rows": 1},
{"view_id": "text_input", "field_id": "user_input_b", "field_rows": 3},
{"view_id": "html", "html_template": "<strong>{{content_a}}</strong>"},
{"view_id": "html", "html_template": "<em>{{content_b}}</em>"}
]
Custom interfaces with HTML, CSS and JavaScript
The html
interface lets you render any plain HTML content, specified as
the "html"
key in the annotation task.
JSON task format{"html": "<img src='image.jpg'>"}
If you don’t want to include the full markup in every task, you can also specify
a html_template
in your global or the project’s prodigy.json
config file.
Prodigy uses Mustache to render the templates,
and makes all task properties available as variables wrapped in double curly
braces, e.g. {{text}}
. Using HTML templates, you can visualize complex, nested
data without having to convert your input to match Prodigy’s format. When you
annotate the task, Prodigy will simply add an "answer"
key.
For example, let’s say your annotation tasks look like this:
JSON task format{
"title": "This is a title",
"image": "https://images.unsplash.com/photo-1472148439583-1f4cf81b80e0?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=21dba460262331eb682c38680f7f1135",
"data": {"value": "This is a value"},
"meta": {
"by": "Peter Nguyen",
"url": "https://unsplash.com/@peterng1618"
},
"html": " "
}
You could then write the following HTML template that has access to the task
properties as template variables, including the nested data.value
:
HTML template<h2>{{title}}</h2>
<strong>{{data.value}}</strong>
<br />
<img src="{{image}}" />
All theme settings will also be available to the custom interface via the
{{theme}}
variable. This lets you re-use Prodigy’s color and styles, without
having to hard-code them into your template. For example:
<mark style="background: {{theme.bgHighlight}}">{{text}}</mark>
JavaScript and CSS New: 1.7
Using custom CSS
The "global_css"
config setting lets you pass in a string of CSS overrides
that will be added to the global scope of the app. This lets you customize
styles beyond just the color theme. As of v1.7.0, the Prodigy app exposes the
following human-readable class names:
Class name | Description |
---|---|
.prodigy-root | Root element the app is rendered into. |
.prodigy-buttons | Row of action buttons (accept, reject, ignore, undo). |
.prodigy-button-accept | New: 1.9 The accept button. |
.prodigy-button-reject | New: 1.9 The reject button. |
.prodigy-button-ignore | New: 1.9 The ignore button. |
.prodigy-button-undo | New: 1.9 The undo button. |
.prodigy-annotator | New: 1.9 Main container of the annotation UI (excluding the sidebar). |
.prodigy-sidebar-wrapper | New: 1.9 Container of the sidebar. |
.prodigy-sidebar | New: 1.9 The sidebar. |
.prodigy-sidebar-title | New: 1.9 The sidebar title bar. |
.prodigy-container | Container of the annotation card (including title, content, meta). |
.prodigy-content | Main content of the annotation card (containing the text or image). |
.prodigy-title | Title bar containing the label(s). |
.prodigy-title-wrapper | Sticky wrapper around the title bar containing the labels. |
.prodigy-meta | Meta information in the bottom right corner of the annotation card. |
.prodigy-text-input | Text input fields (<input> or <textarea> ) created by the text_input interface. |
.prodigy-spans | New: 1.10.8 Spans container in interfaces like ner and ner_manual . |
.prodigy-labels | New: 1.11 Container of selectable labels, e.g. in ner_manual UI. |
.prodigy-label | New: 1.11 Individual selectable label, e.g. in ner_manual UI. |
.prodigy-options | New: 1.11 Container for choice options in choice UI. |
.prodigy-option | New: 1.11 Individual choice option in choice UI. |
In addition to the class names, some element also expose specific data
attributes. This lets you target them based on their value, e.g.
.prodigy-root[data-prodigy-view-id="ner"]
.
Data attribute | Element | Description |
---|---|---|
data-prodigy-view-id | .prodigy-root | The name of the current interface, i.e. "ner_manual" . |
data-prodigy-recipe | .prodigy-root | The name of the current recipe, i.e. ner.manual or terms.teach . |
data-prodigy-label | .prodigy-label | New: 1.11 The name of a selectable label, e.g. "PERSON" . |
data-prodigy-option | .prodigy-option | New: 1.11 The string value of a choice option, e.g. "1" . |
Data attributes also allow you to implement custom styling specific to individual recipes or interfaces in the same global stylesheet. For example:
[data-prodigy-view-id='ner_manual'] .prodigy-title {
/* Change background for the title/labels bar only in the manual NER interface */
background: green;
}
[data-prodigy-recipe='custom-recipe'] .prodigy-buttons {
/* Hide the action buttons only in a custom recipe "custom-recipe" */
display: none;
}
Using custom JavaScript
As of v1.7.0, the "javascript"
config setting is available across all
interfaces and lets you pass in a string of JavaScript code that will be
executed in the global scope. This lets you implement fully custom interfaces
that modify and interact with the current incoming task. The following internals
are exposed via the global window.prodigy
object:
Property | Type | Description |
---|---|---|
viewId | string | The ID of the current interface, e.g. 'ner_manual' . |
config | object | The user configuration settings (e.g. prodigy.json plus recipe config). |
content | object | The content of the current task (e.g. {'text': 'Some text'} ). |
theme | object | Theme variables like colors. |
update | function | Update the current task. Takes an object with the updates and performs a shallow (!) merge. |
answer | function | Answer the current task. Takes the string value of the answer, e.g. 'accept' . |
Here’s a simple example of a custom HTML template and JavaScript function that allows toggling a button to change the task text from uppercase to lowercase, and vice versa. For more details and inspiration, check out this thread on the forum.
HTML Template<button class="custom-button" onClick="updateText()">
👇 Text to uppercase
</button>
<br />
<strong>{{text}}</strong>
JavaScriptlet upper = false
function updateText() {
const text = window.prodigy.content.text
const newText = !upper ? text.toUpperCase() : text.toLowerCase()
window.prodigy.update({ text: newText })
upper = !upper
document.querySelector('.custom-button').textContent =
'👇 Text to ' + (upper ? 'lowercase' : 'uppercase')
}
You can also listen to the following custom events fired by the app:
Event | Description |
---|---|
prodigymount | The app has mounted. |
prodigyupdate | The UI was updated. |
prodigyanswer | The user answered a question. |
prodigyundo | New: 1.8.4 The user hit undo and re-added a task to the queue. |
prodigyspanselected | A span was selected in the manual NER interface. |
prodigyend | New: 1.10.8 No more tasks are available. |
Some events expose additional data via the details
property. For example, the
prodigyanswer
event exposes the task that was answered, as well as the answer
that was chosen: 'accept'
, 'reject'
or 'ignore'
.
JavaScriptdocument.addEventListener('prodigyanswer', event => {
const { answer, task } = event.detail
console.log('The answer was: ', answer)
})