ohif-viewer/platform/viewer/src/connectedComponents/ViewerRetrieveStudyData.js
Rodrigo Antinarelli 2c23818c86
Fix: #1776 - Segmentations not loading (#1795)
* temp comments analyze

* fix: cache derivedDisplaySets

* replace const

* minor refactor

* fix e2e inconsistent value

* increase e2e timeout
2020-06-18 08:24:39 +01:00

397 lines
12 KiB
JavaScript

import React, { useState, useEffect, useContext, useCallback } from 'react';
import { metadata, studies, utils, log } from '@ohif/core';
import usePrevious from '../customHooks/usePrevious';
import ConnectedViewer from './ConnectedViewer.js';
import PropTypes from 'prop-types';
import { extensionManager } from './../App.js';
import { useSnackbarContext } from '@ohif/ui';
// Contexts
import AppContext from '../context/AppContext';
const { OHIFStudyMetadata, OHIFSeriesMetadata } = metadata;
const { retrieveStudiesMetadata, deleteStudyMetadataPromise } = studies;
const { studyMetadataManager, makeCancelable } = utils;
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,
};
};
/**
* 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;
const _seriesLookup = (valueToCompare, displaySet) => {
return displaySet.SeriesInstanceUID === valueToCompare;
};
const promotedResponse = _promoteToFront(
studyMetadata.getDisplaySets(),
seriesInstanceUID,
_seriesLookup
);
study.displaySets = promotedResponse.data;
promoted = promotedResponse.promoted;
}
return promoted;
};
/**
* 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;
}
const { series = [], displaySets = [] } = study;
const firstSeries = isFilterStrategy ? series[0] : displaySets[0];
if (!firstSeries || firstSeries.SeriesInstanceUID !== seriesInstanceUID) {
applied = false;
}
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);
const existingSeries = studyMetadata.getSeriesByUID(series.SeriesInstanceUID);
if (existingSeries) {
studyMetadata.updateSeries(series.SeriesInstanceUID, seriesMetadata);
} else {
studyMetadata.addSeries(seriesMetadata);
}
studyMetadata.createAndAddDisplaySetsForSeries(
sopClassHandlerModules,
seriesMetadata
);
study.displaySets = studyMetadata.getDisplaySets();
study.derivedDisplaySets = studyMetadata.getDerivedDatasets({
Modality: series.Modality,
});
_updateStudyMetadataManager(study, studyMetadata);
};
const _updateStudyMetadataManager = (study, studyMetadata) => {
const { StudyInstanceUID } = study;
if (!studyMetadataManager.get(StudyInstanceUID)) {
studyMetadataManager.add(studyMetadata);
}
};
const _updateStudyDisplaySets = (study, studyMetadata) => {
const sopClassHandlerModules =
extensionManager.modules['sopClassHandlerModule'];
if (!study.displaySets) {
study.displaySets = studyMetadata.createDisplaySets(sopClassHandlerModules);
}
if (study.derivedDisplaySets) {
studyMetadata._addDerivedDisplaySets(study.derivedDisplaySets);
}
studyMetadata.setDisplaySets(study.displaySets);
};
const _sortStudyDisplaySet = (study, studyMetadata) => {
studyMetadata.sortDisplaySets(study.displaySets);
};
const _thinStudyData = study => {
return {
StudyInstanceUID: study.StudyInstanceUID,
series: study.series.map(item => ({
SeriesInstanceUID: item.SeriesInstanceUID,
})),
};
};
function ViewerRetrieveStudyData({
server,
studyInstanceUIDs,
seriesInstanceUIDs,
clearViewportSpecificData,
setStudyData,
}) {
// hooks
const [error, setError] = useState(false);
const [studies, setStudies] = useState([]);
const [isStudyLoaded, setIsStudyLoaded] = useState(false);
const snackbarContext = useSnackbarContext();
const { appConfig = {} } = useContext(AppContext);
const {
filterQueryParam: isFilterStrategy = false,
maxConcurrentMetadataRequests,
} = appConfig;
let cancelableSeriesPromises;
let cancelableStudiesPromises;
/**
* 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
*/
const studyDidLoad = (study, studyMetadata, filters) => {
// User message
const promoted = _promoteList(
study,
studyMetadata,
filters,
isFilterStrategy
);
// Clear viewport to allow new promoted one to be displayed
if (promoted) {
clearViewportSpecificData(0);
}
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
);
setStudies([...studies, study]);
setIsStudyLoaded(true);
};
/**
* 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) {
// Map studies to new format, update metadata manager?
const studies = studiesData.map(study => {
setStudyData(study.StudyInstanceUID, _thinStudyData(study));
const studyMetadata = new OHIFStudyMetadata(
study,
study.StudyInstanceUID
);
_updateStudyDisplaySets(study, studyMetadata);
_updateStudyMetadataManager(study, studyMetadata);
// Attempt to load remaning series if any
cancelableSeriesPromises[study.StudyInstanceUID] = makeCancelable(
loadRemainingSeries(studyMetadata)
)
.then(result => {
if (result && !result.isCanceled) {
studyDidLoad(study, studyMetadata, filters);
}
})
.catch(error => {
if (error && !error.isCanceled) {
setError(true);
log.error(error);
}
});
return study;
});
setStudies(studies);
}
};
const forceRerender = () => setStudies(studies => [...studies]);
const loadRemainingSeries = async studyMetadata => {
const { seriesLoader } = studyMetadata.getData();
if (!seriesLoader) return;
const loadNextSeries = async () => {
if (!seriesLoader.hasNext()) return;
const series = await seriesLoader.next();
_addSeriesToStudy(studyMetadata, series);
forceRerender();
return loadNextSeries();
};
const concurrentRequestsAllowed =
maxConcurrentMetadataRequests || studyMetadata.getSeriesCount();
const promises = Array(concurrentRequestsAllowed)
.fill(null)
.map(loadNextSeries);
return await Promise.all(promises);
};
const loadStudies = async () => {
try {
const filters = {};
// Use the first, discard others
const seriesInstanceUID = seriesInstanceUIDs && seriesInstanceUIDs[0];
const retrieveParams = [server, studyInstanceUIDs];
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);
log.error(error);
}
});
} catch (error) {
if (error) {
setError(true);
log.error(error);
}
}
};
const purgeCancellablePromises = useCallback(() => {
for (let studyInstanceUIDs in cancelableStudiesPromises) {
if ('cancel' in cancelableStudiesPromises[studyInstanceUIDs]) {
cancelableStudiesPromises[studyInstanceUIDs].cancel();
}
}
for (let studyInstanceUIDs in cancelableSeriesPromises) {
if ('cancel' in cancelableSeriesPromises[studyInstanceUIDs]) {
cancelableSeriesPromises[studyInstanceUIDs].cancel();
deleteStudyMetadataPromise(studyInstanceUIDs);
studyMetadataManager.remove(studyInstanceUIDs);
}
}
});
const prevStudyInstanceUIDs = usePrevious(studyInstanceUIDs);
useEffect(() => {
const hasStudyInstanceUIDsChanged = !(
prevStudyInstanceUIDs &&
prevStudyInstanceUIDs.every(e => studyInstanceUIDs.includes(e))
);
if (hasStudyInstanceUIDsChanged) {
studyMetadataManager.purge();
purgeCancellablePromises();
}
}, [prevStudyInstanceUIDs, purgeCancellablePromises, studyInstanceUIDs]);
useEffect(() => {
cancelableSeriesPromises = {};
cancelableStudiesPromises = {};
loadStudies();
return () => {
purgeCancellablePromises();
};
}, []);
if (error) {
return <div>Error: {JSON.stringify(error)}</div>;
}
return (
<ConnectedViewer
studies={studies}
isStudyLoaded={isStudyLoaded}
studyInstanceUIDs={studyInstanceUIDs}
/>
);
}
ViewerRetrieveStudyData.propTypes = {
studyInstanceUIDs: PropTypes.array.isRequired,
seriesInstanceUIDs: PropTypes.array,
server: PropTypes.object,
clearViewportSpecificData: PropTypes.func.isRequired,
setStudyData: PropTypes.func.isRequired,
};
export default ViewerRetrieveStudyData;