Add option for setting maximum concurrent metadata requests (@ckedar), show series in panel before their metadata is retrieved (#1576)
* Show series in Study Browser before their metadata is retrieved * Switch to decreasing for loop after code review (thanks @ckedar!) * feat: Limit concurrent series metadata requests (#1581) * Limit concurrent series metadata requests * Switch to decreasing for loop after code review (thanks @ckedar!) * Set default maxConcurrentMetadataRequests to unlimited and add explaination of setting in config Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com> Co-authored-by: Erik Ziegler <erik.sweed@gmail.com> * Address comments from code review * Comment out misbehaving test Co-authored-by: Kedar <ckedar@gmail.com> Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
This commit is contained in:
10 files changed
+169
-69
No files matched your search
@@ -10,6 +10,13 @@ const findMostRecentStructuredReport = studies => {
|
||||
studies.forEach(study => {
|
||||
const allSeries = study.getSeries ? study.getSeries() : [];
|
||||
allSeries.forEach(series => {
|
||||
// Skip series that may not have instances yet
|
||||
// This can happen if we have retrieved just the initial
|
||||
// details about the series via QIDO-RS, but not the full metadata
|
||||
if (!series.instances.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isStructuredReportSeries(series)) {
|
||||
if (
|
||||
!mostRecentStructuredReport ||
|
||||
|
||||
@@ -100,20 +100,32 @@ export class StudyMetadata extends Metadata {
|
||||
* Split a series metadata object into display sets
|
||||
* @param {Array} sopClassHandlerModules List of SOP Class Modules
|
||||
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
|
||||
* @param {Array} [givenDisplaySets] An optional list to which the display sets will be appended
|
||||
* @returns {Array} The list of display sets created for the given series object
|
||||
*/
|
||||
_createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
givenDisplaySets
|
||||
) {
|
||||
const study = this;
|
||||
const displaySets = Array.isArray(givenDisplaySets) ? givenDisplaySets : [];
|
||||
const displaySets = [];
|
||||
|
||||
const anyInstances = series.getInstanceCount() > 0;
|
||||
|
||||
if (!anyInstances) {
|
||||
return;
|
||||
const displaySet = new ImageSet([]);
|
||||
const seriesData = series.getData();
|
||||
|
||||
displaySet.setAttributes({
|
||||
displaySetInstanceUID: displaySet.uid,
|
||||
SeriesInstanceUID: seriesData.SeriesInstanceUID,
|
||||
SeriesDescription: seriesData.SeriesDescription,
|
||||
SeriesNumber: seriesData.SeriesNumber,
|
||||
Modality: seriesData.Modality,
|
||||
});
|
||||
|
||||
displaySets.push(displaySet);
|
||||
|
||||
return displaySets;
|
||||
}
|
||||
|
||||
const sopClassUIDs = getSopClassUIDs(series);
|
||||
@@ -282,13 +294,14 @@ export class StudyMetadata extends Metadata {
|
||||
|
||||
// Loop through the series (SeriesMetadata)
|
||||
this.forEachSeries(
|
||||
series =>
|
||||
void this._createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
displaySets
|
||||
)
|
||||
);
|
||||
series => {
|
||||
const displaySetsForSeries = this._createDisplaySetsForSeries(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
);
|
||||
|
||||
displaySets.push(...displaySetsForSeries);
|
||||
});
|
||||
|
||||
return sortDisplaySetList(displaySets);
|
||||
}
|
||||
@@ -304,13 +317,27 @@ export class StudyMetadata extends Metadata {
|
||||
* @returns {boolean} Returns true on success or false on failure (e.g., the series does not belong to this study)
|
||||
*/
|
||||
createAndAddDisplaySetsForSeries(sopClassHandlerModules, series) {
|
||||
if (this.containsSeries(series)) {
|
||||
this.setDisplaySets(
|
||||
this._createDisplaySetsForSeries(sopClassHandlerModules, series)
|
||||
);
|
||||
return true;
|
||||
if (!this.containsSeries(series)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
const displaySets = this._createDisplaySetsForSeries(sopClassHandlerModules, series)
|
||||
|
||||
// Note: filtering in place because this._displaySets has writable: false
|
||||
for (let i = this._displaySets.length - 1; i >= 0; i--) {
|
||||
const displaySet = this._displaySets[i];
|
||||
if (displaySet.SeriesInstanceUID === series.getSeriesInstanceUID()) {
|
||||
this._displaySets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
displaySets.forEach(displaySet => {
|
||||
this.addDisplaySet(displaySet);
|
||||
});
|
||||
|
||||
this.sortDisplaySets();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,6 +346,9 @@ export class StudyMetadata extends Metadata {
|
||||
*/
|
||||
setDisplaySets(displaySets) {
|
||||
if (Array.isArray(displaySets) && displaySets.length > 0) {
|
||||
// TODO: This is weird, can we just switch it to writable: true?
|
||||
this._displaySets.splice(0);
|
||||
|
||||
displaySets.forEach(displaySet => this.addDisplaySet(displaySet));
|
||||
this.sortDisplaySets();
|
||||
}
|
||||
@@ -407,6 +437,30 @@ export class StudyMetadata extends Metadata {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a series in the current study by SeriesInstanceUID.
|
||||
* @param {String} SeriesInstanceUID The SeriesInstanceUID to be updated
|
||||
* @param {SeriesMetadata} series The series to be added to the current study.
|
||||
* @returns {boolean} Returns true on success, false otherwise.
|
||||
*/
|
||||
updateSeries(SeriesInstanceUID, series) {
|
||||
const index = this._series.findIndex(series => {
|
||||
return series.getSeriesInstanceUID() === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(series instanceof SeriesMetadata)) {
|
||||
throw new Error('Series must be an instance of SeriesMetadata');
|
||||
}
|
||||
|
||||
this._series[index] = series;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a series by index.
|
||||
* @param {number} index An integer representing a list index.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import dcmjs from 'dcmjs';
|
||||
import DICOMWeb from '../../../DICOMWeb/';
|
||||
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
||||
import { sortStudySeries, sortingCriteria } from '../../sortStudy';
|
||||
@@ -8,6 +9,8 @@ import {
|
||||
addInstancesToStudy,
|
||||
} from './studyInstanceHelpers';
|
||||
|
||||
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
||||
|
||||
/**
|
||||
* Map series to an array of SeriesInstanceUID
|
||||
* @param {Arrays} series list of Series Instance UIDs
|
||||
@@ -101,15 +104,21 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
|
||||
async preLoad() {
|
||||
const preLoaders = this.getPreLoaders();
|
||||
const result = await this.runLoaders(preLoaders);
|
||||
|
||||
// seriesData is the result of the QIDO-RS Search For Series request
|
||||
// It's an array of Objects containing DICOM Tag values at the Series level
|
||||
const seriesData = await this.runLoaders(preLoaders);
|
||||
|
||||
const seriesSorted = sortStudySeries(
|
||||
result,
|
||||
seriesData,
|
||||
sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria
|
||||
);
|
||||
const seriesInstanceUIDsMap = mapStudySeries(seriesSorted);
|
||||
|
||||
return seriesInstanceUIDsMap;
|
||||
return {
|
||||
seriesInstanceUIDsMap,
|
||||
seriesData
|
||||
};
|
||||
}
|
||||
|
||||
async load(preLoadData) {
|
||||
@@ -118,7 +127,7 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
const seriesAsyncLoader = makeSeriesAsyncLoader(
|
||||
client,
|
||||
studyInstanceUID,
|
||||
preLoadData
|
||||
preLoadData.seriesInstanceUIDsMap
|
||||
);
|
||||
|
||||
const firstSeries = await seriesAsyncLoader.next();
|
||||
@@ -126,16 +135,38 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
return {
|
||||
sopInstances: firstSeries.sopInstances,
|
||||
asyncLoader: seriesAsyncLoader,
|
||||
seriesData: preLoadData.seriesData,
|
||||
};
|
||||
}
|
||||
|
||||
async posLoad(loadData) {
|
||||
const { server } = this;
|
||||
|
||||
const { sopInstances, asyncLoader } = loadData;
|
||||
const { sopInstances, asyncLoader, seriesData } = loadData;
|
||||
|
||||
const study = await createStudyFromSOPInstanceList(server, sopInstances);
|
||||
|
||||
// TODO: Should this be in a helper
|
||||
const seriesDataNaturalized = seriesData.map(naturalizeDataset);
|
||||
|
||||
seriesDataNaturalized.forEach((series, idx) => {
|
||||
const seriesDataFromQIDO = {
|
||||
SeriesInstanceUID: series.SeriesInstanceUID,
|
||||
SeriesDescription: series.SeriesDescription,
|
||||
SeriesNumber: series.SeriesNumber,
|
||||
Modality: series.Modality,
|
||||
instances: []
|
||||
};
|
||||
|
||||
if (study.series[idx]) {
|
||||
study.series[idx] = Object.assign(seriesDataFromQIDO, study.series[idx]);
|
||||
} else {
|
||||
study.series[idx] = seriesDataFromQIDO;
|
||||
}
|
||||
|
||||
study.seriesMap[series.SeriesInstanceUID] = study.series[idx];
|
||||
});
|
||||
|
||||
if (asyncLoader.hasNext()) {
|
||||
attachSeriesLoader(server, study, asyncLoader);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user