vsegments 0.1.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/CHANGELOG.md +47 -0
- package/INSTALLATION.md +464 -0
- package/LICENSE +21 -0
- package/PACKAGE_STRUCTURE.md +303 -0
- package/QUICKSTART.md +151 -0
- package/README.md +380 -0
- package/bin/cli.js +159 -0
- package/package.json +48 -0
- package/src/core.js +241 -0
- package/src/index.d.ts +83 -0
- package/src/index.js +20 -0
- package/src/models.js +99 -0
- package/src/utils.js +118 -0
- package/src/visualize.js +182 -0
package/src/visualize.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visualization utilities for drawing bounding boxes and segmentation masks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { createCanvas, loadImage, registerFont } = require('canvas');
|
|
6
|
+
const fs = require('fs').promises;
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// Extended color palette
|
|
10
|
+
const COLORS = [
|
|
11
|
+
'#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF',
|
|
12
|
+
'#FFA500', '#800080', '#FFC0CB', '#A52A2A', '#808080', '#F5F5DC',
|
|
13
|
+
'#40E0D0', '#FF7F50', '#E6E6FA', '#EE82EE', '#FFD700', '#C0C0C0',
|
|
14
|
+
'#000080', '#800000', '#008080', '#808000', '#FF6347', '#4B0082',
|
|
15
|
+
'#DC143C', '#00CED1', '#9370DB', '#FF1493', '#7FFF00', '#D2691E'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Draw bounding boxes on an image
|
|
20
|
+
* @param {Canvas} canvas - Canvas with loaded image
|
|
21
|
+
* @param {BoundingBox[]} boxes - Array of bounding boxes
|
|
22
|
+
* @param {Object} options - Drawing options
|
|
23
|
+
* @returns {Canvas} - Canvas with bounding boxes drawn
|
|
24
|
+
*/
|
|
25
|
+
function plotBoundingBoxes(canvas, boxes, options = {}) {
|
|
26
|
+
const {
|
|
27
|
+
lineWidth = 4,
|
|
28
|
+
fontSize = 14,
|
|
29
|
+
showLabels = true
|
|
30
|
+
} = options;
|
|
31
|
+
|
|
32
|
+
const ctx = canvas.getContext('2d');
|
|
33
|
+
const width = canvas.width;
|
|
34
|
+
const height = canvas.height;
|
|
35
|
+
|
|
36
|
+
ctx.lineWidth = lineWidth;
|
|
37
|
+
ctx.font = `${fontSize}px Arial`;
|
|
38
|
+
|
|
39
|
+
boxes.forEach((box, i) => {
|
|
40
|
+
const color = COLORS[i % COLORS.length];
|
|
41
|
+
const [absX1, absY1, absX2, absY2] = box.toAbsolute(width, height);
|
|
42
|
+
|
|
43
|
+
// Draw rectangle
|
|
44
|
+
ctx.strokeStyle = color;
|
|
45
|
+
ctx.strokeRect(absX1, absY1, absX2 - absX1, absY2 - absY1);
|
|
46
|
+
|
|
47
|
+
// Draw label
|
|
48
|
+
if (showLabels && box.label) {
|
|
49
|
+
ctx.fillStyle = color;
|
|
50
|
+
ctx.fillText(box.label, absX1 + 8, absY1 + fontSize + 6);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return canvas;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Overlay a mask on the image
|
|
59
|
+
* @param {Canvas} canvas - Canvas with image
|
|
60
|
+
* @param {Buffer} maskData - Mask data
|
|
61
|
+
* @param {number} width - Image width
|
|
62
|
+
* @param {number} height - Image height
|
|
63
|
+
* @param {string} color - Color to use for mask
|
|
64
|
+
* @param {number} alpha - Transparency (0-1)
|
|
65
|
+
*/
|
|
66
|
+
function overlayMask(canvas, maskData, width, height, color, alpha) {
|
|
67
|
+
const ctx = canvas.getContext('2d');
|
|
68
|
+
const imageData = ctx.getImageData(0, 0, width, height);
|
|
69
|
+
const data = imageData.data;
|
|
70
|
+
|
|
71
|
+
// Parse color
|
|
72
|
+
const colorInt = parseInt(color.slice(1), 16);
|
|
73
|
+
const r = (colorInt >> 16) & 255;
|
|
74
|
+
const g = (colorInt >> 8) & 255;
|
|
75
|
+
const b = colorInt & 255;
|
|
76
|
+
|
|
77
|
+
// Apply mask
|
|
78
|
+
for (let i = 0; i < maskData.length; i++) {
|
|
79
|
+
const maskValue = maskData[i] / 255;
|
|
80
|
+
if (maskValue > 0) {
|
|
81
|
+
const idx = i * 4;
|
|
82
|
+
const maskAlpha = maskValue * alpha;
|
|
83
|
+
|
|
84
|
+
data[idx] = Math.round(data[idx] * (1 - maskAlpha) + r * maskAlpha);
|
|
85
|
+
data[idx + 1] = Math.round(data[idx + 1] * (1 - maskAlpha) + g * maskAlpha);
|
|
86
|
+
data[idx + 2] = Math.round(data[idx + 2] * (1 - maskAlpha) + b * maskAlpha);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ctx.putImageData(imageData, 0, 0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Draw segmentation masks on an image
|
|
95
|
+
* @param {Canvas} canvas - Canvas with loaded image
|
|
96
|
+
* @param {SegmentationMask[]} masks - Array of segmentation masks
|
|
97
|
+
* @param {Object} options - Drawing options
|
|
98
|
+
* @returns {Canvas} - Canvas with masks drawn
|
|
99
|
+
*/
|
|
100
|
+
function plotSegmentationMasks(canvas, masks, options = {}) {
|
|
101
|
+
const {
|
|
102
|
+
lineWidth = 4,
|
|
103
|
+
fontSize = 14,
|
|
104
|
+
alpha = 0.7,
|
|
105
|
+
showLabels = true
|
|
106
|
+
} = options;
|
|
107
|
+
|
|
108
|
+
const ctx = canvas.getContext('2d');
|
|
109
|
+
const width = canvas.width;
|
|
110
|
+
const height = canvas.height;
|
|
111
|
+
|
|
112
|
+
// Overlay masks first
|
|
113
|
+
masks.forEach((mask, i) => {
|
|
114
|
+
const color = COLORS[i % COLORS.length];
|
|
115
|
+
overlayMask(canvas, mask.mask, width, height, color, alpha);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Draw bounding boxes and labels
|
|
119
|
+
ctx.lineWidth = lineWidth;
|
|
120
|
+
ctx.font = `${fontSize}px Arial`;
|
|
121
|
+
|
|
122
|
+
masks.forEach((mask, i) => {
|
|
123
|
+
const color = COLORS[i % COLORS.length];
|
|
124
|
+
|
|
125
|
+
// Draw bounding box
|
|
126
|
+
ctx.strokeStyle = color;
|
|
127
|
+
ctx.strokeRect(mask.x0, mask.y0, mask.x1 - mask.x0, mask.y1 - mask.y0);
|
|
128
|
+
|
|
129
|
+
// Draw label
|
|
130
|
+
if (showLabels && mask.label) {
|
|
131
|
+
ctx.fillStyle = color;
|
|
132
|
+
ctx.fillText(mask.label, mask.x0 + 8, mask.y0 - 6);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return canvas;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Load image into canvas
|
|
141
|
+
* @param {string} imagePath - Path to image file
|
|
142
|
+
* @param {number} maxSize - Maximum dimension
|
|
143
|
+
* @returns {Promise<Canvas>} - Canvas with loaded image
|
|
144
|
+
*/
|
|
145
|
+
async function loadImageToCanvas(imagePath, maxSize = 1024) {
|
|
146
|
+
const img = await loadImage(imagePath);
|
|
147
|
+
|
|
148
|
+
// Calculate new dimensions
|
|
149
|
+
let width = img.width;
|
|
150
|
+
let height = img.height;
|
|
151
|
+
|
|
152
|
+
if (width > maxSize || height > maxSize) {
|
|
153
|
+
const scale = Math.min(maxSize / width, maxSize / height);
|
|
154
|
+
width = Math.round(width * scale);
|
|
155
|
+
height = Math.round(height * scale);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Create canvas and draw image
|
|
159
|
+
const canvas = createCanvas(width, height);
|
|
160
|
+
const ctx = canvas.getContext('2d');
|
|
161
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
162
|
+
|
|
163
|
+
return canvas;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Save canvas to file
|
|
168
|
+
* @param {Canvas} canvas - Canvas to save
|
|
169
|
+
* @param {string} outputPath - Output file path
|
|
170
|
+
*/
|
|
171
|
+
async function saveCanvas(canvas, outputPath) {
|
|
172
|
+
const buffer = canvas.toBuffer('image/png');
|
|
173
|
+
await fs.writeFile(outputPath, buffer);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
plotBoundingBoxes,
|
|
178
|
+
plotSegmentationMasks,
|
|
179
|
+
loadImageToCanvas,
|
|
180
|
+
saveCanvas,
|
|
181
|
+
COLORS
|
|
182
|
+
};
|