@eyepop.ai/eyepop 3.19.1 → 3.20.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.
@@ -9207,6 +9207,67 @@ Content-Disposition: form-data; name="${part.name}"\r
9207
9207
  });
9208
9208
  }
9209
9209
 
9210
+ // camera.ts
9211
+ var QUATERNION_TOLERANCE = 1e-3;
9212
+ function isFinite_(value) {
9213
+ return value === void 0 || Number.isFinite(value);
9214
+ }
9215
+ function validateCamera(camera) {
9216
+ if (camera.intrinsics !== void 0 && camera.hfovDegrees !== void 0) {
9217
+ throw new Error("camera.intrinsics and camera.hfovDegrees are alternatives, supply one");
9218
+ }
9219
+ if (camera.intrinsics === void 0 && camera.hfovDegrees === void 0) {
9220
+ throw new Error("camera requires either camera.intrinsics or camera.hfovDegrees");
9221
+ }
9222
+ if (camera.hfovDegrees !== void 0) {
9223
+ if (!(camera.hfovDegrees > 0 && camera.hfovDegrees < 180)) {
9224
+ throw new Error("camera.hfovDegrees must be a horizontal field of view in (0, 180) degrees");
9225
+ }
9226
+ } else {
9227
+ const intrinsics = camera.intrinsics;
9228
+ for (const [name, focal] of [
9229
+ ["fx", intrinsics.fx],
9230
+ ["fy", intrinsics.fy]
9231
+ ]) {
9232
+ if (!(focal > 0) || !Number.isFinite(focal)) {
9233
+ throw new Error(`camera.intrinsics.${name} must be a positive, finite focal length`);
9234
+ }
9235
+ }
9236
+ for (const [name, principal, extent] of [
9237
+ ["cx", intrinsics.cx, "width"],
9238
+ ["cy", intrinsics.cy, "height"]
9239
+ ]) {
9240
+ if (!(principal >= 0 && principal <= 1)) {
9241
+ throw new Error(`camera.intrinsics.${name} must be within [0, 1] of the frame ${extent}`);
9242
+ }
9243
+ }
9244
+ }
9245
+ if (camera.distortion !== void 0) {
9246
+ for (const name of ["k1", "k2", "p1", "p2", "k3"]) {
9247
+ if (!isFinite_(camera.distortion[name])) {
9248
+ throw new Error(`camera.distortion.${name} must be a finite number`);
9249
+ }
9250
+ }
9251
+ }
9252
+ if (camera.extrinsics !== void 0) {
9253
+ const translation = camera.extrinsics.translation;
9254
+ if (translation !== void 0) {
9255
+ for (const axis of ["x", "y", "z"]) {
9256
+ if (!Number.isFinite(translation[axis])) {
9257
+ throw new Error(`camera.extrinsics.translation.${axis} must be a finite number`);
9258
+ }
9259
+ }
9260
+ }
9261
+ const rotation = camera.extrinsics.rotation;
9262
+ if (rotation !== void 0) {
9263
+ const norm = Math.sqrt(rotation.w ** 2 + rotation.x ** 2 + rotation.y ** 2 + rotation.z ** 2);
9264
+ if (!(Math.abs(norm - 1) <= QUATERNION_TOLERANCE)) {
9265
+ throw new Error("camera.extrinsics.rotation must be a unit quaternion");
9266
+ }
9267
+ }
9268
+ }
9269
+ }
9270
+
9210
9271
  // worker/worker_types.ts
9211
9272
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
9212
9273
  PredictionVersion3[PredictionVersion3["V1"] = 1] = "V1";
@@ -9222,7 +9283,6 @@ var VideoMode = /* @__PURE__ */ ((VideoMode2) => {
9222
9283
  var PopComponentType = /* @__PURE__ */ ((PopComponentType2) => {
9223
9284
  PopComponentType2["FORWARD"] = "forward";
9224
9285
  PopComponentType2["INFERENCE"] = "inference";
9225
- PopComponentType2["TRACING"] = "tracing";
9226
9286
  PopComponentType2["TRACKING"] = "tracking";
9227
9287
  PopComponentType2["CONTOUR_FINDER"] = "contour_finder";
9228
9288
  PopComponentType2["COMPONENT_FINDER"] = "component_finder";
@@ -9261,6 +9321,16 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
9261
9321
  ContourType2["RECTANGLE"] = "rectangle";
9262
9322
  return ContourType2;
9263
9323
  })(ContourType || {});
9324
+ function validatePop(pop) {
9325
+ if (pop.depthMap !== void 0) {
9326
+ if (pop.depthMap.ability === void 0 === (pop.depthMap.abilityUuid === void 0)) {
9327
+ throw new Error("depthMap requires exactly one of ability or abilityUuid");
9328
+ }
9329
+ }
9330
+ if (pop.defaults?.camera !== void 0) {
9331
+ validateCamera(pop.defaults.camera);
9332
+ }
9333
+ }
9264
9334
 
9265
9335
  // worker/webrtc_whip.ts
9266
9336
  import { v4 as uuidv42 } from "uuid";
@@ -9751,7 +9821,7 @@ var UploadJob = class extends AbstractJob {
9751
9821
  }
9752
9822
  }
9753
9823
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9754
- if (this._params.componentParams || this._params.roi) {
9824
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9755
9825
  const formData = new FormData();
9756
9826
  if (this._params.componentParams) {
9757
9827
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9765,6 +9835,10 @@ var UploadJob = class extends AbstractJob {
9765
9835
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9766
9836
  formData.append("fps", blob);
9767
9837
  }
9838
+ if (this._params.camera) {
9839
+ const blob = new Blob([JSON.stringify(this._params.camera)], { type: "application/json" });
9840
+ formData.append("camera", blob);
9841
+ }
9768
9842
  const fileBlob = await new Response(await this.uploadBody()).blob();
9769
9843
  formData.append("file", fileBlob);
9770
9844
  const headers = {
@@ -9846,7 +9920,7 @@ var UploadJob = class extends AbstractJob {
9846
9920
  }
9847
9921
  }
9848
9922
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9849
- if (this._params.componentParams || this._params.roi) {
9923
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9850
9924
  const formData = new FormData();
9851
9925
  if (this._params.componentParams) {
9852
9926
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9860,6 +9934,10 @@ var UploadJob = class extends AbstractJob {
9860
9934
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9861
9935
  formData.append("fps", blob);
9862
9936
  }
9937
+ if (this._params.camera) {
9938
+ const blob = new Blob([JSON.stringify(this._params.camera)], { type: "application/json" });
9939
+ formData.append("camera", blob);
9940
+ }
9863
9941
  const fileBlob = await new Response(await this.uploadBody()).blob();
9864
9942
  formData.append("file", fileBlob);
9865
9943
  const headers = {
@@ -9916,6 +9994,7 @@ var LoadFromJob = class extends AbstractJob {
9916
9994
  params: this._params.componentParams,
9917
9995
  roi: this._params.roi,
9918
9996
  fps: this._params.fps,
9997
+ camera: this._params.camera,
9919
9998
  version: this._version
9920
9999
  };
9921
10000
  if (this._params.motionDetect) {
@@ -9976,6 +10055,9 @@ var UploadGroupJob = class extends AbstractJob {
9976
10055
  if (this._params.fps) {
9977
10056
  parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
9978
10057
  }
10058
+ if (this._params.camera) {
10059
+ parts.push({ name: "camera", contentType: "application/json", content: JSON.stringify(this._params.camera) });
10060
+ }
9979
10061
  for (const source of this._sources) {
9980
10062
  parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
9981
10063
  }
@@ -10023,6 +10105,9 @@ var LoadFromGroupJob = class extends AbstractJob {
10023
10105
  if (this._params.roi) {
10024
10106
  body["roi"] = this._params.roi;
10025
10107
  }
10108
+ if (this._params.camera) {
10109
+ body["camera"] = this._params.camera;
10110
+ }
10026
10111
  if (this._params.motionDetect) {
10027
10112
  Object.assign(body, this._params.motionDetect);
10028
10113
  }
@@ -10061,6 +10146,7 @@ var LoadFromAssetUuidJob = class extends AbstractJob {
10061
10146
  params: this._params.componentParams,
10062
10147
  roi: this._params.roi,
10063
10148
  fps: this._params.fps,
10149
+ camera: this._params.camera,
10064
10150
  version: this._version
10065
10151
  };
10066
10152
  if (this._params.motionDetect) {
@@ -10115,6 +10201,7 @@ var LoadMediaStreamJob = class extends AbstractJob {
10115
10201
  params: this._params.componentParams,
10116
10202
  roi: this._params.roi,
10117
10203
  fps: this._params.fps,
10204
+ camera: this._params.camera,
10118
10205
  version: this._version
10119
10206
  };
10120
10207
  if (this._params.motionDetect) {
@@ -10726,6 +10813,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10726
10813
  return this.options().pop || null;
10727
10814
  }
10728
10815
  async changePop(pop) {
10816
+ validatePop(pop);
10729
10817
  if (this.options().popId == "transient" /* Transient */) {
10730
10818
  await this.changeTransientPop(pop);
10731
10819
  return;
@@ -10798,7 +10886,22 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10798
10886
  return Promise.reject("unknown source type");
10799
10887
  }
10800
10888
  }
10889
+ /**
10890
+ * Reject a source's calibration before any work is started for it.
10891
+ *
10892
+ * Called by every entry point that takes ProcessParams rather than at the
10893
+ * one boundary they share, because they have none: the group methods build
10894
+ * their jobs directly instead of going through processV2. It runs before
10895
+ * the concurrency limit is acquired, so a request that can never be sent
10896
+ * does not hold a slot.
10897
+ */
10898
+ validateProcessParams(params) {
10899
+ if (params.camera !== void 0) {
10900
+ validateCamera(params.camera);
10901
+ }
10902
+ }
10801
10903
  async processV2(request) {
10904
+ this.validateProcessParams(request);
10802
10905
  if (request.source.file !== void 0) {
10803
10906
  return this.uploadFile(request.source, request);
10804
10907
  } else if (request.source.stream !== void 0) {
@@ -10819,6 +10922,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10819
10922
  if (paths.length === 0) {
10820
10923
  throw new Error("upload group requires at least one path");
10821
10924
  }
10925
+ this.validateProcessParams(params);
10822
10926
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10823
10927
  throw new Error("endpoint not connected, use connect()");
10824
10928
  }
@@ -10857,6 +10961,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10857
10961
  if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10858
10962
  throw new Error("mimeTypes must have the same length as streams");
10859
10963
  }
10964
+ this.validateProcessParams(params);
10860
10965
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10861
10966
  throw new Error("endpoint not connected, use connect()");
10862
10967
  }
@@ -10885,6 +10990,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10885
10990
  if (urls.length === 0) {
10886
10991
  throw new Error("load from group requires at least one url");
10887
10992
  }
10993
+ this.validateProcessParams(params);
10888
10994
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10889
10995
  throw new Error("endpoint not connected, use connect()");
10890
10996
  }
@@ -12804,6 +12910,176 @@ function decodeDepthMap(depth) {
12804
12910
  return new DepthMap(depth.width, depth.height, values);
12805
12911
  }
12806
12912
 
12913
+ // point_cloud.ts
12914
+ var VALUES_PER_POINT = 3;
12915
+ var isLittleEndianPlatform2 = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
12916
+ function base64ToBytes2(base64) {
12917
+ if (typeof Buffer !== "undefined") {
12918
+ const buffer = Buffer.from(base64, "base64");
12919
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
12920
+ }
12921
+ const binary = atob(base64);
12922
+ const bytes = new Uint8Array(binary.length);
12923
+ for (let i = 0; i < binary.length; i++) {
12924
+ bytes[i] = binary.charCodeAt(i);
12925
+ }
12926
+ return bytes;
12927
+ }
12928
+ var PointCloud = class {
12929
+ constructor(width, height, points, box) {
12930
+ this.width = width;
12931
+ this.height = height;
12932
+ this.points = points;
12933
+ this.box = box;
12934
+ }
12935
+ /**
12936
+ * The world point for pixel (i, j), or undefined where the worker placed none.
12937
+ *
12938
+ * (i, j) indexes the cloud's own grid - the mask bitmap, or the depth map
12939
+ * for a scene cloud: i is the column, j is the row.
12940
+ */
12941
+ at(i, j) {
12942
+ if (!(i >= 0 && i < this.width && j >= 0 && j < this.height)) {
12943
+ throw new Error(`(${i}, ${j}) is outside a ${this.width}x${this.height} cloud`);
12944
+ }
12945
+ const offset = (j * this.width + i) * VALUES_PER_POINT;
12946
+ const x = this.points[offset];
12947
+ const y = this.points[offset + 1];
12948
+ const z = this.points[offset + 2];
12949
+ if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
12950
+ return void 0;
12951
+ }
12952
+ return { x, y, z };
12953
+ }
12954
+ /**
12955
+ * The world point for a source frame coordinate inside the cloud's box.
12956
+ *
12957
+ * Needs a cloud built with cloudOfObject(), or with cloudOfDepth() and the
12958
+ * frame size: the grid covers a box, and nothing else knows where it sits.
12959
+ */
12960
+ atSource(x, y) {
12961
+ if (this.box === void 0) {
12962
+ throw new Error("atSource needs a box; build with cloudOfObject or cloudOfDepth");
12963
+ }
12964
+ if (!(this.box.width > 0) || !(this.box.height > 0)) {
12965
+ throw new Error(`invalid bounding box extent: ${this.box.width}x${this.box.height}`);
12966
+ }
12967
+ const i = Math.min(Math.max(Math.floor((x - this.box.x) / this.box.width * this.width), 0), this.width - 1);
12968
+ const j = Math.min(Math.max(Math.floor((y - this.box.y) / this.box.height * this.height), 0), this.height - 1);
12969
+ return this.at(i, j);
12970
+ }
12971
+ isPlaced(i, j) {
12972
+ return this.at(i, j) !== void 0;
12973
+ }
12974
+ /**
12975
+ * Just the points the worker placed.
12976
+ *
12977
+ * The shape a scatter plot or an export wants: the grid with its NaN holes
12978
+ * dropped, and no mask to apply first.
12979
+ */
12980
+ get placedPoints() {
12981
+ const placed = [];
12982
+ for (let offset = 0; offset < this.points.length; offset += VALUES_PER_POINT) {
12983
+ const x = this.points[offset];
12984
+ const y = this.points[offset + 1];
12985
+ const z = this.points[offset + 2];
12986
+ if (!Number.isNaN(x) && !Number.isNaN(y) && !Number.isNaN(z)) {
12987
+ placed.push({ x, y, z });
12988
+ }
12989
+ }
12990
+ return placed;
12991
+ }
12992
+ /**
12993
+ * Per axis (min, max) in metres over the placed points, or undefined if none were.
12994
+ *
12995
+ * The counterpart to DepthMap's finiteMin/finiteMax, which are one axis
12996
+ * because a depth map has one value per pixel.
12997
+ */
12998
+ get bounds() {
12999
+ let found = false;
13000
+ const min = { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY, z: Number.POSITIVE_INFINITY };
13001
+ const max = { x: Number.NEGATIVE_INFINITY, y: Number.NEGATIVE_INFINITY, z: Number.NEGATIVE_INFINITY };
13002
+ for (let offset = 0; offset < this.points.length; offset += VALUES_PER_POINT) {
13003
+ const x = this.points[offset];
13004
+ const y = this.points[offset + 1];
13005
+ const z = this.points[offset + 2];
13006
+ if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
13007
+ continue;
13008
+ }
13009
+ found = true;
13010
+ min.x = Math.min(min.x, x);
13011
+ min.y = Math.min(min.y, y);
13012
+ min.z = Math.min(min.z, z);
13013
+ max.x = Math.max(max.x, x);
13014
+ max.y = Math.max(max.y, y);
13015
+ max.z = Math.max(max.z, z);
13016
+ }
13017
+ return found ? { min, max } : void 0;
13018
+ }
13019
+ };
13020
+ function decode(width, height, world, box) {
13021
+ if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0) {
13022
+ throw new Error(`invalid point cloud dimensions: ${width}x${height}`);
13023
+ }
13024
+ const bytes = base64ToBytes2(world);
13025
+ const count = width * height * VALUES_PER_POINT;
13026
+ if (bytes.byteLength !== count * 4) {
13027
+ throw new Error(`'world' holds ${bytes.byteLength} bytes, expected ${count * 4} for ${width}x${height} xyz float32`);
13028
+ }
13029
+ let points;
13030
+ if (isLittleEndianPlatform2) {
13031
+ const aligned = new Uint8Array(bytes);
13032
+ points = new Float32Array(aligned.buffer, 0, count);
13033
+ } else {
13034
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
13035
+ points = new Float32Array(count);
13036
+ for (let i = 0; i < count; i++) {
13037
+ points[i] = view.getFloat32(i * 4, true);
13038
+ }
13039
+ }
13040
+ return new PointCloud(width, height, points, box);
13041
+ }
13042
+ function decodePointCloud(mask, box) {
13043
+ if (mask === void 0 || mask.world === void 0) {
13044
+ return void 0;
13045
+ }
13046
+ return decode(mask.width, mask.height, mask.world, box);
13047
+ }
13048
+ function cloudOfObject(obj) {
13049
+ if (obj === void 0 || obj.mask === void 0) {
13050
+ return void 0;
13051
+ }
13052
+ return decodePointCloud(obj.mask, { x: obj.x, y: obj.y, width: obj.width, height: obj.height });
13053
+ }
13054
+ function cloudOfDepth(depth, sourceWidth, sourceHeight) {
13055
+ if (depth === void 0 || depth.world === void 0) {
13056
+ return void 0;
13057
+ }
13058
+ const box = sourceWidth && sourceHeight ? { x: 0, y: 0, width: sourceWidth, height: sourceHeight } : void 0;
13059
+ return decode(depth.width, depth.height, depth.world, box);
13060
+ }
13061
+ function cloudsOfPrediction(prediction) {
13062
+ const clouds = [];
13063
+ if (prediction === void 0) {
13064
+ return clouds;
13065
+ }
13066
+ const walk = (objects) => {
13067
+ for (const obj of objects ?? []) {
13068
+ const cloud = cloudOfObject(obj);
13069
+ if (cloud !== void 0) {
13070
+ clouds.push(cloud);
13071
+ }
13072
+ walk(obj.objects);
13073
+ }
13074
+ };
13075
+ walk(prediction.objects);
13076
+ const scene = cloudOfDepth(prediction.depth, prediction.source_width, prediction.source_height);
13077
+ if (scene !== void 0) {
13078
+ clouds.push(scene);
13079
+ }
13080
+ return clouds;
13081
+ }
13082
+
12807
13083
  // worker/webrtc_whep.ts
12808
13084
  var WebrtcWhep = class extends WebrtcBase {
12809
13085
  constructor(ingressId, getSession, client, requestLogger) {
@@ -12908,6 +13184,9 @@ var EyePop;
12908
13184
  } : void 0;
12909
13185
  EyePop2.endpoint = workerEndpoint;
12910
13186
  function workerEndpoint(opts = {}) {
13187
+ if (opts.pop !== void 0) {
13188
+ validatePop(opts.pop);
13189
+ }
12911
13190
  if (opts.isLocalMode === void 0) {
12912
13191
  opts.isLocalMode = stringToBooleanSafe(readEnv("EYEPOP_LOCAL_MODE"));
12913
13192
  }
@@ -13059,8 +13338,10 @@ export {
13059
13338
  ModelTrainingStage,
13060
13339
  ModelType,
13061
13340
  MotionModel,
13341
+ PointCloud,
13062
13342
  PopComponentType,
13063
13343
  PredictionVersion,
13344
+ QUATERNION_TOLERANCE,
13064
13345
  Semaphore,
13065
13346
  Stream,
13066
13347
  TaskMode,
@@ -13079,13 +13360,19 @@ export {
13079
13360
  WorkflowPhase,
13080
13361
  accessTokenCredential,
13081
13362
  apiKeyCredential,
13363
+ cloudOfDepth,
13364
+ cloudOfObject,
13365
+ cloudsOfPrediction,
13082
13366
  decodeDepthMap,
13367
+ decodePointCloud,
13083
13368
  index_default as default,
13084
13369
  encodeMultipartFormData,
13085
13370
  getBaseUrl,
13086
13371
  randomMultipartBoundary,
13087
13372
  readableStreamFromString,
13088
- resolveAuth
13373
+ resolveAuth,
13374
+ validateCamera,
13375
+ validatePop
13089
13376
  };
13090
13377
  /*! Bundled license information:
13091
13378