From ad9f91d0ead5160c86e2b09d27082be7e9a29071 Mon Sep 17 00:00:00 2001 From: EscapedGibbon <101188881+EscapedGibbon@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:04:46 +0200 Subject: [PATCH] feat!: refactor points and add absolute coords calculation (#472) --- src/roi/Roi.ts | 66 ++++++++++++++-------- src/roi/__tests__/points.test.ts | 94 ++++++++++++++++++++++++-------- src/roi/properties/getEllipse.ts | 10 +++- 3 files changed, 122 insertions(+), 48 deletions(-) diff --git a/src/roi/Roi.ts b/src/roi/Roi.ts index 743766fcd..861411392 100644 --- a/src/roi/Roi.ts +++ b/src/roi/Roi.ts @@ -11,11 +11,6 @@ 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 @@ -23,7 +18,8 @@ interface Computed { externalLengths: number[]; borderLengths: number[]; box: number; - points: number[][]; + relativePoints: Point[]; + absolutePoints: Point[]; holesInfo: { number: number; surface: number }; boxIDs: number[]; externalBorders: Border[]; @@ -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(); // Allows to get a unique list without indexOf. @@ -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 }; + } + } + } + } + } } diff --git a/src/roi/__tests__/points.test.ts b/src/roi/__tests__/points.test.ts index 592ab0b8c..26ef2cdb4 100644 --- a/src/roi/__tests__/points.test.ts +++ b/src/roi/__tests__/points.test.ts @@ -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 }, ]); }); diff --git a/src/roi/properties/getEllipse.ts b/src/roi/properties/getEllipse.ts index a81df9244..cf30b3d8a 100644 --- a/src/roi/properties/getEllipse.ts +++ b/src/roi/properties/getEllipse.ts @@ -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. @@ -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 });