1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.ar.slam;
17
18 import com.irurueta.algebra.ArrayUtils;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.WrongSizeException;
21 import com.irurueta.numerical.signal.processing.MeasurementNoiseCovarianceEstimator;
22 import com.irurueta.numerical.signal.processing.SignalProcessingException;
23 import com.irurueta.statistics.InvalidCovarianceMatrixException;
24 import com.irurueta.statistics.MultivariateNormalDist;
25
26 import java.util.Arrays;
27
28
29
30
31
32
33
34
35 @SuppressWarnings("DuplicatedCode")
36 public abstract class BaseSlamCalibrator<D extends BaseCalibrationData> {
37
38
39
40
41 public static final int MIN_SAMPLE_LENGTH = 1;
42
43
44
45
46 public static final int DEFAULT_MIN_NUM_SAMPLES = 20;
47
48
49
50
51 public static final int DEFAULT_MAX_NUM_SAMPLES = 100;
52
53
54
55
56 public static final double DEFAULT_CONVERGENCE_THRESHOLD = 1e-5;
57
58
59
60
61 protected static final boolean DEFAULT_ENABLE_SAMPLE_ACCUMULATION = true;
62
63
64
65
66 protected static final int N_COMPONENTS_3D = 3;
67
68
69
70
71 protected static final double NANOS_TO_SECONDS = 1e-9;
72
73
74
75
76
77 private final int sampleLength;
78
79
80
81
82 protected double[] sample;
83
84
85
86
87 protected MeasurementNoiseCovarianceEstimator estimator;
88
89
90
91
92 protected double[] previousMean;
93
94
95
96
97 protected Matrix previousCovariance;
98
99
100
101
102 protected boolean converged;
103
104
105
106
107 protected boolean failed;
108
109
110
111
112 protected boolean finished;
113
114
115
116
117 protected int sampleCount;
118
119
120
121
122
123 protected double[] meanDiff;
124
125
126
127
128
129 protected Matrix covDiff;
130
131
132
133
134 protected int minNumSamples = DEFAULT_MIN_NUM_SAMPLES;
135
136
137
138
139 protected int maxNumSamples = DEFAULT_MAX_NUM_SAMPLES;
140
141
142
143
144 protected double convergenceThreshold = DEFAULT_CONVERGENCE_THRESHOLD;
145
146
147
148
149 protected boolean accumulationEnabled = DEFAULT_ENABLE_SAMPLE_ACCUMULATION;
150
151
152
153
154
155 protected long accelerometerTimestampNanos = -1;
156
157
158
159
160
161 protected long gyroscopeTimestampNanos = -1;
162
163
164
165
166 protected int accumulatedAccelerometerSamples = 0;
167
168
169
170
171 protected int accumulatedGyroscopeSamples = 0;
172
173
174
175
176
177 protected double accumulatedAccelerationSampleX;
178
179
180
181
182
183 protected double accumulatedAccelerationSampleY;
184
185
186
187
188
189 protected double accumulatedAccelerationSampleZ;
190
191
192
193
194
195 protected double accumulatedAngularSpeedSampleX;
196
197
198
199
200
201 protected double accumulatedAngularSpeedSampleY;
202
203
204
205
206
207 protected double accumulatedAngularSpeedSampleZ;
208
209
210
211
212 protected BaseSlamCalibratorListener<D> listener;
213
214
215
216
217
218
219
220
221 protected BaseSlamCalibrator(final int sampleLength) {
222 if (sampleLength < MIN_SAMPLE_LENGTH) {
223 throw new IllegalArgumentException("length must be greater than 0");
224 }
225
226 this.sampleLength = sampleLength;
227 sample = new double[sampleLength];
228 previousMean = new double[sampleLength];
229 meanDiff = new double[sampleLength];
230 try {
231 previousCovariance = new Matrix(sampleLength, sampleLength);
232 covDiff = new Matrix(sampleLength, sampleLength);
233 estimator = new MeasurementNoiseCovarianceEstimator(sampleLength);
234 } catch (final Exception ignore) {
235
236 }
237 }
238
239
240
241
242
243
244
245
246 public int getSampleLength() {
247 return sampleLength;
248 }
249
250
251
252
253
254
255 public boolean isConverged() {
256 return converged;
257 }
258
259
260
261
262
263
264 public boolean isFailed() {
265 return failed;
266 }
267
268
269
270
271
272
273 public boolean isFinished() {
274 return finished;
275 }
276
277
278
279
280
281
282 public int getSampleCount() {
283 return sampleCount;
284 }
285
286
287
288
289
290
291
292
293 public int getMinNumSamples() {
294 return minNumSamples;
295 }
296
297
298
299
300
301
302
303
304
305 public void setMinNumSamples(final int minNumSamples) {
306 if (minNumSamples < 0) {
307 throw new IllegalArgumentException("minNumSamples must be positive");
308 }
309 this.minNumSamples = minNumSamples;
310 }
311
312
313
314
315
316
317 public int getMaxNumSamples() {
318 return maxNumSamples;
319 }
320
321
322
323
324
325
326
327 public void setMaxNumSamples(final int maxNumSamples) {
328 if (maxNumSamples <= 0) {
329 throw new IllegalArgumentException("maxNumSamples must be positive");
330 }
331 this.maxNumSamples = maxNumSamples;
332 }
333
334
335
336
337
338
339 public double getConvergenceThreshold() {
340 return convergenceThreshold;
341 }
342
343
344
345
346
347
348
349
350 public void setConvergenceThreshold(final double convergenceThreshold) {
351 if (convergenceThreshold < 0.0) {
352 throw new IllegalArgumentException("convergenceThreshold must be positive");
353 }
354 this.convergenceThreshold = convergenceThreshold;
355 }
356
357
358
359
360 public void reset() {
361 converged = failed = false;
362 sampleCount = 0;
363 finished = false;
364 Arrays.fill(sample, 0.0);
365 Arrays.fill(previousMean, 0.0);
366 previousCovariance.initialize(0.0);
367 try {
368 estimator = new MeasurementNoiseCovarianceEstimator(sample.length);
369 } catch (final SignalProcessingException e) {
370
371 }
372 sampleCount = 0;
373 Arrays.fill(meanDiff, 0.0);
374 covDiff.initialize(0.0);
375 accelerometerTimestampNanos = gyroscopeTimestampNanos = -1;
376 accumulatedAccelerometerSamples = accumulatedGyroscopeSamples = 0;
377 accumulatedAccelerationSampleX = accumulatedAccelerationSampleY = accumulatedAccelerationSampleZ = 0.0;
378 accumulatedAngularSpeedSampleX = accumulatedAngularSpeedSampleY = accumulatedAngularSpeedSampleZ = 0.0;
379 }
380
381
382
383
384
385
386 public boolean isAccumulationEnabled() {
387 return accumulationEnabled;
388 }
389
390
391
392
393
394
395
396 public void setAccumulationEnabled(final boolean accumulationEnabled) {
397 this.accumulationEnabled = accumulationEnabled;
398 }
399
400
401
402
403
404
405
406
407 public long getAccelerometerTimestampNanos() {
408 return accelerometerTimestampNanos;
409 }
410
411
412
413
414
415
416
417
418 public long getGyroscopeTimestampNanos() {
419 return gyroscopeTimestampNanos;
420 }
421
422
423
424
425
426
427
428 public int getAccumulatedAccelerometerSamples() {
429 return accumulatedAccelerometerSamples;
430 }
431
432
433
434
435
436
437 public int getAccumulatedGyroscopeSamples() {
438 return accumulatedGyroscopeSamples;
439 }
440
441
442
443
444
445
446
447 public boolean isAccelerometerSampleReceived() {
448 return accumulatedAccelerometerSamples > 0;
449 }
450
451
452
453
454
455
456
457 public boolean isGyroscopeSampleReceived() {
458 return accumulatedGyroscopeSamples > 0;
459 }
460
461
462
463
464
465
466
467 public boolean isFullSampleAvailable() {
468 return isAccelerometerSampleReceived() && isGyroscopeSampleReceived();
469 }
470
471
472
473
474
475
476
477
478 public double getAccumulatedAccelerationSampleX() {
479 return accumulatedAccelerationSampleX;
480 }
481
482
483
484
485
486
487
488
489 public double getAccumulatedAccelerationSampleY() {
490 return accumulatedAccelerationSampleY;
491 }
492
493
494
495
496
497
498
499
500 public double getAccumulatedAccelerationSampleZ() {
501 return accumulatedAccelerationSampleZ;
502 }
503
504
505
506
507
508
509
510
511 public double[] getAccumulatedAccelerationSample() {
512 return new double[]{
513 accumulatedAccelerationSampleX,
514 accumulatedAccelerationSampleY,
515 accumulatedAccelerationSampleZ
516 };
517 }
518
519
520
521
522
523
524
525
526
527
528 public void getAccumulatedAccelerationSample(final double[] result) {
529 if (result.length != N_COMPONENTS_3D) {
530 throw new IllegalArgumentException("result must have length 3");
531 }
532 result[0] = accumulatedAccelerationSampleX;
533 result[1] = accumulatedAccelerationSampleY;
534 result[2] = accumulatedAccelerationSampleZ;
535 }
536
537
538
539
540
541
542
543
544 public double getAccumulatedAngularSpeedSampleX() {
545 return accumulatedAngularSpeedSampleX;
546 }
547
548
549
550
551
552
553
554
555 public double getAccumulatedAngularSpeedSampleY() {
556 return accumulatedAngularSpeedSampleY;
557 }
558
559
560
561
562
563
564
565
566 public double getAccumulatedAngularSpeedSampleZ() {
567 return accumulatedAngularSpeedSampleZ;
568 }
569
570
571
572
573
574
575
576
577 public double[] getAccumulatedAngularSpeedSample() {
578 return new double[]{
579 accumulatedAngularSpeedSampleX,
580 accumulatedAngularSpeedSampleY,
581 accumulatedAngularSpeedSampleZ
582 };
583 }
584
585
586
587
588
589
590
591
592
593
594 public void getAccumulatedAngularSpeedSample(final double[] result) {
595 if (result.length != N_COMPONENTS_3D) {
596 throw new IllegalArgumentException("result must have length 3");
597 }
598 result[0] = accumulatedAngularSpeedSampleX;
599 result[1] = accumulatedAngularSpeedSampleY;
600 result[2] = accumulatedAngularSpeedSampleZ;
601 }
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618 public void updateAccelerometerSample(
619 final long timestamp, final float accelerationX, final float accelerationY, final float accelerationZ) {
620 if (!isFullSampleAvailable()) {
621 accelerometerTimestampNanos = timestamp;
622 if (isAccumulationEnabled() && isAccelerometerSampleReceived()) {
623
624 final var nextSamples = accumulatedAccelerometerSamples + 1;
625 accumulatedAccelerationSampleX = (accumulatedAccelerationSampleX * accumulatedAccelerometerSamples
626 + accelerationX) / nextSamples;
627 accumulatedAccelerationSampleY = (accumulatedAccelerationSampleY * accumulatedAccelerometerSamples
628 + accelerationY) / nextSamples;
629 accumulatedAccelerationSampleZ = (accumulatedAccelerationSampleZ * accumulatedAccelerometerSamples
630 + accelerationZ) / nextSamples;
631 accumulatedAccelerometerSamples = nextSamples;
632 } else {
633
634 accumulatedAccelerationSampleX = accelerationX;
635 accumulatedAccelerationSampleY = accelerationY;
636 accumulatedAccelerationSampleZ = accelerationZ;
637 accumulatedAccelerometerSamples++;
638 }
639 notifyFullSampleAndResetSampleReceive();
640 }
641 }
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657 public void updateAccelerometerSample(final long timestamp, final float[] data) {
658 if (data.length != N_COMPONENTS_3D) {
659 throw new IllegalArgumentException("acceleration must have length 3");
660 }
661 updateAccelerometerSample(timestamp, data[0], data[1], data[2]);
662 }
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680 public void updateGyroscopeSample(
681 final long timestamp, final float angularSpeedX, final float angularSpeedY, final float angularSpeedZ) {
682 if (!isFullSampleAvailable()) {
683 gyroscopeTimestampNanos = timestamp;
684 if (isAccumulationEnabled() && isGyroscopeSampleReceived()) {
685
686 final var nextSamples = accumulatedGyroscopeSamples + 1;
687 accumulatedAngularSpeedSampleX = (accumulatedAngularSpeedSampleX * accumulatedGyroscopeSamples
688 + angularSpeedX) / nextSamples;
689 accumulatedAngularSpeedSampleY = (accumulatedAngularSpeedSampleY * accumulatedGyroscopeSamples
690 + angularSpeedY) / nextSamples;
691 accumulatedAngularSpeedSampleZ = (accumulatedAngularSpeedSampleZ * accumulatedGyroscopeSamples
692 + angularSpeedZ) / nextSamples;
693 accumulatedGyroscopeSamples = nextSamples;
694 } else {
695
696 accumulatedAngularSpeedSampleX = angularSpeedX;
697 accumulatedAngularSpeedSampleY = angularSpeedY;
698 accumulatedAngularSpeedSampleZ = angularSpeedZ;
699 accumulatedGyroscopeSamples++;
700 }
701 notifyFullSampleAndResetSampleReceive();
702 }
703 }
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719 public void updateGyroscopeSample(final long timestamp, final float[] data) {
720 if (data.length != N_COMPONENTS_3D) {
721 throw new IllegalArgumentException("angular speed must have length 3");
722 }
723 updateGyroscopeSample(timestamp, data[0], data[1], data[2]);
724 }
725
726
727
728
729
730
731
732 public long getMostRecentTimestampNanos() {
733 return Math.max(accelerometerTimestampNanos, gyroscopeTimestampNanos);
734 }
735
736
737
738
739
740
741
742
743 public BaseSlamCalibratorListener<D> getListener() {
744 return listener;
745 }
746
747
748
749
750
751
752
753
754 public void setListener(final BaseSlamCalibratorListener<D> listener) {
755 this.listener = listener;
756 }
757
758
759
760
761
762
763
764
765 public double[] getControlMean() {
766 return estimator.getSampleAverage();
767 }
768
769
770
771
772
773
774
775
776
777
778 public void getControlMean(final double[] result) {
779 final var src = getControlMean();
780 if (result.length != src.length) {
781 throw new IllegalArgumentException("wrong length");
782 }
783
784 System.arraycopy(src, 0, result, 0, src.length);
785 }
786
787
788
789
790
791
792
793
794 public Matrix getControlCovariance() {
795 return estimator.getMeasurementNoiseCov();
796 }
797
798
799
800
801
802
803
804
805 public void getControlCovariance(final Matrix result) {
806 final var src = getControlCovariance();
807 src.copyTo(result);
808 }
809
810
811
812
813
814
815
816
817
818
819 public MultivariateNormalDist getControlDistribution() throws InvalidCovarianceMatrixException {
820 final var cov = getControlCovariance();
821 try {
822 cov.symmetrize();
823 } catch (final WrongSizeException ignore) {
824
825 }
826 return new MultivariateNormalDist(getControlMean(), cov, false);
827 }
828
829
830
831
832
833
834
835
836
837
838 public void getControlDistribution(final MultivariateNormalDist dist) throws InvalidCovarianceMatrixException {
839 final var cov = getControlCovariance();
840 try {
841 cov.symmetrize();
842 } catch (final WrongSizeException ignore) {
843
844 }
845 dist.setMeanAndCovariance(getControlMean(), cov, false);
846 }
847
848
849
850
851
852
853
854 public abstract D getCalibrationData();
855
856
857
858
859
860
861 public void getCalibrationData(final D result) {
862 result.setControlMeanAndCovariance(getControlMean(), getControlCovariance());
863 }
864
865
866
867
868
869
870
871
872
873
874
875
876 public MultivariateNormalDist propagateWithControlJacobian(final Matrix controlJacobian)
877 throws InvalidCovarianceMatrixException {
878 return getCalibrationData().propagateWithControlJacobian(controlJacobian);
879 }
880
881
882
883
884
885
886
887
888
889
890
891
892 public void propagateWithControlJacobian(final Matrix controlJacobian, final MultivariateNormalDist result)
893 throws InvalidCovarianceMatrixException {
894 getCalibrationData().propagateWithControlJacobian(controlJacobian, result);
895 }
896
897
898
899
900
901 protected void notifyFullSampleAndResetSampleReceive() {
902 if (isFullSampleAvailable()) {
903 processFullSample();
904 accumulatedAccelerometerSamples = accumulatedGyroscopeSamples = 0;
905 }
906 }
907
908
909
910
911
912
913 protected abstract int getEstimatorStateLength();
914
915
916
917
918 protected abstract void processFullSample();
919
920
921
922
923
924 protected void updateSample() {
925 if (finished) {
926 return;
927 }
928
929 try {
930 estimator.update(sample);
931 } catch (final SignalProcessingException e) {
932 failed = finished = true;
933
934 if (listener != null) {
935 listener.onCalibratorFinished(this, converged, true);
936 }
937 return;
938 }
939
940 sampleCount++;
941
942 final var mean = estimator.getSampleAverage();
943 final var cov = estimator.getMeasurementNoiseCov();
944
945
946 if (sampleCount >= maxNumSamples) {
947 finished = true;
948
949 if (listener != null) {
950 listener.onCalibratorFinished(this, converged, failed);
951 }
952 return;
953 }
954
955
956 if (sampleCount >= minNumSamples) {
957 ArrayUtils.subtract(mean, previousMean, meanDiff);
958 try {
959 cov.subtract(previousCovariance, covDiff);
960 } catch (final WrongSizeException ignore) {
961
962 }
963 final var meanDiffNorm = com.irurueta.algebra.Utils.normF(meanDiff);
964 final var covDiffNorm = com.irurueta.algebra.Utils.normF(covDiff);
965 if (meanDiffNorm <= convergenceThreshold && covDiffNorm <= convergenceThreshold) {
966 converged = finished = true;
967
968 if (listener != null) {
969 listener.onCalibratorFinished(this, true, failed);
970 }
971 return;
972 }
973 }
974
975
976 System.arraycopy(mean, 0, previousMean, 0, mean.length);
977 cov.copyTo(previousCovariance);
978
979 finished = false;
980 }
981
982
983
984
985 public interface BaseSlamCalibratorListener<D extends BaseCalibrationData> {
986
987
988
989
990
991
992 void onFullSampleReceived(final BaseSlamCalibrator<D> calibrator);
993
994
995
996
997
998
999
1000 void onFullSampleProcessed(final BaseSlamCalibrator<D> calibrator);
1001
1002
1003
1004
1005
1006
1007
1008
1009 void onCalibratorFinished(
1010 final BaseSlamCalibrator<D> calibrator, final boolean converged, final boolean failed);
1011 }
1012 }