feat: add support to scoord3d (#5016)
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
1 parent
af40f06524
commit
735405a855
73 files changed
+1032
-993
No files matched your search
@@ -38,10 +38,10 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.30.3",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.32.5",
|
||||
"@ohif/core": "3.11.0-beta.114",
|
||||
"@ohif/ui": "3.11.0-beta.114",
|
||||
"dcmjs": "^0.42.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
@@ -51,12 +51,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.30.3",
|
||||
"@cornerstonejs/ai": "^3.30.3",
|
||||
"@cornerstonejs/core": "^3.30.3",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.30.3",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.30.3",
|
||||
"@cornerstonejs/tools": "^3.30.3",
|
||||
"@cornerstonejs/adapters": "^3.32.5",
|
||||
"@cornerstonejs/ai": "^3.32.5",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.32.5",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@@ -13,7 +13,6 @@ import CinePlayer from '../components/CinePlayer';
|
||||
import type { Types } from '@ohif/core';
|
||||
|
||||
import OHIFViewportActionCorners from '../components/OHIFViewportActionCorners';
|
||||
import ViewportColorbarsContainer from '../components/ViewportColorbar';
|
||||
import { getViewportPresentations } from '../utils/presentations/getViewportPresentations';
|
||||
import { useSynchronizersStore } from '../stores/useSynchronizersStore';
|
||||
import ActiveViewportBehavior from '../utils/ActiveViewportBehavior';
|
||||
@@ -305,31 +304,6 @@ const OHIFCornerstoneViewport = React.memo(
|
||||
loadViewportData();
|
||||
}, [viewportOptions, displaySets, dataSource]);
|
||||
|
||||
/**
|
||||
* There are two scenarios for jump to click
|
||||
* 1. Current viewports contain the displaySet that the annotation was drawn on
|
||||
* 2. Current viewports don't contain the displaySet that the annotation was drawn on
|
||||
* and we need to change the viewports displaySet for jumping.
|
||||
* Since measurement_jump happens via events and listeners, the former case is handled
|
||||
* by the measurement_jump direct callback, but the latter case is handled first by
|
||||
* the viewportGrid to set the correct displaySet on the viewport, AND THEN we check
|
||||
* the cache for jumping to see if there is any jump queued, then we jump to the correct slice.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (isJumpToMeasurementDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { unsubscribe } = measurementService.subscribe(
|
||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
|
||||
event => handleJumpToMeasurement(event, elementRef, viewportId, cornerstoneViewportService)
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [displaySets, elementRef, viewportId, isJumpToMeasurementDisabled, servicesManager]);
|
||||
|
||||
const Notification = customizationService.getCustomization('ui.notificationComponent');
|
||||
|
||||
return (
|
||||
@@ -387,54 +361,6 @@ const OHIFCornerstoneViewport = React.memo(
|
||||
areEqual
|
||||
);
|
||||
|
||||
// Helper function to handle jumping to measurements
|
||||
function handleJumpToMeasurement(event, elementRef, viewportId, cornerstoneViewportService) {
|
||||
const { measurement, isConsumed } = event;
|
||||
if (!measurement || isConsumed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const enabledElement = getEnabledElement(elementRef.current);
|
||||
|
||||
if (!enabledElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = enabledElement.viewport as csTypes.IStackViewport | csTypes.IVolumeViewport;
|
||||
|
||||
const { metadata, displaySetInstanceUID } = measurement;
|
||||
|
||||
const viewportDisplaySets = cornerstoneViewportService.getViewportDisplaySets(viewportId);
|
||||
|
||||
const showingDisplaySet = viewportDisplaySets.find(
|
||||
ds => ds.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
|
||||
let metadataToUse = metadata;
|
||||
// if it is not showing the displaySet we need to remove the FOR from the metadata
|
||||
if (!showingDisplaySet) {
|
||||
metadataToUse = {
|
||||
...metadata,
|
||||
FrameOfReferenceUID: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// Todo: make it work with cases where we want to define FOR based measurements too
|
||||
if (!viewport.isReferenceViewable(metadataToUse, WITH_NAVIGATION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
viewport.setViewReference(metadata);
|
||||
viewport.render();
|
||||
} catch (e) {
|
||||
console.warn('Unable to apply', metadata, e);
|
||||
}
|
||||
|
||||
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
|
||||
event?.consume?.();
|
||||
}
|
||||
|
||||
function _rehydrateSynchronizers(viewportId: string, syncGroupService: any) {
|
||||
const { synchronizersStore } = useSynchronizersStore.getState();
|
||||
const synchronizers = synchronizersStore[viewportId];
|
||||
@@ -558,16 +484,4 @@ function areEqual(prevProps, nextProps) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper function to check if display sets have changed
|
||||
function haveDisplaySetsChanged(prevDisplaySets, currentDisplaySets) {
|
||||
if (prevDisplaySets.length !== currentDisplaySets.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return currentDisplaySets.some((currentDS, index) => {
|
||||
const prevDS = prevDisplaySets[index];
|
||||
return currentDS.displaySetInstanceUID !== prevDS.displaySetInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
export default OHIFCornerstoneViewport;
|
||||
@@ -146,6 +146,70 @@ function commandsModule({
|
||||
}
|
||||
|
||||
const actions = {
|
||||
jumpToMeasurementViewport: ({ annotationUID, measurement }) => {
|
||||
cornerstoneTools.annotation.selection.setAnnotationSelected(annotationUID, true);
|
||||
const { metadata } = measurement;
|
||||
|
||||
const activeViewportId = viewportGridService.getActiveViewportId();
|
||||
// Finds the best viewport to jump to for showing the annotation view reference
|
||||
// This may be different from active if there is a viewport already showing the display set.
|
||||
const viewportId = cornerstoneViewportService.findNavigationCompatibleViewportId(
|
||||
activeViewportId,
|
||||
metadata
|
||||
);
|
||||
if (viewportId) {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
viewport.setViewReference(metadata);
|
||||
viewport.render();
|
||||
return;
|
||||
}
|
||||
|
||||
const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
|
||||
if (!referencedDisplaySetInstanceUID) {
|
||||
console.warn('ViewportGrid::No display set found in', measurement);
|
||||
return;
|
||||
}
|
||||
|
||||
// Finds the viewport to update to show the given displayset/orientation.
|
||||
// This will choose a view already containing the measurement display set
|
||||
// if possible, otherwise will fallback to the active.
|
||||
const viewportToUpdate = cornerstoneViewportService.findUpdateableViewportConfiguration(
|
||||
activeViewportId,
|
||||
measurement
|
||||
);
|
||||
|
||||
if (!viewportToUpdate) {
|
||||
console.warn('Unable to find a viewport to show this in');
|
||||
return;
|
||||
}
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportToUpdate.viewportId,
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
if (!updatedViewports?.[0]) {
|
||||
console.warn(
|
||||
'ViewportGrid::Unable to navigate to viewport containing',
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
updatedViewports[0].viewportOptions = viewportToUpdate.viewportOptions;
|
||||
|
||||
// Update stored position presentation
|
||||
commandsManager.run('updateStoredPositionPresentation', {
|
||||
viewportId: viewportToUpdate.viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySetInstanceUID],
|
||||
referencedImageId: measurement.referencedImageId,
|
||||
options: {
|
||||
...measurement.metadata,
|
||||
},
|
||||
});
|
||||
|
||||
commandsManager.run('setDisplaySetsForViewports', { viewportsToUpdate: updatedViewports });
|
||||
},
|
||||
|
||||
hydrateSecondaryDisplaySet: async ({ displaySet, viewportId }) => {
|
||||
if (!displaySet) {
|
||||
return;
|
||||
@@ -328,6 +392,8 @@ function commandsModule({
|
||||
type,
|
||||
});
|
||||
},
|
||||
|
||||
/** Stores the changed position presentation */
|
||||
updateStoredPositionPresentation: ({
|
||||
viewportId,
|
||||
displaySetInstanceUIDs,
|
||||
@@ -351,22 +417,23 @@ function commandsModule({
|
||||
([key, value]) => {
|
||||
return (
|
||||
displaySetInstanceUIDs.every(uid => key.includes(uid)) &&
|
||||
value.viewportId === viewportId
|
||||
value?.viewportId === viewportId
|
||||
);
|
||||
}
|
||||
)?.[0];
|
||||
}
|
||||
|
||||
// Create presentation data with referencedImageId and options if provided
|
||||
const presentationData = referencedImageId
|
||||
? {
|
||||
...presentations.positionPresentation,
|
||||
viewReference: {
|
||||
referencedImageId,
|
||||
...options,
|
||||
},
|
||||
}
|
||||
: presentations.positionPresentation;
|
||||
const presentationData =
|
||||
referencedImageId || options?.FrameOfReferenceUID
|
||||
? {
|
||||
...presentations.positionPresentation,
|
||||
viewReference: {
|
||||
referencedImageId,
|
||||
...options,
|
||||
},
|
||||
}
|
||||
: presentations.positionPresentation;
|
||||
|
||||
if (previousReferencedDisplaySetStoreKey) {
|
||||
setPositionPresentation(previousReferencedDisplaySetStoreKey, presentationData);
|
||||
@@ -2153,9 +2220,7 @@ function commandsModule({
|
||||
updateMeasurement: {
|
||||
commandFn: actions.updateMeasurement,
|
||||
},
|
||||
jumpToMeasurement: {
|
||||
commandFn: actions.jumpToMeasurement,
|
||||
},
|
||||
jumpToMeasurement: actions.jumpToMeasurement,
|
||||
removeMeasurement: {
|
||||
commandFn: actions.removeMeasurement,
|
||||
},
|
||||
@@ -2415,6 +2480,7 @@ function commandsModule({
|
||||
startRecordingForAnnotationGroup: actions.startRecordingForAnnotationGroup,
|
||||
endRecordingForAnnotationGroup: actions.endRecordingForAnnotationGroup,
|
||||
toggleSegmentLabel: actions.toggleSegmentLabel,
|
||||
jumpToMeasurementViewport: actions.jumpToMeasurementViewport,
|
||||
initializeSegmentLabelTool: actions.initializeSegmentLabelTool,
|
||||
};
|
||||
|
||||
|
||||
@@ -24,36 +24,38 @@ export const groupByStudy = (items, grouping, childProps) => {
|
||||
|
||||
let firstSelected, firstGroup;
|
||||
|
||||
items.forEach(item => {
|
||||
const studyUID = getItemStudyInstanceUID(item);
|
||||
if (!groups.has(studyUID)) {
|
||||
const items = [];
|
||||
const filter = MeasurementFilters.filterAnd(
|
||||
MeasurementFilters.filterMeasurementsByStudyUID(studyUID),
|
||||
grouping.filter
|
||||
);
|
||||
const group = {
|
||||
...grouping,
|
||||
items,
|
||||
displayMeasurements: items,
|
||||
key: studyUID,
|
||||
isSelected: studyUID === activeStudyUID,
|
||||
StudyInstanceUID: studyUID,
|
||||
filter,
|
||||
measurementFilter: filter,
|
||||
};
|
||||
if (group.isSelected && !firstSelected) {
|
||||
firstSelected = group;
|
||||
items
|
||||
.filter(item => item.displaySetInstanceUID)
|
||||
.forEach(item => {
|
||||
const studyUID = getItemStudyInstanceUID(item);
|
||||
if (!groups.has(studyUID)) {
|
||||
const items = [];
|
||||
const filter = MeasurementFilters.filterAnd(
|
||||
MeasurementFilters.filterMeasurementsByStudyUID(studyUID),
|
||||
grouping.filter
|
||||
);
|
||||
const group = {
|
||||
...grouping,
|
||||
items,
|
||||
displayMeasurements: items,
|
||||
key: studyUID,
|
||||
isSelected: studyUID === activeStudyUID,
|
||||
StudyInstanceUID: studyUID,
|
||||
filter,
|
||||
measurementFilter: filter,
|
||||
};
|
||||
if (group.isSelected && !firstSelected) {
|
||||
firstSelected = group;
|
||||
}
|
||||
firstGroup ||= group;
|
||||
groups.set(studyUID, group);
|
||||
}
|
||||
firstGroup ||= group;
|
||||
groups.set(studyUID, group);
|
||||
}
|
||||
if (!firstSelected && firstGroup) {
|
||||
firstGroup.isSelected = true;
|
||||
}
|
||||
const group = groups.get(studyUID);
|
||||
group.items.push(item);
|
||||
});
|
||||
if (!firstSelected && firstGroup) {
|
||||
firstGroup.isSelected = true;
|
||||
}
|
||||
const group = groups.get(studyUID);
|
||||
group.items.push(item);
|
||||
});
|
||||
|
||||
return groups;
|
||||
};
|
||||
|
||||
@@ -34,15 +34,20 @@ export default {
|
||||
if (!viewportId) {
|
||||
return [];
|
||||
}
|
||||
const displaySetInsaneUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
const displaySetInstanceUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
|
||||
if (!displaySetInsaneUIDs) {
|
||||
if (!displaySetInstanceUIDs) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const displaySets = displaySetInsaneUIDs.map(uid =>
|
||||
displaySetService.getDisplaySetByUID(uid)
|
||||
);
|
||||
const displaySets = displaySetInstanceUIDs.map(uid => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(uid);
|
||||
const referencedDisplaySetUID = displaySet?.measurements?.[0]?.displaySetInstanceUID;
|
||||
if (displaySet.Modality === 'SR' && referencedDisplaySetUID) {
|
||||
return displaySetService.getDisplaySetByUID(referencedDisplaySetUID);
|
||||
}
|
||||
return displaySet;
|
||||
});
|
||||
|
||||
return hangingProtocols
|
||||
.map(hp => {
|
||||
|
||||
@@ -279,7 +279,7 @@ export function useViewportRendering(
|
||||
}
|
||||
|
||||
// Get threshold from colormap if available
|
||||
if (properties.colormap && properties.colormap.threshold !== undefined) {
|
||||
if (properties?.colormap && properties.colormap.threshold !== undefined) {
|
||||
setThresholdState(properties.colormap.threshold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ const cornerstoneExtension: Types.Extensions.Extension = {
|
||||
{
|
||||
name: 'cornerstone',
|
||||
component: ExtendedOHIFCornerstoneViewport,
|
||||
isReferenceViewable: props => utils.isReferenceViewable({ ...props, servicesManager }),
|
||||
isReferenceViewable: utils.isReferenceViewable.bind(null, servicesManager),
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -189,17 +189,13 @@ export default async function init({
|
||||
initCineService(servicesManager);
|
||||
initStudyPrefetcherService(servicesManager);
|
||||
|
||||
[
|
||||
measurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
|
||||
measurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
|
||||
].forEach(event => {
|
||||
measurementService.subscribe(event, evt => {
|
||||
const { measurement } = evt;
|
||||
const { uid: annotationUID } = measurement;
|
||||
cornerstoneTools.annotation.selection.setAnnotationSelected(annotationUID, true);
|
||||
});
|
||||
measurementService.subscribe(measurementService.EVENTS.JUMP_TO_MEASUREMENT, evt => {
|
||||
const { measurement } = evt;
|
||||
const { uid: annotationUID } = measurement;
|
||||
commandsManager.runCommand('jumpToMeasurementViewport', { measurement, annotationUID, evt });
|
||||
});
|
||||
|
||||
|
||||
// When a custom image load is performed, update the relevant viewports
|
||||
hangingProtocolService.subscribe(
|
||||
hangingProtocolService.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED,
|
||||
|
||||
@@ -436,7 +436,7 @@ const connectMeasurementServiceToTools = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const { referenceSeriesUID, referenceStudyUID, SOPInstanceUID } = measurement;
|
||||
const { referenceSeriesUID, referenceStudyUID, SOPInstanceUID, metadata } = measurement;
|
||||
|
||||
const instance = DicomMetadataStore.getInstance(
|
||||
referenceStudyUID,
|
||||
@@ -463,8 +463,12 @@ const connectMeasurementServiceToTools = ({
|
||||
annotationUID: measurement.uid,
|
||||
highlighted: false,
|
||||
isLocked: false,
|
||||
invalidated: false,
|
||||
// This is used to force a re-render of the annotation to
|
||||
// re-calculate cached stats since sometimes in SR we
|
||||
// get empty cached stats
|
||||
invalidated: true,
|
||||
metadata: {
|
||||
...metadata,
|
||||
toolName: measurement.toolName,
|
||||
FrameOfReferenceUID: measurement.FrameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
|
||||
@@ -36,7 +36,6 @@ import { useLutPresentationStore } from '../../stores/useLutPresentationStore';
|
||||
import { usePositionPresentationStore } from '../../stores/usePositionPresentationStore';
|
||||
import { useSynchronizersStore } from '../../stores/useSynchronizersStore';
|
||||
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
const EVENTS = {
|
||||
VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged',
|
||||
@@ -47,6 +46,7 @@ const MIN_STACK_VIEWPORTS_TO_ENQUEUE_RESIZE = 12;
|
||||
const MIN_VOLUME_VIEWPORTS_TO_ENQUEUE_RESIZE = 6;
|
||||
|
||||
export const WITH_NAVIGATION = { withNavigation: true, withOrientation: false };
|
||||
export const WITH_ORIENTATION = { withNavigation: true, withOrientation: true };
|
||||
|
||||
/**
|
||||
* Handles cornerstone viewport logic including enabling, disabling, and
|
||||
@@ -551,10 +551,13 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
* was initiated.
|
||||
* @return the viewportId that the measurement should be displayed in.
|
||||
*/
|
||||
public getViewportIdToJump(activeViewportId: string, metadata): string {
|
||||
public findNavigationCompatibleViewportId(activeViewportId: string, metadata): string {
|
||||
// First check if the active viewport can just be navigated to show the given item
|
||||
const activeViewport = this.getCornerstoneViewport(activeViewportId);
|
||||
if (activeViewport.isReferenceViewable(metadata, { withNavigation: true })) {
|
||||
if (!activeViewport) {
|
||||
console.warn('No active viewport found for', activeViewportId);
|
||||
}
|
||||
if (activeViewport?.isReferenceViewable(metadata, { withNavigation: true })) {
|
||||
return activeViewportId;
|
||||
}
|
||||
|
||||
@@ -570,7 +573,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// No viewport is in the right display set/orientation to show this, so see if
|
||||
// the active viewport could change orientations to show this
|
||||
if (
|
||||
activeViewport.isReferenceViewable(metadata, { withNavigation: true, withOrientation: true })
|
||||
activeViewport?.isReferenceViewable(metadata, { withNavigation: true, withOrientation: true })
|
||||
) {
|
||||
return activeViewportId;
|
||||
}
|
||||
@@ -589,6 +592,86 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out which viewport to update when the viewport type needs to change.
|
||||
* This may not be the active viewport if there is already a viewport showing
|
||||
* the display set, but in the wrong orientation.
|
||||
*
|
||||
* The viewport will need to update the viewport type and/or display set to
|
||||
* display the resulting data.
|
||||
*
|
||||
* The first choice will be a viewport already showing the correct display set,
|
||||
* but showing it as a stack.
|
||||
*
|
||||
* Second choice is to see if there is a viewport already showing the right
|
||||
* orientation for the image, but the wrong display set. This fixes the
|
||||
* case where the user is in MPR and a viewport other than active should be
|
||||
* the one to change to display the iamge.
|
||||
*
|
||||
* Final choice is to use the provide activeViewportId. This will cover
|
||||
* changes to/from video and wsi viewports and other cases where no
|
||||
* viewport is really even close to being able to display the measurement.
|
||||
*/
|
||||
public findUpdateableViewportConfiguration(activeViewportId: string, measurement) {
|
||||
const { metadata, displaySetInstanceUID } = measurement;
|
||||
const { volumeId, referencedImageId } = metadata;
|
||||
const { displaySetService, viewportGridService } = this.servicesManager.services;
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
let { viewportType } = displaySet;
|
||||
if (!viewportType) {
|
||||
if (referencedImageId && !displaySet.isReconstructable) {
|
||||
viewportType = csEnums.ViewportType.STACK;
|
||||
} else if (volumeId) {
|
||||
viewportType = 'volume';
|
||||
}
|
||||
}
|
||||
|
||||
// Find viewports that could be updated to be volumes to show this view
|
||||
// That prefers a viewport already showing the right display set.
|
||||
if (volumeId) {
|
||||
for (const id of this.viewportsById.keys()) {
|
||||
const viewport = this.getCornerstoneViewport(id);
|
||||
if (viewport?.isReferenceViewable(metadata, { asVolume: true, withNavigation: true })) {
|
||||
return {
|
||||
viewportId: id,
|
||||
displaySetInstanceUID,
|
||||
viewportOptions: { viewportType },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find a viewport in the correct orientation showing a different display set
|
||||
// which could be used to display the annotation.
|
||||
const altMetadata = { ...metadata, volumeId: null, referencedImageId: null };
|
||||
for (const id of this.viewportsById.keys()) {
|
||||
const viewport = this.getCornerstoneViewport(id);
|
||||
const viewportDisplaySetUID = viewportGridService.getDisplaySetsUIDsForViewport(id)?.[0];
|
||||
if (!viewportDisplaySetUID || !viewport) {
|
||||
continue;
|
||||
}
|
||||
if (volumeId) {
|
||||
altMetadata.volumeId = viewportDisplaySetUID;
|
||||
}
|
||||
altMetadata.FrameOfReferenceUID = this._getFrameOfReferenceUID(viewportDisplaySetUID);
|
||||
if (viewport.isReferenceViewable(altMetadata, { asVolume: true, withNavigation: true })) {
|
||||
return {
|
||||
viewportId: id,
|
||||
displaySetInstanceUID,
|
||||
viewportOptions: { viewportType },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Just display in the active viewport
|
||||
return {
|
||||
viewportId: activeViewportId,
|
||||
displaySetInstanceUID,
|
||||
viewportOptions: { viewportType },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image data for the given viewport.
|
||||
*/
|
||||
@@ -626,7 +709,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// is being used to navigate to the initial view position for measurement
|
||||
// navigation and other navigation forcing specific views.
|
||||
let initialImageIndexToUse =
|
||||
presentations?.positionPresentation?.initialImageIndex ?? initialImageIndex;
|
||||
presentations?.positionPresentation?.initialImageIndex ?? <number>initialImageIndex;
|
||||
|
||||
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
|
||||
|
||||
@@ -662,7 +745,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
initialImageIndexToUse = imageIds.indexOf(referencedImageId);
|
||||
}
|
||||
|
||||
if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) {
|
||||
if (
|
||||
initialImageIndexToUse === undefined ||
|
||||
initialImageIndexToUse === null ||
|
||||
initialImageIndexToUse < 0
|
||||
) {
|
||||
initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
|
||||
}
|
||||
|
||||
@@ -1085,8 +1172,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const { dimensions, spacing } = imageVolume;
|
||||
const slabThickness = Math.sqrt(
|
||||
Math.pow(dimensions[0] * spacing[0], 2) +
|
||||
Math.pow(dimensions[1] * spacing[1], 2) +
|
||||
Math.pow(dimensions[2] * spacing[2], 2)
|
||||
Math.pow(dimensions[1] * spacing[1], 2) +
|
||||
Math.pow(dimensions[2] * spacing[2], 2)
|
||||
);
|
||||
|
||||
return slabThickness;
|
||||
@@ -1214,7 +1301,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
): void {
|
||||
const viewRef = positionPresentation?.viewReference;
|
||||
if (viewRef) {
|
||||
if (viewport.isReferenceViewable(viewRef, WITH_NAVIGATION)) {
|
||||
// The orientation can be updated here to navigate to the specified
|
||||
// measurement or previous item, but this will not switch to volume
|
||||
// or to stack from the other type
|
||||
if (viewport.isReferenceViewable(viewRef, WITH_ORIENTATION)) {
|
||||
viewport.setViewReference(viewRef);
|
||||
} else {
|
||||
console.warn('Unable to apply reference viewable', viewRef);
|
||||
|
||||
@@ -3,20 +3,17 @@ import { Enums } from '@cornerstonejs/core';
|
||||
|
||||
import OrientationAxis = Enums.OrientationAxis;
|
||||
|
||||
export const isReferenceViewable = ({
|
||||
viewportId,
|
||||
reference,
|
||||
viewportOptions,
|
||||
servicesManager,
|
||||
}) => {
|
||||
export const isReferenceViewable = (servicesManager, viewportId, reference, viewportOptions?) => {
|
||||
const { cornerstoneViewportService, displaySetService } = servicesManager.services;
|
||||
|
||||
if (!viewportOptions) {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
// we can make a customization for this to allow
|
||||
// we can make a customization for this to allow specific settings
|
||||
// The annotation can be seen either via navigation or by changing to a volume
|
||||
const isViewable = viewport.isReferenceViewable(reference, {
|
||||
withNavigation: true,
|
||||
asVolume: true,
|
||||
});
|
||||
|
||||
return isViewable;
|
||||
|
||||
@@ -83,7 +83,7 @@ const Bidirectional = {
|
||||
|
||||
function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { metadata, data } = annotation;
|
||||
const { cachedStats } = data;
|
||||
const { cachedStats = {} } = data;
|
||||
const { referencedImageId } = metadata;
|
||||
const targets = Object.keys(cachedStats);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ const CircleROI = {
|
||||
|
||||
function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { metadata, data } = annotation;
|
||||
const { cachedStats } = data;
|
||||
const { cachedStats={} } = data;
|
||||
const { referencedImageId } = metadata;
|
||||
const targets = Object.keys(cachedStats);
|
||||
|
||||
|
||||
@@ -92,6 +92,11 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { metadata, data } = annotation;
|
||||
const { cachedStats } = data;
|
||||
const { referencedImageId } = metadata;
|
||||
|
||||
if( !cachedStats ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targets = Object.keys(cachedStats);
|
||||
|
||||
if (!targets.length) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { getIsLocked } from './utils/getIsLocked';
|
||||
import { getIsVisible } from './utils/getIsVisible';
|
||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||
import { utils } from '@ohif/core';
|
||||
import { config } from '@cornerstonejs/tools/annotation';
|
||||
|
||||
const Length = {
|
||||
toAnnotation: measurement => {},
|
||||
@@ -78,7 +77,6 @@ const Length = {
|
||||
isLocked,
|
||||
isVisible,
|
||||
metadata,
|
||||
// color,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
referencedImageId,
|
||||
|
||||
@@ -95,6 +95,11 @@ function getMappedAnnotations(annotation, displaySetService) {
|
||||
const { metadata, data } = annotation;
|
||||
const { cachedStats } = data;
|
||||
const { referencedImageId } = metadata;
|
||||
|
||||
if( !cachedStats ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targets = Object.keys(cachedStats);
|
||||
|
||||
if (!targets.length) {
|
||||
|
||||
@@ -211,9 +211,11 @@ function getDisplayText(mappedAnnotations, displaySet, customizationService) {
|
||||
mappedAnnotations.forEach(mappedAnnotation => {
|
||||
const { unit, max, SeriesNumber } = mappedAnnotation;
|
||||
|
||||
const maxStr = getStatisticDisplayString(max, unit, 'max');
|
||||
if (Number.isFinite(max)) {
|
||||
const maxStr = getStatisticDisplayString(max, unit, 'max');
|
||||
|
||||
displayText.primary.push(maxStr);
|
||||
displayText.primary.push(maxStr);
|
||||
}
|
||||
displayText.secondary.push(`S: ${SeriesNumber}${instanceText}${frameText}`);
|
||||
});
|
||||
|
||||
|
||||
+32
-5
@@ -1,5 +1,36 @@
|
||||
import * as cornerstone from '@cornerstonejs/core';
|
||||
|
||||
function getDisplaySet({ metadata, displaySetService }) {
|
||||
const { volumeId } = metadata;
|
||||
|
||||
if( volumeId ) {
|
||||
const displaySet = displaySetService.getDisplaySetsBy(displaySet =>
|
||||
volumeId.includes(displaySet.uid)
|
||||
)[0];
|
||||
if( displaySet ) {
|
||||
return displaySet;
|
||||
}
|
||||
console.warn("Unable to find volumeId", volumeId);
|
||||
metadata.volumeId = null;
|
||||
}
|
||||
|
||||
if (!metadata.FrameOfReferenceUID) {
|
||||
throw new Error(
|
||||
'No volumeId and no FrameOfReferenceUID provided. Could not find matching displaySet.'
|
||||
);
|
||||
}
|
||||
const displaySet = Array.from(displaySetService.getDisplaySetCache().values()).find(
|
||||
ds => ds.instance?.FrameOfReferenceUID === metadata.FrameOfReferenceUID
|
||||
);
|
||||
|
||||
if (!displaySet) {
|
||||
throw new Error('Could not find matching displaySet for the provided FrameOfReferenceUID.');
|
||||
}
|
||||
|
||||
return displaySet;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* It checks if the imageId is provided then it uses it to query
|
||||
* the metadata and get the SOPInstanceUID, SeriesInstanceUID and StudyInstanceUID.
|
||||
@@ -13,11 +44,7 @@ export default function getSOPInstanceAttributes(imageId, displaySetService, ann
|
||||
}
|
||||
|
||||
const { metadata } = annotation;
|
||||
const { volumeId } = metadata;
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetsBy(displaySet =>
|
||||
volumeId.includes(displaySet.uid)
|
||||
)[0];
|
||||
const displaySet = getDisplaySet({ metadata, displaySetService });
|
||||
const { StudyInstanceUID, SeriesInstanceUID } = displaySet;
|
||||
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user