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

This commit is contained in:
Ibrahim 2024-04-16 10:31:06 -02:30 committed by GitHub
parent 41a8d105f1
commit e80fc6f477
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 768 additions and 148 deletions

View File

@ -66,6 +66,11 @@ function _askHydrate(uiViewportDialogService, viewportId) {
uiViewportDialogService.hide();
resolve(RESPONSE.CANCEL);
},
onKeyPress: event => {
if (event.key === 'Enter') {
onSubmit(RESPONSE.HYDRATE_SEG);
}
},
});
});
}

View File

@ -63,6 +63,11 @@ function _askHydrate(uiViewportDialogService, viewportId) {
uiViewportDialogService.hide();
resolve(RESPONSE.CANCEL);
},
onKeyPress: event => {
if (event.key === 'Enter') {
onSubmit(RESPONSE.HYDRATE_SEG);
}
},
});
});
}

View File

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

View File

@ -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]);

View File

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

View File

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

View File

@ -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]',
});
};

View File

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

View File

@ -82,6 +82,8 @@ export function VolumeShift({
step={step}
style={{
background: calculateBackground((shift - minShift) / (maxShift - minShift)),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}

View File

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

View File

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

View File

@ -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;
);
}

View File

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

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',
},
},
},
],

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',
},
},
},
],

View File

@ -84,7 +84,11 @@ export const mprAnd3DVolumeViewport = {
{
id: 'mprDisplaySet',
options: {
displayPreset: 'CT-Bone',
displayPreset: {
CT: 'CT-Bone',
MR: 'MR-Default',
default: 'CT-Bone',
},
},
},
],

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',
},
},
},
],

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',
},
},
},
],

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,

View File

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

View File

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

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 };

View File

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

View File

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

View File

@ -11,4 +11,6 @@ export default [
'PlanarFreehandROI',
'SplineROI',
'LivewireContour',
'Probe',
'UltrasoundDirectionalTool',
];

View File

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

View File

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

View File

@ -445,6 +445,7 @@ const commandsModule = ({
displaySetInstanceUID,
onClose: UIModalService.hide,
},
containerDimensions: 'max-w-4xl max-h-4xl',
title: 'DICOM Tag Browser',
});
},

View File

@ -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);
}
},
});
});
}

View File

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

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

View File

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

View File

@ -8,6 +8,7 @@ window.config = {
showCPUFallbackMessage: true,
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
groupEnabledModesFirst: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [

View File

@ -328,7 +328,7 @@ function WorkList({
</>
),
title: (instances || 0).toString(),
gridCol: 4,
gridCol: 2,
},
],
// Todo: This is actually running for all rows, even if they are
@ -454,6 +454,7 @@ function WorkList({
content: AboutModal,
title: t('AboutModal:About OHIF Viewer'),
contentProps: { versionNumber, commitHash },
containerDimensions: 'max-w-4xl max-h-4xl',
}),
},
{

View File

@ -123,7 +123,8 @@ Here are a list of some options available:
what is specified by `maxNumberOfWebWorkers`. Some windows machines require smaller values.
- `acceptHeader` : accept header to request specific dicom transfer syntax ex : [ 'multipart/related; type=image/jls; q=1', 'multipart/related; type=application/octet-stream; q=0.1' ]
- `investigationalUseDialog`: This should contain an object with `option` value, it can be either `always` which always shows the dialog once per session, `never` which never shows the dialog, or `configure` which shows the dialog once and won't show it again until a set number of days defined by the user, if it's set to configure, you are required to add an additional property `days` which is the number of days to wait before showing the dialog again.
- `groupEnabled`: boolean, if set to true, all valid modes for the study get grouped together first, then the rest of the modes. If false, all modes are shown in the order they are defined in the configuration.
- `groupEnabledModesFirst`: boolean, if set to true, all valid modes for the study get grouped together first, then the rest of the modes. If false, all modes are shown in the order they are defined in the configuration.
- `showPatientInfo`: string, if set to 'visible', the patient info header will be shown and its initial state is expanded. If set to 'visibleCollapsed', the patient info header will be shown but it's initial state is collapsed. If set to 'disabled', the patient info header will never be shown, and if set to 'visibleReadOnly', the patient info header will be shown and always expanded.
- `requestTransferSyntaxUID` : Request a specific Transfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applied only if acceptHeader is not set)
- `omitQuotationForMultipartRequest`: Some servers (e.g., .NET) require the `multipart/related` request to be sent without quotation marks. Defaults to `false`. If your server doesn't require this, then setting this flag to `true` might improve performance (by removing the need for preflight requests). Also note that
if auth headers are used, a preflight request is required.

View File

@ -3,19 +3,20 @@
<title>tool-layout</title>
<g id="tool-layout" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" x="0" y="0" width="28" height="28"></rect>
<g id="Group" transform="translate(0.5, 0.5)" stroke="currentColor">
<path d="M5.45696821e-12,9.49450549 L5.45696821e-12,17.3947362 C5.45710348e-12,18.4993057 0.8954305,19.3947362 2,19.3947362 L10.861569,19.3947362 L10.861569,19.3947362" id="Path-3" stroke-width="1.25" stroke-linecap="round"></path>
<path d="M9.49450549,0 L9.49450549,3.85708618 C9.49450549,4.96165568 10.389936,5.85708618 11.4945055,5.85708618 L19.195248,5.85708618 L19.195248,5.85708618" id="Path-3" stroke-width="1.25" stroke-linecap="round" transform="translate(14.3449, 2.9285) rotate(180) translate(-14.3449, -2.9285)"></path>
<path d="M0.0997440877,-0.0997440877 L0.0997440877,7.80048659 C0.0997440877,8.90505609 0.995174588,9.80048659 2.09974409,9.80048659 L9.80048659,9.80048659 L9.80048659,9.80048659" id="Path-3" stroke-width="1.25" stroke-linecap="round" transform="translate(4.9501, 4.8504) rotate(90) translate(-4.9501, -4.8504)"></path>
<g id="Group-10" opacity="0.550000012" transform="translate(2, 2)" stroke-linecap="round" stroke-width="1.5">
<g id="Group" transform="translate(0.5, 4)" stroke-linecap="round">
<g id="Group-3" transform="translate(0, 0)" stroke="currentColor" stroke-width="1.25">
<path d="M5.45696821e-12,9.49450549 L5.45696821e-12,17.3947362 C5.45710348e-12,18.4993057 0.8954305,19.3947362 2,19.3947362 L13.2443085,19.3947362 L13.2443085,19.3947362" id="Path-3"></path>
<path d="M9.49450549,0 L9.49450549,4.91369247 L9.49450549,6 C9.49450549,7.1045695 10.389936,8 11.4945055,8 L19.195248,8 L19.195248,8" id="Path-3" transform="translate(14.3449, 4) rotate(180) translate(-14.3449, -4)"></path>
<path d="M0.0997440877,-0.0997440877 L0.0997440877,7.80048659 C0.0997440877,8.90505609 0.995174588,9.80048659 2.09974409,9.80048659 L9.80048659,9.80048659 L9.80048659,9.80048659" id="Path-3" transform="translate(4.9501, 4.8504) rotate(90) translate(-4.9501, -4.8504)"></path>
</g>
<g id="Group-10" opacity="0.550000012" transform="translate(2, 2)" stroke="currentColor" stroke-width="1.5">
<line x1="7.5" y1="14.5" x2="7.5" y2="0.5" id="Line-2"></line>
<line x1="7" y1="14" x2="7" y2="1" id="Line-2" transform="translate(7, 7.5) rotate(90) translate(-7, -7.5)"></line>
</g>
<g id="gear" transform="translate(20.6492, 15.8718) rotate(-20) translate(-20.6492, -15.8718)translate(15.525, 10.1048)" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25">
<g id="gear" transform="translate(21.0876, 17.2796) rotate(-20) translate(-21.0876, -17.2796)translate(15.9634, 11.5127)" stroke="#348CFD" stroke-linejoin="round" stroke-width="1.25">
<circle id="Oval" cx="5.12378058" cy="5.76719178" r="1.28158482"></circle>
<path d="M6.21361819,0.80762797 L6.59086967,2.04907966 C6.72061095,2.47639719 7.15892721,2.73042581 7.59419808,2.63056421 L8.85259487,2.33893009 C9.34216301,2.22858155 9.84650092,2.45011894 10.0964357,2.88530436 C10.3463705,3.32048979 10.2835655,3.86774756 9.94154064,4.23499782 L9.06128718,5.18481538 C8.75980718,5.51248709 8.75980718,6.0165454 9.06128718,6.3442171 L9.94154064,7.29403466 C10.2835655,7.66128493 10.3463705,8.2085427 10.0964357,8.64372812 C9.84650092,9.07891354 9.34216301,9.30045094 8.85259487,9.1901024 L7.59419808,8.89846827 C7.15663131,8.79632613 6.71524652,9.05279063 6.58730228,9.48352022 L6.2100508,10.7249719 C6.0649735,11.2053044 5.62242271,11.5339347 5.12065911,11.5339347 C4.61889551,11.5339347 4.17634472,11.2053044 4.03126742,10.7249719 L3.65669148,9.48352022 C3.52807698,9.05567912 3.08963152,8.80095947 2.65425492,8.90114382 L1.39585813,9.19277794 C0.90628999,9.30312648 0.401952083,9.08158909 0.152017275,8.64640366 C-0.0979175321,8.21121824 -0.035112502,7.66396047 0.306912358,7.29671021 L1.18716582,6.34689264 C1.48864582,6.01922094 1.48864582,5.51516263 1.18716582,5.18749093 L0.306912358,4.23767336 C-0.035112502,3.8704231 -0.0979175321,3.32316533 0.152017275,2.88797991 C0.401952083,2.45279448 0.90628999,2.23125709 1.39585813,2.34160563 L2.65425492,2.63323975 C3.08937078,2.73350334 3.52770594,2.47923776 3.65669148,2.0517552 L4.03483481,0.810303513 C4.17932126,0.32979188 4.62146882,0.000617032116 5.12323309,0 C5.62499736,-0.000615299293 6.06795204,0.327472644 6.21361819,0.80762797 Z" id="Path"></path>
</g>
<polygon id="w/l-chevron" stroke-linejoin="round" transform="translate(20.5, 25.25) rotate(-360) translate(-20.5, -25.25)" points="20.5 26.75 18 24.1184211 18.35 23.75 20.5 26.0131579 22.65 23.75 23 24.1184211"></polygon>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tool-seg-brush</title>
<g id="tool-seg-brush" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
<path d="M3.24640621,21.8286833 C3.09173375,21.7551472 2.99513748,21.5971513 3.00018895,21.4259625 C3.00524042,21.2547737 3.11098486,21.1027485 3.26972426,21.0384606 C5.3260304,20.2059201 4.66362518,17.8620247 5.27421252,16.6088957 C6.02197747,15.1026514 7.84114758,14.4766383 9.35746132,15.2037675 C13.9485253,17.4422999 8.48346644,24.3211232 3.24640621,21.8286833 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M21.5968442,2.302022 C21.1256527,1.8826075 20.4094461,1.90228142 19.9619901,2.34693083 L9.69255027,12.5887345 C11.0536437,13.0051578 12.1843437,13.9616637 12.8206229,15.2349008 L21.7410706,3.93687606 C22.1347378,3.44008272 22.0714081,2.72222021 21.5968442,2.302022 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tool-seg-eraser</title>
<g id="tool-seg-eraser" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
<path d="M8.47826087,18.0030772 C7.48731947,17.9495317 6.54439458,17.5591084 5.80565217,16.8964685 L4.1066087,15.1958598 C3.49763625,14.5834856 3.49763625,13.5942339 4.1066087,12.9818598 L13.6317391,3.45672934 C14.2441133,2.84775689 15.233365,2.84775689 15.8457391,3.45672934 L19.8926087,7.5035989 C20.5015811,8.11597307 20.5015811,9.10522473 19.8926087,9.7175989 L12.7153043,16.8949033 C11.9769855,17.5579476 11.0343414,17.9489213 10.0434783,18.0030772 L8.47826087,18.0030772 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
<line x1="10.0434783" y1="7.04499021" x2="16.3043478" y2="13.3058598" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></line>
<line x1="3" y1="20.5667293" x2="21" y2="20.5667293" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></line>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tool-seg-shape</title>
<g id="tool-seg-shape" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
<circle id="Oval" stroke="currentColor" stroke-width="1.5" cx="16" cy="15" r="6"></circle>
<rect id="Rectangle" stroke="currentColor" stroke-width="1.5" x="3" y="3" width="13" height="13" rx="2"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 615 B

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>tool-seg-threshold</title>
<g id="tool-seg-threshold" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
<g id="Group" transform="translate(2.4184, 14.3673)" stroke="currentColor" stroke-width="1.5">
<line x1="16.5816327" y1="2.13265306" x2="19.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line>
<line x1="0.0816326531" y1="2.13265306" x2="12.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line>
<circle id="Oval" cx="14.2244898" cy="2.09183673" r="2.09183673"></circle>
</g>
<g id="Group" transform="translate(11.7092, 7.4592) scale(-1, 1) translate(-11.7092, -7.4592)translate(1.9184, 5.3673)" stroke="currentColor" stroke-width="1.5">
<line x1="16" y1="2.13265306" x2="19.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line>
<line x1="0.0816326531" y1="2.13265306" x2="11" y2="2.13265306" id="Line-2" stroke-linecap="round"></line>
<circle id="Oval" cx="13.2244898" cy="2.09183673" r="2.09183673"></circle>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -40,27 +40,30 @@ const ButtonGroup = ({
'border-secondary-light rounded-[5px] border bg-black': !separated,
})}
>
{!separated &&
Children.map(children, (child, index) => {
if (React.isValidElement(child)) {
return cloneElement(child, {
key: index,
className: classnames(
'rounded-[4px] px-2 py-1',
index === activeIndex
? 'bg-customblue-40 text-white'
: 'text-primary-active bg-black',
child.props.className,
disabled ? 'ohif-disabled' : ''
),
onClick: e => {
child.props.onClick && child.props.onClick(e);
handleButtonClick(index);
},
});
}
return child;
})}
{!separated && (
<div className="flex h-[32px] w-full">
{Children.map(children, (child, index) => {
if (React.isValidElement(child)) {
return cloneElement(child, {
key: index,
className: classnames(
'rounded-[4px] px-2 py-1',
index === activeIndex
? 'bg-customblue-40 text-white'
: 'text-primary-active bg-black',
child.props.className,
disabled ? 'ohif-disabled' : ''
),
onClick: e => {
child.props.onClick && child.props.onClick(e);
handleButtonClick(index);
},
});
}
return child;
})}
</div>
)}
{separated && (
<div className="flex space-x-2">
{Children.map(children, (child, index) => {

View File

@ -65,8 +65,7 @@ function Header({
<div className="flex items-center justify-center space-x-2">{children}</div>
</div>
<div className="absolute right-0 top-1/2 flex -translate-y-1/2 select-none items-center">
{(showPatientInfo === PatientInfoVisibility.VISIBLE ||
showPatientInfo === PatientInfoVisibility.VISIBLE_COLLAPSED) && (
{showPatientInfo !== PatientInfoVisibility.DISABLED && (
<HeaderPatientInfo
servicesManager={servicesManager}
appConfig={appConfig}

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Icon } from '@ohif/ui';
import { utils } from '@ohif/core';
import { PatientInfoVisibility } from '../../types';
const { formatDate, formatPN } = utils;
@ -70,7 +71,9 @@ function usePatientInfo(servicesManager) {
}
function HeaderPatientInfo({ servicesManager, appConfig }) {
const initialExpandedState = appConfig.showPatientInfo === 'visible';
const initialExpandedState =
appConfig.showPatientInfo === PatientInfoVisibility.VISIBLE ||
appConfig.showPatientInfo === PatientInfoVisibility.VISIBLE_READONLY;
const [expanded, setExpanded] = useState(initialExpandedState);
const { patientInfo, isMixedPatients } = usePatientInfo(servicesManager);
@ -81,7 +84,7 @@ function HeaderPatientInfo({ servicesManager, appConfig }) {
}, [isMixedPatients, expanded]);
const handleOnClick = () => {
if (!isMixedPatients) {
if (!isMixedPatients && appConfig.showPatientInfo !== PatientInfoVisibility.VISIBLE_READONLY) {
setExpanded(!expanded);
}
};

View File

@ -145,11 +145,11 @@ import iconAdd from './../../assets/icons/icon-add.svg';
import iconRename from './../../assets/icons/icon-rename.svg';
import iconDelete from './../../assets/icons/icon-delete.svg';
import iconMoreMenu from './../../assets/icons/icon-more-menu.svg';
import iconToolBrush from './../../assets/icons/icon-tool-brush.svg';
import iconToolEraser from './../../assets/icons/icon-tool-eraser.svg';
import iconToolBrush from './../../assets/icons/tool-seg-brush.svg';
import iconToolEraser from './../../assets/icons/tool-seg-eraser.svg';
import iconToolScissor from './../../assets/icons/icon-tool-scissor.svg';
import iconToolShape from './../../assets/icons/icon-tool-shape.svg';
import iconToolThreshold from './../../assets/icons/icon-tool-threshold.svg';
import iconToolShape from './../../assets/icons/tool-seg-shape.svg';
import iconToolThreshold from './../../assets/icons/tool-seg-threshold.svg';
import viewportWindowLevel from './../../assets/icons/viewport-window-level.svg';
import dicomTagBrowser from './../../assets/icons/tool-dicom-tag-browser.svg';
import iconToolFreehandRoi from './../../assets/icons/tool-freehand-roi.svg';

View File

@ -64,6 +64,7 @@ const iconSizeClasses = {
medium: 'w-5 h-5',
large: 'w-6 h-6',
toolbar: 'w-[28px] h-[28px]',
toolbox: 'w-[24px] h-[24px]',
};
const fullWidthClasses = {
@ -92,7 +93,7 @@ const IconButton = ({
onClick(e);
};
const padding = size === 'toolbar' ? '6px' : size === 'toolbox' ? '6px' : null;
const padding = size === 'toolbar' ? '6px' : size === 'toolbox' ? '4px' : null;
return (
<button

View File

@ -63,7 +63,7 @@ const InputRange: React.FC<InputRangeProps> = ({
}) => {
const [rangeValue, setRangeValue] = useState(value);
const maxDigits = getMaxDigits(maxValue, step);
const labelWidth = `${maxDigits * 10}px`;
const labelWidth = `${maxDigits * 15}px`;
useEffect(() => setRangeValue(value), [value]);
@ -93,7 +93,7 @@ const InputRange: React.FC<InputRangeProps> = ({
/>
) : (
<span className={classNames(labelClassName ?? 'text-white')}>
{rangeValue.toFixed(maxDigits)} {unit}
{rangeValue} {unit}
</span>
);

View File

@ -30,7 +30,7 @@ const ListMenu = ({ items = [], renderer, onClick }) => {
};
return (
<div className="bg-secondary-dark flex flex-col rounded-md pt-2 pb-2">
<div className="bg-secondary-dark flex flex-col gap-[4px] rounded-md p-2">
{items.map((item, index) => {
return (
<ListItem

View File

@ -66,7 +66,7 @@ const Notification = ({ id, type, message, actions, onSubmit, onOutsideClick, on
return (
<div
ref={notificationRef}
className="border-customblue-10 bg-customblue-400 mx-2 mt-2 flex flex-col rounded-md border-2 p-2"
className="border-customblue-10 bg-customblue-400 mx-2 mt-2 flex flex-col rounded-md border-2 p-2 outline-none"
data-cy={id}
onKeyDown={onKeyPress}
tabIndex={0}

View File

@ -38,7 +38,11 @@ const PanelSection = ({ title, children, actionIcons = [], childrenClassName })
{areChildrenVisible && (
<>
<div className="h-[2px] bg-black"></div>
<div className={`bg-primary-dark rounded-b-[4px] ${childrenClassName}`}>{children}</div>
<div
className={`bg-primary-dark flex flex-col overflow-hidden rounded-b-[4px] ${childrenClassName}`}
>
{children}
</div>
</>
)}
</>

View File

@ -29,7 +29,7 @@ const SegmentItem = ({
return (
<div
className={classnames('text-aqua-pale group/row bg-primary-dark flex min-h-[28px] flex-col', {
'bg-primary-light border-primary-light rounded-l-[4px] border text-black': isActive,
'bg-primary-light border-primary-light rounded-l-[6px] border text-black': isActive,
})}
onClick={e => {
e.stopPropagation();
@ -65,10 +65,6 @@ const SegmentItem = ({
<div>{segmentIndex}</div>
)}
</div>
<div
className=" h-[30px] bg-black"
style={{ width: '3px' }}
></div>
<div
className={classnames('text-aqua-pale relative flex w-full', {
'border border-l-0 border-transparent': !isActive,

View File

@ -74,7 +74,7 @@ const SegmentationGroupTable = ({
const { t } = useTranslation('SegmentationTable');
return (
<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('Segmentation')}
actionIcons={
@ -98,7 +98,7 @@ const SegmentationGroupTable = ({
segmentationConfig={segmentationConfig}
/>
)}
<div className="bg-primary-dark">
<div className="bg-primary-dark ">
{segmentations?.length === 0 ? (
<div className="select-none bg-black pt-[5px] pb-[5px]">
{showAddSegmentation && !disableEditing && (
@ -127,7 +127,7 @@ const SegmentationGroupTable = ({
)}
</div>
{activeSegmentation && (
<div className="ohif-scrollbar flex h-fit max-h-[500px] min-h-0 flex-col overflow-auto bg-black">
<div className="ohif-scrollbar flex h-fit min-h-0 flex-1 flex-col overflow-auto bg-black">
{activeSegmentation?.segments?.map(segment => {
if (!segment) {
return null;

View File

@ -67,7 +67,7 @@ const SegmentationGroupTableExpanded = ({
const { t } = useTranslation('SegmentationTable');
return (
<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('Segmentation')}
actionIcons={
@ -91,14 +91,14 @@ const SegmentationGroupTableExpanded = ({
segmentationConfig={segmentationConfig}
/>
)}
<div className="bg-primary-dark">
<div className="bg-primary-dark flex flex-1 flex-col overflow-hidden">
<div className="select-none bg-black pt-[5px] pb-[5px]">
{showAddSegmentation && !disableEditing && (
<NoSegmentationRow onSegmentationAdd={onSegmentationAdd} />
)}
</div>
{segmentations?.length > 0 && (
<div className="ohif-scrollbar flex max-h-[500px] select-none flex-col gap-[5px] overflow-auto bg-black">
<div className="ohif-scrollbar flex flex-1 select-none flex-col gap-[5px] overflow-auto bg-black">
{segmentations?.map(segmentation => {
return (
<div key={segmentation.id}>

View File

@ -354,7 +354,7 @@ const SidePanel = ({
return (
<div
className={classnames(
'text-primary-active flex grow cursor-pointer justify-center self-center text-[13px]'
'text-primary-active flex grow cursor-pointer select-none justify-center self-center text-[13px]'
)}
style={{
...(side === 'left'
@ -371,7 +371,7 @@ const SidePanel = ({
const getOpenStateComponent = () => {
return (
<div className="bg-primary-dark flex rounded-t pt-1.5 pb-[2px]">
<div className="bg-primary-dark flex select-none rounded-t pt-1.5 pb-[2px] ">
{getCloseIcon()}
{tabs.length === 1 ? getOneTabComponent() : getTabGridComponent()}
</div>
@ -386,14 +386,12 @@ const SidePanel = ({
{panelOpen ? (
<>
{getOpenStateComponent()}
{tabs.map((tab, tabIndex) => (
<div
key={tab.id}
className={tabIndex === activeTabIndex ? '' : 'hidden'}
>
<tab.content />
</div>
))}
{tabs.map((tab, tabIndex) => {
if (tabIndex === activeTabIndex) {
return <tab.content key={tabIndex} />;
}
return null;
})}
</>
) : (
<React.Fragment>{getCloseStateComponent()}</React.Fragment>

View File

@ -63,7 +63,7 @@ const DefaultListItemRenderer = props => {
return (
<div
className={classNames(
'flex h-8 w-full flex-row items-center p-3',
'flex h-8 w-full select-none flex-row items-center p-3',
'whitespace-pre text-base',
className,
`${isActive ? 'hover:opacity-80' : 'hover:bg-primary-dark '}`

View File

@ -41,7 +41,7 @@ const StudyItem = ({
</div>
</div>
<div className="flex flex-row py-1">
<div className="pr-5 text-xl text-blue-300">{modalities}</div>
<div className="text-l pr-5 text-blue-300">{modalities}</div>
<div className="truncate-2-lines break-words text-base text-blue-300">{description}</div>
</div>
</div>
@ -59,7 +59,7 @@ const StudyItem = ({
name="tracked"
className="text-primary-light mr-2 w-4"
/>
{t('Tracked series', {trackedSeries: trackedSeries})}
{t('Tracked series', { trackedSeries: trackedSeries })}
</div>
</div>
)}

View File

@ -32,7 +32,7 @@ const ToolbarButton = ({
? toolTipClassName
: sizeToUse === 'toolbar'
? 'w-[40px] h-[40px]'
: 'w-[40px] h-[40px]';
: 'w-[32px] h-[32px]';
return (
<div key={id}>

View File

@ -201,7 +201,7 @@ const DialogProvider = ({ children, service }) => {
isDragging && 'dragging',
isDraggable && 'draggable'
)}
style={{ zIndex: '999', position: 'absolute', width: '350px' }}
style={{ zIndex: '999', position: 'absolute' }}
onClick={() => _bringToFront(id)}
>
<DialogContent

View File

@ -2,4 +2,5 @@ export enum PatientInfoVisibility {
VISIBLE = 'visible',
VISIBLE_COLLAPSED = 'visibleCollapsed',
DISABLED = 'disabled',
VISIBLE_READONLY = 'visibleReadOnly',
}