-
Notifications
You must be signed in to change notification settings - Fork 26
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 option to train AI using Tasks and their bounding boxes #8310
Changes from 4 commits
293c98f
d45ea84
a271981
675d369
bc2b55b
1c44bf0
0a5a046
f63afb8
a329a16
8ab7b3e
b9e0946
52a80ef
c37fe7f
d526816
d53eb2a
aafd50b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -153,7 +153,7 @@ class AiModelController @Inject()( | |
) | ||
existingAiModelsCount <- aiModelDAO.countByNameAndOrganization(request.body.name, | ||
request.identity._organization) | ||
_ <- bool2Fox(existingAiModelsCount == 0) ?~> "model.nameInUse" | ||
_ <- bool2Fox(existingAiModelsCount == 0) ?~> "aiModel.nameInUse" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for fixing this! |
||
newTrainingJob <- jobService | ||
.submitJob(jobCommand, commandArgs, request.identity, dataStore.name) ?~> "job.couldNotRunTrainModel" | ||
newAiModel = AiModel( | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -47,6 +47,7 @@ import { computeArrayFromBoundingBox } from "libs/utils"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { MagSelectionFormItem } from "components/mag_selection"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { MagInfo } from "oxalis/model/helpers/mag_info"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { V3 } from "libs/mjs"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { getAnnotationsForTask } from "admin/api/tasks"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { TextArea } = Input; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const FormItem = Form.Item; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -55,10 +56,12 @@ const FormItem = Form.Item; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// only the APIAnnotations of the given annotations to train on are loaded from the backend. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Thus, the code needs to handle both HybridTracing | APIAnnotation where APIAnnotation is missing some information. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Therefore, volumeTracings with the matching volumeTracingMags are needed to get more details on each volume annotation layer and its magnifications. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// The userBoundingBoxes are needed for checking for equal bounding box sizes. As training on fallback data is supported and an annotation is not required to have VolumeTracings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// As the userBoundingBoxes should have multiple sizes of the smallest one, a check with a warning should be included. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// As training on fallback data is supported and an annotation is not required to have VolumeTracings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// it is necessary to save userBoundingBoxes separately and not load them from volumeTracings entries to support skeleton only annotations. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Moreover, in case an annotations is a task, its task bounding box should also be used for training. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Note that a copy of the userBoundingBoxes is included in each volume and skeleton tracing of an annotation. Thus, it doesn't matter from which the userBoundingBoxes are taken. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export type AnnotationInfoForAIJob<GenericAnnotation> = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export type AnnotationInfoForAITrainingJob<GenericAnnotation> = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotation: GenericAnnotation; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dataset: APIDataset; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
volumeTracings: VolumeTracing[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -175,8 +178,8 @@ export function TrainAiModelTab<GenericAnnotation extends APIAnnotation | Hybrid | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
getMagsForSegmentationLayer: (annotationId: string, layerName: string) => MagInfo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onClose: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ensureSavedState?: (() => Promise<void>) | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationInfos: Array<AnnotationInfoForAIJob<GenericAnnotation>>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onAddAnnotationsInfos?: (newItems: Array<AnnotationInfoForAIJob<APIAnnotation>>) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationInfos: Array<AnnotationInfoForAITrainingJob<GenericAnnotation>>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onAddAnnotationsInfos?: (newItems: Array<AnnotationInfoForAITrainingJob<APIAnnotation>>) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [form] = Form.useForm(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -495,7 +498,7 @@ export function CollapsibleWorkflowYamlEditor({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function checkAnnotationsForErrorsAndWarnings<T extends HybridTracing | APIAnnotation>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationsWithDatasets: Array<AnnotationInfoForAIJob<T>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationsWithDatasets: Array<AnnotationInfoForAITrainingJob<T>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hasAnnotationErrors: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
errors: string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -596,31 +599,42 @@ function checkBoundingBoxesForErrorsAndWarnings( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function AnnotationsCsvInput({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onAdd, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onAdd: (newItems: Array<AnnotationInfoForAIJob<APIAnnotation>>) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onAdd: (newItems: Array<AnnotationInfoForAITrainingJob<APIAnnotation>>) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [value, setValue] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const onClickAdd = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newItems = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const annotationIdsForTraining = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const unfinishedTasks = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const lines = value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.split("\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.map((line) => line.trim()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.filter((line) => line !== ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (const annotationUrlOrId of lines) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (annotationUrlOrId.includes("/")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
newItems.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationId: annotationUrlOrId.split("/").at(-1) as string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (const taskOrAnnotationIdOrUrl of lines) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (taskOrAnnotationIdOrUrl.includes("/")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationIdsForTraining.push(taskOrAnnotationIdOrUrl.split("/").at(-1) as string); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
newItems.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationId: annotationUrlOrId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let isTask = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const annotations = await getAnnotationsForTask(taskOrAnnotationIdOrUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const finishedAnnotations = annotations.filter(({ state }) => state === "Finished"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (annotations.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationIdsForTraining.push(...finishedAnnotations.map(({ id }) => id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
unfinishedTasks.push(taskOrAnnotationIdOrUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (_e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isTask = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve error handling in task validation. The catch block silently swallows all errors to determine if the input is a task. This could hide real API errors or other issues. Consider this improvement: - try {
- const annotations = await getAnnotationsForTask(taskOrAnnotationIdOrUrl);
- const finishedAnnotations = annotations.filter(({ state }) => state === "Finished");
- if (annotations.length > 0) {
- annotationIdsForTraining.push(...finishedAnnotations.map(({ id }) => id));
- } else {
- unfinishedTasks.push(taskOrAnnotationIdOrUrl);
- }
- } catch (_e) {
- isTask = false;
- }
+ try {
+ const annotations = await getAnnotationsForTask(taskOrAnnotationIdOrUrl);
+ const finishedAnnotations = annotations.filter(({ state }) => state === "Finished");
+ if (annotations.length > 0) {
+ annotationIdsForTraining.push(...finishedAnnotations.map(({ id }) => id));
+ } else {
+ unfinishedTasks.push(taskOrAnnotationIdOrUrl);
+ }
+ } catch (error) {
+ if (error.response?.status === 404) {
+ isTask = false;
+ } else {
+ throw new Error(`Failed to fetch task ${taskOrAnnotationIdOrUrl}: ${error.message}`);
+ }
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!isTask) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationIdsForTraining.push(taskOrAnnotationIdOrUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newAnnotationsWithDatasets = await Promise.all( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
newItems.map(async (item) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const annotation = await getAnnotationInformation(item.annotationId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotationIdsForTraining.map(async (annotationId) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const annotation = await getAnnotationInformation(annotationId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const dataset = await getDataset(annotation.datasetId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const volumeServerTracings: ServerVolumeTracing[] = await Promise.all( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -651,6 +665,16 @@ function AnnotationsCsvInput({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (annotation.task?.boundingBox) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const largestId = Math.max(...userBoundingBoxes.map(({ id }) => id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
userBoundingBoxes.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name: "Task Bounding Box", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
boundingBox: Utils.computeBoundingBoxFromBoundingBoxObject(annotation.task.boundingBox), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
color: [0, 0, 0], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isVisible: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
id: largestId + 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
annotation, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -663,14 +687,18 @@ function AnnotationsCsvInput({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (unfinishedTasks.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Toast.warning( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
`The following tasks have no finished annotations: ${unfinishedTasks.join(", ")}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onAdd(newAnnotationsWithDatasets); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<FormItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name="annotationCsv" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label="Annotations CSV" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label="Annotations or Tasks CSV" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hasFeedback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
initialValue={value} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rules={[ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -693,7 +721,7 @@ function AnnotationsCsvInput({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<TextArea | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="input-monospace" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
placeholder="annotationUrlOrId" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
placeholder="taskOrAnnotationIdOrUrl" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
autoSize={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
minRows: 6, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve changelog entry clarity and correctness.
The changelog entry contains grammatical issues and redundant wording. Consider this revision:
Changes made:
📝 Committable suggestion
🧰 Tools
🪛 LanguageTool
[style] ~25-~25: You’ve already used the word ‘also’ once in your sentence, so using it again may be redundant.
Context: ...now also support inputting task ids and also considers their task bounding box. [#8310](https:...
(REDUNDANT_FILLER)