Skip to content

Instantly share code, notes, and snippets.

@dodijk
Last active July 14, 2020 13:10
Show Gist options
  • Save dodijk/79a437ba36f5151e4c9e558f43812b22 to your computer and use it in GitHub Desktop.
Save dodijk/79a437ba36f5151e4c9e558f43812b22 to your computer and use it in GitHub Desktop.
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
/>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="https://api.labelbox.com/static/labeling-api.js"></script>
<style>
.app {
height: 100vh;
}
.flex-column {
flex-direction: column;
}
.flex-grow {
display: flex;
flex-grow: 1;
}
.content {
margin: 0px 20px;
}
.asset {
max-width: 40%;
max-height: 40%;
}
.sidebar {
display: flex;
height: 100vh;
border-right: 1px solid #e8e8e8;
width: 300px;
min-width: 300px;
}
.text {
background-color: #ececec;
padding: 30px;
font-size: 20px;
overflow: auto;
font-style: italic;
}
.navbar {
padding: 10px;
border-bottom: 1px solid #e8e8e8;
}
.btn {
background-color: aqua
}
.btn:visited {
background-color: yellow
}
.btn:hover {
background-color: skyblue
}
.btn:active {
background-color: pink;
}
.btn:focus {
background-color: pink;
}
#questions {
padding: 10px;
overflow-y: auto;
height: 100%;
}
.label {
color: #717171;
}
.question {
margin-bottom: 15px;
}
[type="radio"]:checked+span:after, [type="radio"].with-gap:checked+span:before, [type="radio"].with-gap:checked+span:after {
border-color: #03a9f4 !important;
}
[type="checkbox"].filled-in:checked+span:not(.lever):after {
border-color: #03a9f4 !important;
background-color: #03a9f4 !important;
}
input.materialize-textarea:focus:not([readonly]) {
border-color: #03a9f4 !important;
}
</style>
<div class="app flex-grow">
<div class="flex-column sidebar">
<div class="navbar">
<i
class="material-icons"
style="color: #9b9b9b; cursor: pointer;"
onclick="goHome()"
>home</i
>
</div>
<div id="questions"></div>
<div class="flex-grow"></div>
<div style="display: flex;">
<a
class="waves-effect waves-light btn-large"
style="background-color: white; color: black; width: 100%;"
onclick="skip()"
>Overslaan</a
>
<a
class="waves-effect waves-light btn-large"
style="background-color: #03a9f4; width: 100%;"
onclick="submit()"
>Versturen</a
>
</div>
</div>
<div class="flex-grow flex-column content">
<div style="display: flex; align-items: center; padding: 10px 0px">
<i
id="back"
class="material-icons"
style="color: #9b9b9b; margin-left: -5px; opacity: 0.2;"
onclick="goBack()"
>keyboard_arrow_left</i
>
<div style="color: #717171; padding: 0px 10px;" id="externalid">
Label deze plaatjes
</div>
<i
id="next"
class="material-icons"
style="color: #9b9b9b; margin-left: -5px; opacity: 0.2;"
onclick="goNext()"
>keyboard_arrow_right</i
>
</div>
<div id="asset">loading...</div>
</div>
</div>
<script>
let state = {
projectId: new URL(window.location.href).searchParams.get("project"),
currentAsset: undefined
};
const defaultConfiguration = {
classifications: [
{
name: "model",
instructions: "Select the car model",
type: "radio",
options: [
{
value: "model_s",
label: "Tesla Model S"
},
{
value: "model_3",
label: "Tesla Model 3"
},
{
value: "model_x",
label: "Tesla Model X"
}
]
},
{
name: "image_problems",
instructions: "Select all that apply",
type: "checklist",
options: [
{
value: "blur",
label: "Blurry"
},
{
value: "saturated",
label: "Over Saturated"
},
{
value: "pixelated",
label: "Pixelated"
}
]
},
{
name: "description",
instructions: "Describe this image",
type: "text"
}
]
};
function createOptionQuestion({ type, name, options, instructions }, answer) {
const createOption = ({ value, label }) => {
return `
<p value=${value}>
<label>
${
type == "radio"
? `<input class="with-gap" type="radio" name="group-${name}" valuetosubmit="${value}" ${
answer === value ? "checked" : ""
} />`
: `<input type="checkbox" class="filled-in" valuetosubmit="${value}" ${
(answer || []).indexOf(value) !== -1 ? "checked" : ""
} />`
}
<span>${label}</span>
</label>
</p>
`;
};
return `
<div class="question" id="${name}" questiontype="${type}">
<div class="label">${instructions}</div>
<form action="#">
${options.map(createOption).join("")}
</form>
</div>
`;
}
function createTextInput({ id, label }, answer) {
return `
<div class="question" id="${id}" questiontype="text">
<div class="input-field col s12">
<div class="label">${label}</div>
<input class="materialize-textarea" data-length="120" value="${answer ||
""}"></input>
</div>
</div>
`;
}
function createQuestion(question, answers) {
const optionalAnswer = (answers || {})[question.name];
if (question.type === "text") {
return createTextInput(
{
id: question.name,
label: question.instructions
},
optionalAnswer
);
}
if (question.type === "radio" || question.type === "checklist") {
return createOptionQuestion(question, optionalAnswer);
}
console.log("Unknown question type", question);
}
let classifications = [];
let markQuestionsAsLoaded;
new Promise(resolve => {
markQuestionsAsLoaded = resolve;
}).then(() => {
Labelbox.currentAsset().subscribe(asset => {
if (asset) {
drawAsset(asset);
}
});
});
function drawQuestions(classifications, answers) {
if (answers && answers.chosenImageId) {
document.querySelector("#chose-" + answers.chosenImageId).checked = true;
}
document.querySelector("#questions").innerHTML = classifications
.map(classification => createQuestion(classification, answers))
.join("");
}
Labelbox.getTemplateCustomization().subscribe(customization => {
classifications =
(customization && customization.classifications) ||
defaultConfiguration.classifications;
drawQuestions(classifications);
markQuestionsAsLoaded();
});
function goHome() {
window.location.href =
"https://app.labelbox.com/projects/" + state.projectId;
}
function skip() {
Labelbox.skip().then(() => {
Labelbox.fetchNextAssetToLabel();
});
}
function getChosenImageId() {
if (document.querySelector("#chose-" + state.leftImageId).checked) {
return state.leftImageId;
}
if (document.querySelector("#chose-" + state.rightImageId).checked) {
return state.rightImageId;
}
return;
}
function getLabel() {
const chosenImageId = getChosenImageId();
const getAnswer = node => {
const key = node.getAttribute("id");
const type = node.getAttribute("questiontype");
if (type === "text") {
return {
[key]: node.querySelector("input").value
};
}
if (type === "radio") {
const inputs = Array.from(node.querySelectorAll("input"));
const selected = inputs.find(child => child.checked);
return {
[key]: selected && selected.getAttribute("valuetosubmit")
};
}
if (type === "checklist") {
const inputs = Array.from(node.querySelectorAll("input"));
const value = inputs
.filter(child => child.checked)
.map(child => child.getAttribute("valuetosubmit"));
return {
[key]: value
};
}
console.log("Unable to find type for", node);
};
const answers = Array.from(
document.querySelector("#questions").children
).map(getAnswer);
return Object.assign({ chosenImageId }, ...answers);
}
function submit() {
const label = JSON.stringify(getLabel());
const jumpToNext = Boolean(!state.currentAsset.label);
// Progress is this asset is new
Labelbox.setLabelForAsset(label).then(() => {
if (jumpToNext) {
Labelbox.fetchNextAssetToLabel();
}
});
if (jumpToNext) {
document.querySelector("#asset").innerHTML = "loading...";
}
}
function goBack() {
if (state.currentAsset.previous) {
Labelbox.setLabelAsCurrentAsset(state.currentAsset.previous);
}
}
function goNext() {
if (state.currentAsset.next) {
Labelbox.setLabelAsCurrentAsset(state.currentAsset.next);
} else {
Labelbox.fetchNextAssetToLabel();
}
}
function getHtmlForAsset({ leftImage, rightImage }) {
return `
<div style="display: flex; flex-direction: column;">
<div style="font-size: 25px; margin: 10px;">Welke afbeelding spreekt je meer aan?</div>
<div style="display: flex; flex-direction: row; justify-content: space-around;">
<img class="asset" src="${
leftImage.url
}" controls style="flex: 47" id="${leftImage.id}"></img>
<img class="asset" src="${
rightImage.url
}" controls style="flex: 47" id="${rightImage.id}"></img>
</div>
<div style="display: flex; flex-direction: row;">
<label style="display: flex; justify-content: center; flex: 47">
<input class="with-gap" type="radio" name="group-image-compare" id="chose-${
leftImage.id
}" />
<span style="font-size: 20px; margin-top: 20px;">Afbeelding A</span>
</label>
<label style="display: flex; justify-content: center; flex: 47">
<input class="with-gap" type="radio" name="group-image-compare" id="chose-${
rightImage.id
}" />
<span style="font-size: 20px; margin-top: 20px;">Afbeelding B</span>
</label>
</div>
</div>
`;
}
function getImages(data) {
try {
const {
compare: { a, b }
} = JSON.parse(data);
if (!a.id || !a.url || !b.id || !b.url) {
throw new Error(
"both a and b must have an id that is used in the label"
);
}
return {
leftImage: a,
rightImage: b
};
} catch (e) {
console.log(e);
alert(
"DataRow format does not match this template. Please see the chrome console for more information"
);
throw new Error(
`Please see the example dataset. DataRows must match this format "{\"compare\":{\"a\":{\"id\":\"your-db-id-89E30C47-5807-5622-AA3D-D390AFE53728\",\"url\":\"http://commondatastorage.googleapis.com/gtv-images-bucket/sample/BigBuckBunny.mp4\"},\"b\":{\"id\":\"your-db-id-0D10F70F-F865-A5B0-A548-7CB0176324AF\",\"url\":\"http://commondatastorage.googleapis.com/gtv-images-bucket/sample/ElephantsDream.mp4\"}}}". Not ${data}`
);
}
}
function drawAsset(asset) {
const backButton = document.querySelector("#back");
backButton.style.opacity = asset.previous ? 1 : 0.2;
backButton.style.cursor = asset.previous ? "pointer" : "inherit";
const hasNext = Boolean(asset.next || asset.label);
const nextButton = document.querySelector("#next");
nextButton.style.opacity = hasNext ? 1 : 0.2;
nextButton.style.cursor = hasNext ? "pointer" : "inherit";
const { leftImage, rightImage } = getImages(asset.data);
if ((state.currentAsset && state.currentAsset.data) !== asset.data) {
document.querySelector("#asset").innerHTML = getHtmlForAsset({
leftImage,
rightImage
});
}
if ((state.currentAsset && state.currentAsset.id) !== asset.id) {
if (asset.label) {
try {
const label = JSON.parse(asset.label);
drawQuestions(classifications, label);
} catch (e) {
console.log("failed to read label", e);
}
} else {
drawQuestions(classifications);
}
}
state = {
...state,
currentAsset: asset,
leftImageId: leftImage.id,
rightImageId: rightImage.id
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment