Skip to content

Add scalar image layer controls to the Rendering tab - #983

Merged
PaulHax merged 5 commits into
Kitware:mainfrom
PaulHax:rendering-layer-controls
Oct 22, 2026
Merged

PaulHax merged 5 commits into
Kitware:mainfrom
PaulHax:rendering-layer-controls

Conversation

@PaulHax

@PaulHax PaulHax commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Adds shared color-map and display-range controls for scalar 2D base images and overlays, with visibility, overlay opacity, and transparent-background blending. Layer settings remain editable with slices off-screen and persist through layout changes and saved sessions. Existing multicomponent overlay rendering remains available, and range edits reuse unchanged opacity data.

Includes a PET/CT Chest example that uses the existing URL importer and automatic DICOM layering.

@netlify

netlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for volview-dev ready!

Name Link
🔨 Latest commit 16c80a7
🔍 Latest deploy log https://app.netlify.com/projects/volview-dev/deploys/6ada7d378ba7ef9a8ed0cfb2
😎 Deploy Preview https://deploy-preview-983--volview-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@PaulHax
PaulHax force-pushed the rendering-layer-controls branch 2 times, most recently from 49e48ca to 6e41e8b Compare October 21, 2026 13:07
@PaulHax
PaulHax marked this pull request as ready for review October 22, 2026 14:01
@PaulHax
PaulHax force-pushed the rendering-layer-controls branch 2 times, most recently from 7223d65 to e2cff06 Compare October 22, 2026 18:09

@PaulHax PaulHax left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Getting close.

Comment thread src/components/SampleDataBrowser.vue Outdated
Comment on lines +74 to +82
const files =
'urls' in sample
? urlsToDataSources(sample.urls)
: [
remoteFileToDataSource(
await fetchFile(sample.url, sample.filename, { progress }),
sample.url
),
];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

make a helper func or something to avoid long ternary with logic in second branch

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed locally: extracted getSampleDataSources to replace the long ternary.

Comment on lines +150 to +152
:disabled="
sample.isDownloading || (sample.isLoaded && !('urls' in sample))
"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

in general for this file, i'm I'm confused about why there's so much thrashing in this file just to add one new option that should be pretty standard. Just default loading of a list of files. But if you think this is simpler, all these file changes in this, in this component, we can keep it, but just take a hard look at it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PET/CT needs both import results and the normal auto-layering path. The remaining changes track both volumes and keep progress active until import finishes.

Comment on lines +45 to +54
watchImmediate(
[() => wlConfig.config.value?.visibility ?? true, imageData],
([visible, image]) => {
sliceRep.actor.setVisibility(visible);
if (visible && image) {
// Hidden image actors are excluded when a layout resize fits clipping.
view.renderer.resetCameraClippingRange(image.getBounds());
}
}
);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

For watch effect style, where we don't have to declare dependencies, if that's appropriate here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Kept the explicit watch: resetting clipping invokes camera callbacks that read reactive state. watchEffect would track those incidental reads too.

sliceRep.mapper.set({
viewSpecificProperties: {
OpenGL: {
ShaderReplacements: replacementValue

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

neat but its it striaghtforward to avoid doing shader replaments for this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These preserve preset opacity during background fading and apply layer opacity to RGBA images, which vtk.js currently ignores in its RGBA shader.

: coloringConfig.value.transferFunction.mappingRange
);
watch(
[colorPreset, () => mappingRange.value[0], () => mappingRange.value[1]],

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

watcheffect prefered if possible.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed locally: changed to watchEffect.

Comment on lines +45 to +46
}
const draftRange = computed(() => [

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

whats up with the no space formating between fucntions and watches in this file?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed locally: added spacing between functions and watchers.

Comment on lines +21 to +22
);
const emit = defineEmits<{

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

space between big defs?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed locally: added spacing between the larger definitions.

Comment thread src/components/LayerProperties.vue Outdated
Comment on lines +70 to +82
const validRange = (
range: readonly [number, number] | undefined
): range is readonly [number, number] =>
!!range &&
range.every(Number.isFinite) &&
range[0] < range[1] &&
Number.isFinite(range[1] - range[0]);
function setRange(range: [number, number]) {
if (coloringUnavailableReason.value || !validRange(range)) return;
layerColoringStore.updateDisplayConfig(props.layer.id, {
mappingRange: range,
});
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

is this kind of code duplicated in the code base? wonder if there is factor out resue opporutnity here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed locally: extracted isValidDisplayRange and reused it in the layer controls, contrast presets, range inputs, and slider.

Comment thread src/store/views.ts
function addView(viewInit: ViewInfoInit) {
const id = idStore.nextId();
let id = idStore.nextId();
while (id in viewByID) id = idStore.nextId();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

is this while really needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Restored view IDs can overlap several upcoming generated IDs; the loop prevents overwriting those views.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

do we really this this as a screeshot test? seems like some logical assertion would be simpler

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed locally: replaced the image baseline with rendered-pixel assertions at 0% and 100% opacity.

Provide shared palette selection, numeric display limits, and a draggable
three-handle range bar with Tab and arrow-key interaction.
Use shared controls for scalar palettes, display ranges, contrast, visibility,
and overlay opacity. Apply base image settings in ordinary and oblique views,
while preserving native RGB colors and RGBA alpha composition.

Preserve display settings across view changes and saved sessions, including
reset appearance and removed layers. Cover the controls, rendering, and saved
state with behavior tests and document the display options.
Load the PET and CT URL collection through ordinary importing and automatic
same-study layering. Reuse loaded DICOM chunks when reopening the sample.

Add sample attribution, a thumbnail, and checksum-pinned browser fixtures
without tracking binary datasets.
@PaulHax
PaulHax force-pushed the rendering-layer-controls branch from 107f20e to 16c80a7 Compare October 22, 2026 21:16
@PaulHax
PaulHax merged commit ae32c32 into Kitware:main Oct 22, 2026
6 of 8 checks passed
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.

1 participant