| | def generate_slides_html(image_paths, image_ids, desc1, desc2, desc3, output_file='gallery_with_descriptions.html'): |
| | |
| | html_content = '''<!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Image Gallery</title> |
| | <style> |
| | body { |
| | font-family: Arial, sans-serif; |
| | max-width: 900px; |
| | margin: 0 auto; |
| | padding: 20px; |
| | background: #f5f5f5; |
| | } |
| | .gallery-container { |
| | position: relative; |
| | background: white; |
| | border-radius: 8px; |
| | padding: 30px; |
| | margin-bottom: 30px; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | min-height: 700px; |
| | } |
| | .gallery-container img { |
| | max-width: 700px; |
| | max-height: 500px; |
| | height: auto; |
| | border-radius: 4px; |
| | display: block; |
| | margin: 0 auto 20px; |
| | } |
| | .slide { |
| | display: none; |
| | } |
| | .slide.active { |
| | display: block; |
| | animation: fadeIn 0.5s; |
| | } |
| | @keyframes fadeIn { |
| | from { opacity: 0; } |
| | to { opacity: 1; } |
| | } |
| | .nav-buttons { |
| | display: flex; |
| | justify-content: space-between; |
| | margin: 20px 0; |
| | } |
| | .nav-button { |
| | padding: 10px 20px; |
| | background: #007bff; |
| | color: white; |
| | border: none; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | font-size: 16px; |
| | } |
| | .nav-button:disabled { |
| | background: #cccccc; |
| | cursor: not-allowed; |
| | } |
| | .image-counter { |
| | text-align: center; |
| | font-weight: bold; |
| | margin-bottom: 20px; |
| | color: #555; |
| | } |
| | .description { |
| | background: #f8f8f8; |
| | padding: 15px; |
| | margin: 15px 0; |
| | border-radius: 4px; |
| | white-space: pre-line; |
| | } |
| | .image-title { |
| | font-size: 1.5em; |
| | font-weight: bold; |
| | margin-bottom: 15px; |
| | color: #333; |
| | text-align: center; |
| | } |
| | .model-title { |
| | font-weight: bold; |
| | color: #666; |
| | margin-bottom: 5px; |
| | } |
| | .random-button { |
| | padding: 10px 20px; |
| | background: #28a745; |
| | color: white; |
| | border: none; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | font-size: 16px; |
| | } |
| | .random-button:hover { |
| | background: #218838; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="gallery-container" id="gallery"> |
| | <div class="image-counter">Image <span id="current-index">1</span> of <span id="total-images">0</span></div> |
| | |
| | <!-- Slides will be generated here --> |
| | ''' |
| | |
| | |
| | for i in range(len(image_paths)): |
| | |
| | desc1_html = desc1[i].replace('\n', '<br>') |
| | desc2_html = desc2[i].replace('\n', '<br>') |
| | desc3_html = desc3[i].replace('\n', '<br>') |
| | |
| | html_content += f''' |
| | <div class="slide" data-image-id="{image_ids[i]}" id="slide-{i}"> |
| | <div class="image-title">Image ID: {image_ids[i]}</div> |
| | <img src="{image_paths[i]}" alt="Image {image_ids[i]}"> |
| | |
| | <div class="description"> |
| | <div class="model-title">Model 1</div> |
| | {desc1_html} |
| | </div> |
| | |
| | <div class="description"> |
| | <div class="model-title">Model 2</div> |
| | {desc2_html} |
| | </div> |
| | |
| | <div class="description"> |
| | <div class="model-title">Model 3</div> |
| | {desc3_html} |
| | </div> |
| | </div> |
| | ''' |
| |
|
| | html_content += ''' |
| | <div class="nav-buttons"> |
| | <button id="prev-button" class="nav-button">Previous</button> |
| | <button id="next-button" class="nav-button">Next</button> |
| | </div> |
| | </div> |
| | |
| | <script> |
| | // Variables to track current slide and history |
| | let currentSlide = 0; |
| | const slides = document.querySelectorAll('.slide'); |
| | const totalSlides = slides.length; |
| | const viewedSlides = new Set([0]); // Track which slides have been viewed |
| | const slideHistory = [0]; // Track navigation history |
| | let historyPosition = 0; // Current position in history |
| | |
| | // Update total images counter |
| | document.getElementById('total-images').textContent = totalSlides; |
| | |
| | // Function to show a specific slide |
| | function goToSlide(index) { |
| | // Hide all slides |
| | slides.forEach(slide => { |
| | slide.classList.remove('active'); |
| | }); |
| | |
| | // Show the selected slide |
| | slides[index].classList.add('active'); |
| | currentSlide = index; |
| | |
| | // Add to viewed slides |
| | viewedSlides.add(index); |
| | |
| | // Update the counter |
| | document.getElementById('current-index').textContent = index + 1; |
| | |
| | // Update button states |
| | document.getElementById('prev-button').disabled = slideHistory.length <= 1; |
| | } |
| | |
| | // Function to go to a random slide and track in history |
| | function goToRandomSlide() { |
| | // Get array of unviewed slide indices |
| | const unviewedSlides = Array.from(Array(totalSlides).keys()) |
| | .filter(index => !viewedSlides.has(index) && index !== currentSlide); |
| | |
| | // If we've seen all slides except the current one, reset |
| | if (unviewedSlides.length === 0) { |
| | viewedSlides.clear(); |
| | // Don't add current slide to viewed set so we don't repeat it immediately |
| | |
| | // Recalculate unviewed slides (now all except current) |
| | const allSlides = Array.from(Array(totalSlides).keys()) |
| | .filter(index => index !== currentSlide); |
| | |
| | // Select a random slide from all slides except current |
| | const randomIndex = Math.floor(Math.random() * allSlides.length); |
| | const newSlideIndex = allSlides[randomIndex]; |
| | |
| | // Add to history and update position |
| | slideHistory.push(newSlideIndex); |
| | historyPosition = slideHistory.length - 1; |
| | |
| | goToSlide(newSlideIndex); |
| | } else { |
| | // Select a random unviewed slide |
| | const randomIndex = Math.floor(Math.random() * unviewedSlides.length); |
| | const newSlideIndex = unviewedSlides[randomIndex]; |
| | |
| | // Add to history and update position |
| | slideHistory.push(newSlideIndex); |
| | historyPosition = slideHistory.length - 1; |
| | |
| | goToSlide(newSlideIndex); |
| | } |
| | } |
| | |
| | // Function to go to previous slide in history |
| | function goToPreviousSlide() { |
| | if (slideHistory.length > 1 && historyPosition > 0) { |
| | historyPosition--; |
| | goToSlide(slideHistory[historyPosition]); |
| | } |
| | } |
| | |
| | // Function for next slide (completely random, no repeats until all seen) |
| | function goToNextSlide() { |
| | goToRandomSlide(); |
| | } |
| | |
| | // Function for previous slide (removed history navigation) |
| | function goToPrevSlide() { |
| | // No longer tracking history - just go to a random slide |
| | goToRandomSlide(); |
| | } |
| | |
| | // Initialize the first slide |
| | goToSlide(0); |
| | |
| | // Event listeners for navigation buttons |
| | document.getElementById('next-button').addEventListener('click', goToRandomSlide); |
| | document.getElementById('prev-button').addEventListener('click', goToPreviousSlide); |
| | |
| | // Add keyboard navigation |
| | document.addEventListener('keydown', (e) => { |
| | if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') { |
| | goToRandomSlide(); // Right arrow, space, or enter goes to next random |
| | } else if (e.key === 'ArrowLeft') { |
| | goToPreviousSlide(); // Left arrow goes to previous |
| | } |
| | }); |
| | |
| | // Initialize - disable previous button at start |
| | document.getElementById('prev-button').disabled = true; |
| | </script> |
| | </body> |
| | </html> |
| | ''' |
| | |
| | with open(output_file, 'w', encoding='utf-8') as f: |
| | f.write(html_content) |
| | |
| | print(f"Gallery with three model outputs has been generated as {output_file}") |
| |
|
| |
|
| |
|
| | def generate_rating_html(image_paths, image_ids, desc1, desc2, desc3, desc4, desc5, output_file='gallery_with_ratings.html'): |
| | |
| | html_content = '''<!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Image Gallery with Ratings</title> |
| | <style> |
| | body { |
| | font-family: Arial, sans-serif; |
| | max-width: 800px; |
| | margin: 0 auto; |
| | padding: 20px; |
| | background: #f5f5f5; |
| | } |
| | .image-container { |
| | background: white; |
| | border-radius: 8px; |
| | padding: 20px; |
| | margin-bottom: 30px; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | } |
| | .image-container img { |
| | max-width: 500px; |
| | height: auto; |
| | border-radius: 4px; |
| | display: block; |
| | margin: 0 auto; |
| | } |
| | .description { |
| | background: #f8f8f8; |
| | padding: 15px; |
| | margin: 10px 0; |
| | border-radius: 4px; |
| | white-space: pre-line; /* This helps preserve line breaks */ |
| | } |
| | .image-title { |
| | font-size: 1.5em; |
| | font-weight: bold; |
| | margin-bottom: 15px; |
| | color: #333; |
| | text-align: center; |
| | } |
| | .model-title { |
| | font-weight: bold; |
| | color: #666; |
| | margin-bottom: 5px; |
| | } |
| | .rating { |
| | display: flex; |
| | align-items: center; |
| | margin-top: 10px; |
| | padding: 10px; |
| | background: #fff; |
| | border-radius: 4px; |
| | } |
| | .rating-label { |
| | margin-right: 10px; |
| | font-weight: bold; |
| | } |
| | .rating-group { |
| | display: flex; |
| | gap: 10px; |
| | } |
| | .rating-radio { |
| | display: none; |
| | } |
| | .rating-button { |
| | padding: 8px 12px; |
| | border: 1px solid #ccc; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | transition: all 0.2s; |
| | } |
| | .rating-radio:checked + .rating-button { |
| | background: #007bff; |
| | color: white; |
| | border-color: #0056b3; |
| | } |
| | .save-button { |
| | position: fixed; |
| | bottom: 20px; |
| | right: 20px; |
| | padding: 10px 20px; |
| | background: #007bff; |
| | color: white; |
| | border: none; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | } |
| | .save-button:disabled { |
| | background: #cccccc; |
| | cursor: not-allowed; |
| | } |
| | #images-container { |
| | /* Container for all image blocks */ |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div id="rater-name-container"> |
| | <label for="rater-name" style="font-weight: bold;">Labeller id:</label> |
| | <input type="text" id="rater-name" style="margin: 10px 0; padding: 5px; width: 200px;"> |
| | </div> |
| | |
| | <div id="images-container"> |
| | <!-- Image containers will be inserted here dynamically --> |
| | </div> |
| | ''' |
| | |
| | |
| | js_image_paths = [] |
| | js_image_ids = [] |
| | js_desc1 = [] |
| | js_desc2 = [] |
| | js_desc3 = [] |
| | js_desc4 = [] |
| | js_desc5 = [] |
| | |
| | for i in range(len(image_paths)): |
| | js_image_paths.append(f'"{image_paths[i]}"') |
| | js_image_ids.append(f'"{image_ids[i]}"') |
| | |
| | |
| | desc1_html = desc1[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc2_html = desc2[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc3_html = desc3[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc4_html = desc4[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc5_html = desc5[i].replace('"', '\\"').replace('\n', '<br>') |
| | |
| | js_desc1.append(f'"{desc1_html}"') |
| | js_desc2.append(f'"{desc2_html}"') |
| | js_desc3.append(f'"{desc3_html}"') |
| | js_desc4.append(f'"{desc4_html}"') |
| | js_desc5.append(f'"{desc5_html}"') |
| | |
| | |
| | html_content += f''' |
| | <button onclick="saveRatings()" class="save-button" id="save-button">Save Ratings as CSV</button> |
| | |
| | <script> |
| | // Store all image data as separate arrays |
| | const imagePaths = [{', '.join(js_image_paths)}]; |
| | const imageIds = [{', '.join(js_image_ids)}]; |
| | const desc1 = [{', '.join(js_desc1)}]; |
| | const desc2 = [{', '.join(js_desc2)}]; |
| | const desc3 = [{', '.join(js_desc3)}]; |
| | const desc4 = [{', '.join(js_desc4)}]; |
| | const desc5 = [{', '.join(js_desc5)}]; |
| | |
| | // Create an array of indices to shuffle for images |
| | let indices = []; |
| | for (let i = 0; i < imageIds.length; i++) {{ |
| | indices.push(i); |
| | }} |
| | |
| | // Function to render all images in randomized order with randomized model order |
| | function renderImages() {{ |
| | const container = document.getElementById('images-container'); |
| | |
| | // Shuffle the indices (this randomizes image order) |
| | shuffleArray(indices); |
| | |
| | // Clear the container |
| | container.innerHTML = ''; |
| | |
| | // Add each image in shuffled order |
| | indices.forEach((originalIndex, newIndex) => {{ |
| | // For each image, we'll create a different random order for the models |
| | let modelOrder = [1, 2, 3, 4, 5]; |
| | shuffleArray(modelOrder); |
| | |
| | // Start building the image container HTML |
| | let imageHtml = ` |
| | <div class="image-container" data-image-id="${{imageIds[originalIndex]}}"> |
| | <div class="image-title">Image ID: ${{imageIds[originalIndex]}}</div> |
| | <img src="${{imagePaths[originalIndex]}}" alt="Image ${{imageIds[originalIndex]}}"> |
| | `; |
| | |
| | // Add descriptions and rating UI for each model in the randomized order |
| | modelOrder.forEach((modelNum, modelIndex) => {{ |
| | // Get the description data for this model |
| | let descData; |
| | if (modelNum === 1) descData = desc1[originalIndex]; |
| | else if (modelNum === 2) descData = desc2[originalIndex]; |
| | else if (modelNum === 3) descData = desc3[originalIndex]; |
| | else if (modelNum === 4) descData = desc4[originalIndex]; |
| | else if (modelNum === 5) descData = desc5[originalIndex]; |
| | |
| | // Create HTML for this model's description and rating |
| | imageHtml += ` |
| | <div class="description"> |
| | <div class="model-title">Model ${{modelNum}}</div> |
| | ${{descData}} |
| | <div class="rating" data-model="${{modelNum}}"> |
| | <span class="rating-label">Rating:</span> |
| | <div class="rating-group"> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="1" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-1"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-1">1</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="2" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-2"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-2">2</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="3" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-3"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-3">3</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="4" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-4"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-4">4</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="5" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-5"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-5">5</label> |
| | </div> |
| | </div> |
| | </div>`; |
| | }}); |
| | |
| | // Close the image container div |
| | imageHtml += `</div>`; |
| | |
| | // Add the complete HTML for this image to the page |
| | container.innerHTML += imageHtml; |
| | }}); |
| | }} |
| | |
| | // Fisher-Yates shuffle algorithm |
| | function shuffleArray(array) {{ |
| | for (let i = array.length - 1; i > 0; i--) {{ |
| | const j = Math.floor(Math.random() * (i + 1)); |
| | [array[i], array[j]] = [array[j], array[i]]; |
| | }} |
| | return array; |
| | }} |
| | |
| | function saveRatings() {{ |
| | const labellerId = document.getElementById('rater-name').value.trim(); |
| | |
| | if (!labellerId) {{ |
| | alert('Please enter a Labeller ID before saving'); |
| | return; |
| | }} |
| | |
| | const ratings = []; |
| | |
| | // Collect all ratings |
| | document.querySelectorAll('.rating-radio:checked').forEach(radio => {{ |
| | ratings.push({{ |
| | model: radio.dataset.model, |
| | image_id: radio.dataset.image, |
| | rating: radio.value |
| | }}); |
| | }}); |
| | |
| | // Convert to CSV |
| | const headers = ['model', 'image_id', 'rating']; |
| | const csvContent = [ |
| | headers.join(','), |
| | ...ratings.map(row => [ |
| | row.model, |
| | row.image_id, |
| | row.rating |
| | ].join(',')) |
| | ].join('\\n'); |
| | |
| | // Create and trigger download with labeller ID in filename |
| | const blob = new Blob([csvContent], {{ type: 'text/csv;charset=utf-8;' }}); |
| | const link = document.createElement('a'); |
| | link.href = URL.createObjectURL(blob); |
| | link.download = `ratings_${{labellerId}}.csv`; |
| | link.click(); |
| | }} |
| | |
| | // Add event listener to enable/disable save button based on labeller ID |
| | document.getElementById('rater-name').addEventListener('input', function() {{ |
| | const saveButton = document.getElementById('save-button'); |
| | saveButton.disabled = !this.value.trim(); |
| | }}); |
| | |
| | // Initially disable save button |
| | document.getElementById('save-button').disabled = true; |
| | |
| | // Render images when the page loads |
| | document.addEventListener('DOMContentLoaded', function() {{ |
| | renderImages(); |
| | }}); |
| | </script> |
| | </body> |
| | </html> |
| | ''' |
| | |
| | with open(output_file, 'w', encoding='utf-8') as f: |
| | f.write(html_content) |
| | |
| | print(f"Rating form with 5 models has been generated as {output_file}") |
| |
|
| |
|
| | def generate_rating_html4(image_paths, image_ids, desc1, desc2, desc3, desc4, output_file='gallery_with_ratings.html'): |
| | |
| | html_content = '''<!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Image Gallery with Ratings</title> |
| | <style> |
| | body { |
| | font-family: Arial, sans-serif; |
| | max-width: 800px; |
| | margin: 0 auto; |
| | padding: 20px; |
| | background: #f5f5f5; |
| | } |
| | .image-container { |
| | background: white; |
| | border-radius: 8px; |
| | padding: 20px; |
| | margin-bottom: 30px; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | } |
| | .image-container img { |
| | max-width: 500px; |
| | height: auto; |
| | border-radius: 4px; |
| | display: block; |
| | margin: 0 auto; |
| | } |
| | .description { |
| | background: #f8f8f8; |
| | padding: 15px; |
| | margin: 10px 0; |
| | border-radius: 4px; |
| | white-space: pre-line; /* This helps preserve line breaks */ |
| | } |
| | .image-title { |
| | font-size: 1.5em; |
| | font-weight: bold; |
| | margin-bottom: 15px; |
| | color: #333; |
| | text-align: center; |
| | } |
| | .model-title { |
| | font-weight: bold; |
| | color: #666; |
| | margin-bottom: 5px; |
| | } |
| | .rating { |
| | display: flex; |
| | align-items: center; |
| | margin-top: 10px; |
| | padding: 10px; |
| | background: #fff; |
| | border-radius: 4px; |
| | } |
| | .rating-label { |
| | margin-right: 10px; |
| | font-weight: bold; |
| | } |
| | .rating-group { |
| | display: flex; |
| | gap: 10px; |
| | } |
| | .rating-radio { |
| | display: none; |
| | } |
| | .rating-button { |
| | padding: 8px 12px; |
| | border: 1px solid #ccc; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | transition: all 0.2s; |
| | } |
| | .rating-radio:checked + .rating-button { |
| | background: #007bff; |
| | color: white; |
| | border-color: #0056b3; |
| | } |
| | .save-button { |
| | position: fixed; |
| | bottom: 20px; |
| | right: 20px; |
| | padding: 10px 20px; |
| | background: #007bff; |
| | color: white; |
| | border: none; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | } |
| | .save-button:disabled { |
| | background: #cccccc; |
| | cursor: not-allowed; |
| | } |
| | #images-container { |
| | /* Container for all image blocks */ |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div id="rater-name-container"> |
| | <label for="rater-name" style="font-weight: bold;">Labeller id:</label> |
| | <input type="text" id="rater-name" style="margin: 10px 0; padding: 5px; width: 200px;"> |
| | </div> |
| | |
| | <div id="images-container"> |
| | <!-- Image containers will be inserted here dynamically --> |
| | </div> |
| | ''' |
| | |
| | |
| | js_image_paths = [] |
| | js_image_ids = [] |
| | js_desc1 = [] |
| | js_desc2 = [] |
| | js_desc3 = [] |
| | js_desc4 = [] |
| | |
| | for i in range(len(image_paths)): |
| | js_image_paths.append(f'"{image_paths[i]}"') |
| | js_image_ids.append(f'"{image_ids[i]}"') |
| | |
| | |
| | desc1_html = desc1[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc2_html = desc2[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc3_html = desc3[i].replace('"', '\\"').replace('\n', '<br>') |
| | desc4_html = desc4[i].replace('"', '\\"').replace('\n', '<br>') |
| | |
| | js_desc1.append(f'"{desc1_html}"') |
| | js_desc2.append(f'"{desc2_html}"') |
| | js_desc3.append(f'"{desc3_html}"') |
| | js_desc4.append(f'"{desc4_html}"') |
| | |
| | |
| | html_content += f''' |
| | <button onclick="saveRatings()" class="save-button" id="save-button">Save Ratings as CSV</button> |
| | |
| | <script> |
| | // Store all image data as separate arrays |
| | const imagePaths = [{', '.join(js_image_paths)}]; |
| | const imageIds = [{', '.join(js_image_ids)}]; |
| | const desc1 = [{', '.join(js_desc1)}]; |
| | const desc2 = [{', '.join(js_desc2)}]; |
| | const desc3 = [{', '.join(js_desc3)}]; |
| | const desc4 = [{', '.join(js_desc4)}]; |
| | |
| | // Create an array of indices to shuffle for images |
| | let indices = []; |
| | for (let i = 0; i < imageIds.length; i++) {{ |
| | indices.push(i); |
| | }} |
| | |
| | // Function to render all images in randomized order with randomized model order |
| | function renderImages() {{ |
| | const container = document.getElementById('images-container'); |
| | |
| | // Shuffle the indices (this randomizes image order) |
| | shuffleArray(indices); |
| | |
| | // Clear the container |
| | container.innerHTML = ''; |
| | |
| | // Add each image in shuffled order |
| | indices.forEach((originalIndex, newIndex) => {{ |
| | // For each image, we'll create a different random order for the models |
| | let modelOrder = [1, 2, 3, 4]; |
| | shuffleArray(modelOrder); |
| | |
| | // Start building the image container HTML |
| | let imageHtml = ` |
| | <div class="image-container" data-image-id="${{imageIds[originalIndex]}}"> |
| | <div class="image-title">Image ID: ${{imageIds[originalIndex]}}</div> |
| | <img src="${{imagePaths[originalIndex]}}" alt="Image ${{imageIds[originalIndex]}}"> |
| | `; |
| | |
| | // Add descriptions and rating UI for each model in the randomized order |
| | modelOrder.forEach((modelNum, modelIndex) => {{ |
| | // Get the description data for this model |
| | let descData; |
| | if (modelNum === 1) descData = desc1[originalIndex]; |
| | else if (modelNum === 2) descData = desc2[originalIndex]; |
| | else if (modelNum === 3) descData = desc3[originalIndex]; |
| | else if (modelNum === 4) descData = desc4[originalIndex]; |
| | |
| | // Create HTML for this model's description and rating |
| | imageHtml += ` |
| | <div class="description"> |
| | <div class="model-title">Model ${{modelNum}}</div> |
| | ${{descData}} |
| | <div class="rating" data-model="${{modelNum}}"> |
| | <span class="rating-label">Rating:</span> |
| | <div class="rating-group"> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="1" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-1"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-1">1</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="2" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-2"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-2">2</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="3" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-3"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-3">3</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="4" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-4"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-4">4</label> |
| | <input type="radio" name="rating-${{newIndex}}-${{modelNum}}" value="5" class="rating-radio" data-image="${{imageIds[originalIndex]}}" data-model="${{modelNum}}" id="rating-${{newIndex}}-${{modelNum}}-5"> |
| | <label class="rating-button" for="rating-${{newIndex}}-${{modelNum}}-5">5</label> |
| | </div> |
| | </div> |
| | </div>`; |
| | }}); |
| | |
| | // Close the image container div |
| | imageHtml += `</div>`; |
| | |
| | // Add the complete HTML for this image to the page |
| | container.innerHTML += imageHtml; |
| | }}); |
| | }} |
| | |
| | // Fisher-Yates shuffle algorithm |
| | function shuffleArray(array) {{ |
| | for (let i = array.length - 1; i > 0; i--) {{ |
| | const j = Math.floor(Math.random() * (i + 1)); |
| | [array[i], array[j]] = [array[j], array[i]]; |
| | }} |
| | return array; |
| | }} |
| | |
| | function saveRatings() {{ |
| | const labellerId = document.getElementById('rater-name').value.trim(); |
| | |
| | if (!labellerId) {{ |
| | alert('Please enter a Labeller ID before saving'); |
| | return; |
| | }} |
| | |
| | const ratings = []; |
| | |
| | // Collect all ratings |
| | document.querySelectorAll('.rating-radio:checked').forEach(radio => {{ |
| | ratings.push({{ |
| | model: radio.dataset.model, |
| | image_id: radio.dataset.image, |
| | rating: radio.value |
| | }}); |
| | }}); |
| | |
| | // Convert to CSV |
| | const headers = ['model', 'image_id', 'rating']; |
| | const csvContent = [ |
| | headers.join(','), |
| | ...ratings.map(row => [ |
| | row.model, |
| | row.image_id, |
| | row.rating |
| | ].join(',')) |
| | ].join('\\n'); |
| | |
| | // Create and trigger download with labeller ID in filename |
| | const blob = new Blob([csvContent], {{ type: 'text/csv;charset=utf-8;' }}); |
| | const link = document.createElement('a'); |
| | link.href = URL.createObjectURL(blob); |
| | link.download = `ratings_${{labellerId}}.csv`; |
| | link.click(); |
| | }} |
| | |
| | // Add event listener to enable/disable save button based on labeller ID |
| | document.getElementById('rater-name').addEventListener('input', function() {{ |
| | const saveButton = document.getElementById('save-button'); |
| | saveButton.disabled = !this.value.trim(); |
| | }}); |
| | |
| | // Initially disable save button |
| | document.getElementById('save-button').disabled = true; |
| | |
| | // Render images when the page loads |
| | document.addEventListener('DOMContentLoaded', function() {{ |
| | renderImages(); |
| | }}); |
| | </script> |
| | </body> |
| | </html> |
| | ''' |
| | |
| | with open(output_file, 'w', encoding='utf-8') as f: |
| | f.write(html_content) |
| | |
| | print(f"Rating form with 4 models has been generated as {output_file}") |