drawdown-arcore-depth 0.1.4 → 0.1.5

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.
package/README.md CHANGED
@@ -188,3 +188,8 @@ No ArUco marker is required by the native ARCore workflow. The existing RFID/NFC
188
188
  ## v0.1.4 Raw Depth field fix
189
189
 
190
190
  The DBH sampler now anchors front-surface range with ARCore Raw Depth plus the matching confidence image (confidence >= 128). Smoothed Depth is used only after a high-confidence foreground tree range has been established, to fill the trunk surface for multi-row boundary estimation. This prevents distant outdoor background depth from being mistaken for the trunk.
191
+
192
+
193
+ ## 0.1.5 diagnostic sweep
194
+
195
+ Version 0.1.5 keeps the 0.1.4 Raw Depth DBH acceptance geometry unchanged and adds compact native sweep diagnostics. `captureDbhSweep()` now reports per-stage frame counts and `nativeRejectionCounts` so zero-observation field failures can be diagnosed without retaining camera or depth frames.
@@ -39,29 +39,69 @@ final class DepthObservationSampler {
39
39
  private static final double MIN_TARGET_DEPTH_MM = 500.0;
40
40
  private static final double MAX_TARGET_DEPTH_MM = 5000.0;
41
41
 
42
- DepthObservation sample(Frame frame, Camera camera, int viewWidth, int viewHeight) {
42
+ DepthSampleResult sample(Frame frame, Camera camera, int viewWidth, int viewHeight) {
43
+ boolean trackingFrame = false;
44
+ boolean rawDepthFrame = false;
45
+ boolean mappedCenterReached = false;
46
+ boolean rawAnchorReached = false;
47
+ boolean rawPatchReached = false;
48
+ boolean smoothDepthMatchReached = false;
49
+ boolean boundaryConsensusReached = false;
50
+
43
51
  if (camera.getTrackingState() != TrackingState.TRACKING) {
44
- return null;
52
+ return DepthSampleResult.failure(
53
+ "tracking_not_tracking",
54
+ false, false, false, false, false, false, false);
45
55
  }
56
+ trackingFrame = true;
57
+
58
+ Image rawDepth = null;
59
+ Image rawConfidence = null;
60
+ Image smoothDepth = null;
61
+ try {
62
+ try {
63
+ rawDepth = frame.acquireRawDepthImage16Bits();
64
+ rawDepthFrame = true;
65
+ } catch (NotYetAvailableException e) {
66
+ return DepthSampleResult.failure(
67
+ "raw_depth_not_yet_available",
68
+ trackingFrame, false, false, false, false, false, false);
69
+ }
70
+
71
+ try {
72
+ rawConfidence = frame.acquireRawDepthConfidenceImage();
73
+ } catch (NotYetAvailableException e) {
74
+ return DepthSampleResult.failure(
75
+ "raw_confidence_not_available",
76
+ trackingFrame, rawDepthFrame, false, false, false, false, false);
77
+ }
46
78
 
47
- try (
48
- Image rawDepth = frame.acquireRawDepthImage16Bits();
49
- Image rawConfidence = frame.acquireRawDepthConfidenceImage();
50
- Image smoothDepth = frame.acquireDepthImage16Bits()) {
79
+ try {
80
+ smoothDepth = frame.acquireDepthImage16Bits();
81
+ } catch (NotYetAvailableException e) {
82
+ return DepthSampleResult.failure(
83
+ "smooth_depth_not_available",
84
+ trackingFrame, rawDepthFrame, false, false, false, false, false);
85
+ }
51
86
 
52
87
  CameraIntrinsics intrinsics = camera.getImageIntrinsics();
53
88
  float[] focal = intrinsics.getFocalLength();
54
89
  int[] imageDimensions = intrinsics.getImageDimensions();
55
90
  if (focal == null || focal.length < 2 || imageDimensions == null || imageDimensions.length < 2) {
56
- return null;
91
+ return DepthSampleResult.failure(
92
+ "image_intrinsics_missing",
93
+ trackingFrame, rawDepthFrame, false, false, false, false, false);
57
94
  }
58
95
 
59
- // ARCore documents raw depth, confidence and smoothed depth as the same size.
96
+ // Keep the 0.1.4 geometry assumptions unchanged; 0.1.5 only makes
97
+ // failures observable instead of silently returning null.
60
98
  if (rawDepth.getWidth() != rawConfidence.getWidth()
61
99
  || rawDepth.getHeight() != rawConfidence.getHeight()
62
100
  || rawDepth.getWidth() != smoothDepth.getWidth()
63
101
  || rawDepth.getHeight() != smoothDepth.getHeight()) {
64
- return null;
102
+ return DepthSampleResult.failure(
103
+ "image_size_mismatch",
104
+ trackingFrame, rawDepthFrame, false, false, false, false, false);
65
105
  }
66
106
 
67
107
  float[] viewCenter = new float[] { viewWidth / 2f, viewHeight / 2f };
@@ -74,13 +114,19 @@ final class DepthObservationSampler {
74
114
 
75
115
  int[] mappedCenter = imageToDepth(frame, rawDepth, imageCenter[0], imageCenter[1]);
76
116
  if (mappedCenter == null) {
77
- return null;
117
+ return DepthSampleResult.failure(
118
+ "image_to_depth_mapping_failed",
119
+ trackingFrame, rawDepthFrame, false, false, false, false, false);
78
120
  }
121
+ mappedCenterReached = true;
79
122
 
80
123
  RawAnchor anchor = findRawAnchor(rawDepth, rawConfidence, mappedCenter[0], mappedCenter[1]);
81
124
  if (anchor == null) {
82
- return null;
125
+ return DepthSampleResult.failure(
126
+ "no_confident_raw_anchor",
127
+ trackingFrame, rawDepthFrame, mappedCenterReached, false, false, false, false);
83
128
  }
129
+ rawAnchorReached = true;
84
130
 
85
131
  double rawToleranceMm = clamp(anchor.depthMm * 0.10, 100.0, 250.0);
86
132
  List<Integer> rawPatch = collectConfidentRawPatch(
@@ -92,13 +138,18 @@ final class DepthObservationSampler {
92
138
  rawToleranceMm,
93
139
  anchor.depthMm);
94
140
  if (rawPatch.size() < MIN_RAW_PATCH_SAMPLES) {
95
- return null;
141
+ return DepthSampleResult.failure(
142
+ "insufficient_raw_patch_samples",
143
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, false, false, false);
96
144
  }
145
+ rawPatchReached = true;
97
146
 
98
147
  double centerDepthMm = median(rawPatch);
99
148
  double centerMadMm = mad(rawPatch, centerDepthMm);
100
149
  if (centerDepthMm < MIN_TARGET_DEPTH_MM || centerDepthMm > MAX_TARGET_DEPTH_MM) {
101
- return null;
150
+ return DepthSampleResult.failure(
151
+ "target_depth_out_of_range",
152
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, false, false);
102
153
  }
103
154
 
104
155
  // Locate the foreground surface in the smoothed depth image, but only
@@ -111,8 +162,11 @@ final class DepthObservationSampler {
111
162
  centerDepthMm,
112
163
  clamp(centerDepthMm * 0.10, 100.0, 250.0));
113
164
  if (centerX < 0) {
114
- return null;
165
+ return DepthSampleResult.failure(
166
+ "no_smooth_depth_match",
167
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, false, false);
115
168
  }
169
+ smoothDepthMatchReached = true;
116
170
 
117
171
  double toleranceMm = clamp(centerDepthMm * 0.08, 80.0, 220.0);
118
172
  Boundary boundary = findConsensusBoundary(
@@ -122,28 +176,41 @@ final class DepthObservationSampler {
122
176
  centerDepthMm,
123
177
  toleranceMm);
124
178
  if (boundary == null || boundary.rightX - boundary.leftX < 3) {
125
- return null;
179
+ return DepthSampleResult.failure(
180
+ "boundary_no_consensus",
181
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, smoothDepthMatchReached, false);
126
182
  }
183
+ boundaryConsensusReached = true;
127
184
 
128
185
  int depthWidth = smoothDepth.getWidth();
129
186
  int boundaryWidth = boundary.rightX - boundary.leftX;
130
187
  if (boundary.leftX <= EDGE_MARGIN_PX
131
- || boundary.rightX >= depthWidth - 1 - EDGE_MARGIN_PX
132
- || boundaryWidth > depthWidth * MAX_TRUNK_WIDTH_FRACTION) {
133
- return null;
188
+ || boundary.rightX >= depthWidth - 1 - EDGE_MARGIN_PX) {
189
+ return DepthSampleResult.failure(
190
+ "boundary_touches_edge",
191
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, smoothDepthMatchReached, boundaryConsensusReached);
192
+ }
193
+ if (boundaryWidth > depthWidth * MAX_TRUNK_WIDTH_FRACTION) {
194
+ return DepthSampleResult.failure(
195
+ "boundary_too_wide",
196
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, smoothDepthMatchReached, boundaryConsensusReached);
134
197
  }
135
198
 
136
199
  float[] leftImage = depthToImage(frame, smoothDepth, boundary.leftX, anchor.y);
137
200
  float[] rightImage = depthToImage(frame, smoothDepth, boundary.rightX, anchor.y);
138
201
  if (leftImage == null || rightImage == null) {
139
- return null;
202
+ return DepthSampleResult.failure(
203
+ "depth_to_image_mapping_failed",
204
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, smoothDepthMatchReached, boundaryConsensusReached);
140
205
  }
141
206
 
142
207
  double dx = rightImage[0] - leftImage[0];
143
208
  double dy = rightImage[1] - leftImage[1];
144
209
  double imageWidthPx = Math.hypot(dx, dy);
145
210
  if (!Double.isFinite(imageWidthPx) || imageWidthPx < 8.0) {
146
- return null;
211
+ return DepthSampleResult.failure(
212
+ "image_width_too_small",
213
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, smoothDepthMatchReached, boundaryConsensusReached);
147
214
  }
148
215
 
149
216
  double normalizedAngularWidth = Math.sqrt(
@@ -152,7 +219,9 @@ final class DepthObservationSampler {
152
219
  if (!Double.isFinite(normalizedAngularWidth)
153
220
  || normalizedAngularWidth <= 0.0
154
221
  || normalizedAngularWidth > 0.95) {
155
- return null;
222
+ return DepthSampleResult.failure(
223
+ "angular_width_invalid",
224
+ trackingFrame, rawDepthFrame, mappedCenterReached, rawAnchorReached, rawPatchReached, smoothDepthMatchReached, boundaryConsensusReached);
156
225
  }
157
226
 
158
227
  double effectiveFocalPx = imageWidthPx / normalizedAngularWidth;
@@ -178,7 +247,7 @@ final class DepthObservationSampler {
178
247
  boundaryConfidence = "low";
179
248
  }
180
249
 
181
- return new DepthObservation(
250
+ DepthObservation observation = new DepthObservation(
182
251
  centerDepthMm / 1000.0,
183
252
  effectiveFocalPx,
184
253
  imageWidthPx,
@@ -187,10 +256,21 @@ final class DepthObservationSampler {
187
256
  boundaryConfidence,
188
257
  centerMadMm / 1000.0,
189
258
  System.currentTimeMillis());
190
- } catch (NotYetAvailableException e) {
191
- return null;
259
+ return DepthSampleResult.success(observation);
192
260
  } catch (Exception e) {
193
- return null;
261
+ return DepthSampleResult.failure(
262
+ "unexpected_exception",
263
+ trackingFrame,
264
+ rawDepthFrame,
265
+ mappedCenterReached,
266
+ rawAnchorReached,
267
+ rawPatchReached,
268
+ smoothDepthMatchReached,
269
+ boundaryConsensusReached);
270
+ } finally {
271
+ if (smoothDepth != null) smoothDepth.close();
272
+ if (rawConfidence != null) rawConfidence.close();
273
+ if (rawDepth != null) rawDepth.close();
194
274
  }
195
275
  }
196
276
 
@@ -0,0 +1,64 @@
1
+ package org.drawdown.arcoredepth;
2
+
3
+ /**
4
+ * One attempted native ARCore sample. A failed sample carries a stable reason
5
+ * plus milestone flags so the sweep can explain where frames are being lost
6
+ * without persisting camera/depth frames.
7
+ */
8
+ final class DepthSampleResult {
9
+ final DepthObservation observation;
10
+ final String rejectionReason;
11
+ final boolean trackingFrame;
12
+ final boolean rawDepthFrame;
13
+ final boolean mappedCenter;
14
+ final boolean rawAnchor;
15
+ final boolean rawPatch;
16
+ final boolean smoothDepthMatch;
17
+ final boolean boundaryConsensus;
18
+
19
+ DepthSampleResult(
20
+ DepthObservation observation,
21
+ String rejectionReason,
22
+ boolean trackingFrame,
23
+ boolean rawDepthFrame,
24
+ boolean mappedCenter,
25
+ boolean rawAnchor,
26
+ boolean rawPatch,
27
+ boolean smoothDepthMatch,
28
+ boolean boundaryConsensus) {
29
+ this.observation = observation;
30
+ this.rejectionReason = rejectionReason;
31
+ this.trackingFrame = trackingFrame;
32
+ this.rawDepthFrame = rawDepthFrame;
33
+ this.mappedCenter = mappedCenter;
34
+ this.rawAnchor = rawAnchor;
35
+ this.rawPatch = rawPatch;
36
+ this.smoothDepthMatch = smoothDepthMatch;
37
+ this.boundaryConsensus = boundaryConsensus;
38
+ }
39
+
40
+ static DepthSampleResult failure(
41
+ String reason,
42
+ boolean trackingFrame,
43
+ boolean rawDepthFrame,
44
+ boolean mappedCenter,
45
+ boolean rawAnchor,
46
+ boolean rawPatch,
47
+ boolean smoothDepthMatch,
48
+ boolean boundaryConsensus) {
49
+ return new DepthSampleResult(
50
+ null,
51
+ reason,
52
+ trackingFrame,
53
+ rawDepthFrame,
54
+ mappedCenter,
55
+ rawAnchor,
56
+ rawPatch,
57
+ smoothDepthMatch,
58
+ boundaryConsensus);
59
+ }
60
+
61
+ static DepthSampleResult success(DepthObservation observation) {
62
+ return new DepthSampleResult(observation, null, true, true, true, true, true, true, true);
63
+ }
64
+ }
@@ -36,7 +36,9 @@ import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationExceptio
36
36
  import android.opengl.GLSurfaceView;
37
37
 
38
38
  import java.util.ArrayList;
39
+ import java.util.LinkedHashMap;
39
40
  import java.util.List;
41
+ import java.util.Map;
40
42
 
41
43
  @CapacitorPlugin(
42
44
  name = "DrawDownArCoreDepth",
@@ -44,7 +46,8 @@ import java.util.List;
44
46
  @Permission(alias = "camera", strings = { Manifest.permission.CAMERA })
45
47
  })
46
48
  public class DrawDownArCoreDepthPlugin extends Plugin {
47
- private static final String PLUGIN_VERSION = "0.1.4";
49
+ private static final String PLUGIN_VERSION = "0.1.5";
50
+ private static final String METHOD_VERSION = "arcore-raw-depth-cylinder-v4-diagnostics";
48
51
  private static final long WARMUP_MS = 1200L;
49
52
 
50
53
  private final Handler mainHandler = new Handler(Looper.getMainLooper());
@@ -68,6 +71,15 @@ public class DrawDownArCoreDepthPlugin extends Plugin {
68
71
  private int maxObservations = 10;
69
72
  private int minObservations = 5;
70
73
  private final List<DepthObservation> sweepObservations = new ArrayList<>();
74
+ private final Map<String, Integer> sweepRejectionCounts = new LinkedHashMap<>();
75
+ private int totalFrameCount = 0;
76
+ private int trackingFrameCount = 0;
77
+ private int rawDepthFrameCount = 0;
78
+ private int mappedCenterCount = 0;
79
+ private int rawAnchorCount = 0;
80
+ private int rawPatchCount = 0;
81
+ private int smoothDepthMatchCount = 0;
82
+ private int boundaryConsensusCount = 0;
71
83
 
72
84
  @PluginMethod
73
85
  public void checkSupport(PluginCall call) {
@@ -251,6 +263,15 @@ public class DrawDownArCoreDepthPlugin extends Plugin {
251
263
 
252
264
  pendingSweepCall = call;
253
265
  sweepObservations.clear();
266
+ sweepRejectionCounts.clear();
267
+ totalFrameCount = 0;
268
+ trackingFrameCount = 0;
269
+ rawDepthFrameCount = 0;
270
+ mappedCenterCount = 0;
271
+ rawAnchorCount = 0;
272
+ rawPatchCount = 0;
273
+ smoothDepthMatchCount = 0;
274
+ boundaryConsensusCount = 0;
254
275
  minObservations = requestedMin;
255
276
  maxObservations = requestedMax;
256
277
  sampleIntervalMs = Math.max(100L, durationMs / Math.max(1, requestedMax * 2));
@@ -372,32 +393,61 @@ public class DrawDownArCoreDepthPlugin extends Plugin {
372
393
 
373
394
  try {
374
395
  Camera camera = frame.getCamera();
375
- DepthObservation observation = sampler.sample(frame, camera, viewWidth, viewHeight);
376
- if (observation == null) {
377
- return;
378
- }
396
+ DepthSampleResult sampleResult = sampler.sample(frame, camera, viewWidth, viewHeight);
379
397
 
380
398
  boolean reachedMax = false;
381
399
  synchronized (sweepLock) {
382
400
  if (!sweepActive || pendingSweepCall == null) {
383
401
  return;
384
402
  }
385
- sweepObservations.add(observation);
386
- reachedMax = sweepObservations.size() >= maxObservations;
403
+ totalFrameCount += 1;
404
+ if (sampleResult.trackingFrame) trackingFrameCount += 1;
405
+ if (sampleResult.rawDepthFrame) rawDepthFrameCount += 1;
406
+ if (sampleResult.mappedCenter) mappedCenterCount += 1;
407
+ if (sampleResult.rawAnchor) rawAnchorCount += 1;
408
+ if (sampleResult.rawPatch) rawPatchCount += 1;
409
+ if (sampleResult.smoothDepthMatch) smoothDepthMatchCount += 1;
410
+ if (sampleResult.boundaryConsensus) boundaryConsensusCount += 1;
411
+
412
+ if (sampleResult.rejectionReason != null) {
413
+ int current = sweepRejectionCounts.containsKey(sampleResult.rejectionReason)
414
+ ? sweepRejectionCounts.get(sampleResult.rejectionReason)
415
+ : 0;
416
+ sweepRejectionCounts.put(sampleResult.rejectionReason, current + 1);
417
+ }
418
+
419
+ if (sampleResult.observation != null) {
420
+ sweepObservations.add(sampleResult.observation);
421
+ reachedMax = sweepObservations.size() >= maxObservations;
422
+ }
387
423
  }
388
424
 
389
425
  if (reachedMax) {
390
426
  mainHandler.post(() -> finishSweep(false));
391
427
  }
392
428
  } catch (Exception ignored) {
393
- // Invalid frames are simply skipped; the app's aggregate QA rejects
394
- // a sweep with too few valid observations.
429
+ synchronized (sweepLock) {
430
+ totalFrameCount += 1;
431
+ int current = sweepRejectionCounts.containsKey("unexpected_exception")
432
+ ? sweepRejectionCounts.get("unexpected_exception")
433
+ : 0;
434
+ sweepRejectionCounts.put("unexpected_exception", current + 1);
435
+ }
395
436
  }
396
437
  }
397
438
 
398
439
  private void finishSweep(boolean stoppedByCaller) {
399
440
  PluginCall sweepCall;
400
441
  List<DepthObservation> observations;
442
+ Map<String, Integer> rejectionCounts;
443
+ int framesTotal;
444
+ int framesTracking;
445
+ int framesRawDepth;
446
+ int centersMapped;
447
+ int anchorsRaw;
448
+ int patchesRaw;
449
+ int matchesSmooth;
450
+ int boundariesConsensus;
401
451
  synchronized (sweepLock) {
402
452
  sweepCall = pendingSweepCall;
403
453
  if (sweepCall == null) {
@@ -407,7 +457,17 @@ public class DrawDownArCoreDepthPlugin extends Plugin {
407
457
  pendingSweepCall = null;
408
458
  sweepActive = false;
409
459
  observations = new ArrayList<>(sweepObservations);
460
+ rejectionCounts = new LinkedHashMap<>(sweepRejectionCounts);
461
+ framesTotal = totalFrameCount;
462
+ framesTracking = trackingFrameCount;
463
+ framesRawDepth = rawDepthFrameCount;
464
+ centersMapped = mappedCenterCount;
465
+ anchorsRaw = rawAnchorCount;
466
+ patchesRaw = rawPatchCount;
467
+ matchesSmooth = smoothDepthMatchCount;
468
+ boundariesConsensus = boundaryConsensusCount;
410
469
  sweepObservations.clear();
470
+ sweepRejectionCounts.clear();
411
471
  }
412
472
 
413
473
  JSArray observationArray = new JSArray();
@@ -415,12 +475,29 @@ public class DrawDownArCoreDepthPlugin extends Plugin {
415
475
  observationArray.put(observation.toJsObject());
416
476
  }
417
477
 
478
+ JSObject rejectionObject = new JSObject();
479
+ for (Map.Entry<String, Integer> entry : rejectionCounts.entrySet()) {
480
+ rejectionObject.put(entry.getKey(), entry.getValue());
481
+ }
482
+
418
483
  JSObject result = new JSObject();
419
484
  result.put("observations", observationArray);
420
485
  result.put("deviceModel", Build.MANUFACTURER + " " + Build.MODEL);
421
486
  result.put("arcoreVersion", installedArcoreVersion());
487
+ result.put("pluginVersion", PLUGIN_VERSION);
488
+ result.put("methodVersion", METHOD_VERSION);
422
489
  result.put("minimumRequested", minObservations);
423
490
  result.put("stoppedByCaller", stoppedByCaller);
491
+ result.put("totalFrameCount", framesTotal);
492
+ result.put("trackingFrameCount", framesTracking);
493
+ result.put("rawDepthFrameCount", framesRawDepth);
494
+ result.put("mappedCenterCount", centersMapped);
495
+ result.put("rawAnchorCount", anchorsRaw);
496
+ result.put("rawPatchCount", patchesRaw);
497
+ result.put("smoothDepthMatchCount", matchesSmooth);
498
+ result.put("boundaryConsensusCount", boundariesConsensus);
499
+ result.put("nativeValidObservationCount", observations.size());
500
+ result.put("nativeRejectionCounts", rejectionObject);
424
501
  sweepCall.resolve(result);
425
502
 
426
503
  if (!stoppedByCaller) {
@@ -44,6 +44,18 @@ export interface CaptureDbhSweepResult {
44
44
  observations: DbhObservation[];
45
45
  deviceModel?: string;
46
46
  arcoreVersion?: string;
47
+ pluginVersion?: string;
48
+ methodVersion?: string;
49
+ totalFrameCount?: number;
50
+ trackingFrameCount?: number;
51
+ rawDepthFrameCount?: number;
52
+ mappedCenterCount?: number;
53
+ rawAnchorCount?: number;
54
+ rawPatchCount?: number;
55
+ smoothDepthMatchCount?: number;
56
+ boundaryConsensusCount?: number;
57
+ nativeValidObservationCount?: number;
58
+ nativeRejectionCounts?: Record<string, number>;
47
59
  }
48
60
  export interface DrawDownArCoreDepthPlugin {
49
61
  checkSupport(): Promise<SupportResult>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "drawdown-arcore-depth",
3
- "version": "0.1.4",
4
- "description": "Small Android-only Capacitor 8 bridge for ARCore Depth DBH observations.",
3
+ "version": "0.1.5",
4
+ "description": "Small Android-only Capacitor 8 bridge for ARCore Raw Depth DBH observations with native sweep diagnostics.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
7
7
  "types": "dist/esm/index.d.ts",