Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include segmentation layers in handling maximum number of renderable layers restricted by hardware #6211

1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added Volume Interpolation feature. When enabled, it suffices to only label every 2nd slice. The skipped slices will be filled automatically by interpolating between the labeled slices. This feature is disabled by default. Note that the feature is even forbidden for tasks by default, but can be enabled/recommended. [#6162](https://github.com/scalableminds/webknossos/pull/6162)
- Added a long-running job that applies the merging done via a merger mode tracing to a new output dataset. The job is accessible via a button next to the merger mode button once the merger mode is active. [#6086](https://github.com/scalableminds/webknossos/pull/6086)
- Added support to stream zarr files using the corresponding [zarr spec](https://zarr.readthedocs.io/en/stable/spec/v2.html#storage). [#6144](https://github.com/scalableminds/webknossos/pull/6144)
- Added segmentation layers to the functionality catching the case that more layers are active that the hardware allows. This prevents rendering issue with more than one segmentation layer and multiple color layers. [#6211](https://github.com/scalableminds/webknossos/pull/6211)

### Changed
- When creating a new annotation with a volume layer (without fallback) for a dataset which has an existing segmentation layer, the original segmentation layer is still listed (and viewable) in the left sidebar. Earlier versions simply hid the original segmentation layer. [#6186](https://github.com/scalableminds/webknossos/pull/6186)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ class PlaneMaterialFactory {
attributes: Record<string, any>;
shaderId: number;
storePropertyUnsubscribers: Array<() => void> = [];
leastRecentlyVisibleColorLayers: Array<string>;
leastRecentlyVisibleLayers: Array<{ name: string; isSegmentationLayer: boolean }>;
oldShaderCode: string | null | undefined;

constructor(planeID: OrthoView, isOrthogonal: boolean, shaderId: number) {
this.planeID = planeID;
this.isOrthogonal = isOrthogonal;
this.shaderId = shaderId;
this.leastRecentlyVisibleColorLayers = [];
this.leastRecentlyVisibleLayers = [];
}

setup() {
Expand Down Expand Up @@ -417,11 +417,12 @@ class PlaneMaterialFactory {
true,
),
);
const oldVisibilityPerLayer = {};
const oldVisibilityPerLayer: Record<string, boolean> = {};
this.storePropertyUnsubscribers.push(
listenToStoreProperty(
(state) => state.datasetConfiguration.layers,
(layerSettings) => {
let updatedLayerVisibility = false;
for (const dataLayer of Model.getAllLayers()) {
const { elementClass } = dataLayer.cube;
const settings = layerSettings[dataLayer.name];
Expand All @@ -431,26 +432,25 @@ class PlaneMaterialFactory {
const isSegmentationLayer = dataLayer.isSegmentation;

if (
!isSegmentationLayer &&
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
oldVisibilityPerLayer[dataLayer.name] != null &&
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
oldVisibilityPerLayer[dataLayer.name] !== isLayerEnabled
) {
if (settings.isDisabled) {
this.onDisableColorLayer(dataLayer.name);
this.onDisableLayer(dataLayer.name, isSegmentationLayer);
} else {
this.onEnableColorLayer(dataLayer.name);
this.onEnableLayer(dataLayer.name);
}
updatedLayerVisibility = true;
}

// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
oldVisibilityPerLayer[dataLayer.name] = isLayerEnabled;
const name = sanitizeName(dataLayer.name);
this.updateUniformsForLayer(settings, name, elementClass, isSegmentationLayer);
}
}

if (updatedLayerVisibility) {
this.recomputeFragmentShader();
}
// TODO: Needed?
app.vent.trigger("rerender");
},
Expand Down Expand Up @@ -623,77 +623,84 @@ class PlaneMaterialFactory {
window.needsRerender = true;
}, RECOMPILATION_THROTTLE_TIME);

getLayersToRender(maximumLayerCountToRender: number): Array<string> {
getLayersToRender(maximumLayerCountToRender: number): [Array<string>, Array<string>] {
// This function determines for which layers
// the shader code should be compiled. If the GPU supports
// all layers, we can simply return all layers here.
// Otherwise, we prioritize layers to render by taking
// into account (a) which layers are activated and (b) which
// layers were least-recently activated (but are now disabled).
// The first array contains the color layer names and the second the segmentation layer names.
if (maximumLayerCountToRender <= 0) {
return [];
return [[], []];
}

const colorLayerNames = getSanitizedColorLayerNames();
const segmentationLayerNames = Model.getSegmentationLayers().map((layer) =>
sanitizeName(layer.name),
);

if (maximumLayerCountToRender >= colorLayerNames.length) {
if (maximumLayerCountToRender >= colorLayerNames.length + segmentationLayerNames.length) {
// We can simply render all available layers.
return colorLayerNames;
return [colorLayerNames, segmentationLayerNames];
}

const state = Store.getState();
const enabledLayerNames = getEnabledLayers(state.dataset, state.datasetConfiguration, {
onlyColorLayers: true,
}).map((layer) => layer.name);
const disabledLayerNames = getEnabledLayers(state.dataset, state.datasetConfiguration, {
const enabledLayers = getEnabledLayers(state.dataset, state.datasetConfiguration, {}).map(
({ name, category }) => ({ name, isSegmentationLayer: category === "segmentation" }),
);
const disabledLayers = getEnabledLayers(state.dataset, state.datasetConfiguration, {
invert: true,
onlyColorLayers: true,
}).map((layer) => layer.name);
// In case, this.leastRecentlyVisibleColorLayers does not contain all disabled layers
}).map(({ name, category }) => ({ name, isSegmentationLayer: category === "segmentation" }));
// In case, this.leastRecentlyVisibleLayers does not contain all disabled layers
// because they were already disabled on page load), append the disabled layers
// which are not already in that array.
// Note that the order of this array is important (earlier elements are more "recently used")
// which is why it is important how this operation is done.
this.leastRecentlyVisibleColorLayers = [
...this.leastRecentlyVisibleColorLayers,
..._.without(disabledLayerNames, ...this.leastRecentlyVisibleColorLayers),
this.leastRecentlyVisibleLayers = [
...this.leastRecentlyVisibleLayers,
...disabledLayers.filter(
({ name }) =>
!this.leastRecentlyVisibleLayers.some((otherLayer) => otherLayer.name === name),
),
];
const names = enabledLayerNames
.concat(this.leastRecentlyVisibleColorLayers)

const names = enabledLayers
.concat(this.leastRecentlyVisibleLayers)
.slice(0, maximumLayerCountToRender)
.sort();
return names.map(sanitizeName);

const [sanitizedColorLayerNames, sanitizedSegmentationLayerNames] = _.partition(
names,
({ isSegmentationLayer }) => !isSegmentationLayer,
).map((layers) => layers.map(({ name }) => sanitizeName(name)));

Comment on lines +673 to +677
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Uhhh 😲 , very nice 🚀

return [sanitizedColorLayerNames, sanitizedSegmentationLayerNames];
}

onDisableColorLayer = (layerName: string) => {
this.leastRecentlyVisibleColorLayers = _.without(
this.leastRecentlyVisibleColorLayers,
layerName,
onDisableLayer = (layerName: string, isSegmentationLayer: boolean) => {
this.leastRecentlyVisibleLayers = this.leastRecentlyVisibleLayers.filter(
(entry) => entry.name !== layerName,
);
this.leastRecentlyVisibleColorLayers = [layerName, ...this.leastRecentlyVisibleColorLayers];
this.recomputeFragmentShader();
this.leastRecentlyVisibleLayers = [
{ name: layerName, isSegmentationLayer },
...this.leastRecentlyVisibleLayers,
];
};

onEnableColorLayer = (layerName: string) => {
this.leastRecentlyVisibleColorLayers = _.without(
this.leastRecentlyVisibleColorLayers,
layerName,
onEnableLayer = (layerName: string) => {
this.leastRecentlyVisibleLayers = this.leastRecentlyVisibleLayers.filter(
(entry) => entry.name !== layerName,
);
this.recomputeFragmentShader();
};

getFragmentShader(): string {
const { initializedGpuFactor, maximumLayerCountToRender } =
Store.getState().temporaryConfiguration.gpuSetup;
// Don't compile code for segmentation in arbitrary mode
const shouldRenderSegmentation = this.isOrthogonal && Model.hasSegmentationLayer();
const colorLayerNames = this.getLayersToRender(
maximumLayerCountToRender - (shouldRenderSegmentation ? 1 : 0),
);
const [colorLayerNames, segmentationLayerNames] =
this.getLayersToRender(maximumLayerCountToRender);
const packingDegreeLookup = getPackingDegreeLookup();
const segmentationLayerNames = shouldRenderSegmentation
? Model.getSegmentationLayers().map((layer) => sanitizeName(layer.name))
: [];
const { dataset } = Store.getState();
const datasetScale = dataset.dataSource.scale;
const lookupTextureWidth = getLookupBufferSize(initializedGpuFactor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -781,14 +781,9 @@ export function getEnabledLayers(
datasetConfiguration: DatasetConfiguration,
options: {
invert?: boolean;
onlyColorLayers: boolean;
} = {
onlyColorLayers: false,
},
Comment on lines -784 to -787
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only code lines where the onlyColorLayers property was used are removed in this pr. Therefore I also remove this option to not have unused code lines

} = {},
): Array<DataLayerType> {
const dataLayers = options.onlyColorLayers
? getColorLayers(dataset)
: dataset.dataSource.dataLayers;
const dataLayers = dataset.dataSource.dataLayers;
const layerSettings = datasetConfiguration.layers;
return dataLayers.filter((layer) => {
const settings = layerSettings[layer.name];
Expand Down