edge-impulse-linux 1.19.0 → 1.21.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.
Files changed (180) hide show
  1. package/build/cli/linux/linux.js +9 -1
  2. package/build/cli/linux/linux.js.map +1 -1
  3. package/build/cli/linux/runner-downloader.js +5 -1
  4. package/build/cli/linux/runner-downloader.js.map +1 -1
  5. package/build/cli/linux/runner.js +21 -6
  6. package/build/cli/linux/runner.js.map +1 -1
  7. package/build/library/classifier/image-classifier.d.ts +2 -0
  8. package/build/library/classifier/image-classifier.js +84 -54
  9. package/build/library/classifier/image-classifier.js.map +1 -1
  10. package/build/library/data-forwarder.d.ts +1 -1
  11. package/build/library/data-forwarder.js +17 -33
  12. package/build/library/data-forwarder.js.map +1 -1
  13. package/build/library/sensors/gstreamer.d.ts +10 -4
  14. package/build/library/sensors/gstreamer.js +248 -58
  15. package/build/library/sensors/gstreamer.js.map +1 -1
  16. package/build/library/sensors/icamera.d.ts +30 -1
  17. package/build/library/sensors/imagesnap.d.ts +2 -2
  18. package/build/library/sensors/imagesnap.js +8 -2
  19. package/build/library/sensors/imagesnap.js.map +1 -1
  20. package/build/library/sensors/prophesee.d.ts +2 -2
  21. package/build/library/sensors/prophesee.js +6 -1
  22. package/build/library/sensors/prophesee.js.map +1 -1
  23. package/build/library/sensors/sensors-helper.d.ts +3 -0
  24. package/build/library/sensors/sensors-helper.js +4 -3
  25. package/build/library/sensors/sensors-helper.js.map +1 -1
  26. package/build/sdk/studio/sdk/api/adminApi.d.ts +4 -6
  27. package/build/sdk/studio/sdk/api/adminApi.js +4782 -4928
  28. package/build/sdk/studio/sdk/api/adminApi.js.map +1 -1
  29. package/build/sdk/studio/sdk/api/authApi.d.ts +4 -6
  30. package/build/sdk/studio/sdk/api/authApi.js +88 -56
  31. package/build/sdk/studio/sdk/api/authApi.js.map +1 -1
  32. package/build/sdk/studio/sdk/api/cDNApi.d.ts +4 -6
  33. package/build/sdk/studio/sdk/api/cDNApi.js +76 -53
  34. package/build/sdk/studio/sdk/api/cDNApi.js.map +1 -1
  35. package/build/sdk/studio/sdk/api/canaryApi.d.ts +4 -6
  36. package/build/sdk/studio/sdk/api/canaryApi.js +79 -53
  37. package/build/sdk/studio/sdk/api/canaryApi.js.map +1 -1
  38. package/build/sdk/studio/sdk/api/classifyApi.d.ts +4 -6
  39. package/build/sdk/studio/sdk/api/classifyApi.js +527 -499
  40. package/build/sdk/studio/sdk/api/classifyApi.js.map +1 -1
  41. package/build/sdk/studio/sdk/api/dSPApi.d.ts +4 -6
  42. package/build/sdk/studio/sdk/api/dSPApi.js +938 -942
  43. package/build/sdk/studio/sdk/api/dSPApi.js.map +1 -1
  44. package/build/sdk/studio/sdk/api/deploymentApi.d.ts +4 -6
  45. package/build/sdk/studio/sdk/api/deploymentApi.js +680 -603
  46. package/build/sdk/studio/sdk/api/deploymentApi.js.map +1 -1
  47. package/build/sdk/studio/sdk/api/devicesApi.d.ts +4 -6
  48. package/build/sdk/studio/sdk/api/devicesApi.js +802 -800
  49. package/build/sdk/studio/sdk/api/devicesApi.js.map +1 -1
  50. package/build/sdk/studio/sdk/api/emailVerificationApi.d.ts +4 -6
  51. package/build/sdk/studio/sdk/api/emailVerificationApi.js +184 -190
  52. package/build/sdk/studio/sdk/api/emailVerificationApi.js.map +1 -1
  53. package/build/sdk/studio/sdk/api/exportApi.d.ts +4 -6
  54. package/build/sdk/studio/sdk/api/exportApi.js +88 -56
  55. package/build/sdk/studio/sdk/api/exportApi.js.map +1 -1
  56. package/build/sdk/studio/sdk/api/featureFlagsApi.d.ts +4 -6
  57. package/build/sdk/studio/sdk/api/featureFlagsApi.js +76 -52
  58. package/build/sdk/studio/sdk/api/featureFlagsApi.js.map +1 -1
  59. package/build/sdk/studio/sdk/api/healthApi.d.ts +4 -6
  60. package/build/sdk/studio/sdk/api/healthApi.js +111 -97
  61. package/build/sdk/studio/sdk/api/healthApi.js.map +1 -1
  62. package/build/sdk/studio/sdk/api/impulseApi.d.ts +4 -6
  63. package/build/sdk/studio/sdk/api/impulseApi.js +942 -902
  64. package/build/sdk/studio/sdk/api/impulseApi.js.map +1 -1
  65. package/build/sdk/studio/sdk/api/integrationsApi.d.ts +4 -6
  66. package/build/sdk/studio/sdk/api/integrationsApi.js +136 -106
  67. package/build/sdk/studio/sdk/api/integrationsApi.js.map +1 -1
  68. package/build/sdk/studio/sdk/api/jobsApi.d.ts +4 -6
  69. package/build/sdk/studio/sdk/api/jobsApi.js +2270 -2326
  70. package/build/sdk/studio/sdk/api/jobsApi.js.map +1 -1
  71. package/build/sdk/studio/sdk/api/learnApi.d.ts +22 -6
  72. package/build/sdk/studio/sdk/api/learnApi.js +1597 -1424
  73. package/build/sdk/studio/sdk/api/learnApi.js.map +1 -1
  74. package/build/sdk/studio/sdk/api/loginApi.d.ts +4 -6
  75. package/build/sdk/studio/sdk/api/loginApi.js +112 -98
  76. package/build/sdk/studio/sdk/api/loginApi.js.map +1 -1
  77. package/build/sdk/studio/sdk/api/metricsApi.d.ts +4 -6
  78. package/build/sdk/studio/sdk/api/metricsApi.js +148 -144
  79. package/build/sdk/studio/sdk/api/metricsApi.js.map +1 -1
  80. package/build/sdk/studio/sdk/api/optimizationApi.d.ts +4 -6
  81. package/build/sdk/studio/sdk/api/optimizationApi.js +844 -844
  82. package/build/sdk/studio/sdk/api/optimizationApi.js.map +1 -1
  83. package/build/sdk/studio/sdk/api/organizationBlocksApi.d.ts +4 -6
  84. package/build/sdk/studio/sdk/api/organizationBlocksApi.js +1968 -1550
  85. package/build/sdk/studio/sdk/api/organizationBlocksApi.js.map +1 -1
  86. package/build/sdk/studio/sdk/api/organizationCreateProjectApi.d.ts +4 -6
  87. package/build/sdk/studio/sdk/api/organizationCreateProjectApi.js +700 -652
  88. package/build/sdk/studio/sdk/api/organizationCreateProjectApi.js.map +1 -1
  89. package/build/sdk/studio/sdk/api/organizationDataApi.d.ts +4 -6
  90. package/build/sdk/studio/sdk/api/organizationDataApi.js +2074 -1985
  91. package/build/sdk/studio/sdk/api/organizationDataApi.js.map +1 -1
  92. package/build/sdk/studio/sdk/api/organizationDataCampaignsApi.d.ts +4 -6
  93. package/build/sdk/studio/sdk/api/organizationDataCampaignsApi.js +626 -602
  94. package/build/sdk/studio/sdk/api/organizationDataCampaignsApi.js.map +1 -1
  95. package/build/sdk/studio/sdk/api/organizationJobsApi.d.ts +4 -6
  96. package/build/sdk/studio/sdk/api/organizationJobsApi.js +417 -399
  97. package/build/sdk/studio/sdk/api/organizationJobsApi.js.map +1 -1
  98. package/build/sdk/studio/sdk/api/organizationPipelinesApi.d.ts +4 -6
  99. package/build/sdk/studio/sdk/api/organizationPipelinesApi.js +419 -401
  100. package/build/sdk/studio/sdk/api/organizationPipelinesApi.js.map +1 -1
  101. package/build/sdk/studio/sdk/api/organizationPortalsApi.d.ts +4 -6
  102. package/build/sdk/studio/sdk/api/organizationPortalsApi.js +372 -352
  103. package/build/sdk/studio/sdk/api/organizationPortalsApi.js.map +1 -1
  104. package/build/sdk/studio/sdk/api/organizationsApi.d.ts +4 -6
  105. package/build/sdk/studio/sdk/api/organizationsApi.js +4024 -4050
  106. package/build/sdk/studio/sdk/api/organizationsApi.js.map +1 -1
  107. package/build/sdk/studio/sdk/api/performanceCalibrationApi.d.ts +4 -6
  108. package/build/sdk/studio/sdk/api/performanceCalibrationApi.js +586 -560
  109. package/build/sdk/studio/sdk/api/performanceCalibrationApi.js.map +1 -1
  110. package/build/sdk/studio/sdk/api/postProcessingApi.d.ts +4 -6
  111. package/build/sdk/studio/sdk/api/postProcessingApi.js +279 -255
  112. package/build/sdk/studio/sdk/api/postProcessingApi.js.map +1 -1
  113. package/build/sdk/studio/sdk/api/projectsApi.d.ts +4 -6
  114. package/build/sdk/studio/sdk/api/projectsApi.js +2758 -2818
  115. package/build/sdk/studio/sdk/api/projectsApi.js.map +1 -1
  116. package/build/sdk/studio/sdk/api/rawDataApi.d.ts +4 -6
  117. package/build/sdk/studio/sdk/api/rawDataApi.js +3055 -3131
  118. package/build/sdk/studio/sdk/api/rawDataApi.js.map +1 -1
  119. package/build/sdk/studio/sdk/api/testApi.d.ts +4 -6
  120. package/build/sdk/studio/sdk/api/testApi.js +152 -108
  121. package/build/sdk/studio/sdk/api/testApi.js.map +1 -1
  122. package/build/sdk/studio/sdk/api/themesApi.d.ts +4 -6
  123. package/build/sdk/studio/sdk/api/themesApi.js +341 -305
  124. package/build/sdk/studio/sdk/api/themesApi.js.map +1 -1
  125. package/build/sdk/studio/sdk/api/thirdPartyAuthApi.d.ts +4 -6
  126. package/build/sdk/studio/sdk/api/thirdPartyAuthApi.js +387 -353
  127. package/build/sdk/studio/sdk/api/thirdPartyAuthApi.js.map +1 -1
  128. package/build/sdk/studio/sdk/api/uploadPortalApi.d.ts +4 -6
  129. package/build/sdk/studio/sdk/api/uploadPortalApi.js +375 -355
  130. package/build/sdk/studio/sdk/api/uploadPortalApi.js.map +1 -1
  131. package/build/sdk/studio/sdk/api/userApi.d.ts +4 -6
  132. package/build/sdk/studio/sdk/api/userApi.js +2345 -2452
  133. package/build/sdk/studio/sdk/api/userApi.js.map +1 -1
  134. package/build/sdk/studio/sdk/api/vlmApi.d.ts +4 -6
  135. package/build/sdk/studio/sdk/api/vlmApi.js +277 -253
  136. package/build/sdk/studio/sdk/api/vlmApi.js.map +1 -1
  137. package/build/sdk/studio/sdk/api/whitelabelsApi.d.ts +4 -6
  138. package/build/sdk/studio/sdk/api/whitelabelsApi.js +408 -398
  139. package/build/sdk/studio/sdk/api/whitelabelsApi.js.map +1 -1
  140. package/build/sdk/studio/sdk/model/deployPretrainedModelRequest.d.ts +4 -0
  141. package/build/sdk/studio/sdk/model/deployPretrainedModelRequest.js +5 -0
  142. package/build/sdk/studio/sdk/model/deployPretrainedModelRequest.js.map +1 -1
  143. package/build/sdk/studio/sdk/model/models.d.ts +47 -7
  144. package/build/sdk/studio/sdk/model/models.js +42 -38
  145. package/build/sdk/studio/sdk/model/models.js.map +1 -1
  146. package/build/sdk/studio/sdk/model/uploadPretrainedModelByUrlRequest.d.ts +47 -0
  147. package/build/sdk/studio/sdk/model/uploadPretrainedModelByUrlRequest.js +55 -0
  148. package/build/sdk/studio/sdk/model/uploadPretrainedModelByUrlRequest.js.map +1 -0
  149. package/build/sdk/studio/sdk/model/uploadPretrainedModelRequest.d.ts +4 -0
  150. package/build/sdk/studio/sdk/model/uploadPretrainedModelRequest.js +5 -0
  151. package/build/sdk/studio/sdk/model/uploadPretrainedModelRequest.js.map +1 -1
  152. package/package.json +4 -2
  153. package/test/gstreamer.test.ts +1487 -3840
  154. package/test/imx219-csi-on-arduino-unoq-inspect.txt +564 -0
  155. package/test/imx219-csi-on-arduino-unoq-monitor.txt +285 -0
  156. package/test/logitech-c920-on-ubuntu-22-parallells.txt +211 -0
  157. package/test/logitech-c922-on-rpi-bullseye-2.txt +270 -0
  158. package/test/logitech-c922-on-rpi-bullseye.txt +331 -0
  159. package/test/macbook-pro-w-internal-and-external-cam-inspect.txt +1496 -0
  160. package/test/macbook-pro-w-internal-and-external-cam-monitor.txt +209 -0
  161. package/test/nvidia-jetson-w-csi-camera.txt +94 -0
  162. package/test/nvidia-jetson-w-logitech-c922-monitor.txt +204 -0
  163. package/test/nvidia-orin-w-basler-camera-inspect.txt +859 -0
  164. package/test/nvidia-orin-w-basler-camera-pylonsrc.txt +806 -0
  165. package/test/qualcomm-rb3-gen-2-ubuntu-no-external-camera-inspect-qtiqmmfsrc.txt +438 -0
  166. package/test/qualcomm-rb3-gen-2-ubuntu-no-external-camera-inspect.txt +958 -0
  167. package/test/qualcomm-rb3-gen-2-ubuntu-no-external-camera-monitor.txt +159 -0
  168. package/test/rb1-debian-inspect.txt +568 -0
  169. package/test/rb1-debian-libcamera-monitor.txt +190 -0
  170. package/test/rpi-cam-v3-c920-webcam-bookworm-no-libcamerasrc.txt +383 -0
  171. package/test/rpi4-trixie-csi-gst-device-monitor.txt +1110 -0
  172. package/test/rpi4-trixie-csi-gst-inspect.txt +1379 -0
  173. package/test/rpi5-with-csi-imx500-camera-monitor.txt +3617 -0
  174. package/test/rpi5-with-csi-imx708-monitor.txt +2277 -0
  175. package/test/usb-camera-on-rpi-w-image-jpg.txt +130 -0
  176. /package/test/{qualcomm-rb3-inspect-qtiqmmfsrc.txt → qualcomm-rb3-gen-2-qimp-linux-1-2-with-logitech-brio-inspect-qtiqmmfsrc.txt} +0 -0
  177. /package/test/{qualcomm-rb3-inspect.txt → qualcomm-rb3-gen-2-qimp-linux-1-2-with-logitech-brio-inspect.txt} +0 -0
  178. /package/test/{qualcomm-rb3-monitor-brio.txt → qualcomm-rb3-gen-2-qimp-linux-1-2-with-logitech-brio-monitor.txt} +0 -0
  179. /package/test/{triple-vision-camera-inspect.txt → triple-vision-ai-industrial-camera-inspect.txt} +0 -0
  180. /package/test/{triple-vision-camera-monitor.txt → triple-vision-ai-industrial-camera-monitor.txt} +0 -0
@@ -20,13 +20,14 @@ const DEFAULT_GST_VIDEO_SOURCE = 'v4l2src';
20
20
  class GStreamer extends tsee_1.EventEmitter {
21
21
  constructor(verbose, options) {
22
22
  super();
23
- this._originalFilesSeen = new Set();
23
+ this._originalAndRgbFilesSeen = new Set();
24
24
  this._handledFiles = {};
25
25
  this._processing = false;
26
26
  this._mode = 'default';
27
27
  this._isStarted = false;
28
28
  this._isRestarting = false;
29
29
  this._emitsOriginalSizeImages = false;
30
+ this._emitsRgbBuffers = false;
30
31
  this._lastGstLaunchCommand = '';
31
32
  this._imagesReceived = 0;
32
33
  this._offset = 0;
@@ -35,6 +36,10 @@ class GStreamer extends tsee_1.EventEmitter {
35
36
  this._spawnHelper = options?.spawnHelperOverride || spawn_helper_1.spawnHelper;
36
37
  this._modeOverride = options?.modeOverride;
37
38
  this._profiling = options?.profiling || false;
39
+ this._overrideColorFormat = options?.colorFormat;
40
+ this._outputRgbBuffers = options?.dontOutputRgbBuffers === true ?
41
+ false :
42
+ true;
38
43
  if (options?.dontRunCleanupLoop === true) {
39
44
  // skip cleanup loop
40
45
  }
@@ -60,31 +65,43 @@ class GStreamer extends tsee_1.EventEmitter {
60
65
  console.log(PREFIX, 'checking for /etc/os-release');
61
66
  osRelease = await fs_1.default.promises.readFile('/etc/os-release', 'utf-8');
62
67
  }
68
+ else {
69
+ // so we don't need to check for undefined below
70
+ osRelease = '';
71
+ }
63
72
  let firmwareModel;
64
73
  // using /proc/device-tree as recommended in user space.
65
74
  if (await this.exists('/proc/device-tree/model')) {
66
75
  firmwareModel = await fs_1.default.promises.readFile('/proc/device-tree/model', 'utf-8');
67
76
  }
68
- if (osRelease) {
69
- if ((osRelease.indexOf('bullseye') > -1)
70
- || (osRelease.indexOf('bookworm') > -1)) {
71
- if (osRelease.indexOf('ID=raspbian') > -1) {
72
- this._mode = 'rpi';
73
- }
74
- if (firmwareModel && firmwareModel.indexOf('Raspberry Pi') > -1) {
75
- this._mode = 'rpi';
76
- }
77
- }
78
- }
79
- if (firmwareModel && firmwareModel.indexOf('Microchip SAMA7G5') > -1) {
80
- this._mode = 'microchip';
77
+ else {
78
+ // so we don't need to check for undefined below
79
+ firmwareModel = '';
81
80
  }
82
- else if (firmwareModel && firmwareModel.indexOf('RB3gen2') > -1 && firmwareModel.indexOf('vision') > -1) {
81
+ if (firmwareModel.indexOf('RB3gen2') > -1 && firmwareModel.indexOf('vision') > -1) {
83
82
  this._mode = 'qualcomm-rb3gen2';
84
83
  }
85
- else if (firmwareModel && firmwareModel.indexOf('Qualcomm') > -1 && firmwareModel.indexOf('Yupik') > -1) {
84
+ else if (firmwareModel.indexOf('Qualcomm') > -1 && firmwareModel.indexOf('Yupik') > -1) {
86
85
  this._mode = 'qualcomm-yupik';
87
86
  }
87
+ else if (firmwareModel.indexOf('Raspberry Pi') > -1) {
88
+ if (((osRelease.indexOf('bullseye') > -1) || (osRelease.indexOf('bookworm') > -1))
89
+ && (osRelease.indexOf('ID=raspbian') === -1)) {
90
+ this._mode = 'rpi';
91
+ }
92
+ else if (osRelease.indexOf('trixie') > -1) {
93
+ this._mode = 'rpi';
94
+ }
95
+ // override to rpi5 if needed
96
+ if (firmwareModel.indexOf('Raspberry Pi 5') > -1) {
97
+ this._mode = 'rpi5';
98
+ }
99
+ }
100
+ else if ((firmwareModel.indexOf('Arduino') > -1 && firmwareModel.indexOf('Imola') > -1) ||
101
+ // this may be incorrect on the another platforms in the future
102
+ (process.env.EI_CLI_ENV === 'arduino')) {
103
+ this._mode = 'unoq';
104
+ }
88
105
  this._mode = (this._modeOverride) ? this._modeOverride : this._mode;
89
106
  }
90
107
  async listDevices() {
@@ -240,16 +257,20 @@ class GStreamer extends tsee_1.EventEmitter {
240
257
  }
241
258
  let lastPhoto = 0;
242
259
  let nextFrame = Date.now();
243
- this._originalFilesSeen = new Set();
260
+ this._originalAndRgbFilesSeen = new Set();
244
261
  this._watcher = fs_1.default.watch(this._tempDir, async (eventType, fileName) => {
245
262
  if (eventType !== 'rename')
246
263
  return;
247
264
  if (fileName === null)
248
265
  return;
249
- if (!(fileName.endsWith('.jpeg') || fileName.endsWith('.jpg')))
266
+ if (!(fileName.endsWith('.jpeg') || fileName.endsWith('.jpg') || fileName.endsWith('.rgb')))
250
267
  return;
251
268
  if (fileName.startsWith('original')) {
252
- this._originalFilesSeen.add(fileName);
269
+ this._originalAndRgbFilesSeen.add(fileName);
270
+ return;
271
+ }
272
+ if (fileName.endsWith('.rgb')) {
273
+ this._originalAndRgbFilesSeen.add(fileName);
253
274
  return;
254
275
  }
255
276
  if (!this._tempDir)
@@ -257,10 +278,11 @@ class GStreamer extends tsee_1.EventEmitter {
257
278
  if (this._handledFiles[fileName])
258
279
  return;
259
280
  this._imagesReceived++;
281
+ const tempDir = this._tempDir; // this can get unset later on -> so cache here
260
282
  // not next frame yet?
261
283
  if (this._processing || Date.now() < nextFrame) {
262
284
  this._handledFiles[fileName] = true;
263
- await this.safeUnlinkFile(path_1.default.join(this._tempDir, fileName));
285
+ await this.safeUnlinkFile(path_1.default.join(tempDir, fileName));
264
286
  return;
265
287
  }
266
288
  nextFrame = Date.now() + options.intervalMs;
@@ -269,7 +291,7 @@ class GStreamer extends tsee_1.EventEmitter {
269
291
  this._handledFiles[fileName] = true;
270
292
  const originalName = fileName.replace('resized', 'original');
271
293
  if (this._emitsOriginalSizeImages) {
272
- if (!this._originalFilesSeen.has(originalName)) {
294
+ if (!this._originalAndRgbFilesSeen.has(originalName)) {
273
295
  let waitForOriginalStart = Date.now();
274
296
  if (this._verbose) {
275
297
  console.log(PREFIX, `Waiting for original file "${originalName}"...`);
@@ -277,13 +299,13 @@ class GStreamer extends tsee_1.EventEmitter {
277
299
  await new Promise(async (resolve, reject) => {
278
300
  let start = Date.now();
279
301
  while (1) {
280
- if (this._originalFilesSeen.has(originalName)) {
302
+ if (this._originalAndRgbFilesSeen.has(originalName)) {
281
303
  return resolve();
282
304
  }
283
305
  if (Date.now() - start > 1000) {
284
306
  return reject(`Did not find original image ("${originalName}") within 1sec`);
285
307
  }
286
- await this.wait(10);
308
+ await this.wait(1);
287
309
  }
288
310
  });
289
311
  if (this._verbose) {
@@ -292,19 +314,46 @@ class GStreamer extends tsee_1.EventEmitter {
292
314
  }
293
315
  }
294
316
  }
317
+ const rgbName = fileName.replace('.jpg', '.rgb');
318
+ if (this._emitsRgbBuffers) {
319
+ if (!this._originalAndRgbFilesSeen.has(rgbName)) {
320
+ let waitForRgbStart = Date.now();
321
+ if (this._verbose) {
322
+ console.log(PREFIX, `Waiting for RGB file "${rgbName}"...`);
323
+ }
324
+ await new Promise(async (resolve, reject) => {
325
+ let start = Date.now();
326
+ while (1) {
327
+ if (this._originalAndRgbFilesSeen.has(rgbName)) {
328
+ return resolve();
329
+ }
330
+ if (Date.now() - start > 1000) {
331
+ return reject(`Did not find RGB image ("${rgbName}") within 1sec`);
332
+ }
333
+ await this.wait(1);
334
+ }
335
+ });
336
+ if (this._verbose) {
337
+ console.log(PREFIX, `Waiting for RGB file "${rgbName}" OK ` +
338
+ `(took ${Date.now() - waitForRgbStart}ms.)`);
339
+ }
340
+ }
341
+ }
295
342
  if (lastPhoto !== 0 && this._verbose) {
296
343
  console.log(PREFIX, 'Got snapshot', fileName, 'time since last:', (Date.now() - lastPhoto) + 'ms.', 'size');
297
344
  }
298
345
  if (this._keepAliveTimeout) {
299
346
  clearTimeout(this._keepAliveTimeout);
300
347
  }
301
- const tempDir = this._tempDir;
302
348
  try {
303
- let [imgBuffer, originalImgBuffer] = await Promise.all([
349
+ let [imgBuffer, originalImgBuffer, rgbBuffer,] = await Promise.all([
304
350
  fs_1.default.promises.readFile(path_1.default.join(tempDir, fileName)),
305
351
  this._emitsOriginalSizeImages ?
306
352
  fs_1.default.promises.readFile(path_1.default.join(tempDir, originalName)) :
307
353
  null,
354
+ this._emitsRgbBuffers ?
355
+ fs_1.default.promises.readFile(path_1.default.join(tempDir, rgbName)) :
356
+ null,
308
357
  ]);
309
358
  // hash not changed? don't emit another event (streamer does this on Rpi)
310
359
  let hash = crypto_1.default.createHash('sha256').update(imgBuffer).digest('hex');
@@ -312,7 +361,12 @@ class GStreamer extends tsee_1.EventEmitter {
312
361
  // snapshot() sends out the original size image
313
362
  this.emit('snapshot', originalImgBuffer || imgBuffer, path_1.default.basename(fileName));
314
363
  // snapshotForInference() sends out the resized image
315
- this.emit('snapshotForInference', imgBuffer, path_1.default.basename(fileName));
364
+ this.emit('snapshotForInference', {
365
+ imageForInferenceJpg: imgBuffer,
366
+ filename: path_1.default.basename(fileName),
367
+ imageFromCameraJpg: originalImgBuffer || imgBuffer,
368
+ imageForInferenceRgb: rgbBuffer || undefined,
369
+ });
316
370
  lastPhoto = Date.now();
317
371
  // 2 seconds no new data? trigger timeout
318
372
  if (this._keepAliveTimeout) {
@@ -341,7 +395,13 @@ class GStreamer extends tsee_1.EventEmitter {
341
395
  (async () => {
342
396
  if (originalName) {
343
397
  await this.safeUnlinkFile(path_1.default.join(tempDir, originalName));
344
- this._originalFilesSeen.delete(originalName);
398
+ this._originalAndRgbFilesSeen.delete(originalName);
399
+ }
400
+ })(),
401
+ (async () => {
402
+ if (rgbName) {
403
+ await this.safeUnlinkFile(path_1.default.join(tempDir, rgbName));
404
+ this._originalAndRgbFilesSeen.delete(rgbName);
345
405
  }
346
406
  })(),
347
407
  ]);
@@ -376,9 +436,25 @@ class GStreamer extends tsee_1.EventEmitter {
376
436
  return;
377
437
  }
378
438
  else {
439
+ const errMsg = `GStreamer (gst-launch-1.0) stopped before emitting any images. This most likely ` +
440
+ `means that the launch command is incorrect or that your camera is unresponsive. Here is the launch command:\n\n` +
441
+ `${this._lastGstLaunchCommand}\n\n` +
442
+ `You can try one of the following:\n\n` +
443
+ `* If your camera used to work:\n` +
444
+ ` * Disconnect and reconnect the camera (if you use an external camera)\n` +
445
+ ` * Kill all other GStreamer commands, via: 'sudo killall gst-launch-1.0'\n` +
446
+ `* Run with '--verbose' to see the raw GStreamer output. It might contain a hint why the process fails.\n` +
447
+ `* Run with '--dont-output-rgb-buffers' - this will disable RGB output buffer creation which can help with ` +
448
+ `targets that advertise RGB capabilities on the video source, but don't actually support this.\n\n` +
449
+ `If this does not resolve your issue, then please open a forum post at https://forum.edgeimpulse.com and include:\n\n` +
450
+ `* Your device, operating system, what camera you're using, and how the camera is connected (e.g. USB, CSI)\n` +
451
+ `* The launch command (above)\n` +
452
+ `* The verbose output (run this application with --verbose)\n` +
453
+ `* The output of 'gst-device-monitor-1.0'\n` +
454
+ `* The output of 'gst-inspect-1.0'`;
379
455
  if (this._imagesReceived === 0 && this._lastGstLaunchCommand) {
380
456
  reject(`Capture process failed with code ${code}\n\n` +
381
- `command: ${this._lastGstLaunchCommand}`);
457
+ `${errMsg}`);
382
458
  }
383
459
  else {
384
460
  reject('Capture process failed with code ' + code);
@@ -429,6 +505,7 @@ class GStreamer extends tsee_1.EventEmitter {
429
505
  }
430
506
  async getGstreamerLaunchCommand(device, dimensions, inferenceDims) {
431
507
  this._emitsOriginalSizeImages = false;
508
+ this._emitsRgbBuffers = false;
432
509
  if (device.id === CUSTOM_GST_LAUNCH_COMMAND) {
433
510
  if (!this._customLaunchCommand) {
434
511
  throw new Error('_customLaunchCommand is null');
@@ -443,7 +520,7 @@ class GStreamer extends tsee_1.EventEmitter {
443
520
  pipeline: customArgs.join(' '),
444
521
  };
445
522
  }
446
- // now we need to determine the resolution... we want something as close as possible to dimensions.widthx480
523
+ // now we need to determine the resolution... we want something as close as possible to dimensions.
447
524
  let caps = device.caps.filter(c => {
448
525
  return c.width >= dimensions.width && c.height >= dimensions.height;
449
526
  }).sort((a, b) => {
@@ -474,20 +551,28 @@ class GStreamer extends tsee_1.EventEmitter {
474
551
  if (device.id) {
475
552
  videoSource.push(`device=${device.id}`);
476
553
  }
477
- if ((this._mode === 'rpi') || (this._mode === 'microchip')) {
478
- // Rpi camera
554
+ if ((this._mode === 'rpi') || (this._mode === 'rpi5') || device.videoSource === 'libcamerasrc') {
555
+ // libcamera devices don't have id set (or have some unique on Raspberry Pi or Microchip)
479
556
  if ((!device.id)
480
557
  || (device.name.indexOf('unicam') > -1)
481
558
  || (device.name.indexOf('bcm2835-isp') > -1)) {
482
559
  videoSource = [
483
560
  ...(this._profiling ? ['-m'] : []),
484
561
  'libcamerasrc',
562
+ ...device.name ? ['camera-name="' + device.name + '"'] : [],
485
563
  ];
486
564
  const hasPlugin = await this.hasGstPlugin('libcamerasrc');
487
565
  if (!hasPlugin) {
488
566
  throw new Error('Missing "libcamerasrc" gstreamer element. Install via `sudo apt install -y gstreamer1.0-libcamera`');
489
567
  }
490
568
  }
569
+ // FIXME: dirty hack for IPASoft on QRB2210
570
+ // Some resolutions reported by camera causes the pipeline to hang. To fix that, it seems
571
+ // we need to increase width and/or height by 8 pixels. This hack should be removed once
572
+ // the hardware driver for ISP on QRB2210 is released.
573
+ if (this._mode === 'unoq') {
574
+ cap.width += 8;
575
+ }
491
576
  }
492
577
  else if ((this._mode === 'qualcomm-rb3gen2') || (this._mode === 'qualcomm-yupik')) {
493
578
  videoSource = [
@@ -503,6 +588,24 @@ class GStreamer extends tsee_1.EventEmitter {
503
588
  'pylonsrc',
504
589
  ];
505
590
  }
591
+ const frameReadyArgs = this._profiling ? [`!`, `identity name=frame_ready silent=false`] : [];
592
+ const jpgencDoneArgs = this._profiling ? [`!`, `identity name=jpegenc_done silent=false`] : [];
593
+ const resizeDoneArgs = this._profiling ? [`!`, `identity name=resize_done silent=false`] : [];
594
+ let teeToBothResizedJpgAndRgb;
595
+ let videoFormat = '';
596
+ if (this._outputRgbBuffers) {
597
+ teeToBothResizedJpgAndRgb = [
598
+ `tee name=u`,
599
+ `u. ! queue ! jpegenc ${jpgencDoneArgs.join(' ')} ! multifilesink location=resized%05d.jpg post-messages=true sync=false `,
600
+ `u. ! queue ! multifilesink location=resized%05d.rgb post-messages=true sync=false `,
601
+ ];
602
+ videoFormat = ',format=RGB'; // Don't do this if outputRgbBuffers is RGB, because we don't want to force RGB video format
603
+ }
604
+ else {
605
+ teeToBothResizedJpgAndRgb = [
606
+ `jpegenc ${jpgencDoneArgs.join(' ')} ! multifilesink location=resized%05d.jpg post-messages=true sync=false`,
607
+ ];
608
+ }
506
609
  let cropArgs = [];
507
610
  if (inferenceDims) {
508
611
  // fast path for fit-shortest and squash
@@ -514,14 +617,14 @@ class GStreamer extends tsee_1.EventEmitter {
514
617
  `!`,
515
618
  `videoscale`, `method=lanczos`,
516
619
  `!`,
517
- `video/x-raw,width=${inferenceDims.width},height=${inferenceDims.height}`,
620
+ `video/x-raw${videoFormat},width=${inferenceDims.width},height=${inferenceDims.height}`,
518
621
  ]);
519
622
  }
520
- else if (inferenceDims.resizeMode === 'squash' || inferenceDims.resizeMode === 'none' /* old model */) {
623
+ else if (inferenceDims.resizeMode === 'squash' || inferenceDims.resizeMode === 'none' /* old model -> squash */) {
521
624
  cropArgs.push(`!`);
522
625
  cropArgs.push(`videoscale`, `method=lanczos`);
523
626
  cropArgs.push(`!`);
524
- cropArgs.push(`video/x-raw,width=${inferenceDims.width},height=${inferenceDims.height}`);
627
+ cropArgs.push(`video/x-raw${videoFormat},width=${inferenceDims.width},height=${inferenceDims.height}`);
525
628
  }
526
629
  }
527
630
  let args;
@@ -532,14 +635,35 @@ class GStreamer extends tsee_1.EventEmitter {
532
635
  `video/x-raw,width=${cap.width},height=${cap.height},format=YUY2`
533
636
  ]);
534
637
  }
638
+ else if (device.videoSource === 'qtiqmmfsrc') {
639
+ videoSource = videoSource.concat([
640
+ `!`,
641
+ `video/x-raw,width=${cap.width},height=${cap.height},format=NV12`
642
+ ]);
643
+ }
644
+ else if (this._mode === 'rpi5') {
645
+ // on RPi 5 we need to set color format
646
+ let colorFormat = '';
647
+ if (this._overrideColorFormat) {
648
+ colorFormat = this._overrideColorFormat;
649
+ }
650
+ else if (cap.formats && cap.formats.indexOf('YUY2') > -1) {
651
+ colorFormat = 'YUY2';
652
+ }
653
+ else if (cap.formats) {
654
+ throw new Error('Detected RPi 5 camera. Please provide the color format with `--camera-color-format`, supported formats: ' + cap.formats.join(', '));
655
+ }
656
+ videoSource = videoSource.concat([
657
+ `!`,
658
+ `video/x-raw,width=${cap.width},height=${cap.height},format=${colorFormat}`
659
+ ]);
660
+ }
535
661
  else {
536
662
  videoSource = videoSource.concat([
537
663
  `!`,
538
664
  `video/x-raw,width=${cap.width},height=${cap.height}`
539
665
  ]);
540
666
  }
541
- let frameReadyArgs = this._profiling ? [`!`, `identity name=frame_ready silent=false`] : [];
542
- let jpgencDoneArgs = this._profiling ? [`!`, `identity name=jpegenc_done silent=false`] : [];
543
667
  if (cropArgs.length === 0) {
544
668
  args = videoSource.concat([
545
669
  ...frameReadyArgs,
@@ -557,11 +681,9 @@ class GStreamer extends tsee_1.EventEmitter {
557
681
  let resized = [
558
682
  `t. ! queue`,
559
683
  ...cropArgs,
684
+ ...resizeDoneArgs,
560
685
  `!`,
561
- `jpegenc`,
562
- ...jpgencDoneArgs,
563
- `!`,
564
- `multifilesink location=resized%05d.jpg post-messages=true sync=false`,
686
+ ...teeToBothResizedJpgAndRgb,
565
687
  ];
566
688
  args = videoSource.concat([
567
689
  ...frameReadyArgs,
@@ -573,15 +695,51 @@ class GStreamer extends tsee_1.EventEmitter {
573
695
  ...resized,
574
696
  ]);
575
697
  this._emitsOriginalSizeImages = true;
698
+ if (this._outputRgbBuffers) {
699
+ this._emitsRgbBuffers = true;
700
+ }
576
701
  }
577
702
  }
578
703
  else if (cap.type === 'image/jpeg') {
579
- args = videoSource.concat([
704
+ videoSource = videoSource.concat([
580
705
  `!`,
581
706
  `image/jpeg,width=${cap.width},height=${cap.height}`,
582
- `!`,
583
- `multifilesink location=resized%05d.jpg post-messages=true sync=false`
584
707
  ]);
708
+ if (cropArgs.length === 0) {
709
+ args = videoSource.concat([
710
+ ...frameReadyArgs,
711
+ `!`,
712
+ `multifilesink`,
713
+ `location=resized%05d.jpg`,
714
+ `post-messages=true`,
715
+ `sync=false`,
716
+ ]);
717
+ }
718
+ else {
719
+ let original = `t. ! queue ! multifilesink location=original%05d.jpg post-messages=true sync=false`;
720
+ let resized = [
721
+ `t. ! queue`,
722
+ `!`,
723
+ `jpegdec`,
724
+ `!`,
725
+ `videoconvert`,
726
+ ...cropArgs,
727
+ ...resizeDoneArgs,
728
+ `!`,
729
+ ...teeToBothResizedJpgAndRgb,
730
+ ];
731
+ args = videoSource.concat([
732
+ ...frameReadyArgs,
733
+ `!`,
734
+ `tee name=t`,
735
+ original,
736
+ ...resized,
737
+ ]);
738
+ this._emitsOriginalSizeImages = true;
739
+ if (this._outputRgbBuffers) {
740
+ this._emitsRgbBuffers = true;
741
+ }
742
+ }
585
743
  }
586
744
  else if (cap.type === 'nvarguscamerasrc') {
587
745
  args = [
@@ -595,7 +753,8 @@ class GStreamer extends tsee_1.EventEmitter {
595
753
  }
596
754
  return {
597
755
  command: 'gst-launch-1.0',
598
- pipeline: args.join(' '),
756
+ // replace multiple spaces with one space
757
+ pipeline: args.join(' ').trim().replace(/(\s+)/g, ' '),
599
758
  };
600
759
  }
601
760
  async stop() {
@@ -607,7 +766,10 @@ class GStreamer extends tsee_1.EventEmitter {
607
766
  this._captureProcess.on('close', code => {
608
767
  if (this._watcher) {
609
768
  this._watcher.on('close', async () => {
610
- await this.cleanupTempDirAsync();
769
+ try {
770
+ await this.cleanupTempDirAsync();
771
+ }
772
+ catch (ex) { /* noop */ }
611
773
  });
612
774
  this._watcher.close();
613
775
  }
@@ -696,7 +858,8 @@ class GStreamer extends tsee_1.EventEmitter {
696
858
  if (!currDevice)
697
859
  continue;
698
860
  if (l.startsWith('name :')) {
699
- currDevice.name = l.split(':')[1].trim();
861
+ // extract name from the l between 'name : ' and '\n', because name may contain colons as well
862
+ currDevice.name = l.split(':').slice(1).join(':').trim();
700
863
  continue;
701
864
  }
702
865
  if (l.startsWith('class :')) {
@@ -731,6 +894,12 @@ class GStreamer extends tsee_1.EventEmitter {
731
894
  if (currDevice.videoSource === 'pipewiresrc') {
732
895
  currDevice.videoSource = DEFAULT_GST_VIDEO_SOURCE;
733
896
  }
897
+ if (currDevice.videoSource === 'avfvideosrc') {
898
+ const m = l.match(/gst-launch-1.0 avfvideosrc device-index=(\d+) \!/);
899
+ if (m && m.length >= 2) {
900
+ currDevice.videoSource = `avfvideosrc device-index=${m[1]}`;
901
+ }
902
+ }
734
903
  }
735
904
  }
736
905
  }
@@ -742,6 +911,7 @@ class GStreamer extends tsee_1.EventEmitter {
742
911
  let width = (l.match(/width=[^\d]+(\d+)/) || [])[1];
743
912
  let height = (l.match(/height=[^\d]+(\d+)/) || [])[1];
744
913
  let framerate = (l.match(/framerate=[^\d]+(\d+)/) || [])[1];
914
+ let format = (l.match(/format=([a-zA-Z0-9]+)/) || [])[1];
745
915
  // Rpi on bullseye has lines like this..
746
916
  // eslint-disable-next-line @stylistic/max-len
747
917
  // image/jpeg, width=160, height=120, pixel-aspect-ratio=1/1, framerate={ (fraction)30/1, (fraction)24/1, (fraction)20/1, (fraction)15/1, (fraction)10/1, (fraction)15/2, (fraction)5/1 }
@@ -759,10 +929,11 @@ class GStreamer extends tsee_1.EventEmitter {
759
929
  width: Number(width || '0'),
760
930
  height: Number(height || '0'),
761
931
  framerate: Number(framerate || '0'),
932
+ formats: format ? [format] : []
762
933
  };
763
934
  return r;
764
935
  });
765
- if (this._mode === 'rpi' || this._mode === 'microchip') { // no framerate here...
936
+ if (this._mode === 'rpi' || this._mode === 'rpi5' || d.videoSource === 'libcamerasrc') { // no framerate here...
766
937
  c = c.filter(x => x.width && x.height);
767
938
  }
768
939
  else if (d.name === 'RZG2L_CRU') {
@@ -780,14 +951,23 @@ class GStreamer extends tsee_1.EventEmitter {
780
951
  else {
781
952
  c = c.filter(x => x.width && x.height && x.framerate);
782
953
  }
954
+ // get list of all formats form the c list
955
+ const uniqueFormats = Array.from(new Set(c.map(cap => cap.formats).flat()));
783
956
  c = c.reduce((curr, o) => {
784
- // deduplicate caps
785
- if (!curr.some(obj => obj.framerate === o.framerate &&
957
+ // deduplicate caps, prioritize 'video/x-raw' type
958
+ const existingIndex = curr.findIndex(obj => obj.framerate === o.framerate &&
786
959
  obj.width === o.width &&
787
- obj.height === o.height &&
788
- obj.framerate === o.framerate)) {
960
+ obj.height === o.height);
961
+ // store all supported formats (YUY2 etc.)
962
+ if (uniqueFormats) {
963
+ o.formats = uniqueFormats.filter((format) => format !== undefined);
964
+ }
965
+ if (existingIndex === -1) {
789
966
  curr.push(o);
790
967
  }
968
+ else if (o.type === 'video/x-raw' && curr[existingIndex].type !== 'video/x-raw') {
969
+ curr[existingIndex] = o;
970
+ }
791
971
  return curr;
792
972
  }, []);
793
973
  d.caps = c;
@@ -804,7 +984,7 @@ class GStreamer extends tsee_1.EventEmitter {
804
984
  // Qualcomm has their own plugin, query its too
805
985
  // because listQtiqmmsrc* returns hardcoded devices ONLY if a specific gst plugin
806
986
  // is available we need to detect device type before, as it may be RubikPi, RB3Gen2 or
807
- // any other with IMSDK loaded and unkniwn cameras
987
+ // any other with IMSDK loaded and unknown cameras
808
988
  if (this._mode === 'qualcomm-rb3gen2') {
809
989
  devices = devices.concat(await this.listQtiqmmsrcDevices());
810
990
  }
@@ -822,10 +1002,18 @@ class GStreamer extends tsee_1.EventEmitter {
822
1002
  videoSource: d.videoSource,
823
1003
  };
824
1004
  });
825
- // deduplicate (by id)
1005
+ // deduplicate (by id if present, otherwise by name)
826
1006
  mapped = mapped.reduce((curr, m) => {
827
- if (curr.find(x => x.id === m.id))
828
- return curr;
1007
+ if (m.id) {
1008
+ if (curr.find(x => x.id === m.id)) {
1009
+ return curr;
1010
+ }
1011
+ }
1012
+ else if (m.name) {
1013
+ if (curr.find(x => x.name === m.name && !x.id)) {
1014
+ return curr;
1015
+ }
1016
+ }
829
1017
  curr.push(m);
830
1018
  return curr;
831
1019
  }, []);
@@ -1253,6 +1441,8 @@ class GStreamer extends tsee_1.EventEmitter {
1253
1441
  const cropTop = Math.max(0, Math.floor((srcH - cropH) / 2));
1254
1442
  const cropBottom = Math.max(0, srcH - cropH - cropTop);
1255
1443
  return {
1444
+ width: cropW,
1445
+ height: cropH,
1256
1446
  left: cropLeft,
1257
1447
  right: cropRight,
1258
1448
  top: cropTop,
@@ -1277,7 +1467,7 @@ class GStreamer extends tsee_1.EventEmitter {
1277
1467
  }
1278
1468
  let filesToUnlink = [];
1279
1469
  // copy as we're manipulating this set
1280
- for (let originalFileName of Array.from(this._originalFilesSeen)) {
1470
+ for (let originalFileName of Array.from(this._originalAndRgbFilesSeen)) {
1281
1471
  let originalNumberMatch = originalFileName.match(/(\d+)/);
1282
1472
  let originalNumber = originalNumberMatch ? Number(originalNumberMatch[1]) : NaN;
1283
1473
  if (isNaN(originalNumber)) {
@@ -1292,13 +1482,13 @@ class GStreamer extends tsee_1.EventEmitter {
1292
1482
  filesToUnlink.push(originalFileName);
1293
1483
  }
1294
1484
  if (this._verbose) {
1295
- console.log(PREFIX, `Unlinking ${filesToUnlink.length} original files...`);
1485
+ console.log(PREFIX, `Unlinking ${filesToUnlink.length} original / RGB files...`);
1296
1486
  }
1297
1487
  if (filesToUnlink.length > 0) {
1298
1488
  const tempDir = this._tempDir;
1299
1489
  await (0, async_pool_1.asyncPool)(10, filesToUnlink, async (originalFileName) => {
1300
1490
  await this.safeUnlinkFile(path_1.default.join(tempDir, originalFileName));
1301
- this._originalFilesSeen.delete(originalFileName);
1491
+ this._originalAndRgbFilesSeen.delete(originalFileName);
1302
1492
  });
1303
1493
  }
1304
1494
  }