Skip to content

feat: redesign phase 4 — Segment Anything first slices, selection bar, rest-of-image slice - #29

Merged
provos merged 3 commits into
mainfrom
feat/redesign-segment
Sep 26, 2026
Merged

provos merged 3 commits into
mainfrom
feat/redesign-segment

Conversation

@provos

@provos provos commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Phase 4 of the UI redesign (docs/redesign/HANDOFF.md, §12 step 4): making slices is now Segment Anything first, and cutting objects no longer leaves holes.

Slices panel (SegmentPanel, replaces the Segmentation tab)

  • Segment objects:
    • Three numbered sub-steps (click → refine → make a slice) that track progress, and a selection summary.
    • New slice, which is primary once a selection exists; with no selection it adds an empty slice as before.
    • Add to selected and Remove from selected.
  • Split by depth, now secondary and collapsible:
    • It's open until the first slices exist.
    • It holds the slice count (moved here from Configuration), the band thresholds, Split image and Balance.
  • Selected slice: a depth slider and field (clamped to 0–255), the ground toggle ("Make ground plane" / "Ground plane") and Fit ground.

SelectionBar

  • It floats under the image while a selection exists, offering Segment N points (multi-point), Add to image_slice_N, Remove from image_slice_N and New slice.
  • The stage keeps that strip clear, so the bar never covers the image.

Backend: a "rest of image" slice, kept in sync. This was the product decision when the handoff was reviewed.

  • Cutting the first object from a project with no slices also creates a farthest rest of image slice: the input image, minus the object.
  • It's updated incrementally, never recomputed, so inpainting it is never undone:
    • Later cuts, and masks added to an object, subtract from it.
    • Deleting an object, or removing mask pixels from one, restores only pixels no other slice covers.
    • Deleting the rest slice itself just removes it.
  • Projects split by depth first behave exactly as before, with no rest slice.
  • It's persisted as rest_slice in the project JSON, exposed as SliceView.isRest, and shown with a "rest of image" badge in the layer panel.

Tests

  • pytest: 11 new tests covering complementary alphas on the first cut, subtraction with a version bump, restoring only uncovered pixels while preserving a simulated inpainted pixel, add/remove sync, no rest slice after a depth split, JSON round trip, and API isRest plus the log line.
  • vitest: new SegmentPanel and SelectionBar tests, and the rest badge.
  • e2e: the driver expands Split by depth before using it.
  • Results: pytest 456 passed, vitest 263 passed, svelte-check 0 errors / 0 warnings, e2e 46/46.
  • Real-model check: on the lighthouse photo, a SAM click followed by New slice created the object slice plus image_slice_0 · rest of image, and the Composite view has no holes.

Next: phase 5, the Inpaint, Ground and Preview panels.

🤖 Generated with Claude Code

provos and others added 2 commits September 26, 2026 00:13
…, rest-of-image slice

HANDOFF.md §12 step 4:

- SegmentPanel replaces the Segmentation tab: "Segment objects" with
  three sub-steps and a selection summary, New slice / Add to selected /
  Remove from selected; a collapsible "Split by depth" (slice count,
  bands, Split image, Balance), open until the first slices exist; and
  "Selected slice" with a depth slider and field, the ground toggle and
  Fit ground. The slice count moves here from Configuration.
- SelectionBar floats under the image while a selection exists: Segment
  N points, Add to / Remove from the selected slice, New slice. The stage
  keeps its strip clear so the bar never covers the image.
- Backend: the first object slice cut from a project with no slices also
  creates a farthest "rest of image" slice (input minus the object). It is
  kept in sync incrementally - later cuts and added masks subtract from
  it; deleting an object or removing mask pixels restores only pixels no
  other slice covers - so inpainting the rest slice is never undone.
  Persisted as `rest_slice`; SliceView.isRest; a "rest of image" badge.
- e2e: the driver opens Split by depth before using it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 26, 2026 07:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Rest-slice syncing on mask removal restores alpha as fully opaque even for partial-alpha removals (e.g., feathered masks), which can violate the intended rest/object alpha relationship and produce incorrect composites.

Review effort: Lite
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

Implements phase 4 of the UI redesign by making “Segment Anything first” slice creation the primary workflow, introducing a floating SelectionBar, and adding backend support for a persisted “rest of image” slice that stays incrementally synced with slice edits.

Changes:

  • Frontend: Replace SegmentationTab with SegmentPanel and add SelectionBar for SAM selection actions.
  • Backend/API: Add ImageSlice.is_rest, persist it in project JSON (rest_slice), expose it as SliceView.isRest, and log first-cut creation.
  • Tests/E2E: Add backend + frontend tests for rest-slice behavior and update the Playwright driver for the collapsible “Split by depth” section.
File Description
parallax_maker/​slice_editing_services.py Creates/syncs the “rest of image” slice during create/delete/add-mask/remove-mask operations.
parallax_maker/​slice.py Adds is_rest flag to ImageSlice.
parallax_maker/​controller.py Persists/restores the rest-slice index in project JSON (rest_slice).
parallax_maker/​api/​schemas.py Extends SliceView with is_rest (serialized as isRest).
parallax_maker/​api/​projects.py Projects ImageSlice.is_rest into API slice views.
parallax_maker/​api/​slice_editing.py Logs rest-slice creation using rest_slice_filename.
parallax_maker/​e2e_server.py Exposes rest_slice index via the e2e oracle state.
parallax_maker/​test_slice_editing_services.py Adds unit tests for rest-slice creation and sync semantics.
parallax_maker/​test_api_slice_editing.py Adds API test verifying isRest and the log line on first cut.
parallax_maker/​test_controller.py Adds JSON round-trip persistence tests for the rest-slice flag.
frontend/​src/​lib/​components/​shell/​Inspector.svelte Swaps SegmentationTab for the new SegmentPanel.
frontend/​src/​lib/​components/​segmentation/​SegmentPanel.svelte New “Slices panel” UI: SAM-first steps, collapsible “Split by depth”, selected slice controls.
frontend/​src/​lib/​components/​segmentation/​SegmentPanel.test.ts New unit tests for SegmentPanel behavior and API calls.
frontend/​src/​lib/​components/​segmentation/​SegmentationTab.svelte Removed legacy Segmentation inspector tab UI.
frontend/​src/​lib/​components/​segmentation/​SegmentationTab.test.ts Removed tests for the legacy Segmentation tab.
frontend/​src/​lib/​components/​canvas/​SelectionBar.svelte New floating toolbar for queued points / mask actions while a selection exists.
frontend/​src/​lib/​components/​canvas/​SelectionBar.test.ts New unit tests for SelectionBar rendering and actions.
frontend/​src/​lib/​components/​canvas/​CanvasArea.svelte Renders SelectionBar and reserves space so it doesn’t cover the image.
frontend/​src/​lib/​components/​layers/​LayerPanel.svelte Displays a “rest of image” badge for the rest slice.
frontend/​src/​lib/​components/​layers/​LayerPanel.test.ts Tests that only the rest slice shows the new badge.
frontend/​src/​lib/​components/​config/​ConfigurationTab.svelte Removes slice-count slider (moved under “Split by depth” in SegmentPanel).
frontend/​src/​lib/​api/​schema.json Adds isRest to the frontend API schema.
frontend/​src/​lib/​api/​generated.ts Adds generated isRest typing to SliceView.
e2e/​drivers/​svelte.ts Ensures “Split by depth” is expanded before clicking split/balance controls.
docs/​svelte-migration/​ARCHITECTURE.md Documents new rest-slice behavior in the HTTP contract.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +492 to +504
if rest_slice is not None:
source = self._require_image(state)
# The area the mask actually removed coverage from; restored into
# the rest slice only where nothing else still covers it.
exposed = (mask > 0) & (before_alpha > 0)
other_slices = [
s
for s in state.image_slices
if s is not object_slice and s is not rest_slice
]
self._restore_uncovered_into_rest(
rest_slice, np.array(source.convert("RGB")), exposed, other_slices
)
Comment thread docs/svelte-migration/ARCHITECTURE.md Outdated
| `POST /api/v1/projects/{id}/slices/{index}/add-mask` | `{}` | `200 ProjectView & {changed: true}` (sync); `add_mask_to_slice`. `index` must equal the project's current `selectedSlice` (`400 invalid_request` otherwise, since the service itself has no index parameter - it always operates on the selection, exactly like Dash). |
| `POST /api/v1/projects/{id}/slices/{index}/remove-mask` | `{}` | Same shape and `index`-must-match-selection rule as add-mask, calling `remove_mask_from_slice`. |
| `POST /api/v1/projects/{id}/slices/create` | `{}` | `200 ProjectView & {changed: true}` (sync); `create_slice` - a new slice from the current mask (or an empty transparent one with none), appended, selected, and saved (JSON + the new slice's own image). When Segment Anything cuts the *first* object slice out of a project with no slices yet, a farthest "rest of image" slice is also created (`SliceView.isRest`): the input image with alpha `255 - object.alpha`, depth 0, logged as `"Created image_slice_N with the rest of the image"`. The selection stays on the object slice even though the rest slice's depth-0 insertion can shift its index. If a rest slice already exists, its alpha is instead reduced by the new object's footprint (`rest.alpha = min(rest.alpha, 255 - object.alpha)`), saved as a new (undoable) version. A project split by depth first (slices exist, none is the rest) behaves exactly as before - no rest slice is added. An empty create (no mask) never creates or changes the rest slice. Persisted as `rest_slice` in the project JSON, the same way `ground_plane_slice` is. |
| `DELETE /api/v1/projects/{id}/slices/{index}` | – | `200 ProjectView & {changed: true}` (sync); `delete_slice` - out-of-range `index` is `400 invalid_request`; the selection is always cleared. When a rest slice exists and an *object* slice is deleted, pixels the deleted slice covered that no remaining (non-rest) slice covers either are restored into the rest slice from the input image (RGB + alpha 255); every other rest pixel - including any inpainting done directly on it - is left alone, and the rest slice is saved as a new version. Deleting the rest slice itself just clears the flag; none is recreated automatically. |
Comment on lines +716 to +718
def test_delete_slice_of_the_rest_slice_itself_just_clears_the_flag(
tmp_path: Path,
) -> None:
…slice

The rest slice's alpha now rises to 255 - the object's remaining alpha,
only where the edit lowered it, and RGB is refilled only where the rest
was fully transparent, so partial removals keep rest.alpha <= 255 -
object.alpha and visible rest pixels stay as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@provos
provos merged commit ad86731 into main Sep 26, 2026
1 check passed
@provos
provos deleted the feat/redesign-segment branch September 26, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants