Skip to content

Commit

Permalink
feat!: refactor points and add absolute coords calculation (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
EscapedGibbon authored Jun 14, 2024
1 parent acd6fd2 commit ad9f91d
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 48 deletions.
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

0 comments on commit ad9f91d

Please sign in to comment.