* Filter segs and fetch correct metadata. * Initiate seg loading logic on series load into viewport. Need to write. * Automatic consumption of SEG functional. * Pull in master. * Fix issues after pulling in master. * Clean up old segmentation handlers. * WIP * WIP * Poppulate viewport with metadata. * Show hide/segmentation in react-vtkjs-viewport. * feat: 🎸 View segmentations in vtkjs viewport * Filter segs and fetch correct metadata. * Initiate seg loading logic on series load into viewport. Need to write. * Automatic consumption of SEG functional. * Pull in master. * Fix issues after pulling in master. * Clean up old segmentation handlers. * WIP * WIP * Poppulate viewport with metadata. * Show hide/segmentation in react-vtkjs-viewport. * feat: 🎸 View segmentations in vtkjs viewport * Rebase on master. * format format format * add basic extension parts * Updated yarn lock * Fetch derived/referenced displaysets in sidepanel * Add buttons, expose seriesDescription, write notes for Danny. * Add growcut tool extention. -> Move to a seperate repo. * Change some text fields. * expose dialog provider * hide draggable area * No pointer events for now; need to revisit * Don't filter seg from the outset for now * fiddling about * fix path to series data * fix logic to determine next available labelmapindex * Ability to swtich between labelmaps * Return a promise so we can make sure our labelmap is loaded before pulling metadata * Better UI to toggle labelmaps * Fix accidental untoggle of active labelmap for ui * Remove brush tool from cornerstone extension * Add cstools in preInit for seg extension * Add buttons for seg magic * Hide the measurements panel for now * Slightly better seg/scissor icons * Add eraser * ability to switch segments * seg color / switcher * Fix vtk labelmap cache. * sidepanel should scroll if there's overflow * Use a fancier range slider * Slightly prettier buttons * Push in progress * A bit more reactive; handle no-meta * Update to vtk.js 11.14.0 and react-vtkjs-viewport 0.6.0 * chore: kickstart preview * Fix conflict issues * Refactor panel * Isolate increment functions * Wip segment table * Hide selector and add scroll to segmentation * Increase width of segment description * Hide not used items * Add new select for segmentations * Align ui * Fix default segmentation * Fix broken import test * Get measurements panel back and add spacing to grouped buttons * Hide brush and selector and refresh when switching segmentations * Add pointer events back and fix broken tests for measurement table * Update segment list when switching humbnails * CR Update: Minor UI adjustments * CR Update: Set active segment * Move components to components folder * Add new settings ui * Integrate configuration * Improve styles * Float numbers * Improve css * Refresh settings menu if side panel changed * Fix settings rendering logic and remove comments * Extract components * Remove not used tools * Remove non used extension * Use style variables and remove comments * Remove non used dep from lock * Remove tools * Remove segmentation from toolbar * Add todo and brushradius from config * Fix packagejson growcut import * Remove webpack config already defined in parent * Update package json description * Change component name to british english to us * Improve prop types * Rename cornerstone label map event * Scope css * Extract and add doc to helper * Update jsdocs for getDerivedDatasets * Add proptypes and cr updates * Update tablistitem proptypes * config is readonly, rollback destructuring * Remove event for side panel * Add jsdocs for util * Extract getimageid to studymetada * Use ohif log instead of console * Remove specific style from side panel * Fix default props for brush radius * Descructure configuration props from settings component to avoid invalid props * Update proptypes to be required (settings) * Update readme * Remove commandsmodule * Get configuration module inside the component * Set selected segment * Rename extension and use modality to filter * Fix for failing tests (#1423) * hello world * Finish rebase for Igor. * Re-fix cornerstoneTools version that got messed up in merge. * Fix reactivity of isDisabled, write implementation for the SEG panel. * Nuke yarn.lock to stack CST to 4.12.0 * Remove debugger statements. * fix: bump cornerstone-tools version to address toFixed issue * test: force tests for MISTER^MR to navigate to study directly * test: remove eraser tool test * test: count is off by one * test: don't save SR state for the time being * test: disable more tests Co-authored-by: James A. Petts <jamesapetts@gmail.com> Co-authored-by: dannyrb <danny.ri.brown@gmail.com> Co-authored-by: igoroctaviano] <igoroctaviano@users.noreply.github.com> Co-authored-by: Mirna Silva <mirna.silva@radicalimaging.com>
This commit is contained in:
78 files changed
+3071
-1962
No files matched your search
@@ -34,7 +34,7 @@
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.10.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
|
||||
@@ -4,19 +4,9 @@ import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
|
||||
import OHIF from '@ohif/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import handleSegmentationStorage from './handleSegmentationStorage.js';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
|
||||
const specialCaseHandlers = {};
|
||||
specialCaseHandlers[
|
||||
SOP_CLASSES.SEGMENTATION_STORAGE
|
||||
] = handleSegmentationStorage;
|
||||
|
||||
class OHIFCornerstoneViewport extends Component {
|
||||
state = {
|
||||
viewportData: null,
|
||||
@@ -124,42 +114,24 @@ class OHIFCornerstoneViewport extends Component {
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
let viewportData;
|
||||
|
||||
switch (SOPClassUID) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
const specialCaseHandler =
|
||||
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
viewportData = await specialCaseHandler(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
viewportData = {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
viewportData = {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
|
||||
return viewportData;
|
||||
};
|
||||
@@ -184,13 +156,10 @@ class OHIFCornerstoneViewport extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const SOPClassUID = sopClassUIDs && sopClassUIDs[0];
|
||||
|
||||
this.getViewportData(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
).then(viewportData => {
|
||||
@@ -205,7 +174,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const { displaySet } = this.props.viewportData;
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
import * as dcmjs from 'dcmjs';
|
||||
|
||||
import OHIF from '@ohif/core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { StackManager, DicomLoaderService } = OHIF.utils;
|
||||
|
||||
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
return study.displaySets.filter(set => {
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
function parseSeg(arrayBuffer, imageIds) {
|
||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||
imageIds,
|
||||
arrayBuffer,
|
||||
cornerstone.metaData
|
||||
);
|
||||
}
|
||||
|
||||
function addSegMetadataToCornerstoneToolState(
|
||||
segMetadata,
|
||||
toolState,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||
toolState
|
||||
);
|
||||
|
||||
const brushModule = cornerstoneTools.store.modules.brush;
|
||||
|
||||
for (let i = 0; i < segMetadata.length; i++) {
|
||||
brushModule.setters.metadata(displaySetInstanceUID, i, segMetadata[i]);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSegmentationStorage(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
displaySet,
|
||||
studies
|
||||
);
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
);
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const displaySets = getDisplaySetsBySeries(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
}
|
||||
|
||||
const referenceDisplaySet = displaySets[0];
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
|
||||
const results = parseSeg(arrayBuffer, imageIds);
|
||||
|
||||
if (!results) {
|
||||
throw new Error('Fractional segmentations are not supported');
|
||||
}
|
||||
|
||||
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
||||
const { setters } = cornerstoneTools.getModule('segmentation');
|
||||
|
||||
setters.labelmap3DByFirstImageId(
|
||||
imageIds[0],
|
||||
labelmapBuffer,
|
||||
0, // TODO -> Can define a color LUT based on colors in the SEG later.
|
||||
segMetadata,
|
||||
imageIds.length,
|
||||
segmentsOnFrame
|
||||
);
|
||||
|
||||
const cachedStack = StackManager.findOrCreateStack(
|
||||
study,
|
||||
referenceDisplaySet
|
||||
);
|
||||
const stack = Object.assign({}, cachedStack);
|
||||
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
return {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
|
||||
export default handleSegmentationStorage;
|
||||
@@ -66,7 +66,6 @@ export default function init({ servicesManager, configuration }) {
|
||||
csTools.DragProbeTool,
|
||||
csTools.RectangleRoiTool,
|
||||
],
|
||||
segmentation: [csTools.BrushTool],
|
||||
other: [
|
||||
csTools.PanTool,
|
||||
csTools.ZoomTool,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
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');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
devtool: 'source-map',
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: ROOT_DIR,
|
||||
library: 'OHIFExtDicomSeg',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Open Health Imaging Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,65 @@
|
||||
# @ohif/dicom-segmentation
|
||||
|
||||

|
||||
|
||||
This extension adds support to view and navigate the segments available in the DICOM SEG,
|
||||
allowing control over visibility of a given segment and providing metadata about the segment(s).
|
||||
|
||||
<!-- TODO: Simple image or GIF? -->
|
||||
|
||||
#### Index
|
||||
|
||||
Extension Id: `com.ohif.dicom-segmentation`
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
Tools can be configured through extension configuration using the tools key:
|
||||
|
||||
```js
|
||||
...
|
||||
cornerstoneExtensionConfig: {
|
||||
tools: {
|
||||
ArrowAnnotate: {
|
||||
configuration: {
|
||||
getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
## Annotate Tools Configuration
|
||||
|
||||
*We currently support one property for annotation tools.*
|
||||
|
||||
### Hide handles
|
||||
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
|
||||
|
||||
```js
|
||||
...
|
||||
cornerstoneExtensionConfig: {
|
||||
hideHandles: true,
|
||||
},
|
||||
...
|
||||
|
||||
## Resources
|
||||
|
||||
### Repositories
|
||||
|
||||
- [cornerstonejs/react-cornerstone-viewport][react-viewport]
|
||||
- [cornerstonejs/cornerstoneTools][cornerstone-tools]
|
||||
- [cornerstonejs/cornerstone][cornerstone]
|
||||
|
||||
<!--
|
||||
Links
|
||||
-->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
[docs-commands]: https://www.com
|
||||
[docs-hotkeys]: https://www.com
|
||||
[docs-userprefs]: htt
|
||||
[react-viewport]: https://github.com/cornerstonejs/react-cornerstone-viewport
|
||||
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
|
||||
[cornerstone]: https://github.com/cornerstonejs/cornerstone
|
||||
<!-- prettier-ignore-end -->
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-segmentation",
|
||||
"version": "0.0.1",
|
||||
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"dev:dicom-html": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"prepublishOnly": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"dcmjs": "^0.6.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"react-select": "^3.0.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { MODULE_TYPES, utils } from '@ohif/core';
|
||||
import loadSegmentation from './loadSegmentation';
|
||||
|
||||
// TODO: Should probably use dcmjs for this
|
||||
const SOP_CLASS_UIDS = {
|
||||
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
|
||||
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
||||
|
||||
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
||||
// same SOP Class.
|
||||
const OHIFDicomSegSopClassHandler = {
|
||||
id: 'OHIFDicomSegSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries: function(
|
||||
series,
|
||||
study,
|
||||
dicomWebClient,
|
||||
authorizationHeaders
|
||||
) {
|
||||
const instance = series.getFirstInstance();
|
||||
const metadata = instance.getData().metadata;
|
||||
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesDescription,
|
||||
FrameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
} = metadata;
|
||||
|
||||
const segDisplaySet = {
|
||||
Modality: 'SEG',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
authorizationHeaders,
|
||||
metadata,
|
||||
isDerived: true,
|
||||
referencedDisplaySetUID: null, // Assigned when loaded.
|
||||
labelmapIndex: null, // Assigned when loaded.
|
||||
isLoaded: false,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesDescription,
|
||||
};
|
||||
|
||||
segDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||
return loadSegmentation(
|
||||
segDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies
|
||||
).catch(error => {
|
||||
segDisplaySet.isLoaded = false;
|
||||
throw new Error(error);
|
||||
});
|
||||
};
|
||||
|
||||
return segDisplaySet;
|
||||
},
|
||||
};
|
||||
|
||||
export default OHIFDicomSegSopClassHandler;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
.dcmseg-brush-color-selector .selector-buttons button {
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 4px 12px;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dcmseg-brush-color-selector .selector-buttons button:hover,
|
||||
.dcmseg-brush-color-selector .selector-buttons button:active,
|
||||
.dcmseg-brush-color-selector .selector-buttons button.active {
|
||||
background-color: var(--active-color);
|
||||
border-color: var(--ui-border-color-active);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dcmseg-brush-color-selector .selector-buttons {
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dcmseg-brush-color-selector .selector-buttons button:last-of-type {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.dcmseg-brush-color-selector .selector-active-segment {
|
||||
border-radius: 100%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './BrushColorSelector.css';
|
||||
|
||||
const BrushColorSelector = ({ defaultColor, index, onNext, onPrev }) => (
|
||||
<div className="dcmseg-brush-color-selector">
|
||||
<div
|
||||
className="selector-active-segment"
|
||||
style={{ backgroundColor: defaultColor }}
|
||||
>
|
||||
{index}
|
||||
</div>
|
||||
<div className="selector-buttons">
|
||||
<button onClick={onPrev}>
|
||||
Previous
|
||||
</button>
|
||||
<button onClick={onNext}>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
BrushColorSelector.propTypes = {
|
||||
defaultColor: PropTypes.string.isRequired,
|
||||
index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
onNext: PropTypes.func.isRequired,
|
||||
onPrev: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default BrushColorSelector;
|
||||
@@ -0,0 +1,4 @@
|
||||
.dcmseg-brush-radius label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Range } from '@ohif/ui';
|
||||
|
||||
import './BrushRadius.css';
|
||||
|
||||
const BrushRadius = ({ value, onChange, min, max, step }) => (
|
||||
<div className="dcmseg-brush-radius">
|
||||
<label htmlFor="dcmseg-brush-radius">Brush Radius</label>
|
||||
<Range
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
onChange={onChange}
|
||||
id="dcmseg-brush-radius"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
BrushRadius.propTypes = {
|
||||
value: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
step: PropTypes.number,
|
||||
};
|
||||
|
||||
BrushRadius.defaultProps = {
|
||||
onChange: () => { },
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
};
|
||||
|
||||
export default BrushRadius;
|
||||
@@ -0,0 +1,64 @@
|
||||
.dcmseg-segment-item .segment-color-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-color {
|
||||
margin-top: 8px;
|
||||
border-radius: 100%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-label {
|
||||
overflow: hidden;
|
||||
max-width: calc(100% - 50px);
|
||||
text-overflow: ellipsis;
|
||||
overflow-wrap: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-info {
|
||||
display: inline-block;
|
||||
margin-top: 9px;
|
||||
padding-left: 9px;
|
||||
width: 90px;
|
||||
border-left: 1px solid var(--text-secondary-color);
|
||||
color: var(--text-primary-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item.selected .segment-actions {
|
||||
height: 35px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-actions {
|
||||
margin-left: -1px;
|
||||
background-color: var(--ui-gray-darker);
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
visibility: hidden;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-actions .btnAction {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--default-color);
|
||||
cursor: pointer;
|
||||
line-height: 35px;
|
||||
height: 35px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-actions .btnAction:hover, .dcmseg-segment-item .segment-actions .btnAction:active {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
.dcmseg-segment-item .segment-actions .btnAction i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableListItem, Icon } from '@ohif/ui';
|
||||
|
||||
import './SegmentItem.css';
|
||||
|
||||
const ColoredCircle = ({ color }) => {
|
||||
return (
|
||||
<div
|
||||
className="segment-color"
|
||||
style={{ backgroundColor: `rgba(${color.join(',')})` }}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
ColoredCircle.propTypes = {
|
||||
color: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
const SegmentItem = ({ index, label, onClick, itemClass, color }) => (
|
||||
<div className="dcmseg-segment-item">
|
||||
<TableListItem
|
||||
key={index}
|
||||
itemKey={index}
|
||||
itemIndex={index}
|
||||
itemClass={itemClass}
|
||||
itemMeta={<ColoredCircle color={color} />}
|
||||
itemMetaClass="segment-color-section"
|
||||
onItemClick={onClick}
|
||||
>
|
||||
<div>
|
||||
<div className="segment-label" style={{ marginBottom: 4 }}>
|
||||
{label}
|
||||
</div>
|
||||
{false && <div className="segment-info">{'...'}</div>}
|
||||
{false && (
|
||||
<div className="segment-actions">
|
||||
<button
|
||||
className="btnAction"
|
||||
onClick={() => console.log('Relabelling...')}
|
||||
>
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Relabel
|
||||
</button>
|
||||
<button
|
||||
className="btnAction"
|
||||
onClick={() => console.log('Editing description...')}
|
||||
>
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Description
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableListItem>
|
||||
</div>
|
||||
);
|
||||
|
||||
SegmentItem.propTypes = {
|
||||
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
itemClass: PropTypes.string,
|
||||
color: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
SegmentItem.defaultProps = {
|
||||
itemClass: '',
|
||||
onClick: () => { }
|
||||
};
|
||||
|
||||
export default SegmentItem;
|
||||
@@ -0,0 +1,25 @@
|
||||
.dcmseg-segmentation-item {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-item .segmentation-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-item .segmentation-meta-title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
max-width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-item .segmentation-meta-description {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './SegmentationItem.css';
|
||||
|
||||
const SegmentationItem = ({ onClick, title, description }) => {
|
||||
return (
|
||||
<li className="dcmseg-segmentation-item" onClick={onClick}>
|
||||
<div className="segmentation-meta">
|
||||
<div className="segmentation-meta-title">{title}</div>
|
||||
<div className="segmentation-meta-description">{description}</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentationItem.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
SegmentationItem.defaultProps = {
|
||||
description: ''
|
||||
};
|
||||
|
||||
export default SegmentationItem;
|
||||
@@ -0,0 +1,61 @@
|
||||
.dcmseg-segmentation-panel {
|
||||
color: white;
|
||||
min-height: 200px;
|
||||
background-color: var(--primary-background-color);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel h3 {
|
||||
padding-left: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .segmentations {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .cog-icon {
|
||||
margin: 20px 20px 0 0;
|
||||
align-self: flex-end;
|
||||
color: var(--default-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .cog-icon:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .segmentation-icon {
|
||||
margin-right: 8px;
|
||||
margin-top: 12px;
|
||||
min-width: 14px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .selector-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .selector-form>div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .selector-form>div:first-of-type {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-panel .tableList .tableListHeader .numberOfItems {
|
||||
float: unset;
|
||||
max-width: unset;
|
||||
flex: unset;
|
||||
margin-right: 16px;
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import moment from 'moment';
|
||||
import { utils, log } from '@ohif/core';
|
||||
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
||||
|
||||
import {
|
||||
BrushColorSelector,
|
||||
BrushRadius,
|
||||
SegmentationItem,
|
||||
SegmentItem,
|
||||
SegmentationSelect,
|
||||
} from '../index';
|
||||
|
||||
import './SegmentationPanel.css';
|
||||
import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
const refreshViewport = () => {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
cornerstone.updateImage(enabledElement.element);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* SegmentationPanel component
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Array} props.studies
|
||||
* @param {Array} props.viewports - viewportSpecificData
|
||||
* @param {number} props.activeIndex - activeViewportIndex
|
||||
* @returns component
|
||||
*/
|
||||
const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
|
||||
/*
|
||||
* TODO: wrap get/set interactions with the cornerstoneTools
|
||||
* store with context to make these kind of things less blurry.
|
||||
*/
|
||||
const segmentationModule = cornerstoneTools.getModule('segmentation');
|
||||
const { configuration } = segmentationModule;
|
||||
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
|
||||
|
||||
const [brushRadius, setBrushRadius] = useState(DEFAULT_BRUSH_RADIUS);
|
||||
|
||||
/* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
|
||||
const [brushColor, setBrushColor] = useState('rgba(221, 85, 85, 1)');
|
||||
const [selectedSegment, setSelectedSegment] = useState();
|
||||
const [showSegSettings, setShowSegSettings] = useState(false);
|
||||
const [selectedSegmentation, setSelectedSegmentation] = useState();
|
||||
|
||||
const viewport = viewports[activeIndex];
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
} = viewport;
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
const firstImageId = studyMetadata.getFirstImageId(displaySetInstanceUID);
|
||||
|
||||
/* CornerstoneTools */
|
||||
const [brushStackState, setBrushStackState] = useState(
|
||||
segmentationModule.state.series[firstImageId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setShowSegSettings(showSegSettings && !isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
setBrushStackState(segmentationModule.state.series[firstImageId]);
|
||||
}, [studies, viewports, activeIndex, firstImageId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (brushStackState) {
|
||||
setSelectedSegmentation(brushStackState.activeLabelmapIndex);
|
||||
}
|
||||
|
||||
const labelmapModifiedHandler = event => {
|
||||
log.warn('labelmap modified', event);
|
||||
setBrushStackState(segmentationModule.state.series[firstImageId]);
|
||||
};
|
||||
|
||||
/*
|
||||
* These are specific to each element;
|
||||
* Need to iterate cornerstone-tools tracked enabled elements?
|
||||
* Then only care about the one tied to active viewport?
|
||||
*/
|
||||
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
||||
enabledElement.addEventListener(
|
||||
'cornerstonetoolslabelmapmodified',
|
||||
labelmapModifiedHandler
|
||||
)
|
||||
);
|
||||
|
||||
return () => {
|
||||
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
||||
enabledElement.removeEventListener(
|
||||
'cornerstonetoolslabelmapmodified',
|
||||
labelmapModifiedHandler
|
||||
)
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
if (!brushStackState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labelmap3D =
|
||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||
|
||||
/*
|
||||
* 2. UseEffect to update state? or to a least trigger a re-render
|
||||
* 4. Toggle visibility of labelmap?
|
||||
* 5. Toggle visibility of seg?
|
||||
*
|
||||
* If the port is cornerstone, just need to call a re-render.
|
||||
* If the port is vtkjs, its a bit more tricky as we now need to create a new
|
||||
*/
|
||||
|
||||
const getLabelmapList = () => {
|
||||
/* Get list of SEG labelmaps specific to active viewport (reference series) */
|
||||
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
return referencedSegDisplaysets.map((displaySet, index) => {
|
||||
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
|
||||
|
||||
/* Map to display representation */
|
||||
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
|
||||
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
|
||||
const isActiveLabelmap =
|
||||
labelmapIndex === brushStackState.activeLabelmapIndex;
|
||||
const displayDate = date.format('ddd, MMM Do YYYY');
|
||||
const displayTime = date.format('h:mm:ss a');
|
||||
const displayDescription = displaySet.SeriesDescription;
|
||||
|
||||
return {
|
||||
value: labelmapIndex,
|
||||
title: displayDescription,
|
||||
description: displayDate,
|
||||
onClick: async () => {
|
||||
const activatedLabelmapIndex = await _setActiveLabelmap(
|
||||
viewport,
|
||||
studies,
|
||||
displaySet,
|
||||
firstImageId,
|
||||
brushStackState.activeLabelmapIndex
|
||||
);
|
||||
setSelectedSegmentation(activatedLabelmapIndex);
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const labelmapList = getLabelmapList();
|
||||
|
||||
const segmentList = [];
|
||||
|
||||
if (labelmap3D) {
|
||||
/*
|
||||
* Newly created segments have no `meta`
|
||||
* So we instead build a list of all segment indexes in use
|
||||
* Then find any associated metadata
|
||||
*/
|
||||
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
|
||||
.reduce((acc, labelmap2D) => {
|
||||
if (labelmap2D) {
|
||||
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
|
||||
|
||||
for (let i = 0; i < segmentIndexes.length; i++) {
|
||||
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
|
||||
acc.push(segmentIndexes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const colorLutTable =
|
||||
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
|
||||
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
|
||||
|
||||
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
|
||||
const segmentIndex = uniqueSegmentIndexes[i];
|
||||
|
||||
const color = colorLutTable[segmentIndex];
|
||||
let segmentLabel = '(unlabeled)';
|
||||
let segmentNumber = segmentIndex;
|
||||
|
||||
/* Meta */
|
||||
if (hasLabelmapMeta) {
|
||||
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
|
||||
|
||||
if (segmentMeta) {
|
||||
segmentNumber = segmentMeta.SegmentNumber;
|
||||
segmentLabel = segmentMeta.SegmentLabel;
|
||||
}
|
||||
}
|
||||
|
||||
const sameSegment = selectedSegment === segmentNumber;
|
||||
const setCurrentSelectedSegment = () => {
|
||||
_setActiveSegment(
|
||||
firstImageId,
|
||||
segmentNumber,
|
||||
labelmap3D.activeSegmentIndex
|
||||
);
|
||||
setSelectedSegment(sameSegment ? null : segmentNumber);
|
||||
};
|
||||
|
||||
segmentList.push(
|
||||
<SegmentItem
|
||||
key={segmentNumber}
|
||||
itemClass={`segment-item ${sameSegment && 'selected'}`}
|
||||
onClick={setCurrentSelectedSegment}
|
||||
label={segmentLabel}
|
||||
index={segmentNumber}
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Let's iterate over segmentIndexes ^ above
|
||||
* If meta has a match, use it to show info
|
||||
* If now, add "no-meta" class
|
||||
* Show default name
|
||||
*/
|
||||
}
|
||||
|
||||
const updateBrushSize = evt => {
|
||||
const updatedRadius = Number(evt.target.value);
|
||||
|
||||
if (updatedRadius !== brushRadius) {
|
||||
setBrushRadius(updatedRadius);
|
||||
segmentationModule.setters.radius(updatedRadius);
|
||||
}
|
||||
};
|
||||
|
||||
const decrementSegment = event => {
|
||||
event.preventDefault();
|
||||
if (labelmap3D.activeSegmentIndex > 1) {
|
||||
labelmap3D.activeSegmentIndex--;
|
||||
}
|
||||
setSelectedSegment(labelmap3D.activeSegmentIndex);
|
||||
updateActiveSegmentColor();
|
||||
};
|
||||
|
||||
const incrementSegment = event => {
|
||||
event.preventDefault();
|
||||
labelmap3D.activeSegmentIndex++;
|
||||
setSelectedSegment(labelmap3D.activeSegmentIndex);
|
||||
updateActiveSegmentColor();
|
||||
};
|
||||
|
||||
const updateActiveSegmentColor = () => {
|
||||
const color = getActiveSegmentColor();
|
||||
setBrushColor(color);
|
||||
};
|
||||
|
||||
const getActiveSegmentColor = () => {
|
||||
if (!brushStackState) {
|
||||
return 'rgba(255, 255, 255, 1)';
|
||||
}
|
||||
|
||||
const colorLutTable =
|
||||
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
|
||||
const color = colorLutTable[labelmap3D.activeSegmentIndex];
|
||||
|
||||
return `rgba(${color.join(',')})`;
|
||||
};
|
||||
|
||||
const updateConfiguration = newConfiguration => {
|
||||
/* Supported configuration */
|
||||
configuration.renderFill = newConfiguration.renderFill;
|
||||
configuration.renderOutline = newConfiguration.renderOutline;
|
||||
configuration.shouldRenderInactiveLabelmaps =
|
||||
newConfiguration.shouldRenderInactiveLabelmaps;
|
||||
configuration.fillAlpha = newConfiguration.fillAlpha;
|
||||
configuration.outlineAlpha = newConfiguration.outlineAlpha;
|
||||
configuration.outlineWidth = newConfiguration.outlineWidth;
|
||||
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
|
||||
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
|
||||
refreshViewport();
|
||||
};
|
||||
|
||||
if (showSegSettings) {
|
||||
return (
|
||||
<SegmentationSettings
|
||||
configuration={configuration}
|
||||
onBack={() => setShowSegSettings(false)}
|
||||
onChange={updateConfiguration}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="dcmseg-segmentation-panel">
|
||||
<Icon
|
||||
className="cog-icon"
|
||||
name="cog"
|
||||
width="25px"
|
||||
height="25px"
|
||||
onClick={() => setShowSegSettings(true)}
|
||||
/>
|
||||
{false && (
|
||||
<form className="selector-form">
|
||||
<BrushColorSelector
|
||||
defaultColor={brushColor}
|
||||
index={labelmap3D.activeSegmentIndex}
|
||||
onNext={incrementSegment}
|
||||
onPrev={decrementSegment}
|
||||
/>
|
||||
<BrushRadius
|
||||
value={brushRadius}
|
||||
onChange={updateBrushSize}
|
||||
min={configuration.minRadius}
|
||||
max={configuration.maxRadius}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
<h3>Segmentations</h3>
|
||||
<div className="segmentations">
|
||||
<SegmentationSelect
|
||||
value={
|
||||
labelmapList.find(i => i.value === selectedSegmentation) || null
|
||||
}
|
||||
formatOptionLabel={SegmentationItem}
|
||||
options={labelmapList}
|
||||
/>
|
||||
</div>
|
||||
<ScrollableArea>
|
||||
<TableList
|
||||
customHeader={<SegmentsHeader count={segmentList.length} />}
|
||||
>
|
||||
{segmentList}
|
||||
</TableList>
|
||||
</ScrollableArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
SegmentationPanel.propTypes = {
|
||||
/*
|
||||
* An object, with int index keys?
|
||||
* Maps to: state.viewports.viewportSpecificData, in `viewer`
|
||||
* Passed in MODULE_TYPES.PANEL when specifying component in viewer
|
||||
*/
|
||||
viewports: PropTypes.shape({
|
||||
displaySetInstanceUID: PropTypes.string,
|
||||
frameRate: PropTypes.any,
|
||||
InstanceNumber: PropTypes.number,
|
||||
isMultiFrame: PropTypes.bool,
|
||||
isReconstructable: PropTypes.bool,
|
||||
Modality: PropTypes.string,
|
||||
plugin: PropTypes.string,
|
||||
SeriesDate: PropTypes.string,
|
||||
SeriesDescription: PropTypes.string,
|
||||
SeriesInstanceUID: PropTypes.string,
|
||||
SeriesNumber: PropTypes.any,
|
||||
SeriesTime: PropTypes.string,
|
||||
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
||||
StudyInstanceUID: PropTypes.string,
|
||||
}),
|
||||
activeIndex: PropTypes.number.isRequired,
|
||||
studies: PropTypes.array.isRequired,
|
||||
};
|
||||
SegmentationPanel.defaultProps = {};
|
||||
|
||||
/**
|
||||
* Returns SEG Displaysets that reference the target series, sorted by dateTime
|
||||
*
|
||||
* @param {string} StudyInstanceUID
|
||||
* @param {string} SeriesInstanceUID
|
||||
* @returns Array
|
||||
*/
|
||||
const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
|
||||
/* Referenced DisplaySets */
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: SeriesInstanceUID,
|
||||
Modality: 'SEG',
|
||||
});
|
||||
|
||||
/* Sort */
|
||||
referencedDisplaysets.sort((a, b) => {
|
||||
const aNumber = Number(`${a.SeriesDate}${a.SeriesTime}`);
|
||||
const bNumber = Number(`${b.SeriesDate}${b.SeriesTime}`);
|
||||
return aNumber - bNumber;
|
||||
});
|
||||
|
||||
return referencedDisplaysets;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {*} viewportSpecificData
|
||||
* @param {*} studies
|
||||
* @param {*} displaySet
|
||||
* @param {*} firstImageId
|
||||
* @param {*} activeLabelmapIndex
|
||||
* @returns
|
||||
*/
|
||||
const _setActiveLabelmap = async (
|
||||
viewportSpecificData,
|
||||
studies,
|
||||
displaySet,
|
||||
firstImageId,
|
||||
activeLabelmapIndex
|
||||
) => {
|
||||
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
||||
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!displaySet.isLoaded) {
|
||||
// What props does this expect `viewportSpecificData` to have?
|
||||
// TODO: Should this return the `labelmapIndex`?
|
||||
await displaySet.load(viewportSpecificData, studies);
|
||||
}
|
||||
|
||||
const { state } = cornerstoneTools.getModule('segmentation');
|
||||
const brushStackState = state.series[firstImageId];
|
||||
|
||||
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
|
||||
|
||||
refreshViewport();
|
||||
|
||||
return displaySet.labelmapIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} firstImageId
|
||||
* @param {*} activeSegmentIndex
|
||||
* @returns
|
||||
*/
|
||||
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
|
||||
if (segmentIndex === activeSegmentIndex) {
|
||||
log.info(`${activeSegmentIndex} is already the active segment`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { state } = cornerstoneTools.getModule('segmentation');
|
||||
const brushStackState = state.series[firstImageId];
|
||||
|
||||
const labelmap3D =
|
||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||
labelmap3D.activeSegmentIndex = segmentIndex;
|
||||
|
||||
refreshViewport();
|
||||
|
||||
return segmentIndex;
|
||||
};
|
||||
|
||||
const SegmentsHeader = ({ count }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="tableListHeaderTitle">Segments</div>
|
||||
<div className="numberOfItems">{count}</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default SegmentationPanel;
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
|
||||
const SegmentationSelect = ({ value, formatOptionLabel, options }) => (
|
||||
<Select
|
||||
value={value}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
styles={segmentationSelectStyles}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
|
||||
const computedstyle = getComputedStyle(document.body);
|
||||
const uiGrayDarker = computedstyle.getPropertyValue('--ui-gray-darker');
|
||||
const activeColor = computedstyle.getPropertyValue('--active-color');
|
||||
const defaultColor = computedstyle.getPropertyValue('--default-color');
|
||||
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
|
||||
|
||||
const segmentationSelectStyles = {
|
||||
control: (base, state) => ({
|
||||
...base,
|
||||
cursor: 'pointer',
|
||||
background: uiGrayDarker,
|
||||
borderRadius: state.isFocused ? '5px 5px 5px 5px' : 5,
|
||||
borderColor: state.isFocused ? activeColor : defaultColor,
|
||||
boxShadow: state.isFocused ? null : null,
|
||||
minHeight: '50px',
|
||||
'&:hover': {
|
||||
borderColor: activeColor,
|
||||
},
|
||||
}),
|
||||
menu: base => ({
|
||||
...base,
|
||||
borderRadius: 5,
|
||||
background: uiGrayDarker,
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
cursor: 'pointer',
|
||||
'&:first-of-type': {
|
||||
borderTopLeftRadius: 5,
|
||||
borderTopRightRadius: 5,
|
||||
},
|
||||
'&:last-of-type': {
|
||||
borderBottomLeftRadius: 5,
|
||||
borderBottomRightRadius: 5,
|
||||
},
|
||||
background: state.isSelected ? uiGrayDark : uiGrayDarker,
|
||||
'&:hover': {
|
||||
background: uiGrayDark,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default SegmentationSelect;
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
.dcmseg-segmentation-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .range label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-title h3 {
|
||||
color: var(--text-primary-color);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--text-primary-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-group .range {
|
||||
display: flex;
|
||||
padding: 20px 15px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-group .range:not(:last-of-type) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-group .range label {
|
||||
margin: 0;
|
||||
display: unset;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-group .range input {
|
||||
width: 100%;
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .custom-check label {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 2px solid black;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .custom-check input {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .settings-group {
|
||||
background-color: var(--ui-gray-dark);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .return-button {
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 4px 12px;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-settings .return-button:hover,
|
||||
.dcmseg-segmentation-settings .return-button:active,
|
||||
.dcmseg-segmentation-settings .return-button.active {
|
||||
background-color: var(--active-color);
|
||||
border-color: var(--ui-border-color-active);
|
||||
color: #fff;
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Range } from '@ohif/ui';
|
||||
|
||||
import './SegmentationSettings.css';
|
||||
|
||||
const SegmentationSettings = ({ configuration, onBack, onChange }) => {
|
||||
const [state, setState] = useState({
|
||||
renderFill: configuration.renderFill,
|
||||
renderOutline: configuration.renderOutline,
|
||||
shouldRenderInactiveLabelmaps: configuration.shouldRenderInactiveLabelmaps,
|
||||
fillAlpha: configuration.fillAlpha,
|
||||
outlineAlpha: configuration.outlineAlpha,
|
||||
outlineWidth: configuration.outlineWidth,
|
||||
fillAlphaInactive: configuration.fillAlphaInactive,
|
||||
outlineAlphaInactive: configuration.outlineAlphaInactive
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onChange(state);
|
||||
}, [state]);
|
||||
|
||||
const check = field => {
|
||||
setState(state => ({ ...state, [field]: !state[field] }));
|
||||
};
|
||||
|
||||
const save = (field, value) => {
|
||||
setState(state => ({ ...state, [field]: value }));
|
||||
};
|
||||
|
||||
const toFloat = value => parseFloat(value / 100).toFixed(2);
|
||||
|
||||
return (
|
||||
<div className="dcmseg-segmentation-settings">
|
||||
<div className="settings-title">
|
||||
<h3>Segmentations Settings</h3>
|
||||
<button className="return-button" onClick={onBack}>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="settings-group"
|
||||
style={{ marginBottom: state.renderFill ? 15 : 0 }}
|
||||
>
|
||||
<CustomCheck
|
||||
label="Segment Fill"
|
||||
checked={state.renderFill}
|
||||
onChange={() => check('renderFill')}
|
||||
/>
|
||||
{state.renderFill && (
|
||||
<CustomRange
|
||||
label="Opacity"
|
||||
step={1}
|
||||
min={0}
|
||||
max={100}
|
||||
value={state.fillAlpha * 100}
|
||||
onChange={event => save('fillAlpha', toFloat(event.target.value))}
|
||||
showPercentage
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="settings-group"
|
||||
style={{ marginBottom: state.renderOutline ? 15 : 0 }}
|
||||
>
|
||||
<CustomCheck
|
||||
label="Segment Outline"
|
||||
checked={state.renderOutline}
|
||||
onChange={() => check('renderOutline')}
|
||||
/>
|
||||
{state.renderOutline && (
|
||||
<>
|
||||
<CustomRange
|
||||
value={state.outlineAlpha * 100}
|
||||
label="Opacity"
|
||||
showPercentage
|
||||
step={1}
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
|
||||
/>
|
||||
<CustomRange
|
||||
value={state.outlineWidth}
|
||||
label="Width"
|
||||
showValue
|
||||
step={1}
|
||||
min={0}
|
||||
max={5}
|
||||
onChange={event => save('outlineWidth', parseInt(event.target.value))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{(state.renderFill || state.renderOutline) && (
|
||||
<div
|
||||
className="settings-group"
|
||||
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
|
||||
>
|
||||
<CustomCheck
|
||||
label="Render inactive segmentations"
|
||||
checked={state.shouldRenderInactiveLabelmaps}
|
||||
onChange={() => check('shouldRenderInactiveLabelmaps')}
|
||||
/>
|
||||
{state.shouldRenderInactiveLabelmaps && (
|
||||
<>
|
||||
{state.renderFill && (
|
||||
<CustomRange
|
||||
label="Fill Opacity"
|
||||
showPercentage
|
||||
step={1}
|
||||
min={0}
|
||||
max={100}
|
||||
value={state.fillAlphaInactive * 100}
|
||||
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
|
||||
/>
|
||||
)}
|
||||
{state.renderOutline && (
|
||||
<CustomRange
|
||||
label="Outline Opacity"
|
||||
showPercentage
|
||||
step={1}
|
||||
min={0}
|
||||
max={100}
|
||||
value={state.outlineAlphaInactive * 100}
|
||||
onChange={event => save('outlineAlphaInactive', toFloat(event.target.value))}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomCheck = ({ label, checked, onChange }) => {
|
||||
return (
|
||||
<div className="custom-check">
|
||||
<label>
|
||||
<span>{label}</span>
|
||||
<input type="checkbox" checked={checked} onChange={onChange} />
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomRange = props => {
|
||||
const { label, onChange } = props;
|
||||
return (
|
||||
<div className="range">
|
||||
<label htmlFor="range">{label}</label>
|
||||
<Range
|
||||
{...props}
|
||||
onChange={event => {
|
||||
event.persist();
|
||||
onChange(event);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentationSettings.propTypes = {
|
||||
configuration: PropTypes.shape({
|
||||
renderFill: PropTypes.bool.isRequired,
|
||||
renderOutline: PropTypes.bool.isRequired,
|
||||
shouldRenderInactiveLabelmaps: PropTypes.bool.isRequired,
|
||||
fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
|
||||
outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
|
||||
outlineWidth: PropTypes.number.isRequired,
|
||||
fillAlphaInactive: PropTypes.number.isRequired,
|
||||
outlineAlphaInactive: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
onBack: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SegmentationSettings;
|
||||
@@ -0,0 +1,7 @@
|
||||
export { default as BrushColorSelector } from './BrushColorSelector/BrushColorSelector';
|
||||
export { default as BrushRadius } from './BrushRadius/BrushRadius';
|
||||
export { default as SegmentationItem } from './SegmentationItem/SegmentationItem';
|
||||
export { default as SegmentationSettings } from './SegmentationSettings/SegmentationSettings';
|
||||
export { default as SegmentationPanel } from './SegmentationPanel/SegmentationPanel';
|
||||
export { default as SegmentItem } from './SegmentItem/SegmentItem';
|
||||
export { default as SegmentationSelect } from './SegmentationSelect';
|
||||
@@ -0,0 +1,30 @@
|
||||
import init from './init.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import panelModule from './panelModule.js';
|
||||
import sopClassHandlerModule from './OHIFDicomSegSopClassHandler.js';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'com.ohif.dicom-segmentation',
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {object} [configuration={}]
|
||||
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
||||
*/
|
||||
preRegistration({ servicesManager, configuration = {} }) {
|
||||
init({ servicesManager, configuration });
|
||||
},
|
||||
getToolbarModule({ servicesManager }) {
|
||||
return toolbarModule;
|
||||
},
|
||||
getPanelModule({ servicesManager }) {
|
||||
return panelModule;
|
||||
},
|
||||
getSopClassHandlerModule({ servicesManager }) {
|
||||
return sopClassHandlerModule;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import csTools from 'cornerstone-tools';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} configuration
|
||||
* @param {Object|Array} configuration.csToolsConfig
|
||||
*/
|
||||
export default function init({ servicesManager, configuration = {} }) {
|
||||
const { BrushTool, SphericalBrushTool, CorrectionScissorsTool } = csTools;
|
||||
const tools = [BrushTool, SphericalBrushTool, CorrectionScissorsTool];
|
||||
|
||||
tools.forEach(tool => csTools.addTool(tool));
|
||||
|
||||
csTools.addTool(BrushTool, {
|
||||
name: 'BrushEraser',
|
||||
configuration: {
|
||||
alwaysEraseOnClick: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import * as dcmjs from 'dcmjs';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { DicomLoaderService } = OHIF.utils;
|
||||
|
||||
export default async function loadSegmentation(
|
||||
segDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies
|
||||
) {
|
||||
const { StudyInstanceUID } = referencedDisplaySet;
|
||||
|
||||
// Set here is loading is asynchronous.
|
||||
// If this function throws its set back to false.
|
||||
segDisplaySet.isLoaded = true;
|
||||
|
||||
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
segDisplaySet,
|
||||
studies
|
||||
);
|
||||
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
|
||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
);
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const imageIds = _getImageIdsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
referencedDisplaySet.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const results = _parseSeg(segArrayBuffer, imageIds);
|
||||
|
||||
if (!results) {
|
||||
throw new Error('Fractional segmentations are not yet supported');
|
||||
}
|
||||
|
||||
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
||||
const { setters } = cornerstoneTools.getModule('segmentation');
|
||||
|
||||
// TODO: Could define a color LUT based on colors in the SEG.
|
||||
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
||||
|
||||
setters.labelmap3DByFirstImageId(
|
||||
imageIds[0],
|
||||
labelmapBuffer,
|
||||
labelmapIndex,
|
||||
segMetadata,
|
||||
imageIds.length,
|
||||
segmentsOnFrame
|
||||
);
|
||||
|
||||
segDisplaySet.labelmapIndex = labelmapIndex;
|
||||
}
|
||||
|
||||
function _getNextLabelmapIndex(firstImageId) {
|
||||
const { state } = cornerstoneTools.getModule('segmentation');
|
||||
const brushStackState = state.series[firstImageId];
|
||||
|
||||
let labelmapIndex = 0;
|
||||
|
||||
if (brushStackState) {
|
||||
const { labelmaps3D } = brushStackState;
|
||||
labelmapIndex = labelmaps3D.length;
|
||||
|
||||
for (let i = 0; i < labelmaps3D.length; i++) {
|
||||
if (!labelmaps3D[i]) {
|
||||
labelmapIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labelmapIndex;
|
||||
}
|
||||
|
||||
function _parseSeg(arrayBuffer, imageIds) {
|
||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||
imageIds,
|
||||
arrayBuffer,
|
||||
cornerstone.metaData
|
||||
);
|
||||
}
|
||||
|
||||
function _getImageIdsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySets = study.displaySets.filter(displaySet => {
|
||||
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
// TODO -> We could make check the instance list and see if any match?
|
||||
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
|
||||
// ^ Will that even happen?
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySets[0];
|
||||
|
||||
return referencedDisplaySet.images.map(image => image.getImageId());
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||
|
||||
export default {
|
||||
menuOptions: [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'Segmentations',
|
||||
target: 'segmentation-panel',
|
||||
isDisabled: studies => {
|
||||
if (!studies) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = 0; i < studies.length; i++) {
|
||||
const study = studies[i];
|
||||
|
||||
if (study && study.series) {
|
||||
for (let j = 0; j < study.series.length; j++) {
|
||||
const series = study.series[j];
|
||||
|
||||
if (series.Modality === 'SEG') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 'segmentation-panel',
|
||||
component: SegmentationPanel,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
/* TODO: Creation tools (future release).
|
||||
|
||||
const TOOLBAR_BUTTON_TYPES = {
|
||||
COMMAND: 'command',
|
||||
SET_TOOL_ACTIVE: 'setToolActive',
|
||||
BUILT_IN: 'builtIn',
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
const definitions = []; /* TODO: Creation tools (future release). [
|
||||
{
|
||||
id: 'SegDropdown',
|
||||
label: 'Segmentation',
|
||||
icon: 'ellipse-circle',
|
||||
buttons: [
|
||||
{
|
||||
id: 'Brush',
|
||||
label: 'Brush',
|
||||
icon: 'brush',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Brush' },
|
||||
},
|
||||
{
|
||||
id: 'SphericalBrush',
|
||||
label: 'Spherical',
|
||||
icon: 'sphere',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'SphericalBrush' },
|
||||
},
|
||||
{
|
||||
id: 'CorrectionScissors',
|
||||
label: 'Correction Scissors',
|
||||
icon: 'scissors',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'CorrectionScissors' },
|
||||
},
|
||||
{
|
||||
id: 'BrushEraser',
|
||||
label: 'Eraser',
|
||||
icon: 'trash',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'BrushEraser' },
|
||||
},
|
||||
],
|
||||
},
|
||||
]; */
|
||||
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||
};
|
||||
+68
-321
@@ -1,7 +1,7 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
All notable changes to this project will be documented in this file. See
|
||||
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.23...@ohif/extension-vtk@1.2.24) (2020-03-09)
|
||||
|
||||
@@ -428,621 +428,368 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.4...@ohif/extension-vtk@0.53.5) (2019-11-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.3...@ohif/extension-vtk@0.53.4) (2019-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.2...@ohif/extension-vtk@0.53.3) (2019-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mpr2d vtkjs viewport does not render if range is set to `NaN` values ([#1157](https://github.com/OHIF/Viewers/issues/1157)) ([84a0972](https://github.com/OHIF/Viewers/commit/84a097212babc0f98198b9ced1def9743973a5a8))
|
||||
|
||||
|
||||
|
||||
|
||||
- mpr2d vtkjs viewport does not render if range is set to `NaN` values
|
||||
([#1157](https://github.com/OHIF/Viewers/issues/1157))
|
||||
([84a0972](https://github.com/OHIF/Viewers/commit/84a097212babc0f98198b9ced1def9743973a5a8))
|
||||
|
||||
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.1...@ohif/extension-vtk@0.53.2) (2019-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.0...@ohif/extension-vtk@0.53.1) (2019-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.32...@ohif/extension-vtk@0.53.0) (2019-11-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* modal provider ([#1151](https://github.com/OHIF/Viewers/issues/1151)) ([75d88bc](https://github.com/OHIF/Viewers/commit/75d88bc454710d2dcdbc7d68c4d9df041159c840)), closes [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1146](https://github.com/OHIF/Viewers/issues/1146) [#1142](https://github.com/OHIF/Viewers/issues/1142) [#1143](https://github.com/OHIF/Viewers/issues/1143) [#1110](https://github.com/OHIF/Viewers/issues/1110) [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1119](https://github.com/OHIF/Viewers/issues/1119)
|
||||
|
||||
|
||||
|
||||
|
||||
- modal provider ([#1151](https://github.com/OHIF/Viewers/issues/1151))
|
||||
([75d88bc](https://github.com/OHIF/Viewers/commit/75d88bc454710d2dcdbc7d68c4d9df041159c840)),
|
||||
closes [#1086](https://github.com/OHIF/Viewers/issues/1086)
|
||||
[#1116](https://github.com/OHIF/Viewers/issues/1116)
|
||||
[#1116](https://github.com/OHIF/Viewers/issues/1116)
|
||||
[#1146](https://github.com/OHIF/Viewers/issues/1146)
|
||||
[#1142](https://github.com/OHIF/Viewers/issues/1142)
|
||||
[#1143](https://github.com/OHIF/Viewers/issues/1143)
|
||||
[#1110](https://github.com/OHIF/Viewers/issues/1110)
|
||||
[#1086](https://github.com/OHIF/Viewers/issues/1086)
|
||||
[#1116](https://github.com/OHIF/Viewers/issues/1116)
|
||||
[#1119](https://github.com/OHIF/Viewers/issues/1119)
|
||||
|
||||
## [0.52.32](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.31...@ohif/extension-vtk@0.52.32) (2019-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.31](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.30...@ohif/extension-vtk@0.52.31) (2019-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
## [0.52.30](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.29...@ohif/extension-vtk@0.52.30) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.29](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.28...@ohif/extension-vtk@0.52.29) (2019-11-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Upgrade react-vtkjs-viewport to fix volume orientation ([#1143](https://github.com/OHIF/Viewers/issues/1143)) ([1d86b1a](https://github.com/OHIF/Viewers/commit/1d86b1a102fc422053cff103de8c12ca6f437354))
|
||||
|
||||
|
||||
|
||||
|
||||
- 🐛 Upgrade react-vtkjs-viewport to fix volume orientation
|
||||
([#1143](https://github.com/OHIF/Viewers/issues/1143))
|
||||
([1d86b1a](https://github.com/OHIF/Viewers/commit/1d86b1a102fc422053cff103de8c12ca6f437354))
|
||||
|
||||
## [0.52.28](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.27...@ohif/extension-vtk@0.52.28) (2019-11-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.27](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.26...@ohif/extension-vtk@0.52.27) (2019-10-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.26](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.25...@ohif/extension-vtk@0.52.26) (2019-10-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.25](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.24...@ohif/extension-vtk@0.52.25) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.23...@ohif/extension-vtk@0.52.24) (2019-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.22...@ohif/extension-vtk@0.52.23) (2019-10-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.21...@ohif/extension-vtk@0.52.22) (2019-10-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* MIP styling ([#1109](https://github.com/OHIF/Viewers/issues/1109)) ([0d21cc6](https://github.com/OHIF/Viewers/commit/0d21cc6ad0c47706b9e62e05fe2a0f1d86339760))
|
||||
|
||||
|
||||
|
||||
|
||||
- MIP styling ([#1109](https://github.com/OHIF/Viewers/issues/1109))
|
||||
([0d21cc6](https://github.com/OHIF/Viewers/commit/0d21cc6ad0c47706b9e62e05fe2a0f1d86339760))
|
||||
|
||||
## [0.52.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.20...@ohif/extension-vtk@0.52.21) (2019-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.19...@ohif/extension-vtk@0.52.20) (2019-10-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.18...@ohif/extension-vtk@0.52.19) (2019-10-25)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.17...@ohif/extension-vtk@0.52.18) (2019-10-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Orthographic MPR fix ([#1092](https://github.com/OHIF/Viewers/issues/1092)) ([460e375](https://github.com/OHIF/Viewers/commit/460e375f0aa75d35f7a46b4d48e6cc706019956d))
|
||||
|
||||
|
||||
|
||||
|
||||
- 🐛 Orthographic MPR fix ([#1092](https://github.com/OHIF/Viewers/issues/1092))
|
||||
([460e375](https://github.com/OHIF/Viewers/commit/460e375f0aa75d35f7a46b4d48e6cc706019956d))
|
||||
|
||||
## [0.52.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.16...@ohif/extension-vtk@0.52.17) (2019-10-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix crosshairs location when using MIP ([#1080](https://github.com/OHIF/Viewers/issues/1080)) ([b451ce4](https://github.com/OHIF/Viewers/commit/b451ce440796f13b0891739d2130e8ee71993d3d))
|
||||
|
||||
|
||||
|
||||
|
||||
- 🐛 Fix crosshairs location when using MIP
|
||||
([#1080](https://github.com/OHIF/Viewers/issues/1080))
|
||||
([b451ce4](https://github.com/OHIF/Viewers/commit/b451ce440796f13b0891739d2130e8ee71993d3d))
|
||||
|
||||
## [0.52.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.15...@ohif/extension-vtk@0.52.16) (2019-10-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Switch to orhtographic view for 2D MPR ([#1074](https://github.com/OHIF/Viewers/issues/1074)) ([13d337a](https://github.com/OHIF/Viewers/commit/13d337aaabb8dadf6366c6262c5e47e7781edd08))
|
||||
|
||||
|
||||
|
||||
|
||||
- 🐛 Switch to orhtographic view for 2D MPR
|
||||
([#1074](https://github.com/OHIF/Viewers/issues/1074))
|
||||
([13d337a](https://github.com/OHIF/Viewers/commit/13d337aaabb8dadf6366c6262c5e47e7781edd08))
|
||||
|
||||
## [0.52.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.14...@ohif/extension-vtk@0.52.15) (2019-10-23)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* ⚡️ Throttle UI refresh of synced WL value in vtk port ([#1070](https://github.com/OHIF/Viewers/issues/1070)) ([9d08e81](https://github.com/OHIF/Viewers/commit/9d08e81aa5def63a5e34f464aae099f7c29f0ac5))
|
||||
|
||||
|
||||
|
||||
|
||||
- ⚡️ Throttle UI refresh of synced WL value in vtk port
|
||||
([#1070](https://github.com/OHIF/Viewers/issues/1070))
|
||||
([9d08e81](https://github.com/OHIF/Viewers/commit/9d08e81aa5def63a5e34f464aae099f7c29f0ac5))
|
||||
|
||||
## [0.52.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.13...@ohif/extension-vtk@0.52.14) (2019-10-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.12...@ohif/extension-vtk@0.52.13) (2019-10-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.11...@ohif/extension-vtk@0.52.12) (2019-10-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.10...@ohif/extension-vtk@0.52.11) (2019-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.9...@ohif/extension-vtk@0.52.10) (2019-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.8...@ohif/extension-vtk@0.52.9) (2019-10-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.7...@ohif/extension-vtk@0.52.8) (2019-10-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.6...@ohif/extension-vtk@0.52.7) (2019-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.5...@ohif/extension-vtk@0.52.6) (2019-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.4...@ohif/extension-vtk@0.52.5) (2019-10-10)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* ⚡️ Update react-vtkjs-viewport for performance improvement ([6ee5094](https://github.com/OHIF/Viewers/commit/6ee5094))
|
||||
|
||||
|
||||
|
||||
|
||||
- ⚡️ Update react-vtkjs-viewport for performance improvement
|
||||
([6ee5094](https://github.com/OHIF/Viewers/commit/6ee5094))
|
||||
|
||||
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.3...@ohif/extension-vtk@0.52.4) (2019-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.2...@ohif/extension-vtk@0.52.3) (2019-10-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.1...@ohif/extension-vtk@0.52.2) (2019-10-09)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 🎸 Update vtk viewport for faster image reconstruction ([#1016](https://github.com/OHIF/Viewers/issues/1016)) ([8513ad9](https://github.com/OHIF/Viewers/commit/8513ad9))
|
||||
|
||||
|
||||
|
||||
|
||||
- 🎸 Update vtk viewport for faster image reconstruction
|
||||
([#1016](https://github.com/OHIF/Viewers/issues/1016))
|
||||
([8513ad9](https://github.com/OHIF/Viewers/commit/8513ad9))
|
||||
|
||||
## [0.52.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.0...@ohif/extension-vtk@0.52.1) (2019-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.52.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.3...@ohif/extension-vtk@0.52.0) (2019-10-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Synced Window Leveling ([559b637](https://github.com/OHIF/Viewers/commit/559b637)), closes [#558](https://github.com/OHIF/Viewers/issues/558)
|
||||
|
||||
|
||||
|
||||
|
||||
- 🎸 Synced Window Leveling
|
||||
([559b637](https://github.com/OHIF/Viewers/commit/559b637)), closes
|
||||
[#558](https://github.com/OHIF/Viewers/issues/558)
|
||||
|
||||
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.2...@ohif/extension-vtk@0.51.3) (2019-10-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.1...@ohif/extension-vtk@0.51.2) (2019-10-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.0...@ohif/extension-vtk@0.51.1) (2019-10-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **rotate:** Use new MPR rotate in react-vtkjs-viewport 0.1.2 ([#964](https://github.com/OHIF/Viewers/issues/964)) ([8503cbc](https://github.com/OHIF/Viewers/commit/8503cbc))
|
||||
|
||||
|
||||
|
||||
|
||||
- **rotate:** Use new MPR rotate in react-vtkjs-viewport 0.1.2
|
||||
([#964](https://github.com/OHIF/Viewers/issues/964))
|
||||
([8503cbc](https://github.com/OHIF/Viewers/commit/8503cbc))
|
||||
|
||||
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.23...@ohif/extension-vtk@0.51.0) (2019-10-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 MPR UI improvements. Added MinIP, AvgIP, slab thickness slider and mode toggle ([#947](https://github.com/OHIF/Viewers/issues/947)) ([c79c0c3](https://github.com/OHIF/Viewers/commit/c79c0c3))
|
||||
|
||||
|
||||
|
||||
|
||||
- 🎸 MPR UI improvements. Added MinIP, AvgIP, slab thickness slider and mode
|
||||
toggle ([#947](https://github.com/OHIF/Viewers/issues/947))
|
||||
([c79c0c3](https://github.com/OHIF/Viewers/commit/c79c0c3))
|
||||
|
||||
## [0.50.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.22...@ohif/extension-vtk@0.50.23) (2019-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.21...@ohif/extension-vtk@0.50.22) (2019-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.20...@ohif/extension-vtk@0.50.21) (2019-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.19...@ohif/extension-vtk@0.50.20) (2019-09-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.18...@ohif/extension-vtk@0.50.19) (2019-09-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
|
||||
|
||||
|
||||
|
||||
|
||||
- Add some code splitting for PWA build
|
||||
([#937](https://github.com/OHIF/Viewers/issues/937))
|
||||
([8938035](https://github.com/OHIF/Viewers/commit/8938035))
|
||||
|
||||
## [0.50.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.17...@ohif/extension-vtk@0.50.18) (2019-09-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.16...@ohif/extension-vtk@0.50.17) (2019-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.15...@ohif/extension-vtk@0.50.16) (2019-09-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Set transfer function range to data range when loading volumes in VTK ([#930](https://github.com/OHIF/Viewers/issues/930)) ([2341b32](https://github.com/OHIF/Viewers/commit/2341b32))
|
||||
|
||||
|
||||
|
||||
|
||||
- Set transfer function range to data range when loading volumes in VTK
|
||||
([#930](https://github.com/OHIF/Viewers/issues/930))
|
||||
([2341b32](https://github.com/OHIF/Viewers/commit/2341b32))
|
||||
|
||||
## [0.50.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.14...@ohif/extension-vtk@0.50.15) (2019-09-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Bump VTK viewport dependency ([#929](https://github.com/OHIF/Viewers/issues/929)) ([66a0aad](https://github.com/OHIF/Viewers/commit/66a0aad))
|
||||
* Stop adding empty labelmap to VTK Viewports for now. Wait until paint tools are implemented ([#926](https://github.com/OHIF/Viewers/issues/926)) ([7f642a0](https://github.com/OHIF/Viewers/commit/7f642a0))
|
||||
|
||||
|
||||
|
||||
|
||||
- Bump VTK viewport dependency
|
||||
([#929](https://github.com/OHIF/Viewers/issues/929))
|
||||
([66a0aad](https://github.com/OHIF/Viewers/commit/66a0aad))
|
||||
- Stop adding empty labelmap to VTK Viewports for now. Wait until paint tools
|
||||
are implemented ([#926](https://github.com/OHIF/Viewers/issues/926))
|
||||
([7f642a0](https://github.com/OHIF/Viewers/commit/7f642a0))
|
||||
|
||||
## [0.50.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.13...@ohif/extension-vtk@0.50.14) (2019-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bump cornerstone-tools version in peerDeps ([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c))
|
||||
|
||||
|
||||
|
||||
|
||||
- bump cornerstone-tools version in peerDeps
|
||||
([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c))
|
||||
|
||||
## [0.50.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.12...@ohif/extension-vtk@0.50.13) (2019-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.11...@ohif/extension-vtk@0.50.12) (2019-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.10...@ohif/extension-vtk@0.50.11) (2019-09-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* simplify runtime-extension usage ([ac5dbda](https://github.com/OHIF/Viewers/commit/ac5dbda))
|
||||
|
||||
|
||||
|
||||
|
||||
- simplify runtime-extension usage
|
||||
([ac5dbda](https://github.com/OHIF/Viewers/commit/ac5dbda))
|
||||
|
||||
## [0.50.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.9...@ohif/extension-vtk@0.50.10) (2019-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.8...@ohif/extension-vtk@0.50.9) (2019-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.7...@ohif/extension-vtk@0.50.8) (2019-09-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.6...@ohif/extension-vtk@0.50.7) (2019-09-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.5...@ohif/extension-vtk@0.50.6) (2019-09-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.4...@ohif/extension-vtk@0.50.5) (2019-09-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* measurementsAPI issue caused by production build ([#842](https://github.com/OHIF/Viewers/issues/842)) ([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
|
||||
|
||||
|
||||
|
||||
|
||||
- measurementsAPI issue caused by production build
|
||||
([#842](https://github.com/OHIF/Viewers/issues/842))
|
||||
([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
|
||||
|
||||
## [0.50.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.3...@ohif/extension-vtk@0.50.4) (2019-08-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.2...@ohif/extension-vtk@0.50.3) (2019-08-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.50.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.1...@ohif/extension-vtk@0.50.2) (2019-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update vtk and viewport dependencies ([98c40bc](https://github.com/OHIF/Viewers/commit/98c40bc))
|
||||
|
||||
|
||||
|
||||
|
||||
- Update vtk and viewport dependencies
|
||||
([98c40bc](https://github.com/OHIF/Viewers/commit/98c40bc))
|
||||
|
||||
## [0.50.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.0-alpha.10...@ohif/extension-vtk@0.50.1) (2019-08-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.50.0-alpha.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.1.4-alpha.9...@ohif/extension-vtk@0.50.0-alpha.10) (2019-08-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.4-alpha.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.1.4-alpha.8...@ohif/extension-vtk@0.1.4-alpha.9) (2019-08-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 0.1.4-alpha.8 (2019-08-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@ohif/i18n": "^0.50.0",
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.10.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
@@ -54,7 +55,7 @@
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.5.3",
|
||||
"@ohif/ui": "^1.3.3",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-tools": "4.12.5",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"gh-pages": "^2.0.1",
|
||||
|
||||
@@ -5,33 +5,30 @@ import LoadingIndicator from './LoadingIndicator.js';
|
||||
import OHIF from '@ohif/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import handleSegmentationStorage from './handleSegmentationStorage.js';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
|
||||
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
|
||||
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
|
||||
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
||||
|
||||
const segmentationModule = cornerstoneTools.getModule('segmentation');
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
|
||||
const specialCaseHandlers = {};
|
||||
specialCaseHandlers[
|
||||
SOP_CLASSES.SEGMENTATION_STORAGE
|
||||
] = handleSegmentationStorage;
|
||||
|
||||
// TODO: Figure out where we plan to put this long term
|
||||
const volumeCache = {};
|
||||
const labelmapCache = {};
|
||||
|
||||
/**
|
||||
* Create a labelmap image with the same dimensions as our background volume.
|
||||
*
|
||||
* @param backgroundImageData vtkImageData
|
||||
*/
|
||||
/* TODO: Not currently used until we have drawing tools in vtkjs.
|
||||
function createLabelMapImageData(backgroundImageData) {
|
||||
// TODO => Need to do something like this if we start drawing a new segmentation
|
||||
// On a vtkjs viewport.
|
||||
|
||||
const labelMapData = vtkImageData.newInstance(
|
||||
backgroundImageData.get('spacing', 'origin', 'direction')
|
||||
);
|
||||
@@ -46,7 +43,7 @@ function createLabelMapImageData(backgroundImageData) {
|
||||
labelMapData.getPointData().setScalars(dataArray);
|
||||
|
||||
return labelMapData;
|
||||
}
|
||||
} */
|
||||
|
||||
class OHIFVTKViewport extends Component {
|
||||
state = {
|
||||
@@ -147,33 +144,58 @@ class OHIFVTKViewport extends Component {
|
||||
frameIndex
|
||||
);
|
||||
|
||||
let imageDataObject;
|
||||
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||
let labelmapDataObject;
|
||||
let labelmapColorLUT;
|
||||
|
||||
switch (SOPClassUID) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
throw new Error('Not yet implemented');
|
||||
/*
|
||||
const data = handleSegmentationStorage(
|
||||
stack.imageIds,
|
||||
displaySetInstanceUID
|
||||
const firstImageId = stack.imageIds[0];
|
||||
const { state } = segmentationModule;
|
||||
const brushStackState = state.series[firstImageId];
|
||||
|
||||
if (brushStackState) {
|
||||
const { activeLabelmapIndex } = brushStackState;
|
||||
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
||||
|
||||
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
|
||||
|
||||
if (labelmapCache[vtkLabelmapID]) {
|
||||
labelmapDataObject = labelmapCache[vtkLabelmapID];
|
||||
} else {
|
||||
// TODO -> We need an imageId based getter in cornerstoneTools
|
||||
const labelmapBuffer = labelmap3D.buffer;
|
||||
|
||||
// Create VTK Image Data with buffer as input
|
||||
labelmapDataObject = vtkImageData.newInstance();
|
||||
|
||||
const dataArray = vtkDataArray.newInstance({
|
||||
numberOfComponents: 1, // labelmap with single component
|
||||
values: new Uint16Array(labelmapBuffer),
|
||||
});
|
||||
|
||||
labelmapDataObject.getPointData().setScalars(dataArray);
|
||||
labelmapDataObject.setDimensions(...imageDataObject.dimensions);
|
||||
labelmapDataObject.setSpacing(
|
||||
...imageDataObject.vtkImageData.getSpacing()
|
||||
);
|
||||
labelmapDataObject.setOrigin(
|
||||
...imageDataObject.vtkImageData.getOrigin()
|
||||
);
|
||||
labelmapDataObject.setDirection(
|
||||
...imageDataObject.vtkImageData.getDirection()
|
||||
);
|
||||
|
||||
imageDataObject = data.referenceDataObject;
|
||||
labelmapDataObject = data.labelmapDataObject;
|
||||
// Cache the labelmap volume.
|
||||
labelmapCache[vtkLabelmapID] = labelmapDataObject;
|
||||
}
|
||||
|
||||
return loadImageData(imageDataObject).then(() => {
|
||||
return {
|
||||
data: imageDataObject.vtkImageData,
|
||||
labelmap: labelmapDataObject,
|
||||
};
|
||||
});
|
||||
*/
|
||||
default:
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||
|
||||
return imageDataObject;
|
||||
labelmapColorLUT = state.colorLutTables[labelmap3D.colorLUTIndex];
|
||||
}
|
||||
|
||||
return {
|
||||
imageDataObject,
|
||||
labelmapDataObject,
|
||||
labelmapColorLUT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -251,40 +273,48 @@ class OHIFVTKViewport extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const SOPClassUID = sopClassUIDs[0];
|
||||
const imageDataObject = this.getViewportData(
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const dataDetails = {
|
||||
studyDate: study.studyDate,
|
||||
studyTime: study.studyTime,
|
||||
studyDescription: study.studyDescription,
|
||||
patientName: study.patientName,
|
||||
patientId: study.patientId,
|
||||
seriesNumber: String(displaySet.seriesNumber),
|
||||
seriesDescription: displaySet.seriesDescription,
|
||||
};
|
||||
|
||||
const {
|
||||
imageDataObject,
|
||||
labelmapDataObject,
|
||||
labelmapColorLUT,
|
||||
} = this.getViewportData(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
this.imageDataObject = imageDataObject;
|
||||
|
||||
// TODO: Temporarily disabling this since it is not yet
|
||||
// being used and hurts performance significantly.
|
||||
/*if (!labelmap) {
|
||||
/* TODO: Not currently used until we have drawing tools in vtkjs.
|
||||
if (!labelmap) {
|
||||
labelmap = createLabelMapImageData(data);
|
||||
}*/
|
||||
} */
|
||||
|
||||
const volumeActor = this.getOrCreateVolume(
|
||||
imageDataObject,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
this.setState({
|
||||
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||
paintFilterLabelMapImageData: null, // TODO
|
||||
percentComplete: 0,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||
paintFilterLabelMapImageData: null, // TODO
|
||||
percentComplete: 0,
|
||||
dataDetails,
|
||||
},
|
||||
() => {
|
||||
this.loadProgressively(imageDataObject);
|
||||
@@ -296,6 +326,9 @@ class OHIFVTKViewport extends Component {
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
volumes: [volumeActor],
|
||||
paintFilterLabelMapImageData: labelmapDataObject,
|
||||
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||
labelmapColorLUT,
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
@@ -307,7 +340,7 @@ class OHIFVTKViewport extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const { displaySet } = this.props.viewportData;
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
@@ -347,12 +380,15 @@ class OHIFVTKViewport extends Component {
|
||||
});
|
||||
|
||||
Promise.all(insertPixelDataPromises).then(() => {
|
||||
this.setState({ isLoaded: true });
|
||||
this.setState({
|
||||
isLoaded: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let childrenWithProps = null;
|
||||
const { configuration } = segmentationModule;
|
||||
|
||||
// TODO: Does it make more sense to use Context?
|
||||
if (this.props.children && this.props.children.length) {
|
||||
@@ -367,6 +403,12 @@ class OHIFVTKViewport extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const visible = configuration.renderFill || configuration.renderOutline;
|
||||
const opacity = configuration.fillAlpha;
|
||||
const outlineThickness = configuration.outlineThickness;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={style}>
|
||||
@@ -383,6 +425,14 @@ class OHIFVTKViewport extends Component {
|
||||
this.state.paintFilterBackgroundImageData
|
||||
}
|
||||
viewportIndex={this.props.viewportIndex}
|
||||
dataDetails={this.state.dataDetails}
|
||||
labelmapRenderingOptions={{
|
||||
colorLUT: this.state.labelmapColorLUT,
|
||||
globalOpacity: opacity,
|
||||
visible,
|
||||
outlineThickness,
|
||||
renderOutline: true,
|
||||
}}
|
||||
onScroll={this.props.onScroll}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import * as dcmjs from 'dcmjs';
|
||||
|
||||
import OHIF from '@ohif/core';
|
||||
import { api } from 'dicomweb-client';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
return study.displaySets.filter(set => {
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
function getCornerstoneStack(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
// Create shortcut to displaySet
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// Get stack from Stack Manager
|
||||
const stack = StackManager.findOrCreateStack(study, displaySet);
|
||||
|
||||
// Clone the stack here so we don't mutate it later
|
||||
const stackClone = Object.assign({}, stack);
|
||||
stackClone.currentImageIdIndex = 0;
|
||||
|
||||
return stackClone;
|
||||
}
|
||||
|
||||
function retrieveDicomData(
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUid,
|
||||
wadoRoot
|
||||
) {
|
||||
const config = {
|
||||
url: wadoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader(),
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUid,
|
||||
};
|
||||
|
||||
return dicomWeb.retrieveInstance(options);
|
||||
}
|
||||
|
||||
async function handleSegmentationStorage(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
|
||||
// from google cloud
|
||||
const wadoRoot = displaySet.images[0].getData().wadoRoot;
|
||||
|
||||
const arrayBuffer = await retrieveDicomData(
|
||||
displaySet.images[0].getStudyInstanceUID(),
|
||||
displaySet.images[0].getSeriesInstanceUID(),
|
||||
displaySet.images[0].getSOPInstanceUID(),
|
||||
wadoRoot
|
||||
);
|
||||
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
|
||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
);
|
||||
|
||||
const segments = dcmjs.adapters.VTKjs.Segmentation.generateSegments(dataset);
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const displaySets = getDisplaySetsBySeries(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
}
|
||||
|
||||
const referenceDisplaySet = displaySets[0];
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
|
||||
|
||||
if (!results) {
|
||||
throw new Error('Fractional segmentations are not supported');
|
||||
}
|
||||
|
||||
const cachedStack = StackManager.findOrCreateStack(
|
||||
study,
|
||||
referenceDisplaySet
|
||||
);
|
||||
const stack = Object.assign({}, cachedStack);
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
return {
|
||||
referenceDataObject,
|
||||
labelmapDataObject,
|
||||
};
|
||||
|
||||
return {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
|
||||
export default handleSegmentationStorage;
|
||||
Reference in new issue
Block a user