diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb795910d18..1b30438ce55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
-
### Fixed
--
+- Data for disabled or invisible layers will no longer be downloaded, saving bandwidth and speeding up webKnossos in general. [#4202](https://github.com/scalableminds/webknossos/pull/4202)
### Removed
-
diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
index 28077cea768..f85dedde6a6 100644
--- a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
+++ b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
@@ -10,9 +10,14 @@ import type {
APISegmentationLayer,
ElementClass,
} from "admin/api_flow_types";
-import type { Settings, DataLayerType } from "oxalis/store";
+import type { Settings, DataLayerType, DatasetConfiguration } from "oxalis/store";
import ErrorHandling from "libs/error_handling";
-import constants, { ViewModeValues, type Vector3, Vector3Indicies } from "oxalis/constants";
+import constants, {
+ ViewModeValues,
+ type Vector3,
+ Vector3Indicies,
+ type ViewMode,
+} from "oxalis/constants";
import { aggregateBoundingBox } from "libs/utils";
import { formatExtentWithLength, formatNumberToLength } from "libs/format_utils";
import messages from "messages";
@@ -310,4 +315,20 @@ export function getResolutionByMax(dataset: APIDataset, maxDim: number): Vector3
return keyedResolutionsByMax[maxDim];
}
+export function isLayerVisible(
+ dataset: APIDataset,
+ layerName: string,
+ datasetConfiguration: DatasetConfiguration,
+ viewMode: ViewMode,
+): boolean {
+ const isPlaneMode = constants.MODES_PLANE.includes(viewMode);
+ if (isSegmentationLayer(dataset, layerName)) {
+ // Segmentation layers are only displayed in plane mode for now
+ return datasetConfiguration.segmentationOpacity > 0 && isPlaneMode;
+ } else {
+ const layerConfig = datasetConfiguration.layers[layerName];
+ return !layerConfig.isDisabled && layerConfig.alpha > 0;
+ }
+}
+
export default {};
diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.js b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.js
index 8b9c04d7566..15aba4c03f1 100644
--- a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.js
+++ b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.js
@@ -18,6 +18,7 @@ import {
getResolutions,
getByteCount,
getElementClass,
+ isLayerVisible,
} from "oxalis/model/accessors/dataset_accessor";
import AsyncBucketPickerWorker from "oxalis/workers/async_bucket_picker.worker";
import type DataCube from "oxalis/model/bucket_data_handling/data_cube";
@@ -101,7 +102,7 @@ export default class LayerRenderingManager {
lastAreas: OrthoViewMap;
lastZoomedMatrix: M4x4;
lastViewMode: ViewMode;
- lastIsInvisible: boolean;
+ lastIsVisible: boolean;
textureBucketManager: TextureBucketManager;
textureWidth: number;
cube: DataCube;
@@ -110,7 +111,6 @@ export default class LayerRenderingManager {
cachedAnchorPoint: Vector4 = [0, 0, 0, 0];
name: string;
- isSegmentation: boolean;
needsRefresh: boolean = false;
currentBucketPickerTick: number = 0;
latestTaskExecutor: LatestTaskExecutor = new LatestTaskExecutor();
@@ -121,14 +121,12 @@ export default class LayerRenderingManager {
cube: DataCube,
textureWidth: number,
dataTextureCount: number,
- isSegmentation: boolean,
) {
this.name = name;
this.pullQueue = pullQueue;
this.cube = cube;
this.textureWidth = textureWidth;
this.dataTextureCount = dataTextureCount;
- this.isSegmentation = isSegmentation;
}
refresh() {
@@ -180,8 +178,7 @@ export default class LayerRenderingManager {
const { viewMode } = state.temporaryConfiguration;
const isArbitrary = constants.MODES_ARBITRARY.includes(viewMode);
const { sphericalCapRadius } = state.userConfiguration;
- const isInvisible =
- this.isSegmentation && (datasetConfiguration.segmentationOpacity === 0 || isArbitrary);
+ const isVisible = isLayerVisible(dataset, this.name, datasetConfiguration, viewMode);
if (
isAnchorPointNew ||
!_.isEqual(areas, this.lastAreas) ||
@@ -189,7 +186,7 @@ export default class LayerRenderingManager {
(isArbitrary && !_.isEqual(this.lastZoomedMatrix, matrix)) ||
viewMode !== this.lastViewMode ||
sphericalCapRadius !== this.lastSphericalCapRadius ||
- isInvisible !== this.lastIsInvisible ||
+ isVisible !== this.lastIsVisible ||
this.needsRefresh
) {
this.lastSubBucketLocality = subBucketLocality;
@@ -197,14 +194,14 @@ export default class LayerRenderingManager {
this.lastZoomedMatrix = matrix;
this.lastViewMode = viewMode;
this.lastSphericalCapRadius = sphericalCapRadius;
- this.lastIsInvisible = isInvisible;
+ this.lastIsVisible = isVisible;
this.needsRefresh = false;
this.currentBucketPickerTick++;
this.pullQueue.clear();
let pickingPromise: Promise = Promise.resolve(dummyBuffer);
- if (!isInvisible) {
+ if (isVisible) {
const { initializedGpuFactor } = state.temporaryConfiguration.gpuSetup;
pickingPromise = this.latestTaskExecutor.schedule(() =>
asyncBucketPick(
diff --git a/frontend/javascripts/oxalis/model/data_layer.js b/frontend/javascripts/oxalis/model/data_layer.js
index ff3cc3526c5..a305d4826c9 100644
--- a/frontend/javascripts/oxalis/model/data_layer.js
+++ b/frontend/javascripts/oxalis/model/data_layer.js
@@ -64,7 +64,6 @@ class DataLayer {
this.cube,
textureWidth,
dataTextureCount,
- isSegmentation,
);
}
diff --git a/frontend/javascripts/oxalis/model/sagas/prefetch_saga.js b/frontend/javascripts/oxalis/model/sagas/prefetch_saga.js
index fced055a7da..bca378e2f60 100644
--- a/frontend/javascripts/oxalis/model/sagas/prefetch_saga.js
+++ b/frontend/javascripts/oxalis/model/sagas/prefetch_saga.js
@@ -1,6 +1,6 @@
// @flow
import { FlycamActions } from "oxalis/model/actions/flycam_actions";
-import type { OxalisState, DataLayerType } from "oxalis/store";
+import type { OxalisState } from "oxalis/store";
import { PrefetchStrategyArbitrary } from "oxalis/model/bucket_data_handling/prefetch_strategy_arbitrary";
import {
PrefetchStrategySkeleton,
@@ -13,14 +13,10 @@ import {
getRequestLogZoomStep,
getAreasFromState,
} from "oxalis/model/accessors/flycam_accessor";
-import {
- isSegmentationLayer,
- getResolutions,
- getColorLayers,
-} from "oxalis/model/accessors/dataset_accessor";
+import { getResolutions, isLayerVisible } from "oxalis/model/accessors/dataset_accessor";
import DataLayer from "oxalis/model/data_layer";
import Model from "oxalis/model";
-import constants, { type Vector3 } from "oxalis/constants";
+import constants, { type Vector3, type ViewMode } from "oxalis/constants";
const PREFETCH_THROTTLE_TIME = 50;
const DIRECTION_VECTOR_SMOOTHER = 0.125;
@@ -42,32 +38,26 @@ export function* watchDataRelevantChanges(): Saga {
);
}
-function* shouldPrefetchForDataLayer(dataLayer: DataLayer): Saga {
- const segmentationOpacity = yield* select(
- state => state.datasetConfiguration.segmentationOpacity,
+function* shouldPrefetchForDataLayer(dataLayer: DataLayer, viewMode: ViewMode): Saga {
+ // There is no need to prefetch data for layers that are not visible
+ return yield* select(state =>
+ isLayerVisible(state.dataset, dataLayer.name, state.datasetConfiguration, viewMode),
);
- const isSegmentationVisible = segmentationOpacity !== 0;
- const isSegmentation = yield* select(state => isSegmentationLayer(state.dataset, dataLayer.name));
- // There is no need to prefetch data for segmentation layers that are not visible
- return !isSegmentation || isSegmentationVisible;
}
export function* triggerDataPrefetching(previousProperties: Object): Saga {
- const currentViewMode = yield* select(state => state.temporaryConfiguration.viewMode);
- const isPlaneMode = constants.MODES_PLANE.includes(currentViewMode);
+ const viewMode = yield* select(state => state.temporaryConfiguration.viewMode);
+ const isPlaneMode = constants.MODES_PLANE.includes(viewMode);
- if (isPlaneMode) {
- const dataLayers = yield* call([Model, Model.getAllLayers]);
- for (const dataLayer of dataLayers) {
- if (yield* call(shouldPrefetchForDataLayer, dataLayer)) {
+ const dataLayers = yield* call([Model, Model.getAllLayers]);
+ for (const dataLayer of dataLayers) {
+ if (yield* call(shouldPrefetchForDataLayer, dataLayer, viewMode)) {
+ if (isPlaneMode) {
yield* call(prefetchForPlaneMode, dataLayer, previousProperties);
+ } else {
+ yield* call(prefetchForArbitraryMode, dataLayer, previousProperties);
}
}
- } else {
- const dataLayers = yield* select(state => getColorLayers(state.dataset));
- for (const colorLayer of dataLayers) {
- yield* call(prefetchForArbitraryMode, colorLayer, previousProperties);
- }
}
}
@@ -154,7 +144,7 @@ export function* prefetchForPlaneMode(layer: DataLayer, previousProperties: Obje
}
export function* prefetchForArbitraryMode(
- layer: DataLayerType,
+ layer: DataLayer,
previousProperties: Object,
): Saga {
const position = yield* select(state => getPosition(state.flycam));
diff --git a/frontend/javascripts/test/sagas/prefetch_saga.spec.js b/frontend/javascripts/test/sagas/prefetch_saga.spec.js
index 9eb70255163..ba8918cdc8f 100644
--- a/frontend/javascripts/test/sagas/prefetch_saga.spec.js
+++ b/frontend/javascripts/test/sagas/prefetch_saga.spec.js
@@ -24,51 +24,47 @@ const {
test("Prefetch saga should trigger prefetching for the correct view mode", t => {
// These are not the correct layers (those should be data_layer instances)
// but they are sufficient to test this saga
- const colorLayers = DATASET.dataSource.dataLayers.filter(layer => layer.category === "color");
+ const allLayers = DATASET.dataSource.dataLayers;
const previousProperties = {};
const saga = triggerDataPrefetching(previousProperties);
saga.next(); // select viewMode
- saga.next(constants.MODE_ARBITRARY);
+ saga.next(constants.MODE_ARBITRARY); // Model.getAllLayers
+ saga.next(allLayers); // shouldPrefetchForDataLayer
expectValueDeepEqual(
t,
- saga.next(colorLayers),
- call(prefetchForArbitraryMode, colorLayers[0], previousProperties),
+ saga.next(true),
+ call(prefetchForArbitraryMode, allLayers[0], previousProperties),
);
- t.is(saga.next().done, true);
+ // Saga should not be over as there are multiple layers
+ t.is(saga.next().done, false);
});
test("Prefetch saga should not prefetch segmentation if it is not visible", t => {
// These are not the correct layers (those should be data_layer instances)
// but they are sufficient to test this saga
const allLayers = DATASET.dataSource.dataLayers;
- const colorLayers = DATASET.dataSource.dataLayers.filter(layer => layer.category === "color");
const previousProperties = {};
+ const viewMode = constants.MODE_PLANE_TRACING;
const saga = triggerDataPrefetching(previousProperties);
saga.next(); // select viewMode
- expectValueDeepEqual(
- t,
- saga.next(constants.MODE_PLANE_TRACING),
- call([Model, Model.getAllLayers]),
- );
+ expectValueDeepEqual(t, saga.next(viewMode), call([Model, Model.getAllLayers]));
- let shouldPrefetchSaga = execCall(t, saga.next(allLayers)); // shouldPrefetchForDataLayer
- shouldPrefetchSaga.next();
- shouldPrefetchSaga.next(0); // select segmentationOpacity
- const shouldPrefetchColorLayer = shouldPrefetchSaga.next(false).value;
- // The color layer should be prefetched, although the segmentationOpacity is 0
- t.is(shouldPrefetchColorLayer, true);
+ let shouldPrefetchSaga = execCall(t, saga.next(allLayers, viewMode)); // shouldPrefetchForDataLayer
+ shouldPrefetchSaga.next(); // isLayerVisible
+ const shouldPrefetchDataLayer = shouldPrefetchSaga.next(true).value;
+ // The color layer should be prefetched, because it is visible
+ t.is(shouldPrefetchDataLayer, true);
expectValueDeepEqual(
t,
- saga.next(shouldPrefetchColorLayer),
- call(prefetchForPlaneMode, colorLayers[0], previousProperties),
+ saga.next(shouldPrefetchDataLayer),
+ call(prefetchForPlaneMode, allLayers[0], previousProperties),
);
shouldPrefetchSaga = execCall(t, saga.next()); // shouldPrefetchForDataLayer
- shouldPrefetchSaga.next();
- shouldPrefetchSaga.next(0); // select segmentationOpacity
- const shouldPrefetchSegmentationLayer = shouldPrefetchSaga.next(true).value;
+ shouldPrefetchSaga.next(); // isLayerVisible
+ const shouldPrefetchSegmentationLayer = shouldPrefetchSaga.next(false).value;
// The segmentation layer should not be prefetched because it is not visible
t.is(shouldPrefetchSegmentationLayer, false);
- // So the saga should be over afterwards, no prefetch call
+ // The saga should be over afterwards - no prefetch call
t.is(saga.next(shouldPrefetchSegmentationLayer).done, true);
});