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 UI for segment statistics (volume and bbox) #7249

Merged
merged 61 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
d2f1211
very much WIP: first steps towards route and frontend
dieknolle3333 Jul 14, 2023
739e34e
Merge branch 'master' into segment-volume-route
dieknolle3333 Jul 31, 2023
3f2da45
add url params
dieknolle3333 Jul 31, 2023
4a364c1
show segment size in context menu without unit
dieknolle3333 Aug 1, 2023
3319ab5
start tests
dieknolle3333 Aug 1, 2023
b33297f
add format in multiple dimensions (early implementation) and resp. tests
dieknolle3333 Aug 4, 2023
2de3d67
show volume in context menu of segment
dieknolle3333 Aug 4, 2023
fc8cfbf
Merge branch 'master' into segment-volume-route
dieknolle3333 Aug 4, 2023
537a8c0
add reload button for segment volume
dieknolle3333 Aug 4, 2023
dabc845
add segment statistics entry in group menu and style icons
dieknolle3333 Aug 8, 2023
7a27826
WIP: start implementing segment statistics modal
dieknolle3333 Aug 8, 2023
4c819a4
move statistics modal outside of context menu
dieknolle3333 Aug 11, 2023
a321f2c
show segment sizes in modal
dieknolle3333 Aug 11, 2023
c8e249e
merge master
dieknolle3333 Aug 11, 2023
efa3631
lint
dieknolle3333 Aug 11, 2023
e1223fa
add table to statistics modal
dieknolle3333 Aug 11, 2023
2bfb726
export segment sizes to CSV
dieknolle3333 Aug 11, 2023
5d874a2
make linter happier before I leave
dieknolle3333 Aug 11, 2023
0300bba
revert unintentional change
dieknolle3333 Aug 28, 2023
0ebca26
WIP: add segment bounding box route
fm3 Aug 28, 2023
9c1101f
calculate real segment bbox
fm3 Aug 28, 2023
b699121
performance optimization: filter out inner bucket positions
fm3 Aug 28, 2023
1aa00b6
remove unused import
fm3 Aug 28, 2023
5e4f204
make statistics routes post, allow for multiple segment ids
fm3 Aug 28, 2023
326c14b
rename method
fm3 Aug 28, 2023
9364769
do zero check without type conversion
fm3 Aug 28, 2023
b1fd9b3
Revert "do zero check without type conversion"
fm3 Aug 28, 2023
fc0ad22
compare to segment id correctly
fm3 Aug 28, 2023
1a8e82e
find group segments recursively
dieknolle3333 Aug 28, 2023
b01faf4
very much WIP: use batch request in frontend
dieknolle3333 Aug 28, 2023
6fbe58f
use batch requests in frontend
dieknolle3333 Aug 29, 2023
d7e6910
use bounding box route
dieknolle3333 Aug 29, 2023
cf941c8
Merge branch 'master' into segment-volume-route
dieknolle3333 Aug 29, 2023
0489093
show bounding box in statistics modal
dieknolle3333 Aug 30, 2023
5d2df86
add bounding box to modal
dieknolle3333 Aug 30, 2023
acbb128
add bounding boxes to modal and CSV and add spin while loading data
dieknolle3333 Sep 1, 2023
fba6b52
move cyclic dependency
dieknolle3333 Sep 1, 2023
522869e
Merge branch 'master' into segment-volume-route
dieknolle3333 Sep 1, 2023
2eb9817
WIP: add bounding box to indiv. segment context menu in viewport
dieknolle3333 Sep 1, 2023
6caf73f
add bounding box size to indiv. segment context menu
dieknolle3333 Sep 3, 2023
28fee08
improve bounding box info in indiv. segment context menu
dieknolle3333 Sep 4, 2023
abbd2e1
improve dependencies for requests of bounding box info
dieknolle3333 Sep 4, 2023
bde4511
minor changes to make code ready for review
dieknolle3333 Sep 4, 2023
3ff196b
lint
dieknolle3333 Sep 4, 2023
c27dfdc
Merge branch 'master' into segment-volume-route
dieknolle3333 Sep 4, 2023
4645164
add changelog
dieknolle3333 Sep 4, 2023
da02af4
add keys to table rows
dieknolle3333 Sep 5, 2023
a00ae72
fix errors for datasets with fallback layers
dieknolle3333 Sep 5, 2023
11eb4bb
very much WIP: started to address review
dieknolle3333 Sep 7, 2023
7702b60
[WIP because of failing tests] address review
dieknolle3333 Sep 8, 2023
8b9c9fa
add tests
dieknolle3333 Sep 10, 2023
0354378
use clean way to find out a segments parent group
dieknolle3333 Sep 10, 2023
4e1d173
revert change in test.sh
dieknolle3333 Sep 10, 2023
69c128d
fix frontend merge conflicts
dieknolle3333 Sep 14, 2023
be623d7
trying to fix backend merge conflicts
dieknolle3333 Sep 14, 2023
79c77fc
fix imports after merge
fm3 Sep 14, 2023
41083de
fix saving before opening modal
dieknolle3333 Sep 15, 2023
f6037e0
address re-review
dieknolle3333 Sep 15, 2023
25a9fe0
improve performance of segment list tab
philippotto Sep 18, 2023
4edf2f3
improve csv export and change positioning of export modal
philippotto Sep 18, 2023
351ff05
Merge branch 'master' of github.com:scalableminds/webknossos into seg…
philippotto Sep 18, 2023
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 CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added disabled drag handles to volume and skeleton layers for visual consistency. These layer cannot be dragged or reordered. [#7295](https://github.com/scalableminds/webknossos/pull/7295)
- Dataset thumbnails for grayscale layers can now be colored using the value in the view configuration. [#7255](https://github.com/scalableminds/webknossos/pull/7255)
- OpenID Connect authorization is now compatible with Providers that send the user information in an id_token. [#7294](https://github.com/scalableminds/webknossos/pull/7294)
- Volume and bounding box information is shown in segments' context menus as well as in a separate modal in the segments tab. There is also an option to export the statistics. [#7249](https://github.com/scalableminds/webknossos/pull/7249)
- A banner underneath the navigation bar informs about current and upcoming maintenances of WEBKNOSSOS. [#7284](https://github.com/scalableminds/webknossos/pull/7284)
- The AI-based quick select tool can now also be used for ND datasets. [#7287](https://github.com/scalableminds/webknossos/pull/7287)

Expand Down
32 changes: 32 additions & 0 deletions frontend/javascripts/admin/admin_rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,38 @@ export function getNewestVersionForTracing(
);
}

export function getSegmentVolumes(
philippotto marked this conversation as resolved.
Show resolved Hide resolved
tracingStoreUrl: string,
tracingId: string,
mag: Vector3,
segmentIds: Array<number>,
): Promise<number[]> {
return doWithToken((token) =>
Request.sendJSONReceiveJSON(
`${tracingStoreUrl}/tracings/volume/${tracingId}/segmentStatistics/volume?token=${token}`,
{
data: { mag, segmentIds },
},
),
);
}

export function getSegmentBoundingBoxes(
tracingStoreUrl: string,
tracingId: string,
mag: Vector3,
segmentIds: Array<number>,
): Promise<Array<{ topLeft: Vector3; width: number; height: number; depth: number }>> {
return doWithToken((token) =>
Request.sendJSONReceiveJSON(
`${tracingStoreUrl}/tracings/volume/${tracingId}/segmentStatistics/boundingBox?token=${token}`,
{
data: { mag, segmentIds },
},
),
);
}

export async function importVolumeTracing(
tracing: Tracing,
volumeTracing: VolumeTracing,
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/libs/browser_feature_check.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default function checkBrowserFeatures() {
new AbortController();
Object.fromEntries([]);
new BigUint64Array(1);
"hello".replaceAll("l", "k");
} catch (exception) {
console.error(
"This browser lacks support for some modern features. Exception caught during test of features:",
Expand Down
29 changes: 27 additions & 2 deletions frontend/javascripts/libs/format_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,33 @@ const nmFactorToUnit = new Map([
[1e9, "m"],
[1e12, "km"],
]);
export function formatNumberToLength(lengthInNm: number): string {
return formatNumberToUnit(lengthInNm, nmFactorToUnit);
export function formatNumberToLength(lengthInNm: number, decimalPrecision: number = 1): string {
return formatNumberToUnit(lengthInNm, nmFactorToUnit, true, decimalPrecision);
}

const nmFactorToUnit2D = new Map([
[1e-6, "pm²"],
[1, "nm²"],
[1e6, "µm²"],
[1e12, "mm²"],
[1e18, "m²"],
[1e24, "km²"],
]);

export function formatNumberToArea(lengthInNm2: number, decimalPrecision: number = 1): string {
return formatNumberToUnit(lengthInNm2, nmFactorToUnit2D, true, decimalPrecision);
}

const nmFactorToUnit3D = new Map([
[1e-9, "pm³"],
[1, "nm³"],
[1e9, "µm³"],
[1e18, "mm³"],
[1e27, "m³"],
[1e36, "km³"],
]);
export function formatNumberToVolume(lengthInNm3: number, decimalPrecision: number = 1): string {
return formatNumberToUnit(lengthInNm3, nmFactorToUnit3D, true, decimalPrecision);
}

const byteFactorToUnit = new Map([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The save saga uses a retry mechanism which is based
// on exponential back-off.
export const PUSH_THROTTLE_TIME = 30000; // 30s
export const PUSH_THROTTLE_TIME = 300000; // 30s

export const SAVE_RETRY_WAITING_TIME = 2000;
export const MAX_SAVE_RETRY_WAITING_TIME = 300000; // 5m
Expand Down
101 changes: 97 additions & 4 deletions frontend/javascripts/oxalis/view/context_menu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CopyOutlined, PushpinOutlined } from "@ant-design/icons";
import { CopyOutlined, PushpinOutlined, ReloadOutlined } from "@ant-design/icons";
import type { Dispatch } from "redux";
import { Dropdown, Empty, notification, Tooltip, Popover, Input, MenuProps } from "antd";
import { connect } from "react-redux";
import React, { createContext, MouseEvent, useContext } from "react";
import React, { createContext, MouseEvent, useContext, useState } from "react";
import type {
APIConnectomeFile,
APIDataset,
Expand Down Expand Up @@ -52,7 +52,7 @@ import {
deleteBranchpointByIdAction,
addTreesAndGroupsAction,
} from "oxalis/model/actions/skeletontracing_actions";
import { formatNumberToLength, formatLengthAsVx } from "libs/format_utils";
import { formatNumberToLength, formatLengthAsVx, formatNumberToVolume } from "libs/format_utils";
import {
getActiveSegmentationTracing,
getSegmentsForLayer,
Expand All @@ -69,6 +69,7 @@ import {
import {
getVisibleSegmentationLayer,
getMappingInfo,
getResolutionInfo,
} from "oxalis/model/accessors/dataset_accessor";
import {
loadAgglomerateSkeletonAtPosition,
Expand Down Expand Up @@ -101,6 +102,9 @@ import {
MenuItemType,
SubMenuType,
} from "antd/lib/menu/hooks/useItems";
import { getSegmentBoundingBoxes, getSegmentVolumes } from "admin/admin_rest_api";
import { useFetch } from "libs/react_helpers";
import { AsyncIconButton } from "components/async_clickables";
import { type AdditionalCoordinate } from "types/api_flow_types";

type ContextMenuContextValue = React.MutableRefObject<HTMLElement | null> | null;
Expand Down Expand Up @@ -1084,6 +1088,9 @@ function getInfoMenuItem(
}

function ContextMenuInner(propsWithInputRef: Props) {
const [lastTimeSegmentInfoShouldBeFetched, setLastTimeSegmentInfoShouldBeFetched] = useState(
new Date(),
);
const inputRef = useContext(ContextMenuContext);
const { ...props } = propsWithInputRef;
const {
Expand All @@ -1098,6 +1105,46 @@ function ContextMenuInner(propsWithInputRef: Props) {
maybeViewport,
} = props;

const segmentIdAtPosition = globalPosition != null ? getSegmentIdForPosition(globalPosition) : 0;
const { visibleSegmentationLayer, volumeTracing } = props;
const hasNoFallbackLayer =
visibleSegmentationLayer != null &&
"fallbackLayer" in visibleSegmentationLayer &&
visibleSegmentationLayer.fallbackLayer == null;
const [segmentVolume, boundingBoxInfo] = useFetch(
async () => {
if (contextMenuPosition == null || volumeTracing == null || !hasNoFallbackLayer) {
return [];
} else {
const tracingId = volumeTracing.tracingId;
const tracingStoreUrl = Store.getState().tracing.tracingStore.url;
const mag = getResolutionInfo(visibleSegmentationLayer.resolutions);
const [segmentSize] = await getSegmentVolumes(
tracingStoreUrl,
tracingId,
mag.getLowestResolution(),
[segmentIdAtPosition],
);
const [boundingBox] = await getSegmentBoundingBoxes(
tracingStoreUrl,
tracingId,
mag.getLowestResolution(),
[segmentIdAtPosition],
);
const boundingBoxTopLeftString = `(${boundingBox.topLeft[0]}, ${boundingBox.topLeft[1]}, ${boundingBox.topLeft[2]})`;
const boundingBoxSizeString = `(${boundingBox.width}, ${boundingBox.height}, ${boundingBox.depth})`;
return [
formatNumberToVolume(segmentSize),
`${boundingBoxTopLeftString}, ${boundingBoxSizeString}`,
];
}
},
["loading", "loading"],
// Update segment infos when opening the context menu, in case the annotation was saved since the context menu was last opened.
// Of course the info should also be updated when the menu is opened for another segment, or after the refresh button was pressed.
[contextMenuPosition, segmentIdAtPosition, lastTimeSegmentInfoShouldBeFetched],
);

if (contextMenuPosition == null || maybeViewport == null) {
return <></>;
}
Expand Down Expand Up @@ -1138,7 +1185,6 @@ function ContextMenuInner(propsWithInputRef: Props) {
nodeContextMenuNode != null
? positionToString(nodeContextMenuNode.position, nodeContextMenuNode.additionalCoordinates)
: "";
const segmentIdAtPosition = globalPosition != null ? getSegmentIdForPosition(globalPosition) : 0;
const infoRows = [];

if (maybeClickedNodeId != null && nodeContextMenuTree != null) {
Expand Down Expand Up @@ -1175,6 +1221,53 @@ function ContextMenuInner(propsWithInputRef: Props) {
);
}

const handleRefreshSegmentVolume = async () => {
await api.tracing.save();
setLastTimeSegmentInfoShouldBeFetched(new Date());
};

const refreshButton = (
<Tooltip title="Update this statistic">
<AsyncIconButton
onClick={handleRefreshSegmentVolume}
type="primary"
icon={<ReloadOutlined />}
style={{ marginLeft: 4 }}
/>
</Tooltip>
);

if (hasNoFallbackLayer) {
infoRows.push(
getInfoMenuItem(
"volumeInfo",
<>
<i className="fas fa-expand-alt segment-context-icon" />
Volume: {segmentVolume}
{copyIconWithTooltip(segmentVolume as string, "Copy volume")}
{refreshButton}
</>,
),
);
}

if (hasNoFallbackLayer) {
infoRows.push(
getInfoMenuItem(
"boundingBoxPositionInfo",
<>
<i className="fas fa-dice-d6 segment-context-icon" />
<>Bounding Box: </>
<div style={{ marginLeft: 22, marginTop: -5 }}>
{boundingBoxInfo}
{copyIconWithTooltip(boundingBoxInfo as string, "Copy BBox top left point and extent")}
{refreshButton}
</div>
</>,
),
);
}

if (distanceToSelection != null) {
infoRows.push(
getInfoMenuItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ type Props = {
mapId: (arg0: number) => number;
isJSONMappingEnabled: boolean;
mappingInfo: ActiveMappingInfo;
isHoveredSegmentId: boolean;
centeredSegmentId: number | null | undefined;
selectedSegmentIds: number[] | null | undefined;
activeCellId: number | null | undefined;
Expand Down Expand Up @@ -361,7 +360,6 @@ function _SegmentListItem({
mapId,
isJSONMappingEnabled,
mappingInfo,
isHoveredSegmentId,
centeredSegmentId,
selectedSegmentIds,
activeCellId,
Expand Down Expand Up @@ -392,6 +390,9 @@ function _SegmentListItem({
(state: OxalisState) => getSegmentColorAsHSLA(state, mappedId),
(a: Vector4, b: Vector4) => V4.isEqual(a, b),
);
const isHoveredSegmentId = useSelector(
(state: OxalisState) => state.temporaryConfiguration.hoveredSegmentId === segment.id,
);

const segmentColorRGBA = Utils.hslaToRgba(segmentColorHSLA);

Expand Down
Loading