feat(ViewportActionMenu): window level per viewport / new patient info / colorbars/ 3D presets and 3D volume rendering (#3963)
Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com>
This commit is contained in:
1 parent
07301fc020
commit
b7f90e3951
164 files changed
+5887
-932
No files matched your search
-10
@@ -401,15 +401,6 @@ function _mapDisplaySets(
|
||||
const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID];
|
||||
const componentType = _getComponentType(ds);
|
||||
const numPanes = viewportGridService.getNumViewportPanes();
|
||||
const viewportIdentificator = [];
|
||||
|
||||
if (numPanes !== 1) {
|
||||
viewports.forEach(viewportData => {
|
||||
if (viewportData?.displaySetInstanceUIDs?.includes(ds.displaySetInstanceUID)) {
|
||||
viewportIdentificator.push(viewportData.viewportLabel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const array =
|
||||
componentType === 'thumbnailTracked' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets;
|
||||
@@ -435,7 +426,6 @@ function _mapDisplaySets(
|
||||
},
|
||||
isTracked: trackedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID),
|
||||
isHydratedForDerivedDisplaySet: ds.isHydrated,
|
||||
viewportIdentificator,
|
||||
};
|
||||
|
||||
if (componentType === 'thumbnailNoImage') {
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import OHIF, { utils } from '@ohif/core';
|
||||
|
||||
import { ViewportActionBar, Tooltip, Icon } from '@ohif/ui';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip, Icon, ViewportActionArrows, useViewportGrid } from '@ohif/ui';
|
||||
|
||||
import { annotation } from '@cornerstonejs/tools';
|
||||
import { useTrackedMeasurements } from './../getContextModule';
|
||||
import { BaseVolumeViewport, Enums } from '@cornerstonejs/core';
|
||||
|
||||
const { formatDate } = utils;
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function TrackedCornerstoneViewport(props) {
|
||||
const { displaySets, viewportId, viewportLabel, servicesManager, extensionManager } = props;
|
||||
const { displaySets, viewportId, servicesManager, extensionManager } = props;
|
||||
|
||||
const { t } = useTranslation('Common');
|
||||
|
||||
const { measurementService, cornerstoneViewportService, viewportGridService } =
|
||||
servicesManager.services;
|
||||
const {
|
||||
measurementService,
|
||||
cornerstoneViewportService,
|
||||
viewportGridService,
|
||||
viewportActionCornersService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// Todo: handling more than one displaySet on the same viewport
|
||||
const displaySet = displaySets[0];
|
||||
const { t } = useTranslation('Common');
|
||||
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
const { activeViewportId } = viewportGrid;
|
||||
|
||||
const [trackedMeasurements, sendTrackedMeasurementsEvent] = useTrackedMeasurements();
|
||||
|
||||
@@ -31,18 +33,7 @@ function TrackedCornerstoneViewport(props) {
|
||||
|
||||
const { trackedSeries } = trackedMeasurements.context;
|
||||
|
||||
const { SeriesDate, SeriesDescription, SeriesInstanceUID, SeriesNumber } = displaySet;
|
||||
|
||||
const {
|
||||
PatientID,
|
||||
PatientName,
|
||||
PatientSex,
|
||||
PatientAge,
|
||||
SliceThickness,
|
||||
SpacingBetweenSlices,
|
||||
StudyDate,
|
||||
ManufacturerModelName,
|
||||
} = displaySet.images[0];
|
||||
const { SeriesInstanceUID } = displaySet;
|
||||
|
||||
const updateIsTracked = useCallback(() => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
@@ -170,22 +161,51 @@ function TrackedCornerstoneViewport(props) {
|
||||
};
|
||||
}, [measurementService, sendTrackedMeasurementsEvent, viewportId, viewportGridService]);
|
||||
|
||||
function switchMeasurement(direction) {
|
||||
const newTrackedMeasurementUID = _getNextMeasurementUID(
|
||||
direction,
|
||||
servicesManager,
|
||||
trackedMeasurementUID,
|
||||
trackedMeasurements
|
||||
const switchMeasurement = useCallback(
|
||||
direction => {
|
||||
const newTrackedMeasurementUID = _getNextMeasurementUID(
|
||||
direction,
|
||||
servicesManager,
|
||||
trackedMeasurementUID,
|
||||
trackedMeasurements
|
||||
);
|
||||
|
||||
if (!newTrackedMeasurementUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTrackedMeasurementUID(newTrackedMeasurementUID);
|
||||
|
||||
measurementService.jumpToMeasurement(viewportId, newTrackedMeasurementUID);
|
||||
},
|
||||
[measurementService, servicesManager, trackedMeasurementUID, trackedMeasurements, viewportId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const statusComponent = _getStatusComponent(isTracked, t);
|
||||
const arrowsComponent = _getArrowsComponent(
|
||||
isTracked,
|
||||
switchMeasurement,
|
||||
viewportId === activeViewportId
|
||||
);
|
||||
|
||||
if (!newTrackedMeasurementUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTrackedMeasurementUID(newTrackedMeasurementUID);
|
||||
|
||||
measurementService.jumpToMeasurement(viewportId, newTrackedMeasurementUID);
|
||||
}
|
||||
viewportActionCornersService.setComponents([
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportStatusComponent',
|
||||
component: statusComponent,
|
||||
indexPriority: -100,
|
||||
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||
},
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportActionArrowsComponent',
|
||||
component: arrowsComponent,
|
||||
indexPriority: 0,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
]);
|
||||
}, [activeViewportId, isTracked, switchMeasurement, viewportActionCornersService, viewportId]);
|
||||
|
||||
const getCornerstoneViewport = () => {
|
||||
const { component: Component } = extensionManager.getModuleEntry(
|
||||
@@ -203,35 +223,6 @@ function TrackedCornerstoneViewport(props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewportActionBar
|
||||
onDoubleClick={evt => {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
}}
|
||||
useAltStyling={isTracked}
|
||||
onArrowsClick={direction => switchMeasurement(direction)}
|
||||
getStatusComponent={() => _getStatusComponent(isTracked)}
|
||||
studyData={{
|
||||
label: viewportLabel,
|
||||
studyDate: formatDate(SeriesDate) || formatDate(StudyDate) || t('NoStudyDate'),
|
||||
currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based
|
||||
seriesDescription: SeriesDescription,
|
||||
patientInformation: {
|
||||
patientName: PatientName ? OHIF.utils.formatPN(PatientName) : '',
|
||||
patientSex: PatientSex || '',
|
||||
patientAge: PatientAge || '',
|
||||
MRN: PatientID || '',
|
||||
thickness: SliceThickness ? `${parseFloat(SliceThickness).toFixed(2)}` : '',
|
||||
thicknessUnits: t('mm'),
|
||||
spacing:
|
||||
SpacingBetweenSlices !== undefined
|
||||
? `${parseFloat(SpacingBetweenSlices).toFixed(2)}${t('mm')}`
|
||||
: '',
|
||||
scanner: ManufacturerModelName || '',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* TODO: Viewport interface to accept stack or layers of content like this? */}
|
||||
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||
{getCornerstoneViewport()}
|
||||
</div>
|
||||
@@ -291,18 +282,11 @@ function _getNextMeasurementUID(
|
||||
// Not tracking a measurement, or previous measurement now deleted, revert to 0.
|
||||
measurementIndex = 0;
|
||||
} else {
|
||||
if (direction === 'left') {
|
||||
measurementIndex--;
|
||||
|
||||
if (measurementIndex < 0) {
|
||||
measurementIndex = measurementCount - 1;
|
||||
}
|
||||
} else if (direction === 'right') {
|
||||
measurementIndex++;
|
||||
|
||||
if (measurementIndex === measurementCount) {
|
||||
measurementIndex = 0;
|
||||
}
|
||||
measurementIndex += direction;
|
||||
if (measurementIndex < 0) {
|
||||
measurementIndex = measurementCount - 1;
|
||||
} else if (measurementIndex === measurementCount) {
|
||||
measurementIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,9 +295,23 @@ function _getNextMeasurementUID(
|
||||
return newTrackedMeasurementId;
|
||||
}
|
||||
|
||||
function _getStatusComponent(isTracked) {
|
||||
const { t } = useTranslation('TrackedCornerstoneViewport');
|
||||
const trackedIcon = isTracked ? 'status-tracked' : 'status-untracked';
|
||||
const _getArrowsComponent = (isTracked, switchMeasurement, isActiveViewport) => {
|
||||
if (!isTracked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewportActionArrows
|
||||
onArrowsClick={direction => switchMeasurement(direction)}
|
||||
className={isActiveViewport ? 'visible' : 'invisible group-hover:visible'}
|
||||
></ViewportActionArrows>
|
||||
);
|
||||
};
|
||||
|
||||
function _getStatusComponent(isTracked, t) {
|
||||
if (!isTracked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
@@ -344,7 +342,7 @@ function _getStatusComponent(isTracked) {
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name={trackedIcon}
|
||||
name={'viewport-status-tracked'}
|
||||
className="text-aqua-pale"
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
Reference in new issue
Block a user