Here is my experimental design:
- Total of 1028 reference images, evenly divided into 4 categories: animals, people, nature, and food (256 images per category).
- Each image can have 3 distortions applied to them (01, 02, 03), and each distortion has 3 levels (A, B, C).

- I want approximately 300 observers to rate around 400 reference images each such that on the observer level:
1. Each observer rates an equal number of images from each of the 4 categories.
2. The distortions and levels are evenly applied within each category.
3. Each reference image is maximally seen once.

On a group level, all reference images should receive a similar number of ratings if possible.

My initial solution, involves generating separate condition files for each observer before running the experiment. But it currently returns 1200 images per observer and I am requesting 400

# Create a list of image categories and their corresponding range of image indices
categories = {
    'animals': range(1, 257),
    'people': range(257, 513),
    'nature': range(513, 769),
    'food': range(769, 1025)
}

# Define the distortions and levels, adding an 'original” distortion type
distortions = ['01', '02', '03', '']
levels = ['A', 'B', 'C']

# Define a function to create a list of image filenames for a given category and distortion-level combination
def image_combinations(category, distortion, level):
    image_indices = categories[category]
    if distortion == 'original':
        images = [f"{idx}{distortion}.jpg" for idx in image_indices]
    else:
        images = [f"{idx}{distortion}{level}.jpg" for idx in image_indices]

    return images

# Initialize a global image count dictionary
global_image_counts = {image: 0 for category_name in categories.keys() for distortion, level in [(distortion, level) for distortion in distortions for level in levels] for image in image_combinations(category_name, distortion, level)}

# Define a function to generate a balanced condition file for an observer
def generate_conditions(observer_id, num_observers, images_per_observer):
    images = []

    for category in categories.keys():
        for distortion in distortions:
            if distortion == 'original':
                level_sampled = False  # No level for original images
            else:
                level_sampled = True  # Sample distortion level
            for level in levels:
                if not level_sampled:
                    level = 'None'  # Levels are not used for original images
                    level_sampled = True  # Only sample original images once
                combinations = image_combinations(category, distortion, level)
                selected = random.sample(combinations, min(images_per_observer // (len(categories) * len(distortions)), len(combinations)))
                images.extend(selected)

                # Update the global_image_counts dictionary
                for img in selected:
                    global_image_counts[img] += 1

    random.shuffle(images)

    filename = f"conditions{observer_id}.csv"
    with open(filename, 'w', newline='') as csvfile:
        csvwriter = csv.writer(csvfile)

        csvwriter.writerow(["image"])
        for img in images:
            csvwriter.writerow([img])

# Generate conditions for the specified number of observers
num_observers = 300
images_per_observer = 400
distortions_levels = [(distortion, level) for distortion in distortions for level in levels]

for i in range(1, num_observers + 1):
    generate_conditions(i, num_observers, images_per_observer)