1
- import { EigenvalueDecomposition } from 'ml-matrix' ;
2
- import { xVariance , xyCovariance } from 'ml-spectra-processing' ;
3
-
4
1
import { Mask } from '../Mask' ;
5
2
import {
6
3
GetBorderPointsOptions ,
@@ -9,25 +6,14 @@ import {
9
6
getConvexHull ,
10
7
getMbr ,
11
8
Mbr ,
12
- FeretDiameter ,
13
9
} from '../maskAnalysis' ;
14
- import { getAngle } from '../maskAnalysis/utils/getAngle' ;
15
- import { toDegrees } from '../utils/geometry/angles' ;
16
10
import { Point } from '../utils/geometry/points' ;
17
11
18
12
import { RoiMap } from './RoiMapManager' ;
19
13
import { getBorderPoints } from './getBorderPoints' ;
20
14
import { getMask , GetMaskOptions } from './getMask' ;
15
+ import { Ellipse , getEllipse } from './properties/getEllipse' ;
21
16
22
- interface Ellipse {
23
- center : {
24
- column : number ;
25
- row : number ;
26
- } ;
27
- majorAxis : FeretDiameter ;
28
- minorAxis : FeretDiameter ;
29
- surface : number ;
30
- }
31
17
interface Border {
32
18
connectedID : number ; // refers to the roiID of the contiguous ROI
33
19
length : number ;
@@ -635,352 +621,7 @@ export class Roi {
635
621
* @param x
636
622
*/
637
623
computeIndex ( y : number , x : number ) : number {
638
- const roiMap = this . getMap ( ) ;
624
+ const roiMap = this . map ;
639
625
return ( y + this . origin . row ) * roiMap . width + x + this . origin . column ;
640
626
}
641
627
}
642
-
643
-
644
- /**
645
- *
646
- * @param roi -ROI
647
- * @returns object which tells how many pixels are exposed externally to how many sides
648
- */
649
- function getPerimeterInfo ( roi : Roi ) {
650
- const roiMap = roi . getMap ( ) ;
651
- const data = roiMap . data ;
652
- let one = 0 ;
653
- let two = 0 ;
654
- let three = 0 ;
655
- let four = 0 ;
656
- let externalIDs = roi . externalBorders . map ( ( element ) => element . connectedID ) ;
657
- for ( let column = 0 ; column < roi . width ; column ++ ) {
658
- for ( let row = 0 ; row < roi . height ; row ++ ) {
659
- let target = roi . computeIndex ( row , column ) ;
660
- if ( data [ target ] === roi . id ) {
661
- let nbAround = 0 ;
662
- if ( column === 0 ) {
663
- nbAround ++ ;
664
- } else if ( externalIDs . includes ( data [ target - 1 ] ) ) {
665
- nbAround ++ ;
666
- }
667
-
668
- if ( column === roiMap . width - 1 ) {
669
- nbAround ++ ;
670
- } else if ( externalIDs . includes ( data [ target + 1 ] ) ) {
671
- nbAround ++ ;
672
- }
673
-
674
- if ( row === 0 ) {
675
- nbAround ++ ;
676
- } else if ( externalIDs . includes ( data [ target - roiMap . width ] ) ) {
677
- nbAround ++ ;
678
- }
679
-
680
- if ( row === roiMap . height - 1 ) {
681
- nbAround ++ ;
682
- } else if ( externalIDs . includes ( data [ target + roiMap . width ] ) ) {
683
- nbAround ++ ;
684
- }
685
- switch ( nbAround ) {
686
- case 1 :
687
- one ++ ;
688
- break ;
689
- case 2 :
690
- two ++ ;
691
- break ;
692
- case 3 :
693
- three ++ ;
694
- break ;
695
- case 4 :
696
- four ++ ;
697
- break ;
698
- default :
699
- }
700
- }
701
- }
702
- }
703
- return { one, two, three, four } ;
704
- }
705
-
706
- /**
707
- *
708
- * @param roi - ROI
709
- * @returns the surface of holes in ROI
710
- */
711
- function getHolesInfo ( roi : Roi ) {
712
- let surface = 0 ;
713
- const data = roi . getMap ( ) . data ;
714
- for ( let column = 1 ; column < roi . width - 1 ; column ++ ) {
715
- for ( let row = 1 ; row < roi . height - 1 ; row ++ ) {
716
- let target = roi . computeIndex ( row , column ) ;
717
- if ( roi . internalIDs . includes ( data [ target ] ) && data [ target ] !== roi . id ) {
718
- surface ++ ;
719
- }
720
- }
721
- }
722
- return {
723
- number : roi . internalIDs . length - 1 ,
724
- surface,
725
- } ;
726
- }
727
- /**
728
- * Calculates internal IDs of the ROI
729
- *
730
- * @param roi
731
- * @returns internalIDs
732
- */
733
- function getInternalIDs ( roi : Roi ) {
734
- let internal = [ roi . id ] ;
735
- let roiMap = roi . getMap ( ) ;
736
- let data = roiMap . data ;
737
-
738
- if ( roi . height > 2 ) {
739
- for ( let column = 0 ; column < roi . width ; column ++ ) {
740
- let target = roi . computeIndex ( 0 , column ) ;
741
- if ( internal . includes ( data [ target ] ) ) {
742
- let id = data [ target + roiMap . width ] ;
743
- if ( ! internal . includes ( id ) && ! roi . boxIDs . includes ( id ) ) {
744
- internal . push ( id ) ;
745
- }
746
- }
747
- }
748
- }
749
-
750
- let array = new Array ( 4 ) ;
751
- for ( let column = 1 ; column < roi . width - 1 ; column ++ ) {
752
- for ( let row = 1 ; row < roi . height - 1 ; row ++ ) {
753
- let target = roi . computeIndex ( row , column ) ;
754
- if ( internal . includes ( data [ target ] ) ) {
755
- // we check if one of the neighbour is not yet in
756
-
757
- array [ 0 ] = data [ target - 1 ] ;
758
- array [ 1 ] = data [ target + 1 ] ;
759
- array [ 2 ] = data [ target - roiMap . width ] ;
760
- array [ 3 ] = data [ target + roiMap . width ] ;
761
-
762
- for ( let i = 0 ; i < 4 ; i ++ ) {
763
- let id = array [ i ] ;
764
- if ( ! internal . includes ( id ) && ! roi . boxIDs . includes ( id ) ) {
765
- internal . push ( id ) ;
766
- }
767
- }
768
- }
769
- }
770
- }
771
-
772
- return internal ;
773
- }
774
-
775
- function getBoxIDs ( roi : Roi ) : number [ ] {
776
- let surroundingIDs = new Set < number > ( ) ; // allows to get a unique list without indexOf
777
-
778
- const roiMap = roi . getMap ( ) ;
779
- const data = roiMap . data ;
780
-
781
- // we check the first line and the last line
782
- for ( let row of [ 0 , roi . height - 1 ] ) {
783
- for ( let column = 0 ; column < roi . width ; column ++ ) {
784
- let target = roi . computeIndex ( row , column ) ;
785
- if (
786
- column - roi . origin . column > 0 &&
787
- data [ target ] === roi . id &&
788
- data [ target - 1 ] !== roi . id
789
- ) {
790
- let value = data [ target - 1 ] ;
791
- surroundingIDs . add ( value ) ;
792
- }
793
- if (
794
- roiMap . width - column - roi . origin . column > 1 &&
795
- data [ target ] === roi . id &&
796
- data [ target + 1 ] !== roi . id
797
- ) {
798
- let value = data [ target + 1 ] ;
799
- surroundingIDs . add ( value ) ;
800
- }
801
- }
802
- }
803
-
804
- // we check the first column and the last column
805
- for ( let column of [ 0 , roi . width - 1 ] ) {
806
- for ( let row = 0 ; row < roi . height ; row ++ ) {
807
- let target = roi . computeIndex ( row , column ) ;
808
- if (
809
- row - roi . origin . row > 0 &&
810
- data [ target ] === roi . id &&
811
- data [ target - roiMap . width ] !== roi . id
812
- ) {
813
- let value = data [ target - roiMap . width ] ;
814
- surroundingIDs . add ( value ) ;
815
- }
816
- if (
817
- roiMap . height - row - roi . origin . row > 1 &&
818
- data [ target ] === roi . id &&
819
- data [ target + roiMap . width ] !== roi . id
820
- ) {
821
- let value = data [ target + roiMap . width ] ;
822
- surroundingIDs . add ( value ) ;
823
- }
824
- }
825
- }
826
-
827
- return Array . from ( surroundingIDs ) ; // the selection takes the whole rectangle
828
- }
829
-
830
- /**
831
- *
832
- * @param roi - ROI
833
- * @returns borders' length and their IDs
834
- */
835
- function getBorders ( roi : Roi ) : Border [ ] {
836
- const roiMap = roi . getMap ( ) ;
837
- const data = roiMap . data ;
838
- let surroudingIDs = new Set < number > ( ) ; // allows to get a unique list without indexOf
839
- let surroundingBorders = new Map ( ) ;
840
- let visitedData = new Set ( ) ;
841
- let dx = [ + 1 , 0 , - 1 , 0 ] ;
842
- let dy = [ 0 , + 1 , 0 , - 1 ] ;
843
-
844
- for (
845
- let column = roi . origin . column ;
846
- column <= roi . origin . column + roi . width ;
847
- column ++
848
- ) {
849
- for ( let row = roi . origin . row ; row <= roi . origin . row + roi . height ; row ++ ) {
850
- let target = column + row * roiMap . width ;
851
- if ( data [ target ] === roi . id ) {
852
- for ( let dir = 0 ; dir < 4 ; dir ++ ) {
853
- let newX = column + dx [ dir ] ;
854
- let newY = row + dy [ dir ] ;
855
- if (
856
- newX >= 0 &&
857
- newY >= 0 &&
858
- newX < roiMap . width &&
859
- newY < roiMap . height
860
- ) {
861
- let neighbour = newX + newY * roiMap . width ;
862
-
863
- if ( data [ neighbour ] !== roi . id && ! visitedData . has ( neighbour ) ) {
864
- visitedData . add ( neighbour ) ;
865
- surroudingIDs . add ( data [ neighbour ] ) ;
866
- let surroundingBorder = surroundingBorders . get ( data [ neighbour ] ) ;
867
- if ( ! surroundingBorder ) {
868
- surroundingBorders . set ( data [ neighbour ] , 1 ) ;
869
- } else {
870
- surroundingBorders . set ( data [ neighbour ] , ++ surroundingBorder ) ;
871
- }
872
- }
873
- }
874
- }
875
- }
876
- }
877
- }
878
- let id : number [ ] = Array . from ( surroudingIDs ) ;
879
- return id . map ( ( id ) => {
880
- return {
881
- connectedID : id ,
882
- length : surroundingBorders . get ( id ) ,
883
- } ;
884
- } ) ;
885
- }
886
-
887
- function getEllipse ( roi : Roi , scale : number ) : Ellipse {
888
- const nbSD = 2 ;
889
-
890
- let xCenter = roi . centroid . column ;
891
- let yCenter = roi . centroid . row ;
892
-
893
- let xCentered = roi . points . map ( ( point : number [ ] ) => point [ 0 ] - xCenter ) ;
894
- let yCentered = roi . points . map ( ( point : number [ ] ) => point [ 1 ] - yCenter ) ;
895
-
896
- let centeredXVariance = xVariance ( xCentered , { unbiased : false } ) ;
897
- let centeredYVariance = xVariance ( yCentered , { unbiased : false } ) ;
898
-
899
- let centeredCovariance = xyCovariance (
900
- {
901
- x : xCentered ,
902
- y : yCentered ,
903
- } ,
904
- { unbiased : false } ,
905
- ) ;
906
-
907
- //spectral decomposition of the sample covariance matrix
908
- let sampleCovarianceMatrix = [
909
- [ centeredXVariance , centeredCovariance ] ,
910
- [ centeredCovariance , centeredYVariance ] ,
911
- ] ;
912
- let e = new EigenvalueDecomposition ( sampleCovarianceMatrix ) ;
913
- let eigenvalues = e . realEigenvalues ;
914
- let vectors = e . eigenvectorMatrix ;
915
-
916
- let radiusMajor : number ;
917
- let radiusMinor : number ;
918
- let vectorMajor : number [ ] ;
919
- let vectorMinor : number [ ] ;
920
-
921
- if ( eigenvalues [ 0 ] > eigenvalues [ 1 ] ) {
922
- radiusMajor = Math . sqrt ( eigenvalues [ 0 ] * nbSD ) ;
923
- radiusMinor = Math . sqrt ( eigenvalues [ 1 ] * nbSD ) ;
924
- vectorMajor = vectors . getColumn ( 0 ) ;
925
- vectorMinor = vectors . getColumn ( 1 ) ;
926
- } else if ( eigenvalues [ 0 ] < eigenvalues [ 1 ] ) {
927
- radiusMajor = Math . sqrt ( eigenvalues [ 1 ] * nbSD ) ;
928
- radiusMinor = Math . sqrt ( eigenvalues [ 0 ] * nbSD ) ;
929
- vectorMajor = vectors . getColumn ( 1 ) ;
930
- vectorMinor = vectors . getColumn ( 0 ) ;
931
- } else {
932
- // order here does not matter
933
- radiusMajor = Math . sqrt ( eigenvalues [ 1 ] * nbSD ) ;
934
- radiusMinor = Math . sqrt ( eigenvalues [ 0 ] * nbSD ) ;
935
- vectorMajor = vectors . getColumn ( 1 ) ;
936
- vectorMinor = vectors . getColumn ( 0 ) ;
937
- }
938
-
939
- radiusMajor *= scale ;
940
- radiusMinor *= scale ;
941
- let majorAxisPoint1 = {
942
- column : xCenter + radiusMajor * vectorMajor [ 0 ] ,
943
- row : yCenter + radiusMajor * vectorMajor [ 1 ] ,
944
- } ;
945
- let majorAxisPoint2 = {
946
- column : xCenter - radiusMajor * vectorMajor [ 0 ] ,
947
- row : yCenter - radiusMajor * vectorMajor [ 1 ] ,
948
- } ;
949
- let minorAxisPoint1 = {
950
- column : xCenter + radiusMinor * vectorMinor [ 0 ] ,
951
- row : yCenter + radiusMinor * vectorMinor [ 1 ] ,
952
- } ;
953
- let minorAxisPoint2 = {
954
- column : xCenter - radiusMinor * vectorMinor [ 0 ] ,
955
- row : yCenter - radiusMinor * vectorMinor [ 1 ] ,
956
- } ;
957
-
958
- const majorLength = Math . sqrt (
959
- ( majorAxisPoint1 . column - majorAxisPoint2 . column ) ** 2 +
960
- ( majorAxisPoint1 . row - majorAxisPoint2 . row ) ** 2 ,
961
- ) ;
962
- const minorLength = Math . sqrt (
963
- ( minorAxisPoint1 . column - majorAxisPoint2 . column ) ** 2 +
964
- ( minorAxisPoint1 . row - minorAxisPoint2 . row ) ** 2 ,
965
- ) ;
966
-
967
- let ellipseSurface = ( ( ( minorLength / 2 ) * majorLength ) / 2 ) * Math . PI ;
968
- return {
969
- center : {
970
- column : xCenter ,
971
- row : yCenter ,
972
- } ,
973
- majorAxis : {
974
- points : [ majorAxisPoint1 , majorAxisPoint2 ] ,
975
- length : majorLength ,
976
- angle : toDegrees ( getAngle ( majorAxisPoint1 , majorAxisPoint2 ) ) ,
977
- } ,
978
- minorAxis : {
979
- points : [ minorAxisPoint1 , minorAxisPoint1 ] ,
980
- length : minorLength ,
981
- angle : toDegrees ( getAngle ( minorAxisPoint1 , minorAxisPoint2 ) ) ,
982
- } ,
983
- surface : ellipseSurface ,
984
- } ;
985
- }
986
-
0 commit comments