Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(surface rendering): Add surface rendering as segmentation representation #808

Merged
merged 37 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0978f17
feat: add surface segmentation representation
sedghi Feb 3, 2023
86b8f81
fix examples
sedghi Feb 3, 2023
7214c35
revert
sedghi Jul 1, 2023
c849e78
Merge branch 'main' of github.com:cornerstonejs/cornerstone3D into fe…
sedghi Sep 28, 2023
095d5eb
rename
sedghi Sep 28, 2023
b4c4216
webpack
sedghi Sep 28, 2023
f3845b8
webpack
sedghi Sep 28, 2023
193c34c
Add surface clipping via filter
rodrigobasilio2022 Oct 3, 2023
4a5014e
Reset camera clipping range
rodrigobasilio2022 Oct 4, 2023
862bb8a
refactor how viewport updates clipping planes
rodrigobasilio2022 Oct 4, 2023
28f2dc5
Add polydata cache
rodrigobasilio2022 Oct 4, 2023
9bfd377
Add event for clipping planes update
rodrigobasilio2022 Oct 4, 2023
c48c39f
Stack surface example created
rodrigobasilio2022 Oct 5, 2023
cc3411e
Refactor imageSlice creation
rodrigobasilio2022 Oct 5, 2023
7023837
fix polydata not showing in stack
rodrigobasilio2022 Oct 5, 2023
07a4da4
Adding a RT stack example
rodrigobasilio2022 Oct 5, 2023
8964f7f
Create SegmentationIntersectionTool
rodrigobasilio2022 Oct 6, 2023
6f00829
fix
sedghi Oct 6, 2023
022939a
Remove duplicate points
rodrigobasilio2022 Oct 6, 2023
8f3ad71
Fix color bug and remove duplicate points
rodrigobasilio2022 Oct 6, 2023
3ada3e2
Fix bug multiple polylines
rodrigobasilio2022 Oct 9, 2023
3d0c578
Refactor unwanted canAdded planes variable
rodrigobasilio2022 Oct 9, 2023
3492cd5
Restore file
rodrigobasilio2022 Oct 9, 2023
350c91b
Add comments in changed files
rodrigobasilio2022 Oct 9, 2023
1a87432
minor refactoring to build and update api
rodrigobasilio2022 Oct 9, 2023
48d7e55
Fix minor regression bug
rodrigobasilio2022 Oct 9, 2023
8d85975
Merge branch 'main' of github.com:cornerstonejs/cornerstone3D into pr…
sedghi Oct 18, 2023
0ea6c17
cleanup
sedghi Oct 18, 2023
d75e8f8
wip
sedghi Oct 18, 2023
080cadc
wip
sedghi Oct 18, 2023
0bfff5a
cleanup
sedghi Oct 18, 2023
b0e116d
fix the performance issue with zoom and pan revisiting same slice
sedghi Oct 18, 2023
0bdc040
fix reconstructed views
sedghi Oct 19, 2023
d0119f1
api
sedghi Oct 19, 2023
654c455
demo
sedghi Oct 19, 2023
57d0833
doc
sedghi Oct 19, 2023
62f3e44
fix vtk actors
sedghi Oct 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion common/reviews/api/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,9 @@ export { geometryLoader }
// @public (undocumented)
enum GeometryType {
// (undocumented)
CONTOUR = "contour"
CONTOUR = "contour",
// (undocumented)
SURFACE = "Surface"
}

// @public (undocumented)
Expand Down Expand Up @@ -2076,6 +2078,14 @@ type PTScaling = {
// @public (undocumented)
type PublicContourSetData = ContourSetData;

// @public (undocumented)
type PublicSurfaceData = {
id: string;
data: SurfaceData;
frameOfReferenceUID: string;
color?: Point3;
};

// @public (undocumented)
type PublicViewportInput = {
element: HTMLDivElement;
Expand Down Expand Up @@ -2399,6 +2409,12 @@ type StackViewportScrollEventDetail = {
direction: number;
};

// @public (undocumented)
type SurfaceData = {
points: number[];
polys: number[];
};

// @public (undocumented)
function threePlaneIntersection(firstPlane: Plane, secondPlane: Plane, thirdPlane: Plane): Point3;

Expand Down
8 changes: 8 additions & 0 deletions common/reviews/api/streaming-image-volume-loader.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,14 @@ type PTScaling = {
// @public (undocumented)
type PublicContourSetData = ContourSetData;

// @public (undocumented)
type PublicSurfaceData = {
id: string;
data: SurfaceData;
frameOfReferenceUID: string;
color?: Point3;
};

// @public
type PublicViewportInput = {
element: HTMLDivElement;
Expand Down
20 changes: 19 additions & 1 deletion common/reviews/api/tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4043,6 +4043,14 @@ type PTScaling = {
// @public (undocumented)
type PublicContourSetData = ContourSetData;

// @public (undocumented)
type PublicSurfaceData = {
id: string;
data: SurfaceData;
frameOfReferenceUID: string;
color?: Point3;
};

// @public (undocumented)
type PublicToolProps = SharedToolProp & {
name?: string;
Expand Down Expand Up @@ -4500,6 +4508,7 @@ export function removeTool(ToolClass: any): void;
type RepresentationConfig = {
LABELMAP?: LabelmapConfig;
CONTOUR?: ContourConfig;
SURFACE?: any;
};

// @public (undocumented)
Expand Down Expand Up @@ -4720,6 +4729,7 @@ type SegmentationRepresentationConfig = {
type SegmentationRepresentationData = {
LABELMAP?: LabelmapSegmentationData;
CONTOUR?: ContourSegmentationData;
SURFACE?: SurfaceSegmentationData;
};

// @public (undocumented)
Expand All @@ -4745,7 +4755,9 @@ enum SegmentationRepresentations {
// (undocumented)
Contour = "CONTOUR",
// (undocumented)
Labelmap = "LABELMAP"
Labelmap = "LABELMAP",
// (undocumented)
Surface = "SURFACE"
}

// @public (undocumented)
Expand Down Expand Up @@ -5082,6 +5094,12 @@ type StyleSpecifier = {
annotationUID?: string;
};

// @public (undocumented)
type SurfaceData = {
points: number[];
polys: number[];
};

// @public (undocumented)
type SVGCursorDescriptor = {
iconContent: string;
Expand Down
6 changes: 3 additions & 3 deletions packages/core/examples/volumeBasic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ async function run() {
// Get Cornerstone imageIds and fetch metadata into RAM
const imageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID:
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',
'1.2.276.0.7230010.3.1.2.531406877.716565.1673190654.866619',
SeriesInstanceUID:
'1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561',
wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',
'1.2.276.0.7230010.3.1.3.531406877.716565.1673190655.866747',
wadoRsRoot: 'http://localhost/dicom-web
});

// Instantiate a rendering engine
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@cornerstonejs/core",
"version": "1.19.1",
"description": "",
"main": "dist/umd/index.js",
"main": "src/index.ts",
"types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js",
"repository": "https://github.com/cornerstonejs/cornerstone3D",
Expand Down
68 changes: 52 additions & 16 deletions packages/core/src/RenderingEngine/Viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class Viewport implements IViewport {
readonly defaultOptions: any;
/** options for the viewport which includes orientation axis, backgroundColor and displayArea */
options: ViewportInputOptions;
/** informs if a new actor was added before a resetCameraClippingRange phase */
protected newActorAdded;
private _suppressCameraModifiedEvents = false;
/** A flag representing if viewport methods should fire events or not */
readonly suppressEvents: boolean;
Expand Down Expand Up @@ -109,6 +111,7 @@ class Viewport implements IViewport {
: false;
this.options = _cloneDeep(props.defaultOptions);
this.isDisabled = false;
this.newActorAdded = false;
}

getRotation: () => number;
Expand Down Expand Up @@ -481,6 +484,7 @@ class Viewport implements IViewport {
const renderer = this.getRenderer();
renderer.addActor(actor);
this._actors.set(actorUID, Object.assign({}, actorEntry));
this.newActorAdded = true;
}

/**
Expand Down Expand Up @@ -1080,20 +1084,34 @@ class Viewport implements IViewport {
vtkCamera.setClippingRange(clippingRange);
}

// update clippingPlanes if volume viewports
const actorEntry = this.getDefaultActor();

if (!actorEntry || !actorEntry.actor) {
return;
}

const isImageSlice = actorIsA(actorEntry, 'vtkImageSlice');
// update clipping range only if focal point changed of a new actor is added
const oldCamera = previousCamera;
const oldFocalPoint = oldCamera.focalPoint;
if (oldFocalPoint && focalPoint) {
const currentViewPlaneNormal = <Point3>vtkCamera.getViewPlaneNormal();
const deltaCamera = <Point3>[
focalPoint[0] - oldFocalPoint[0],
focalPoint[1] - oldFocalPoint[1],
focalPoint[2] - oldFocalPoint[2],
];

if (!isImageSlice) {
this.updateClippingPlanesForActors(updatedCamera);
} else {
const renderer = this.getRenderer();
renderer.resetCameraClippingRange();
const cameraModifiedInPlane =
Math.abs(vtkMath.dot(deltaCamera, currentViewPlaneNormal)) > 1e-2;

if (cameraModifiedInPlane || this.newActorAdded) {
const actorEntry = this.getDefaultActor();
if (!actorEntry || !actorEntry.actor) {
return;
}

const isImageSlice = actorIsA(actorEntry, 'vtkImageSlice');
if (!isImageSlice) {
this.updateClippingPlanesForActors(updatedCamera);
} else {
const renderer = this.getRenderer();
renderer.resetCameraClippingRange();
}
}
}

if (storeAsInitialCamera) {
Expand Down Expand Up @@ -1133,9 +1151,9 @@ class Viewport implements IViewport {
* Updates the actors clipping planes orientation from the camera properties
* @param updatedCamera - ICamera
*/
protected updateClippingPlanesForActors(updatedCamera: ICamera): void {
protected async updateClippingPlanesForActors(updatedCamera: ICamera): void {
const actorEntries = this.getActors();
actorEntries.forEach((actorEntry) => {
await actorEntries.forEach(async (actorEntry) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is async here that you need an await?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because if we remove the await, the 3d volume render will not correctly call the renderer.resetCameraClippingRange();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But which part of the the inner functions is asynchronous? Do you know?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I understand what you are saying... I really dont know.... Any of these functionas appears to be the one. I remember if I removed this await if will not render correctly the volumeViewport3D. Maybe you can do this test again...

// we assume that the first two clipping plane of the mapper are always
// the 'camera' clipping. Update clipping planes only if the actor is
// a vtkVolume
Expand All @@ -1144,7 +1162,13 @@ class Viewport implements IViewport {
}

const mapper = actorEntry.actor.getMapper();
const vtkPlanes = mapper.getClippingPlanes();
let vtkPlanes = actorEntry?.clippingFilter
? actorEntry.clippingFilter.getClippingPlanes()
: mapper.getClippingPlanes();

if (!vtkPlanes?.length) {
vtkPlanes = [vtkPlane.newInstance(), vtkPlane.newInstance()];
}

let slabThickness = RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
if (actorEntry.slabThickness) {
Expand All @@ -1159,7 +1183,19 @@ class Viewport implements IViewport {
viewPlaneNormal,
focalPoint
);
if (actorEntry?.clippingFilter) {
const clippingFilter = actorEntry.clippingFilter;
clippingFilter.setClippingPlanes(vtkPlanes);
clippingFilter.update();
const filteredData = clippingFilter.getOutputData();
mapper.setInputData(filteredData);
}
});
if (this.newActorAdded) {
const renderer = this.getRenderer();
renderer.resetCameraClippingRange();
this.newActorAdded = false;
}
}

public setOrientationOfClippingPlanes(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/RenderingEngine/VolumeViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ class VolumeViewport extends BaseVolumeViewport {
const mapper = actorEntry.actor.getMapper();
const vtkPlanes = mapper.getClippingPlanes();

if (vtkPlanes.length === 0) {
if (vtkPlanes.length === 0 && !actorEntry?.clippingFilter) {
const clipPlane1 = vtkPlane.newInstance();
const clipPlane2 = vtkPlane.newInstance();
const newVtkPlanes = [clipPlane1, clipPlane2];
Expand Down
49 changes: 49 additions & 0 deletions packages/core/src/cache/classes/Surface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { SurfaceData, Point3 } from '../../types';

type SurfaceProps = {
id: string;
data: SurfaceData;
frameOfReferenceUID: string;
color?: Point3;
};

/**
* Surface class for storing surface data
*/
export class Surface {
readonly id: string;
readonly sizeInBytes: number;
readonly frameOfReferenceUID: string;
private color: Point3 = [200, 0, 0]; // default color
private points: number[];
private polys: number[];

constructor(props: SurfaceProps) {
this.id = props.id;
this.points = props.data.points;
this.polys = props.data.polys;
this.color = props.color ?? this.color;
this.frameOfReferenceUID = props.frameOfReferenceUID;
this.sizeInBytes = this._getSizeInBytes();
}

_getSizeInBytes(): number {
return this.points.length * 4 + this.polys.length * 4;
}

public getColor(): Point3 {
return this.color;
}

public getPoints(): number[] {
return this.points;
}

public getPolys(): number[] {
return this.polys;
}

public getSizeInBytes(): number {
return this.sizeInBytes;
}
}
1 change: 1 addition & 0 deletions packages/core/src/enums/GeometryType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
enum GeometryType {
CONTOUR = 'contour',
SURFACE = 'Surface',
}

export default GeometryType;
17 changes: 13 additions & 4 deletions packages/core/src/loaders/geometryLoader.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import '@kitware/vtk.js/Rendering/Profiles/Geometry';

import cache from '../cache';
import { ContourSet } from '../cache/classes/ContourSet';
import { GeometryType } from '../enums';
import { IGeometry, PublicContourSetData } from '../types';
import { IGeometry, PublicContourSetData, PublicSurfaceData } from '../types';
import { createContourSet } from './utils/contourSet/createContourSet';
import { createSurface } from './utils/surface/createSurface';

type GeometryOptions = {
type: GeometryType;
geometryData: PublicContourSetData; // | PublicClosedSurfaceData, ...
geometryData: PublicContourSetData | PublicSurfaceData;
};

/**
Expand All @@ -33,7 +34,15 @@ async function createAndCacheGeometry(
}

if (options.type === GeometryType.CONTOUR) {
geometry = _createContourSet(geometryId, options.geometryData);
geometry = createContourSet(
geometryId,
options.geometryData as PublicContourSetData
);
} else if (options.type === GeometryType.SURFACE) {
geometry = createSurface(
geometryId,
options.geometryData as PublicSurfaceData
);
} else {
throw new Error('Unknown geometry type, Only CONTOUR is supported');
}
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/loaders/utils/contourSet/createContourSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { IGeometry, PublicContourSetData } from '../../../types';
import { GeometryType } from '../../../enums';
import { validateContourSet } from './validateContourSet';
import { ContourSet } from '../../../cache/classes/ContourSet';

export function createContourSet(
geometryId: string,
contourSetData: PublicContourSetData
) {
// validate the data to make sure it is a valid contour set
validateContourSet(contourSetData);

const contourSet = new ContourSet({
id: contourSetData.id,
data: contourSetData.data,
color: contourSetData.color,
frameOfReferenceUID: contourSetData.frameOfReferenceUID,
});

const geometry: IGeometry = {
id: geometryId,
type: GeometryType.CONTOUR,
data: contourSet,
sizeInBytes: contourSet.getSizeInBytes(),
};

return geometry;
}
Loading