Switch to react app with dependencies on ohif-core and react-viewerbase
This commit is contained in:
1 parent
fc5bd73c1e
commit
11d36a4e6b
24 files changed
+1689
-88
No files matched your search
@@ -1,13 +1,12 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import ViewerRouting from "./ViewerRouting.js";
|
||||
import IHEInvokeImageDisplay from './IHEInvokeImageDisplay.js';
|
||||
import './App.css';
|
||||
import { StudyList } from 'react-viewerbase';
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
const Studylist = OHIF.studylist.components.StudyList;
|
||||
|
||||
function setContext(context) {
|
||||
/*Rollbar.configure({
|
||||
@@ -24,7 +23,7 @@ class App extends Component {
|
||||
window.router = {
|
||||
history: this.props.history
|
||||
};
|
||||
|
||||
|
||||
this.unlisten = this.props.history.listen((location, action) => {
|
||||
setContext(window.location.pathname);
|
||||
});
|
||||
@@ -40,14 +39,14 @@ class App extends Component {
|
||||
<Route
|
||||
exact
|
||||
path="/studylist"
|
||||
component={Studylist}
|
||||
component={StudyList}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
component={Studylist}
|
||||
component={StudyList}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
@@ -70,8 +69,8 @@ class App extends Component {
|
||||
store={this.props.store}
|
||||
/>
|
||||
|
||||
{/*<Route path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route path="/logout-redirect.html" onEnter={reload} />*/}
|
||||
<Route path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route path="/logout-redirect.html" onEnter={reload} />
|
||||
<Route render={() =>
|
||||
<div> Sorry, this page does not exist. </div>}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ViewerFromStudyData from './ViewerFromStudyData.js';
|
||||
|
||||
const isActive = (a) => a.active === true;
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const activeServer = state.servers.servers.find(isActive);
|
||||
|
||||
return {
|
||||
server: activeServer,
|
||||
...ownProps
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedViewerFromStudyData = connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(ViewerFromStudyData);
|
||||
|
||||
export default ConnectedViewerFromStudyData;
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { Component } from "react";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ViewerFromStudyData from "./ViewerFromStudyData.js";
|
||||
|
||||
function IHEInvokeImageDisplay({ match }) {
|
||||
const requestType = match.params.query.requestType;
|
||||
|
||||
let studyInstanceUids;
|
||||
let patientUids;
|
||||
let displayStudyList = false;
|
||||
|
||||
if (requestType === "STUDY") {
|
||||
studyInstanceUids = match.params.query.studyUID.split(';');
|
||||
} else if (requestType === "STUDYBASE64") {
|
||||
@@ -14,15 +15,14 @@ function IHEInvokeImageDisplay({ match }) {
|
||||
const decodedData = window.atob(uids);
|
||||
studyInstanceUids = decodedData.split(';');
|
||||
} else if (requestType === "PATIENT") {
|
||||
const patientUids = this.params.query.patientID.split(';');
|
||||
patientUids = this.params.query.patientID.split(';');
|
||||
displayStudyList = true
|
||||
} else {
|
||||
displayStudyList = true
|
||||
}
|
||||
|
||||
if (displayStudyList) {
|
||||
//return (<StudyList/>);
|
||||
return ('');
|
||||
return ('')//<StudyList patientUids={patientUids}/>);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -30,4 +30,16 @@ function IHEInvokeImageDisplay({ match }) {
|
||||
);
|
||||
}
|
||||
|
||||
IHEInvokeImageDisplay.propTypes = {
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
query: PropTypes.shape({
|
||||
requestType: PropTypes.string.isRequired,
|
||||
studyUID: PropTypes.string,
|
||||
patientID: PropTypes.string
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
export default IHEInvokeImageDisplay;
|
||||
@@ -1,43 +1,44 @@
|
||||
import React, {Component} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Viewer from "./viewer/viewer.js";
|
||||
import { OHIF } from 'ohif-core';
|
||||
import OHIF from 'ohif-core';
|
||||
import { sortingManager } from './lib/sortingManager.js';
|
||||
import { updateMetaDataManager } from './lib/updateMetaDataManager.js';
|
||||
|
||||
// TODO: Move to react-viewerbase
|
||||
|
||||
function createDisplaySets(studies) {
|
||||
// Define the OHIF.viewer.data global object
|
||||
// TODO: Save all data that is currently in OHIF.viewer in redux instead
|
||||
OHIF.viewer.data = OHIF.viewer.data || {};
|
||||
//OHIF.viewer.data = OHIF.viewer.data || {};
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.Studies collection
|
||||
OHIF.viewer.Studies.removeAll();
|
||||
//OHIF.viewer.Studies.removeAll();
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.StudyMetadataList collection
|
||||
OHIF.viewer.StudyMetadataList.removeAll();
|
||||
//OHIF.viewer.StudyMetadataList.removeAll();
|
||||
|
||||
OHIF.viewer.data.studyInstanceUids = [];
|
||||
//OHIF.viewer.data.studyInstanceUids = [];
|
||||
|
||||
const updatedStudies = studies.map(study => {
|
||||
const studyMetadata = new OHIF.metadata.OHIFStudyMetadata(study, study.studyInstanceUid);
|
||||
let displaySets = study.displaySets;
|
||||
|
||||
if (!study.displaySets) {
|
||||
displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
||||
displaySets = sortingManager.getDisplaySets(studyMetadata);
|
||||
study.displaySets = displaySets;
|
||||
}
|
||||
|
||||
studyMetadata.setDisplaySets(displaySets);
|
||||
|
||||
study.selected = true;
|
||||
OHIF.viewer.Studies.insert(study);
|
||||
OHIF.viewer.StudyMetadataList.insert(studyMetadata);
|
||||
OHIF.viewer.data.studyInstanceUids.push(study.studyInstanceUid);
|
||||
//OHIF.viewer.Studies.insert(study);
|
||||
//OHIF.viewer.StudyMetadataList.insert(studyMetadata);
|
||||
//OHIF.viewer.data.studyInstanceUids.push(study.studyInstanceUid);
|
||||
|
||||
// Updates WADO-RS metaDataManager
|
||||
OHIF.viewerbase.updateMetaDataManager(study);
|
||||
updateMetaDataManager(study);
|
||||
|
||||
return study;
|
||||
});
|
||||
@@ -58,16 +59,11 @@ class ViewerFromStudyData extends Component {
|
||||
componentDidMount() {
|
||||
// TODO: Avoid using timepoints here
|
||||
//const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} };
|
||||
const params = {
|
||||
studyInstanceUids: this.props.studyInstanceUids,
|
||||
seriesInstanceUids: this.props.seriesInstanceUids,
|
||||
}
|
||||
const promise = OHIF.viewerbase.prepareViewerData(params);
|
||||
const { studyInstanceUids, seriesInstanceUids, server } = this.props;
|
||||
const promise = OHIF.studies.retrieveStudiesMetadata(server, studyInstanceUids, seriesInstanceUids)
|
||||
|
||||
// Render the viewer when the data is ready
|
||||
promise.then(({ studies, viewerData }) => {
|
||||
OHIF.viewer.data = viewerData;
|
||||
|
||||
promise.then(studies => {
|
||||
const updatedStudies = createDisplaySets(studies);
|
||||
|
||||
this.setState({
|
||||
@@ -75,8 +71,10 @@ class ViewerFromStudyData extends Component {
|
||||
});
|
||||
}).catch(error => {
|
||||
this.setState({
|
||||
error,
|
||||
error: true,
|
||||
});
|
||||
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
}
|
||||
@@ -96,7 +94,8 @@ class ViewerFromStudyData extends Component {
|
||||
|
||||
ViewerFromStudyData.propTypes = {
|
||||
studyInstanceUids: PropTypes.array.isRequired,
|
||||
seriesInstanceUids: PropTypes.array
|
||||
seriesInstanceUids: PropTypes.array,
|
||||
server: PropTypes.object
|
||||
};
|
||||
|
||||
export default ViewerFromStudyData;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {Component} from "react";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ViewerFromStudyData from "./ViewerFromStudyData.js";
|
||||
import ConnectedViewerFromStudyData from "./ConnectedViewerFromStudyData";
|
||||
|
||||
// TODO: Move to react-viewerbase
|
||||
|
||||
@@ -18,7 +18,7 @@ function ViewerRouting({ match }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewerFromStudyData studyInstanceUids={studyUIDs} seriesInstanceUids={seriesUIDs}/>
|
||||
<ConnectedViewerFromStudyData studyInstanceUids={studyUIDs} seriesInstanceUids={seriesUIDs}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
import { OHIF } from 'ohif-core';
|
||||
import dicomParser from 'dicom-parser';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import cornerstoneMath from 'cornerstone-math';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import Hammer from 'hammerjs';
|
||||
import OHIF from 'ohif-core';
|
||||
import sha from './sha.js';
|
||||
import version from './version.js';
|
||||
|
||||
OHIF.info = {
|
||||
window.info = {
|
||||
sha,
|
||||
version
|
||||
};
|
||||
|
||||
cornerstoneTools.external.cornerstone = cornerstone;
|
||||
cornerstoneTools.external.Hammer = Hammer;
|
||||
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
|
||||
cornerstoneTools.init();
|
||||
|
||||
const maxWebWorkers = Math.max(navigator.hardwareConcurrency - 1, 1);
|
||||
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
|
||||
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
|
||||
|
||||
// TODO: find a better way to get app root URL other than window.location.origin
|
||||
const config = {
|
||||
maxWebWorkers,
|
||||
maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1),
|
||||
startWebWorkersOnDemand: true,
|
||||
webWorkerPath: OHIF.utils.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderWebWorker.es5.js'),
|
||||
webWorkerPath: window.location.origin + '/cornerstoneWADOImageLoaderWebWorker.min.js',
|
||||
taskConfiguration: {
|
||||
decodeTask: {
|
||||
loadCodecsOnStartup: true,
|
||||
initializeCodecsOnStartup: false,
|
||||
codecsPath: OHIF.utils.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderCodecs.es5.js'),
|
||||
usePDFJS: false
|
||||
codecsPath: window.location.origin + '/cornerstoneWADOImageLoaderCodecs.min.js',
|
||||
usePDFJS: false,
|
||||
strict: false
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -35,3 +48,19 @@ cornerstoneWADOImageLoader.configure({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set the tool font and font size
|
||||
// context.font = "[style] [variant] [weight] [size]/[line height] [font family]";
|
||||
const fontFamily =
|
||||
'Work Sans, Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif';
|
||||
cornerstoneTools.textStyle.setFont(`16px ${fontFamily}`);
|
||||
|
||||
// Set the tool width
|
||||
cornerstoneTools.toolStyle.setToolWidth(2);
|
||||
// Set color for inactive tools
|
||||
cornerstoneTools.toolColors.setToolColor('rgb(255, 255, 0)');
|
||||
|
||||
// Set color for active tools
|
||||
cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)');
|
||||
|
||||
cornerstoneTools.store.state.touchProximity = 40;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { connect } from 'react-redux';
|
||||
import CornerstoneViewport from 'react-cornerstone-viewport';
|
||||
import OHIF from 'ohif-core'
|
||||
|
||||
const { setViewportActive } = OHIF.redux.actions;
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
activeViewportIndex: state.viewports.activeViewportIndex,
|
||||
activeTool: state.tools.buttons.find(tool => tool.active === true).command
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
setViewportActive: viewportIndex => {
|
||||
console.log(`setViewportActive: ${viewportIndex}`);
|
||||
dispatch(setViewportActive(viewportIndex))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const ConnectedCornerstoneViewport = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CornerstoneViewport);
|
||||
|
||||
export default ConnectedCornerstoneViewport;
|
||||
@@ -0,0 +1,142 @@
|
||||
import { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import { LayoutManager } from 'react-viewerbase';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport.js';
|
||||
import StackManager from '../lib/StackManager.js';
|
||||
import './ViewerMain.styl';
|
||||
|
||||
//const { StudyLoadingListener, StudyPrefetcher, ResizeViewportManager } = OHIF.classes;
|
||||
|
||||
|
||||
//window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
||||
|
||||
function getCornerstoneStack(studies, viewportData) {
|
||||
const {
|
||||
displaySetInstanceUid,
|
||||
studyInstanceUid,
|
||||
} = viewportData;
|
||||
|
||||
// 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 stack = StackManager.findOrCreateStack(study, displaySet);
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
class ViewerMain extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
displaySets: []
|
||||
};
|
||||
|
||||
this.getCornerstoneViewport = this.getCornerstoneViewport.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Attach the Window resize listener
|
||||
// Don't use jQuery here. "window.onresize" will always be null
|
||||
// If its necessary, check all the code for window.onresize getter
|
||||
// and change it to jQuery._data(window, 'events')['resize'].
|
||||
// Otherwise this function will be probably overrided.
|
||||
// See cineDialog instance.setResizeHandler function
|
||||
//window.addEventListener('resize', window.ResizeViewportManager.getResizeHandler());
|
||||
|
||||
// Add beforeUnload event handler to check for unsaved changes
|
||||
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||
|
||||
const { studies } = this.props;
|
||||
//this.studyPrefetcher = StudyPrefetcher.getInstance();
|
||||
//this.studyPrefetcher.setStudies(studies);
|
||||
//this.studyLoadingListener = StudyLoadingListener.getInstance();
|
||||
//this.studyLoadingListener.clear();
|
||||
//this.studyLoadingListener.addStudies(studies);
|
||||
|
||||
// Get all the display sets for the viewer studies
|
||||
const displaySets = [];
|
||||
studies.forEach((study) => {
|
||||
study.displaySets.forEach(dSet => dSet.images.length && displaySets.push(dSet));
|
||||
});
|
||||
|
||||
this.setState({
|
||||
displaySets
|
||||
});
|
||||
|
||||
debugger;
|
||||
}
|
||||
|
||||
getCornerstoneViewport(data, index) {
|
||||
const stack = getCornerstoneStack(this.props.studies, data)
|
||||
const viewportData = {
|
||||
stack,
|
||||
...data
|
||||
};
|
||||
|
||||
return (<ConnectedCornerstoneViewport
|
||||
key={index}
|
||||
viewportData={viewportData}
|
||||
cornerstone={cornerstone}
|
||||
cornerstoneTools={cornerstoneTools}
|
||||
/>);
|
||||
};
|
||||
|
||||
render() {
|
||||
// TODO: re-add plugins back in
|
||||
const viewportData = this.state.displaySets.map((dSet, index) => {
|
||||
return this.getCornerstoneViewport(dSet, index);
|
||||
})
|
||||
|
||||
// TODO: Connect LayoutManager to redux
|
||||
return (
|
||||
<div className="ViewerMain">
|
||||
<LayoutManager viewportData={viewportData}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Remove the Window resize listener
|
||||
//window.removeEventListener('resize', window.ResizeViewportManager.getResizeHandler());
|
||||
|
||||
// Remove beforeUnload event handler...
|
||||
//window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||
|
||||
// Destroy the synchronizer used to update reference lines
|
||||
OHIF.viewer.updateImageSynchronizer.destroy();
|
||||
|
||||
// Stop prefetching when we close the viewer
|
||||
//this.studyPrefetcher.destroy();
|
||||
|
||||
// Destroy stack loading listeners when we close the viewer
|
||||
//this.studyLoadingListener.clear();
|
||||
|
||||
// Clear references to all stacks in the StackManager
|
||||
//OHIF.viewerbase.stackManager.clearStacks();
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.Studies collection
|
||||
//OHIF.viewer.Studies.removeAll();
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.StudyMetadataList collection
|
||||
//OHIF.viewer.StudyMetadataList.removeAll();
|
||||
}
|
||||
}
|
||||
|
||||
ViewerMain.propTypes = {
|
||||
studies: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default ViewerMain;
|
||||
@@ -0,0 +1,34 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
#viewer
|
||||
height: "calc(100% - %s)" % $topBarHeight
|
||||
|
||||
&>.loadingTextDiv
|
||||
//theme('color', '$textSecondaryColor')
|
||||
font-size: 30px
|
||||
height: 100%
|
||||
line-height: "calc(100% - %s)" % $topBarHeight
|
||||
|
||||
.ViewerMain
|
||||
width: 100%
|
||||
height: 100%
|
||||
//transition(all 0.3s ease)
|
||||
|
||||
#imageViewerViewports
|
||||
.viewportContainer
|
||||
//theme('border', '%s solid $uiBorderColorDark' % $viewportBorderThickness)
|
||||
float: left
|
||||
position: relative
|
||||
|
||||
outline: 0 // Prevent blue outline in Chrome
|
||||
|
||||
&:hover
|
||||
&.active
|
||||
&:hover.active
|
||||
outline: 0 // Prevent blue outline in Chrome
|
||||
|
||||
&:hover
|
||||
//theme('border', '%s solid $uiBorderColor' % $viewportBorderThickness)
|
||||
|
||||
&.active, &:hover.active
|
||||
//theme('border', '%s solid $uiBorderColorActive' % $viewportBorderThickness)
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import { StudyBrowser } from 'react-viewerbase';
|
||||
import OHIF from 'ohif-core';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import { StudyBrowser } from 'react-viewerbase';
|
||||
import ViewerMain from './ViewerMain.js';
|
||||
// TODO: Where should we put ViewerMain? ohif-core or react-viewerbase?
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper around Cornerstone's renderToCanvas method.
|
||||
@@ -51,7 +53,8 @@ const loadError = function(error, studyInstanceUid, displaySetInstanceUid) {
|
||||
const study = studies.find(study => study.studyInstanceUid === studyInstanceUid);
|
||||
const thumbnail = study.thumbnails.find(t => t.displaySetInstanceUid === displaySetInstanceUid);
|
||||
|
||||
thumbnail.error = error;
|
||||
thumbnail.error = true;
|
||||
//thumbnail.details = error;
|
||||
|
||||
this.setState({
|
||||
studiesForBrowser: studies
|
||||
@@ -95,11 +98,7 @@ class FlexboxLayout extends Component {
|
||||
}
|
||||
|
||||
getStudiesForBrowser() {
|
||||
// @TypeSafeStudies
|
||||
const studies = OHIF.viewer.Studies.findAllBy({
|
||||
selected: true
|
||||
});
|
||||
|
||||
const { studies } = this.props;
|
||||
|
||||
// TODO[react]:
|
||||
// - Add sorting of display sets
|
||||
@@ -152,7 +151,7 @@ class FlexboxLayout extends Component {
|
||||
<StudyBrowser studies={this.state.studiesForBrowser}/>
|
||||
</div>
|
||||
<div className={mainContentClassName}>
|
||||
{/* TODO: Needs to be moved from Meteor package to react-viewerbase: <ViewerMain studies={this.props.studies}/>*/}
|
||||
<ViewerMain studies={this.props.studies}/>
|
||||
</div>
|
||||
<div className={this.state.rightSidebarOpen ? "sidebarMenu sidebar-right sidebar-open" : "sidebarMenu sidebar-right"}>
|
||||
{/*{{> measurementLightTable (clone this)}}*/}
|
||||
|
||||
@@ -4,11 +4,34 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore } from 'redux';
|
||||
import App from './App.js';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import OHIF from 'ohif-core';
|
||||
import './config';
|
||||
|
||||
const store = createStore(OHIF.redux.combinedReducer);
|
||||
|
||||
const servers = {
|
||||
dicomWeb: [
|
||||
{
|
||||
"name": "DCM4CHEE",
|
||||
"wadoUriRoot": "http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado",
|
||||
"qidoRoot": "http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs",
|
||||
"wadoRoot": "http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/rs",
|
||||
"qidoSupportsIncludeField": true,
|
||||
"imageRendering": "wadors",
|
||||
"thumbnailRendering": "wadors",
|
||||
"requestOptions": {
|
||||
"requestFromBrowser": true,
|
||||
"logRequests": true,
|
||||
"logResponses": false,
|
||||
"logTiming": true,
|
||||
"auth": "admin:admin"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
OHIF.utils.addServers(servers, store);
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
window.store = store;
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import OHIF from 'ohif-core';
|
||||
import { getImageId } from './getImageId';
|
||||
|
||||
let stackMap = {};
|
||||
let configuration = {};
|
||||
const stackUpdatedCallbacks = [];
|
||||
|
||||
const { OHIFError } = OHIF.classes;
|
||||
|
||||
|
||||
/**
|
||||
* Loop through the current series and add metadata to the
|
||||
* Cornerstone meta data provider. This will be used to fill information
|
||||
* into the viewport overlays, and to calculate reference lines and orientation markers
|
||||
* @param {Object} stackMap stackMap object
|
||||
* @param {Object} study Study object
|
||||
* @param {Object} displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
function createAndAddStack(stackMap, study, displaySet) {
|
||||
const metadataProvider = OHIF.viewer.metadataProvider;
|
||||
const numImages = displaySet.images.length;
|
||||
const imageIds = [];
|
||||
let imageId;
|
||||
|
||||
displaySet.images.forEach((instance, imageIndex) => {
|
||||
const image = instance.getData();
|
||||
const metaData = {
|
||||
instance: image, // in this context, instance will be the data of the InstanceMetadata object...
|
||||
series: displaySet, // TODO: Check this
|
||||
study,
|
||||
numImages,
|
||||
imageIndex: imageIndex + 1
|
||||
};
|
||||
|
||||
const numberOfFrames = image.numberOfFrames;
|
||||
if (numberOfFrames > 1) {
|
||||
OHIF.log.info('Multiframe image detected');
|
||||
for (let i = 0; i < numberOfFrames; i++) {
|
||||
metaData.frameNumber = i;
|
||||
imageId = getImageId(image, i);
|
||||
imageIds.push(imageId);
|
||||
metadataProvider.addMetadata(imageId, metaData);
|
||||
}
|
||||
} else {
|
||||
metaData.frameNumber = 1;
|
||||
imageId = getImageId(image);
|
||||
imageIds.push(imageId);
|
||||
metadataProvider.addMetadata(imageId, metaData);
|
||||
}
|
||||
});
|
||||
|
||||
const stack = {
|
||||
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
||||
imageIds,
|
||||
frameRate: displaySet.frameRate,
|
||||
isClip: displaySet.isClip
|
||||
};
|
||||
|
||||
stackMap[displaySet.displaySetInstanceUid] = stack;
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
configuration = {
|
||||
createAndAddStack
|
||||
};
|
||||
|
||||
/**
|
||||
* This object contains all the functions needed for interacting with the stack manager.
|
||||
* Generally, findStack is the only function used. If you want to know when new stacks
|
||||
* come in, you can register a callback with addStackUpdatedCallback.
|
||||
*/
|
||||
const StackManager = {
|
||||
/**
|
||||
* Removes all current stacks
|
||||
*/
|
||||
clearStacks() {
|
||||
stackMap = {};
|
||||
},
|
||||
/**
|
||||
* Create a stack from an image set, as well as add in the metadata on a per image bases.
|
||||
* @param study The study who's metadata will be added
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
makeAndAddStack(study, displaySet) {
|
||||
return configuration.createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks);
|
||||
},
|
||||
/**
|
||||
* Find a stack from the currently created stacks.
|
||||
* @param displaySetInstanceUid The UID of the stack to find.
|
||||
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
||||
*/
|
||||
findStack(displaySetInstanceUid) {
|
||||
return stackMap[displaySetInstanceUid];
|
||||
},
|
||||
/**
|
||||
* Find a stack or reate one if it has not been created yet
|
||||
* @param study The study who's metadata will be added
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
findOrCreateStack(study, displaySet) {
|
||||
let stack = this.findStack(displaySet.displaySetInstanceUid);
|
||||
|
||||
if (!stack || !stack.imageIds) {
|
||||
stack = this.makeAndAddStack(study, displaySet);
|
||||
}
|
||||
|
||||
return stack;
|
||||
},
|
||||
/**
|
||||
* Gets the underlying map of displaySetInstanceUid to stack object.
|
||||
* WARNING: Do not change this object. It directly affects the manager.
|
||||
* @returns {{}} map of displaySetInstanceUid -> stack.
|
||||
*/
|
||||
getAllStacks() {
|
||||
return stackMap;
|
||||
},
|
||||
/**
|
||||
* Adds in a callback to be called on a stack being added / updated.
|
||||
* @param callback must accept at minimum one argument,
|
||||
* which is the stack that was added / updated.
|
||||
*/
|
||||
addStackUpdatedCallback(callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new OHIFError('callback must be provided as a function');
|
||||
}
|
||||
stackUpdatedCallbacks.push(callback);
|
||||
},
|
||||
/**
|
||||
* Return configuration
|
||||
*/
|
||||
getConfiguration() {
|
||||
return configuration;
|
||||
},
|
||||
/**
|
||||
* Set configuration, in order to provide compatibility
|
||||
* with other systems by overriding this functions
|
||||
* @param {Object} config object with functions to be overrided
|
||||
*
|
||||
* For now, only makeAndAddStack can be overrided
|
||||
*/
|
||||
setConfiguration(config) {
|
||||
configuration = config;
|
||||
}
|
||||
};
|
||||
|
||||
export { StackManager };
|
||||
export default StackManager;
|
||||
@@ -0,0 +1,128 @@
|
||||
import OHIF from 'ohif-core';
|
||||
import { isImage } from './isImage';
|
||||
|
||||
const { ImageSet } = OHIF.classes;
|
||||
|
||||
const isMultiFrame = instance => {
|
||||
// NumberOfFrames (0028,0008)
|
||||
return instance.getRawValue('x00280008') > 1;
|
||||
};
|
||||
|
||||
const makeDisplaySet = (series, instances) => {
|
||||
const instance = instances[0];
|
||||
|
||||
const imageSet = new ImageSet(instances);
|
||||
const seriesData = series.getData();
|
||||
|
||||
// set appropriate attributes to image set...
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
||||
seriesDate: seriesData.seriesDate,
|
||||
seriesTime: seriesData.seriesTime,
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
seriesNumber: instance.getRawValue('x00200011'),
|
||||
seriesDescription: instance.getRawValue('x0008103e'),
|
||||
numImageFrames: instances.length,
|
||||
frameRate: instance.getRawValue('x00181063'),
|
||||
modality: instance.getRawValue('x00080060'),
|
||||
isMultiFrame: isMultiFrame(instance)
|
||||
});
|
||||
|
||||
// Sort the images in this series if needed
|
||||
const shallSort = true//!OHIF.utils.ObjectPath.get(Meteor, 'settings.public.ui.sortSeriesByIncomingOrder');
|
||||
if (shallSort) {
|
||||
imageSet.sortBy((a, b) => {
|
||||
// Sort by InstanceNumber (0020,0013)
|
||||
return (parseInt(a.getRawValue('x00200013', 0)) || 0) - (parseInt(b.getRawValue('x00200013', 0)) || 0);
|
||||
});
|
||||
}
|
||||
|
||||
// Include the first image instance number (after sorted)
|
||||
imageSet.setAttribute('instanceNumber', imageSet.getImage(0).getRawValue('x00200013'));
|
||||
|
||||
return imageSet;
|
||||
};
|
||||
|
||||
const isSingleImageModality = modality => {
|
||||
return (modality === 'CR' ||
|
||||
modality === 'MG' ||
|
||||
modality === 'DX');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a set of series to be placed in the Study Metadata
|
||||
* The series that appear in the Study Metadata must represent
|
||||
* imaging modalities.
|
||||
*
|
||||
* Furthermore, for drag/drop functionality,
|
||||
* it is easiest if the stack objects also contain information about
|
||||
* which study they are linked to.
|
||||
*
|
||||
* @param study The study instance metadata to be used
|
||||
* @returns {Array} An array of series to be placed in the Study Metadata
|
||||
*/
|
||||
const createStacks = study => {
|
||||
// Define an empty array of display sets
|
||||
const displaySets = [];
|
||||
|
||||
if (!study || !study.getSeriesCount()) {
|
||||
return displaySets;
|
||||
}
|
||||
|
||||
// Loop through the series (SeriesMetadata)
|
||||
study.forEachSeries(series => {
|
||||
// If the series has no instances, skip it
|
||||
if (!series.getInstanceCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search through the instances (InstanceMedatada object) of this series
|
||||
// Split Multi-frame instances and Single-image modalities
|
||||
// into their own specific display sets. Place the rest of each
|
||||
// series into another display set.
|
||||
const stackableInstances = [];
|
||||
series.forEachInstance(instance => {
|
||||
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
||||
if (!isImage(instance.getRawValue('x00080016')) && !instance.getRawValue('x00280010')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let displaySet;
|
||||
if (isMultiFrame(instance)) {
|
||||
displaySet = makeDisplaySet(series, [ instance ]);
|
||||
displaySet.setAttributes({
|
||||
isClip: true,
|
||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid for drag/drop purposes
|
||||
numImageFrames: instance.getRawValue('x00280008'), // Override the default value of instances.length
|
||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
||||
acquisitionDatetime: instance.getRawValue('x0008002a') // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else if (isSingleImageModality(instance.modality)) {
|
||||
displaySet = makeDisplaySet(series, [ instance ]);
|
||||
displaySet.setAttributes({
|
||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid
|
||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
||||
acquisitionDatetime: instance.getRawValue('x0008002a') // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else {
|
||||
stackableInstances.push(instance);
|
||||
}
|
||||
});
|
||||
|
||||
if (stackableInstances.length) {
|
||||
const displaySet = makeDisplaySet(series, stackableInstances);
|
||||
displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID());
|
||||
displaySets.push(displaySet);
|
||||
}
|
||||
});
|
||||
|
||||
return displaySets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose "createStacks"...
|
||||
*/
|
||||
|
||||
export { createStacks };
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getWADORSImageId } from './getWADORSImageId';
|
||||
|
||||
// https://stackoverflow.com/a/6021027/3895126
|
||||
function updateQueryStringParameter(uri, key, value) {
|
||||
const regex = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
|
||||
const separator = uri.indexOf('?') !== -1 ? '&' : '?';
|
||||
if (uri.match(regex)) {
|
||||
return uri.replace(regex, '$1' + key + '=' + value + '$2');
|
||||
} else {
|
||||
return uri + separator + key + '=' + value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone from an image instance
|
||||
*
|
||||
* @param instance
|
||||
* @param frame
|
||||
* @param thumbnail
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
export function getImageId(instance, frame, thumbnail=false) {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof instance.getImageId === 'function') {
|
||||
return instance.getImageId();
|
||||
}
|
||||
|
||||
if (instance.url) {
|
||||
if (frame !== undefined) {
|
||||
instance.url = updateQueryStringParameter(instance.url, 'frame', frame);
|
||||
}
|
||||
|
||||
return instance.url;
|
||||
}
|
||||
|
||||
const renderingAttr = thumbnail ? 'thumbnailRendering' : 'imageRendering';
|
||||
|
||||
if (!instance[renderingAttr] || instance[renderingAttr] === 'wadouri' || !instance.wadorsuri) {
|
||||
let imageId = 'dicomweb:' + instance.wadouri;
|
||||
if (frame !== undefined) {
|
||||
imageId += '&frame=' + frame;
|
||||
}
|
||||
|
||||
return imageId;
|
||||
} else {
|
||||
return getWADORSImageId(instance, frame, thumbnail); // WADO-RS Retrieve Frame
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
function getWADORSImageUrl(instance, frame) {
|
||||
let wadorsuri = instance.wadorsuri;
|
||||
|
||||
if (!wadorsuri) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to sum 1 because WADO-RS frame number is 1-based
|
||||
frame = (frame || 0) + 1;
|
||||
|
||||
// Replaces /frame/1 by /frame/{frame}
|
||||
wadorsuri = wadorsuri.replace(/(%2Fframes%2F)(\d+)/, `$1${frame}`);
|
||||
|
||||
return wadorsuri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
|
||||
*
|
||||
* @param {object} instanceMetada metadata object (InstanceMetadata)
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
export function getWADORSImageId(instance, frame) {
|
||||
const uri = getWADORSImageUrl(instance, frame);
|
||||
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
return `wadors:${uri}`;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import { sopClassDictionary } from './sopClassDictionary';
|
||||
|
||||
/**
|
||||
* Checks whether dicom files with specified SOP Class UID have image data
|
||||
* @param {string} sopClassUid - SOP Class UID to be checked
|
||||
* @returns {boolean} - true if it has image data
|
||||
*/
|
||||
export function isImage(sopClassUid) {
|
||||
if (sopClassUid === sopClassDictionary.ComputedRadiographyImageStorage
|
||||
|| sopClassUid === sopClassDictionary.DigitalXRayImageStorageForPresentation
|
||||
|| sopClassUid === sopClassDictionary.DigitalXRayImageStorageForProcessing
|
||||
|| sopClassUid === sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation
|
||||
|| sopClassUid === sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing
|
||||
|| sopClassUid === sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation
|
||||
|| sopClassUid === sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing
|
||||
|| sopClassUid === sopClassDictionary.CTImageStorage
|
||||
|| sopClassUid === sopClassDictionary.EnhancedCTImageStorage
|
||||
|| sopClassUid === sopClassDictionary.LegacyConvertedEnhancedCTImageStorage
|
||||
|| sopClassUid === sopClassDictionary.UltrasoundMultiframeImageStorage
|
||||
|| sopClassUid === sopClassDictionary.MRImageStorage
|
||||
|| sopClassUid === sopClassDictionary.EnhancedMRImageStorage
|
||||
|| sopClassUid === sopClassDictionary.EnhancedMRColorImageStorage
|
||||
|| sopClassUid === sopClassDictionary.LegacyConvertedEnhancedMRImageStorage
|
||||
|| sopClassUid === sopClassDictionary.UltrasoundImageStorage
|
||||
|| sopClassUid === sopClassDictionary.SecondaryCaptureImageStorage
|
||||
|| sopClassUid === sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage
|
||||
|| sopClassUid === sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage
|
||||
|| sopClassUid === sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage
|
||||
|| sopClassUid === sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage
|
||||
|| sopClassUid === sopClassDictionary.XRayAngiographicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.EnhancedXAImageStorage
|
||||
|| sopClassUid === sopClassDictionary.XRayRadiofluoroscopicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.EnhancedXRFImageStorage
|
||||
|| sopClassUid === sopClassDictionary.XRay3DAngiographicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.XRay3DCraniofacialImageStorage
|
||||
|| sopClassUid === sopClassDictionary.BreastTomosynthesisImageStorage
|
||||
|| sopClassUid === sopClassDictionary.BreastProjectionXRayImageStorageForPresentation
|
||||
|| sopClassUid === sopClassDictionary.BreastProjectionXRayImageStorageForProcessing
|
||||
|| sopClassUid === sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation
|
||||
|| sopClassUid === sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing
|
||||
|| sopClassUid === sopClassDictionary.NuclearMedicineImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VLEndoscopicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VideoEndoscopicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VLMicroscopicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VideoMicroscopicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VLPhotographicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VideoPhotographicImageStorage
|
||||
|| sopClassUid === sopClassDictionary.OphthalmicPhotography8BitImageStorage
|
||||
|| sopClassUid === sopClassDictionary.OphthalmicPhotography16BitImageStorage
|
||||
|| sopClassUid === sopClassDictionary.OphthalmicTomographyImageStorage
|
||||
|| sopClassUid === sopClassDictionary.VLWholeSlideMicroscopyImageStorage
|
||||
|| sopClassUid === sopClassDictionary.PositronEmissionTomographyImageStorage
|
||||
|| sopClassUid === sopClassDictionary.EnhancedPETImageStorage
|
||||
|| sopClassUid === sopClassDictionary.LegacyConvertedEnhancedPETImageStorage
|
||||
|| sopClassUid === sopClassDictionary.RTImageStorage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// TODO: Deprecate since we have the same thing in dcmjs?
|
||||
export const sopClassDictionary = {
|
||||
ComputedRadiographyImageStorage: "1.2.840.10008.5.1.4.1.1.1",
|
||||
DigitalXRayImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.1.1",
|
||||
DigitalXRayImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.1.1.1",
|
||||
DigitalMammographyXRayImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.1.2",
|
||||
DigitalMammographyXRayImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.1.2.1",
|
||||
DigitalIntraOralXRayImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.1.3",
|
||||
DigitalIntraOralXRayImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.1.3.1",
|
||||
CTImageStorage: "1.2.840.10008.5.1.4.1.1.2",
|
||||
EnhancedCTImageStorage: "1.2.840.10008.5.1.4.1.1.2.1",
|
||||
LegacyConvertedEnhancedCTImageStorage: "1.2.840.10008.5.1.4.1.1.2.2",
|
||||
UltrasoundMultiframeImageStorage: "1.2.840.10008.5.1.4.1.1.3.1",
|
||||
MRImageStorage: "1.2.840.10008.5.1.4.1.1.4",
|
||||
EnhancedMRImageStorage: "1.2.840.10008.5.1.4.1.1.4.1",
|
||||
MRSpectroscopyStorage: "1.2.840.10008.5.1.4.1.1.4.2",
|
||||
EnhancedMRColorImageStorage: "1.2.840.10008.5.1.4.1.1.4.3",
|
||||
LegacyConvertedEnhancedMRImageStorage: "1.2.840.10008.5.1.4.1.1.4.4",
|
||||
UltrasoundImageStorage: "1.2.840.10008.5.1.4.1.1.6.1",
|
||||
EnhancedUSVolumeStorage: "1.2.840.10008.5.1.4.1.1.6.2",
|
||||
SecondaryCaptureImageStorage: "1.2.840.10008.5.1.4.1.1.7",
|
||||
MultiframeSingleBitSecondaryCaptureImageStorage: "1.2.840.10008.5.1.4.1.1.7.1",
|
||||
MultiframeGrayscaleByteSecondaryCaptureImageStorage: "1.2.840.10008.5.1.4.1.1.7.2",
|
||||
MultiframeGrayscaleWordSecondaryCaptureImageStorage: "1.2.840.10008.5.1.4.1.1.7.3",
|
||||
MultiframeTrueColorSecondaryCaptureImageStorage: "1.2.840.10008.5.1.4.1.1.7.4",
|
||||
Sop12LeadECGWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.1.1",
|
||||
GeneralECGWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.1.2",
|
||||
AmbulatoryECGWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.1.3",
|
||||
HemodynamicWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.2.1",
|
||||
CardiacElectrophysiologyWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.3.1",
|
||||
BasicVoiceAudioWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.4.1",
|
||||
GeneralAudioWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.4.2",
|
||||
ArterialPulseWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.5.1",
|
||||
RespiratoryWaveformStorage: "1.2.840.10008.5.1.4.1.1.9.6.1",
|
||||
GrayscaleSoftcopyPresentationStateStorage: "1.2.840.10008.5.1.4.1.1.11.1",
|
||||
ColorSoftcopyPresentationStateStorage: "1.2.840.10008.5.1.4.1.1.11.2",
|
||||
PseudoColorSoftcopyPresentationStateStorage: "1.2.840.10008.5.1.4.1.1.11.3",
|
||||
BlendingSoftcopyPresentationStateStorage: "1.2.840.10008.5.1.4.1.1.11.4",
|
||||
XAXRFGrayscaleSoftcopyPresentationStateStorage: "1.2.840.10008.5.1.4.1.1.11.5",
|
||||
XRayAngiographicImageStorage: "1.2.840.10008.5.1.4.1.1.12.1",
|
||||
EnhancedXAImageStorage: "1.2.840.10008.5.1.4.1.1.12.1.1",
|
||||
XRayRadiofluoroscopicImageStorage: "1.2.840.10008.5.1.4.1.1.12.2",
|
||||
EnhancedXRFImageStorage: "1.2.840.10008.5.1.4.1.1.12.2.1",
|
||||
XRay3DAngiographicImageStorage: "1.2.840.10008.5.1.4.1.1.13.1.1",
|
||||
XRay3DCraniofacialImageStorage: "1.2.840.10008.5.1.4.1.1.13.1.2",
|
||||
BreastTomosynthesisImageStorage: "1.2.840.10008.5.1.4.1.1.13.1.3",
|
||||
BreastProjectionXRayImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.13.1.4",
|
||||
BreastProjectionXRayImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.13.1.5",
|
||||
IntravascularOpticalCoherenceTomographyImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.14.1",
|
||||
IntravascularOpticalCoherenceTomographyImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.14.2",
|
||||
NuclearMedicineImageStorage: "1.2.840.10008.5.1.4.1.1.20",
|
||||
RawDataStorage: "1.2.840.10008.5.1.4.1.1.66",
|
||||
SpatialRegistrationStorage: "1.2.840.10008.5.1.4.1.1.66.1",
|
||||
SpatialFiducialsStorage: "1.2.840.10008.5.1.4.1.1.66.2",
|
||||
DeformableSpatialRegistrationStorage: "1.2.840.10008.5.1.4.1.1.66.3",
|
||||
SegmentationStorage: "1.2.840.10008.5.1.4.1.1.66.4",
|
||||
SurfaceSegmentationStorage: "1.2.840.10008.5.1.4.1.1.66.5",
|
||||
RealWorldValueMappingStorage: "1.2.840.10008.5.1.4.1.1.67",
|
||||
SurfaceScanMeshStorage: "1.2.840.10008.5.1.4.1.1.68.1",
|
||||
SurfaceScanPointCloudStorage: "1.2.840.10008.5.1.4.1.1.68.2",
|
||||
VLEndoscopicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.1",
|
||||
VideoEndoscopicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.1.1",
|
||||
VLMicroscopicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.2",
|
||||
VideoMicroscopicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.2.1",
|
||||
VLSlideCoordinatesMicroscopicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.3",
|
||||
VLPhotographicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.4",
|
||||
VideoPhotographicImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.4.1",
|
||||
OphthalmicPhotography8BitImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.5.1",
|
||||
OphthalmicPhotography16BitImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.5.2",
|
||||
StereometricRelationshipStorage: "1.2.840.10008.5.1.4.1.1.77.1.5.3",
|
||||
OphthalmicTomographyImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.5.4",
|
||||
VLWholeSlideMicroscopyImageStorage: "1.2.840.10008.5.1.4.1.1.77.1.6",
|
||||
LensometryMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.78.1",
|
||||
AutorefractionMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.78.2",
|
||||
KeratometryMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.78.3",
|
||||
SubjectiveRefractionMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.78.4",
|
||||
VisualAcuityMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.78.5",
|
||||
SpectaclePrescriptionReportStorage: "1.2.840.10008.5.1.4.1.1.78.6",
|
||||
OphthalmicAxialMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.78.7",
|
||||
IntraocularLensCalculationsStorage: "1.2.840.10008.5.1.4.1.1.78.8",
|
||||
MacularGridThicknessandVolumeReport: "1.2.840.10008.5.1.4.1.1.79.1",
|
||||
OphthalmicVisualFieldStaticPerimetryMeasurementsStorage: "1.2.840.10008.5.1.4.1.1.80.1",
|
||||
OphthalmicThicknessMapStorage: "1.2.840.10008.5.1.4.1.1.81.1",
|
||||
CornealTopographyMapStorage: "1.2.840.10008.5.1.4.1.1.82.1",
|
||||
BasicTextSR: "1.2.840.10008.5.1.4.1.1.88.11",
|
||||
EnhancedSR: "1.2.840.10008.5.1.4.1.1.88.22",
|
||||
ComprehensiveSR: "1.2.840.10008.5.1.4.1.1.88.33",
|
||||
Comprehensive3DSR: "1.2.840.10008.5.1.4.1.1.88.34",
|
||||
ProcedureLog: "1.2.840.10008.5.1.4.1.1.88.40",
|
||||
MammographyCADSR: "1.2.840.10008.5.1.4.1.1.88.50",
|
||||
KeyObjectSelection: "1.2.840.10008.5.1.4.1.1.88.59",
|
||||
ChestCADSR: "1.2.840.10008.5.1.4.1.1.88.65",
|
||||
XRayRadiationDoseSR: "1.2.840.10008.5.1.4.1.1.88.67",
|
||||
RadiopharmaceuticalRadiationDoseSR: "1.2.840.10008.5.1.4.1.1.88.68",
|
||||
ColonCADSR: "1.2.840.10008.5.1.4.1.1.88.69",
|
||||
ImplantationPlanSRDocumentStorage: "1.2.840.10008.5.1.4.1.1.88.70",
|
||||
EncapsulatedPDFStorage: "1.2.840.10008.5.1.4.1.1.104.1",
|
||||
EncapsulatedCDAStorage: "1.2.840.10008.5.1.4.1.1.104.2",
|
||||
PositronEmissionTomographyImageStorage: "1.2.840.10008.5.1.4.1.1.128",
|
||||
EnhancedPETImageStorage: "1.2.840.10008.5.1.4.1.1.130",
|
||||
LegacyConvertedEnhancedPETImageStorage: "1.2.840.10008.5.1.4.1.1.128.1",
|
||||
BasicStructuredDisplayStorage: "1.2.840.10008.5.1.4.1.1.131",
|
||||
RTImageStorage: "1.2.840.10008.5.1.4.1.1.481.1",
|
||||
RTDoseStorage: "1.2.840.10008.5.1.4.1.1.481.2",
|
||||
RTStructureSetStorage: "1.2.840.10008.5.1.4.1.1.481.3",
|
||||
RTBeamsTreatmentRecordStorage: "1.2.840.10008.5.1.4.1.1.481.4",
|
||||
RTPlanStorage: "1.2.840.10008.5.1.4.1.1.481.5",
|
||||
RTBrachyTreatmentRecordStorage: "1.2.840.10008.5.1.4.1.1.481.6",
|
||||
RTTreatmentSummaryRecordStorage: "1.2.840.10008.5.1.4.1.1.481.7",
|
||||
RTIonPlanStorage: "1.2.840.10008.5.1.4.1.1.481.8",
|
||||
RTIonBeamsTreatmentRecordStorage: "1.2.840.10008.5.1.4.1.1.481.9",
|
||||
RTBeamsDeliveryInstructionStorage: "1.2.840.10008.5.1.4.34.7",
|
||||
GenericImplantTemplateStorage: "1.2.840.10008.5.1.4.43.1",
|
||||
ImplantAssemblyTemplateStorage: "1.2.840.10008.5.1.4.44.1",
|
||||
ImplantTemplateGroupStorage: "1.2.840.10008.5.1.4.45.1"
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createStacks } from './createStacks';
|
||||
|
||||
const getDisplaySets = (studyMetadata, seriesNumber, iteratorFunction) => {
|
||||
const iteratorFn = typeof iteratorFunction !== 'function' ? createStacks : iteratorFunction;
|
||||
|
||||
return iteratorFn(studyMetadata, seriesNumber);
|
||||
};
|
||||
|
||||
const sortingManager = {
|
||||
getDisplaySets
|
||||
};
|
||||
|
||||
export { sortingManager };
|
||||
@@ -0,0 +1,88 @@
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import OHIF from 'ohif-core';
|
||||
import { getWADORSImageId } from './getWADORSImageId';
|
||||
|
||||
function getRadiopharmaceuticalInfoMetaData(instance) {
|
||||
const radiopharmaceuticalInfo = instance.radiopharmaceuticalInfo;
|
||||
|
||||
if ((instance.modality !== 'PT') || !radiopharmaceuticalInfo) {
|
||||
return;
|
||||
}
|
||||
const { WadoRsMetaDataBuilder } = OHIF.metadata;
|
||||
|
||||
return new WadoRsMetaDataBuilder()
|
||||
.addTag('00181072', radiopharmaceuticalInfo.radiopharmaceuticalStartTime)
|
||||
.addTag('00181074', radiopharmaceuticalInfo.radionuclideTotalDose)
|
||||
.addTag('00181075', radiopharmaceuticalInfo.radionuclideHalfLife)
|
||||
.toJSON();
|
||||
}
|
||||
|
||||
const getWadoRsInstanceMetaData = (study, series, instance) => {
|
||||
const { WadoRsMetaDataBuilder } = OHIF.metadata;
|
||||
return new WadoRsMetaDataBuilder()
|
||||
.addTag('00080016', instance.sopClassUid)
|
||||
.addTag('00080018', instance.sopInstanceUid)
|
||||
.addTag('00080021', series.seriesDate)
|
||||
.addTag('00080031', series.seriesTime)
|
||||
.addTag('00080060', instance.modality)
|
||||
.addTag('00101010', study.patientAge)
|
||||
.addTag('00101020', study.patientSize)
|
||||
.addTag('00101030', study.patientWeight)
|
||||
.addTag('00180050', instance.sliceThickness)
|
||||
.addTag('0020000e', series.seriesInstanceUid)
|
||||
.addTag('00200011', series.seriesNumber)
|
||||
.addTag('0020000d', study.studyInstanceUid)
|
||||
.addTag('00200013', instance.instanceNumber)
|
||||
.addTag('00200032', instance.imagePositionPatient, true)
|
||||
.addTag('00200037', instance.imageOrientationPatient, true)
|
||||
.addTag('00200052', instance.frameOfReferenceUID)
|
||||
.addTag('00201041', instance.sliceLocation)
|
||||
.addTag('00280002', instance.samplesPerPixel)
|
||||
.addTag('00280004', instance.photometricInterpretation)
|
||||
.addTag('00280006', instance.planarConfiguration)
|
||||
.addTag('00280010', instance.rows)
|
||||
.addTag('00280011', instance.columns)
|
||||
.addTag('00280030', instance.pixelSpacing, true)
|
||||
.addTag('00280034', instance.pixelAspectRatio, true)
|
||||
.addTag('00280100', instance.bitsAllocated)
|
||||
.addTag('00280101', instance.bitsStored)
|
||||
.addTag('00280102', instance.highBit)
|
||||
.addTag('00280103', instance.pixelRepresentation)
|
||||
.addTag('00280106', instance.smallestPixelValue)
|
||||
.addTag('00280107', instance.largestPixelValue)
|
||||
.addTag('00281050', instance.windowCenter, true)
|
||||
.addTag('00281051', instance.windowWidth, true)
|
||||
.addTag('00281052', instance.rescaleIntercept)
|
||||
.addTag('00281053', instance.rescaleSlope)
|
||||
.addTag('00281054', instance.rescaleType)
|
||||
.addTag('00281101', instance.redPaletteColorLookupTableDescriptor)
|
||||
.addTag('00281102', instance.greenPaletteColorLookupTableDescriptor)
|
||||
.addTag('00281103', instance.bluePaletteColorLookupTableDescriptor)
|
||||
.addTag('00281201', instance.redPaletteColorLookupTableData)
|
||||
.addTag('00281202', instance.greenPaletteColorLookupTableData)
|
||||
.addTag('00281203', instance.bluePaletteColorLookupTableData)
|
||||
.addTag('00540016', getRadiopharmaceuticalInfoMetaData(instance))
|
||||
.toJSON();
|
||||
};
|
||||
|
||||
export function updateMetaDataManager(study) {
|
||||
study.seriesList.forEach(series => {
|
||||
series.instances.forEach(instance => {
|
||||
// Cache just images that are going to be loaded via WADO-RS
|
||||
if ((instance.imageRendering !== 'wadors') && (instance.thumbnailRendering !== 'wadors')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metaData = getWadoRsInstanceMetaData(study, series, instance);
|
||||
const numberOfFrames = instance.numberOfFrames || 1;
|
||||
|
||||
// We can share the same metaData with all frames because it doesn't have
|
||||
// any frame specific data, such as frameNumber, pixelData, offset, etc.
|
||||
// WADO-RS frame number is 1-based
|
||||
for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) {
|
||||
const imageId = getWADORSImageId(instance, frameNumber);
|
||||
cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export default '7b66dec163174eae2f6aca133f740529c8b4d08c';
|
||||
export default 'fc5bd73c1ed247b233bfc7ebed1fb00c990f98d5';
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import OHIF from 'ohif-core';
|
||||
import { CineDialog } from 'react-viewerbase';
|
||||
import FlexboxLayout from '../flexboxLayout/flexboxLayout.js';
|
||||
|
||||
@@ -48,7 +48,7 @@ OHIF.viewer.cine = {
|
||||
loop: true
|
||||
};
|
||||
|
||||
const viewportUtils = OHIF.viewerbase.viewportUtils;
|
||||
/*const viewportUtils = OHIF.viewerbase.viewportUtils;
|
||||
|
||||
OHIF.viewer.functionList = {
|
||||
toggleCineDialog: viewportUtils.toggleCineDialog,
|
||||
@@ -56,7 +56,7 @@ OHIF.viewer.functionList = {
|
||||
clearTools: viewportUtils.clearTools,
|
||||
resetViewport: viewportUtils.resetViewport,
|
||||
invert: viewportUtils.invert
|
||||
};
|
||||
};*/
|
||||
|
||||
// Create the synchronizer used to update reference lines
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('cornerstonenewimage', cornerstoneTools.updateImageSynchronizer);
|
||||
|
||||
Reference in new issue
Block a user