|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { appState, setCurrentEmbryoIndex, getEmbryoRemark } from '../state.js';
|
|
|
import { loadImage } from '../utils/imageUtils.js';
|
|
|
import { showToast } from './toast.js';
|
|
|
import { showConfirmModal } from './modal.js';
|
|
|
|
|
|
let canvas, ctx;
|
|
|
let selectedEmbryoIndex = null;
|
|
|
let isDragging = false;
|
|
|
let dragType = null;
|
|
|
let dragStart = { x: 0, y: 0 };
|
|
|
let originalBox = null;
|
|
|
let imageElement = null;
|
|
|
let scaleFactor = 1;
|
|
|
let offsetX = 0;
|
|
|
let offsetY = 0;
|
|
|
|
|
|
const HANDLE_SIZE = 10;
|
|
|
const COLORS = [
|
|
|
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
|
|
|
'#F7DC6F', '#BB8FCE', '#85C1E2', '#F8B739', '#52B788'
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function initializeInlineDetection(imageData, detections) {
|
|
|
canvas = document.getElementById('detectionCanvas');
|
|
|
if (!canvas) {
|
|
|
console.error('Detection canvas not found');
|
|
|
showToast('Failed to initialize detection view', 'error');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
ctx = canvas.getContext('2d');
|
|
|
if (!ctx) {
|
|
|
console.error('Failed to get canvas context');
|
|
|
showToast('Canvas not supported', 'error');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
imageElement = await loadImage(imageData);
|
|
|
|
|
|
|
|
|
canvas.width = imageElement.width;
|
|
|
canvas.height = imageElement.height;
|
|
|
|
|
|
|
|
|
const container = canvas.parentElement;
|
|
|
if (!container) {
|
|
|
console.error('Canvas container not found');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const maxWidth = container.clientWidth - 40;
|
|
|
const maxHeight = 600;
|
|
|
|
|
|
scaleFactor = Math.min(
|
|
|
maxWidth / imageElement.width,
|
|
|
maxHeight / imageElement.height,
|
|
|
1
|
|
|
);
|
|
|
|
|
|
canvas.style.width = `${imageElement.width * scaleFactor}px`;
|
|
|
canvas.style.height = `${imageElement.height * scaleFactor}px`;
|
|
|
|
|
|
|
|
|
drawDetections();
|
|
|
|
|
|
|
|
|
canvas.addEventListener('mousedown', handleMouseDown);
|
|
|
canvas.addEventListener('mousemove', handleMouseMove);
|
|
|
canvas.addEventListener('mouseup', handleMouseUp);
|
|
|
canvas.addEventListener('mouseleave', handleMouseUp);
|
|
|
|
|
|
|
|
|
canvas.addEventListener('touchstart', handleTouchStart);
|
|
|
canvas.addEventListener('touchmove', handleTouchMove);
|
|
|
canvas.addEventListener('touchend', handleTouchEnd);
|
|
|
|
|
|
|
|
|
updateEmbryoList(detections);
|
|
|
|
|
|
|
|
|
const info = document.getElementById('detectionInfo');
|
|
|
if (info) {
|
|
|
info.innerHTML = `
|
|
|
<p>Click on an embryo to adjust its crop area</p>
|
|
|
<button class="btn btn-secondary" id="addManualEmbryoBtn" style="margin-top: 10px;">
|
|
|
+ Add Embryo Manually
|
|
|
</button>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
setupManualAddButton();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawDetections() {
|
|
|
if (!imageElement || !ctx) return;
|
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
ctx.drawImage(imageElement, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
appState.croppedEmbryos.forEach((detection, index) => {
|
|
|
const isSelected = index === selectedEmbryoIndex;
|
|
|
const color = COLORS[index % COLORS.length];
|
|
|
|
|
|
drawBoundingBox(detection.box, color, isSelected, index);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawBoundingBox(box, color, isSelected, index) {
|
|
|
const lineWidth = isSelected ? 3 : 2;
|
|
|
const alpha = isSelected ? 1 : 0.7;
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = color;
|
|
|
ctx.lineWidth = lineWidth;
|
|
|
ctx.globalAlpha = alpha;
|
|
|
ctx.strokeRect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1);
|
|
|
|
|
|
|
|
|
const label = `Embryo ${index + 1}`;
|
|
|
ctx.font = '14px Arial';
|
|
|
const textWidth = ctx.measureText(label).width;
|
|
|
const labelHeight = 20;
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
ctx.globalAlpha = 0.9;
|
|
|
ctx.fillRect(box.x1, box.y1 - labelHeight, textWidth + 10, labelHeight);
|
|
|
|
|
|
|
|
|
ctx.fillStyle = 'white';
|
|
|
ctx.globalAlpha = 1;
|
|
|
ctx.fillText(label, box.x1 + 5, box.y1 - 5);
|
|
|
|
|
|
|
|
|
if (isSelected) {
|
|
|
drawResizeHandles(box, color);
|
|
|
}
|
|
|
|
|
|
ctx.globalAlpha = 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function drawResizeHandles(box, color) {
|
|
|
const handles = [
|
|
|
{ x: box.x1, y: box.y1, type: 'nw' },
|
|
|
{ x: box.x2, y: box.y1, type: 'ne' },
|
|
|
{ x: box.x1, y: box.y2, type: 'sw' },
|
|
|
{ x: box.x2, y: box.y2, type: 'se' },
|
|
|
{ x: (box.x1 + box.x2) / 2, y: box.y1, type: 'n' },
|
|
|
{ x: (box.x1 + box.x2) / 2, y: box.y2, type: 's' },
|
|
|
{ x: box.x1, y: (box.y1 + box.y2) / 2, type: 'w' },
|
|
|
{ x: box.x2, y: (box.y1 + box.y2) / 2, type: 'e' }
|
|
|
];
|
|
|
|
|
|
ctx.fillStyle = color;
|
|
|
handles.forEach(handle => {
|
|
|
ctx.fillRect(
|
|
|
handle.x - HANDLE_SIZE / 2,
|
|
|
handle.y - HANDLE_SIZE / 2,
|
|
|
HANDLE_SIZE,
|
|
|
HANDLE_SIZE
|
|
|
);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getMousePos(e) {
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
const scaleX = canvas.width / rect.width;
|
|
|
const scaleY = canvas.height / rect.height;
|
|
|
|
|
|
return {
|
|
|
x: (e.clientX - rect.left) * scaleX,
|
|
|
y: (e.clientY - rect.top) * scaleY
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getHandleAtPosition(box, x, y) {
|
|
|
const handles = [
|
|
|
{ x: box.x1, y: box.y1, type: 'nw' },
|
|
|
{ x: box.x2, y: box.y1, type: 'ne' },
|
|
|
{ x: box.x1, y: box.y2, type: 'sw' },
|
|
|
{ x: box.x2, y: box.y2, type: 'se' },
|
|
|
{ x: (box.x1 + box.x2) / 2, y: box.y1, type: 'n' },
|
|
|
{ x: (box.x1 + box.x2) / 2, y: box.y2, type: 's' },
|
|
|
{ x: box.x1, y: (box.y1 + box.y2) / 2, type: 'w' },
|
|
|
{ x: box.x2, y: (box.y1 + box.y2) / 2, type: 'e' }
|
|
|
];
|
|
|
|
|
|
for (const handle of handles) {
|
|
|
const dist = Math.sqrt(
|
|
|
Math.pow(x - handle.x, 2) + Math.pow(y - handle.y, 2)
|
|
|
);
|
|
|
if (dist < HANDLE_SIZE) {
|
|
|
return handle.type;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isPointInBox(box, x, y) {
|
|
|
return x >= box.x1 && x <= box.x2 && y >= box.y1 && y <= box.y2;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleMouseDown(e) {
|
|
|
const pos = getMousePos(e);
|
|
|
|
|
|
|
|
|
if (isDrawingNewBox) {
|
|
|
newBoxStart = pos;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (selectedEmbryoIndex !== null) {
|
|
|
const box = appState.croppedEmbryos[selectedEmbryoIndex].box;
|
|
|
const handle = getHandleAtPosition(box, pos.x, pos.y);
|
|
|
|
|
|
if (handle) {
|
|
|
isDragging = true;
|
|
|
dragType = handle;
|
|
|
dragStart = pos;
|
|
|
originalBox = { ...box };
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (isPointInBox(box, pos.x, pos.y)) {
|
|
|
isDragging = true;
|
|
|
dragType = 'move';
|
|
|
dragStart = pos;
|
|
|
originalBox = { ...box };
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
for (let i = appState.croppedEmbryos.length - 1; i >= 0; i--) {
|
|
|
const box = appState.croppedEmbryos[i].box;
|
|
|
if (isPointInBox(box, pos.x, pos.y)) {
|
|
|
selectEmbryo(i);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
selectedEmbryoIndex = null;
|
|
|
drawDetections();
|
|
|
updateEmbryoList(appState.croppedEmbryos);
|
|
|
|
|
|
|
|
|
const info = document.getElementById('detectionInfo');
|
|
|
if (info) {
|
|
|
info.innerHTML = `
|
|
|
<p>Click on an embryo to adjust its crop area</p>
|
|
|
<button class="btn btn-secondary" id="addManualEmbryoBtn" style="margin-top: 10px;">
|
|
|
+ Add Embryo Manually
|
|
|
</button>
|
|
|
`;
|
|
|
setupManualAddButton();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleMouseMove(e) {
|
|
|
const pos = getMousePos(e);
|
|
|
|
|
|
|
|
|
if (isDrawingNewBox && newBoxStart) {
|
|
|
drawDetections();
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = '#00B8D4';
|
|
|
ctx.lineWidth = 3;
|
|
|
ctx.setLineDash([5, 5]);
|
|
|
ctx.strokeRect(
|
|
|
newBoxStart.x,
|
|
|
newBoxStart.y,
|
|
|
pos.x - newBoxStart.x,
|
|
|
pos.y - newBoxStart.y
|
|
|
);
|
|
|
ctx.setLineDash([]);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!isDragging || selectedEmbryoIndex === null) {
|
|
|
|
|
|
updateCursor(pos);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const dx = pos.x - dragStart.x;
|
|
|
const dy = pos.y - dragStart.y;
|
|
|
|
|
|
const box = appState.croppedEmbryos[selectedEmbryoIndex].box;
|
|
|
|
|
|
if (dragType === 'move') {
|
|
|
|
|
|
box.x1 = Math.max(0, Math.min(originalBox.x1 + dx, canvas.width - (originalBox.x2 - originalBox.x1)));
|
|
|
box.y1 = Math.max(0, Math.min(originalBox.y1 + dy, canvas.height - (originalBox.y2 - originalBox.y1)));
|
|
|
box.x2 = box.x1 + (originalBox.x2 - originalBox.x1);
|
|
|
box.y2 = box.y1 + (originalBox.y2 - originalBox.y1);
|
|
|
} else {
|
|
|
|
|
|
resizeBox(box, originalBox, dx, dy, dragType);
|
|
|
}
|
|
|
|
|
|
drawDetections();
|
|
|
updateEmbryoCrop(selectedEmbryoIndex);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resizeBox(box, original, dx, dy, type) {
|
|
|
const minSize = 30;
|
|
|
|
|
|
switch (type) {
|
|
|
case 'nw':
|
|
|
box.x1 = Math.max(0, Math.min(original.x1 + dx, original.x2 - minSize));
|
|
|
box.y1 = Math.max(0, Math.min(original.y1 + dy, original.y2 - minSize));
|
|
|
break;
|
|
|
case 'ne':
|
|
|
box.x2 = Math.min(canvas.width, Math.max(original.x2 + dx, original.x1 + minSize));
|
|
|
box.y1 = Math.max(0, Math.min(original.y1 + dy, original.y2 - minSize));
|
|
|
break;
|
|
|
case 'sw':
|
|
|
box.x1 = Math.max(0, Math.min(original.x1 + dx, original.x2 - minSize));
|
|
|
box.y2 = Math.min(canvas.height, Math.max(original.y2 + dy, original.y1 + minSize));
|
|
|
break;
|
|
|
case 'se':
|
|
|
box.x2 = Math.min(canvas.width, Math.max(original.x2 + dx, original.x1 + minSize));
|
|
|
box.y2 = Math.min(canvas.height, Math.max(original.y2 + dy, original.y1 + minSize));
|
|
|
break;
|
|
|
case 'n':
|
|
|
box.y1 = Math.max(0, Math.min(original.y1 + dy, original.y2 - minSize));
|
|
|
break;
|
|
|
case 's':
|
|
|
box.y2 = Math.min(canvas.height, Math.max(original.y2 + dy, original.y1 + minSize));
|
|
|
break;
|
|
|
case 'w':
|
|
|
box.x1 = Math.max(0, Math.min(original.x1 + dx, original.x2 - minSize));
|
|
|
break;
|
|
|
case 'e':
|
|
|
box.x2 = Math.min(canvas.width, Math.max(original.x2 + dx, original.x1 + minSize));
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateCursor(pos) {
|
|
|
|
|
|
if (isDrawingNewBox) {
|
|
|
canvas.style.cursor = 'crosshair';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (selectedEmbryoIndex !== null) {
|
|
|
const box = appState.croppedEmbryos[selectedEmbryoIndex].box;
|
|
|
const handle = getHandleAtPosition(box, pos.x, pos.y);
|
|
|
|
|
|
if (handle) {
|
|
|
const cursors = {
|
|
|
'nw': 'nw-resize', 'ne': 'ne-resize',
|
|
|
'sw': 'sw-resize', 'se': 'se-resize',
|
|
|
'n': 'n-resize', 's': 's-resize',
|
|
|
'w': 'w-resize', 'e': 'e-resize'
|
|
|
};
|
|
|
canvas.style.cursor = cursors[handle];
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (isPointInBox(box, pos.x, pos.y)) {
|
|
|
canvas.style.cursor = 'move';
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
for (const detection of appState.croppedEmbryos) {
|
|
|
if (isPointInBox(detection.box, pos.x, pos.y)) {
|
|
|
canvas.style.cursor = 'pointer';
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
canvas.style.cursor = 'default';
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleMouseUp(e) {
|
|
|
|
|
|
if (isDrawingNewBox && newBoxStart) {
|
|
|
const pos = getMousePos(e);
|
|
|
const newBox = {
|
|
|
x1: newBoxStart.x,
|
|
|
y1: newBoxStart.y,
|
|
|
x2: pos.x,
|
|
|
y2: pos.y
|
|
|
};
|
|
|
|
|
|
completeManualCrop(newBox);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (isDragging && selectedEmbryoIndex !== null) {
|
|
|
|
|
|
updateEmbryoCrop(selectedEmbryoIndex);
|
|
|
showToast('Crop area updated', 'success');
|
|
|
}
|
|
|
|
|
|
isDragging = false;
|
|
|
dragType = null;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleTouchStart(e) {
|
|
|
e.preventDefault();
|
|
|
const touch = e.touches[0];
|
|
|
const mouseEvent = new MouseEvent('mousedown', {
|
|
|
clientX: touch.clientX,
|
|
|
clientY: touch.clientY
|
|
|
});
|
|
|
canvas.dispatchEvent(mouseEvent);
|
|
|
}
|
|
|
|
|
|
function handleTouchMove(e) {
|
|
|
e.preventDefault();
|
|
|
const touch = e.touches[0];
|
|
|
const mouseEvent = new MouseEvent('mousemove', {
|
|
|
clientX: touch.clientX,
|
|
|
clientY: touch.clientY
|
|
|
});
|
|
|
canvas.dispatchEvent(mouseEvent);
|
|
|
}
|
|
|
|
|
|
function handleTouchEnd(e) {
|
|
|
e.preventDefault();
|
|
|
|
|
|
const touch = e.changedTouches[0];
|
|
|
const mouseEvent = new MouseEvent('mouseup', {
|
|
|
clientX: touch.clientX,
|
|
|
clientY: touch.clientY
|
|
|
});
|
|
|
canvas.dispatchEvent(mouseEvent);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectEmbryo(index) {
|
|
|
selectedEmbryoIndex = index;
|
|
|
setCurrentEmbryoIndex(index);
|
|
|
drawDetections();
|
|
|
updateEmbryoList(appState.croppedEmbryos);
|
|
|
|
|
|
const info = document.getElementById('detectionInfo');
|
|
|
if (info) {
|
|
|
info.innerHTML = `
|
|
|
<p><strong>Embryo ${index + 1} selected</strong> - Drag to move, drag handles to resize</p>
|
|
|
<button class="btn btn-secondary" id="addManualEmbryoBtn" style="margin-top: 10px;">
|
|
|
+ Add Embryo Manually
|
|
|
</button>
|
|
|
`;
|
|
|
setupManualAddButton();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function updateEmbryoCrop(index) {
|
|
|
const detection = appState.croppedEmbryos[index];
|
|
|
const box = detection.box;
|
|
|
|
|
|
|
|
|
const tempCanvas = document.createElement('canvas');
|
|
|
const tempCtx = tempCanvas.getContext('2d');
|
|
|
|
|
|
const width = box.x2 - box.x1;
|
|
|
const height = box.y2 - box.y1;
|
|
|
|
|
|
tempCanvas.width = width;
|
|
|
tempCanvas.height = height;
|
|
|
|
|
|
|
|
|
tempCtx.drawImage(
|
|
|
imageElement,
|
|
|
box.x1, box.y1, width, height,
|
|
|
0, 0, width, height
|
|
|
);
|
|
|
|
|
|
|
|
|
detection.imageData = tempCanvas.toDataURL('image/png');
|
|
|
|
|
|
|
|
|
updateEmbryoList(appState.croppedEmbryos);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateEmbryoList(detections) {
|
|
|
const listContainer = document.getElementById('embryoList');
|
|
|
if (!listContainer) return;
|
|
|
|
|
|
listContainer.innerHTML = '';
|
|
|
|
|
|
detections.forEach((detection, index) => {
|
|
|
const card = document.createElement('div');
|
|
|
card.className = 'embryo-card';
|
|
|
if (index === selectedEmbryoIndex) {
|
|
|
card.classList.add('selected');
|
|
|
}
|
|
|
|
|
|
const hasRemark = getEmbryoRemark(index).trim().length > 0;
|
|
|
|
|
|
card.innerHTML = `
|
|
|
<div class="embryo-card-thumbnail">
|
|
|
<img src="${detection.imageData}" alt="Embryo ${index + 1}">
|
|
|
</div>
|
|
|
<div class="embryo-card-info">
|
|
|
<div class="embryo-card-title">
|
|
|
Embryo ${index + 1}
|
|
|
</div>
|
|
|
<div class="embryo-card-confidence">
|
|
|
Confidence: ${(detection.confidence * 100).toFixed(1)}%
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="embryo-card-actions">
|
|
|
<button class="embryo-card-btn discard" data-index="${index}">
|
|
|
Discard
|
|
|
</button>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
|
|
|
card.addEventListener('click', (e) => {
|
|
|
if (!e.target.classList.contains('embryo-card-btn')) {
|
|
|
selectEmbryo(index);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
const discardBtn = card.querySelector('.discard');
|
|
|
discardBtn.addEventListener('click', (e) => {
|
|
|
e.stopPropagation();
|
|
|
discardEmbryo(index);
|
|
|
});
|
|
|
|
|
|
listContainer.appendChild(card);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function discardEmbryo(index) {
|
|
|
if (appState.croppedEmbryos.length <= 1) {
|
|
|
showToast('Cannot discard the last embryo', 'warning');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const confirmed = await showConfirmModal(
|
|
|
`Are you sure you want to discard Embryo ${index + 1}?`,
|
|
|
'Discard Embryo'
|
|
|
);
|
|
|
|
|
|
if (!confirmed) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
appState.croppedEmbryos.splice(index, 1);
|
|
|
|
|
|
|
|
|
if (selectedEmbryoIndex === index) {
|
|
|
selectedEmbryoIndex = Math.max(0, Math.min(selectedEmbryoIndex, appState.croppedEmbryos.length - 1));
|
|
|
} else if (selectedEmbryoIndex > index) {
|
|
|
selectedEmbryoIndex--;
|
|
|
}
|
|
|
|
|
|
|
|
|
drawDetections();
|
|
|
updateEmbryoList(appState.croppedEmbryos);
|
|
|
|
|
|
showToast('Embryo discarded', 'success');
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function setupManualAddButton() {
|
|
|
const addBtn = document.getElementById('addManualEmbryoBtn');
|
|
|
if (!addBtn) return;
|
|
|
|
|
|
addBtn.addEventListener('click', startManualCrop);
|
|
|
}
|
|
|
|
|
|
let isDrawingNewBox = false;
|
|
|
let newBoxStart = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function startManualCrop() {
|
|
|
isDrawingNewBox = true;
|
|
|
selectedEmbryoIndex = null;
|
|
|
|
|
|
const detectionInfo = document.getElementById('detectionInfo');
|
|
|
if (detectionInfo) {
|
|
|
detectionInfo.innerHTML = `
|
|
|
<p style="color: var(--primary-color); font-weight: 600;">Draw a box around the embryo</p>
|
|
|
<p style="font-size: 0.9em;">Click and drag on the image to create a new bounding box</p>
|
|
|
<button class="btn btn-secondary" id="cancelManualAdd" style="margin-top: 10px;">Cancel</button>
|
|
|
`;
|
|
|
|
|
|
const cancelBtn = document.getElementById('cancelManualAdd');
|
|
|
if (cancelBtn) {
|
|
|
cancelBtn.addEventListener('click', cancelManualCrop);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
drawDetections();
|
|
|
showToast('Draw a box around the embryo', 'info');
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function cancelManualCrop() {
|
|
|
isDrawingNewBox = false;
|
|
|
newBoxStart = null;
|
|
|
|
|
|
const detectionInfo = document.getElementById('detectionInfo');
|
|
|
if (detectionInfo) {
|
|
|
detectionInfo.innerHTML = `
|
|
|
<p>Click on an embryo to adjust its crop area</p>
|
|
|
<button class="btn btn-secondary" id="addManualEmbryoBtn" style="margin-top: 10px;">
|
|
|
+ Add Embryo Manually
|
|
|
</button>
|
|
|
`;
|
|
|
setupManualAddButton();
|
|
|
}
|
|
|
|
|
|
drawDetections();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function completeManualCrop(box) {
|
|
|
|
|
|
const minSize = 50;
|
|
|
const boxWidth = Math.abs(box.x2 - box.x1);
|
|
|
const boxHeight = Math.abs(box.y2 - box.y1);
|
|
|
|
|
|
if (boxWidth < minSize || boxHeight < minSize) {
|
|
|
showToast('Box too small. Please draw a larger area.', 'warning');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const normalizedBox = {
|
|
|
x1: Math.min(box.x1, box.x2),
|
|
|
y1: Math.min(box.y1, box.y2),
|
|
|
x2: Math.max(box.x1, box.x2),
|
|
|
y2: Math.max(box.y1, box.y2)
|
|
|
};
|
|
|
|
|
|
|
|
|
const croppedCanvas = document.createElement('canvas');
|
|
|
const croppedCtx = croppedCanvas.getContext('2d');
|
|
|
|
|
|
croppedCanvas.width = normalizedBox.x2 - normalizedBox.x1;
|
|
|
croppedCanvas.height = normalizedBox.y2 - normalizedBox.y1;
|
|
|
|
|
|
croppedCtx.drawImage(
|
|
|
imageElement,
|
|
|
normalizedBox.x1, normalizedBox.y1,
|
|
|
croppedCanvas.width, croppedCanvas.height,
|
|
|
0, 0,
|
|
|
croppedCanvas.width, croppedCanvas.height
|
|
|
);
|
|
|
|
|
|
const croppedImageData = croppedCanvas.toDataURL();
|
|
|
|
|
|
|
|
|
const newEmbryo = {
|
|
|
imageData: croppedImageData,
|
|
|
box: normalizedBox,
|
|
|
confidence: 1.0,
|
|
|
isManual: true
|
|
|
};
|
|
|
|
|
|
appState.croppedEmbryos.push(newEmbryo);
|
|
|
|
|
|
|
|
|
isDrawingNewBox = false;
|
|
|
newBoxStart = null;
|
|
|
|
|
|
|
|
|
cancelManualCrop();
|
|
|
drawDetections();
|
|
|
updateEmbryoList(appState.croppedEmbryos);
|
|
|
|
|
|
|
|
|
if (appState.croppedEmbryos.length > 0) {
|
|
|
import('./stepper.js').then(module => {
|
|
|
module.enableNextButton(2);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
showToast('Embryo added successfully', 'success');
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getSelectedEmbryoIndex() {
|
|
|
return selectedEmbryoIndex;
|
|
|
}
|
|
|
|
|
|
export function redrawDetections() {
|
|
|
drawDetections();
|
|
|
}
|
|
|
|