Feature: Adaptive size selection based on local detail - #3
Conversation
Use Sobel gradient magnitude to bias circle size selection: small circles near edges/detail, large circles in smooth areas. Mutation perturbation is also scaled by local gradient for fine-tuning near edges. - New GradientMap class: computes and normalizes Sobel gradient per grid cell - Circle.randomize() uses gradient-weighted size selection when enabled - Circle.mutateShape() scales position perturbation and uses gradient-biased size selection near edges - GradientMap wired into Worker and Model (computed once from target image) - USE_ADAPTIVE_SIZE feature flag in AppConstants (default true) - Comprehensive tests verifying gradient correctness and adaptive vs uniform - Visual comparison images in test-results/proposal3/ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds “Proposal 3” adaptive circle sizing driven by a precomputed Sobel gradient map of the target image, aiming to use smaller circles near detailed/edge regions and larger circles in smooth areas.
Changes:
- Introduces
GradientMapto compute a normalized, coarse-grid Sobel magnitude map and provide gradient-weighted size selection + mutation scaling. - Wires adaptive sizing into
Modelinitialization andWorker/Circleshape randomization & mutation under a newUSE_ADAPTIVE_SIZEflag. - Adds an end-to-end JUnit test suite and commits visual benchmark artifacts under
test-results/proposal3/.
Reviewed changes
Copilot reviewed 7 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test-results/proposal3/photo_detail_uniform.png | Adds visual benchmark artifact for uniform sizing (photo_detail). |
| test-results/proposal3/photo_detail_target.png | Adds target image artifact for photo_detail benchmark. |
| test-results/proposal3/photo_detail_gradient.png | Adds gradient-map visualization artifact for photo_detail. |
| test-results/proposal3/photo_detail_diff.png | Adds diff heatmap artifact (uniform vs adaptive) for photo_detail. |
| test-results/proposal3/photo_detail_adaptive.png | Adds visual benchmark artifact for adaptive sizing (photo_detail). |
| test-results/proposal3/nature_uniform.png | Adds visual benchmark artifact for uniform sizing (nature). |
| test-results/proposal3/nature_target.png | Adds target image artifact for nature benchmark. |
| test-results/proposal3/nature_gradient.png | Adds gradient-map visualization artifact for nature. |
| test-results/proposal3/nature_diff.png | Adds diff heatmap artifact (uniform vs adaptive) for nature. |
| test-results/proposal3/nature_adaptive.png | Adds visual benchmark artifact for adaptive sizing (nature). |
| test-results/proposal3/edges_uniform.png | Adds visual benchmark artifact for uniform sizing (edges). |
| test-results/proposal3/edges_target.png | Adds target image artifact for edges benchmark. |
| test-results/proposal3/edges_gradient.png | Adds gradient-map visualization artifact for edges. |
| test-results/proposal3/edges_diff.png | Adds diff heatmap artifact (uniform vs adaptive) for edges. |
| test-results/proposal3/edges_adaptive.png | Adds visual benchmark artifact for adaptive sizing (edges). |
| src/test/java/com/bobrust/generator/AdaptiveSizeSelectionTest.java | Adds correctness + end-to-end regression tests and generates benchmark images. |
| src/main/resources/version | Bumps app version to 0.6.80. |
| src/main/java/com/bobrust/util/data/AppConstants.java | Adds USE_ADAPTIVE_SIZE feature flag (default true). |
| src/main/java/com/bobrust/generator/Worker.java | Stores and exposes a GradientMap for use during shape generation/mutation. |
| src/main/java/com/bobrust/generator/Model.java | Computes gradient map once at initialization and wires it into Worker. |
| src/main/java/com/bobrust/generator/GradientMap.java | New Sobel-based gradient map with gradient-biased size selection and mutation scaling. |
| src/main/java/com/bobrust/generator/Circle.java | Uses gradient map to bias size selection and scale position mutation near edges. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| float[] weights = new float[numSizes]; | ||
| float totalWeight = 0; | ||
| for (int i = 0; i < numSizes; i++) { | ||
| float sizeNorm = (float) i / (numSizes - 1); // 0=smallest, 1=largest | ||
| // High gradient -> prefer small (low sizeNorm), low gradient -> prefer large | ||
| weights[i] = (float) Math.exp(-4.0 * Math.abs(sizeNorm - (1.0 - gradient))); | ||
| totalWeight += weights[i]; | ||
| } | ||
| // Weighted random selection | ||
| float r = rnd.nextFloat() * totalWeight; | ||
| float cumulative = 0; | ||
| for (int i = 0; i < numSizes; i++) { | ||
| cumulative += weights[i]; |
There was a problem hiding this comment.
selectSizeIndex allocates a new float[] weights on every call. This method is called frequently (e.g., from Circle.randomize() / mutateShape()), so the per-call allocation will create substantial GC pressure and slow down generation. Consider rewriting selection to avoid allocations (e.g., two-pass compute total then select without storing the array, or reuse a preallocated array via ThreadLocal).
| float[] weights = newfloat[numSizes]; | |
| floattotalWeight = 0; | |
| for (inti = 0; i < numSizes; i++) { | |
| floatsizeNorm = (float) i / (numSizes - 1); // 0=smallest, 1=largest | |
| // High gradient -> prefer small (low sizeNorm), low gradient -> prefer large | |
| weights[i] = (float) Math.exp(-4.0 * Math.abs(sizeNorm - (1.0 - gradient))); | |
| totalWeight += weights[i]; | |
| } | |
| // Weighted random selection | |
| floatr = rnd.nextFloat() * totalWeight; | |
| floatcumulative= 0; | |
| for (inti = 0;i< numSizes; i++) { | |
| cumulative += weights[i]; | |
| floattotalWeight = 0; | |
| floattargetSizeNorm = 1.0f - gradient; | |
| for (inti = 0; i < numSizes; i++) { | |
| floatsizeNorm = (float) i / (numSizes - 1); // 0=smallest, 1=largest | |
| // High gradient -> prefer small (low sizeNorm), low gradient -> prefer large | |
| totalWeight += (float) Math.exp(-4.0 * Math.abs(sizeNorm - targetSizeNorm)); | |
| } | |
| // Weighted random selection without per-call array allocation | |
| floatr = rnd.nextFloat() * totalWeight; | |
| floatcumulative = 0; | |
| for (inti= 0;i < numSizes; i++) { | |
| floatsizeNorm = (float)i/ (numSizes - 1); // 0=smallest, 1=largest | |
| cumulative += (float) Math.exp(-4.0 * Math.abs(sizeNorm - targetSizeNorm)); |
| private static final int ALPHA = 128; | ||
| private static final int BACKGROUND = 0xFFFFFFFF; | ||
| private static final File OUTPUT_DIR = new File("test-results/proposal3"); | ||
| @BeforeAll | ||
| static void setup() { | ||
| OUTPUT_DIR.mkdirs(); | ||
| } |
There was a problem hiding this comment.
This test writes generated images into test-results/proposal3, which is not ignored by git and is also committed in this PR. Running the test will overwrite tracked files and/or leave a dirty working tree. Consider writing outputs under build/ (as ErrorGuidedPlacementTest does) or gating the visual-output portion behind a system property / @Disabled so regular test runs don’t mutate the repository.
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Method; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; |
There was a problem hiding this comment.
Unused import: java.util.Arrays is imported but not used in this test class. Removing it will keep the test clean and avoid IDE/compiler warnings.
| import java.util.Arrays; |
Uh oh!
There was an error while loading. Please reload this page.
Summary
GradientMapclass computes Sobel gradient magnitude of the target image, downsampled to a 32x32 grid, normalized to [0,1]. High values = edges/detail, low values = smooth regions.Circle.randomize()now uses gradient-weighted size selection whenUSE_ADAPTIVE_SIZEis enabled: small circles near edges, large circles in smooth areas. Usesexp(-4 * |sizeNorm - (1 - gradient)|)weighting.Circle.mutateShape()scales position perturbation by gradient (fine-tune near edges, explore broadly in smooth areas) and uses gradient-biased size selection during mutation.WorkerandModel— gradient map computed once from target image at initialization.USE_ADAPTIVE_SIZEfeature flag inAppConstants(defaulttrue).Test Results
Visual comparisons for 200 shapes on three test images are committed to
test-results/proposal3/:Test Plan
./gradlew clean buildpassesImplements Proposal 3 from PROPOSALS.md.