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