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
54 changes: 38 additions & 16 deletions src/roi/Roi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import { getMask, GetMaskOptions } from './getMask';
import { getEllipse } from './properties/getEllipse';
import { Border, Ellipse } from './roi.types';

export interface PointsOptions {
/**
* Kind of coordinates that will be calculated and cached.
* For each kind the cache is different as well.
EscapedGibbon marked this conversation as resolved.
Show resolved Hide resolved
*/
kind: 'relative' | 'absolute';
}
/**
* Properties of borders of ROI.
*
Expand All @@ -23,7 +30,8 @@ interface Computed {
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 @@ -292,25 +300,17 @@ 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.
* @param options - Points options.
* @returns Array of points with relative or absolute 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]);
}
}
}
points(options: PointsOptions) {
return this.#getComputed(`${options.kind}Points`, () => {
const absolute = options.kind === 'absolute';
const points = Array.from(this.#points(absolute));
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 +631,26 @@ export class Roi {
const roiMap = this.map;
return (y + this.origin.row) * roiMap.width + x + this.origin.column;
}

// Generator function to find ROIs points.
*#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 };
}
}
}
}
}
}
61 changes: 43 additions & 18 deletions src/roi/__tests__/points.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ 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].points({ kind: 'relative' })).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 },
]);
});

Expand All @@ -31,14 +31,39 @@ test('points 2nd test', () => {
const roiMapManager = fromMask(mask);
const rois = roiMapManager.getRois();

expect(rois[0].points).toStrictEqual([
[2, 0],
[1, 1],
[2, 1],
[0, 2],
[1, 2],
[2, 2],
[3, 2],
[2, 3],
expect(rois[0].points({ kind: 'relative' })).toStrictEqual([
{ column: 2, row: 0 },
{ column: 1, row: 1 },
{ column: 2, row: 1 },
{ column: 0, row: 2 },
{ column: 1, row: 2 },
{ column: 2, row: 2 },
{ column: 3, row: 2 },
{ column: 2, row: 3 },
]);
});

test('points 3rd test with absolute values', () => {
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].points({ kind: 'absolute' })).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
.points({ kind: 'relative' })
.map((point: Point) => point.column - xCenter);
const yCentered = roi
.points({ kind: 'relative' })
.map((point: Point) => point.row - yCenter);

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