Skip to content

Commit

Permalink
Include segmentation layers in handling maximum number of renderable …
Browse files Browse the repository at this point in the history
…layers restricted by hardware (#6211)

* innclude segmentation layer in function handling maximum number of renderable layers restricted by hardware

* pretty code

* add changelog entry

* hotfix invalid shader bug when switching between many segmentation layers

* apply renaming and fix duplication of layers

Co-authored-by: Philipp Otto <[email protected]>
  • Loading branch information
MichaelBuessemeyer and philippotto authored May 24, 2022
1 parent f9f0917 commit 5bd20c4
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 53 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added a batching mechanism to the task creation via NML to support uploading more than 100 NMLs at a time. [#6216](https://github.com/scalableminds/webknossos/pull/6216)
- 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)
- Selecting "Download" from the tracing actions now opens a new modal, which lets the user select data for download, start TIFF export jobs or copy code snippets to get started quickly with the webKonossos Python Client. [#6171](https://github.com/scalableminds/webknossos/pull/6171)
- Adding a New Volume Layer via the left border tab now gives the option to restrict resolutions for the new layer. [#6229](https://github.com/scalableminds/webknossos/pull/6229)

Expand All @@ -29,7 +30,6 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Fixed
- Fixed a bug in the task creation, where creation for some tasks with initial volume data would fail. [#6178](https://github.com/scalableminds/webknossos/pull/6178)
- Fixed a bug in the task cration, where creation for some tasks with initial volume data would fail. [#6178](https://github.com/scalableminds/webknossos/pull/6178)
- Fixed a rendering bug which could cause an incorrect magnification to be rendered in rare scenarios. [#6029](https://github.com/scalableminds/webknossos/pull/6029)
- Fixed a bug which could cause a segmentation layer's "ID mapping" dropdown to disappear. [#6215](https://github.com/scalableminds/webknossos/pull/6215)

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)));

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,
},
} = {},
): 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

0 comments on commit 5bd20c4

Please sign in to comment.