@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.
@@ -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";
@@ -9313,7 +9382,6 @@ var VideoMode = /* @__PURE__ */ ((VideoMode2) => {
9313
9382
  var PopComponentType = /* @__PURE__ */ ((PopComponentType2) => {
9314
9383
  PopComponentType2["FORWARD"] = "forward";
9315
9384
  PopComponentType2["INFERENCE"] = "inference";
9316
- PopComponentType2["TRACING"] = "tracing";
9317
9385
  PopComponentType2["TRACKING"] = "tracking";
9318
9386
  PopComponentType2["CONTOUR_FINDER"] = "contour_finder";
9319
9387
  PopComponentType2["COMPONENT_FINDER"] = "component_finder";
@@ -9352,6 +9420,16 @@ var ContourType = /* @__PURE__ */ ((ContourType2) => {
9352
9420
  ContourType2["RECTANGLE"] = "rectangle";
9353
9421
  return ContourType2;
9354
9422
  })(ContourType || {});
9423
+ function validatePop(pop) {
9424
+ if (pop.depthMap !== void 0) {
9425
+ if (pop.depthMap.ability === void 0 === (pop.depthMap.abilityUuid === void 0)) {
9426
+ throw new Error("depthMap requires exactly one of ability or abilityUuid");
9427
+ }
9428
+ }
9429
+ if (pop.defaults?.camera !== void 0) {
9430
+ validateCamera(pop.defaults.camera);
9431
+ }
9432
+ }
9355
9433
 
9356
9434
  // worker/webrtc_whip.ts
9357
9435
  var import_uuid2 = require("uuid");
@@ -9842,7 +9920,7 @@ var UploadJob = class extends AbstractJob {
9842
9920
  }
9843
9921
  }
9844
9922
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9845
- if (this._params.componentParams || this._params.roi) {
9923
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9846
9924
  const formData = new FormData();
9847
9925
  if (this._params.componentParams) {
9848
9926
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9856,6 +9934,10 @@ var UploadJob = class extends AbstractJob {
9856
9934
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9857
9935
  formData.append("fps", blob);
9858
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
+ }
9859
9941
  const fileBlob = await new Response(await this.uploadBody()).blob();
9860
9942
  formData.append("file", fileBlob);
9861
9943
  const headers = {
@@ -9937,7 +10019,7 @@ var UploadJob = class extends AbstractJob {
9937
10019
  }
9938
10020
  }
9939
10021
  const postUrl = `${session.baseUrl.replace(/\/+$/, "")}/pipelines/${session.pipelineId}/source?${queryParams.toString()}`;
9940
- if (this._params.componentParams || this._params.roi) {
10022
+ if (this._params.componentParams || this._params.roi || this._params.camera) {
9941
10023
  const formData = new FormData();
9942
10024
  if (this._params.componentParams) {
9943
10025
  const blob = new Blob([JSON.stringify(this._params.componentParams)], { type: "application/json" });
@@ -9951,6 +10033,10 @@ var UploadJob = class extends AbstractJob {
9951
10033
  const blob = new Blob([JSON.stringify(this._params.fps)], { type: "application/json" });
9952
10034
  formData.append("fps", blob);
9953
10035
  }
10036
+ if (this._params.camera) {
10037
+ const blob = new Blob([JSON.stringify(this._params.camera)], { type: "application/json" });
10038
+ formData.append("camera", blob);
10039
+ }
9954
10040
  const fileBlob = await new Response(await this.uploadBody()).blob();
9955
10041
  formData.append("file", fileBlob);
9956
10042
  const headers = {
@@ -10007,6 +10093,7 @@ var LoadFromJob = class extends AbstractJob {
10007
10093
  params: this._params.componentParams,
10008
10094
  roi: this._params.roi,
10009
10095
  fps: this._params.fps,
10096
+ camera: this._params.camera,
10010
10097
  version: this._version
10011
10098
  };
10012
10099
  if (this._params.motionDetect) {
@@ -10067,6 +10154,9 @@ var UploadGroupJob = class extends AbstractJob {
10067
10154
  if (this._params.fps) {
10068
10155
  parts.push({ name: "fps", contentType: "application/json", content: JSON.stringify(this._params.fps) });
10069
10156
  }
10157
+ if (this._params.camera) {
10158
+ parts.push({ name: "camera", contentType: "application/json", content: JSON.stringify(this._params.camera) });
10159
+ }
10070
10160
  for (const source of this._sources) {
10071
10161
  parts.push({ name: "file", contentType: source.mimeType, content: source.stream });
10072
10162
  }
@@ -10114,6 +10204,9 @@ var LoadFromGroupJob = class extends AbstractJob {
10114
10204
  if (this._params.roi) {
10115
10205
  body["roi"] = this._params.roi;
10116
10206
  }
10207
+ if (this._params.camera) {
10208
+ body["camera"] = this._params.camera;
10209
+ }
10117
10210
  if (this._params.motionDetect) {
10118
10211
  Object.assign(body, this._params.motionDetect);
10119
10212
  }
@@ -10152,6 +10245,7 @@ var LoadFromAssetUuidJob = class extends AbstractJob {
10152
10245
  params: this._params.componentParams,
10153
10246
  roi: this._params.roi,
10154
10247
  fps: this._params.fps,
10248
+ camera: this._params.camera,
10155
10249
  version: this._version
10156
10250
  };
10157
10251
  if (this._params.motionDetect) {
@@ -10206,6 +10300,7 @@ var LoadMediaStreamJob = class extends AbstractJob {
10206
10300
  params: this._params.componentParams,
10207
10301
  roi: this._params.roi,
10208
10302
  fps: this._params.fps,
10303
+ camera: this._params.camera,
10209
10304
  version: this._version
10210
10305
  };
10211
10306
  if (this._params.motionDetect) {
@@ -10817,6 +10912,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10817
10912
  return this.options().pop || null;
10818
10913
  }
10819
10914
  async changePop(pop) {
10915
+ validatePop(pop);
10820
10916
  if (this.options().popId == "transient" /* Transient */) {
10821
10917
  await this.changeTransientPop(pop);
10822
10918
  return;
@@ -10889,7 +10985,22 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10889
10985
  return Promise.reject("unknown source type");
10890
10986
  }
10891
10987
  }
10988
+ /**
10989
+ * Reject a source's calibration before any work is started for it.
10990
+ *
10991
+ * Called by every entry point that takes ProcessParams rather than at the
10992
+ * one boundary they share, because they have none: the group methods build
10993
+ * their jobs directly instead of going through processV2. It runs before
10994
+ * the concurrency limit is acquired, so a request that can never be sent
10995
+ * does not hold a slot.
10996
+ */
10997
+ validateProcessParams(params) {
10998
+ if (params.camera !== void 0) {
10999
+ validateCamera(params.camera);
11000
+ }
11001
+ }
10892
11002
  async processV2(request) {
11003
+ this.validateProcessParams(request);
10893
11004
  if (request.source.file !== void 0) {
10894
11005
  return this.uploadFile(request.source, request);
10895
11006
  } else if (request.source.stream !== void 0) {
@@ -10910,6 +11021,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10910
11021
  if (paths.length === 0) {
10911
11022
  throw new Error("upload group requires at least one path");
10912
11023
  }
11024
+ this.validateProcessParams(params);
10913
11025
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10914
11026
  throw new Error("endpoint not connected, use connect()");
10915
11027
  }
@@ -10948,6 +11060,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10948
11060
  if (mimeTypes !== void 0 && mimeTypes.length !== streams.length) {
10949
11061
  throw new Error("mimeTypes must have the same length as streams");
10950
11062
  }
11063
+ this.validateProcessParams(params);
10951
11064
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10952
11065
  throw new Error("endpoint not connected, use connect()");
10953
11066
  }
@@ -10976,6 +11089,7 @@ var _WorkerEndpoint = class _WorkerEndpoint extends Endpoint {
10976
11089
  if (urls.length === 0) {
10977
11090
  throw new Error("load from group requires at least one url");
10978
11091
  }
11092
+ this.validateProcessParams(params);
10979
11093
  if (!this._baseUrl || !this._pipelineId || !this._client || !this._limit) {
10980
11094
  throw new Error("endpoint not connected, use connect()");
10981
11095
  }
@@ -12895,6 +13009,176 @@ function decodeDepthMap(depth) {
12895
13009
  return new DepthMap(depth.width, depth.height, values);
12896
13010
  }
12897
13011
 
13012
+ // point_cloud.ts
13013
+ var VALUES_PER_POINT = 3;
13014
+ var isLittleEndianPlatform2 = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
13015
+ function base64ToBytes2(base64) {
13016
+ if (typeof Buffer !== "undefined") {
13017
+ const buffer = Buffer.from(base64, "base64");
13018
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
13019
+ }
13020
+ const binary = atob(base64);
13021
+ const bytes = new Uint8Array(binary.length);
13022
+ for (let i = 0; i < binary.length; i++) {
13023
+ bytes[i] = binary.charCodeAt(i);
13024
+ }
13025
+ return bytes;
13026
+ }
13027
+ var PointCloud = class {
13028
+ constructor(width, height, points, box) {
13029
+ this.width = width;
13030
+ this.height = height;
13031
+ this.points = points;
13032
+ this.box = box;
13033
+ }
13034
+ /**
13035
+ * The world point for pixel (i, j), or undefined where the worker placed none.
13036
+ *
13037
+ * (i, j) indexes the cloud's own grid - the mask bitmap, or the depth map
13038
+ * for a scene cloud: i is the column, j is the row.
13039
+ */
13040
+ at(i, j) {
13041
+ if (!(i >= 0 && i < this.width && j >= 0 && j < this.height)) {
13042
+ throw new Error(`(${i}, ${j}) is outside a ${this.width}x${this.height} cloud`);
13043
+ }
13044
+ const offset = (j * this.width + i) * VALUES_PER_POINT;
13045
+ const x = this.points[offset];
13046
+ const y = this.points[offset + 1];
13047
+ const z = this.points[offset + 2];
13048
+ if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
13049
+ return void 0;
13050
+ }
13051
+ return { x, y, z };
13052
+ }
13053
+ /**
13054
+ * The world point for a source frame coordinate inside the cloud's box.
13055
+ *
13056
+ * Needs a cloud built with cloudOfObject(), or with cloudOfDepth() and the
13057
+ * frame size: the grid covers a box, and nothing else knows where it sits.
13058
+ */
13059
+ atSource(x, y) {
13060
+ if (this.box === void 0) {
13061
+ throw new Error("atSource needs a box; build with cloudOfObject or cloudOfDepth");
13062
+ }
13063
+ if (!(this.box.width > 0) || !(this.box.height > 0)) {
13064
+ throw new Error(`invalid bounding box extent: ${this.box.width}x${this.box.height}`);
13065
+ }
13066
+ const i = Math.min(Math.max(Math.floor((x - this.box.x) / this.box.width * this.width), 0), this.width - 1);
13067
+ const j = Math.min(Math.max(Math.floor((y - this.box.y) / this.box.height * this.height), 0), this.height - 1);
13068
+ return this.at(i, j);
13069
+ }
13070
+ isPlaced(i, j) {
13071
+ return this.at(i, j) !== void 0;
13072
+ }
13073
+ /**
13074
+ * Just the points the worker placed.
13075
+ *
13076
+ * The shape a scatter plot or an export wants: the grid with its NaN holes
13077
+ * dropped, and no mask to apply first.
13078
+ */
13079
+ get placedPoints() {
13080
+ const placed = [];
13081
+ for (let offset = 0; offset < this.points.length; offset += VALUES_PER_POINT) {
13082
+ const x = this.points[offset];
13083
+ const y = this.points[offset + 1];
13084
+ const z = this.points[offset + 2];
13085
+ if (!Number.isNaN(x) && !Number.isNaN(y) && !Number.isNaN(z)) {
13086
+ placed.push({ x, y, z });
13087
+ }
13088
+ }
13089
+ return placed;
13090
+ }
13091
+ /**
13092
+ * Per axis (min, max) in metres over the placed points, or undefined if none were.
13093
+ *
13094
+ * The counterpart to DepthMap's finiteMin/finiteMax, which are one axis
13095
+ * because a depth map has one value per pixel.
13096
+ */
13097
+ get bounds() {
13098
+ let found = false;
13099
+ const min = { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY, z: Number.POSITIVE_INFINITY };
13100
+ const max = { x: Number.NEGATIVE_INFINITY, y: Number.NEGATIVE_INFINITY, z: Number.NEGATIVE_INFINITY };
13101
+ for (let offset = 0; offset < this.points.length; offset += VALUES_PER_POINT) {
13102
+ const x = this.points[offset];
13103
+ const y = this.points[offset + 1];
13104
+ const z = this.points[offset + 2];
13105
+ if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
13106
+ continue;
13107
+ }
13108
+ found = true;
13109
+ min.x = Math.min(min.x, x);
13110
+ min.y = Math.min(min.y, y);
13111
+ min.z = Math.min(min.z, z);
13112
+ max.x = Math.max(max.x, x);
13113
+ max.y = Math.max(max.y, y);
13114
+ max.z = Math.max(max.z, z);
13115
+ }
13116
+ return found ? { min, max } : void 0;
13117
+ }
13118
+ };
13119
+ function decode(width, height, world, box) {
13120
+ if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0) {
13121
+ throw new Error(`invalid point cloud dimensions: ${width}x${height}`);
13122
+ }
13123
+ const bytes = base64ToBytes2(world);
13124
+ const count = width * height * VALUES_PER_POINT;
13125
+ if (bytes.byteLength !== count * 4) {
13126
+ throw new Error(`'world' holds ${bytes.byteLength} bytes, expected ${count * 4} for ${width}x${height} xyz float32`);
13127
+ }
13128
+ let points;
13129
+ if (isLittleEndianPlatform2) {
13130
+ const aligned = new Uint8Array(bytes);
13131
+ points = new Float32Array(aligned.buffer, 0, count);
13132
+ } else {
13133
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
13134
+ points = new Float32Array(count);
13135
+ for (let i = 0; i < count; i++) {
13136
+ points[i] = view.getFloat32(i * 4, true);
13137
+ }
13138
+ }
13139
+ return new PointCloud(width, height, points, box);
13140
+ }
13141
+ function decodePointCloud(mask, box) {
13142
+ if (mask === void 0 || mask.world === void 0) {
13143
+ return void 0;
13144
+ }
13145
+ return decode(mask.width, mask.height, mask.world, box);
13146
+ }
13147
+ function cloudOfObject(obj) {
13148
+ if (obj === void 0 || obj.mask === void 0) {
13149
+ return void 0;
13150
+ }
13151
+ return decodePointCloud(obj.mask, { x: obj.x, y: obj.y, width: obj.width, height: obj.height });
13152
+ }
13153
+ function cloudOfDepth(depth, sourceWidth, sourceHeight) {
13154
+ if (depth === void 0 || depth.world === void 0) {
13155
+ return void 0;
13156
+ }
13157
+ const box = sourceWidth && sourceHeight ? { x: 0, y: 0, width: sourceWidth, height: sourceHeight } : void 0;
13158
+ return decode(depth.width, depth.height, depth.world, box);
13159
+ }
13160
+ function cloudsOfPrediction(prediction) {
13161
+ const clouds = [];
13162
+ if (prediction === void 0) {
13163
+ return clouds;
13164
+ }
13165
+ const walk = (objects) => {
13166
+ for (const obj of objects ?? []) {
13167
+ const cloud = cloudOfObject(obj);
13168
+ if (cloud !== void 0) {
13169
+ clouds.push(cloud);
13170
+ }
13171
+ walk(obj.objects);
13172
+ }
13173
+ };
13174
+ walk(prediction.objects);
13175
+ const scene = cloudOfDepth(prediction.depth, prediction.source_width, prediction.source_height);
13176
+ if (scene !== void 0) {
13177
+ clouds.push(scene);
13178
+ }
13179
+ return clouds;
13180
+ }
13181
+
12898
13182
  // worker/webrtc_whep.ts
12899
13183
  var WebrtcWhep = class extends WebrtcBase {
12900
13184
  constructor(ingressId, getSession, client, requestLogger) {
@@ -12999,6 +13283,9 @@ var EyePop;
12999
13283
  } : void 0;
13000
13284
  EyePop2.endpoint = workerEndpoint;
13001
13285
  function workerEndpoint(opts = {}) {
13286
+ if (opts.pop !== void 0) {
13287
+ validatePop(opts.pop);
13288
+ }
13002
13289
  if (opts.isLocalMode === void 0) {
13003
13290
  opts.isLocalMode = stringToBooleanSafe(readEnv("EYEPOP_LOCAL_MODE"));
13004
13291
  }
@@ -13151,8 +13438,10 @@ var index_default = EyePop;
13151
13438
  ModelTrainingStage,
13152
13439
  ModelType,
13153
13440
  MotionModel,
13441
+ PointCloud,
13154
13442
  PopComponentType,
13155
13443
  PredictionVersion,
13444
+ QUATERNION_TOLERANCE,
13156
13445
  Semaphore,
13157
13446
  Stream,
13158
13447
  TaskMode,
@@ -13171,12 +13460,18 @@ var index_default = EyePop;
13171
13460
  WorkflowPhase,
13172
13461
  accessTokenCredential,
13173
13462
  apiKeyCredential,
13463
+ cloudOfDepth,
13464
+ cloudOfObject,
13465
+ cloudsOfPrediction,
13174
13466
  decodeDepthMap,
13467
+ decodePointCloud,
13175
13468
  encodeMultipartFormData,
13176
13469
  getBaseUrl,
13177
13470
  randomMultipartBoundary,
13178
13471
  readableStreamFromString,
13179
- resolveAuth
13472
+ resolveAuth,
13473
+ validateCamera,
13474
+ validatePop
13180
13475
  });
13181
13476
  /*! Bundled license information:
13182
13477