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

Do not load data for disabled or invisible layers #4202

Merged
merged 5 commits into from
Jul 29, 2019
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-
Expand Down
25 changes: 23 additions & 2 deletions frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 {};
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -101,7 +102,7 @@ export default class LayerRenderingManager {
lastAreas: OrthoViewMap<Area>;
lastZoomedMatrix: M4x4;
lastViewMode: ViewMode;
lastIsInvisible: boolean;
lastIsVisible: boolean;
textureBucketManager: TextureBucketManager;
textureWidth: number;
cube: DataCube;
Expand All @@ -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<Function> = new LatestTaskExecutor();
Expand All @@ -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() {
Expand Down Expand Up @@ -180,31 +178,30 @@ 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) ||
!_.isEqual(subBucketLocality, this.lastSubBucketLocality) ||
(isArbitrary && !_.isEqual(this.lastZoomedMatrix, matrix)) ||
viewMode !== this.lastViewMode ||
sphericalCapRadius !== this.lastSphericalCapRadius ||
isInvisible !== this.lastIsInvisible ||
isVisible !== this.lastIsVisible ||
this.needsRefresh
) {
this.lastSubBucketLocality = subBucketLocality;
this.lastAreas = areas;
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<ArrayBuffer> = Promise.resolve(dummyBuffer);

if (!isInvisible) {
if (isVisible) {
const { initializedGpuFactor } = state.temporaryConfiguration.gpuSetup;
pickingPromise = this.latestTaskExecutor.schedule(() =>
asyncBucketPick(
Expand Down
1 change: 0 additions & 1 deletion frontend/javascripts/oxalis/model/data_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class DataLayer {
this.cube,
textureWidth,
dataTextureCount,
isSegmentation,
);
}

Expand Down
42 changes: 16 additions & 26 deletions frontend/javascripts/oxalis/model/sagas/prefetch_saga.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand All @@ -42,32 +38,26 @@ export function* watchDataRelevantChanges(): Saga<void> {
);
}

function* shouldPrefetchForDataLayer(dataLayer: DataLayer): Saga<boolean> {
const segmentationOpacity = yield* select(
state => state.datasetConfiguration.segmentationOpacity,
function* shouldPrefetchForDataLayer(dataLayer: DataLayer, viewMode: ViewMode): Saga<boolean> {
// 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<void> {
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);
}
}
}

Expand Down Expand Up @@ -154,7 +144,7 @@ export function* prefetchForPlaneMode(layer: DataLayer, previousProperties: Obje
}

export function* prefetchForArbitraryMode(
layer: DataLayerType,
layer: DataLayer,
previousProperties: Object,
): Saga<void> {
const position = yield* select(state => getPosition(state.flycam));
Expand Down
42 changes: 19 additions & 23 deletions frontend/javascripts/test/sagas/prefetch_saga.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});