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 6, 2024
1 parent df54205 commit f885afe
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 133 deletions.
10 changes: 10 additions & 0 deletions Sources/Rendering/Core/ImageCPRMapper/Constants.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export declare enum ProjectionMode {
MAX = 0,
MIN = 1,
AVERAGE = 2,
}

declare const _default: {
ProjectionMode: typeof ProjectionMode;
};
export default _default;
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 ProjectionMode = {
MAX: 0,
MIN: 1,
AVERAGE: 2,
};

export default {
ProjectionMode,
};
42 changes: 38 additions & 4 deletions Sources/Rendering/Core/ImageCPRMapper/example/controller.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
<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;">Projection</h3></td>
</tr>
<tr>
<td>Mode</td>
<td>
<select id='projectionMode' style="width: 100%"></select>
</td>
</tr>
<tr>
<td>Thickness</td>
<td>
<input id='projectionThickness' type='range' min='0' max='1' value='0' step="0.01"/>
</td>
</tr>
<tr>
<td>Number of samples</td>
<td>
<input id='projectionSamples' type='range' min='1' max='101' value='1' step="2"/>
</td>
</tr>
</table>
47 changes: 47 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 { ProjectionMode } from '../Constants';

const volumePath = `${__BASE_PATH__}/data/volume/LIDC2.vti`;
const centerlineJsons = { Aorta: aortaJSON, Spine: spineJSON };
Expand All @@ -44,8 +45,12 @@ 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 projectionModeEl = document.getElementById('projectionMode');
const projectionThicknessEl = document.getElementById('projectionThickness');
const projectionSamplesEl = document.getElementById('projectionSamples');

const interactor = renderWindow.getInteractor();
interactor.setInteractorStyle(vtkInteractorStyleImage.newInstance());
Expand Down Expand Up @@ -354,6 +359,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 +407,34 @@ modeEl.appendChild(straightEl);
modeEl.addEventListener('input', () => setUseStretched(modeEl.value));
modeEl.value = 'straightened';

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

projectionModeEl.addEventListener('input', (ev) => {
mapper.setProjectionMode(ProjectionMode[projectionModeEl.value]);
renderWindow.render();
});

projectionThicknessEl.addEventListener('input', (ev) => {
const thickness = Number.parseFloat(projectionThicknessEl.value, 10);
mapper.setProjectionSlabThickness(thickness);
renderWindow.render();
});
mapper.setProjectionSlabThickness(0.1);
projectionThicknessEl.value = mapper.getProjectionSlabThickness();

projectionSamplesEl.addEventListener('input', (ev) => {
const samples = Number.parseInt(projectionSamplesEl.value, 10);
mapper.setProjectionSlabNumberOfSamples(samples);
renderWindow.render();
});
projectionSamplesEl.value = mapper.getProjectionSlabNumberOfSamples();

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

Expand Down
46 changes: 46 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 { ProjectionMode } from "./Constants";

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

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

/**
* @see getProjectionSlabThickness
* @param projectionSlabThickness
*/
setProjectionSlabThickness(ProjectionSlabThickness: number): boolean;

/**
* Total number of samples of the volume done by the projection mode
* If this number is equal or less than 1, projection is disabled
* Using an odd number is advised
* If this number is even, the center of the slab will not be sampled
*/
getProjectionSlabNumberOfSamples(): number;

/**
* @see getProjectionSlabNumberOfSamples
* @param projectionSlabNumberOfSamples
*/
setProjectionSlabNumberOfSamples(projectionSlabNumberOfSamples: number): boolean;

/**
* Returns wether projection is enabled
* It is based on the number of samples
* @see getProjectionSlabNumberOfSamples
*/
isProjectionEnabled(): boolean;

/**
* The different modes of projection
* Available modes include MIP, MinIP and AverageIP
*/
getProjectionMode(): ProjectionMode;

/**
* @see getProjectionMode
* @param projectionMode
*/
setProjectionMode(projectionMode: ProjectionMode): boolean;

/**
* Find the data array to use for orientation in the input polydata ( @see getOrientationArrayName )
*/
Expand Down
9 changes: 9 additions & 0 deletions 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 { ProjectionMode } from './Constants';

const { vtkErrorMacro } = macro;

Expand Down Expand Up @@ -306,6 +307,8 @@ function vtkImageCPRMapper(publicAPI, model) {
});
};

publicAPI.isProjectionEnabled = () => model.projectionSlabNumberOfSamples > 1;

publicAPI.setCenterlineData = (centerlineData) =>
publicAPI.setInputData(centerlineData, 1);

Expand Down Expand Up @@ -338,6 +341,9 @@ const DEFAULT_VALUES = {
tangentDirection: [1, 0, 0],
bitangentDirection: [0, 1, 0],
normalDirection: [0, 0, 1],
projectionSlabThickness: 1,
projectionSlabNumberOfSamples: 1,
projectionMode: ProjectionMode.MAX,
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -365,6 +371,9 @@ export function extend(publicAPI, model, initialValues = {}) {
'tangentDirection',
'bitangentDirection',
'normalDirection',
'projectionSlabThickness',
'projectionSlabNumberOfSamples',
'projectionMode',
]);
CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);

Expand Down
Loading

0 comments on commit f885afe

Please sign in to comment.