Skip to content

Commit

Permalink
Show warnings if bounding boxes for CNN training are suboptimal. Make…
Browse files Browse the repository at this point in the history
… errors and warnings more prominent by using Alerts. Include annotation ID and topleft + size for offending boxes.
  • Loading branch information
daniel-wer committed Nov 25, 2024
1 parent 50b8072 commit 7d7945c
Showing 1 changed file with 126 additions and 28 deletions.
154 changes: 126 additions & 28 deletions frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box";
import { formatVoxels } from "libs/format_utils";
import * as Utils from "libs/utils";
import type { APIAnnotation, APIDataset, ServerVolumeTracing } from "types/api_flow_types";
import type { Vector3 } from "oxalis/constants";
import type { Vector3, Vector6 } from "oxalis/constants";
import { serverVolumeToClientVolumeTracing } from "oxalis/model/reducers/volumetracing_reducer";
import { convertUserBoundingBoxesFromServerToFrontend } from "oxalis/model/reducers/reducer_helpers";
import { computeArrayFromBoundingBox } from "libs/utils";

const { TextArea } = Input;
const FormItem = Form.Item;
Expand Down Expand Up @@ -66,7 +67,7 @@ const ExperimentalWarning = () => (
<Row style={{ display: "grid", marginBottom: 16 }}>
<Alert
message="Please note that this feature is experimental. All bounding boxes should have equal dimensions or have dimensions which are multiples of the smallest bounding box. Ensure the size is not too small (we recommend at least 10 Vx per dimension) and choose boxes that represent the data well."
type="warning"
type="info"
showIcon
/>
</Row>
Expand Down Expand Up @@ -216,19 +217,29 @@ export function TrainAiModelTab<GenericAnnotation extends APIAnnotation | Hybrid
modelCategory: AiModelCategory.EM_NEURONS,
};

const userBoundingBoxes = annotationInfos.flatMap(({ userBoundingBoxes }) => userBoundingBoxes);
const userBoundingBoxes = annotationInfos.flatMap(({ userBoundingBoxes, annotation }) =>
userBoundingBoxes.map((box) => ({
...box,
annotationId: "id" in annotation ? annotation.id : annotation.annotationId,
})),
);

const bboxesVoxelCount = _.sum(
(userBoundingBoxes || []).map((bbox) => new BoundingBox(bbox.boundingBox).getVolume()),
);

const { areSomeAnnotationsInvalid, invalidAnnotationsReason } =
areInvalidAnnotationsIncluded(annotationInfos);
const { areSomeBBoxesInvalid, invalidBBoxesReason } =
areInvalidBoundingBoxesIncluded(userBoundingBoxes);
const invalidReasons = [invalidAnnotationsReason, invalidBBoxesReason]
.filter((reason) => reason)
.join("\n");
const { hasAnnotationErrors, errors: annotationErrors } =
checkAnnotationsForErrorsAndWarnings(annotationInfos);
const {
hasBBoxErrors,
hasBBoxWarnings,
errors: bboxErrors,
warnings: bboxWarnings,
} = checkBoundingBoxesForErrorsAndWarnings(userBoundingBoxes);
const hasErrors = hasAnnotationErrors || hasBBoxErrors;
const hasWarnings = hasBBoxWarnings;
const errors = [...annotationErrors, ...bboxErrors];
const warnings = bboxWarnings;

return (
<Form
Expand Down Expand Up @@ -332,16 +343,46 @@ export function TrainAiModelTab<GenericAnnotation extends APIAnnotation | Hybrid
</div>
</FormItem>
) : null}

{hasErrors
? errors.map((error) => (
<Alert
key={error}
description={error}
style={{
marginBottom: 12,
whiteSpace: "pre-line",
}}
type="error"
showIcon
/>
))
: null}
{hasWarnings
? warnings.map((warning) => (
<Alert
key={warning}
description={warning}
style={{
marginBottom: 12,
whiteSpace: "pre-line",
}}
type="warning"
showIcon
/>
))
: null}

<FormItem>
<Tooltip title={invalidReasons}>
<Tooltip title={hasErrors ? "Solve the errors displayed above before continuing." : ""}>
<Button
size="large"
type="primary"
htmlType="submit"
style={{
width: "100%",
}}
disabled={areSomeBBoxesInvalid || areSomeAnnotationsInvalid}
disabled={hasErrors}
>
Start Training
</Button>
Expand Down Expand Up @@ -384,16 +425,16 @@ export function CollapsibleWorkflowYamlEditor({
);
}

function areInvalidAnnotationsIncluded<T extends HybridTracing | APIAnnotation>(
function checkAnnotationsForErrorsAndWarnings<T extends HybridTracing | APIAnnotation>(
annotationsWithDatasets: Array<AnnotationInfoForAIJob<T>>,
): {
areSomeAnnotationsInvalid: boolean;
invalidAnnotationsReason: string | null;
hasAnnotationErrors: boolean;
errors: string[];
} {
if (annotationsWithDatasets.length === 0) {
return {
areSomeAnnotationsInvalid: true,
invalidAnnotationsReason: "At least one annotation must be defined.",
hasAnnotationErrors: true,
errors: ["At least one annotation must be defined."],
};
}
const annotationsWithoutBoundingBoxes = annotationsWithDatasets.filter(
Expand All @@ -406,24 +447,81 @@ function areInvalidAnnotationsIncluded<T extends HybridTracing | APIAnnotation>(
"id" in annotation ? annotation.id : annotation.annotationId,
);
return {
areSomeAnnotationsInvalid: true,
invalidAnnotationsReason: `All annotations must have at least one bounding box. Annotations without bounding boxes are: ${annotationIds.join(", ")}`,
hasAnnotationErrors: true,
errors: [
`All annotations must have at least one bounding box. Annotations without bounding boxes are: ${annotationIds.join(", ")}`,
],
};
}
return { areSomeAnnotationsInvalid: false, invalidAnnotationsReason: null };
return { hasAnnotationErrors: false, errors: [] };
}

function areInvalidBoundingBoxesIncluded(userBoundingBoxes: UserBoundingBox[]): {
areSomeBBoxesInvalid: boolean;
invalidBBoxesReason: string | null;
function checkBoundingBoxesForErrorsAndWarnings(
userBoundingBoxes: (UserBoundingBox & { annotationId: string })[],
): {
hasBBoxErrors: boolean;
hasBBoxWarnings: boolean;
errors: string[];
warnings: string[];
} {
let hasBBoxErrors = false;
let hasBBoxWarnings = false;
const errors = [];
const warnings = [];
if (userBoundingBoxes.length === 0) {
return {
areSomeBBoxesInvalid: true,
invalidBBoxesReason: "At least one bounding box must be defined.",
};
hasBBoxErrors = true;
errors.push("At least one bounding box must be defined.");
}
return { areSomeBBoxesInvalid: false, invalidBBoxesReason: null };
// Find smallest bounding box dimensions
const minDimensions = userBoundingBoxes.reduce(
(min, { boundingBox: box }) => ({
x: Math.min(min.x, box.max[0] - box.min[0]),
y: Math.min(min.y, box.max[1] - box.min[1]),
z: Math.min(min.z, box.max[2] - box.min[2]),
}),
{ x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY, z: Number.POSITIVE_INFINITY },
);

// Validate minimum size and multiple requirements
type BoundingBoxWithAnnotationId = { boundingBox: Vector6; annotationId: string };
const tooSmallBoxes: BoundingBoxWithAnnotationId[] = [];
const nonMultipleBoxes: BoundingBoxWithAnnotationId[] = [];
userBoundingBoxes.map(({ boundingBox: box, annotationId }) => {
const arrayBox = computeArrayFromBoundingBox(box);
const [_x, _y, _z, width, height, depth] = arrayBox;
if (width < 10 || height < 10 || depth < 10) {
tooSmallBoxes.push({ boundingBox: arrayBox, annotationId });
}

if (
width % minDimensions.x !== 0 ||
height % minDimensions.y !== 0 ||
depth % minDimensions.z !== 0
) {
nonMultipleBoxes.push({ boundingBox: arrayBox, annotationId });
}
});

const boxWithIdToString = ({ boundingBox, annotationId }: BoundingBoxWithAnnotationId) =>
boundingBox.join(", ") + ` (${annotationId})`;

if (tooSmallBoxes.length > 0) {
hasBBoxWarnings = true;
const tooSmallBoxesStrings = tooSmallBoxes.map(boxWithIdToString);
warnings.push(
`The following bounding boxes are not at least 10 Vx in each dimension which is suboptimal for the training:\n${tooSmallBoxesStrings.join("\n")}`,
);
}

if (nonMultipleBoxes.length > 0) {
hasBBoxWarnings = true;
const nonMultipleBoxesStrings = nonMultipleBoxes.map(boxWithIdToString);
warnings.push(
`The minimum bounding box dimensions are ${minDimensions.x} x ${minDimensions.y} x ${minDimensions.z}. The following bounding boxes have dimensions which are not a multiple of the minimum dimensions which is suboptimal for the training:\n${nonMultipleBoxesStrings.join("\n")}`,
);
}

return { hasBBoxErrors, hasBBoxWarnings, errors, warnings };
}

function AnnotationsCsvInput({
Expand Down

0 comments on commit 7d7945c

Please sign in to comment.