@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,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Transformer
|
|
3
|
+
*
|
|
4
|
+
* Flexible data transformation from any source format to Label Studio tasks.
|
|
5
|
+
* Supports nested data, predictions, and custom field mapping.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface TaskTransformRule {
|
|
9
|
+
/**
|
|
10
|
+
* Field mapping: Label Studio field name -> source data path
|
|
11
|
+
* Example: { "image": "image_url", "date": "metadata.timestamp" }
|
|
12
|
+
*/
|
|
13
|
+
dataFields: { [lsField: string]: string }
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Prediction configuration (optional)
|
|
17
|
+
*/
|
|
18
|
+
predictions?: PredictionConfig
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Metadata to attach to task (optional)
|
|
22
|
+
*/
|
|
23
|
+
meta?: { [key: string]: string }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PredictionConfig {
|
|
27
|
+
/**
|
|
28
|
+
* Enable predictions
|
|
29
|
+
*/
|
|
30
|
+
enabled: boolean
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Path to prediction result in source data
|
|
34
|
+
*/
|
|
35
|
+
resultPath: string
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Path to confidence score (optional)
|
|
39
|
+
*/
|
|
40
|
+
scorePath?: string
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Model version identifier (optional)
|
|
44
|
+
*/
|
|
45
|
+
modelVersion?: string
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Transform function for prediction result
|
|
49
|
+
*/
|
|
50
|
+
resultTransform?: (result: any) => any
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LabelStudioTask {
|
|
54
|
+
data: string // JSON string
|
|
55
|
+
predictions?: Array<{
|
|
56
|
+
result: any
|
|
57
|
+
score?: number
|
|
58
|
+
model_version?: string
|
|
59
|
+
}>
|
|
60
|
+
meta?: any
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class TaskTransformer {
|
|
64
|
+
/**
|
|
65
|
+
* Transform source data to Label Studio tasks
|
|
66
|
+
*
|
|
67
|
+
* @param sourceData - Array of source data objects
|
|
68
|
+
* @param rule - Transformation rules
|
|
69
|
+
* @returns Array of Label Studio tasks
|
|
70
|
+
*/
|
|
71
|
+
static transform(sourceData: any[], rule: TaskTransformRule): LabelStudioTask[] {
|
|
72
|
+
return sourceData.map(data => this.transformOne(data, rule)).filter(task => task !== null) as LabelStudioTask[]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Transform a single data object
|
|
77
|
+
*/
|
|
78
|
+
static transformOne(sourceData: any, rule: TaskTransformRule): LabelStudioTask | null {
|
|
79
|
+
try {
|
|
80
|
+
// Build task data
|
|
81
|
+
const taskData: any = {}
|
|
82
|
+
for (const [lsField, sourcePath] of Object.entries(rule.dataFields)) {
|
|
83
|
+
const value = this.getNestedValue(sourceData, sourcePath)
|
|
84
|
+
if (value !== undefined) {
|
|
85
|
+
taskData[lsField] = value
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Skip if no data extracted
|
|
90
|
+
if (Object.keys(taskData).length === 0) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const task: LabelStudioTask = {
|
|
95
|
+
data: JSON.stringify(taskData)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Add predictions if configured
|
|
99
|
+
if (rule.predictions?.enabled) {
|
|
100
|
+
const prediction = this.buildPrediction(sourceData, rule.predictions)
|
|
101
|
+
if (prediction) {
|
|
102
|
+
task.predictions = [prediction]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add metadata if configured
|
|
107
|
+
if (rule.meta) {
|
|
108
|
+
const meta: any = {}
|
|
109
|
+
for (const [metaKey, sourcePath] of Object.entries(rule.meta)) {
|
|
110
|
+
const value = this.getNestedValue(sourceData, sourcePath)
|
|
111
|
+
if (value !== undefined) {
|
|
112
|
+
meta[metaKey] = value
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (Object.keys(meta).length > 0) {
|
|
116
|
+
task.meta = meta
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return task
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Failed to transform task:', error)
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build prediction object from source data
|
|
129
|
+
*/
|
|
130
|
+
private static buildPrediction(
|
|
131
|
+
sourceData: any,
|
|
132
|
+
config: PredictionConfig
|
|
133
|
+
): { result: any; score?: number; model_version?: string } | null {
|
|
134
|
+
const result = this.getNestedValue(sourceData, config.resultPath)
|
|
135
|
+
if (!result) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const prediction: any = {
|
|
140
|
+
result: config.resultTransform ? config.resultTransform(result) : result
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (config.scorePath) {
|
|
144
|
+
const score = this.getNestedValue(sourceData, config.scorePath)
|
|
145
|
+
if (score !== undefined) {
|
|
146
|
+
prediction.score = score
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (config.modelVersion) {
|
|
151
|
+
prediction.model_version = config.modelVersion
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return prediction
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get nested value from object using dot notation path
|
|
159
|
+
* Example: "metadata.user.name" -> obj.metadata.user.name
|
|
160
|
+
*/
|
|
161
|
+
private static getNestedValue(obj: any, path: string): any {
|
|
162
|
+
if (!path || !obj) return undefined
|
|
163
|
+
|
|
164
|
+
const keys = path.split('.')
|
|
165
|
+
let current = obj
|
|
166
|
+
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
if (current === null || current === undefined) {
|
|
169
|
+
return undefined
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Support array indexing: "items[0].name"
|
|
173
|
+
const arrayMatch = key.match(/^(\w+)\[(\d+)\]$/)
|
|
174
|
+
if (arrayMatch) {
|
|
175
|
+
const [, arrayKey, index] = arrayMatch
|
|
176
|
+
current = current[arrayKey]?.[parseInt(index, 10)]
|
|
177
|
+
} else {
|
|
178
|
+
current = current[key]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return current
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Transform with multiple rules (useful for heterogeneous data)
|
|
187
|
+
*/
|
|
188
|
+
static transformMultiRule(sourceData: any[], rules: TaskTransformRule[]): LabelStudioTask[] {
|
|
189
|
+
const tasks: LabelStudioTask[] = []
|
|
190
|
+
|
|
191
|
+
for (const rule of rules) {
|
|
192
|
+
tasks.push(...this.transform(sourceData, rule))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return tasks
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Pre-built transformation templates for common scenarios
|
|
201
|
+
*/
|
|
202
|
+
export class TaskTransformTemplates {
|
|
203
|
+
/**
|
|
204
|
+
* WBM Image Classification with AI predictions
|
|
205
|
+
*/
|
|
206
|
+
static wbmImageClassification(includeAiPrediction: boolean = true): TaskTransformRule {
|
|
207
|
+
const rule: TaskTransformRule = {
|
|
208
|
+
dataFields: {
|
|
209
|
+
image: 'image_url',
|
|
210
|
+
date: 'timestamp',
|
|
211
|
+
device_id: 'device_id'
|
|
212
|
+
},
|
|
213
|
+
meta: {
|
|
214
|
+
wafer_id: 'wafer_id',
|
|
215
|
+
lot_id: 'lot_id'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (includeAiPrediction) {
|
|
220
|
+
rule.predictions = {
|
|
221
|
+
enabled: true,
|
|
222
|
+
resultPath: 'ai_prediction',
|
|
223
|
+
scorePath: 'confidence',
|
|
224
|
+
modelVersion: 'wbm-classifier-v1',
|
|
225
|
+
resultTransform: result => {
|
|
226
|
+
// Transform AI result to Label Studio format
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
from_name: 'rank1',
|
|
230
|
+
to_name: 'data',
|
|
231
|
+
type: 'choices',
|
|
232
|
+
value: {
|
|
233
|
+
choices: [result.rank1]
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
from_name: 'rank2',
|
|
238
|
+
to_name: 'data',
|
|
239
|
+
type: 'choices',
|
|
240
|
+
value: {
|
|
241
|
+
choices: [result.rank2]
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
from_name: 'rank3',
|
|
246
|
+
to_name: 'data',
|
|
247
|
+
type: 'choices',
|
|
248
|
+
value: {
|
|
249
|
+
choices: [result.rank3]
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return rule
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Simple image classification
|
|
262
|
+
*/
|
|
263
|
+
static imageClassification(imageField: string = 'image_url'): TaskTransformRule {
|
|
264
|
+
return {
|
|
265
|
+
dataFields: {
|
|
266
|
+
image: imageField
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Text classification with metadata
|
|
273
|
+
*/
|
|
274
|
+
static textClassification(textField: string = 'text', metaFields: string[] = []): TaskTransformRule {
|
|
275
|
+
const rule: TaskTransformRule = {
|
|
276
|
+
dataFields: {
|
|
277
|
+
text: textField
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (metaFields.length > 0) {
|
|
282
|
+
rule.meta = {}
|
|
283
|
+
for (const field of metaFields) {
|
|
284
|
+
rule.meta[field] = field
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return rule
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Time series data
|
|
293
|
+
*/
|
|
294
|
+
static timeSeries(valuesField: string, timestampField?: string): TaskTransformRule {
|
|
295
|
+
const dataFields: any = {
|
|
296
|
+
timeseries: valuesField
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (timestampField) {
|
|
300
|
+
dataFields.timestamp = timestampField
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
dataFields
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Object detection with bounding box predictions
|
|
310
|
+
*/
|
|
311
|
+
static objectDetection(imageField: string = 'image_url', predictionsField?: string): TaskTransformRule {
|
|
312
|
+
const rule: TaskTransformRule = {
|
|
313
|
+
dataFields: {
|
|
314
|
+
image: imageField
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (predictionsField) {
|
|
319
|
+
rule.predictions = {
|
|
320
|
+
enabled: true,
|
|
321
|
+
resultPath: predictionsField,
|
|
322
|
+
resultTransform: bboxes => {
|
|
323
|
+
// Transform bounding boxes to Label Studio format
|
|
324
|
+
return bboxes.map((bbox: any) => ({
|
|
325
|
+
from_name: 'bbox',
|
|
326
|
+
to_name: 'data',
|
|
327
|
+
type: 'rectanglelabels',
|
|
328
|
+
value: {
|
|
329
|
+
x: bbox.x,
|
|
330
|
+
y: bbox.y,
|
|
331
|
+
width: bbox.width,
|
|
332
|
+
height: bbox.height,
|
|
333
|
+
rectanglelabels: [bbox.label]
|
|
334
|
+
}
|
|
335
|
+
}))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return rule
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test script for AI Prediction integration with Label Studio
|
|
3
|
+
*
|
|
4
|
+
* This script tests the complete flow:
|
|
5
|
+
* 1. Get a task from Label Studio
|
|
6
|
+
* 2. Run AI inference on the task's image
|
|
7
|
+
* 3. Convert AI results to Label Studio format
|
|
8
|
+
* 4. Create prediction in Label Studio
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const axios = require('axios')
|
|
12
|
+
|
|
13
|
+
// Configuration
|
|
14
|
+
// NOTE: With subdomain cookie-sharing approach, we access Label Studio directly
|
|
15
|
+
// No proxy path needed - Label Studio runs on its own subdomain
|
|
16
|
+
const LABEL_STUDIO_URL = process.env.LABEL_STUDIO_URL || 'http://label.dataset.localhost:8080'
|
|
17
|
+
const LABEL_STUDIO_TOKEN = process.env.LABEL_STUDIO_API_TOKEN || ''
|
|
18
|
+
const THINGS_FACTORY_GRAPHQL = process.env.THINGS_FACTORY_GRAPHQL || 'http://dataset.localhost:3000/graphql'
|
|
19
|
+
|
|
20
|
+
// Test configuration
|
|
21
|
+
const PROJECT_ID = process.env.TEST_PROJECT_ID || 2 // Change to your project ID
|
|
22
|
+
const TASK_ID = process.env.TEST_TASK_ID // Optional: specific task ID
|
|
23
|
+
|
|
24
|
+
async function testLabelStudioConnection() {
|
|
25
|
+
console.log('\nš Testing Label Studio connection...')
|
|
26
|
+
console.log(` URL: ${LABEL_STUDIO_URL}`)
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const response = await axios.get(`${LABEL_STUDIO_URL}/api/projects`, {
|
|
30
|
+
headers: {
|
|
31
|
+
'Authorization': `Token ${LABEL_STUDIO_TOKEN}`
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
console.log(`ā
Label Studio connected successfully`)
|
|
36
|
+
console.log(` Found ${response.data.results?.length || 0} projects`)
|
|
37
|
+
return true
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`ā Label Studio connection failed:`, error.message)
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getTestTask() {
|
|
45
|
+
console.log(`\nš Getting test task from project ${PROJECT_ID}...`)
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Get tasks from project
|
|
49
|
+
const response = await axios.get(
|
|
50
|
+
`${LABEL_STUDIO_URL}/api/projects/${PROJECT_ID}/tasks`,
|
|
51
|
+
{
|
|
52
|
+
headers: { 'Authorization': `Token ${LABEL_STUDIO_TOKEN}` },
|
|
53
|
+
params: { page_size: 10 }
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const tasks = response.data.results || response.data
|
|
58
|
+
|
|
59
|
+
if (!tasks || tasks.length === 0) {
|
|
60
|
+
console.log(`ā ļø No tasks found in project ${PROJECT_ID}`)
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Find a task with an image
|
|
65
|
+
const taskWithImage = tasks.find(task => task.data && task.data.image)
|
|
66
|
+
|
|
67
|
+
if (!taskWithImage) {
|
|
68
|
+
console.log(`ā ļø No tasks with images found`)
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`ā
Found task ${taskWithImage.id}`)
|
|
73
|
+
console.log(` Image: ${taskWithImage.data.image}`)
|
|
74
|
+
console.log(` Annotations: ${taskWithImage.annotations?.length || 0}`)
|
|
75
|
+
console.log(` Predictions: ${taskWithImage.predictions?.length || 0}`)
|
|
76
|
+
|
|
77
|
+
return taskWithImage
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`ā Failed to get task:`, error.message)
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function testAIInference(imageUrl) {
|
|
85
|
+
console.log(`\nš¤ Testing AI inference...`)
|
|
86
|
+
console.log(` Image: ${imageUrl}`)
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const query = `
|
|
90
|
+
query DetectObjects($input: InferenceRequest!) {
|
|
91
|
+
detectObjects(input: $input) {
|
|
92
|
+
className
|
|
93
|
+
confidence
|
|
94
|
+
bbox {
|
|
95
|
+
x
|
|
96
|
+
y
|
|
97
|
+
width
|
|
98
|
+
height
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
`
|
|
103
|
+
|
|
104
|
+
const response = await axios.post(THINGS_FACTORY_GRAPHQL, {
|
|
105
|
+
query,
|
|
106
|
+
variables: {
|
|
107
|
+
input: {
|
|
108
|
+
imageUrl,
|
|
109
|
+
confidenceThreshold: 0.5
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
if (response.data.errors) {
|
|
115
|
+
console.error(`ā GraphQL errors:`, response.data.errors)
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const objects = response.data.data.detectObjects
|
|
120
|
+
console.log(`ā
AI detected ${objects.length} objects:`)
|
|
121
|
+
objects.forEach((obj, i) => {
|
|
122
|
+
console.log(` ${i + 1}. ${obj.className} (${(obj.confidence * 100).toFixed(1)}%)`)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return objects
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`ā AI inference failed:`, error.message)
|
|
128
|
+
if (error.response?.data) {
|
|
129
|
+
console.error(` Response:`, JSON.stringify(error.response.data, null, 2))
|
|
130
|
+
}
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function testPredictionGeneration(taskId, imageUrl) {
|
|
136
|
+
console.log(`\nšÆ Testing prediction generation for task ${taskId}...`)
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const mutation = `
|
|
140
|
+
mutation GeneratePrediction($input: GeneratePredictionRequest!) {
|
|
141
|
+
generatePrediction(input: $input) {
|
|
142
|
+
taskId
|
|
143
|
+
predictionId
|
|
144
|
+
success
|
|
145
|
+
objectCount
|
|
146
|
+
avgConfidence
|
|
147
|
+
error
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`
|
|
151
|
+
|
|
152
|
+
const response = await axios.post(THINGS_FACTORY_GRAPHQL, {
|
|
153
|
+
query: mutation,
|
|
154
|
+
variables: {
|
|
155
|
+
input: {
|
|
156
|
+
taskId,
|
|
157
|
+
imageUrl,
|
|
158
|
+
confidenceThreshold: 0.5
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
if (response.data.errors) {
|
|
164
|
+
console.error(`ā GraphQL errors:`, response.data.errors)
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = response.data.data.generatePrediction
|
|
169
|
+
|
|
170
|
+
if (result.success) {
|
|
171
|
+
console.log(`ā
Prediction created successfully!`)
|
|
172
|
+
console.log(` Prediction ID: ${result.predictionId}`)
|
|
173
|
+
console.log(` Objects detected: ${result.objectCount}`)
|
|
174
|
+
console.log(` Avg confidence: ${(result.avgConfidence * 100).toFixed(1)}%`)
|
|
175
|
+
} else {
|
|
176
|
+
console.log(`ā ļø Prediction generation reported error:`, result.error)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(`ā Prediction generation failed:`, error.message)
|
|
182
|
+
if (error.response?.data) {
|
|
183
|
+
console.error(` Response:`, JSON.stringify(error.response.data, null, 2))
|
|
184
|
+
}
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function verifyPrediction(taskId) {
|
|
190
|
+
console.log(`\nā Verifying prediction in Label Studio...`)
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const response = await axios.get(
|
|
194
|
+
`${LABEL_STUDIO_URL}/api/tasks/${taskId}`,
|
|
195
|
+
{
|
|
196
|
+
headers: { 'Authorization': `Token ${LABEL_STUDIO_TOKEN}` }
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
const task = response.data
|
|
201
|
+
const predictionCount = task.predictions?.length || 0
|
|
202
|
+
|
|
203
|
+
console.log(`ā
Task has ${predictionCount} prediction(s)`)
|
|
204
|
+
|
|
205
|
+
if (predictionCount > 0) {
|
|
206
|
+
const latestPrediction = task.predictions[task.predictions.length - 1]
|
|
207
|
+
console.log(` Latest prediction:`)
|
|
208
|
+
console.log(` - ID: ${latestPrediction.id}`)
|
|
209
|
+
console.log(` - Model: ${latestPrediction.model_version}`)
|
|
210
|
+
console.log(` - Score: ${(latestPrediction.score * 100).toFixed(1)}%`)
|
|
211
|
+
console.log(` - Objects: ${latestPrediction.result?.length || 0}`)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return true
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(`ā Failed to verify prediction:`, error.message)
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function main() {
|
|
222
|
+
console.log('š Starting AI Prediction Integration Test\n')
|
|
223
|
+
console.log('=' .repeat(60))
|
|
224
|
+
|
|
225
|
+
// Step 1: Test Label Studio connection
|
|
226
|
+
const lsConnected = await testLabelStudioConnection()
|
|
227
|
+
if (!lsConnected) {
|
|
228
|
+
console.log('\nā Cannot proceed without Label Studio connection')
|
|
229
|
+
process.exit(1)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Step 2: Get a test task
|
|
233
|
+
const task = await getTestTask()
|
|
234
|
+
if (!task) {
|
|
235
|
+
console.log('\nā Cannot proceed without a test task')
|
|
236
|
+
console.log('š” Please create a task with an image in Label Studio project', PROJECT_ID)
|
|
237
|
+
process.exit(1)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Step 3: Test AI inference (optional - direct test)
|
|
241
|
+
const objects = await testAIInference(task.data.image)
|
|
242
|
+
if (!objects) {
|
|
243
|
+
console.log('\nā ļø AI inference test failed, but continuing with prediction generation...')
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Step 4: Generate prediction via GraphQL
|
|
247
|
+
const result = await testPredictionGeneration(task.id, task.data.image)
|
|
248
|
+
if (!result || !result.success) {
|
|
249
|
+
console.log('\nā Prediction generation failed')
|
|
250
|
+
process.exit(1)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Step 5: Verify prediction was created
|
|
254
|
+
await verifyPrediction(task.id)
|
|
255
|
+
|
|
256
|
+
console.log('\n' + '='.repeat(60))
|
|
257
|
+
console.log('⨠Test completed successfully!')
|
|
258
|
+
console.log(`\nš Next steps:`)
|
|
259
|
+
console.log(` 1. Open Label Studio: ${LABEL_STUDIO_URL}/projects/${PROJECT_ID}`)
|
|
260
|
+
console.log(` 2. Open task ${task.id} to see the AI-generated prediction`)
|
|
261
|
+
console.log(` 3. The prediction should appear as pre-labeled regions`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Run the test
|
|
265
|
+
main().catch(error => {
|
|
266
|
+
console.error('\nš„ Test failed with error:', error)
|
|
267
|
+
process.exit(1)
|
|
268
|
+
})
|