Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c4ba568
WIP: add logging for number of updated buckets
knollengewaechs Sep 29, 2025
f6074a1
include more logging
knollengewaechs Sep 29, 2025
98d45b2
update auto save time
knollengewaechs Sep 29, 2025
cfae01d
revert some changes
knollengewaechs Sep 29, 2025
425959b
add toast when more than 4k buckets are in pushqueeue
knollengewaechs Sep 30, 2025
5819d92
Merge branch 'master' into encourage-coarser-mags-for-large-volume-an…
knollengewaechs Sep 30, 2025
683849c
fix typo
knollengewaechs Sep 30, 2025
fa10734
add docs for mag restrictions
knollengewaechs Sep 30, 2025
461fc7a
revert dev changes
knollengewaechs Oct 6, 2025
a5fbb91
move code to save_queue
knollengewaechs Oct 6, 2025
18fc591
undo rename
knollengewaechs Oct 6, 2025
977f391
remove console.log
knollengewaechs Oct 6, 2025
2012b1b
save intermediate result before moving stuff to data_cube
knollengewaechs Oct 7, 2025
63c6332
to be reverted: check total number of buckets
knollengewaechs Oct 7, 2025
c956a5b
move code from data_cube to save_queue_draining
knollengewaechs Oct 7, 2025
38df61b
dispatch actions in pushqueue and consume in save_saga
knollengewaechs Oct 7, 2025
63129bb
Merge branch 'master' into encourage-coarser-mags-for-large-volume-an…
knollengewaechs Oct 8, 2025
9e916a2
WIP: start to implement modal
knollengewaechs Oct 8, 2025
fd7e73c
extract toast
knollengewaechs Oct 8, 2025
a9aa7ad
turn toast into modal and dispatch action to open it
knollengewaechs Oct 9, 2025
a9165c5
move warning value to application.conf
knollengewaechs Oct 9, 2025
c6af70d
make warning toast a react component, fix functionality within toast
knollengewaechs Oct 9, 2025
f8bf436
Merge branch 'master' into encourage-coarser-mags-for-large-volume-an…
knollengewaechs Oct 9, 2025
613f58b
add styling to toast
knollengewaechs Oct 10, 2025
9f22533
refresh snapshot
knollengewaechs Oct 10, 2025
1696a6b
fix tests
knollengewaechs Oct 10, 2025
b68663f
change time interval for bucket check to 120s
knollengewaechs Oct 10, 2025
3a1463f
fix time intervals and remove some dev changes
knollengewaechs Oct 10, 2025
dc774c1
improve time intervals
knollengewaechs Oct 10, 2025
1edc7e8
remember closing modal in session
knollengewaechs Oct 10, 2025
64093d5
fix time interval
knollengewaechs Oct 10, 2025
b00c9c9
fix checkbox
knollengewaechs Oct 13, 2025
5a7d667
Merge branch 'master' into encourage-coarser-mags-for-large-volume-an…
knollengewaechs Oct 13, 2025
401e815
re-add activeUser to e2e snapshots
fm3 Oct 14, 2025
87e68b5
update text
knollengewaechs Oct 14, 2025
3f61c1a
update button test
knollengewaechs Oct 14, 2025
658c788
Merge branch 'master' into encourage-coarser-mags-for-large-volume-an…
knollengewaechs Oct 17, 2025
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 conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ features {
publicDemoDatasetUrl = "https://webknossos.org/datasets/scalable_minds/l4dense_motta_et_al_demo"
exportTiffMaxVolumeMVx = 1024
exportTiffMaxEdgeLengthVx = 8192
bucketSaveWarningThreshold = 1000
defaultToLegacyBindings = false
editableMappingsEnabled = true
# The only valid item value is currently "ConnectomeView":
Expand Down
17 changes: 16 additions & 1 deletion docs/volume_annotation/import_export.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,19 @@ After finishing the annotation of a volume layer with a fallback layer, the comb
Icon to open the materialize volume annotation dialog
///

This button opens up a dialog that starts a long-running job which will materialize the volume annotation.
This button opens up a dialog that starts a long-running job which will materialize the volume annotation.

## Restricting magnifications

WEBKNOSSOS allows data annotation in different magnifications.
Restricting the available magnifications can greatly improve the performance when annotating large structures, such as nuclei, since the volume data does not need to be stored in all quality levels.
How to read: Mag 1 is the most detailed, 4-4-2 is downsampled by factor 4 in x and y, and by factor 2 in z.
When annotating large structures, consider restricting the available magnifications in this specific annotation or in this volume annotation layer.

### Restricting magnifications for a whole annotation

Go to the dataset dashboard. Click on the three dots next to `New Annotation` for the dataset you want to annotate. Use the slider to restrict the volume magnifications, e.g. by omitting the finest magnification, which is usually 1-1-1, or the two finest magnifications.

### Restricting magnifications for volume annotation layers

Within an existing annotation, go to the `Layers` tab on the left. Click `Add Volume Annotation Layer` at the bottom of the list of all layers in this annotation. Restrict the magnifications as explained above.
5 changes: 4 additions & 1 deletion frontend/javascripts/libs/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export type ToastConfig = {
sticky?: boolean;
timeout?: number;
key?: string;
customFooter?: React.ReactNode;
onClose?: () => void;
className?: string;
};

export type NotificationAPI = ReturnType<typeof notification.useNotification>[0];
Expand Down Expand Up @@ -148,8 +150,9 @@ const Toast = {
duration: useManualTimeout || sticky ? 0 : timeOutInSeconds,
message: toastMessage,
style: {},
className: "",
className: config.className || "",
onClose,
btn: config.customFooter,
};

if (type === "error") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ exports[`Misc APIs (E2E) > getFeatureToggles() 1`] = `
{
"alignmentCostPerGVx": 0.5,
"allowDeleteDatasets": true,
"bucketSaveWarningThreshold": 1000,
"costPerCreditInDollar": 5.75,
"costPerCreditInEuro": 5,
"defaultToLegacyBindings": false,
Expand Down
3 changes: 3 additions & 0 deletions frontend/javascripts/test/helpers/apiHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from "test/fixtures/hybridtracing_server_objects";
import type { ElementClass, ServerTracing } from "types/api_types";
import { getConstructorForElementClass } from "viewer/model/helpers/typed_buffer";
import { __setFeatures } from "features";

const TOKEN = "secure-token";
const ANNOTATION_TYPE = "annotationTypeValue";
Expand Down Expand Up @@ -377,6 +378,8 @@ export async function setupWebknossosForTesting(
segmentMeshController: { meshesGroupsPerSegmentId: {} },
});

__setFeatures({});

try {
await Model.fetch(
null, // no compound annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ describe("wkstore_adapter", () => {

// @ts-ignore pushTransaction is a private method
return pushQueue.pushTransaction(batch).then(() => {
expect(Store.dispatch).toHaveBeenCalledTimes(1);
expect(Store.dispatch).toHaveBeenCalledTimes(2);
expect(Store.dispatch).toHaveBeenCalledWith(expectedSaveQueueItems);
});
});
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/types/api_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ export type APIFeatureToggles = {
readonly publicDemoDatasetUrl: string;
readonly exportTiffMaxVolumeMVx: number;
readonly exportTiffMaxEdgeLengthVx: number;
readonly bucketSaveWarningThreshold: number;
readonly defaultToLegacyBindings: boolean;
readonly editableMappingsEnabled?: boolean;
readonly optInTabs?: Array<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export type AddPrecomputedMeshAction = ReturnType<typeof addPrecomputedMeshActio
export type SetOthersMayEditForAnnotationAction = ReturnType<
typeof setOthersMayEditForAnnotationAction
>;
export type showTooManyBucketsWarningToastAction = ReturnType<
typeof showTooManyBucketsWarningToastAction
>;

export type AnnotationActionTypes =
| InitializeAnnotationAction
Expand Down Expand Up @@ -373,6 +376,11 @@ export const setOthersMayEditForAnnotationAction = (othersMayEdit: boolean) =>
othersMayEdit,
}) as const;

export const showTooManyBucketsWarningToastAction = () =>
({
type: "SHOW_TOO_MANY_BUCKETS_WARNING_TOAST",
}) as const;

export const dispatchMaybeFetchMeshFilesAsync = async (
dispatch: Dispatch<any>,
segmentationLayer: APIDataLayer | null | undefined,
Expand Down
5 changes: 5 additions & 0 deletions frontend/javascripts/viewer/model/actions/save_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type PushSaveQueueTransaction = {
items: UpdateAction[];
transactionId: string;
};
export type NotifyAboutUpdateBucketAction = ReturnType<typeof notifyAboutUpdateBucketAction>; // use saga to listen to these. measure how long this takes and whether it creates a lag todo_c
type SaveNowAction = ReturnType<typeof saveNowAction>;
export type ShiftSaveQueueAction = ReturnType<typeof shiftSaveQueueAction>;
type DiscardSaveQueuesAction = ReturnType<typeof discardSaveQueuesAction>;
Expand All @@ -31,6 +32,7 @@ export type SaveAction =
| PushSaveQueueTransaction
| SaveNowAction
| ShiftSaveQueueAction
| NotifyAboutUpdateBucketAction
| DiscardSaveQueuesAction
| SetSaveBusyAction
| SetLastSaveTimestampAction
Expand Down Expand Up @@ -61,6 +63,9 @@ export const pushSaveQueueTransactionIsolated = (
transactionId: getUid(),
}) as const;

export const notifyAboutUpdateBucketAction = (count: number) =>
({ type: "NOTIFY_ABOUT_UPDATE_BUCKET_ACTION", count }) as const;

export const saveNowAction = () =>
({
type: "SAVE_NOW",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type DataCube from "viewer/model/bucket_data_handling/data_cube";
import { createCompressedUpdateBucketActions } from "viewer/model/bucket_data_handling/wkstore_adapter";
import Store from "viewer/store";
import { escalateErrorAction } from "../actions/actions";
import { pushSaveQueueTransaction } from "../actions/save_actions";
import { notifyAboutUpdateBucketAction, pushSaveQueueTransaction } from "../actions/save_actions";
import type { UpdateActionWithoutIsolationRequirement } from "../sagas/volume/update_actions";

// Only process the PushQueue after there was no user interaction (or bucket modification due to
Expand Down Expand Up @@ -155,7 +155,8 @@ class PushQueue {
createCompressedUpdateBucketActions(batch),
);
Store.dispatch(pushSaveQueueTransaction(items));

Store.dispatch(notifyAboutUpdateBucketAction(items.length));
console.log("notify about ", items.length, " items");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove

this.compressingBucketCount -= batch.length;
} catch (error) {
// See other usage of escalateErrorAction for a detailed explanation.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { getUpdateActionLog } from "admin/rest_api";
import features from "features";
import ErrorHandling from "libs/error_handling";
import Toast from "libs/toast";
import { sleep } from "libs/utils";
import _ from "lodash";
import { call, fork, put, takeEvery } from "typed-redux-saga";
import type { APIUpdateActionBatch } from "types/api_types";
import { getLayerByName, getMappingInfo } from "viewer/model/accessors/dataset_accessor";
import { setVersionNumberAction } from "viewer/model/actions/save_actions";
import { showTooManyBucketsWarningToastAction } from "viewer/model/actions/annotation_actions";
import {
type NotifyAboutUpdateBucketAction,
setVersionNumberAction,
} from "viewer/model/actions/save_actions";
import { applySkeletonUpdateActionsFromServerAction } from "viewer/model/actions/skeletontracing_actions";
import { applyVolumeUpdateActionsFromServerAction } from "viewer/model/actions/volumetracing_actions";
import { globalPositionToBucketPositionWithMag } from "viewer/model/helpers/position_converter";
import type { Saga } from "viewer/model/sagas/effect-generators";
import { select } from "viewer/model/sagas/effect-generators";
import { ensureWkReady } from "viewer/model/sagas/ready_sagas";
import { Model } from "viewer/singletons";
import { Model, Store } from "viewer/singletons";
import type { SkeletonTracing, VolumeTracing } from "viewer/store";
import { takeEveryWithBatchActionSupport } from "../saga_helpers";
import { updateLocalHdf5Mapping } from "../volume/mapping_saga";
Expand All @@ -31,11 +36,45 @@ export function* setupSavingToServer(): Saga<void> {
yield* takeEvery("INITIALIZE_ANNOTATION_WITH_TRACINGS", setupSavingForAnnotation);
yield* takeEveryWithBatchActionSupport("INITIALIZE_SKELETONTRACING", setupSavingForTracingType);
yield* takeEveryWithBatchActionSupport("INITIALIZE_VOLUMETRACING", setupSavingForTracingType);
yield* takeEvery("WK_READY", watchForNumberOfBucketsInSaveQueue);
}

const VERSION_POLL_INTERVAL_COLLAB = 10 * 1000;
const VERSION_POLL_INTERVAL_READ_ONLY = 60 * 1000;
const VERSION_POLL_INTERVAL_SINGLE_EDITOR = 30 * 1000;
const CHECK_NUMBER_OF_BUCKETS_IN_SAVE_QUEUE_INTERVAL = 10 * 1000;

function* watchForNumberOfBucketsInSaveQueue(): Saga<void> {
const bucketSaveWarningThreshold = features().bucketSaveWarningThreshold;
let bucketsForCurrentInterval = 0;
let currentBuckets: Array<number> = [];
yield* call(
setInterval,
() => {
const sumOfBuckets = _.sum(currentBuckets);
console.log(
"buckets in last interval: ",
bucketsForCurrentInterval,
"currentBucketsArray: ",
currentBuckets,
"sumOfBuckets: ",
sumOfBuckets,
Comment on lines +55 to +61
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove

);
if (sumOfBuckets > bucketSaveWarningThreshold) {
Store.dispatch(showTooManyBucketsWarningToastAction());
}
currentBuckets.push(bucketsForCurrentInterval);
if (currentBuckets.length > 12) {
currentBuckets.shift();
}
bucketsForCurrentInterval = 0;
},
CHECK_NUMBER_OF_BUCKETS_IN_SAVE_QUEUE_INTERVAL,
);
yield* takeEvery("NOTIFY_ABOUT_UPDATE_BUCKET_ACTION", (action: NotifyAboutUpdateBucketAction) => {
bucketsForCurrentInterval += action.count;
});
}

function* watchForSaveConflicts(): Saga<void> {
function* checkForNewVersion(): Saga<boolean> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Button, Checkbox, type CheckboxChangeEvent, Space } from "antd";
import { useInterval } from "libs/react_helpers";
import Toast from "libs/toast";
import UserLocalStorage from "libs/user_local_storage";
import { useCallback, useEffect, useRef } from "react";
import { useReduxActionListener } from "viewer/model/helpers/listener_helpers";

const TOO_MANY_BUCKETS_TOAST_KEY = "tooManyBucketsWarningModal";

export function TooManyBucketsWarning(): React.ReactNode {
const notificationAPIRef = useRef(Toast.notificationAPI);
const neverShowAgainRef = useRef(false);
const dontShowAgainInThisSessionRef = useRef(false);

useEffect(() => {
if (Toast.notificationAPI != null) {
notificationAPIRef.current = Toast.notificationAPI;
}
}, []);
useInterval(() => {
UserLocalStorage.setItem("suppressBucketWarning", "false");
console.log("resetting suppressBucketWarning to false every 120s for dev purposes");
}, 120 * 1000); //TODO_C dev

const onClose = useCallback(() => {
notificationAPIRef.current?.destroy(TOO_MANY_BUCKETS_TOAST_KEY);
dontShowAgainInThisSessionRef.current = true;
UserLocalStorage.setItem("suppressBucketWarning", neverShowAgainRef.current.toString());
}, []);
const handleCheckboxChange = (event: CheckboxChangeEvent) => {
neverShowAgainRef.current = event.target.checked;
};

const warningMessage =
"You are annotating a large area with fine magnifications. This can significantly slow down WEBKNOSSOS. Consider creating an annotation or annotation layer with restricted magnifications.";
const linkToDocs =
"https://docs.webknossos.org/volume_annotation/import_export.html#restricting-magnifications";
const neverShowAgainCheckbox = (
<Checkbox onChange={handleCheckboxChange} style={{ marginTop: "8px", marginBottom: "5px" }}>
Never show this again
</Checkbox>
);
const closeButton = (
<Button
onClick={() => {
onClose();
}}
>
Close
</Button>
);
const linkToDocsButton = (
<Button href={linkToDocs} target="_blank" rel="noopener noreferrer" type="primary">
Learn how
</Button>
);
const footer = (
<div>
<Space>
{linkToDocsButton}
{closeButton}
</Space>
</div>
);

const showWarningToast = () => {
const supressTooManyBucketsWarning = UserLocalStorage.getItem("suppressBucketWarning");
if (notificationAPIRef.current == null) {
return null;
}
if (supressTooManyBucketsWarning !== "true" && dontShowAgainInThisSessionRef.current !== true) {
console.warn(warningMessage + " For more info, visit: " + linkToDocs);
Toast.warning(
<>
{warningMessage}
<br />
{neverShowAgainCheckbox}
</>,
{
customFooter: footer,
key: TOO_MANY_BUCKETS_TOAST_KEY,
sticky: true,
onClose,
className: "many-buckets-warning",
},
);
} else {
console.log("suppressing warning toast");
}
};
useReduxActionListener("SHOW_TOO_MANY_BUCKETS_WARNING_TOAST", () => showWarningToast());
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import WelcomeToast from "viewer/view/novel_user_experiences/welcome_toast";
import { importTracingFiles } from "viewer/view/right-border-tabs/trees_tab/skeleton_tab_view";
import TracingView from "viewer/view/tracing_view";
import VersionView from "viewer/view/version_view";
import { TooManyBucketsWarning } from "../components/many_buckets_warning";
import TabTitle from "../components/tab_title_component";
import { determineLayout } from "./default_layout_configs";
import FlexLayoutWrapper from "./flex_layout_wrapper";
Expand Down Expand Up @@ -364,6 +365,7 @@ class TracingLayoutView extends React.PureComponent<PropsWithRouter, State> {
display: "flex",
}}
>
<TooManyBucketsWarning />
<ActionBarView
layoutProps={{
storedLayoutNamesForView: currentLayoutNames,
Expand Down
6 changes: 6 additions & 0 deletions frontend/stylesheets/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -716,4 +716,10 @@ button.narrow {
>div:last-of-type {
margin-bottom: 15px;
}
}

.many-buckets-warning{
.ant-notification-notice-btn{
margin-top: 0 !important;
}
}