|
|
|
|
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function classifyImage(imageData) {
|
|
|
const statusDiv = document.getElementById('classificationStatus');
|
|
|
|
|
|
try {
|
|
|
if (statusDiv) {
|
|
|
statusDiv.innerHTML = `
|
|
|
<div style="text-align: center;">
|
|
|
<div class="spinner" style="width: 30px; height: 30px; margin: 10px auto;"></div>
|
|
|
<p>Analyzing image...</p>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
showToast('Classifying image...', 'info');
|
|
|
|
|
|
const result = await runClassification(imageData);
|
|
|
displayClassificationStatus(result);
|
|
|
|
|
|
|
|
|
if (result && result.label === 'yes') {
|
|
|
enableNextButton(1);
|
|
|
showToast('Image classified successfully - Embryo detected!', 'success');
|
|
|
} else if (result && result.label === 'no') {
|
|
|
disableNextButton(1);
|
|
|
showToast('No embryo detected in the image', 'warning');
|
|
|
if (statusDiv) {
|
|
|
statusDiv.innerHTML = `
|
|
|
<p style="color: var(--warning-color);"><strong>No embryo detected</strong></p>
|
|
|
<p>Please upload an image containing an embryo to continue.</p>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('Classification error:', error);
|
|
|
showToast(`Classification failed: ${error.message}`, 'error');
|
|
|
if (statusDiv) {
|
|
|
statusDiv.innerHTML = `
|
|
|
<p style="color: var(--error-color);"><strong>Classification failed</strong></p>
|
|
|
<p>${error.message}</p>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function showEmbryoImageAndDetect() {
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
if (canvas && appState.currentImage) {
|
|
|
try {
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
const { loadImage } = await import('../utils/imageUtils.js');
|
|
|
const imageElement = await loadImage(appState.currentImage);
|
|
|
|
|
|
|
|
|
canvas.width = imageElement.width;
|
|
|
canvas.height = imageElement.height;
|
|
|
|
|
|
|
|
|
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`;
|
|
|
}
|
|
|
|
|
|
|
|
|
ctx.drawImage(imageElement, 0, 0);
|
|
|
} catch (error) {
|
|
|
console.error('Error displaying embryo image:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
|
|
|
await detectAndDisplayEmbryos();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function detectAndDisplayEmbryos() {
|
|
|
if (!isYOLOAvailable()) {
|
|
|
showToast('Detection model not available', 'error');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
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));
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
showToast('Detecting embryos...', 'info');
|
|
|
|
|
|
const detections = await detectEmbryos(appState.currentImage);
|
|
|
|
|
|
if (detections.length === 0) {
|
|
|
showToast('No embryos detected - You can add manually', 'info');
|
|
|
|
|
|
|
|
|
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');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function displayDetectedEmbryosForSelection(detections) {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
|
|
|
|
|
await initializeInlineDetection(appState.currentImage, detections);
|
|
|
|
|
|
|
|
|
if (detections.length > 0) {
|
|
|
enableNextButton(2);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectEmbryoForAnalysis(index) {
|
|
|
setCurrentEmbryoIndex(index);
|
|
|
displayCroppedEmbryo(index, appState.croppedEmbryos[index], appState.croppedEmbryos.length);
|
|
|
|
|
|
|
|
|
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 = `
|
|
|
<p><strong>Embryo ${index + 1} of ${appState.croppedEmbryos.length} selected</strong></p>
|
|
|
<p>Click 'Analyze Selected Embryo' to continue</p>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
showCropEditor(index);
|
|
|
|
|
|
|
|
|
updateNavigationButtons();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function navigateEmbryo(direction) {
|
|
|
const newIndex = appState.currentEmbryoIndex + direction;
|
|
|
if (newIndex >= 0 && newIndex < appState.croppedEmbryos.length) {
|
|
|
selectEmbryoForAnalysis(newIndex);
|
|
|
}
|
|
|
|
|
|
|
|
|
updateNavigationButtons();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 (appState.currentEmbryoIndex >= appState.croppedEmbryos.length) {
|
|
|
appState.currentEmbryoIndex = Math.max(0, appState.croppedEmbryos.length - 1);
|
|
|
}
|
|
|
|
|
|
|
|
|
redisplayEmbryos();
|
|
|
|
|
|
showToast('Embryo discarded', 'success');
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}`;
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
thumbnail.addEventListener('click', ((currentIndex) => {
|
|
|
return () => selectEmbryoForAnalysis(currentIndex);
|
|
|
})(index));
|
|
|
|
|
|
thumbnailWrapper.appendChild(thumbnail);
|
|
|
thumbnailContainer.appendChild(thumbnailWrapper);
|
|
|
});
|
|
|
|
|
|
|
|
|
if (appState.croppedEmbryos.length > 0) {
|
|
|
selectEmbryoForAnalysis(appState.currentEmbryoIndex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function evaluateAllEmbryos() {
|
|
|
const resultsDiv = document.getElementById('finalResults');
|
|
|
|
|
|
try {
|
|
|
showToast('Processing all embryos...', 'info');
|
|
|
|
|
|
if (resultsDiv) {
|
|
|
resultsDiv.innerHTML = `
|
|
|
<div style="text-align: center;">
|
|
|
<div class="spinner" style="width: 40px; height: 40px; margin: 20px auto;"></div>
|
|
|
<p>Analyzing ${appState.croppedEmbryos.length} embryo(s)...</p>
|
|
|
<p style="font-size: 0.9em; color: var(--text-secondary);">This may take a few moments</p>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
const evaluationPromises = appState.croppedEmbryos.map(async (embryo, index) => {
|
|
|
try {
|
|
|
const result = await evaluateEmbryo(embryo.imageData);
|
|
|
return {
|
|
|
index,
|
|
|
embryo,
|
|
|
result,
|
|
|
success: true
|
|
|
};
|
|
|
} catch (error) {
|
|
|
console.error(`Error evaluating embryo ${index + 1}:`, error);
|
|
|
return {
|
|
|
index,
|
|
|
embryo,
|
|
|
result: null,
|
|
|
success: false,
|
|
|
error: error.message
|
|
|
};
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const results = await Promise.all(evaluationPromises);
|
|
|
|
|
|
|
|
|
appState.embryoResults = results;
|
|
|
|
|
|
|
|
|
displayAllEmbryosWithResults(results);
|
|
|
|
|
|
|
|
|
markStepCompleted(3);
|
|
|
|
|
|
const successCount = results.filter(r => r.success).length;
|
|
|
if (successCount === results.length) {
|
|
|
showToast(`Successfully processed all ${results.length} embryos`, 'success');
|
|
|
} else {
|
|
|
showToast(`Processed ${successCount} of ${results.length} embryos (${results.length - successCount} failed)`, 'warning');
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('Evaluation error:', error);
|
|
|
showToast(`Evaluation failed: ${error.message}`, 'error');
|
|
|
if (resultsDiv) {
|
|
|
resultsDiv.innerHTML = `
|
|
|
<p style="color: var(--error-color);"><strong>Evaluation failed</strong></p>
|
|
|
<p>${error.message}</p>
|
|
|
`;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displayAllEmbryosWithResults(results) {
|
|
|
const resultsDiv = document.getElementById('finalResults');
|
|
|
if (!resultsDiv) return;
|
|
|
|
|
|
resultsDiv.innerHTML = `
|
|
|
<div class="all-embryos-results">
|
|
|
<h3>All Embryos Results</h3>
|
|
|
<p style="margin-bottom: 20px; color: var(--text-secondary);">
|
|
|
Click on any embryo to view its detailed results
|
|
|
</p>
|
|
|
<div class="embryo-results-grid" id="embryoResultsGrid"></div>
|
|
|
<div class="selected-embryo-details" id="selectedEmbryoDetails" style="margin-top: 30px;"></div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
const grid = document.getElementById('embryoResultsGrid');
|
|
|
|
|
|
results.forEach((resultData, index) => {
|
|
|
const card = document.createElement('div');
|
|
|
card.className = 'embryo-result-card';
|
|
|
card.setAttribute('data-embryo-index', index);
|
|
|
|
|
|
const hasRemark = getEmbryoRemark(index).trim().length > 0;
|
|
|
|
|
|
if (resultData.success) {
|
|
|
const grade = resultData.result.grade || 'Unknown';
|
|
|
const quality = resultData.result.quality || 'Unknown';
|
|
|
|
|
|
card.innerHTML = `
|
|
|
<div class="embryo-result-thumbnail">
|
|
|
<img src="${resultData.embryo.imageData}" alt="Embryo ${index + 1}">
|
|
|
<div class="embryo-result-badge">${grade}</div>
|
|
|
</div>
|
|
|
<div class="embryo-result-info">
|
|
|
<strong>Embryo ${index + 1}</strong>
|
|
|
<span class="quality-indicator quality-${quality.toLowerCase()}">${quality}</span>
|
|
|
</div>
|
|
|
`;
|
|
|
} else {
|
|
|
card.innerHTML = `
|
|
|
<div class="embryo-result-thumbnail">
|
|
|
<img src="${resultData.embryo.imageData}" alt="Embryo ${index + 1}">
|
|
|
<div class="embryo-result-badge error">Error</div>
|
|
|
</div>
|
|
|
<div class="embryo-result-info">
|
|
|
<strong>Embryo ${index + 1}</strong>
|
|
|
<span class="quality-indicator quality-error">Failed</span>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
card.addEventListener('click', () => showEmbryoDetailedResult(index, resultData));
|
|
|
|
|
|
grid.appendChild(card);
|
|
|
});
|
|
|
|
|
|
|
|
|
if (results.length > 0) {
|
|
|
showEmbryoDetailedResult(0, results[0]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function showEmbryoDetailedResult(index, resultData) {
|
|
|
const detailsDiv = document.getElementById('selectedEmbryoDetails');
|
|
|
if (!detailsDiv) return;
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.embryo-result-card').forEach((card, i) => {
|
|
|
card.classList.toggle('active', i === index);
|
|
|
});
|
|
|
|
|
|
const currentRemark = getEmbryoRemark(index);
|
|
|
|
|
|
if (!resultData.success) {
|
|
|
detailsDiv.innerHTML = `
|
|
|
<div class="detailed-result-container">
|
|
|
<h3>Embryo ${index + 1} - Processing Failed</h3>
|
|
|
<div class="error-message">
|
|
|
<p>${resultData.error || 'Unknown error occurred'}</p>
|
|
|
</div>
|
|
|
<div class="remarks-section">
|
|
|
<label for="embryoRemark${index}">Remarks:</label>
|
|
|
<textarea
|
|
|
id="embryoRemark${index}"
|
|
|
class="remarks-textarea"
|
|
|
placeholder="Add your observations, notes, or comments about this embryo..."
|
|
|
data-embryo-index="${index}"
|
|
|
>${currentRemark}</textarea>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
|
|
|
const remarkTextarea = document.getElementById(`embryoRemark${index}`);
|
|
|
if (remarkTextarea) {
|
|
|
remarkTextarea.addEventListener('input', (e) => {
|
|
|
setEmbryoRemark(index, e.target.value);
|
|
|
});
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const result = resultData.result;
|
|
|
|
|
|
detailsDiv.innerHTML = `
|
|
|
<div class="detailed-result-container">
|
|
|
<div class="result-header">
|
|
|
<h3>Embryo ${index + 1} - Detailed Results</h3>
|
|
|
</div>
|
|
|
<div class="result-content">
|
|
|
<div class="result-image-section">
|
|
|
<img src="${resultData.embryo.imageData}" alt="Embryo ${index + 1}" class="result-embryo-image">
|
|
|
</div>
|
|
|
<div class="result-details-section">
|
|
|
<div class="result-row">
|
|
|
<span class="result-label">Quality Assessment:</span>
|
|
|
<span class="result-value quality-${result.quality?.toLowerCase() || 'unknown'}">${result.quality || 'Unknown'}</span>
|
|
|
</div>
|
|
|
<div class="result-row">
|
|
|
<span class="result-label">Predicted Grade:</span>
|
|
|
<span class="result-value grade-value">${result.grade || 'Unknown'}</span>
|
|
|
</div>
|
|
|
<div class="result-row">
|
|
|
<span class="result-label">Actual Grade:</span>
|
|
|
<div class="actual-grade-selectors">
|
|
|
<select id="actualGradeNum${index}" class="grade-select" data-embryo-index="${index}">
|
|
|
<option value="">-</option>
|
|
|
<option value="1">1</option>
|
|
|
<option value="2">2</option>
|
|
|
<option value="3">3</option>
|
|
|
<option value="4">4</option>
|
|
|
<option value="5">5</option>
|
|
|
<option value="6">6</option>
|
|
|
</select>
|
|
|
<select id="actualGradeLetter1${index}" class="grade-select" data-embryo-index="${index}">
|
|
|
<option value="">-</option>
|
|
|
<option value="A">A</option>
|
|
|
<option value="B">B</option>
|
|
|
<option value="C">C</option>
|
|
|
</select>
|
|
|
<select id="actualGradeLetter2${index}" class="grade-select" data-embryo-index="${index}">
|
|
|
<option value="">-</option>
|
|
|
<option value="A">A</option>
|
|
|
<option value="B">B</option>
|
|
|
<option value="C">C</option>
|
|
|
</select>
|
|
|
<span class="grade-preview" id="gradePreview${index}"></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
${result.confidence ? `
|
|
|
<div class="result-row">
|
|
|
<span class="result-label">Confidence:</span>
|
|
|
<span class="result-value">${(result.confidence * 100).toFixed(1)}%</span>
|
|
|
</div>
|
|
|
` : ''}
|
|
|
${result.poorGoodConfidence ? `
|
|
|
<div class="result-row">
|
|
|
<span class="result-label">Quality Confidence:</span>
|
|
|
<span class="result-value">${(result.poorGoodConfidence * 100).toFixed(1)}%</span>
|
|
|
</div>
|
|
|
` : ''}
|
|
|
<div class="remarks-section">
|
|
|
<label for="embryoRemark${index}">Remarks:</label>
|
|
|
<textarea
|
|
|
id="embryoRemark${index}"
|
|
|
class="remarks-textarea"
|
|
|
placeholder="Add your observations, notes, or comments about this embryo..."
|
|
|
data-embryo-index="${index}"
|
|
|
>${currentRemark}</textarea>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
|
|
|
const remarkTextarea = document.getElementById(`embryoRemark${index}`);
|
|
|
if (remarkTextarea) {
|
|
|
remarkTextarea.addEventListener('input', (e) => {
|
|
|
setEmbryoRemark(index, e.target.value);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
const existingGrade = resultData.embryo.actualGrade || '';
|
|
|
if (existingGrade) {
|
|
|
|
|
|
const match = existingGrade.match(/^(\d)([A-C])([A-C])$/);
|
|
|
if (match) {
|
|
|
const numSelect = document.getElementById(`actualGradeNum${index}`);
|
|
|
const letter1Select = document.getElementById(`actualGradeLetter1${index}`);
|
|
|
const letter2Select = document.getElementById(`actualGradeLetter2${index}`);
|
|
|
|
|
|
if (numSelect) numSelect.value = match[1];
|
|
|
if (letter1Select) letter1Select.value = match[2];
|
|
|
if (letter2Select) letter2Select.value = match[3];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
const updateActualGrade = () => {
|
|
|
const numSelect = document.getElementById(`actualGradeNum${index}`);
|
|
|
const letter1Select = document.getElementById(`actualGradeLetter1${index}`);
|
|
|
const letter2Select = document.getElementById(`actualGradeLetter2${index}`);
|
|
|
const gradePreview = document.getElementById(`gradePreview${index}`);
|
|
|
|
|
|
const num = numSelect?.value || '';
|
|
|
const letter1 = letter1Select?.value || '';
|
|
|
const letter2 = letter2Select?.value || '';
|
|
|
|
|
|
const combinedGrade = num + letter1 + letter2;
|
|
|
|
|
|
|
|
|
if (gradePreview) {
|
|
|
gradePreview.textContent = combinedGrade || '-';
|
|
|
}
|
|
|
|
|
|
|
|
|
const embryo = appState.croppedEmbryos[index];
|
|
|
if (embryo) {
|
|
|
embryo.actualGrade = combinedGrade;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
const numSelect = document.getElementById(`actualGradeNum${index}`);
|
|
|
const letter1Select = document.getElementById(`actualGradeLetter1${index}`);
|
|
|
const letter2Select = document.getElementById(`actualGradeLetter2${index}`);
|
|
|
|
|
|
if (numSelect) {
|
|
|
numSelect.addEventListener('change', updateActualGrade);
|
|
|
}
|
|
|
if (letter1Select) {
|
|
|
letter1Select.addEventListener('change', updateActualGrade);
|
|
|
}
|
|
|
if (letter2Select) {
|
|
|
letter2Select.addEventListener('change', updateActualGrade);
|
|
|
}
|
|
|
|
|
|
|
|
|
updateActualGrade();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function evaluateSelectedEmbryo() {
|
|
|
return evaluateAllEmbryos();
|
|
|
}
|
|
|
|