Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
215 commits
Select commit Hold shift + click to select a range
a581368
chore(segmentation): baseline for segment model work
PaulHax Sep 1, 2026
0536d73
feat(segmentation): add segment model types and helpers
PaulHax Sep 1, 2026
208a3a4
feat(segmentation): add segmentation store
PaulHax Sep 1, 2026
93c19e6
refactor(segmentation): move segment identity to segmentation store
PaulHax Sep 1, 2026
f8229d7
refactor(segmentation): inject shared segment registry into annotatio…
PaulHax Sep 1, 2026
8f6f34a
feat(segmentation): active segment target and cross-image intent
PaulHax Sep 1, 2026
17e0880
refactor(segmentation): retarget edit paths to stable segment identity
PaulHax Sep 1, 2026
f155bcd
feat(segmentation): serialize segmentation schema, manifest 7.0.0
PaulHax Sep 1, 2026
157be21
feat(segmentation): migrate legacy segment group state files
PaulHax Sep 1, 2026
d880a7e
refactor(segmentation): remove segment group identity surface
PaulHax Sep 1, 2026
211e898
test(segmentation): cover rasterize target and legacy labelmap migration
PaulHax Sep 1, 2026
211464e
test(segmentation): cover segment identity under edits and across images
PaulHax Sep 1, 2026
8e07e13
fix(segmentation): keep cross-image landing map when reselecting a se…
PaulHax Sep 1, 2026
8feaecd
fix(segmentation): rasterize into a default segment when an image has…
PaulHax Sep 1, 2026
74af43d
fix(segmentation): stop all-segments processes creating a segment
PaulHax Sep 1, 2026
71faf0a
fix(segmentation): declare segmentation manifest refs for the save ba…
PaulHax Sep 1, 2026
e3d1265
fix(segmentation): allow rasterizing a polygon that carries no segment
PaulHax Sep 1, 2026
bee58cf
fix(segmentation): serialize props for segments no tool references yet
PaulHax Sep 1, 2026
6e08bed
fix(segmentation): scope the active segment to the viewed image
PaulHax Sep 1, 2026
b0522f7
refactor(segmentation): drop dead parameters and duplicated helpers
PaulHax Sep 1, 2026
6aaf3b8
perf(segmentation): hoist stroke invariants out of the paint predicate
PaulHax Sep 1, 2026
0346d17
fix(segmentation): stop paint tool activation creating a segment
PaulHax Sep 1, 2026
e482b4b
refactor(segmentation): give processes their resolved target
PaulHax Sep 1, 2026
05a237f
fix(segmentation): route rasterize through the single edit entry point
PaulHax Sep 1, 2026
f757a57
fix(segmentation): scope the paint widget labelmap to its own image
PaulHax Sep 1, 2026
9a845b4
fix(segmentation): unbind segments whose artifact is pruned on save
PaulHax Sep 1, 2026
a4df54a
docs(segmentation): note why the decode color cursor is store-scoped
PaulHax Sep 1, 2026
870ec15
fix(segmentation): record the segment a polygon actually rasterized into
PaulHax Sep 1, 2026
0390c61
fix(segmentation): parse functional CSS colors instead of blackening …
PaulHax Sep 1, 2026
9224e61
fix(segmentation): make migrated segment ids unique and keep the acti…
PaulHax Sep 1, 2026
b43c50b
fix(segmentation): scope the orphan backstop to declared artifacts
PaulHax Sep 1, 2026
927de81
feat(segmentation): put display state on the segment model
PaulHax Sep 1, 2026
d8b3592
feat(segmentation): define the segment voxel accessor contract
PaulHax Sep 1, 2026
d6eb2bc
refactor(segmentation): route labelmap consumers through the voxel ac…
PaulHax Sep 1, 2026
068081b
test(segmentation): audit labelmap access at the accessor seam
PaulHax Sep 1, 2026
b1f0e7e
feat(segmentation): flat image-scoped segment list
PaulHax Sep 1, 2026
1b4c95c
feat(segmentation): store one bounded mask per segment
PaulHax Sep 2, 2026
61aa900
refactor(segmentation): delete the segment group layer
PaulHax Sep 2, 2026
6a1a138
fix(segmentation): keep segment storage in step with its bindings
PaulHax Sep 2, 2026
d10e0d3
fix(segmentation): apply the segmentation's fill opacity to its segments
PaulHax Sep 2, 2026
646ee96
fix(tools): move annotations onto a renamed template's id
PaulHax Sep 2, 2026
961ad88
fix(polygon): rasterize into the template the polygon names
PaulHax Sep 2, 2026
f122c1e
fix(state-file): migrate an unused legacy label into a template
PaulHax Sep 2, 2026
718bb68
test(e2e): expect the current manifest version on re-save
PaulHax Sep 2, 2026
841989b
fix(fill-holes): reject a slice the selected segment does not reach
PaulHax Sep 2, 2026
1d41603
fix(segmentation): stop named-color lookup reaching Object.prototype
PaulHax Sep 2, 2026
a027d2c
style: trim development storytelling from comments
PaulHax Sep 2, 2026
523257e
perf(processes): stop snapshotting the process output twice
PaulHax Sep 2, 2026
7d5b40f
fix(segment-registry): declare a template instead of minting a segment
PaulHax Sep 2, 2026
b34ab68
fix(state-file): keep the name a manifest gave a restored labelmap
PaulHax Sep 2, 2026
1689a7e
test(e2e): wait for a segment chip's name, not just the chip
PaulHax Sep 2, 2026
e526387
fix(segmentation): let a segment fill reach its own opacity
PaulHax Sep 2, 2026
67463f7
fix(cine): refuse to start a segmentation on a clip
PaulHax Sep 2, 2026
9e68d30
feat(segmentation): add display controls for the viewed image
PaulHax Sep 2, 2026
bfaaf11
feat(segmentation): name imported segments after the file
PaulHax Sep 2, 2026
242b36a
refactor(tools): drop unreachable members from the tool registry
PaulHax Sep 2, 2026
c56507d
refactor(segmentation): share the mask buffer, offset, and descriptor…
PaulHax Sep 2, 2026
a13af9a
refactor(segmentation): drop the labelmap handles and descriptors not…
PaulHax Sep 2, 2026
59e2188
fix(processes): refuse a result that is the storage buffer it was given
PaulHax Sep 2, 2026
1054059
feat(segmentation): let a locked segment keep the voxels written over it
PaulHax Sep 2, 2026
d545546
refactor(processes): run every process on one segment's own mask
PaulHax Sep 2, 2026
d88fd8c
feat(segmentation): save overlapping segments as separate files
PaulHax Sep 3, 2026
a506839
perf(segmentation): sweep the shared box a row at a time
PaulHax Sep 3, 2026
de9ae29
fix(processes): name the lock when every segment is locked
PaulHax Sep 3, 2026
87b576b
fix(polygon): refuse to rasterize into a locked segment
PaulHax Sep 3, 2026
95dac81
feat(processing): say that a staged segmentation is flattened
PaulHax Sep 3, 2026
aa6491f
fix(processes): stop a process at the voxels another segment holds
PaulHax Sep 3, 2026
1319cff
docs(processes): say what Fill Holes actually fills
PaulHax Sep 3, 2026
895f940
refactor(segmentation): reuse the shared extent and offset helpers
PaulHax Sep 3, 2026
b4e8bf7
perf(processes): clip the neighbouring masks to the box a run walks
PaulHax Sep 3, 2026
79a0c72
perf(processes): let a process say a segment has nothing to do
PaulHax Sep 3, 2026
2f9193f
perf(processing): refresh the flatten notice off a signal
PaulHax Sep 3, 2026
7342002
refactor(segmentation): make a write path name the gesture it is
PaulHax Sep 3, 2026
2b93605
fix(processes): let an all-segments preview outlive the selection
PaulHax Sep 3, 2026
2812c29
refactor(segmentation): sweep the shared box with plain j and k loops
PaulHax Sep 3, 2026
21365c4
refactor(processes): collapse a nested guard and an unreachable fallback
PaulHax Sep 3, 2026
923c592
refactor(tools): keep one name for the tool registry type
PaulHax Sep 3, 2026
f0247f2
refactor(io): read imported labelmap scalars through the shared accessor
PaulHax Sep 3, 2026
d6bf1ad
refactor(tools): declare a label through the one template-pool write
PaulHax Sep 3, 2026
1064e85
refactor(io): name the save archive in one place
PaulHax Sep 3, 2026
9c64204
test(segmentation): collapse the duplicated composite and seeding hel…
PaulHax Sep 3, 2026
e1639d2
refactor(segmentation): bind the display sliders to the viewed segmen…
PaulHax Sep 3, 2026
9ebf74b
feat(segmentation): say on the lock what it does to painting over a s…
PaulHax Sep 3, 2026
3e6fac8
test(segmentation): cover lock, overlap and layered save end to end
PaulHax Sep 3, 2026
2ab9527
test(segmentation): update scroll coverage for flat segment list
PaulHax Sep 4, 2026
1c0544c
fix(segmentation): harden segment model workflows
PaulHax Sep 4, 2026
a34522e
fix(segmentation): resolve label collisions on restore and pad stroke…
PaulHax Sep 5, 2026
fa82191
fix(processes): clamp smoothing padding to the parent image faces
PaulHax Sep 5, 2026
fa1c035
fix(tools): keep a scene's own templates when a session restores
PaulHax Sep 5, 2026
fc37c2d
fix(io): preserve the legacy fill opacity through the 7.0.0 migration
PaulHax Sep 5, 2026
6fe51eb
fix(processing): refresh the flatten notice after a voxel edit
PaulHax Sep 5, 2026
4ebf0b6
feat(tools): mint a segment when a delineation annotation is started
PaulHax Sep 6, 2026
12ef21f
feat(segmentation): add one registry for segment types
PaulHax Sep 6, 2026
45a22d8
refactor(segmentation): make a labelmap record reference a type
PaulHax Sep 6, 2026
0278668
refactor(tools): point shapes and pickers at the shared registry
PaulHax Sep 6, 2026
19af661
feat(config): configure segment types instead of per-tool labels
PaulHax Sep 6, 2026
71920a5
feat(io): carry segment types on the 7.0.0 manifest
PaulHax Sep 6, 2026
0da92f6
test(segmentation): move the suites onto global segment types
PaulHax Sep 6, 2026
0d48279
refactor(segmentation): name the registry entry a segment and its per…
PaulHax Sep 7, 2026
2d6ee69
feat(segmentation): stack the annotations sidebar and list segments a…
PaulHax Sep 7, 2026
2835b6c
feat(segmentation): open the segment editor from its color dot
PaulHax Sep 7, 2026
624eefa
feat(segmentation): fold the measurement lists into the segment list
PaulHax Sep 7, 2026
a5ee0b0
fix(segmentation): read pre-7.0 label configs into the segment list
PaulHax Sep 7, 2026
0cd910b
chore(hooks): add local quality checks
PaulHax Sep 7, 2026
fe09d47
chore(segmentation): drop the orphaned ColorDot component
PaulHax Sep 7, 2026
ff9b86c
test(state-file): share the scene-to-state-files serialize step
PaulHax Sep 7, 2026
d776c9a
test(paint): share the active-segment seat between the paint specs
PaulHax Sep 7, 2026
9d477ac
test(state-file): fold the two legacy-restore tests onto one setup
PaulHax Sep 7, 2026
e989de2
test(segmentation): share the extent contract and the e2e square
PaulHax Sep 7, 2026
d4774e5
test(segmentation): fold repeated setup in six more specs
PaulHax Sep 7, 2026
1b84c83
test(store): share the plain 4x4x4 parent image
PaulHax Sep 7, 2026
0a14c68
test: fold the last repeated setup in three specs
PaulHax Sep 7, 2026
23092df
test(state-file): share the segmentation snapshot
PaulHax Sep 7, 2026
2ab4af1
test: share the restore path and the legacy view config
PaulHax Sep 7, 2026
b9727fe
test(paint): pin the gaussian smooth output byte for byte
PaulHax Sep 7, 2026
7d6de7d
refactor(segmentation): reuse the shared helpers and drop repeat sweeps
PaulHax Sep 7, 2026
b07d8e3
refactor(tools): register segment references from the shared composable
PaulHax Sep 7, 2026
a64cc51
refactor(segmentation): name the wire order walk and the in-flight guard
PaulHax Sep 7, 2026
38b87d2
refactor(types): flatten the appearance resolver and the extent sweep
PaulHax Sep 7, 2026
777da27
refactor(polygon): group the slice plane and stop carrying a stale ex…
PaulHax Sep 7, 2026
8b2bdfa
refactor(paint): split the stroke value and the line walk out of pain…
PaulHax Sep 7, 2026
8fb84eb
refactor(paint): clip the sub-volume loops instead of testing per voxel
PaulHax Sep 7, 2026
fd9c5f8
refactor(segmentation): move the state-file half into its own module
PaulHax Sep 7, 2026
8008a22
refactor(segmentation): move voxel access into its own module
PaulHax Sep 7, 2026
4a089a7
refactor(tools): share the appearance lookup and the placement start
PaulHax Sep 7, 2026
c7222ee
perf(processing): stop rebuilding every mask extent to detect a change
PaulHax Sep 7, 2026
eac82dc
fix(config): say when a config color cannot be parsed
PaulHax Sep 7, 2026
77c0d96
refactor(ui): pass the item through the list slots instead of by index
PaulHax Sep 7, 2026
914d338
refactor(polygon): fold the process cancel into rasterizePolygon
PaulHax Sep 7, 2026
8fed473
perf(segments): group the shapes once instead of filtering per segment
PaulHax Sep 7, 2026
9c0c926
refactor(segments): name the offset constant and fold the icon pair i…
PaulHax Sep 7, 2026
6fe26fb
chore(lint): close the feature boundary gaps in the pure-layer lists
PaulHax Sep 7, 2026
2ee4ed7
perf(segmentation): keep an unchanged artifact's projection identity
PaulHax Sep 7, 2026
caa7a91
perf(segmentation): hold the projection identity when no artifact cha…
PaulHax Sep 7, 2026
5c09b16
fix(shortcuts): cycle the segment selection for every tool
PaulHax Sep 7, 2026
5632cfb
refactor(segmentation): let the legacy split hold its labelmap locally
PaulHax Sep 8, 2026
931bb99
refactor(segmentation): drop the stored label value from a mask
PaulHax Sep 8, 2026
accf1b3
refactor(segmentation): hold a mask's voxels on its own binding
PaulHax Sep 8, 2026
a257fdd
refactor(state-file): carry legacy group display only where it has no…
PaulHax Sep 8, 2026
e83070d
refactor(state-file): save a mask's labelmap on its own binding
PaulHax Sep 8, 2026
45e43a3
refactor(segmentation): name the renderer and probe locals after masks
PaulHax Sep 8, 2026
e598884
test(e2e): read a saved labelmap name off the mask that owns it
PaulHax Sep 8, 2026
70697c2
fix(segments): harden processing, persistence, and annotation workflows
PaulHax Sep 10, 2026
73d1a69
fix: honor segment deletion during scene restore
PaulHax Sep 10, 2026
95508d9
fix: settle restore when artifact sources stop loading
PaulHax Sep 10, 2026
e60d5cb
fix: cancel pending processing when targets become locked
PaulHax Sep 10, 2026
78c71c6
fix(image-stats): terminate completed histogram workers
PaulHax Sep 10, 2026
c40ec9c
fix(segments): expose accessible list actions and state
PaulHax Sep 10, 2026
e57bc3e
fix(segments): allow Escape to cancel the editor
PaulHax Sep 10, 2026
3753a1e
feat(annotations): restore flat measurement list
PaulHax Sep 11, 2026
89921b7
feat(annotations): refine sidebar controls
PaulHax Sep 11, 2026
561ee6c
feat(annotations): make segments collapsible
PaulHax Sep 11, 2026
b5952d8
fix: omit empty segment voxels from scalar probe
PaulHax Sep 11, 2026
9530e59
fix: reveal occupied segment slices with a highlight pulse
PaulHax Sep 11, 2026
b2ee6fc
fix: pad segment outlines with reusable render buffers
PaulHax Sep 11, 2026
1bd740e
feat(annotations): constrain segment list height
PaulHax Sep 14, 2026
01e5a1a
feat(data): show segmentation conversion progress
PaulHax Sep 14, 2026
af86a87
perf(paint): improve responsiveness and reuse imported segments
PaulHax Sep 14, 2026
ee3c9ca
perf(segmentation): speed up equivalent label grid imports
PaulHax Sep 14, 2026
c98d6d2
perf(segmentation): snapshot bounds before copying mask rows
PaulHax Sep 14, 2026
574f643
perf(segmentation): lazily mount segment tooltips
PaulHax Sep 14, 2026
5d54cc5
feat(segmentation): add segment shortcuts and responsive list controls
PaulHax Sep 15, 2026
b3f8a0c
feat(segmentation): add eyedropper mode and align selection with disp…
PaulHax Sep 15, 2026
61502d6
refactor(segmentation): isolate process inputs and coordinate previews
PaulHax Sep 15, 2026
0c07fc8
refactor(segmentation): narrow storage and interchange boundaries
PaulHax Sep 15, 2026
49cf24c
refactor(segmentation): colocate model editing and presentation
PaulHax Sep 15, 2026
1ae7950
refactor(segmentation): separate geometry and enforce dependency dire…
PaulHax Sep 15, 2026
13ae982
refactor(segmentation): simplify labelmap import and restore
PaulHax Sep 15, 2026
39bd42d
style(segments): tighten panel spacing and align list rows
PaulHax Sep 15, 2026
650eb6a
fix(paint): keep brush preview moving during size adjustment
PaulHax Sep 15, 2026
b2e6dc9
fix(segments): clarify overlap save and cover duplicate imports
PaulHax Sep 15, 2026
ca044f4
refactor: bind processing inputs directly to segmentations
PaulHax Sep 15, 2026
aab6d84
refactor: adopt segmentation naming and migrate configuration
PaulHax Sep 15, 2026
0e6f912
refactor: rename segmentation result instruction
PaulHax Sep 15, 2026
279ad1d
feat: choose labelmap bit depth from label count
PaulHax Sep 15, 2026
1164598
feat: pack processing labelmaps according to input multiplicity
PaulHax Sep 15, 2026
820a416
chore(deps): sync package-lock with package.json
claude Sep 19, 2026
2887476
test: fold new test clones and cut complexity regressions
claude Sep 19, 2026
1334de5
ci(checks): run the quality ratchets against the base branch
claude Sep 19, 2026
de729cf
fix(tools): skip restored annotations whose image did not load
claude Sep 19, 2026
bcf2064
fix(segmentation): refuse a labelmap split for a parent that was removed
claude Sep 19, 2026
a82e7c9
fix(segmentation): check the lock before minting a target mask
claude Sep 19, 2026
4dfff72
fix(resample): return a same-grid labelmap without reslicing
claude Sep 19, 2026
97ab9fc
docs(segmentation): stop promising render depth from registry order
claude Sep 19, 2026
09487d0
refactor(rendering): drop the inert per-segment depth offset
claude Sep 19, 2026
aba28db
perf(segmentation): index masks and names so import scales with label…
claude Sep 19, 2026
57b0354
perf(segmentation): plan overlap layers with an occupancy buffer
claude Sep 19, 2026
ee44b90
fix(processing): leave an empty segmentation unbound as a labelmap input
claude Sep 19, 2026
90e9698
fix(processing): start single-file packing from a segment the image h…
claude Sep 19, 2026
0ea7786
fix(processing): keep a segment's colour when a result colour is unsu…
claude Sep 19, 2026
187338d
docs(contract): describe labelmap packing and the accepted colour syntax
claude Sep 19, 2026
1e26e01
perf(processing): stop re-running the overlap sweep on every segment …
claude Sep 19, 2026
af5bde8
fix(processing): say when a result intent is not recognized
claude Sep 19, 2026
166e9d5
fix(processing): keep an unconfirmed preview when a task stages no la…
claude Sep 19, 2026
ccc7aaf
fix(segmentation): surface a crashed process worker instead of hanging
claude Sep 19, 2026
c24cd47
fix(segmentation): terminate a cancelled process worker
claude Sep 19, 2026
16e25e7
perf(segmentation): drop a full-volume copy and a redundant scan from…
claude Sep 19, 2026
a1ab5ab
perf(segmentation): scope a polygon fill to the polygon
claude Sep 19, 2026
1dadf2f
perf(segmentation): normalize labelmap scalars with an indexed loop
claude Sep 19, 2026
2cd82e6
perf(segmentation): cap concurrent mask reads and writes on save and …
claude Sep 19, 2026
d35a89f
fix(segmentation): report masks dropped during restore
claude Sep 19, 2026
76d228a
fix(segmentation): join a conversion already running for an image
claude Sep 19, 2026
948cbd9
fix(segmentation): keep name and id handling consistent in the registry
claude Sep 19, 2026
fd45186
perf(segmentation): release a mask name instead of rescanning the scene
claude Sep 19, 2026
088b8af
fix(segmentation): say what deleting a segment removed
claude Sep 19, 2026
3212423
perf(segmentation): keep segment row identity across unrelated annota…
claude Sep 19, 2026
fd0d17f
fix(segmentation): make the preview toggle idempotent
claude Sep 19, 2026
122592d
refactor(segmentation): reuse boundedMask and drop unused voxel acces…
claude Sep 19, 2026
6549e50
fix(crop): skip restored crops whose image did not load
claude Sep 19, 2026
d6dd66c
fix(layers): skip restored layers whose image did not load
claude Sep 19, 2026
410f4eb
chore(checks): drop the unused checks eslint config
claude Sep 19, 2026
585ef2b
fix(checks): make the convention check see nested modules and staged …
claude Sep 19, 2026
1d53991
fix(checks): allow tracked binaries outside the e2e baselines
claude Sep 19, 2026
3343f21
chore(lint): close the composables and nested-path gaps in the pure l…
claude Sep 19, 2026
dcc9616
docs(contract): describe the intent vocabulary version honestly
claude Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ jobs:
name: Code check and tests
steps:
- uses: actions/checkout@v4
with:
# The ratchets diff against the merge base with the target branch, so
# the checkout needs history rather than a single commit.
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v4
with:
Expand All @@ -19,6 +23,16 @@ jobs:
run: npx prettier --list-different "src/**/*.[jt]s" "tests/**/*.[jt]s" "src/**/*.vue"
- name: Lint
run: npm run lint
- name: Quality ratchets
# On a pull request the checkout is the merge commit, so the merge base
# with the target branch is what the change is measured against. The
# merge queue has no pull request to read a base from.
env:
BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
run: |
git fetch --no-tags origin \
"+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF"
node scripts/checks/check.mjs --base "origin/$BASE_REF"
- name: Tests
run: npm test

Expand Down
25 changes: 22 additions & 3 deletions backend-contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ validate `annotations-file.schema.json`, then enforce
`validateAnnotationsFileSemantics` — every nonempty `labelName` must be declared
in its own tool-kind label namespace, which JSON Schema cannot express either.
Backend conformance tests must also assert that every payload under
`fixtures/negative/` is rejected by the combined validation path.
`fixtures/negative/` is rejected by the combined validation path. The one
exception is `negative/wrong-length-color.json`, which only the strict
known-intent union rejects: `result-intent.schema.json` is deliberately open,
so it accepts the row and demotes it to an ordinary result carrying no state
action.

## The neutral REST surface (OpenAPI)

Expand Down Expand Up @@ -106,8 +110,23 @@ Two versions on separate clocks:
`info.version`, kept in lockstep by `processing/__tests__/openapi.spec.ts`.
Versions this package as a published thing.
- **Shape versions**: `INTENT_VOCABULARY_VERSION` (`processing/wire.ts`) and
the task-spec `specVersion`. These version the wire vocabulary for additive
compatibility negotiation.
the task-spec `specVersion`. These name the shape of the wire vocabulary in
the generated OpenAPI description and in release notes. Neither travels on
the wire, so neither is negotiated: additive compatibility rests on both
sides failing open on a value they do not know.

### Result instruction rollout

Contract artifact 0.3.0 uses intent vocabulary 3 and names segmentation import
`import-segmentation`. Deploy the updated producer and VolView client together.
An older client treats the unfamiliar instruction as an ordinary result and
will not apply its segmentation automatically. Update Girder's pinned VolView
package when releasing the paired change.

This vocabulary change does not change task-spec versions or saved-session
schemas. Girder projects stored job outputs into current instructions when
results are requested; stored output references and mask provenance keep their
identities.

## Regenerating

Expand Down
2 changes: 1 addition & 1 deletion backend-contract/fixtures/negative/wrong-length-color.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "6600000000000000000000e1",
"intent": "add-segment-group",
"intent": "import-segmentation",
"url": "/api/v1/file/6600000000000000000000e1/proxiable/otsu.nii.gz",
"name": "otsu.nii.gz",
"segments": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "6600000000000000000000e2",
"intent": "add-segment-group",
"intent": "import-segmentation",
"url": "/api/v1/file/6600000000000000000000e2/proxiable/threshold.seg.nrrd",
"name": "threshold.seg.nrrd",
"source": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "6600000000000000000000e1",
"intent": "add-segment-group",
"intent": "import-segmentation",
"url": "/api/v1/file/6600000000000000000000e1/proxiable/otsu.nii.gz",
"name": "otsu.nii.gz",
"segments": [
Expand Down
3 changes: 3 additions & 0 deletions backend-contract/generated/annotations-file.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -46,6 +47,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -68,6 +70,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/generated/job-results.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"properties": {
"intent": {
"type": "string",
"const": "add-segment-group"
"const": "import-segmentation"
},
"id": {
"type": "string",
Expand Down
11 changes: 7 additions & 4 deletions backend-contract/generated/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "VolView neutral backend contract",
"version": "0.2.0",
"description": "DRAFT 0.x — shapes may change until a second backend passes the conformance kit (the pinned 1.0 criterion). The neutral REST surface the VolView client calls to run processing tasks against a backend. A conforming server-side BACKEND implements these endpoints and the referenced wire schemas — no VolView client change is needed to bring a new backend online. Everything here is neutral: no backend routes, ids, status enums, or URL shapes leak. The artifact version is the draft artifact version, distinct from the shape versions: the result-intent vocabulary is at version 2 (INTENT_VOCABULARY_VERSION); the task-spec shape at version 1 (specVersion)."
"version": "0.3.0",
"description": "DRAFT 0.x — shapes may change until a second backend passes the conformance kit (the pinned 1.0 criterion). The neutral REST surface the VolView client calls to run processing tasks against a backend. A conforming server-side BACKEND implements these endpoints and the referenced wire schemas — no VolView client change is needed to bring a new backend online. Everything here is neutral: no backend routes, ids, status enums, or URL shapes leak. The artifact version is the draft artifact version, distinct from the shape versions: the result-intent vocabulary is at version 3 (INTENT_VOCABULARY_VERSION); the task-spec shape at version 1 (specVersion)."
},
"servers": [
{
Expand Down Expand Up @@ -717,7 +717,7 @@
}
},
"multiple": {
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. Only labelmap source refs bind plurally today — every group whose parent is the active dataset, in store order; on other type tags the flag has no effect yet.",
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. For a labelmap input the client stages the active image's segmentation as overlap-free parts: when true, every mask, spread across as many files as it takes for no two masks to share a voxel; when absent or false, only the non-conflicting subset that fits one file, with the remaining masks omitted whole and reported to the user. No mask is ever clipped to fit.",
"type": "boolean"
}
},
Expand Down Expand Up @@ -1203,7 +1203,7 @@
"properties": {
"intent": {
"type": "string",
"const": "add-segment-group"
"const": "import-segmentation"
},
"id": {
"type": "string",
Expand Down Expand Up @@ -1701,6 +1701,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -1723,6 +1724,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -1745,6 +1747,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/generated/result-intent.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"properties": {
"intent": {
"type": "string",
"const": "add-segment-group"
"const": "import-segmentation"
},
"id": {
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/generated/task-spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
}
},
"multiple": {
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. Only labelmap source refs bind plurally today — every group whose parent is the active dataset, in store order; on other type tags the flag has no effect yet.",
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. For a labelmap input the client stages the active image's segmentation as overlap-free parts: when true, every mask, spread across as many files as it takes for no two masks to share a voxel; when absent or false, only the non-conflicting subset that fits one file, with the remaining masks omitted whole and reported to the user. No mask is ever clipped to fit.",
"type": "boolean"
}
},
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@volview/backend-contract",
"version": "0.2.0",
"version": "0.3.0",
"private": true
}
26 changes: 13 additions & 13 deletions backend-contract/processing/__tests__/wire.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ describe('neutral job status fixtures', () => {
// ---------------------------------------------------------------------------

describe('result intent fixtures', () => {
it('exports vocabulary version 2 and the exactly-four state intents', () => {
expect(INTENT_VOCABULARY_VERSION).toBe(2);
it('exports vocabulary version 3 and the exactly-four state intents', () => {
expect(INTENT_VOCABULARY_VERSION).toBe(3);
expect([...RESULT_INTENTS]).toEqual([
'add-base-image',
'add-layer',
'add-segment-group',
'import-segmentation',
'add-annotations',
]);
expect(wire).not.toHaveProperty('intent.download');
Expand All @@ -208,8 +208,8 @@ describe('result intent fixtures', () => {
it.each([
'intent.add-base-image',
'intent.add-layer',
'intent.add-segment-group.with-segments',
'intent.add-segment-group.embedded',
'intent.import-segmentation.with-segments',
'intent.import-segmentation.embedded',
'intent.add-annotations',
'intent.unknown',
])('validates %s', (name) => {
Expand Down Expand Up @@ -253,11 +253,11 @@ describe('result intent fixtures', () => {
).toBe(false);
});

it('parses add-segment-group WITH segments and a source provenance tag', () => {
it('parses import-segmentation WITH segments and a source provenance tag', () => {
const parsed = resultIntentSchema.parse(
wire['intent.add-segment-group.with-segments']
wire['intent.import-segmentation.with-segments']
) as Record<string, unknown>;
expect(parsed.intent).toBe('add-segment-group');
expect(parsed.intent).toBe('import-segmentation');
expect(Array.isArray(parsed.segments)).toBe(true);
expect(parsed.source).toEqual({
providerId: 'analysis-provider',
Expand All @@ -266,18 +266,18 @@ describe('result intent fixtures', () => {
});
});

it('parses add-segment-group WITHOUT segments (embedded metadata) but with source', () => {
it('parses import-segmentation WITHOUT segments (embedded metadata) but with source', () => {
const parsed = resultIntentSchema.parse(
wire['intent.add-segment-group.embedded']
wire['intent.import-segmentation.embedded']
) as Record<string, unknown>;
expect(parsed.intent).toBe('add-segment-group');
expect(parsed.intent).toBe('import-segmentation');
expect(parsed.segments).toBeUndefined();
expect(parsed.source).toMatchObject({ outputId: 'outputLabelmap' });
});

it('rejects a segment-group source without provider identity', () => {
const value = structuredClone(
wire['intent.add-segment-group.with-segments']
wire['intent.import-segmentation.with-segments']
) as { source: { providerId?: string } };
delete value.source.providerId;
expect(knownResultIntentSchema.safeParse(value).success).toBe(false);
Expand Down Expand Up @@ -373,7 +373,7 @@ describe('result intent fixtures', () => {
expect(knownResultIntentSchema.safeParse(short).success).toBe(false);
expect(resultIntentSchema.safeParse(short).success).toBe(true);

const good = wire['intent.add-segment-group.with-segments'] as {
const good = wire['intent.import-segmentation.with-segments'] as {
segments: { color: number[] }[];
};
const long = structuredClone(good);
Expand Down
7 changes: 6 additions & 1 deletion backend-contract/processing/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ export type WirePolygon = z.infer<typeof wirePolygonSchema>;

// A label's style. Every field is optional: a label may exist purely as a name.
export const annotationLabelSchema = z.strictObject({
color: z.string().optional(),
color: z
.string()
.optional()
.describe(
'A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.'
),
strokeWidth: z.number().optional(),
fillColor: z.string().optional(),
});
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/processing/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ export const buildOpenApiDocument = (): Record<string, unknown> => ({
// VERSION / specVersion) below. It is deliberately literal, not derived from
// the shape-version constants — the artifact and the shapes version on
// separate clocks.
version: '0.2.0',
version: '0.3.0',
description:
'DRAFT 0.x — shapes may change until a second backend passes the ' +
'conformance kit (the pinned 1.0 criterion). ' +
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/processing/task-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const sourceRefParam = z.object({
.boolean()
.optional()
.describe(
'When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. Only labelmap source refs bind plurally today — every group whose parent is the active dataset, in store order; on other type tags the flag has no effect yet.'
"When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. For a labelmap input the client stages the active image's segmentation as overlap-free parts: when true, every mask, spread across as many files as it takes for no two masks to share a voxel; when absent or false, only the non-conflicting subset that fits one file, with the remaining masks omitted whole and reported to the user. No mask is ever clipped to fit."
),
});

Expand Down
18 changes: 11 additions & 7 deletions backend-contract/processing/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import {
} from './task-spec';
import { pathSegmentIdSchema } from './ids';

// Bump when the intent vocabulary's shape changes so producers and the applier
// can negotiate compatibility. Adding an intent is a compatible bump: an older
// client demotes the unknown intent through the fail-open branch above.
export const INTENT_VOCABULARY_VERSION = 2;
// A client-side marker for the shape of the intent vocabulary below, bumped
// when that shape changes. It never travels on the wire: no request header,
// response field, or schema property carries it, so the two sides never see
// each other's value and cannot negotiate on it. It names the vocabulary in
// the generated OpenAPI description and in this package's release notes.
// Adding an intent stays compatible without it: an older client demotes the
// unknown intent through the fail-open branch above.
export const INTENT_VOCABULARY_VERSION = 3;

// ---------------------------------------------------------------------------
// Input value: what the client sends at submit
Expand Down Expand Up @@ -158,7 +162,7 @@ export type NeutralJobStatus = z.infer<typeof neutralJobStatusSchema>;
export const RESULT_INTENTS = [
'add-base-image',
'add-layer',
'add-segment-group',
'import-segmentation',
'add-annotations',
] as const;
export type ResultIntentName = (typeof RESULT_INTENTS)[number];
Expand Down Expand Up @@ -212,13 +216,13 @@ const addLayer = z
.object({ intent: z.literal('add-layer'), ...resultListItemSchema.shape })
.passthrough();

// `add-segment-group` carries OPTIONAL `segments` (the bare-labelmap +
// `import-segmentation` carries OPTIONAL `segments` (the bare-labelmap +
// labels-sidecar case; a `seg.nrrd` with embedded metadata carries none — the
// client uses `segments` when present, else the file's own metadata) and an
// optional `source` provenance tag (the idempotency key).
const addSegmentGroup = z
.object({
intent: z.literal('add-segment-group'),
intent: z.literal('import-segmentation'),
...resultListItemSchema.shape,
segments: z.array(segmentDescriptorSchema).optional(),
source: resultSourceSchema.optional(),
Expand Down
Loading