SeamlessFix is a Python pipeline for converting material photos into tileable textures. The project validates every generated image automatically, writes a 3x3 tiled preview, and can optionally generate a compact PBR material set from the selected seamless albedo texture.
The acceptance set is the four PNG images in Test_Image.
The final output directory is results. Each material contains:
seamless_algorithm.png- selected seamless albedo texture;tile_3x3.png- 3x3 preview for visual seam inspection;metrics.json- validator report and selected candidate metadata;pbr/- optional PBR maps generated with the--pbrflag.
| Input | Selected method | Passed | Border max | Low-frequency seam | Artifact score | Detail ratio |
|---|---|---|---|---|---|---|
84953235_01.png |
delight-0.55-best-tonal-crop |
yes | 0.0000 |
0.0000 |
0.1260 |
0.7949 |
85346059.png |
edge-cut |
yes | 0.1062 |
0.0000 |
0.1118 |
0.9964 |
89436101_02.png |
delight-0.75-structured-grid-crop-narrow |
yes | 0.0000 |
2.7482 |
0.1607 |
1.3111 |
89438339.png |
edge-equalized-narrow-detail |
yes | 1.5677 |
0.0000 |
0.0416 |
1.1618 |
| Material | Seamless texture | 3x3 tiled preview |
|---|---|---|
84953235_01 |
![]() |
![]() |
85346059 |
![]() |
![]() |
89436101_02 |
![]() |
![]() |
89438339 |
![]() |
![]() |
The primary method is a local algorithmic candidate search with automatic validation. For each input image the generator creates several candidate seamless textures, evaluates them with the local validator, and selects the best passing candidate. If no candidate passes, it falls back to the least bad candidate by rank.
This method was chosen as the main path because it is deterministic, runs offline, preserves the source material identity, and is not blocked by API quota or model behavior. Image-generation APIs were tested as optional repair branches, but their outputs often changed the material too much or failed strict tileability checks.
The selected algorithmic candidates are built from these techniques:
- narrow opposite-edge equalization;
- low-error edge cuts inside a small overlap band;
- Moisan-style periodic-plus-smooth decomposition;
- tonal crop search that minimizes broad color bands near tile boundaries;
- Retinex-style de-lighting for albedo-like correction;
- structured grid crop for orthogonal plank/tile textures;
- high-frequency edge detail restoration to reduce soft blend zones.
The final selection is not a single fixed operation for every input. It is a ranked candidate system. This is important because the four test images have different failure modes: diagonal parquet, plank floors, broad lighting gradients, and visible local seams.
The earliest baseline directly averaged opposite borders and feathered the edit into the image. It makes pixel borders tileable quickly, but wider bands can create visible soft strips in the 3x3 preview. It remains useful as a fallback and as a building block.
The project implements a Moisan-style periodic component. This can remove hard boundary discontinuities while preserving much of the texture. On structured wood textures it can introduce visible repeated cross/band artifacts, so the ranker penalizes periodic candidates when the structured-texture artifact score is high.
The crop search tries many candidate crops and prefers borders whose low-frequency tones match internal bands. It helps when the source image has broad lighting or color drift near the edges. The downside is that cropping can reduce source coverage and can cut through logical board patterns if geometry is not also considered.
The Retinex-style de-lighting pass estimates broad illumination from luminance and divides it out while preserving average color. It improved albedo consistency for lighter wood textures, especially 89436101_02.png. It is intentionally conservative because aggressive de-lighting can shift color identity.
For orthogonal plank or tile textures, the generator detects projected line positions and crops to a repeatable grid span. This is the best local strategy for preserving board logic. It is still a simple projection-based method, so it does not fully solve arbitrary parquet or herringbone reconstruction.
Two API branches were implemented and tested:
- OpenAI image edit with a planning step;
- OpenRouter image generation with a reference image.
Both branches are optional. The local validator remains the first gate, and an optional API judge can review rejected API candidates. Live OpenAI testing reached the API but was blocked by platform quota/billing. OpenRouter returned images, but the tested candidates were rejected by the validator because they changed detail or failed tileability. For this reason the API path is not the main acceptance path.
Strengths:
- fully local algorithmic path;
- deterministic command-line runs with seed control;
- automatic validation on border mismatch, gradient mismatch, seam artifacts, low-frequency color seams, source distance, detail preservation, and structured boundary metrics;
- preserves the original material better than tested image-generation API outputs;
- writes visual 3x3 previews for human inspection;
- optional PBR generation from the final selected seamless texture.
Weaknesses:
- structured geometry is still heuristic, not a full board/plank synthesis engine;
- some accepted outputs still show soft blend zones when the source image has difficult lighting or incomplete repeat structure;
- diagonal parquet/herringbone textures need a stronger 2D phase-aware validator;
- PBR maps are derived from a single albedo image and should be treated as generated approximations, not scanned ground truth;
- API branches depend on external services, quota, and model behavior.
PBR generation is enabled with --pbr. It uses the bundled DeepBump ONNX models to infer normal maps, then derives height, curvature, roughness, and ambient occlusion maps from the selected seamless albedo.
For each material:
pbr/albedo.pngpbr/normal.pngpbr/height.pngpbr/curvature.pngpbr/roughness.pngpbr/ambient_occlusion.pngpbr/<map>_tile_3x3.png
Example PBR previews:
| Material | Normal 3x3 | Height 3x3 | Roughness 3x3 |
|---|---|---|---|
84953235_01 |
![]() |
![]() |
![]() |
85346059 |
![]() |
![]() |
![]() |
89436101_02 |
![]() |
![]() |
![]() |
89438339 |
![]() |
![]() |
![]() |
DeepBump is GPL-3.0 licensed. The bundled runtime license is kept at seamlessfix/deepbump_backend/LICENSE.DeepBump-GPL-3.0.
seamlessfix/
cli.py Command-line interface
generator.py Local algorithmic candidate generation and selection
metrics.py Tileability validator and candidate ranking
delight.py Conservative de-lighting / albedo approximation
pbr.py DeepBump-backed PBR map generation
openai_branch.py Optional OpenAI repair branch
openrouter_branch.py Optional OpenRouter repair branch
api_judge.py Optional multimodal judge for API candidates
deepbump_backend/ Bundled DeepBump runtime and ONNX models
tests/ Regression and integration tests
Test_Image/ Acceptance input images
results/ Final generated textures, previews, metrics, and PBR maps
Create and activate a virtual environment, then install dependencies:
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txtThe project was verified with Python 3.14 on Windows.
Validate existing images:
.\.venv\Scripts\python.exe -m seamlessfix validate --input Test_Image --output results\validationGenerate seamless textures only:
.\.venv\Scripts\python.exe -m seamlessfix generate --input Test_Image --output results --method algorithm --preview 3 --max-candidates 8 --seed 7Generate seamless textures and PBR maps:
.\.venv\Scripts\python.exe -m seamlessfix generate --input Test_Image --output results --method algorithm --preview 3 --max-candidates 8 --seed 7 --pbrOptional API branches:
.\.venv\Scripts\python.exe -m seamlessfix generate --input Test_Image --output results --method openai --api-judge
.\.venv\Scripts\python.exe -m seamlessfix generate --input Test_Image --output results --method openrouter --api-judgeAPI generation receives the full selected algorithmic texture as a reference. By default --api-edit-scope full allows the API model to redraw the whole texture if that produces a better seamless material. Use --api-edit-scope border for the older conservative behavior where OpenAI edits only tile-boundary bands and OpenRouter is prompted to preserve the interior as much as possible.
Final output selection is controlled by --output-selection:
validated- default behavior. API output is used only if it passes the validator or API judge;algorithm- always buildtile_3x3.pngand PBR maps from the algorithmic candidate;api- force the best generated API image fortile_3x3.pngand PBR maps, even if the validator rejected it.
Force an OpenRouter result as the final texture:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method openrouter --preview 3 --output-selection api --pbrConservative API edge repair:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method openrouter --preview 3 --api-edit-scope border --output-selection validatedThe forced API texture is also saved as seamless_openrouter_forced.png or seamless_openai_forced.png.
API keys are loaded from .env, which is ignored by git. Use .env.example as the template.
General flags:
| Flag | Values / default | What it does | When to use |
|---|---|---|---|
--input |
required | Path to one image file or a directory with images. Supported extensions include PNG, JPG, WebP, BMP, TIFF. | Always required for validate and generate. |
--output |
required | Directory where result folders and reports are written. | Usually results. |
--preview |
default 3 |
Repeat count for the tiled preview. 3 writes tile_3x3.png. |
Keep 3 for the assignment report. |
--seed |
default 7 |
Seed for random crop candidates. | Use the same seed for reproducible algorithmic results. |
Generation method flags:
| Flag | Values / default | What it does | Notes |
|---|---|---|---|
--method |
auto, algorithm, openai, openrouter; default auto |
Chooses which generation branches run. | algorithm is local and reliable. openai and openrouter still run the algorithm first as a fallback/reference. |
--max-candidates |
default 8 |
Controls random algorithmic crop candidates. Deterministic candidates are always included. | Higher values may improve search but cost more time. |
--output-selection / --select-output |
validated, algorithm, api; default validated |
Chooses what image becomes final tile_3x3.png and PBR source. |
This is the key switch when API output looks better than the validator thinks. |
--output-selection behavior:
| Value | Final output source | Behavior |
|---|---|---|
validated |
Best accepted API result if accepted, otherwise algorithm | Default safe mode. API output must pass the validator or API judge. |
algorithm |
Algorithmic candidate | Strictly ignores API output for final tile_3x3.png and PBR, even if API succeeds. |
api |
Best generated API image | Forces API output even when rejected by the validator. If the API produced no decodable image, the command fails instead of silently falling back. |
API control flags:
| Flag | Values / default | What it does | Notes |
|---|---|---|---|
--api-edit-scope |
full, border; default full |
Controls how much freedom the API prompt/edit has. | full lets the model redraw the whole texture. border keeps the older conservative seam-repair behavior. |
--api-judge |
off by default | Lets an OpenAI vision model approve rejected API candidates. | Costs an extra OpenAI API call and needs OpenAI quota. |
--api-judge-model |
env/default | Model used by the visual judge. | Optional. |
--api-judge-threshold |
env/default | Minimum judge confidence for override. | Higher is stricter. |
OpenAI-specific flags:
| Flag | Default | What it does |
|---|---|---|
--openai-model |
gpt-image-2 |
Image edit model. |
--openai-analysis-model |
gpt-5.4-mini |
Planning model that analyzes the source and current tiled preview before image edit. |
--openai-variants |
2 |
Number of image edit variants to request, clamped to 1..4. |
OpenRouter-specific flags:
| Flag | Default | What it does |
|---|---|---|
--openrouter-model |
env OPENROUTER_MODEL / sourceful/riverflow-v2.5-pro:free |
OpenRouter image model. Can also use a comma-separated model list through OPENROUTER_MODELS. |
--openrouter-variants |
1 |
Number of variants per selected model, clamped to 1..4. |
--openrouter-image-size |
1K |
Value passed to OpenRouter image_config.image_size. |
--openrouter-reference-max-dim |
1024 |
Max dimension of the reference image sent to OpenRouter. |
--openrouter-timeout |
180 |
Seconds to wait for each OpenRouter request. |
PBR flags:
| Flag | Values / default | What it does |
|---|---|---|
--pbr |
off by default | Generates pbr/albedo, normal, height, curvature, roughness, ambient_occlusion, plus tiled previews. |
--pbr-overlap |
SMALL, MEDIUM, LARGE; default LARGE |
DeepBump color-to-normal overlap. Larger overlap is slower but smoother. |
--pbr-curvature-blur |
SMALLEST..LARGEST; default MEDIUM |
Blur radius for DeepBump normals-to-curvature. |
--pbr-verbose |
off by default | Prints DeepBump tile inference progress. |
Local algorithm only:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method algorithm --preview 3 --pbrTry OpenRouter, but keep the algorithm unless API passes validation:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method openrouter --preview 3 --output-selection validated --pbrForce OpenRouter output even if the validator rejects it:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method openrouter --preview 3 --api-edit-scope full --output-selection api --pbrUse conservative API seam repair:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method openrouter --preview 3 --api-edit-scope border --output-selection validatedForce OpenAI output:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image\84953235_01.png" --output results --method openai --preview 3 --api-edit-scope full --output-selection api --pbrRun on a whole folder:
.\.venv\Scripts\python.exe -m seamlessfix generate --input "E:\SeamlessFix\Test_Image" --output results --method algorithm --preview 3 --pbrFinal test run:
.\.venv\Scripts\python.exe -m pytest -q
34 passed in 166.62s
Final generation run:
.\.venv\Scripts\python.exe -m seamlessfix generate --input Test_Image --output results --method algorithm --preview 3 --max-candidates 8 --seed 7 --pbr
ALG 84953235_01.png: delight-0.55-best-tonal-crop, pass=True, border=0.0
PBR 84953235_01.png: generated 6 maps in results\84953235_01\pbr
ALG 85346059.png: edge-cut, pass=True, border=0.1062
PBR 85346059.png: generated 6 maps in results\85346059\pbr
ALG 89436101_02.png: delight-0.75-structured-grid-crop-narrow, pass=True, border=0.0
PBR 89436101_02.png: generated 6 maps in results\89436101_02\pbr
ALG 89438339.png: edge-equalized-narrow-detail, pass=True, border=1.5677
PBR 89438339.png: generated 6 maps in results\89438339\pbr
The final result set is intentionally kept in the repository because the task asks to include output seamless textures, 3x3 previews, and PBR maps. Diagnostic experiments and API smoke outputs are not needed for the report and should not be committed.



















