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

Make layer order editable via drag and drop #7188

Merged
merged 15 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
[Commits](https://github.com/scalableminds/webknossos/compare/23.08.0...HEAD)

### Added
- Added the option to change the ordering of color layers via drag and drop. This is useful when using the cover blend mode. [#7188](https://github.com/scalableminds/webknossos/pull/7188)
- Added configuration to require users' emails to be verified, added flow to verify emails via link. [#7161](https://github.com/scalableminds/webknossos/pull/7161)

### Changed
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const settingsTooltips: Partial<Record<keyof RecommendedConfiguration, st
zoom: "Zoom in or out in the data viewports",
displayScalebars: "Show a scale in the lower-right corner of each viewport",
blendMode:
"Set the blend mode for the dataset. The additive mode (default) adds the data values of all color layers. In cover mode, color layers are rendered on top of each other so that the data values of lower color layers are hidden by values of higher layers.",
"Set the blend mode for the dataset. The additive mode (default) adds the data values of all color layers. In cover mode, color layers are rendered on top of each other so that the data values of lower color layers are hidden by values of higher layers. Cover mode enables reordering of color layers.",
renderWatermark: "Show a WEBKNOSSOS logo in the lower-left corner of each screenshot.",
antialiasRendering: "Antialias rendering (can impact performance)",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,23 @@ class PlaneMaterialFactory {
),
);

let oldLayerOrder: Array<string> = [];
Copy link
Member

Choose a reason for hiding this comment

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

Is this ever written to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right, I missed that. This was designed to catch cases where the user drops the color layer at the same location to avoid shader recompilation. I now added a line that keep a copy of the previous layer order 👍 .

this.storePropertyUnsubscribers.push(
listenToStoreProperty(
(state) => state.datasetConfiguration.colorLayerOrder,
(colorLayerOrder) => {
let changedLayerOrder =
colorLayerOrder.length !== oldLayerOrder.length ||
colorLayerOrder.some((layerName, index) => layerName !== oldLayerOrder[index]);
if (changedLayerOrder) {
oldLayerOrder = [...colorLayerOrder];
this.recomputeShaders();
}
app.vent.emit("rerender");
},
false,
),
);
if (Model.hasSegmentationLayer()) {
this.storePropertyUnsubscribers.push(
listenToStoreProperty(
Expand Down Expand Up @@ -864,7 +881,9 @@ class PlaneMaterialFactory {
app.vent.emit("rerender");
}, RECOMPILATION_THROTTLE_TIME);

getLayersToRender(maximumLayerCountToRender: number): [Array<string>, Array<string>, number] {
getLayersToRender(
maximumLayerCountToRender: number,
): [Array<string>, Array<string>, Array<string>, number] {
// 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.
Expand All @@ -874,21 +893,28 @@ class PlaneMaterialFactory {
// The first array contains the color layer names and the second the segmentation layer names.
// The third parameter returns the number of globally available layers (this is not always equal
// to the sum of the lengths of the first two arrays, as not all layers might be rendered.)
const state = Store.getState();
const sanitizedOrderedColorLayerNames =
state.datasetConfiguration.colorLayerOrder.map(sanitizeName);
const colorLayerNames = getSanitizedColorLayerNames();
const segmentationLayerNames = Model.getSegmentationLayers().map((layer) =>
sanitizeName(layer.name),
);
const globalLayerCount = colorLayerNames.length + segmentationLayerNames.length;
if (maximumLayerCountToRender <= 0) {
return [[], [], globalLayerCount];
return [[], [], [], globalLayerCount];
}

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

const state = Store.getState();
const enabledLayers = getEnabledLayers(state.dataset, state.datasetConfiguration, {}).map(
({ name, category }) => ({ name, isSegmentationLayer: category === "segmentation" }),
);
Expand Down Expand Up @@ -918,7 +944,12 @@ class PlaneMaterialFactory {
({ isSegmentationLayer }) => !isSegmentationLayer,
).map((layers) => layers.map(({ name }) => sanitizeName(name)));

return [sanitizedColorLayerNames, sanitizedSegmentationLayerNames, globalLayerCount];
return [
sanitizedColorLayerNames,
sanitizedSegmentationLayerNames,
sanitizedOrderedColorLayerNames,
globalLayerCount,
];
}

onDisableLayer = (layerName: string, isSegmentationLayer: boolean) => {
Expand All @@ -939,7 +970,7 @@ class PlaneMaterialFactory {

getFragmentShaderWithUniforms(): [string, Uniforms] {
const { maximumLayerCountToRender } = Store.getState().temporaryConfiguration.gpuSetup;
const [colorLayerNames, segmentationLayerNames, globalLayerCount] =
const [colorLayerNames, segmentationLayerNames, orderedColorLayerNames, globalLayerCount] =
this.getLayersToRender(maximumLayerCountToRender);

const availableLayerNames = colorLayerNames.concat(segmentationLayerNames);
Expand All @@ -953,6 +984,7 @@ class PlaneMaterialFactory {
const datasetScale = dataset.dataSource.scale;
const code = getMainFragmentShader({
globalLayerCount,
orderedColorLayerNames,
colorLayerNames,
segmentationLayerNames,
textureLayerInfos,
Expand All @@ -978,7 +1010,7 @@ class PlaneMaterialFactory {

getVertexShader(): string {
const { maximumLayerCountToRender } = Store.getState().temporaryConfiguration.gpuSetup;
const [colorLayerNames, segmentationLayerNames, globalLayerCount] =
const [colorLayerNames, segmentationLayerNames, orderedColorLayerNames, globalLayerCount] =
this.getLayersToRender(maximumLayerCountToRender);

const textureLayerInfos = getTextureLayerInfos();
Expand All @@ -987,6 +1019,7 @@ class PlaneMaterialFactory {

return getMainVertexShader({
globalLayerCount,
orderedColorLayerNames,
colorLayerNames,
segmentationLayerNames,
textureLayerInfos,
Expand Down
27 changes: 26 additions & 1 deletion frontend/javascripts/oxalis/model_initialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getSegmentationLayers,
getLayerByName,
getSegmentationLayerByName,
isColorLayer,
} from "oxalis/model/accessors/dataset_accessor";
import { getNullableSkeletonTracing } from "oxalis/model/accessors/skeletontracing_accessor";
import { getServerVolumeTracings } from "oxalis/model/accessors/volumetracing_accessor";
Expand Down Expand Up @@ -180,11 +181,15 @@ export async function initialize(
dataset,
initialDatasetSettings,
);
const initialDatasetSettingsWithLayerOrder = ensureDatasetSettingsHasLayerOrder(
annotationSpecificDatasetSettings,
dataset,
);
const enforcedInitialUserSettings =
enforcePricingRestrictionsOnUserConfiguration(initialUserSettings);
initializeSettings(
enforcedInitialUserSettings,
annotationSpecificDatasetSettings,
initialDatasetSettingsWithLayerOrder,
initialDatasetSettings,
);
let initializationInformation = null;
Expand Down Expand Up @@ -787,6 +792,26 @@ function enforcePricingRestrictionsOnUserConfiguration(
return userConfiguration;
}

function ensureDatasetSettingsHasLayerOrder(
datasetConfiguration: DatasetConfiguration,
dataset: APIDataset,
): DatasetConfiguration {
const colorLayerNames = _.keys(datasetConfiguration.layers).filter((layerName) =>
isColorLayer(dataset, layerName),
);
const onlyExistingLayers =
datasetConfiguration?.colorLayerOrder?.filter(
(layerName) => colorLayerNames.indexOf(layerName) >= 0,
) || [];
if (onlyExistingLayers.length < colorLayerNames.length) {
return {
...datasetConfiguration,
colorLayerOrder: colorLayerNames,
};
}
return { ...datasetConfiguration, colorLayerOrder: onlyExistingLayers };
}

function applyAnnotationSpecificViewConfiguration(
annotation: APIAnnotation | null | undefined,
dataset: APIDataset,
Expand Down
6 changes: 4 additions & 2 deletions frontend/javascripts/oxalis/shaders/main_data_shaders.glsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
type Params = {
globalLayerCount: number;
colorLayerNames: string[];
orderedColorLayerNames: string[];
segmentationLayerNames: string[];
textureLayerInfos: Record<string, { packingDegree: number; dataTextureCount: number }>;
resolutionsCount: number;
Expand Down Expand Up @@ -202,7 +203,8 @@ void main() {

// Get Color Value(s)
vec3 color_value = vec3(0.0);
<% _.each(colorLayerNames, function(name, layerIndex) { %>
<% _.each(orderedColorLayerNames, function(name, layerIndex) { %>
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
<% const color_layer_index = colorLayerNames.indexOf(name); %>
float <%= name %>_effective_alpha = <%= name %>_alpha * (1. - <%= name %>_unrenderable);
if (<%= name %>_effective_alpha > 0.) {
// Get grayscale value for <%= name %>
Expand All @@ -216,7 +218,7 @@ void main() {
if (!isOutsideOfBoundingBox(transformedCoordUVW)) {
MaybeFilteredColor maybe_filtered_color =
getMaybeFilteredColorOrFallback(
<%= formatNumberAsGLSLFloat(layerIndex) %>,
<%= formatNumberAsGLSLFloat(color_layer_index) %>,
<%= name %>_data_texture_width,
<%= formatNumberAsGLSLFloat(textureLayerInfos[name].packingDegree) %>,
transformedCoordUVW,
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/oxalis/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ export type DatasetConfiguration = {
readonly fourBit: boolean;
readonly interpolation: boolean;
readonly layers: Record<string, DatasetLayerConfiguration>;
readonly colorLayerOrder: Array<string>;
readonly position?: Vector3;
readonly zoom?: number;
readonly rotation?: Vector3;
Expand Down
Loading