label-studio-converter 1.1.0 → 1.3.0
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/README.md +898 -273
- package/dist/bash-complete.cjs +1609 -685
- package/dist/bash-complete.cjs.map +1 -1
- package/dist/bash-complete.js +1600 -678
- package/dist/bash-complete.js.map +1 -1
- package/dist/cli.cjs +1608 -684
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1600 -678
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +893 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -8
- package/dist/index.d.ts +119 -8
- package/dist/index.js +879 -101
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/bash-complete.cjs
CHANGED
|
@@ -38,12 +38,11 @@ var init_cjs_shims = __esm({
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
// src/constants.ts
|
|
41
|
-
var
|
|
41
|
+
var DEFAULT_LABEL_NAME, DEFAULT_LABEL_STUDIO_FULL_JSON, DEFAULT_CREATE_FILE_PER_IMAGE, DEFAULT_CREATE_FILE_LIST_FOR_SERVING, DEFAULT_FILE_LIST_NAME, DEFAULT_BASE_SERVER_URL, DEFAULT_PPOCR_FILE_NAME, SORT_VERTICAL_NONE, SORT_VERTICAL_TOP_BOTTOM, SORT_VERTICAL_BOTTOM_TOP, DEFAULT_SORT_VERTICAL, SORT_HORIZONTAL_NONE, SORT_HORIZONTAL_LTR, SORT_HORIZONTAL_RTL, DEFAULT_SORT_HORIZONTAL, SHAPE_NORMALIZE_NONE, SHAPE_NORMALIZE_RECTANGLE, DEFAULT_SHAPE_NORMALIZE, DEFAULT_WIDTH_INCREMENT, DEFAULT_HEIGHT_INCREMENT, DEFAULT_LABEL_STUDIO_PRECISION, DEFAULT_PPOCR_PRECISION, DEFAULT_RECURSIVE, DEFAULT_PPOCR_FILE_PATTERN, DEFAULT_LABEL_STUDIO_FILE_PATTERN, OUTPUT_MODE_ANNOTATIONS, OUTPUT_MODE_PREDICTIONS, DEFAULT_OUTPUT_MODE, DEFAULT_BACKUP, BACKUP_SUFFIX_PREFIX;
|
|
42
42
|
var init_constants = __esm({
|
|
43
43
|
"src/constants.ts"() {
|
|
44
44
|
"use strict";
|
|
45
45
|
init_cjs_shims();
|
|
46
|
-
OUTPUT_BASE_DIR = "./output";
|
|
47
46
|
DEFAULT_LABEL_NAME = "Text";
|
|
48
47
|
DEFAULT_LABEL_STUDIO_FULL_JSON = true;
|
|
49
48
|
DEFAULT_CREATE_FILE_PER_IMAGE = false;
|
|
@@ -66,6 +65,86 @@ var init_constants = __esm({
|
|
|
66
65
|
DEFAULT_HEIGHT_INCREMENT = 0;
|
|
67
66
|
DEFAULT_LABEL_STUDIO_PRECISION = -1;
|
|
68
67
|
DEFAULT_PPOCR_PRECISION = 0;
|
|
68
|
+
DEFAULT_RECURSIVE = false;
|
|
69
|
+
DEFAULT_PPOCR_FILE_PATTERN = ".*\\.txt$";
|
|
70
|
+
DEFAULT_LABEL_STUDIO_FILE_PATTERN = ".*\\.json$";
|
|
71
|
+
OUTPUT_MODE_ANNOTATIONS = "annotations";
|
|
72
|
+
OUTPUT_MODE_PREDICTIONS = "predictions";
|
|
73
|
+
DEFAULT_OUTPUT_MODE = OUTPUT_MODE_ANNOTATIONS;
|
|
74
|
+
DEFAULT_BACKUP = false;
|
|
75
|
+
BACKUP_SUFFIX_PREFIX = "backup-";
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// src/lib/backup-utils.ts
|
|
80
|
+
async function backupFileIfExists(filePath) {
|
|
81
|
+
if (!(0, import_fs.existsSync)(filePath)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").split(".")[0];
|
|
85
|
+
const backupPath = `${filePath}.${BACKUP_SUFFIX_PREFIX}${timestamp}`;
|
|
86
|
+
await copyFileAsync(filePath, backupPath);
|
|
87
|
+
return backupPath;
|
|
88
|
+
}
|
|
89
|
+
var import_fs, import_util, copyFileAsync;
|
|
90
|
+
var init_backup_utils = __esm({
|
|
91
|
+
"src/lib/backup-utils.ts"() {
|
|
92
|
+
"use strict";
|
|
93
|
+
init_cjs_shims();
|
|
94
|
+
import_fs = require("fs");
|
|
95
|
+
import_util = require("util");
|
|
96
|
+
init_constants();
|
|
97
|
+
copyFileAsync = (0, import_util.promisify)(import_fs.copyFile);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// src/lib/file-utils.ts
|
|
102
|
+
async function findFiles(dirs, pattern, recursive) {
|
|
103
|
+
const allFiles = [];
|
|
104
|
+
const regex = new RegExp(pattern);
|
|
105
|
+
async function scanDirectory(dirPath) {
|
|
106
|
+
const entries = await (0, import_promises.readdir)(dirPath, { withFileTypes: true });
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const fullPath = (0, import_path.join)(dirPath, entry.name);
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
if (recursive) {
|
|
111
|
+
await scanDirectory(fullPath);
|
|
112
|
+
}
|
|
113
|
+
} else if (entry.isFile()) {
|
|
114
|
+
if (regex.test(entry.name)) {
|
|
115
|
+
allFiles.push(fullPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const dir of dirs) {
|
|
121
|
+
const dirStat = await (0, import_promises.stat)(dir);
|
|
122
|
+
if (dirStat.isDirectory()) {
|
|
123
|
+
await scanDirectory(dir);
|
|
124
|
+
} else if (dirStat.isFile()) {
|
|
125
|
+
if (regex.test(dir)) {
|
|
126
|
+
allFiles.push(dir);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return allFiles;
|
|
131
|
+
}
|
|
132
|
+
function getRelativePathFromInputs(filePath, inputDirs) {
|
|
133
|
+
for (const inputDir of inputDirs) {
|
|
134
|
+
const rel = (0, import_path.relative)(inputDir, filePath);
|
|
135
|
+
if (!rel.startsWith("..") && !rel.startsWith("/")) {
|
|
136
|
+
return rel;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return (0, import_path.relative)(process.cwd(), filePath);
|
|
140
|
+
}
|
|
141
|
+
var import_promises, import_path;
|
|
142
|
+
var init_file_utils = __esm({
|
|
143
|
+
"src/lib/file-utils.ts"() {
|
|
144
|
+
"use strict";
|
|
145
|
+
init_cjs_shims();
|
|
146
|
+
import_promises = require("fs/promises");
|
|
147
|
+
import_path = require("path");
|
|
69
148
|
}
|
|
70
149
|
});
|
|
71
150
|
|
|
@@ -155,273 +234,533 @@ var init_geometry = __esm({
|
|
|
155
234
|
}
|
|
156
235
|
});
|
|
157
236
|
|
|
158
|
-
// src/lib/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
237
|
+
// src/lib/sort.ts
|
|
238
|
+
function getBoundingBoxCenter(points) {
|
|
239
|
+
let minX = Infinity;
|
|
240
|
+
let minY = Infinity;
|
|
241
|
+
let maxX = -Infinity;
|
|
242
|
+
let maxY = -Infinity;
|
|
243
|
+
for (const [x, y] of points) {
|
|
244
|
+
if (x !== void 0 && y !== void 0) {
|
|
245
|
+
minX = Math.min(minX, x);
|
|
246
|
+
minY = Math.min(minY, y);
|
|
247
|
+
maxX = Math.max(maxX, x);
|
|
248
|
+
maxY = Math.max(maxY, y);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
x: (minX + maxX) / 2,
|
|
253
|
+
y: (minY + maxY) / 2,
|
|
254
|
+
width: maxX - minX,
|
|
255
|
+
height: maxY - minY
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function sortBoundingBoxes(annotations, verticalSort, horizontalSort) {
|
|
259
|
+
if (verticalSort === SORT_VERTICAL_NONE && horizontalSort === SORT_HORIZONTAL_NONE) {
|
|
260
|
+
return annotations;
|
|
261
|
+
}
|
|
262
|
+
const sorted = [...annotations];
|
|
263
|
+
const isVerticalText = sorted.length > 0 && (() => {
|
|
264
|
+
const verticalCount = sorted.filter((ann) => {
|
|
265
|
+
const center = getBoundingBoxCenter(ann.points);
|
|
266
|
+
return center.height > center.width * 1.5;
|
|
267
|
+
}).length;
|
|
268
|
+
return verticalCount > sorted.length / 2;
|
|
269
|
+
})();
|
|
270
|
+
if (horizontalSort === SORT_HORIZONTAL_RTL && verticalSort !== SORT_VERTICAL_NONE && isVerticalText) {
|
|
271
|
+
const annotationsWithCenters = sorted.map((ann) => ({
|
|
272
|
+
annotation: ann,
|
|
273
|
+
center: getBoundingBoxCenter(ann.points)
|
|
274
|
+
}));
|
|
275
|
+
const columns = [];
|
|
276
|
+
for (const item of annotationsWithCenters) {
|
|
277
|
+
let addedToColumn = false;
|
|
278
|
+
for (const column of columns) {
|
|
279
|
+
const avgX = column.reduce((sum, c) => sum + c.center.x, 0) / column.length;
|
|
280
|
+
if (Math.abs(item.center.x - avgX) < GROUPING_TOLERANCE) {
|
|
281
|
+
column.push(item);
|
|
282
|
+
addedToColumn = true;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (!addedToColumn) {
|
|
287
|
+
columns.push([item]);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
columns.sort((colA, colB) => {
|
|
291
|
+
const avgXA = colA.reduce((sum, c) => sum + c.center.x, 0) / colA.length;
|
|
292
|
+
const avgXB = colB.reduce((sum, c) => sum + c.center.x, 0) / colB.length;
|
|
293
|
+
return avgXB - avgXA;
|
|
294
|
+
});
|
|
295
|
+
for (const column of columns) {
|
|
296
|
+
column.sort((a, b) => {
|
|
297
|
+
return verticalSort === SORT_VERTICAL_TOP_BOTTOM ? a.center.y - b.center.y : b.center.y - a.center.y;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return columns.flat().map((item) => item.annotation);
|
|
301
|
+
}
|
|
302
|
+
sorted.sort((a, b) => {
|
|
303
|
+
const centerA = getBoundingBoxCenter(a.points);
|
|
304
|
+
const centerB = getBoundingBoxCenter(b.points);
|
|
305
|
+
if (verticalSort !== SORT_VERTICAL_NONE) {
|
|
306
|
+
const yDiff = verticalSort === SORT_VERTICAL_TOP_BOTTOM ? centerA.y - centerB.y : centerB.y - centerA.y;
|
|
307
|
+
if (Math.abs(yDiff) > GROUPING_TOLERANCE) {
|
|
308
|
+
return yDiff;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (horizontalSort !== SORT_HORIZONTAL_NONE) {
|
|
312
|
+
return horizontalSort === SORT_HORIZONTAL_LTR ? centerA.x - centerB.x : centerB.x - centerA.x;
|
|
313
|
+
}
|
|
314
|
+
return 0;
|
|
315
|
+
});
|
|
316
|
+
return sorted;
|
|
317
|
+
}
|
|
318
|
+
var GROUPING_TOLERANCE;
|
|
319
|
+
var init_sort = __esm({
|
|
320
|
+
"src/lib/sort.ts"() {
|
|
162
321
|
"use strict";
|
|
163
322
|
init_cjs_shims();
|
|
164
|
-
import_node_crypto = require("crypto");
|
|
165
|
-
import_node_fs = require("fs");
|
|
166
|
-
import_node_path = require("path");
|
|
167
|
-
import_image_size = __toESM(require("image-size"), 1);
|
|
168
323
|
init_constants();
|
|
324
|
+
GROUPING_TOLERANCE = 50;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// src/lib/enhance.ts
|
|
329
|
+
function enhancePPOCRLabel(data, options) {
|
|
330
|
+
const {
|
|
331
|
+
sortVertical,
|
|
332
|
+
sortHorizontal,
|
|
333
|
+
normalizeShape: normalizeShape2,
|
|
334
|
+
widthIncrement = 0,
|
|
335
|
+
heightIncrement = 0,
|
|
336
|
+
precision = 0
|
|
337
|
+
} = options;
|
|
338
|
+
let enhanced = data;
|
|
339
|
+
if (sortVertical && sortHorizontal) {
|
|
340
|
+
enhanced = sortBoundingBoxes(enhanced, sortVertical, sortHorizontal);
|
|
341
|
+
}
|
|
342
|
+
if (normalizeShape2 || widthIncrement !== 0 || heightIncrement !== 0) {
|
|
343
|
+
enhanced = enhanced.map((annotation) => {
|
|
344
|
+
let points = transformPoints(annotation.points, {
|
|
345
|
+
normalizeShape: normalizeShape2,
|
|
346
|
+
widthIncrement,
|
|
347
|
+
heightIncrement
|
|
348
|
+
});
|
|
349
|
+
points = roundPoints(points, precision);
|
|
350
|
+
return {
|
|
351
|
+
...annotation,
|
|
352
|
+
points
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return enhanced;
|
|
357
|
+
}
|
|
358
|
+
var init_enhance = __esm({
|
|
359
|
+
"src/lib/enhance.ts"() {
|
|
360
|
+
"use strict";
|
|
361
|
+
init_cjs_shims();
|
|
362
|
+
init_geometry();
|
|
363
|
+
init_sort();
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// src/lib/label-studio.ts
|
|
368
|
+
var turf, labelStudioToPPOCR, minLabelStudioToPPOCR, processAnnotationOrPrediction, enhanceLabelStudioData;
|
|
369
|
+
var init_label_studio = __esm({
|
|
370
|
+
"src/lib/label-studio.ts"() {
|
|
371
|
+
"use strict";
|
|
372
|
+
init_cjs_shims();
|
|
373
|
+
turf = __toESM(require("@turf/turf"), 1);
|
|
374
|
+
init_constants();
|
|
375
|
+
init_enhance();
|
|
169
376
|
init_geometry();
|
|
170
|
-
|
|
377
|
+
labelStudioToPPOCR = async (data, options) => {
|
|
171
378
|
const {
|
|
172
|
-
|
|
173
|
-
baseServerUrl,
|
|
174
|
-
inputDir,
|
|
175
|
-
toFullJson = true,
|
|
176
|
-
taskId = 1,
|
|
177
|
-
labelName = DEFAULT_LABEL_NAME,
|
|
379
|
+
baseImageDir,
|
|
178
380
|
normalizeShape: normalizeShape2,
|
|
179
381
|
widthIncrement = 0,
|
|
180
382
|
heightIncrement = 0,
|
|
181
|
-
precision =
|
|
383
|
+
precision = 0
|
|
182
384
|
} = options || {};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
let original_width = 1920;
|
|
214
|
-
let original_height = 1080;
|
|
215
|
-
const resolvedImagePath = inputDir ? (0, import_node_path.join)(inputDir, imagePath) : imagePath;
|
|
216
|
-
if (!(0, import_node_fs.existsSync)(resolvedImagePath)) {
|
|
217
|
-
throw new Error(`Image file not found: ${resolvedImagePath}`);
|
|
218
|
-
}
|
|
219
|
-
const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
|
|
220
|
-
const dimensions = (0, import_image_size.default)(buffer);
|
|
221
|
-
if (!dimensions.width || !dimensions.height) {
|
|
222
|
-
throw new Error(
|
|
223
|
-
`Failed to read image dimensions from: ${resolvedImagePath}`
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
original_width = dimensions.width;
|
|
227
|
-
original_height = dimensions.height;
|
|
228
|
-
const fileName = imagePath.split("/").pop() || imagePath;
|
|
229
|
-
const result = [
|
|
230
|
-
{
|
|
231
|
-
id: taskId,
|
|
232
|
-
annotations: [
|
|
233
|
-
{
|
|
234
|
-
id: taskId,
|
|
235
|
-
completed_by: 1,
|
|
236
|
-
result: data.map((item) => {
|
|
237
|
-
let { points } = item;
|
|
238
|
-
points = transformPoints(points, {
|
|
239
|
-
normalizeShape: normalizeShape2,
|
|
240
|
-
widthIncrement,
|
|
241
|
-
heightIncrement
|
|
242
|
-
});
|
|
243
|
-
const annotationId = (0, import_node_crypto.randomUUID)().slice(0, 10);
|
|
244
|
-
const polygonPoints = points.map(([x, y]) => [
|
|
245
|
-
roundToPrecision((x ?? 0) / original_width * 100, precision),
|
|
246
|
-
roundToPrecision((y ?? 0) / original_height * 100, precision)
|
|
385
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const task of data) {
|
|
387
|
+
let imagePath = task.file_upload || "";
|
|
388
|
+
if (task.data.ocr) {
|
|
389
|
+
const urlPath = task.data.ocr.replace(/^https?:\/\/[^/]+\//, "");
|
|
390
|
+
imagePath = decodeURIComponent(urlPath);
|
|
391
|
+
}
|
|
392
|
+
if (baseImageDir) {
|
|
393
|
+
imagePath = `${baseImageDir}/${task.file_upload || imagePath.split("/").pop() || imagePath}`;
|
|
394
|
+
}
|
|
395
|
+
const imageAnnotations = [];
|
|
396
|
+
for (const annotation of task.annotations) {
|
|
397
|
+
const groupedById = /* @__PURE__ */ new Map();
|
|
398
|
+
for (const resultItem of annotation.result) {
|
|
399
|
+
const { id } = resultItem;
|
|
400
|
+
if (!groupedById.has(id)) {
|
|
401
|
+
groupedById.set(id, []);
|
|
402
|
+
}
|
|
403
|
+
groupedById.get(id).push(resultItem);
|
|
404
|
+
}
|
|
405
|
+
for (const [_, resultItems] of groupedById) {
|
|
406
|
+
let points;
|
|
407
|
+
let transcription = "";
|
|
408
|
+
for (const resultItem of resultItems) {
|
|
409
|
+
if ("points" in resultItem.value && resultItem.value.points) {
|
|
410
|
+
const { points: valuePoints } = resultItem.value;
|
|
411
|
+
const { original_width, original_height } = resultItem;
|
|
412
|
+
points = valuePoints.map(([x, y]) => [
|
|
413
|
+
(x ?? 0) * original_width / 100,
|
|
414
|
+
(y ?? 0) * original_height / 100
|
|
247
415
|
]);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
to_name: "image",
|
|
261
|
-
type: "polygon",
|
|
262
|
-
origin: "manual"
|
|
263
|
-
},
|
|
264
|
-
// 2. Labels with polygon geometry
|
|
265
|
-
{
|
|
266
|
-
original_width,
|
|
267
|
-
original_height,
|
|
268
|
-
image_rotation: 0,
|
|
269
|
-
value: {
|
|
270
|
-
points: polygonPoints,
|
|
271
|
-
closed: true,
|
|
272
|
-
labels: [labelName]
|
|
273
|
-
},
|
|
274
|
-
id: annotationId,
|
|
275
|
-
from_name: "label",
|
|
276
|
-
to_name: "image",
|
|
277
|
-
type: "labels",
|
|
278
|
-
origin: "manual"
|
|
279
|
-
},
|
|
280
|
-
// 3. Textarea with polygon geometry and text
|
|
281
|
-
{
|
|
282
|
-
original_width,
|
|
283
|
-
original_height,
|
|
284
|
-
image_rotation: 0,
|
|
285
|
-
value: {
|
|
286
|
-
points: polygonPoints,
|
|
287
|
-
closed: true,
|
|
288
|
-
text: [item.transcription]
|
|
289
|
-
},
|
|
290
|
-
id: annotationId,
|
|
291
|
-
from_name: "transcription",
|
|
292
|
-
to_name: "image",
|
|
293
|
-
type: "textarea",
|
|
294
|
-
origin: "manual"
|
|
295
|
-
}
|
|
416
|
+
} else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
|
|
417
|
+
const { x, y, width, height } = resultItem.value;
|
|
418
|
+
const { original_width, original_height } = resultItem;
|
|
419
|
+
const absX = x * original_width / 100;
|
|
420
|
+
const absY = y * original_height / 100;
|
|
421
|
+
const absWidth = width * original_width / 100;
|
|
422
|
+
const absHeight = height * original_height / 100;
|
|
423
|
+
points = [
|
|
424
|
+
[absX, absY],
|
|
425
|
+
[absX + absWidth, absY],
|
|
426
|
+
[absX + absWidth, absY + absHeight],
|
|
427
|
+
[absX, absY + absHeight]
|
|
296
428
|
];
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
updated_at: now,
|
|
302
|
-
draft_created_at: now,
|
|
303
|
-
lead_time: 0,
|
|
304
|
-
prediction: {},
|
|
305
|
-
result_count: data.length * 3,
|
|
306
|
-
unique_id: (0, import_node_crypto.randomUUID)(),
|
|
307
|
-
import_id: null,
|
|
308
|
-
last_action: null,
|
|
309
|
-
bulk_created: false,
|
|
310
|
-
task: taskId,
|
|
311
|
-
project: 1,
|
|
312
|
-
updated_by: 1,
|
|
313
|
-
parent_prediction: null,
|
|
314
|
-
parent_annotation: null,
|
|
315
|
-
last_created_by: null
|
|
429
|
+
}
|
|
430
|
+
if ("text" in resultItem.value && Array.isArray(resultItem.value.text)) {
|
|
431
|
+
transcription = resultItem.value.text[0] || "";
|
|
432
|
+
}
|
|
316
433
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
434
|
+
if (points && points.length > 0) {
|
|
435
|
+
points = transformPoints(points, {
|
|
436
|
+
normalizeShape: normalizeShape2,
|
|
437
|
+
widthIncrement,
|
|
438
|
+
heightIncrement
|
|
439
|
+
});
|
|
440
|
+
points = roundPoints(points, precision);
|
|
441
|
+
let dt_score = 1;
|
|
442
|
+
try {
|
|
443
|
+
const firstPoint = points[0];
|
|
444
|
+
if (firstPoint) {
|
|
445
|
+
const polygon2 = turf.polygon([points.concat([firstPoint])]);
|
|
446
|
+
const area2 = turf.area(polygon2);
|
|
447
|
+
dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
dt_score = 0.8;
|
|
451
|
+
}
|
|
452
|
+
imageAnnotations.push({
|
|
453
|
+
transcription,
|
|
454
|
+
points,
|
|
455
|
+
dt_score
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (imageAnnotations.length > 0) {
|
|
461
|
+
resultMap.set(imagePath, imageAnnotations);
|
|
336
462
|
}
|
|
337
|
-
];
|
|
338
|
-
return result;
|
|
339
|
-
};
|
|
340
|
-
ppocrToMinLabelStudio = (data, imagePath, baseServerUrl, inputDir, labelName = "text", normalizeShape2, widthIncrement = 0, heightIncrement = 0, precision = DEFAULT_LABEL_STUDIO_PRECISION) => {
|
|
341
|
-
const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
|
|
342
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
343
|
-
let original_width = 1920;
|
|
344
|
-
let original_height = 1080;
|
|
345
|
-
const resolvedImagePath = inputDir ? (0, import_node_path.join)(inputDir, imagePath) : imagePath;
|
|
346
|
-
if (!(0, import_node_fs.existsSync)(resolvedImagePath)) {
|
|
347
|
-
throw new Error(`Image file not found: ${resolvedImagePath}`);
|
|
348
463
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
464
|
+
return resultMap;
|
|
465
|
+
};
|
|
466
|
+
minLabelStudioToPPOCR = async (data, options) => {
|
|
467
|
+
const {
|
|
468
|
+
baseImageDir,
|
|
469
|
+
normalizeShape: normalizeShape2,
|
|
470
|
+
widthIncrement = 0,
|
|
471
|
+
heightIncrement = 0,
|
|
472
|
+
precision = 0
|
|
473
|
+
} = options || {};
|
|
474
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
475
|
+
for (const item of data) {
|
|
476
|
+
let imagePath = item.ocr || "";
|
|
477
|
+
if (imagePath) {
|
|
478
|
+
imagePath = decodeURIComponent(
|
|
479
|
+
imagePath.replace(/^https?:\/\/[^/]+\//, "")
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
if (baseImageDir) {
|
|
483
|
+
imagePath = `${baseImageDir}/${imagePath.split("/").pop() || imagePath}`;
|
|
484
|
+
}
|
|
485
|
+
const numAnnotations = Math.max(
|
|
486
|
+
item.poly?.length || 0,
|
|
487
|
+
item.bbox?.length || 0,
|
|
488
|
+
item.transcription?.length || 0
|
|
354
489
|
);
|
|
490
|
+
for (let i = 0; i < numAnnotations; i++) {
|
|
491
|
+
let points;
|
|
492
|
+
if (item.poly && item.poly.length > i && item.poly[i]) {
|
|
493
|
+
const poly = item.poly[i];
|
|
494
|
+
if (poly) {
|
|
495
|
+
const { points: polyPoints } = poly;
|
|
496
|
+
points = polyPoints;
|
|
497
|
+
}
|
|
498
|
+
} else if (item.bbox && item.bbox.length > i && item.bbox[i]) {
|
|
499
|
+
const bbox = item.bbox[i];
|
|
500
|
+
if (bbox) {
|
|
501
|
+
const { x, y, width, height } = bbox;
|
|
502
|
+
points = [
|
|
503
|
+
[x, y],
|
|
504
|
+
[x + width, y],
|
|
505
|
+
[x + width, y + height],
|
|
506
|
+
[x, y + height]
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!points) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
points = transformPoints(points, {
|
|
514
|
+
normalizeShape: normalizeShape2,
|
|
515
|
+
widthIncrement,
|
|
516
|
+
heightIncrement
|
|
517
|
+
});
|
|
518
|
+
points = roundPoints(points, precision);
|
|
519
|
+
const transcription = item.transcription && item.transcription.length > i ? item.transcription[i] : "";
|
|
520
|
+
let dt_score = 1;
|
|
521
|
+
try {
|
|
522
|
+
const firstPoint = points[0];
|
|
523
|
+
if (firstPoint) {
|
|
524
|
+
const polygon2 = turf.polygon([points.concat([firstPoint])]);
|
|
525
|
+
const area2 = turf.area(polygon2);
|
|
526
|
+
dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
|
|
527
|
+
}
|
|
528
|
+
} catch {
|
|
529
|
+
dt_score = 0.8;
|
|
530
|
+
}
|
|
531
|
+
const annotation = {
|
|
532
|
+
transcription: transcription ?? "",
|
|
533
|
+
points,
|
|
534
|
+
dt_score
|
|
535
|
+
};
|
|
536
|
+
if (!resultMap.has(imagePath)) {
|
|
537
|
+
resultMap.set(imagePath, []);
|
|
538
|
+
}
|
|
539
|
+
resultMap.get(imagePath).push(annotation);
|
|
540
|
+
}
|
|
355
541
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
542
|
+
return resultMap;
|
|
543
|
+
};
|
|
544
|
+
processAnnotationOrPrediction = (annotation, options) => {
|
|
545
|
+
const {
|
|
546
|
+
sortVertical,
|
|
547
|
+
sortHorizontal,
|
|
548
|
+
normalizeShape: normalizeShape2,
|
|
549
|
+
widthIncrement,
|
|
550
|
+
heightIncrement,
|
|
551
|
+
precision
|
|
552
|
+
} = options;
|
|
553
|
+
const groupedById = /* @__PURE__ */ new Map();
|
|
554
|
+
for (const resultItem of annotation.result) {
|
|
555
|
+
const { id } = resultItem;
|
|
556
|
+
if (!groupedById.has(id)) {
|
|
557
|
+
groupedById.set(id, []);
|
|
558
|
+
}
|
|
559
|
+
groupedById.get(id).push(resultItem);
|
|
560
|
+
}
|
|
561
|
+
const enhancedResult = [];
|
|
562
|
+
for (const [, resultItems] of groupedById) {
|
|
563
|
+
let ppocrAnnotations = [];
|
|
564
|
+
for (const resultItem of resultItems) {
|
|
565
|
+
let points;
|
|
566
|
+
if ("points" in resultItem.value && resultItem.value.points) {
|
|
567
|
+
const { points: valuePoints } = resultItem.value;
|
|
568
|
+
const { original_width, original_height } = resultItem;
|
|
569
|
+
points = valuePoints.map(([x, y]) => [
|
|
570
|
+
(x ?? 0) * original_width / 100,
|
|
571
|
+
(y ?? 0) * original_height / 100
|
|
572
|
+
]);
|
|
573
|
+
} else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
|
|
574
|
+
const { x, y, width, height } = resultItem.value;
|
|
575
|
+
const { original_width, original_height } = resultItem;
|
|
576
|
+
const absX = x * original_width / 100;
|
|
577
|
+
const absY = y * original_height / 100;
|
|
578
|
+
const absWidth = width * original_width / 100;
|
|
579
|
+
const absHeight = height * original_height / 100;
|
|
580
|
+
points = [
|
|
581
|
+
[absX, absY],
|
|
582
|
+
[absX + absWidth, absY],
|
|
583
|
+
[absX + absWidth, absY + absHeight],
|
|
584
|
+
[absX, absY + absHeight]
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
if (points) {
|
|
588
|
+
ppocrAnnotations.push({
|
|
589
|
+
transcription: "",
|
|
590
|
+
points,
|
|
591
|
+
dt_score: 1
|
|
592
|
+
});
|
|
382
593
|
}
|
|
383
594
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
595
|
+
if (ppocrAnnotations.length > 0) {
|
|
596
|
+
ppocrAnnotations = enhancePPOCRLabel(ppocrAnnotations, {
|
|
597
|
+
sortVertical,
|
|
598
|
+
sortHorizontal,
|
|
599
|
+
normalizeShape: normalizeShape2,
|
|
600
|
+
widthIncrement,
|
|
601
|
+
heightIncrement,
|
|
602
|
+
precision
|
|
603
|
+
});
|
|
604
|
+
for (let i = 0; i < resultItems.length; i++) {
|
|
605
|
+
const resultItem = resultItems[i];
|
|
606
|
+
const enhanced = ppocrAnnotations[i];
|
|
607
|
+
if (!enhanced) {
|
|
608
|
+
enhancedResult.push(resultItem);
|
|
609
|
+
continue;
|
|
398
610
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
611
|
+
if ("points" in resultItem.value && resultItem.value.points) {
|
|
612
|
+
const { original_width, original_height } = resultItem;
|
|
613
|
+
enhancedResult.push({
|
|
614
|
+
...resultItem,
|
|
615
|
+
value: {
|
|
616
|
+
...resultItem.value,
|
|
617
|
+
points: enhanced.points.map(
|
|
618
|
+
([x, y]) => [
|
|
619
|
+
(x ?? 0) / original_width * 100,
|
|
620
|
+
(y ?? 0) / original_height * 100
|
|
621
|
+
]
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
} else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
|
|
626
|
+
const { original_width, original_height } = resultItem;
|
|
627
|
+
const xs = enhanced.points.map(([x]) => x ?? 0);
|
|
628
|
+
const ys = enhanced.points.map(([, y]) => y ?? 0);
|
|
629
|
+
const minX = Math.min(...xs);
|
|
630
|
+
const maxX = Math.max(...xs);
|
|
631
|
+
const minY = Math.min(...ys);
|
|
632
|
+
const maxY = Math.max(...ys);
|
|
633
|
+
enhancedResult.push({
|
|
634
|
+
...resultItem,
|
|
635
|
+
value: {
|
|
636
|
+
...resultItem.value,
|
|
637
|
+
x: minX / original_width * 100,
|
|
638
|
+
y: minY / original_height * 100,
|
|
639
|
+
width: (maxX - minX) / original_width * 100,
|
|
640
|
+
height: (maxY - minY) / original_height * 100
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
} else {
|
|
644
|
+
enhancedResult.push(resultItem);
|
|
407
645
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
enhancedResult.push(...resultItems);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
...annotation,
|
|
653
|
+
result: enhancedResult
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
enhanceLabelStudioData = async (data, isFull, options) => {
|
|
657
|
+
const {
|
|
658
|
+
sortVertical,
|
|
659
|
+
sortHorizontal,
|
|
660
|
+
normalizeShape: normalizeShape2,
|
|
661
|
+
widthIncrement = 0,
|
|
662
|
+
heightIncrement = 0,
|
|
663
|
+
precision = 0,
|
|
664
|
+
outputMode
|
|
665
|
+
} = options;
|
|
666
|
+
if (isFull) {
|
|
667
|
+
const fullData = data;
|
|
668
|
+
const isPredictions = outputMode === OUTPUT_MODE_PREDICTIONS;
|
|
669
|
+
return fullData.map((task) => {
|
|
670
|
+
const processOptions = {
|
|
671
|
+
sortVertical,
|
|
672
|
+
sortHorizontal,
|
|
673
|
+
normalizeShape: normalizeShape2,
|
|
674
|
+
widthIncrement,
|
|
675
|
+
heightIncrement,
|
|
676
|
+
precision
|
|
677
|
+
};
|
|
678
|
+
const allItems = [
|
|
679
|
+
...task.annotations.map((a) => ({
|
|
680
|
+
item: a,
|
|
681
|
+
source: "annotations"
|
|
682
|
+
})),
|
|
683
|
+
...(task.predictions || []).map((p) => ({
|
|
684
|
+
item: p,
|
|
685
|
+
source: "predictions"
|
|
686
|
+
}))
|
|
687
|
+
];
|
|
688
|
+
const processedItems = allItems.map(
|
|
689
|
+
({ item }) => (
|
|
690
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
691
|
+
processAnnotationOrPrediction(item, processOptions)
|
|
692
|
+
)
|
|
693
|
+
);
|
|
694
|
+
if (isPredictions) {
|
|
695
|
+
return {
|
|
696
|
+
...task,
|
|
697
|
+
annotations: [],
|
|
698
|
+
predictions: processedItems,
|
|
699
|
+
total_annotations: 0,
|
|
700
|
+
total_predictions: processedItems.length
|
|
701
|
+
};
|
|
702
|
+
} else {
|
|
703
|
+
return {
|
|
704
|
+
...task,
|
|
705
|
+
annotations: processedItems,
|
|
706
|
+
predictions: [],
|
|
707
|
+
total_annotations: processedItems.length,
|
|
708
|
+
total_predictions: 0
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
} else {
|
|
713
|
+
const minData = data;
|
|
714
|
+
return minData.map((item) => {
|
|
715
|
+
let ppocrAnnotations = [];
|
|
716
|
+
const numAnnotations = Math.max(
|
|
717
|
+
item.poly?.length || 0,
|
|
718
|
+
item.bbox?.length || 0,
|
|
719
|
+
item.transcription?.length || 0
|
|
720
|
+
);
|
|
721
|
+
for (let i = 0; i < numAnnotations; i++) {
|
|
722
|
+
let points;
|
|
723
|
+
if (item.poly && item.poly.length > i && item.poly[i]) {
|
|
724
|
+
const { points: polyPoints } = item.poly[i];
|
|
725
|
+
points = polyPoints;
|
|
726
|
+
} else if (item.bbox && item.bbox.length > i && item.bbox[i]) {
|
|
727
|
+
const { x, y, width, height } = item.bbox[i];
|
|
728
|
+
points = [
|
|
729
|
+
[x, y],
|
|
730
|
+
[x + width, y],
|
|
731
|
+
[x + width, y + height],
|
|
732
|
+
[x, y + height]
|
|
733
|
+
];
|
|
416
734
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
735
|
+
if (points) {
|
|
736
|
+
ppocrAnnotations.push({
|
|
737
|
+
transcription: item.transcription && item.transcription.length > i ? item.transcription[i] ?? "" : "",
|
|
738
|
+
points,
|
|
739
|
+
dt_score: 1
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (ppocrAnnotations.length > 0) {
|
|
744
|
+
ppocrAnnotations = enhancePPOCRLabel(ppocrAnnotations, {
|
|
745
|
+
sortVertical,
|
|
746
|
+
sortHorizontal,
|
|
747
|
+
normalizeShape: normalizeShape2,
|
|
748
|
+
widthIncrement,
|
|
749
|
+
heightIncrement,
|
|
750
|
+
precision
|
|
751
|
+
});
|
|
752
|
+
const newPoly = ppocrAnnotations.map((ann) => ({
|
|
753
|
+
points: ann.points
|
|
754
|
+
}));
|
|
755
|
+
const { bbox: _, ...itemWithoutBbox } = item;
|
|
756
|
+
return {
|
|
757
|
+
...itemWithoutBbox,
|
|
758
|
+
poly: newPoly
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
return item;
|
|
762
|
+
});
|
|
763
|
+
}
|
|
425
764
|
};
|
|
426
765
|
}
|
|
427
766
|
});
|
|
@@ -763,507 +1102,860 @@ var init_schema = __esm({
|
|
|
763
1102
|
import_zod.default.object({
|
|
764
1103
|
transcription: import_zod.default.string(),
|
|
765
1104
|
points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
|
|
766
|
-
dt_score: import_zod.default.number()
|
|
1105
|
+
dt_score: import_zod.default.number().optional(),
|
|
1106
|
+
// Detection score (from PaddleOCR)
|
|
1107
|
+
difficult: import_zod.default.boolean().optional()
|
|
1108
|
+
// Difficult flag (from PPOCRLabel tool)
|
|
767
1109
|
})
|
|
768
1110
|
);
|
|
769
1111
|
}
|
|
770
1112
|
});
|
|
771
1113
|
|
|
772
|
-
// src/
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
};
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
1114
|
+
// src/commands/enhance-labelstudio/impl.ts
|
|
1115
|
+
var impl_exports = {};
|
|
1116
|
+
__export(impl_exports, {
|
|
1117
|
+
enhanceLabelStudio: () => enhanceLabelStudio
|
|
1118
|
+
});
|
|
1119
|
+
async function enhanceLabelStudio(flags, ...inputDirs) {
|
|
1120
|
+
const {
|
|
1121
|
+
outDir,
|
|
1122
|
+
fileName,
|
|
1123
|
+
backup = false,
|
|
1124
|
+
sortVertical = DEFAULT_SORT_VERTICAL,
|
|
1125
|
+
sortHorizontal = DEFAULT_SORT_HORIZONTAL,
|
|
1126
|
+
normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
|
|
1127
|
+
widthIncrement = DEFAULT_WIDTH_INCREMENT,
|
|
1128
|
+
heightIncrement = DEFAULT_HEIGHT_INCREMENT,
|
|
1129
|
+
precision = DEFAULT_LABEL_STUDIO_PRECISION,
|
|
1130
|
+
recursive = DEFAULT_RECURSIVE,
|
|
1131
|
+
filePattern = DEFAULT_LABEL_STUDIO_FILE_PATTERN,
|
|
1132
|
+
outputMode = DEFAULT_OUTPUT_MODE
|
|
1133
|
+
} = flags;
|
|
1134
|
+
console.log(import_chalk.default.blue("Finding files..."));
|
|
1135
|
+
const filePaths = await findFiles(inputDirs, filePattern, recursive);
|
|
1136
|
+
if (filePaths.length === 0) {
|
|
1137
|
+
console.log(import_chalk.default.yellow("No files found matching the pattern."));
|
|
1138
|
+
return;
|
|
796
1139
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const avgX = column.reduce((sum, c) => sum + c.center.x, 0) / column.length;
|
|
815
|
-
if (Math.abs(item.center.x - avgX) < GROUPING_TOLERANCE) {
|
|
816
|
-
column.push(item);
|
|
817
|
-
addedToColumn = true;
|
|
818
|
-
break;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
if (!addedToColumn) {
|
|
822
|
-
columns.push([item]);
|
|
1140
|
+
console.log(import_chalk.default.blue(`Found ${filePaths.length} files to process
|
|
1141
|
+
`));
|
|
1142
|
+
for (const filePath of filePaths) {
|
|
1143
|
+
const file = (0, import_path2.basename)(filePath);
|
|
1144
|
+
console.log(import_chalk.default.gray(`Processing file: ${filePath}`));
|
|
1145
|
+
try {
|
|
1146
|
+
const fileData = await (0, import_promises2.readFile)(filePath, "utf-8");
|
|
1147
|
+
const labelStudioData = JSON.parse(fileData);
|
|
1148
|
+
const { data, isFull } = isLabelStudioFullJSON(labelStudioData);
|
|
1149
|
+
if (outputMode !== DEFAULT_OUTPUT_MODE && !isFull) {
|
|
1150
|
+
console.log(
|
|
1151
|
+
import_chalk.default.red(
|
|
1152
|
+
` Skipping file: ${filePath}
|
|
1153
|
+
Error: --outputMode can only be used with Full JSON format. This file is in Min JSON format which does not support annotations/predictions distinction.`
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
continue;
|
|
823
1157
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return verticalSort === SORT_VERTICAL_TOP_BOTTOM ? a.center.y - b.center.y : b.center.y - a.center.y;
|
|
1158
|
+
const enhanced = await enhanceLabelStudioData(data, isFull, {
|
|
1159
|
+
sortVertical,
|
|
1160
|
+
sortHorizontal,
|
|
1161
|
+
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1162
|
+
widthIncrement,
|
|
1163
|
+
heightIncrement,
|
|
1164
|
+
precision,
|
|
1165
|
+
outputMode
|
|
833
1166
|
});
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
if (
|
|
843
|
-
|
|
1167
|
+
const outputSubDir = outDir ? (() => {
|
|
1168
|
+
const relativePath = getRelativePathFromInputs(filePath, inputDirs);
|
|
1169
|
+
const relativeDir = (0, import_path2.dirname)(relativePath);
|
|
1170
|
+
return (0, import_path2.join)(outDir, relativeDir);
|
|
1171
|
+
})() : (0, import_path2.dirname)(filePath);
|
|
1172
|
+
await (0, import_promises2.mkdir)(outputSubDir, { recursive: true });
|
|
1173
|
+
const outputFileName = fileName || file;
|
|
1174
|
+
const outputFilePath = (0, import_path2.join)(outputSubDir, outputFileName);
|
|
1175
|
+
if (backup) {
|
|
1176
|
+
const backupPath = await backupFileIfExists(outputFilePath);
|
|
1177
|
+
if (backupPath) {
|
|
1178
|
+
console.log(import_chalk.default.gray(` Backed up to: ${backupPath}`));
|
|
1179
|
+
}
|
|
844
1180
|
}
|
|
1181
|
+
await (0, import_promises2.writeFile)(
|
|
1182
|
+
outputFilePath,
|
|
1183
|
+
JSON.stringify(enhanced, null, 2),
|
|
1184
|
+
"utf-8"
|
|
1185
|
+
);
|
|
1186
|
+
console.log(import_chalk.default.green(`\u2713 Enhanced file saved: ${outputFilePath}`));
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
console.error(
|
|
1189
|
+
import_chalk.default.red(`Error processing file ${file}:`),
|
|
1190
|
+
error instanceof Error ? error.message : String(error)
|
|
1191
|
+
);
|
|
845
1192
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
return 0;
|
|
850
|
-
});
|
|
851
|
-
return sorted;
|
|
1193
|
+
}
|
|
1194
|
+
console.log(import_chalk.default.green("\n\u2713 Enhancement complete!"));
|
|
852
1195
|
}
|
|
853
|
-
var
|
|
854
|
-
var
|
|
855
|
-
"src/
|
|
1196
|
+
var import_promises2, import_path2, import_chalk, isLabelStudioFullJSON;
|
|
1197
|
+
var init_impl = __esm({
|
|
1198
|
+
"src/commands/enhance-labelstudio/impl.ts"() {
|
|
856
1199
|
"use strict";
|
|
857
1200
|
init_cjs_shims();
|
|
1201
|
+
import_promises2 = require("fs/promises");
|
|
1202
|
+
import_path2 = require("path");
|
|
1203
|
+
import_chalk = __toESM(require("chalk"), 1);
|
|
858
1204
|
init_constants();
|
|
859
|
-
|
|
1205
|
+
init_backup_utils();
|
|
1206
|
+
init_file_utils();
|
|
1207
|
+
init_label_studio();
|
|
1208
|
+
init_schema();
|
|
1209
|
+
isLabelStudioFullJSON = (data) => {
|
|
1210
|
+
const parsedFull = FullOCRLabelStudioSchema.safeParse(data);
|
|
1211
|
+
if (parsedFull.success) {
|
|
1212
|
+
return { isFull: true, data: parsedFull.data };
|
|
1213
|
+
}
|
|
1214
|
+
if (!Array.isArray(data) && typeof data === "object" && data !== null) {
|
|
1215
|
+
const parsedSingleFull = FullOCRLabelStudioSchema.safeParse([data]);
|
|
1216
|
+
if (parsedSingleFull.success) {
|
|
1217
|
+
return { isFull: true, data: parsedSingleFull.data };
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const parsedMin = MinOCRLabelStudioSchema.safeParse(data);
|
|
1221
|
+
if (parsedMin.success) {
|
|
1222
|
+
return { isFull: false, data: parsedMin.data };
|
|
1223
|
+
}
|
|
1224
|
+
throw new Error("Input data is not valid Label Studio JSON format.");
|
|
1225
|
+
};
|
|
860
1226
|
}
|
|
861
1227
|
});
|
|
862
1228
|
|
|
863
|
-
// src/commands/
|
|
864
|
-
var
|
|
865
|
-
__export(
|
|
866
|
-
|
|
1229
|
+
// src/commands/enhance-ppocr/impl.ts
|
|
1230
|
+
var impl_exports2 = {};
|
|
1231
|
+
__export(impl_exports2, {
|
|
1232
|
+
enhancePPOCR: () => enhancePPOCR
|
|
867
1233
|
});
|
|
868
|
-
async function
|
|
1234
|
+
async function enhancePPOCR(flags, ...inputDirs) {
|
|
869
1235
|
const {
|
|
870
|
-
outDir
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
createFilePerImage = DEFAULT_CREATE_FILE_PER_IMAGE,
|
|
874
|
-
createFileListForServing = DEFAULT_CREATE_FILE_LIST_FOR_SERVING,
|
|
875
|
-
fileListName = DEFAULT_FILE_LIST_NAME,
|
|
876
|
-
baseServerUrl = DEFAULT_BASE_SERVER_URL,
|
|
1236
|
+
outDir,
|
|
1237
|
+
fileName,
|
|
1238
|
+
backup = false,
|
|
877
1239
|
sortVertical = DEFAULT_SORT_VERTICAL,
|
|
878
1240
|
sortHorizontal = DEFAULT_SORT_HORIZONTAL,
|
|
879
1241
|
normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
|
|
880
1242
|
widthIncrement = DEFAULT_WIDTH_INCREMENT,
|
|
881
1243
|
heightIncrement = DEFAULT_HEIGHT_INCREMENT,
|
|
882
|
-
precision =
|
|
1244
|
+
precision = DEFAULT_PPOCR_PRECISION,
|
|
1245
|
+
recursive = DEFAULT_RECURSIVE,
|
|
1246
|
+
filePattern = DEFAULT_PPOCR_FILE_PATTERN
|
|
883
1247
|
} = flags;
|
|
884
|
-
|
|
885
|
-
await (
|
|
886
|
-
|
|
887
|
-
console.log(
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
}
|
|
904
|
-
const [imagePath, annotationsStr] = parts;
|
|
905
|
-
const annotations = JSON.parse(annotationsStr);
|
|
906
|
-
PPOCRLabelSchema.parse(annotations);
|
|
907
|
-
imageDataMap.set(imagePath, annotations);
|
|
908
|
-
}
|
|
909
|
-
const allLabelStudioData = [];
|
|
910
|
-
const fileList = [];
|
|
911
|
-
let taskId = 1;
|
|
912
|
-
for (const [imagePath, ppocrData] of imageDataMap.entries()) {
|
|
913
|
-
const sortedPpocrData = sortBoundingBoxes(
|
|
914
|
-
ppocrData,
|
|
915
|
-
sortVertical,
|
|
916
|
-
sortHorizontal
|
|
917
|
-
);
|
|
918
|
-
const finalImagePath = createFileListForServing ? encodeURI(`${newBaseServerUrl}${imagePath}`) : imagePath;
|
|
919
|
-
const labelStudioData = await ppocrToLabelStudio(sortedPpocrData, {
|
|
920
|
-
toFullJson,
|
|
921
|
-
imagePath,
|
|
922
|
-
baseServerUrl: newBaseServerUrl,
|
|
923
|
-
inputDir,
|
|
924
|
-
taskId,
|
|
925
|
-
labelName: defaultLabelName,
|
|
926
|
-
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
927
|
-
widthIncrement,
|
|
928
|
-
heightIncrement,
|
|
929
|
-
precision
|
|
930
|
-
});
|
|
931
|
-
if (toFullJson) {
|
|
932
|
-
allLabelStudioData.push(labelStudioData[0]);
|
|
933
|
-
} else {
|
|
934
|
-
allLabelStudioData.push(...labelStudioData);
|
|
935
|
-
}
|
|
936
|
-
if (createFilePerImage) {
|
|
937
|
-
const imageBaseName = imagePath.replace(/\//g, "_").replace(/\.[^.]+$/, "");
|
|
938
|
-
const individualOutputPath = (0, import_path.join)(
|
|
939
|
-
outDir,
|
|
940
|
-
`${imageBaseName}_${toFullJson ? "full" : "min"}.json`
|
|
941
|
-
);
|
|
942
|
-
await (0, import_promises.writeFile)(
|
|
943
|
-
individualOutputPath,
|
|
944
|
-
JSON.stringify(
|
|
945
|
-
toFullJson ? labelStudioData[0] : labelStudioData,
|
|
946
|
-
null,
|
|
947
|
-
2
|
|
948
|
-
),
|
|
949
|
-
"utf-8"
|
|
950
|
-
);
|
|
951
|
-
console.log(
|
|
952
|
-
import_chalk.default.gray(
|
|
953
|
-
` \u2713 Created individual file: ${individualOutputPath}`
|
|
954
|
-
)
|
|
955
|
-
);
|
|
956
|
-
}
|
|
957
|
-
if (createFileListForServing) {
|
|
958
|
-
fileList.push(finalImagePath);
|
|
959
|
-
}
|
|
960
|
-
taskId++;
|
|
1248
|
+
console.log(import_chalk2.default.blue("Finding files..."));
|
|
1249
|
+
const filePaths = await findFiles(inputDirs, filePattern, recursive);
|
|
1250
|
+
if (filePaths.length === 0) {
|
|
1251
|
+
console.log(import_chalk2.default.yellow("No files found matching the pattern."));
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
console.log(import_chalk2.default.blue(`Found ${filePaths.length} files to process
|
|
1255
|
+
`));
|
|
1256
|
+
for (const filePath of filePaths) {
|
|
1257
|
+
const file = (0, import_path3.basename)(filePath);
|
|
1258
|
+
console.log(import_chalk2.default.gray(`Processing file: ${filePath}`));
|
|
1259
|
+
try {
|
|
1260
|
+
const fileData = await (0, import_promises3.readFile)(filePath, "utf-8");
|
|
1261
|
+
const lines = fileData.trim().split("\n");
|
|
1262
|
+
const enhancedLines = [];
|
|
1263
|
+
for (const line of lines) {
|
|
1264
|
+
const parts = line.split(" ");
|
|
1265
|
+
if (parts.length !== 2) {
|
|
1266
|
+
throw new Error(`Invalid PPOCRLabelV2 format in line: ${line}`);
|
|
961
1267
|
}
|
|
962
|
-
const
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1268
|
+
const [imagePath, annotationsStr] = parts;
|
|
1269
|
+
const annotations = JSON.parse(annotationsStr);
|
|
1270
|
+
PPOCRLabelSchema.parse(annotations);
|
|
1271
|
+
const enhanced = enhancePPOCRLabel(annotations, {
|
|
1272
|
+
sortVertical,
|
|
1273
|
+
sortHorizontal,
|
|
1274
|
+
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1275
|
+
widthIncrement,
|
|
1276
|
+
heightIncrement,
|
|
1277
|
+
precision
|
|
1278
|
+
});
|
|
1279
|
+
PPOCRLabelSchema.parse(enhanced);
|
|
1280
|
+
const jsonArray = JSON.stringify(enhanced);
|
|
1281
|
+
enhancedLines.push(`${imagePath} ${jsonArray}`);
|
|
1282
|
+
}
|
|
1283
|
+
const outputSubDir = outDir ? (() => {
|
|
1284
|
+
const relativePath = getRelativePathFromInputs(filePath, inputDirs);
|
|
1285
|
+
const relativeDir = (0, import_path3.dirname)(relativePath);
|
|
1286
|
+
return (0, import_path3.join)(outDir, relativeDir);
|
|
1287
|
+
})() : (0, import_path3.dirname)(filePath);
|
|
1288
|
+
await (0, import_promises3.mkdir)(outputSubDir, { recursive: true });
|
|
1289
|
+
const outputFileName = fileName || file;
|
|
1290
|
+
const outputFilePath = (0, import_path3.join)(outputSubDir, outputFileName);
|
|
1291
|
+
if (backup) {
|
|
1292
|
+
const backupPath = await backupFileIfExists(outputFilePath);
|
|
1293
|
+
if (backupPath) {
|
|
1294
|
+
console.log(import_chalk2.default.gray(` Backed up to: ${backupPath}`));
|
|
981
1295
|
}
|
|
982
|
-
} catch (error) {
|
|
983
|
-
console.error(
|
|
984
|
-
import_chalk.default.red(`\u2717 Failed to process ${file}:`),
|
|
985
|
-
error instanceof Error ? error.message : error
|
|
986
|
-
);
|
|
987
1296
|
}
|
|
1297
|
+
await (0, import_promises3.writeFile)(outputFilePath, enhancedLines.join("\n"), "utf-8");
|
|
1298
|
+
console.log(import_chalk2.default.green(`\u2713 Enhanced file saved: ${outputFilePath}`));
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
console.error(
|
|
1301
|
+
import_chalk2.default.red(`Error processing file ${file}:`),
|
|
1302
|
+
error instanceof Error ? error.message : String(error)
|
|
1303
|
+
);
|
|
988
1304
|
}
|
|
989
1305
|
}
|
|
990
|
-
console.log(
|
|
1306
|
+
console.log(import_chalk2.default.green("\n\u2713 Enhancement complete!"));
|
|
991
1307
|
}
|
|
992
|
-
var
|
|
993
|
-
var
|
|
994
|
-
"src/commands/
|
|
1308
|
+
var import_promises3, import_path3, import_chalk2;
|
|
1309
|
+
var init_impl2 = __esm({
|
|
1310
|
+
"src/commands/enhance-ppocr/impl.ts"() {
|
|
995
1311
|
"use strict";
|
|
996
1312
|
init_cjs_shims();
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1313
|
+
import_promises3 = require("fs/promises");
|
|
1314
|
+
import_path3 = require("path");
|
|
1315
|
+
import_chalk2 = __toESM(require("chalk"), 1);
|
|
1000
1316
|
init_constants();
|
|
1001
|
-
|
|
1317
|
+
init_backup_utils();
|
|
1318
|
+
init_enhance();
|
|
1319
|
+
init_file_utils();
|
|
1002
1320
|
init_schema();
|
|
1003
|
-
init_sort();
|
|
1004
1321
|
}
|
|
1005
1322
|
});
|
|
1006
1323
|
|
|
1007
|
-
// src/lib/label
|
|
1008
|
-
var
|
|
1009
|
-
var
|
|
1010
|
-
"src/lib/label
|
|
1324
|
+
// src/lib/ppocr-label.ts
|
|
1325
|
+
var import_node_crypto, import_node_fs, import_node_path, import_image_size, ppocrToLabelStudio, ppocrToFullLabelStudio, ppocrToMinLabelStudio;
|
|
1326
|
+
var init_ppocr_label = __esm({
|
|
1327
|
+
"src/lib/ppocr-label.ts"() {
|
|
1011
1328
|
"use strict";
|
|
1012
1329
|
init_cjs_shims();
|
|
1013
|
-
|
|
1330
|
+
import_node_crypto = require("crypto");
|
|
1331
|
+
import_node_fs = require("fs");
|
|
1332
|
+
import_node_path = require("path");
|
|
1333
|
+
import_image_size = __toESM(require("image-size"), 1);
|
|
1334
|
+
init_constants();
|
|
1014
1335
|
init_geometry();
|
|
1015
|
-
|
|
1336
|
+
ppocrToLabelStudio = async (data, options) => {
|
|
1016
1337
|
const {
|
|
1017
|
-
|
|
1338
|
+
imagePath,
|
|
1339
|
+
baseServerUrl,
|
|
1340
|
+
inputDir,
|
|
1341
|
+
relativeDir,
|
|
1342
|
+
toFullJson = true,
|
|
1343
|
+
taskId = 1,
|
|
1344
|
+
labelName = DEFAULT_LABEL_NAME,
|
|
1018
1345
|
normalizeShape: normalizeShape2,
|
|
1019
1346
|
widthIncrement = 0,
|
|
1020
1347
|
heightIncrement = 0,
|
|
1021
|
-
precision =
|
|
1348
|
+
precision = DEFAULT_LABEL_STUDIO_PRECISION,
|
|
1349
|
+
outputMode = DEFAULT_OUTPUT_MODE
|
|
1022
1350
|
} = options || {};
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1351
|
+
if (toFullJson) {
|
|
1352
|
+
return ppocrToFullLabelStudio(
|
|
1353
|
+
data,
|
|
1354
|
+
imagePath,
|
|
1355
|
+
baseServerUrl,
|
|
1356
|
+
inputDir,
|
|
1357
|
+
taskId,
|
|
1358
|
+
labelName,
|
|
1359
|
+
normalizeShape2,
|
|
1360
|
+
widthIncrement,
|
|
1361
|
+
heightIncrement,
|
|
1362
|
+
precision,
|
|
1363
|
+
relativeDir,
|
|
1364
|
+
outputMode
|
|
1365
|
+
);
|
|
1366
|
+
} else {
|
|
1367
|
+
return ppocrToMinLabelStudio(
|
|
1368
|
+
data,
|
|
1369
|
+
imagePath,
|
|
1370
|
+
baseServerUrl,
|
|
1371
|
+
inputDir,
|
|
1372
|
+
labelName,
|
|
1373
|
+
normalizeShape2,
|
|
1374
|
+
widthIncrement,
|
|
1375
|
+
heightIncrement,
|
|
1376
|
+
precision,
|
|
1377
|
+
relativeDir
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
ppocrToFullLabelStudio = (data, imagePath, baseServerUrl, inputDir, taskId = 1, labelName = DEFAULT_LABEL_NAME, normalizeShape2, widthIncrement = 0, heightIncrement = 0, precision = DEFAULT_LABEL_STUDIO_PRECISION, relativeDir, outputMode = DEFAULT_OUTPUT_MODE) => {
|
|
1382
|
+
const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
|
|
1383
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1384
|
+
let original_width = 1920;
|
|
1385
|
+
let original_height = 1080;
|
|
1386
|
+
let resolvedImagePath;
|
|
1387
|
+
if (inputDir) {
|
|
1388
|
+
const lastDirComponent = (0, import_node_path.basename)(inputDir);
|
|
1389
|
+
const imagePathParts = imagePath.split("/");
|
|
1390
|
+
if (imagePathParts[0] === lastDirComponent && imagePathParts.length > 1) {
|
|
1391
|
+
resolvedImagePath = (0, import_node_path.join)(inputDir, imagePathParts.slice(1).join("/"));
|
|
1392
|
+
} else {
|
|
1393
|
+
resolvedImagePath = (0, import_node_path.join)(inputDir, imagePath);
|
|
1029
1394
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1395
|
+
} else {
|
|
1396
|
+
resolvedImagePath = imagePath;
|
|
1397
|
+
}
|
|
1398
|
+
if ((0, import_node_fs.existsSync)(resolvedImagePath)) {
|
|
1399
|
+
try {
|
|
1400
|
+
const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
|
|
1401
|
+
const dimensions = (0, import_image_size.default)(buffer);
|
|
1402
|
+
if (dimensions.width && dimensions.height) {
|
|
1403
|
+
original_width = dimensions.width;
|
|
1404
|
+
original_height = dimensions.height;
|
|
1405
|
+
} else {
|
|
1406
|
+
console.warn(
|
|
1407
|
+
`Warning: Failed to read dimensions from ${resolvedImagePath}, using defaults`
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
console.warn(
|
|
1412
|
+
`Warning: Error reading image ${resolvedImagePath}, using defaults:`,
|
|
1413
|
+
error instanceof Error ? error.message : error
|
|
1414
|
+
);
|
|
1032
1415
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1416
|
+
} else {
|
|
1417
|
+
console.warn(
|
|
1418
|
+
`Warning: Image file not found: ${resolvedImagePath}, using default dimensions`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
const fileName = imagePath.split("/").pop() || imagePath;
|
|
1422
|
+
let normalizedImagePath = imagePath;
|
|
1423
|
+
if (inputDir && relativeDir) {
|
|
1424
|
+
const lastDirComponent = (0, import_node_path.basename)(inputDir);
|
|
1425
|
+
const imagePathParts = imagePath.split("/");
|
|
1426
|
+
if (imagePathParts[0] === lastDirComponent && imagePathParts.length > 1) {
|
|
1427
|
+
normalizedImagePath = imagePathParts.slice(1).join("/");
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
const isPredictions = outputMode === OUTPUT_MODE_PREDICTIONS;
|
|
1431
|
+
const resultItems = data.map((item) => {
|
|
1432
|
+
let { points } = item;
|
|
1433
|
+
points = transformPoints(points, {
|
|
1434
|
+
normalizeShape: normalizeShape2,
|
|
1435
|
+
widthIncrement,
|
|
1436
|
+
heightIncrement
|
|
1437
|
+
});
|
|
1438
|
+
const annotationId = (0, import_node_crypto.randomUUID)().slice(0, 10);
|
|
1439
|
+
const polygonPoints = points.map(([x, y]) => [
|
|
1440
|
+
roundToPrecision((x ?? 0) / original_width * 100, precision),
|
|
1441
|
+
roundToPrecision((y ?? 0) / original_height * 100, precision)
|
|
1442
|
+
]);
|
|
1443
|
+
const scoreField = isPredictions && item.dt_score !== void 0 ? { score: item.dt_score } : {};
|
|
1444
|
+
return [
|
|
1445
|
+
// 1. Polygon geometry only
|
|
1446
|
+
{
|
|
1447
|
+
original_width,
|
|
1448
|
+
original_height,
|
|
1449
|
+
image_rotation: 0,
|
|
1450
|
+
value: {
|
|
1451
|
+
points: polygonPoints,
|
|
1452
|
+
closed: true
|
|
1453
|
+
},
|
|
1454
|
+
id: annotationId,
|
|
1455
|
+
from_name: "poly",
|
|
1456
|
+
to_name: "image",
|
|
1457
|
+
type: "polygon",
|
|
1458
|
+
origin: "manual",
|
|
1459
|
+
...scoreField
|
|
1460
|
+
},
|
|
1461
|
+
// 2. Labels with polygon geometry
|
|
1462
|
+
{
|
|
1463
|
+
original_width,
|
|
1464
|
+
original_height,
|
|
1465
|
+
image_rotation: 0,
|
|
1466
|
+
value: {
|
|
1467
|
+
points: polygonPoints,
|
|
1468
|
+
closed: true,
|
|
1469
|
+
labels: [labelName]
|
|
1470
|
+
},
|
|
1471
|
+
id: annotationId,
|
|
1472
|
+
from_name: "label",
|
|
1473
|
+
to_name: "image",
|
|
1474
|
+
type: "labels",
|
|
1475
|
+
origin: "manual",
|
|
1476
|
+
...scoreField
|
|
1477
|
+
},
|
|
1478
|
+
// 3. Textarea with polygon geometry and text
|
|
1479
|
+
{
|
|
1480
|
+
original_width,
|
|
1481
|
+
original_height,
|
|
1482
|
+
image_rotation: 0,
|
|
1483
|
+
value: {
|
|
1484
|
+
points: polygonPoints,
|
|
1485
|
+
closed: true,
|
|
1486
|
+
text: [item.transcription]
|
|
1487
|
+
},
|
|
1488
|
+
id: annotationId,
|
|
1489
|
+
from_name: "transcription",
|
|
1490
|
+
to_name: "image",
|
|
1491
|
+
type: "textarea",
|
|
1492
|
+
origin: "manual",
|
|
1493
|
+
...scoreField
|
|
1042
1494
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1495
|
+
];
|
|
1496
|
+
}).flat();
|
|
1497
|
+
const result = [
|
|
1498
|
+
{
|
|
1499
|
+
id: taskId,
|
|
1500
|
+
annotations: isPredictions ? [] : [
|
|
1501
|
+
{
|
|
1502
|
+
id: taskId,
|
|
1503
|
+
completed_by: 1,
|
|
1504
|
+
result: resultItems,
|
|
1505
|
+
was_cancelled: false,
|
|
1506
|
+
ground_truth: false,
|
|
1507
|
+
created_at: now,
|
|
1508
|
+
updated_at: now,
|
|
1509
|
+
draft_created_at: now,
|
|
1510
|
+
lead_time: 0,
|
|
1511
|
+
prediction: {},
|
|
1512
|
+
result_count: data.length * 3,
|
|
1513
|
+
unique_id: (0, import_node_crypto.randomUUID)(),
|
|
1514
|
+
import_id: null,
|
|
1515
|
+
last_action: null,
|
|
1516
|
+
bulk_created: false,
|
|
1517
|
+
task: taskId,
|
|
1518
|
+
project: 1,
|
|
1519
|
+
updated_by: 1,
|
|
1520
|
+
parent_prediction: null,
|
|
1521
|
+
parent_annotation: null,
|
|
1522
|
+
last_created_by: null
|
|
1071
1523
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (firstPoint) {
|
|
1083
|
-
const polygon2 = turf.polygon([points.concat([firstPoint])]);
|
|
1084
|
-
const area2 = turf.area(polygon2);
|
|
1085
|
-
dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
|
|
1086
|
-
}
|
|
1087
|
-
} catch {
|
|
1088
|
-
dt_score = 0.8;
|
|
1089
|
-
}
|
|
1090
|
-
imageAnnotations.push({
|
|
1091
|
-
transcription,
|
|
1092
|
-
points,
|
|
1093
|
-
dt_score
|
|
1094
|
-
});
|
|
1524
|
+
],
|
|
1525
|
+
file_upload: fileName,
|
|
1526
|
+
drafts: [],
|
|
1527
|
+
predictions: isPredictions ? [
|
|
1528
|
+
{
|
|
1529
|
+
model_version: "ppocr-v1",
|
|
1530
|
+
result: resultItems,
|
|
1531
|
+
created_at: now,
|
|
1532
|
+
task: taskId,
|
|
1533
|
+
project: 1
|
|
1095
1534
|
}
|
|
1096
|
-
|
|
1535
|
+
] : [],
|
|
1536
|
+
data: {
|
|
1537
|
+
ocr: relativeDir ? `${newBaseServerUrl}${relativeDir}/${normalizedImagePath}` : `${newBaseServerUrl}${normalizedImagePath}`
|
|
1538
|
+
},
|
|
1539
|
+
meta: {},
|
|
1540
|
+
created_at: now,
|
|
1541
|
+
updated_at: now,
|
|
1542
|
+
allow_skip: false,
|
|
1543
|
+
inner_id: taskId,
|
|
1544
|
+
total_annotations: isPredictions ? 0 : 1,
|
|
1545
|
+
cancelled_annotations: 0,
|
|
1546
|
+
total_predictions: isPredictions ? 1 : 0,
|
|
1547
|
+
comment_count: 0,
|
|
1548
|
+
unresolved_comment_count: 0,
|
|
1549
|
+
last_comment_updated_at: null,
|
|
1550
|
+
project: 1,
|
|
1551
|
+
updated_by: 1,
|
|
1552
|
+
comment_authors: []
|
|
1097
1553
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1554
|
+
];
|
|
1555
|
+
return result;
|
|
1556
|
+
};
|
|
1557
|
+
ppocrToMinLabelStudio = (data, imagePath, baseServerUrl, inputDir, labelName = "text", normalizeShape2, widthIncrement = 0, heightIncrement = 0, precision = DEFAULT_LABEL_STUDIO_PRECISION, relativeDir) => {
|
|
1558
|
+
const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
|
|
1559
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1560
|
+
let original_width = 1920;
|
|
1561
|
+
let original_height = 1080;
|
|
1562
|
+
let resolvedImagePath;
|
|
1563
|
+
if (inputDir) {
|
|
1564
|
+
const lastDirComponent = (0, import_node_path.basename)(inputDir);
|
|
1565
|
+
const imagePathParts = imagePath.split("/");
|
|
1566
|
+
if (imagePathParts[0] === lastDirComponent && imagePathParts.length > 1) {
|
|
1567
|
+
resolvedImagePath = (0, import_node_path.join)(inputDir, imagePathParts.slice(1).join("/"));
|
|
1568
|
+
} else {
|
|
1569
|
+
resolvedImagePath = (0, import_node_path.join)(inputDir, imagePath);
|
|
1100
1570
|
}
|
|
1571
|
+
} else {
|
|
1572
|
+
resolvedImagePath = imagePath;
|
|
1101
1573
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1574
|
+
if ((0, import_node_fs.existsSync)(resolvedImagePath)) {
|
|
1575
|
+
try {
|
|
1576
|
+
const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
|
|
1577
|
+
const dimensions = (0, import_image_size.default)(buffer);
|
|
1578
|
+
if (dimensions.width && dimensions.height) {
|
|
1579
|
+
original_width = dimensions.width;
|
|
1580
|
+
original_height = dimensions.height;
|
|
1581
|
+
} else {
|
|
1582
|
+
console.warn(
|
|
1583
|
+
`Warning: Failed to read dimensions from ${resolvedImagePath}, using defaults`
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
console.warn(
|
|
1588
|
+
`Warning: Error reading image ${resolvedImagePath}, using defaults:`,
|
|
1589
|
+
error instanceof Error ? error.message : error
|
|
1118
1590
|
);
|
|
1119
1591
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1592
|
+
} else {
|
|
1593
|
+
console.warn(
|
|
1594
|
+
`Warning: Image file not found: ${resolvedImagePath}, using default dimensions`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
let normalizedImagePath = imagePath;
|
|
1598
|
+
if (inputDir && relativeDir) {
|
|
1599
|
+
const lastDirComponent = (0, import_node_path.basename)(inputDir);
|
|
1600
|
+
const imagePathParts = imagePath.split("/");
|
|
1601
|
+
if (imagePathParts[0] === lastDirComponent && imagePathParts.length > 1) {
|
|
1602
|
+
normalizedImagePath = imagePathParts.slice(1).join("/");
|
|
1122
1603
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1604
|
+
}
|
|
1605
|
+
return data.map((item, index) => {
|
|
1606
|
+
let { points } = item;
|
|
1607
|
+
points = transformPoints(points, {
|
|
1608
|
+
normalizeShape: normalizeShape2,
|
|
1609
|
+
widthIncrement,
|
|
1610
|
+
heightIncrement
|
|
1611
|
+
});
|
|
1612
|
+
const roundedPoints = points.map(
|
|
1613
|
+
([x, y]) => [
|
|
1614
|
+
roundToPrecision(x ?? 0, precision),
|
|
1615
|
+
roundToPrecision(y ?? 0, precision)
|
|
1616
|
+
]
|
|
1127
1617
|
);
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1618
|
+
let minX = Infinity;
|
|
1619
|
+
let minY = Infinity;
|
|
1620
|
+
let maxX = -Infinity;
|
|
1621
|
+
let maxY = -Infinity;
|
|
1622
|
+
for (const point of roundedPoints) {
|
|
1623
|
+
const [x, y] = point;
|
|
1624
|
+
if (x !== void 0 && y !== void 0) {
|
|
1625
|
+
minX = Math.min(minX, x);
|
|
1626
|
+
minY = Math.min(minY, y);
|
|
1627
|
+
maxX = Math.max(maxX, x);
|
|
1628
|
+
maxY = Math.max(maxY, y);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const width = maxX - minX;
|
|
1632
|
+
const height = maxY - minY;
|
|
1633
|
+
return {
|
|
1634
|
+
ocr: relativeDir ? encodeURI(`${newBaseServerUrl}${relativeDir}/${normalizedImagePath}`) : encodeURI(`${newBaseServerUrl}${normalizedImagePath}`),
|
|
1635
|
+
id: index + 1,
|
|
1636
|
+
bbox: [
|
|
1637
|
+
{
|
|
1638
|
+
x: minX,
|
|
1639
|
+
y: minY,
|
|
1640
|
+
width,
|
|
1641
|
+
height,
|
|
1642
|
+
rotation: 0,
|
|
1643
|
+
original_width,
|
|
1644
|
+
original_height
|
|
1135
1645
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
[x, y + height]
|
|
1145
|
-
];
|
|
1646
|
+
],
|
|
1647
|
+
label: [
|
|
1648
|
+
{
|
|
1649
|
+
points: roundedPoints,
|
|
1650
|
+
closed: true,
|
|
1651
|
+
labels: [labelName],
|
|
1652
|
+
original_width,
|
|
1653
|
+
original_height
|
|
1146
1654
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1655
|
+
],
|
|
1656
|
+
transcription: [item.transcription],
|
|
1657
|
+
poly: [
|
|
1658
|
+
{
|
|
1659
|
+
points: roundedPoints,
|
|
1660
|
+
closed: true,
|
|
1661
|
+
original_width,
|
|
1662
|
+
original_height
|
|
1663
|
+
}
|
|
1664
|
+
],
|
|
1665
|
+
annotator: 1,
|
|
1666
|
+
annotation_id: index + 1,
|
|
1667
|
+
created_at: now,
|
|
1668
|
+
updated_at: now,
|
|
1669
|
+
lead_time: 0
|
|
1670
|
+
};
|
|
1671
|
+
});
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
// src/commands/toLabelStudio/impl.ts
|
|
1677
|
+
var impl_exports3 = {};
|
|
1678
|
+
__export(impl_exports3, {
|
|
1679
|
+
convertToLabelStudio: () => convertToLabelStudio
|
|
1680
|
+
});
|
|
1681
|
+
async function convertToLabelStudio(flags, ...inputDirs) {
|
|
1682
|
+
const {
|
|
1683
|
+
outDir,
|
|
1684
|
+
fileName,
|
|
1685
|
+
backup = false,
|
|
1686
|
+
defaultLabelName = DEFAULT_LABEL_NAME,
|
|
1687
|
+
toFullJson = DEFAULT_LABEL_STUDIO_FULL_JSON,
|
|
1688
|
+
createFilePerImage = DEFAULT_CREATE_FILE_PER_IMAGE,
|
|
1689
|
+
createFileListForServing = DEFAULT_CREATE_FILE_LIST_FOR_SERVING,
|
|
1690
|
+
fileListName = DEFAULT_FILE_LIST_NAME,
|
|
1691
|
+
baseServerUrl = DEFAULT_BASE_SERVER_URL,
|
|
1692
|
+
sortVertical = DEFAULT_SORT_VERTICAL,
|
|
1693
|
+
sortHorizontal = DEFAULT_SORT_HORIZONTAL,
|
|
1694
|
+
normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
|
|
1695
|
+
widthIncrement = DEFAULT_WIDTH_INCREMENT,
|
|
1696
|
+
heightIncrement = DEFAULT_HEIGHT_INCREMENT,
|
|
1697
|
+
precision = DEFAULT_LABEL_STUDIO_PRECISION,
|
|
1698
|
+
recursive = DEFAULT_RECURSIVE,
|
|
1699
|
+
filePattern = DEFAULT_PPOCR_FILE_PATTERN,
|
|
1700
|
+
outputMode = DEFAULT_OUTPUT_MODE
|
|
1701
|
+
} = flags;
|
|
1702
|
+
if (outputMode !== DEFAULT_OUTPUT_MODE && !toFullJson) {
|
|
1703
|
+
console.log(
|
|
1704
|
+
import_chalk3.default.red(
|
|
1705
|
+
"Error: --outputMode can only be used with --toFullJson (Full JSON format). Min JSON format does not support annotations/predictions distinction."
|
|
1706
|
+
)
|
|
1707
|
+
);
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "");
|
|
1711
|
+
console.log(import_chalk3.default.blue("Finding files..."));
|
|
1712
|
+
const filePaths = await findFiles(inputDirs, filePattern, recursive);
|
|
1713
|
+
if (filePaths.length === 0) {
|
|
1714
|
+
console.log(import_chalk3.default.yellow("No files found matching the pattern."));
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
console.log(import_chalk3.default.blue(`Found ${filePaths.length} files to process
|
|
1718
|
+
`));
|
|
1719
|
+
let fileListPath = null;
|
|
1720
|
+
if (createFileListForServing && outDir) {
|
|
1721
|
+
fileListPath = (0, import_path4.join)(outDir, fileListName);
|
|
1722
|
+
await (0, import_promises4.mkdir)(outDir, { recursive: true });
|
|
1723
|
+
await (0, import_promises4.writeFile)(fileListPath, "", "utf-8");
|
|
1724
|
+
}
|
|
1725
|
+
for (const filePath of filePaths) {
|
|
1726
|
+
const file = (0, import_path4.basename)(filePath);
|
|
1727
|
+
const baseImageDir = (0, import_path4.dirname)(filePath);
|
|
1728
|
+
const relativePath = getRelativePathFromInputs(filePath, inputDirs);
|
|
1729
|
+
const relativeDir = (0, import_path4.dirname)(relativePath);
|
|
1730
|
+
console.log(import_chalk3.default.gray(`Processing file: ${filePath}`));
|
|
1731
|
+
try {
|
|
1732
|
+
const fileData = await (0, import_promises4.readFile)(filePath, "utf-8");
|
|
1733
|
+
const trimmedData = fileData.trim();
|
|
1734
|
+
if (trimmedData === "") {
|
|
1735
|
+
console.log(import_chalk3.default.yellow(` Skipping empty file: ${filePath}`));
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
const lines = trimmedData.split("\n");
|
|
1739
|
+
const imageDataMap = /* @__PURE__ */ new Map();
|
|
1740
|
+
for (const line of lines) {
|
|
1741
|
+
if (line.trim() === "") {
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
const parts = line.split(" ");
|
|
1745
|
+
if (parts.length !== 2) {
|
|
1746
|
+
throw new Error(`Invalid PPOCRLabelV2 format in line: ${line}`);
|
|
1747
|
+
}
|
|
1748
|
+
const [imagePath, annotationsStr] = parts;
|
|
1749
|
+
const annotations = JSON.parse(annotationsStr);
|
|
1750
|
+
PPOCRLabelSchema.parse(annotations);
|
|
1751
|
+
imageDataMap.set(imagePath, annotations);
|
|
1752
|
+
}
|
|
1753
|
+
if (imageDataMap.size === 0) {
|
|
1754
|
+
console.log(
|
|
1755
|
+
import_chalk3.default.yellow(` Skipping file with no valid data: ${filePath}`)
|
|
1756
|
+
);
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const allLabelStudioData = [];
|
|
1760
|
+
let taskId = 1;
|
|
1761
|
+
for (const [imagePath, ppocrData] of imageDataMap.entries()) {
|
|
1762
|
+
const sortedPpocrData = sortBoundingBoxes(
|
|
1763
|
+
ppocrData,
|
|
1764
|
+
sortVertical,
|
|
1765
|
+
sortHorizontal
|
|
1766
|
+
);
|
|
1767
|
+
const finalImagePath = createFileListForServing ? encodeURI(`${newBaseServerUrl}${relativeDir}/${imagePath}`) : imagePath;
|
|
1768
|
+
const labelStudioData = await ppocrToLabelStudio(sortedPpocrData, {
|
|
1769
|
+
toFullJson,
|
|
1770
|
+
imagePath,
|
|
1771
|
+
baseServerUrl: newBaseServerUrl,
|
|
1772
|
+
inputDir: baseImageDir,
|
|
1773
|
+
relativeDir,
|
|
1774
|
+
taskId,
|
|
1775
|
+
labelName: defaultLabelName,
|
|
1776
|
+
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1777
|
+
widthIncrement,
|
|
1778
|
+
heightIncrement,
|
|
1779
|
+
precision,
|
|
1780
|
+
outputMode
|
|
1781
|
+
});
|
|
1782
|
+
if (toFullJson) {
|
|
1783
|
+
allLabelStudioData.push(labelStudioData[0]);
|
|
1784
|
+
} else {
|
|
1785
|
+
allLabelStudioData.push(...labelStudioData);
|
|
1786
|
+
}
|
|
1787
|
+
if (createFilePerImage) {
|
|
1788
|
+
const imageBaseName = imagePath.replace(/\//g, "_").replace(/\.[^.]+$/, "");
|
|
1789
|
+
const outputSubDir2 = outDir ? (0, import_path4.join)(outDir, relativeDir) : (0, import_path4.dirname)(filePath);
|
|
1790
|
+
await (0, import_promises4.mkdir)(outputSubDir2, { recursive: true });
|
|
1791
|
+
const individualOutputPath = (0, import_path4.join)(
|
|
1792
|
+
outputSubDir2,
|
|
1793
|
+
`${imageBaseName}_${toFullJson ? "full" : "min"}.json`
|
|
1794
|
+
);
|
|
1795
|
+
await (0, import_promises4.writeFile)(
|
|
1796
|
+
individualOutputPath,
|
|
1797
|
+
JSON.stringify(
|
|
1798
|
+
toFullJson ? labelStudioData[0] : labelStudioData,
|
|
1799
|
+
null,
|
|
1800
|
+
2
|
|
1801
|
+
),
|
|
1802
|
+
"utf-8"
|
|
1803
|
+
);
|
|
1804
|
+
console.log(
|
|
1805
|
+
import_chalk3.default.gray(` \u2713 Created individual file: ${individualOutputPath}`)
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
if (fileListPath) {
|
|
1809
|
+
await (0, import_promises4.writeFile)(fileListPath, `${finalImagePath}
|
|
1810
|
+
`, {
|
|
1811
|
+
encoding: "utf-8",
|
|
1812
|
+
flag: "a"
|
|
1155
1813
|
});
|
|
1156
|
-
points = roundPoints(points, precision);
|
|
1157
|
-
const transcription = item.transcription && item.transcription.length > i ? item.transcription[i] : "";
|
|
1158
|
-
let dt_score = 1;
|
|
1159
|
-
try {
|
|
1160
|
-
const firstPoint = points[0];
|
|
1161
|
-
if (firstPoint) {
|
|
1162
|
-
const polygon2 = turf.polygon([points.concat([firstPoint])]);
|
|
1163
|
-
const area2 = turf.area(polygon2);
|
|
1164
|
-
dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
|
|
1165
|
-
}
|
|
1166
|
-
} catch {
|
|
1167
|
-
dt_score = 0.8;
|
|
1168
|
-
}
|
|
1169
|
-
const annotation = {
|
|
1170
|
-
transcription: transcription ?? "",
|
|
1171
|
-
points,
|
|
1172
|
-
dt_score
|
|
1173
|
-
};
|
|
1174
|
-
if (!resultMap.has(imagePath)) {
|
|
1175
|
-
resultMap.set(imagePath, []);
|
|
1176
|
-
}
|
|
1177
|
-
resultMap.get(imagePath).push(annotation);
|
|
1178
1814
|
}
|
|
1815
|
+
taskId++;
|
|
1179
1816
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1817
|
+
const baseName = fileName || file.replace(".txt", "");
|
|
1818
|
+
const outputSubDir = outDir ? (0, import_path4.join)(outDir, relativeDir) : (0, import_path4.dirname)(filePath);
|
|
1819
|
+
await (0, import_promises4.mkdir)(outputSubDir, { recursive: true });
|
|
1820
|
+
const outputPath = (0, import_path4.join)(
|
|
1821
|
+
outputSubDir,
|
|
1822
|
+
fileName ? `${fileName}.json` : `${baseName}_${toFullJson ? "full" : "min"}.json`
|
|
1823
|
+
);
|
|
1824
|
+
if (backup) {
|
|
1825
|
+
const backupPath = await backupFileIfExists(outputPath);
|
|
1826
|
+
if (backupPath) {
|
|
1827
|
+
console.log(import_chalk3.default.gray(` \u2713 Created backup: ${backupPath}`));
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
await (0, import_promises4.writeFile)(
|
|
1831
|
+
outputPath,
|
|
1832
|
+
JSON.stringify(allLabelStudioData, null, 2),
|
|
1833
|
+
"utf-8"
|
|
1834
|
+
);
|
|
1835
|
+
console.log(import_chalk3.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
console.error(
|
|
1838
|
+
import_chalk3.default.red(`\u2717 Failed to process ${file}:`),
|
|
1839
|
+
error instanceof Error ? error.message : error
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
console.log(import_chalk3.default.green("\n\u2713 Conversion completed!"));
|
|
1844
|
+
}
|
|
1845
|
+
var import_promises4, import_path4, import_chalk3;
|
|
1846
|
+
var init_impl3 = __esm({
|
|
1847
|
+
"src/commands/toLabelStudio/impl.ts"() {
|
|
1848
|
+
"use strict";
|
|
1849
|
+
init_cjs_shims();
|
|
1850
|
+
import_promises4 = require("fs/promises");
|
|
1851
|
+
import_path4 = require("path");
|
|
1852
|
+
import_chalk3 = __toESM(require("chalk"), 1);
|
|
1853
|
+
init_constants();
|
|
1854
|
+
init_backup_utils();
|
|
1855
|
+
init_file_utils();
|
|
1856
|
+
init_ppocr_label();
|
|
1857
|
+
init_schema();
|
|
1858
|
+
init_sort();
|
|
1182
1859
|
}
|
|
1183
1860
|
});
|
|
1184
1861
|
|
|
1185
1862
|
// src/commands/toPPOCR/impl.ts
|
|
1186
|
-
var
|
|
1187
|
-
__export(
|
|
1863
|
+
var impl_exports4 = {};
|
|
1864
|
+
__export(impl_exports4, {
|
|
1188
1865
|
convertToPPOCR: () => convertToPPOCR
|
|
1189
1866
|
});
|
|
1190
1867
|
async function convertToPPOCR(flags, ...inputDirs) {
|
|
1191
1868
|
const {
|
|
1192
|
-
outDir
|
|
1869
|
+
outDir,
|
|
1193
1870
|
fileName = DEFAULT_PPOCR_FILE_NAME,
|
|
1871
|
+
backup = false,
|
|
1194
1872
|
baseImageDir,
|
|
1195
1873
|
sortVertical = DEFAULT_SORT_VERTICAL,
|
|
1196
1874
|
sortHorizontal = DEFAULT_SORT_HORIZONTAL,
|
|
1197
1875
|
normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
|
|
1198
1876
|
widthIncrement = DEFAULT_WIDTH_INCREMENT,
|
|
1199
1877
|
heightIncrement = DEFAULT_HEIGHT_INCREMENT,
|
|
1200
|
-
precision = DEFAULT_PPOCR_PRECISION
|
|
1878
|
+
precision = DEFAULT_PPOCR_PRECISION,
|
|
1879
|
+
recursive = DEFAULT_RECURSIVE,
|
|
1880
|
+
filePattern = DEFAULT_LABEL_STUDIO_FILE_PATTERN
|
|
1201
1881
|
} = flags;
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1882
|
+
console.log(import_chalk4.default.blue("Finding files..."));
|
|
1883
|
+
const filePaths = await findFiles(inputDirs, filePattern, recursive);
|
|
1884
|
+
if (filePaths.length === 0) {
|
|
1885
|
+
console.log(import_chalk4.default.yellow("No files found matching the pattern."));
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
console.log(import_chalk4.default.blue(`Found ${filePaths.length} files to process
|
|
1889
|
+
`));
|
|
1890
|
+
for (const filePath of filePaths) {
|
|
1891
|
+
const file = (0, import_path5.basename)(filePath);
|
|
1892
|
+
const relativePath = getRelativePathFromInputs(filePath, inputDirs);
|
|
1893
|
+
const relativeDir = (0, import_path5.dirname)(relativePath);
|
|
1894
|
+
console.log(import_chalk4.default.gray(`Processing file: ${filePath}`));
|
|
1895
|
+
try {
|
|
1896
|
+
const fileData = await (0, import_promises5.readFile)(filePath, "utf-8");
|
|
1897
|
+
const labelStudioData = JSON.parse(fileData);
|
|
1898
|
+
const { data, isFull } = isLabelStudioFullJSON2(labelStudioData);
|
|
1899
|
+
const ppocrDataMap = isFull ? await labelStudioToPPOCR(data, {
|
|
1900
|
+
baseImageDir,
|
|
1901
|
+
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1902
|
+
widthIncrement,
|
|
1903
|
+
heightIncrement,
|
|
1904
|
+
precision
|
|
1905
|
+
}) : await minLabelStudioToPPOCR(data, {
|
|
1906
|
+
baseImageDir,
|
|
1907
|
+
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1908
|
+
widthIncrement,
|
|
1909
|
+
heightIncrement,
|
|
1910
|
+
precision
|
|
1911
|
+
});
|
|
1912
|
+
const outputLines = [];
|
|
1913
|
+
for (const [imagePath, annotations] of ppocrDataMap.entries()) {
|
|
1914
|
+
const sortedAnnotations = sortBoundingBoxes(
|
|
1915
|
+
annotations,
|
|
1916
|
+
sortVertical,
|
|
1917
|
+
sortHorizontal
|
|
1918
|
+
);
|
|
1919
|
+
PPOCRLabelSchema.parse(sortedAnnotations);
|
|
1920
|
+
const jsonArray = JSON.stringify(sortedAnnotations);
|
|
1921
|
+
outputLines.push(`${imagePath} ${jsonArray}`);
|
|
1209
1922
|
}
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1219
|
-
widthIncrement,
|
|
1220
|
-
heightIncrement,
|
|
1221
|
-
precision
|
|
1222
|
-
}) : await minLabelStudioToPPOCR(data, {
|
|
1223
|
-
baseImageDir,
|
|
1224
|
-
normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
|
|
1225
|
-
widthIncrement,
|
|
1226
|
-
heightIncrement,
|
|
1227
|
-
precision
|
|
1228
|
-
});
|
|
1229
|
-
const outputLines = [];
|
|
1230
|
-
for (const [imagePath, annotations] of ppocrDataMap.entries()) {
|
|
1231
|
-
const sortedAnnotations = sortBoundingBoxes(
|
|
1232
|
-
annotations,
|
|
1233
|
-
sortVertical,
|
|
1234
|
-
sortHorizontal
|
|
1235
|
-
);
|
|
1236
|
-
PPOCRLabelSchema.parse(sortedAnnotations);
|
|
1237
|
-
const jsonArray = JSON.stringify(sortedAnnotations);
|
|
1238
|
-
outputLines.push(`${imagePath} ${jsonArray}`);
|
|
1923
|
+
const baseName = file.replace(".json", "");
|
|
1924
|
+
const outputSubDir = outDir ? (0, import_path5.join)(outDir, relativeDir) : (0, import_path5.dirname)(filePath);
|
|
1925
|
+
await (0, import_promises5.mkdir)(outputSubDir, { recursive: true });
|
|
1926
|
+
const outputPath = (0, import_path5.join)(outputSubDir, `${baseName}_${fileName}`);
|
|
1927
|
+
if (backup) {
|
|
1928
|
+
const backupPath = await backupFileIfExists(outputPath);
|
|
1929
|
+
if (backupPath) {
|
|
1930
|
+
console.log(import_chalk4.default.gray(` Backed up to: ${backupPath}`));
|
|
1239
1931
|
}
|
|
1240
|
-
const baseName = file.replace(".json", "");
|
|
1241
|
-
const outputPath = (0, import_path2.join)(outDir, `${baseName}_${fileName}`);
|
|
1242
|
-
await (0, import_promises2.writeFile)(outputPath, outputLines.join("\n"), "utf-8");
|
|
1243
|
-
console.log(import_chalk2.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
|
|
1244
|
-
} catch (error) {
|
|
1245
|
-
console.error(
|
|
1246
|
-
import_chalk2.default.red(`\u2717 Failed to process ${file}:`),
|
|
1247
|
-
error instanceof Error ? error.message : error
|
|
1248
|
-
);
|
|
1249
1932
|
}
|
|
1933
|
+
await (0, import_promises5.writeFile)(outputPath, outputLines.join("\n"), "utf-8");
|
|
1934
|
+
console.log(import_chalk4.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
console.error(
|
|
1937
|
+
import_chalk4.default.red(`\u2717 Failed to process ${file}:`),
|
|
1938
|
+
error instanceof Error ? error.message : error
|
|
1939
|
+
);
|
|
1250
1940
|
}
|
|
1251
1941
|
}
|
|
1252
|
-
console.log(
|
|
1942
|
+
console.log(import_chalk4.default.green("\n\u2713 Conversion completed!"));
|
|
1253
1943
|
}
|
|
1254
|
-
var
|
|
1255
|
-
var
|
|
1944
|
+
var import_promises5, import_path5, import_chalk4, isLabelStudioFullJSON2;
|
|
1945
|
+
var init_impl4 = __esm({
|
|
1256
1946
|
"src/commands/toPPOCR/impl.ts"() {
|
|
1257
1947
|
"use strict";
|
|
1258
1948
|
init_cjs_shims();
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1949
|
+
import_promises5 = require("fs/promises");
|
|
1950
|
+
import_path5 = require("path");
|
|
1951
|
+
import_chalk4 = __toESM(require("chalk"), 1);
|
|
1262
1952
|
init_constants();
|
|
1953
|
+
init_backup_utils();
|
|
1954
|
+
init_file_utils();
|
|
1263
1955
|
init_label_studio();
|
|
1264
1956
|
init_schema();
|
|
1265
1957
|
init_sort();
|
|
1266
|
-
|
|
1958
|
+
isLabelStudioFullJSON2 = (data) => {
|
|
1267
1959
|
const parsedFull = FullOCRLabelStudioSchema.safeParse(data);
|
|
1268
1960
|
if (parsedFull.success) {
|
|
1269
1961
|
return { isFull: true, data: parsedFull.data };
|
|
@@ -1285,24 +1977,210 @@ var init_impl2 = __esm({
|
|
|
1285
1977
|
|
|
1286
1978
|
// src/bin/bash-complete.ts
|
|
1287
1979
|
init_cjs_shims();
|
|
1288
|
-
var
|
|
1980
|
+
var import_core6 = require("@stricli/core");
|
|
1289
1981
|
|
|
1290
1982
|
// src/app.ts
|
|
1291
1983
|
init_cjs_shims();
|
|
1292
1984
|
var import_auto_complete = require("@stricli/auto-complete");
|
|
1293
|
-
var
|
|
1985
|
+
var import_core5 = require("@stricli/core");
|
|
1294
1986
|
|
|
1295
1987
|
// package.json
|
|
1296
|
-
var version = "1.
|
|
1988
|
+
var version = "1.3.0";
|
|
1297
1989
|
var description = "Convert between Label Studio OCR format and PPOCRLabelv2 format";
|
|
1298
1990
|
|
|
1299
|
-
// src/commands/
|
|
1991
|
+
// src/commands/enhance-labelstudio/command.ts
|
|
1300
1992
|
init_cjs_shims();
|
|
1301
1993
|
var import_core = require("@stricli/core");
|
|
1302
1994
|
init_constants();
|
|
1303
|
-
var
|
|
1995
|
+
var enhanceLabelStudioCommand = (0, import_core.buildCommand)({
|
|
1996
|
+
loader: async () => {
|
|
1997
|
+
const { enhanceLabelStudio: enhanceLabelStudio2 } = await Promise.resolve().then(() => (init_impl(), impl_exports));
|
|
1998
|
+
return enhanceLabelStudio2;
|
|
1999
|
+
},
|
|
2000
|
+
parameters: {
|
|
2001
|
+
positional: {
|
|
2002
|
+
kind: "array",
|
|
2003
|
+
parameter: {
|
|
2004
|
+
brief: "Input directories containing Label Studio JSON files",
|
|
2005
|
+
parse: String
|
|
2006
|
+
},
|
|
2007
|
+
minimum: 1
|
|
2008
|
+
},
|
|
2009
|
+
flags: {
|
|
2010
|
+
outDir: {
|
|
2011
|
+
kind: "parsed",
|
|
2012
|
+
brief: "Output directory. If not specified, files are saved in the same directory as the source files",
|
|
2013
|
+
parse: String,
|
|
2014
|
+
optional: true
|
|
2015
|
+
},
|
|
2016
|
+
fileName: {
|
|
2017
|
+
kind: "parsed",
|
|
2018
|
+
brief: "Custom output filename. If not specified, uses the same name as the source file",
|
|
2019
|
+
parse: String,
|
|
2020
|
+
optional: true
|
|
2021
|
+
},
|
|
2022
|
+
backup: {
|
|
2023
|
+
kind: "boolean",
|
|
2024
|
+
brief: `Create backup of existing files before overwriting. Default: ${DEFAULT_BACKUP}`,
|
|
2025
|
+
optional: true
|
|
2026
|
+
},
|
|
2027
|
+
sortVertical: {
|
|
2028
|
+
kind: "parsed",
|
|
2029
|
+
brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
|
|
2030
|
+
parse: String,
|
|
2031
|
+
optional: true
|
|
2032
|
+
},
|
|
2033
|
+
sortHorizontal: {
|
|
2034
|
+
kind: "parsed",
|
|
2035
|
+
brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
|
|
2036
|
+
parse: String,
|
|
2037
|
+
optional: true
|
|
2038
|
+
},
|
|
2039
|
+
normalizeShape: {
|
|
2040
|
+
kind: "parsed",
|
|
2041
|
+
brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
|
|
2042
|
+
parse: String,
|
|
2043
|
+
optional: true
|
|
2044
|
+
},
|
|
2045
|
+
widthIncrement: {
|
|
2046
|
+
kind: "parsed",
|
|
2047
|
+
brief: `Increase bounding box width by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_WIDTH_INCREMENT}`,
|
|
2048
|
+
parse: Number,
|
|
2049
|
+
optional: true
|
|
2050
|
+
},
|
|
2051
|
+
heightIncrement: {
|
|
2052
|
+
kind: "parsed",
|
|
2053
|
+
brief: `Increase bounding box height by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_HEIGHT_INCREMENT}`,
|
|
2054
|
+
parse: Number,
|
|
2055
|
+
optional: true
|
|
2056
|
+
},
|
|
2057
|
+
precision: {
|
|
2058
|
+
kind: "parsed",
|
|
2059
|
+
brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_LABEL_STUDIO_PRECISION}`,
|
|
2060
|
+
parse: Number,
|
|
2061
|
+
optional: true
|
|
2062
|
+
},
|
|
2063
|
+
recursive: {
|
|
2064
|
+
kind: "boolean",
|
|
2065
|
+
brief: `Recursively search directories for files. Default: ${DEFAULT_RECURSIVE}`,
|
|
2066
|
+
optional: true
|
|
2067
|
+
},
|
|
2068
|
+
filePattern: {
|
|
2069
|
+
kind: "parsed",
|
|
2070
|
+
brief: `Regex pattern to match Label Studio files (should match .json files). Default: "${DEFAULT_LABEL_STUDIO_FILE_PATTERN}"`,
|
|
2071
|
+
parse: String,
|
|
2072
|
+
optional: true
|
|
2073
|
+
},
|
|
2074
|
+
outputMode: {
|
|
2075
|
+
kind: "parsed",
|
|
2076
|
+
brief: `Output mode: "${OUTPUT_MODE_ANNOTATIONS}" for editable annotations (ground truth) or "${OUTPUT_MODE_PREDICTIONS}" for read-only predictions (pre-annotations). Default: "${DEFAULT_OUTPUT_MODE}"`,
|
|
2077
|
+
parse: String,
|
|
2078
|
+
optional: true
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
},
|
|
2082
|
+
docs: {
|
|
2083
|
+
brief: "Enhance Label Studio files with sorting, normalization, and resizing"
|
|
2084
|
+
}
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
// src/commands/enhance-ppocr/command.ts
|
|
2088
|
+
init_cjs_shims();
|
|
2089
|
+
var import_core2 = require("@stricli/core");
|
|
2090
|
+
init_constants();
|
|
2091
|
+
var enhancePPOCRCommand = (0, import_core2.buildCommand)({
|
|
2092
|
+
loader: async () => {
|
|
2093
|
+
const { enhancePPOCR: enhancePPOCR2 } = await Promise.resolve().then(() => (init_impl2(), impl_exports2));
|
|
2094
|
+
return enhancePPOCR2;
|
|
2095
|
+
},
|
|
2096
|
+
parameters: {
|
|
2097
|
+
positional: {
|
|
2098
|
+
kind: "array",
|
|
2099
|
+
parameter: {
|
|
2100
|
+
brief: "Input directories containing PPOCRLabel files",
|
|
2101
|
+
parse: String
|
|
2102
|
+
},
|
|
2103
|
+
minimum: 1
|
|
2104
|
+
},
|
|
2105
|
+
flags: {
|
|
2106
|
+
outDir: {
|
|
2107
|
+
kind: "parsed",
|
|
2108
|
+
brief: "Output directory. If not specified, files are saved in the same directory as the source files",
|
|
2109
|
+
parse: String,
|
|
2110
|
+
optional: true
|
|
2111
|
+
},
|
|
2112
|
+
fileName: {
|
|
2113
|
+
kind: "parsed",
|
|
2114
|
+
brief: "Custom output filename. If not specified, uses the same name as the source file",
|
|
2115
|
+
parse: String,
|
|
2116
|
+
optional: true
|
|
2117
|
+
},
|
|
2118
|
+
backup: {
|
|
2119
|
+
kind: "boolean",
|
|
2120
|
+
brief: `Create backup of existing files before overwriting. Default: ${DEFAULT_BACKUP}`,
|
|
2121
|
+
optional: true
|
|
2122
|
+
},
|
|
2123
|
+
sortVertical: {
|
|
2124
|
+
kind: "parsed",
|
|
2125
|
+
brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
|
|
2126
|
+
parse: String,
|
|
2127
|
+
optional: true
|
|
2128
|
+
},
|
|
2129
|
+
sortHorizontal: {
|
|
2130
|
+
kind: "parsed",
|
|
2131
|
+
brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
|
|
2132
|
+
parse: String,
|
|
2133
|
+
optional: true
|
|
2134
|
+
},
|
|
2135
|
+
normalizeShape: {
|
|
2136
|
+
kind: "parsed",
|
|
2137
|
+
brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
|
|
2138
|
+
parse: String,
|
|
2139
|
+
optional: true
|
|
2140
|
+
},
|
|
2141
|
+
widthIncrement: {
|
|
2142
|
+
kind: "parsed",
|
|
2143
|
+
brief: `Increase bounding box width by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_WIDTH_INCREMENT}`,
|
|
2144
|
+
parse: Number,
|
|
2145
|
+
optional: true
|
|
2146
|
+
},
|
|
2147
|
+
heightIncrement: {
|
|
2148
|
+
kind: "parsed",
|
|
2149
|
+
brief: `Increase bounding box height by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_HEIGHT_INCREMENT}`,
|
|
2150
|
+
parse: Number,
|
|
2151
|
+
optional: true
|
|
2152
|
+
},
|
|
2153
|
+
precision: {
|
|
2154
|
+
kind: "parsed",
|
|
2155
|
+
brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_PPOCR_PRECISION} (integers)`,
|
|
2156
|
+
parse: Number,
|
|
2157
|
+
optional: true
|
|
2158
|
+
},
|
|
2159
|
+
recursive: {
|
|
2160
|
+
kind: "boolean",
|
|
2161
|
+
brief: `Recursively search directories for files. Default: ${DEFAULT_RECURSIVE}`,
|
|
2162
|
+
optional: true
|
|
2163
|
+
},
|
|
2164
|
+
filePattern: {
|
|
2165
|
+
kind: "parsed",
|
|
2166
|
+
brief: `Regex pattern to match PPOCRLabel files (should match .txt files). Default: "${DEFAULT_PPOCR_FILE_PATTERN}"`,
|
|
2167
|
+
parse: String,
|
|
2168
|
+
optional: true
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
},
|
|
2172
|
+
docs: {
|
|
2173
|
+
brief: "Enhance PPOCRLabel files with sorting, normalization, and resizing"
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
|
|
2177
|
+
// src/commands/toLabelStudio/command.ts
|
|
2178
|
+
init_cjs_shims();
|
|
2179
|
+
var import_core3 = require("@stricli/core");
|
|
2180
|
+
init_constants();
|
|
2181
|
+
var toLabelStudioCommand = (0, import_core3.buildCommand)({
|
|
1304
2182
|
loader: async () => {
|
|
1305
|
-
const { convertToLabelStudio: convertToLabelStudio2 } = await Promise.resolve().then(() => (
|
|
2183
|
+
const { convertToLabelStudio: convertToLabelStudio2 } = await Promise.resolve().then(() => (init_impl3(), impl_exports3));
|
|
1306
2184
|
return convertToLabelStudio2;
|
|
1307
2185
|
},
|
|
1308
2186
|
parameters: {
|
|
@@ -1317,58 +2195,69 @@ var toLabelStudioCommand = (0, import_core.buildCommand)({
|
|
|
1317
2195
|
flags: {
|
|
1318
2196
|
outDir: {
|
|
1319
2197
|
kind: "parsed",
|
|
1320
|
-
brief:
|
|
2198
|
+
brief: "Output directory. If not specified, files are saved in the same directory as the source files",
|
|
2199
|
+
parse: String,
|
|
2200
|
+
optional: true
|
|
2201
|
+
},
|
|
2202
|
+
fileName: {
|
|
2203
|
+
kind: "parsed",
|
|
2204
|
+
brief: "Custom output filename (without extension). If not specified, uses source filename with format suffix",
|
|
1321
2205
|
parse: String,
|
|
1322
2206
|
optional: true
|
|
1323
2207
|
},
|
|
2208
|
+
backup: {
|
|
2209
|
+
kind: "boolean",
|
|
2210
|
+
brief: `Create backup of existing files before overwriting. Default: ${DEFAULT_BACKUP}`,
|
|
2211
|
+
optional: true
|
|
2212
|
+
},
|
|
1324
2213
|
defaultLabelName: {
|
|
1325
2214
|
kind: "parsed",
|
|
1326
|
-
brief: `Default label name for text annotations. Default
|
|
2215
|
+
brief: `Default label name for text annotations. Default: "${DEFAULT_LABEL_NAME}"`,
|
|
1327
2216
|
parse: String,
|
|
1328
2217
|
optional: true
|
|
1329
2218
|
},
|
|
1330
2219
|
toFullJson: {
|
|
1331
2220
|
kind: "boolean",
|
|
1332
|
-
brief: `Convert to Full OCR Label Studio format. Default
|
|
2221
|
+
brief: `Convert to Full OCR Label Studio format. Default: "${DEFAULT_LABEL_STUDIO_FULL_JSON}"`,
|
|
1333
2222
|
optional: true
|
|
1334
2223
|
},
|
|
1335
2224
|
createFilePerImage: {
|
|
1336
2225
|
kind: "boolean",
|
|
1337
|
-
brief: `Create a separate Label Studio JSON file for each image. Default
|
|
2226
|
+
brief: `Create a separate Label Studio JSON file for each image. Default: "${DEFAULT_CREATE_FILE_PER_IMAGE}"`,
|
|
1338
2227
|
optional: true
|
|
1339
2228
|
},
|
|
1340
2229
|
createFileListForServing: {
|
|
1341
2230
|
kind: "boolean",
|
|
1342
|
-
brief: `Create a file list for serving in Label Studio. Default
|
|
2231
|
+
brief: `Create a file list for serving in Label Studio. Default: "${DEFAULT_CREATE_FILE_LIST_FOR_SERVING}"`,
|
|
1343
2232
|
optional: true
|
|
1344
2233
|
},
|
|
1345
2234
|
fileListName: {
|
|
1346
2235
|
kind: "parsed",
|
|
1347
|
-
brief: `Name of the file list for serving. Default
|
|
2236
|
+
brief: `Name of the file list for serving. Default: "${DEFAULT_FILE_LIST_NAME}"`,
|
|
1348
2237
|
parse: String,
|
|
1349
2238
|
optional: true
|
|
1350
2239
|
},
|
|
1351
2240
|
baseServerUrl: {
|
|
1352
2241
|
kind: "parsed",
|
|
1353
|
-
brief: `Base server URL for constructing image URLs in the file list. Default
|
|
2242
|
+
brief: `Base server URL for constructing image URLs in the file list. Default: "${DEFAULT_BASE_SERVER_URL}"`,
|
|
1354
2243
|
parse: String,
|
|
1355
2244
|
optional: true
|
|
1356
2245
|
},
|
|
1357
2246
|
sortVertical: {
|
|
1358
2247
|
kind: "parsed",
|
|
1359
|
-
brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}"
|
|
2248
|
+
brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
|
|
1360
2249
|
parse: String,
|
|
1361
2250
|
optional: true
|
|
1362
2251
|
},
|
|
1363
2252
|
sortHorizontal: {
|
|
1364
2253
|
kind: "parsed",
|
|
1365
|
-
brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}"
|
|
2254
|
+
brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
|
|
1366
2255
|
parse: String,
|
|
1367
2256
|
optional: true
|
|
1368
2257
|
},
|
|
1369
2258
|
normalizeShape: {
|
|
1370
2259
|
kind: "parsed",
|
|
1371
|
-
brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}"
|
|
2260
|
+
brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
|
|
1372
2261
|
parse: String,
|
|
1373
2262
|
optional: true
|
|
1374
2263
|
},
|
|
@@ -1389,6 +2278,23 @@ var toLabelStudioCommand = (0, import_core.buildCommand)({
|
|
|
1389
2278
|
brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_LABEL_STUDIO_PRECISION}`,
|
|
1390
2279
|
parse: Number,
|
|
1391
2280
|
optional: true
|
|
2281
|
+
},
|
|
2282
|
+
recursive: {
|
|
2283
|
+
kind: "boolean",
|
|
2284
|
+
brief: `Recursively search directories for files. Default: ${DEFAULT_RECURSIVE}`,
|
|
2285
|
+
optional: true
|
|
2286
|
+
},
|
|
2287
|
+
filePattern: {
|
|
2288
|
+
kind: "parsed",
|
|
2289
|
+
brief: `Regex pattern to match PPOCRLabel files (should match .txt files). Default: "${DEFAULT_PPOCR_FILE_PATTERN}"`,
|
|
2290
|
+
parse: String,
|
|
2291
|
+
optional: true
|
|
2292
|
+
},
|
|
2293
|
+
outputMode: {
|
|
2294
|
+
kind: "parsed",
|
|
2295
|
+
brief: `Output mode: "${OUTPUT_MODE_ANNOTATIONS}" for editable annotations (ground truth) or "${OUTPUT_MODE_PREDICTIONS}" for read-only predictions (pre-annotations). Default: "${DEFAULT_OUTPUT_MODE}"`,
|
|
2296
|
+
parse: String,
|
|
2297
|
+
optional: true
|
|
1392
2298
|
}
|
|
1393
2299
|
}
|
|
1394
2300
|
},
|
|
@@ -1399,11 +2305,11 @@ var toLabelStudioCommand = (0, import_core.buildCommand)({
|
|
|
1399
2305
|
|
|
1400
2306
|
// src/commands/toPPOCR/commands.ts
|
|
1401
2307
|
init_cjs_shims();
|
|
1402
|
-
var
|
|
2308
|
+
var import_core4 = require("@stricli/core");
|
|
1403
2309
|
init_constants();
|
|
1404
|
-
var toPPOCRCommand = (0,
|
|
2310
|
+
var toPPOCRCommand = (0, import_core4.buildCommand)({
|
|
1405
2311
|
loader: async () => {
|
|
1406
|
-
const { convertToPPOCR: convertToPPOCR2 } = await Promise.resolve().then(() => (
|
|
2312
|
+
const { convertToPPOCR: convertToPPOCR2 } = await Promise.resolve().then(() => (init_impl4(), impl_exports4));
|
|
1407
2313
|
return convertToPPOCR2;
|
|
1408
2314
|
},
|
|
1409
2315
|
parameters: {
|
|
@@ -1418,16 +2324,21 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
|
|
|
1418
2324
|
flags: {
|
|
1419
2325
|
outDir: {
|
|
1420
2326
|
kind: "parsed",
|
|
1421
|
-
brief:
|
|
2327
|
+
brief: "Output directory. If not specified, files are saved in the same directory as the source files",
|
|
1422
2328
|
parse: String,
|
|
1423
2329
|
optional: true
|
|
1424
2330
|
},
|
|
1425
2331
|
fileName: {
|
|
1426
2332
|
kind: "parsed",
|
|
1427
|
-
brief: `Output PPOCR file name. Default
|
|
2333
|
+
brief: `Output PPOCR file name. Default: "${DEFAULT_PPOCR_FILE_NAME}"`,
|
|
1428
2334
|
parse: String,
|
|
1429
2335
|
optional: true
|
|
1430
2336
|
},
|
|
2337
|
+
backup: {
|
|
2338
|
+
kind: "boolean",
|
|
2339
|
+
brief: `Create backup of existing files before overwriting. Default: ${DEFAULT_BACKUP}`,
|
|
2340
|
+
optional: true
|
|
2341
|
+
},
|
|
1431
2342
|
baseImageDir: {
|
|
1432
2343
|
kind: "parsed",
|
|
1433
2344
|
brief: 'Base directory path to prepend to image filenames in output (e.g., "ch" or "images/ch")',
|
|
@@ -1436,19 +2347,19 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
|
|
|
1436
2347
|
},
|
|
1437
2348
|
sortVertical: {
|
|
1438
2349
|
kind: "parsed",
|
|
1439
|
-
brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}"
|
|
2350
|
+
brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
|
|
1440
2351
|
parse: String,
|
|
1441
2352
|
optional: true
|
|
1442
2353
|
},
|
|
1443
2354
|
sortHorizontal: {
|
|
1444
2355
|
kind: "parsed",
|
|
1445
|
-
brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}"
|
|
2356
|
+
brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
|
|
1446
2357
|
parse: String,
|
|
1447
2358
|
optional: true
|
|
1448
2359
|
},
|
|
1449
2360
|
normalizeShape: {
|
|
1450
2361
|
kind: "parsed",
|
|
1451
|
-
brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}"
|
|
2362
|
+
brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
|
|
1452
2363
|
parse: String,
|
|
1453
2364
|
optional: true
|
|
1454
2365
|
},
|
|
@@ -1469,6 +2380,17 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
|
|
|
1469
2380
|
brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_PPOCR_PRECISION} (integers)`,
|
|
1470
2381
|
parse: Number,
|
|
1471
2382
|
optional: true
|
|
2383
|
+
},
|
|
2384
|
+
recursive: {
|
|
2385
|
+
kind: "boolean",
|
|
2386
|
+
brief: `Recursively search directories for files. Default: ${DEFAULT_RECURSIVE}`,
|
|
2387
|
+
optional: true
|
|
2388
|
+
},
|
|
2389
|
+
filePattern: {
|
|
2390
|
+
kind: "parsed",
|
|
2391
|
+
brief: `Regex pattern to match Label Studio files (should match .json files). Default: "${DEFAULT_LABEL_STUDIO_FILE_PATTERN}"`,
|
|
2392
|
+
parse: String,
|
|
2393
|
+
optional: true
|
|
1472
2394
|
}
|
|
1473
2395
|
}
|
|
1474
2396
|
},
|
|
@@ -1478,10 +2400,12 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
|
|
|
1478
2400
|
});
|
|
1479
2401
|
|
|
1480
2402
|
// src/app.ts
|
|
1481
|
-
var routes = (0,
|
|
2403
|
+
var routes = (0, import_core5.buildRouteMap)({
|
|
1482
2404
|
routes: {
|
|
1483
2405
|
toLabelStudio: toLabelStudioCommand,
|
|
1484
2406
|
toPPOCR: toPPOCRCommand,
|
|
2407
|
+
"enhance-labelstudio": enhanceLabelStudioCommand,
|
|
2408
|
+
"enhance-ppocr": enhancePPOCRCommand,
|
|
1485
2409
|
install: (0, import_auto_complete.buildInstallCommand)("label-studio-converter", {
|
|
1486
2410
|
bash: "__label-studio-converter_bash_complete"
|
|
1487
2411
|
}),
|
|
@@ -1495,7 +2419,7 @@ var routes = (0, import_core3.buildRouteMap)({
|
|
|
1495
2419
|
}
|
|
1496
2420
|
}
|
|
1497
2421
|
});
|
|
1498
|
-
var app = (0,
|
|
2422
|
+
var app = (0, import_core5.buildApplication)(routes, {
|
|
1499
2423
|
name: "label-studio-converter",
|
|
1500
2424
|
versionInfo: {
|
|
1501
2425
|
currentVersion: version
|
|
@@ -1522,9 +2446,9 @@ function buildContext(process2) {
|
|
|
1522
2446
|
if (process.env.COMP_LINE?.endsWith(" ")) {
|
|
1523
2447
|
inputs.push("");
|
|
1524
2448
|
}
|
|
1525
|
-
await (0,
|
|
2449
|
+
await (0, import_core6.proposeCompletions)(app, inputs, buildContext(process));
|
|
1526
2450
|
try {
|
|
1527
|
-
for (const { completion } of await (0,
|
|
2451
|
+
for (const { completion } of await (0, import_core6.proposeCompletions)(
|
|
1528
2452
|
app,
|
|
1529
2453
|
inputs,
|
|
1530
2454
|
buildContext(process)
|