Skip to content

Viewport fidelity state and events #2902

Open

Description

@wayfarer3130

Owner: @mbellehumeur (intended assignee — not assignable via the API without push access on this repo; needs assigning by hand)
Funded scope: OHIF / FU Berlin Set-Aside, task T3 (Aim 2.3, required)
Consumed by: OHIF viewport fidelity indicator (T6) — OHIF/Viewers#6256
Specification: viewport-fidelity/requirements.md in OHIF/Viewers#6254 — §5.5 VF-CS3D is this issue's requirement group; other VF- identifiers cited below are defined there


Summary

Cornerstone3D needs to expose, per viewport, what fidelity was requested versus what is
actually being displayed — as a defined interface with viewport events, plus example
implementations wired to the existing progressive loaders.

This is the prerequisite for the OHIF fidelity indicator (T6). It is also read directly by the
T11 measurement harness and the T18 final comparison, both of which record "current viewport
fidelity state", so it must be assertable without going through a UI.

Scope

  1. A defined class and interface surface representing viewport fidelity information
    (VF-CS3D-1).

  2. Viewport events emitted when fidelity information changes (VF-CS3D-2). A getter alone
    is not sufficient — the OHIF indicator subscribes rather than polls (VF-STATE-3), and must
    update without a study reload (VF-STATE-2).

  3. The information carries, separately (VF-CS3D-3):

    • the affected dimension — data resolution, display resolution, sampling density,
      decoded extent, pixel value quantisation, transform approximation, or rendering path;
    • the cause — a device capability limit, a performance target, an incomplete load, or an
      irreversibly lossy decode;
    • the quantified magnitude — per-axis decimation factors, decoded bytes, output
      resolution percentage, sampling ratio. Consumers must be able to render
      Decimated volume x/2, y/2, z/4 or Progressive decode, 32 kB initial, not just
      "reduced quality";
    • whether the difference is transient or standing.

    Separating dimension from cause is what lets a consumer render "showing less detail"
    alongside "because this GPU cannot hold the full volume" without a combinatorial string
    table.

  4. The transition must be observable, not just the current value. A progressive load that
    completes without reaching full resolution moves from transient to standing, and that
    transition is the single most important thing the indicator reports (VF-INFO-5a) — a
    stalled lossy load is visually indistinguishable from one still in flight.

  5. Example implementation for stack progressive loading (VF-CS3D-4).

  6. Example implementation for volume progressive loading (VF-CS3D-5).

  7. Both examples source their settings and values from the existing progressive loaders
    (VF-CS3D-6) rather than introducing a parallel configuration path.

  8. Readable programmatically without a UI (VF-CS3D-7).

Relationship to ImageQualityStatus

ImageQualityStatus (FAR_REPLICATE = 1, ADJACENT_REPLICATE = 3, SUBRESOLUTION = 6,
LOSSY = 7, FULL_RESOLUTION = 8) stays exactly as it is, with its consumers unchanged
(VF-CS3D-8). It is internal to progressive image loading: per-image, numeric, ordered,
readable via cache.getImageQuality(imageId) and carried on IImage.imageQualityStatus.

The new state is per-viewport and distinguishes requested from displayed — a different
shape, not a replacement. The viewport state may draw on ImageQualityStatus as one input
among several.

Notes for implementation

  • Events.IMAGE_RETRIEVAL_STAGE fires from ProgressiveRetrieveImages with detail
    {stageId, numberOfFailures, numberOfImages, stageDurationInMS, startDurationInMS} — no
    fidelity field — and has no OHIF subscribers today. Either its detail gains the
    information or a new per-viewport event carries it.
  • Contributing sources of state, as those tasks land: T9 records the pre-allocation
    downsample and its reason; T14 the active interaction reduction; T15 the ordered
    fallback reason; T4 the device-class limits that make a cause explicable. T13, if
    delivered, adds rendering-path substitution.
  • T14 degrades fidelity every frame during rotate, zoom and pan by design, so the state changes
    at interaction rates. It must be truthful frame-by-frame; the consumer is responsible for
    presenting that unobtrusively (VF-STATE-5), not the state for smoothing it.
  • Per-image granularity matters to consumers: the OHIF indicator reports the state of the image
    currently displayed, not the worst across the display set (VF-VIS-4a).

Out of scope

  • Detection, prevention, or recovery from insufficient browser memory (excluded by T9).
  • The GPU taxonomy itself (T4), the level binding (T9), and the selection policy (T15).
  • Any settable fidelity API. The state is queryable and observable; selection stays a
    deterministic side-effect-free decision of the T15 policy.
  • Fidelity of overlay layers such as segmentation labelmaps — acknowledged gap, unfunded.

Verification

Unit and example-level tests in the Cornerstone3D suite: dimension, cause, magnitude and
persistence populated correctly per fixture; events fire on change; the transient→standing
transition on a load that completes lossy; state assertable without a UI.

Activity

  1. linear commented on Sep 4, 2026

    @linear
  2. wayfarer3130 commented on Sep 21, 2026

    @wayfarer3130
    CollaboratorAuthor

    The component definition for this issue

    This comment adds the component definition. It comes from the planning session for #2921, which is the producer of this state.

    One change to the field list of this issue. The existing text asks for a reduction source that separates "server box-averaged level versus client-derived reduction, partial decode or slice subset". That one field mixes where the data came from with what arithmetic reduced the data. The definition below splits the field in two, because a client-derived level can be a box average or a decimation, and the indicator must clear for one and not for the other.

    Why this component lands first

    Task T3 and task T4 are the two tasks that unblock the OHIF work. The breakdown says:

    T3 and T4 unblock the two OHIF tasks, which need the longest lead time for manual UI validation.
    A second developer can therefore be productive on OHIF from day one, provided T3 and T4 land
    first — which is why both are deliberately small and consumer-free.

    The multi-resolution work of #2921
    is the producer of this state. That work is large. If this component waits for that work, then the
    OHIF fidelity indicator (#6256) waits too.

    Therefore this component lands as an early commit, and it lands with no consumer. The OHIF
    developer can then build against the type and against the event, before the producer exists.


    Scope, and the rule for a change to this document

    This document has two requirement sections, and the two sections stay separate:

    • § User requirements — what the user must be able to determine, to work safely.
    • § Implementation requirements — how to do the work.

    You can change an implementation requirement freely when a better approach appears. You change a
    user requirement in two cases only: the requirement describes the behaviour wrongly, or the
    intended behaviour for the user changes.


    § User requirements

    VFS-U-1. The user can determine the quality of what each viewport shows.

    VFS-U-2. Two viewports that show one volume report their quality independently. One viewport
    can report "lossless" while the other viewport reports "lossy".

    VFS-U-3. A viewport that shows reduced data at a magnification where the reduction is not
    visible reports the view as lossless. The user does not get a warning that is not necessary.

    VFS-U-4. The user can determine why the quality is reduced, and the user can determine
    whether the quality improves later.

    VFS-U-5. The user does not select the resolution, and the user does not configure the fidelity.
    The software selects the resolution. This is requirement VF-AUTO of the OHIF specification.


    § Implementation requirements

    Group 1 — the shape of the state

    VFS-API-1. Define one type, ViewportFidelityState. The type is a defined class or interface,
    and the type is not a scalar and not an enumeration.

    VFS-API-2. The state is complete from the start. Add every field now, even where no
    producer writes the field yet. A field that arrives later forces every consumer to change.

    VFS-API-3. The state carries these fields:

    Field What it holds Source
    dataResolution the spacing and the dimensions of the data that the render path read T3
    displayResolution the pixels that the viewport drew T3
    reductionKind none, boxAverage, decimation, partialDecode, sliceSubset P14
    reductionSource serverStoredLevel, clientDerived, decoderSubResolution, partialLoad T3
    magnitude the factor for each axis, for example x/2, y/2, z/4 T3
    cause deviceTextureLimit, memoryBudget, performanceTarget, incompleteLoad, lossyDecode T3
    transient true for a reduction that ends when the interaction ends. false for a standing reduction. T3
    progressive the state of a load that is not complete, and the step that the load reached T3
    substitutedDefaults the values that the code replaced, because the source values were not valid. The VOI LUT case is the example. T3
    renderPathId the identifier of the render path that produced this state this work

    VFS-API-4. reductionKind is new, and P14 requires it. A decimation and a box average at
    one spacing have different loss. A decimation keeps the high spatial frequencies and folds them
    into the signal, which a reformat shows as stair-stepping. A box average removes them. The OHIF
    indicator must be able to clear for a box average and not clear for a decimation, so one level
    number is not a sufficient report. This is also requirement VOL-X-8.4.

    VFS-API-5. magnitude states a factor for each axis. A reduction is not uniform. A volume that
    has an edge of 2049 voxels exceeds a limit of 2048 on one axis, and the state reports that.

    Group 2 — the record, and the verdict

    VFS-API-6. The state is an absolute record. The state says which data exists, at which
    spacing, with which loss, and how much data is missing. The state does not say whether the view
    is good enough.

    VFS-API-7. A separate pure function compares an absolute record against the requirement of
    one reader, and that function produces the verdict. Each render path can therefore produce a
    different answer from the same record, according to what that render path can use.

    VFS-API-8. VFS-API-6 and VFS-API-7 are the reason that VFS-U-2 works. Two viewports hold two
    records, and the function runs once for each viewport.

    VFS-API-9. An absolute record is cacheable. A verdict is not cacheable, because a 3D viewport
    changes its required sampling on every frame of an interaction.

    Group 3 — the event

    VFS-API-10. Add VIEWPORT_FIDELITY_CHANGED to packages/core/src/enums/Events.ts.

    VFS-API-11. A viewport fires the event when its fidelity state changes. A consumer subscribes
    to the event, and a consumer does not poll.

    VFS-API-12. The event carries the viewport identifier and the new state.

    VFS-API-13. A transient reduction during an interaction fires the event without a debounce.
    Task T14 degrades the fidelity on every frame, and the OHIF indicator must show that. This is
    requirement VF-STATE-5.

    VFS-API-14. The state updates without a reload of the study. This is requirement VF-STATE-2.

    Group 4 — the relation to ImageQualityStatus

    VFS-API-15. ImageQualityStatus stays as the comparable summary, and the code derives that
    summary from the absolute record. Every existing minQuality floor continues to work.

    VFS-API-16. ImageQualityStatus has five members today:
    FAR_REPLICATE = 1, ADJACENT_REPLICATE = 3, SUBRESOLUTION = 6, LOSSY = 7,
    FULL_RESOLUTION = 8. The values 2, 4 and 5 are free. You can add a value, and you can renumber a
    gap. The values are not a stable format for storage.

    VFS-API-17. SUBRESOLUTION does not mean "fewer pixels" today. Its producers return the value
    when the bytes are not complete, or for a partial response. Nothing derives the value from
    decodeLevel. Do not read the existing value as a statement about the resolution.

    Group 5 — the tests

    VFS-API-18. Every field of VFS-API-3 is representable, and a unit test covers each field.

    VFS-API-19. A test asserts the state without a user interface. The state is data, and a test
    reads the data.

    VFS-API-20. A test builds two viewports over one volume, and the test asserts that the two
    viewports report a different verdict from the same record.


    Done when

    1. ViewportFidelityState exists as a type, and the type carries every field of VFS-API-3.
    2. The pure comparison function of VFS-API-7 exists, and the function takes a record and a reader
      requirement, and the function returns a verdict.
    3. VIEWPORT_FIDELITY_CHANGED exists in enums/Events.ts, and a viewport fires the event.
    4. The unit tests of VFS-API-18, VFS-API-19 and VFS-API-20 pass.
    5. The documentation states that the record is absolute, and that the verdict is separate.
    6. No consumer is necessary. This component lands with no consumer, on purpose.

    Out of scope

    Item Where it belongs
    The OHIF indicator and its dialog task T6, OHIF#6255 and OHIF#6256
    The producer of the state, that is the multi-resolution voxel manager task T2, cornerstone3D#2921
    The degradation of the fidelity during an interaction task T14, cornerstone3D#2922
    The automatic selection policy that decides the level task T15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions