From 0d72d3d6a0f08ba62886ac13322f5a0418d02a70 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Mon, 2 Jul 2018 11:27:34 +0200 Subject: [PATCH] No four bit segmentation (#2828) * don't request segmentation as four bit and also don't send fourBit flag when sending buckets * adapt and add new tests for four-bit-mode * fix linting --- .../bucket_data_handling/bucket_builder.js | 32 ------- .../bucket_data_handling/wkstore_adapter.js | 55 ++++++++--- .../oxalis/model/sagas/update_actions.js | 6 +- .../binary/layers/wkstore_adapter.spec.js | 91 ++++++++++++++----- 4 files changed, 114 insertions(+), 70 deletions(-) delete mode 100644 app/assets/javascripts/oxalis/model/bucket_data_handling/bucket_builder.js diff --git a/app/assets/javascripts/oxalis/model/bucket_data_handling/bucket_builder.js b/app/assets/javascripts/oxalis/model/bucket_data_handling/bucket_builder.js deleted file mode 100644 index c3d5b9aa490..00000000000 --- a/app/assets/javascripts/oxalis/model/bucket_data_handling/bucket_builder.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * bucket_builder.js - * @flow - */ - -import Store from "oxalis/store"; -import type { Vector3, Vector4 } from "oxalis/constants"; -import constants from "oxalis/constants"; -import { bucketPositionToGlobalAddress } from "oxalis/model/helpers/position_converter"; - -export type BucketInfo = { - position: Vector3, - zoomStep: number, - cubeSize: number, - fourBit: boolean, -}; - -// Converts a zoomed address ([x, y, z, zoomStep] array) into a bucket JSON -// object as expected by the server on bucket request -export default { - fromZoomedAddress(zoomedAddress: Vector4, resolutions: Array): BucketInfo { - const position = bucketPositionToGlobalAddress(zoomedAddress, resolutions); - const zoomStep = zoomedAddress[3]; - - return { - position, - zoomStep, - cubeSize: constants.BUCKET_WIDTH, - fourBit: Store.getState().datasetConfiguration.fourBit, - }; - }, -}; diff --git a/app/assets/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.js b/app/assets/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.js index 881554eb43f..c8e3b185ff7 100644 --- a/app/assets/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.js +++ b/app/assets/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.js @@ -1,36 +1,65 @@ // @flow import Base64 from "base64-js"; - -import BucketBuilder from "oxalis/model/bucket_data_handling/bucket_builder"; import Request from "libs/request"; import Store from "oxalis/store"; import { pushSaveQueueAction } from "oxalis/model/actions/save_actions"; import { updateBucket } from "oxalis/model/sagas/update_actions"; import Utils from "libs/utils"; import { doWithToken } from "admin/admin_rest_api"; -import type { BucketInfo } from "oxalis/model/bucket_data_handling/bucket_builder"; import type { DataBucket } from "oxalis/model/bucket_data_handling/bucket"; -import type { Vector4 } from "oxalis/constants"; +import type { Vector3, Vector4 } from "oxalis/constants"; import type { DataLayerType } from "oxalis/store"; -import { getResolutions } from "oxalis/model/accessors/dataset_accessor.js"; +import { getResolutions, isSegmentationLayer } from "oxalis/model/accessors/dataset_accessor.js"; +import { bucketPositionToGlobalAddress } from "oxalis/model/helpers/position_converter"; +import constants from "oxalis/constants"; export const REQUEST_TIMEOUT = 30000; -function buildBuckets(layerInfo: DataLayerType, batch: Array): Array { - return batch.map((bucketAddress: Vector4) => - BucketBuilder.fromZoomedAddress(bucketAddress, getResolutions(Store.getState().dataset)), - ); +export type SendBucketInfo = { + position: Vector3, + zoomStep: number, + cubeSize: number, +}; + +type RequestBucketInfo = { + ...SendBucketInfo, + fourBit: boolean, +}; + +// Converts a zoomed address ([x, y, z, zoomStep] array) into a bucket JSON +// object as expected by the server on bucket request +const createRequestBucketInfo = ( + zoomedAddress: Vector4, + resolutions: Array, + fourBit: boolean, +): RequestBucketInfo => ({ + ...createSendBucketInfo(zoomedAddress, resolutions), + fourBit, +}); + +function createSendBucketInfo(zoomedAddress: Vector4, resolutions: Array): SendBucketInfo { + return { + position: bucketPositionToGlobalAddress(zoomedAddress, resolutions), + zoomStep: zoomedAddress[3], + cubeSize: constants.BUCKET_WIDTH, + }; } export async function requestFromStore( layerInfo: DataLayerType, batch: Array, ): Promise { - const bucketInfo = buildBuckets(layerInfo, batch); + const fourBit = + Store.getState().datasetConfiguration.fourBit && + !isSegmentationLayer(Store.getState().dataset, layerInfo.name); + const resolutions = getResolutions(Store.getState().dataset); + const bucketInfo = batch.map(zoomedAddress => + createRequestBucketInfo(zoomedAddress, resolutions, fourBit), + ); + return doWithToken(async token => { const state = Store.getState(); - const wasFourBit = state.datasetConfiguration.fourBit; const datasetName = state.dataset.name; const dataStoreUrl = state.dataset.dataStore.url; @@ -43,7 +72,7 @@ export async function requestFromStore( ); let result = new Uint8Array(responseBuffer); - if (wasFourBit) { + if (fourBit) { result = decodeFourBit(result); } return result; @@ -76,7 +105,7 @@ export async function sendToStore(batch: Array): Promise { // eslint-disable-next-line no-await-in-loop if (counter % YIELD_AFTER_X_BUCKETS === 0) await Utils.sleep(1); const bucketData = bucket.getData(); - const bucketInfo = BucketBuilder.fromZoomedAddress( + const bucketInfo = createSendBucketInfo( bucket.zoomedAddress, getResolutions(Store.getState().dataset), ); diff --git a/app/assets/javascripts/oxalis/model/sagas/update_actions.js b/app/assets/javascripts/oxalis/model/sagas/update_actions.js index 492fff60014..1f2a95279c0 100644 --- a/app/assets/javascripts/oxalis/model/sagas/update_actions.js +++ b/app/assets/javascripts/oxalis/model/sagas/update_actions.js @@ -10,7 +10,7 @@ import type { TreeGroupType, } from "oxalis/store"; import type { Vector3 } from "oxalis/constants"; -import type { BucketInfo } from "oxalis/model/bucket_data_handling/bucket_builder"; +import type { SendBucketInfo } from "oxalis/model/bucket_data_handling/wkstore_adapter"; import { convertFrontendBoundingBoxToServer } from "oxalis/model/reducers/reducer_helpers"; export type NodeWithTreeIdType = { treeId: number } & NodeType; @@ -106,7 +106,7 @@ type UpdateVolumeTracingUpdateAction = { }; type UpdateBucketUpdateAction = { name: "updateBucket", - value: BucketInfo & { + value: SendBucketInfo & { base64Data: string, }, }; @@ -285,7 +285,7 @@ export function updateVolumeTracing( }, }; } -export function updateBucket(bucketInfo: BucketInfo, base64Data: string) { +export function updateBucket(bucketInfo: SendBucketInfo, base64Data: string) { return { name: "updateBucket", value: Object.assign({}, bucketInfo, { diff --git a/app/assets/javascripts/test/model/binary/layers/wkstore_adapter.spec.js b/app/assets/javascripts/test/model/binary/layers/wkstore_adapter.spec.js index c6d04bfb650..cf2205e5918 100644 --- a/app/assets/javascripts/test/model/binary/layers/wkstore_adapter.spec.js +++ b/app/assets/javascripts/test/model/binary/layers/wkstore_adapter.spec.js @@ -15,6 +15,12 @@ const RequestMock = { receiveJSON: sinon.stub(), }; const { dataSource } = datasetServerObject; +let _fourBit = false; + +function setFourBit(bool) { + _fourBit = bool; +} + const StoreMock = { getState: () => ({ dataset: { @@ -25,7 +31,7 @@ const StoreMock = { }, dataSource, }, - datasetConfiguration: { fourBit: false }, + datasetConfiguration: { fourBit: _fourBit }, }), dispatch: sinon.stub(), }; @@ -38,26 +44,21 @@ const { requestFromStore, sendToStore } = mockRequire.reRequire( "oxalis/model/bucket_data_handling/wkstore_adapter", ); -const layerInfo = { - name: "layername", - category: "color", - elementClass: "uint16", - resolutions: [[1, 1, 1], [2, 2, 2], [4, 4, 4], [8, 8, 8], [16, 16, 16], [32, 32, 32]], -}; const tokenResponse = { token: "token" }; test.beforeEach(t => { RequestMock.receiveJSON = sinon.stub(); RequestMock.receiveJSON.returns(Promise.resolve(tokenResponse)); - t.context.layer = layerInfo; + t.context.layer = dataSource.dataLayers[0]; + t.context.segmentationLayer = dataSource.dataLayers[1]; }); test.serial("Initialization should set the attributes correctly", t => { const { layer } = t.context; - t.is(layer.name, "layername"); + t.is(layer.name, "color"); t.is(layer.category, "color"); - t.is(getBitDepth(layer), 16); + t.is(getBitDepth(layer), 8); }); function prepare() { @@ -95,25 +96,29 @@ test.serial("requestFromStore: Token Handling should re-request a token when it' t.is(RequestMock.sendJSONReceiveArraybuffer.callCount, 2); const url = RequestMock.sendJSONReceiveArraybuffer.getCall(0).args[0]; - t.is(url, "url/data/datasets/dataSet/layers/layername/data?token=token"); + t.is(url, "url/data/datasets/dataSet/layers/color/data?token=token"); const url2 = RequestMock.sendJSONReceiveArraybuffer.getCall(1).args[0]; - t.is(url2, "url/data/datasets/dataSet/layers/layername/data?token=token2"); + t.is(url2, "url/data/datasets/dataSet/layers/color/data?token=token2"); }); }); -test.serial("requestFromStore: Request Handling: should pass the correct request parameters", t => { - const { layer } = t.context; - const { batch } = prepare(); - - const expectedUrl = "url/data/datasets/dataSet/layers/layername/data?token=token2"; - const expectedOptions = { +function createExpectedOptions(fourBit: boolean = false) { + return { data: [ - { position: [0, 0, 0], zoomStep: 0, cubeSize: 32, fourBit: false }, - { position: [64, 64, 64], zoomStep: 1, cubeSize: 32, fourBit: false }, + { position: [0, 0, 0], zoomStep: 0, cubeSize: 32, fourBit }, + { position: [64, 64, 64], zoomStep: 1, cubeSize: 32, fourBit }, ], timeout: 30000, }; +} + +test.serial("requestFromStore: Request Handling: should pass the correct request parameters", t => { + const { layer } = t.context; + const { batch } = prepare(); + + const expectedUrl = "url/data/datasets/dataSet/layers/color/data?token=token2"; + const expectedOptions = createExpectedOptions(); return requestFromStore(layer, batch).then(() => { t.is(RequestMock.sendJSONReceiveArraybuffer.callCount, 1); @@ -124,6 +129,50 @@ test.serial("requestFromStore: Request Handling: should pass the correct request }); }); +test.serial( + "requestFromStore: Request Handling: four bit mode should be respected for color layers", + async t => { + setFourBit(true); + // test four bit color and 8 bit seg + const { layer } = t.context; + const { batch } = prepare(); + + const expectedUrl = "url/data/datasets/dataSet/layers/color/data?token=token2"; + const expectedOptions = createExpectedOptions(true); + + await requestFromStore(layer, batch).then(() => { + t.is(RequestMock.sendJSONReceiveArraybuffer.callCount, 1); + + const [url, options] = RequestMock.sendJSONReceiveArraybuffer.getCall(0).args; + t.is(url, expectedUrl); + t.deepEqual(options, expectedOptions); + }); + + setFourBit(false); + }, +); + +test.serial( + "requestFromStore: Request Handling: four bit mode should not be respected for segmentation layers", + async t => { + setFourBit(true); + const { segmentationLayer } = t.context; + + const { batch } = prepare(); + const expectedUrl = "url/data/datasets/dataSet/layers/segmentation/data?token=token2"; + const expectedOptions = createExpectedOptions(false); + + await requestFromStore(segmentationLayer, batch).then(() => { + t.is(RequestMock.sendJSONReceiveArraybuffer.callCount, 1); + + const [url, options] = RequestMock.sendJSONReceiveArraybuffer.getCall(0).args; + t.is(url, expectedUrl); + t.deepEqual(options, expectedOptions); + }); + setFourBit(false); + }, +); + test.serial("sendToStore: Request Handling should send the correct request parameters", t => { const data = new Uint8Array(2); const bucket1 = new DataBucket(8, [0, 0, 0, 0], null); @@ -144,7 +193,6 @@ test.serial("sendToStore: Request Handling should send the correct request param position: [0, 0, 0], zoomStep: 0, cubeSize: 32, - fourBit: false, base64Data: Base64.fromByteArray(data), }, }, @@ -154,7 +202,6 @@ test.serial("sendToStore: Request Handling should send the correct request param position: [64, 64, 64], zoomStep: 1, cubeSize: 32, - fourBit: false, base64Data: Base64.fromByteArray(data), }, },