@eyepop.ai/eyepop 3.19.1 → 3.20.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.
@@ -8693,8 +8693,10 @@ __export(index_exports, {
8693
8693
  ModelTrainingStage: () => ModelTrainingStage,
8694
8694
  ModelType: () => ModelType,
8695
8695
  MotionModel: () => MotionModel,
8696
+ PointCloud: () => PointCloud,
8696
8697
  PopComponentType: () => PopComponentType,
8697
8698
  PredictionVersion: () => PredictionVersion,
8699
+ QUATERNION_TOLERANCE: () => QUATERNION_TOLERANCE,
8698
8700
  Semaphore: () => Semaphore,
8699
8701
  Stream: () => Stream,
8700
8702
  TaskMode: () => TaskMode,
@@ -8713,13 +8715,19 @@ __export(index_exports, {
8713
8715
  WorkflowPhase: () => WorkflowPhase,
8714
8716
  accessTokenCredential: () => accessTokenCredential,
8715
8717
  apiKeyCredential: () => apiKeyCredential,
8718
+ cloudOfDepth: () => cloudOfDepth,
8719
+ cloudOfObject: () => cloudOfObject,
8720
+ cloudsOfPrediction: () => cloudsOfPrediction,
8716
8721
  decodeDepthMap: () => decodeDepthMap,
8722
+ decodePointCloud: () => decodePointCloud,
8717
8723
  default: () => index_default,
8718
8724
  encodeMultipartFormData: () => encodeMultipartFormData,
8719
8725
  getBaseUrl: () => getBaseUrl,
8720
8726
  randomMultipartBoundary: () => randomMultipartBoundary,
8721
8727
  readableStreamFromString: () => readableStreamFromString,
8722
- resolveAuth: () => resolveAuth
8728
+ resolveAuth: () => resolveAuth,
8729
+ validateCamera: () => validateCamera,
8730
+ validatePop: () => validatePop
8723
8731
  });
8724
8732
  module.exports = __toCommonJS(index_exports);
8725
8733
 
@@ -9298,6 +9306,67 @@ Content-Disposition: form-data; name="${part.name}"\r
9298
9306
  });
9299
9307
  }
9300
9308
 
9309
+ // camera.ts
9310
+ var QUATERNION_TOLERANCE = 1e-3;
9311
+ function isFinite_(value) {
9312
+ return value === void 0 || Number.isFinite(value);
9313
+ }
9314
+ function validateCamera(camera) {
9315
+ if (camera.intrinsics !== void 0 && camera.hfovDegrees !== void 0) {
9316
+ throw new Error("camera.intrinsics and camera.hfovDegrees are alternatives, supply one");
9317
+ }
9318
+ if (camera.intrinsics === void 0 && camera.hfovDegrees === void 0) {
9319
+ throw new Error("camera requires either camera.intrinsics or camera.hfovDegrees");
9320
+ }
9321
+ if (camera.hfovDegrees !== void 0) {
9322
+ if (!(camera.hfovDegrees > 0 && camera.hfovDegrees < 180)) {
9323
+ throw new Error("camera.hfovDegrees must be a horizontal field of view in (0, 180) degrees");
9324
+ }
9325
+ } else {
9326
+ const intrinsics = camera.intrinsics;
9327
+ for (const [name, focal] of [
9328
+ ["fx", intrinsics.fx],
9329
+ ["fy", intrinsics.fy]
9330
+ ]) {
9331
+ if (!(focal > 0) || !Number.isFinite(focal)) {
9332
+ throw new Error(`camera.intrinsics.${name} must be a positive, finite focal length`);
9333
+ }
9334
+ }
9335
+ for (const [name, principal, extent] of [
9336
+ ["cx", intrinsics.cx, "width"],
9337
+ ["cy", intrinsics.cy, "height"]
9338
+ ]) {
9339
+ if (!(principal >= 0 && principal <= 1)) {
9340
+ throw new Error(`camera.intrinsics.${name} must be within [0, 1] of the frame ${extent}`);
9341
+ }
9342
+ }
9343
+ }
9344
+ if (camera.distortion !== void 0) {
9345
+ for (const name of ["k1", "k2", "p1", "p2", "k3"]) {
9346
+ if (!isFinite_(camera.distortion[name])) {
9347
+ throw new Error(`camera.distortion.${name} must be a finite number`);
9348
+ }
9349
+ }
9350
+ }
9351
+ if (camera.extrinsics !== void 0) {
9352
+ const translation = camera.extrinsics.translation;
9353
+ if (translation !== void 0) {
9354
+ for (const axis of ["x", "y", "z"]) {
9355
+ if (!Number.isFinite(translation[axis])) {
9356
+ throw new Error(`camera.extrinsics.translation.${axis} must be a finite number`);
9357
+ }
9358
+ }
9359
+ }
9360
+ const rotation = camera.extrinsics.rotation;
9361
+ if (rotation !== void 0) {
9362
+ const norm = Math.sqrt(rotation.w ** 2 + rotation.x ** 2 + rotation.y ** 2 + rotation.z ** 2);
9363
+ if (!(Math.abs(norm - 1) <= QUATERNION_TOLERANCE)) {
9364
+ throw new Error("camera.extrinsics.rotation must be a unit quaternion");
9365
+ }
9366
+ }
9367
+ }
9368
+ }
9369
+
9301
9370
  // worker/worker_types.ts
9302
9371
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
9303
9372
  PredictionVersion3[PredictionVersion3["V1"] = 1] = "V1";
@@ -9352,6 +9421,16 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
9352
9421
  ContourType2["RECTANGLE"] = "rectangle";
9353
9422
  return ContourType2;
9354
9423
  })(ContourType || {});
9424
+ function validatePop(pop) {
9425
+ if (pop.depthMap !== void 0) {
9426
+ if (pop.depthMap.ability === void 0 === (pop.depthMap.abilityUuid === void 0)) {
9427
+ throw new Error("depthMap requires exactly one of ability or abilityUuid");
9428
+ }
9429
+ }
9430
+ if (pop.defaults?.camera !== void 0) {
9431
+ validateCamera(pop.defaults.camera);
9432
+ }
9433
+ }
9355
9434
 
9356
9435
  // worker/webrtc_whip.ts
9357
9436
  var import_uuid2 = require("uuid");
@@ -9842,7 +9921,7 @@ var UploadJob = class extends AbstractJob {
9842
9921
  }
9843
9922
  }
9844
9923
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9845
- if (this._params.componentParams || this._params.roi) {
9924
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9846
9925
  const formData = new FormData();
9847
9926
  if (this._params.componentParams) {
9848
9927
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9856,6 +9935,10 @@ var UploadJob = class extends AbstractJob {
9856
9935
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9857
9936
  formData.append("fps", blob);
9858
9937
  }
9938
+ if (this._params.camera) {
9939
+ const blob = new Blob([JSON.stringify(this._params.camera)], { type: "application/json" });
9940
+ formData.append("camera", blob);
9941
+ }
9859
9942
  const fileBlob = await new Response(await this.uploadBody()).blob();
9860
9943
  formData.append("file", fileBlob);
9861
9944
  const headers = {
@@ -9937,7 +10020,7 @@ var UploadJob = class extends AbstractJob {
9937
10020
  }
9938
10021
  }
9939
10022
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9940
- if (this._params.componentParams || this._params.roi) {
10023
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9941
10024
  const formData = new FormData();
9942
10025
  if (this._params.componentParams) {
9943
10026
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9951,6 +10034,10 @@ var UploadJob = class extends AbstractJob {
9951
10034
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9952
10035
  formData.append("fps", blob);
9953
10036
  }
10037
+ if (this._params.camera) {
10038
+ const blob = new Blob([JSON.stringify(this._params.camera)], { type: "application/json" });
10039
+ formData.append("camera", blob);
10040
+ }
9954
10041
  const fileBlob = await new Response(await this.uploadBody()).blob();
9955
10042
  formData.append("file", fileBlob);
9956
10043
  const headers = {
@@ -10007,6 +10094,7 @@ var LoadFromJob = class extends AbstractJob {
10007
10094
  params: this._params.componentParams,
10008
10095
  roi: this._params.roi,
10009
10096
  fps: this._params.fps,
10097
+ camera: this._params.camera,
10010
10098
  version: this._version
10011
10099
  };
10012
10100
  if (this._params.motionDetect) {
@@ -10067,6 +10155,9 @@ var UploadGroupJob = class extends AbstractJob {
10067
10155
  if (this._params.fps) {
10068
10156
  parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
10069
10157
  }
10158
+ if (this._params.camera) {
10159
+ parts.push({ name: "camera", contentType: "application/json", content: JSON.stringify(this._params.camera) });
10160
+ }
10070
10161
  for (const source of this._sources) {
10071
10162
  parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
10072
10163
  }
@@ -10114,6 +10205,9 @@ var LoadFromGroupJob = class extends AbstractJob {
10114
10205
  if (this._params.roi) {
10115
10206
  body["roi"] = this._params.roi;
10116
10207
  }
10208
+ if (this._params.camera) {
10209
+ body["camera"] = this._params.camera;
10210
+ }
10117
10211
  if (this._params.motionDetect) {
10118
10212
  Object.assign(body, this._params.motionDetect);
10119
10213
  }
@@ -10152,6 +10246,7 @@ var LoadFromAssetUuidJob = class extends AbstractJob {
10152
10246
  params: this._params.componentParams,
10153
10247
  roi: this._params.roi,
10154
10248
  fps: this._params.fps,
10249
+ camera: this._params.camera,
10155
10250
  version: this._version
10156
10251
  };
10157
10252
  if (this._params.motionDetect) {
@@ -10206,6 +10301,7 @@ var LoadMediaStreamJob = class extends AbstractJob {
10206
10301
  params: this._params.componentParams,
10207
10302
  roi: this._params.roi,
10208
10303
  fps: this._params.fps,
10304
+ camera: this._params.camera,
10209
10305
  version: this._version
10210
10306
  };
10211
10307
  if (this._params.motionDetect) {
@@ -10817,6 +10913,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10817
10913
  return this.options().pop || null;
10818
10914
  }
10819
10915
  async changePop(pop) {
10916
+ validatePop(pop);
10820
10917
  if (this.options().popId == "transient" /* Transient */) {
10821
10918
  await this.changeTransientPop(pop);
10822
10919
  return;
@@ -10889,7 +10986,22 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10889
10986
  return Promise.reject("unknown source type");
10890
10987
  }
10891
10988
  }
10989
+ /**
10990
+ * Reject a source's calibration before any work is started for it.
10991
+ *
10992
+ * Called by every entry point that takes ProcessParams rather than at the
10993
+ * one boundary they share, because they have none: the group methods build
10994
+ * their jobs directly instead of going through processV2. It runs before
10995
+ * the concurrency limit is acquired, so a request that can never be sent
10996
+ * does not hold a slot.
10997
+ */
10998
+ validateProcessParams(params) {
10999
+ if (params.camera !== void 0) {
11000
+ validateCamera(params.camera);
11001
+ }
11002
+ }
10892
11003
  async processV2(request) {
11004
+ this.validateProcessParams(request);
10893
11005
  if (request.source.file !== void 0) {
10894
11006
  return this.uploadFile(request.source, request);
10895
11007
  } else if (request.source.stream !== void 0) {
@@ -10910,6 +11022,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10910
11022
  if (paths.length === 0) {
10911
11023
  throw new Error("upload group requires at least one path");
10912
11024
  }
11025
+ this.validateProcessParams(params);
10913
11026
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10914
11027
  throw new Error("endpoint not connected, use connect()");
10915
11028
  }
@@ -10948,6 +11061,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10948
11061
  if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10949
11062
  throw new Error("mimeTypes must have the same length as streams");
10950
11063
  }
11064
+ this.validateProcessParams(params);
10951
11065
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10952
11066
  throw new Error("endpoint not connected, use connect()");
10953
11067
  }
@@ -10976,6 +11090,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10976
11090
  if (urls.length === 0) {
10977
11091
  throw new Error("load from group requires at least one url");
10978
11092
  }
11093
+ this.validateProcessParams(params);
10979
11094
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10980
11095
  throw new Error("endpoint not connected, use connect()");
10981
11096
  }
@@ -12895,6 +13010,176 @@ function decodeDepthMap(depth) {
12895
13010
  return new DepthMap(depth.width, depth.height, values);
12896
13011
  }
12897
13012
 
13013
+ // point_cloud.ts
13014
+ var VALUES_PER_POINT = 3;
13015
+ var isLittleEndianPlatform2 = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
13016
+ function base64ToBytes2(base64) {
13017
+ if (typeof Buffer !== "undefined") {
13018
+ const buffer = Buffer.from(base64, "base64");
13019
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
13020
+ }
13021
+ const binary = atob(base64);
13022
+ const bytes = new Uint8Array(binary.length);
13023
+ for (let i = 0; i < binary.length; i++) {
13024
+ bytes[i] = binary.charCodeAt(i);
13025
+ }
13026
+ return bytes;
13027
+ }
13028
+ var PointCloud = class {
13029
+ constructor(width, height, points, box) {
13030
+ this.width = width;
13031
+ this.height = height;
13032
+ this.points = points;
13033
+ this.box = box;
13034
+ }
13035
+ /**
13036
+ * The world point for pixel (i, j), or undefined where the worker placed none.
13037
+ *
13038
+ * (i, j) indexes the cloud's own grid - the mask bitmap, or the depth map
13039
+ * for a scene cloud: i is the column, j is the row.
13040
+ */
13041
+ at(i, j) {
13042
+ if (!(i >= 0 && i < this.width && j >= 0 && j < this.height)) {
13043
+ throw new Error(`(${i}, ${j}) is outside a ${this.width}x${this.height} cloud`);
13044
+ }
13045
+ const offset = (j * this.width + i) * VALUES_PER_POINT;
13046
+ const x = this.points[offset];
13047
+ const y = this.points[offset + 1];
13048
+ const z = this.points[offset + 2];
13049
+ if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
13050
+ return void 0;
13051
+ }
13052
+ return { x, y, z };
13053
+ }
13054
+ /**
13055
+ * The world point for a source frame coordinate inside the cloud's box.
13056
+ *
13057
+ * Needs a cloud built with cloudOfObject(), or with cloudOfDepth() and the
13058
+ * frame size: the grid covers a box, and nothing else knows where it sits.
13059
+ */
13060
+ atSource(x, y) {
13061
+ if (this.box === void 0) {
13062
+ throw new Error("atSource needs a box; build with cloudOfObject or cloudOfDepth");
13063
+ }
13064
+ if (!(this.box.width > 0) || !(this.box.height > 0)) {
13065
+ throw new Error(`invalid bounding box extent: ${this.box.width}x${this.box.height}`);
13066
+ }
13067
+ const i = Math.min(Math.max(Math.floor((x - this.box.x) / this.box.width * this.width), 0), this.width - 1);
13068
+ const j = Math.min(Math.max(Math.floor((y - this.box.y) / this.box.height * this.height), 0), this.height - 1);
13069
+ return this.at(i, j);
13070
+ }
13071
+ isPlaced(i, j) {
13072
+ return this.at(i, j) !== void 0;
13073
+ }
13074
+ /**
13075
+ * Just the points the worker placed.
13076
+ *
13077
+ * The shape a scatter plot or an export wants: the grid with its NaN holes
13078
+ * dropped, and no mask to apply first.
13079
+ */
13080
+ get placedPoints() {
13081
+ const placed = [];
13082
+ for (let offset = 0; offset < this.points.length; offset += VALUES_PER_POINT) {
13083
+ const x = this.points[offset];
13084
+ const y = this.points[offset + 1];
13085
+ const z = this.points[offset + 2];
13086
+ if (!Number.isNaN(x) && !Number.isNaN(y) && !Number.isNaN(z)) {
13087
+ placed.push({ x, y, z });
13088
+ }
13089
+ }
13090
+ return placed;
13091
+ }
13092
+ /**
13093
+ * Per axis (min, max) in metres over the placed points, or undefined if none were.
13094
+ *
13095
+ * The counterpart to DepthMap's finiteMin/finiteMax, which are one axis
13096
+ * because a depth map has one value per pixel.
13097
+ */
13098
+ get bounds() {
13099
+ let found = false;
13100
+ const min = { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY, z: Number.POSITIVE_INFINITY };
13101
+ const max = { x: Number.NEGATIVE_INFINITY, y: Number.NEGATIVE_INFINITY, z: Number.NEGATIVE_INFINITY };
13102
+ for (let offset = 0; offset < this.points.length; offset += VALUES_PER_POINT) {
13103
+ const x = this.points[offset];
13104
+ const y = this.points[offset + 1];
13105
+ const z = this.points[offset + 2];
13106
+ if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
13107
+ continue;
13108
+ }
13109
+ found = true;
13110
+ min.x = Math.min(min.x, x);
13111
+ min.y = Math.min(min.y, y);
13112
+ min.z = Math.min(min.z, z);
13113
+ max.x = Math.max(max.x, x);
13114
+ max.y = Math.max(max.y, y);
13115
+ max.z = Math.max(max.z, z);
13116
+ }
13117
+ return found ? { min, max } : void 0;
13118
+ }
13119
+ };
13120
+ function decode(width, height, world, box) {
13121
+ if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0) {
13122
+ throw new Error(`invalid point cloud dimensions: ${width}x${height}`);
13123
+ }
13124
+ const bytes = base64ToBytes2(world);
13125
+ const count = width * height * VALUES_PER_POINT;
13126
+ if (bytes.byteLength !== count * 4) {
13127
+ throw new Error(`'world' holds ${bytes.byteLength} bytes, expected ${count * 4} for ${width}x${height} xyz float32`);
13128
+ }
13129
+ let points;
13130
+ if (isLittleEndianPlatform2) {
13131
+ const aligned = new Uint8Array(bytes);
13132
+ points = new Float32Array(aligned.buffer, 0, count);
13133
+ } else {
13134
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
13135
+ points = new Float32Array(count);
13136
+ for (let i = 0; i < count; i++) {
13137
+ points[i] = view.getFloat32(i * 4, true);
13138
+ }
13139
+ }
13140
+ return new PointCloud(width, height, points, box);
13141
+ }
13142
+ function decodePointCloud(mask, box) {
13143
+ if (mask === void 0 || mask.world === void 0) {
13144
+ return void 0;
13145
+ }
13146
+ return decode(mask.width, mask.height, mask.world, box);
13147
+ }
13148
+ function cloudOfObject(obj) {
13149
+ if (obj === void 0 || obj.mask === void 0) {
13150
+ return void 0;
13151
+ }
13152
+ return decodePointCloud(obj.mask, { x: obj.x, y: obj.y, width: obj.width, height: obj.height });
13153
+ }
13154
+ function cloudOfDepth(depth, sourceWidth, sourceHeight) {
13155
+ if (depth === void 0 || depth.world === void 0) {
13156
+ return void 0;
13157
+ }
13158
+ const box = sourceWidth && sourceHeight ? { x: 0, y: 0, width: sourceWidth, height: sourceHeight } : void 0;
13159
+ return decode(depth.width, depth.height, depth.world, box);
13160
+ }
13161
+ function cloudsOfPrediction(prediction) {
13162
+ const clouds = [];
13163
+ if (prediction === void 0) {
13164
+ return clouds;
13165
+ }
13166
+ const walk = (objects) => {
13167
+ for (const obj of objects ?? []) {
13168
+ const cloud = cloudOfObject(obj);
13169
+ if (cloud !== void 0) {
13170
+ clouds.push(cloud);
13171
+ }
13172
+ walk(obj.objects);
13173
+ }
13174
+ };
13175
+ walk(prediction.objects);
13176
+ const scene = cloudOfDepth(prediction.depth, prediction.source_width, prediction.source_height);
13177
+ if (scene !== void 0) {
13178
+ clouds.push(scene);
13179
+ }
13180
+ return clouds;
13181
+ }
13182
+
12898
13183
  // worker/webrtc_whep.ts
12899
13184
  var WebrtcWhep = class extends WebrtcBase {
12900
13185
  constructor(ingressId, getSession, client, requestLogger) {
@@ -12999,6 +13284,9 @@ var EyePop;
12999
13284
  } : void 0;
13000
13285
  EyePop2.endpoint = workerEndpoint;
13001
13286
  function workerEndpoint(opts = {}) {
13287
+ if (opts.pop !== void 0) {
13288
+ validatePop(opts.pop);
13289
+ }
13002
13290
  if (opts.isLocalMode === void 0) {
13003
13291
  opts.isLocalMode = stringToBooleanSafe(readEnv("EYEPOP_LOCAL_MODE"));
13004
13292
  }
@@ -13151,8 +13439,10 @@ var index_default = EyePop;
13151
13439
  ModelTrainingStage,
13152
13440
  ModelType,
13153
13441
  MotionModel,
13442
+ PointCloud,
13154
13443
  PopComponentType,
13155
13444
  PredictionVersion,
13445
+ QUATERNION_TOLERANCE,
13156
13446
  Semaphore,
13157
13447
  Stream,
13158
13448
  TaskMode,
@@ -13171,12 +13461,18 @@ var index_default = EyePop;
13171
13461
  WorkflowPhase,
13172
13462
  accessTokenCredential,
13173
13463
  apiKeyCredential,
13464
+ cloudOfDepth,
13465
+ cloudOfObject,
13466
+ cloudsOfPrediction,
13174
13467
  decodeDepthMap,
13468
+ decodePointCloud,
13175
13469
  encodeMultipartFormData,
13176
13470
  getBaseUrl,
13177
13471
  randomMultipartBoundary,
13178
13472
  readableStreamFromString,
13179
- resolveAuth
13473
+ resolveAuth,
13474
+ validateCamera,
13475
+ validatePop
13180
13476
  });
13181
13477
  /*! Bundled license information:
13182
13478