diff --git a/.circleci/config.yml b/.circleci/config.yml index f35317647f1..a70616eb2d6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -118,6 +118,7 @@ jobs: - run: name: Run end-to-end tests command: | + mkdir -p binaryData/Organization_X && chmod 777 binaryData/Organization_X for i in {1..3}; do # retry .circleci/not-on-master.sh docker-compose run e2e-tests && s=0 && break || s=$? done diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 091a8e5a9ce..8c6758f299e 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -36,6 +36,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Fix that scrolling in the trees and segments tab did not work while dragging. [#8162](https://github.com/scalableminds/webknossos/pull/8162) - Fixed that uploading a dataset which needs a conversion failed when the angstrom unit was configured for the conversion. [#8173](https://github.com/scalableminds/webknossos/pull/8173) - Fixed that the skeleton search did not automatically expand groups that contained the selected tree [#8129](https://github.com/scalableminds/webknossos/pull/8129) +- Fixed interactions in the trees and segments tab like the search due to a bug introduced by [#8162](https://github.com/scalableminds/webknossos/pull/8162). [#8186](https://github.com/scalableminds/webknossos/pull/8186) - Fixed a bug that zarr streaming version 3 returned the shape of mag (1, 1, 1) / the finest mag for all mags. [#8116](https://github.com/scalableminds/webknossos/pull/8116) - Fixed sorting of mags in outbound zarr streaming. [#8125](https://github.com/scalableminds/webknossos/pull/8125) - Fixed a bug where you could not create annotations for public datasets of other organizations. [#8107](https://github.com/scalableminds/webknossos/pull/8107) @@ -44,6 +45,9 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Removed unnecessary scrollbars in skeleton tab that occurred especially after resizing. [#8148](https://github.com/scalableminds/webknossos/pull/8148) - Deleting a bounding box is now possible independently of a visible segmentation layer. [#8164](https://github.com/scalableminds/webknossos/pull/8164) - S3-compliant object storages can now be accessed via HTTPS. [#8167](https://github.com/scalableminds/webknossos/pull/8167) +- Fixed that skeleton tree nodes were created with the wrong mag. [#8185](https://github.com/scalableminds/webknossos/pull/8185) +- Fixed the expected type of a tree node received from the server. Fixes nml export to include the `inMag` field correctly. [#8187](https://github.com/scalableminds/webknossos/pull/8187) +- Fixed a layout persistence bug leading to empty viewports, triggered when switching between orthogonal, flight, or oblique mode. [#8177](https://github.com/scalableminds/webknossos/pull/8177) ### Removed diff --git a/conf/messages b/conf/messages index 0e747814bbc..bd4288cde76 100644 --- a/conf/messages +++ b/conf/messages @@ -73,10 +73,6 @@ oidc.disabled=OIDC is disabled oidc.configuration.invalid=OIDC configuration is invalid oidc.authentication.failed=Failed to register / log in via Single-Sign-On (SSO with OIDC) -braintracing.new=An account on braintracing.org was created for you. You can use the same credentials as on WEBKNOSSOS to login. -braintracing.error=We could not automatically create an account for you on braintracing.org. Please do it on your own. -braintracing.exists=Great, you already have an account on braintracing.org. Please double check that you have uploaded all requested information. - dataset=Dataset dataset.notFound=Dataset {0} does not exist or could not be accessed dataset.notFoundConsiderLogin=Dataset {0} does not exist or could not be accessed. You may need to log in. diff --git a/docker-compose.yml b/docker-compose.yml index 76f802426bb..a1f0e549c0e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -201,7 +201,7 @@ services: -Ddatastore.redis.address=redis -Ddatastore.watchFileSystem.enabled=false" volumes: - - ./binaryData/Connectomics department:/home/${USER_NAME:-sbt-user}/webknossos/binaryData/Organization_X + - ./binaryData/Organization_X:/home/${USER_NAME:-sbt-user}/webknossos/binaryData/Organization_X screenshot-tests: image: scalableminds/puppeteer:master diff --git a/frontend/javascripts/oxalis/model/helpers/generate_dummy_trees.ts b/frontend/javascripts/oxalis/model/helpers/generate_dummy_trees.ts index 3d48d91c3f6..3ed8d9b4174 100644 --- a/frontend/javascripts/oxalis/model/helpers/generate_dummy_trees.ts +++ b/frontend/javascripts/oxalis/model/helpers/generate_dummy_trees.ts @@ -34,7 +34,7 @@ export default function generateDummyTrees( }, radius: 112.39999389648438, viewport: 1, - resolution: 1, + mag: 1, bitDepth: 4, interpolation: true, createdTimestamp: 1507550793899, diff --git a/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts b/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts index e594576d314..95400af562d 100644 --- a/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts +++ b/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts @@ -412,7 +412,7 @@ function serializeNodes( rotY: node.rotation[1], rotZ: node.rotation[2], inVp: node.viewport, - inMag: node.mag, + inMag: node.resolution, bitDepth: node.bitDepth, interpolation: node.interpolation, time: node.timestamp, @@ -963,7 +963,7 @@ export function parseNml(nmlString: string): Promise<{ }), bitDepth: _parseInt(attr, "bitDepth", { defaultValue: DEFAULT_BITDEPTH }), viewport: _parseInt(attr, "inVp", { defaultValue: DEFAULT_VIEWPORT }), - mag: _parseInt(attr, "inMag", { defaultValue: DEFAULT_RESOLUTION }), + resolution: _parseInt(attr, "inMag", { defaultValue: DEFAULT_RESOLUTION }), radius: _parseFloat(attr, "radius", { defaultValue: Constants.DEFAULT_NODE_RADIUS }), timestamp: _parseTimestamp(attr, "time", { defaultValue: DEFAULT_TIMESTAMP }), }; diff --git a/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts b/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts index 99b7398c644..784a14a1346 100644 --- a/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts +++ b/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts @@ -146,7 +146,7 @@ export function createNode( radius, rotation, viewport, - mag: resolution, + resolution, id: nextNewId, timestamp, bitDepth: state.datasetConfiguration.fourBit ? 4 : 8, @@ -846,7 +846,7 @@ function serverNodeToMutableNode(n: ServerNode): MutableNode { rotation: Utils.point3ToVector3(n.rotation), bitDepth: n.bitDepth, viewport: n.viewport, - mag: n.resolution, + resolution: n.mag, radius: n.radius, timestamp: n.createdTimestamp, interpolation: n.interpolation, diff --git a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts index aa2727ae8af..05910411232 100644 --- a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts @@ -270,7 +270,7 @@ function* createEditableMapping(): Saga { // Get volume tracing again to make sure the version is up to date const upToDateVolumeTracing = yield* select((state) => getActiveSegmentationTracing(state)); if (upToDateVolumeTracing == null) { - throw new Error("No active segmentation tracing layer. Cannot create editble mapping."); + throw new Error("No active segmentation tracing layer. Cannot create editable mapping."); } const volumeTracingId = upToDateVolumeTracing.tracingId; diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index f36f1c95592..dc4847893e1 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -89,7 +89,7 @@ export type MutableNode = { rotation: Vector3; bitDepth: number; viewport: number; - mag: number; + resolution: number; radius: number; timestamp: number; interpolation: boolean; diff --git a/frontend/javascripts/oxalis/view/layouting/flex_layout_wrapper.tsx b/frontend/javascripts/oxalis/view/layouting/flex_layout_wrapper.tsx index 6c79170cbcf..59b34a3300a 100644 --- a/frontend/javascripts/oxalis/view/layouting/flex_layout_wrapper.tsx +++ b/frontend/javascripts/oxalis/view/layouting/flex_layout_wrapper.tsx @@ -207,10 +207,7 @@ class FlexLayoutWrapper extends React.PureComponent { rebuildLayout() { const model = this.loadCurrentModel(); this.updateToModelStateAndAdjustIt(model); - this.setState({ - model, - }); - setTimeout(this.onLayoutChange, 1); + this.setState({ model }, () => this.onLayoutChange()); if (this.props.layoutName !== DEFAULT_LAYOUT_NAME) { sendAnalyticsEvent("load_custom_layout", { diff --git a/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx b/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx index a1671264ae5..4a92943d240 100644 --- a/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx +++ b/frontend/javascripts/oxalis/view/layouting/tracing_layout_view.tsx @@ -194,14 +194,12 @@ class TracingLayoutView extends React.PureComponent { app.vent.emit("rerender"); if (model != null) { - this.setState({ - model, + this.setState({ model }, () => { + if (this.props.autoSaveLayouts) { + this.saveCurrentLayout(layoutName); + } }); } - - if (this.props.autoSaveLayouts) { - this.saveCurrentLayout(layoutName); - } }; debouncedOnLayoutChange = _.debounce(() => this.onLayoutChange(), Constants.RESIZE_THROTTLE_TIME); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx index 75535fa47b8..1b22d003da9 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx @@ -163,7 +163,7 @@ const synapseNodeCreator = (synapseId: number, synapsePosition: Vector3): Mutabl radius: Constants.DEFAULT_NODE_RADIUS, rotation: [0, 0, 0], viewport: 0, - mag: 0, + resolution: 0, id: synapseId, timestamp: Date.now(), bitDepth: 8, diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/scrollable_virtualized_tree.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/scrollable_virtualized_tree.tsx index f2a8a8439fe..aa02951bc02 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/scrollable_virtualized_tree.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/scrollable_virtualized_tree.tsx @@ -1,7 +1,7 @@ import { Tree as AntdTree, type TreeProps } from "antd"; import type { BasicDataNode } from "antd/es/tree"; import { throttle } from "lodash"; -import { useCallback, useRef } from "react"; +import { forwardRef, useCallback, useRef } from "react"; import type RcTree from "rc-tree"; const MIN_SCROLL_SPEED = 30; @@ -10,8 +10,10 @@ const MIN_SCROLL_AREA_HEIGHT = 60; const SCROLL_AREA_RATIO = 10; // 1/10th of the container height const THROTTLE_TIME = 25; -function ScrollableVirtualizedTree( - props: TreeProps & { ref: React.RefObject }, +// React.forwardRef does not support generic types, so we need to define the type of the ref separately. +function ScrollableVirtualizedTreeInner( + props: TreeProps, + ref: React.Ref, ) { const wrapperRef = useRef(null); // biome-ignore lint/correctness/useExhaustiveDependencies: biome is not smart enough to notice that the function needs to be re-created when wrapperRef changes. @@ -56,9 +58,15 @@ function ScrollableVirtualizedTree( return (
- +
); } +const ScrollableVirtualizedTree = forwardRef(ScrollableVirtualizedTreeInner) as < + T extends BasicDataNode, +>( + props: TreeProps & { ref?: React.Ref }, +) => ReturnType; + export default ScrollableVirtualizedTree; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index f37f3860e08..2188a8bd2c9 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -1904,7 +1904,7 @@ class SegmentsView extends React.Component { overflow: "hidden", }} > - + { test.before("Reset database and change token", async () => { resetDatabase(); setCurrToken(tokenUserA); + await api.triggerDatasetCheck("http://localhost:9000"); }); test.serial("getDatasets", async (t) => { let datasets = await api.getDatasets(); @@ -29,19 +36,19 @@ test.serial("getDatasets", async (t) => { writeTypeCheckingFile(datasets, "dataset", "APIDatasetCompact", { isArray: true, }); - t.snapshot(datasets); + t.snapshot(replaceVolatileValues(datasets)); }); test("getActiveDatasets", async (t) => { let datasets = await api.getActiveDatasetsOfMyOrganization(); datasets = _.sortBy(datasets, (d) => d.name); - t.snapshot(datasets); + t.snapshot(replaceVolatileValues(datasets)); }); test("getDatasetAccessList", async (t) => { const dataset = await getFirstDataset(); const accessList = _.sortBy(await api.getDatasetAccessList(dataset), (user) => user.id); - t.snapshot(accessList); + t.snapshot(replaceVolatileValues(accessList)); }); test("updateDatasetTeams", async (t) => { const [dataset, newTeams] = await Promise.all([getFirstDataset(), api.getEditableTeams()]); @@ -49,7 +56,7 @@ test("updateDatasetTeams", async (t) => { dataset, newTeams.map((team) => team.id), ); - t.snapshot(updatedDataset); + t.snapshot(replaceVolatileValues(updatedDataset)); // undo the Change await api.updateDatasetTeams( dataset, @@ -62,3 +69,42 @@ test("updateDatasetTeams", async (t) => { // await api.revokeDatasetSharingToken(dataset.name); // t.pass(); // }); + +test("Zarr streaming", async (t) => { + const zattrsResp = await fetch("/data/zarr/Organization_X/test-dataset/segmentation/.zattrs", { + headers: new Headers(), + }); + const zattrs = await zattrsResp.text(); + t.snapshot(zattrs); + + const rawDataResponse = await fetch( + "/data/zarr/Organization_X/test-dataset/segmentation/1/0.1.1.0", + { + headers: new Headers(), + }, + ); + const bytes = await rawDataResponse.arrayBuffer(); + const base64 = btoa(String.fromCharCode(...new Uint8Array(bytes.slice(-128)))); + t.snapshot(base64); +}); + +test("Zarr 3 streaming", async (t) => { + const zarrJsonResp = await fetch( + "/data/zarr3_experimental/Organization_X/test-dataset/segmentation/zarr.json", + { + headers: new Headers(), + }, + ); + const zarrJson = await zarrJsonResp.text(); + t.snapshot(zarrJson); + + const rawDataResponse = await fetch( + "/data/zarr3_experimental/Organization_X/test-dataset/segmentation/1/0.1.1.0", + { + headers: new Headers(), + }, + ); + const bytes = await rawDataResponse.arrayBuffer(); + const base64 = btoa(String.fromCharCode(...new Uint8Array(bytes.slice(-128)))); + t.snapshot(base64); +}); diff --git a/frontend/javascripts/test/e2e-setup.ts b/frontend/javascripts/test/e2e-setup.ts index 9e8dee48c81..b1330eb25e1 100644 --- a/frontend/javascripts/test/e2e-setup.ts +++ b/frontend/javascripts/test/e2e-setup.ts @@ -39,6 +39,7 @@ const volatileKeys: Array = [ "lastActivity", "tracingTime", "tracingId", + "sortingKey", ]; export function replaceVolatileValues(obj: ArbitraryObject | null | undefined) { if (obj == null) return obj; @@ -130,7 +131,7 @@ export async function writeTypeCheckingFile( const fullTypeAnnotation = options.isArray ? `Array<${typeString}>` : typeString; fs.writeFileSync( `frontend/javascripts/test/snapshots/type-check/test-type-checking-${name}.ts`, - ` + ` import type { ${typeString} } from "types/api_flow_types"; const a: ${fullTypeAnnotation} = ${JSON.stringify(object)}`, ); diff --git a/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts b/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts index 55a2c1fa71d..cbcec2723a7 100644 --- a/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts +++ b/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts @@ -22,7 +22,7 @@ export const tracing: ServerSkeletonTracing = { }, radius: 112.39999389648438, viewport: 0, - resolution: 1, + mag: 1, bitDepth: 4, interpolation: true, createdTimestamp: 1502302785450, @@ -70,7 +70,7 @@ export const tracing: ServerSkeletonTracing = { }, radius: 112.39999389648438, viewport: 0, - resolution: 1, + mag: 1, bitDepth: 4, interpolation: true, createdTimestamp: 1502302785447, @@ -90,7 +90,7 @@ export const tracing: ServerSkeletonTracing = { }, radius: 112.39999389648438, viewport: 0, - resolution: 1, + mag: 1, bitDepth: 4, interpolation: true, createdTimestamp: 1502302785448, diff --git a/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts b/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts index 8da34fd0361..cd1d0d08f28 100644 --- a/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts +++ b/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts @@ -20,7 +20,7 @@ export const tracing: ServerSkeletonTracing = { }, radius: 120, viewport: 1, - resolution: 1, + mag: 1, bitDepth: 0, interpolation: false, createdTimestamp: 1528811979356, diff --git a/frontend/javascripts/test/libs/nml.spec.ts b/frontend/javascripts/test/libs/nml.spec.ts index e73937386f7..5506e01c012 100644 --- a/frontend/javascripts/test/libs/nml.spec.ts +++ b/frontend/javascripts/test/libs/nml.spec.ts @@ -34,7 +34,7 @@ const createDummyNode = (id: number): Node => ({ untransformedPosition: [id, id, id], additionalCoordinates: [], radius: id, - mag: 10, + resolution: 10, rotation: [id, id, id], timestamp: id, viewport: 1, diff --git a/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts b/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts index 5cf6b5767bd..2eb6a6858b4 100644 --- a/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts +++ b/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts @@ -130,7 +130,7 @@ test("SkeletonTracing should add a new node", (t) => { untransformedPosition: position, rotation, viewport, - mag: resolution, + resolution, id: 1, radius: 1, }); @@ -295,7 +295,7 @@ test("SkeletonTracing should delete nodes and split the tree", (t) => { untransformedPosition: [0, 0, 0], additionalCoordinates: null, radius: 10, - mag: 10, + resolution: 10, rotation: [0, 0, 0], timestamp: 0, viewport: 1, @@ -453,7 +453,7 @@ test("SkeletonTracing should delete an edge and split the tree", (t) => { untransformedPosition: [0, 0, 0], additionalCoordinates: null, radius: 10, - mag: 10, + resolution: 10, rotation: [0, 0, 0], timestamp: 0, viewport: 1, diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.md index 719d80174c7..f54066d7901 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.md @@ -11,10 +11,10 @@ Generated by [AVA](https://avajs.dev). [ { colorLayerNames: [], - created: 1460379470082, + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', - id: '570b9f4e4bb848d0885ee711', + id: 'id', isActive: false, isEditable: true, isUnreported: true, @@ -27,10 +27,10 @@ Generated by [AVA](https://avajs.dev). }, { colorLayerNames: [], - created: 1460379470080, + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', - id: '570b9f4e4bb848d0885ee713', + id: 'id', isActive: false, isEditable: true, isUnreported: true, @@ -43,10 +43,10 @@ Generated by [AVA](https://avajs.dev). }, { colorLayerNames: [], - created: 1460379470079, + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', - id: '570b9f4e4bb848d0885ee712', + id: 'id', isActive: false, isEditable: true, isUnreported: true, @@ -58,51 +58,43 @@ Generated by [AVA](https://avajs.dev). tags: [], }, { - colorLayerNames: [ - 'color_1', - 'color_2', - 'color_3', - ], - created: 1508495293763, + colorLayerNames: [], + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', - id: '59e9cfbdba632ac2ab8b23b3', - isActive: true, + id: 'id', + isActive: false, isEditable: true, - isUnreported: false, + isUnreported: true, lastUsedByUser: 0, name: 'confocal-multi_knossos', owningOrganization: 'Organization_X', segmentationLayerNames: [], - status: '', + status: 'No longer available on datastore.', tags: [], }, { - colorLayerNames: [ - 'color', - ], - created: 1508495293789, + colorLayerNames: [], + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', - id: '59e9cfbdba632ac2ab8b23b5', - isActive: true, + id: 'id', + isActive: false, isEditable: true, - isUnreported: false, + isUnreported: true, lastUsedByUser: 0, name: 'l4_sample', owningOrganization: 'Organization_X', - segmentationLayerNames: [ - 'segmentation', - ], - status: '', + segmentationLayerNames: [], + status: 'No longer available on datastore.', tags: [], }, { colorLayerNames: [], - created: 1460379603792, + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', - id: '570b9fd34bb848d0885ee716', + id: 'id', isActive: false, isEditable: true, isUnreported: true, @@ -113,276 +105,51 @@ Generated by [AVA](https://avajs.dev). status: 'No longer available on datastore.', tags: [], }, - ] - -## getActiveDatasets - -> Snapshot 1 - - [ { - allowedTeams: [ - { - id: '570b9f4b2a7c0e3b008da6ec', - name: 'team_X1', - organization: 'Organization_X', - }, - ], - allowedTeamsCumulative: [ - { - id: '570b9f4b2a7c0e3b008da6ec', - name: 'team_X1', - organization: 'Organization_X', - }, - ], - created: 1508495293763, - dataSource: { - dataLayers: [ - { - boundingBox: { - depth: 256, - height: 512, - topLeft: [ - 0, - 0, - 0, - ], - width: 512, - }, - category: 'color', - elementClass: 'uint8', - name: 'color_1', - resolutions: [ - [ - 1, - 1, - 1, - ], - [ - 2, - 2, - 2, - ], - [ - 4, - 4, - 4, - ], - [ - 8, - 8, - 8, - ], - [ - 16, - 16, - 16, - ], - ], - }, - { - boundingBox: { - depth: 256, - height: 512, - topLeft: [ - 0, - 0, - 0, - ], - width: 512, - }, - category: 'color', - elementClass: 'uint8', - name: 'color_2', - resolutions: [ - [ - 1, - 1, - 1, - ], - [ - 2, - 2, - 2, - ], - [ - 4, - 4, - 4, - ], - [ - 8, - 8, - 8, - ], - [ - 16, - 16, - 16, - ], - ], - }, - { - boundingBox: { - depth: 256, - height: 512, - topLeft: [ - 0, - 0, - 0, - ], - width: 512, - }, - category: 'color', - elementClass: 'uint8', - name: 'color_3', - resolutions: [ - [ - 1, - 1, - 1, - ], - [ - 2, - 2, - 2, - ], - [ - 4, - 4, - 4, - ], - [ - 8, - 8, - 8, - ], - [ - 16, - 16, - 16, - ], - ], - }, - ], - id: { - name: 'confocal-multi_knossos', - team: 'Organization_X', - }, - scale: { - factor: [ - 22, - 22, - 44.599998474121094, - ], - unit: 'nanometer', - }, - }, - dataStore: { - allowsUpload: true, - isScratch: false, - jobsEnabled: false, - jobsSupportedByAvailableWorkers: [], - name: 'localhost', - url: 'http://localhost:9000', - }, - description: null, + colorLayerNames: [], + created: 'created', displayName: null, folderId: '570b9f4e4bb848d0885ea917', + id: 'id', isActive: true, isEditable: true, - isPublic: false, isUnreported: false, lastUsedByUser: 0, - logoUrl: '/assets/images/mpi-logos.svg', - metadata: [ - { - key: 'key', - type: 'number', - value: 4, - }, - ], - name: 'confocal-multi_knossos', + name: 'test-dataset', owningOrganization: 'Organization_X', - publication: null, - sortingKey: 1508495293763, + segmentationLayerNames: [ + 'segmentation', + ], + status: '', tags: [], - usedStorageBytes: 0, }, + ] + +## getActiveDatasets + +> Snapshot 1 + + [ { - allowedTeams: [ - { - id: '570b9f4b2a7c0e3b008da6ec', - name: 'team_X1', - organization: 'Organization_X', - }, - ], - allowedTeamsCumulative: [ - { - id: '570b9f4b2a7c0e3b008da6ec', - name: 'team_X1', - organization: 'Organization_X', - }, - ], - created: 1508495293789, + allowedTeams: [], + allowedTeamsCumulative: [], + created: 'created', dataSource: { dataLayers: [ { boundingBox: { - depth: 1024, - height: 1024, + depth: 100, + height: 100, topLeft: [ - 3072, - 3072, - 512, + 50, + 50, + 25, ], - width: 1024, - }, - category: 'color', - elementClass: 'uint8', - name: 'color', - resolutions: [ - [ - 1, - 1, - 1, - ], - [ - 2, - 2, - 1, - ], - [ - 4, - 4, - 1, - ], - [ - 8, - 8, - 2, - ], - [ - 16, - 16, - 4, - ], - ], - }, - { - boundingBox: { - depth: 1024, - height: 1024, - topLeft: [ - 3072, - 3072, - 512, - ], - width: 1024, + width: 100, }, category: 'segmentation', elementClass: 'uint32', - largestSegmentId: 2504697, + largestSegmentId: 176, name: 'segmentation', resolutions: [ [ @@ -390,37 +157,14 @@ Generated by [AVA](https://avajs.dev). 1, 1, ], - [ - 2, - 2, - 1, - ], - [ - 4, - 4, - 1, - ], - [ - 8, - 8, - 2, - ], - [ - 16, - 16, - 4, - ], ], }, ], - id: { - name: 'l4_sample', - team: 'Organization_X', - }, + id: 'id', scale: { factor: [ - 11.239999771118164, - 11.239999771118164, + 11.24, + 11.24, 28, ], unit: 'nanometer', @@ -444,10 +188,10 @@ Generated by [AVA](https://avajs.dev). lastUsedByUser: 0, logoUrl: '/assets/images/mpi-logos.svg', metadata: [], - name: 'l4_sample', + name: 'test-dataset', owningOrganization: 'Organization_X', publication: null, - sortingKey: 1508495293789, + sortingKey: 'sortingKey', tags: [], usedStorageBytes: 0, }, @@ -461,24 +205,24 @@ Generated by [AVA](https://avajs.dev). { email: 'user_A@scalableminds.com', firstName: 'user_A', - id: '570b9f4d2a7c0e4d008da6ef', + id: 'id', isAdmin: true, isAnonymous: false, isDatasetManager: true, lastName: 'last_A', teams: [ { - id: '570b9f4b2a7c0e3b008da6ec', + id: 'id', isTeamManager: true, name: 'team_X1', }, { - id: '59882b370d889b84020efd3f', + id: 'id', isTeamManager: false, name: 'team_X3', }, { - id: '59882b370d889b84020efd6f', + id: 'id', isTeamManager: true, name: 'team_X4', }, @@ -487,35 +231,19 @@ Generated by [AVA](https://avajs.dev). { email: 'user_B@scalableminds.com', firstName: 'user_B', - id: '670b9f4d2a7c0e4d008da6ef', + id: 'id', isAdmin: false, isAnonymous: false, isDatasetManager: true, lastName: 'last_B', teams: [ { - id: '570b9f4b2a7c0e3b008da6ec', + id: 'id', isTeamManager: true, name: 'team_X1', }, ], }, - { - email: 'user_C@scalableminds.com', - firstName: 'user_C', - id: '770b9f4d2a7c0e4d008da6ef', - isAdmin: false, - isAnonymous: false, - isDatasetManager: false, - lastName: 'last_C', - teams: [ - { - id: '570b9f4b2a7c0e3b008da6ec', - isTeamManager: false, - name: 'team_X1', - }, - ], - }, ] ## updateDatasetTeams @@ -528,3 +256,23 @@ Generated by [AVA](https://avajs.dev). '59882b370d889b84020efd6f', '69882b370d889b84020efd4f', ] + +## Zarr streaming + +> Snapshot 1 + + '{"multiscales":[{"version":"0.4","name":"segmentation","axes":[{"name":"c","type":"channel"},{"name":"x","type":"space","unit":"nanometer"},{"name":"y","type":"space","unit":"nanometer"},{"name":"z","type":"space","unit":"nanometer"}],"datasets":[{"path":"1","coordinateTransformations":[{"type":"scale","scale":[1,11.24,11.24,28]}]}]}]}' + +> Snapshot 2 + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAA=' + +## Zarr 3 streaming + +> Snapshot 1 + + '{"zarr_format":3,"node_type":"group","attributes":{"ome":{"version":"0.5","multiscales":[{"name":"segmentation","axes":[{"name":"c","type":"channel"},{"name":"x","type":"space","unit":"nanometer"},{"name":"y","type":"space","unit":"nanometer"},{"name":"z","type":"space","unit":"nanometer"}],"datasets":[{"path":"1","coordinateTransformations":[{"type":"scale","scale":[1,11.24,11.24,28]}]}]}]}}}' + +> Snapshot 2 + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAA=' diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.snap index 054bb7f37c2..a47da3e0e5c 100644 Binary files a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.snap and b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/datasets.e2e.js.snap differ diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index c27d3356693..a4c6df351f5 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -756,7 +756,7 @@ export type ServerNode = { rotation: Point3; bitDepth: number; viewport: number; - resolution: number; + mag: number; radius: number; createdTimestamp: number; interpolation: boolean; diff --git a/test/dataset/.gitignore b/test/dataset/.gitignore new file mode 100644 index 00000000000..867373c7958 --- /dev/null +++ b/test/dataset/.gitignore @@ -0,0 +1 @@ +!test-dataset.zip diff --git a/test/dataset/test-dataset.zip b/test/dataset/test-dataset.zip new file mode 100644 index 00000000000..16e1a3924d2 Binary files /dev/null and b/test/dataset/test-dataset.zip differ diff --git a/test/e2e/End2EndSpec.scala b/test/e2e/End2EndSpec.scala index 9a85437e684..1de30f63de7 100644 --- a/test/e2e/End2EndSpec.scala +++ b/test/e2e/End2EndSpec.scala @@ -1,5 +1,6 @@ package e2e +import com.scalableminds.util.io.ZipIO import com.typesafe.scalalogging.LazyLogging import org.scalatestplus.play.guice._ import org.specs2.main.Arguments @@ -8,6 +9,8 @@ import play.api.inject.guice.GuiceApplicationBuilder import play.api.libs.ws.{WSClient, WSResponse} import play.api.test.WithServer +import java.io.File +import java.nio.file.Paths import scala.concurrent.Await import scala.concurrent.duration._ import scala.sys.process._ @@ -27,6 +30,8 @@ class End2EndSpec(arguments: Arguments) extends Specification with GuiceFakeAppl "pass the e2e tests" in new WithServer(app = application, port = testPort) { + ensureTestDataset() + val resp: WSResponse = Await.result(ws.url(s"http://localhost:$testPort").get(), 2 seconds) resp.status === 200 @@ -43,4 +48,41 @@ class End2EndSpec(arguments: Arguments) extends Specification with GuiceFakeAppl customArgumentsMap.groupBy(_(0).substring(2)).view.mapValues(_(0).last).toMap } + private def ensureTestDataset(): Unit = { + val testDatasetPath = "test/dataset/test-dataset.zip" + val dataDirectory = new File("binaryData/Organization_X") + if (!dataDirectory.exists()) { + dataDirectory.mkdirs() + } + val testDatasetZip = new File(testDatasetPath) + if (!testDatasetZip.exists()) { + throw new Exception("Test dataset zip file does not exist.") + } + // Skip unzipping if the test dataset is already present + if (!dataDirectory.listFiles().exists(_.getName == "test-dataset")) + ZipIO.unzipToFolder( + testDatasetZip, + Paths.get(dataDirectory.toPath.toString, "test-dataset"), + includeHiddenFiles = true, + hiddenFilesWhitelist = List(), + truncateCommonPrefix = true, + excludeFromPrefix = None + ) + + // Test if the dataset was unzipped successfully + if (!dataDirectory.listFiles().exists(_.getName == "test-dataset")) { + throw new Exception("Test dataset was not unzipped successfully.") + } + val testFile = new File(dataDirectory, "test-dataset/datasource-properties.json") + if (!testFile.exists()) { + throw new Exception("Required file does not exist.") + } + val testFileSource = scala.io.Source.fromFile(testFile) + val testFileContent = try testFileSource.mkString + finally testFileSource.close() + if (testFileContent.isEmpty) { + throw new Exception("Required file is empty.") + } + } + }