feat: cornerstone3D stack and volume viewports (#2787)

* feat: cs3d working stack viewport and tools (#19)

* Squashed everything

* fix weird react issue

* fix eslint / prettier stuff

* thumbnails work now with cpu rendering

* feat: use new loadImageToCanvas in cs3d

* make jump to measurement work

* fix active thumbnail

* fix measurement delete

* fix window level presets

* remove segmentation and sync groups for now

* fix the dicom pdf and dicom video

* fix cornerstone window assignment for cypress

* apply review comments

Co-authored-by: Erik <erik.sweed@gmail.com>

* feat: cs3d tools and toolGroups (#20)

* add more tools to work with cs3d mode

* fix hotkeys

* add stack manager usage for stach viewports

* add image scrollbar

* wip viewport overlay

* fix toAnnotation schema for tools

* fix the unnecessary size change that triggered resize

* hanging protocol improvement to allow unmatched errors

* study description matching for hanging protocol

* fix the displaysetOptions to work

* fix handle the active tool when a new viewport is added

* fix separate toolGroups for mode

* apply review comments

* apply review comments

* yarn lock

* feat: overlay component (#21)

* fix default displayset options

* add viewport overlay

* apply review comments

* feat: loading and orientation indicators (#22)

* add loading indicator

* add orientation marker initial work

* apply review comments

* fix: orientation markers (#23)

* finished the orientation markers

* fix various broken cypress tests

* apply review comments

* update yarn lock

* feat: re-working measurement tracking mode with cornerstone3d (#2805)

* feat: cs3d working stack viewport and tools (#19)

* Squashed everything

* fix weird react issue

* fix eslint / prettier stuff

* thumbnails work now with cpu rendering

* feat: use new loadImageToCanvas in cs3d

* make jump to measurement work

* fix active thumbnail

* fix measurement delete

* fix window level presets

* remove segmentation and sync groups for now

* fix the dicom pdf and dicom video

* fix cornerstone window assignment for cypress

* apply review comments

Co-authored-by: Erik <erik.sweed@gmail.com>

* feat: Add Measurement tracking mode with cs3D (#2789)

* feat: first render for cornerstone3d tracked viewport

* make tool active work

* wip for SR extension

* renamed dicom sr to cornerstone dicom sr

* remove cornerstone from dicom pdf and video

* move dicom sr logic to sr extension

* feat: Add hydration for the length tool

* fix SR display tool for length using cs3d

* fix default config

* fix: various bugs with sr viewport and tracking

* fix promptying to continue tracking for when SR is created

* feat: add keep trackign of unique identifiers

* fix hydration for same imageIds

* feat: Add SR toolGroup creation on modeEnter

* feat: remove the need for separate mapper for SR hydration

* add SR display for ellipse

* handle hydration of elliptical ROI tool

* remove cornerstone extension

* add arrow mapping

* feat: Add ArrowAnnotate SR display and hydration

* apply review comments

* apply review comments

* move viewport labels to the viewportData

* apply review comments

* fix: integration cypress tests with cornerstone3D and add CINE tool (#2795)

* fix: integration cypress tests with cornerstone3D

* revert to addOrUpdate as it makes more sense

* fix local drag and drop

* fix tests

* move dicomLoaderService to cornerstone extension

* fix various import bugs

* fix bug for local PT series

* fix various unit tests

* bump cs3d versions

* add angle and magnify tool

* bump dependencies to avoid broken peerDeps

* feat: add initial work for capture using cs3d

* feat: show annotations on the image capture

* feat: add svg layer export

* feat: Add CINE Tool

* feat: remove unnecessary viewport rendering for cine state changes

Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>

* docs: modify and improve documentation (#2800)

* cleanup docs versionings

* feat: Add all version explanations

* version docs for 3.0

* wip for changing docs

* wip for updated docs

* add utility module documentation

* fix demo with nohoisting of history

* add slides and video to resources

* apply review comments

* fix: drag and drop SR into SR viewport (#2803)

* update yarn lock

Co-authored-by: Erik <erik.sweed@gmail.com>

* update pathnames to match v3-stable

* update the e2e pathname

* fix: various bugs with regard to tracking workflow (#2811)

* fix: various issues with measurement panel

* fix: update default tool style for annotations

* fix: annotatoin label getting removed

* feat: Add backward compatibility for SR hydration with legacy cornerstone

* fix: cursors and ellipse ROI max style

* fix: ArrowAnnotate SRDisplay

* apply review comments

* bump package versions

* fix: bugin rehydration of SR

* fix: e2e tests

* fix active viewport thickness and arrowTool ui

* add readme for measurement tracking

* use uploaded image for readme

* add back images

* try to fix e2e test

* fix: window level presets hotkeys

* Update README.md

* update yarn lock

* feat: volume api and TMTV mode (#2817)

* feat: volumeAPI and TMTV mode

* feat: use cs3d cache service to obtain viewportData

* wip for volume api

* wip: fusion viewport

* feat: add blend mode option

* wip for image scrollbar

* fix drag and drop thumbnail

* wip for image scrollbar for voluems

* fix: element mismatch bug for scroll

* feat: Add image scrollbar to volumes

* feat: add syncGroups to volume api

* feat: add tmtv mode initial setup

* feat: add initial image options for the stack viewports

* feat: Add custom load strategy for volume viewports via HP

* apply review comments

fix: Jump presets cs3d (#2812)

* feat: Add JumpPreset to OHIF for Cornerstone3D

* fix: accessing viewport service from servicesManager

feat: volume API and TMTV mode (#2814)

* fix: do not display overlays on mip viewports

* feat: add optional disableCommands for toggle buttons

* fix: toggleCrossharis

* feat: config the crosshairs

* feat: add PetSUV Panel for changing metadata

* feat: initial work for the rectangleROIThreshold panel

* wip: measurement service

* roi threshold working

* feat: Add displayText to segmentations

* feat: add remove segmentation

* feat: add csv export

* feat: add RT export for annotations in tmtv mode

* fix: fusion to use pt in tmtv mode and measuremet mappings

* fix: various bugs

* apply review comments

* apply review comments

* feat: add fusion color maps

* add readme to tmtv mode

* Update README.md

* fix: try to fix build

* fix unit tests

* Update README.md

* feat: add about to the panel

* fix: changing strategy in roi panel

* apply review comments

* update package versions

* wip for stackPrefetch

* fix: cornerstone3d hydration and renaming (#2818)

* update readme

* renamed cornerstone extension

* wip for renaming cornerstone3D variables

* wip for renaming cornerstone3D variables

* wip for fixing bugs for SR viewport

* fix: jumpToMeasurement and initial label after hydration

* fix: fileName capitalization

* feat: use the new prefetch stack in the cs3d (#2820)

* feat: use the new prefetch stack in the cs3d

* use viewport scroll api for stack viewport

* fix cine stop when scrollbar changes

* feat: use new prefetch events

* fix loading state to not show repeatedly

* fix: various bugs for tmtv mode thresholding and new icons (#2823)

* feat: make tmtv mode available in worklist

* feat: add new icons for tmtv mode

* feat: add fusion color icon

* fix: parallel scale calculation

* fix: bump Cornerstone to get large image support working

* fix: Fix issues with CPU viewport flipping, including config files

* fix: bump cornerstone to fix magnify tool

* fix: Bump Cornerstone version to fix resetCamera issue in StackViewport

* ci: Add _headers file to enable CORS headers for Netlify Drag/Drop deploys

* fix: WADO-URI was not working. PET Metadata was coming from the wrong place. Fixed some minor React errors

* feat(OHIF):Allow modes and extensions to be added after commpile time. (#2838)

Also works with the compile time add that the existing cli uses, so that both build types work.
Eric and I agreed this doesn't change existing functionality, but is almost entirely build issues/fixes.

* bump: dependency versions to fix hydration bugs (#2848)

* bump: dcmjs version to fix hydration bugs

* try to fix tests

* bump dependency versions

Co-authored-by: Alireza <ar.sedghi@gmail.com>
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
authored and GitHub committed 2022-07-27 12:39:04 -04:00
1 parent 86dcaf6293
commit bd1c38c00c
668 files changed
+41669 -13410

No files matched your search

+243
View File
@@ -0,0 +1,243 @@
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { Input, Button } from '@ohif/ui';
import { classes, DicomMetadataStore } from '@ohif/core';
import { useTranslation } from 'react-i18next';
const DEFAULT_MEATADATA = {
PatientWeight: null,
PatientSex: null,
SeriesTime: null,
RadiopharmaceuticalInformationSequence: {
RadionuclideTotalDose: null,
RadionuclideHalfLife: null,
RadiopharmaceuticalStartTime: null,
},
};
/*
* PETSUV panel enables the user to modify the patient related information, such as
* patient sex, patientWeight. This is allowed since
* sometimes these metadata are missing or wrong. By changing them
* @param param0
* @returns
*/
export default function PanelPetSUV({ servicesManager, commandsManager }) {
const { t } = useTranslation('PanelSUV');
const { DisplaySetService } = servicesManager.services;
const [metadata, setMetadata] = useState(DEFAULT_MEATADATA);
const [ptDisplaySet, setPtDisplaySet] = useState(null);
const handleMetadataChange = useCallback(
metadata => {
setMetadata(prevState => {
const newState = { ...prevState };
Object.keys(metadata).forEach(key => {
if (typeof metadata[key] === 'object') {
newState[key] = {
...prevState[key],
...metadata[key],
};
} else {
newState[key] = metadata[key];
}
});
return newState;
});
},
[metadata]
);
const getMatchingPTDisplaySet = useCallback(() => {
const ptDisplaySet = commandsManager.runCommand('getMatchingPTDisplaySet');
if (!ptDisplaySet) {
return;
}
const metadata = commandsManager.runCommand('getPTMetadata', {
ptDisplaySet,
});
return {
ptDisplaySet,
metadata,
};
}, []);
useEffect(() => {
const displaySets = DisplaySetService.activeDisplaySets;
if (!displaySets.length) {
return;
}
const displaySetInfo = getMatchingPTDisplaySet();
if (!displaySetInfo) {
return;
}
const { ptDisplaySet, metadata } = displaySetInfo;
setPtDisplaySet(ptDisplaySet);
setMetadata(metadata);
}, []);
// get the patientMetadata from the StudyInstanceUIDs and update the state
useEffect(() => {
const { unsubscribe } = DisplaySetService.subscribe(
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
() => {
const displaySetInfo = getMatchingPTDisplaySet();
if (!displaySetInfo) {
return;
}
const { ptDisplaySet, metadata } = displaySetInfo;
setPtDisplaySet(ptDisplaySet);
setMetadata(metadata);
}
);
return () => {
unsubscribe();
};
}, []);
function updateMetadata() {
if (!ptDisplaySet) {
throw new Error('No ptDisplaySet found');
}
// metadata should be dcmjs naturalized
DicomMetadataStore.updateMetadataForSeries(
ptDisplaySet.StudyInstanceUID,
ptDisplaySet.SeriesInstanceUID,
metadata
);
// update the displaySets
DisplaySetService.setDisplaySetMetadataInvalidated(
ptDisplaySet.displaySetInstanceUID
);
}
return (
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
{
<div className="flex flex-col">
<div className="flex flex-col p-4 space-y-2 bg-primary-dark">
<Input
label={t('Patient Sex')}
labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={metadata.PatientSex || ''}
onChange={e => {
handleMetadataChange({
PatientSex: e.target.value,
});
}}
/>
<Input
label={t('Patient Weight')}
labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={metadata.PatientWeight || ''}
onChange={e => {
handleMetadataChange({
PatientWeight: e.target.value,
});
}}
/>
<Input
label={t('Total Dose')}
labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={
metadata.RadiopharmaceuticalInformationSequence
.RadionuclideTotalDose || ''
}
onChange={e => {
handleMetadataChange({
RadiopharmaceuticalInformationSequence: {
RadionuclideTotalDose: e.target.value,
},
});
}}
/>
<Input
label={t('Half Life')}
labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={
metadata.RadiopharmaceuticalInformationSequence
.RadionuclideHalfLife || ''
}
onChange={e => {
handleMetadataChange({
RadiopharmaceuticalInformationSequence: {
RadionuclideHalfLife: e.target.value,
},
});
}}
/>
<Input
label={t('Injection Time')}
labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={
metadata.RadiopharmaceuticalInformationSequence
.RadiopharmaceuticalStartTime || ''
}
onChange={e => {
handleMetadataChange({
RadiopharmaceuticalInformationSequence: {
RadiopharmaceuticalStartTime: e.target.value,
},
});
}}
/>
<Input
label={t('Acquisition Time')}
labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={metadata.SeriesTime || ''}
onChange={() => {}}
/>
</div>
<Button
color="primary"
onClick={updateMetadata}
className="px-1 py-1 mx-4 mt-2 text-xs text-white border-b border-transparent"
>
Reload Data
</Button>
</div>
}
</div>
);
}
PanelPetSUV.propTypes = {
servicesManager: PropTypes.shape({
services: PropTypes.shape({
MeasurementService: PropTypes.shape({
getMeasurements: PropTypes.func.isRequired,
subscribe: PropTypes.func.isRequired,
EVENTS: PropTypes.object.isRequired,
VALUE_TYPES: PropTypes.object.isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
@@ -0,0 +1,44 @@
import React from 'react';
import { Button, ButtonGroup } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
function ExportReports({ segmentations, tmtvValue, config, commandsManager }) {
const { t } = useTranslation('PanelSUVExport');
return (
<>
{segmentations?.length ? (
<div className="flex justify-center mt-4 space-x-2">
<ButtonGroup color="black" size="inherit">
<Button
className="px-2 py-2 text-base"
disabled={tmtvValue === null}
onClick={() => {
commandsManager.runCommand('exportTMTVReportCSV', {
segmentations,
tmtv: tmtvValue,
config,
});
}}
>
{t('Export CSV')}
</Button>
</ButtonGroup>
<ButtonGroup color="black" size="inherit">
<Button
className="px-2 py-2 text-base"
onClick={() => {
commandsManager.runCommand('createTMTVRTReport');
}}
disabled={tmtvValue === null}
>
{t('Create RT Report')}
</Button>
</ButtonGroup>
</div>
) : null}
</>
);
}
export default ExportReports;
@@ -0,0 +1,322 @@
import React, { useEffect, useState, useCallback, useReducer } from 'react';
import PropTypes from 'prop-types';
import { SegmentationTable, Button, Icon } from '@ohif/ui';
import classnames from 'classnames';
import { useTranslation } from 'react-i18next';
import segmentationEditHandler from './segmentationEditHandler';
import ExportReports from './ExportReports';
import ROIThresholdConfiguration, {
ROI_STAT,
} from './ROIThresholdConfiguration';
const LOWER_THRESHOLD_DEFAULT = 0;
const UPPER_THRESHOLD_DEFAULT = 100;
const WEIGHT_DEFAULT = 0.41; // a default weight for suv max often used in the literature
const DEFAULT_STRATEGY = ROI_STAT;
function reducer(state, action) {
const { payload } = action;
const { strategy, lower, upper, weight } = payload;
switch (action.type) {
case 'setStrategy':
return {
...state,
strategy,
};
case 'setThreshold':
return {
...state,
lower: lower ? lower : state.lower,
upper: upper ? upper : state.upper,
};
case 'setWeight':
return {
...state,
weight,
};
default:
return state;
}
}
export default function PanelRoiThresholdSegmentation({
servicesManager,
commandsManager,
}) {
const { SegmentationService } = servicesManager.services;
const { t } = useTranslation('PanelSUV');
const [showConfig, setShowConfig] = useState(false);
const [labelmapLoading, setLabelmapLoading] = useState(false);
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
const [segmentations, setSegmentations] = useState(() =>
SegmentationService.getSegmentations()
);
const [config, dispatch] = useReducer(reducer, {
strategy: DEFAULT_STRATEGY,
lower: LOWER_THRESHOLD_DEFAULT,
upper: UPPER_THRESHOLD_DEFAULT,
weight: WEIGHT_DEFAULT,
});
const [tmtvValue, setTmtvValue] = useState(null);
const runCommand = useCallback(
(commandName, commandOptions = {}) => {
return commandsManager.runCommand(commandName, commandOptions);
},
[commandsManager]
);
const handleTMTVCalculation = useCallback(() => {
const tmtv = runCommand('calculateTMTV', { segmentations });
if (tmtv !== undefined) {
setTmtvValue(tmtv.toFixed(2));
}
}, [segmentations, runCommand]);
const handleROIThresholding = useCallback(() => {
const labelmap = runCommand('thresholdSegmentationByRectangleROITool', {
segmentationId: selectedSegmentationId,
config,
});
const lesionStats = runCommand('getLesionStats', { labelmap });
const suvPeak = runCommand('calculateSuvPeak', { labelmap });
const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue;
// update segDetails with the suv peak for the active segmentation
const segmentation = SegmentationService.getSegmentation(
selectedSegmentationId
);
const data = {
lesionStats,
suvPeak,
lesionGlyoclysisStats,
};
const notYetUpdatedAtSource = true;
SegmentationService.addOrUpdateSegmentation(
selectedSegmentationId,
{
...segmentation,
data: Object.assign(segmentation.data, data),
text: [`SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`],
},
notYetUpdatedAtSource
);
handleTMTVCalculation();
}, [selectedSegmentationId, config]);
/**
* Update UI based on segmentation changes (added, removed, updated)
*/
useEffect(() => {
// ~~ Subscription
const added = SegmentationService.EVENTS.SEGMENTATION_ADDED;
const updated = SegmentationService.EVENTS.SEGMENTATION_UPDATED;
const subscriptions = [];
[added, updated].forEach(evt => {
const { unsubscribe } = SegmentationService.subscribe(evt, () => {
const segmentations = SegmentationService.getSegmentations();
setSegmentations(segmentations);
});
subscriptions.push(unsubscribe);
});
return () => {
subscriptions.forEach(unsub => {
unsub();
});
};
}, []);
useEffect(() => {
const { unsubscribe } = SegmentationService.subscribe(
SegmentationService.EVENTS.SEGMENTATION_REMOVED,
() => {
const segmentations = SegmentationService.getSegmentations();
setSegmentations(segmentations);
if (segmentations.length > 0) {
setSelectedSegmentationId(segmentations[0].id);
handleTMTVCalculation();
} else {
setSelectedSegmentationId(null);
setTmtvValue(null);
}
}
);
return () => {
unsubscribe();
};
}, []);
/**
* Toggle visibility of the segmentation
*/
useEffect(() => {
const subscription = SegmentationService.subscribe(
SegmentationService.EVENTS.SEGMENTATION_VISIBILITY_CHANGED,
({ segmentation }) => {
runCommand('toggleSegmentationVisibility', {
segmentationId: segmentation.id,
});
}
);
return () => {
subscription.unsubscribe();
};
}, [SegmentationService]);
/**
* Whenever the segmentations change, update the TMTV calculations
*/
useEffect(() => {
if (!selectedSegmentationId && segmentations.length > 0) {
setSelectedSegmentationId(segmentations[0].id);
}
handleTMTVCalculation();
}, [segmentations, selectedSegmentationId]);
return (
<div className="flex flex-col justify-between h-full">
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
<div className="flex mx-4 my-4 mb-4 space-x-4">
<Button
color="primary"
onClick={() => {
setLabelmapLoading(true);
setTimeout(() => {
runCommand('createNewLabelmapFromPT').then(segmentationId => {
setLabelmapLoading(false);
setSelectedSegmentationId(segmentationId);
});
});
}}
className={classnames(
'text-xs text-white border-b border-transparent'
)}
>
{labelmapLoading ? 'loading ...' : 'New Label'}
</Button>
<Button
color="primary"
onClick={handleROIThresholding}
className="text-xs text-white border-b border-transparent "
>
Run
</Button>
</div>
<div
className="flex items-center justify-around h-8 mb-2 border-t outline-none cursor-pointer select-none bg-secondary-dark first:border-0 border-secondary-light"
onClick={() => {
setShowConfig(!showConfig);
}}
>
<div className="px-4 text-base text-white">
{t('ROI Threshold Configuration')}
</div>
</div>
{showConfig && (
<ROIThresholdConfiguration
config={config}
dispatch={dispatch}
runCommand={runCommand}
/>
)}
{/* show segmentation table */}
<div className="mt-4">
{segmentations?.length ? (
<SegmentationTable
title={t('Segmentations')}
amount={segmentations.length}
segmentations={segmentations}
activeSegmentationId={selectedSegmentationId}
onClick={id => {
runCommand('setSegmentationActiveForToolGroups', {
segmentationId: id,
});
setSelectedSegmentationId(id);
}}
onToggleVisibility={id => {
SegmentationService.toggleSegmentationsVisibility([id]);
}}
onToggleVisibilityAll={ids => {
SegmentationService.toggleSegmentationsVisibility(ids);
}}
onDelete={id => {
SegmentationService.remove(id);
}}
onEdit={id => {
segmentationEditHandler({
id,
servicesManager,
});
}}
/>
) : null}
</div>
{tmtvValue !== null ? (
<div className="flex items-baseline justify-between px-2 py-1 mt-4 bg-secondary-dark">
<span className="text-base font-bold tracking-widest text-white uppercase">
{'TMTV:'}
</span>
<div className="text-white">{`${tmtvValue} mL`}</div>
</div>
) : null}
<ExportReports
segmentations={segmentations}
tmtvValue={tmtvValue}
config={config}
commandsManager={commandsManager}
/>
</div>
<div
className="opacity-50 hover:opacity-80 flex items-center justify-center text-blue-400 mb-4 cursor-pointer"
onClick={() => {
// navigate to a url in a new tab
window.open(
'https://github.com/OHIF/Viewers/blob/feat/segmentation-service/modes/tmtv/README.md',
'_blank'
);
}}
>
<Icon
width="15px"
height="15px"
name={'info'}
className={'ml-4 mr-3 text-primary-active'}
/>
<span>{'About'}</span>
</div>
</div>
);
}
PanelRoiThresholdSegmentation.propTypes = {
commandsManager: PropTypes.shape({
runCommand: PropTypes.func.isRequired,
}),
servicesManager: PropTypes.shape({
services: PropTypes.shape({
SegmentationService: PropTypes.shape({
getSegmentation: PropTypes.func.isRequired,
getSegmentations: PropTypes.func.isRequired,
toggleSegmentationsVisibility: PropTypes.func.isRequired,
subscribe: PropTypes.func.isRequired,
EVENTS: PropTypes.object.isRequired,
VALUE_TYPES: PropTypes.object.isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
@@ -0,0 +1,116 @@
import React from 'react';
import { Input, Select, Button, ButtonGroup } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
export const ROI_STAT = 'roi_stat';
const RANGE = 'range';
const options = [
{ value: ROI_STAT, label: 'Max', placeHolder: 'Max' },
{ value: RANGE, label: 'Range', placeHolder: 'Range' },
];
function ROIThresholdConfiguration({ config, dispatch, runCommand }) {
const { t } = useTranslation('ROIThresholdConfiguration');
return (
<div className="flex flex-col px-4 space-y-4 bg-primary-dark">
<div className="flex items-end space-x-2">
<div className="flex flex-col w-1/2 mt-2 ">
<Select
label={t('Strategy')}
closeMenuOnSelect={true}
className="mr-2 bg-black border-primary-main "
options={options}
placeholder={
options.find(option => option.value === config.strategy)
.placeHolder
}
value={config.strategy}
onChange={({ value }) => {
dispatch({
type: 'setStrategy',
payload: {
strategy: value,
},
});
}}
/>
</div>
<div className="w-1/2">
<ButtonGroup color="black" size="inherit">
<Button
className="px-2 py-2 text-base"
onClick={() => runCommand('setStartSliceForROIThresholdTool')}
>
{t('Start')}
</Button>
</ButtonGroup>
<ButtonGroup color="black" size="inherit">
<Button
className="px-2 py-2 text-base"
onClick={() => runCommand('setEndSliceForROIThresholdTool')}
>
{t('End')}
</Button>
</ButtonGroup>
</div>
</div>
{config.strategy === ROI_STAT && (
<Input
label={t('Percentage of Max SUV')}
labelClassName="text-white"
className="mt-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={config.weight}
onChange={e => {
dispatch({
type: 'setWeight',
payload: e.target.value,
});
}}
/>
)}
{config.strategy !== ROI_STAT && (
<div className="flex justify-between">
<Input
label={t('Lower')}
labelClassName="text-white"
className="mt-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={config.lower}
onChange={e => {
dispatch({
type: 'setThreshold',
payload: {
lower: e.target.value,
},
});
}}
/>
<Input
label={t('Upper')}
labelClassName="text-white"
className="mt-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={config.upper}
onChange={e => {
dispatch({
type: 'setThreshold',
payload: {
upper: e.target.value,
},
});
}}
/>
</div>
)}
</div>
);
}
export default ROIThresholdConfiguration;
@@ -0,0 +1,3 @@
import PanelROIThresholdSegmentation from './PanelROIThresholdSegmentation';
export default PanelROIThresholdSegmentation;
@@ -0,0 +1,70 @@
import React from 'react';
import { Input, Dialog } from '@ohif/ui';
function segmentationItemEditHandler({ id, servicesManager }) {
const { SegmentationService, UIDialogService } = servicesManager.services;
const segmentation = SegmentationService.getSegmentation(id);
const onSubmitHandler = ({ action, value }) => {
switch (action.id) {
case 'save': {
SegmentationService.addOrUpdateSegmentation(
id,
{
...segmentation,
...value,
},
true
);
}
}
UIDialogService.dismiss({ id: 'enter-annotation' });
};
UIDialogService.create({
id: 'enter-annotation',
centralize: true,
isDraggable: false,
showOverlay: true,
content: Dialog,
contentProps: {
title: 'Enter your annotation',
noCloseButton: true,
value: { label: segmentation.label || '' },
body: ({ value, setValue }) => {
const onChangeHandler = event => {
event.persist();
setValue(value => ({ ...value, label: event.target.value }));
};
const onKeyPressHandler = event => {
if (event.key === 'Enter') {
onSubmitHandler({ value, action: { id: 'save' } });
}
};
return (
<div className="p-4 bg-primary-dark">
<Input
autoFocus
className="mt-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
/>
</div>
);
},
actions: [
// temp: swap button types until colors are updated
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
],
onSubmit: onSubmitHandler,
},
});
}
export default segmentationItemEditHandler;
+4
View File
@@ -0,0 +1,4 @@
import PanelPetSUV from './PanelPetSUV';
import PanelROIThresholdSegmentation from './PanelROIThresholdSegmentation';
export { PanelPetSUV, PanelROIThresholdSegmentation };