StudyBrowser almost works

This commit is contained in:
Erik Ziegler 2020-05-15 12:48:00 +02:00 committed by James A. Petts
parent ec2df02e83
commit 70b7158c9b
13 changed files with 138 additions and 38 deletions

View File

@ -34,8 +34,8 @@
"cornerstone-core": "^2.3.0", "cornerstone-core": "^2.3.0",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.3", "dcmjs": "^0.12.3",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -12,6 +12,9 @@ const { setViewportSpecificData } = OHIF.redux.actions;
const commandsModule = ({ servicesManager }) => { const commandsModule = ({ servicesManager }) => {
const actions = { const actions = {
getCornerstoneLibraries: () => {
return { cornerstone, cornerstoneTools };
},
rotateViewport: ({ viewports, rotation }) => { rotateViewport: ({ viewports, rotation }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex); const enabledElement = getEnabledElement(viewports.activeViewportIndex);
@ -262,23 +265,28 @@ const commandsModule = ({ servicesManager }) => {
StudyInstanceUID, StudyInstanceUID,
SOPInstanceUID, SOPInstanceUID,
frameIndex, frameIndex,
activeViewportIndex activeViewportIndex,
}) => { }) => {
const study = studyMetadataManager.get(StudyInstanceUID); const study = studyMetadataManager.get(StudyInstanceUID);
const displaySet = study.findDisplaySet(ds => { const displaySet = study.findDisplaySet(ds => {
return ds.images && ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID) return (
ds.images &&
ds.images.find(i => i.getSOPInstanceUID() === SOPInstanceUID)
);
}); });
displaySet.SOPInstanceUID = SOPInstanceUID; displaySet.SOPInstanceUID = SOPInstanceUID;
displaySet.frameIndex = frameIndex; displaySet.frameIndex = frameIndex;
window.store.dispatch(setViewportSpecificData(activeViewportIndex, displaySet)); window.store.dispatch(
setViewportSpecificData(activeViewportIndex, displaySet)
);
cornerstone.getEnabledElements().forEach(enabledElement => { cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element); cornerstone.updateImage(enabledElement.element);
}); });
} },
}; };
const definitions = { const definitions = {
@ -287,6 +295,12 @@ const commandsModule = ({ servicesManager }) => {
storeContexts: [], storeContexts: [],
options: {}, options: {},
}, },
getCornerstoneLibraries: {
commandFn: actions.getCornerstoneLibraries,
storeContexts: [],
options: {},
context: 'VIEWER',
},
getNearbyToolData: { getNearbyToolData: {
commandFn: actions.getNearbyToolData, commandFn: actions.getNearbyToolData,
storeContexts: [], storeContexts: [],

View File

@ -4,6 +4,7 @@ import cornerstone from 'cornerstone-core';
import csTools from 'cornerstone-tools'; import csTools from 'cornerstone-tools';
import merge from 'lodash.merge'; import merge from 'lodash.merge';
import initCornerstoneTools from './initCornerstoneTools.js'; import initCornerstoneTools from './initCornerstoneTools.js';
import initWADOImageLoader from './initWADOImageLoader.js';
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
/** /**

View File

@ -0,0 +1,18 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import dicomParser from 'dicom-parser';
//import { initWebWorkers } from './utils/index.js';
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
cornerstoneWADOImageLoader.configure({
beforeSend: function(xhr) {
/*const headers = OHIF.DICOMWeb.getAuthorizationHeader();
if (headers.Authorization) {
xhr.setRequestHeader('Authorization', headers.Authorization);
}*/
},
});

View File

@ -31,7 +31,7 @@ function ViewerLayout({
const getPanelData = id => { const getPanelData = id => {
const entry = extensionManager.getModuleEntry(id); const entry = extensionManager.getModuleEntry(id);
// TODO, not sure why sidepanel content has to be JSX, and not a children prop? // TODO, not sure why sidepanel content has to be JSX, and not a children prop?
const content = entry.component({ extensionManager }); const content = entry.component;
return { return {
iconName: entry.iconName, iconName: entry.iconName,

View File

@ -7,37 +7,86 @@ import {
displaySetManager, displaySetManager,
} from '@ohif/core'; } from '@ohif/core';
function StudyBrowserPanel({ extensionManager }) { // Create map in local state from displaySetInstanceUids to thumbnails
// TODO: need to check how this is intended to be passed in // Get thumbnail imageId from displaySet
const dataSource = extensionManager.dataSourceMap.dicomweb[0]; // When displaySetInstanceUids change, initiate async render into canvas
// Get image data-uri from canvas offscreen
// Set image data-uri into state (NOTE: This will probably end up in the fucking browser cache, NEED to find a way to prevent that from happening)
// state triggers rerender
//
// TODO:
// - No loading UI exists yet
// - cancel promises when component is destroyed
// - show errors in UI for thumbnails if promise fails
function getImageSrc(imageId, { cornerstone }) {
// TODO: Switch to async/await when it stops failing
return new Promise((resolve, reject) => {
cornerstone.loadAndCacheImage(imageId).then(image => {
const canvas = document.createElement('canvas');
cornerstone.renderToCanvas(canvas, image);
resolve(canvas.toDataURL());
});
});
}
function StudyBrowserPanel({ getDataSources, commandsManager }) {
const viewModel = useViewModel(); const viewModel = useViewModel();
const dataSource = getDataSources('dicomweb')[0];
const [studyData, setStudyData] = useState([]);
const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState(new Map());
const updateThumbnailMap = (k, v) => {
setThumbnailImageSrcMap(thumbnailImageSrcMap.set(k, v));
};
useEffect(() => {
const command = commandsManager.getCommand(
'getCornerstoneLibraries',
'VIEWER'
);
if (!command) {
throw new Error('Required command not found');
}
const { cornerstone, cornerstoneTools } = command.commandFn();
if (!viewModel.displaySetInstanceUids.length) {
return;
}
viewModel.displaySetInstanceUids.forEach(uid => {
const imageIds = dataSource.getImageIdsForDisplaySet(uid);
const imageId = imageIds[0];
getImageSrc(imageId, { cornerstone }).then(imageSrc => {
updateThumbnailMap(uid, imageSrc);
});
});
}, [viewModel.displaySetInstanceUids]);
// TODO // TODO
const viewportData = []; //useViewportGrid(); const viewportData = []; //useViewportGrid();
const seriesTracking = {}; //useSeriesTracking(); const seriesTracking = {}; //useSeriesTracking();
const [studyData, setStudyData] = useState([]);
console.log(viewModel);
const displaySets = viewModel.displaySetInstanceUids.map( const displaySets = viewModel.displaySetInstanceUids.map(
displaySetManager.getDisplaySetByUID displaySetManager.getDisplaySetByUID
); );
console.log(displaySets);
if (!displaySets.length) {
return;
}
// TODO: // TODO:
// - Put this in something so it only runs once // - Put this in something so it only runs once
// - Have update the query update the dicom data store at the study level and then have this component use the data in the view model // - Have update the query update the dicom data store at the study level and then have this component use the data in the view model
useEffect(() => { useEffect(() => {
if (!viewModel.displaySetInstanceUids.length) {
return;
}
const dSets = viewModel.displaySetInstanceUids.map( const dSets = viewModel.displaySetInstanceUids.map(
displaySetManager.getDisplaySetByUID displaySetManager.getDisplaySetByUID
); );
const aDisplaySet = dSets[0]; const aDisplaySet = dSets[0];
const firstStudy = dicomMetadataStore.getStudy( const firstStudy = dicomMetadataStore.getStudy(
aDisplaySet.StudyInstanceUID aDisplaySet.StudyInstanceUID
@ -74,6 +123,7 @@ function StudyBrowserPanel({ extensionManager }) {
modality: ds.Modality, modality: ds.Modality,
date: ds.SeriesDate, date: ds.SeriesDate,
numInstances: ds.numImageFrames, numInstances: ds.numImageFrames,
//imageSrc,
}; };
const displaySetViewportData = viewportData.find( const displaySetViewportData = viewportData.find(
@ -203,21 +253,30 @@ function StudyBrowserPanel({ extensionManager }) {
return <StudyBrowser tabs={tabs} onClickStudy={memoOnClickStudy} />; return <StudyBrowser tabs={tabs} onClickStudy={memoOnClickStudy} />;
} }
function getPanelModule() { function getPanelModule({ getDataSources, commandsManager }) {
const wrappedStudyBrowserPanel = () => {
return (
<StudyBrowserPanel
getDataSources={getDataSources}
commandsManager={commandsManager}
/>
);
};
return [ return [
{ {
name: 'seriesList', name: 'seriesList',
iconName: 'group-layers', iconName: 'group-layers',
iconLabel: 'Studies', iconLabel: 'Studies',
label: 'Studies', label: 'Studies',
component: StudyBrowserPanel, component: wrappedStudyBrowserPanel,
}, },
{ {
name: 'measure', name: 'measure',
iconName: 'list-bullets', iconName: 'list-bullets',
iconLabel: 'Measure', iconLabel: 'Measure',
label: 'Measurements', label: 'Measurements',
component: StudyBrowserPanel, component: wrappedStudyBrowserPanel,
}, },
]; ];
} }

View File

@ -28,13 +28,12 @@
"start": "yarn run dev" "start": "yarn run dev"
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^2.5.1",
"@ohif/i18n": "^0.50.0", "@ohif/i18n": "^0.52.6",
"@ohif/ui": "^0.50.0", "@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.3.0",
"cornerstone-tools": "4.15.1", "cornerstone-wado-image-loader": "^3.1.2",
"cornerstone-wado-image-loader": "^3.1.0", "dcmjs": "0.12.2",
"dcmjs": "^0.12.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
@ -56,9 +55,10 @@
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.9.6", "@ohif/core": "^2.9.6",
"@ohif/ui": "^1.4.4",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0", "@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.3.0",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1", "gh-pages": "^2.0.1",
"i18next": "^17.0.3", "i18next": "^17.0.3",

View File

@ -32,7 +32,7 @@
"peerDependencies": { "peerDependencies": {
"cornerstone-core": "^2.3.0", "cornerstone-core": "^2.3.0",
"cornerstone-tools": "^4.12.0", "cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3" "dicom-parser": "^1.8.3"
}, },
"dependencies": { "dependencies": {

View File

@ -177,6 +177,7 @@ export default class ExtensionManager {
try { try {
const extensionModule = getModuleFn({ const extensionModule = getModuleFn({
getDataSources: this.getDataSources,
servicesManager: this._servicesManager, servicesManager: this._servicesManager,
commandsManager: this._commandsManager, commandsManager: this._commandsManager,
appConfig: this._appConfig, appConfig: this._appConfig,

View File

@ -141,7 +141,7 @@ const SidePanel = ({
</span> </span>
</Button> </Button>
</div> </div>
{childComponent.content} <childComponent.content/>
</React.Fragment> </React.Fragment>
) : ( ) : (
<React.Fragment>{getPanelButtons()}</React.Fragment> <React.Fragment>{getPanelButtons()}</React.Fragment>

View File

@ -18,6 +18,8 @@ const ThumbnailList = ({ thumbnails }) => {
viewportIdentificator, viewportIdentificator,
isTracked, isTracked,
isActive, isActive,
imageSrc,
imageAltText,
}) => { }) => {
switch (componentType) { switch (componentType) {
case 'thumbnail': case 'thumbnail':
@ -27,6 +29,9 @@ const ThumbnailList = ({ thumbnails }) => {
description={description} description={description}
seriesNumber={seriesNumber} seriesNumber={seriesNumber}
numInstances={numInstances} numInstances={numInstances}
imageSrc={imageSrc}
imageAltText={imageAltText}
viewportIdentificator={viewportIdentificator}
isActive={isActive} isActive={isActive}
onClick={() => {}} onClick={() => {}}
/> />
@ -48,6 +53,8 @@ const ThumbnailList = ({ thumbnails }) => {
description={description} description={description}
seriesNumber={seriesNumber} seriesNumber={seriesNumber}
numInstances={numInstances} numInstances={numInstances}
imageSrc={imageSrc}
imageAltText={imageAltText}
viewportIdentificator={viewportIdentificator} viewportIdentificator={viewportIdentificator}
isTracked={isTracked} isTracked={isTracked}
isActive={isActive} isActive={isActive}

View File

@ -67,8 +67,8 @@
"core-js": "^3.2.1", "core-js": "^3.2.1",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.15.1", "cornerstone-tools": "4.15.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "^0.12.2", "dcmjs": "^0.12.2",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4", "dicomweb-client": "^0.4.4",
"dotenv-webpack": "^1.7.0", "dotenv-webpack": "^1.7.0",

View File

@ -5791,10 +5791,10 @@ cornerstone-tools@^4.12.0:
"@babel/runtime" "7.1.2" "@babel/runtime" "7.1.2"
cornerstone-math "0.1.7" cornerstone-math "0.1.7"
cornerstone-wado-image-loader@^3.0.0: cornerstone-wado-image-loader@^3.1.2:
version "3.0.6" version "3.1.2"
resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-3.0.6.tgz#7b2be91f8f658de3a7474e029aa8314a35725132" resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-3.1.2.tgz#5113c317938510670061d11d05deb70266e4a35e"
integrity sha512-1luWOh1GzkboZ3uxfu4T8vh7K9nlDiiEbQ4dYIY9oobRZVZQbQiLT8wEZPbb4JTUe7UWJISHPCNgtAU8UE9DNw== integrity sha512-cnDHrsa2GREqdMb9pjsoHgue+suHkDyuS/W0R8ZVD1lr+LQdv56dVM8oK9n7yOeorA4/IXG8630IL7uHdCHAQA==
dependencies: dependencies:
dicom-parser "^1.8.3" dicom-parser "^1.8.3"
pako "^1.0.10" pako "^1.0.10"