image-js
Version:
Image processing and manipulation in JavaScript
139 lines • 6.94 kB
JavaScript
import { assert } from '../utils/validators/assert.js';
import { RoiMapManager } from './RoiMapManager.js';
/**
* Extract the ROIs of an image.
* @param mask - Mask to extract the ROIs from.
* @param options - From mask options.
* @returns The corresponding ROI manager.
*/
export function fromMask(mask, options = {}) {
const { allowCorners = false } = options;
const MAX_TODO_ARRAY_FILTER = 65535; // 65535 should be enough for most of the cases
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
// split will always return an array of images
let positiveId = 0;
let negativeId = 0;
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++) {
if (data[row * mask.width + column] === 0) {
// need to process the whole surface
analyseSurface(column, row);
}
}
}
// x column
// y row
function analyseSurface(column, row) {
let from = 0;
let to = 0;
const targetState = mask.getBit(column, row);
const id = targetState ? ++positiveId : --negativeId;
assert(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_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 (currentColumn > 0 &&
data[currentRow * mask.width + currentColumn - 1] === 0 &&
mask.getBit(currentColumn - 1, currentRow) === targetState) {
// LEFT
to++;
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 &&
data[(currentRow - 1) * mask.width + currentColumn] === 0 &&
mask.getBit(currentColumn, currentRow - 1) === targetState) {
// TOP
to++;
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 &&
data[currentRow * mask.width + currentColumn + 1] === 0 &&
mask.getBit(currentColumn + 1, currentRow) === targetState) {
// RIGHT
to++;
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 &&
data[(currentRow + 1) * mask.width + currentColumn] === 0 &&
mask.getBit(currentColumn, currentRow + 1) === targetState) {
// BOTTOM
to++;
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 (currentColumn > 0 &&
currentRow > 0 &&
data[(currentRow - 1) * mask.width + currentColumn - 1] === 0 &&
mask.getBit(currentColumn - 1, currentRow - 1) === targetState) {
// TOP LEFT
to++;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn - 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow - 1;
data[(currentRow - 1) * mask.width + currentColumn - 1] =
MAX_NEGATIVE_ID;
}
if (currentColumn < mask.width - 1 &&
currentRow > 0 &&
data[(currentRow - 1) * mask.width + currentColumn + 1] === 0 &&
mask.getBit(currentColumn + 1, currentRow - 1) === targetState) {
// TOP RIGHT
to++;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn + 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow - 1;
data[(currentRow - 1) * mask.width + currentColumn + 1] =
MAX_NEGATIVE_ID;
}
if (currentColumn > 0 &&
currentRow < mask.height - 1 &&
data[(currentRow + 1) * mask.width + currentColumn - 1] === 0 &&
mask.getBit(currentColumn - 1, currentRow + 1) === targetState) {
// BOTTOM LEFT
to++;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn - 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow + 1;
data[(currentRow + 1) * mask.width + currentColumn - 1] =
MAX_NEGATIVE_ID;
}
if (currentColumn < mask.width - 1 &&
currentRow < mask.height - 1 &&
data[(currentRow + 1) * mask.width + currentColumn + 1] === 0 &&
mask.getBit(currentColumn + 1, currentRow + 1) === targetState) {
// BOTTOM RIGHT
to++;
columnToProcess[to & MAX_TODO_ARRAY_FILTER] = currentColumn + 1;
rowToProcess[to & MAX_TODO_ARRAY_FILTER] = currentRow + 1;
data[(currentRow + 1) * mask.width + currentColumn + 1] =
MAX_NEGATIVE_ID;
}
}
from++;
assert(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');
}
}
return new RoiMapManager({
width: mask.width,
height: mask.height,
data,
nbNegative: Math.abs(negativeId),
nbPositive: positiveId,
});
}
//# sourceMappingURL=fromMask.js.map