{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Adjust image brightness and contrast" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem\n", "\n", "You need to fix inconsistent lighting across hundreds of images—adjusting brightness, contrast, and color saturation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Solution\n", "\n", "**What's in this recipe:**\n", "\n", "- Adjust brightness, contrast, and saturation\n", "- Test adjustments before applying\n", "- Process multiple images in batch\n", "\n", "You adjust brightness, contrast, and saturation for images in your table using custom UDFs that wrap Pillow's `ImageEnhance` module (relies on PIL/Pillow). This lets you control enhancement levels to match your needs.\n", "\n", "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 adjustments to all images in your table.\n", "\n", "For more on this workflow, see [Get fast feedback on transformations](https://docs.pixeltable.com/howto/cookbooks/core/dev-iterative-workflow)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setup" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:02.122912Z", "iopub.status.busy": "2025-12-12T02:39:02.122497Z", "iopub.status.idle": "2025-12-12T02:39:04.646419Z", "shell.execute_reply": "2025-12-12T02:39:04.645945Z" } }, "outputs": [], "source": [ "%pip install -qU pixeltable" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:04.663070Z", "iopub.status.busy": "2025-12-12T02:39:04.662902Z", "iopub.status.idle": "2025-12-12T02:39:05.886050Z", "shell.execute_reply": "2025-12-12T02:39:05.885675Z" } }, "outputs": [], "source": [ "import pixeltable as pxt\n", "from PIL import ImageEnhance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load images" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:05.888065Z", "iopub.status.busy": "2025-12-12T02:39:05.887889Z", "iopub.status.idle": "2025-12-12T02:39:06.082418Z", "shell.execute_reply": "2025-12-12T02:39:06.081960Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Connected to Pixeltable database at: postgresql+psycopg://postgres:@/pixeltable?host=/Users/pjlb/.pixeltable/pgdata\n", "Created directory 'image_demo'.\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create a fresh directory (drop existing if present)\n", "pxt.drop_dir('image_demo', force=True)\n", "pxt.create_dir('image_demo')" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:06.084875Z", "iopub.status.busy": "2025-12-12T02:39:06.084707Z", "iopub.status.idle": "2025-12-12T02:39:06.144547Z", "shell.execute_reply": "2025-12-12T02:39:06.143959Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created table 'enhancements'.\n" ] } ], "source": [ "t = pxt.create_table('image_demo/enhancements', {'image': pxt.Image})" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:06.146800Z", "iopub.status.busy": "2025-12-12T02:39:06.146644Z", "iopub.status.idle": "2025-12-12T02:39:07.182762Z", "shell.execute_reply": "2025-12-12T02:39:07.182191Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\r", "Inserting rows into `enhancements`: 0 rows [00:00, ? rows/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "Inserting rows into `enhancements`: 3 rows [00:00, 601.16 rows/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Inserted 3 rows with 0 errors.\n" ] }, { "data": { "text/plain": [ "3 rows inserted, 6 values computed." ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t.insert(\n", " [\n", " {\n", " 'image': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000016.jpg'\n", " },\n", " {\n", " 'image': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000049.jpg'\n", " },\n", " {\n", " 'image': 'https://raw.githubusercontent.com/pixeltable/pixeltable/main/docs/resources/images/000000000036.jpg'\n", " },\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Iterate: adjust brightness and contrast for a few images first" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:07.186415Z", "iopub.status.busy": "2025-12-12T02:39:07.185785Z", "iopub.status.idle": "2025-12-12T02:39:07.189839Z", "shell.execute_reply": "2025-12-12T02:39:07.189359Z" } }, "outputs": [], "source": [ "@pxt.udf\n", "def adjust_brightness(img: pxt.Image, factor: float) -> pxt.Image:\n", " \"\"\"Adjust brightness. factor < 1 = darker, > 1 = brighter.\"\"\"\n", " return ImageEnhance.Brightness(img).enhance(factor)\n", "\n", "\n", "@pxt.udf\n", "def adjust_contrast(img: pxt.Image, factor: float) -> pxt.Image:\n", " \"\"\"Adjust contrast. factor < 1 = lower, > 1 = higher.\"\"\"\n", " return ImageEnhance.Contrast(img).enhance(factor)\n", "\n", "\n", "@pxt.udf\n", "def adjust_saturation(img: pxt.Image, factor: float) -> pxt.Image:\n", " \"\"\"Adjust saturation. factor < 1 = less saturated, > 1 = more saturated.\"\"\"\n", " return ImageEnhance.Color(img).enhance(factor)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2025-12-12T02:39:07.194342Z", "iopub.status.busy": "2025-12-12T02:39:07.194112Z", "iopub.status.idle": "2025-12-12T02:39:07.370881Z", "shell.execute_reply": "2025-12-12T02:39:07.367780Z" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
imageadjust_brightnessadjust_brightness_1
\n", " \n", "
\n", " \n", "
\n", " \n", "
" ], "text/plain": [ " image \\\n", "0 \n", " \n", " \n", " image\n", " darker\n", " brighter\n", " \n", " \n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "" ], "text/plain": [ " image \\\n", "0 \n", " \n", " \n", " image\n", " low_contrast\n", " high_contrast\n", " \n", " \n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "" ], "text/plain": [ " image \\\n", "0 \n", " \n", " \n", " image\n", " desaturated\n", " saturated\n", " \n", " \n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", "
\n", " \n", "
\n", " \n", " \n", "" ], "text/plain": [ " image \\\n", "0