feat(read-only config): read only ui for SEG/RT/SR (#3586)

This commit is contained in:
rodrigobasilio2022 2023-08-16 11:26:28 -03:00 committed by GitHub
parent c6d5f719e7
commit 3c4795d8cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 97 additions and 52 deletions

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { SegmentationGroupTable } from '@ohif/ui'; import { SegmentationGroupTable } from '@ohif/ui';
import callInputDialog from './callInputDialog'; import callInputDialog from './callInputDialog';
import { useAppConfig } from '@state';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export default function PanelSegmentation({ export default function PanelSegmentation({
@ -10,6 +10,8 @@ export default function PanelSegmentation({
commandsManager, commandsManager,
}) { }) {
const { segmentationService, uiDialogService } = servicesManager.services; const { segmentationService, uiDialogService } = servicesManager.services;
const [appConfig] = useAppConfig();
const disableEditing = appConfig?.disableEditing;
const { t } = useTranslation('PanelSegmentation'); const { t } = useTranslation('PanelSegmentation');
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
@ -203,6 +205,7 @@ export default function PanelSegmentation({
onSegmentationEdit={onSegmentationEdit} onSegmentationEdit={onSegmentationEdit}
onSegmentClick={onSegmentClick} onSegmentClick={onSegmentClick}
onSegmentEdit={onSegmentEdit} onSegmentEdit={onSegmentEdit}
disableEditing={disableEditing}
onSegmentColorClick={onSegmentColorClick} onSegmentColorClick={onSegmentColorClick}
onSegmentDelete={onSegmentDelete} onSegmentDelete={onSegmentDelete}
onToggleSegmentVisibility={onToggleSegmentVisibility} onToggleSegmentVisibility={onToggleSegmentVisibility}
@ -268,6 +271,7 @@ PanelSegmentation.propTypes = {
commandsManager: PropTypes.shape({ commandsManager: PropTypes.shape({
runCommand: PropTypes.func.isRequired, runCommand: PropTypes.func.isRequired,
}), }),
appConfig: PropTypes.object.isRequired,
servicesManager: PropTypes.shape({ servicesManager: PropTypes.shape({
services: PropTypes.shape({ services: PropTypes.shape({
segmentationService: PropTypes.shape({ segmentationService: PropTypes.shape({

View File

@ -2,6 +2,8 @@ import { utilities, metaData } from '@cornerstonejs/core';
import OHIF, { DicomMetadataStore } from '@ohif/core'; import OHIF, { DicomMetadataStore } from '@ohif/core';
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData'; import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
import { adaptersSR } from '@cornerstonejs/adapters'; import { adaptersSR } from '@cornerstonejs/adapters';
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
const { locking } = CsAnnotation;
const { guid } = OHIF.utils; const { guid } = OHIF.utils;
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D; const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
@ -41,9 +43,11 @@ const convertSites = (codingValues, sites) => {
* *
*/ */
export default function hydrateStructuredReport( export default function hydrateStructuredReport(
{ servicesManager, extensionManager }, { servicesManager, extensionManager, appConfig },
displaySetInstanceUID displaySetInstanceUID
) { ) {
const annotationManager = CsAnnotation.state.getAnnotationManager();
const disableEditing = appConfig?.disableEditing;
const dataSource = extensionManager.getActiveDataSource()[0]; const dataSource = extensionManager.getActiveDataSource()[0];
const { const {
measurementService, measurementService,
@ -218,7 +222,7 @@ export default function hydrateStructuredReport(
m => m.annotationType === annotationType m => m.annotationType === annotationType
); );
measurementService.addRawMeasurement( const newAnnotationUID = measurementService.addRawMeasurement(
source, source,
annotationType, annotationType,
{ annotation }, { annotation },
@ -226,6 +230,13 @@ export default function hydrateStructuredReport(
dataSource dataSource
); );
if (disableEditing) {
const addedAnnotation = annotationManager.getAnnotation(
newAnnotationUID
);
locking.setAnnotationLocked(addedAnnotation, true);
}
if (!imageIds.includes(imageId)) { if (!imageIds.includes(imageId)) {
imageIds.push(imageId); imageIds.push(imageId);
} }

View File

@ -7,6 +7,7 @@ import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSR
import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui';
import hydrateStructuredReport from '../utils/hydrateStructuredReport'; import hydrateStructuredReport from '../utils/hydrateStructuredReport';
import { useAppConfig } from '@state';
const { formatDate } = utils; const { formatDate } = utils;
@ -27,6 +28,8 @@ function OHIFCornerstoneSRViewport(props) {
extensionManager, extensionManager,
} = props; } = props;
const [appConfig] = useAppConfig();
const { const {
displaySetService, displaySetService,
cornerstoneViewportService, cornerstoneViewportService,
@ -76,7 +79,7 @@ function OHIFCornerstoneSRViewport(props) {
sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => { sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => {
measurementService.clearMeasurements(); measurementService.clearMeasurements();
const { SeriesInstanceUIDs } = hydrateStructuredReport( const { SeriesInstanceUIDs } = hydrateStructuredReport(
{ servicesManager, extensionManager }, { servicesManager, extensionManager, appConfig },
displaySetInstanceUID displaySetInstanceUID
); );
const displaySets = displaySetService.getDisplaySetsForSeries( const displaySets = displaySetService.getDisplaySetsForSeries(

View File

@ -13,6 +13,7 @@ import promptTrackNewStudy from './promptTrackNewStudy';
import promptSaveReport from './promptSaveReport'; import promptSaveReport from './promptSaveReport';
import promptHydrateStructuredReport from './promptHydrateStructuredReport'; import promptHydrateStructuredReport from './promptHydrateStructuredReport';
import hydrateStructuredReport from './hydrateStructuredReport'; import hydrateStructuredReport from './hydrateStructuredReport';
import { useAppConfig } from '@state';
const TrackedMeasurementsContext = React.createContext(); const TrackedMeasurementsContext = React.createContext();
TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext'; TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext';
@ -29,6 +30,8 @@ function TrackedMeasurementsContextProvider(
{ servicesManager, commandsManager, extensionManager }, // Bound by consumer { servicesManager, commandsManager, extensionManager }, // Bound by consumer
{ children } // Component props { children } // Component props
) { ) {
const [appConfig] = useAppConfig();
const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportGrid, viewportGridService] = useViewportGrid();
const { activeViewportIndex, viewports } = viewportGrid; const { activeViewportIndex, viewports } = viewportGrid;
const { measurementService, displaySetService } = servicesManager.services; const { measurementService, displaySetService } = servicesManager.services;
@ -125,27 +128,33 @@ function TrackedMeasurementsContextProvider(
promptBeginTracking: promptBeginTracking.bind(null, { promptBeginTracking: promptBeginTracking.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
appConfig,
}), }),
promptTrackNewSeries: promptTrackNewSeries.bind(null, { promptTrackNewSeries: promptTrackNewSeries.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
appConfig,
}), }),
promptTrackNewStudy: promptTrackNewStudy.bind(null, { promptTrackNewStudy: promptTrackNewStudy.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
appConfig,
}), }),
promptSaveReport: promptSaveReport.bind(null, { promptSaveReport: promptSaveReport.bind(null, {
servicesManager, servicesManager,
commandsManager, commandsManager,
extensionManager, extensionManager,
appConfig,
}), }),
promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, { promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
appConfig,
}), }),
hydrateStructuredReport: hydrateStructuredReport.bind(null, { hydrateStructuredReport: hydrateStructuredReport.bind(null, {
servicesManager, servicesManager,
extensionManager, extensionManager,
appConfig,
}), }),
}); });
@ -245,6 +254,7 @@ TrackedMeasurementsContextProvider.propTypes = {
servicesManager: PropTypes.object.isRequired, servicesManager: PropTypes.object.isRequired,
commandsManager: PropTypes.object.isRequired, commandsManager: PropTypes.object.isRequired,
extensionManager: PropTypes.object.isRequired, extensionManager: PropTypes.object.isRequired,
appConfig: PropTypes.object,
}; };
export { export {

View File

@ -12,7 +12,7 @@ const RESPONSE = {
}; };
function promptHydrateStructuredReport( function promptHydrateStructuredReport(
{ servicesManager, extensionManager }, { servicesManager, extensionManager, appConfig },
ctx, ctx,
evt evt
) { ) {
@ -25,7 +25,7 @@ function promptHydrateStructuredReport(
displaySetInstanceUID displaySetInstanceUID
); );
return new Promise(async function(resolve, reject) { return new Promise(async function (resolve, reject) {
const promptResult = await _askTrackMeasurements( const promptResult = await _askTrackMeasurements(
uiViewportDialogService, uiViewportDialogService,
viewportIndex viewportIndex
@ -37,7 +37,7 @@ function promptHydrateStructuredReport(
if (promptResult === RESPONSE.HYDRATE_REPORT) { if (promptResult === RESPONSE.HYDRATE_REPORT) {
console.warn('!! HYDRATING STRUCTURED REPORT'); console.warn('!! HYDRATING STRUCTURED REPORT');
const hydrationResult = hydrateStructuredReport( const hydrationResult = hydrateStructuredReport(
{ servicesManager, extensionManager }, { servicesManager, extensionManager, appConfig },
displaySetInstanceUID displaySetInstanceUID
); );
@ -57,7 +57,7 @@ function promptHydrateStructuredReport(
} }
function _askTrackMeasurements(uiViewportDialogService, viewportIndex) { function _askTrackMeasurements(uiViewportDialogService, viewportIndex) {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
const message = const message =
'Do you want to continue tracking measurements for this study?'; 'Do you want to continue tracking measurements for this study?';
const actions = [ const actions = [

View File

@ -484,7 +484,7 @@ class MeasurementService extends PubSubService {
}); });
} }
return newMeasurement.id; return newMeasurement.uid;
} }
/** /**

View File

@ -111,7 +111,8 @@ window.config = ({ servicesManager } = {}) => {
## Configuration Options ## Configuration Options
Here are a list of some options available: Here are a list of some options available:
- `disableEditing`: If true, it disables editing in OHIF, hiding edit buttons in segmentation
panel and locking already stored measurements.
- `maxNumberOfWebWorkers`: The maximum number of web workers to use for - `maxNumberOfWebWorkers`: The maximum number of web workers to use for
decoding. Defaults to minimum of `navigator.hardwareConcurrency` and decoding. Defaults to minimum of `navigator.hardwareConcurrency` and
what is specified by `maxNumberOfWebWorkers`. Some windows machines require smaller values. what is specified by `maxNumberOfWebWorkers`. Some windows machines require smaller values.

View File

@ -13,11 +13,12 @@ const AddNewSegmentRow = ({
onSegmentAdd, onSegmentAdd,
isVisible, isVisible,
showAddSegment, showAddSegment,
disableEditing,
}) => { }) => {
return ( return (
<div> <div>
<div className="flex items-center pl-[29px] bg-black text-primary-active hover:opacity-80 cursor-pointer text-[12px] py-1"> <div className="flex items-center pl-[29px] bg-black text-primary-active hover:opacity-80 cursor-pointer text-[12px] py-1">
{showAddSegment && ( {showAddSegment && !disableEditing && (
<div className="flex items-center" onClick={() => onSegmentAdd()}> <div className="flex items-center" onClick={() => onSegmentAdd()}>
<Icon name="row-add" className="w-5 h-5" /> <Icon name="row-add" className="w-5 h-5" />
<div className="">Add Segment</div> <div className="">Add Segment</div>
@ -53,6 +54,7 @@ const SegmentGroupHeader = ({
isActive, isActive,
segmentCount, segmentCount,
onSegmentationEdit, onSegmentationEdit,
disableEditing,
onSegmentationDelete, onSegmentationDelete,
}) => { }) => {
return ( return (
@ -92,6 +94,7 @@ const SegmentGroupHeader = ({
e.stopPropagation(); e.stopPropagation();
}} }}
> >
{!disableEditing && (
<Dropdown <Dropdown
id="segmentation-dropdown" id="segmentation-dropdown"
showDropdownIcon={false} showDropdownIcon={false}
@ -120,6 +123,7 @@ const SegmentGroupHeader = ({
<Icon name="panel-group-more" /> <Icon name="panel-group-more" />
</IconButton> </IconButton>
</Dropdown> </Dropdown>
)}
</div> </div>
</div> </div>
); );
@ -134,6 +138,7 @@ const SegmentationGroup = ({
onSegmentClick, onSegmentClick,
isMinimized, isMinimized,
onSegmentColorClick, onSegmentColorClick,
disableEditing,
showAddSegment, showAddSegment,
segments, segments,
activeSegmentIndex, activeSegmentIndex,
@ -162,6 +167,7 @@ const SegmentationGroup = ({
segmentCount={segmentCount} segmentCount={segmentCount}
onSegmentationEdit={onSegmentationEdit} onSegmentationEdit={onSegmentationEdit}
onSegmentationDelete={onSegmentationDelete} onSegmentationDelete={onSegmentationDelete}
disableEditing={disableEditing}
/> />
{!isMinimized && ( {!isMinimized && (
<div className="flex flex-col flex-auto min-h-0"> <div className="flex flex-col flex-auto min-h-0">
@ -171,6 +177,7 @@ const SegmentationGroup = ({
isVisible={isVisible} isVisible={isVisible}
onToggleSegmentationVisibility={onToggleSegmentationVisibility} onToggleSegmentationVisibility={onToggleSegmentationVisibility}
id={id} id={id}
disableEditing={disableEditing}
showAddSegment={showAddSegment} showAddSegment={showAddSegment}
/> />
<div className="flex flex-col min-h-0 ohif-scrollbar overflow-y-hidden"> <div className="flex flex-col min-h-0 ohif-scrollbar overflow-y-hidden">
@ -197,6 +204,7 @@ const SegmentationGroup = ({
isActive={activeSegmentIndex === segmentIndex} isActive={activeSegmentIndex === segmentIndex}
isLocked={isLocked} isLocked={isLocked}
isVisible={isVisible} isVisible={isVisible}
disableEditing={disableEditing}
onClick={onSegmentClick} onClick={onSegmentClick}
onEdit={onSegmentEdit} onEdit={onSegmentEdit}
onDelete={onSegmentDelete} onDelete={onSegmentDelete}
@ -241,6 +249,7 @@ SegmentationGroup.propTypes = {
onSegmentationConfigChange: PropTypes.func.isRequired, onSegmentationConfigChange: PropTypes.func.isRequired,
onSegmentationDelete: PropTypes.func.isRequired, onSegmentationDelete: PropTypes.func.isRequired,
onSegmentEdit: PropTypes.func.isRequired, onSegmentEdit: PropTypes.func.isRequired,
disableEditing: PropTypes.bool,
}; };
SegmentationGroup.defaultProps = { SegmentationGroup.defaultProps = {

View File

@ -12,6 +12,7 @@ const SegmentItem = ({
isVisible, isVisible,
color, color,
showSegmentDelete, showSegmentDelete,
disableEditing,
isLocked = false, isLocked = false,
onClick, onClick,
onEdit, onEdit,
@ -116,6 +117,7 @@ const SegmentItem = ({
)} )}
{isHovering && ( {isHovering && (
<div className={classnames('flex items-center')}> <div className={classnames('flex items-center')}>
{!disableEditing && (
<Icon <Icon
name="row-edit" name="row-edit"
className={classnames('w-5 h-5', { className={classnames('w-5 h-5', {
@ -127,6 +129,7 @@ const SegmentItem = ({
onEdit(segmentationId, segmentIndex); onEdit(segmentationId, segmentIndex);
}} }}
/> />
)}
{isVisible ? ( {isVisible ? (
<Icon <Icon
name="row-hide" name="row-hide"
@ -224,6 +227,7 @@ SegmentItem.propTypes = {
segmentIndex: PropTypes.number.isRequired, segmentIndex: PropTypes.number.isRequired,
segmentationId: PropTypes.string.isRequired, segmentationId: PropTypes.string.isRequired,
label: PropTypes.string, label: PropTypes.string,
disableEditing: PropTypes.bool,
// color as array // color as array
color: PropTypes.array, color: PropTypes.array,
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,

View File

@ -15,6 +15,7 @@ const SegmentationGroupTable = ({
onSegmentClick, onSegmentClick,
onSegmentAdd, onSegmentAdd,
segmentationConfig, segmentationConfig,
disableEditing,
onSegmentDelete, onSegmentDelete,
onSegmentEdit, onSegmentEdit,
onToggleSegmentationVisibility, onToggleSegmentationVisibility,
@ -60,6 +61,7 @@ const SegmentationGroupTable = ({
id={id} id={id}
key={id} key={id}
label={label} label={label}
disableEditing={disableEditing}
isMinimized={isMinimized[id]} isMinimized={isMinimized[id]}
segments={segments} segments={segments}
showAddSegment={showAddSegment} showAddSegment={showAddSegment}
@ -82,7 +84,7 @@ const SegmentationGroupTable = ({
); );
})} })}
</div> </div>
{showAddSegmentation && ( {showAddSegmentation && !disableEditing && (
<div <div
className="flex items-center cursor-pointer hover:opacity-80 text-primary-active bg-black text-[12px] pl-1 h-[45px]" className="flex items-center cursor-pointer hover:opacity-80 text-primary-active bg-black text-[12px] pl-1 h-[45px]"
onClick={() => onSegmentationAdd()} onClick={() => onSegmentationAdd()}
@ -106,6 +108,7 @@ SegmentationGroupTable.propTypes = {
onToggleVisibility: PropTypes.func.isRequired, onToggleVisibility: PropTypes.func.isRequired,
onToggleVisibilityAll: PropTypes.func.isRequired, onToggleVisibilityAll: PropTypes.func.isRequired,
segmentationConfig: PropTypes.object, segmentationConfig: PropTypes.object,
disableEditing: PropTypes.bool,
}; };
SegmentationGroupTable.defaultProps = { SegmentationGroupTable.defaultProps = {