Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a4280163d | ||
|
|
2d75e01ea0 | ||
|
|
5c5a49486d |
No files matched your search
@@ -48,7 +48,7 @@ If everything worked as expected, you should have a new `dist/` directory in the
|
||||
project's folder. It should roughly resemble the following:
|
||||
|
||||
```bash
|
||||
dist/
|
||||
<root>platform/viewer/dist/
|
||||
├── app-config.js
|
||||
├── app.bundle.js
|
||||
├── app.css
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "0.52.9",
|
||||
"version": "0.52.10",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -51,7 +51,7 @@
|
||||
"react-vtkjs-viewport": "^0.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^1.3.2",
|
||||
"@ohif/core": "^1.4.0",
|
||||
"@ohif/ui": "^0.54.0",
|
||||
"cornerstone-tools": "^4.0.9",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.2...@ohif/core@1.4.0) (2019-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.1...@ohif/core@1.3.2) (2019-10-14)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SeriesMetadata } from './SeriesMetadata';
|
||||
import { api } from 'dicomweb-client';
|
||||
// - createStacks
|
||||
import { isImage } from '../../utils/isImage';
|
||||
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
|
||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||
|
||||
export class StudyMetadata extends Metadata {
|
||||
@@ -620,6 +621,16 @@ const makeDisplaySet = (series, instances) => {
|
||||
imageSet.getImage(0).getRawValue('x00200013')
|
||||
);
|
||||
|
||||
const isReconstructable = isDisplaySetReconstructable(series, instances);
|
||||
|
||||
imageSet.isReconstructable = isReconstructable.value;
|
||||
|
||||
if (isReconstructable.missingFrames) {
|
||||
// TODO -> This is currently unused, but may be used for reconstructing
|
||||
// Volumes with gaps later on.
|
||||
imageSet.missingFrames = isReconstructable.missingFrames;
|
||||
}
|
||||
|
||||
return imageSet;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Checks if a series is reconstructable to a 3D volume.
|
||||
*
|
||||
* @param {Object} series The `OHIFSeriesMetadata` object.
|
||||
* @param {Object[]} instances The `OHIFInstanceMetadata` object
|
||||
*/
|
||||
export default function isDisplaySetReconstructable(series, instances) {
|
||||
// Can't reconstruct if we only have one image.
|
||||
|
||||
const modality = series._data.modality; // TODO -> Is there a better way to get this?
|
||||
const isMultiframe = instances[0].getRawValue('x00280008') > 1;
|
||||
|
||||
if (!constructableModalities.includes(modality)) {
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
if (!isMultiframe && instances.length === 1) {
|
||||
return { values: false };
|
||||
}
|
||||
|
||||
if (isMultiframe) {
|
||||
return processMultiframe(instances[0]);
|
||||
} else {
|
||||
return processSingleframe(instances);
|
||||
}
|
||||
}
|
||||
|
||||
function processMultiframe(instance) {
|
||||
//TODO: deal with multriframe checks! return true for now.
|
||||
return { value: true };
|
||||
}
|
||||
|
||||
function processSingleframe(instances) {
|
||||
const firstImage = instances[0];
|
||||
const firstImageRows = firstImage.getTagValue('x00280010');
|
||||
const firstImageColumns = firstImage.getTagValue('x00280011');
|
||||
const firstImageSamplesPerPixel = firstImage.getTagValue('x00280002');
|
||||
// Note: No need to unpack iop, can compare string form.
|
||||
const firstImageOrientationPatient = firstImage.getTagValue('x00200037');
|
||||
|
||||
// Can't reconstruct if we:
|
||||
// -- Have a different dimensions within a displaySet.
|
||||
// -- Have a different number of components within a displaySet.
|
||||
// -- Have different orientations within a displaySet.
|
||||
for (let i = 1; i < instances.length; i++) {
|
||||
const instance = instances[i];
|
||||
const rows = instance.getTagValue('x00280010');
|
||||
const columns = instance.getTagValue('x00280011');
|
||||
const samplesPerPixel = instance.getTagValue('x00280002');
|
||||
const imageOrientationPatient = instance.getTagValue('x00200037');
|
||||
|
||||
if (
|
||||
rows !== firstImageRows ||
|
||||
columns !== firstImageColumns ||
|
||||
samplesPerPixel !== firstImageSamplesPerPixel ||
|
||||
imageOrientationPatient !== firstImageOrientationPatient
|
||||
) {
|
||||
return { value: false };
|
||||
}
|
||||
}
|
||||
|
||||
let missingFrames = 0;
|
||||
|
||||
// Check if frame spacing is approximately equal within a tolerance.
|
||||
// If spacing is on a uniform grid but we are missing frames,
|
||||
// Allow reconstruction, but pass back the number of missing frames.
|
||||
if (instances.length > 2) {
|
||||
const firstIpp = _getImagePositionPatient(firstImage);
|
||||
const lastIpp = _getImagePositionPatient(instances[instances.length - 1]);
|
||||
const averageSpacingBetweenFrames =
|
||||
_getPerpendicularDistance(firstIpp, lastIpp) / (instances.length - 1);
|
||||
|
||||
let previousIpp = firstIpp;
|
||||
|
||||
for (let i = 1; i < instances.length; i++) {
|
||||
const instance = instances[i];
|
||||
const ipp = _getImagePositionPatient(instance);
|
||||
|
||||
const spacingBetweenFrames = _getPerpendicularDistance(ipp, previousIpp);
|
||||
const spacingIssue = _getSpacingIssue(
|
||||
spacingBetweenFrames,
|
||||
averageSpacingBetweenFrames
|
||||
);
|
||||
|
||||
if (spacingIssue) {
|
||||
const issue = spacingIssue.issue;
|
||||
|
||||
if (issue === reconstructionIssues.MISSING_FRAMES) {
|
||||
missingFrames += spacingIssue.missingFrames;
|
||||
} else if (issue === reconstructionIssues.IRREGULAR_SPACING) {
|
||||
return { value: false };
|
||||
}
|
||||
}
|
||||
|
||||
previousIpp = ipp;
|
||||
}
|
||||
}
|
||||
|
||||
return { value: true, missingFrames };
|
||||
}
|
||||
|
||||
// TODO: Is 10% a reasonable tolerance for spacing?
|
||||
const tolerance = 0.1;
|
||||
|
||||
/**
|
||||
* Checks for spacing issues.
|
||||
*
|
||||
* @param {number} spacing The spacing between two frames.
|
||||
* @param {number} averageSpacing The average spacing between all frames.
|
||||
*
|
||||
* @returns {Object} An object containing the issue and extra information if necessary.
|
||||
*/
|
||||
function _getSpacingIssue(spacing, averageSpacing) {
|
||||
const equalWithinTolerance =
|
||||
Math.abs(spacing - averageSpacing) < averageSpacing * tolerance;
|
||||
|
||||
if (equalWithinTolerance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const multipleOfAverageSpacing = spacing / averageSpacing;
|
||||
|
||||
const numberOfSpacings = Math.round(multipleOfAverageSpacing);
|
||||
|
||||
const errorForEachSpacing =
|
||||
Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;
|
||||
|
||||
if (errorForEachSpacing < tolerance * averageSpacing) {
|
||||
return {
|
||||
issue: reconstructionIssues.MISSING_FRAMES,
|
||||
missingFrames: numberOfSpacings - 1,
|
||||
};
|
||||
}
|
||||
|
||||
return { issue: reconstructionIssues.IRREGULAR_SPACING };
|
||||
}
|
||||
|
||||
function _getImagePositionPatient(instance) {
|
||||
return instance
|
||||
.getTagValue('x00200032')
|
||||
.split('\\')
|
||||
.map(element => Number(element));
|
||||
}
|
||||
|
||||
function _getPerpendicularDistance(a, b) {
|
||||
return Math.sqrt(
|
||||
Math.pow(a[0] - b[0], 2) +
|
||||
Math.pow(a[1] - b[1], 2) +
|
||||
Math.pow(a[2] - b[2], 2)
|
||||
);
|
||||
}
|
||||
|
||||
const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
|
||||
const reconstructionIssues = {
|
||||
MISSING_FRAMES: 'missingframes',
|
||||
IRREGULAR_SPACING: 'irregularspacing',
|
||||
};
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.8.0...@ohif/viewer@1.9.0) (2019-10-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.7.0...@ohif/viewer@1.8.0) (2019-10-14)
|
||||
|
||||
|
||||
|
||||
@@ -42,9 +42,6 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.get('@moreBtn')
|
||||
.should('be.visible')
|
||||
.contains('More');
|
||||
cy.get('@twodmprBtn')
|
||||
.should('be.visible')
|
||||
.contains('2D MPR');
|
||||
cy.get('@layoutBtn')
|
||||
.should('be.visible')
|
||||
.contains('Layout');
|
||||
|
||||
@@ -10,7 +10,6 @@ export function initCornerstoneToolsAliases() {
|
||||
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
|
||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
||||
cy.get('.expandableToolMenu').as('moreBtn');
|
||||
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.0",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -45,12 +45,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^1.3.2",
|
||||
"@ohif/core": "^1.4.0",
|
||||
"@ohif/extension-cornerstone": "^2.0.0",
|
||||
"@ohif/extension-dicom-html": "^1.0.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.0",
|
||||
"@ohif/extension-vtk": "^0.52.9",
|
||||
"@ohif/extension-vtk": "^0.52.10",
|
||||
"@ohif/i18n": "^0.50.5",
|
||||
"@ohif/ui": "^0.54.0",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
|
||||
@@ -6,10 +6,8 @@ import { connect } from 'react-redux';
|
||||
|
||||
const { setLayout } = OHIF.redux.actions;
|
||||
|
||||
const ConnectedPluginSwitch = (props) => {
|
||||
return (
|
||||
<PluginSwitch {...props} />
|
||||
)
|
||||
const ConnectedPluginSwitch = props => {
|
||||
return <PluginSwitch {...props} />;
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
@@ -26,7 +24,7 @@ const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
setLayout: data => {
|
||||
dispatch(setLayout(data));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -40,13 +38,12 @@ const mapDispatchToProps = dispatch => {
|
||||
}*/
|
||||
|
||||
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
||||
//const { activeViewportIndex, layout } = propsFromState;
|
||||
const { activeViewportIndex, viewportSpecificData } = propsFromState;
|
||||
const { studies } = ownProps;
|
||||
const { setLayout } = propsFromDispatch;
|
||||
|
||||
// TODO: Do not display certain options if the current display set
|
||||
// cannot be displayed using these view types
|
||||
const mpr = () => {
|
||||
commandsManager.runCommand("mpr2d");
|
||||
commandsManager.runCommand('mpr2d');
|
||||
};
|
||||
|
||||
const exitMpr = () => {
|
||||
@@ -61,7 +58,10 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
|
||||
|
||||
return {
|
||||
mpr,
|
||||
exitMpr
|
||||
exitMpr,
|
||||
activeViewportIndex,
|
||||
viewportSpecificData,
|
||||
studies,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import './PluginSwitch.css';
|
||||
class PluginSwitch extends Component {
|
||||
static propTypes = {
|
||||
mpr: PropTypes.func,
|
||||
exitMpr: PropTypes.func
|
||||
activeViewportIndex: PropTypes.number,
|
||||
viewportSpecificData: PropTypes.object,
|
||||
studies: PropTypes.array,
|
||||
exitMpr: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {};
|
||||
@@ -14,8 +17,8 @@ class PluginSwitch extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
isPlugSwitchOn: false,
|
||||
label: "2D MPR",
|
||||
icon: "cube"
|
||||
label: '2D MPR',
|
||||
icon: 'cube',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,15 +26,15 @@ class PluginSwitch extends Component {
|
||||
if (this.state.isPlugSwitchOn) {
|
||||
this.setState({
|
||||
isPlugSwitchOn: false,
|
||||
label: "2D MPR",
|
||||
icon: "cube"
|
||||
label: '2D MPR',
|
||||
icon: 'cube',
|
||||
});
|
||||
this.props.exitMpr();
|
||||
} else {
|
||||
this.setState({
|
||||
isPlugSwitchOn: true,
|
||||
label: "Exit 2D MPR",
|
||||
icon: "times"
|
||||
label: 'Exit 2D MPR',
|
||||
icon: 'times',
|
||||
});
|
||||
this.props.mpr();
|
||||
}
|
||||
@@ -40,12 +43,67 @@ class PluginSwitch extends Component {
|
||||
render() {
|
||||
const { label, icon } = this.state;
|
||||
|
||||
// Render exit mpr if switched on, otherwise check if mpr button should be displayed.
|
||||
|
||||
debugger;
|
||||
|
||||
const shouldRender =
|
||||
this.state.isPlugSwitchOn || _shouldRenderMpr2DButton.call(this);
|
||||
|
||||
return (
|
||||
<div className="PluginSwitch">
|
||||
<ToolbarButton label={label} icon={icon} onClick={this.handleClick} />
|
||||
</div>
|
||||
<>
|
||||
{shouldRender && (
|
||||
<div className="PluginSwitch">
|
||||
<ToolbarButton
|
||||
label={label}
|
||||
icon={icon}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _shouldRenderMpr2DButton() {
|
||||
const { viewportSpecificData, studies, activeViewportIndex } = this.props;
|
||||
|
||||
if (!viewportSpecificData[activeViewportIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
||||
activeViewportIndex
|
||||
];
|
||||
|
||||
const displaySet = _getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
);
|
||||
|
||||
if (!displaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
return displaySet.isReconstructable;
|
||||
}
|
||||
|
||||
function _getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
return;
|
||||
}
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
export default PluginSwitch;
|
||||
@@ -25,6 +25,7 @@ class ToolbarRow extends Component {
|
||||
selectedRightSidePanel: PropTypes.string.isRequired,
|
||||
handleSidePanelChange: PropTypes.func,
|
||||
activeContexts: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
studies: PropTypes.array,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -128,7 +129,7 @@ class ToolbarRow extends Component {
|
||||
</div>
|
||||
{buttonComponents}
|
||||
<ConnectedLayoutButton />
|
||||
<ConnectedPluginSwitch />
|
||||
<ConnectedPluginSwitch studies={this.props.studies} />
|
||||
<div
|
||||
className="pull-right m-t-1 rm-x-1"
|
||||
style={{ marginLeft: 'auto' }}
|
||||
@@ -229,7 +230,6 @@ function _getButtonComponents(toolbarButtons, activeButtons) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A handy way for us to handle different button types. IE. firing commands for
|
||||
* buttons, or initiation built in behavior.
|
||||
|
||||
@@ -279,6 +279,7 @@ class Viewer extends Component {
|
||||
|
||||
this.setState(updatedState);
|
||||
}}
|
||||
studies={this.props.studies}
|
||||
/>
|
||||
|
||||
{/*<ConnectedStudyLoadingMonitor studies={this.props.studies} />*/}
|
||||
|
||||
Reference in new issue
Block a user