---
title: "Mural Registry"
description: "Public-art mural registry API: wall photo uploads with thumbnails, grayscale copies and color tags"
url: "https://www.pixeltable.com/recipes/mural-registry"
source: "https://github.com/pierrebrunelle/pixeltable-mural-registry"
---

# Mural Registry

Public-art mural registry API: wall photo uploads with thumbnails, grayscale copies and color tags

One `app.py` with 1 table, 5 computed columns, 5 HTTP routes. Needs `pixeltable[serve]>=0.7.14`.

## Tables

### murals (table)

| Column | Kind | Type or expression |
| --- | --- | --- |
| id | primary key | `pxtf.uuid.uuid7()` |
| title | stored | String |
| artist | stored | String |
| neighborhood | stored | String |
| wall_photo | stored | Image |
| thumb | computed on insert | `wall_photo.resize((96, 64))` |
| gray | computed on insert | `wall_photo.convert('L')` |
| aspect | computed on insert | `aspect_band(wall_photo)` |
| channel | computed on insert | `dominant_channel(wall_photo)` |
| tone | computed on insert | `tone_tag(aspect, channel)` |

- B-tree index on `neighborhood`

## Routes (service `mural_api`)

- `POST /murals`: inserts into `murals` (takes title, artist, neighborhood; returns id, aspect, channel, tone)
- `POST /analyze`: computes `murals` columns without storing (returns aspect, channel, tone)
- `POST /murals/delete`: deletes from `murals`
- `GET /murals/by-neighborhood`: runs the query `in_neighborhood` (takes neighborhood)
- `GET /murals/thumb`: runs the query `mural_thumb` (takes title)

## Run it locally

```sh
git clone https://github.com/pierrebrunelle/pixeltable-mural-registry
cd pixeltable-mural-registry
uv sync
uv run pxt schema update app.py murals
uv run python seed.py murals
uv run pxt service run app.py murals --port 8000
```

## Run it on Pixeltable Cloud

```sh
uv run pxt new    # a free database, no account
# put the pxt:// address it prints in pixeltable.toml
uv run pxt db update pxt://<org>:main -f
uv run pxt schema update app.py pxt://<org>:main -f
uv run pxt service update app.py pxt://<org>:main -f
```

## app.py

At commit `adde260c` (https://github.com/pierrebrunelle/pixeltable-mural-registry/blob/adde260cf27eaf6fbd3c8e13573030a58b0f7b6d/app.py):

```python
"""Mural Registry API built with Pixeltable.

    pxt schema update app.py murals
    pxt service run app.py murals
"""
import pixeltable as pxt
import pixeltable.functions as pxtf
from pixeltable.serving import FastAPIRouter

from udfs import aspect_band, dominant_channel, tone_tag

# ---- tables ----
TableModel = pxt.model_base()


class Murals(TableModel, name='murals', has_default_idxs=False):
    id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)
    title: pxt.String
    artist: pxt.String
    neighborhood: pxt.String
    wall_photo: pxt.Image

    thumb = wall_photo.resize((96, 64))      # generated media
    gray = wall_photo.convert('L')           # generated media
    aspect = aspect_band(wall_photo)
    channel = dominant_channel(wall_photo)
    tone = tone_tag(aspect, channel)         # chained on two computed columns

    __indexes__ = [pxt.BtreeIndex(neighborhood)]


# ---- queries ----
@pxt.query
def in_neighborhood(neighborhood: str):
    """Murals in one neighborhood (neighborhood index)."""
    return Murals.where(Murals.neighborhood == neighborhood).select(
        Murals.id, Murals.title, Murals.artist, Murals.aspect, Murals.tone
    ).order_by(Murals.title)


@pxt.query
def mural_thumb(title: str):
    """The thumbnail for a mural title (served as an image file)."""
    return Murals.where(Murals.title == title).order_by(Murals.id, asc=False).limit(1).select(Murals.thumb)


# ---- routes ----
mural_api = FastAPIRouter(name='mural_api')
mural_api.add_insert_route(
    Murals, path='/murals',
    inputs=[Murals.title, Murals.artist, Murals.neighborhood],
    uploadfile_inputs=[Murals.wall_photo],
    outputs=[Murals.id, Murals.aspect, Murals.channel, Murals.tone],
)
mural_api.add_compute_route(Murals, path='/analyze', inputs=[], uploadfile_inputs=[Murals.wall_photo],
                            outputs=[Murals.aspect, Murals.channel, Murals.tone])
mural_api.add_delete_route(Murals, path='/murals/delete')
mural_api.add_query_route(path='/murals/by-neighborhood', query=in_neighborhood, method='get')
mural_api.add_query_route(path='/murals/thumb', query=mural_thumb, method='get', one_row=True,
                          return_fileresponse=True)
```
