/** * Workflow Handlers - Simplified 3-Step Workflow */ import { appState, setCroppedEmbryos, setCurrentEmbryoIndex, getEmbryoRemark, setEmbryoRemark } from '../state.js'; import { runClassification, evaluateEmbryo } from '../models/inference.js'; import { detectEmbryos, isYOLOAvailable } from '../models/yoloDetection.js'; import { showToast } from '../ui/toast.js'; import { displayClassificationStatus, displayFinalResults } from '../ui/results.js'; import { displayCroppedEmbryo } from '../ui/imageDisplay.js'; import { enableNextButton, disableNextButton, markStepCompleted } from '../ui/stepper.js'; import { showCropEditor, hideCropEditor } from '../ui/cropEditor.js'; import { showConfirmModal } from '../ui/modal.js'; import { initializeInlineDetection } from '../ui/inlineDetection.js'; /** * Classify if image contains embryo (auto-triggered on upload) */ export async function classifyImage(imageData) { const statusDiv = document.getElementById('classificationStatus'); try { if (statusDiv) { statusDiv.innerHTML = `
Analyzing image...
No embryo detected
Please upload an image containing an embryo to continue.
`; } } } catch (error) { console.error('Classification error:', error); showToast(`Classification failed: ${error.message}`, 'error'); if (statusDiv) { statusDiv.innerHTML = `Classification failed
${error.message}
`; } } } /** * Show embryo image in Step 2 and then trigger detection */ export async function showEmbryoImageAndDetect() { // First, display the original embryo image const step2Content = document.getElementById('step2'); if (!step2Content || !step2Content.classList.contains('active')) { console.error('Step 2 not active yet, waiting...'); await new Promise(resolve => setTimeout(resolve, 200)); } const canvas = document.getElementById('detectionCanvas'); // Show the original embryo image on the canvas first if (canvas && appState.currentImage) { try { const ctx = canvas.getContext('2d'); const { loadImage } = await import('../utils/imageUtils.js'); const imageElement = await loadImage(appState.currentImage); // Set canvas size to match image canvas.width = imageElement.width; canvas.height = imageElement.height; // Calculate scale factor for display const container = canvas.parentElement; if (container) { const maxWidth = container.clientWidth - 40; const maxHeight = 600; const scaleFactor = Math.min( maxWidth / imageElement.width, maxHeight / imageElement.height, 1 ); canvas.style.width = `${imageElement.width * scaleFactor}px`; canvas.style.height = `${imageElement.height * scaleFactor}px`; } // Draw the original image ctx.drawImage(imageElement, 0, 0); } catch (error) { console.error('Error displaying embryo image:', error); } } // Wait a moment to let the UI update and show the image await new Promise(resolve => setTimeout(resolve, 500)); // Now trigger detection await detectAndDisplayEmbryos(); } /** * Detect and display embryos (triggered on Step 2) */ export async function detectAndDisplayEmbryos() { if (!isYOLOAvailable()) { showToast('Detection model not available', 'error'); return; } // Verify Step 2 is visible const step2Content = document.getElementById('step2'); if (!step2Content || !step2Content.classList.contains('active')) { console.error('Step 2 not active yet, waiting...'); // Wait and retry await new Promise(resolve => setTimeout(resolve, 200)); } try { showToast('Detecting embryos...', 'info'); const detections = await detectEmbryos(appState.currentImage); if (detections.length === 0) { showToast('No embryos detected - You can add manually', 'info'); // Initialize with empty array to allow manual addition setCroppedEmbryos([]); await displayDetectedEmbryosForSelection([]); return; } setCroppedEmbryos(detections); await displayDetectedEmbryosForSelection(detections); showToast(`Detected ${detections.length} embryo(s) - Click to select`, 'success'); } catch (error) { console.error('Detection error:', error); showToast(`Detection failed: ${error.message}`, 'error'); } } /** * Display detected embryos for selection (Step 2) - Using inline detection view */ async function displayDetectedEmbryosForSelection(detections) { // Wait a bit for DOM to be ready (Step 2 to be visible) await new Promise(resolve => setTimeout(resolve, 100)); // Initialize the inline detection canvas with all embryos (or empty array) await initializeInlineDetection(appState.currentImage, detections); // Enable next button only if there are embryos (either detected or manually added) if (detections.length > 0) { enableNextButton(2); } } /** * Select embryo for analysis (updates selection in Step 2) */ function selectEmbryoForAnalysis(index) { setCurrentEmbryoIndex(index); displayCroppedEmbryo(index, appState.croppedEmbryos[index], appState.croppedEmbryos.length); // Update active thumbnail document.querySelectorAll('.embryo-thumbnail-wrapper').forEach((wrapper, i) => { const thumbnail = wrapper.querySelector('.embryo-thumbnail'); if (thumbnail) { thumbnail.classList.toggle('active', i === index); } }); const selectionInfo = document.getElementById('selectionInfo'); if (selectionInfo) { selectionInfo.innerHTML = `Embryo ${index + 1} of ${appState.croppedEmbryos.length} selected
Click 'Analyze Selected Embryo' to continue
`; } // Show crop editor for selected embryo showCropEditor(index); // Update navigation buttons updateNavigationButtons(); } /** * Navigate between embryos in Step 2 */ export function navigateEmbryo(direction) { const newIndex = appState.currentEmbryoIndex + direction; if (newIndex >= 0 && newIndex < appState.croppedEmbryos.length) { selectEmbryoForAnalysis(newIndex); } // Update navigation button states updateNavigationButtons(); } /** * Update navigation button states */ function updateNavigationButtons() { const prevBtn = document.getElementById('prevEmbryoBtn'); const nextBtn = document.getElementById('nextEmbryoBtn'); if (prevBtn) { prevBtn.disabled = appState.currentEmbryoIndex === 0; } if (nextBtn) { nextBtn.disabled = appState.currentEmbryoIndex >= appState.croppedEmbryos.length - 1; } } /** * Discard an embryo from the analysis */ 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; } // Remove embryo from array appState.croppedEmbryos.splice(index, 1); // Adjust current index if necessary if (appState.currentEmbryoIndex >= appState.croppedEmbryos.length) { appState.currentEmbryoIndex = Math.max(0, appState.croppedEmbryos.length - 1); } // Update the display redisplayEmbryos(); showToast('Embryo discarded', 'success'); } /** * Redisplay all embryos after changes (e.g., discard) */ function redisplayEmbryos() { const thumbnailContainer = document.getElementById('embryoThumbnails'); if (!thumbnailContainer) return; thumbnailContainer.innerHTML = ''; appState.croppedEmbryos.forEach((detection, index) => { const thumbnailWrapper = document.createElement('div'); thumbnailWrapper.className = 'embryo-thumbnail-wrapper'; thumbnailWrapper.setAttribute('data-embryo-index', index); const thumbnail = document.createElement('div'); thumbnail.className = 'embryo-thumbnail'; if (index === appState.currentEmbryoIndex) thumbnail.classList.add('active'); const img = document.createElement('img'); img.src = detection.imageData; img.alt = `Embryo ${index + 1}`; // Add discard button with closure to capture current index const discardBtn = document.createElement('button'); discardBtn.className = 'discard-embryo-btn'; discardBtn.innerHTML = '×'; discardBtn.title = 'Discard this embryo'; discardBtn.addEventListener('click', ((currentIndex) => { return (e) => { e.stopPropagation(); discardEmbryo(currentIndex); }; })(index)); thumbnail.appendChild(img); thumbnail.appendChild(discardBtn); // Add click handler with closure to capture current index thumbnail.addEventListener('click', ((currentIndex) => { return () => selectEmbryoForAnalysis(currentIndex); })(index)); thumbnailWrapper.appendChild(thumbnail); thumbnailContainer.appendChild(thumbnailWrapper); }); // Select current embryo (index has been adjusted in discardEmbryo) if (appState.croppedEmbryos.length > 0) { selectEmbryoForAnalysis(appState.currentEmbryoIndex); } } /** * Evaluate all embryos in background (triggered when entering Step 3) */ export async function evaluateAllEmbryos() { const resultsDiv = document.getElementById('finalResults'); try { showToast('Processing all embryos...', 'info'); if (resultsDiv) { resultsDiv.innerHTML = `Analyzing ${appState.croppedEmbryos.length} embryo(s)...
This may take a few moments
Evaluation failed
${error.message}
`; } } } /** * Display all embryos with clickable thumbnails in Step 3 */ function displayAllEmbryosWithResults(results) { const resultsDiv = document.getElementById('finalResults'); if (!resultsDiv) return; resultsDiv.innerHTML = `Click on any embryo to view its detailed results