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

Register all segments in a bounding box #7979

Merged
merged 23 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
34d847f
move delete, go to center and export options for bounding boxes to co…
dieknolle3333 Aug 6, 2024
a2be950
register all segments in a bounding box
dieknolle3333 Aug 6, 2024
7e2a53b
lint
dieknolle3333 Aug 7, 2024
925c2a3
add limits for segment count and bb volume
dieknolle3333 Aug 7, 2024
78a887f
add limits for segment number and bb volume; improve styling
dieknolle3333 Aug 7, 2024
bbfac6f
merge master
dieknolle3333 Aug 7, 2024
dc36fbd
remove dev edits
dieknolle3333 Aug 7, 2024
aae02ea
require visible segmentation layer to register segments
dieknolle3333 Aug 8, 2024
963f010
register segments to seperate new group
dieknolle3333 Aug 8, 2024
16ca245
add tooltip
dieknolle3333 Aug 8, 2024
2ff64de
add margin and add info icon for tooltip
dieknolle3333 Aug 8, 2024
7484c13
move method
dieknolle3333 Aug 8, 2024
2d5265d
add changelog
dieknolle3333 Aug 8, 2024
c7b2700
improve changelog
dieknolle3333 Aug 8, 2024
c3b8a23
WIP not tested: address review
dieknolle3333 Aug 12, 2024
ee6bbb4
address review pt 2
dieknolle3333 Aug 12, 2024
a2442fe
merge master
dieknolle3333 Aug 12, 2024
87ebda6
fix some errors that occured when solving merge conflicts
dieknolle3333 Aug 12, 2024
59fa49d
unify and fix the bbox context menu in case the annotation is not edi…
dieknolle3333 Aug 12, 2024
be78bf0
improve toast message
dieknolle3333 Aug 12, 2024
ff6b51e
fix that segment 0 was registered
dieknolle3333 Aug 12, 2024
7574861
Merge branch 'master' into register-all-segments-in-bb
dieknolle3333 Aug 13, 2024
a6fe782
small fixes
dieknolle3333 Aug 13, 2024
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 @@ -28,6 +28,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- To improve performance, only the visible bounding boxes are rendered in the bounding box tab (so-called virtualization). [#7974](https://github.com/scalableminds/webknossos/pull/7974)
- Added support for reading zstd-compressed zarr2 datasets [#7964](https://github.com/scalableminds/webknossos/pull/7964)
- The alignment job is in a separate tab of the "AI Tools" now. The "Align Sections" AI job now supports including manually created matches between adjacent section given as skeletons. [#7967](https://github.com/scalableminds/webknossos/pull/7967)
- Added a feature to register all segments for a given bounding box at once via the context menu of the bounding box. [#7979](https://github.com/scalableminds/webknossos/pull/7979)

### Changed
- Replaced skeleton tab component with antd's `<Tree />`component. Added support for selecting tree ranges with SHIFT. [#7819](https://github.com/scalableminds/webknossos/pull/7819)
Expand Down
83 changes: 82 additions & 1 deletion frontend/javascripts/oxalis/api/api_latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ import Constants, {
} from "oxalis/constants";
import DataLayer from "oxalis/model/data_layer";
import type { OxalisModel } from "oxalis/model";
import { Model } from "oxalis/singletons";
import { Model, api } from "oxalis/singletons";
import Request from "libs/request";
import type {
MappingType,
Expand Down Expand Up @@ -599,6 +599,87 @@ class TracingApi {
);
}

/**
* Registers all segments that exist at least partially in the given bounding box.
*
* @example
* api.tracing.registerSegmentsForBoundingBox(
* [0, 0, 0],
* [10, 10, 10],
* "My Bounding Box"
* );
*/
registerSegmentsForBoundingBox = async (
min: Vector3,
max: Vector3,
bbName: string,
options?: { maximumSegmentCount?: number; maximumVolume?: number },
) => {
const maximumVolume = options?.maximumVolume ?? Constants.REGISTER_SEGMENTS_BB_MAX_VOLUME_VX;
const maximumSegmentCount =
options?.maximumSegmentCount ?? Constants.REGISTER_SEGMENTS_BB_MAX_SEGMENT_COUNT;
const shape = Utils.computeShapeFromBoundingBox({ min, max });
const volume = Math.ceil(shape[0] * shape[1] * shape[2]);
if (volume > maximumVolume) {
Toast.error(
`The volume of the bounding box exeeds ${maximumVolume} Vx, please make it smaller.`,
);
return;
} else if (volume > maximumVolume / 8) {
Toast.warning(
"The volume of the bounding box is very large, registering all segments might take a while.",
);
}

const segmentationLayerName = api.data.getSegmentationLayerNames()[0];
const data = await api.data.getDataForBoundingBox(segmentationLayerName, {
min,
max,
});

const segmentIdToPosition = new Map();
let idx = 0;
for (let z = min[2]; z < max[2]; z++) {
for (let y = min[1]; y < max[1]; y++) {
for (let x = min[0]; x < max[0]; x++) {
const id = data[idx];
if (id !== 0 && !segmentIdToPosition.has(id)) {
segmentIdToPosition.set(id, [x, y, z]);
}
idx++;
}
}
}

const segmentIdCount = Array.from(segmentIdToPosition.entries()).length;
const halfMaxNoSegments = maximumSegmentCount / 2;
if (segmentIdCount > maximumSegmentCount) {
Toast.error(
`The given bounding box contains ${segmentIdCount} segments, but only ${maximumSegmentCount} segments can be registered at once. Please reduce the size of the bounding box.`,
);
return;
} else if (segmentIdCount > halfMaxNoSegments) {
Toast.warning(
`The bounding box contains more than ${halfMaxNoSegments} segments. Registering all segments might take a while.`,
);
}

const groupId = api.tracing.createSegmentGroup(
`Segments for ${bbName}`,
-1,
segmentationLayerName,
);
const updateSegmentActions: BatchableUpdateSegmentAction[] = [];
for (const [segmentId, position] of segmentIdToPosition.entries()) {
api.tracing.registerSegment(segmentId, position, undefined, segmentationLayerName);
updateSegmentActions.push(
updateSegmentAction(segmentId, { groupId, id: segmentId }, segmentationLayerName),
);
}
if (updateSegmentActions.length > 0)
Store.dispatch(batchUpdateGroupsAndSegmentsAction(updateSegmentActions));
};

/**
* Gets a segment object within the referenced volume layer. Note that this object
* does not support any modifications made to it.
Expand Down
2 changes: 2 additions & 0 deletions frontend/javascripts/oxalis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ const Constants = {
SCALEBAR_HEIGHT: 22,
SCALEBAR_OFFSET: 10,
OBJECT_ID_STRING_LENGTH: 24,
REGISTER_SEGMENTS_BB_MAX_VOLUME_VX: 512 * 512 * 512,
REGISTER_SEGMENTS_BB_MAX_SEGMENT_COUNT: 5000,
};
export default Constants;

Expand Down
151 changes: 116 additions & 35 deletions frontend/javascripts/oxalis/view/components/setting_input_views.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import { Row, Col, Slider, InputNumber, Switch, Input, Select, Popover, PopoverProps } from "antd";
import { DeleteOutlined, DownloadOutlined, EditOutlined, ScanOutlined } from "@ant-design/icons";
import {
Row,
Col,
Slider,
InputNumber,
Switch,
Input,
Select,
Popover,
PopoverProps,
Dropdown,
MenuProps,
} from "antd";
import {
BorderInnerOutlined,
DeleteOutlined,
DownloadOutlined,
EditOutlined,
EllipsisOutlined,
InfoCircleOutlined,
ScanOutlined,
} from "@ant-design/icons";
import * as React from "react";
import _ from "lodash";
import type { Vector3, Vector6 } from "oxalis/constants";
import { type Vector3, type Vector6 } from "oxalis/constants";
import * as Utils from "libs/utils";
import messages from "messages";
import { getVisibleSegmentationLayer } from "oxalis/model/accessors/dataset_accessor";
import { connect } from "react-redux";
import { OxalisState } from "oxalis/store";
import { APISegmentationLayer } from "types/api_flow_types";
import { api } from "oxalis/singletons";
import FastTooltip from "components/fast_tooltip";

const ROW_GUTTER = 1;
Expand Down Expand Up @@ -367,6 +392,7 @@ type UserBoundingBoxInputProps = {
disabled: boolean;
isLockedByOwner: boolean;
isOwner: boolean;
visibleSegmentationLayer: APISegmentationLayer | null | undefined;
};
type State = {
isEditing: boolean;
Expand All @@ -377,7 +403,7 @@ type State = {

const FORMAT_TOOLTIP = "Format: minX, minY, minZ, width, height, depth";

export class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInputProps, State> {
class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInputProps, State> {
constructor(props: UserBoundingBoxInputProps) {
super(props);
this.state = {
Expand Down Expand Up @@ -455,6 +481,12 @@ export class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInp
}
};

onRegisterSegmentsForBB(value: Vector6, name: string): void {
const min: Vector3 = [value[0], value[1], value[2]];
const max: Vector3 = [value[0] + value[3], value[1] + value[4], value[2] + value[5]];
api.tracing.registerSegmentsForBoundingBox(min, max, name);
}

render() {
const { name } = this.state;
const {
Expand All @@ -469,26 +501,82 @@ export class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInp
isOwner,
} = this.props;
const upscaledColor = color.map((colorPart) => colorPart * 255) as any as Vector3;
const iconStyle = {
marginRight: 0,
const marginRightStyle = {
marginRight: 8,
};
const marginLeftStyle = {
marginLeft: 6,
};
const disabledIconStyle = { ...iconStyle, opacity: 0.5, cursor: "not-allowed" };
const exportIconStyle = isExportEnabled ? iconStyle : disabledIconStyle;
const exportButtonTooltip = isExportEnabled
? "Export data from this bounding box."
: messages["data.bounding_box_export_not_supported"];
const exportColumn = isExportEnabled ? (
<Col span={2}>
<FastTooltip title={exportButtonTooltip} placement="top-end">
<DownloadOutlined onClick={onExport} style={exportIconStyle} />
</FastTooltip>
</Col>
) : null;
const disabledIconStyle = { ...marginRightStyle, opacity: 0.5, cursor: "not-allowed" };
const exportButton = (
<>
<DownloadOutlined style={isExportEnabled ? marginRightStyle : disabledIconStyle} />
Export data
</>
);
const deleteButton = (
<>
<DeleteOutlined style={disabled ? disabledIconStyle : marginRightStyle} />
Delete
</>
);
const editingDisallowedExplanation = messages["tracing.read_only_mode_notification"](
isLockedByOwner,
isOwner,
);
const isDeleteEnabled = !disabled && this.props.visibleSegmentationLayer != null;

const getContextMenu = () => {
const items: MenuProps["items"] = [
{
key: "registerSegments",
label: (
<>
Register all segments in this bounding box
<FastTooltip title="Moves/registers all segments within this bounding box into a new segment group">
<InfoCircleOutlined style={marginLeftStyle} />
</FastTooltip>
</>
),
icon: <ScanOutlined />,
onClick: () => this.onRegisterSegmentsForBB(this.props.value, name),
disabled: this.props.visibleSegmentationLayer == null || disabled,
},
{
key: "goToCenter",
label: "Go to center",
icon: <BorderInnerOutlined />,
onClick: onGoToBoundingBox,
},
{
key: "export",
label: isExportEnabled ? (
exportButton
) : (
<FastTooltip title={editingDisallowedExplanation}>{exportButton}</FastTooltip>
),
disabled: !isExportEnabled,
onClick: onExport,
},
{
key: "delete",
label: isDeleteEnabled ? (
deleteButton
) : (
<FastTooltip title={editingDisallowedExplanation}>{deleteButton}</FastTooltip>
),
onClick: onDelete,
disabled: !isDeleteEnabled,
},
];

return (
<Dropdown menu={{ items }}>
<EllipsisOutlined style={marginLeftStyle} />
</Dropdown>
);
};

return (
<>
<Row
Expand Down Expand Up @@ -526,17 +614,7 @@ export class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInp
</span>
</FastTooltip>
</Col>
{exportColumn}
<Col span={2}>
<FastTooltip
title={disabled ? editingDisallowedExplanation : "Delete this bounding box."}
>
<DeleteOutlined
onClick={disabled ? () => {} : onDelete}
style={disabled ? disabledIconStyle : iconStyle}
/>
</FastTooltip>
</Col>
<Col span={2}>{getContextMenu()}</Col>
</Row>
<Row
style={{
Expand Down Expand Up @@ -571,21 +649,24 @@ export class UserBoundingBoxInput extends React.PureComponent<UserBoundingBoxInp
<ColorSetting
value={Utils.rgbToHex(upscaledColor)}
onChange={this.handleColorChange}
style={iconStyle}
style={marginLeftStyle}
disabled={disabled}
/>
</FastTooltip>
</Col>
<Col span={2}>
<FastTooltip title="Go to the center of the bounding box.">
<ScanOutlined onClick={onGoToBoundingBox} style={{ ...iconStyle, marginTop: 6 }} />
</FastTooltip>
</Col>
</Row>
</>
);
}
}

const mapStateToProps = (state: OxalisState) => ({
visibleSegmentationLayer: getVisibleSegmentationLayer(state),
});

const connector = connect(mapStateToProps)(UserBoundingBoxInput);
export default connector;

type ColorSettingPropTypes = {
value: string;
onChange: (value: Vector3) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PlusSquareOutlined } from "@ant-design/icons";
import { useSelector, useDispatch } from "react-redux";
import React, { useEffect, useRef, useState } from "react";
import _ from "lodash";
import { UserBoundingBoxInput } from "oxalis/view/components/setting_input_views";
import UserBoundingBoxInput from "oxalis/view/components/setting_input_views";
import { Vector3, Vector6, BoundingBoxType, ControlModeEnum } from "oxalis/constants";
import {
changeUserBoundingBoxAction,
Expand Down
2 changes: 1 addition & 1 deletion frontend/stylesheets/trace_view/_right_menu.less
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
border-bottom: none;
}
.ant-table-cell {
padding: 0px !important;
padding: 0px 20px !important;
}
.ant-table-row:last-child {
border-bottom: none;
Expand Down