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:
IbrahimandJoe Boccanfuso authored and GitHub committed 2024-04-02 09:35:30 -04:00
1 parent 07301fc020
commit b7f90e3951
164 files changed
+5887 -932

No files matched your search

@@ -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>