Skip to content

Commit

Permalink
feat(CPR): Add MIP to ImageCPRMapper
Browse files Browse the repository at this point in the history
  • Loading branch information
bruyeret committed Mar 5, 2024
1 parent 2d94482 commit 2bd15fd
Show file tree
Hide file tree
Showing 7 changed files with 416 additions and 134 deletions.
9 changes: 9 additions & 0 deletions Sources/Rendering/Core/ImageCPRMapper/Constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const MipMode = {
MAX: 0,
MIN: 1,
AVERAGE: 2,
};

export default {
MipMode,
};
10 changes: 10 additions & 0 deletions Sources/Rendering/Core/ImageCPRMapper/Constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export declare enum MipMode {
MAX = 0,
MIN = 1,
AVERAGE = 2,
}

declare const _default: {
MipMode: typeof MipMode;
};
export default _default;
48 changes: 44 additions & 4 deletions Sources/Rendering/Core/ImageCPRMapper/example/controller.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,56 @@
<table>
<tr>
<td><h3 style="margin: 5px; padding-top: 5px;">CPR</h3></td>
</tr>
<tr>
<td>Mode</td>
<td>
<select id='mode' style="width: 100%"></select>
</td>
</tr>
<tr>
<td>Centerline</td>
<td>
<select id='centerline' style="width: 100%"></select>
</td>
</tr>
<td>
<label>Angle</label>
<input id='angle' type='range' min='0' max='360' value='0' step="1"/>
</td>
<tr>
<td>Angle</td>
<td>
<input id='angle' type='range' min='0' max='360' value='0' step="1"/>
</td>
</tr>
<tr>
<td>Animate Angle</td>
<td>
<input id='animate' type='checkbox' />
</td>
</tr>
<tr>
<td><h3 style="margin: 5px; padding-top: 5px;">MIP</h3></td>
</tr>
<tr>
<td>Enable</td>
<td>
<input id='mipEnable' type='checkbox' />
</td>
</tr>
<tr>
<td>Mode</td>
<td>
<select id='mipMode' style="width: 100%"></select>
</td>
</tr>
<tr>
<td>Thickness</td>
<td>
<input id='mipThickness' type='range' min='0' max='1' value='0' step="0.01"/>
</td>
</tr>
<tr>
<td>Number of samples</td>
<td>
<input id='mipSamples' type='range' min='1' max='101' value='1' step="2"/>
</td>
</tr>
</table>
52 changes: 52 additions & 0 deletions Sources/Rendering/Core/ImageCPRMapper/example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import widgetBehavior from 'vtk.js/Sources/Widgets/Widgets3D/ResliceCursorWidget
import controlPanel from './controller.html';
import aortaJSON from './aorta_centerline.json';
import spineJSON from './spine_centerline.json';
import { MipMode } from '../Constants';

const volumePath = `${__BASE_PATH__}/data/volume/LIDC2.vti`;
const centerlineJsons = { Aorta: aortaJSON, Spine: spineJSON };
Expand All @@ -44,8 +45,13 @@ const renderWindow = fullScreenRenderer.getRenderWindow();

fullScreenRenderer.addController(controlPanel);
const angleEl = document.getElementById('angle');
const animateEl = document.getElementById('animate');
const centerlineEl = document.getElementById('centerline');
const modeEl = document.getElementById('mode');
const mipEnableEl = document.getElementById('mipEnable');
const mipModeEl = document.getElementById('mipMode');
const mipThicknessEl = document.getElementById('mipThickness');
const mipSamplesEl = document.getElementById('mipSamples');

const interactor = renderWindow.getInteractor();
interactor.setInteractorStyle(vtkInteractorStyleImage.newInstance());
Expand Down Expand Up @@ -354,6 +360,20 @@ angleEl.addEventListener('input', () =>
setAngleFromSlider(radiansFromDegrees(Number.parseFloat(angleEl.value, 10)))
);

let animationId;
animateEl.addEventListener('change', () => {
if (animateEl.checked) {
animationId = setInterval(() => {
const currentAngle = radiansFromDegrees(
Number.parseFloat(angleEl.value, 10)
);
setAngleFromSlider(currentAngle + 0.1);
}, 60);
} else {
clearInterval(animationId);
}
});

function useStraightenedMode() {
mapper.useStraightenedMode();
updateDistanceAndDirection();
Expand Down Expand Up @@ -388,6 +408,38 @@ modeEl.appendChild(straightEl);
modeEl.addEventListener('input', () => setUseStretched(modeEl.value));
modeEl.value = 'straightened';

mipEnableEl.addEventListener('change', (ev) => {
mapper.setUseMip(mipEnableEl.checked);
renderWindow.render();
});

Object.keys(MipMode).forEach((mipMode) => {
const optionEl = document.createElement('option');
optionEl.innerText = mipMode.charAt(0) + mipMode.substring(1).toLowerCase();
optionEl.value = mipMode;
mipModeEl.appendChild(optionEl);
});

mipModeEl.addEventListener('input', (ev) => {
mapper.setMipMode(MipMode[mipModeEl.value]);
renderWindow.render();
});

mipThicknessEl.addEventListener('input', (ev) => {
const thickness = Number.parseFloat(mipThicknessEl.value, 10);
mapper.setMipSlabThickness(thickness);
renderWindow.render();
});
mapper.setMipSlabThickness(0.1);
mipThicknessEl.value = mapper.getMipSlabThickness();

mipSamplesEl.addEventListener('input', (ev) => {
const samples = Number.parseInt(mipSamplesEl.value, 10);
mapper.setMipNumberOfSamples(samples);
renderWindow.render();
});
mipSamplesEl.value = mapper.getMipNumberOfSamples();

stretchViewWidgetInstance.onInteractionEvent(updateDistanceAndDirection);
crossViewWidgetInstance.onInteractionEvent(updateDistanceAndDirection);

Expand Down
54 changes: 54 additions & 0 deletions Sources/Rendering/Core/ImageCPRMapper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import vtkDataArray from "../../../Common/Core/DataArray";
import vtkImageData from "../../../Common/DataModel/ImageData";
import vtkPolyData from "../../../Common/DataModel/PolyData";
import vtkPolyLine from "../../../Common/DataModel/PolyLine";
import { MipMode } from "./Constants";

interface ICoincidentTopology {
factor: number;
Expand Down Expand Up @@ -157,6 +158,59 @@ export interface vtkImageCPRMapper extends vtkAbstractMapper3D {
*/
setDirectionMatrix(mat: mat3): boolean;

/**
* Enable or disable Maximum Intensity Projection
* Can be configured to use other modes of projection
* @see getMipSlabThickness
* @see getMipNumberOfSamples
* @see getMipMode
*/
getUseMip(): boolean;

/**
* @see getUseMip
* @param useMip
*/
setUseMip(useMip: boolean): boolean;

/**
* Thickness of the MIP slab in image coordinates (NOT in voxels)
* Usually in millimeters if the spacing of the input image is set from a DICOM
*/
getMipSlabThickness(): number;

/**
* @see getMipSlabThickness
* @param mipSlabThickness
*/
setMipSlabThickness(mipSlabThickness: number): boolean;

/**
* Total number of samples of the volume done by the MIP mode
* Using an odd number is advised
* If this number is even, the center of the slab will not be sampled
*/
getMipNumberOfSamples(): number;

/**
* @see getMipNumberOfSamples
* @param mipNumberOfSamples
*/
setMipNumberOfSamples(mipNumberOfSamples: number): boolean;

/**
* The different modes of projection
* Even if the name of the mode is MIP (which implies a maximum intensity projection),
* other modes are available such as MinIP and Average Intensity Projection
*/
getMipMode(): MipMode;

/**
* @see getMipMode
* @param mipMode
*/
setMipMode(mipMode: MipMode): boolean;

/**
* Find the data array to use for orientation in the input polydata ( @see getOrientationArrayName )
*/
Expand Down
14 changes: 13 additions & 1 deletion Sources/Rendering/Core/ImageCPRMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import vtkAbstractImageMapper from 'vtk.js/Sources/Rendering/Core/AbstractImageM
import macro from 'vtk.js/Sources/macros';
import vtkPoints from 'vtk.js/Sources/Common/Core/Points';
import vtkPolyLine from 'vtk.js/Sources/Common/DataModel/PolyLine';
import { MipMode } from './Constants';

const { vtkErrorMacro } = macro;

Expand Down Expand Up @@ -103,7 +104,10 @@ function vtkImageCPRMapper(publicAPI, model) {
: orientationDataArray.getNumberOfComponents();
switch (numComps) {
case 16:
convert = mat4.getRotation;
convert = (outQuat, inMat) => {
mat4.getRotation(outQuat, inMat);
quat.normalize(outQuat, outQuat);
};
break;
case 9:
convert = (outQuat, inMat) => {
Expand Down Expand Up @@ -335,6 +339,10 @@ const DEFAULT_VALUES = {
tangentDirection: [1, 0, 0],
bitangentDirection: [0, 1, 0],
normalDirection: [0, 0, 1],
useMip: false,
mipSlabThickness: 1,
mipNumberOfSamples: 31,
mipMode: MipMode.MAX,
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -362,6 +370,10 @@ export function extend(publicAPI, model, initialValues = {}) {
'tangentDirection',
'bitangentDirection',
'normalDirection',
'useMip',
'mipSlabThickness',
'mipNumberOfSamples',
'mipMode',
]);
CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);

Expand Down
Loading

0 comments on commit 2bd15fd

Please sign in to comment.