forked from ci4s/label-studio
662 lines
17 KiB
Plaintext
662 lines
17 KiB
Plaintext
---
|
|
type: playground
|
|
order: 201
|
|
meta_title: Data Labeling & Annotation Tool Interactive Demo
|
|
meta_description: Label Studio interactive demo and playground for data labeling and annotation for machine learning and data science projects.
|
|
---
|
|
<style>
|
|
.sidebar {
|
|
display: none;
|
|
}
|
|
.content {
|
|
max-width: none !important;
|
|
margin-left: 0 !important;
|
|
padding: 1em 0 0 0;
|
|
}
|
|
|
|
.validation {
|
|
margin-top: 1em;
|
|
margin-left: 1em;
|
|
color: red;
|
|
text-transform: capitalize;
|
|
}
|
|
|
|
.CodeMirror {
|
|
min-height: 500px !important;
|
|
}
|
|
|
|
h1 {
|
|
margin-bottom: 0.5em !important;
|
|
}
|
|
|
|
h3 {
|
|
margin: 1em 0 !important;
|
|
font-weight: normal;
|
|
width: unset;
|
|
height: unset;
|
|
}
|
|
|
|
iframe {
|
|
border: 0;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
#render-editor {
|
|
width: 100%;
|
|
}
|
|
|
|
#editor-wrap {
|
|
padding: 0;
|
|
margin: 0;
|
|
display: none;
|
|
}
|
|
|
|
.preview {
|
|
padding: 5px;
|
|
overflow: auto;
|
|
}
|
|
|
|
.editor-row {
|
|
display: flex;
|
|
margin-bottom: 1em;
|
|
width: 100% !important;
|
|
}
|
|
|
|
.data-row {
|
|
display: flex;
|
|
}
|
|
|
|
.preview-col {
|
|
width: 60%;
|
|
flex: 1;
|
|
}
|
|
|
|
.editor-area {
|
|
border: 1px solid rgba(34,36,38,.15);
|
|
border-radius: 0.28571429rem;
|
|
}
|
|
|
|
.config-col {
|
|
color: rgba(0,0,0,.6);
|
|
margin-right: 2em;
|
|
width: 40%;
|
|
}
|
|
|
|
.input-col {
|
|
width: 49%;
|
|
padding-right: 2%;
|
|
}
|
|
|
|
.output-col {
|
|
width: 49%;
|
|
}
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
/* hide title "basic template configs" */
|
|
#basic-templates>.title {
|
|
display: none;
|
|
}
|
|
#adv-templates>.title {
|
|
margin-bottom: 1em;
|
|
cursor: pointer;
|
|
}
|
|
#adv-templates>.content {
|
|
display: none
|
|
}
|
|
|
|
.message, .accordion {
|
|
width: 90%;
|
|
max-width: 1000px;
|
|
margin: 1em auto 1.75em auto;
|
|
}
|
|
.grid {
|
|
display: -webkit-box;
|
|
display: -ms-flexbox;
|
|
display: flex;
|
|
-webkit-box-orient: horizontal;
|
|
-webkit-box-direction: normal;
|
|
-ms-flex-direction: row;
|
|
flex-direction: row;
|
|
-ms-flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
-webkit-box-align: stretch;
|
|
-ms-flex-align: stretch;
|
|
align-items: stretch;
|
|
padding: 0;
|
|
}
|
|
|
|
.column {
|
|
width: 20% !important;
|
|
}
|
|
.use-template {
|
|
font-weight: normal!important;
|
|
}
|
|
.use-template:hover {
|
|
border-bottom: 1px dashed darkorange;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Icons';
|
|
src: url("/fonts/icons.eot");
|
|
src: url("/fonts/icons.eot?#iefix") format('embedded-opentype'), url("/fonts/icons.woff2") format('woff2'), url("/fonts/icons.woff") format('woff'), url("/fonts/icons.ttf") format('truetype'), url("/fonts/icons.svg#icons") format('svg');
|
|
font-style: normal;
|
|
font-weight: normal;
|
|
font-variant: normal;
|
|
text-decoration: inherit;
|
|
text-transform: none;
|
|
}
|
|
i.icon {
|
|
opacity: 0.75;
|
|
display: inline-block;
|
|
margin: 0 0.25rem 0 0;
|
|
width: 1.18em;
|
|
height: 1em;
|
|
font-family: 'Icons';
|
|
font-style: normal;
|
|
font-weight: normal;
|
|
text-decoration: inherit;
|
|
text-align: center;
|
|
speak: none;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
-webkit-font-smoothing: antialiased;
|
|
-webkit-backface-visibility: hidden;
|
|
backface-visibility: hidden;
|
|
}
|
|
i.icon:before {
|
|
background: none !important;
|
|
}
|
|
i.icon.sound:before {
|
|
content: "\f025";
|
|
}
|
|
i.icon.image:before {
|
|
content: "\f03e";
|
|
}
|
|
i.icon.code:before {
|
|
content: "\f121";
|
|
}
|
|
i.icon.font:before {
|
|
content: "\f031";
|
|
}
|
|
i.icon.video:before {
|
|
content: "\f03d";
|
|
}
|
|
i.icon.share:before {
|
|
content: "\f064"
|
|
}
|
|
i.icon.copy.outline:before {
|
|
content: "\f0c5"
|
|
}
|
|
i.icon.archive:before {
|
|
content: "\f187";
|
|
}
|
|
i.icon.eye:before {
|
|
content: "\f06e";
|
|
}
|
|
i.icon.bullseye:before {
|
|
content: "\f140";
|
|
}
|
|
i.icon.vector.square:before {
|
|
content: "\f5cb";
|
|
}
|
|
i.icon.wave.square:before {
|
|
content: "\f83e"
|
|
}
|
|
i.icon.dropdown:before {
|
|
content: "\f0da";
|
|
}
|
|
i.icon.dropdown.active:before {
|
|
content: "\f0d7";
|
|
}
|
|
|
|
.share-buttons {
|
|
float:right;
|
|
margin: 1.2em 1em 1em 1em;
|
|
}
|
|
.share-buttons i {
|
|
cursor: pointer;
|
|
opacity: 0.5 !important;
|
|
color: #f58a48;
|
|
transition: 0.25s;
|
|
}
|
|
.share-buttons i:hover {
|
|
opacity: 1 !important;
|
|
transition: 0.25s;
|
|
}
|
|
|
|
.intro {
|
|
max-width: 700px;
|
|
margin: 0 auto;
|
|
margin-top: 1.5em;
|
|
}
|
|
|
|
@media screen and (max-width: 900px) {
|
|
.sidebar {
|
|
display: flex;
|
|
}
|
|
}
|
|
@media only screen and (max-width: 767.98px) {
|
|
.intro {
|
|
padding-left: 0;
|
|
}
|
|
.grid {
|
|
width: auto;
|
|
margin-left: 0 !important;
|
|
margin-right: 0 !important;
|
|
}
|
|
.column {
|
|
width: 100% !important;
|
|
margin: 0 0 !important;
|
|
-webkit-box-shadow: none !important;
|
|
box-shadow: none !important;
|
|
padding: 1rem 1rem !important;
|
|
}
|
|
|
|
.editor-row {
|
|
flex-direction: column;
|
|
}
|
|
.data-row {
|
|
flex-direction: column;
|
|
}
|
|
.preview-col {
|
|
width: 100%;
|
|
}
|
|
.config-col {
|
|
width: 100%;
|
|
}
|
|
.input-col, .output-col {
|
|
width: 100%;
|
|
}
|
|
|
|
}
|
|
</style>
|
|
|
|
<script src="js.cookie.js"></script>
|
|
|
|
<!-- html -->
|
|
|
|
<div class="intro">
|
|
<h3 style="text-align: center">1. Choose annotation template</h3>
|
|
</div>
|
|
|
|
|
|
<%- include('template_titles') %>
|
|
|
|
<div>
|
|
<div class="editor-row">
|
|
<div class="config-col">
|
|
<div>
|
|
<h3 style="display: inline-block">2. Edit Labeling config</h3>
|
|
<span class="share-buttons">
|
|
<i class="icon copy outline" style="cursor: pointer" title="Copy labeling config"></i>
|
|
<i class="icon share" style="cursor: pointer" title="Copy link to this playground"></i>
|
|
</span>
|
|
</div>
|
|
<div class="editor-area">
|
|
<!-- Textarea -->
|
|
<textarea name="label_config" cols="40" rows="10" class="project-form htx-html-editor"
|
|
id="id_label_config"></textarea>
|
|
</div>
|
|
|
|
<div style="margin-top: 1em">
|
|
Start typing in the config, and you can quickly preview the labeling interface.
|
|
At the bottom of the page, you have live serialization updates
|
|
of what Label Studio expects as an input and what it gives you as a result of your labeling work.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="preview-col">
|
|
<h3>3. Inspect Interface preview</h3>
|
|
<div class="validation"></div>
|
|
<div id="editor-wrap">
|
|
</div>
|
|
<div class="preview" id="preload-editor">
|
|
<div class="loading" style="margin: 20px; opacity: 0.8">
|
|
<img width="40px" src="/images/loading.gif">
|
|
<span style="position: relative; top: -14px"> Loading Label Studio, please wait ...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Preview in two cols -->
|
|
<div class="data-row">
|
|
<div class="input-col">
|
|
<h3>Input preview</h3>
|
|
<pre class="preview" id="upload-data-example">...</pre>
|
|
</div>
|
|
<div class="output-col">
|
|
<h3>Output preview</h3>
|
|
<pre class="preview" id="data-results">...</pre>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Hidden template codes -->
|
|
<empty>
|
|
<%- include('template_start') %>
|
|
<%- include('template_codes') %>
|
|
</empty>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
// copy to clipboard
|
|
var copyToClipboard = function (str) {
|
|
var el = document.createElement('textarea'); // Create a <textarea> element
|
|
el.value = str; // Set its value to the string that you want copied
|
|
el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof
|
|
el.style.position = 'absolute';
|
|
el.style.left = '-9999px'; // Move outside the screen to make it invisible
|
|
document.body.appendChild(el); // Append the <textarea> element to the HTML document
|
|
var selected =
|
|
document.getSelection().rangeCount > 0 // Check if there is any content selected previously
|
|
? document.getSelection().getRangeAt(0) // Store selection if found
|
|
: false; // Mark as false to know no selection existed before
|
|
el.select(); // Select the <textarea> content
|
|
document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
|
|
document.body.removeChild(el); // Remove the <textarea> element
|
|
if (selected) { // If a selection existed before copying
|
|
document.getSelection().removeAllRanges(); // Unselect everything on the HTML document
|
|
document.getSelection().addRange(selected); // Restore the original selection
|
|
}
|
|
};
|
|
|
|
function uuidv4() {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
var confirm_already_shown = true;
|
|
var edit_count = 0;
|
|
var current_template_name = 'start';
|
|
var current_template_category = 'start';
|
|
var page_hash = uuidv4();
|
|
var user_hash = Cookies.get('user_hash');
|
|
if (user_hash === "null" || !user_hash) {
|
|
user_hash = uuidv4();
|
|
Cookies.set('user_hash', user_hash);
|
|
}
|
|
var lookup = {};
|
|
|
|
$.ajax({
|
|
url: 'https://extreme-ip-lookup.com/json/',
|
|
success: function(o) { lookup = o },
|
|
async: false
|
|
});
|
|
|
|
$(function () {
|
|
|
|
function addTemplateConfig($el) {
|
|
var template_pk = $el.data('value');
|
|
var value = $('[data-template-pk="' + template_pk + '"]').html();
|
|
|
|
// extract readme from config
|
|
var starter = '<!-- readme', terminator = '-->';
|
|
var start = value.indexOf(starter);
|
|
if (start >= 0) {
|
|
var body_length = value.indexOf(terminator, start) - start - starter.length;
|
|
var readme = value.substr(start + starter.length, body_length);
|
|
|
|
// find first XML tag (<View> as usual) and start from it
|
|
value = value.slice(value.indexOf('<', start + starter.length + body_length + terminator.length))
|
|
}
|
|
|
|
labelEditor.setValue(value);
|
|
first_render = true;
|
|
}
|
|
|
|
$('.use-template').on('click', function () {
|
|
var $el = $(this);
|
|
edit_count = 0;
|
|
current_template_name = $el.text();
|
|
current_template_category = $($el.parent().parent().find('i')[0]).attr('title');
|
|
|
|
if (labelEditor.getValue() !== '' && !confirm_already_shown) {
|
|
var dialog = $('#confirm-config-template-dialog');
|
|
dialog.modal({
|
|
closable: true,
|
|
keyboardShortcuts: true,
|
|
onApprove: function () {
|
|
addTemplateConfig($el);
|
|
}
|
|
}).modal('show');
|
|
|
|
// close on enter, unfortunately keyboardShortcuts doesn't work
|
|
dialog.on('keypress', function () {
|
|
if (event.keyCode === 13) {
|
|
dialog.modal('hide');
|
|
addTemplateConfig($el);
|
|
}
|
|
});
|
|
|
|
confirm_already_shown = true;
|
|
|
|
} else {
|
|
addTemplateConfig($el);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
var iframeTimer = null;
|
|
|
|
function debounce(func, wait, immediate) {
|
|
let timeout;
|
|
|
|
return function () {
|
|
const context = this, args = arguments;
|
|
const later = () => {
|
|
timeout = null;
|
|
if (!immediate) func.apply(context, args);
|
|
};
|
|
const callNow = immediate && !timeout;
|
|
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
if (callNow) func.apply(context, args);
|
|
};
|
|
}
|
|
|
|
var prev_completion = null;
|
|
|
|
// serialize editor output by timer
|
|
setInterval(function () {
|
|
let iframe = document.getElementById('render-editor');
|
|
if (iframe !== null) {
|
|
let Htx = iframe.contentWindow.Htx;
|
|
if (typeof Htx !== 'undefined') {
|
|
var completion = JSON.stringify(Htx.completionStore.selected.serializeCompletion(), null, 4);
|
|
if (prev_completion !== completion) {
|
|
if (completion.length > 3000) {
|
|
completion = completion.slice(0, 3000) + ' ...';
|
|
}
|
|
$('#data-results').text(completion);
|
|
prev_completion = completion;
|
|
}
|
|
}
|
|
}
|
|
}, 500);
|
|
|
|
|
|
var host = "https://app.heartex.ai";
|
|
var url_string = window.location.href;
|
|
var url = new URL(url_string);
|
|
|
|
// Label code mirror
|
|
var labelEditor = CodeMirror.fromTextArea(document.getElementById('id_label_config'), {
|
|
lineNumbers: true,
|
|
mode: "text/html"
|
|
});
|
|
labelEditor.focus();
|
|
|
|
var _c = url.searchParams.get("config");
|
|
if (_c && _c.length > 0) {
|
|
var config = url.searchParams.get("config");
|
|
config = config.replace(/[<][b][r][>]/gm, "\n");
|
|
labelEditor.setValue(config);
|
|
} else {
|
|
labelEditor.setValue($('#start-template').html());
|
|
}
|
|
validate_config(labelEditor);
|
|
|
|
// refresh for proper line numbers drawing
|
|
labelEditor.refresh();
|
|
// add validation
|
|
labelEditor.on('change', debounce(function (editor) {
|
|
validate_config(editor);
|
|
}, 500));
|
|
|
|
window.labelEditor = labelEditor;
|
|
|
|
function validate_name() {
|
|
let name = $('#id_title').val();
|
|
validation_message('', 0);
|
|
return 0;
|
|
}
|
|
|
|
function validation_message(msg, status) {
|
|
let o = $('.validation');
|
|
o.text(msg);
|
|
|
|
if (status === -1) {
|
|
o.removeClass('hidden');
|
|
o.addClass('visible');
|
|
}
|
|
if (status === 0) {
|
|
o.removeClass('visible');
|
|
o.addClass('hidden');
|
|
}
|
|
}
|
|
|
|
// storage of validation results
|
|
// let is_collection_ok = false;
|
|
let is_label_ok = false;
|
|
|
|
function editor_iframe(res) {
|
|
// generate new iframe
|
|
let iframe = $('<iframe><iframe>');
|
|
iframe.className = "editor-preview";
|
|
// add iframe to wrapper div
|
|
$('#editor-wrap').html(iframe);
|
|
$('#editor-wrap').fadeIn();
|
|
|
|
iframe.on('load', function () {
|
|
// remove old iframe
|
|
$('#render-editor').hide();
|
|
$('#render-editor').remove();
|
|
// assign id to new iframe
|
|
iframe.attr('id', 'render-editor');
|
|
// force to hide undo / redo / reset buttons
|
|
$('#render-editor').contents().find('head').append('<style>.ls-panel{display:none;}' +
|
|
'.ls-editor { margin-left: 3px!important;}</style>');
|
|
iframe.show();
|
|
let obj = document.getElementById('render-editor');
|
|
|
|
// wait until all images and resources from iframe loading
|
|
clearTimeout(iframeTimer);
|
|
iframeTimer = setInterval(function () {
|
|
if (obj.contentWindow) {
|
|
obj.style.height = (obj.contentWindow.document.body.scrollHeight) + 'px';
|
|
}
|
|
}, 100);
|
|
// hide "..."
|
|
$('#preload-editor').hide();
|
|
});
|
|
|
|
// load new data into iframe
|
|
iframe.attr('srcdoc', res);
|
|
}
|
|
|
|
function show_render_editor(editor) {
|
|
let config = labelEditor.getValue();
|
|
edit_count++;
|
|
$.ajax({
|
|
url: host + '/demo/render-editor?full_editor=t&playground=1',
|
|
method: 'POST',
|
|
xhrFields: { withCredentials: true },
|
|
data: {
|
|
config: config,
|
|
lookup: lookup,
|
|
page_hash: page_hash,
|
|
user_hash: user_hash,
|
|
current_template_name : current_template_name,
|
|
current_template_category: current_template_category,
|
|
edit_count: edit_count
|
|
},
|
|
success: editor_iframe,
|
|
error: function () {
|
|
$('#preload-editor').show();
|
|
}
|
|
})
|
|
}
|
|
|
|
// send request to server with configs to validate
|
|
function validate_config(editor) {
|
|
|
|
// get current scheme type from current editor
|
|
let url = host + '/api/projects/validate/';
|
|
let val = labelEditor.getValue();
|
|
|
|
if (!val.length)
|
|
return;
|
|
|
|
// label config validation
|
|
$.ajax({
|
|
url: url,
|
|
method: 'POST',
|
|
data: {label_config: val},
|
|
success: function (res) {
|
|
is_label_ok = true;
|
|
validation_message('', 0);
|
|
$('#render-editor').show();
|
|
show_render_editor(editor);
|
|
// check_submit_button();
|
|
},
|
|
error: function (res) {
|
|
is_label_ok = false;
|
|
validation_message(res.responseJSON['label_config'][0], -1);
|
|
$('#render-editor').hide();
|
|
// check_submit_button();
|
|
}
|
|
});
|
|
|
|
// load sample task
|
|
$.post({
|
|
url: host + '/business/projects/upload-example/?playground=1',
|
|
data: {label_config: val}
|
|
})
|
|
.fail(function(o) {
|
|
$('#upload-data-example').text('...')
|
|
})
|
|
.done(function(o) {
|
|
$('#upload-data-example').text(JSON.stringify(JSON.parse(o), null, 4))
|
|
})
|
|
}
|
|
|
|
$('.share-buttons .copy').on('click', function() {
|
|
copyToClipboard(labelEditor.getValue());
|
|
$(event.target).css('color', 'green');
|
|
});
|
|
|
|
$('.share-buttons .share').on('click', function() {
|
|
let config = labelEditor.getValue();
|
|
config = encodeURIComponent(config.replace(/(\r\n|\n|\r)/gm, "<br>"));
|
|
let link = window.location.origin + window.location.pathname + '?config=' + config;
|
|
copyToClipboard(link);
|
|
$(event.target).css('color', 'green');
|
|
});
|
|
|
|
|
|
});
|
|
</script> |