AI-Created Software

Last Updated: October 13, 2026

The following is AI-generated (aka vibe-coded) software. The software is specific to my needs and my system and may not work for you or your system. Support is limited, but if you report a bug I'll certainly look into it. All code is provided in a zip file for you to review should you choose.

Depth Map Reference Mask

Paint over part of a depth map and get a grayscale mask of everything at that depth. The painted area comes out white, and every other pixel fades toward black the further its depth is from the painted area’s. You control how wide the fully-white band is, how far the fade reaches toward and away from camera, and the shape of the fade.

Typical uses are isolating a subject for grading or compositing, building depth-of-field or fog passes, and cutting a foreground element out of a color frame using its depth map.

Requirements

Python 3.8 or newer with Tk (included with the python.org installers; on Debian/Ubuntu install python3-tk). Batch mode and the self-test run without Tk; opening the window without it stops with a message saying how to install it.

pip install pillow numpy

Three optional packages add capabilities. The app runs without them and tells you when one would help.

Package Adds
opencv-python EXR depth maps, and full precision for 16-bit color PNG/TIFF files (Pillow reads those at 8 bits)
scipy Faster “connected region only” on large images
rawpy Camera RAW files as color frames

For EXR files, install OpenCV 4: pip install "opencv-python<5". OpenCV 5.0 from pip is built without EXR support. With it, the app still reads 16-bit color files at full precision, but it doesn’t offer EXR files, and opening one says why. --selftest TRUE shows which you have.

Command line

python depth-map-reference-mask.py

To open a saved project straight away:

python depth-map-reference-mask.py --project-file-or-folder-path results/shot01/shot01.json

The project path can be the .json file or the results folder that holds it.

Every option is written --name VALUE or --name=VALUE, in any order, and every option takes a value: yes/no options are TRUE or FALSE (1/0, YES/NO and ON/OFF work too, in any case). An option you leave out is blank, which means its default. Script launchers that read the options (such as Scriptary) offer the values of the yes/no options and --bit-depth as buttons.
python depth-map-reference-mask.py --help lists them all.

Option Effect
--project-file-or-folder-path PROJECT A saved project: its .json file, or the results folder that holds it. Opened in the window, or in batch mode the settings applied to every frame.
--working-folder-path FOLDER The folder for the settings file and temporary files, created if needed (see Settings). Blank: %APPDATA%\depth-map-reference-mask on Windows, ~/Library/Application Support/depth-map-reference-mask on macOS, ~/.config/depth-map-reference-mask elsewhere; --help shows it for your computer.
--frames-file-or-folder-path FRAMES Runs batch mode instead of opening the window: the depth frames to process, as a file, a folder, or a wildcard pattern such as "frames/shot01_*.png".
--output-folder-path FOLDER Batch mode: where to write. Blank: <project name>_batch next to the first frame.
--rgba TRUE Batch mode: also write RGBA cutouts. Blank or FALSE: no cutouts.
--color-folder-path FOLDER Batch mode: color frames for the cutouts, matched by file name (frame_0012.png pairs with frame_0012.jpg).
Blank: the cutouts use the depth map.
--fixed-reference TRUE Batch mode: use the project’s saved reference for every frame instead of re-estimating it. Blank or FALSE: re-estimate it.
--bit-depth match\|8\|16 Batch mode: the masks’ bit depth. Blank: the project’s setting.
--overwrite TRUE Batch mode: replace existing results. Blank or FALSE: frames that already have a result are skipped.
--selftest TRUE Check the app’s own functions with images it makes itself, without opening the window or touching your files, then exit: 0 when every check passes, 1 when one fails. See Testing.

A mistake on the command line (an unknown or shortened option, an option given twice, a value it doesn’t take, a bare --rgba without a value, or a batch option without --frames-file-or-folder-path) stops with a message saying what was wrong and exit code 2. Started without a console, with pythonw.exe, the message appears in a box instead.

Quick start

  1. Open depth map and choose a depth map (PNG, TIFF, JPEG, BMP, or EXR with OpenCV).
  2. Paint over the surface you want to keep with the left mouse button.
    The result appears on the right as soon as you release.
  3. Adjust the falloff in the Falloff shaping panel, or drag the lines on the histogram.
  4. Optionally Open matching photo to add the color photo of the same frame, at the same size, which becomes the RGB of the cutout. It is read as a photo: its embedded color profile is converted to sRGB, and it is turned upright by its EXIF orientation. The status line says how its colors were read (“Converted from Adobe RGB (1998) to sRGB”, “No embedded profile, assumed sRGB”).
  5. Save as picks a parent folder and writes a results folder. After that, Save (Ctrl+S) updates the same folder.

The interface

The interface is drawn in neutral grays only (every color has R = G = B), so nothing next to the depth map or the color frame tints how you see it. Color appears only in the images and in data drawn from them: the red paint overlay, blue invalid pixels, and the lines on the histogram, which are also labeled, so none is told apart by color alone.
The brush outline is a white ring over a black one, so it shows on any depth; it is solid for painting, dashed for erasing, and has a crosshair when picking a reference.

Warnings in the status bar start with ⚠ and are in bold. Numbers and paths use the same typeface as the rest of the interface, in a smaller size.

Everything follows the display scaling set in Windows or Linux, not only the text: buttons, spacing, sliders, lines and the histogram. At 150 % they are all half as large again as at 100 %. At 100 % the window opens at 1400 × 940 and can shrink to 1100 × 780; at other scaling, both sizes grow to match.

The Open depth map button is drawn solid because it’s where you start. In each switch, the selected choice is filled light; a selected Off is filled a quieter gray.

Rest the pointer on any button, switch, slider or field in the Depth histogram, Falloff shaping and Output panels, or on the histogram itself, for half a second and a tooltip says what it does. The tips open above the controls when there’s no room below them in the window. The Save and Save as buttons have tooltips too, listing the files they write under the open depth map’s name, and for Save the folder it will update (see Saved results). So do Open depth map, Open matching photo and Load project, saying what file each expects.

How the mask is built

All depth maths happens on a 0–255 scale, whatever the file’s bit depth. A 16-bit source keeps its precision and can be saved as a 16-bit result.

Reference

The reference is the depth the mask centers on.

  • Painted: estimated from the painted area.
    Estimate chooses how:
    • Median (the default) ignores a brush that spills over an edge.
    • Trimmed averages the middle 80 % of painted depths.
    • Mean matches versions before 3.0.
  • Locked: a fixed value. Type it into the field next to the buttons, Alt-click the depth map (Option-click on macOS), or press I and then click. Picking takes the median depth inside the brush circle, since single depth pixels are noisy. Dragging the reference line on the histogram also locks it.

spread shows how much depth varies under the paint (5th to 95th percentile). A large spread usually means the brush crossed an edge.

Falloff

Control What it does
Plateau A band either side of the reference that stays fully white.
Fit to paint sets it to half the painted spread.
Near falloff How far the fade reaches toward camera before hitting black.
Far falloff How far it reaches away from camera.
Auto range Sets near and far so the fade just reaches the ends of the image’s depth range. It ignores the outer 0.5 % at each end, so a few stray pixels can’t stretch it. Turn it off to set the falloffs by hand.
Brighter = nearer Which direction counts as “near”. Leave on for the usual convention (white is close); turn off for maps where larger values are further away, such as metric depth.
Curve Linear; Smooth (smoothstep, which removes the creases where the fade meets white and black); or Gamma with its slider (above 1 falls away faster, below 1 holds brightness longer).
Connected Keeps only areas joined to the painted pixels through mask values at or above the threshold. This separates two objects at the same depth.
With auto range on, most of the image stays above a low threshold and joins up through the background, so this works best with manual falloffs or a higher threshold.

The histogram

The histogram shows how many pixels sit at each depth, on a log scale, on a black ground. Over it are the mask curve (green), the reference (red, labeled with its value, dashed when locked), the plateau (blue band) and the falloff ends (dashed blue, labeled Near and Far).

Drag the reference line to lock a new reference, a plateau edge to resize the plateau, or a falloff end to set that falloff (this turns auto range off).

Input range

Input range decides which file values map onto 0 and 255.

  • Format: the range the file type implies. 8-bit files use 0–255 and 16-bit files 0–65535. Float files are checked against 0–1, 0–255 and 0–65535, whichever fits the data.
  • Stretch: the data’s own 0.1–99.9 percentile range.
    Use this for metric or disparity maps that only fill a small part of their format’s range.
  • Custom: type the low and high values.

The data readout shows the file’s actual minimum and maximum. Changing the input range keeps a locked reference at the same file depth.

Invalid pixels

NaN and infinite values in float maps (often the sky in EXR renders) count as having no depth. They show blue on the depth map and are always black in the mask.

When a file needs your attention, a notes link appears in the status bar. Examples: invalid pixels, precision lost to a missing OpenCV install, or depth that would read better with Stretch.

Mouse and keyboard shortcuts

Action Input
Paint / erase Left drag / right drag
Pick reference under the brush Alt-click (Option-click on macOS), or I then click; Esc cancels
Brush size [** and **], or the Brush slider
Toggle mask overlay \
Pan Middle drag, Space + drag, or left drag on the result pane
Zoom at the cursor Ctrl + mouse wheel (Cmd on macOS)
Scroll Mouse wheel; Shift + wheel scrolls sideways
Fit / 100 % Ctrl+0 / Ctrl+1
Undo / redo Ctrl+Z / Ctrl+Y or Ctrl+Shift+Z
Save / Save as Ctrl+S / Ctrl+Shift+S
Open depth map Ctrl+O

On macOS the Command key works for all the Ctrl shortcuts. Shortcuts, single-key and Ctrl alike, are ignored while you’re typing in a text field or a dialog is open, so Ctrl+Z in the reference field doesn’t undo your painting. Caps Lock makes no difference: Ctrl+Z still undoes and Ctrl+S still saves.

Fit keeps the whole depth map in view: the image is fitted again whenever its pane changes size, such as when the window is resized or maximized, until you pick another zoom.

Brush units: with Display, the brush keeps the same size on screen at any zoom. With Source, it covers a fixed number of image pixels, which is better for repeatable work.

Undo covers painting, erasing and Clear painted mask, which is why clearing doesn’t ask for confirmation. Undo history is capped at about 256 MB. The Undo and Redo buttons are grayed out when there is nothing to undo or redo.

Unsaved work: opening another depth map, loading a project, and closing the window all offer to save an unsaved result first.

Saved results

Save as creates a folder named after the depth map inside the parent folder you choose. If that name is taken, it adds _01, _02, and so on. For a depth map called shot01.png the folder contains:

File Contents
shot01_with_reference_set.png The mask. 8- or 16-bit grayscale, set by Bit depth: Match writes 16-bit when the source has more than 8 bits.
shot01_mask.png The painted area, so the project can be reopened and repainted.
shot01_rgba.png With RGBA cutout on: the color frame (or the depth map if none is loaded) with the mask as alpha. Always 8-bit sRGB, with an sRGB profile.
shot01.json The project file: every setting, the reference used, and paint statistics.
shot01.png The source depth map, and the color frame if one is linked. The app tries a symbolic link, then a hard link, then a copy.

The images are written to temporary files first and only then moved into place. A failed save therefore leaves the previous results untouched rather than a new mask next to an old painted area.

Projects

Load project restores the depth map, color frame, painted area and all settings. The app first looks for the source files next to the project file and then at the recorded paths, so a results folder can be moved or copied.

If a color frame was resized to fit when it was first linked, it is resized again on load.

After loading, the app compares its result with the saved file. The project only shows as saved if they match.

Projects from older versions still open. They keep the Mean estimator so the reference is unchanged. Auto range now ignores outliers, however, so their result can differ; in that case it shows as unsaved.

Batch mode

Apply a saved project to a sequence of frames without opening the window:

python depth-map-reference-mask.py --project-file-or-folder-path shot01.json --frames-file-or-folder-path frames/
python depth-map-reference-mask.py --project-file-or-folder-path shot01.json --frames-file-or-folder-path "frames/*.png" --output-folder-path masks/ --rgba TRUE --color-folder-path color/ --overwrite TRUE

For each frame, the reference is re-estimated from the project’s painted area, so it follows a subject whose depth drifts over the shot.
Auto range, Stretch input range and connected-region filtering are also worked out per frame. The batch options are in the Command line table.

--frames-file-or-folder-path takes one value: a single frame, a folder (every image in it), or a wildcard pattern (* for any characters, ? for one, [0-4] for one of a set). The app expands the pattern itself, so it works the same on Windows; on Linux and macOS put it in quotes, or the shell expands it first and the extra file names are refused. Files the tool wrote itself are skipped, so the output folder can be the input folder.

A few conditions apply:

  • Frames must be the same size as the painted area, unless the reference is locked or fixed.
  • Stretch maps every frame separately, which can make results flicker across a sequence; Custom keeps the mapping constant.
  • Each run writes batch.json to the output folder, recording the reference and falloffs used for every frame.
  • The exit code is 0 when every frame was written or skipped, and 1 if any frame failed.

Batch mode doesn’t import Tk, so it runs on machines without a display.

Settings

Window size and position, last-used folders, and view preferences are stored in depth-map-reference-mask.ini in the working folder: %APPDATA%\depth-map-reference-mask on Windows unless --working-folder-path names another. They are saved when you close the window, and view choices as you change them. Mask settings are not stored there; they belong to projects.

Earlier versions kept the settings file beside the launcher script.
The first time the app finds no settings in its working folder, it reads that file instead, so nothing is lost, and saves them to the working folder from then on. The old file is left in place; delete it whenever you like.

The window opens where you left it, maximized if it was. A position that would put the title bar off every screen (a monitor that has since been unplugged, say) is dropped, and the window opens at the default place instead. A value edited by hand that the app can’t use falls back to its default, and a file that can’t be read is ignored. Settings files from older versions, which kept everything in one [settings] section, are read and rewritten in the current layout.

To keep the settings somewhere else, for example a second set for another kind of work:

python depth-map-reference-mask.py --working-folder-path D:\depth-settings\portraits

A folder given that can’t be created stops the app with a message (exit code 2). If the default folder can’t be created, the app says so and runs without saving its settings. If the settings file can’t be written (a full disk, or a folder you can’t write to), the app says so once, in a message, and goes on; your choices just aren’t kept.

The window icon is depth-map-reference-mask.ico, beside the launcher. On Windows the title bar and the taskbar each take the size they need from it; elsewhere every size in it is used. Without it the app runs with the default Tk icon.
python tools/generate-icons.py draws it again.

On Windows the app registers its own taskbar identity, so the taskbar shows its icon rather than Python’s, and pinning it pins this app. The identity is infolific.depth-map-reference-mask, as for the owner’s other apps. It changed on 2026-09-30, so a pin made before then opens a separate button: unpin it, start the app and pin it again.

Troubleshooting

  • The window doesn’t open, and the app says Tkinter is not available. Install Tk for your Python: the python.org installers include it; on Debian and Ubuntu, sudo apt install python3-tk. Batch mode works without it.
  • Something doesn’t work as described.
    python depth-map-reference-mask.py --selftest TRUE checks the app’s own functions and says which optional packages it found. Every line should end in ok.
  • EXR files aren’t offered, or opening one says OpenCV was built without EXR support. Install OpenCV 4: pip install "opencv-python<5" (OpenCV 5.0 from pip has no EXR).
  • A note says a 16-bit color depth map was read at 8 bits. Install opencv-python to keep the full precision.
  • The mask is almost all white or all black, or the histogram is squeezed into a few bars. The depth map probably fills only a small part of its format’s range, as metric and disparity maps do.
    Choose Stretch under Input range, or Custom with the file’s own values (the Data readout shows them).
  • The mask reaches the wrong way. Turn Brighter = nearer off for maps where larger values are further away.
  • Connected keeps too much. With auto range on, most of the image stays above a low threshold and joins up through the background. Set the falloffs by hand, or raise the threshold.
  • The app asks to resize the color frame. The color frame must be the same size as the depth map. A frame stored sideways with its orientation in EXIF is turned upright first, so it only asks when the sizes really differ.
  • The taskbar shows a second button, or Python’s icon. A pin made before 2026-09-30 belongs to an old taskbar identity: unpin it, start the app and pin it again.
  • The window opens somewhere odd, or you want the defaults back. Close the app and delete depth-map-reference-mask.ini in the working folder (--help shows where it is on your computer).
  • The command line stops with “Error: …”. A mistake on the command line (exit code 2); the message says what was wrong, and --help lists the options.

Limitations

  • One reference depth per mask: the mask is white around one depth and fades with distance from it. Two separate depth bands need two masks.
  • RGBA cutouts are always 8-bit; the masks themselves can be 16-bit.
  • Camera RAW color frames need rawpy, and are decoded with the camera’s white balance and a default rendering, not your darktable edit. For a matching cutout, export the frame from darktable as a TIFF or PNG instead.
  • In batch mode, frames must be the same size as the painted area unless the reference is locked or fixed, and Stretch maps each frame separately, which can make results flicker across a sequence.
  • Undo history is capped at about 256 MB; zoom goes from 1% to 3200%.
  • The Windows-only parts (display scaling awareness, the taskbar identity, the .ico icon) aren’t exercised by the tests, which run under Linux.

Project layout

depth-map-reference-mask.py   launcher: the command line, then the app or batch mode
depth-map-reference-mask.ico  the window icon
CLAUDE.md                     working notes and conventions for Claude Code
depthmask/
    constants.py              limits, file suffixes, paths
    depth_io.py               reading depth maps, bit-depth detection, input range
    maths.py                  mask curve, auto range, reference estimators, connected regions
    pipeline.py               mask settings, project files, rendering, safe saving
    batch.py                  batch mode
    settings.py               the preferences INI file
    command_line.py           the command-line parser, copied from the app templates
    optional.py               optional OpenCV / SciPy support, and whether Tkinter is there
    photo_io.py               reading color frames as sRGB photos, copied from the app templates
    selftest.py               the checks run by --selftest TRUE
    app.py                    the Tk interface
    window.py                 DPI awareness, taskbar ID, the icon, display scaling, saving and restoring the window
    theme.py, widgets.py      the look: palette, fonts, display scaling, buttons and switches
tests/                        pytest tests: test_gui.py opens the window; conftest.py and helpers.py hold fixtures
pytest.ini                    pytest's settings (keeps it out of templates/)
tools/
    generate-icons.py         draws the window icon, depth-map-reference-mask.ico
    get_templates.py          fetches the app templates into templates/ (not part of the app)
templates.ini                 which app templates this app follows

Everything except app.py, window.py, theme.py and widgets.py works without Tk, so the maths and file handling can be reused in other scripts.

Testing

To check an installation:

python depth-map-reference-mask.py --selftest TRUE

It prints one line per check, ending in ok or FAIL: the mask response and its falloffs, the reference estimators, connected regions, reading 8-bit, 16-bit and float depth maps, saving a 16-bit result, project files old and new, and batch mode.
Then it says whether OpenCV, SciPy and Tkinter are installed, and ends with “All checks passed.” or “Some checks FAILED.”. It works in a temporary folder and needs no display.

The tests use pytest (pip install pytest):

python -m pytest
python -m pytest -m "not gui"     # only the tests that don't open windows

The 101 tests take about 16 seconds, 5 without the window tests (measured in the cloud container; 3 are skipped there without OpenCV).
The window tests open the real window: they use the display in $DISPLAY, or on Linux start Xvfb (and openbox, which maximizing needs) themselves, and are skipped when there is no display at all. They cover painting a mask, settings saved and restored (and hand-edited ones, and a settings file that can’t be written), the window put back where it was left, main() opening a project, closing with work queued, a second window, Fit following the pane after a project opens and when the window is resized, drawing at every zoom from 100% to 300%, shortcuts while typing or with a dialog open, Ctrl shortcuts with Caps Lock on, Undo and Redo grayed out with nothing to do, the Save tooltips naming the open depth map’s files, a file that fails to open, and every control fitting at the minimum window size. Run them at 150% display scaling too: start Xvfb :98 -screen 0 3000x1800x24 -dpi 144 and DISPLAY=:98 openbox, then DISPLAY=:98 python -m pytest.

Tests that need OpenCV (or OpenCV with EXR) are skipped when it isn’t installed. The connected-region tests check the built-in fallback against SciPy and OpenCV when those are available.

(1 votes, average: 5.00 out of 5)

Leave a Reply

Your email address will not be published. Required fields are marked *

Notify me of followup comments via e-mail.