@eyepop.ai/eyepop 3.19.0 → 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.
@@ -8677,6 +8677,7 @@ __export(index_exports, {
8677
8677
  EvaluationStatus: () => EvaluationStatus,
8678
8678
  ExportedBy: () => ExportedBy,
8679
8679
  EyePop: () => EyePop,
8680
+ EyePopDataWebSocketError: () => EyePopDataWebSocketError,
8680
8681
  EyepopLineDecoder: () => EyepopLineDecoder,
8681
8682
  ForwardOperatorType: () => ForwardOperatorType,
8682
8683
  InferenceType: () => InferenceType,
@@ -8692,8 +8693,10 @@ __export(index_exports, {
8692
8693
  ModelTrainingStage: () => ModelTrainingStage,
8693
8694
  ModelType: () => ModelType,
8694
8695
  MotionModel: () => MotionModel,
8696
+ PointCloud: () => PointCloud,
8695
8697
  PopComponentType: () => PopComponentType,
8696
8698
  PredictionVersion: () => PredictionVersion,
8699
+ QUATERNION_TOLERANCE: () => QUATERNION_TOLERANCE,
8697
8700
  Semaphore: () => Semaphore,
8698
8701
  Stream: () => Stream,
8699
8702
  TaskMode: () => TaskMode,
@@ -8712,13 +8715,19 @@ __export(index_exports, {
8712
8715
  WorkflowPhase: () => WorkflowPhase,
8713
8716
  accessTokenCredential: () => accessTokenCredential,
8714
8717
  apiKeyCredential: () => apiKeyCredential,
8718
+ cloudOfDepth: () => cloudOfDepth,
8719
+ cloudOfObject: () => cloudOfObject,
8720
+ cloudsOfPrediction: () => cloudsOfPrediction,
8715
8721
  decodeDepthMap: () => decodeDepthMap,
8722
+ decodePointCloud: () => decodePointCloud,
8716
8723
  default: () => index_default,
8717
8724
  encodeMultipartFormData: () => encodeMultipartFormData,
8718
8725
  getBaseUrl: () => getBaseUrl,
8719
8726
  randomMultipartBoundary: () => randomMultipartBoundary,
8720
8727
  readableStreamFromString: () => readableStreamFromString,
8721
- resolveAuth: () => resolveAuth
8728
+ resolveAuth: () => resolveAuth,
8729
+ validateCamera: () => validateCamera,
8730
+ validatePop: () => validatePop
8722
8731
  });
8723
8732
  module.exports = __toCommonJS(index_exports);
8724
8733
 
@@ -9297,6 +9306,67 @@ Content-Disposition: form-data; name="${part.name}"\r
9297
9306
  });
9298
9307
  }
9299
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
+
9300
9370
  // worker/worker_types.ts
9301
9371
  var PredictionVersion = /* @__PURE__ */ ((PredictionVersion3) => {
9302
9372
  PredictionVersion3[PredictionVersion3["V1"] = 1] = "V1";
@@ -9351,6 +9421,16 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
9351
9421
  ContourType2["RECTANGLE"] = "rectangle";
9352
9422
  return ContourType2;
9353
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
+ }
9354
9434
 
9355
9435
  // worker/webrtc_whip.ts
9356
9436
  var import_uuid2 = require("uuid");
@@ -9841,7 +9921,7 @@ var UploadJob = class extends AbstractJob {
9841
9921
  }
9842
9922
  }
9843
9923
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9844
- if (this._params.componentParams || this._params.roi) {
9924
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9845
9925
  const formData = new FormData();
9846
9926
  if (this._params.componentParams) {
9847
9927
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9855,6 +9935,10 @@ var UploadJob = class extends AbstractJob {
9855
9935
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9856
9936
  formData.append("fps", blob);
9857
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
+ }
9858
9942
  const fileBlob = await new Response(await this.uploadBody()).blob();
9859
9943
  formData.append("file", fileBlob);
9860
9944
  const headers = {
@@ -9936,7 +10020,7 @@ var UploadJob = class extends AbstractJob {
9936
10020
  }
9937
10021
  }
9938
10022
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9939
- if (this._params.componentParams || this._params.roi) {
10023
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9940
10024
  const formData = new FormData();
9941
10025
  if (this._params.componentParams) {
9942
10026
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9950,6 +10034,10 @@ var UploadJob = class extends AbstractJob {
9950
10034
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9951
10035
  formData.append("fps", blob);
9952
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
+ }
9953
10041
  const fileBlob = await new Response(await this.uploadBody()).blob();
9954
10042
  formData.append("file", fileBlob);
9955
10043
  const headers = {
@@ -10006,6 +10094,7 @@ var LoadFromJob = class extends AbstractJob {
10006
10094
  params: this._params.componentParams,
10007
10095
  roi: this._params.roi,
10008
10096
  fps: this._params.fps,
10097
+ camera: this._params.camera,
10009
10098
  version: this._version
10010
10099
  };
10011
10100
  if (this._params.motionDetect) {
@@ -10066,6 +10155,9 @@ var UploadGroupJob = class extends AbstractJob {
10066
10155
  if (this._params.fps) {
10067
10156
  parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
10068
10157
  }
10158
+ if (this._params.camera) {
10159
+ parts.push({ name: "camera", contentType: "application/json", content: JSON.stringify(this._params.camera) });
10160
+ }
10069
10161
  for (const source of this._sources) {
10070
10162
  parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
10071
10163
  }
@@ -10113,6 +10205,9 @@ var LoadFromGroupJob = class extends AbstractJob {
10113
10205
  if (this._params.roi) {
10114
10206
  body["roi"] = this._params.roi;
10115
10207
  }
10208
+ if (this._params.camera) {
10209
+ body["camera"] = this._params.camera;
10210
+ }
10116
10211
  if (this._params.motionDetect) {
10117
10212
  Object.assign(body, this._params.motionDetect);
10118
10213
  }
@@ -10151,6 +10246,7 @@ var LoadFromAssetUuidJob = class extends AbstractJob {
10151
10246
  params: this._params.componentParams,
10152
10247
  roi: this._params.roi,
10153
10248
  fps: this._params.fps,
10249
+ camera: this._params.camera,
10154
10250
  version: this._version
10155
10251
  };
10156
10252
  if (this._params.motionDetect) {
@@ -10205,6 +10301,7 @@ var LoadMediaStreamJob = class extends AbstractJob {
10205
10301
  params: this._params.componentParams,
10206
10302
  roi: this._params.roi,
10207
10303
  fps: this._params.fps,
10304
+ camera: this._params.camera,
10208
10305
  version: this._version
10209
10306
  };
10210
10307
  if (this._params.motionDetect) {
@@ -10816,6 +10913,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10816
10913
  return this.options().pop || null;
10817
10914
  }
10818
10915
  async changePop(pop) {
10916
+ validatePop(pop);
10819
10917
  if (this.options().popId == "transient" /* Transient */) {
10820
10918
  await this.changeTransientPop(pop);
10821
10919
  return;
@@ -10888,7 +10986,22 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10888
10986
  return Promise.reject("unknown source type");
10889
10987
  }
10890
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
+ }
10891
11003
  async processV2(request) {
11004
+ this.validateProcessParams(request);
10892
11005
  if (request.source.file !== void 0) {
10893
11006
  return this.uploadFile(request.source, request);
10894
11007
  } else if (request.source.stream !== void 0) {
@@ -10909,6 +11022,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10909
11022
  if (paths.length === 0) {
10910
11023
  throw new Error("upload group requires at least one path");
10911
11024
  }
11025
+ this.validateProcessParams(params);
10912
11026
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10913
11027
  throw new Error("endpoint not connected, use connect()");
10914
11028
  }
@@ -10947,6 +11061,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10947
11061
  if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10948
11062
  throw new Error("mimeTypes must have the same length as streams");
10949
11063
  }
11064
+ this.validateProcessParams(params);
10950
11065
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10951
11066
  throw new Error("endpoint not connected, use connect()");
10952
11067
  }
@@ -10975,6 +11090,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10975
11090
  if (urls.length === 0) {
10976
11091
  throw new Error("load from group requires at least one url");
10977
11092
  }
11093
+ this.validateProcessParams(params);
10978
11094
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10979
11095
  throw new Error("endpoint not connected, use connect()");
10980
11096
  }
@@ -11522,57 +11638,8 @@ if ("document" in globalThis && "implementation" in globalThis.document) {
11522
11638
  };
11523
11639
  }
11524
11640
 
11525
- // ../../node_modules/url-join/lib/url-join.js
11526
- function normalize(strArray) {
11527
- var resultArray = [];
11528
- if (strArray.length === 0) {
11529
- return "";
11530
- }
11531
- if (typeof strArray[0] !== "string") {
11532
- throw new TypeError("Url must be a string. Received " + strArray[0]);
11533
- }
11534
- if (strArray[0].match(/^[^/:]+:\/*$/) && strArray.length > 1) {
11535
- var first = strArray.shift();
11536
- strArray[0] = first + strArray[0];
11537
- }
11538
- if (strArray[0].match(/^file:\/\/\//)) {
11539
- strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1:///");
11540
- } else {
11541
- strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, "$1://");
11542
- }
11543
- for (var i = 0; i < strArray.length; i++) {
11544
- var component = strArray[i];
11545
- if (typeof component !== "string") {
11546
- throw new TypeError("Url must be a string. Received " + component);
11547
- }
11548
- if (component === "") {
11549
- continue;
11550
- }
11551
- if (i > 0) {
11552
- component = component.replace(/^[\/]+/, "");
11553
- }
11554
- if (i < strArray.length - 1) {
11555
- component = component.replace(/[\/]+$/, "");
11556
- } else {
11557
- component = component.replace(/[\/]+$/, "/");
11558
- }
11559
- resultArray.push(component);
11560
- }
11561
- var str = resultArray.join("/");
11562
- str = str.replace(/\/(\?|&|#[^!])/g, "$1");
11563
- var parts = str.split("?");
11564
- str = parts.shift() + (parts.length > 0 ? "?" : "") + parts.join("&");
11565
- return str;
11566
- }
11567
- function urlJoin() {
11568
- var input;
11569
- if (typeof arguments[0] === "object") {
11570
- input = arguments[0];
11571
- } else {
11572
- input = [].slice.call(arguments);
11573
- }
11574
- return normalize(input);
11575
- }
11641
+ // data/data_endpoint.ts
11642
+ var import_url_join = __toESM(require("url-join"));
11576
11643
 
11577
11644
  // data/data_types.ts
11578
11645
  var DataApiType = /* @__PURE__ */ ((DataApiType2) => {
@@ -11925,6 +11992,33 @@ var EvaluateDatasetJob = class {
11925
11992
  // data/data_endpoint.ts
11926
11993
  var WS_INITIAL_RECONNECT_DELAY = 1e3;
11927
11994
  var WS_MAX_RECONNECT_DELAY = 6e4;
11995
+ function ws_origin(url) {
11996
+ try {
11997
+ return new URL(url).origin;
11998
+ } catch {
11999
+ return url;
12000
+ }
12001
+ }
12002
+ function ws_failure_reason(event, sourceError) {
12003
+ if (sourceError instanceof Error) {
12004
+ return sourceError.message;
12005
+ }
12006
+ if (typeof sourceError === "string") {
12007
+ return sourceError;
12008
+ }
12009
+ return event?.message ?? "";
12010
+ }
12011
+ var EyePopDataWebSocketError = class extends Error {
12012
+ constructor(url, event) {
12013
+ const sourceError = event?.error ?? event;
12014
+ const reason = ws_failure_reason(event, sourceError);
12015
+ super(reason ? `EyePop Dataset event WebSocket connection failed: ${reason}` : "EyePop Dataset event WebSocket connection failed");
12016
+ this.code = "DATA_WEBSOCKET_CONNECTION_FAILED";
12017
+ this.name = "EyePopDataWebSocketError";
12018
+ this.endpoint = ws_origin(url);
12019
+ this.sourceError = sourceError;
12020
+ }
12021
+ };
11928
12022
  function omit_nulls(_, value) {
11929
12023
  if (value === null) {
11930
12024
  return void 0;
@@ -11938,6 +12032,9 @@ var DataEndpoint = class extends Endpoint {
11938
12032
  this.vlmApiUrl = null;
11939
12033
  this._ws = null;
11940
12034
  this._ws_current_reconnect_delay = null;
12035
+ this._ws_reconnect_timeout = null;
12036
+ this._ws_reconnect_enabled = false;
12037
+ this._ws_connecting = null;
11941
12038
  this._accountId = options.accountId || null;
11942
12039
  this._account_event_handlers = [];
11943
12040
  this._dataset_uuid_to_event_handlers = /* @__PURE__ */ new Map();
@@ -11961,14 +12058,15 @@ var DataEndpoint = class extends Endpoint {
11961
12058
  };
11962
12059
  }
11963
12060
  async disconnect(wait = true) {
12061
+ this._ws_reconnect_enabled = false;
12062
+ if (this._ws_reconnect_timeout) {
12063
+ clearTimeout(this._ws_reconnect_timeout);
12064
+ this._ws_reconnect_timeout = null;
12065
+ }
11964
12066
  this.datasetApiUrl = null;
11965
12067
  this.vlmApiUrl = null;
11966
12068
  if (this._ws) {
11967
- try {
11968
- this._ws.close();
11969
- } catch (e) {
11970
- this._logger.warn("ignored exception", e);
11971
- }
12069
+ this.close_ws(this._ws, "unsubscribe");
11972
12070
  this._ws = null;
11973
12071
  }
11974
12072
  await super.disconnect(wait);
@@ -12006,13 +12104,14 @@ var DataEndpoint = class extends Endpoint {
12006
12104
  const vlmApiUrl = new URL(config.vlm_api_url, this.eyepopUrl());
12007
12105
  this.vlmApiUrl = vlmApiUrl.toString();
12008
12106
  if (this.options().disableWs) {
12107
+ this._ws_reconnect_enabled = false;
12009
12108
  this._logger.info("disabled ws connection");
12010
12109
  } else {
12011
12110
  await this.reconnect_ws();
12012
12111
  }
12013
12112
  if (this.options().useCookie) {
12014
12113
  const client = this._client;
12015
- const url = urlJoin(this.datasetApiUrl, "security", "check?set_cookie=true");
12114
+ const url = (0, import_url_join.default)(this.datasetApiUrl, "security", "check?set_cookie=true");
12016
12115
  const headers2 = {
12017
12116
  Authorization: await this.authorizationHeader()
12018
12117
  };
@@ -12029,37 +12128,68 @@ var DataEndpoint = class extends Endpoint {
12029
12128
  return Promise.resolve(this);
12030
12129
  }
12031
12130
  async reconnect_ws() {
12131
+ if (this._ws_connecting) {
12132
+ return this._ws_connecting;
12133
+ }
12134
+ const connecting = this.open_ws();
12135
+ this._ws_connecting = connecting;
12136
+ try {
12137
+ await connecting;
12138
+ } finally {
12139
+ this._ws_connecting = null;
12140
+ }
12141
+ }
12142
+ async open_ws() {
12032
12143
  if (this._ws) {
12033
- this._ws.close();
12144
+ this.close_ws(this._ws, "unsubscribe");
12034
12145
  this._ws = null;
12035
12146
  }
12036
12147
  const baseUrl = this.datasetApiUrl?.replace("https://", "wss://").replace("http://", "ws://");
12037
12148
  if (!baseUrl) {
12038
12149
  return;
12039
12150
  }
12040
- const ws_url = urlJoin(baseUrl, "events");
12151
+ const ws_url = (0, import_url_join.default)(baseUrl, "events");
12041
12152
  this._requestLogger.debug("about to connect ws to: %s", ws_url);
12042
12153
  return new Promise((resolve, reject) => {
12154
+ let settled = false;
12155
+ const settle_ok = () => {
12156
+ settled = true;
12157
+ resolve();
12158
+ };
12159
+ const settle_err = (error) => {
12160
+ settled = true;
12161
+ reject(error);
12162
+ };
12043
12163
  const ws = createWebSocket(ws_url);
12044
12164
  if (!ws) {
12045
- return resolve();
12165
+ return settle_ok();
12046
12166
  }
12047
12167
  this._ws = ws;
12048
- ws.onopen = async () => {
12049
- const auth_header = await this.authorizationHeader();
12050
- let msg = JSON.stringify({ authorization: auth_header });
12051
- this._requestLogger.debug("ws send: %s", msg);
12052
- ws.send(msg);
12053
- msg = JSON.stringify({ subscribe: { account_uuid: this._accountId } });
12054
- this._requestLogger.debug("ws send: %s", msg);
12055
- ws.send(msg);
12056
- for (let dataset_uuid in this._dataset_uuid_to_event_handlers.keys()) {
12057
- const msg2 = JSON.stringify({ subscribe: { dataset_uuid } });
12058
- this._requestLogger.debug("ws send: %s", msg2);
12059
- ws.send(msg2);
12060
- }
12061
- this._ws_current_reconnect_delay = WS_INITIAL_RECONNECT_DELAY;
12062
- resolve();
12168
+ ws.onopen = () => {
12169
+ void this.authorizationHeader().then((auth_header) => {
12170
+ if (this._ws !== ws) {
12171
+ this.close_ws(ws, "unsubscribe");
12172
+ settle_ok();
12173
+ return;
12174
+ }
12175
+ let msg = JSON.stringify({ authorization: auth_header });
12176
+ this._requestLogger.debug("ws send: %s", msg);
12177
+ ws.send(msg);
12178
+ msg = JSON.stringify({ subscribe: { account_uuid: this._accountId } });
12179
+ this._requestLogger.debug("ws send: %s", msg);
12180
+ ws.send(msg);
12181
+ for (const dataset_uuid of this._dataset_uuid_to_event_handlers.keys()) {
12182
+ const msg2 = JSON.stringify({ subscribe: { dataset_uuid } });
12183
+ this._requestLogger.debug("ws send: %s", msg2);
12184
+ ws.send(msg2);
12185
+ }
12186
+ this._ws_current_reconnect_delay = WS_INITIAL_RECONNECT_DELAY;
12187
+ this._ws_reconnect_enabled = true;
12188
+ settle_ok();
12189
+ }).catch((error) => {
12190
+ this.close_ws(ws, "authorization failed", 1011);
12191
+ settle_err(error);
12192
+ });
12063
12193
  };
12064
12194
  ws.onmessage = (event) => {
12065
12195
  const data = JSON.parse(event.data);
@@ -12070,24 +12200,54 @@ var DataEndpoint = class extends Endpoint {
12070
12200
  ws.onclose = (event) => {
12071
12201
  const { code, reason } = event;
12072
12202
  this._requestLogger.debug("ws closed: %d %s", code, reason);
12073
- if (reason !== "unsubscribe") {
12074
- if (!this._ws_current_reconnect_delay) {
12075
- this._ws_current_reconnect_delay = WS_INITIAL_RECONNECT_DELAY;
12076
- } else if (this._ws_current_reconnect_delay < WS_MAX_RECONNECT_DELAY) {
12077
- this._ws_current_reconnect_delay *= 1.5;
12078
- }
12079
- setTimeout(async () => {
12080
- await this.reconnect_ws();
12081
- }, this._ws_current_reconnect_delay);
12203
+ if (!settled) {
12204
+ settle_err(new EyePopDataWebSocketError(ws_url, new Error(`closed before the connection was established: ${code} ${reason}`)));
12205
+ }
12206
+ if (this._ws !== ws) {
12207
+ return;
12082
12208
  }
12209
+ this._ws = null;
12210
+ this.schedule_ws_reconnect();
12083
12211
  };
12084
12212
  ws.onerror = (event) => {
12085
- this._logger.error(event);
12086
- reject(event);
12213
+ const error = new EyePopDataWebSocketError(ws_url, event);
12214
+ this._logger.error({ err: error }, "dataset event WebSocket connection failed");
12215
+ settle_err(error);
12087
12216
  return null;
12088
12217
  };
12089
12218
  });
12090
12219
  }
12220
+ close_ws(ws, reason, code = 1e3) {
12221
+ try {
12222
+ ws.close(code, reason);
12223
+ } catch (e) {
12224
+ this._logger.warn({ err: e }, "ignored exception closing the dataset event WebSocket");
12225
+ }
12226
+ }
12227
+ schedule_ws_reconnect() {
12228
+ if (!this._ws_reconnect_enabled || this._ws_reconnect_timeout) {
12229
+ return;
12230
+ }
12231
+ if (!this._ws_current_reconnect_delay) {
12232
+ this._ws_current_reconnect_delay = WS_INITIAL_RECONNECT_DELAY;
12233
+ } else if (this._ws_current_reconnect_delay < WS_MAX_RECONNECT_DELAY) {
12234
+ this._ws_current_reconnect_delay *= 1.5;
12235
+ }
12236
+ this._ws_reconnect_timeout = setTimeout(() => {
12237
+ if (!this._ws_reconnect_enabled) {
12238
+ this._ws_reconnect_timeout = null;
12239
+ return;
12240
+ }
12241
+ void this.reconnect_ws().catch((error) => {
12242
+ this._requestLogger.debug("dataset event WebSocket reconnect failed: %s", error);
12243
+ }).finally(() => {
12244
+ this._ws_reconnect_timeout = null;
12245
+ if (!this._ws) {
12246
+ this.schedule_ws_reconnect();
12247
+ }
12248
+ });
12249
+ }, this._ws_current_reconnect_delay);
12250
+ }
12091
12251
  options() {
12092
12252
  return this._options;
12093
12253
  }
@@ -12112,7 +12272,7 @@ var DataEndpoint = class extends Endpoint {
12112
12272
  body: options.body
12113
12273
  };
12114
12274
  this._requestLogger.debug("Request - " + ri.method + " " + path2 + " BODY: " + (ri.body ? ri.body : ""));
12115
- return await client.fetch(urlJoin(baseUrl ?? "", path2), ri);
12275
+ return await client.fetch((0, import_url_join.default)(baseUrl ?? "", path2), ri);
12116
12276
  });
12117
12277
  if (response.status === 204) {
12118
12278
  return null;
@@ -12850,6 +13010,176 @@ function decodeDepthMap(depth) {
12850
13010
  return new DepthMap(depth.width, depth.height, values);
12851
13011
  }
12852
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
+
12853
13183
  // worker/webrtc_whep.ts
12854
13184
  var WebrtcWhep = class extends WebrtcBase {
12855
13185
  constructor(ingressId, getSession, client, requestLogger) {
@@ -12954,6 +13284,9 @@ var EyePop;
12954
13284
  } : void 0;
12955
13285
  EyePop2.endpoint = workerEndpoint;
12956
13286
  function workerEndpoint(opts = {}) {
13287
+ if (opts.pop !== void 0) {
13288
+ validatePop(opts.pop);
13289
+ }
12957
13290
  if (opts.isLocalMode === void 0) {
12958
13291
  opts.isLocalMode = stringToBooleanSafe(readEnv("EYEPOP_LOCAL_MODE"));
12959
13292
  }
@@ -13090,6 +13423,7 @@ var index_default = EyePop;
13090
13423
  EvaluationStatus,
13091
13424
  ExportedBy,
13092
13425
  EyePop,
13426
+ EyePopDataWebSocketError,
13093
13427
  EyepopLineDecoder,
13094
13428
  ForwardOperatorType,
13095
13429
  InferenceType,
@@ -13105,8 +13439,10 @@ var index_default = EyePop;
13105
13439
  ModelTrainingStage,
13106
13440
  ModelType,
13107
13441
  MotionModel,
13442
+ PointCloud,
13108
13443
  PopComponentType,
13109
13444
  PredictionVersion,
13445
+ QUATERNION_TOLERANCE,
13110
13446
  Semaphore,
13111
13447
  Stream,
13112
13448
  TaskMode,
@@ -13125,12 +13461,18 @@ var index_default = EyePop;
13125
13461
  WorkflowPhase,
13126
13462
  accessTokenCredential,
13127
13463
  apiKeyCredential,
13464
+ cloudOfDepth,
13465
+ cloudOfObject,
13466
+ cloudsOfPrediction,
13128
13467
  decodeDepthMap,
13468
+ decodePointCloud,
13129
13469
  encodeMultipartFormData,
13130
13470
  getBaseUrl,
13131
13471
  randomMultipartBoundary,
13132
13472
  readableStreamFromString,
13133
- resolveAuth
13473
+ resolveAuth,
13474
+ validateCamera,
13475
+ validatePop
13134
13476
  });
13135
13477
  /*! Bundled license information:
13136
13478