Skip to content

Commit

Permalink
colorMap (#390)
Browse files Browse the repository at this point in the history
* chore: change colorMap to have only the length of the number of ROIs

* fix: bug preventing over 32767 ROIs
  • Loading branch information
lpatiny committed Sep 8, 2023
1 parent bc5437a commit 6adde86
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 93 deletions.
13 changes: 8 additions & 5 deletions src/roi/__tests__/fromMask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ test('6x6 mask, allowCorners true', () => {
);
});

test('exceed max number of ROIs error', () => {
const size = 513;
test('no way to exceed max number of ROIs error', () => {
const size = 4097;
const mask = new Mask(size, size);
let pos = true;
for (let row = 0; row < size; row++) {
Expand All @@ -174,7 +174,10 @@ test('exceed max number of ROIs error', () => {
pos = !pos;
}
}
expect(() => {
fromMask(mask);
}).toThrow(/too many regions of interest/);

const roiMap = fromMask(mask);
//@ts-expect-error This is a test
expect(roiMap.map.nbNegative).toBe(8392704);
//@ts-expect-error This is a test
expect(roiMap.map.nbPositive).toBe(8392705);
});
3 changes: 1 addition & 2 deletions src/roi/colorRois.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Image } from '..';

import { RoiKind } from './getRois';
import { colorMapCenter } from './utils/constants';
import { getColorMap } from './utils/getColorMap';

import { RoiMapManager } from '.';
Expand Down Expand Up @@ -63,7 +62,7 @@ export function colorRois(
const data32 = new Uint32Array(image.getRawImage().data.buffer);

for (let index = 0; index < image.size; index++) {
data32[index] = colorMap[map.data[index] + colorMapCenter];
data32[index] = colorMap[map.data[index] + map.nbNegative];
}

return image;
Expand Down
67 changes: 33 additions & 34 deletions src/roi/fromMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Mask } from '..';
import { assert } from '../utils/validators/assert';

import { RoiMapManager } from './RoiMapManager';
import { maxNumberRois, maxRoiId } from './utils/constants';

export interface FromMaskOptions {
/**
Expand All @@ -24,10 +23,10 @@ export function fromMask(
): RoiMapManager {
const { allowCorners = false } = options;

const MAX_ARRAY = maxNumberRois - 1; // 65535 should be enough for most of the cases
const MAX_TODO_ARRAY_FILTER = 65535; // 65535 should be enough for most of the cases

const maxPositiveId = maxRoiId - 1;
const maxNegativeId = -maxRoiId;
const MAX_POSITIVE_ID = 2 ** 31 - 1;
const MAX_NEGATIVE_ID = -(2 ** 31 - 1);

// based on a binary image we will create plenty of small images
const data = new Int32Array(mask.size); // maxValue: maxPositiveId, minValue: maxNegativeId
Expand All @@ -36,8 +35,8 @@ export function fromMask(
let positiveId = 0;
let negativeId = 0;

const columnToProcess = new Uint16Array(maxNumberRois);
const rowToProcess = new Uint16Array(maxNumberRois);
const columnToProcess = new Uint16Array(MAX_TODO_ARRAY_FILTER + 1);
const rowToProcess = new Uint16Array(MAX_TODO_ARRAY_FILTER + 1);

for (let column = 0; column < mask.width; column++) {
for (let row = 0; row < mask.height; row++) {
Expand All @@ -55,14 +54,14 @@ export function fromMask(
const targetState = mask.getBit(column, row);
const id = targetState ? ++positiveId : --negativeId;
assert(
positiveId <= maxPositiveId && negativeId >= maxNegativeId,
positiveId <= MAX_POSITIVE_ID && negativeId >= MAX_NEGATIVE_ID,
'too many regions of interest',
);
columnToProcess[0] = column;
rowToProcess[0] = row;
while (from <= to) {
const currentColumn = columnToProcess[from & MAX_ARRAY];
const currentRow = rowToProcess[from & MAX_ARRAY];
const currentColumn = columnToProcess[from & MAX_TODO_ARRAY_FILTER];
const currentRow = rowToProcess[from & MAX_TODO_ARRAY_FILTER];
data[currentRow * mask.width + currentColumn] = id;
// need to check all around mask pixel
if (
Expand All @@ -72,9 +71,9 @@ export function fromMask(
) {
// LEFT
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn - 1;
rowToProcess[to & MAX_ARRAY] = currentRow;
data[currentRow * mask.width + currentColumn - 1] = maxNegativeId;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn - 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow;
data[currentRow * mask.width + currentColumn - 1] = MAX_NEGATIVE_ID;
}
if (
currentRow > 0 &&
Expand All @@ -83,9 +82,9 @@ export function fromMask(
) {
// TOP
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn;
rowToProcess[to & MAX_ARRAY] = currentRow - 1;
data[(currentRow - 1) * mask.width + currentColumn] = maxNegativeId;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow - 1;
data[(currentRow - 1) * mask.width + currentColumn] = MAX_NEGATIVE_ID;
}
if (
currentColumn < mask.width - 1 &&
Expand All @@ -94,9 +93,9 @@ export function fromMask(
) {
// RIGHT
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn + 1;
rowToProcess[to & MAX_ARRAY] = currentRow;
data[currentRow * mask.width + currentColumn + 1] = maxNegativeId;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn + 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow;
data[currentRow * mask.width + currentColumn + 1] = MAX_NEGATIVE_ID;
}
if (
currentRow < mask.height - 1 &&
Expand All @@ -105,9 +104,9 @@ export function fromMask(
) {
// BOTTOM
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn;
rowToProcess[to & MAX_ARRAY] = currentRow + 1;
data[(currentRow + 1) * mask.width + currentColumn] = maxNegativeId;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow + 1;
data[(currentRow + 1) * mask.width + currentColumn] = MAX_NEGATIVE_ID;
}
if (allowCorners) {
if (
Expand All @@ -118,10 +117,10 @@ export function fromMask(
) {
// TOP LEFT
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn - 1;
rowToProcess[to & MAX_ARRAY] = currentRow - 1;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn - 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow - 1;
data[(currentRow - 1) * mask.width + currentColumn - 1] =
maxNegativeId;
MAX_NEGATIVE_ID;
}
if (
currentColumn < mask.width - 1 &&
Expand All @@ -131,10 +130,10 @@ export function fromMask(
) {
// TOP RIGHT
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn + 1;
rowToProcess[to & MAX_ARRAY] = currentRow - 1;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn + 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow - 1;
data[(currentRow - 1) * mask.width + currentColumn + 1] =
maxNegativeId;
MAX_NEGATIVE_ID;
}
if (
currentColumn > 0 &&
Expand All @@ -144,10 +143,10 @@ export function fromMask(
) {
// BOTTOM LEFT
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn - 1;
rowToProcess[to & MAX_ARRAY] = currentRow + 1;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn - 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow + 1;
data[(currentRow + 1) * mask.width + currentColumn - 1] =
maxNegativeId;
MAX_NEGATIVE_ID;
}
if (
currentColumn < mask.width - 1 &&
Expand All @@ -157,17 +156,17 @@ export function fromMask(
) {
// BOTTOM RIGHT
to++;
columnToProcess[to & MAX_ARRAY] = currentColumn + 1;
rowToProcess[to & MAX_ARRAY] = currentRow + 1;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn + 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow + 1;
data[(currentRow + 1) * mask.width + currentColumn + 1] =
maxNegativeId;
MAX_NEGATIVE_ID;
}
}

from++;

assert(
to - from <= MAX_ARRAY,
to - from <= MAX_TODO_ARRAY_FILTER,
'fromMask can not finish, the array to manage internal data is not big enough.' +
'You could improve mask by changing MAX_ARRAY',
);
Expand Down
42 changes: 11 additions & 31 deletions src/roi/utils/__tests__/getColorMap.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import { getBinaryMap } from '../colorMaps/getBinaryMap';
import { maxNumberRois, colorMapCenter } from '../constants';
import { getColorMap } from '../getColorMap';

test('default options', () => {
const colorMap = getBinaryMap({ nbNegative: 1, nbPositive: 1 });

expect(colorMap).toHaveLength(maxNumberRois);
expect(colorMap[colorMapCenter - 1]).toBe(0xff0000ff); // red
expect(colorMap[colorMapCenter + 1]).toBe(0xff00ff00); // green
expect(Array.from(colorMap)).toStrictEqual([4278190335, 0, 4278255360]);
});

test('binary, BW', () => {
const colorMap = getColorMap({
nbNegative: 1,
nbPositive: 1,
});

expect(colorMap).toHaveLength(maxNumberRois);
expect(colorMap[colorMapCenter - 1]).toBe(0xff0000ff); // red
expect(colorMap[colorMapCenter + 1]).toBe(0xff00ff00); // green
expect(Array.from(colorMap)).toStrictEqual([4278190335, 0, 4278255360]);
});

test('binary, WHITE', () => {
Expand All @@ -27,9 +20,7 @@ test('binary, WHITE', () => {
nbPositive: 1,
roiKind: 'white',
});

expect(colorMap[colorMapCenter - 1]).toBe(0);
expect(colorMap[colorMapCenter + 1]).toBe(0xff00ff00);
expect(Array.from(colorMap)).toStrictEqual([0, 0, 4278255360]);
});

test('binary, BLACK', () => {
Expand All @@ -38,9 +29,7 @@ test('binary, BLACK', () => {
nbPositive: 1,
roiKind: 'black',
});

expect(colorMap[colorMapCenter - 1]).toBe(0xff0000ff);
expect(colorMap[colorMapCenter + 1]).toBe(0);
expect(Array.from(colorMap)).toStrictEqual([4278190335, 0, 0]);
});

test('SATURATION, 1 negative and 1 positive ROIs', () => {
Expand All @@ -49,9 +38,7 @@ test('SATURATION, 1 negative and 1 positive ROIs', () => {
nbNegative: 1,
nbPositive: 1,
});

expect(colorMap[colorMapCenter - 1]).toBe(0xffff0000); // blue
expect(colorMap[colorMapCenter + 1]).toBe(0xff0000ff); // red
expect(Array.from(colorMap)).toStrictEqual([4294901760, 0, 4278190335]);
});

test('RAINBOW, 1 negative and 2 positive ROIs, WHITE', () => {
Expand All @@ -61,10 +48,7 @@ test('RAINBOW, 1 negative and 2 positive ROIs, WHITE', () => {
nbPositive: 2,
roiKind: 'white',
});

expect(colorMap[colorMapCenter - 1]).toBe(0); // transparent
expect(colorMap[colorMapCenter + 1]).toBe(0xff0000ff); // red
expect(colorMap[colorMapCenter + 2]).toBe(0xffffff00); // turquoise
expect(Array.from(colorMap)).toStrictEqual([0, 0, 4278190335, 4294967040]);
});

test('RAINBOW, 1 negative and 2 positive ROIs, BLACK', () => {
Expand All @@ -74,21 +58,17 @@ test('RAINBOW, 1 negative and 2 positive ROIs, BLACK', () => {
nbPositive: 2,
roiKind: 'black',
});

expect(colorMap[colorMapCenter - 1]).toBe(0xff0000ff);
expect(colorMap[colorMapCenter + 1]).toBe(0);
expect(colorMap[colorMapCenter + 2]).toBe(0);
expect(Array.from(colorMap)).toStrictEqual([4278190335, 0, 0, 0]);
});

test('RAINBOW, 1 negative and 1 positive ROIs, BW', () => {
const colorMap = getColorMap({
mode: 'rainbow',
nbNegative: 1,
nbNegative: 2,
nbPositive: 1,
roiKind: 'bw',
});

expect(colorMap[colorMapCenter - 1]).toBe(0xff0000ff);
expect(colorMap[colorMapCenter + 1]).toBe(0xffffff00);
expect(colorMap[colorMapCenter + 2]).toBe(0);
expect(Array.from(colorMap)).toStrictEqual([
4278190335, 4278255360, 0, 4294901760,
]);
});
11 changes: 5 additions & 6 deletions src/roi/utils/colorMaps/getBinaryMap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { RoiKind } from '../../getRois';
import { maxNumberRois, colorMapCenter } from '../constants';
import { hsvToRgb } from '../hsvToRgb';
import { rgbToNumber } from '../rgbToNumber';

Expand Down Expand Up @@ -47,20 +46,20 @@ export function getBinaryMap(options: GetBinaryMapOptions): Uint32Array {
roiKind = 'bw',
} = options;

const colorMap = new Uint32Array(maxNumberRois);
const colorMap = new Uint32Array(nbNegative + nbPositive + 1);

// negative values
if (roiKind === 'bw' || roiKind === 'black') {
for (let i = colorMapCenter - nbNegative; i < colorMapCenter; i++) {
for (let i = -nbNegative; i < 0; i++) {
const hsv = [blackHue, 255, 255];
colorMap[i] = rgbToNumber(hsvToRgb(hsv));
colorMap[i + nbNegative] = rgbToNumber(hsvToRgb(hsv));
}
}
if (roiKind === 'bw' || roiKind === 'white') {
// positive values
for (let i = colorMapCenter + 1; i < colorMapCenter + 1 + nbPositive; i++) {
for (let i = 1; i <= nbPositive; i++) {
const hsv = [whiteHue, 255, 255];
colorMap[i] = rgbToNumber(hsvToRgb(hsv));
colorMap[i + nbNegative] = rgbToNumber(hsvToRgb(hsv));
}
}

Expand Down
11 changes: 5 additions & 6 deletions src/roi/utils/colorMaps/getRainbowMap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { RoiKind } from '../../getRois';
import { maxNumberRois, colorMapCenter } from '../constants';
import { hsvToRgb } from '../hsvToRgb';
import { rgbToNumber } from '../rgbToNumber';

Expand Down Expand Up @@ -27,7 +26,7 @@ export interface GetRainbowMapOptions {
export function getRainbowMap(options: GetRainbowMapOptions): Uint32Array {
const { nbNegative, nbPositive, roiKind = 'bw' } = options;

const colorMap = new Uint32Array(maxNumberRois);
const colorMap = new Uint32Array(nbNegative + nbPositive + 1);

const hueRange = 360;

Expand All @@ -53,17 +52,17 @@ export function getRainbowMap(options: GetRainbowMapOptions): Uint32Array {
// negative values
let hue = 0;
if (roiKind === 'bw' || roiKind === 'black') {
for (let i = colorMapCenter - nbNegative; i < colorMapCenter; i++) {
for (let i = -nbNegative; i < 0; i++) {
const hsv = [hue, 255, 255];
colorMap[i] = rgbToNumber(hsvToRgb(hsv));
colorMap[i + nbNegative] = rgbToNumber(hsvToRgb(hsv));
hue += step;
}
}
// positive values
if (roiKind === 'bw' || roiKind === 'white') {
for (let i = colorMapCenter + 1; i < colorMapCenter + 1 + nbPositive; i++) {
for (let i = 1; i <= nbPositive; i++) {
const hsv = [hue, 255, 255];
colorMap[i] = rgbToNumber(hsvToRgb(hsv));
colorMap[i + nbNegative] = rgbToNumber(hsvToRgb(hsv));
hue += step;
}
}
Expand Down
Loading

0 comments on commit 6adde86

Please sign in to comment.