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

Add loaded meshes to sharing link #5993

Merged
merged 39 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a95d9d4
add Utils.values which is Object.values but with the correct type, cl…
daniel-wer Jan 26, 2022
0a65443
allow to share ad-hoc computed meshes via link
daniel-wer Jan 26, 2022
6025ca3
clean up isosurface related code, remove auto-loading triggered by fl…
daniel-wer Jan 27, 2022
c56a556
refactor loadPrecomputedMesh method into a saga and adapt usages
daniel-wer Jan 27, 2022
f486a46
floor mesh seed position in url
daniel-wer Jan 27, 2022
8553d24
log to airbrake if the ad-hoc mesh loading limit is reached
daniel-wer Jan 27, 2022
a4f3602
load precomputed meshes from the inside out by sorting chunks
daniel-wer Jan 27, 2022
99fe876
remove unnecessary types
daniel-wer Jan 31, 2022
e39f12d
PR feedback 1
daniel-wer Jan 31, 2022
16d493b
rework task_pool to be a saga and to execute sagas to make it fully c…
daniel-wer Jan 31, 2022
dc7f772
fix mesh sharing via link if no mesh file is active
daniel-wer Feb 1, 2022
8acfa37
more PR feedback
daniel-wer Feb 1, 2022
350f734
include additional information in url for meshes to make sure the sam…
daniel-wer Feb 3, 2022
88abd54
use new kubernetix url
philippotto Feb 8, 2022
e051c30
trigger nightly (needs to be reverted afterwards)
philippotto Feb 8, 2022
c8b8c6b
Merge branch 'master' of github.com:scalableminds/webknossos into mes…
daniel-wer Feb 9, 2022
26a9aa8
fix refresh-datasets in nightly and other outdated kubernetix link
philippotto Feb 9, 2022
d105dbf
upgrade puppeteer from 1.13 to 13.2
philippotto Feb 9, 2022
01a895e
use mappingInfo parameter instead of mappingName and mappingType, PR …
daniel-wer Feb 9, 2022
25adf35
fix deprecation warning
philippotto Feb 9, 2022
498dddd
update two snapshots which only changed subtly
philippotto Feb 9, 2022
13bea43
fix url json schema and fix that mappingInfo was overwritten by meshInfo
daniel-wer Feb 9, 2022
c114ba4
update changelog
daniel-wer Feb 9, 2022
2a98dfd
Downgrade puppeteer to 11.0.0 to avoid that some segments are rendere…
daniel-wer Feb 10, 2022
961f8a1
adapt screenshot snapshots to sensible scalebar
philippotto Feb 14, 2022
b382b1b
trigger nightly now
philippotto Feb 14, 2022
779f669
Merge branch 'master' of github.com:scalableminds/webknossos into mes…
daniel-wer Feb 17, 2022
7441470
add screenshot test for mesh linking, add more logging to screenshot …
daniel-wer Feb 17, 2022
2139c83
adapt docs for sharing link format and hide the format behind a toggl…
daniel-wer Feb 17, 2022
968ad00
use large resource class for nightly tests as well
daniel-wer Feb 17, 2022
9f8c2b3
change screenshot test url temporarily and add more debugging output
daniel-wer Feb 17, 2022
1369467
Merge branch 'meshes-in-link' of github.com:scalableminds/webknossos …
daniel-wer Feb 17, 2022
9d2bdf1
Merge branch 'adapt-screenshots-to-scalebar' of github.com:scalablemi…
daniel-wer Feb 17, 2022
82a1aa2
update puppeteer and pixelmatch, --use-gl=egl, update screenshots, ma…
daniel-wer Feb 17, 2022
b2a385f
check chrome version in CI
daniel-wer Feb 18, 2022
3f779fd
use switftshader and replace screenshots, temporarily slim down night…
daniel-wer Feb 18, 2022
5598681
test meshesinlink PR
daniel-wer Feb 18, 2022
7325ff8
replace meshes screenshot with the one from the CI
daniel-wer Feb 18, 2022
5d47edc
revert temporary changes
daniel-wer Feb 21, 2022
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 .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ jobs:
nightly:
docker:
- image: scalableminds/puppeteer:master
resource_class: large
steps:
- checkout
- run:
Expand Down
76 changes: 50 additions & 26 deletions docs/sharing.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,33 +124,57 @@ To get the sharing link of an annotation, follow the same steps as for changing

#### Sharing Link Format

As already indicated, the sharing link encodes certain properties, like the current position, rotation, zoom, and active mapping to ensure that users you share the link with see the same things you saw when you copied the link. Alternatively, the link can be crafted manually or programmatically to direct users to specific locations in a dataset. The information is json encoded in the URL fragment and has the following format (flow type definition):

```javascript
type MappingType = "JSON" | "HDF5";
type ViewMode = "orthogonal" | "oblique" | "flight" | "volume";
type Vector3 = [number, number, number];

type UrlStateByLayer = {
[layerName: string]: {
mappingInfo?: {
mappingName: string,
mappingType: MappingType,
agglomerateIdsToImport?: [number],
As already indicated, the sharing link encodes certain properties, like the current position, rotation, zoom, active mapping, and visible meshes to ensure that users you share the link with see the same things you saw when you copied the link. Alternatively, the link can be crafted manually or programmatically to direct users to specific locations in a dataset. The information is json encoded in the URL fragment and has the following format (flow type definition):

<details>
<summary>URL Fragment Format</summary>
```javascript
type MappingType = "JSON" | "HDF5";
type ViewMode = "orthogonal" | "oblique" | "flight" | "volume";
type Vector3 = [number, number, number];

type BaseMeshUrlDescriptor = {|
+segmentId: number,
+seedPosition: Vector3,
|};
type AdHocMeshUrlDescriptor = {|
...BaseMeshUrlDescriptor,
+isPrecomputed: false,
mappingName: ?string,
mappingType: ?MappingType,
|};
type PrecomputedMeshUrlDescriptor = {|
...BaseMeshUrlDescriptor,
+isPrecomputed: true,
meshFileName: string,
|};
type MeshUrlDescriptor = AdHocMeshUrlDescriptor | PrecomputedMeshUrlDescriptor;

type UrlStateByLayer = {
[layerName: string]: {
meshInfo?: {
meshFileName: ?string,
meshes: Array<MeshUrlDescriptor>,
},
mappingInfo?: {
mappingName: string,
mappingType: MappingType,
agglomerateIdsToImport?: Array<number>,
},
},
},
};

type UrlManagerState = {|
position?: Vector3,
mode?: ViewMode,
zoomStep?: number,
activeNode?: number,
rotation?: Vector3,
stateByLayer?: UrlStateByLayer,
|};

```
};

type UrlManagerState = {|
position?: Vector3,
mode?: ViewMode,
zoomStep?: number,
activeNode?: number,
rotation?: Vector3,
stateByLayer?: UrlStateByLayer,
|};

```
</details>

To avoid having to create annotations in advance when programmatically crafting links, a sandbox tracing can be used. A sandbox tracing is always accessible through the same URL and offers all available tracing features, however, changes are not saved. At any point, users can decide to copy the current state to their account. The sandbox can be accessed at `<webknossos_host>/datasets/<organization>/<dataset>/sandbox/skeleton`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fetch, { Headers, Request, Response, FetchError } from "node-fetch";
import path from "path";
import puppeteer, { type Browser } from "puppeteer";

import { compareScreenshot } from "./screenshot_helpers";
import { compareScreenshot, isPixelEquivalent } from "./screenshot_helpers";
import {
screenshotDataset,
screenshotDatasetWithMapping,
Expand Down Expand Up @@ -61,9 +61,11 @@ test.beforeEach(async t => {
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--use-gl=swiftshader",
],
dumpio: true,
});
console.log(`\nRunning chrome version ${await t.context.browser.version()}\n`);
global.Headers = Headers;
global.fetch = fetch;
global.Request = Request;
Expand All @@ -89,6 +91,8 @@ const viewOverrides: { [key: string]: string } = {
"Multi-Channel-Test": "1201,1072,7,0,0.683",
"test-agglomerate-file":
'{"position":[60,60,60],"mode":"orthogonal","zoomStep":0.5,"stateByLayer":{"segmentation":{"mappingInfo":{"mappingName":"agglomerate_view_70","mappingType":"HDF5","agglomerateIdsToImport":[1, 6]}}}}',
"test-agglomerate-file-with-meshes":
'{"position":[63,67,118],"mode":"orthogonal","zoomStep":0.826,"stateByLayer":{"segmentation":{"meshInfo":{"meshFileName":"meshfile-with-name","meshes":[{"segmentId":4,"seedPosition":[64,75,118],"isPrecomputed":true,"meshFileName":"meshfile-with-name"},{"segmentId":12,"seedPosition":[107,125,118],"isPrecomputed":false,"mappingName":"agglomerate_view_70","mappingType":"HDF5"},{"segmentId":79,"seedPosition":[110,78,118],"isPrecomputed":false,"mappingName":null,"mappingType":null}]}}}}',
};

const datasetConfigOverrides: { [key: string]: DatasetConfiguration } = {
Expand Down Expand Up @@ -128,16 +132,10 @@ async function withRetry(
resolveFn(condition);
return;
}
console.error(`Test failed, retrying. This will be attempt ${i + 2}/${retryCount}.`);
}
}

function isPixelEquivalent(changedPixels, width, height) {
// There may be a difference of 0.1 %
const allowedThreshold = 0.1 / 100;
const allowedChangedPixel = allowedThreshold * width * height;
return changedPixels < allowedChangedPixel;
}

datasetNames.map(async datasetName => {
test.serial(`it should render dataset ${datasetName} correctly`, async t => {
await withRetry(
Expand Down Expand Up @@ -274,6 +272,41 @@ test.serial(
},
);

test.serial(
"it should render a dataset linked to with ad-hoc and precomputed meshes correctly",
async t => {
const datasetName = "test-agglomerate-file";
const viewOverride = viewOverrides["test-agglomerate-file-with-meshes"];
await withRetry(
3,
async () => {
const datasetId = { name: datasetName, owningOrganization: "sample_organization" };
const { screenshot, width, height } = await screenshotDataset(
await getNewPage(t.context.browser),
URL,
datasetId,
viewOverride,
);
const changedPixels = await compareScreenshot(
screenshot,
width,
height,
BASE_PATH,
`${datasetName}_with_meshes_link`,
);

return isPixelEquivalent(changedPixels, width, height);
},
condition => {
t.true(
condition,
`Dataset with name: "${datasetName}", ad-hoc and precomputed meshes does not look the same.`,
);
},
);
},
);

test.afterEach(async t => {
await t.context.browser.close();
});
47 changes: 34 additions & 13 deletions frontend/javascripts/test/puppeteer/dataset_rendering_helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import pixelmatch from "pixelmatch";

import type { APIDatasetId } from "../../types/api_flow_types";
import { createExplorational, updateDatasetConfiguration } from "../../admin/admin_rest_api";
import { bufferToPng, isPixelEquivalent } from "./screenshot_helpers";

export const { WK_AUTH_TOKEN } = process.env;
if (!WK_AUTH_TOKEN) {
Expand Down Expand Up @@ -106,13 +107,15 @@ export async function screenshotSandboxWithMappingLink(
}

async function waitForMappingEnabled(page: Page) {
console.log("Waiting for mapping to be enabled");
let isMappingEnabled;
while (!isMappingEnabled) {
await page.waitForTimeout(5000);
isMappingEnabled = await page.evaluate(
"webknossos.apiReady().then(async api => api.data.isMappingEnabled())",
);
}
console.log("Mapping was enabled");
}

async function waitForTracingViewLoad(page: Page) {
Expand All @@ -124,17 +127,29 @@ async function waitForTracingViewLoad(page: Page) {
}

async function waitForRenderingFinish(page: Page) {
const width = 1920;
const height = 1080;
let currentShot;
let lastShot = await page.screenshot({ fullPage: true });
let changedPixels = Infinity;
// If the screenshot of the page didn't change in the last x seconds, rendering should be finished
while (currentShot == null || changedPixels > 0) {
while (currentShot == null || !isPixelEquivalent(changedPixels, width, height)) {
console.log(`Waiting for rendering to finish. Changed pixels: ${changedPixels}`);
await page.waitForTimeout(10000);
currentShot = await page.screenshot({ fullPage: true });
if (lastShot != null) {
changedPixels = pixelmatch(lastShot, currentShot, {}, 1920, 1080, {
threshold: 0.0,
});
changedPixels = pixelmatch(
// The buffers need to be converted to png before comparing them
Copy link
Member

Choose a reason for hiding this comment

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

Has this become necessary due to the update to pixelmatch? Or has this always been an issue? Good catch, either way 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

From what I read and experienced, pixelmatch started to assert this in the new version but the problem existed before. Usually the difference was fairly small (which is why it mostly worked before), but I also encountered garbled diffs during my testing with other --use-gl flags.

// as they might have different lengths, otherwise (probably due to different png encodings)
(await bufferToPng(lastShot, width, height)).data,
(await bufferToPng(currentShot, width, height)).data,
null,
width,
height,
{
threshold: 0.0,
},
);
}
lastShot = currentShot;
}
Expand All @@ -147,12 +162,16 @@ async function openTracingView(
optionalViewOverride: ?string,
) {
const urlSlug = optionalViewOverride != null ? `#${optionalViewOverride}` : "";
await page.goto(urljoin(baseUrl, `/annotations/Explorational/${annotationId}${urlSlug}`), {
const url = urljoin(baseUrl, `/annotations/Explorational/${annotationId}${urlSlug}`);
await page.goto(url, {
timeout: 0,
});

console.log(`Opening tracing view at ${url}`);
await waitForTracingViewLoad(page);
console.log("Loaded tracing view");
await waitForRenderingFinish(page);
console.log("Finished rendering tracing view");
}

async function openSandboxView(
Expand All @@ -162,21 +181,23 @@ async function openSandboxView(
optionalViewOverride: ?string,
) {
const urlSlug = optionalViewOverride != null ? `#${optionalViewOverride}` : "";
await page.goto(
urljoin(
baseUrl,
`/datasets/${datasetId.owningOrganization}/${datasetId.name}/sandbox/skeleton${urlSlug}`,
),
{
timeout: 0,
},
const url = urljoin(
baseUrl,
`/datasets/${datasetId.owningOrganization}/${datasetId.name}/sandbox/skeleton${urlSlug}`,
);
await page.goto(url, {
timeout: 0,
});

console.log(`Opening sandbox tracing view at ${url}`);
await waitForTracingViewLoad(page);
console.log("Loaded tracing view");
await waitForRenderingFinish(page);
console.log("Finished rendering tracing view");
}

async function screenshotTracingView(page: Page): Promise<Screenshot> {
console.log("Screenshot tracing view");
// Take screenshots of the other rendered planes
const PLANE_IDS = [
"#inputcatcher_TDView",
Expand Down
9 changes: 8 additions & 1 deletion frontend/javascripts/test/puppeteer/screenshot_helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { PNG } from "pngjs";
import fs from "fs";
import pixelmatch from "pixelmatch";

export function isPixelEquivalent(changedPixels: number, width: number, height: number) {
// There may be a difference of 0.1 %
const allowedThreshold = 0.1 / 100;
const allowedChangedPixel = allowedThreshold * width * height;
return changedPixels < allowedChangedPixel;
}

function openScreenshot(path: string, name: string): Promise<typeof PNG> {
return new Promise(resolve => {
fs.createReadStream(`${path}/${name}.png`)
Expand All @@ -29,7 +36,7 @@ function saveScreenshot(png: typeof PNG, path: string, name: string): Promise<vo
});
}

function bufferToPng(buffer: Buffer, width: number, height: number): Promise<typeof PNG> {
export function bufferToPng(buffer: Buffer, width: number, height: number): Promise<typeof PNG> {
return new Promise(resolve => {
const png = new PNG({ width, height });
png.parse(buffer, () => resolve(png));
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/javascripts/test/screenshots/Multi-Channel-Test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/javascripts/test/screenshots/ROI2017_wkw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/javascripts/test/screenshots/ROI2017_wkw_fallback.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/javascripts/test/screenshots/dsA_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/javascripts/test/screenshots/float_test_dataset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@
"node-fetch": "^2.6.7",
"nyc": "^13.3.0",
"pg": "^7.4.1",
"pixelmatch": "^4.0.2",
"pixelmatch": "^5.2.0",
"pngjs": "^3.3.3",
"prettier": "1.16.4",
"proto-loader6": "^0.4.0",
"puppeteer": "^11.0.0",
"puppeteer": "^13.3.2",
"randomstring": "^1.1.5",
"react-test-renderer": "^16.8.0",
"redux-mock-store": "^1.2.2",
Expand Down
Loading