Support for additional "tracking" mode
This commit is contained in:
parent
fae21bed9c
commit
d557acbb40
8
extensions/measurement-tracking/.webpack/webpack.dev.js
Normal file
8
extensions/measurement-tracking/.webpack/webpack.dev.js
Normal file
@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
};
|
||||
44
extensions/measurement-tracking/.webpack/webpack.prod.js
Normal file
44
extensions/measurement-tracking/.webpack/webpack.prod.js
Normal file
@ -0,0 +1,44 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
const ROOT_DIR = path.join(__dirname, './..');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
devtool: 'source-map',
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: ROOT_DIR,
|
||||
library: 'OHIFExtDefault',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
40
extensions/measurement-tracking/package.json
Normal file
40
extensions/measurement-tracking/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@ohif/extension-measurement-tracking",
|
||||
"version": "0.0.1",
|
||||
"description": "Tracking features and functionality for basic image viewing",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.19.1"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"dev:dicom-pdf": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"webpack": "^4.0.0",
|
||||
"dcmjs": "^0.12.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
MeasurementsPanel,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
IconButton,
|
||||
} from '@ohif/ui';
|
||||
|
||||
export default function MeasurementTable({ servicesManager, commandsManager }) {
|
||||
const { MeasurementService } = servicesManager.services;
|
||||
|
||||
console.log('MeasurementTable rendering!!!!!!!!!!!!!');
|
||||
|
||||
const actionButtons = (
|
||||
<React.Fragment>
|
||||
<ButtonGroup onClick={() => alert('Export')}>
|
||||
<Button
|
||||
className="px-2 py-2 text-base text-white bg-black border-primary-main"
|
||||
size="initial"
|
||||
color="inherit"
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<IconButton
|
||||
className="px-2 text-white bg-black border-primary-main"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
>
|
||||
<Icon name="arrow-down" />
|
||||
</IconButton>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
className="px-2 py-2 ml-2 text-base text-white bg-black border border-primary-main"
|
||||
variant="outlined"
|
||||
size="initial"
|
||||
color="inherit"
|
||||
onClick={() => alert('Create Report')}
|
||||
>
|
||||
Create Report
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const descriptionData = {
|
||||
date: '07-Sep-2010',
|
||||
modality: 'CT',
|
||||
description: 'CHEST/ABD/PELVIS W CONTRAST',
|
||||
};
|
||||
|
||||
const activeMeasurementItem = 0;
|
||||
|
||||
const measurementTableData = {
|
||||
title: 'Measurements',
|
||||
amount: 10,
|
||||
data: new Array(10).fill({}).map((el, i) => ({
|
||||
id: i + 1,
|
||||
label: 'Label short description',
|
||||
displayText: '24.0 x 24.0 mm (S:4, I:22)',
|
||||
isActive: activeMeasurementItem === i + 1,
|
||||
})),
|
||||
// onClick: id => setActiveMeasurementItem(s => (s === id ? null : id)),
|
||||
onClick: () => {},
|
||||
onEdit: id => alert(`Edit: ${id}`),
|
||||
};
|
||||
|
||||
return (
|
||||
<MeasurementsPanel
|
||||
descriptionData={descriptionData}
|
||||
measurementTableData={measurementTableData}
|
||||
actionButtons={actionButtons}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,284 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StudyBrowser, useImageViewer } from '@ohif/ui';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
*/
|
||||
function PanelStudyBrowser({
|
||||
DisplaySetService,
|
||||
getImageSrc,
|
||||
getStudiesForPatientByStudyInstanceUID,
|
||||
requestDisplaySetCreationForStudy,
|
||||
dataSource,
|
||||
}) {
|
||||
// Normally you nest the components so the tree isn't so deep, and the data
|
||||
// doesn't have to have such an intense shape. This works well enough for now.
|
||||
// Tabs --> Studies --> DisplaySets --> Thumbnails
|
||||
const [{ StudyInstanceUIDs }, dispatch] = useImageViewer();
|
||||
const [activeTabName, setActiveTabName] = useState('primary');
|
||||
const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([]);
|
||||
const [studyDisplayList, setStudyDisplayList] = useState([]);
|
||||
const [displaySets, setDisplaySets] = useState([]);
|
||||
const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({});
|
||||
|
||||
// ~~ studyDisplayList
|
||||
useEffect(() => {
|
||||
// Fetch all studies for the patient in each primary study
|
||||
async function fetchStudiesForPatient(StudyInstanceUID) {
|
||||
const qidoStudiesForPatient =
|
||||
(await getStudiesForPatientByStudyInstanceUID(StudyInstanceUID)) || [];
|
||||
|
||||
// TODO: This should be "naturalized DICOM JSON" studies
|
||||
const mappedStudies = _mapDataSourceStudies(qidoStudiesForPatient);
|
||||
const actuallyMappedStudies = mappedStudies.map(qidoStudy => {
|
||||
return {
|
||||
studyInstanceUid: qidoStudy.StudyInstanceUID,
|
||||
date: qidoStudy.StudyDate,
|
||||
description: qidoStudy.StudyDescription,
|
||||
modalities: qidoStudy.ModalitiesInStudy,
|
||||
numInstances: qidoStudy.NumInstances,
|
||||
// displaySets: []
|
||||
};
|
||||
});
|
||||
|
||||
setStudyDisplayList(actuallyMappedStudies);
|
||||
}
|
||||
|
||||
StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid));
|
||||
}, [StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID]);
|
||||
|
||||
// ~~ Initial Thumbnails
|
||||
useEffect(() => {
|
||||
const currentDisplaySets = DisplaySetService.activeDisplaySets;
|
||||
currentDisplaySets.forEach(async dSet => {
|
||||
const newImageSrcEntry = {};
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
dSet.displaySetInstanceUID
|
||||
);
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
const imageId = imageIds[Math.floor(imageIds.length / 2)];
|
||||
|
||||
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
|
||||
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
|
||||
setThumbnailImageSrcMap(prevState => {
|
||||
return { ...prevState, ...newImageSrcEntry };
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
// ~~ displaySets
|
||||
useEffect(() => {
|
||||
// TODO: Are we sure `activeDisplaySets` will always be accurate?
|
||||
const currentDisplaySets = DisplaySetService.activeDisplaySets;
|
||||
const mappedDisplaySets = _mapDisplaySets(
|
||||
currentDisplaySets,
|
||||
thumbnailImageSrcMap
|
||||
);
|
||||
|
||||
setDisplaySets(mappedDisplaySets);
|
||||
}, [thumbnailImageSrcMap]);
|
||||
|
||||
// ~~ subscriptions --> displaySets
|
||||
useEffect(() => {
|
||||
// DISPLAY_SETS_ADDED returns an array of DisplaySets that were added
|
||||
const SubscriptionDisplaySetsAdded = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
newDisplaySets => {
|
||||
newDisplaySets.forEach(async dSet => {
|
||||
const newImageSrcEntry = {};
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
dSet.displaySetInstanceUID
|
||||
);
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
const imageId = imageIds[Math.floor(imageIds.length / 2)];
|
||||
|
||||
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
|
||||
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(
|
||||
imageId
|
||||
);
|
||||
setThumbnailImageSrcMap(prevState => {
|
||||
return { ...prevState, ...newImageSrcEntry };
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Will this always hold _all_ the displaySets we care about?
|
||||
// DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets`
|
||||
const SubscriptionDisplaySetsChanged = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED,
|
||||
changedDisplaySets => {
|
||||
const mappedDisplaySets = _mapDisplaySets(
|
||||
changedDisplaySets,
|
||||
thumbnailImageSrcMap
|
||||
);
|
||||
|
||||
setDisplaySets(mappedDisplaySets);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
SubscriptionDisplaySetsAdded.unsubscribe();
|
||||
SubscriptionDisplaySetsChanged.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tabs = _createStudyBrowserTabs(
|
||||
StudyInstanceUIDs,
|
||||
studyDisplayList,
|
||||
displaySets
|
||||
);
|
||||
|
||||
// TODO: Should not fire this on "close"
|
||||
function _handleStudyClick(StudyInstanceUID) {
|
||||
const shouldCollapseStudy = expandedStudyInstanceUIDs.includes(
|
||||
StudyInstanceUID
|
||||
);
|
||||
const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
? [...expandedStudyInstanceUIDs.filter(stdyUid => stdyUid !== StudyInstanceUID)]
|
||||
: [...expandedStudyInstanceUIDs, StudyInstanceUID];
|
||||
|
||||
setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs);
|
||||
|
||||
if (!shouldCollapseStudy) {
|
||||
requestDisplaySetCreationForStudy(DisplaySetService, StudyInstanceUID);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StudyBrowser
|
||||
tabs={tabs}
|
||||
activeTabName={activeTabName}
|
||||
expandedStudyInstanceUIDs={expandedStudyInstanceUIDs}
|
||||
onClickStudy={_handleStudyClick}
|
||||
onClickTab={clickedTabName => {
|
||||
setActiveTabName(clickedTabName);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
PanelStudyBrowser.propTypes = {
|
||||
DisplaySetService: PropTypes.shape({
|
||||
EVENTS: PropTypes.object.isRequired,
|
||||
activeDisplaySets: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
getDisplaySetByUID: PropTypes.func.isRequired,
|
||||
hasDisplaySetsForStudy: PropTypes.func.isRequired,
|
||||
subscribe: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
dataSource: PropTypes.shape({
|
||||
getImageIdsForDisplaySet: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
getImageSrc: PropTypes.func.isRequired,
|
||||
getStudiesForPatientByStudyInstanceUID: PropTypes.func.isRequired,
|
||||
requestDisplaySetCreationForStudy: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default PanelStudyBrowser;
|
||||
|
||||
/**
|
||||
* Maps from the DataSource's format to a naturalized object
|
||||
*
|
||||
* @param {*} studies
|
||||
*/
|
||||
function _mapDataSourceStudies(studies) {
|
||||
return studies.map(study => {
|
||||
// TODO: Why does the data source return in this format?
|
||||
return {
|
||||
AccessionNumber: study.accession,
|
||||
StudyDate: study.date,
|
||||
StudyDescription: study.description,
|
||||
NumInstances: study.instances,
|
||||
ModalitiesInStudy: study.modalities,
|
||||
PatientID: study.mrn,
|
||||
PatientName: study.patientName,
|
||||
StudyInstanceUID: study.studyInstanceUid,
|
||||
StudyTime: study.time,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function _mapDisplaySets(displaySets, thumbnailImageSrcMap) {
|
||||
return displaySets.map(ds => {
|
||||
const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID];
|
||||
return {
|
||||
displaySetInstanceUID: ds.displaySetInstanceUID,
|
||||
description: ds.SeriesDescription,
|
||||
seriesNumber: ds.SeriesNumber,
|
||||
modality: ds.Modality,
|
||||
date: ds.SeriesDate,
|
||||
numInstances: ds.numImageFrames,
|
||||
StudyInstanceUID: ds.StudyInstanceUID,
|
||||
componentType: 'thumbnail', // 'thumbnailNoImage' || 'thumbnailTracked' // TODO: PUT THIS SOMEWHERE ELSE
|
||||
imageSrc,
|
||||
dragData: {
|
||||
type: 'displayset',
|
||||
displaySetInstanceUID: ds.displaySetInstanceUID,
|
||||
// .. Any other data to pass
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string[]} primaryStudyInstanceUIDs
|
||||
* @param {object[]} studyDisplayList
|
||||
* @param {string} studyDisplayList.studyInstanceUid
|
||||
* @param {string} studyDisplayList.date
|
||||
* @param {string} studyDisplayList.description
|
||||
* @param {string} studyDisplayList.modalities
|
||||
* @param {number} studyDisplayList.numInstances
|
||||
* @param {object[]} displaySets
|
||||
* @returns tabs - The prop object expected by the StudyBrowser component
|
||||
*/
|
||||
function _createStudyBrowserTabs(
|
||||
primaryStudyInstanceUIDs,
|
||||
studyDisplayList,
|
||||
displaySets
|
||||
) {
|
||||
const primaryStudies = [];
|
||||
const recentStudies = [];
|
||||
const allStudies = [];
|
||||
|
||||
studyDisplayList.forEach(study => {
|
||||
const displaySetsForStudy = displaySets.filter(
|
||||
ds => ds.StudyInstanceUID === study.studyInstanceUid
|
||||
);
|
||||
const tabStudy = Object.assign({}, study, {
|
||||
displaySets: displaySetsForStudy,
|
||||
});
|
||||
|
||||
if (primaryStudyInstanceUIDs.includes(study.studyInstanceUid)) {
|
||||
primaryStudies.push(tabStudy);
|
||||
} else {
|
||||
// TODO: Filter allStudies to dates within one year of current date
|
||||
recentStudies.push(tabStudy);
|
||||
allStudies.push(tabStudy);
|
||||
}
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: 'primary',
|
||||
label: 'Primary',
|
||||
studies: primaryStudies,
|
||||
},
|
||||
{
|
||||
name: 'recent',
|
||||
label: 'Recent',
|
||||
studies: recentStudies,
|
||||
},
|
||||
{
|
||||
name: 'all',
|
||||
label: 'All',
|
||||
studies: allStudies,
|
||||
},
|
||||
];
|
||||
|
||||
return tabs;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @param {*} cornerstone
|
||||
* @param {*} imageId
|
||||
*/
|
||||
function getImageSrcFromImageId(cornerstone, imageId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cornerstone
|
||||
.loadAndCacheImage(imageId)
|
||||
.then(image => {
|
||||
const canvas = document.createElement('canvas');
|
||||
cornerstone.renderToCanvas(canvas, image);
|
||||
|
||||
resolve(canvas.toDataURL());
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
export default getImageSrcFromImageId;
|
||||
@ -0,0 +1,23 @@
|
||||
async function getStudiesForPatientByStudyInstanceUID(
|
||||
dataSource,
|
||||
StudyInstanceUID
|
||||
) {
|
||||
// TODO: The `DicomMetadataStore` should short-circuit both of these requests
|
||||
// Data _could_ be here from route query, or if using JSON data source
|
||||
// We could also force this to "await" these values being available in the DICOMStore?
|
||||
// Kind of like promise fulfillment in cornerstone-wado-image-loader when there are multiple
|
||||
// outgoing requests for the same data
|
||||
const getStudyResult = await dataSource.query.studies.search({
|
||||
studyInstanceUid: StudyInstanceUID,
|
||||
});
|
||||
|
||||
// TODO: To Erik's point, the data source likely shouldn't deviate from
|
||||
// Naturalized DICOM JSON when returning. It makes things like this awkward (mrn)
|
||||
if (getStudyResult && getStudyResult.length && getStudyResult[0].mrn) {
|
||||
return dataSource.query.studies.search({
|
||||
patientId: getStudyResult[0].mrn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default getStudiesForPatientByStudyInstanceUID;
|
||||
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
//
|
||||
import PanelStudyBrowser from './PanelStudyBrowser';
|
||||
import getImageSrcFromImageId from './getImageSrcFromImageId';
|
||||
import getStudiesForPatientByStudyInstanceUID from './getStudiesForPatientByStudyInstanceUID';
|
||||
import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy';
|
||||
|
||||
/**
|
||||
* Wraps the PanelStudyBrowser and provides features afforded by managers/services
|
||||
*
|
||||
* @param {object} params
|
||||
* @param {object} commandsManager
|
||||
* @param {object} extensionManager
|
||||
*/
|
||||
function WrappedPanelStudyBrowser({
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
}) {
|
||||
// TODO: This should be made available a different way; route should have
|
||||
// already determined our datasource
|
||||
const dataSource = extensionManager.getDataSources('dicomweb')[0];
|
||||
const _getStudiesForPatientByStudyInstanceUID = getStudiesForPatientByStudyInstanceUID.bind(
|
||||
null,
|
||||
dataSource
|
||||
);
|
||||
const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn(
|
||||
commandsManager.getCommand.bind(commandsManager)
|
||||
);
|
||||
const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind(
|
||||
null,
|
||||
dataSource
|
||||
);
|
||||
|
||||
return (
|
||||
<PanelStudyBrowser
|
||||
DisplaySetService={servicesManager.services.DisplaySetService}
|
||||
dataSource={dataSource}
|
||||
getImageSrc={_getImageSrcFromImageId}
|
||||
getStudiesForPatientByStudyInstanceUID={
|
||||
_getStudiesForPatientByStudyInstanceUID
|
||||
}
|
||||
requestDisplaySetCreationForStudy={_requestDisplaySetCreationForStudy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs cornerstone library reference using a dependent command from
|
||||
* the @ohif/extension-cornerstone extension. Then creates a helper function
|
||||
* that can take an imageId and return an image src.
|
||||
*
|
||||
* @param {func} getCommand - CommandManager's getCommand method
|
||||
* @returns {func} getImageSrcFromImageId - A utility function powered by
|
||||
* cornerstone
|
||||
*/
|
||||
function _createGetImageSrcFromImageIdFn(getCommand) {
|
||||
try {
|
||||
const command = getCommand('getCornerstoneLibraries', 'VIEWER');
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
const { cornerstone } = command.commandFn();
|
||||
|
||||
return getImageSrcFromImageId.bind(null, cornerstone);
|
||||
} catch (ex) {
|
||||
throw new Error('Required command not found');
|
||||
}
|
||||
}
|
||||
|
||||
WrappedPanelStudyBrowser.propTypes = {
|
||||
commandsManager: PropTypes.object.isRequired,
|
||||
extensionManager: PropTypes.object.isRequired,
|
||||
servicesManager: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default WrappedPanelStudyBrowser;
|
||||
@ -0,0 +1,14 @@
|
||||
function requestDisplaySetCreationForStudy(
|
||||
dataSource,
|
||||
DisplaySetService,
|
||||
StudyInstanceUID
|
||||
) {
|
||||
// TODO: is this already short-circuited by the map of Retrieve promises?
|
||||
if (DisplaySetService.hasDisplaySetsForStudy(StudyInstanceUID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource.retrieveSeriesMetadata({ StudyInstanceUID });
|
||||
}
|
||||
|
||||
export default requestDisplaySetCreationForStudy;
|
||||
4
extensions/measurement-tracking/src/Panels/index.js
Normal file
4
extensions/measurement-tracking/src/Panels/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import PanelStudyBrowserTracking from './PanelStudyBrowserTracking';
|
||||
import PanelMeasurementTableTracking from './PanelMeasurementTableTracking';
|
||||
|
||||
export { PanelMeasurementTableTracking, PanelStudyBrowserTracking };
|
||||
@ -0,0 +1,193 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import CornerstoneViewport from 'react-cornerstone-viewport';
|
||||
import OHIF from '@ohif/core';
|
||||
import debounce from 'lodash.debounce';
|
||||
import throttle from 'lodash.throttle';
|
||||
|
||||
|
||||
// const cine = viewportSpecificData.cine;
|
||||
|
||||
// isPlaying = cine.isPlaying === true;
|
||||
// frameRate = cine.cineFrameRate || frameRate;
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
class OHIFCornerstoneViewport extends Component {
|
||||
state = {
|
||||
viewportData: null,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
customProps: {},
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
displaySet: PropTypes.object,
|
||||
viewportIndex: PropTypes.number,
|
||||
dataSource: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
customProps: PropTypes.object,
|
||||
};
|
||||
|
||||
static name = 'OHIFCornerstoneViewport';
|
||||
|
||||
static init() {
|
||||
console.log('OHIFCornerstoneViewport init()');
|
||||
}
|
||||
|
||||
// TODO: Still needed? Better way than import `OHIF` and destructure?
|
||||
// Why is this managed by `core`?
|
||||
static destroy() {
|
||||
console.log('OHIFCornerstoneViewport destroy()');
|
||||
StackManager.clearStacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the CornerstoneTools Stack for the specified display set.
|
||||
*
|
||||
* @param {Object} displaySet
|
||||
* @param {Object} dataSource
|
||||
* @return {Object} CornerstoneTools Stack
|
||||
*/
|
||||
static getCornerstoneStack(displaySet, dataSource) {
|
||||
const { frameIndex } = displaySet;
|
||||
|
||||
// Get stack from Stack Manager
|
||||
const storedStack = StackManager.findOrCreateStack(displaySet, dataSource);
|
||||
|
||||
// Clone the stack here so we don't mutate it
|
||||
const stack = Object.assign({}, storedStack);
|
||||
|
||||
stack.currentImageIdIndex = frameIndex;
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
getViewportData = async displaySet => {
|
||||
let viewportData;
|
||||
|
||||
const { dataSource } = this.props;
|
||||
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
displaySet,
|
||||
dataSource
|
||||
);
|
||||
|
||||
viewportData = {
|
||||
StudyInstanceUID: displaySet.StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
|
||||
return viewportData;
|
||||
};
|
||||
|
||||
setStateFromProps() {
|
||||
const { displaySet } = this.props;
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
sopClassUids,
|
||||
} = displaySet;
|
||||
|
||||
if (!StudyInstanceUID || !displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sopClassUids && sopClassUids.length > 1) {
|
||||
console.warn(
|
||||
'More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
this.getViewportData(displaySet).then(viewportData => {
|
||||
this.setState({
|
||||
viewportData,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setStateFromProps();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { displaySet } = this.props;
|
||||
const prevDisplaySet = prevProps.displaySet;
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
this.setStateFromProps();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let childrenWithProps = null;
|
||||
|
||||
if (!this.state.viewportData) {
|
||||
return null;
|
||||
}
|
||||
const { viewportIndex } = this.props;
|
||||
const {
|
||||
imageIds,
|
||||
currentImageIdIndex,
|
||||
// If this comes from the instance, would be a better default
|
||||
// `FrameTime` in the instance
|
||||
// frameRate = 0,
|
||||
} = this.state.viewportData.stack;
|
||||
|
||||
// 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 (
|
||||
child &&
|
||||
React.cloneElement(child, {
|
||||
viewportIndex: this.props.viewportIndex,
|
||||
key: index,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const debouncedNewImageHandler = debounce(
|
||||
({ currentImageIdIndex, sopInstanceUid }) => {
|
||||
const { displaySet } = this.props;
|
||||
const { StudyInstanceUID } = displaySet;
|
||||
if (currentImageIdIndex > 0) {
|
||||
this.props.onNewImage({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID: sopInstanceUid,
|
||||
frameIndex: currentImageIdIndex,
|
||||
activeViewportIndex: viewportIndex,
|
||||
});
|
||||
}
|
||||
},
|
||||
700
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CornerstoneViewport
|
||||
viewportIndex={viewportIndex}
|
||||
imageIds={imageIds}
|
||||
imageIdIndex={currentImageIdIndex}
|
||||
onNewImage={debouncedNewImageHandler}
|
||||
// TODO: ViewportGrid Context?
|
||||
isActive={true} // todo
|
||||
isStackPrefetchEnabled={true} // todo
|
||||
isPlaying={false}
|
||||
frameRate={24}
|
||||
/>
|
||||
{childrenWithProps}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OHIFCornerstoneViewport;
|
||||
64
extensions/measurement-tracking/src/getCommandsModule.js
Normal file
64
extensions/measurement-tracking/src/getCommandsModule.js
Normal file
@ -0,0 +1,64 @@
|
||||
// SEE:
|
||||
// https://github.com/OHIF/Viewers/blob/b58aa4575ab72fe3f493cc5a4261b4f8256516ab/platform/viewer/src/appExtensions/MeasurementsPanel/index.js#L18-L49
|
||||
import React from 'react';
|
||||
import { useViewportGrid } from '@ohif/ui';
|
||||
|
||||
function getCommandsModule({ servicesManager }) {
|
||||
const { UIDialogService } = servicesManager.services;
|
||||
|
||||
const definitions = {
|
||||
toggleLayoutSelectionDialog: {
|
||||
commandFn: () => {
|
||||
if (!UIDialogService) {
|
||||
window.alert('Unable to show dialog; no UI Dialog Service available.');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: use SimpleDialog component
|
||||
// TODO: update position on window resize
|
||||
// TODO: Expand service API to check if dialog w/ ID is already open
|
||||
// TODO: Import and call `useViewportGrid`
|
||||
UIDialogService.dismiss({ id: 'layoutSelection' });
|
||||
UIDialogService.create({
|
||||
id: 'layoutSelection',
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
showOverlay: true,
|
||||
content: Test,
|
||||
});
|
||||
|
||||
},
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
context: 'VIEWER',
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
definitions,
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
}
|
||||
|
||||
function Test() {
|
||||
const [
|
||||
{ numCols, numRows, activeViewportIndex, viewports },
|
||||
dispatch,
|
||||
] = useViewportGrid();
|
||||
|
||||
return <div
|
||||
onClick={() => {
|
||||
dispatch({ type: '', payload: {
|
||||
numCols: 3,
|
||||
numRows: 3,
|
||||
activeViewportIndex: 0,
|
||||
viewports: [],
|
||||
}})
|
||||
}}
|
||||
style={{ color: 'white' }}
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
}
|
||||
|
||||
export default getCommandsModule;
|
||||
38
extensions/measurement-tracking/src/getContextModule.js
Normal file
38
extensions/measurement-tracking/src/getContextModule.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ViewModelContext } from '@ohif/core';
|
||||
|
||||
const HelloWorldContext = React.createContext({
|
||||
message: 'HelloWorldContextTesting',
|
||||
setMessage: () => {},
|
||||
});
|
||||
|
||||
HelloWorldContext.displayName = 'HelloWorldContext';
|
||||
|
||||
function HelloWorldContextProvider({ children }) {
|
||||
const [message, setMessage] = useState('HelloWorldContextTesting');
|
||||
|
||||
return (
|
||||
<HelloWorldContext.Provider
|
||||
value={{
|
||||
message,
|
||||
setMessage,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</HelloWorldContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function getContextModule() {
|
||||
return [
|
||||
{
|
||||
name: 'HelloWorldContext',
|
||||
context: HelloWorldContext,
|
||||
provider: HelloWorldContextProvider,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export { HelloWorldContext };
|
||||
|
||||
export default getContextModule;
|
||||
41
extensions/measurement-tracking/src/getPanelModule.js
Normal file
41
extensions/measurement-tracking/src/getPanelModule.js
Normal file
@ -0,0 +1,41 @@
|
||||
import {
|
||||
PanelStudyBrowserTracking,
|
||||
PanelMeasurementTableTracking,
|
||||
} from './Panels';
|
||||
|
||||
// TODO:
|
||||
// - No loading UI exists yet
|
||||
// - cancel promises when component is destroyed
|
||||
// - show errors in UI for thumbnails if promise fails
|
||||
function getPanelModule({
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
}) {
|
||||
return [
|
||||
{
|
||||
name: 'seriesList',
|
||||
iconName: 'group-layers',
|
||||
iconLabel: 'Studies',
|
||||
label: 'Studies',
|
||||
component: PanelStudyBrowserTracking.bind(null, {
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'measure',
|
||||
iconName: 'list-bullets',
|
||||
iconLabel: 'Measure',
|
||||
label: 'Measurements',
|
||||
component: PanelMeasurementTableTracking.bind(null, {
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default getPanelModule;
|
||||
31
extensions/measurement-tracking/src/getViewportModule.js
Normal file
31
extensions/measurement-tracking/src/getViewportModule.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./Viewports/OHIFCornerstoneViewport');
|
||||
});
|
||||
|
||||
const OHIFCornerstoneViewport = props => {
|
||||
return (
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<Component {...props} />
|
||||
</React.Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
function getViewportModule({ commandsManager }) {
|
||||
const ExtendedOHIFCornerstoneViewport = props => {
|
||||
const onNewImageHandler = jumpData => {
|
||||
alert('here we are!')
|
||||
commandsManager.runCommand('jumpToImage', jumpData);
|
||||
};
|
||||
return (
|
||||
<OHIFCornerstoneViewport {...props} onNewImage={onNewImageHandler} />
|
||||
);
|
||||
};
|
||||
|
||||
return [
|
||||
{ name: 'cornerstone-tracked', component: ExtendedOHIFCornerstoneViewport },
|
||||
];
|
||||
};
|
||||
|
||||
export default getViewportModule;
|
||||
15
extensions/measurement-tracking/src/index.js
Normal file
15
extensions/measurement-tracking/src/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import getCommandsModule from './getCommandsModule.js';
|
||||
import getContextModule from './getContextModule.js';
|
||||
import getPanelModule from './getPanelModule.js';
|
||||
import getViewportModule from './getViewportModule.js';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'org.ohif.measurement-tracking',
|
||||
getCommandsModule,
|
||||
getContextModule,
|
||||
getPanelModule,
|
||||
getViewportModule,
|
||||
};
|
||||
8
modes/longitudinal/.webpack/webpack.dev.js
Normal file
8
modes/longitudinal/.webpack/webpack.dev.js
Normal file
@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
};
|
||||
44
modes/longitudinal/.webpack/webpack.prod.js
Normal file
44
modes/longitudinal/.webpack/webpack.prod.js
Normal file
@ -0,0 +1,44 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
const ROOT_DIR = path.join(__dirname, './..');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
devtool: 'source-map',
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: ROOT_DIR,
|
||||
library: 'OHIFExtCornerstone',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
21
modes/longitudinal/LICENSE
Normal file
21
modes/longitudinal/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Open Health Imaging Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1
modes/longitudinal/babel.config.js
Normal file
1
modes/longitudinal/babel.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('../../babel.config.js');
|
||||
35
modes/longitudinal/package.json
Normal file
35
modes/longitudinal/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@ohif/mode-longitudinal",
|
||||
"version": "0.0.1",
|
||||
"description": "Longitudinal Workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"dev:cornerstone": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"start": "yarn run dev",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6"
|
||||
}
|
||||
}
|
||||
98
modes/longitudinal/src/index.js
Normal file
98
modes/longitudinal/src/index.js
Normal file
@ -0,0 +1,98 @@
|
||||
export default function mode({ modeConfiguration }) {
|
||||
return {
|
||||
// TODO: We're using this as a route segment
|
||||
// We should not be.
|
||||
id: 'longitudinal-workflow',
|
||||
validationTags: {
|
||||
study: [],
|
||||
series: [],
|
||||
},
|
||||
isValidMode: (studyTags, seriesTags) => {
|
||||
// All series are welcome in this mode!
|
||||
return true;
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
path: 'longitudinal',
|
||||
init: ({ servicesManager, extensionManager }) => {
|
||||
const { ToolBarService } = servicesManager.services;
|
||||
ToolBarService.init(extensionManager);
|
||||
ToolBarService.addButtons([
|
||||
{
|
||||
id: 'Zoom',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Zoom',
|
||||
},
|
||||
{
|
||||
id: 'Levels',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Wwwc',
|
||||
},
|
||||
{
|
||||
id: 'Pan',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Pan',
|
||||
},
|
||||
{
|
||||
id: 'Capture',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Capture',
|
||||
},
|
||||
{
|
||||
id: 'Layout',
|
||||
namespace: 'org.ohif.default.toolbarModule.Layout',
|
||||
},
|
||||
{
|
||||
id: 'Annotate',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Annotate',
|
||||
},
|
||||
{
|
||||
id: 'Bidirectional',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Bidirectional',
|
||||
},
|
||||
{
|
||||
id: 'Ellipse',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Ellipse',
|
||||
},
|
||||
{
|
||||
id: 'Length',
|
||||
namespace: 'org.ohif.cornerstone.toolbarModule.Length',
|
||||
},
|
||||
]);
|
||||
|
||||
// Could import layout selector here from org.ohif.default (when it exists!)
|
||||
ToolBarService.setToolBarLayout([
|
||||
// Primary
|
||||
{
|
||||
tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'],
|
||||
moreTools: ['Zoom'],
|
||||
},
|
||||
// Secondary
|
||||
{
|
||||
tools: ['Annotate', 'Bidirectional', 'Ellipse', 'Length'],
|
||||
},
|
||||
]);
|
||||
},
|
||||
layoutTemplate: ({ routeProps }) => {
|
||||
return {
|
||||
id: 'org.ohif.default.layoutTemplateModule.viewerLayout',
|
||||
props: {
|
||||
// named slots
|
||||
leftPanels: ['org.ohif.default.panelModule.seriesList'],
|
||||
// TODO: Should be optional, or required to pass empty array for slots?
|
||||
rightPanels: [], // // ['org.ohif.default.panelModule.measure'],
|
||||
viewports: [
|
||||
{
|
||||
namespace: 'org.ohif.measurement-tracking.viewportModule.cornerstone-tracked',
|
||||
displaySetsToDisplay: [
|
||||
'org.ohif.default.sopClassHandlerModule.stack',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
extensions: ['org.ohif.default', 'org.ohif.cornerstone', 'org.ohif.measurement-tracking'],
|
||||
sopClassHandlers: ['org.ohif.default.sopClassHandlerModule.stack'],
|
||||
};
|
||||
}
|
||||
|
||||
window.longitudinalMode = mode({});
|
||||
@ -58,8 +58,10 @@
|
||||
"@ohif/extension-dicom-microscopy": "^0.50.6",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.1",
|
||||
"@ohif/extension-lesion-tracker": "^0.2.0",
|
||||
"@ohif/extension-measurement-tracking": "^0.0.1",
|
||||
"@ohif/extension-vtk": "^1.5.6",
|
||||
"@ohif/i18n": "^0.52.8",
|
||||
"@ohif/mode-longitudinal": "^0.0.1",
|
||||
"@ohif/ui": "^1.4.4",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"@types/react": "^16.0.0",
|
||||
|
||||
@ -19,6 +19,7 @@ import appInit from './appInit.js';
|
||||
|
||||
// TODO: Temporarily for testing
|
||||
import '@ohif/mode-example';
|
||||
import '@ohif/mode-longitudinal';
|
||||
|
||||
/**
|
||||
* ENV Variable to determine routing behavior
|
||||
|
||||
@ -66,6 +66,7 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
// TODO: Remove this
|
||||
if (!appConfig.modes.length) {
|
||||
appConfig.modes.push(window.exampleMode);
|
||||
appConfig.modes.push(window.longitudinalMode);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -114,7 +114,7 @@ function ViewerViewportGrid(props) {
|
||||
};
|
||||
|
||||
// const ViewportPanes = React.useMemo(getViewportPanes, [
|
||||
// viewportComp'onents,
|
||||
// viewportComponents,
|
||||
// activeViewportIndex,
|
||||
// viewportGrid,
|
||||
// ]);
|
||||
|
||||
@ -23,11 +23,16 @@ import ReactDOM from 'react-dom';
|
||||
*/
|
||||
import OHIFDefaultExtension from '@ohif/extension-default';
|
||||
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
|
||||
import OHIFMeasurementTrackingExtension from '@ohif/extension-measurement-tracking';
|
||||
|
||||
/** Combine our appConfiguration and "baked-in" extensions */
|
||||
const appProps = {
|
||||
config: window ? window.config : {},
|
||||
defaultExtensions: [OHIFDefaultExtension, OHIFCornerstoneExtension],
|
||||
defaultExtensions: [
|
||||
OHIFDefaultExtension,
|
||||
OHIFCornerstoneExtension,
|
||||
OHIFMeasurementTrackingExtension,
|
||||
],
|
||||
};
|
||||
|
||||
/** Create App */
|
||||
|
||||
21
yarn.lock
21
yarn.lock
@ -868,27 +868,13 @@
|
||||
pirates "^4.0.0"
|
||||
source-map-support "^0.5.16"
|
||||
|
||||
"@babel/runtime@7.1.2":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3"
|
||||
integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6":
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4":
|
||||
version "7.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
|
||||
integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4":
|
||||
version "7.9.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
|
||||
integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/standalone@^7.4.5":
|
||||
version "7.8.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.8.6.tgz#1364534775c83bf7b7988e4ca98823bef56a0a53"
|
||||
@ -17296,11 +17282,6 @@ regenerator-runtime@^0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-runtime@^0.12.0:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
||||
|
||||
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2:
|
||||
version "0.13.3"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user