Skip to main content
Open in Kaggle  Open in Colab  Download Notebook
This documentation page is also available as an interactive notebook. You can launch the notebook in Kaggle or Colab, or download it for use with an IDE or local Jupyter installation, by clicking one of the above links.

Problem

You need to make hundreds of images semi-transparent for backgrounds, overlays, or watermarks.

Solution

What’s in this recipe:
  • Set image opacity (transparency level)
  • Test transformations before applying
  • Apply to multiple images automatically
You adjust image transparency using a custom UDF that modifies alpha channels (relies on PIL/Pillow). This gives you precise control over transparency levels. You can iterate on transformations before adding them to your table. Use .select() with .collect() to preview results on sample images—nothing is stored in your table. If you want to collect only the first few rows, use .head(n) instead of .collect(). Once you’re satisfied, use .add_computed_column() to apply the opacity adjustment to all images in your table. For more on this workflow, see Get fast feedback on transformations.

Setup

%pip install -qU pixeltable
import pixeltable as pxt
from PIL import Image

Load images

# Create a fresh directory (drop existing if present)
pxt.drop_dir('image_demo', force=True)
pxt.create_dir('image_demo')
Connected to Pixeltable database at: postgresql+psycopg://postgres:@/pixeltable?host=/Users/alison-pxt/.pixeltable/pgdata
Created directory ‘image_demo’.
<pixeltable.catalog.dir.Dir at 0x132cc55b0>
t = pxt.create_table('image_demo.opacity', {'image': pxt.Image})
Created table ‘opacity’.
t.insert([
    {'image': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000776.jpg'},
    {'image': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000885.jpg'},
    {'image': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000016.jpg'},
])
Inserting rows into `opacity`: 3 rows [00:00, 782.71 rows/s]
Inserted 3 rows with 0 errors.
3 rows inserted, 6 values computed.

Iterate: Adjust opacity for a few images first

You define a custom function using the @pxt.udf decorator to make it available in Pixeltable. Inside the function, you use standard PIL (Pillow) operations to manipulate images. Pixeltable handles applying your function to every row in your table. How it works: - All image manipulation (.convert(), .split(), .point(), .putalpha()) comes from the PIL/Pillow library - These are standard Python image operations—see Pillow docs for reference - The @pxt.udf decorator lets Pixeltable apply your function to table rows - The opacity parameter (0.0 = fully transparent, 1.0 = fully opaque) controls the alpha scaling
@pxt.udf
def set_opacity(img: Image.Image, opacity: float) -> Image.Image:
    """Set image opacity (0.0 = fully transparent, 1.0 = fully opaque)."""
    img = img.convert('RGBA')
    alpha = img.split()[3]  # Get alpha channel
    alpha = alpha.point(lambda p: int(p * opacity))  # Scale alpha values
    img.putalpha(alpha)
    return img
# Test 25%, 50%, and 75% opacity
t.select(
    t.image,
    alpha_25=set_opacity(t.image, 0.25),
    alpha_50=set_opacity(t.image, 0.5),
    alpha_75=set_opacity(t.image, 0.75)
).head(1)

Add: Adjust opacity for all images in your table

# Create 50% opacity for backgrounds
t.add_computed_column(semi_transparent=set_opacity(t.image, 0.5))
Added 3 column values with 0 errors.
3 rows updated, 3 values computed.
# View original and semi-transparent side by side
t.collect()

Explanation

How the opacity technique works: The UDF modifies the alpha channel to control transparency. The function converts the image to RGBA mode (which includes an alpha channel for transparency), extracts the alpha channel with .split()[3], scales all values by the desired opacity factor using .point(lambda p: int(p * opacity)), and applies it back with .putalpha(). This preserves the original image while adjusting only the transparency level. To customize the UDF: - Opacity levels: Use 0.25 for very faint backgrounds, 0.5 for standard transparency, 0.75 for subtle effects - Selective transparency: Modify the lambda function in .point() to apply different transparency to different pixel values - Preserve regions: Add conditional logic to keep certain areas fully opaque The Pixeltable workflow: In traditional databases, .select() just picks which columns to view. In Pixeltable, .select() also lets you compute new transformations on the fly—define new columns without storing them. This makes .select() perfect for testing transformations before you commit them. When you use .select(), you’re creating a query that doesn’t execute until you call .collect(). You must use .collect() to execute the query and return results—nothing is stored in your table. If you want to collect only the first few rows, use .head(n) instead of .collect() to test on a subset before processing your full dataset. Once satisfied, use .add_computed_column() with the same expression to persist results permanently. For more on this workflow, see Get fast feedback on transformations.

See also