fix(bugs): enhancements and bug fixes (#4036)
This commit is contained in:
parent
41a8d105f1
commit
e80fc6f477
@ -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
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -8,6 +8,7 @@ window.config = {
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
strictZSpacingForVolumeViewport: true,
|
||||
groupEnabledModesFirst: true,
|
||||
// filterQueryParam: false,
|
||||
defaultDataSourceName: 'dicomweb',
|
||||
dataSources: [
|
||||
|
||||
@ -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',
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 |
9
platform/ui/src/assets/icons/tool-seg-brush.svg
Normal file
9
platform/ui/src/assets/icons/tool-seg-brush.svg
Normal 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 |
10
platform/ui/src/assets/icons/tool-seg-eraser.svg
Normal file
10
platform/ui/src/assets/icons/tool-seg-eraser.svg
Normal 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 |
9
platform/ui/src/assets/icons/tool-seg-shape.svg
Normal file
9
platform/ui/src/assets/icons/tool-seg-shape.svg
Normal 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 |
17
platform/ui/src/assets/icons/tool-seg-threshold.svg
Normal file
17
platform/ui/src/assets/icons/tool-seg-threshold.svg
Normal 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 |
@ -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) => {
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 '}`
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -2,4 +2,5 @@ export enum PatientInfoVisibility {
|
||||
VISIBLE = 'visible',
|
||||
VISIBLE_COLLAPSED = 'visibleCollapsed',
|
||||
DISABLED = 'disabled',
|
||||
VISIBLE_READONLY = 'visibleReadOnly',
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user