Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion 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
6 changes: 6 additions & 0 deletions backend-contract/generated/annotations-file.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
"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": {
"type": "number"
},
"fillColor": {
"description": "Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.",
"type": "string"
}
},
Expand All @@ -46,12 +48,14 @@
"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": {
"type": "number"
},
"fillColor": {
"description": "Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.",
"type": "string"
}
},
Expand All @@ -68,12 +72,14 @@
"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": {
"type": "number"
},
"fillColor": {
"description": "Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.",
"type": "string"
}
},
Expand Down
8 changes: 7 additions & 1 deletion backend-contract/generated/openapi.json
Original file line number Diff line number Diff line change
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 @@ -1701,12 +1701,14 @@
"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": {
"type": "number"
},
"fillColor": {
"description": "Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.",
"type": "string"
}
},
Expand All @@ -1723,12 +1725,14 @@
"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": {
"type": "number"
},
"fillColor": {
"description": "Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.",
"type": "string"
}
},
Expand All @@ -1745,12 +1749,14 @@
"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": {
"type": "number"
},
"fillColor": {
"description": "Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.",
"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
14 changes: 12 additions & 2 deletions backend-contract/processing/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,19 @@ 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(),
fillColor: z
.string()
.optional()
.describe(
'Accepted so an existing producer keeps validating, and ignored: the client draws every rectangle unfilled.'
),
});
export type AnnotationLabel = z.infer<typeof annotationLabelSchema>;

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
139 changes: 90 additions & 49 deletions docs/configuration_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ By loading a JSON file, you can set VolView's configuration:

- View layouts (grid size, view types, or hierarchical layouts)
- Disabled view types
- Labels for tools
- Segments
- Visibility of Sample Data section
- Keyboard shortcuts

Expand Down Expand Up @@ -149,66 +149,127 @@ Use `disabledViewTypes` to prevent certain view types from being available in th

This removes the specified view types from the dropdown menu and replaces them in the default layout with allowed types. Valid values: `"2D"`, `"3D"`, `"Oblique"`

## Labels for tools
## Segments

Each tool type (Rectangle, Polygon, etc.) can have tool specific labels. To share labels
across tools, define the `defaultLabels` key and don't provide labels for a tool that
should use the default labels.
Paint, rectangles, polygons and rulers share one registry of segments, configured under
`segments`. Each entry is keyed by name, and every appearance field is optional: an
omitted one means the app default for a new segment. For an existing session segment,
omitted fields keep the appearance it had before configuration. Replacing a config entry
removes its previous appearance overrides, including color, while keeping the segment id,
visibility and lock state.

```json
{
"segments": {
"lesion": { "color": "#ff0000" },
"tumor": { "color": "green", "strokeWidth": 3, "fillOpacity": 0.5 }
}
}
```

Fields: `color`, `fillOpacity`, `outlineOpacity`, `strokeWidth`.

Omitting the key leaves the registry alone. An empty record (`{}`) or `null` clears what an
earlier config contributed, keeping any segment your content still references with its
last configured appearance. A configured
segment keeps its id across config changes, so renaming or recoloring one never detaches
the masks and shapes that reference it.

### Pre-7.0 `labels`

A pre-7.0 `labels` section is converted into `segments` at configuration ingestion, with a deprecation warning. Runtime configuration contains only `segments`. Its `defaultLabels`, `rulerLabels`,
`rectangleLabels` and `polygonLabels` all describe the one registry now, so they read as
`segments` entries. A name that appears in more than one becomes a single segment: the
first record to declare it sets its appearance, reading `rulerLabels`, `rectangleLabels`
and `polygonLabels` in that order and `defaultLabels` last, since it stood in only for the
tools that declared no record of their own. A rectangle label's `fillColor` is dropped,
since fill color is a property of the rectangle rather than of the segment. A config
carrying both `segments` and `labels` has been converted already, so `segments` is read
and `labels` is ignored.

Converting a config by hand:

```json
{
"labels": {
"defaultLabels": {
"lesion": { "color": "#ff0000" },
"tumor": { "color": "green", "strokeWidth": 3 }
}
"defaultLabels": { "lesion": { "color": "#ff0000" } },
"rulerLabels": { "big": { "color": "#ff0000" } }
}
}
```

## Segment Group File Format
becomes

The `segmentGroupSaveFormat` key specifies the file extension of the segment group images
```json
{
"segments": {
"lesion": { "color": "#ff0000" },
"big": { "color": "#ff0000" }
}
}
```

## Session Mask File Format

The `segmentationSaveFormat` key specifies the file extension of the mask images
VolView will include in the volview.zip file.

```json
{
"io": {
"segmentGroupSaveFormat": "nii"
"segmentationSaveFormat": "nii"
}
}
```

Working segment group file formats:
The legacy `io.segmentGroupSaveFormat` key is migrated at ingestion. Matching
old and new values are accepted; conflicting values are rejected. This setting
controls mask files inside saved sessions, independently of the explicit
segmentation export dialog. Existing saved-session encodings remain readable.

Working mask file formats:

hdf5, iwi.cbor, mha, nii, nii.gz, nrrd, vtk

## Automatic Layers and Segment Groups by File Name
## Automatic Layers and Segmentations by File Name

When loading multiple files, VolView can automatically associate related images based on file naming patterns.
Example: `base.[extension].nrrd` will match `base.nii`.

The extension must appear anywhere in the filename after splitting by dots, and the filename must start with the same prefix as the base image (everything before the first dot). Files matching `base.[extension]...` will be associated with a base image named `base.*`.

**Ordering:** When multiple layers/segment groups match a base image, they are sorted alphabetically by filename and added to the stack in that order. To control the stacking order explicitly, you could use numeric prefixes in your filenames.
**Ordering:** When multiple layers/segmentations match a base image, they are sorted alphabetically by filename and added to the stack in that order. To control the stacking order explicitly, you could use numeric prefixes in your filenames.

For example, with a base image `patient001.nrrd`:

- Layers (sorted alphabetically): `patient001.layer.1.pet.nii`, `patient001.layer.2.ct.mha`, `patient001.layer.3.overlay.vtk`
- Segment groups: `patient001.seg.1.tumor.nii.gz`, `patient001.seg.2.lesion.mha`
- Segmentations: `patient001.seg.1.tumor.nii.gz`, `patient001.seg.2.lesion.mha`

Both features default to `''` which disables them.

### Segment Groups
### Configuration migration

Use `io.segmentationExtension` in new configuration. The old
`io.segmentGroupExtension` key is accepted at ingestion and converted to the
new key. If both keys are present, their values must match; conflicting values
are rejected. An explicit empty string disables automatic matching.

The value `seg` is the filename marker in `patient.seg.nii.gz`; `nii.gz` is
its encoding extension. This setting preserves the existing filename matching
rule and does not add support for additional segmentation formats.

Directly loading an old key in VolView also reports a deprecation warning.

### Segmentations

Use `segmentGroupExtension` to automatically convert matching non-DICOM images to segment groups.
For example, `myFile.seg.nrrd` becomes a segment group for `myFile.nii`.
Use `segmentationExtension` to automatically convert matching non-DICOM images to segmentations.
For example, `myFile.seg.nrrd` becomes a segmentation for `myFile.nii`.
Defaults to `''` which disables matching.

```json
{
"io": {
"segmentGroupExtension": "seg"
"segmentationExtension": "seg"
}
}
```
Expand Down Expand Up @@ -246,11 +307,9 @@ To configure a key for an action, add its action name and the key(s) under the `

```json
{
"labels": {
"defaultLabels": {
"lesion": { "color": "#ff0000" },
"tumor": { "color": "green", "strokeWidth": 3 }
}
"segments": {
"lesion": { "color": "#ff0000" },
"tumor": { "color": "green", "strokeWidth": 3 }
},
"layouts": {
"single-view": {
Expand All @@ -264,28 +323,10 @@ To configure a key for an action, add its action name and the key(s) under the `

```json
{
"labels": {
"defaultLabels": {
"lesion": { "color": "#ff0000" },
"tumor": { "color": "green", "strokeWidth": 3 },
"innocuous": { "color": "white" }
},
"rulerLabels": {
"big": { "color": "#ff0000" },
"small": { "color": "white" }
},
"rectangleLabels": {
"red": { "color": "#ff0000", "fillColor": "transparent" },
"green": { "color": "green", "fillColor": "transparent" },
"white-yellow-fill": {
"color": "white",
"fillColor": "#00ff0030"
}
},
"polygonLabels": {
"poly1": { "color": "#ff0000" },
"poly2Label": { "color": "green" }
}
"segments": {
"lesion": { "color": "#ff0000" },
"tumor": { "color": "green", "strokeWidth": 3, "fillOpacity": 0.5 },
"innocuous": { "color": "white", "outlineOpacity": 0.8 }
},
"layouts": {
"Volume primary": {
Expand All @@ -312,8 +353,8 @@ To configure a key for an action, add its action name and the key(s) under the `
"showKeyboardShortcuts": "t"
},
"io": {
"segmentGroupSaveFormat": "nrrd",
"segmentGroupExtension": "seg",
"segmentationSaveFormat": "nrrd",
"segmentationExtension": "seg",
"layerExtension": "layer"
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/loading_data.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ To layer images:

## State Files

Load preconfigured scenes with annotations, segment groups, and view settings via [state files](./state_files.md). State files can embed data (`*.volview.zip`) or reference remote data via URIs (`*.volview.json`).
Load preconfigured scenes with annotations, segmentations, and view settings via [state files](./state_files.md). State files can embed data (`*.volview.zip`) or reference remote data via URIs (`*.volview.json`).
2 changes: 1 addition & 1 deletion docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ directly. For longer-running work, VolView also ships a Jobs panel that talks to
a processing backend over the neutral API defined in the `backend-contract`
package: the backend advertises its tasks, VolView builds the submission form
from each task specification, and completed outputs load back into the scene as
images, layers, or segment groups. Any service that implements the contract
images, layers, or segmentations. Any service that implements the contract
works, since VolView knows only the shared vocabulary and never a backend's
native task format.

Expand Down
Loading
Loading