Skip to content

Commit

Permalink
Store segmentation as mono value image
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmartel committed Nov 20, 2024
1 parent bafee60 commit 8914d17
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 132 deletions.
4 changes: 1 addition & 3 deletions src/app/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -1887,9 +1887,7 @@ export class App {
// (assuming RGB data)
if (data.image.getMeta().Modality === 'SEG') {
view.setAlphaFunction(function (value /*, index*/) {
if (value[0] === 0 &&
value[1] === 0 &&
value[2] === 0) {
if (value === 0) {
return 0;
} else {
return 0xff;
Expand Down
30 changes: 24 additions & 6 deletions src/image/changeSegmentColourCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export class ChangeSegmentColourCommand {
this.#previousColour = segment.displayRGBValue;
} else {
this.#previousColour = segment.displayValue;
this.#offsets = mask.getOffsets(this.#previousColour);
}
this.#offsets = mask.getOffsets(this.#previousColour);
}

/**
Expand All @@ -88,7 +88,11 @@ export class ChangeSegmentColourCommand {
* @returns {boolean} True if the command is valid.
*/
isValid() {
return this.#offsets.length !== 0;
let valid = true;
if (typeof this.#offsets !== 'undefined') {
valid = this.#offsets.length !== 0;
}
return valid;
}

/**
Expand All @@ -97,12 +101,19 @@ export class ChangeSegmentColourCommand {
* @fires ChangeSegmentColourCommand#changemasksegmentcolour
*/
execute() {
// remove
this.#mask.setAtOffsets(this.#offsets, this.#newColour);
// update segment property
if (typeof this.#newColour === 'number') {
// remove
this.#mask.setAtOffsets(this.#offsets, this.#newColour);
// update segment
this.#segment.displayValue = this.#newColour;
} else {
// update palette colour map (sends update event)
this.#mask.updatePaletteColourMap(
this.#segment.number,
this.#newColour
);
// update segment
this.#segment.displayRGBValue = this.#newColour;
}

Expand All @@ -129,12 +140,19 @@ export class ChangeSegmentColourCommand {
* @fires ChangeSegmentColourCommand#changemasksegmentcolour
*/
undo() {
// re-draw
this.#mask.setAtOffsets(this.#offsets, this.#previousColour);
// update segment property
if (typeof this.#previousColour === 'number') {
// update values
this.#mask.setAtOffsets(this.#offsets, this.#previousColour);
// update segment
this.#segment.displayValue = this.#previousColour;
} else {
// update palette colour map (sends update event)
this.#mask.updatePaletteColourMap(
this.#segment.number,
this.#previousColour
);
// udpate segment
this.#segment.displayRGBValue = this.#previousColour;
}

Expand Down
12 changes: 4 additions & 8 deletions src/image/deleteSegmentCommand.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {MaskSegmentHelper} from './maskSegmentHelper';
import {BLACK} from '../utils/colour';

// doc imports
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -51,7 +50,7 @@ export class DeleteSegmentCommand {
this.#isSilent = (typeof silent === 'undefined') ? false : silent;
// list of offsets with the colour to delete
if (typeof segment.displayRGBValue !== 'undefined') {
this.#offsets = mask.getOffsets(segment.displayRGBValue);
this.#offsets = mask.getOffsets(segment.number);
} else {
this.#offsets = mask.getOffsets(segment.displayValue);
}
Expand All @@ -72,6 +71,7 @@ export class DeleteSegmentCommand {
* @returns {boolean} True if the command is valid.
*/
isValid() {
// check that input segment is still there
const segments = this.#mask.getMeta().custom.segments;
return segments.some(segmentItem =>
segmentItem.number === this.#segment.number
Expand All @@ -86,11 +86,7 @@ export class DeleteSegmentCommand {
execute() {
if (this.#offsets.length !== 0) {
// remove from image
if (typeof this.#segment.displayRGBValue !== 'undefined') {
this.#mask.setAtOffsets(this.#offsets, BLACK);
} else {
this.#mask.setAtOffsets(this.#offsets, 0);
}
this.#mask.setAtOffsets(this.#offsets, 0);
}

// remove from segments
Expand Down Expand Up @@ -122,7 +118,7 @@ export class DeleteSegmentCommand {
if (this.#offsets.length !== 0) {
// re-draw in image
if (typeof this.#segment.displayRGBValue !== 'undefined') {
this.#mask.setAtOffsets(this.#offsets, this.#segment.displayRGBValue);
this.#mask.setAtOffsets(this.#offsets, this.#segment.number);
} else {
this.#mask.setAtOffsets(this.#offsets, this.#segment.displayValue);
}
Expand Down
58 changes: 30 additions & 28 deletions src/image/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ export class Image {
*/
setPaletteColourMap(map) {
this.#paletteColourMap = map;
// fire imagecontentchange
this.#fireEvent({type: 'imagecontentchange'});
}

/**
Expand All @@ -510,6 +512,20 @@ export class Image {
return this.#paletteColourMap;
}

/**
* Update the palette colour map.
*
* @param {number} index The index to change the colour of.
* @param {RGB} colour The colour to use at index.
*/
updatePaletteColourMap(index, colour) {
this.#paletteColourMap.red[index] = colour.r;
this.#paletteColourMap.green[index] = colour.g;
this.#paletteColourMap.blue[index] = colour.b;
// fire imagecontentchange
this.#fireEvent({type: 'imagecontentchange'});
}

/**
* Get the planarConfiguration of the image.
*
Expand Down Expand Up @@ -1082,7 +1098,7 @@ export class Image {
*
* @param {number[][]} offsetsLists List of offset lists where
* to set the data.
* @param {RGB} value The value to set at the given offsets.
* @param {number} value The value to set at the given offsets.
* @returns {Array} A list of objects representing the original values before
* replacing them.
* @fires Image#imagecontentchange
Expand All @@ -1094,29 +1110,19 @@ export class Image {
for (let j = 0; j < offsetsLists.length; ++j) {
const offsets = offsetsLists[j];
// first colour
let offset = offsets[0] * 3;
let previousColour = {
r: this.#buffer[offset],
g: this.#buffer[offset + 1],
b: this.#buffer[offset + 2]
};
let offset = offsets[0];
let previousColour = this.#buffer[offset];
// original value storage
const originalColours = [];
originalColours.push({
index: 0,
colour: previousColour
});
for (let i = 0; i < offsets.length; ++i) {
offset = offsets[i] * 3;
const currentColour = {
r: this.#buffer[offset],
g: this.#buffer[offset + 1],
b: this.#buffer[offset + 2]
};
offset = offsets[i];
const currentColour = this.#buffer[offset];
// check if new colour
if (previousColour.r !== currentColour.r ||
previousColour.g !== currentColour.g ||
previousColour.b !== currentColour.b) {
if (previousColour !== currentColour) {
// store new colour
originalColours.push({
index: i,
Expand All @@ -1125,9 +1131,7 @@ export class Image {
previousColour = currentColour;
}
// write update colour
this.#buffer[offset] = value.r;
this.#buffer[offset + 1] = value.g;
this.#buffer[offset + 2] = value.b;
this.#buffer[offset] = value;
}
originalColoursLists.push(originalColours);
}
Expand All @@ -1141,21 +1145,21 @@ export class Image {
*
* @param {number[][]} offsetsLists List of offset lists
* where to set the data.
* @param {RGB|Array} value The value to set at the given offsets.
* @param {number|Array} value The value to set at the given offsets.
* @fires Image#imagecontentchange
*/
setAtOffsetsWithIterator(offsetsLists, value) {
const isValueArray = Array.isArray(value);

for (let j = 0; j < offsetsLists.length; ++j) {
const offsets = offsetsLists[j];
let iterator;
if (Array.isArray(value)) {
if (isValueArray) {
// input value is a list of iterators
// created by setAtOffsetsAndGetOriginals
iterator = colourRange(
value[j], offsets.length);
} else if (typeof value.r !== 'undefined' &&
typeof value.g !== 'undefined' &&
typeof value.b !== 'undefined') {
} else {
// input value is a simple color
iterator = colourRange(
[{index: 0, colour: value}], offsets.length);
Expand All @@ -1164,10 +1168,8 @@ export class Image {
// set values
let ival = iterator.next();
while (!ival.done) {
const offset = offsets[ival.index] * 3;
this.#buffer[offset] = ival.value.r;
this.#buffer[offset + 1] = ival.value.g;
this.#buffer[offset + 2] = ival.value.b;
const offset = offsets[ival.index];
this.#buffer[offset] = ival.value;
ival = iterator.next();
}
}
Expand Down
78 changes: 29 additions & 49 deletions src/image/maskFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {Matrix33, REAL_WORLD_EPSILON} from '../math/matrix';
import {logger} from '../utils/logger';
import {arraySortEquals} from '../utils/array';
import {Size} from './size';
import {ColourMap} from './luts';

// doc imports
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -151,51 +152,21 @@ function createRoiSliceBuffers(
segments,
sliceOffset
) {
// access functions
const numberOfComponents = image.getNumberOfComponents();
const isRGB = numberOfComponents === 3;
let getPixelValue;
let equalValues;
if (isRGB) {
getPixelValue = function (inputOffset) {
return {
r: image.getValueAtOffset(inputOffset),
g: image.getValueAtOffset(inputOffset + 1),
b: image.getValueAtOffset(inputOffset + 2)
};
};
equalValues = function (value, segment) {
return (
segment.displayRGBValue !== undefined &&
value.r === segment.displayRGBValue.r &&
value.g === segment.displayRGBValue.g &&
value.b === segment.displayRGBValue.b
);
};
} else {
getPixelValue = function (inputOffset) {
return image.getValueAtOffset(inputOffset);
};
equalValues = function (value, segment) {
return value === segment.displayValue;
};
}

// create binary mask buffers
const geometry = image.getGeometry();
const size = geometry.getSize();
const sliceSize = size.getDimSize(2);
const buffers = {};
for (let o = 0; o < sliceSize; ++o) {
const inputOffset = (sliceOffset + o) * numberOfComponents;
const pixelValue = getPixelValue(inputOffset);
const inputOffset = sliceOffset + o;
const pixelValue = image.getValueAtOffset(inputOffset);
for (const segment of segments) {
const number2 = segment.number - 1;
if (equalValues(pixelValue, segment)) {
if (buffers[number2] === undefined) {
buffers[number2] = new Uint8Array(sliceSize);
const segmentIndex = segment.number - 1;
if (pixelValue === segment.number) {
if (buffers[segmentIndex] === undefined) {
buffers[segmentIndex] = new Uint8Array(sliceSize);
}
buffers[number2][o] = 1;
buffers[segmentIndex][o] = 1;
}
}
}
Expand Down Expand Up @@ -411,17 +382,28 @@ export class MaskFactory {
throw new Error('Missing or empty segmentation sequence');
}
const segments = [];
let storeAsRGB = false;
// segment number is unique and starts at 1, use 0 as background
const redLut = [0];
const greenLut = [0];
const blueLut = [0];
for (let i = 0; i < segSequence.value.length; ++i) {
const segment = getSegment(segSequence.value[i]);
if (typeof segment.displayRGBValue !== 'undefined') {
// create rgb image
storeAsRGB = true;
// add palette colour
redLut[segment.number] = segment.displayRGBValue.r;
greenLut[segment.number] = segment.displayRGBValue.g;
blueLut[segment.number] = segment.displayRGBValue.b;
}
// store
segments.push(segment);
}

let hasDisplayRGBValue = false;
let paletteColourMap;
if (redLut.length > 1) {
hasDisplayRGBValue = true;
paletteColourMap = new ColourMap(redLut, greenLut, blueLut);
}

// Shared Functional Groups Sequence
let spacing;
Expand Down Expand Up @@ -635,10 +617,9 @@ export class MaskFactory {
};

// create output buffer
const mul = storeAsRGB ? 3 : 1;
const buffer =
// @ts-ignore
new pixelBuffer.constructor(mul * sliceSize * numberOfSlices);
new pixelBuffer.constructor(sliceSize * numberOfSlices);
buffer.fill(0);
// merge frame buffers
let sliceOffset = null;
Expand All @@ -654,11 +635,9 @@ export class MaskFactory {
);
for (let l = 0; l < sliceSize; ++l) {
if (pixelBuffer[frameOffset + l] !== 0) {
const offset = mul * (sliceOffset + l);
if (storeAsRGB) {
buffer[offset] = frameSegment.displayRGBValue.r;
buffer[offset + 1] = frameSegment.displayRGBValue.g;
buffer[offset + 2] = frameSegment.displayRGBValue.b;
const offset = sliceOffset + l;
if (hasDisplayRGBValue) {
buffer[offset] = frameSegment.number;
} else {
buffer[offset] = frameSegment.displayValue;
}
Expand All @@ -668,8 +647,9 @@ export class MaskFactory {

// create image
const image = new Image(geometry, buffer, uids);
if (storeAsRGB) {
image.setPhotometricInterpretation('RGB');
if (hasDisplayRGBValue) {
image.setPhotometricInterpretation('PALETTE COLOR');
image.setPaletteColourMap(paletteColourMap);
}
// meta information
const meta = getDefaultDicomSegJson();
Expand Down
Loading

0 comments on commit 8914d17

Please sign in to comment.