MATCHA / library /utils_html.py
Chris Addis
base version
9883bdb
def generate_slides_html(image_paths, image_ids, desc1, desc2, desc3, output_file='gallery_with_descriptions.html'):
# Start of HTML content
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 -->
'''
# Generate content for each image
for i in range(len(image_paths)):
# Process descriptions to handle line breaks
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'):
# Start of HTML content with linebreaks
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>
'''
# Create JavaScript arrays for each piece of data
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]}"')
# Process descriptions to properly handle line breaks - replace \n with <br> for HTML rendering
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}"')
# Add JavaScript to handle randomization and image display
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'):
# Start of HTML content
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>
'''
# Create JavaScript arrays for each piece of data
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]}"')
# Process descriptions to properly handle line breaks - replace \n with <br> for HTML rendering
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}"')
# Add JavaScript to handle randomization and image display
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}")