Skip to content

Commit 5062aa2

Browse files
committed
chore: move function getEllipse to a separate folder
1 parent dad528b commit 5062aa2

File tree

2 files changed

+123
-361
lines changed

2 files changed

+123
-361
lines changed

src/roi/Roi.ts

Lines changed: 2 additions & 361 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import { EigenvalueDecomposition } from 'ml-matrix';
2-
import { xVariance, xyCovariance } from 'ml-spectra-processing';
3-
41
import { Mask } from '../Mask';
52
import {
63
GetBorderPointsOptions,
@@ -9,25 +6,14 @@ import {
96
getConvexHull,
107
getMbr,
118
Mbr,
12-
FeretDiameter,
139
} from '../maskAnalysis';
14-
import { getAngle } from '../maskAnalysis/utils/getAngle';
15-
import { toDegrees } from '../utils/geometry/angles';
1610
import { Point } from '../utils/geometry/points';
1711

1812
import { RoiMap } from './RoiMapManager';
1913
import { getBorderPoints } from './getBorderPoints';
2014
import { getMask, GetMaskOptions } from './getMask';
15+
import { Ellipse, getEllipse } from './properties/getEllipse';
2116

22-
interface Ellipse {
23-
center: {
24-
column: number;
25-
row: number;
26-
};
27-
majorAxis: FeretDiameter;
28-
minorAxis: FeretDiameter;
29-
surface: number;
30-
}
3117
interface Border {
3218
connectedID: number; // refers to the roiID of the contiguous ROI
3319
length: number;
@@ -635,352 +621,7 @@ export class Roi {
635621
* @param x
636622
*/
637623
computeIndex(y: number, x: number): number {
638-
const roiMap = this.getMap();
624+
const roiMap = this.map;
639625
return (y + this.origin.row) * roiMap.width + x + this.origin.column;
640626
}
641627
}
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

Comments
 (0)