Skip to content

Commit b291137

Browse files
committed
fix(layers): make layers visible by setting mapping range
1 parent 536c844 commit b291137

10 files changed

Lines changed: 108 additions & 42 deletions

File tree

‎.github/workflows/e2e.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
name: E2E Testing on ${{ matrix.os }}
1111
runs-on: ${{ matrix.os }}
1212
env:
13-
DOWNLOAD_TIMEOUT: 90000
13+
DOWNLOAD_TIMEOUT: 160000
1414
steps:
1515
- uses: actions/checkout@v4
1616
with:

‎src/components/vtk/VtkLayerSliceRepresentation.vue‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { vtkFieldRef } from '@/src/core/vtk/vtkFieldRef';
1111
import { syncRef } from '@vueuse/core';
1212
import { useSliceConfig } from '@/src/composables/useSliceConfig';
1313
import useLayerColoringStore from '@/src/store/view-configs/layers';
14-
import { useLayerConfigInitializer } from '@/src/composables/useLayerConfigInitializer';
1514
import { applyColoring } from '@/src/composables/useColoringEffect';
1615
import { useImageCacheStore } from '@/src/store/image-cache';
1716
@@ -66,9 +65,6 @@ const slice = vtkFieldRef(sliceRep.mapper, 'slice');
6665
const { slice: storedSlice } = useSliceConfig(viewId, parentId);
6766
syncRef(storedSlice, slice, { immediate: true });
6867
69-
// initialize layer coloring
70-
useLayerConfigInitializer(viewId, layerId);
71-
7268
// apply layer coloring
7369
const applyLayerColoring = () => {
7470
const config = coloringConfig.value;
@@ -97,6 +93,7 @@ const applyLayerColoring = () => {
9793
sliceRep.property.setColorWindow(width);
9894
sliceRep.property.setColorLevel(center);
9995
sliceRep.property.setOpacity(config.blendConfig.opacity);
96+
sliceRep.actor.setVisibility(config.blendConfig.visibility);
10097
};
10198
10299
watchEffect(applyLayerColoring);

‎src/composables/useLayerConfigInitializer.ts‎

Lines changed: 0 additions & 21 deletions
This file was deleted.

‎src/store/view-configs/layers.ts‎

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,43 @@ export const useLayerColoringStore = defineStore('layerColoring', () => {
6464

6565
const configs = reactive<DoubleKeyRecord<LayersConfig>>({});
6666

67-
const getConfig = (viewID: Maybe<string>, dataID: Maybe<string>) =>
68-
getDoubleKeyRecord(configs, viewID, dataID) ?? defaultLayersConfig();
67+
const computeDefaultConfig = (dataID: string): LayersConfig => {
68+
const defaults = defaultLayersConfig();
69+
const preset = getPreset(dataID);
70+
71+
const image = imageCacheStore.getVtkImageData(dataID);
72+
if (!image) return defaults;
73+
74+
const imageDataRange = image.getPointData().getScalars().getRange();
75+
76+
const ctRange = getColorFunctionRangeFromPreset(preset);
77+
const ctFunc: Partial<ColorTransferFunction> = {
78+
preset,
79+
mappingRange: ctRange || imageDataRange,
80+
};
81+
82+
const opacityFunction = {
83+
...getOpacityFunctionFromPreset(preset),
84+
mappingRange: imageDataRange,
85+
};
86+
87+
return {
88+
...defaults,
89+
transferFunction: { ...defaults.transferFunction, ...ctFunc },
90+
opacityFunction,
91+
};
92+
};
93+
94+
const getConfig = (viewID: Maybe<string>, dataID: Maybe<string>) => {
95+
if (!viewID || !dataID) return defaultLayersConfig();
96+
const config = getDoubleKeyRecord(configs, viewID, dataID);
97+
if (config) return config;
98+
99+
// If no config exists, compute and store the default with proper preset
100+
const defaultConfig = computeDefaultConfig(dataID);
101+
patchDoubleKeyRecord(configs, viewID, dataID, defaultConfig);
102+
return defaultConfig;
103+
};
69104

70105
const updateConfig = (
71106
viewID: string,
@@ -116,8 +151,10 @@ export const useLayerColoringStore = defineStore('layerColoring', () => {
116151
};
117152
updateColorTransferFunction(viewID, layerID, ctFunc);
118153

119-
const opFunc = getOpacityFunctionFromPreset(preset);
120-
opFunc.mappingRange = imageDataRange;
154+
const opFunc = {
155+
...getOpacityFunctionFromPreset(preset),
156+
mappingRange: imageDataRange,
157+
};
121158
updateOpacityFunction(viewID, layerID, opFunc);
122159
};
123160

‎src/store/view-configs/volume-coloring.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,14 @@ export const useVolumeColoringStore = defineStore('volumeColoring', () => {
147147
const { colorFunc, opacityFunc } =
148148
getColorAndOpacityFuncsFromPreset(preset);
149149
colorFunc.mappingRange ||= imageDataRange;
150-
opacityFunc.mappingRange = imageDataRange;
150+
151+
const completeOpacityFunc = {
152+
...opacityFunc,
153+
mappingRange: imageDataRange,
154+
};
151155

152156
updateColorTransferFunction(viewID, imageID, colorFunc);
153-
updateOpacityFunction(viewID, imageID, opacityFunc);
157+
updateOpacityFunction(viewID, imageID, completeOpacityFunc);
154158
};
155159

156160
const resetToDefaultColoring = (

‎src/utils/vtk-helpers.ts‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import vtkOpenGLRenderWindow from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow
66
import type { Vector2, Vector3 } from '@kitware/vtk.js/types';
77
import type { vtkObject } from '@kitware/vtk.js/interfaces';
88
import { intersectDisplayWithPlane } from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
9-
import { OpacityFunction } from '@/src/types/views';
9+
import {
10+
OpacityGaussians,
11+
OpacityPoints,
12+
OpacityNodes,
13+
} from '@/src/types/views';
1014
import vtkFieldData from '@kitware/vtk.js/Common/DataModel/DataSetAttributes/FieldData';
1115
import { Maybe } from '@/src/types';
1216

@@ -125,10 +129,10 @@ export function getShiftedOpacityFromPreset(
125129
) {
126130
const preset = vtkColorMaps.getPresetByName(presetName);
127131
if (preset.OpacityPoints) {
128-
const OpacityPoints = preset.OpacityPoints as number[];
132+
const opacityPoints = preset.OpacityPoints as number[];
129133
const points = [];
130-
for (let i = 0; i < OpacityPoints.length; i += 2) {
131-
points.push([OpacityPoints[i], OpacityPoints[i + 1]]);
134+
for (let i = 0; i < opacityPoints.length; i += 2) {
135+
points.push([opacityPoints[i], opacityPoints[i + 1]]);
132136
}
133137

134138
const [xmin, xmax] = effectiveRange;
@@ -144,12 +148,18 @@ export function getShiftedOpacityFromPreset(
144148
return null;
145149
}
146150

151+
type OpacityFunctionWithoutRange =
152+
| Omit<OpacityGaussians, 'mappingRange'>
153+
| Omit<OpacityPoints, 'mappingRange'>
154+
| Omit<OpacityNodes, 'mappingRange'>;
155+
147156
/**
148-
* Retrieves an OpacityFunction from a preset.
157+
* Retrieves an OpacityFunction from a preset, without mappingRange.
158+
* Caller must provide mappingRange to complete the OpacityFunction.
149159
*/
150160
export function getOpacityFunctionFromPreset(
151161
presetName: string
152-
): Partial<OpacityFunction> {
162+
): OpacityFunctionWithoutRange {
153163
const preset = vtkColorMaps.getPresetByName(presetName);
154164

155165
if (preset.OpacityPoints) {
173 KB
Loading

‎tests/pageobjects/volview.page.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class VolViewPage extends Page {
182182
}
183183

184184
get layerOpacitySliders() {
185-
return $$('div[data-testid="layer-opacity-slider"] input');
185+
return $$('div[data-testid="layer-opacity-slider"]');
186186
}
187187

188188
async getVolumeRenderingSection() {
@@ -206,6 +206,11 @@ class VolViewPage extends Page {
206206
const exists = await view2D.isExisting();
207207
return exists ? view2D : null;
208208
}
209+
210+
async getViews2D() {
211+
const views2D = $$('div[data-testid="vtk-view vtk-two-view"]');
212+
return views2D;
213+
}
209214
}
210215

211216
export const volViewPage = new VolViewPage();

‎tests/specs/layers.e2e.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,39 @@ describe('Add Layer button', () => {
6363
timeoutMsg: `Expected at least one layer opacity slider`,
6464
}
6565
);
66+
67+
const layerOpacitySliders = await volViewPage.layerOpacitySliders;
68+
const firstSlider = layerOpacitySliders[0];
69+
70+
await firstSlider.click();
71+
72+
// Use keyboard to set to maximum (End key goes to max)
73+
await browser.keys('End');
74+
75+
await browser.waitUntil(
76+
async () => {
77+
const inputElement = await firstSlider.$('input');
78+
const value = await inputElement.getValue();
79+
return value === '1';
80+
},
81+
{
82+
timeoutMsg: 'Expected slider value to be 1 (max opacity)',
83+
timeout: 5000,
84+
}
85+
);
86+
87+
const views2D = await volViewPage.getViews2D();
88+
const viewCount = await views2D.length;
89+
if (!views2D || viewCount === 0) {
90+
throw new Error('Could not find 2D views for screenshot');
91+
}
92+
const lastView2D = views2D[viewCount - 1];
93+
const result = await browser.checkElement(
94+
lastView2D,
95+
'layers_max_opacity_2d_view'
96+
);
97+
98+
const THRESHOLD = 5; // percent
99+
await expect(result).toBeLessThan(THRESHOLD);
66100
});
67101
});

‎wdio.shared.conf.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { projectRoot } from './tests/e2eTestUtils';
66
export const WINDOW_SIZE = [1200, 800] as const;
77
export const TEST_PORT = 4567;
88
// for slow connections try:
9-
// DOWNLOAD_TIMEOUT=60000 npm run test:e2e:dev
10-
export const DOWNLOAD_TIMEOUT = Number(process.env.DOWNLOAD_TIMEOUT ?? 8000);
9+
// DOWNLOAD_TIMEOUT=60000 && npm run test:e2e:dev
10+
export const DOWNLOAD_TIMEOUT = Number(process.env.DOWNLOAD_TIMEOUT ?? 16000);
1111

1212
const ROOT = projectRoot();
1313
const TMP = '.tmp/';
@@ -72,7 +72,7 @@ export const config: Options.Testrunner = {
7272
reporters: ['spec', 'html-nice'],
7373
mochaOpts: {
7474
ui: 'bdd',
75-
timeout: 120 * 1000,
75+
timeout: 160 * 1000,
7676
},
7777

7878
//

0 commit comments

Comments
 (0)