label-studio-converter 1.0.0 → 1.2.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/dist/cli.cjs CHANGED
@@ -38,7 +38,7 @@ var init_cjs_shims = __esm({
38
38
  });
39
39
 
40
40
  // src/constants.ts
41
- var OUTPUT_BASE_DIR, 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;
41
+ var OUTPUT_BASE_DIR, 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;
42
42
  var init_constants = __esm({
43
43
  "src/constants.ts"() {
44
44
  "use strict";
@@ -59,247 +59,578 @@ var init_constants = __esm({
59
59
  SORT_HORIZONTAL_LTR = "ltr";
60
60
  SORT_HORIZONTAL_RTL = "rtl";
61
61
  DEFAULT_SORT_HORIZONTAL = SORT_HORIZONTAL_NONE;
62
+ SHAPE_NORMALIZE_NONE = "none";
63
+ SHAPE_NORMALIZE_RECTANGLE = "rectangle";
64
+ DEFAULT_SHAPE_NORMALIZE = SHAPE_NORMALIZE_NONE;
65
+ DEFAULT_WIDTH_INCREMENT = 0;
66
+ DEFAULT_HEIGHT_INCREMENT = 0;
67
+ DEFAULT_LABEL_STUDIO_PRECISION = -1;
68
+ DEFAULT_PPOCR_PRECISION = 0;
62
69
  }
63
70
  });
64
71
 
65
- // src/lib/ppocr-label.ts
66
- var import_node_crypto, import_node_fs, import_node_path, import_image_size, ppocrToLabelStudio, ppocrToFullLabelStudio, ppocrToMinLabelStudio;
67
- var init_ppocr_label = __esm({
68
- "src/lib/ppocr-label.ts"() {
72
+ // src/lib/geometry.ts
73
+ function roundToPrecision(value, precision) {
74
+ if (precision < 0) {
75
+ return value;
76
+ }
77
+ const multiplier = Math.pow(10, precision);
78
+ return Math.round(value * multiplier) / multiplier;
79
+ }
80
+ function roundPoints(points, precision) {
81
+ if (precision < 0) {
82
+ return points;
83
+ }
84
+ return points.map(
85
+ ([x, y]) => [roundToPrecision(x, precision), roundToPrecision(y, precision)]
86
+ );
87
+ }
88
+ function calculateCenter(points) {
89
+ const sum = points.reduce((acc, [x, y]) => [acc[0] + x, acc[1] + y], [
90
+ 0,
91
+ 0
92
+ ]);
93
+ return [sum[0] / points.length, sum[1] / points.length];
94
+ }
95
+ function getMinimumBoundingRect(points) {
96
+ const minX = Math.min(...points.map(([x]) => x));
97
+ const maxX = Math.max(...points.map(([x]) => x));
98
+ const minY = Math.min(...points.map(([, y]) => y));
99
+ const maxY = Math.max(...points.map(([, y]) => y));
100
+ return {
101
+ minX,
102
+ minY,
103
+ maxX,
104
+ maxY,
105
+ width: maxX - minX,
106
+ height: maxY - minY
107
+ };
108
+ }
109
+ function normalizeShape(points) {
110
+ if (points.length < 3) {
111
+ return points;
112
+ }
113
+ const { minX, minY, maxX, maxY } = getMinimumBoundingRect(points);
114
+ return [
115
+ [minX, minY],
116
+ [maxX, minY],
117
+ [maxX, maxY],
118
+ [minX, maxY]
119
+ ];
120
+ }
121
+ function resizeBoundingBox(points, widthIncrement, heightIncrement) {
122
+ if (points.length === 0) {
123
+ return points;
124
+ }
125
+ const center = calculateCenter(points);
126
+ const bbox = getMinimumBoundingRect(points);
127
+ const newWidth = Math.max(1, bbox.width + widthIncrement);
128
+ const newHeight = Math.max(1, bbox.height + heightIncrement);
129
+ const scaleX = newWidth / bbox.width;
130
+ const scaleY = newHeight / bbox.height;
131
+ return points.map(([x, y]) => {
132
+ const relX = x - center[0];
133
+ const relY = y - center[1];
134
+ return [center[0] + relX * scaleX, center[1] + relY * scaleY];
135
+ });
136
+ }
137
+ function transformPoints(points, options) {
138
+ let result = points;
139
+ if (options.normalizeShape && options.normalizeShape === "rectangle") {
140
+ result = normalizeShape(result);
141
+ }
142
+ if (options.widthIncrement !== void 0 || options.heightIncrement !== void 0) {
143
+ result = resizeBoundingBox(
144
+ result,
145
+ options.widthIncrement ?? 0,
146
+ options.heightIncrement ?? 0
147
+ );
148
+ }
149
+ return result;
150
+ }
151
+ var init_geometry = __esm({
152
+ "src/lib/geometry.ts"() {
69
153
  "use strict";
70
154
  init_cjs_shims();
71
- import_node_crypto = require("crypto");
72
- import_node_fs = require("fs");
73
- import_node_path = require("path");
74
- import_image_size = __toESM(require("image-size"), 1);
75
- init_constants();
76
- ppocrToLabelStudio = async (data, options) => {
77
- const {
78
- imagePath,
79
- baseServerUrl,
80
- inputDir,
81
- toFullJson = true,
82
- taskId = 1,
83
- labelName = DEFAULT_LABEL_NAME
84
- } = options || {};
85
- if (toFullJson) {
86
- return ppocrToFullLabelStudio(
87
- data,
88
- imagePath,
89
- baseServerUrl,
90
- inputDir,
91
- taskId,
92
- labelName
93
- );
94
- } else {
95
- return ppocrToMinLabelStudio(
96
- data,
97
- imagePath,
98
- baseServerUrl,
99
- inputDir,
100
- labelName
101
- );
155
+ }
156
+ });
157
+
158
+ // src/lib/sort.ts
159
+ function getBoundingBoxCenter(points) {
160
+ let minX = Infinity;
161
+ let minY = Infinity;
162
+ let maxX = -Infinity;
163
+ let maxY = -Infinity;
164
+ for (const [x, y] of points) {
165
+ if (x !== void 0 && y !== void 0) {
166
+ minX = Math.min(minX, x);
167
+ minY = Math.min(minY, y);
168
+ maxX = Math.max(maxX, x);
169
+ maxY = Math.max(maxY, y);
170
+ }
171
+ }
172
+ return {
173
+ x: (minX + maxX) / 2,
174
+ y: (minY + maxY) / 2,
175
+ width: maxX - minX,
176
+ height: maxY - minY
177
+ };
178
+ }
179
+ function sortBoundingBoxes(annotations, verticalSort, horizontalSort) {
180
+ if (verticalSort === SORT_VERTICAL_NONE && horizontalSort === SORT_HORIZONTAL_NONE) {
181
+ return annotations;
182
+ }
183
+ const sorted = [...annotations];
184
+ const isVerticalText = sorted.length > 0 && (() => {
185
+ const verticalCount = sorted.filter((ann) => {
186
+ const center = getBoundingBoxCenter(ann.points);
187
+ return center.height > center.width * 1.5;
188
+ }).length;
189
+ return verticalCount > sorted.length / 2;
190
+ })();
191
+ if (horizontalSort === SORT_HORIZONTAL_RTL && verticalSort !== SORT_VERTICAL_NONE && isVerticalText) {
192
+ const annotationsWithCenters = sorted.map((ann) => ({
193
+ annotation: ann,
194
+ center: getBoundingBoxCenter(ann.points)
195
+ }));
196
+ const columns = [];
197
+ for (const item of annotationsWithCenters) {
198
+ let addedToColumn = false;
199
+ for (const column of columns) {
200
+ const avgX = column.reduce((sum, c) => sum + c.center.x, 0) / column.length;
201
+ if (Math.abs(item.center.x - avgX) < GROUPING_TOLERANCE) {
202
+ column.push(item);
203
+ addedToColumn = true;
204
+ break;
205
+ }
102
206
  }
103
- };
104
- ppocrToFullLabelStudio = (data, imagePath, baseServerUrl, inputDir, taskId = 1, labelName = DEFAULT_LABEL_NAME) => {
105
- const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
106
- const now = (/* @__PURE__ */ new Date()).toISOString();
107
- let original_width = 1920;
108
- let original_height = 1080;
109
- const resolvedImagePath = inputDir ? (0, import_node_path.join)(inputDir, imagePath) : imagePath;
110
- if (!(0, import_node_fs.existsSync)(resolvedImagePath)) {
111
- throw new Error(`Image file not found: ${resolvedImagePath}`);
207
+ if (!addedToColumn) {
208
+ columns.push([item]);
112
209
  }
113
- const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
114
- const dimensions = (0, import_image_size.default)(buffer);
115
- if (!dimensions.width || !dimensions.height) {
116
- throw new Error(
117
- `Failed to read image dimensions from: ${resolvedImagePath}`
118
- );
210
+ }
211
+ columns.sort((colA, colB) => {
212
+ const avgXA = colA.reduce((sum, c) => sum + c.center.x, 0) / colA.length;
213
+ const avgXB = colB.reduce((sum, c) => sum + c.center.x, 0) / colB.length;
214
+ return avgXB - avgXA;
215
+ });
216
+ for (const column of columns) {
217
+ column.sort((a, b) => {
218
+ return verticalSort === SORT_VERTICAL_TOP_BOTTOM ? a.center.y - b.center.y : b.center.y - a.center.y;
219
+ });
220
+ }
221
+ return columns.flat().map((item) => item.annotation);
222
+ }
223
+ sorted.sort((a, b) => {
224
+ const centerA = getBoundingBoxCenter(a.points);
225
+ const centerB = getBoundingBoxCenter(b.points);
226
+ if (verticalSort !== SORT_VERTICAL_NONE) {
227
+ const yDiff = verticalSort === SORT_VERTICAL_TOP_BOTTOM ? centerA.y - centerB.y : centerB.y - centerA.y;
228
+ if (Math.abs(yDiff) > GROUPING_TOLERANCE) {
229
+ return yDiff;
119
230
  }
120
- original_width = dimensions.width;
121
- original_height = dimensions.height;
122
- const fileName = imagePath.split("/").pop() || imagePath;
123
- const result = [
124
- {
125
- id: taskId,
126
- annotations: [
127
- {
128
- id: taskId,
129
- completed_by: 1,
130
- result: data.map((item) => {
131
- const { points } = item;
132
- const annotationId = (0, import_node_crypto.randomUUID)().slice(0, 10);
133
- const polygonPoints = points.map(([x, y]) => [
134
- (x ?? 0) / original_width * 100,
135
- (y ?? 0) / original_height * 100
231
+ }
232
+ if (horizontalSort !== SORT_HORIZONTAL_NONE) {
233
+ return horizontalSort === SORT_HORIZONTAL_LTR ? centerA.x - centerB.x : centerB.x - centerA.x;
234
+ }
235
+ return 0;
236
+ });
237
+ return sorted;
238
+ }
239
+ var GROUPING_TOLERANCE;
240
+ var init_sort = __esm({
241
+ "src/lib/sort.ts"() {
242
+ "use strict";
243
+ init_cjs_shims();
244
+ init_constants();
245
+ GROUPING_TOLERANCE = 50;
246
+ }
247
+ });
248
+
249
+ // src/lib/enhance.ts
250
+ function enhancePPOCRLabel(data, options) {
251
+ const {
252
+ sortVertical,
253
+ sortHorizontal,
254
+ normalizeShape: normalizeShape2,
255
+ widthIncrement = 0,
256
+ heightIncrement = 0,
257
+ precision = 0
258
+ } = options;
259
+ let enhanced = data;
260
+ if (sortVertical && sortHorizontal) {
261
+ enhanced = sortBoundingBoxes(enhanced, sortVertical, sortHorizontal);
262
+ }
263
+ if (normalizeShape2 || widthIncrement !== 0 || heightIncrement !== 0) {
264
+ enhanced = enhanced.map((annotation) => {
265
+ let points = transformPoints(annotation.points, {
266
+ normalizeShape: normalizeShape2,
267
+ widthIncrement,
268
+ heightIncrement
269
+ });
270
+ points = roundPoints(points, precision);
271
+ return {
272
+ ...annotation,
273
+ points
274
+ };
275
+ });
276
+ }
277
+ return enhanced;
278
+ }
279
+ var init_enhance = __esm({
280
+ "src/lib/enhance.ts"() {
281
+ "use strict";
282
+ init_cjs_shims();
283
+ init_geometry();
284
+ init_sort();
285
+ }
286
+ });
287
+
288
+ // src/lib/label-studio.ts
289
+ var turf, labelStudioToPPOCR, minLabelStudioToPPOCR, enhanceLabelStudioData;
290
+ var init_label_studio = __esm({
291
+ "src/lib/label-studio.ts"() {
292
+ "use strict";
293
+ init_cjs_shims();
294
+ turf = __toESM(require("@turf/turf"), 1);
295
+ init_enhance();
296
+ init_geometry();
297
+ labelStudioToPPOCR = async (data, options) => {
298
+ const {
299
+ baseImageDir,
300
+ normalizeShape: normalizeShape2,
301
+ widthIncrement = 0,
302
+ heightIncrement = 0,
303
+ precision = 0
304
+ } = options || {};
305
+ const resultMap = /* @__PURE__ */ new Map();
306
+ for (const task of data) {
307
+ let imagePath = task.file_upload || "";
308
+ if (task.data.ocr) {
309
+ const urlPath = task.data.ocr.replace(/^https?:\/\/[^/]+\//, "");
310
+ imagePath = decodeURIComponent(urlPath);
311
+ }
312
+ if (baseImageDir) {
313
+ imagePath = `${baseImageDir}/${task.file_upload || imagePath.split("/").pop() || imagePath}`;
314
+ }
315
+ const imageAnnotations = [];
316
+ for (const annotation of task.annotations) {
317
+ const groupedById = /* @__PURE__ */ new Map();
318
+ for (const resultItem of annotation.result) {
319
+ const { id } = resultItem;
320
+ if (!groupedById.has(id)) {
321
+ groupedById.set(id, []);
322
+ }
323
+ groupedById.get(id).push(resultItem);
324
+ }
325
+ for (const [_, resultItems] of groupedById) {
326
+ let points;
327
+ let transcription = "";
328
+ for (const resultItem of resultItems) {
329
+ if ("points" in resultItem.value && resultItem.value.points) {
330
+ const { points: valuePoints } = resultItem.value;
331
+ const { original_width, original_height } = resultItem;
332
+ points = valuePoints.map(([x, y]) => [
333
+ (x ?? 0) * original_width / 100,
334
+ (y ?? 0) * original_height / 100
136
335
  ]);
137
- return [
138
- // 1. Polygon geometry only
139
- {
140
- original_width,
141
- original_height,
142
- image_rotation: 0,
143
- value: {
144
- points: polygonPoints,
145
- closed: true
146
- },
147
- id: annotationId,
148
- from_name: "poly",
149
- to_name: "image",
150
- type: "polygon",
151
- origin: "manual"
152
- },
153
- // 2. Labels with polygon geometry
154
- {
155
- original_width,
156
- original_height,
157
- image_rotation: 0,
158
- value: {
159
- points: polygonPoints,
160
- closed: true,
161
- labels: [labelName]
162
- },
163
- id: annotationId,
164
- from_name: "label",
165
- to_name: "image",
166
- type: "labels",
167
- origin: "manual"
168
- },
169
- // 3. Textarea with polygon geometry and text
170
- {
171
- original_width,
172
- original_height,
173
- image_rotation: 0,
174
- value: {
175
- points: polygonPoints,
176
- closed: true,
177
- text: [item.transcription]
178
- },
179
- id: annotationId,
180
- from_name: "transcription",
181
- to_name: "image",
182
- type: "textarea",
183
- origin: "manual"
184
- }
336
+ } else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
337
+ const { x, y, width, height } = resultItem.value;
338
+ const { original_width, original_height } = resultItem;
339
+ const absX = x * original_width / 100;
340
+ const absY = y * original_height / 100;
341
+ const absWidth = width * original_width / 100;
342
+ const absHeight = height * original_height / 100;
343
+ points = [
344
+ [absX, absY],
345
+ [absX + absWidth, absY],
346
+ [absX + absWidth, absY + absHeight],
347
+ [absX, absY + absHeight]
185
348
  ];
186
- }).flat(),
187
- was_cancelled: false,
188
- ground_truth: false,
189
- created_at: now,
190
- updated_at: now,
191
- draft_created_at: now,
192
- lead_time: 0,
193
- prediction: {},
194
- result_count: data.length * 3,
195
- unique_id: (0, import_node_crypto.randomUUID)(),
196
- import_id: null,
197
- last_action: null,
198
- bulk_created: false,
199
- task: taskId,
200
- project: 1,
201
- updated_by: 1,
202
- parent_prediction: null,
203
- parent_annotation: null,
204
- last_created_by: null
349
+ }
350
+ if ("text" in resultItem.value && Array.isArray(resultItem.value.text)) {
351
+ transcription = resultItem.value.text[0] || "";
352
+ }
205
353
  }
206
- ],
207
- file_upload: fileName,
208
- drafts: [],
209
- predictions: [],
210
- data: { ocr: `${newBaseServerUrl}${imagePath}` },
211
- meta: {},
212
- created_at: now,
213
- updated_at: now,
214
- allow_skip: false,
215
- inner_id: taskId,
216
- total_annotations: 1,
217
- cancelled_annotations: 0,
218
- total_predictions: 0,
219
- comment_count: 0,
220
- unresolved_comment_count: 0,
221
- last_comment_updated_at: null,
222
- project: 1,
223
- updated_by: 1,
224
- comment_authors: []
354
+ if (points && points.length > 0) {
355
+ points = transformPoints(points, {
356
+ normalizeShape: normalizeShape2,
357
+ widthIncrement,
358
+ heightIncrement
359
+ });
360
+ points = roundPoints(points, precision);
361
+ let dt_score = 1;
362
+ try {
363
+ const firstPoint = points[0];
364
+ if (firstPoint) {
365
+ const polygon2 = turf.polygon([points.concat([firstPoint])]);
366
+ const area2 = turf.area(polygon2);
367
+ dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
368
+ }
369
+ } catch {
370
+ dt_score = 0.8;
371
+ }
372
+ imageAnnotations.push({
373
+ transcription,
374
+ points,
375
+ dt_score
376
+ });
377
+ }
378
+ }
379
+ }
380
+ if (imageAnnotations.length > 0) {
381
+ resultMap.set(imagePath, imageAnnotations);
225
382
  }
226
- ];
227
- return result;
228
- };
229
- ppocrToMinLabelStudio = (data, imagePath, baseServerUrl, inputDir, labelName = "text") => {
230
- const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
231
- const now = (/* @__PURE__ */ new Date()).toISOString();
232
- let original_width = 1920;
233
- let original_height = 1080;
234
- const resolvedImagePath = inputDir ? (0, import_node_path.join)(inputDir, imagePath) : imagePath;
235
- if (!(0, import_node_fs.existsSync)(resolvedImagePath)) {
236
- throw new Error(`Image file not found: ${resolvedImagePath}`);
237
383
  }
238
- const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
239
- const dimensions = (0, import_image_size.default)(buffer);
240
- if (!dimensions.width || !dimensions.height) {
241
- throw new Error(
242
- `Failed to read image dimensions from: ${resolvedImagePath}`
384
+ return resultMap;
385
+ };
386
+ minLabelStudioToPPOCR = async (data, options) => {
387
+ const {
388
+ baseImageDir,
389
+ normalizeShape: normalizeShape2,
390
+ widthIncrement = 0,
391
+ heightIncrement = 0,
392
+ precision = 0
393
+ } = options || {};
394
+ const resultMap = /* @__PURE__ */ new Map();
395
+ for (const item of data) {
396
+ let imagePath = item.ocr || "";
397
+ if (imagePath) {
398
+ imagePath = decodeURIComponent(
399
+ imagePath.replace(/^https?:\/\/[^/]+\//, "")
400
+ );
401
+ }
402
+ if (baseImageDir) {
403
+ imagePath = `${baseImageDir}/${imagePath.split("/").pop() || imagePath}`;
404
+ }
405
+ const numAnnotations = Math.max(
406
+ item.poly?.length || 0,
407
+ item.bbox?.length || 0,
408
+ item.transcription?.length || 0
243
409
  );
244
- }
245
- original_width = dimensions.width;
246
- original_height = dimensions.height;
247
- return data.map((item, index) => {
248
- const { points } = item;
249
- let minX = Infinity;
250
- let minY = Infinity;
251
- let maxX = -Infinity;
252
- let maxY = -Infinity;
253
- for (const point of points) {
254
- const [x, y] = point;
255
- if (x !== void 0 && y !== void 0) {
256
- minX = Math.min(minX, x);
257
- minY = Math.min(minY, y);
258
- maxX = Math.max(maxX, x);
259
- maxY = Math.max(maxY, y);
410
+ for (let i = 0; i < numAnnotations; i++) {
411
+ let points;
412
+ if (item.poly && item.poly.length > i && item.poly[i]) {
413
+ const poly = item.poly[i];
414
+ if (poly) {
415
+ const { points: polyPoints } = poly;
416
+ points = polyPoints;
417
+ }
418
+ } else if (item.bbox && item.bbox.length > i && item.bbox[i]) {
419
+ const bbox = item.bbox[i];
420
+ if (bbox) {
421
+ const { x, y, width, height } = bbox;
422
+ points = [
423
+ [x, y],
424
+ [x + width, y],
425
+ [x + width, y + height],
426
+ [x, y + height]
427
+ ];
428
+ }
429
+ }
430
+ if (!points) {
431
+ continue;
432
+ }
433
+ points = transformPoints(points, {
434
+ normalizeShape: normalizeShape2,
435
+ widthIncrement,
436
+ heightIncrement
437
+ });
438
+ points = roundPoints(points, precision);
439
+ const transcription = item.transcription && item.transcription.length > i ? item.transcription[i] : "";
440
+ let dt_score = 1;
441
+ try {
442
+ const firstPoint = points[0];
443
+ if (firstPoint) {
444
+ const polygon2 = turf.polygon([points.concat([firstPoint])]);
445
+ const area2 = turf.area(polygon2);
446
+ dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
447
+ }
448
+ } catch {
449
+ dt_score = 0.8;
260
450
  }
451
+ const annotation = {
452
+ transcription: transcription ?? "",
453
+ points,
454
+ dt_score
455
+ };
456
+ if (!resultMap.has(imagePath)) {
457
+ resultMap.set(imagePath, []);
458
+ }
459
+ resultMap.get(imagePath).push(annotation);
261
460
  }
262
- const width = maxX - minX;
263
- const height = maxY - minY;
264
- return {
265
- ocr: encodeURI(`${newBaseServerUrl}${imagePath}`),
266
- id: index + 1,
267
- bbox: [
268
- {
269
- x: minX,
270
- y: minY,
271
- width,
272
- height,
273
- rotation: 0,
274
- original_width,
275
- original_height
461
+ }
462
+ return resultMap;
463
+ };
464
+ enhanceLabelStudioData = async (data, isFull, options) => {
465
+ const {
466
+ sortVertical,
467
+ sortHorizontal,
468
+ normalizeShape: normalizeShape2,
469
+ widthIncrement = 0,
470
+ heightIncrement = 0,
471
+ precision = 0
472
+ } = options;
473
+ if (isFull) {
474
+ const fullData = data;
475
+ return fullData.map((task) => ({
476
+ ...task,
477
+ annotations: task.annotations.map((annotation) => {
478
+ const groupedById = /* @__PURE__ */ new Map();
479
+ for (const resultItem of annotation.result) {
480
+ const { id } = resultItem;
481
+ if (!groupedById.has(id)) {
482
+ groupedById.set(id, []);
483
+ }
484
+ groupedById.get(id).push(resultItem);
276
485
  }
277
- ],
278
- label: [
279
- {
280
- points,
281
- closed: true,
282
- labels: [labelName],
283
- original_width,
284
- original_height
486
+ const enhancedResult = [];
487
+ for (const [_, resultItems] of groupedById) {
488
+ let ppocrAnnotations = [];
489
+ for (const resultItem of resultItems) {
490
+ let points;
491
+ if ("points" in resultItem.value && resultItem.value.points) {
492
+ const { points: valuePoints } = resultItem.value;
493
+ const { original_width, original_height } = resultItem;
494
+ points = valuePoints.map(([x, y]) => [
495
+ (x ?? 0) * original_width / 100,
496
+ (y ?? 0) * original_height / 100
497
+ ]);
498
+ } else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
499
+ const { x, y, width, height } = resultItem.value;
500
+ const { original_width, original_height } = resultItem;
501
+ const absX = x * original_width / 100;
502
+ const absY = y * original_height / 100;
503
+ const absWidth = width * original_width / 100;
504
+ const absHeight = height * original_height / 100;
505
+ points = [
506
+ [absX, absY],
507
+ [absX + absWidth, absY],
508
+ [absX + absWidth, absY + absHeight],
509
+ [absX, absY + absHeight]
510
+ ];
511
+ }
512
+ if (points) {
513
+ ppocrAnnotations.push({
514
+ transcription: "",
515
+ points,
516
+ dt_score: 1
517
+ });
518
+ }
519
+ }
520
+ if (ppocrAnnotations.length > 0) {
521
+ ppocrAnnotations = enhancePPOCRLabel(ppocrAnnotations, {
522
+ sortVertical,
523
+ sortHorizontal,
524
+ normalizeShape: normalizeShape2,
525
+ widthIncrement,
526
+ heightIncrement,
527
+ precision
528
+ });
529
+ for (let i = 0; i < resultItems.length; i++) {
530
+ const resultItem = resultItems[i];
531
+ const enhanced = ppocrAnnotations[i];
532
+ if (!enhanced) {
533
+ enhancedResult.push(resultItem);
534
+ continue;
535
+ }
536
+ if ("points" in resultItem.value && resultItem.value.points) {
537
+ const { original_width, original_height } = resultItem;
538
+ enhancedResult.push({
539
+ ...resultItem,
540
+ value: {
541
+ ...resultItem.value,
542
+ points: enhanced.points.map(
543
+ ([x, y]) => [
544
+ (x ?? 0) / original_width * 100,
545
+ (y ?? 0) / original_height * 100
546
+ ]
547
+ )
548
+ }
549
+ });
550
+ } else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
551
+ const { original_width, original_height } = resultItem;
552
+ const xs = enhanced.points.map(([x]) => x ?? 0);
553
+ const ys = enhanced.points.map(([, y]) => y ?? 0);
554
+ const minX = Math.min(...xs);
555
+ const maxX = Math.max(...xs);
556
+ const minY = Math.min(...ys);
557
+ const maxY = Math.max(...ys);
558
+ enhancedResult.push({
559
+ ...resultItem,
560
+ value: {
561
+ ...resultItem.value,
562
+ x: minX / original_width * 100,
563
+ y: minY / original_height * 100,
564
+ width: (maxX - minX) / original_width * 100,
565
+ height: (maxY - minY) / original_height * 100
566
+ }
567
+ });
568
+ } else {
569
+ enhancedResult.push(resultItem);
570
+ }
571
+ }
572
+ } else {
573
+ enhancedResult.push(...resultItems);
574
+ }
285
575
  }
286
- ],
287
- transcription: [item.transcription],
288
- poly: [
289
- {
290
- points,
291
- closed: true,
292
- original_width,
293
- original_height
576
+ return {
577
+ ...annotation,
578
+ result: enhancedResult
579
+ };
580
+ })
581
+ }));
582
+ } else {
583
+ const minData = data;
584
+ return minData.map((item) => {
585
+ let ppocrAnnotations = [];
586
+ const numAnnotations = Math.max(
587
+ item.poly?.length || 0,
588
+ item.bbox?.length || 0,
589
+ item.transcription?.length || 0
590
+ );
591
+ for (let i = 0; i < numAnnotations; i++) {
592
+ let points;
593
+ if (item.poly && item.poly.length > i && item.poly[i]) {
594
+ const { points: polyPoints } = item.poly[i];
595
+ points = polyPoints;
596
+ } else if (item.bbox && item.bbox.length > i && item.bbox[i]) {
597
+ const { x, y, width, height } = item.bbox[i];
598
+ points = [
599
+ [x, y],
600
+ [x + width, y],
601
+ [x + width, y + height],
602
+ [x, y + height]
603
+ ];
294
604
  }
295
- ],
296
- annotator: 1,
297
- annotation_id: index + 1,
298
- created_at: now,
299
- updated_at: now,
300
- lead_time: 0
301
- };
302
- });
605
+ if (points) {
606
+ ppocrAnnotations.push({
607
+ transcription: item.transcription && item.transcription.length > i ? item.transcription[i] ?? "" : "",
608
+ points,
609
+ dt_score: 1
610
+ });
611
+ }
612
+ }
613
+ if (ppocrAnnotations.length > 0) {
614
+ ppocrAnnotations = enhancePPOCRLabel(ppocrAnnotations, {
615
+ sortVertical,
616
+ sortHorizontal,
617
+ normalizeShape: normalizeShape2,
618
+ widthIncrement,
619
+ heightIncrement,
620
+ precision
621
+ });
622
+ const newPoly = ppocrAnnotations.map((ann) => ({
623
+ points: ann.points
624
+ }));
625
+ const { bbox: _, ...itemWithoutBbox } = item;
626
+ return {
627
+ ...itemWithoutBbox,
628
+ poly: newPoly
629
+ };
630
+ }
631
+ return item;
632
+ });
633
+ }
303
634
  };
304
635
  }
305
636
  });
@@ -320,6 +651,7 @@ var init_schema = __esm({
320
651
  completed_by: import_zod.default.number(),
321
652
  result: import_zod.default.array(
322
653
  import_zod.default.union([
654
+ // Most specific rectangle variants first (with text or labels)
323
655
  import_zod.default.object({
324
656
  original_width: import_zod.default.number(),
325
657
  original_height: import_zod.default.number(),
@@ -329,7 +661,8 @@ var init_schema = __esm({
329
661
  y: import_zod.default.number(),
330
662
  width: import_zod.default.number(),
331
663
  height: import_zod.default.number(),
332
- rotation: import_zod.default.number()
664
+ rotation: import_zod.default.number(),
665
+ text: import_zod.default.array(import_zod.default.string())
333
666
  }),
334
667
  id: import_zod.default.string(),
335
668
  from_name: import_zod.default.string(),
@@ -355,6 +688,7 @@ var init_schema = __esm({
355
688
  type: import_zod.default.string(),
356
689
  origin: import_zod.default.string()
357
690
  }),
691
+ // Base rectangle without text or labels
358
692
  import_zod.default.object({
359
693
  original_width: import_zod.default.number(),
360
694
  original_height: import_zod.default.number(),
@@ -364,8 +698,7 @@ var init_schema = __esm({
364
698
  y: import_zod.default.number(),
365
699
  width: import_zod.default.number(),
366
700
  height: import_zod.default.number(),
367
- rotation: import_zod.default.number(),
368
- text: import_zod.default.array(import_zod.default.string())
701
+ rotation: import_zod.default.number()
369
702
  }),
370
703
  id: import_zod.default.string(),
371
704
  from_name: import_zod.default.string(),
@@ -373,13 +706,15 @@ var init_schema = __esm({
373
706
  type: import_zod.default.string(),
374
707
  origin: import_zod.default.string()
375
708
  }),
709
+ // Most specific polygon variants first (with text or labels)
376
710
  import_zod.default.object({
377
711
  original_width: import_zod.default.number(),
378
712
  original_height: import_zod.default.number(),
379
713
  image_rotation: import_zod.default.number(),
380
714
  value: import_zod.default.object({
381
715
  points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
382
- closed: import_zod.default.boolean()
716
+ closed: import_zod.default.boolean(),
717
+ text: import_zod.default.array(import_zod.default.string())
383
718
  }),
384
719
  id: import_zod.default.string(),
385
720
  from_name: import_zod.default.string(),
@@ -402,14 +737,14 @@ var init_schema = __esm({
402
737
  type: import_zod.default.string(),
403
738
  origin: import_zod.default.string()
404
739
  }),
740
+ // Base polygon without text or labels
405
741
  import_zod.default.object({
406
742
  original_width: import_zod.default.number(),
407
743
  original_height: import_zod.default.number(),
408
744
  image_rotation: import_zod.default.number(),
409
745
  value: import_zod.default.object({
410
746
  points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
411
- closed: import_zod.default.boolean(),
412
- text: import_zod.default.array(import_zod.default.string())
747
+ closed: import_zod.default.boolean()
413
748
  }),
414
749
  id: import_zod.default.string(),
415
750
  from_name: import_zod.default.string(),
@@ -448,6 +783,7 @@ var init_schema = __esm({
448
783
  created_ago: import_zod.default.string(),
449
784
  result: import_zod.default.array(
450
785
  import_zod.default.union([
786
+ // Most specific rectangle variants first (with text or labels)
451
787
  import_zod.default.object({
452
788
  original_width: import_zod.default.number(),
453
789
  original_height: import_zod.default.number(),
@@ -457,7 +793,8 @@ var init_schema = __esm({
457
793
  y: import_zod.default.number(),
458
794
  width: import_zod.default.number(),
459
795
  height: import_zod.default.number(),
460
- rotation: import_zod.default.number()
796
+ rotation: import_zod.default.number(),
797
+ text: import_zod.default.array(import_zod.default.string())
461
798
  }),
462
799
  id: import_zod.default.string(),
463
800
  from_name: import_zod.default.string(),
@@ -483,6 +820,7 @@ var init_schema = __esm({
483
820
  type: import_zod.default.string(),
484
821
  origin: import_zod.default.string()
485
822
  }),
823
+ // Base rectangle without text or labels
486
824
  import_zod.default.object({
487
825
  original_width: import_zod.default.number(),
488
826
  original_height: import_zod.default.number(),
@@ -492,8 +830,7 @@ var init_schema = __esm({
492
830
  y: import_zod.default.number(),
493
831
  width: import_zod.default.number(),
494
832
  height: import_zod.default.number(),
495
- rotation: import_zod.default.number(),
496
- text: import_zod.default.array(import_zod.default.string())
833
+ rotation: import_zod.default.number()
497
834
  }),
498
835
  id: import_zod.default.string(),
499
836
  from_name: import_zod.default.string(),
@@ -501,13 +838,15 @@ var init_schema = __esm({
501
838
  type: import_zod.default.string(),
502
839
  origin: import_zod.default.string()
503
840
  }),
841
+ // Most specific polygon variants first (with text or labels)
504
842
  import_zod.default.object({
505
843
  original_width: import_zod.default.number(),
506
844
  original_height: import_zod.default.number(),
507
845
  image_rotation: import_zod.default.number(),
508
846
  value: import_zod.default.object({
509
847
  points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
510
- closed: import_zod.default.boolean()
848
+ closed: import_zod.default.boolean(),
849
+ text: import_zod.default.array(import_zod.default.string())
511
850
  }),
512
851
  id: import_zod.default.string(),
513
852
  from_name: import_zod.default.string(),
@@ -530,14 +869,14 @@ var init_schema = __esm({
530
869
  type: import_zod.default.string(),
531
870
  origin: import_zod.default.string()
532
871
  }),
872
+ // Base polygon without text or labels
533
873
  import_zod.default.object({
534
874
  original_width: import_zod.default.number(),
535
875
  original_height: import_zod.default.number(),
536
876
  image_rotation: import_zod.default.number(),
537
877
  value: import_zod.default.object({
538
878
  points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
539
- closed: import_zod.default.boolean(),
540
- text: import_zod.default.array(import_zod.default.string())
879
+ closed: import_zod.default.boolean()
541
880
  }),
542
881
  id: import_zod.default.string(),
543
882
  from_name: import_zod.default.string(),
@@ -588,7 +927,7 @@ var init_schema = __esm({
588
927
  original_width: import_zod.default.number(),
589
928
  original_height: import_zod.default.number()
590
929
  })
591
- ),
930
+ ).optional().default([]),
592
931
  label: import_zod.default.array(
593
932
  import_zod.default.union([
594
933
  import_zod.default.object({
@@ -609,8 +948,11 @@ var init_schema = __esm({
609
948
  original_height: import_zod.default.number()
610
949
  })
611
950
  ])
612
- ),
613
- transcription: import_zod.default.array(import_zod.default.string()),
951
+ ).optional().default([]),
952
+ transcription: import_zod.default.union([import_zod.default.string(), import_zod.default.array(import_zod.default.string())]).optional().transform((val) => {
953
+ if (!val) return [];
954
+ return Array.isArray(val) ? val : [val];
955
+ }),
614
956
  poly: import_zod.default.array(
615
957
  import_zod.default.object({
616
958
  points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
@@ -618,7 +960,7 @@ var init_schema = __esm({
618
960
  original_width: import_zod.default.number(),
619
961
  original_height: import_zod.default.number()
620
962
  })
621
- ),
963
+ ).optional().default([]),
622
964
  annotator: import_zod.default.number(),
623
965
  annotation_id: import_zod.default.number(),
624
966
  created_at: import_zod.default.string(),
@@ -630,106 +972,450 @@ var init_schema = __esm({
630
972
  import_zod.default.object({
631
973
  transcription: import_zod.default.string(),
632
974
  points: import_zod.default.array(import_zod.default.array(import_zod.default.number())),
633
- dt_score: import_zod.default.number()
975
+ dt_score: import_zod.default.number().optional(),
976
+ // Detection score (from PaddleOCR)
977
+ difficult: import_zod.default.boolean().optional()
978
+ // Difficult flag (from PPOCRLabel tool)
634
979
  })
635
980
  );
636
981
  }
637
982
  });
638
983
 
639
- // src/lib/sort.ts
640
- function getBoundingBoxCenter(points) {
641
- let minX = Infinity;
642
- let minY = Infinity;
643
- let maxX = -Infinity;
644
- let maxY = -Infinity;
645
- for (const [x, y] of points) {
646
- if (x !== void 0 && y !== void 0) {
647
- minX = Math.min(minX, x);
648
- minY = Math.min(minY, y);
649
- maxX = Math.max(maxX, x);
650
- maxY = Math.max(maxY, y);
984
+ // src/commands/enhance-labelstudio/impl.ts
985
+ var impl_exports = {};
986
+ __export(impl_exports, {
987
+ enhanceLabelStudio: () => enhanceLabelStudio
988
+ });
989
+ async function enhanceLabelStudio(flags, ...inputDirs) {
990
+ const {
991
+ outDir = OUTPUT_BASE_DIR,
992
+ sortVertical = DEFAULT_SORT_VERTICAL,
993
+ sortHorizontal = DEFAULT_SORT_HORIZONTAL,
994
+ normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
995
+ widthIncrement = DEFAULT_WIDTH_INCREMENT,
996
+ heightIncrement = DEFAULT_HEIGHT_INCREMENT,
997
+ precision = DEFAULT_LABEL_STUDIO_PRECISION
998
+ } = flags;
999
+ await (0, import_promises.mkdir)(outDir, { recursive: true });
1000
+ for (const inputDir of inputDirs) {
1001
+ console.log(import_chalk.default.blue(`Processing input directory: ${inputDir}`));
1002
+ const files = await (0, import_promises.readdir)(inputDir);
1003
+ for (const file of files) {
1004
+ if (!file.endsWith(".json")) {
1005
+ continue;
1006
+ }
1007
+ const filePath = (0, import_path.join)(inputDir, file);
1008
+ console.log(import_chalk.default.gray(`Processing file: ${file}`));
1009
+ try {
1010
+ const fileData = await (0, import_promises.readFile)(filePath, "utf-8");
1011
+ const labelStudioData = JSON.parse(fileData);
1012
+ const { data, isFull } = isLabelStudioFullJSON(labelStudioData);
1013
+ const enhanced = await enhanceLabelStudioData(data, isFull, {
1014
+ sortVertical,
1015
+ sortHorizontal,
1016
+ normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
1017
+ widthIncrement,
1018
+ heightIncrement,
1019
+ precision
1020
+ });
1021
+ const outputFilePath = (0, import_path.join)(outDir, file);
1022
+ await (0, import_promises.writeFile)(
1023
+ outputFilePath,
1024
+ JSON.stringify(enhanced, null, 2),
1025
+ "utf-8"
1026
+ );
1027
+ console.log(import_chalk.default.green(`\u2713 Enhanced file saved: ${outputFilePath}`));
1028
+ } catch (error) {
1029
+ console.error(
1030
+ import_chalk.default.red(`Error processing file ${file}:`),
1031
+ error instanceof Error ? error.message : String(error)
1032
+ );
1033
+ }
651
1034
  }
652
1035
  }
653
- return {
654
- x: (minX + maxX) / 2,
655
- y: (minY + maxY) / 2,
656
- width: maxX - minX,
657
- height: maxY - minY
658
- };
1036
+ console.log(import_chalk.default.green("\n\u2713 Enhancement complete!"));
659
1037
  }
660
- function sortBoundingBoxes(annotations, verticalSort, horizontalSort) {
661
- if (verticalSort === SORT_VERTICAL_NONE && horizontalSort === SORT_HORIZONTAL_NONE) {
662
- return annotations;
663
- }
664
- const sorted = [...annotations];
665
- const isVerticalText = sorted.length > 0 && (() => {
666
- const verticalCount = sorted.filter((ann) => {
667
- const center = getBoundingBoxCenter(ann.points);
668
- return center.height > center.width * 1.5;
669
- }).length;
670
- return verticalCount > sorted.length / 2;
671
- })();
672
- if (horizontalSort === SORT_HORIZONTAL_RTL && verticalSort !== SORT_VERTICAL_NONE && isVerticalText) {
673
- const annotationsWithCenters = sorted.map((ann) => ({
674
- annotation: ann,
675
- center: getBoundingBoxCenter(ann.points)
676
- }));
677
- const columns = [];
678
- for (const item of annotationsWithCenters) {
679
- let addedToColumn = false;
680
- for (const column of columns) {
681
- const avgX = column.reduce((sum, c) => sum + c.center.x, 0) / column.length;
682
- if (Math.abs(item.center.x - avgX) < GROUPING_TOLERANCE) {
683
- column.push(item);
684
- addedToColumn = true;
685
- break;
1038
+ var import_promises, import_path, import_chalk, isLabelStudioFullJSON;
1039
+ var init_impl = __esm({
1040
+ "src/commands/enhance-labelstudio/impl.ts"() {
1041
+ "use strict";
1042
+ init_cjs_shims();
1043
+ import_promises = require("fs/promises");
1044
+ import_path = require("path");
1045
+ import_chalk = __toESM(require("chalk"), 1);
1046
+ init_constants();
1047
+ init_label_studio();
1048
+ init_schema();
1049
+ isLabelStudioFullJSON = (data) => {
1050
+ const parsedFull = FullOCRLabelStudioSchema.safeParse(data);
1051
+ if (parsedFull.success) {
1052
+ return { isFull: true, data: parsedFull.data };
1053
+ }
1054
+ if (!Array.isArray(data) && typeof data === "object" && data !== null) {
1055
+ const parsedSingleFull = FullOCRLabelStudioSchema.safeParse([data]);
1056
+ if (parsedSingleFull.success) {
1057
+ return { isFull: true, data: parsedSingleFull.data };
686
1058
  }
687
1059
  }
688
- if (!addedToColumn) {
689
- columns.push([item]);
1060
+ const parsedMin = MinOCRLabelStudioSchema.safeParse(data);
1061
+ if (parsedMin.success) {
1062
+ return { isFull: false, data: parsedMin.data };
690
1063
  }
691
- }
692
- columns.sort((colA, colB) => {
693
- const avgXA = colA.reduce((sum, c) => sum + c.center.x, 0) / colA.length;
694
- const avgXB = colB.reduce((sum, c) => sum + c.center.x, 0) / colB.length;
695
- return avgXB - avgXA;
696
- });
697
- for (const column of columns) {
698
- column.sort((a, b) => {
699
- return verticalSort === SORT_VERTICAL_TOP_BOTTOM ? a.center.y - b.center.y : b.center.y - a.center.y;
700
- });
701
- }
702
- return columns.flat().map((item) => item.annotation);
1064
+ throw new Error("Input data is not valid Label Studio JSON format.");
1065
+ };
703
1066
  }
704
- sorted.sort((a, b) => {
705
- const centerA = getBoundingBoxCenter(a.points);
706
- const centerB = getBoundingBoxCenter(b.points);
707
- if (verticalSort !== SORT_VERTICAL_NONE) {
708
- const yDiff = verticalSort === SORT_VERTICAL_TOP_BOTTOM ? centerA.y - centerB.y : centerB.y - centerA.y;
709
- if (Math.abs(yDiff) > GROUPING_TOLERANCE) {
710
- return yDiff;
1067
+ });
1068
+
1069
+ // src/commands/enhance-ppocr/impl.ts
1070
+ var impl_exports2 = {};
1071
+ __export(impl_exports2, {
1072
+ enhancePPOCR: () => enhancePPOCR
1073
+ });
1074
+ async function enhancePPOCR(flags, ...inputDirs) {
1075
+ const {
1076
+ outDir = OUTPUT_BASE_DIR,
1077
+ sortVertical = DEFAULT_SORT_VERTICAL,
1078
+ sortHorizontal = DEFAULT_SORT_HORIZONTAL,
1079
+ normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
1080
+ widthIncrement = DEFAULT_WIDTH_INCREMENT,
1081
+ heightIncrement = DEFAULT_HEIGHT_INCREMENT,
1082
+ precision = DEFAULT_PPOCR_PRECISION
1083
+ } = flags;
1084
+ await (0, import_promises2.mkdir)(outDir, { recursive: true });
1085
+ for (const inputDir of inputDirs) {
1086
+ console.log(import_chalk2.default.blue(`Processing input directory: ${inputDir}`));
1087
+ const files = await (0, import_promises2.readdir)(inputDir);
1088
+ for (const file of files) {
1089
+ if (!file.endsWith(".txt")) {
1090
+ continue;
1091
+ }
1092
+ const filePath = (0, import_path2.join)(inputDir, file);
1093
+ console.log(import_chalk2.default.gray(`Processing file: ${file}`));
1094
+ try {
1095
+ const fileData = await (0, import_promises2.readFile)(filePath, "utf-8");
1096
+ const lines = fileData.trim().split("\n");
1097
+ const enhancedLines = [];
1098
+ for (const line of lines) {
1099
+ const parts = line.split(" ");
1100
+ if (parts.length !== 2) {
1101
+ throw new Error(`Invalid PPOCRLabelV2 format in line: ${line}`);
1102
+ }
1103
+ const [imagePath, annotationsStr] = parts;
1104
+ const annotations = JSON.parse(annotationsStr);
1105
+ PPOCRLabelSchema.parse(annotations);
1106
+ const enhanced = enhancePPOCRLabel(annotations, {
1107
+ sortVertical,
1108
+ sortHorizontal,
1109
+ normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
1110
+ widthIncrement,
1111
+ heightIncrement,
1112
+ precision
1113
+ });
1114
+ PPOCRLabelSchema.parse(enhanced);
1115
+ const jsonArray = JSON.stringify(enhanced);
1116
+ enhancedLines.push(`${imagePath} ${jsonArray}`);
1117
+ }
1118
+ const outputFilePath = (0, import_path2.join)(outDir, file);
1119
+ await (0, import_promises2.writeFile)(outputFilePath, enhancedLines.join("\n"), "utf-8");
1120
+ console.log(import_chalk2.default.green(`\u2713 Enhanced file saved: ${outputFilePath}`));
1121
+ } catch (error) {
1122
+ console.error(
1123
+ import_chalk2.default.red(`Error processing file ${file}:`),
1124
+ error instanceof Error ? error.message : String(error)
1125
+ );
711
1126
  }
712
1127
  }
713
- if (horizontalSort !== SORT_HORIZONTAL_NONE) {
714
- return horizontalSort === SORT_HORIZONTAL_LTR ? centerA.x - centerB.x : centerB.x - centerA.x;
715
- }
716
- return 0;
717
- });
718
- return sorted;
1128
+ }
1129
+ console.log(import_chalk2.default.green("\n\u2713 Enhancement complete!"));
719
1130
  }
720
- var GROUPING_TOLERANCE;
721
- var init_sort = __esm({
722
- "src/lib/sort.ts"() {
1131
+ var import_promises2, import_path2, import_chalk2;
1132
+ var init_impl2 = __esm({
1133
+ "src/commands/enhance-ppocr/impl.ts"() {
723
1134
  "use strict";
724
1135
  init_cjs_shims();
1136
+ import_promises2 = require("fs/promises");
1137
+ import_path2 = require("path");
1138
+ import_chalk2 = __toESM(require("chalk"), 1);
725
1139
  init_constants();
726
- GROUPING_TOLERANCE = 50;
1140
+ init_enhance();
1141
+ init_schema();
1142
+ }
1143
+ });
1144
+
1145
+ // src/lib/ppocr-label.ts
1146
+ var import_node_crypto, import_node_fs, import_node_path, import_image_size, ppocrToLabelStudio, ppocrToFullLabelStudio, ppocrToMinLabelStudio;
1147
+ var init_ppocr_label = __esm({
1148
+ "src/lib/ppocr-label.ts"() {
1149
+ "use strict";
1150
+ init_cjs_shims();
1151
+ import_node_crypto = require("crypto");
1152
+ import_node_fs = require("fs");
1153
+ import_node_path = require("path");
1154
+ import_image_size = __toESM(require("image-size"), 1);
1155
+ init_constants();
1156
+ init_geometry();
1157
+ ppocrToLabelStudio = async (data, options) => {
1158
+ const {
1159
+ imagePath,
1160
+ baseServerUrl,
1161
+ inputDir,
1162
+ toFullJson = true,
1163
+ taskId = 1,
1164
+ labelName = DEFAULT_LABEL_NAME,
1165
+ normalizeShape: normalizeShape2,
1166
+ widthIncrement = 0,
1167
+ heightIncrement = 0,
1168
+ precision = DEFAULT_LABEL_STUDIO_PRECISION
1169
+ } = options || {};
1170
+ if (toFullJson) {
1171
+ return ppocrToFullLabelStudio(
1172
+ data,
1173
+ imagePath,
1174
+ baseServerUrl,
1175
+ inputDir,
1176
+ taskId,
1177
+ labelName,
1178
+ normalizeShape2,
1179
+ widthIncrement,
1180
+ heightIncrement,
1181
+ precision
1182
+ );
1183
+ } else {
1184
+ return ppocrToMinLabelStudio(
1185
+ data,
1186
+ imagePath,
1187
+ baseServerUrl,
1188
+ inputDir,
1189
+ labelName,
1190
+ normalizeShape2,
1191
+ widthIncrement,
1192
+ heightIncrement,
1193
+ precision
1194
+ );
1195
+ }
1196
+ };
1197
+ ppocrToFullLabelStudio = (data, imagePath, baseServerUrl, inputDir, taskId = 1, labelName = DEFAULT_LABEL_NAME, normalizeShape2, widthIncrement = 0, heightIncrement = 0, precision = DEFAULT_LABEL_STUDIO_PRECISION) => {
1198
+ const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
1199
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1200
+ let original_width = 1920;
1201
+ let original_height = 1080;
1202
+ const resolvedImagePath = inputDir ? (0, import_node_path.join)(inputDir, imagePath) : imagePath;
1203
+ if (!(0, import_node_fs.existsSync)(resolvedImagePath)) {
1204
+ throw new Error(`Image file not found: ${resolvedImagePath}`);
1205
+ }
1206
+ const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
1207
+ const dimensions = (0, import_image_size.default)(buffer);
1208
+ if (!dimensions.width || !dimensions.height) {
1209
+ throw new Error(
1210
+ `Failed to read image dimensions from: ${resolvedImagePath}`
1211
+ );
1212
+ }
1213
+ original_width = dimensions.width;
1214
+ original_height = dimensions.height;
1215
+ const fileName = imagePath.split("/").pop() || imagePath;
1216
+ const result = [
1217
+ {
1218
+ id: taskId,
1219
+ annotations: [
1220
+ {
1221
+ id: taskId,
1222
+ completed_by: 1,
1223
+ result: data.map((item) => {
1224
+ let { points } = item;
1225
+ points = transformPoints(points, {
1226
+ normalizeShape: normalizeShape2,
1227
+ widthIncrement,
1228
+ heightIncrement
1229
+ });
1230
+ const annotationId = (0, import_node_crypto.randomUUID)().slice(0, 10);
1231
+ const polygonPoints = points.map(([x, y]) => [
1232
+ roundToPrecision((x ?? 0) / original_width * 100, precision),
1233
+ roundToPrecision((y ?? 0) / original_height * 100, precision)
1234
+ ]);
1235
+ return [
1236
+ // 1. Polygon geometry only
1237
+ {
1238
+ original_width,
1239
+ original_height,
1240
+ image_rotation: 0,
1241
+ value: {
1242
+ points: polygonPoints,
1243
+ closed: true
1244
+ },
1245
+ id: annotationId,
1246
+ from_name: "poly",
1247
+ to_name: "image",
1248
+ type: "polygon",
1249
+ origin: "manual"
1250
+ },
1251
+ // 2. Labels with polygon geometry
1252
+ {
1253
+ original_width,
1254
+ original_height,
1255
+ image_rotation: 0,
1256
+ value: {
1257
+ points: polygonPoints,
1258
+ closed: true,
1259
+ labels: [labelName]
1260
+ },
1261
+ id: annotationId,
1262
+ from_name: "label",
1263
+ to_name: "image",
1264
+ type: "labels",
1265
+ origin: "manual"
1266
+ },
1267
+ // 3. Textarea with polygon geometry and text
1268
+ {
1269
+ original_width,
1270
+ original_height,
1271
+ image_rotation: 0,
1272
+ value: {
1273
+ points: polygonPoints,
1274
+ closed: true,
1275
+ text: [item.transcription]
1276
+ },
1277
+ id: annotationId,
1278
+ from_name: "transcription",
1279
+ to_name: "image",
1280
+ type: "textarea",
1281
+ origin: "manual"
1282
+ }
1283
+ ];
1284
+ }).flat(),
1285
+ was_cancelled: false,
1286
+ ground_truth: false,
1287
+ created_at: now,
1288
+ updated_at: now,
1289
+ draft_created_at: now,
1290
+ lead_time: 0,
1291
+ prediction: {},
1292
+ result_count: data.length * 3,
1293
+ unique_id: (0, import_node_crypto.randomUUID)(),
1294
+ import_id: null,
1295
+ last_action: null,
1296
+ bulk_created: false,
1297
+ task: taskId,
1298
+ project: 1,
1299
+ updated_by: 1,
1300
+ parent_prediction: null,
1301
+ parent_annotation: null,
1302
+ last_created_by: null
1303
+ }
1304
+ ],
1305
+ file_upload: fileName,
1306
+ drafts: [],
1307
+ predictions: [],
1308
+ data: { ocr: `${newBaseServerUrl}${imagePath}` },
1309
+ meta: {},
1310
+ created_at: now,
1311
+ updated_at: now,
1312
+ allow_skip: false,
1313
+ inner_id: taskId,
1314
+ total_annotations: 1,
1315
+ cancelled_annotations: 0,
1316
+ total_predictions: 0,
1317
+ comment_count: 0,
1318
+ unresolved_comment_count: 0,
1319
+ last_comment_updated_at: null,
1320
+ project: 1,
1321
+ updated_by: 1,
1322
+ comment_authors: []
1323
+ }
1324
+ ];
1325
+ return result;
1326
+ };
1327
+ ppocrToMinLabelStudio = (data, imagePath, baseServerUrl, inputDir, labelName = "text", normalizeShape2, widthIncrement = 0, heightIncrement = 0, precision = DEFAULT_LABEL_STUDIO_PRECISION) => {
1328
+ const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
1329
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1330
+ let original_width = 1920;
1331
+ let original_height = 1080;
1332
+ const resolvedImagePath = inputDir ? (0, import_node_path.join)(inputDir, imagePath) : imagePath;
1333
+ if (!(0, import_node_fs.existsSync)(resolvedImagePath)) {
1334
+ throw new Error(`Image file not found: ${resolvedImagePath}`);
1335
+ }
1336
+ const buffer = (0, import_node_fs.readFileSync)(resolvedImagePath);
1337
+ const dimensions = (0, import_image_size.default)(buffer);
1338
+ if (!dimensions.width || !dimensions.height) {
1339
+ throw new Error(
1340
+ `Failed to read image dimensions from: ${resolvedImagePath}`
1341
+ );
1342
+ }
1343
+ original_width = dimensions.width;
1344
+ original_height = dimensions.height;
1345
+ return data.map((item, index) => {
1346
+ let { points } = item;
1347
+ points = transformPoints(points, {
1348
+ normalizeShape: normalizeShape2,
1349
+ widthIncrement,
1350
+ heightIncrement
1351
+ });
1352
+ const roundedPoints = points.map(
1353
+ ([x, y]) => [
1354
+ roundToPrecision(x ?? 0, precision),
1355
+ roundToPrecision(y ?? 0, precision)
1356
+ ]
1357
+ );
1358
+ let minX = Infinity;
1359
+ let minY = Infinity;
1360
+ let maxX = -Infinity;
1361
+ let maxY = -Infinity;
1362
+ for (const point of roundedPoints) {
1363
+ const [x, y] = point;
1364
+ if (x !== void 0 && y !== void 0) {
1365
+ minX = Math.min(minX, x);
1366
+ minY = Math.min(minY, y);
1367
+ maxX = Math.max(maxX, x);
1368
+ maxY = Math.max(maxY, y);
1369
+ }
1370
+ }
1371
+ const width = maxX - minX;
1372
+ const height = maxY - minY;
1373
+ return {
1374
+ ocr: encodeURI(`${newBaseServerUrl}${imagePath}`),
1375
+ id: index + 1,
1376
+ bbox: [
1377
+ {
1378
+ x: minX,
1379
+ y: minY,
1380
+ width,
1381
+ height,
1382
+ rotation: 0,
1383
+ original_width,
1384
+ original_height
1385
+ }
1386
+ ],
1387
+ label: [
1388
+ {
1389
+ points: roundedPoints,
1390
+ closed: true,
1391
+ labels: [labelName],
1392
+ original_width,
1393
+ original_height
1394
+ }
1395
+ ],
1396
+ transcription: [item.transcription],
1397
+ poly: [
1398
+ {
1399
+ points: roundedPoints,
1400
+ closed: true,
1401
+ original_width,
1402
+ original_height
1403
+ }
1404
+ ],
1405
+ annotator: 1,
1406
+ annotation_id: index + 1,
1407
+ created_at: now,
1408
+ updated_at: now,
1409
+ lead_time: 0
1410
+ };
1411
+ });
1412
+ };
727
1413
  }
728
1414
  });
729
1415
 
730
1416
  // src/commands/toLabelStudio/impl.ts
731
- var impl_exports = {};
732
- __export(impl_exports, {
1417
+ var impl_exports3 = {};
1418
+ __export(impl_exports3, {
733
1419
  convertToLabelStudio: () => convertToLabelStudio
734
1420
  });
735
1421
  async function convertToLabelStudio(flags, ...inputDirs) {
@@ -742,21 +1428,25 @@ async function convertToLabelStudio(flags, ...inputDirs) {
742
1428
  fileListName = DEFAULT_FILE_LIST_NAME,
743
1429
  baseServerUrl = DEFAULT_BASE_SERVER_URL,
744
1430
  sortVertical = DEFAULT_SORT_VERTICAL,
745
- sortHorizontal = DEFAULT_SORT_HORIZONTAL
1431
+ sortHorizontal = DEFAULT_SORT_HORIZONTAL,
1432
+ normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
1433
+ widthIncrement = DEFAULT_WIDTH_INCREMENT,
1434
+ heightIncrement = DEFAULT_HEIGHT_INCREMENT,
1435
+ precision = DEFAULT_LABEL_STUDIO_PRECISION
746
1436
  } = flags;
747
1437
  const newBaseServerUrl = baseServerUrl.replace(/\/+$/, "") + (baseServerUrl === "" ? "" : "/");
748
- await (0, import_promises.mkdir)(outDir, { recursive: true });
1438
+ await (0, import_promises3.mkdir)(outDir, { recursive: true });
749
1439
  for (const inputDir of inputDirs) {
750
- console.log(import_chalk.default.blue(`Processing input directory: ${inputDir}`));
751
- const files = await (0, import_promises.readdir)(inputDir);
1440
+ console.log(import_chalk3.default.blue(`Processing input directory: ${inputDir}`));
1441
+ const files = await (0, import_promises3.readdir)(inputDir);
752
1442
  for (const file of files) {
753
1443
  if (!file.endsWith(".txt")) {
754
1444
  continue;
755
1445
  }
756
- const filePath = (0, import_path.join)(inputDir, file);
757
- console.log(import_chalk.default.gray(`Processing file: ${file}`));
1446
+ const filePath = (0, import_path3.join)(inputDir, file);
1447
+ console.log(import_chalk3.default.gray(`Processing file: ${file}`));
758
1448
  try {
759
- const fileData = await (0, import_promises.readFile)(filePath, "utf-8");
1449
+ const fileData = await (0, import_promises3.readFile)(filePath, "utf-8");
760
1450
  const lines = fileData.trim().split("\n");
761
1451
  const imageDataMap = /* @__PURE__ */ new Map();
762
1452
  for (const line of lines) {
@@ -785,7 +1475,11 @@ async function convertToLabelStudio(flags, ...inputDirs) {
785
1475
  baseServerUrl: newBaseServerUrl,
786
1476
  inputDir,
787
1477
  taskId,
788
- labelName: defaultLabelName
1478
+ labelName: defaultLabelName,
1479
+ normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
1480
+ widthIncrement,
1481
+ heightIncrement,
1482
+ precision
789
1483
  });
790
1484
  if (toFullJson) {
791
1485
  allLabelStudioData.push(labelStudioData[0]);
@@ -794,11 +1488,11 @@ async function convertToLabelStudio(flags, ...inputDirs) {
794
1488
  }
795
1489
  if (createFilePerImage) {
796
1490
  const imageBaseName = imagePath.replace(/\//g, "_").replace(/\.[^.]+$/, "");
797
- const individualOutputPath = (0, import_path.join)(
1491
+ const individualOutputPath = (0, import_path3.join)(
798
1492
  outDir,
799
1493
  `${imageBaseName}_${toFullJson ? "full" : "min"}.json`
800
1494
  );
801
- await (0, import_promises.writeFile)(
1495
+ await (0, import_promises3.writeFile)(
802
1496
  individualOutputPath,
803
1497
  JSON.stringify(
804
1498
  toFullJson ? labelStudioData[0] : labelStudioData,
@@ -808,7 +1502,7 @@ async function convertToLabelStudio(flags, ...inputDirs) {
808
1502
  "utf-8"
809
1503
  );
810
1504
  console.log(
811
- import_chalk.default.gray(
1505
+ import_chalk3.default.gray(
812
1506
  ` \u2713 Created individual file: ${individualOutputPath}`
813
1507
  )
814
1508
  );
@@ -819,43 +1513,43 @@ async function convertToLabelStudio(flags, ...inputDirs) {
819
1513
  taskId++;
820
1514
  }
821
1515
  const baseName = file.replace(".txt", "");
822
- const outputPath = (0, import_path.join)(
1516
+ const outputPath = (0, import_path3.join)(
823
1517
  outDir,
824
1518
  `${baseName}_${toFullJson ? "full" : "min"}.json`
825
1519
  );
826
- await (0, import_promises.writeFile)(
1520
+ await (0, import_promises3.writeFile)(
827
1521
  outputPath,
828
1522
  JSON.stringify(allLabelStudioData, null, 2),
829
1523
  "utf-8"
830
1524
  );
831
- console.log(import_chalk.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
1525
+ console.log(import_chalk3.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
832
1526
  if (createFileListForServing && fileList.length > 0) {
833
- const fileListPath = (0, import_path.join)(outDir, fileListName);
834
- await (0, import_promises.writeFile)(fileListPath, fileList.join("\n"), "utf-8");
1527
+ const fileListPath = (0, import_path3.join)(outDir, fileListName);
1528
+ await (0, import_promises3.writeFile)(fileListPath, fileList.join("\n"), "utf-8");
835
1529
  console.log(
836
- import_chalk.default.green(
1530
+ import_chalk3.default.green(
837
1531
  `\u2713 Created file list: ${fileListPath} (${fileList.length} files)`
838
1532
  )
839
1533
  );
840
1534
  }
841
1535
  } catch (error) {
842
1536
  console.error(
843
- import_chalk.default.red(`\u2717 Failed to process ${file}:`),
1537
+ import_chalk3.default.red(`\u2717 Failed to process ${file}:`),
844
1538
  error instanceof Error ? error.message : error
845
1539
  );
846
1540
  }
847
1541
  }
848
1542
  }
849
- console.log(import_chalk.default.green("\n\u2713 Conversion completed!"));
1543
+ console.log(import_chalk3.default.green("\n\u2713 Conversion completed!"));
850
1544
  }
851
- var import_promises, import_path, import_chalk;
852
- var init_impl = __esm({
1545
+ var import_promises3, import_path3, import_chalk3;
1546
+ var init_impl3 = __esm({
853
1547
  "src/commands/toLabelStudio/impl.ts"() {
854
1548
  "use strict";
855
1549
  init_cjs_shims();
856
- import_promises = require("fs/promises");
857
- import_path = require("path");
858
- import_chalk = __toESM(require("chalk"), 1);
1550
+ import_promises3 = require("fs/promises");
1551
+ import_path3 = require("path");
1552
+ import_chalk3 = __toESM(require("chalk"), 1);
859
1553
  init_constants();
860
1554
  init_ppocr_label();
861
1555
  init_schema();
@@ -863,147 +1557,9 @@ var init_impl = __esm({
863
1557
  }
864
1558
  });
865
1559
 
866
- // src/lib/label-studio.ts
867
- var turf, labelStudioToPPOCR, minLabelStudioToPPOCR;
868
- var init_label_studio = __esm({
869
- "src/lib/label-studio.ts"() {
870
- "use strict";
871
- init_cjs_shims();
872
- turf = __toESM(require("@turf/turf"), 1);
873
- labelStudioToPPOCR = async (data, baseImageDir) => {
874
- const resultMap = /* @__PURE__ */ new Map();
875
- for (const task of data) {
876
- let imagePath = task.file_upload || "";
877
- if (task.data.ocr) {
878
- const urlPath = task.data.ocr.replace(/^https?:\/\/[^/]+\//, "");
879
- imagePath = decodeURIComponent(urlPath);
880
- }
881
- if (baseImageDir) {
882
- imagePath = `${baseImageDir}/${task.file_upload || imagePath.split("/").pop() || imagePath}`;
883
- }
884
- const imageAnnotations = [];
885
- for (const annotation of task.annotations) {
886
- const groupedById = /* @__PURE__ */ new Map();
887
- for (const resultItem of annotation.result) {
888
- const { id } = resultItem;
889
- if (!groupedById.has(id)) {
890
- groupedById.set(id, []);
891
- }
892
- groupedById.get(id).push(resultItem);
893
- }
894
- for (const [_, resultItems] of groupedById) {
895
- let points;
896
- let transcription = "";
897
- for (const resultItem of resultItems) {
898
- if ("points" in resultItem.value && resultItem.value.points) {
899
- const { points: valuePoints } = resultItem.value;
900
- const { original_width, original_height } = resultItem;
901
- points = valuePoints.map(([x, y]) => [
902
- (x ?? 0) * original_width / 100,
903
- (y ?? 0) * original_height / 100
904
- ]);
905
- } else if ("x" in resultItem.value && "y" in resultItem.value && "width" in resultItem.value && "height" in resultItem.value) {
906
- const { x, y, width, height } = resultItem.value;
907
- const { original_width, original_height } = resultItem;
908
- const absX = x * original_width / 100;
909
- const absY = y * original_height / 100;
910
- const absWidth = width * original_width / 100;
911
- const absHeight = height * original_height / 100;
912
- points = [
913
- [absX, absY],
914
- [absX + absWidth, absY],
915
- [absX + absWidth, absY + absHeight],
916
- [absX, absY + absHeight]
917
- ];
918
- }
919
- if ("text" in resultItem.value && Array.isArray(resultItem.value.text)) {
920
- transcription = resultItem.value.text[0] || "";
921
- }
922
- }
923
- if (points && points.length > 0) {
924
- let dt_score = 1;
925
- try {
926
- const firstPoint = points[0];
927
- if (firstPoint) {
928
- const polygon2 = turf.polygon([points.concat([firstPoint])]);
929
- const area2 = turf.area(polygon2);
930
- dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
931
- }
932
- } catch {
933
- dt_score = 0.8;
934
- }
935
- imageAnnotations.push({
936
- transcription,
937
- points,
938
- dt_score
939
- });
940
- }
941
- }
942
- }
943
- if (imageAnnotations.length > 0) {
944
- resultMap.set(imagePath, imageAnnotations);
945
- }
946
- }
947
- return resultMap;
948
- };
949
- minLabelStudioToPPOCR = async (data, baseImageDir) => {
950
- const resultMap = /* @__PURE__ */ new Map();
951
- for (const item of data) {
952
- let imagePath = item.ocr || "";
953
- if (imagePath) {
954
- imagePath = decodeURIComponent(
955
- imagePath.replace(/^https?:\/\/[^/]+\//, "")
956
- );
957
- }
958
- if (baseImageDir) {
959
- imagePath = `${baseImageDir}/${imagePath.split("/").pop() || imagePath}`;
960
- }
961
- let points;
962
- if (item.poly.length > 0 && item.poly[0]) {
963
- const { points: polyPoints } = item.poly[0];
964
- points = polyPoints;
965
- } else if (item.bbox.length > 0 && item.bbox[0]) {
966
- const bbox = item.bbox[0];
967
- const { x, y, width, height } = bbox;
968
- points = [
969
- [x, y],
970
- [x + width, y],
971
- [x + width, y + height],
972
- [x, y + height]
973
- ];
974
- } else {
975
- continue;
976
- }
977
- const transcription = item.transcription.length > 0 ? item.transcription[0] : "";
978
- let dt_score = 1;
979
- try {
980
- const firstPoint = points[0];
981
- if (firstPoint) {
982
- const polygon2 = turf.polygon([points.concat([firstPoint])]);
983
- const area2 = turf.area(polygon2);
984
- dt_score = Math.min(1, Math.max(0.5, area2 / 1e4));
985
- }
986
- } catch {
987
- dt_score = 0.8;
988
- }
989
- const annotation = {
990
- transcription: transcription ?? "",
991
- points,
992
- dt_score
993
- };
994
- if (!resultMap.has(imagePath)) {
995
- resultMap.set(imagePath, []);
996
- }
997
- resultMap.get(imagePath).push(annotation);
998
- }
999
- return resultMap;
1000
- };
1001
- }
1002
- });
1003
-
1004
1560
  // src/commands/toPPOCR/impl.ts
1005
- var impl_exports2 = {};
1006
- __export(impl_exports2, {
1561
+ var impl_exports4 = {};
1562
+ __export(impl_exports4, {
1007
1563
  convertToPPOCR: () => convertToPPOCR
1008
1564
  });
1009
1565
  async function convertToPPOCR(flags, ...inputDirs) {
@@ -1012,26 +1568,39 @@ async function convertToPPOCR(flags, ...inputDirs) {
1012
1568
  fileName = DEFAULT_PPOCR_FILE_NAME,
1013
1569
  baseImageDir,
1014
1570
  sortVertical = DEFAULT_SORT_VERTICAL,
1015
- sortHorizontal = DEFAULT_SORT_HORIZONTAL
1571
+ sortHorizontal = DEFAULT_SORT_HORIZONTAL,
1572
+ normalizeShape: normalizeShape2 = DEFAULT_SHAPE_NORMALIZE,
1573
+ widthIncrement = DEFAULT_WIDTH_INCREMENT,
1574
+ heightIncrement = DEFAULT_HEIGHT_INCREMENT,
1575
+ precision = DEFAULT_PPOCR_PRECISION
1016
1576
  } = flags;
1017
- await (0, import_promises2.mkdir)(outDir, { recursive: true });
1577
+ await (0, import_promises4.mkdir)(outDir, { recursive: true });
1018
1578
  for (const inputDir of inputDirs) {
1019
- console.log(import_chalk2.default.blue(`Processing input directory: ${inputDir}`));
1020
- const files = await (0, import_promises2.readdir)(inputDir);
1579
+ console.log(import_chalk4.default.blue(`Processing input directory: ${inputDir}`));
1580
+ const files = await (0, import_promises4.readdir)(inputDir);
1021
1581
  for (const file of files) {
1022
1582
  if (!file.endsWith(".json")) {
1023
1583
  continue;
1024
1584
  }
1025
- const filePath = (0, import_path2.join)(inputDir, file);
1026
- console.log(import_chalk2.default.gray(`Processing file: ${file}`));
1585
+ const filePath = (0, import_path4.join)(inputDir, file);
1586
+ console.log(import_chalk4.default.gray(`Processing file: ${file}`));
1027
1587
  try {
1028
- const fileData = await (0, import_promises2.readFile)(filePath, "utf-8");
1588
+ const fileData = await (0, import_promises4.readFile)(filePath, "utf-8");
1029
1589
  const labelStudioData = JSON.parse(fileData);
1030
- const { data, isFull } = isLabelStudioFullJSON(labelStudioData);
1031
- const ppocrDataMap = isFull ? await labelStudioToPPOCR(data, baseImageDir) : await minLabelStudioToPPOCR(
1032
- data,
1033
- baseImageDir
1034
- );
1590
+ const { data, isFull } = isLabelStudioFullJSON2(labelStudioData);
1591
+ const ppocrDataMap = isFull ? await labelStudioToPPOCR(data, {
1592
+ baseImageDir,
1593
+ normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
1594
+ widthIncrement,
1595
+ heightIncrement,
1596
+ precision
1597
+ }) : await minLabelStudioToPPOCR(data, {
1598
+ baseImageDir,
1599
+ normalizeShape: normalizeShape2 !== SHAPE_NORMALIZE_NONE ? normalizeShape2 : void 0,
1600
+ widthIncrement,
1601
+ heightIncrement,
1602
+ precision
1603
+ });
1035
1604
  const outputLines = [];
1036
1605
  for (const [imagePath, annotations] of ppocrDataMap.entries()) {
1037
1606
  const sortedAnnotations = sortBoundingBoxes(
@@ -1044,32 +1613,32 @@ async function convertToPPOCR(flags, ...inputDirs) {
1044
1613
  outputLines.push(`${imagePath} ${jsonArray}`);
1045
1614
  }
1046
1615
  const baseName = file.replace(".json", "");
1047
- const outputPath = (0, import_path2.join)(outDir, `${baseName}_${fileName}`);
1048
- await (0, import_promises2.writeFile)(outputPath, outputLines.join("\n"), "utf-8");
1049
- console.log(import_chalk2.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
1616
+ const outputPath = (0, import_path4.join)(outDir, `${baseName}_${fileName}`);
1617
+ await (0, import_promises4.writeFile)(outputPath, outputLines.join("\n"), "utf-8");
1618
+ console.log(import_chalk4.default.green(`\u2713 Converted ${file} -> ${outputPath}`));
1050
1619
  } catch (error) {
1051
1620
  console.error(
1052
- import_chalk2.default.red(`\u2717 Failed to process ${file}:`),
1621
+ import_chalk4.default.red(`\u2717 Failed to process ${file}:`),
1053
1622
  error instanceof Error ? error.message : error
1054
1623
  );
1055
1624
  }
1056
1625
  }
1057
1626
  }
1058
- console.log(import_chalk2.default.green("\n\u2713 Conversion completed!"));
1627
+ console.log(import_chalk4.default.green("\n\u2713 Conversion completed!"));
1059
1628
  }
1060
- var import_promises2, import_path2, import_chalk2, isLabelStudioFullJSON;
1061
- var init_impl2 = __esm({
1629
+ var import_promises4, import_path4, import_chalk4, isLabelStudioFullJSON2;
1630
+ var init_impl4 = __esm({
1062
1631
  "src/commands/toPPOCR/impl.ts"() {
1063
1632
  "use strict";
1064
1633
  init_cjs_shims();
1065
- import_promises2 = require("fs/promises");
1066
- import_path2 = require("path");
1067
- import_chalk2 = __toESM(require("chalk"), 1);
1634
+ import_promises4 = require("fs/promises");
1635
+ import_path4 = require("path");
1636
+ import_chalk4 = __toESM(require("chalk"), 1);
1068
1637
  init_constants();
1069
1638
  init_label_studio();
1070
1639
  init_schema();
1071
1640
  init_sort();
1072
- isLabelStudioFullJSON = (data) => {
1641
+ isLabelStudioFullJSON2 = (data) => {
1073
1642
  const parsedFull = FullOCRLabelStudioSchema.safeParse(data);
1074
1643
  if (parsedFull.success) {
1075
1644
  return { isFull: true, data: parsedFull.data };
@@ -1091,24 +1660,160 @@ var init_impl2 = __esm({
1091
1660
 
1092
1661
  // src/bin/cli.ts
1093
1662
  init_cjs_shims();
1094
- var import_core4 = require("@stricli/core");
1663
+ var import_core6 = require("@stricli/core");
1095
1664
 
1096
1665
  // src/app.ts
1097
1666
  init_cjs_shims();
1098
1667
  var import_auto_complete = require("@stricli/auto-complete");
1099
- var import_core3 = require("@stricli/core");
1668
+ var import_core5 = require("@stricli/core");
1100
1669
 
1101
1670
  // package.json
1102
- var version = "1.0.0";
1671
+ var version = "1.2.0";
1103
1672
  var description = "Convert between Label Studio OCR format and PPOCRLabelv2 format";
1104
1673
 
1105
- // src/commands/toLabelStudio/command.ts
1674
+ // src/commands/enhance-labelstudio/command.ts
1106
1675
  init_cjs_shims();
1107
1676
  var import_core = require("@stricli/core");
1108
1677
  init_constants();
1109
- var toLabelStudioCommand = (0, import_core.buildCommand)({
1678
+ var enhanceLabelStudioCommand = (0, import_core.buildCommand)({
1679
+ loader: async () => {
1680
+ const { enhanceLabelStudio: enhanceLabelStudio2 } = await Promise.resolve().then(() => (init_impl(), impl_exports));
1681
+ return enhanceLabelStudio2;
1682
+ },
1683
+ parameters: {
1684
+ positional: {
1685
+ kind: "array",
1686
+ parameter: {
1687
+ brief: "Input directories containing Label Studio JSON files",
1688
+ parse: String
1689
+ },
1690
+ minimum: 1
1691
+ },
1692
+ flags: {
1693
+ outDir: {
1694
+ kind: "parsed",
1695
+ brief: `Output directory. Default: "${OUTPUT_BASE_DIR}"`,
1696
+ parse: String,
1697
+ optional: true
1698
+ },
1699
+ sortVertical: {
1700
+ kind: "parsed",
1701
+ brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
1702
+ parse: String,
1703
+ optional: true
1704
+ },
1705
+ sortHorizontal: {
1706
+ kind: "parsed",
1707
+ brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
1708
+ parse: String,
1709
+ optional: true
1710
+ },
1711
+ normalizeShape: {
1712
+ kind: "parsed",
1713
+ brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
1714
+ parse: String,
1715
+ optional: true
1716
+ },
1717
+ widthIncrement: {
1718
+ kind: "parsed",
1719
+ brief: `Increase bounding box width by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_WIDTH_INCREMENT}`,
1720
+ parse: Number,
1721
+ optional: true
1722
+ },
1723
+ heightIncrement: {
1724
+ kind: "parsed",
1725
+ brief: `Increase bounding box height by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_HEIGHT_INCREMENT}`,
1726
+ parse: Number,
1727
+ optional: true
1728
+ },
1729
+ precision: {
1730
+ kind: "parsed",
1731
+ brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_LABEL_STUDIO_PRECISION}`,
1732
+ parse: Number,
1733
+ optional: true
1734
+ }
1735
+ }
1736
+ },
1737
+ docs: {
1738
+ brief: "Enhance Label Studio files with sorting, normalization, and resizing"
1739
+ }
1740
+ });
1741
+
1742
+ // src/commands/enhance-ppocr/command.ts
1743
+ init_cjs_shims();
1744
+ var import_core2 = require("@stricli/core");
1745
+ init_constants();
1746
+ var enhancePPOCRCommand = (0, import_core2.buildCommand)({
1747
+ loader: async () => {
1748
+ const { enhancePPOCR: enhancePPOCR2 } = await Promise.resolve().then(() => (init_impl2(), impl_exports2));
1749
+ return enhancePPOCR2;
1750
+ },
1751
+ parameters: {
1752
+ positional: {
1753
+ kind: "array",
1754
+ parameter: {
1755
+ brief: "Input directories containing PPOCRLabel files",
1756
+ parse: String
1757
+ },
1758
+ minimum: 1
1759
+ },
1760
+ flags: {
1761
+ outDir: {
1762
+ kind: "parsed",
1763
+ brief: `Output directory. Default: "${OUTPUT_BASE_DIR}"`,
1764
+ parse: String,
1765
+ optional: true
1766
+ },
1767
+ sortVertical: {
1768
+ kind: "parsed",
1769
+ brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
1770
+ parse: String,
1771
+ optional: true
1772
+ },
1773
+ sortHorizontal: {
1774
+ kind: "parsed",
1775
+ brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
1776
+ parse: String,
1777
+ optional: true
1778
+ },
1779
+ normalizeShape: {
1780
+ kind: "parsed",
1781
+ brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
1782
+ parse: String,
1783
+ optional: true
1784
+ },
1785
+ widthIncrement: {
1786
+ kind: "parsed",
1787
+ brief: `Increase bounding box width by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_WIDTH_INCREMENT}`,
1788
+ parse: Number,
1789
+ optional: true
1790
+ },
1791
+ heightIncrement: {
1792
+ kind: "parsed",
1793
+ brief: `Increase bounding box height by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_HEIGHT_INCREMENT}`,
1794
+ parse: Number,
1795
+ optional: true
1796
+ },
1797
+ precision: {
1798
+ kind: "parsed",
1799
+ brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_PPOCR_PRECISION} (integers)`,
1800
+ parse: Number,
1801
+ optional: true
1802
+ }
1803
+ }
1804
+ },
1805
+ docs: {
1806
+ brief: "Enhance PPOCRLabel files with sorting, normalization, and resizing"
1807
+ }
1808
+ });
1809
+
1810
+ // src/commands/toLabelStudio/command.ts
1811
+ init_cjs_shims();
1812
+ var import_core3 = require("@stricli/core");
1813
+ init_constants();
1814
+ var toLabelStudioCommand = (0, import_core3.buildCommand)({
1110
1815
  loader: async () => {
1111
- const { convertToLabelStudio: convertToLabelStudio2 } = await Promise.resolve().then(() => (init_impl(), impl_exports));
1816
+ const { convertToLabelStudio: convertToLabelStudio2 } = await Promise.resolve().then(() => (init_impl3(), impl_exports3));
1112
1817
  return convertToLabelStudio2;
1113
1818
  },
1114
1819
  parameters: {
@@ -1123,54 +1828,78 @@ var toLabelStudioCommand = (0, import_core.buildCommand)({
1123
1828
  flags: {
1124
1829
  outDir: {
1125
1830
  kind: "parsed",
1126
- brief: `Output directory. Default to "${OUTPUT_BASE_DIR}"`,
1831
+ brief: `Output directory. Default: "${OUTPUT_BASE_DIR}"`,
1127
1832
  parse: String,
1128
1833
  optional: true
1129
1834
  },
1130
1835
  defaultLabelName: {
1131
1836
  kind: "parsed",
1132
- brief: `Default label name for text annotations. Default to "${DEFAULT_LABEL_NAME}"`,
1837
+ brief: `Default label name for text annotations. Default: "${DEFAULT_LABEL_NAME}"`,
1133
1838
  parse: String,
1134
1839
  optional: true
1135
1840
  },
1136
1841
  toFullJson: {
1137
1842
  kind: "boolean",
1138
- brief: `Convert to Full OCR Label Studio format. Default to "${DEFAULT_LABEL_STUDIO_FULL_JSON}"`,
1843
+ brief: `Convert to Full OCR Label Studio format. Default: "${DEFAULT_LABEL_STUDIO_FULL_JSON}"`,
1139
1844
  optional: true
1140
1845
  },
1141
1846
  createFilePerImage: {
1142
1847
  kind: "boolean",
1143
- brief: `Create a separate Label Studio JSON file for each image. Default to "${DEFAULT_CREATE_FILE_PER_IMAGE}"`,
1848
+ brief: `Create a separate Label Studio JSON file for each image. Default: "${DEFAULT_CREATE_FILE_PER_IMAGE}"`,
1144
1849
  optional: true
1145
1850
  },
1146
1851
  createFileListForServing: {
1147
1852
  kind: "boolean",
1148
- brief: `Create a file list for serving in Label Studio. Default to "${DEFAULT_CREATE_FILE_LIST_FOR_SERVING}"`,
1853
+ brief: `Create a file list for serving in Label Studio. Default: "${DEFAULT_CREATE_FILE_LIST_FOR_SERVING}"`,
1149
1854
  optional: true
1150
1855
  },
1151
1856
  fileListName: {
1152
1857
  kind: "parsed",
1153
- brief: `Name of the file list for serving. Default to "${DEFAULT_FILE_LIST_NAME}"`,
1858
+ brief: `Name of the file list for serving. Default: "${DEFAULT_FILE_LIST_NAME}"`,
1154
1859
  parse: String,
1155
1860
  optional: true
1156
1861
  },
1157
1862
  baseServerUrl: {
1158
1863
  kind: "parsed",
1159
- brief: `Base server URL for constructing image URLs in the file list. Default to "${DEFAULT_BASE_SERVER_URL}"`,
1864
+ brief: `Base server URL for constructing image URLs in the file list. Default: "${DEFAULT_BASE_SERVER_URL}"`,
1160
1865
  parse: String,
1161
1866
  optional: true
1162
1867
  },
1163
1868
  sortVertical: {
1164
1869
  kind: "parsed",
1165
- brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}" (default), "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}"`,
1870
+ brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
1166
1871
  parse: String,
1167
1872
  optional: true
1168
1873
  },
1169
1874
  sortHorizontal: {
1170
1875
  kind: "parsed",
1171
- brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}" (default), "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}"`,
1876
+ brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
1877
+ parse: String,
1878
+ optional: true
1879
+ },
1880
+ normalizeShape: {
1881
+ kind: "parsed",
1882
+ brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
1172
1883
  parse: String,
1173
1884
  optional: true
1885
+ },
1886
+ widthIncrement: {
1887
+ kind: "parsed",
1888
+ brief: `Increase bounding box width by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_WIDTH_INCREMENT}`,
1889
+ parse: Number,
1890
+ optional: true
1891
+ },
1892
+ heightIncrement: {
1893
+ kind: "parsed",
1894
+ brief: `Increase bounding box height by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_HEIGHT_INCREMENT}`,
1895
+ parse: Number,
1896
+ optional: true
1897
+ },
1898
+ precision: {
1899
+ kind: "parsed",
1900
+ brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_LABEL_STUDIO_PRECISION}`,
1901
+ parse: Number,
1902
+ optional: true
1174
1903
  }
1175
1904
  }
1176
1905
  },
@@ -1181,11 +1910,11 @@ var toLabelStudioCommand = (0, import_core.buildCommand)({
1181
1910
 
1182
1911
  // src/commands/toPPOCR/commands.ts
1183
1912
  init_cjs_shims();
1184
- var import_core2 = require("@stricli/core");
1913
+ var import_core4 = require("@stricli/core");
1185
1914
  init_constants();
1186
- var toPPOCRCommand = (0, import_core2.buildCommand)({
1915
+ var toPPOCRCommand = (0, import_core4.buildCommand)({
1187
1916
  loader: async () => {
1188
- const { convertToPPOCR: convertToPPOCR2 } = await Promise.resolve().then(() => (init_impl2(), impl_exports2));
1917
+ const { convertToPPOCR: convertToPPOCR2 } = await Promise.resolve().then(() => (init_impl4(), impl_exports4));
1189
1918
  return convertToPPOCR2;
1190
1919
  },
1191
1920
  parameters: {
@@ -1200,13 +1929,13 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
1200
1929
  flags: {
1201
1930
  outDir: {
1202
1931
  kind: "parsed",
1203
- brief: `Output directory. Default to "${OUTPUT_BASE_DIR}"`,
1932
+ brief: `Output directory. Default: "${OUTPUT_BASE_DIR}"`,
1204
1933
  parse: String,
1205
1934
  optional: true
1206
1935
  },
1207
1936
  fileName: {
1208
1937
  kind: "parsed",
1209
- brief: `Output PPOCR file name. Default to "${DEFAULT_PPOCR_FILE_NAME}"`,
1938
+ brief: `Output PPOCR file name. Default: "${DEFAULT_PPOCR_FILE_NAME}"`,
1210
1939
  parse: String,
1211
1940
  optional: true
1212
1941
  },
@@ -1218,15 +1947,39 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
1218
1947
  },
1219
1948
  sortVertical: {
1220
1949
  kind: "parsed",
1221
- brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}" (default), "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}"`,
1950
+ brief: `Sort bounding boxes vertically. Options: "${SORT_VERTICAL_NONE}", "${SORT_VERTICAL_TOP_BOTTOM}", "${SORT_VERTICAL_BOTTOM_TOP}". Default: "${DEFAULT_SORT_VERTICAL}"`,
1222
1951
  parse: String,
1223
1952
  optional: true
1224
1953
  },
1225
1954
  sortHorizontal: {
1226
1955
  kind: "parsed",
1227
- brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}" (default), "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}"`,
1956
+ brief: `Sort bounding boxes horizontally. Options: "${SORT_HORIZONTAL_NONE}", "${SORT_HORIZONTAL_LTR}", "${SORT_HORIZONTAL_RTL}". Default: "${DEFAULT_SORT_HORIZONTAL}"`,
1957
+ parse: String,
1958
+ optional: true
1959
+ },
1960
+ normalizeShape: {
1961
+ kind: "parsed",
1962
+ brief: `Normalize diamond-like shapes to axis-aligned rectangles. Options: "${SHAPE_NORMALIZE_NONE}", "${SHAPE_NORMALIZE_RECTANGLE}". Default: "${DEFAULT_SHAPE_NORMALIZE}"`,
1228
1963
  parse: String,
1229
1964
  optional: true
1965
+ },
1966
+ widthIncrement: {
1967
+ kind: "parsed",
1968
+ brief: `Increase bounding box width by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_WIDTH_INCREMENT}`,
1969
+ parse: Number,
1970
+ optional: true
1971
+ },
1972
+ heightIncrement: {
1973
+ kind: "parsed",
1974
+ brief: `Increase bounding box height by this amount (in pixels). Can be negative to decrease. Default: ${DEFAULT_HEIGHT_INCREMENT}`,
1975
+ parse: Number,
1976
+ optional: true
1977
+ },
1978
+ precision: {
1979
+ kind: "parsed",
1980
+ brief: `Number of decimal places for coordinates. Use -1 for full precision (no rounding). Default: ${DEFAULT_PPOCR_PRECISION} (integers)`,
1981
+ parse: Number,
1982
+ optional: true
1230
1983
  }
1231
1984
  }
1232
1985
  },
@@ -1236,10 +1989,12 @@ var toPPOCRCommand = (0, import_core2.buildCommand)({
1236
1989
  });
1237
1990
 
1238
1991
  // src/app.ts
1239
- var routes = (0, import_core3.buildRouteMap)({
1992
+ var routes = (0, import_core5.buildRouteMap)({
1240
1993
  routes: {
1241
1994
  toLabelStudio: toLabelStudioCommand,
1242
1995
  toPPOCR: toPPOCRCommand,
1996
+ "enhance-labelstudio": enhanceLabelStudioCommand,
1997
+ "enhance-ppocr": enhancePPOCRCommand,
1243
1998
  install: (0, import_auto_complete.buildInstallCommand)("label-studio-converter", {
1244
1999
  bash: "__label-studio-converter_bash_complete"
1245
2000
  }),
@@ -1253,7 +2008,7 @@ var routes = (0, import_core3.buildRouteMap)({
1253
2008
  }
1254
2009
  }
1255
2010
  });
1256
- var app = (0, import_core3.buildApplication)(routes, {
2011
+ var app = (0, import_core5.buildApplication)(routes, {
1257
2012
  name: "label-studio-converter",
1258
2013
  versionInfo: {
1259
2014
  currentVersion: version
@@ -1276,6 +2031,6 @@ function buildContext(process2) {
1276
2031
 
1277
2032
  // src/bin/cli.ts
1278
2033
  (async () => {
1279
- (0, import_core4.run)(app, process.argv.slice(2), buildContext(process));
2034
+ (0, import_core6.run)(app, process.argv.slice(2), buildContext(process));
1280
2035
  })();
1281
2036
  //# sourceMappingURL=cli.cjs.map