-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(4D-ROI): ROI Segmentation panel (4D) (#3574)
- Loading branch information
Showing
40 changed files
with
1,663 additions
and
562 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
extensions/cornerstone-dynamic-volume/src/panels/ROISegmentationPanel/BrushConfiguration.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import React, { ReactElement } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Label, Select, InputRange } from '@ohif/ui'; | ||
|
||
function BrushConfiguration({ | ||
brushThresholdOptions, | ||
brushThresholdId, | ||
brushSize, | ||
showThresholdSettings, | ||
onBrushThresholdChange, | ||
onBrushSizeChange, | ||
}: { | ||
brushThresholdOptions: { | ||
value: string; | ||
label: string; | ||
placeHolder: string; | ||
}; | ||
brushThresholdId: string; | ||
brushSize: number; | ||
showThresholdSettings: boolean; | ||
onBrushThresholdChange: (thresholdId: string) => void; | ||
onBrushSizeChange: (brushSize: number) => void; | ||
}): ReactElement { | ||
return ( | ||
<div className="flex flex-col px-4 py-2 space-y-4 bg-primary-dark text-white"> | ||
{showThresholdSettings && ( | ||
<> | ||
<div>Threshold</div> | ||
<div className="pb-2"> | ||
<Select | ||
label="Brush Threshold" | ||
closeMenuOnSelect={true} | ||
className="mr-2 bg-black border-primary-main text-white " | ||
options={brushThresholdOptions} | ||
placeholder={ | ||
brushThresholdOptions.find( | ||
option => option.value === brushThresholdId | ||
).placeHolder | ||
} | ||
value={brushThresholdId} | ||
onChange={({ value }) => onBrushThresholdChange(value)} | ||
/> | ||
</div> | ||
</> | ||
)} | ||
<div> | ||
<Label className="text-white">Brush Size</Label> | ||
<InputRange | ||
minValue={5} | ||
maxValue={50} | ||
value={brushSize} | ||
step={1} | ||
unit="" | ||
showLabel={true} | ||
onChange={brushSize => onBrushSizeChange(brushSize)} | ||
inputClassName="w-full" | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
BrushConfiguration.defaultPprops = { | ||
showThresholdSettings: false, | ||
}; | ||
|
||
BrushConfiguration.propTypes = { | ||
brushThresholdOptions: PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
value: PropTypes.string.isRequired, | ||
label: PropTypes.string.isRequired, | ||
placeHolder: PropTypes.string.isRequired, | ||
}) | ||
).isRequired, | ||
brushThresholdId: PropTypes.string, | ||
brushSize: PropTypes.number.isRequired, | ||
showThresholdSettings: PropTypes.bool, | ||
onBrushThresholdChange: PropTypes.func, | ||
onBrushSizeChange: PropTypes.func.isRequired, | ||
}; | ||
|
||
export { BrushConfiguration as default }; |
174 changes: 174 additions & 0 deletions
174
...erstone-dynamic-volume/src/panels/ROISegmentationPanel/BrushConfigurationWithServices.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import React, { useEffect, useState, useCallback, ReactElement } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { utilities as cstUtils } from '@cornerstonejs/tools'; | ||
import BrushConfiguration from './BrushConfiguration'; | ||
import { ServicesManager } from '@ohif/core'; | ||
|
||
const { segmentation: segmentationUtils } = cstUtils; | ||
|
||
const DEFAULT_BRUSH_SIZE = 25; | ||
const brushThresholds = [ | ||
{ | ||
id: 'ct-fat', | ||
threshold: [-150, -70], | ||
name: 'CT Fat', | ||
}, | ||
{ | ||
id: 'ct-bone', | ||
threshold: [200, 1000], | ||
name: 'CT Bone', | ||
}, | ||
{ | ||
id: 'pt', | ||
threshold: [2.5, 100], | ||
name: 'PT', | ||
}, | ||
]; | ||
|
||
const getViewportIdByIndex = (servicesManager, viewportIndex) => { | ||
const { viewportGridService } = servicesManager.services; | ||
return viewportGridService.getState().viewports[viewportIndex]?.id; | ||
}; | ||
|
||
const getToolGroupThresholdSettings = toolGroup => { | ||
const currentBrushThreshold = segmentationUtils.getBrushThresholdForToolGroup( | ||
toolGroup.id | ||
); | ||
|
||
const brushThreshold = brushThresholds.find( | ||
brushThresholdItem => | ||
currentBrushThreshold && | ||
brushThresholdItem.threshold[0] === currentBrushThreshold[0] && | ||
brushThresholdItem.threshold[1] === currentBrushThreshold[1] | ||
); | ||
|
||
if (currentBrushThreshold && !brushThreshold) { | ||
console.warn( | ||
`No brush threshold setting found for [${currentBrushThreshold[0]}, ${currentBrushThreshold[1]}]` | ||
); | ||
} | ||
|
||
return brushThreshold ?? brushThresholds[0]; | ||
}; | ||
|
||
const getViewportBrushToolSettings = (servicesManager, viewportIndex) => { | ||
const { toolGroupService } = servicesManager.services; | ||
const viewportId = getViewportIdByIndex(servicesManager, viewportIndex); | ||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); | ||
const brushThreshold = getToolGroupThresholdSettings(toolGroup); | ||
const brushSize = | ||
(toolGroup && segmentationUtils.getBrushSizeForToolGroup(toolGroup.id)) ?? | ||
DEFAULT_BRUSH_SIZE; | ||
|
||
return { brushThreshold, brushSize }; | ||
}; | ||
|
||
function BrushConfigurationWithServices({ | ||
servicesManager, | ||
showThresholdSettings = false, | ||
}: { | ||
servicesManager: ServicesManager; | ||
}): ReactElement { | ||
const { viewportGridService, toolGroupService } = servicesManager.services; | ||
|
||
const [activeViewportIndex, setActiveViewportIndex] = useState( | ||
() => viewportGridService.getState().activeViewportIndex ?? 0 | ||
); | ||
|
||
const getActiveViewportBrushToolSettings = useCallback( | ||
() => getViewportBrushToolSettings(servicesManager, activeViewportIndex), | ||
[servicesManager, activeViewportIndex] | ||
); | ||
|
||
const [selectedBrushThresholdId, setSelectedBrushThresholdId] = useState( | ||
getActiveViewportBrushToolSettings().brushThreshold.id | ||
); | ||
|
||
const [brushSize, setBrushSize] = useState( | ||
() => getActiveViewportBrushToolSettings().brushSize | ||
); | ||
|
||
const brushThresholdOptions = brushThresholds.map( | ||
({ id, threshold, name }) => ({ | ||
value: id, | ||
label: `${name} (${threshold.join(', ')})`, | ||
placeHolder: `${name} (${threshold.join(', ')})`, | ||
}) | ||
); | ||
|
||
const handleBrushThresholdChange = brushThresholdId => { | ||
const brushThreshold = brushThresholds.find( | ||
brushThreshold => brushThreshold.id === brushThresholdId | ||
); | ||
|
||
const toolGroup = toolGroupService.getToolGroup(); | ||
|
||
if (!toolGroup) { | ||
console.warn('toolGroup not found'); | ||
return; | ||
} | ||
|
||
segmentationUtils.setBrushThresholdForToolGroup( | ||
toolGroup.id, | ||
brushThreshold.threshold | ||
); | ||
|
||
setSelectedBrushThresholdId(brushThreshold.id); | ||
}; | ||
|
||
const handleBrushSizeChange = brushSize => { | ||
const toolGroup = toolGroupService.getToolGroup(); | ||
|
||
if (!toolGroup) { | ||
console.warn('toolGroup not found'); | ||
return; | ||
} | ||
|
||
segmentationUtils.setBrushSizeForToolGroup(toolGroup.id, brushSize); | ||
setBrushSize(brushSize); | ||
}; | ||
|
||
// Updates the thresholdId for the active viewport | ||
useEffect(() => { | ||
const { brushThreshold, brushSize } = getActiveViewportBrushToolSettings(); | ||
|
||
setSelectedBrushThresholdId(brushThreshold.id); | ||
setBrushSize(brushSize); | ||
}, [activeViewportIndex, getActiveViewportBrushToolSettings]); | ||
|
||
// Updates the active viewport index whenever it changes | ||
useEffect(() => { | ||
const { unsubscribe } = viewportGridService.subscribe( | ||
viewportGridService.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, | ||
({ viewportIndex, ...rest }) => { | ||
setActiveViewportIndex(viewportIndex); | ||
} | ||
); | ||
|
||
return () => { | ||
unsubscribe(); | ||
}; | ||
}, [viewportGridService]); | ||
|
||
return ( | ||
<BrushConfiguration | ||
brushThresholdOptions={brushThresholdOptions} | ||
brushThresholdId={selectedBrushThresholdId} | ||
brushSize={brushSize} | ||
showThresholdSettings={showThresholdSettings} | ||
onBrushThresholdChange={handleBrushThresholdChange} | ||
onBrushSizeChange={handleBrushSizeChange} | ||
/> | ||
); | ||
} | ||
|
||
BrushConfigurationWithServices.defaultProps = { | ||
showThresholdSettings: false, | ||
}; | ||
|
||
BrushConfigurationWithServices.propTypes = { | ||
servicesManager: PropTypes.instanceOf(ServicesManager), | ||
showThresholdSettings: PropTypes.bool.isRequired, | ||
}; | ||
|
||
export { BrushConfigurationWithServices as default }; |
Oops, something went wrong.