Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: refactor points and add absolute coords calculation #472

Merged
66 changes: 44 additions & 22 deletions src/roi/Roi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,15 @@ import { getMask, GetMaskOptions } from './getMask';
import { getEllipse } from './properties/getEllipse';
import { Border, Ellipse } from './roi.types';

/**
* Properties of borders of ROI.
*
*/

interface Computed {
perimeter: number;
borders: Border[]; // external and internal ids which are not equal to the current roi ID
perimeterInfo: { one: number; two: number; three: number; four: number };
externalLengths: number[];
borderLengths: number[];
box: number;
points: number[][];
relativePoints: Point[];
absolutePoints: Point[];
holesInfo: { number: number; surface: number };
boxIDs: number[];
externalBorders: Border[];
Expand Down Expand Up @@ -291,26 +287,26 @@ export class Roi {
);
}
/**
* Computes current ROI points.
* @returns Array of points. It's an array of tuples, each tuple being the x and y coordinates of the ROI point.
* Computes ROI points relative to ROIs point of `origin`.
* @returns Array of points with relative ROI coordinates.
*/
get points() {
return this.#getComputed('points', () => {
const points = [];
for (let row = 0; row < this.height; row++) {
for (let column = 0; column < this.width; column++) {
const target =
(row + this.origin.row) * this.map.width +
column +
this.origin.column;
if (this.map.data[target] === this.id) {
points.push([column, row]);
}
}
}
get relativePoints() {
return this.#getComputed(`relativePoints`, () => {
const points = Array.from(this.points(false));
return points;
});
}
/**
* Computes ROI points relative to Image's/Mask's point of `origin`.
* @returns Array of points with absolute ROI coordinates.
*/
get absolutePoints() {
return this.#getComputed(`absolutePoints`, () => {
const points = Array.from(this.points(true));
return points;
});
}

get boxIDs() {
return this.#getComputed('boxIDs', () => {
const surroundingIDs = new Set<number>(); // Allows to get a unique list without indexOf.
Expand Down Expand Up @@ -631,4 +627,30 @@ export class Roi {
const roiMap = this.map;
return (y + this.origin.row) * roiMap.width + x + this.origin.column;
}

/**
* Generator function to calculate point's coordinates.
* @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).
* @yields Coordinates of each point of ROI.
*/
*points(absolute: boolean) {
for (let row = 0; row < this.height; row++) {
for (let column = 0; column < this.width; column++) {
const target =
(row + this.origin.row) * this.map.width +
column +
this.origin.column;
if (this.map.data[target] === this.id) {
if (absolute) {
yield {
column: this.origin.column + column,
row: this.origin.row + row,
};
} else {
yield { column, row };
}
}
}
}
}
}
94 changes: 71 additions & 23 deletions src/roi/__tests__/points.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,84 @@ test('points 1st test', () => {
]);
const roiMapManager = fromMask(mask);
const rois = roiMapManager.getRois();
expect(rois[0].points).toStrictEqual([
[0, 0],
[1, 0],
[0, 1],
[1, 1],
[0, 2],
[1, 2],
[0, 3],
[1, 3],
expect(rois[0].relativePoints).toStrictEqual([
{ column: 0, row: 0 },
{ column: 1, row: 0 },
{ column: 0, row: 1 },
{ column: 1, row: 1 },
{ column: 0, row: 2 },
{ column: 1, row: 2 },
{ column: 0, row: 3 },
{ column: 1, row: 3 },
]);
});

test('points 2nd test', () => {
test('points 2nt test for absolute coordinates', () => {
const mask = testUtils.createMask([
[0, 0, 1, 0],
[0, 1, 1, 0],
[1, 1, 1, 1],
[0, 0, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
]);
const roiMapManager = fromMask(mask);
const rois = roiMapManager.getRois();
expect(rois[0].absolutePoints).toStrictEqual([
{ column: 1, row: 0 },
{ column: 2, row: 0 },
{ column: 1, row: 1 },
{ column: 2, row: 1 },
{ column: 1, row: 2 },
{ column: 2, row: 2 },
{ column: 1, row: 3 },
{ column: 2, row: 3 },
]);
});

expect(rois[0].points).toStrictEqual([
[2, 0],
[1, 1],
[2, 1],
[0, 2],
[1, 2],
[2, 2],
[3, 2],
[2, 3],
test('points 3rd test for relative coordinates', () => {
const mask = testUtils.createMask([
[0, 0, 0, 1, 1, 1],
[0, 1, 0, 1, 0, 1],
[0, 1, 0, 1, 0, 1],
[1, 1, 0, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
]);
const roiMapManager = fromMask(mask);
const rois = roiMapManager.getRois();
expect(rois[1].relativePoints).toStrictEqual([
{ column: 0, row: 0 },
{ column: 1, row: 0 },
{ column: 2, row: 0 },
{ column: 0, row: 1 },
{ column: 2, row: 1 },
{ column: 0, row: 2 },
{ column: 2, row: 2 },
{ column: 0, row: 3 },
{ column: 1, row: 3 },
{ column: 2, row: 3 },
]);
});

test('points 4th test for absolute coordinates', () => {
const mask = testUtils.createMask([
[0, 0, 0, 1, 1, 1],
[0, 1, 0, 1, 0, 1],
[0, 1, 0, 1, 0, 1],
[1, 1, 0, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
]);
const roiMapManager = fromMask(mask);
const rois = roiMapManager.getRois();
expect(rois[1].absolutePoints).toStrictEqual([
{ column: 3, row: 0 },
{ column: 4, row: 0 },
{ column: 5, row: 0 },
{ column: 3, row: 1 },
{ column: 5, row: 1 },
{ column: 3, row: 2 },
{ column: 5, row: 2 },
{ column: 3, row: 3 },
{ column: 4, row: 3 },
{ column: 5, row: 3 },
]);
});
10 changes: 7 additions & 3 deletions src/roi/properties/getEllipse.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { EigenvalueDecomposition } from 'ml-matrix';
import { xVariance, xyCovariance } from 'ml-spectra-processing';

import { Point } from '../../geometry';
import { getAngle } from '../../maskAnalysis/utils/getAngle';
import { toDegrees } from '../../utils/geometry/angles';
import { assert } from '../../utils/validators/assert';
import { Roi } from '../Roi';
import { Ellipse } from '../roi.types';

/**
* Calculates ellipse on around ROI.
* @param roi - Region of interest.
Expand All @@ -16,8 +16,12 @@ export function getEllipse(roi: Roi): Ellipse {
const xCenter = roi.centroid.column;
const yCenter = roi.centroid.row;

const xCentered = roi.points.map((point: number[]) => point[0] - xCenter);
const yCentered = roi.points.map((point: number[]) => point[1] - yCenter);
const xCentered = roi.relativePoints.map(
(point: Point) => point.column - xCenter,
);
const yCentered = roi.relativePoints.map(
(point: Point) => point.row - yCenter,
);

const centeredXVariance = xVariance(xCentered, { unbiased: false });
const centeredYVariance = xVariance(yCentered, { unbiased: false });
Expand Down
Loading