@things-factory/integration-label-studio 9.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/EXTERNAL_DATA_SOURCING.md +484 -0
- package/IMPLEMENTATION_GUIDE.md +469 -0
- package/INTEGRATION.md +279 -0
- package/README.md +1014 -0
- package/SETUP_GUIDE.md +577 -0
- package/TEST_GUIDE.md +387 -0
- package/UI_CUSTOMIZATION.md +395 -0
- package/USER_SYNC_GUIDE.md +514 -0
- package/client/bootstrap.ts +1 -0
- package/client/index.ts +1 -0
- package/client/label-studio-label-page.ts +52 -0
- package/client/label-studio-project-create.ts +216 -0
- package/client/label-studio-project-list.ts +214 -0
- package/client/label-studio-wrapper.ts +294 -0
- package/client/route.ts +15 -0
- package/client/tsconfig.json +13 -0
- package/config/config.development.js +124 -0
- package/config/config.production.js +182 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +2 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +2 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/label-studio-label-page.d.ts +8 -0
- package/dist-client/label-studio-label-page.js +54 -0
- package/dist-client/label-studio-label-page.js.map +1 -0
- package/dist-client/label-studio-project-create.d.ts +16 -0
- package/dist-client/label-studio-project-create.js +235 -0
- package/dist-client/label-studio-project-create.js.map +1 -0
- package/dist-client/label-studio-project-list.d.ts +16 -0
- package/dist-client/label-studio-project-list.js +222 -0
- package/dist-client/label-studio-project-list.js.map +1 -0
- package/dist-client/label-studio-wrapper.d.ts +57 -0
- package/dist-client/label-studio-wrapper.js +304 -0
- package/dist-client/label-studio-wrapper.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +14 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/controller/label-studio-role-mapper.d.ts +35 -0
- package/dist-server/controller/label-studio-role-mapper.js +65 -0
- package/dist-server/controller/label-studio-role-mapper.js.map +1 -0
- package/dist-server/controller/user-provisioning-service.d.ts +66 -0
- package/dist-server/controller/user-provisioning-service.js +264 -0
- package/dist-server/controller/user-provisioning-service.js.map +1 -0
- package/dist-server/index.d.ts +7 -0
- package/dist-server/index.js +19 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/route/label-studio-sso.d.ts +2 -0
- package/dist-server/route/label-studio-sso.js +156 -0
- package/dist-server/route/label-studio-sso.js.map +1 -0
- package/dist-server/route/webhook.d.ts +65 -0
- package/dist-server/route/webhook.js +248 -0
- package/dist-server/route/webhook.js.map +1 -0
- package/dist-server/route.d.ts +1 -0
- package/dist-server/route.js +21 -0
- package/dist-server/route.js.map +1 -0
- package/dist-server/service/ai-prediction-service.d.ts +27 -0
- package/dist-server/service/ai-prediction-service.js +222 -0
- package/dist-server/service/ai-prediction-service.js.map +1 -0
- package/dist-server/service/dataset-labeling-integration.d.ts +44 -0
- package/dist-server/service/dataset-labeling-integration.js +512 -0
- package/dist-server/service/dataset-labeling-integration.js.map +1 -0
- package/dist-server/service/external-data-source-service.d.ts +78 -0
- package/dist-server/service/external-data-source-service.js +415 -0
- package/dist-server/service/external-data-source-service.js.map +1 -0
- package/dist-server/service/index.d.ts +12 -0
- package/dist-server/service/index.js +27 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/label-studio-sso-service.d.ts +38 -0
- package/dist-server/service/label-studio-sso-service.js +98 -0
- package/dist-server/service/label-studio-sso-service.js.map +1 -0
- package/dist-server/service/ml/ml-backend-service.d.ts +23 -0
- package/dist-server/service/ml/ml-backend-service.js +153 -0
- package/dist-server/service/ml/ml-backend-service.js.map +1 -0
- package/dist-server/service/prediction/prediction-management.d.ts +32 -0
- package/dist-server/service/prediction/prediction-management.js +299 -0
- package/dist-server/service/prediction/prediction-management.js.map +1 -0
- package/dist-server/service/project/project-management.d.ts +36 -0
- package/dist-server/service/project/project-management.js +309 -0
- package/dist-server/service/project/project-management.js.map +1 -0
- package/dist-server/service/task/task-management.d.ts +42 -0
- package/dist-server/service/task/task-management.js +372 -0
- package/dist-server/service/task/task-management.js.map +1 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.d.ts +28 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.js +111 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.js.map +1 -0
- package/dist-server/service/webhook/webhook-management.d.ts +21 -0
- package/dist-server/service/webhook/webhook-management.js +134 -0
- package/dist-server/service/webhook/webhook-management.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/dist-server/types/dataset-labeling-types.d.ts +71 -0
- package/dist-server/types/dataset-labeling-types.js +259 -0
- package/dist-server/types/dataset-labeling-types.js.map +1 -0
- package/dist-server/types/label-studio-types.d.ts +128 -0
- package/dist-server/types/label-studio-types.js +494 -0
- package/dist-server/types/label-studio-types.js.map +1 -0
- package/dist-server/types/prediction-types.d.ts +39 -0
- package/dist-server/types/prediction-types.js +121 -0
- package/dist-server/types/prediction-types.js.map +1 -0
- package/dist-server/utils/annotation-exporter.d.ts +104 -0
- package/dist-server/utils/annotation-exporter.js +261 -0
- package/dist-server/utils/annotation-exporter.js.map +1 -0
- package/dist-server/utils/label-config-builder.d.ts +117 -0
- package/dist-server/utils/label-config-builder.js +286 -0
- package/dist-server/utils/label-config-builder.js.map +1 -0
- package/dist-server/utils/label-studio-api-client.d.ts +180 -0
- package/dist-server/utils/label-studio-api-client.js +401 -0
- package/dist-server/utils/label-studio-api-client.js.map +1 -0
- package/dist-server/utils/media-url-extractor.d.ts +45 -0
- package/dist-server/utils/media-url-extractor.js +152 -0
- package/dist-server/utils/media-url-extractor.js.map +1 -0
- package/dist-server/utils/task-transformer.d.ts +108 -0
- package/dist-server/utils/task-transformer.js +260 -0
- package/dist-server/utils/task-transformer.js.map +1 -0
- package/package.json +47 -0
- package/server/SERVER_STRUCTURE.md +351 -0
- package/server/controller/label-studio-role-mapper.ts +76 -0
- package/server/controller/user-provisioning-service.ts +340 -0
- package/server/index.ts +19 -0
- package/server/route/label-studio-sso.ts +194 -0
- package/server/route/webhook.ts +304 -0
- package/server/route.ts +35 -0
- package/server/service/ai-prediction-service.ts +239 -0
- package/server/service/dataset-labeling-integration.ts +590 -0
- package/server/service/external-data-source-service.ts +438 -0
- package/server/service/index.ts +24 -0
- package/server/service/label-studio-sso-service.ts +108 -0
- package/server/service/labeling-scenario-service.ts.deprecated +566 -0
- package/server/service/ml/ml-backend-service.ts +127 -0
- package/server/service/prediction/prediction-management.ts +281 -0
- package/server/service/project/project-management.ts +284 -0
- package/server/service/task/task-management.ts +363 -0
- package/server/service/user-provisioning/user-sync-mutation.ts +80 -0
- package/server/service/webhook/webhook-management.ts +109 -0
- package/server/tsconfig.json +11 -0
- package/server/types/dataset-labeling-types.ts +181 -0
- package/server/types/global.d.ts +23 -0
- package/server/types/label-studio-types.ts +346 -0
- package/server/types/prediction-types.ts +86 -0
- package/server/types/scenario-types.ts.deprecated +362 -0
- package/server/utils/annotation-exporter.ts +340 -0
- package/server/utils/label-config-builder.ts +340 -0
- package/server/utils/label-studio-api-client.ts +487 -0
- package/server/utils/media-url-extractor.ts +193 -0
- package/server/utils/task-transformer.ts +342 -0
- package/test-ai-prediction.js +268 -0
- package/test-dataset-integration.js +449 -0
- package/test-simple.js +89 -0
- package/things-factory.config.js +12 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatasetLabelingIntegration = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const ai_inference_1 = require("@things-factory/ai-inference");
|
|
8
|
+
const dataset_1 = require("@things-factory/dataset");
|
|
9
|
+
const label_studio_api_client_js_1 = require("../utils/label-studio-api-client.js");
|
|
10
|
+
const dataset_labeling_types_js_1 = require("../types/dataset-labeling-types.js");
|
|
11
|
+
const prediction_types_js_1 = require("../types/prediction-types.js");
|
|
12
|
+
const media_url_extractor_js_1 = require("../utils/media-url-extractor.js");
|
|
13
|
+
/**
|
|
14
|
+
* Dataset Labeling Integration Service
|
|
15
|
+
*
|
|
16
|
+
* Integrates Things-Factory Dataset module with Label Studio for AI-assisted labeling
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Create Label Studio tasks from DataSamples
|
|
20
|
+
* - Auto-generate AI predictions for labeling tasks
|
|
21
|
+
* - Sync human annotations back to DataSamples
|
|
22
|
+
* - Track labeling progress and status
|
|
23
|
+
*/
|
|
24
|
+
let DatasetLabelingIntegration = class DatasetLabelingIntegration {
|
|
25
|
+
/**
|
|
26
|
+
* Create Label Studio labeling tasks from DataSamples in a DataSet
|
|
27
|
+
* Optionally generates AI predictions automatically
|
|
28
|
+
*/
|
|
29
|
+
async createLabelingTasksFromDataset(input, context) {
|
|
30
|
+
const { domain } = context.state;
|
|
31
|
+
console.log(`[Dataset Labeling] Creating tasks for DataSet ${input.dataSetId} in LS project ${input.projectId}`);
|
|
32
|
+
try {
|
|
33
|
+
// 1. Get DataSet configuration
|
|
34
|
+
const dataSet = await (0, shell_1.getRepository)(dataset_1.DataSet).findOne({
|
|
35
|
+
where: { domain: { id: domain.id }, id: input.dataSetId }
|
|
36
|
+
});
|
|
37
|
+
if (!dataSet) {
|
|
38
|
+
throw new Error(`DataSet not found: ${input.dataSetId}`);
|
|
39
|
+
}
|
|
40
|
+
// 2. Query DataSamples
|
|
41
|
+
const queryBuilder = (0, shell_1.getRepository)(dataset_1.DataSample)
|
|
42
|
+
.createQueryBuilder('sample')
|
|
43
|
+
.where('sample.domainId = :domainId', { domainId: domain.id })
|
|
44
|
+
.andWhere('sample.dataSetId = :dataSetId', { dataSetId: input.dataSetId });
|
|
45
|
+
if (input.sinceDate) {
|
|
46
|
+
queryBuilder.andWhere('sample.collectedAt >= :sinceDate', { sinceDate: input.sinceDate });
|
|
47
|
+
}
|
|
48
|
+
if (input.limit) {
|
|
49
|
+
queryBuilder.limit(input.limit);
|
|
50
|
+
}
|
|
51
|
+
queryBuilder.orderBy('sample.collectedAt', 'DESC');
|
|
52
|
+
const samples = await queryBuilder.getMany();
|
|
53
|
+
console.log(`[Dataset Labeling] Found ${samples.length} DataSamples to process`);
|
|
54
|
+
// 3. Create tasks for each sample
|
|
55
|
+
const result = {
|
|
56
|
+
totalSamples: samples.length,
|
|
57
|
+
tasksCreated: 0,
|
|
58
|
+
tasksFailed: 0,
|
|
59
|
+
tasksSkipped: 0,
|
|
60
|
+
taskIds: [],
|
|
61
|
+
predictionsCreated: 0
|
|
62
|
+
};
|
|
63
|
+
// Get base URL for converting relative attachment paths to absolute URLs
|
|
64
|
+
const baseUrl = (0, media_url_extractor_js_1.getBaseUrl)(context);
|
|
65
|
+
for (const sample of samples) {
|
|
66
|
+
try {
|
|
67
|
+
// Extract all media URLs from DataSample (image, video, audio)
|
|
68
|
+
const mediaUrls = (0, media_url_extractor_js_1.extractMediaUrls)(sample, baseUrl);
|
|
69
|
+
if (Object.keys(mediaUrls).length === 0) {
|
|
70
|
+
console.log(`[Dataset Labeling] Skipping sample ${sample.id}: no media found`);
|
|
71
|
+
result.tasksSkipped++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Create Label Studio task with all media URLs
|
|
75
|
+
const task = await label_studio_api_client_js_1.labelStudioApi.createTask(input.projectId, {
|
|
76
|
+
data: mediaUrls,
|
|
77
|
+
meta: {
|
|
78
|
+
dataSampleId: sample.id,
|
|
79
|
+
dataSampleName: sample.name,
|
|
80
|
+
dataSetId: input.dataSetId,
|
|
81
|
+
dataSetName: dataSet.name,
|
|
82
|
+
collectedAt: sample.collectedAt?.toISOString(),
|
|
83
|
+
source: 'things-factory-dataset'
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
result.taskIds.push(task.id);
|
|
87
|
+
result.tasksCreated++;
|
|
88
|
+
console.log(`[Dataset Labeling] Created task ${task.id} for sample ${sample.id}`);
|
|
89
|
+
// 4. Auto-generate AI prediction if requested (for image detection)
|
|
90
|
+
if (input.autoGeneratePredictions && mediaUrls.image) {
|
|
91
|
+
try {
|
|
92
|
+
const modelClient = input.modelId
|
|
93
|
+
? ai_inference_1.AIModelClientFactory.getClient(input.modelId)
|
|
94
|
+
: ai_inference_1.AIModelClientFactory.getDefaultClient();
|
|
95
|
+
// Use first image URL if multiple images
|
|
96
|
+
const imageUrl = Array.isArray(mediaUrls.image) ? mediaUrls.image[0] : mediaUrls.image;
|
|
97
|
+
const objects = await modelClient.detectObjects(imageUrl, {
|
|
98
|
+
confidenceThreshold: input.confidenceThreshold
|
|
99
|
+
});
|
|
100
|
+
if (objects.length > 0) {
|
|
101
|
+
const labelStudioResult = this.convertToLabelStudioFormat(objects);
|
|
102
|
+
const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length;
|
|
103
|
+
await label_studio_api_client_js_1.labelStudioApi.createPrediction({
|
|
104
|
+
task: task.id,
|
|
105
|
+
result: labelStudioResult,
|
|
106
|
+
score: avgConfidence,
|
|
107
|
+
model_version: input.modelId || 'default-model-v1.0'
|
|
108
|
+
});
|
|
109
|
+
result.predictionsCreated = (result.predictionsCreated || 0) + 1;
|
|
110
|
+
console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (predError) {
|
|
114
|
+
console.error(`[Dataset Labeling] Failed to create prediction for task ${task.id}:`, predError);
|
|
115
|
+
// Continue even if prediction fails
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (taskError) {
|
|
120
|
+
console.error(`[Dataset Labeling] Failed to create task for sample ${sample.id}:`, taskError);
|
|
121
|
+
result.tasksFailed++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log(`[Dataset Labeling] Completed: ${result.tasksCreated} created, ${result.tasksFailed} failed, ${result.tasksSkipped} skipped`);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error('[Dataset Labeling] Failed to create tasks:', error);
|
|
129
|
+
throw new Error(`Failed to create labeling tasks: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Sync completed annotations from Label Studio back to DataSamples
|
|
134
|
+
*/
|
|
135
|
+
async syncAnnotationsToDataset(input, context) {
|
|
136
|
+
const { domain } = context.state;
|
|
137
|
+
console.log(`[Dataset Labeling] Syncing annotations from LS project ${input.projectId} to DataSet ${input.dataSetId}`);
|
|
138
|
+
try {
|
|
139
|
+
// 1. Get all tasks from Label Studio project
|
|
140
|
+
const tasksResponse = await label_studio_api_client_js_1.labelStudioApi.getTasks(input.projectId, {
|
|
141
|
+
page_size: 1000 // Adjust as needed
|
|
142
|
+
});
|
|
143
|
+
const tasks = tasksResponse.tasks || tasksResponse.results || [];
|
|
144
|
+
console.log(`[Dataset Labeling] Found ${tasks.length} tasks in Label Studio`);
|
|
145
|
+
// 2. Filter tasks related to this DataSet
|
|
146
|
+
const dataSetTasks = tasks.filter((task) => task.meta?.dataSetId === input.dataSetId);
|
|
147
|
+
console.log(`[Dataset Labeling] ${dataSetTasks.length} tasks belong to DataSet ${input.dataSetId}`);
|
|
148
|
+
const result = {
|
|
149
|
+
totalAnnotations: 0,
|
|
150
|
+
samplesUpdated: 0,
|
|
151
|
+
updatesFailed: 0,
|
|
152
|
+
skipped: 0
|
|
153
|
+
};
|
|
154
|
+
// 3. Process each task
|
|
155
|
+
for (const task of dataSetTasks) {
|
|
156
|
+
const dataSampleId = task.meta?.dataSampleId;
|
|
157
|
+
if (!dataSampleId) {
|
|
158
|
+
result.skipped++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// Get annotations for this task
|
|
162
|
+
const annotations = task.annotations || [];
|
|
163
|
+
if (annotations.length === 0) {
|
|
164
|
+
result.skipped++;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
// Filter by completion status if requested
|
|
168
|
+
let relevantAnnotations = annotations;
|
|
169
|
+
if (input.completedOnly) {
|
|
170
|
+
relevantAnnotations = annotations.filter((ann) => ann.was_cancelled === false);
|
|
171
|
+
}
|
|
172
|
+
// Filter by date if requested
|
|
173
|
+
if (input.sinceDate) {
|
|
174
|
+
relevantAnnotations = relevantAnnotations.filter((ann) => new Date(ann.updated_at) >= input.sinceDate);
|
|
175
|
+
}
|
|
176
|
+
if (relevantAnnotations.length === 0) {
|
|
177
|
+
result.skipped++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
result.totalAnnotations += relevantAnnotations.length;
|
|
181
|
+
try {
|
|
182
|
+
// 4. Get DataSample
|
|
183
|
+
const sample = await (0, shell_1.getRepository)(dataset_1.DataSample).findOne({
|
|
184
|
+
where: { domain: { id: domain.id }, id: dataSampleId }
|
|
185
|
+
});
|
|
186
|
+
if (!sample) {
|
|
187
|
+
console.log(`[Dataset Labeling] DataSample not found: ${dataSampleId}`);
|
|
188
|
+
result.updatesFailed++;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// 5. Convert annotations to judgment data
|
|
192
|
+
const latestAnnotation = relevantAnnotations[relevantAnnotations.length - 1];
|
|
193
|
+
const judgment = this.convertAnnotationToJudgment(latestAnnotation, task);
|
|
194
|
+
// 6. Update DataSample
|
|
195
|
+
sample.judgment = judgment;
|
|
196
|
+
// Optionally update OOC/OOS flags based on annotation
|
|
197
|
+
// This is application-specific logic
|
|
198
|
+
if (judgment.defectsFound > 0) {
|
|
199
|
+
sample.ooc = true;
|
|
200
|
+
}
|
|
201
|
+
await (0, shell_1.getRepository)(dataset_1.DataSample).save(sample);
|
|
202
|
+
result.samplesUpdated++;
|
|
203
|
+
console.log(`[Dataset Labeling] Updated DataSample ${dataSampleId} with annotation ${latestAnnotation.id}`);
|
|
204
|
+
}
|
|
205
|
+
catch (updateError) {
|
|
206
|
+
console.error(`[Dataset Labeling] Failed to update DataSample ${dataSampleId}:`, updateError);
|
|
207
|
+
result.updatesFailed++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
console.log(`[Dataset Labeling] Sync completed: ${result.samplesUpdated} updated, ${result.updatesFailed} failed, ${result.skipped} skipped`);
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error('[Dataset Labeling] Failed to sync annotations:', error);
|
|
215
|
+
throw new Error(`Failed to sync annotations: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Generate AI predictions for existing DataSet samples
|
|
220
|
+
*/
|
|
221
|
+
async generatePredictionsForDataset(input, context) {
|
|
222
|
+
const { domain } = context.state;
|
|
223
|
+
console.log(`[Dataset Labeling] Generating predictions for DataSet ${input.dataSetId} in LS project ${input.projectId}`);
|
|
224
|
+
try {
|
|
225
|
+
// 1. Get all tasks from Label Studio project
|
|
226
|
+
const tasksResponse = await label_studio_api_client_js_1.labelStudioApi.getTasks(input.projectId, {
|
|
227
|
+
page_size: 1000
|
|
228
|
+
});
|
|
229
|
+
const tasks = tasksResponse.tasks || tasksResponse.results || [];
|
|
230
|
+
const dataSetTasks = tasks.filter((task) => task.meta?.dataSetId === input.dataSetId);
|
|
231
|
+
console.log(`[Dataset Labeling] Found ${dataSetTasks.length} tasks for DataSet`);
|
|
232
|
+
const result = {
|
|
233
|
+
total: dataSetTasks.length,
|
|
234
|
+
succeeded: 0,
|
|
235
|
+
failed: 0,
|
|
236
|
+
results: [],
|
|
237
|
+
modelVersion: input.modelId || 'default-model-v1.0'
|
|
238
|
+
};
|
|
239
|
+
// 2. Get AI model client
|
|
240
|
+
const modelClient = input.modelId
|
|
241
|
+
? ai_inference_1.AIModelClientFactory.getClient(input.modelId)
|
|
242
|
+
: ai_inference_1.AIModelClientFactory.getDefaultClient();
|
|
243
|
+
// 3. Process tasks in batches
|
|
244
|
+
const BATCH_SIZE = 5;
|
|
245
|
+
for (let i = 0; i < dataSetTasks.length; i += BATCH_SIZE) {
|
|
246
|
+
const batch = dataSetTasks.slice(i, i + BATCH_SIZE);
|
|
247
|
+
const batchPromises = batch.map(async (task) => {
|
|
248
|
+
try {
|
|
249
|
+
// Check if prediction already exists
|
|
250
|
+
if (!input.forceRegenerate && task.predictions && task.predictions.length > 0) {
|
|
251
|
+
result.results.push({
|
|
252
|
+
taskId: task.id,
|
|
253
|
+
success: true,
|
|
254
|
+
objectCount: 0,
|
|
255
|
+
error: 'Prediction already exists (skipped)'
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const imageUrl = task.data?.image;
|
|
260
|
+
if (!imageUrl) {
|
|
261
|
+
result.failed++;
|
|
262
|
+
result.results.push({
|
|
263
|
+
taskId: task.id,
|
|
264
|
+
success: false,
|
|
265
|
+
objectCount: 0,
|
|
266
|
+
error: 'No image URL in task data'
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Run AI inference
|
|
271
|
+
const objects = await modelClient.detectObjects(imageUrl, {
|
|
272
|
+
confidenceThreshold: input.confidenceThreshold
|
|
273
|
+
});
|
|
274
|
+
if (objects.length === 0) {
|
|
275
|
+
result.succeeded++;
|
|
276
|
+
result.results.push({
|
|
277
|
+
taskId: task.id,
|
|
278
|
+
success: true,
|
|
279
|
+
objectCount: 0,
|
|
280
|
+
error: 'No objects detected'
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// Create prediction
|
|
285
|
+
const labelStudioResult = this.convertToLabelStudioFormat(objects);
|
|
286
|
+
const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length;
|
|
287
|
+
const prediction = await label_studio_api_client_js_1.labelStudioApi.createPrediction({
|
|
288
|
+
task: task.id,
|
|
289
|
+
result: labelStudioResult,
|
|
290
|
+
score: avgConfidence,
|
|
291
|
+
model_version: result.modelVersion
|
|
292
|
+
});
|
|
293
|
+
result.succeeded++;
|
|
294
|
+
result.results.push({
|
|
295
|
+
taskId: task.id,
|
|
296
|
+
predictionId: prediction.id,
|
|
297
|
+
success: true,
|
|
298
|
+
objectCount: objects.length,
|
|
299
|
+
avgConfidence
|
|
300
|
+
});
|
|
301
|
+
console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
result.failed++;
|
|
305
|
+
result.results.push({
|
|
306
|
+
taskId: task.id,
|
|
307
|
+
success: false,
|
|
308
|
+
objectCount: 0,
|
|
309
|
+
error: error.message
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
await Promise.all(batchPromises);
|
|
314
|
+
}
|
|
315
|
+
console.log(`[Dataset Labeling] Prediction generation completed: ${result.succeeded}/${result.total} succeeded`);
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.error('[Dataset Labeling] Failed to generate predictions:', error);
|
|
320
|
+
throw new Error(`Failed to generate predictions: ${error.message}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Query labeling status for a DataSet
|
|
325
|
+
*/
|
|
326
|
+
async datasetLabelingStatus(input, context) {
|
|
327
|
+
const { domain } = context.state;
|
|
328
|
+
try {
|
|
329
|
+
// 1. Get DataSet
|
|
330
|
+
const dataSet = await (0, shell_1.getRepository)(dataset_1.DataSet).findOne({
|
|
331
|
+
where: { domain: { id: domain.id }, id: input.dataSetId }
|
|
332
|
+
});
|
|
333
|
+
if (!dataSet) {
|
|
334
|
+
throw new Error(`DataSet not found: ${input.dataSetId}`);
|
|
335
|
+
}
|
|
336
|
+
// 2. Count total DataSamples
|
|
337
|
+
const totalSamples = await (0, shell_1.getRepository)(dataset_1.DataSample).count({
|
|
338
|
+
where: { domain: { id: domain.id }, dataSetId: input.dataSetId }
|
|
339
|
+
});
|
|
340
|
+
// 3. Get Label Studio tasks if projectId provided
|
|
341
|
+
let tasksCreated = 0;
|
|
342
|
+
let withPredictions = 0;
|
|
343
|
+
let withAnnotations = 0;
|
|
344
|
+
let annotationsCompleted = 0;
|
|
345
|
+
if (input.projectId) {
|
|
346
|
+
const tasksResponse = await label_studio_api_client_js_1.labelStudioApi.getTasks(input.projectId, {
|
|
347
|
+
page_size: 1000
|
|
348
|
+
});
|
|
349
|
+
const tasks = tasksResponse.tasks || tasksResponse.results || [];
|
|
350
|
+
const dataSetTasks = tasks.filter((task) => task.meta?.dataSetId === input.dataSetId);
|
|
351
|
+
tasksCreated = dataSetTasks.length;
|
|
352
|
+
for (const task of dataSetTasks) {
|
|
353
|
+
if (task.predictions && task.predictions.length > 0) {
|
|
354
|
+
withPredictions++;
|
|
355
|
+
}
|
|
356
|
+
if (task.annotations && task.annotations.length > 0) {
|
|
357
|
+
withAnnotations++;
|
|
358
|
+
const completedAnnotations = task.annotations.filter((ann) => ann.was_cancelled === false);
|
|
359
|
+
if (completedAnnotations.length > 0) {
|
|
360
|
+
annotationsCompleted++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const notProcessed = totalSamples - tasksCreated;
|
|
366
|
+
const completionRate = totalSamples > 0 ? annotationsCompleted / totalSamples : 0;
|
|
367
|
+
return {
|
|
368
|
+
dataSetId: input.dataSetId,
|
|
369
|
+
dataSetName: dataSet.name,
|
|
370
|
+
totalSamples,
|
|
371
|
+
tasksCreated,
|
|
372
|
+
withPredictions,
|
|
373
|
+
withAnnotations,
|
|
374
|
+
annotationsCompleted,
|
|
375
|
+
notProcessed,
|
|
376
|
+
completionRate,
|
|
377
|
+
projectId: input.projectId,
|
|
378
|
+
lastSyncedAt: new Date()
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.error('[Dataset Labeling] Failed to get status:', error);
|
|
383
|
+
throw new Error(`Failed to get labeling status: ${error.message}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Helper: Extract image URL from DataSample
|
|
388
|
+
*/
|
|
389
|
+
extractImageUrl(sample, imageField) {
|
|
390
|
+
// Try data field first
|
|
391
|
+
if (sample.data && typeof sample.data === 'object') {
|
|
392
|
+
if (sample.data[imageField]) {
|
|
393
|
+
return sample.data[imageField];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Try rawData field
|
|
397
|
+
if (sample.rawData) {
|
|
398
|
+
try {
|
|
399
|
+
const rawData = typeof sample.rawData === 'string' ? JSON.parse(sample.rawData) : sample.rawData;
|
|
400
|
+
if (rawData[imageField]) {
|
|
401
|
+
return rawData[imageField];
|
|
402
|
+
}
|
|
403
|
+
// If rawData is just a string URL
|
|
404
|
+
if (typeof rawData === 'string' && rawData.startsWith('http')) {
|
|
405
|
+
return rawData;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (e) {
|
|
409
|
+
// If rawData is a plain URL string
|
|
410
|
+
if (typeof sample.rawData === 'string' && sample.rawData.startsWith('http')) {
|
|
411
|
+
return sample.rawData;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Helper: Convert AI detection results to Label Studio format
|
|
419
|
+
*/
|
|
420
|
+
convertToLabelStudioFormat(objects) {
|
|
421
|
+
return objects.map(obj => ({
|
|
422
|
+
from_name: 'label',
|
|
423
|
+
to_name: 'image',
|
|
424
|
+
type: 'rectanglelabels',
|
|
425
|
+
value: {
|
|
426
|
+
x: obj.bbox.x,
|
|
427
|
+
y: obj.bbox.y,
|
|
428
|
+
width: obj.bbox.width,
|
|
429
|
+
height: obj.bbox.height,
|
|
430
|
+
rectanglelabels: [obj.className]
|
|
431
|
+
}
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Helper: Convert Label Studio annotation to DataSample judgment format
|
|
436
|
+
*/
|
|
437
|
+
convertAnnotationToJudgment(annotation, task) {
|
|
438
|
+
const result = annotation.result || [];
|
|
439
|
+
const objects = result
|
|
440
|
+
.filter((item) => item.type === 'rectanglelabels')
|
|
441
|
+
.map((item) => ({
|
|
442
|
+
type: item.value.rectanglelabels?.[0] || 'unknown',
|
|
443
|
+
bbox: {
|
|
444
|
+
x: item.value.x,
|
|
445
|
+
y: item.value.y,
|
|
446
|
+
width: item.value.width,
|
|
447
|
+
height: item.value.height
|
|
448
|
+
},
|
|
449
|
+
source: 'human-annotation'
|
|
450
|
+
}));
|
|
451
|
+
return {
|
|
452
|
+
annotationId: annotation.id,
|
|
453
|
+
taskId: task.id,
|
|
454
|
+
annotatedBy: annotation.completed_by,
|
|
455
|
+
annotatedAt: annotation.updated_at,
|
|
456
|
+
objects,
|
|
457
|
+
objectCount: objects.length,
|
|
458
|
+
defectsFound: objects.length, // Application-specific logic
|
|
459
|
+
verified: true,
|
|
460
|
+
source: 'label-studio'
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
exports.DatasetLabelingIntegration = DatasetLabelingIntegration;
|
|
465
|
+
tslib_1.__decorate([
|
|
466
|
+
(0, type_graphql_1.Mutation)(returns => dataset_labeling_types_js_1.TaskCreationResult, {
|
|
467
|
+
description: 'Create Label Studio labeling tasks from DataSamples in a DataSet. Optionally auto-generates AI predictions for each task.'
|
|
468
|
+
}),
|
|
469
|
+
(0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "mutation")'),
|
|
470
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
471
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
472
|
+
tslib_1.__metadata("design:type", Function),
|
|
473
|
+
tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.CreateLabelingTasksRequest, Object]),
|
|
474
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
475
|
+
], DatasetLabelingIntegration.prototype, "createLabelingTasksFromDataset", null);
|
|
476
|
+
tslib_1.__decorate([
|
|
477
|
+
(0, type_graphql_1.Mutation)(returns => dataset_labeling_types_js_1.SyncAnnotationsResult, {
|
|
478
|
+
description: 'Sync Label Studio annotations back to DataSamples, updating judgment fields'
|
|
479
|
+
}),
|
|
480
|
+
(0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "mutation")'),
|
|
481
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
482
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
483
|
+
tslib_1.__metadata("design:type", Function),
|
|
484
|
+
tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.SyncAnnotationsRequest, Object]),
|
|
485
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
486
|
+
], DatasetLabelingIntegration.prototype, "syncAnnotationsToDataset", null);
|
|
487
|
+
tslib_1.__decorate([
|
|
488
|
+
(0, type_graphql_1.Mutation)(returns => prediction_types_js_1.BatchPredictionResult, {
|
|
489
|
+
description: 'Generate AI predictions for DataSet samples that already have Label Studio tasks'
|
|
490
|
+
}),
|
|
491
|
+
(0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "mutation")'),
|
|
492
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
493
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
494
|
+
tslib_1.__metadata("design:type", Function),
|
|
495
|
+
tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.GeneratePredictionsForDatasetRequest, Object]),
|
|
496
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
497
|
+
], DatasetLabelingIntegration.prototype, "generatePredictionsForDataset", null);
|
|
498
|
+
tslib_1.__decorate([
|
|
499
|
+
(0, type_graphql_1.Query)(returns => dataset_labeling_types_js_1.DatasetLabelingStatus, {
|
|
500
|
+
description: 'Get labeling status and progress for a DataSet'
|
|
501
|
+
}),
|
|
502
|
+
(0, type_graphql_1.Directive)('@privilege(category: "label-studio", privilege: "query")'),
|
|
503
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('input')),
|
|
504
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
505
|
+
tslib_1.__metadata("design:type", Function),
|
|
506
|
+
tslib_1.__metadata("design:paramtypes", [dataset_labeling_types_js_1.DatasetLabelingStatusRequest, Object]),
|
|
507
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
508
|
+
], DatasetLabelingIntegration.prototype, "datasetLabelingStatus", null);
|
|
509
|
+
exports.DatasetLabelingIntegration = DatasetLabelingIntegration = tslib_1.__decorate([
|
|
510
|
+
(0, type_graphql_1.Resolver)()
|
|
511
|
+
], DatasetLabelingIntegration);
|
|
512
|
+
//# sourceMappingURL=dataset-labeling-integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-labeling-integration.js","sourceRoot":"","sources":["../../server/service/dataset-labeling-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import { Resolver, Query, Mutation, Arg, Ctx, Int, Directive } from 'type-graphql'\nimport { getRepository } from '@things-factory/shell'\nimport { AIModelClientFactory } from '@things-factory/ai-inference'\nimport { DataSample, DataSet } from '@things-factory/dataset'\nimport { labelStudioApi } from '../utils/label-studio-api-client.js'\nimport {\n CreateLabelingTasksRequest,\n TaskCreationResult,\n SyncAnnotationsRequest,\n SyncAnnotationsResult,\n DatasetLabelingStatus,\n DatasetLabelingStatusRequest,\n GeneratePredictionsForDatasetRequest\n} from '../types/dataset-labeling-types.js'\nimport { BatchPredictionResult } from '../types/prediction-types.js'\nimport { extractMediaUrls, getBaseUrl } from '../utils/media-url-extractor.js'\n\n/**\n * Dataset Labeling Integration Service\n *\n * Integrates Things-Factory Dataset module with Label Studio for AI-assisted labeling\n *\n * Features:\n * - Create Label Studio tasks from DataSamples\n * - Auto-generate AI predictions for labeling tasks\n * - Sync human annotations back to DataSamples\n * - Track labeling progress and status\n */\n@Resolver()\nexport class DatasetLabelingIntegration {\n /**\n * Create Label Studio labeling tasks from DataSamples in a DataSet\n * Optionally generates AI predictions automatically\n */\n @Mutation(returns => TaskCreationResult, {\n description:\n 'Create Label Studio labeling tasks from DataSamples in a DataSet. Optionally auto-generates AI predictions for each task.'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"mutation\")')\n async createLabelingTasksFromDataset(\n @Arg('input') input: CreateLabelingTasksRequest,\n @Ctx() context: ResolverContext\n ): Promise<TaskCreationResult> {\n const { domain } = context.state\n\n console.log(\n `[Dataset Labeling] Creating tasks for DataSet ${input.dataSetId} in LS project ${input.projectId}`\n )\n\n try {\n // 1. Get DataSet configuration\n const dataSet = await getRepository(DataSet).findOne({\n where: { domain: { id: domain.id }, id: input.dataSetId }\n })\n\n if (!dataSet) {\n throw new Error(`DataSet not found: ${input.dataSetId}`)\n }\n\n // 2. Query DataSamples\n const queryBuilder = getRepository(DataSample)\n .createQueryBuilder('sample')\n .where('sample.domainId = :domainId', { domainId: domain.id })\n .andWhere('sample.dataSetId = :dataSetId', { dataSetId: input.dataSetId })\n\n if (input.sinceDate) {\n queryBuilder.andWhere('sample.collectedAt >= :sinceDate', { sinceDate: input.sinceDate })\n }\n\n if (input.limit) {\n queryBuilder.limit(input.limit)\n }\n\n queryBuilder.orderBy('sample.collectedAt', 'DESC')\n\n const samples = await queryBuilder.getMany()\n\n console.log(`[Dataset Labeling] Found ${samples.length} DataSamples to process`)\n\n // 3. Create tasks for each sample\n const result: TaskCreationResult = {\n totalSamples: samples.length,\n tasksCreated: 0,\n tasksFailed: 0,\n tasksSkipped: 0,\n taskIds: [],\n predictionsCreated: 0\n }\n\n // Get base URL for converting relative attachment paths to absolute URLs\n const baseUrl = getBaseUrl(context)\n\n for (const sample of samples) {\n try {\n // Extract all media URLs from DataSample (image, video, audio)\n const mediaUrls = extractMediaUrls(sample, baseUrl)\n\n if (Object.keys(mediaUrls).length === 0) {\n console.log(`[Dataset Labeling] Skipping sample ${sample.id}: no media found`)\n result.tasksSkipped++\n continue\n }\n\n // Create Label Studio task with all media URLs\n const task = await labelStudioApi.createTask(input.projectId, {\n data: mediaUrls,\n meta: {\n dataSampleId: sample.id,\n dataSampleName: sample.name,\n dataSetId: input.dataSetId,\n dataSetName: dataSet.name,\n collectedAt: sample.collectedAt?.toISOString(),\n source: 'things-factory-dataset'\n }\n })\n\n result.taskIds.push(task.id)\n result.tasksCreated++\n\n console.log(`[Dataset Labeling] Created task ${task.id} for sample ${sample.id}`)\n\n // 4. Auto-generate AI prediction if requested (for image detection)\n if (input.autoGeneratePredictions && mediaUrls.image) {\n try {\n const modelClient = input.modelId\n ? AIModelClientFactory.getClient(input.modelId)\n : AIModelClientFactory.getDefaultClient()\n\n // Use first image URL if multiple images\n const imageUrl = Array.isArray(mediaUrls.image) ? mediaUrls.image[0] : mediaUrls.image\n\n const objects = await modelClient.detectObjects(imageUrl, {\n confidenceThreshold: input.confidenceThreshold\n })\n\n if (objects.length > 0) {\n const labelStudioResult = this.convertToLabelStudioFormat(objects)\n const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length\n\n await labelStudioApi.createPrediction({\n task: task.id,\n result: labelStudioResult,\n score: avgConfidence,\n model_version: input.modelId || 'default-model-v1.0'\n })\n\n result.predictionsCreated = (result.predictionsCreated || 0) + 1\n\n console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`)\n }\n } catch (predError) {\n console.error(`[Dataset Labeling] Failed to create prediction for task ${task.id}:`, predError)\n // Continue even if prediction fails\n }\n }\n } catch (taskError) {\n console.error(`[Dataset Labeling] Failed to create task for sample ${sample.id}:`, taskError)\n result.tasksFailed++\n }\n }\n\n console.log(\n `[Dataset Labeling] Completed: ${result.tasksCreated} created, ${result.tasksFailed} failed, ${result.tasksSkipped} skipped`\n )\n\n return result\n } catch (error) {\n console.error('[Dataset Labeling] Failed to create tasks:', error)\n throw new Error(`Failed to create labeling tasks: ${error.message}`)\n }\n }\n\n /**\n * Sync completed annotations from Label Studio back to DataSamples\n */\n @Mutation(returns => SyncAnnotationsResult, {\n description: 'Sync Label Studio annotations back to DataSamples, updating judgment fields'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"mutation\")')\n async syncAnnotationsToDataset(\n @Arg('input') input: SyncAnnotationsRequest,\n @Ctx() context: ResolverContext\n ): Promise<SyncAnnotationsResult> {\n const { domain } = context.state\n\n console.log(`[Dataset Labeling] Syncing annotations from LS project ${input.projectId} to DataSet ${input.dataSetId}`)\n\n try {\n // 1. Get all tasks from Label Studio project\n const tasksResponse = await labelStudioApi.getTasks(input.projectId, {\n page_size: 1000 // Adjust as needed\n })\n\n const tasks = tasksResponse.tasks || tasksResponse.results || []\n\n console.log(`[Dataset Labeling] Found ${tasks.length} tasks in Label Studio`)\n\n // 2. Filter tasks related to this DataSet\n const dataSetTasks = tasks.filter((task: any) => task.meta?.dataSetId === input.dataSetId)\n\n console.log(`[Dataset Labeling] ${dataSetTasks.length} tasks belong to DataSet ${input.dataSetId}`)\n\n const result: SyncAnnotationsResult = {\n totalAnnotations: 0,\n samplesUpdated: 0,\n updatesFailed: 0,\n skipped: 0\n }\n\n // 3. Process each task\n for (const task of dataSetTasks) {\n const dataSampleId = task.meta?.dataSampleId\n\n if (!dataSampleId) {\n result.skipped++\n continue\n }\n\n // Get annotations for this task\n const annotations = task.annotations || []\n\n if (annotations.length === 0) {\n result.skipped++\n continue\n }\n\n // Filter by completion status if requested\n let relevantAnnotations = annotations\n if (input.completedOnly) {\n relevantAnnotations = annotations.filter((ann: any) => ann.was_cancelled === false)\n }\n\n // Filter by date if requested\n if (input.sinceDate) {\n relevantAnnotations = relevantAnnotations.filter(\n (ann: any) => new Date(ann.updated_at) >= input.sinceDate\n )\n }\n\n if (relevantAnnotations.length === 0) {\n result.skipped++\n continue\n }\n\n result.totalAnnotations += relevantAnnotations.length\n\n try {\n // 4. Get DataSample\n const sample = await getRepository(DataSample).findOne({\n where: { domain: { id: domain.id }, id: dataSampleId }\n })\n\n if (!sample) {\n console.log(`[Dataset Labeling] DataSample not found: ${dataSampleId}`)\n result.updatesFailed++\n continue\n }\n\n // 5. Convert annotations to judgment data\n const latestAnnotation = relevantAnnotations[relevantAnnotations.length - 1]\n const judgment = this.convertAnnotationToJudgment(latestAnnotation, task)\n\n // 6. Update DataSample\n sample.judgment = judgment\n\n // Optionally update OOC/OOS flags based on annotation\n // This is application-specific logic\n if (judgment.defectsFound > 0) {\n sample.ooc = true\n }\n\n await getRepository(DataSample).save(sample)\n\n result.samplesUpdated++\n\n console.log(`[Dataset Labeling] Updated DataSample ${dataSampleId} with annotation ${latestAnnotation.id}`)\n } catch (updateError) {\n console.error(`[Dataset Labeling] Failed to update DataSample ${dataSampleId}:`, updateError)\n result.updatesFailed++\n }\n }\n\n console.log(\n `[Dataset Labeling] Sync completed: ${result.samplesUpdated} updated, ${result.updatesFailed} failed, ${result.skipped} skipped`\n )\n\n return result\n } catch (error) {\n console.error('[Dataset Labeling] Failed to sync annotations:', error)\n throw new Error(`Failed to sync annotations: ${error.message}`)\n }\n }\n\n /**\n * Generate AI predictions for existing DataSet samples\n */\n @Mutation(returns => BatchPredictionResult, {\n description: 'Generate AI predictions for DataSet samples that already have Label Studio tasks'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"mutation\")')\n async generatePredictionsForDataset(\n @Arg('input') input: GeneratePredictionsForDatasetRequest,\n @Ctx() context: ResolverContext\n ): Promise<BatchPredictionResult> {\n const { domain } = context.state\n\n console.log(\n `[Dataset Labeling] Generating predictions for DataSet ${input.dataSetId} in LS project ${input.projectId}`\n )\n\n try {\n // 1. Get all tasks from Label Studio project\n const tasksResponse = await labelStudioApi.getTasks(input.projectId, {\n page_size: 1000\n })\n\n const tasks = tasksResponse.tasks || tasksResponse.results || []\n const dataSetTasks = tasks.filter((task: any) => task.meta?.dataSetId === input.dataSetId)\n\n console.log(`[Dataset Labeling] Found ${dataSetTasks.length} tasks for DataSet`)\n\n const result: BatchPredictionResult = {\n total: dataSetTasks.length,\n succeeded: 0,\n failed: 0,\n results: [],\n modelVersion: input.modelId || 'default-model-v1.0'\n }\n\n // 2. Get AI model client\n const modelClient = input.modelId\n ? AIModelClientFactory.getClient(input.modelId)\n : AIModelClientFactory.getDefaultClient()\n\n // 3. Process tasks in batches\n const BATCH_SIZE = 5\n for (let i = 0; i < dataSetTasks.length; i += BATCH_SIZE) {\n const batch = dataSetTasks.slice(i, i + BATCH_SIZE)\n\n const batchPromises = batch.map(async (task: any) => {\n try {\n // Check if prediction already exists\n if (!input.forceRegenerate && task.predictions && task.predictions.length > 0) {\n result.results.push({\n taskId: task.id,\n success: true,\n objectCount: 0,\n error: 'Prediction already exists (skipped)'\n })\n return\n }\n\n const imageUrl = task.data?.image\n\n if (!imageUrl) {\n result.failed++\n result.results.push({\n taskId: task.id,\n success: false,\n objectCount: 0,\n error: 'No image URL in task data'\n })\n return\n }\n\n // Run AI inference\n const objects = await modelClient.detectObjects(imageUrl, {\n confidenceThreshold: input.confidenceThreshold\n })\n\n if (objects.length === 0) {\n result.succeeded++\n result.results.push({\n taskId: task.id,\n success: true,\n objectCount: 0,\n error: 'No objects detected'\n })\n return\n }\n\n // Create prediction\n const labelStudioResult = this.convertToLabelStudioFormat(objects)\n const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length\n\n const prediction = await labelStudioApi.createPrediction({\n task: task.id,\n result: labelStudioResult,\n score: avgConfidence,\n model_version: result.modelVersion\n })\n\n result.succeeded++\n result.results.push({\n taskId: task.id,\n predictionId: prediction.id,\n success: true,\n objectCount: objects.length,\n avgConfidence\n })\n\n console.log(`[Dataset Labeling] Created prediction for task ${task.id} (${objects.length} objects)`)\n } catch (error) {\n result.failed++\n result.results.push({\n taskId: task.id,\n success: false,\n objectCount: 0,\n error: error.message\n })\n }\n })\n\n await Promise.all(batchPromises)\n }\n\n console.log(`[Dataset Labeling] Prediction generation completed: ${result.succeeded}/${result.total} succeeded`)\n\n return result\n } catch (error) {\n console.error('[Dataset Labeling] Failed to generate predictions:', error)\n throw new Error(`Failed to generate predictions: ${error.message}`)\n }\n }\n\n /**\n * Query labeling status for a DataSet\n */\n @Query(returns => DatasetLabelingStatus, {\n description: 'Get labeling status and progress for a DataSet'\n })\n @Directive('@privilege(category: \"label-studio\", privilege: \"query\")')\n async datasetLabelingStatus(\n @Arg('input') input: DatasetLabelingStatusRequest,\n @Ctx() context: ResolverContext\n ): Promise<DatasetLabelingStatus> {\n const { domain } = context.state\n\n try {\n // 1. Get DataSet\n const dataSet = await getRepository(DataSet).findOne({\n where: { domain: { id: domain.id }, id: input.dataSetId }\n })\n\n if (!dataSet) {\n throw new Error(`DataSet not found: ${input.dataSetId}`)\n }\n\n // 2. Count total DataSamples\n const totalSamples = await getRepository(DataSample).count({\n where: { domain: { id: domain.id }, dataSetId: input.dataSetId }\n })\n\n // 3. Get Label Studio tasks if projectId provided\n let tasksCreated = 0\n let withPredictions = 0\n let withAnnotations = 0\n let annotationsCompleted = 0\n\n if (input.projectId) {\n const tasksResponse = await labelStudioApi.getTasks(input.projectId, {\n page_size: 1000\n })\n\n const tasks = tasksResponse.tasks || tasksResponse.results || []\n const dataSetTasks = tasks.filter((task: any) => task.meta?.dataSetId === input.dataSetId)\n\n tasksCreated = dataSetTasks.length\n\n for (const task of dataSetTasks) {\n if (task.predictions && task.predictions.length > 0) {\n withPredictions++\n }\n\n if (task.annotations && task.annotations.length > 0) {\n withAnnotations++\n\n const completedAnnotations = task.annotations.filter((ann: any) => ann.was_cancelled === false)\n if (completedAnnotations.length > 0) {\n annotationsCompleted++\n }\n }\n }\n }\n\n const notProcessed = totalSamples - tasksCreated\n const completionRate = totalSamples > 0 ? annotationsCompleted / totalSamples : 0\n\n return {\n dataSetId: input.dataSetId,\n dataSetName: dataSet.name,\n totalSamples,\n tasksCreated,\n withPredictions,\n withAnnotations,\n annotationsCompleted,\n notProcessed,\n completionRate,\n projectId: input.projectId,\n lastSyncedAt: new Date()\n }\n } catch (error) {\n console.error('[Dataset Labeling] Failed to get status:', error)\n throw new Error(`Failed to get labeling status: ${error.message}`)\n }\n }\n\n /**\n * Helper: Extract image URL from DataSample\n */\n private extractImageUrl(sample: DataSample, imageField: string): string | null {\n // Try data field first\n if (sample.data && typeof sample.data === 'object') {\n if (sample.data[imageField]) {\n return sample.data[imageField]\n }\n }\n\n // Try rawData field\n if (sample.rawData) {\n try {\n const rawData = typeof sample.rawData === 'string' ? JSON.parse(sample.rawData) : sample.rawData\n if (rawData[imageField]) {\n return rawData[imageField]\n }\n // If rawData is just a string URL\n if (typeof rawData === 'string' && rawData.startsWith('http')) {\n return rawData\n }\n } catch (e) {\n // If rawData is a plain URL string\n if (typeof sample.rawData === 'string' && sample.rawData.startsWith('http')) {\n return sample.rawData\n }\n }\n }\n\n return null\n }\n\n /**\n * Helper: Convert AI detection results to Label Studio format\n */\n private convertToLabelStudioFormat(objects: any[]) {\n return objects.map(obj => ({\n from_name: 'label',\n to_name: 'image',\n type: 'rectanglelabels',\n value: {\n x: obj.bbox.x,\n y: obj.bbox.y,\n width: obj.bbox.width,\n height: obj.bbox.height,\n rectanglelabels: [obj.className]\n }\n }))\n }\n\n /**\n * Helper: Convert Label Studio annotation to DataSample judgment format\n */\n private convertAnnotationToJudgment(annotation: any, task: any): any {\n const result = annotation.result || []\n\n const objects = result\n .filter((item: any) => item.type === 'rectanglelabels')\n .map((item: any) => ({\n type: item.value.rectanglelabels?.[0] || 'unknown',\n bbox: {\n x: item.value.x,\n y: item.value.y,\n width: item.value.width,\n height: item.value.height\n },\n source: 'human-annotation'\n }))\n\n return {\n annotationId: annotation.id,\n taskId: task.id,\n annotatedBy: annotation.completed_by,\n annotatedAt: annotation.updated_at,\n objects,\n objectCount: objects.length,\n defectsFound: objects.length, // Application-specific logic\n verified: true,\n source: 'label-studio'\n }\n }\n}\n"]}
|