fix(bugs): enhancements and bug fixes (#4036)

This commit is contained in:
Ibrahim authored and GitHub committed 2024-04-16 09:01:06 -04:00
1 parent 41a8d105f1
commit e80fc6f477
58 files changed
+768 -148

No files matched your search

@@ -66,6 +66,11 @@ function _askHydrate(uiViewportDialogService, viewportId) {
uiViewportDialogService.hide();
resolve(RESPONSE.CANCEL);
},
onKeyPress: event => {
if (event.key === 'Enter') {
onSubmit(RESPONSE.HYDRATE_SEG);
}
},
});
});
}
@@ -63,6 +63,11 @@ function _askHydrate(uiViewportDialogService, viewportId) {
uiViewportDialogService.hide();
resolve(RESPONSE.CANCEL);
},
onKeyPress: event => {
if (event.key === 'Enter') {
onSubmit(RESPONSE.HYDRATE_SEG);
}
},
});
});
}
@@ -22,7 +22,7 @@ const Header = ({ title, tooltip }) => (
<div className="flex items-center space-x-1">
<Tooltip
content={<div className="text-white">{tooltip}</div>}
position="bottom"
position="bottom-left"
tight={true}
tooltipBoxClassName="max-w-xs"
>
@@ -68,6 +68,11 @@ export function Colormap({
key: index,
style: {
minWidth: `calc(100% / ${displaySets.length})`,
fontSize: '0.8rem',
textAlign: 'center',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
}));
}, [displaySets]);
@@ -60,7 +60,11 @@ export function VolumeLighting({
min={0}
type="range"
step={0.1}
style={{ background: calculateBackground(ambient) }}
style={{
background: calculateBackground(ambient),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
@@ -84,7 +88,11 @@ export function VolumeLighting({
min={0}
type="range"
step={0.1}
style={{ background: calculateBackground(diffuse) }}
style={{
background: calculateBackground(diffuse),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
@@ -109,7 +117,11 @@ export function VolumeLighting({
min={0}
type="range"
step={0.1}
style={{ background: calculateBackground(specular) }}
style={{
background: calculateBackground(specular),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
@@ -25,10 +25,10 @@ export function VolumeRenderingOptions({
commandsManager={commandsManager}
serviceManager={serviceManager}
/>
<div className="all-in-one-menu-item flex w-full justify-start">
<div className="all-in-one-menu-item flex !h-[20px] w-full justify-start">
<div className="text-aqua-pale text-[13px]">LIGHTING</div>
</div>
<AllInOneMenu.DividerItem />
<div className="bg-primary-dark mt-1 mb-1 h-[2px] w-full"></div>
<div className="all-in-one-menu-item flex w-full justify-center">
<VolumeShade
commandsManager={commandsManager}
@@ -23,7 +23,7 @@ export function VolumeRenderingPresets({
commandsManager,
},
containerDimensions: 'h-[543px] w-[460px]',
contentDimensions: 'h-[493px] w-[460px] pl-[12px]',
contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]',
});
};
@@ -60,7 +60,11 @@ export function VolumeRenderingQuality({
type="range"
step={step}
onChange={e => onChange(parseInt(e.target.value, 10))}
style={{ background: calculateBackground((quality - min) / (max - min)) }}
style={{
background: calculateBackground((quality - min) / (max - min)),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
@@ -82,6 +82,8 @@ export function VolumeShift({
step={step}
style={{
background: calculateBackground((shift - minShift) / (maxShift - minShift)),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
export type WindowLevelProps = {
viewportId: string;
presets: Record<string, Array<WindowLevelPreset>>;
presets: Array<Record<string, Array<WindowLevelPreset>>>;
commandsManager: CommandsManager;
};
@@ -23,25 +23,34 @@ export function WindowLevel({
commandName: 'setViewportWindowLevel',
commandOptions: {
...props,
viewportId,
},
context: 'CORNERSTONE',
});
},
[commandsManager]
[commandsManager, viewportId]
);
return (
<AllInOneMenu.ItemPanel>
<AllInOneMenu.HeaderItem>
{t('Modality Presets', { modality: Object.keys(presets)[0] })}
</AllInOneMenu.HeaderItem>
{Object.values(presets)[0].map((preset, index) => (
<AllInOneMenu.Item
key={index}
label={preset.description}
secondaryLabel={`${preset.window} / ${preset.level}`}
onClick={() => onSetWindowLevel({ ...preset, viewportId })}
></AllInOneMenu.Item>
{presets.map((modalityPresets, modalityIndex) => (
<React.Fragment key={modalityIndex}>
{Object.entries(modalityPresets).map(([modality, presetsArray]) => (
<React.Fragment key={modality}>
<AllInOneMenu.HeaderItem>
{t('Modality Presets', { modality })}
</AllInOneMenu.HeaderItem>
{presetsArray.map((preset, index) => (
<AllInOneMenu.Item
key={`${modality}-${index}`}
label={preset.description}
secondaryLabel={`${preset.window} / ${preset.level}`}
onClick={() => onSetWindowLevel(preset)}
/>
))}
</React.Fragment>
))}
</React.Fragment>
))}
</AllInOneMenu.ItemPanel>
);
@@ -19,7 +19,7 @@ import { utilities } from '@cornerstonejs/core';
export type WindowLevelActionMenuProps = {
viewportId: string;
element: HTMLElement;
presets: Record<string, Array<WindowLevelPreset>>;
presets: Array<Record<string, Array<WindowLevelPreset>>>;
verticalDirection: AllInOneMenu.VerticalDirection;
horizontalDirection: AllInOneMenu.HorizontalDirection;
commandsManager: CommandsManager;
@@ -128,10 +128,8 @@ export function WindowLevelActionMenu({
iconClassName={classNames(
// Visible on hover and for the active viewport
activeViewportId === viewportId ? 'visible' : 'invisible group-hover:visible',
'flex shrink-0 cursor-pointer rounded active:text-white',
isLight
? 'text-aqua-pale hover:bg-secondary-dark'
: 'text-primary-light hover:bg-secondary-light/60'
'flex shrink-0 cursor-pointer rounded active:text-white text-primary-light',
isLight ? ' hover:bg-secondary-dark' : 'hover:bg-secondary-light/60'
)}
menuStyle={{ maxHeight: vpHeight - 32, minWidth: 218 }}
onVisibilityChange={() => {
@@ -166,10 +164,10 @@ export function WindowLevelActionMenu({
</AllInOneMenu.SubMenu>
)}
{presets && !is3DVolume && (
{presets && presets.length > 0 && !is3DVolume && (
<AllInOneMenu.SubMenu
key="windowLevelPresets"
itemLabel={t('Modality Window Presets', { modality: Object.keys(presets)[0] })}
itemLabel={t('Modality Window Presets')}
itemIcon="viewport-window-level"
>
<WindowLevel
@@ -24,13 +24,11 @@ export function getWindowLevelActionMenu({
return { [displaySet.Modality]: presets[displaySet.Modality] };
});
const hasMenu = displaySetPresets.length > 0;
return hasMenu ? (
return (
<WindowLevelActionMenu
viewportId={viewportId}
element={element}
presets={displaySetPresets[0]}
presets={displaySetPresets}
verticalDirection={verticalDirection}
horizontalDirection={horizontalDirection}
commandsManager={commandsManager}
@@ -40,5 +38,5 @@ export function getWindowLevelActionMenu({
volumeRenderingPresets={volumeRenderingPresets}
volumeRenderingQualityRange={volumeRenderingQualityRange}
/>
) : null;
);
}
@@ -43,8 +43,8 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
return {
disabled: false,
className: isPrimaryActive
? '!text-black bg-primary-light'
: '!text-common-bright hover:!bg-primary-dark hover:!text-primary-light',
? '!text-black bg-primary-light rounded'
: '!text-common-bright hover:!bg-primary-dark hover:!text-primary-light rounded',
// Todo: isActive right now is used for nested buttons where the primary
// button needs to be fully rounded (vs partial rounded) when active
// otherwise it does not have any other use
+5 -2
View File
@@ -77,8 +77,11 @@ export const fourUp = {
{
id: 'mprDisplaySet',
options: {
// ToDo: choose appropriate preset
displayPreset: 'CT-Bone',
displayPreset: {
CT: 'CT-Bone',
MR: 'MR-Default',
default: 'CT-Bone',
},
},
},
],
+5 -2
View File
@@ -77,8 +77,11 @@ export const main3D = {
{
id: 'mprDisplaySet',
options: {
// ToDo: choose appropriate preset
displayPreset: 'CT-Bone',
displayPreset: {
CT: 'CT-Bone',
MR: 'MR-Default',
default: 'CT-Bone',
},
},
},
],
@@ -84,7 +84,11 @@ export const mprAnd3DVolumeViewport = {
{
id: 'mprDisplaySet',
options: {
displayPreset: 'CT-Bone',
displayPreset: {
CT: 'CT-Bone',
MR: 'MR-Default',
default: 'CT-Bone',
},
},
},
],
+5 -2
View File
@@ -51,8 +51,11 @@ export const only3D = {
{
id: 'mprDisplaySet',
options: {
// ToDo: choose appropriate preset
displayPreset: 'CT-Bone',
displayPreset: {
CT: 'CT-Bone',
MR: 'MR-Default',
default: 'CT-Bone',
},
},
},
],
+5 -2
View File
@@ -77,8 +77,11 @@ export const primary3D = {
{
id: 'mprDisplaySet',
options: {
// ToDo: choose appropriate preset
displayPreset: 'CT-Bone',
displayPreset: {
CT: 'CT-Bone',
MR: 'MR-Default',
default: 'CT-Bone',
},
},
},
],
+1 -1
View File
@@ -160,7 +160,7 @@ export default async function init({
const labelmapRepresentation = cornerstoneTools.Enums.SegmentationRepresentations.Labelmap;
cornerstoneTools.segmentation.config.setGlobalRepresentationConfig(labelmapRepresentation, {
fillAlpha: 0.3,
fillAlpha: 0.5,
fillAlphaInactive: 0.2,
outlineOpacity: 1,
outlineOpacityInactive: 0.65,
@@ -33,6 +33,8 @@ const initMeasurementService = (
PlanarFreehandROI,
SplineROI,
LivewireContour,
Probe,
UltrasoundDirectional,
} = measurementServiceMappingsFactory(
measurementService,
displaySetService,
@@ -157,6 +159,22 @@ const initMeasurementService = (
LivewireContour.toMeasurement
);
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'Probe',
Probe.matchingCriteria,
Probe.toAnnotation,
Probe.toMeasurement
);
measurementService.addMapping(
csTools3DVer1MeasurementSource,
'UltrasoundDirectionalTool',
UltrasoundDirectional.matchingCriteria,
UltrasoundDirectional.toAnnotation,
UltrasoundDirectional.toMeasurement
);
return csTools3DVer1MeasurementSource;
};
@@ -721,10 +721,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
}
public async setVolumesForViewport(viewport, volumeInputArray, presentations) {
const { displaySetService, toolGroupService } = this.servicesManager.services;
const { displaySetService, toolGroupService, viewportGridService } =
this.servicesManager.services;
const viewportInfo = this.getViewportInfo(viewport.id);
const displaySetOptions = viewportInfo.getDisplaySetOptions();
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewport.id);
const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]);
const displaySetModality = displaySet?.Modality;
// Todo: use presentations states
const volumesProperties = volumeInputArray.map((volumeInput, index) => {
@@ -750,7 +754,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
}
if (displayPreset !== undefined) {
properties.preset = displayPreset;
properties.preset = displayPreset[displaySetModality] || displayPreset.default;
}
return { properties, volumeId };
+16 -16
View File
@@ -1,4 +1,20 @@
const colormaps = [
{
ColorSpace: 'RGB',
Name: 'Grayscale',
name: 'Grayscale',
NanColor: [1, 0, 0],
RGBPoints: [0, 0, 0, 0, 1, 1, 1, 1],
description: 'Grayscale',
},
{
ColorSpace: 'RGB',
Name: 'X Ray',
name: 'X Ray',
NanColor: [1, 0, 0],
RGBPoints: [0, 1, 1, 1, 1, 0, 0, 0],
description: 'X Ray',
},
{
ColorSpace: 'RGB',
Name: 'hsv',
@@ -1579,22 +1595,6 @@ const colormaps = [
],
description: 'Siemens',
},
{
ColorSpace: 'RGB',
Name: 'X Ray',
name: 'X Ray',
NanColor: [1, 0, 0],
RGBPoints: [0, 1, 1, 1, 1, 0, 0, 0],
description: 'X Ray',
},
{
ColorSpace: 'RGB',
Name: 'Grayscale',
name: 'Grayscale',
NanColor: [1, 0, 0],
RGBPoints: [0, 0, 0, 0, 1, 1, 1, 1],
description: 'Grayscale',
},
];
export { colormaps };
@@ -0,0 +1,187 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
const Probe = {
toAnnotation: measurement => {},
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
*/
toMeasurement: (
csToolsEventDetail,
displaySetService,
CornerstoneViewportService,
getValueTypeFromToolType,
customizationService
) => {
const { annotation, viewportId } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
console.warn('Length tool: Missing metadata or data');
return null;
}
const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
const validToolType = SUPPORTED_TOOLS.includes(toolName);
if (!validToolType) {
throw new Error('Tool not supported');
}
const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } =
getSOPInstanceAttributes(referencedImageId);
let displaySet;
if (SOPInstanceUID) {
displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID
);
} else {
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
}
const { points } = data.handles;
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService);
const getReport = () =>
_getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService);
return {
uid: annotationUID,
SOPInstanceUID,
FrameOfReferenceUID,
points,
metadata,
referenceSeriesUID: SeriesInstanceUID,
referenceStudyUID: StudyInstanceUID,
frameNumber: mappedAnnotations?.[0]?.frameNumber || 1,
toolName: metadata.toolName,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
label: data.label,
displayText: displayText,
data: data.cachedStats,
type: getValueTypeFromToolType(toolName),
getReport,
};
},
};
function getMappedAnnotations(annotation, DisplaySetService) {
const { metadata, data } = annotation;
const { cachedStats } = data;
const { referencedImageId } = metadata;
const targets = Object.keys(cachedStats);
if (!targets.length) {
return;
}
const annotations = [];
Object.keys(cachedStats).forEach(targetId => {
const targetStats = cachedStats[targetId];
if (!referencedImageId) {
throw new Error('Non-acquisition plane measurement mapping not supported');
}
const { SOPInstanceUID, SeriesInstanceUID, frameNumber } =
getSOPInstanceAttributes(referencedImageId);
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID,
frameNumber
);
const { SeriesNumber } = displaySet;
const { value } = targetStats;
const unit = 'HU';
annotations.push({
SeriesInstanceUID,
SOPInstanceUID,
SeriesNumber,
frameNumber,
unit,
value,
});
});
return annotations;
}
/*
This function is used to convert the measurement data to a format that is
suitable for the report generation (e.g. for the csv report). The report
returns a list of columns and corresponding values.
*/
function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) {
const columns = [];
const values = [];
// Add Type
columns.push('AnnotationType');
values.push('Cornerstone:Probe');
mappedAnnotations.forEach(annotation => {
const { value, unit } = annotation;
columns.push(`Probe (${unit})`);
values.push(value);
});
if (FrameOfReferenceUID) {
columns.push('FrameOfReferenceUID');
values.push(FrameOfReferenceUID);
}
if (points) {
columns.push('points');
values.push(points.map(p => p.join(' ')).join(';'));
}
return {
columns,
values,
};
}
function getDisplayText(mappedAnnotations, displaySet, customizationService) {
if (!mappedAnnotations || !mappedAnnotations.length) {
return '';
}
const displayText = [];
const { value, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
let InstanceNumber;
if (instance) {
InstanceNumber = instance.InstanceNumber;
}
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
if (value === undefined) {
return displayText;
}
const roundedValue = utils.roundNumber(value, 2);
displayText.push(
`${roundedValue} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})`
);
return displayText;
}
export default Probe;
@@ -0,0 +1,206 @@
import SUPPORTED_TOOLS from './constants/supportedTools';
import { getDisplayUnit } from './utils';
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
import { utils } from '@ohif/core';
const UltrasoundDirectional = {
toAnnotation: measurement => {},
/**
* Maps cornerstone annotation event data to measurement service format.
*
* @param {Object} cornerstone Cornerstone event data
* @return {Measurement} Measurement instance
*/
toMeasurement: (
csToolsEventDetail,
displaySetService,
CornerstoneViewportService,
getValueTypeFromToolType,
customizationService
) => {
const { annotation, viewportId } = csToolsEventDetail;
const { metadata, data, annotationUID } = annotation;
if (!metadata || !data) {
console.warn('Length tool: Missing metadata or data');
return null;
}
const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
const validToolType = SUPPORTED_TOOLS.includes(toolName);
if (!validToolType) {
throw new Error('Tool not supported');
}
const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } =
getSOPInstanceAttributes(referencedImageId);
let displaySet;
if (SOPInstanceUID) {
displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID
);
} else {
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
}
const { points } = data.handles;
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
const displayText = getDisplayText(mappedAnnotations, displaySet, customizationService);
const getReport = () =>
_getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService);
return {
uid: annotationUID,
SOPInstanceUID,
FrameOfReferenceUID,
points,
metadata,
referenceSeriesUID: SeriesInstanceUID,
referenceStudyUID: StudyInstanceUID,
frameNumber: mappedAnnotations?.[0]?.frameNumber || 1,
toolName: metadata.toolName,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
label: data.label,
displayText: displayText,
data: data.cachedStats,
type: getValueTypeFromToolType(toolName),
getReport,
};
},
};
function getMappedAnnotations(annotation, DisplaySetService) {
const { metadata, data } = annotation;
const { cachedStats } = data;
const { referencedImageId } = metadata;
const targets = Object.keys(cachedStats);
if (!targets.length) {
return;
}
const annotations = [];
Object.keys(cachedStats).forEach(targetId => {
const targetStats = cachedStats[targetId];
if (!referencedImageId) {
throw new Error('Non-acquisition plane measurement mapping not supported');
}
const { SOPInstanceUID, SeriesInstanceUID, frameNumber } =
getSOPInstanceAttributes(referencedImageId);
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
SOPInstanceUID,
SeriesInstanceUID,
frameNumber
);
const { SeriesNumber } = displaySet;
const { xValues, yValues, units, isUnitless, isHorizontal } = targetStats;
annotations.push({
SeriesInstanceUID,
SOPInstanceUID,
SeriesNumber,
frameNumber,
xValues,
yValues,
units,
isUnitless,
isHorizontal,
});
});
return annotations;
}
/*
This function is used to convert the measurement data to a format that is
suitable for the report generation (e.g. for the csv report). The report
returns a list of columns and corresponding values.
*/
function _getReport(mappedAnnotations, points, FrameOfReferenceUID, customizationService) {
const columns = [];
const values = [];
// Add Type
columns.push('AnnotationType');
values.push('Cornerstone:UltrasoundDirectional');
mappedAnnotations.forEach(annotation => {
const { xValues, yValues, units, isUnitless } = annotation;
if (isUnitless) {
columns.push('Length' + units[0]);
values.push(utils.roundNumber(xValues[0], 2));
} else {
const dist1 = Math.abs(xValues[1] - xValues[0]);
const dist2 = Math.abs(yValues[1] - yValues[0]);
columns.push('Time' + units[0]);
values.push(utils.roundNumber(dist1, 2));
columns.push('Length' + units[1]);
values.push(utils.roundNumber(dist2, 2));
}
});
if (FrameOfReferenceUID) {
columns.push('FrameOfReferenceUID');
values.push(FrameOfReferenceUID);
}
if (points) {
columns.push('points');
values.push(points.map(p => p.join(' ')).join(';'));
}
return {
columns,
values,
};
}
function getDisplayText(mappedAnnotations, displaySet, customizationService) {
if (!mappedAnnotations || !mappedAnnotations.length) {
return '';
}
const displayText = [];
const { xValues, yValues, units, isUnitless, SeriesNumber, SOPInstanceUID, frameNumber } =
mappedAnnotations[0];
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
let InstanceNumber;
if (instance) {
InstanceNumber = instance.InstanceNumber;
}
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
const seriesText = `(S: ${SeriesNumber}${instanceText}${frameText})`;
if (xValues === undefined || yValues === undefined) {
return displayText;
}
if (isUnitless) {
displayText.push(`${utils.roundNumber(xValues[0], 2)} ${units[0]} ${seriesText}`);
} else {
const dist1 = Math.abs(xValues[1] - xValues[0]);
const dist2 = Math.abs(yValues[1] - yValues[0]);
displayText.push(`${utils.roundNumber(dist1)} ${units[0]} ${seriesText}`);
displayText.push(`${utils.roundNumber(dist2)} ${units[1]} ${seriesText}`);
}
return displayText;
}
export default UltrasoundDirectional;
@@ -11,4 +11,6 @@ export default [
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
'Probe',
'UltrasoundDirectionalTool',
];
@@ -10,6 +10,8 @@ import PlanarFreehandROI from './PlanarFreehandROI';
import RectangleROI from './RectangleROI';
import SplineROI from './SplineROI';
import LivewireContour from './LivewireContour';
import Probe from './Probe';
import UltrasoundDirectional from './UltrasoundDirectional';
const measurementServiceMappingsFactory = (
measurementService: MeasurementService,
@@ -44,6 +46,8 @@ const measurementServiceMappingsFactory = (
Angle: ANGLE,
SplineROI: POLYLINE,
LivewireContour: POLYLINE,
Probe: POINT,
UltrasoundDirectional: POLYLINE,
};
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
@@ -211,6 +215,24 @@ const measurementServiceMappingsFactory = (
],
},
Probe: {
toAnnotation: Probe.toAnnotation,
toMeasurement: csToolsAnnotation =>
Probe.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType,
customizationService
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.POINT,
points: 1,
},
],
},
CobbAngle: {
toAnnotation: CobbAngle.toAnnotation,
toMeasurement: csToolsAnnotation =>
@@ -244,6 +266,23 @@ const measurementServiceMappingsFactory = (
},
],
},
UltrasoundDirectional: {
toAnnotation: UltrasoundDirectional.toAnnotation,
toMeasurement: csToolsAnnotation =>
UltrasoundDirectional.toMeasurement(
csToolsAnnotation,
displaySetService,
cornerstoneViewportService,
_getValueTypeFromToolType,
customizationService
),
matchingCriteria: [
{
valueType: MeasurementService.VALUE_TYPES.POLYLINE,
points: 2,
},
],
},
};
return factories;
@@ -53,6 +53,7 @@ function ViewerHeader({ hotkeysManager, extensionManager, servicesManager, appCo
content: AboutModal,
title: t('AboutModal:About OHIF Viewer'),
contentProps: { versionNumber, commitHash },
containerDimensions: 'max-w-4xl max-h-4xl',
}),
},
{
@@ -114,7 +115,7 @@ function ViewerHeader({ hotkeysManager, extensionManager, servicesManager, appCo
appConfig={appConfig}
>
<ErrorBoundary context="Primary Toolbar">
<div className="relative flex justify-center">
<div className="relative flex justify-center gap-[4px]">
<Toolbar servicesManager={servicesManager} />
</div>
</ErrorBoundary>
+1
View File
@@ -445,6 +445,7 @@ const commandsModule = ({
displaySetInstanceUID,
onClose: UIModalService.hide,
},
containerDimensions: 'max-w-4xl max-h-4xl',
title: 'DICOM Tag Browser',
});
},
@@ -74,6 +74,12 @@ function _askTrackMeasurements(uiViewportDialogService, viewportId) {
uiViewportDialogService.hide();
resolve(RESPONSE.CANCEL);
},
onKeyPress: event => {
if (event.key === 'Enter') {
const action = actions.find(action => action.value === RESPONSE.HYDRATE_REPORT);
onSubmit(action.value);
}
},
});
});
}
@@ -329,6 +329,65 @@ function PanelStudyBrowserTracking({
}
}, [expandedStudyInstanceUIDs, jumpToDisplaySet, tabs]);
const onClickUntrack = displaySetInstanceUID => {
const onConfirm = () => {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
sendTrackedMeasurementsEvent('UNTRACK_SERIES', {
SeriesInstanceUID: displaySet.SeriesInstanceUID,
});
const measurements = measurementService.getMeasurements();
measurements.forEach(m => {
if (m.referenceSeriesUID === displaySet.SeriesInstanceUID) {
measurementService.remove(m.uid);
}
});
};
uiDialogService.create({
id: 'untrack-series',
centralize: true,
isDraggable: false,
showOverlay: true,
content: Dialog,
contentProps: {
title: 'Untrack Series',
body: () => (
<div className="bg-primary-dark p-4 text-white">
<p>Are you sure you want to untrack this series?</p>
<p className="mt-2">
This action cannot be undone and will delete all your existing measurements.
</p>
</div>
),
actions: [
{
id: 'cancel',
text: 'Cancel',
type: ButtonEnums.type.secondary,
},
{
id: 'yes',
text: 'Yes',
type: ButtonEnums.type.primary,
classes: ['untrack-yes-button'],
},
],
onClose: () => uiDialogService.dismiss({ id: 'untrack-series' }),
onSubmit: async ({ action }) => {
switch (action.id) {
case 'yes':
onConfirm();
uiDialogService.dismiss({ id: 'untrack-series' });
break;
case 'cancel':
uiDialogService.dismiss({ id: 'untrack-series' });
break;
}
},
},
});
};
return (
<StudyBrowser
tabs={tabs}
@@ -340,18 +399,7 @@ function PanelStudyBrowserTracking({
setActiveTabName(clickedTabName);
}}
onClickUntrack={displaySetInstanceUID => {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
// TODO: shift this somewhere else where we're centralizing this logic?
// Potentially a helper from displaySetInstanceUID to this
sendTrackedMeasurementsEvent('UNTRACK_SERIES', {
SeriesInstanceUID: displaySet.SeriesInstanceUID,
});
const measurements = measurementService.getMeasurements();
measurements.forEach(m => {
if (m.referenceSeriesUID === displaySet.SeriesInstanceUID) {
measurementService.remove(m.uid);
}
});
onClickUntrack(displaySetInstanceUID);
}}
onClickThumbnail={() => {}}
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
+1 -1
View File
@@ -127,7 +127,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
}
return (
<div className="ohif-scrollbar flex min-h-0 flex-auto select-none flex-col justify-between overflow-auto">
<div className="flex min-h-0 flex-col bg-black text-[13px] font-[300]">
<div className="flex min-h-0 flex-1 flex-col bg-black text-[13px] font-[300]">
<PanelSection title={t('Patient Information')}>
<div className="flex flex-col">
<div className="bg-primary-dark flex flex-col gap-4 p-2">
@@ -77,7 +77,7 @@ export default function PanelRoiThresholdSegmentation({ servicesManager, command
return (
<>
<div className="mt-1 flex flex-col">
<div className="mt-2 mb-10 flex flex-col">
<div className="invisible-scrollbar overflow-y-auto overflow-x-hidden">
{tmtvValue !== null ? (
<div className="bg-secondary-dark mt-1 flex items-baseline justify-between px-2 py-1">
@@ -87,7 +87,7 @@ export default function PanelRoiThresholdSegmentation({ servicesManager, command
<div className="text-white">{`${tmtvValue} mL`}</div>
</div>
) : null}
<div className="mt-1 flex justify-center">
<div className="mt-2 flex justify-center">
<ActionButtons
actions={actions}
t={t}