@eyepop.ai/eyepop-render-2d 3.14.7 → 3.15.1

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 CHANGED
@@ -46,7 +46,7 @@ const example_image_path = 'examples/example.jpg'
46
46
 
47
47
  context.drawImage(image, 0, 0)
48
48
 
49
- const endpoint = await EyePop.endpoint().connect()
49
+ const endpoint = await EyePop.workerEndpoint().connect()
50
50
  try {
51
51
  let results = await endpoint.process({ source: { path: example_image_path } })
52
52
  for await (let result of results) {
@@ -93,7 +93,7 @@ const example_image_path = 'examples/example.jpg'
93
93
 
94
94
  const endpoint = await EyePop.workerEndpoint({auth: {oAuth2: true}}).connect();
95
95
  await endpoint.changePop({ components: [{
96
- type: PopComponentType.INFERENCE,
96
+ type: 'inference',
97
97
  model: 'eyepop.person:latest',
98
98
  categoryName: 'person'
99
99
  }]})
@@ -116,7 +116,7 @@ Change this rendering behaviour by passing in rendering rule(s), e.g.:
116
116
 
117
117
  ```javascript
118
118
  // ...
119
- Render2d.renderer(context, [Render2.renderFace()]).draw(result)
119
+ Render2d.renderer(context, [Render2d.renderFace()]).draw(result)
120
120
  // ...
121
121
  ```
122
122
 
@@ -129,11 +129,11 @@ Most prebuild render classes provide a reasonable defaults, as shown below.
129
129
 
130
130
  ```typescript
131
131
  Render2d.renderBox({
132
- showClass = true,
133
- showConfidence = false,
134
- showTraceId = false,
135
- showNestedClasses = false,
136
- target = '$..objects.*',
132
+ showClass: true,
133
+ showConfidence: false,
134
+ showTraceId: false,
135
+ showNestedClasses: false,
136
+ target: '$..objects.*',
137
137
  })
138
138
  ```
139
139
 
@@ -157,8 +157,8 @@ Render2d.renderHand({
157
157
 
158
158
  ```typescript
159
159
  Render2d.renderFace({
160
- showLabels = false,
161
- target = '$..objects[?(@.classLabel=="face")]',
160
+ showLabels: false,
161
+ target: '$..objects[?(@.classLabel=="face")]',
162
162
  })
163
163
  ```
164
164
 
@@ -178,16 +178,19 @@ type RenderTextOptions = {
178
178
  showText: boolean;
179
179
  fitToBounds: boolean;
180
180
  textPadding?: number;
181
+ maxLines?: number;
181
182
  } & RenderTarget;
182
183
  declare class RenderText implements Render {
183
184
  target: string;
184
185
  fitToBounds: boolean;
185
186
  textPadding: number;
187
+ maxLines: number;
186
188
  private context;
187
189
  private style;
188
190
  constructor(options?: Partial<RenderTextOptions>);
189
191
  start(context: CanvasRenderingContext2D, style: Style): void;
190
192
  draw(element: PredictedObject, xOffset: number, yOffset: number, xScale: number, yScale: number, streamTime: StreamTime): void;
193
+ private wrapLine;
191
194
  drawText(text: string, context: CanvasRenderingContext2D, xPos: number, yPos: number, style: Style, fontSize: number): void;
192
195
  static toTitleCase(str: string): string;
193
196
  getMinFontSize(context: CanvasRenderingContext2D, element: any, width: number, height: number, style: any, scaleToFit?: boolean): number;
@@ -178,16 +178,19 @@ type RenderTextOptions = {
178
178
  showText: boolean;
179
179
  fitToBounds: boolean;
180
180
  textPadding?: number;
181
+ maxLines?: number;
181
182
  } & RenderTarget;
182
183
  declare class RenderText implements Render {
183
184
  target: string;
184
185
  fitToBounds: boolean;
185
186
  textPadding: number;
187
+ maxLines: number;
186
188
  private context;
187
189
  private style;
188
190
  constructor(options?: Partial<RenderTextOptions>);
189
191
  start(context: CanvasRenderingContext2D, style: Style): void;
190
192
  draw(element: PredictedObject, xOffset: number, yOffset: number, xScale: number, yScale: number, streamTime: StreamTime): void;
193
+ private wrapLine;
191
194
  drawText(text: string, context: CanvasRenderingContext2D, xPos: number, yPos: number, style: Style, fontSize: number): void;
192
195
  static toTitleCase(str: string): string;
193
196
  getMinFontSize(context: CanvasRenderingContext2D, element: any, width: number, height: number, style: any, scaleToFit?: boolean): number;
@@ -589,7 +589,7 @@ var RenderFace = class {
589
589
  let expressions = [];
590
590
  if (face.classes) {
591
591
  for (let i = 0; i < face.classes.length; i++) {
592
- if (face.classes[i].confidence > 0.95) {
592
+ if ((face.classes[i].confidence ?? 0) > 0.95) {
593
593
  const label = EXPRESSION_LABELS.get(face.classes[i].classLabel);
594
594
  if (label) {
595
595
  expressions.push(label);
@@ -3420,7 +3420,7 @@ var RenderHand = class {
3420
3420
  let minZ = -1;
3421
3421
  for (let i2 = 0; i2 < keyPoints.points.length; i2++) {
3422
3422
  const keyPoint = keyPoints.points[i2];
3423
- labelsToPoints.set(keyPoint.classLabel, keyPoint);
3423
+ labelsToPoints.set(keyPoint.classLabel ?? "<undefined>", keyPoint);
3424
3424
  if (keyPoint.z) {
3425
3425
  if (keyPoint.z > maxZ) {
3426
3426
  maxZ = keyPoint.z;
@@ -3548,7 +3548,7 @@ var RenderPose = class {
3548
3548
  let minZ = -1;
3549
3549
  for (let i2 = 0; i2 < keyPoints.points.length; i2++) {
3550
3550
  const keyPoint = keyPoints.points[i2];
3551
- labelsToPoints.set(keyPoint.classLabel, keyPoint);
3551
+ labelsToPoints.set(keyPoint.classLabel ?? "<undefined>", keyPoint);
3552
3552
  if (keyPoint.z) {
3553
3553
  if (keyPoint.z > maxZ) {
3554
3554
  maxZ = keyPoint.z;
@@ -3592,9 +3592,9 @@ var RenderPose = class {
3592
3592
  const radius = MAX_RADIUS - (z - minZ) * (MAX_RADIUS - MIN_RADIUS) / (maxZ - minZ);
3593
3593
  context.beginPath();
3594
3594
  context.arc(x, y, radius, 0, Math.PI * 2, false);
3595
- if (p.classLabel.includes("left")) {
3595
+ if (p.classLabel?.includes("left")) {
3596
3596
  context.fillStyle = style.colors.left_color;
3597
- } else if (p.classLabel.includes("right")) {
3597
+ } else if (p.classLabel?.includes("right")) {
3598
3598
  context.fillStyle = style.colors.right_color;
3599
3599
  } else {
3600
3600
  context.fillStyle = style.colors.primary_color;
@@ -3935,9 +3935,9 @@ var RenderKeyPoints = class {
3935
3935
  if (!this.showLabels) continue;
3936
3936
  let label;
3937
3937
  if (typeof p.visible == "undefined") {
3938
- label = p.classLabel;
3938
+ label = p.classLabel ?? "<undefined>";
3939
3939
  } else {
3940
- label = `${p.classLabel} (${p.visible ? "visible" : "invisible"})`;
3940
+ label = `${p.classLabel ?? "<undefined>"} (${p.visible ? "visible" : "invisible"})`;
3941
3941
  }
3942
3942
  context.font = style.font;
3943
3943
  context.fillStyle = "#ffffff";
@@ -3957,10 +3957,12 @@ var RenderText = class {
3957
3957
  this.target = DEFAULT_TARGET;
3958
3958
  this.fitToBounds = true;
3959
3959
  this.textPadding = 0.1;
3960
- const { target = "$..objects[?(@.texts)]", fitToBounds = true, textPadding = 0.1 } = options;
3960
+ this.maxLines = 12;
3961
+ const { target = "$..objects[?(@.texts)]", fitToBounds = true, textPadding = 0.1, maxLines = 12 } = options;
3961
3962
  this.target = target;
3962
3963
  this.fitToBounds = fitToBounds;
3963
3964
  this.textPadding = Math.max(0.02, Math.min(0.2, textPadding));
3965
+ this.maxLines = maxLines;
3964
3966
  }
3965
3967
  start(context, style) {
3966
3968
  this.context = context;
@@ -3982,37 +3984,74 @@ var RenderText = class {
3982
3984
  padding = Math.min(padding, w * this.textPadding, h * this.textPadding);
3983
3985
  const boundingBoxWidth = w - padding * 2;
3984
3986
  const boundingBoxHeight = h - padding * 2;
3985
- let fontSize = this.getMinFontSize(context, element, boundingBoxWidth, boundingBoxHeight, style, this.fitToBounds);
3986
- let label = "";
3987
+ const baseFontSize = parseInt(style.font.split(" ")[0]);
3987
3988
  const fontType = style.font.split(" ")[1];
3989
+ const rawLines = [];
3990
+ for (const entry of element.texts) {
3991
+ const text = entry?.text || "";
3992
+ if (!text) continue;
3993
+ rawLines.push(...text.split("\n"));
3994
+ }
3995
+ if (rawLines.length === 0) return;
3996
+ const isShortSingleLine = rawLines.length === 1 && rawLines[0].length <= 60;
3997
+ let fontSize;
3998
+ if (this.fitToBounds && isShortSingleLine) {
3999
+ fontSize = this.getMinFontSize(context, element, boundingBoxWidth, boundingBoxHeight, style, true);
4000
+ } else {
4001
+ fontSize = baseFontSize;
4002
+ if (this.fitToBounds) {
4003
+ const maxFontFromHeight = Math.floor(boundingBoxHeight * 0.12);
4004
+ fontSize = Math.min(fontSize, Math.max(maxFontFromHeight, 10));
4005
+ }
4006
+ }
3988
4007
  context.font = `${fontSize}px ${fontType}`;
3989
- let totalTextHeight = 0;
3990
- const textMetrics = [];
3991
- for (let i = 0; i < element.texts.length; i++) {
3992
- label = element.texts[i]?.text || "";
3993
- if (!label) continue;
3994
- const metrics = context.measureText(label);
3995
- const lineHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
3996
- totalTextHeight += lineHeight;
3997
- textMetrics.push({
3998
- text: label,
3999
- width: metrics.width,
4000
- height: lineHeight
4001
- });
4008
+ const wrappedLines = [];
4009
+ for (const line of rawLines) {
4010
+ const wrapped = this.wrapLine(context, line, boundingBoxWidth);
4011
+ wrappedLines.push(...wrapped);
4012
+ }
4013
+ let displayLines = wrappedLines;
4014
+ const truncated = this.maxLines > 0 && wrappedLines.length > this.maxLines;
4015
+ if (truncated) {
4016
+ displayLines = wrappedLines.slice(0, this.maxLines);
4017
+ const last = displayLines[displayLines.length - 1];
4018
+ displayLines[displayLines.length - 1] = last.replace(/\s+\S*$/, "") + "\u2026";
4002
4019
  }
4003
4020
  const spacing = fontSize * 0.2;
4004
- if (element.texts.length > 1) {
4005
- totalTextHeight += spacing * (element.texts.length - 1);
4021
+ const totalTextHeight = displayLines.length * fontSize + (displayLines.length - 1) * spacing;
4022
+ if (totalTextHeight > boundingBoxHeight && displayLines.length > 0) {
4023
+ const scaleFactor = boundingBoxHeight / totalTextHeight;
4024
+ fontSize = Math.max(8, Math.floor(fontSize * scaleFactor));
4025
+ context.font = `${fontSize}px ${fontType}`;
4006
4026
  }
4007
- let textY = element.y * yScale + yOffset + (h - totalTextHeight) / 2;
4008
- for (let i = 0; i < textMetrics.length; i++) {
4009
- const metrics = textMetrics[i];
4010
- let textX = element.x * xScale + xOffset + (w - metrics.width) / 2;
4011
- this.drawText(metrics.text, context, textX, textY, style, fontSize);
4012
- textY += metrics.height + spacing;
4027
+ const finalSpacing = fontSize * 0.2;
4028
+ const finalLineHeight = fontSize + finalSpacing;
4029
+ const finalTotalHeight = displayLines.length * finalLineHeight - finalSpacing;
4030
+ let textY = element.y * yScale + yOffset + (h - finalTotalHeight) / 2;
4031
+ const textX = element.x * xScale + xOffset + padding;
4032
+ for (const line of displayLines) {
4033
+ this.drawText(line, context, textX, textY, style, fontSize);
4034
+ textY += finalLineHeight;
4013
4035
  }
4014
4036
  }
4015
- // Draw a label on the canvas
4037
+ wrapLine(context, text, maxWidth) {
4038
+ if (!text) return [""];
4039
+ if (context.measureText(text).width <= maxWidth) return [text];
4040
+ const words = text.split(" ");
4041
+ const lines = [];
4042
+ let current = "";
4043
+ for (const word of words) {
4044
+ const candidate = current ? `${current} ${word}` : word;
4045
+ if (context.measureText(candidate).width > maxWidth && current) {
4046
+ lines.push(current);
4047
+ current = word;
4048
+ } else {
4049
+ current = candidate;
4050
+ }
4051
+ }
4052
+ if (current) lines.push(current);
4053
+ return lines.length > 0 ? lines : [text];
4054
+ }
4016
4055
  drawText(text, context, xPos, yPos, style, fontSize) {
4017
4056
  context.fillStyle = "#ffffff";
4018
4057
  context.textAlign = "left";
@@ -4024,10 +4063,9 @@ var RenderText = class {
4024
4063
  static toTitleCase(str) {
4025
4064
  if (!str) return "";
4026
4065
  return str.replace(/\w\S*/g, function(txt) {
4027
- return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
4066
+ return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase();
4028
4067
  });
4029
4068
  }
4030
- // Gets the optimal font size for all labels to fit in the bounding box
4031
4069
  getMinFontSize(context, element, width, height, style, scaleToFit = false) {
4032
4070
  const textLines = (element.texts || []).map((t) => t?.text || "").filter((t) => t);
4033
4071
  const textCount = textLines.length;