image-bbox / layouts /task_layout.html
davidjurgens's picture
Deploy Potato demo: Potato — Image Bounding Boxes
a98230c verified
Raw
History Blame Contribute Delete
17.7 kB
<!-- CONFIG_HASH: 699e5e95747aae42d452fa7b4d557076_51f6085f563f5d680c0377ce351f17b0 -->
<!-- Generated annotation layout file -->
<!-- This file was automatically generated based on the annotation schemes in your config -->
<!-- You can customize this file to modify the layout of your annotation interface -->
<!-- Changes to this file will be preserved across server restarts -->
<div class="annotation_schema">
<form id="object_detection" class="annotation-form image-annotation" action="javascript:void(0)">
<fieldset schema="object_detection">
<legend>Draw boxes around objects you see in the image. Use the polygon tool for irregular shapes.</legend>
<!-- Image Annotation Container -->
<div class="image-annotation-container" data-schema="object_detection" data-ai-enabled="false">
<!-- Toolbar -->
<div class="image-annotation-toolbar">
<!-- Tool buttons -->
<div class="tool-group">
<span class="tool-group-label">Tools:</span>
<button type="button" class="tool-btn" data-tool="bbox" title="Bounding Box (B)">□ Box</button>
<button type="button" class="tool-btn" data-tool="polygon" title="Polygon (P)">⬡ Polygon</button>
</div>
<!-- Label selector -->
<div class="label-group">
<span class="tool-group-label">Label:</span>
<button type="button" class="label-btn" data-label="person" data-color="#FF6B6B" title="person (1)" style="--label-color: #FF6B6B;"><span class="label-color-dot" style="background-color: #FF6B6B;"></span>person</button>
<button type="button" class="label-btn" data-label="vehicle" data-color="#4ECDC4" title="vehicle (2)" style="--label-color: #4ECDC4;"><span class="label-color-dot" style="background-color: #4ECDC4;"></span>vehicle</button>
<button type="button" class="label-btn" data-label="animal" data-color="#45B7D1" title="animal (3)" style="--label-color: #45B7D1;"><span class="label-color-dot" style="background-color: #45B7D1;"></span>animal</button>
<button type="button" class="label-btn" data-label="object" data-color="#96CEB4" title="object (4)" style="--label-color: #96CEB4;"><span class="label-color-dot" style="background-color: #96CEB4;"></span>object</button>
</div>
<!-- Zoom controls -->
<div class="zoom-group">
<button type="button" class="zoom-btn" data-action="zoom-in" title="Zoom In (+)">+</button>
<button type="button" class="zoom-btn" data-action="zoom-out" title="Zoom Out (-)">-</button>
<button type="button" class="zoom-btn" data-action="zoom-fit" title="Fit to View (0)">Fit</button>
<button type="button" class="zoom-btn" data-action="zoom-reset" title="Reset Zoom">100%</button>
</div>
<!-- Edit controls -->
<div class="edit-group">
<button type="button" class="edit-btn" data-action="undo" title="Undo (Ctrl+Z)">Undo</button>
<button type="button" class="edit-btn" data-action="redo" title="Redo (Ctrl+Shift+Z)">Redo</button>
<button type="button" class="edit-btn delete-btn" data-action="delete" title="Delete Selected (Del)">Delete</button>
</div>
<!-- Brush size control (shown when brush/eraser selected) -->
<div class="brush-size-group" style="display: none;">
<span class="tool-group-label">Size:</span>
<input type="range" class="brush-size-slider" min="1" max="100" value="20" title="Brush Size">
<span class="brush-size-value">20</span>
</div>
<!-- Annotation count -->
<div class="count-group">
<span class="annotation-count">Annotations: <span class="count-value">0</span></span>
</div>
</div>
<!-- Canvas wrapper -->
<div class="canvas-wrapper">
<canvas id="canvas-object_detection" class="annotation-canvas"></canvas>
<!-- Mask canvas for segmentation (overlaid on top) -->
<canvas id="mask-canvas-object_detection" class="mask-canvas" style="display: none;"></canvas>
</div>
<!-- Hidden input for storing annotation data -->
<input type="hidden"
name="object_detection"
id="input-object_detection"
class="annotation-data-input"
value="">
<!-- Hidden input for storing mask data (RLE encoded) -->
<input type="hidden"
name="object_detection_masks"
id="mask-input-object_detection"
class="mask-data-input"
value="">
</div>
<!-- Initialize the annotation manager -->
<script>
(function() {
// Wait for DOM and dependencies
function initWhenReady() {
if (typeof ImageAnnotationManager === 'undefined') {
setTimeout(initWhenReady, 100);
return;
}
var container = document.querySelector('.image-annotation-container[data-schema="object_detection"]');
if (!container) return;
var config = {"schemaName": "object_detection", "tools": ["bbox", "polygon"], "labels": [{"name": "person", "color": "#FF6B6B", "key_value": "1"}, {"name": "vehicle", "color": "#4ECDC4", "key_value": "2"}, {"name": "animal", "color": "#45B7D1", "key_value": "3"}, {"name": "object", "color": "#96CEB4", "key_value": "4"}], "zoomEnabled": true, "panEnabled": true, "minAnnotations": 1, "maxAnnotations": null, "freeformBrushSize": 5, "freeformSimplify": true, "brushSize": 20, "eraserSize": 20, "maskOpacity": 0.5, "aiSupport": false, "aiFeatures": {}, "sourceField": ""};
var canvasId = 'canvas-object_detection';
var inputId = 'input-object_detection';
// Get image URL from the instance display
// Priority: 1) data-image-url on #text-content, 2) img src, 3) text content itself
var instanceContainer = document.getElementById('instance-text');
console.log('[ImageAnnotation] Instance container:', instanceContainer);
var textContent = instanceContainer ? instanceContainer.querySelector('#text-content') : null;
console.log('[ImageAnnotation] Text content element:', textContent);
var imageUrl = null;
// Method 1: Check data-image-url attribute (set by template when has_image_annotation=true)
if (textContent) {
imageUrl = textContent.getAttribute('data-image-url');
if (imageUrl) {
console.log('[ImageAnnotation] Found URL from data-image-url:', imageUrl);
}
}
// Method 2: Fallback to looking for an img element with data-source-url or src
if (!imageUrl && instanceContainer) {
var imgElement = instanceContainer.querySelector('img');
if (imgElement) {
imageUrl = imgElement.getAttribute('data-source-url') || imgElement.src;
console.log('[ImageAnnotation] Found URL from img element:', imageUrl);
}
}
// Method 3: If text content looks like a URL, use it directly
if (!imageUrl && textContent) {
var textVal = textContent.textContent.trim();
if (textVal.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif|webp|svg)/i)) {
imageUrl = textVal;
console.log('[ImageAnnotation] Found URL from text content:', imageUrl);
}
}
// Method 4: Check source_field in instance data (via display fields)
if (!imageUrl && config.sourceField) {
var displayFields = document.querySelectorAll('[data-field-key="' + config.sourceField + '"]');
displayFields.forEach(function(field) {
var url = field.getAttribute('data-source-url') || field.getAttribute('src');
if (url && !imageUrl) {
imageUrl = url;
console.log('[ImageAnnotation] Found URL from source_field display:', imageUrl);
}
});
}
if (!imageUrl) {
console.error('[ImageAnnotation] No image URL found! Check that the instance data contains an image URL.');
}
// Initialize manager
console.log('Initializing ImageAnnotationManager with canvas:', canvasId);
var manager = new ImageAnnotationManager(canvasId, inputId, config);
// Store reference on container
container.annotationManager = manager;
// Load image if available
if (imageUrl) {
console.log('Calling loadImage with:', imageUrl);
manager.loadImage(imageUrl);
} else {
console.warn('No image URL found for annotation');
}
// Wire up toolbar buttons
container.querySelectorAll('.tool-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var tool = this.dataset.tool;
manager.setTool(tool);
container.querySelectorAll('.tool-btn').forEach(function(b) {
b.classList.remove('active');
});
this.classList.add('active');
// Show/hide brush size control for brush/eraser tools
var brushSizeGroup = container.querySelector('.brush-size-group');
if (brushSizeGroup) {
brushSizeGroup.style.display = (tool === 'brush' || tool === 'eraser') ? 'flex' : 'none';
}
});
});
// Wire up brush size slider
var brushSlider = container.querySelector('.brush-size-slider');
var brushSizeValue = container.querySelector('.brush-size-value');
if (brushSlider) {
brushSlider.addEventListener('input', function() {
var size = parseInt(this.value);
if (brushSizeValue) brushSizeValue.textContent = size;
manager.setBrushSize(size);
});
}
container.querySelectorAll('.label-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var label = this.dataset.label;
var color = this.dataset.color;
manager.setLabel(label, color);
container.querySelectorAll('.label-btn').forEach(function(b) {
b.classList.remove('active');
});
this.classList.add('active');
});
});
container.querySelectorAll('.zoom-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var action = this.dataset.action;
if (action === 'zoom-in') manager.zoom(1.2);
else if (action === 'zoom-out') manager.zoom(0.8);
else if (action === 'zoom-fit') manager.zoomFit();
else if (action === 'zoom-reset') manager.zoomReset();
});
});
container.querySelectorAll('.edit-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var action = this.dataset.action;
if (action === 'undo') manager.undo();
else if (action === 'redo') manager.redo();
else if (action === 'delete') manager.deleteSelected();
});
});
// Set default tool and label
var firstToolBtn = container.querySelector('.tool-btn');
if (firstToolBtn) firstToolBtn.click();
var firstLabelBtn = container.querySelector('.label-btn');
if (firstLabelBtn) firstLabelBtn.click();
// Update count display
manager.onAnnotationChange = function(count) {
var countEl = container.querySelector('.count-value');
if (countEl) countEl.textContent = count;
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWhenReady);
} else {
initWhenReady();
}
})();
</script>
</fieldset>
</form>
<form id="image_quality" class="annotation-form radio shadcn-radio-container" action="javascript:void(0)" data-annotation-id="1" data-annotation-type="radio" data-schema-name="image_quality" data-grid-columns="1">
<fieldset schema="image_quality">
<legend class="shadcn-radio-title">How clear is this image?</legend>
<div class="shadcn-radio-options">
<div class="shadcn-radio-option">
<input class="image_quality shadcn-radio-input annotation-input"
type="radio"
id="image_quality_Very clear_radio"
name="image_quality"
value="Very clear"
selection_constraint="single"
schema="image_quality"
label_name="Very clear"
onclick="onlyOne(this);registerAnnotation(this);"
validation=""
data-key="c">
<label for="image_quality_Very clear_radio" class="shadcn-radio-label" >Very Clear <span class="keybinding-badge">C</span></label>
</div>
<div class="shadcn-radio-option">
<input class="image_quality shadcn-radio-input annotation-input"
type="radio"
id="image_quality_Somewhat clear_radio"
name="image_quality"
value="Somewhat clear"
selection_constraint="single"
schema="image_quality"
label_name="Somewhat clear"
onclick="onlyOne(this);registerAnnotation(this);"
validation=""
data-key="s">
<label for="image_quality_Somewhat clear_radio" class="shadcn-radio-label" >Somewhat Clear <span class="keybinding-badge">S</span></label>
</div>
<div class="shadcn-radio-option">
<input class="image_quality shadcn-radio-input annotation-input"
type="radio"
id="image_quality_Blurry_radio"
name="image_quality"
value="Blurry"
selection_constraint="single"
schema="image_quality"
label_name="Blurry"
onclick="onlyOne(this);registerAnnotation(this);"
validation=""
data-key="b">
<label for="image_quality_Blurry_radio" class="shadcn-radio-label" >Blurry <span class="keybinding-badge">B</span></label>
</div>
</div></fieldset></form>
</div>