src/helpers/helpers.stack.js
/** * Imports ***/
import { helpersBorder } from '../helpers/helpers.border';
import { helpersBoundingBox } from '../helpers/helpers.boundingbox';
import { helpersSlice } from '../helpers/helpers.slice';
/**
* Helper to easily display and interact with a stack.<br>
*<br>
* Defaults:<br>
* - orientation: 0 (acquisition direction)<br>
* - index: middle slice in acquisition direction<br>
*<br>
* Features:<br>
* - slice from the stack (in any direction)<br>
* - slice border<br>
* - stack bounding box<br>
*<br>
* Live demo at: {@link http://jsfiddle.net/gh/get/library/pure/fnndsc/ami/tree/master/lessons/01#run|Lesson 01}
*
* @example
* let stack = new VJS.Models.Stack();
* ... // prepare the stack
*
* let helpersStack = new VJS.Helpers.Stack(stack);
* stackHelper.bbox.color = 0xF9F9F9;
* stackHelper.border.color = 0xF9F9F9;
*
* let scene = new THREE.Scene();
* scene.add(stackHelper);
*
* @see module:helpers/border
* @see module:helpers/boundingbox
* @see module:helpers/slice
*
* @module helpers/stack
*/
const helpersStack = (three = window.THREE) => {
if (three === undefined || three.Object3D === undefined) {
return null;
}
const Constructor = three.Object3D;
return class extends Constructor {
constructor(stack) {
//
super();
this._stack = stack;
this._bBox = null;
this._slice = null;
this._border = null;
this._dummy = null;
this._orientation = 0;
this._index = 0;
this._uniforms = null;
this._autoWindowLevel = false;
this._outOfBounds = false;
this._orientationMaxIndex = 0;
this._orientationSpacing = 0;
this._canvasWidth = 0;
this._canvasHeight = 0;
this._borderColor = null;
this._create();
}
/**
* Get stack.
*
* @type {ModelsStack}
*/
get stack() {
return this._stack;
}
/**
* Set stack.
*
* @type {ModelsStack}
*/
set stack(stack) {
this._stack = stack;
}
/**
* Get bounding box helper.
*
* @type {HelpersBoundingBox}
*/
get bbox() {
return this._bBox;
}
/**
* Get slice helper.
*
* @type {HelpersSlice}
*/
get slice() {
return this._slice;
}
/**
* Get border helper.
*
* @type {HelpersSlice}
*/
get border() {
return this._border;
}
/**
* Set/get current slice index.<br>
* Sets outOfBounds flag to know if target index is in/out stack bounding box.<br>
* <br>
* Internally updates the sliceHelper index and position. Also updates the
* borderHelper with the updated sliceHelper.
*
* @type {number}
*/
get index() {
return this._index;
}
set index(index) {
this._index = index;
// update the slice
this._slice.index = index;
let halfDimensions = this._stack.halfDimensionsIJK;
this._slice.planePosition = this._prepareSlicePosition(halfDimensions, this._index);
// also update the border
this._border.helpersSlice = this._slice;
// update ourOfBounds flag
this._isIndexOutOfBounds();
}
/**
* Set/get current slice orientation.<br>
* Values: <br>
* - 0: acquisition direction (slice normal is z_cosine)<br>
* - 1: next direction (slice normal is x_cosine)<br>
* - 2: next direction (slice normal is y_cosine)<br>
* - n: set orientation to 0<br>
* <br>
* Internally updates the sliceHelper direction. Also updates the
* borderHelper with the updated sliceHelper.
*
* @type {number}
*/
set orientation(orientation) {
this._orientation = orientation;
this._computeOrientationMaxIndex();
this._computeOrientationSpacing();
this._slice.spacing = Math.abs(this._orientationSpacing);
this._slice.thickness = this._slice.spacing;
this._slice.planeDirection = this._prepareDirection(this._orientation);
// also update the border
this._border.helpersSlice = this._slice;
}
get orientation() {
return this._orientation;
}
/**
* Set/get the outOfBound flag.
*
* @type {boolean}
*/
set outOfBounds(outOfBounds) {
this._outOfBounds = outOfBounds;
}
get outOfBounds() {
return this._outOfBounds;
}
/**
* Set/get the orientationMaxIndex.
*
* @type {number}
*/
set orientationMaxIndex(orientationMaxIndex) {
this._orientationMaxIndex = orientationMaxIndex;
}
get orientationMaxIndex() {
return this._orientationMaxIndex;
}
/**
* Set/get the orientationSpacing.
*
* @type {number}
*/
set orientationSpacing(orientationSpacing) {
this._orientationSpacing = orientationSpacing;
}
get orientationSpacing() {
return this._orientationSpacing;
}
set canvasWidth(canvasWidth) {
this._canvasWidth = canvasWidth;
this._slice.canvasWidth = this._canvasWidth;
}
get canvasWidth() {
return this._canvasWidth;
}
set canvasHeight(canvasHeight) {
this._canvasHeight = canvasHeight;
this._slice.canvasHeight = this._canvasHeight;
}
get canvasHeight() {
return this._canvasHeight;
}
set borderColor(borderColor) {
this._borderColor = borderColor;
this._border.color = borderColor;
this._slice.borderColor = this._borderColor;
}
get borderColor() {
return this._borderColor;
}
//
// PRIVATE METHODS
//
/**
* Initial setup, including stack prepare, bbox prepare, slice prepare and
* border prepare.
*
* @private
*/
_create() {
if (this._stack) {
// prepare sthe stack internals
this._prepareStack();
// prepare visual objects
this._prepareBBox();
this._prepareSlice();
this._prepareBorder();
// todo: Arrow
} else {
window.console.log('no stack to be prepared...');
}
}
_computeOrientationSpacing() {
let spacing = this._stack._spacing;
switch (this._orientation) {
case 0:
this._orientationSpacing = spacing.z;
break;
case 1:
this._orientationSpacing = spacing.x;
break;
case 2:
this._orientationSpacing = spacing.y;
break;
default:
this._orientationSpacing = 0;
break;
}
}
_computeOrientationMaxIndex() {
let dimensionsIJK = this._stack.dimensionsIJK;
this._orientationMaxIndex = 0;
switch (this._orientation) {
case 0:
this._orientationMaxIndex = dimensionsIJK.z - 1;
break;
case 1:
this._orientationMaxIndex = dimensionsIJK.x - 1;
break;
case 2:
this._orientationMaxIndex = dimensionsIJK.y - 1;
break;
default:
// do nothing!
break;
}
}
/**
* Given orientation, check if index is in/out of bounds.
*
* @private
*/
_isIndexOutOfBounds() {
this._computeOrientationMaxIndex();
if (this._index >= this._orientationMaxIndex || this._index < 0) {
this._outOfBounds = true;
} else {
this._outOfBounds = false;
}
}
/**
* Prepare a stack for visualization. (image to world transform, frames order,
* pack data into 8 bits textures, etc.)
*
* @private
*/
_prepareStack() {
// make sure there is something, if not throw an error
// compute image to workd transform, order frames, etc.
if (!this._stack.prepared) {
this._stack.prepare();
}
// pack data into 8 bits rgba texture for the shader
// this one can be slow...
if (!this._stack.packed) {
this._stack.pack();
}
}
/**
* Setup bounding box helper given prepared stack and add bounding box helper
* to stack helper.
*
* @private
*/
_prepareBBox() {
const HelpersBoundingBoxConstructor = helpersBoundingBox(three);
this._bBox = new HelpersBoundingBoxConstructor(this._stack);
this.add(this._bBox);
}
/**
* Setup border helper given slice helper and add border helper
* to stack helper.
*
* @private
*/
_prepareBorder() {
const HelpersBorderContructor = helpersBorder(three);
this._border = new HelpersBorderContructor(this._slice);
this.add(this._border);
}
/**
* Setup slice helper given prepared stack helper and add slice helper
* to stack helper.
*
* @private
*/
_prepareSlice() {
let halfDimensionsIJK = this._stack.halfDimensionsIJK;
// compute initial index given orientation
this._index = this._prepareSliceIndex(halfDimensionsIJK);
// compute initial position given orientation and index
let position = this._prepareSlicePosition(halfDimensionsIJK, this._index);
// compute initial direction orientation
let direction = this._prepareDirection(this._orientation);
const SliceHelperConstructor = helpersSlice(three);
this._slice = new SliceHelperConstructor(this._stack, this._index, position, direction);
this.add(this._slice);
}
/**
* Compute slice index depending on orientation.
*
* @param {Vector3} indices - Indices in each direction.
*
* @returns {number} Slice index according to current orientation.
*
* @private
*/
_prepareSliceIndex(indices) {
let index = 0;
switch (this._orientation) {
case 0:
index = Math.floor(indices.z);
break;
case 1:
index = Math.floor(indices.x);
break;
case 2:
index = Math.floor(indices.y);
break;
default:
// do nothing!
break;
}
return index;
}
/**
* Compute slice position depending on orientation.
* Sets index in proper location of reference position.
*
* @param {Vector3} rPosition - Reference position.
* @param {number} index - Current index.
*
* @returns {number} Slice index according to current orientation.
*
* @private
*/
_prepareSlicePosition(rPosition, index) {
let position = new three.Vector3(0, 0, 0);
switch (this._orientation) {
case 0:
position = new three.Vector3(Math.floor(rPosition.x), Math.floor(rPosition.y), index);
break;
case 1:
position = new three.Vector3(index, Math.floor(rPosition.y), Math.floor(rPosition.z));
break;
case 2:
position = new three.Vector3(Math.floor(rPosition.x), index, Math.floor(rPosition.z));
break;
default:
// do nothing!
break;
}
return position;
}
/**
* Compute slice direction depending on orientation.
*
* @param {number} orientation - Slice orientation.
*
* @returns {Vector3} Slice direction
*
* @private
*/
_prepareDirection(orientation) {
let direction = new three.Vector3(0, 0, 1);
switch (orientation) {
case 0:
direction = new three.Vector3(0, 0, 1);
break;
case 1:
direction = new three.Vector3(1, 0, 0);
break;
case 2:
direction = new three.Vector3(0, 1, 0);
break;
default:
// do nothing!
break;
}
return direction;
}
/**
* Release the stack helper memory including the slice memory.
*
* @public
*/
dispose() {
this.remove(this._slice);
this._slice.dispose();
this._slice = null;
this._bBox.dispose();
this._bBox = null;
this._border.dispose();
this._border = null;
}
};
};
// export factory
export { helpersStack };
// default export to
export default helpersStack();