Gallery Layout Starter: Architecture, Specification, Interfaces, and Extension Guide

Github link

This project is a starter framework for experimenting with algorithmic gallery layout. It takes a collection of artworks, a wall specification, and a scoring model, generates many candidate layouts, evaluates them, and exports the best results as HTML pages that can be opened in a browser. The current version is intentionally simple, but it is organized so that students can clearly see the separation between data, algorithms, evaluation, and presentation.

At a high level, the system answers this question:

Given a set of paintings and a wall, how can we automatically generate multiple plausible arrangements, score them according to curatorial and spatial criteria, and keep the best few?

This is a good teaching project because it combines practical programming, data modeling, heuristic algorithms, evaluation design, and visual output. It can start as a one-wall exercise and grow into a full gallery planning system.

1. Overall Purpose

The program models a simplified exhibition-planning problem. It assumes:

  • There is a gallery wall with a fixed width and height.
  • There is a set of artworks, each with metadata and physical dimensions.
  • A layout algorithm places some subset of those artworks on the wall.
  • An evaluator judges how good that layout is.
  • The system generates many candidate layouts, ranks them, and exports the best ones.

This is not meant to be a production-ready museum system. It is a teaching and experimentation platform. The goal is to make the architecture clean enough so you can can modify one part without rewriting the rest.

2. Core Design Principles

The framework is built around a few important ideas.

First, the input data is externalized into YAML files. That means the gallery definition, the show definition, and the scoring parameters are all separated from the Python code. This is important because it keeps the program flexible and makes it easy to run different experiments without editing source files.

Second, the major responsibilities are separated into modules. Parsing input, generating layouts, evaluating layouts, and exporting results are all distinct steps. This is a classic pipeline design and makes the system easier to understand and extend.

Third, the program is intentionally heuristic. It does not try to find the mathematically optimal arrangement. Instead, it generates many candidate solutions and ranks them. That makes it appropriate for teaching greedy methods now, while leaving room for hill climbing, simulated annealing, beam search, backtracking, or genetic algorithms later.

3. High-Level Architecture

The program follows this pipeline:

YAML specification -> parser -> internal data model -> layout generator -> evaluator -> ranking -> HTML export

More concretely:

  1. The parser reads three YAML files:
    • gallery.yaml
    • show.yaml
    • scoring.yaml
  2. The parser converts these into Python objects:
    • Wall
    • Artwork
    • scoring configuration dictionary
  3. The generator creates L candidate layouts.
  4. The evaluator scores each layout.
  5. The layouts are sorted by total score.
  6. The top T layouts are written out as HTML and CSV.

This design is important because each stage has a clear contract. The parser should only build valid internal objects. The generator should only worry about making layouts. The evaluator should only worry about scoring them. The exporter should only worry about displaying results.

4. Input Files and Their Roles

The current YAML-based version uses three main data files.

4.1 gallery.yaml

This file defines the physical exhibition environment. In the current starter version, it includes a single wall inside a single gallery.

Typical information stored here includes:

  • gallery name
  • wall id
  • room name
  • wall type
  • wall width in feet
  • wall height in feet
  • light level
  • ideal spacing between artworks
  • minimum and maximum spacing
  • centerline position

This file describes the space into which layouts must fit.

4.2 show.yaml

This file defines the exhibition content. It includes:

  • show title
  • a list of artworks

Each artwork includes metadata such as:

  • id
  • title
  • artist
  • year
  • period
  • theme
  • width
  • height
  • image URL
  • source URL

This file describes what can be placed on the wall.

4.3 scoring.yaml

This file defines scoring-related values. In the current version, the evaluator reads this file, but much of the evaluator logic is still hard-coded. That is deliberate. One of the important extensions is to make the evaluator fully data-driven.

Even in its current form, this file establishes the idea that evaluation policy should live outside the code.

5. Internal Data Model

The parser converts YAML into Python dataclasses. These are the core objects the rest of the system uses.

5.1 Artwork

An Artwork represents a single painting or other framed work. It includes both descriptive metadata and physical size.

Important fields include:

  • id: unique key used internally
  • title
  • artist
  • year
  • period
  • theme
  • width_ft
  • height_ft
  • image_url
  • source_url

The layout algorithms and evaluator both depend heavily on this object.

5.2 Wall

A Wall represents the available display surface.

Important fields include:

  • width_ft
  • height_ft
  • light
  • ideal_gap_ft
  • min_gap_ft
  • max_gap_ft
  • centerline_ft

The wall determines the physical constraints for the placement process.

5.3 Placement

A Placement records the position of one artwork within a layout.

Important fields include:

  • artwork_id
  • x_ft
  • y_ft
  • gap_after_ft

This object is the generator’s main output and the evaluator’s main input.

5.4 Layout

A Layout is a candidate arrangement of artworks on a wall.

Important fields include:

  • id
  • description
  • placements

A layout does not contain full artwork data itself. It refers to artworks by id. That is a good design because it avoids duplicating data.

5.5 ScoreBreakdown

A ScoreBreakdown records the evaluator’s judgment of a layout.

Important fields include:

  • total
  • fit_score
  • spacing_score
  • cohesion_score
  • variety_score
  • rhythm_score
  • balance_score
  • center_score
  • height_score
  • notes

This is important we can see not just which layout won, but why it won.

6. Module-by-Module Explanation

The project is divided into several modules.

6.1 gallery/parser.py

This module is responsible for reading YAML input and creating internal objects.

It typically provides functions like:

  • load_gallery(path)
  • load_show(path)
  • load_scoring(path)

Its job is purely translational. It should not contain layout logic or scoring logic.

A good rule is this: if a function is deciding whether a layout is good, it does not belong in the parser.

6.2 gallery/generator.py

This module produces candidate layouts.

The current implementation uses a greedy layout strategy with randomized variation. That means the system does not produce exactly one layout. Instead, it produces many different greedy-ish layouts by changing the random seed and some tie-breaking decisions.

The generator generally works like this:

  1. Shuffle or randomize candidate artworks.
  2. Choose a target number of artworks for the wall.
  3. Bias selection toward certain themes or periods.
  4. Place artworks left to right while respecting wall width.
  5. Compute placement coordinates.
  6. Optionally center the overall arrangement.

This module is where new layout algorithms should go.

6.3 gallery/evaluator.py

This module scores layouts.

The evaluator looks at relationships among placements and computes a multi-part score. The current scoring model includes factors such as:

  • whether the layout fits on the wall
  • whether gaps between works are reasonable
  • whether adjacent works are coherent in theme or period
  • whether the arrangement has good rhythm
  • whether visual mass is balanced left versus right
  • whether the arrangement is centered
  • whether heights are consistent
  • whether the mix of themes and artists has some variety

This module should not generate layouts. Its job is to judge them.

6.4 gallery/html_export.py

This module writes the results out to files the user can view.

The exporter usually creates:

  • index.html
  • one HTML file for each top layout
  • scores.csv

The HTML output shows the wall visually, with each artwork positioned approximately according to its placement. If an artwork has an image URL, the image is shown. If not, the system can fall back to a labeled frame or title card.

This module should not do ranking or scoring. It just presents the output.

6.5 gallery/app.py

This module coordinates the whole pipeline.

Its role is to:

  • load YAML data
  • build internal objects
  • generate layouts
  • evaluate layouts
  • sort layouts
  • export results

This is the orchestration layer. It is the glue that ties the pieces together.

6.6 run.py

This is the command-line entry point.

It parses command-line arguments such as:

  • gallery path
  • show path
  • scoring path
  • L: number of candidate layouts
  • T: number of top layouts to export
  • random seed
  • output folder

Then it calls the app layer.

7. How the Program Runs

A typical run looks like this:

  1. The user runs:
    python3 run.py -L 50 -T 5
  2. The program loads:
    • one gallery specification
    • one show specification
    • one scoring specification
  3. The program generates 50 candidate layouts.
  4. Each layout is evaluated.
  5. The layouts are sorted from best to worst.
  6. The best 5 are exported to a timestamped output folder.
  7. The user opens index.html and clicks through the top layouts.

This makes the whole process concrete. Students can change an algorithm or scoring function and immediately see the visual result.

8. Interface Contracts

The project works well because the interfaces between modules are clean.

8.1 Parser Interface

The parser must return valid internal objects.

Expected output:

  • gallery name and Wall
  • show title and list of Artwork
  • scoring dictionary

The rest of the program assumes the parser has done its job correctly.

8.2 Generator Interface

A generator should take:

  • a list of Artwork
  • a Wall
  • a layout count or random seed if needed

It should return:

  • a list of Layout objects

The evaluator should not need to know how the layouts were produced.

8.3 Evaluator Interface

An evaluator should take:

  • a Layout
  • a Wall
  • a mapping from artwork id to Artwork
  • optionally scoring parameters

It should return:

  • a ScoreBreakdown

The ranking step then sorts layouts by score.total.

8.4 Export Interface

The exporter should take:

  • output path
  • gallery/show metadata
  • wall
  • ranked layouts
  • artwork lookup table

It should write files, but not change layout logic or scores.

9. Adding a New Layout Algorithm

This is one of the main intended extensions.

A new layout algorithm should be implemented as a new function or class in gallery/generator.py, or in a new module such as:

  • gallery/algorithms/greedy.py
  • gallery/algorithms/hill_climb.py
  • gallery/algorithms/annealing.py

The simplest pattern is:

  1. Define a function that produces one layout.
  2. Define a wrapper that produces many candidate layouts.
  3. Return a list of Layout objects.

A new algorithm should respect the same data contract. It should not invent a new layout format.

For example, a hill-climbing algorithm might:

  • start from a greedy layout
  • repeatedly swap, insert, or remove artworks
  • keep changes that improve the score
  • stop after a fixed number of iterations

A simulated annealing algorithm might:

  • start with a layout
  • propose random changes
  • sometimes accept worse layouts early on
  • become more selective over time

A beam search algorithm might:

  • build partial layouts incrementally
  • keep only the best few partial candidates at each stage

The key idea is that all of these still produce Layout objects. That means the same evaluator and exporter can still be used.

10. Adding a New Evaluator

Students can also create different evaluation policies.

The cleanest way to do this is to define a new evaluation function in gallery/evaluator.py or in a parallel module such as:

  • gallery/evaluators/basic.py
  • gallery/evaluators/curatorial.py
  • gallery/evaluators/provocative.py

A new evaluator should accept the same inputs and return the same ScoreBreakdown structure.

For example, one evaluator might emphasize coherence:

  • reward similar periods
  • reward theme continuity
  • penalize abrupt contrasts

Another evaluator might emphasize provocation:

  • reward unexpected juxtapositions
  • reward contrast between styles
  • penalize too much sameness

Another evaluator might emphasize symmetry:

  • reward centered arrangements
  • reward balanced masses
  • reward equal spacing

As long as the evaluator returns a valid score breakdown, the rest of the system does not care which evaluation philosophy is being used.

11. How to Make Scoring Fully Data-Driven

The current version loads scoring.yaml, but still uses mostly fixed scoring logic in code. This is a deliberate next step.

To make scoring fully data-driven, students should move numeric constants out of the evaluator and into YAML.

For example, instead of writing:

score += 10 * theme_similarity(...)

the evaluator could do something like:

score += scoring["theme_weight"] * theme_similarity(...)

This allows easy experimentation with scoring policies without changing code.

A more advanced version would allow the scoring YAML to specify:

  • weights
  • penalties
  • thresholds
  • preferred gap ranges
  • preferred hanging heights
  • theme compatibility tables
  • period compatibility tables

That would turn the evaluator into a general policy engine rather than a fixed heuristic.

12. Current Limitations

The starter framework has several limitations, all of which are useful teaching opportunities.

First, some artworks have blank image URLs. These still work because the source URL is present, but they do not render as fully visual frames.

Second, the current generator only handles one wall at a time.

Third, the evaluator assumes a simplified notion of visual balance and spacing.

Fourth, the system uses heuristics rather than exact optimization.

Fifth, the scoring model is only partly data-driven.

These are not flaws in the educational sense. They are opportunities for extension.

13. How to Expand to Multiple Walls and Multiple Rooms

A natural next step is to move from one wall to a full gallery.

The first architectural change is to generalize gallery.yaml so it contains multiple rooms and multiple walls.

For example, instead of a single wall object, the file might contain:

  • gallery
    • rooms
      • each room has walls
      • each wall has dimensions and lighting

At that point, the internal model should also change. Instead of a layout being just a list of placements on one wall, it may need to include:

  • multiple wall layouts
  • room assignments
  • wall assignments

One possible design is:

  • Gallery
    • contains rooms
  • Room
    • contains walls
  • Wall
    • contains physical constraints
  • Placement
    • includes both artwork id and wall id

Now the layout algorithm has a larger problem to solve:

  • choose which artworks go on which wall
  • choose where they go on each wall
  • possibly group them by theme, period, or show section

The evaluator also becomes more complex:

  • score each wall locally
  • score room-level coherence
  • score show-level structure across rooms

This is a major but very natural extension.

14. How to Expand to Multiple Shows

A further extension is to support multiple simultaneous shows.

At that point, the system should separate:

  • the physical gallery inventory
  • the set of available artworks
  • the selected show or shows

A cleaner model might include:

  • gallery definition
  • artwork inventory
  • show definitions
  • show-to-room or show-to-wall assignments

Then the generator becomes a planner that must decide:

  • which artworks belong to which show
  • which show occupies which room
  • how each room is laid out internally

This begins to resemble real exhibition planning software.

15. Suggested Refactoring Path

A good incremental path for expansion is:

  • First, keep the current one-wall system working and understandable.
  • Next, make scoring truly data-driven.
  • Then, add a second algorithm such as hill climbing or simulated annealing.
  • After that, refactor gallery.yaml to support multiple walls in one room.
  • Then refactor the internal layout model so placements know which wall they belong to.
  • Then allow one show to span multiple walls.
  • Finally, support multiple shows across multiple rooms.

This sequence keeps the system stable while gradually increasing complexity.

16. Why this project?

This project works particularly well in our algorithms course because it creates a visual and intuitive optimization problem.

You can compare:

  • greedy methods
  • backtracking
  • beam search
  • hill climbing
  • simulated annealing
  • genetic algorithms

They can also compare different evaluation functions and see how the ranking changes. That is pedagogically powerful because it makes the relationship between representation, algorithm, and objective function very concrete.

This is not just about coding layouts. It is about understanding how problem formulation affects algorithm behavior.

17. Conclusion

The Gallery Layout Starter is a clean foundation for experimenting with algorithmic exhibition design. Its architecture separates data specification, parsing, layout generation, evaluation, and presentation. That separation makes it easier to understand, easier to extend, and better suited for incremental work.

In its current form, the framework supports one wall, one show, and one main evaluation pipeline. That is enough for meaningful experiments. At the same time, the structure clearly points toward larger extensions: fully data-driven scoring, multiple layout algorithms, multiple evaluators, multi-wall galleries, multi-room galleries, and multiple simultaneous shows.

Scroll to Top