Skip to content
This repository was archived by the owner on Jul 26, 2025. It is now read-only.

Commit ad9f91d

Browse files
feat!: refactor points and add absolute coords calculation (#472)
1 parent acd6fd2 commit ad9f91d

File tree

3 files changed

+122
-48
lines changed

3 files changed

+122
-48
lines changed

src/roi/Roi.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,15 @@ import { getMask, GetMaskOptions } from './getMask';
1111
import { getEllipse } from './properties/getEllipse';
1212
import { Border, Ellipse } from './roi.types';
1313

14-
/**
15-
* Properties of borders of ROI.
16-
*
17-
*/
18-
1914
interface Computed {
2015
perimeter: number;
2116
borders: Border[]; // external and internal ids which are not equal to the current roi ID
2217
perimeterInfo: { one: number; two: number; three: number; four: number };
2318
externalLengths: number[];
2419
borderLengths: number[];
2520
box: number;
26-
points: number[][];
21+
relativePoints: Point[];
22+
absolutePoints: Point[];
2723
holesInfo: { number: number; surface: number };
2824
boxIDs: number[];
2925
externalBorders: Border[];
@@ -291,26 +287,26 @@ export class Roi {
291287
);
292288
}
293289
/**
294-
* Computes current ROI points.
295-
* @returns Array of points. It's an array of tuples, each tuple being the x and y coordinates of the ROI point.
290+
* Computes ROI points relative to ROIs point of `origin`.
291+
* @returns Array of points with relative ROI coordinates.
296292
*/
297-
get points() {
298-
return this.#getComputed('points', () => {
299-
const points = [];
300-
for (let row = 0; row < this.height; row++) {
301-
for (let column = 0; column < this.width; column++) {
302-
const target =
303-
(row + this.origin.row) * this.map.width +
304-
column +
305-
this.origin.column;
306-
if (this.map.data[target] === this.id) {
307-
points.push([column, row]);
308-
}
309-
}
310-
}
293+
get relativePoints() {
294+
return this.#getComputed(`relativePoints`, () => {
295+
const points = Array.from(this.points(false));
296+
return points;
297+
});
298+
}
299+
/**
300+
* Computes ROI points relative to Image's/Mask's point of `origin`.
301+
* @returns Array of points with absolute ROI coordinates.
302+
*/
303+
get absolutePoints() {
304+
return this.#getComputed(`absolutePoints`, () => {
305+
const points = Array.from(this.points(true));
311306
return points;
312307
});
313308
}
309+
314310
get boxIDs() {
315311
return this.#getComputed('boxIDs', () => {
316312
const surroundingIDs = new Set<number>(); // Allows to get a unique list without indexOf.
@@ -631,4 +627,30 @@ export class Roi {
631627
const roiMap = this.map;
632628
return (y + this.origin.row) * roiMap.width + x + this.origin.column;
633629
}
630+
631+
/**
632+
* Generator function to calculate point's coordinates.
633+
* @param absolute - controls whether coordinates should be relative to ROI's point of `origin` (relative), or relative to ROI's position on the Image/Mask (absolute).
634+
* @yields Coordinates of each point of ROI.
635+
*/
636+
*points(absolute: boolean) {
637+
for (let row = 0; row < this.height; row++) {
638+
for (let column = 0; column < this.width; column++) {
639+
const target =
640+
(row + this.origin.row) * this.map.width +
641+
column +
642+
this.origin.column;
643+
if (this.map.data[target] === this.id) {
644+
if (absolute) {
645+
yield {
646+
column: this.origin.column + column,
647+
row: this.origin.row + row,
648+
};
649+
} else {
650+
yield { column, row };
651+
}
652+
}
653+
}
654+
}
655+
}
634656
}

src/roi/__tests__/points.test.ts

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,84 @@ test('points 1st test', () => {
99
]);
1010
const roiMapManager = fromMask(mask);
1111
const rois = roiMapManager.getRois();
12-
expect(rois[0].points).toStrictEqual([
13-
[0, 0],
14-
[1, 0],
15-
[0, 1],
16-
[1, 1],
17-
[0, 2],
18-
[1, 2],
19-
[0, 3],
20-
[1, 3],
12+
expect(rois[0].relativePoints).toStrictEqual([
13+
{ column: 0, row: 0 },
14+
{ column: 1, row: 0 },
15+
{ column: 0, row: 1 },
16+
{ column: 1, row: 1 },
17+
{ column: 0, row: 2 },
18+
{ column: 1, row: 2 },
19+
{ column: 0, row: 3 },
20+
{ column: 1, row: 3 },
2121
]);
2222
});
23-
24-
test('points 2nd test', () => {
23+
test('points 2nt test for absolute coordinates', () => {
2524
const mask = testUtils.createMask([
26-
[0, 0, 1, 0],
2725
[0, 1, 1, 0],
28-
[1, 1, 1, 1],
29-
[0, 0, 1, 0],
26+
[0, 1, 1, 0],
27+
[0, 1, 1, 0],
28+
[0, 1, 1, 0],
3029
]);
3130
const roiMapManager = fromMask(mask);
3231
const rois = roiMapManager.getRois();
32+
expect(rois[0].absolutePoints).toStrictEqual([
33+
{ column: 1, row: 0 },
34+
{ column: 2, row: 0 },
35+
{ column: 1, row: 1 },
36+
{ column: 2, row: 1 },
37+
{ column: 1, row: 2 },
38+
{ column: 2, row: 2 },
39+
{ column: 1, row: 3 },
40+
{ column: 2, row: 3 },
41+
]);
42+
});
3343

34-
expect(rois[0].points).toStrictEqual([
35-
[2, 0],
36-
[1, 1],
37-
[2, 1],
38-
[0, 2],
39-
[1, 2],
40-
[2, 2],
41-
[3, 2],
42-
[2, 3],
44+
test('points 3rd test for relative coordinates', () => {
45+
const mask = testUtils.createMask([
46+
[0, 0, 0, 1, 1, 1],
47+
[0, 1, 0, 1, 0, 1],
48+
[0, 1, 0, 1, 0, 1],
49+
[1, 1, 0, 1, 1, 1],
50+
[0, 0, 0, 0, 0, 0],
51+
[0, 0, 0, 0, 0, 0],
52+
]);
53+
const roiMapManager = fromMask(mask);
54+
const rois = roiMapManager.getRois();
55+
expect(rois[1].relativePoints).toStrictEqual([
56+
{ column: 0, row: 0 },
57+
{ column: 1, row: 0 },
58+
{ column: 2, row: 0 },
59+
{ column: 0, row: 1 },
60+
{ column: 2, row: 1 },
61+
{ column: 0, row: 2 },
62+
{ column: 2, row: 2 },
63+
{ column: 0, row: 3 },
64+
{ column: 1, row: 3 },
65+
{ column: 2, row: 3 },
66+
]);
67+
});
68+
69+
test('points 4th test for absolute coordinates', () => {
70+
const mask = testUtils.createMask([
71+
[0, 0, 0, 1, 1, 1],
72+
[0, 1, 0, 1, 0, 1],
73+
[0, 1, 0, 1, 0, 1],
74+
[1, 1, 0, 1, 1, 1],
75+
[0, 0, 0, 0, 0, 0],
76+
[0, 0, 0, 0, 0, 0],
77+
]);
78+
const roiMapManager = fromMask(mask);
79+
const rois = roiMapManager.getRois();
80+
expect(rois[1].absolutePoints).toStrictEqual([
81+
{ column: 3, row: 0 },
82+
{ column: 4, row: 0 },
83+
{ column: 5, row: 0 },
84+
{ column: 3, row: 1 },
85+
{ column: 5, row: 1 },
86+
{ column: 3, row: 2 },
87+
{ column: 5, row: 2 },
88+
{ column: 3, row: 3 },
89+
{ column: 4, row: 3 },
90+
{ column: 5, row: 3 },
4391
]);
4492
});

src/roi/properties/getEllipse.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { EigenvalueDecomposition } from 'ml-matrix';
22
import { xVariance, xyCovariance } from 'ml-spectra-processing';
33

4+
import { Point } from '../../geometry';
45
import { getAngle } from '../../maskAnalysis/utils/getAngle';
56
import { toDegrees } from '../../utils/geometry/angles';
67
import { assert } from '../../utils/validators/assert';
78
import { Roi } from '../Roi';
89
import { Ellipse } from '../roi.types';
9-
1010
/**
1111
* Calculates ellipse on around ROI.
1212
* @param roi - Region of interest.
@@ -16,8 +16,12 @@ export function getEllipse(roi: Roi): Ellipse {
1616
const xCenter = roi.centroid.column;
1717
const yCenter = roi.centroid.row;
1818

19-
const xCentered = roi.points.map((point: number[]) => point[0] - xCenter);
20-
const yCentered = roi.points.map((point: number[]) => point[1] - yCenter);
19+
const xCentered = roi.relativePoints.map(
20+
(point: Point) => point.column - xCenter,
21+
);
22+
const yCentered = roi.relativePoints.map(
23+
(point: Point) => point.row - yCenter,
24+
);
2125

2226
const centeredXVariance = xVariance(xCentered, { unbiased: false });
2327
const centeredYVariance = xVariance(yCentered, { unbiased: false });

0 commit comments

Comments
 (0)