Chore/kill yalc with fire (#629)

* init

* more shifting

* Move @babel dependencies up to workspace root

* Begin updating dependent libraries to use sync'd webpack builds w/ watches

* Commit changes before a long weekend

* Split packages into platform and extensions

* Consolidate how/where we specify file/module type entrypoints

* Make it possible to pass in extensions as App props

* Resolve viewer's modules

* Target for UMD bundle

* Add Webpack Stylus loader

* Provide WebPack build options for microscopy, vtk, and ui

* Better entrypoint for extensions

* Push changes up to switch PCs

* Fix ESM symlink build for Viewers

* Shift docs to root

* Specify additional lerna config props

* Begin providing basic readme info

* Rename example extension folder

* Shift links to bottom of doc

* First attempt at a netlify deploy preview

* Attempt to fix deploy preview build

* push workspace enabled to initial command; remove second yarn install; use npx to call gitbook cli commands

* Try to use global gitbook-cli

* Export node_modules path

* More agressive with modifying PATH

* Follow deploy log output advice

* try again to set bin path

* Try swapping bash with a new shell

* Try bash -l instead of exec bash

* Annd let's try again

* and again

* We may have figured it out johnny, boy

* We need to pull cornerstone-wado-image-loader files from hoisted node_modules

* Attempt to fix ticks/escapes

* Change copy syntax; try running tests on viewers from root for circleci

* Lower version to match circleci image

* Try alternative jest-canvas-mock location and version file syntax

* Run version command instead of calling node directly

* Try alternative jest transform

* Set path and enable workspaces

* Add codecov flags

* Use cpx so our copying finishes?

* Get all platform unit tests to run

* Run and report individually and in parallel

* Try once relying on codecov yaml to split w/ flags

* Add projects to split by flags

* Generate separate reports

* Trigger codecov after everything has finished running; these may not support flags

* Use recommended flags from issue comments for codecov

* Fix typo

* Remove clear flag

* Shift back to calling codecov from root

* Generate example for codecov issue

* Try fixing paths

* Simplify

* Try using codecov orb

* Bump circleci version

* Upload core and viewer

* Use full string paths

* Remove individual codecov calls

* Fix dir

* Combine files before upload

* Escape anchors

* tryfix syntax

* Escape string literal

* Combine lines to reduce path

* long paths

* Use home alias

* Fix filename

* Try to see the contents of our cat'd file

* Also upload `core`

* Try running with aliased folder

* Fix path; split PR and Merge unit tests into separate jobs

* Set default threshold

* Fix typo

* Changing to scoped package names

* Adding note

* Catch more updates

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.0
 - @ohif/extension-dicom-html@0.0.4-alpha.0
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.0
 - @ohif/extension-dicom-pdf@0.0.8-alpha.0
 - @ohif/extension-vtk@0.1.4-alpha.0
 - @ohif/core@0.11.1-alpha.0
 - @ohif/i18n@0.2.3-alpha.0
 - @ohif/ui@0.2.18-alpha.0
 - @ohif/viewer@0.0.22-alpha.0

* Clean up primary readme

* Support for dev and dev:* commands

* Shift build command; satisfy default PWA build

* Remove old scripts

* Clean up UI to set webpack scripts

* Tidy up project links

* Clean duplicate code in UI project

* Update UI project's docs

* Clean up ui and i18n config

* Reduce duplicate code in `@ohif/core`

* Fix @ohif/ui versions

* bump @ohif/core versions

* Redux testkit dep

* Remove unused/duplicate config files for projects/packages

* ohif-core --> @ohif/core

* Safer publish command

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.1
 - @ohif/extension-dicom-html@0.0.4-alpha.1
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.1
 - @ohif/extension-dicom-pdf@0.0.8-alpha.1
 - @ohif/extension-vtk@0.1.4-alpha.1
 - @ohif/core@0.11.1-alpha.1
 - @ohif/i18n@0.2.3-alpha.1
 - @ohif/ui@0.2.18-alpha.1
 - @ohif/viewer@0.0.22-alpha.1

* test version bump

* Add global prettier config

* chore(release): publish %s [skip ci]

 - @ohif/viewer@0.0.22-alpha.2

* test lerna publish

* Update package.jsons

* Fix build scripts

* catch changes

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.2
 - @ohif/extension-dicom-html@0.0.4-alpha.2
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.2
 - @ohif/extension-dicom-pdf@0.0.8-alpha.2
 - @ohif/extension-vtk@0.1.4-alpha.2
 - @ohif/core@0.11.1-alpha.2
 - @ohif/i18n@0.2.3-alpha.2
 - @ohif/ui@0.2.18-alpha.2
 - @ohif/viewer@0.0.22-alpha.3

* fix eslint issues caused by eslint 6 and our react-app config

* Publish with canary tag

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.3
 - @ohif/extension-dicom-html@0.0.4-alpha.3
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.3
 - @ohif/extension-dicom-pdf@0.0.8-alpha.3
 - @ohif/extension-vtk@0.1.4-alpha.3
 - @ohif/core@0.11.1-alpha.3
 - @ohif/i18n@0.2.3-alpha.3
 - @ohif/ui@0.2.18-alpha.3
 - @ohif/viewer@0.0.22-alpha.4

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.4
 - @ohif/extension-dicom-html@0.0.4-alpha.4
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.4
 - @ohif/extension-dicom-pdf@0.0.8-alpha.4
 - @ohif/extension-vtk@0.1.4-alpha.4
 - @ohif/core@0.11.1-alpha.4
 - @ohif/i18n@0.2.3-alpha.4
 - @ohif/ui@0.2.18-alpha.4
 - @ohif/viewer@0.0.22-alpha.5

* Simplify build

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.5
 - @ohif/extension-dicom-html@0.0.4-alpha.5
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.5
 - @ohif/extension-dicom-pdf@0.0.8-alpha.5
 - @ohif/extension-vtk@0.1.4-alpha.5
 - @ohif/core@0.11.1-alpha.5
 - @ohif/i18n@0.2.3-alpha.5
 - @ohif/ui@0.2.18-alpha.5
 - @ohif/viewer@0.0.22-alpha.6

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.6
 - @ohif/extension-dicom-html@0.0.4-alpha.6
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.6
 - @ohif/extension-dicom-pdf@0.0.8-alpha.6
 - @ohif/extension-vtk@0.1.4-alpha.6
 - @ohif/core@0.11.1-alpha.6
 - @ohif/i18n@0.2.3-alpha.6
 - @ohif/ui@0.2.18-alpha.6
 - @ohif/viewer@0.0.22-alpha.7

* Split version and publish

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.7
 - @ohif/extension-dicom-html@0.0.4-alpha.7
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.7
 - @ohif/extension-dicom-pdf@0.0.8-alpha.7
 - @ohif/extension-vtk@0.1.4-alpha.7
 - @ohif/core@0.11.1-alpha.7
 - @ohif/i18n@0.2.3-alpha.7
 - @ohif/ui@0.2.18-alpha.7
 - @ohif/viewer@0.0.22-alpha.8

* Try simplify netlify build

* react-viewerbase --> @ohif/ui

* add test for stylus loader

* Try new way to specify publish path

* Use an output folder that won't be ignored

* Try move command

* fiddle with paths

* Dig for some information

* Make sure we're in root for our commands

* Remove discovery

* Setup script-tag example

* Fix typo

* Move config folder

* Update cornerstone extension to use shared/root webpack configs

* capture changes

* Update extension webpack configs

* Don't check in docz output

* Fix babel runtime resolution issue

* Simplify ui build steps

* Push in progress

* Shift directories

* Get viewer dev path to work

* Fix viewer commonjs build

* Fix build pwa config for viewers

* Make sure our package specific command can be called from root

* Update notes

* Fix imports and misc. merge notes

* Bump wado-image-loader version

* Switch up commands to remove webworkers copy

* Fix CI config

* Fix more broken package references

* Add react-dropzone

* Persist so e2e test can use

* Fix lockfile

* Use cypress orb to simplify CI

* Fix config

* Try again

* Install yarn latest for orb

* Install yarn a different way

* Wait on correct port

* Run tests in viewer project

* Fix cypress config

* Make Cypress happier - use default location

* Simplify e2e testing

* Capture UI changes since fork

* Catch core up

* Grab latest deps after merge

* Fix broken import

* Make sure cornerstone-wado-image-loader resolves to 3+

* Note regarding orbs

* Fix refresh on 404 routes

* Fix odd error for cornerstone-wado-image-loader

* Different target for UMD

* Simplify dev script

* Try to get fancy with netlify output

* Let's do it again

* Try to fix measurementTools issue

* See if we can get our lerna magic to work on merge to monorepo branch
This commit is contained in:
Danny Brown authored and GitHub committed 2019-08-13 15:36:46 -04:00
1 parent cac911f08e
commit 08ff214ea6
919 files changed
+45652 -52097

No files matched your search

@@ -0,0 +1,80 @@
import OHIF from "@ohif/core";
import { View2D } from "react-vtkjs-viewport";
import { connect } from "react-redux";
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
const mapStateToProps = (state, ownProps) => {
let dataFromStore;
if (state.extensions && state.extensions.vtk) {
dataFromStore = state.extensions.vtk;
}
// If this is the active viewport, enable prefetching.
const { viewportIndex } = ownProps;
const isActive = viewportIndex === state.viewports.activeViewportIndex;
const viewportLayout = state.viewports.layout.viewports[viewportIndex];
const pluginDetails = viewportLayout.vtk || {};
return {
layout: state.viewports.layout,
isActive,
...pluginDetails,
// Hopefully this doesn't break anything under the hood for this one
// activeTool: activeButton && activeButton.command,
...dataFromStore,
enableStackPrefetch: isActive
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
const { viewportIndex } = ownProps;
return {
setViewportActive: () => {
dispatch(setViewportActive(viewportIndex));
},
setViewportSpecificData: data => {
dispatch(setViewportSpecificData(viewportIndex, data));
}
};
};
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
const { afterCreation } = propsFromState;
const props = {
...propsFromState,
...propsFromDispatch,
...ownProps,
/**
* Our component sets up the underlying dom element on "componentDidMount"
* for use with VTK.
*
* The onCreated prop passes back an Object containing many of the internal
* components of the VTK scene. We can grab a reference to these here, to
* make playing with VTK's native methods easier.
*
* A similar approach is taken with the Cornerstone extension.
*/
onCreated: api => {
// Store the API details for later
//setViewportSpecificData({ vtkApi: api });
if (afterCreation && typeof afterCreation === "function") {
afterCreation(api);
}
}
};
return props;
};
const ConnectedVTKViewport = connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(View2D);
export default ConnectedVTKViewport;
+17
View File
@@ -0,0 +1,17 @@
.imageViewerLoadingIndicator {
color: #91b9cd;
}
.loadingIndicator {
background-color: rgba(0, 0, 0, 0.75);
font-size: 18px;
height: 100%;
overflow: hidden;
position: absolute;
top: 0;
width: 100%;
z-index: 1;
/* Necessary for click-through to cornerstone element below */
pointer-events: none;
}
+49
View File
@@ -0,0 +1,49 @@
import './LoadingIndicator.css';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
class LoadingIndicator extends PureComponent {
static propTypes = {
percentComplete: PropTypes.number.isRequired,
error: PropTypes.object,
};
static defaultProps = {
percentComplete: 0,
error: null,
};
render() {
let percComplete;
if (this.props.percentComplete && this.props.percentComplete !== 100) {
percComplete = `${this.props.percentComplete}%`;
}
return (
<React.Fragment>
{this.props.error ? (
<div className="imageViewerErrorLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<h4>Error Loading Image</h4>
<p className="description">An error has occurred.</p>
<p className="details">{this.props.error.message}</p>
</div>
</div>
) : (
<div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<p>
Loading... <i className="fa fa-spin fa-circle-o-notch fa-fw" />{' '}
{percComplete}
</p>
</div>
</div>
)}
</React.Fragment>
);
}
}
export default LoadingIndicator;
+306
View File
@@ -0,0 +1,306 @@
import React, { Component } from "react";
import { getImageData, loadImageData } from "react-vtkjs-viewport";
import ConnectedVTKViewport from "./ConnectedVTKViewport";
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 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 { StackManager } = OHIF.utils;
// Metadata configuration
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
cornerstone.metaData.addProvider(
metadataProvider.provider.bind(metadataProvider)
);
StackManager.setMetadataProvider(metadataProvider);
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 = {};
/**
* Create a labelmap image with the same dimensions as our background volume.
*
* @param backgroundImageData vtkImageData
*/
function createLabelMapImageData(backgroundImageData) {
const labelMapData = vtkImageData.newInstance(
backgroundImageData.get("spacing", "origin", "direction")
);
labelMapData.setDimensions(backgroundImageData.getDimensions());
labelMapData.computeTransforms();
const values = new Uint8Array(backgroundImageData.getNumberOfPoints());
const dataArray = vtkDataArray.newInstance({
numberOfComponents: 1, // labelmap with single component
values
});
labelMapData.getPointData().setScalars(dataArray);
return labelMapData;
}
class OHIFVTKViewport extends Component {
state = {
volumes: null,
paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null
};
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number,
children: PropTypes.node
};
static id = "OHIFVTKViewport";
static init() {
console.log("OHIFVTKViewport init()");
}
static destroy() {
console.log("OHIFVTKViewport destroy()");
StackManager.clearStacks();
}
static getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopInstanceUid,
frameIndex
) {
// Create shortcut to displaySet
const study = studies.find(
study => study.studyInstanceUid === studyInstanceUid
);
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUid === displaySetInstanceUid;
});
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(study, displaySet);
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
if (frameIndex !== undefined) {
stack.currentImageIdIndex = frameIndex;
} else if (sopInstanceUid) {
const index = stack.imageIds.findIndex(imageId => {
const sopCommonModule = cornerstone.metaData.get(
"sopCommonModule",
imageId
);
if (!sopCommonModule) {
return;
}
return sopCommonModule.sopInstanceUID === sopInstanceUid;
});
if (index > -1) {
stack.currentImageIdIndex = index;
}
} else {
stack.currentImageIdIndex = 0;
}
return stack;
}
getViewportData = (
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
) => {
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
let imageDataObject;
let labelmapDataObject;
switch (sopClassUid) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error("Not yet implemented");
const data = handleSegmentationStorage(
stack.imageIds,
displaySetInstanceUid
);
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject
};
});
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData
};
});
}
};
getOrCreateVolume(data, displaySetInstanceUid) {
if (volumeCache[displaySetInstanceUid]) {
return volumeCache[displaySetInstanceUid];
}
const volumeActor = vtkVolume.newInstance();
const volumeMapper = vtkVolumeMapper.newInstance();
volumeActor.setMapper(volumeMapper);
volumeMapper.setInputData(data);
// TODO: Should look into implementing autoAdjustSampleDistance in vtk
const sampleDistance =
1.2 *
Math.sqrt(
data
.getSpacing()
.map((v) => v * v)
.reduce((a, b) => a + b, 0)
);
volumeMapper.setSampleDistance(sampleDistance);
volumeCache[displaySetInstanceUid] = volumeActor;
return volumeActor;
}
async setStateFromProps() {
const { studies, displaySet } = this.props.viewportData;
const {
studyInstanceUid,
displaySetInstanceUid,
sopClassUids,
sopInstanceUid,
frameIndex
} = displaySet;
if (sopClassUids.length > 1) {
console.warn(
"More than one SOPClassUid in the same series is not yet supported."
);
}
const sopClassUid = sopClassUids[0];
let { data, labelmap } = await this.getViewportData(
studies,
studyInstanceUid,
displaySetInstanceUid,
sopClassUid,
sopInstanceUid,
frameIndex
);
if (!labelmap) {
labelmap = createLabelMapImageData(data);
}
const volumeActor = this.getOrCreateVolume(data, displaySetInstanceUid);
this.setState({
volumes: [volumeActor],
paintFilterBackgroundImageData: data,
paintFilterLabelMapImageData: labelmap
});
}
componentDidMount() {
this.setStateFromProps();
}
componentDidUpdate(prevProps) {
const { studies, displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
displaySet.displaySetInstanceUid !==
prevDisplaySet.displaySetInstanceUid ||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
this.setStateFromProps();
}
}
render() {
let childrenWithProps = null;
// TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) {
childrenWithProps = this.props.children.map((child, index) => {
return React.cloneElement(child, {
viewportIndex: this.props.viewportIndex,
key: index
});
});
}
const style = { width: "100%", height: "100%", position: "relative" };
return (
<>
{this.state.volumes ? (
<ConnectedVTKViewport
volumes={this.state.volumes}
paintFilterLabelMapImageData={
this.state.paintFilterLabelMapImageData
}
paintFilterBackgroundImageData={
this.state.paintFilterBackgroundImageData
}
viewportIndex={this.props.viewportIndex}
/>
) : (
<div style={style}>
<LoadingIndicator />
</div>
)}
{childrenWithProps}
</>
);
}
}
export default OHIFVTKViewport;
+391
View File
@@ -0,0 +1,391 @@
import {
vtkInteractorStyleMPRCrosshairs,
vtkInteractorStyleMPRWindowLevel,
vtkInteractorStyleMPRSlice,
vtkSVGCrosshairsWidget,
vtkSVGWidgetManager,
} from 'react-vtkjs-viewport';
import setMPRLayout from './utils/setMPRLayout.js';
import setViewportToVTK from './utils/setViewportToVTK.js';
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder';
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
const { BlendMode } = Constants;
// TODO: Put this somewhere else
let apis = {};
let currentSlabThickness = 0.1;
function getCrosshairCallbackForIndex(index) {
return ({ worldPos }) => {
// Set camera focal point to world coordinate for linked views
apis.forEach((api, viewportIndex) => {
if (viewportIndex !== index) {
// We are basically doing the same as getSlice but with the world coordinate
// that we want to jump to instead of the camera focal point.
// I would rather do the camera adjustment directly but I keep
// doing it wrong and so this is good enough for now.
const renderWindow = api.genericRenderWindow.getRenderWindow();
const istyle = renderWindow.getInteractor().getInteractorStyle();
const sliceNormal = istyle.getSliceNormal();
const transform = vtkMatrixBuilder
.buildFromDegree()
.identity()
.rotateFromDirections(sliceNormal, [1, 0, 0]);
const mutatedWorldPos = worldPos.slice();
transform.apply(mutatedWorldPos);
const slice = mutatedWorldPos[0];
istyle.setSlice(slice);
renderWindow.render();
}
const renderer = api.genericRenderWindow.getRenderer();
const wPos = vtkCoordinate.newInstance();
wPos.setCoordinateSystemToWorld();
wPos.setValue(worldPos);
const displayPosition = wPos.getComputedDisplayValue(renderer);
const { svgWidgetManager } = api;
api.svgWidgets.crosshairsWidget.setPoint(
displayPosition[0],
displayPosition[1]
);
svgWidgetManager.render();
});
};
}
async function _getActiveViewportVTKApi(viewports) {
const { layout, viewportSpecificData, activeViewportIndex } = viewports;
const currentData = layout.viewports[activeViewportIndex];
if (currentData && currentData.plugin === 'vtk') {
// TODO: I was storing/pulling this from Redux but ran into weird issues
if (apis[activeViewportIndex]) {
return apis[activeViewportIndex];
}
}
const displaySet = viewportSpecificData[activeViewportIndex];
let api;
if (!api) {
try {
api = await setViewportToVTK(
displaySet,
activeViewportIndex,
layout,
viewportSpecificData
);
} catch (error) {
throw new Error(error);
}
}
return api;
}
function _setView(api, sliceNormal, viewUp) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
const istyle = renderWindow.getInteractor().getInteractorStyle();
istyle.setSliceNormal(...sliceNormal);
camera.setViewUp(...viewUp);
renderWindow.render();
}
function switchMPRInteractors(api, istyle) {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
const currentIStyle = renderWindow.getInteractor().getInteractorStyle();
let currentNormal;
if (currentIStyle.getSliceNormal && istyle.getSliceNormal) {
currentNormal = currentIStyle.getSliceNormal();
}
let currentSlabThickness;
if (currentIStyle.getSlabThickness && istyle.getSlabThickness) {
currentSlabThickness = currentIStyle.getSlabThickness();
}
renderWindow.getInteractor().setInteractorStyle(istyle);
// TODO: Not sure why this is required the second time this function is called
istyle.setInteractor(renderWindow.getInteractor());
if (istyle.getVolumeMapper() !== api.volumes[0]) {
if (currentNormal) {
istyle.setSliceNormal(currentNormal);
}
if (currentSlabThickness) {
istyle.setSlabThickness(currentSlabThickness);
}
istyle.setVolumeMapper(api.volumes[0]);
}
}
const actions = {
axial: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [0, 0, 1], [0, -1, 0]);
},
sagittal: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [1, 0, 0], [0, 0, 1]);
},
coronal: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports);
apis[viewports.activeViewportIndex] = api;
_setView(api, [0, 1, 0], [0, 0, 1]);
},
enableRotateTool: () => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRSlice.newInstance();
switchMPRInteractors(api, istyle);
});
},
enableCrosshairsTool: () => {
apis.forEach((api, index) => {
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
switchMPRInteractors(api, istyle);
istyle.setCallback(getCrosshairCallbackForIndex(index));
});
},
enableLevelTool: () => {
apis.forEach(api => {
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
switchMPRInteractors(api, istyle);
});
},
setSlabThickness: slabThickness => {
currentSlabThickness = slabThickness;
apis.forEach(api => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const istyle = renderWindow.getInteractor().getInteractorStyle();
if (istyle.setSlabThickness) {
istyle.setSlabThickness(currentSlabThickness);
// TODO: Do this inside the interactors in a setSlabThickness function instead
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
const dist = camera.getDistance();
const near = dist - currentSlabThickness / 2;
const far = dist + currentSlabThickness / 2;
camera.setClippingRange(near, far);
}
renderWindow.render();
});
},
changeSlabThickness: ({ change }) => {
currentSlabThickness += change;
currentSlabThickness = Math.max(currentSlabThickness, 0.1);
apis.forEach(api => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const istyle = renderWindow.getInteractor().getInteractorStyle();
if (istyle.setSlabThickness) {
istyle.setSlabThickness(currentSlabThickness);
}
renderWindow.render();
});
},
setBlendMode: ({ blendMode }) => {
apis.forEach(api => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const istyle = renderWindow.getInteractor().getInteractorStyle();
api.volumes[0].getMapper().setBlendMode(blendMode);
renderWindow.render();
});
},
mpr2d: async ({ viewports }) => {
const displaySet =
viewports.viewportSpecificData[viewports.activeViewportIndex];
let apiByViewport;
try {
apiByViewport = await setMPRLayout(displaySet);
} catch (error) {
throw new Error(error);
}
apis = apiByViewport;
/*const rgbTransferFunction = apiByViewport[0].volumes[0]
.getProperty()
.getRGBTransferFunction(0);
rgbTransferFunction.onModified(() => {
apiByViewport.forEach(a => {
const renderWindow = a.genericRenderWindow.getRenderWindow();
renderWindow.render();
});
});*/
apiByViewport.forEach((api, index) => {
const renderWindow = api.genericRenderWindow.getRenderWindow();
const renderer = api.genericRenderWindow.getRenderer();
const camera = renderer.getActiveCamera();
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
renderWindow.getInteractor().setInteractorStyle(istyle);
istyle.setVolumeMapper(api.volumes[0]);
istyle.setCallback(getCrosshairCallbackForIndex(index));
const svgWidgetManager = vtkSVGWidgetManager.newInstance();
svgWidgetManager.setRenderer(renderer);
svgWidgetManager.setScale(1);
const crosshairsWidget = vtkSVGCrosshairsWidget.newInstance();
svgWidgetManager.addWidget(crosshairsWidget);
svgWidgetManager.render();
api.svgWidgetManager = svgWidgetManager;
api.svgWidgets = {
crosshairsWidget,
};
switch (index) {
default:
case 0:
//Axial
istyle.setSliceNormal(0, 0, 1);
camera.setViewUp(0, -1, 0);
break;
case 1:
// sagittal
istyle.setSliceNormal(1, 0, 0);
camera.setViewUp(0, 0, 1);
break;
case 2:
// Coronal
istyle.setSliceNormal(0, 1, 0);
camera.setViewUp(0, 0, 1);
break;
}
renderWindow.render();
});
},
};
window.vtkActions = actions;
const definitions = {
axial: {
commandFn: actions.axial,
storeContexts: ['viewports'],
options: {},
},
coronal: {
commandFn: actions.coronal,
storeContexts: ['viewports'],
options: {},
},
sagittal: {
commandFn: actions.sagittal,
storeContexts: ['viewports'],
options: {},
},
enableRotateTool: {
commandFn: actions.enableRotateTool,
storeContexts: [],
options: {},
},
enableCrosshairsTool: {
commandFn: actions.enableCrosshairsTool,
storeContexts: [],
options: {},
},
enableLevelTool: {
commandFn: actions.enableLevelTool,
storeContexts: [],
options: {},
},
setBlendModeToComposite: {
commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.COMPOSITE_BLEND },
},
setBlendModeToMaximumIntensity: {
commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
},
setBlendModeToMinimumIntensity: {
commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
},
setBlendModeToAverageIntensity: {
commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
},
setSlabThickness: {
// TODO: How do we pass in a function argument?
commandFn: actions.setSlabThickness,
storeContexts: [],
options: {},
},
increaseSlabThickness: {
commandFn: actions.changeSlabThickness,
storeContexts: [],
options: {
change: 3,
},
},
decreaseSlabThickness: {
commandFn: actions.changeSlabThickness,
storeContexts: [],
options: {
change: -3,
},
},
mpr2d: {
commandFn: actions.mpr2d,
storeContexts: ['viewports'],
options: {},
context: 'VIEWER',
},
};
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
};
@@ -0,0 +1,150 @@
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 StudyInstanceUID = displaySet.images[0].getStudyInstanceUID();
const SeriesInstanceUID = displaySet.images[0].getSeriesInstanceUID();
const SOPInstanceUID = displaySet.images[0].getSOPInstanceUID();
const arrayBuffer = await retrieveDicomData(
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID,
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;
+28
View File
@@ -0,0 +1,28 @@
import OHIFVTKViewport from './OHIFVTKViewport.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
// This feels weird
// import loadLocales from './loadLocales';
const vtkExtension = {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'vtk',
getViewportModule() {
return OHIFVTKViewport;
},
getToolbarModule() {
return toolbarModule;
},
getCommandsModule() {
return commandsModule;
},
};
export default vtkExtension;
export { vtkExtension };
// loadLocales();
+20
View File
@@ -0,0 +1,20 @@
import i18n from '@ohif/i18n';
import locales from './locales';
async function loadLocales() {
// Note: We should wait for the i18n initializing
// promise to complete before adding any new
// resources to i18next. If the resources are added
// to i18next before initialization is complete, the
// backend values (e.g. those from Locize) will not
// be loaded properly.
try {
await i18n.initializing;
} catch(error) {
throw new Error(error);
}
i18n.addLocales(locales);
}
export default loadLocales;
@@ -0,0 +1,3 @@
{
"Rotate": "Rotate"
}
+7
View File
@@ -0,0 +1,7 @@
import Buttons from './Buttons.json';
export default {
en: {
Buttons
}
};
@@ -0,0 +1,3 @@
{
"Rotate": "Rotacionar"
}
+7
View File
@@ -0,0 +1,7 @@
import Buttons from './Buttons.json';
export default {
es: {
Buttons
}
};
+7
View File
@@ -0,0 +1,7 @@
import en from './en';
import es from './es';
export default {
...en,
...es
};
+76
View File
@@ -0,0 +1,76 @@
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
SET_TOOL_ACTIVE: 'setToolActive',
};
const definitions = [
{
id: 'Crosshairs',
label: 'Crosshairs',
icon: 'crosshairs',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'enableCrosshairsTool',
commandOptions: {},
},
{
id: 'WWWC',
label: 'WWWC',
icon: 'level',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'enableLevelTool',
commandOptions: {},
},
{
id: 'Rotate',
label: 'Rotate',
icon: '3d-rotate',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'enableRotateTool',
commandOptions: {},
},
{
id: 'setBlendModeToComposite',
label: 'Disable MIP',
icon: 'times',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'setBlendModeToComposite',
commandOptions: {},
},
{
id: 'setBlendModeToMaximumIntensity',
label: 'Enable MIP',
icon: 'soft-tissue',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'setBlendModeToMaximumIntensity',
commandOptions: {},
},
{
id: 'increaseSlabThickness',
label: 'Increase Slab Thickness',
icon: 'caret-up',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'increaseSlabThickness',
commandOptions: {},
},
{
id: 'decreaseSlabThickness',
label: 'Decrease Slab Thickness',
icon: 'caret-down',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'decreaseSlabThickness',
commandOptions: {},
},
];
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
};
@@ -0,0 +1,10 @@
import { redux } from "@ohif/core";
const { setViewportLayoutAndData } = redux.actions;
// TODO: Should not be getting dispatch from the window, but I'm not sure how else to do it cleanly
export default function setLayoutAndViewportData(layout, viewportSpecificData) {
const action = setViewportLayoutAndData(layout, viewportSpecificData);
window.store.dispatch(action);
}
+61
View File
@@ -0,0 +1,61 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
import setSingleLayoutData from './setSingleLayoutData.js';
export default function setMPRLayout(displaySet) {
return new Promise((resolve, reject) => {
let viewports = [];
const rows = 1;
const columns = 3;
const numViewports = rows * columns;
const viewportSpecificData = {};
for (let i = 0; i < numViewports; i++) {
viewports.push({
height: `${100 / rows}%`,
width: `${100 / columns}%`,
});
viewportSpecificData[i] = displaySet;
viewportSpecificData[i].plugin = 'vtk';
}
const layout = {
viewports,
};
const viewportIndices = [0, 1, 2];
let updatedViewports = layout.viewports;
const apis = [];
viewportIndices.forEach(viewportIndex => {
apis[viewportIndex] = null;
/*const currentData = layout.viewports[viewportIndex];
if (currentData && currentData.plugin === 'vtk') {
reject(new Error('Should not have reached this point??'));
}*/
const data = {
// plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
apis[viewportIndex] = api;
if (apis.every(a => !!a)) {
resolve(apis);
}
},
},
};
updatedViewports = setSingleLayoutData(
updatedViewports,
viewportIndex,
data
);
});
setLayoutAndViewportData(
{ viewports: updatedViewports },
viewportSpecificData
);
});
}
@@ -0,0 +1,12 @@
export default function setSingleLayoutData(
originalArray,
viewportIndex,
data
) {
const viewports = originalArray.slice();
const layoutData = Object.assign({}, viewports[viewportIndex], data);
viewports[viewportIndex] = layoutData;
return viewports;
}
@@ -0,0 +1,39 @@
import setLayoutAndViewportData from './setLayoutAndViewportData.js';
import setSingleLayoutData from './setSingleLayoutData.js';
export default function setViewportToVTK(
displaySet,
viewportIndex,
layout,
viewportSpecificData
) {
return new Promise((resolve, reject) => {
/*const currentData = layout.viewports[viewportIndex];
if (currentData && currentData.plugin === 'vtk') {
reject(new Error('Should not have reached this point??'));
}*/
const data = {
// plugin: 'vtk',
vtk: {
mode: 'mpr', // TODO: not used
afterCreation: api => {
resolve(api);
},
},
};
const updatedViewports = setSingleLayoutData(
layout.viewports,
viewportIndex,
data
);
const updatedViewportData = viewportSpecificData;
setLayoutAndViewportData(
{ viewports: updatedViewports },
updatedViewportData
);
});
}