Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(segmentation): Enhanced segmentation panel design for TMTV #3988

Merged
merged 19 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion extensions/cornerstone-dicom-seg/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ const commandsModule = ({
loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => {
// Todo: handle adding more than one segmentation
const displaySet = displaySets[0];
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
displaySet.referencedDisplaySetInstanceUID
);

updateViewportsForSegmentationRendering({
viewportId,
Expand All @@ -221,7 +224,8 @@ const commandsModule = ({

const boundFn = segmentationService[serviceFunction].bind(segmentationService);
const segmentationId = await boundFn(segDisplaySet, null, suppressEvents);

const segmentation = segmentationService.getSegmentation(segmentationId);
segmentation.description = `S${referencedDisplaySet.SeriesNumber}: ${referencedDisplaySet.SeriesDescription}`;
return segmentationId;
},
});
Expand Down
11 changes: 11 additions & 0 deletions extensions/cornerstone-dicom-seg/src/getPanelModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { useAppConfig } from '@state';
import { Toolbox } from '@ohif/ui';
import PanelSegmentation from './panels/PanelSegmentation';
import { SegmentationPanelMode } from './types/segmentation';

const getPanelModule = ({
commandsManager,
Expand All @@ -17,6 +18,9 @@ const getPanelModule = ({
const [appConfig] = useAppConfig();

const disableEditingForMode = customizationService.get('segmentation.disableEditing');
const segmentationPanelMode =
customizationService.get('segmentation.segmentationPanelMode')?.value ||
SegmentationPanelMode.Dropdown;

return (
<PanelSegmentation
Expand All @@ -26,12 +30,18 @@ const getPanelModule = ({
configuration={{
...configuration,
disableEditing: appConfig.disableEditing || disableEditingForMode?.value,
segmentationPanelMode: segmentationPanelMode,
}}
/>
);
};

const wrappedPanelSegmentationWithTools = configuration => {
const [appConfig] = useAppConfig();
const segmentationPanelMode =
customizationService.get('segmentation.segmentationPanelMode')?.value ||
SegmentationPanelMode.Dropdown;

return (
<>
<Toolbox
Expand All @@ -50,6 +60,7 @@ const getPanelModule = ({
extensionManager={extensionManager}
configuration={{
...configuration,
segmentationPanelMode: segmentationPanelMode,
}}
/>
</>
Expand Down
4 changes: 3 additions & 1 deletion extensions/cornerstone-dicom-seg/src/getToolbarModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export function getToolbarModule({ commandsManager, servicesManager }) {
return [
{
name: 'evaluate.cornerstone.segmentation',
evaluate: ({ viewportId, button, toolNames }) => {
evaluate: ({ viewportId, button, toolNames, disabledText }) => {
// Todo: we need to pass in the button section Id since we are kind of
// forcing the button to have black background since initially
// it is designed for the toolbox not the toolbar on top
Expand All @@ -13,6 +13,7 @@ export function getToolbarModule({ commandsManager, servicesManager }) {
return {
disabled: true,
className: '!text-common-bright !bg-black opacity-50',
disabledText: disabledText ?? 'No segmentations available',
};
}

Expand All @@ -28,6 +29,7 @@ export function getToolbarModule({ commandsManager, servicesManager }) {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}

Expand Down
123 changes: 69 additions & 54 deletions extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { createReportAsync } from '@ohif/extension-default';
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { SegmentationGroupTable } from '@ohif/ui';

import { SegmentationGroupTable, SegmentationGroupTableExpanded } from '@ohif/ui';
import { SegmentationPanelMode } from '../types/segmentation';
import callInputDialog from './callInputDialog';
import callColorPickerDialog from './colorPickerDialog';
import { useTranslation } from 'react-i18next';

const components = {
[SegmentationPanelMode.Expanded]: SegmentationGroupTableExpanded,
[SegmentationPanelMode.Dropdown]: SegmentationGroupTable,
};

export default function PanelSegmentation({
servicesManager,
commandsManager,
Expand Down Expand Up @@ -170,6 +175,22 @@ export default function PanelSegmentation({

const onToggleSegmentationVisibility = segmentationId => {
segmentationService.toggleSegmentationVisibility(segmentationId);
const segmentation = segmentationService.getSegmentation(segmentationId);
const isVisible = segmentation.isVisible;
const segments = segmentation.segments;

const toolGroupIds = getToolGroupIds(segmentationId);

toolGroupIds.forEach(toolGroupId => {
segments.forEach((segment, segmentIndex) => {
segmentationService.setSegmentVisibility(
segmentationId,
segmentIndex,
isVisible,
toolGroupId
);
});
});
};

const _setSegmentationConfiguration = useCallback(
Expand Down Expand Up @@ -221,59 +242,53 @@ export default function PanelSegmentation({
});
};

const SegmentationGroupTableComponent = components[configuration?.segmentationPanelMode];

return (
<>
<div className="ohif-scrollbar flex min-h-0 flex-auto select-none flex-col justify-between overflow-auto">
<SegmentationGroupTable
title={t('Segmentations')}
segmentations={segmentations}
disableEditing={configuration.disableEditing}
activeSegmentationId={selectedSegmentationId || ''}
onSegmentationAdd={onSegmentationAdd}
onSegmentationClick={onSegmentationClick}
onSegmentationDelete={onSegmentationDelete}
onSegmentationDownload={onSegmentationDownload}
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
storeSegmentation={storeSegmentation}
onSegmentationEdit={onSegmentationEdit}
onSegmentClick={onSegmentClick}
onSegmentEdit={onSegmentEdit}
onSegmentAdd={onSegmentAdd}
onSegmentColorClick={onSegmentColorClick}
onSegmentDelete={onSegmentDelete}
onToggleSegmentVisibility={onToggleSegmentVisibility}
onToggleSegmentLock={onToggleSegmentLock}
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
showDeleteSegment={true}
segmentationConfig={{ initialConfig: segmentationConfiguration }}
setRenderOutline={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value)
}
setOutlineOpacityActive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value)
}
setRenderFill={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value)
}
setRenderInactiveSegmentations={value =>
_setSegmentationConfiguration(
selectedSegmentationId,
'renderInactiveSegmentations',
value
)
}
setOutlineWidthActive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value)
}
setFillAlpha={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value)
}
setFillAlphaInactive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
}
/>
</div>
</>
<SegmentationGroupTableComponent
title={t('Segmentations')}
segmentations={segmentations}
disableEditing={configuration.disableEditing}
activeSegmentationId={selectedSegmentationId || ''}
onSegmentationAdd={onSegmentationAdd}
onSegmentationClick={onSegmentationClick}
onSegmentationDelete={onSegmentationDelete}
onSegmentationDownload={onSegmentationDownload}
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
storeSegmentation={storeSegmentation}
onSegmentationEdit={onSegmentationEdit}
onSegmentClick={onSegmentClick}
onSegmentEdit={onSegmentEdit}
onSegmentAdd={onSegmentAdd}
onSegmentColorClick={onSegmentColorClick}
onSegmentDelete={onSegmentDelete}
onToggleSegmentVisibility={onToggleSegmentVisibility}
onToggleSegmentLock={onToggleSegmentLock}
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
showDeleteSegment={true}
segmentationConfig={{ initialConfig: segmentationConfiguration }}
setRenderOutline={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value)
}
setOutlineOpacityActive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value)
}
setRenderFill={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value)
}
setRenderInactiveSegmentations={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'renderInactiveSegmentations', value)
}
setOutlineWidthActive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value)
}
setFillAlpha={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value)
}
setFillAlphaInactive={value =>
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
}
/>
);
}

Expand Down
4 changes: 4 additions & 0 deletions extensions/cornerstone-dicom-seg/src/types/segmentation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum SegmentationPanelMode {
Expanded = 'expanded',
Dropdown = 'dropdown',
}
12 changes: 8 additions & 4 deletions extensions/cornerstone/src/getToolbarModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
// enabled or not
{
name: 'evaluate.cornerstoneTool',
evaluate: ({ viewportId, button }) => {
evaluate: ({ viewportId, button, disabledText }) => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);

if (!toolGroup) {
Expand All @@ -34,6 +34,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}

Expand Down Expand Up @@ -109,7 +110,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
},
{
name: 'evaluate.cornerstoneTool.toggle',
evaluate: ({ viewportId, button }) => {
evaluate: ({ viewportId, button, disabledText }) => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);

if (!toolGroup) {
Expand All @@ -121,6 +122,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}

Expand Down Expand Up @@ -168,13 +170,14 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
},
{
name: 'evaluate.not3D',
evaluate: ({ viewportId, button }) => {
evaluate: ({ viewportId, disabledText }) => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);

if (viewport?.type === 'volume3d') {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}
},
Expand Down Expand Up @@ -211,7 +214,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
},
{
name: 'evaluate.mpr',
evaluate: ({ viewportId, button }) => {
evaluate: ({ viewportId, disabledText = 'Selected viewport is not reconstructable' }) => {
const { protocol } = hangingProtocolService.getActiveProtocol();

const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
Expand All @@ -230,6 +233,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,7 @@ class SegmentationService extends PubSubService {
referencedVolumeId: volumeId, // Todo: this is so ugly
},
},
description: `S${displaySet.SeriesNumber}: ${displaySet.SeriesDescription}`,
};

this.addOrUpdateSegmentation(segmentation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type Segment = {
isVisible: boolean;
// whether the segment is locked
isLocked: boolean;
// display texts
displayText?: string[];
};

type Segmentation = {
Expand Down
13 changes: 1 addition & 12 deletions extensions/default/src/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ export function Toolbar({ servicesManager }) {
}

const { id, Component, componentProps } = toolDef;
const { disabled } = componentProps;

const tool = (
<Component
key={id}
Expand All @@ -33,16 +31,7 @@ export function Toolbar({ servicesManager }) {
/>
);

return disabled ? (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate tooltip bug

<Tooltip
key={id}
position="bottom"
content={componentProps.label}
secondaryContent={'Not available on the current viewport'}
>
<div className={classnames('mr-1')}>{tool}</div>
</Tooltip>
) : (
return (
<div
key={id}
className="mr-1"
Expand Down
7 changes: 7 additions & 0 deletions extensions/tmtv/.webpack/webpack.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const ENTRY = {
app: `${SRC_DIR}/index.tsx`,
};

const outputName = `ohif-${pkg.name.split('/').pop()}`;

module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });

Expand Down Expand Up @@ -42,6 +45,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new MiniCssExtractPlugin({
filename: `./dist/${outputName}.css`,
chunkFilename: `./dist/${outputName}.css`,
}),
],
});
};
Loading
Loading