2019-11-19 09:33:48 +01:00
|
|
|
import React, { useState, useEffect, useContext } from 'react';
|
2019-10-03 09:51:29 +02:00
|
|
|
import { metadata, studies, utils, log } from '@ohif/core';
|
2020-01-15 15:15:10 +01:00
|
|
|
import usePrevious from '../customHooks/usePrevious';
|
2019-06-15 02:28:41 +02:00
|
|
|
|
2019-10-03 09:51:29 +02:00
|
|
|
import ConnectedViewer from './ConnectedViewer.js';
|
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
import { extensionManager } from './../App.js';
|
2019-11-19 09:33:48 +01:00
|
|
|
import { useSnackbarContext } from '@ohif/ui';
|
2019-01-09 00:13:44 +01:00
|
|
|
|
2019-10-03 09:51:29 +02:00
|
|
|
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
|
2019-11-05 20:47:54 +01:00
|
|
|
const { retrieveStudiesMetadata, deleteStudyMetadataPromise } = studies;
|
2019-11-19 09:33:48 +01:00
|
|
|
const { studyMetadataManager, updateMetaDataManager, makeCancelable } = utils;
|
2018-12-14 15:23:48 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
// Contexts
|
|
|
|
|
import AppContext from '../context/AppContext';
|
|
|
|
|
|
|
|
|
|
const _promoteToFront = (list, value, searchMethod) => {
|
|
|
|
|
let response = [...list];
|
|
|
|
|
let promoted = false;
|
|
|
|
|
const index = response.findIndex(searchMethod.bind(undefined, value));
|
|
|
|
|
|
|
|
|
|
if (index > 0) {
|
|
|
|
|
const first = response.splice(index, 1);
|
|
|
|
|
response = [...first, ...response];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index >= 0) {
|
|
|
|
|
promoted = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
promoted,
|
|
|
|
|
data: response,
|
2019-02-07 15:10:03 +01:00
|
|
|
};
|
2019-11-19 09:33:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Promote series to front if find found equivalent on filters object
|
|
|
|
|
* @param {Object} study - study reference to promote series against
|
|
|
|
|
* @param {Object} [filters] - Object containing filters to be applied
|
|
|
|
|
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
|
|
|
|
* @param {boolean} isFilterStrategy - if filtering by query param strategy ON
|
|
|
|
|
*/
|
|
|
|
|
const _promoteList = (study, studyMetadata, filters, isFilterStrategy) => {
|
|
|
|
|
let promoted = false;
|
|
|
|
|
// Promote only if no filter should be applied
|
|
|
|
|
if (!isFilterStrategy) {
|
|
|
|
|
_sortStudyDisplaySet(study, studyMetadata);
|
|
|
|
|
promoted = _promoteStudyDisplaySet(study, studyMetadata, filters);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return promoted;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const _promoteStudyDisplaySet = (study, studyMetadata, filters) => {
|
|
|
|
|
let promoted = false;
|
|
|
|
|
const queryParamsLength = Object.keys(filters).length;
|
|
|
|
|
const shouldPromoteToFront = queryParamsLength > 0;
|
|
|
|
|
|
|
|
|
|
if (shouldPromoteToFront) {
|
|
|
|
|
const { seriesInstanceUID } = filters;
|
2019-02-07 15:10:03 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
const _seriesLookup = (valueToCompare, displaySet) => {
|
|
|
|
|
return displaySet.seriesInstanceUid === valueToCompare;
|
2019-10-03 09:51:29 +02:00
|
|
|
};
|
2019-11-19 09:33:48 +01:00
|
|
|
const promotedResponse = _promoteToFront(
|
|
|
|
|
studyMetadata.getDisplaySets(),
|
|
|
|
|
seriesInstanceUID,
|
|
|
|
|
_seriesLookup
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
study.displaySets = promotedResponse.data;
|
|
|
|
|
promoted = promotedResponse.promoted;
|
2019-10-03 09:51:29 +02:00
|
|
|
}
|
2019-02-07 15:10:03 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
return promoted;
|
|
|
|
|
};
|
2019-11-05 22:48:44 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
/**
|
|
|
|
|
* Method to identify if query param (from url) was applied to given list
|
|
|
|
|
* @param {Object} study - study reference to promote series against
|
|
|
|
|
* @param {Object} [filters] - Object containing filters to be applied
|
|
|
|
|
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
|
|
|
|
* @param {boolean} isFilterStrategy - if filtering by query param strategy ON
|
|
|
|
|
*/
|
|
|
|
|
const _isQueryParamApplied = (study, filters = {}, isFilterStrategy) => {
|
|
|
|
|
const { seriesInstanceUID } = filters;
|
|
|
|
|
let applied = true;
|
|
|
|
|
// skip in case no filter or no toast manager
|
|
|
|
|
if (!seriesInstanceUID) {
|
|
|
|
|
return applied;
|
|
|
|
|
}
|
2019-11-05 22:48:44 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
const { seriesList = [], displaySets = [] } = study;
|
|
|
|
|
const firstSeries = isFilterStrategy ? seriesList[0] : displaySets[0];
|
2019-11-05 22:48:44 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
if (!firstSeries || firstSeries.seriesInstanceUid !== seriesInstanceUID) {
|
|
|
|
|
applied = false;
|
2019-10-03 09:51:29 +02:00
|
|
|
}
|
2019-06-15 02:28:41 +02:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
return applied;
|
|
|
|
|
};
|
|
|
|
|
const _showUserMessage = (queryParamApplied, message, dialog = {}) => {
|
|
|
|
|
if (queryParamApplied) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { show: showUserMessage = () => { } } = dialog;
|
|
|
|
|
showUserMessage({
|
|
|
|
|
message,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const _addSeriesToStudy = (studyMetadata, series) => {
|
|
|
|
|
const sopClassHandlerModules =
|
|
|
|
|
extensionManager.modules['sopClassHandlerModule'];
|
|
|
|
|
const study = studyMetadata.getData();
|
|
|
|
|
const seriesMetadata = new OHIFSeriesMetadata(series, study);
|
|
|
|
|
studyMetadata.addSeries(seriesMetadata);
|
|
|
|
|
studyMetadata.createAndAddDisplaySetsForSeries(
|
|
|
|
|
sopClassHandlerModules,
|
|
|
|
|
seriesMetadata,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
study.displaySets = studyMetadata.getDisplaySets();
|
|
|
|
|
_updateMetaDataManager(study, series.seriesInstanceUid);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const _updateMetaDataManager = (study, studyMetadata, series) => {
|
|
|
|
|
updateMetaDataManager(study, series);
|
|
|
|
|
|
2020-01-15 15:15:10 +01:00
|
|
|
const { studyInstanceUid } = study;
|
2019-11-19 09:33:48 +01:00
|
|
|
|
2020-01-15 15:15:10 +01:00
|
|
|
if (!studyMetadataManager.get(studyInstanceUid)) {
|
2019-11-19 09:33:48 +01:00
|
|
|
studyMetadataManager.add(studyMetadata);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const _updateStudyDisplaySets = (study, studyMetadata) => {
|
|
|
|
|
const sopClassHandlerModules =
|
|
|
|
|
extensionManager.modules['sopClassHandlerModule'];
|
|
|
|
|
|
|
|
|
|
if (!study.displaySets) {
|
|
|
|
|
study.displaySets = studyMetadata.createDisplaySets(sopClassHandlerModules);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
studyMetadata.setDisplaySets(study.displaySets);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const _sortStudyDisplaySet = (study, studyMetadata) => {
|
|
|
|
|
studyMetadata.sortDisplaySets(study.displaySets);
|
|
|
|
|
};
|
|
|
|
|
const _loadRemainingSeries = studyMetadata => {
|
|
|
|
|
const { seriesLoader } = studyMetadata.getData();
|
|
|
|
|
if (!seriesLoader) {
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
const promisesLoaders = [];
|
|
|
|
|
while (seriesLoader.hasNext()) {
|
|
|
|
|
promisesLoaders.push(
|
|
|
|
|
seriesLoader
|
|
|
|
|
.next()
|
|
|
|
|
.then(
|
|
|
|
|
series => void _addSeriesToStudy(studyMetadata, series),
|
|
|
|
|
error => void log.error(error)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Promise.all(promisesLoaders);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function ViewerRetrieveStudyData({
|
|
|
|
|
server,
|
|
|
|
|
studyInstanceUids,
|
|
|
|
|
seriesInstanceUids,
|
|
|
|
|
clearViewportSpecificData,
|
|
|
|
|
}) {
|
|
|
|
|
// hooks
|
|
|
|
|
const [error, setError] = useState(false);
|
|
|
|
|
const [studies, setStudies] = useState([]);
|
2019-12-11 17:14:01 +01:00
|
|
|
const [isStudyLoaded, setIsStudyLoaded] = useState(false);
|
2019-11-19 09:33:48 +01:00
|
|
|
const snackbarContext = useSnackbarContext();
|
|
|
|
|
const { appConfig = {} } = useContext(AppContext);
|
|
|
|
|
const { filterQueryParam: isFilterStrategy = false } = appConfig;
|
|
|
|
|
|
|
|
|
|
let cancelableSeriesPromises;
|
|
|
|
|
let cancelableStudiesPromises;
|
2019-11-05 22:48:44 +01:00
|
|
|
/**
|
2019-11-19 09:33:48 +01:00
|
|
|
* Callback method when study is totally loaded
|
|
|
|
|
* @param {object} study study loaded
|
|
|
|
|
* @param {object} studyMetadata studyMetadata for given study
|
|
|
|
|
* @param {Object} [filters] - Object containing filters to be applied
|
|
|
|
|
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
2019-11-05 22:48:44 +01:00
|
|
|
*/
|
2019-11-19 09:33:48 +01:00
|
|
|
const studyDidLoad = (study, studyMetadata, filters) => {
|
|
|
|
|
// User message
|
|
|
|
|
const promoted = _promoteList(
|
|
|
|
|
study,
|
|
|
|
|
studyMetadata,
|
|
|
|
|
filters,
|
|
|
|
|
isFilterStrategy
|
|
|
|
|
);
|
2019-11-05 22:48:44 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
// Clear viewport to allow new promoted one to be displayed
|
|
|
|
|
if (promoted) {
|
|
|
|
|
clearViewportSpecificData(0);
|
2019-11-05 22:48:44 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
const isQueryParamApplied = _isQueryParamApplied(
|
|
|
|
|
study,
|
|
|
|
|
filters,
|
|
|
|
|
isFilterStrategy
|
|
|
|
|
);
|
|
|
|
|
// Show message in case not promoted neither filtered but should to
|
|
|
|
|
_showUserMessage(
|
|
|
|
|
isQueryParamApplied,
|
|
|
|
|
'Query parameters were not applied. Using original series list for given study.',
|
|
|
|
|
snackbarContext
|
|
|
|
|
);
|
2019-11-05 22:48:44 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
setStudies([...studies, study]);
|
2019-12-11 17:14:01 +01:00
|
|
|
setIsStudyLoaded(true);
|
2019-11-19 09:33:48 +01:00
|
|
|
};
|
2019-11-05 22:48:44 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
/**
|
|
|
|
|
* Method to process studies. It will update displaySet, studyMetadata, load remaining series, ...
|
|
|
|
|
* @param {Array} studiesData Array of studies retrieved from server
|
|
|
|
|
* @param {Object} [filters] - Object containing filters to be applied
|
|
|
|
|
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
|
|
|
|
*/
|
|
|
|
|
const processStudies = (studiesData, filters) => {
|
|
|
|
|
if (Array.isArray(studiesData) && studiesData.length > 0) {
|
2019-06-15 02:28:41 +02:00
|
|
|
// Map studies to new format, update metadata manager?
|
2019-11-19 09:33:48 +01:00
|
|
|
const studies = studiesData.map(study => {
|
2019-06-15 02:28:41 +02:00
|
|
|
const studyMetadata = new OHIFStudyMetadata(
|
|
|
|
|
study,
|
|
|
|
|
study.studyInstanceUid
|
|
|
|
|
);
|
2019-11-19 09:33:48 +01:00
|
|
|
|
|
|
|
|
_updateStudyDisplaySets(study, studyMetadata);
|
|
|
|
|
_updateMetaDataManager(study, studyMetadata);
|
|
|
|
|
|
2019-10-03 09:51:29 +02:00
|
|
|
// Attempt to load remaning series if any
|
2019-11-19 09:33:48 +01:00
|
|
|
cancelableSeriesPromises[study.studyInstanceUid] = makeCancelable(
|
|
|
|
|
_loadRemainingSeries(studyMetadata)
|
|
|
|
|
)
|
|
|
|
|
.then(result => {
|
|
|
|
|
if (result && !result.isCanceled) {
|
|
|
|
|
studyDidLoad(study, studyMetadata, filters);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
if (error && !error.isCanceled) {
|
|
|
|
|
setError(true);
|
2020-02-07 17:09:37 +01:00
|
|
|
log.error(error);
|
2019-11-19 09:33:48 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-15 02:28:41 +02:00
|
|
|
return study;
|
|
|
|
|
});
|
2019-11-19 09:33:48 +01:00
|
|
|
|
|
|
|
|
setStudies(studies);
|
2019-10-03 09:51:29 +02:00
|
|
|
}
|
2019-11-19 09:33:48 +01:00
|
|
|
};
|
2019-06-15 02:28:41 +02:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
const loadStudies = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const filters = {};
|
|
|
|
|
// Use the first, discard others
|
|
|
|
|
const seriesInstanceUID = seriesInstanceUids && seriesInstanceUids[0];
|
2019-06-15 02:28:41 +02:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
const retrieveParams = [server, studyInstanceUids];
|
2019-11-05 20:47:54 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
if (seriesInstanceUID) {
|
|
|
|
|
filters.seriesInstanceUID = seriesInstanceUID;
|
|
|
|
|
// Query param filtering controlled by appConfig property
|
|
|
|
|
if (isFilterStrategy) {
|
|
|
|
|
retrieveParams.push(filters);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancelableStudiesPromises[studyInstanceUids] = makeCancelable(
|
|
|
|
|
retrieveStudiesMetadata(...retrieveParams)
|
|
|
|
|
)
|
|
|
|
|
.then(result => {
|
|
|
|
|
if (result && !result.isCanceled) {
|
|
|
|
|
processStudies(result, filters);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
if (error && !error.isCanceled) {
|
|
|
|
|
setError(true);
|
2020-02-07 17:09:37 +01:00
|
|
|
log.error(error);
|
2019-11-19 09:33:48 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error) {
|
|
|
|
|
setError(true);
|
2020-02-07 17:09:37 +01:00
|
|
|
log.error(error);
|
2019-11-19 09:33:48 +01:00
|
|
|
}
|
2019-10-03 09:51:29 +02:00
|
|
|
}
|
2019-11-19 09:33:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const purgeCancellablePromises = () => {
|
|
|
|
|
for (let studyInstanceUids in cancelableStudiesPromises) {
|
|
|
|
|
if ('cancel' in cancelableStudiesPromises[studyInstanceUids]) {
|
|
|
|
|
cancelableStudiesPromises[studyInstanceUids].cancel();
|
|
|
|
|
}
|
2019-11-05 20:47:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
for (let studyInstanceUids in cancelableSeriesPromises) {
|
|
|
|
|
if ('cancel' in cancelableSeriesPromises[studyInstanceUids]) {
|
|
|
|
|
cancelableSeriesPromises[studyInstanceUids].cancel();
|
|
|
|
|
deleteStudyMetadataPromise(studyInstanceUids);
|
|
|
|
|
studyMetadataManager.remove(studyInstanceUids);
|
2019-11-05 20:47:54 +01:00
|
|
|
}
|
2019-06-15 02:28:41 +02:00
|
|
|
}
|
2019-11-19 09:33:48 +01:00
|
|
|
};
|
2018-12-14 15:23:48 +01:00
|
|
|
|
2020-01-15 15:15:10 +01:00
|
|
|
const prevStudyInstanceUids = usePrevious(studyInstanceUids);
|
|
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
useEffect(() => {
|
2020-01-15 15:15:10 +01:00
|
|
|
const hasStudyInstanceUidsChanged = !(prevStudyInstanceUids && prevStudyInstanceUids.every(e => studyInstanceUids.includes(e)));
|
|
|
|
|
|
|
|
|
|
if (hasStudyInstanceUidsChanged) {
|
|
|
|
|
studyMetadataManager.purge();
|
|
|
|
|
purgeCancellablePromises();
|
|
|
|
|
}
|
2019-11-19 09:33:48 +01:00
|
|
|
}, [studyInstanceUids]);
|
2019-10-03 09:51:29 +02:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
useEffect(() => {
|
|
|
|
|
cancelableSeriesPromises = {};
|
|
|
|
|
cancelableStudiesPromises = {};
|
|
|
|
|
loadStudies();
|
2019-02-07 15:10:03 +01:00
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
return () => {
|
|
|
|
|
purgeCancellablePromises();
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
return <div>Error: {JSON.stringify(error)}</div>;
|
2019-02-07 15:10:03 +01:00
|
|
|
}
|
2019-11-19 09:33:48 +01:00
|
|
|
|
|
|
|
|
return (
|
2019-12-11 17:14:01 +01:00
|
|
|
<ConnectedViewer
|
|
|
|
|
studies={studies}
|
|
|
|
|
isStudyLoaded={isStudyLoaded}
|
|
|
|
|
studyInstanceUids={studyInstanceUids}
|
|
|
|
|
/>
|
2019-11-19 09:33:48 +01:00
|
|
|
);
|
2018-12-14 15:23:48 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-19 09:33:48 +01:00
|
|
|
ViewerRetrieveStudyData.propTypes = {
|
|
|
|
|
studyInstanceUids: PropTypes.array.isRequired,
|
|
|
|
|
seriesInstanceUids: PropTypes.array,
|
|
|
|
|
server: PropTypes.object,
|
|
|
|
|
clearViewportSpecificData: PropTypes.func.isRequired,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default ViewerRetrieveStudyData;
|