WIP build up displaysets.
This commit is contained in:
1 parent
d4f51c5e3c
commit
5c19671d3c
26 files changed
+1496
-350
No files matched your search
@@ -1,54 +0,0 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import { mapParams, search, processResults } from './qido.js';
|
||||
import IWebApiDataSource from './IWebApiDataSource.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} name - Data source name
|
||||
* @param {string} wadoUriRoot - Legacy?
|
||||
* @param {string} qidoRoot - Base URL to use for QIDO requests
|
||||
* @param {string} wadoRoot - Base URL to use for WADO requests
|
||||
* @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
|
||||
* @param {string} imageRengering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
||||
*/
|
||||
function createDicomWebApi(dicomWebConfig) {
|
||||
const { qidoRoot } = dicomWebConfig;
|
||||
const config = {
|
||||
url: qidoRoot,
|
||||
// headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
};
|
||||
|
||||
const dicomWebClient = new api.DICOMwebClient(config);
|
||||
|
||||
return IWebApiDataSource.create({
|
||||
query: {
|
||||
studies: {
|
||||
mapParams: mapParams.bind(),
|
||||
search: search.bind(undefined, dicomWebClient, null, null),
|
||||
processResults: processResults.bind(),
|
||||
},
|
||||
instances: {
|
||||
search: (studyInstanceUid, queryParamaters) =>
|
||||
search.call(
|
||||
undefined,
|
||||
dicomWebClient,
|
||||
studyInstanceUid,
|
||||
null,
|
||||
queryParamaters
|
||||
),
|
||||
},
|
||||
},
|
||||
retrieveMetadata: (dicomMetadataStore, queryParamaters) => {
|
||||
// TODO -> Write implementation
|
||||
// WADO-RS retrieve study metadata.
|
||||
// Then different WADO-RS retrieve series metadata calls.
|
||||
// Then dicomMetadataStore.addInstance(instance)
|
||||
// dicomMetadataStore naturalizes the instance and adds it to the store.
|
||||
// dicomMetadataStore emits an even that the SopClassHandlerManager can listen to.
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { createDicomWebApi };
|
||||
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* QIDO - Query based on ID for DICOM Objects
|
||||
* search for studies, series and instances by patient ID, and receive their
|
||||
* unique identifiers for further usage.
|
||||
*
|
||||
* Quick: https://www.dicomstandard.org/dicomweb/query-qido-rs/
|
||||
* Standard: http://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.6
|
||||
*
|
||||
* Routes:
|
||||
* ==========
|
||||
* /studies?
|
||||
* /studies/{studyInstanceUid}/series?
|
||||
* /studies/{studyInstanceUid}/series/{seriesInstanceUid}/instances?
|
||||
*
|
||||
* Query Parameters:
|
||||
* ================
|
||||
* | KEY | VALUE |
|
||||
* |------------------|--------------------|
|
||||
* | {attributeId} | {value} |
|
||||
* | includeField | {attribute} or all |
|
||||
* | fuzzymatching | true OR false |
|
||||
* | limit | {number} |
|
||||
* | offset | {number} |
|
||||
*/
|
||||
import { getString, getName, getModalities } from '../../../DICOMWeb/';
|
||||
|
||||
/**
|
||||
* Parses resulting data from a QIDO call into a set of Study MetaData
|
||||
*
|
||||
* @param {Array} qidoStudies - An array of study objects. Each object contains a keys for DICOM tags.
|
||||
* @param {object} qidoStudies[0].qidoStudy - An object where each key is the DICOM Tag group+element
|
||||
* @param {object} qidoStudies[0].qidoStudy[dicomTag] - Optional object that represents DICOM Tag
|
||||
* @param {string} qidoStudies[0].qidoStudy[dicomTag].vr - Value Representation
|
||||
* @param {string[]} qidoStudies[0].qidoStudy[dicomTag].Value - Optional string array representation of the DICOM Tag's value
|
||||
* @returns {Array} An array of Study MetaData objects
|
||||
*/
|
||||
function processResults(qidoStudies) {
|
||||
if (!qidoStudies || !qidoStudies.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const studies = [];
|
||||
|
||||
qidoStudies.forEach(qidoStudy =>
|
||||
studies.push({
|
||||
studyInstanceUid: getString(qidoStudy['0020000D']),
|
||||
studyDate: getString(qidoStudy['00080020']),
|
||||
studyTime: getString(qidoStudy['00080030']),
|
||||
accessionNumber: getString(qidoStudy['00080050']),
|
||||
referringPhysicianName: getString(qidoStudy['00080090']),
|
||||
patientName: getName(qidoStudy['00100010']),
|
||||
patientId: getString(qidoStudy['00100020']),
|
||||
patientBirthdate: getString(qidoStudy['00100030']),
|
||||
patientSex: getString(qidoStudy['00100040']),
|
||||
studyId: getString(qidoStudy['00200010']),
|
||||
numberOfStudyRelatedSeries: getString(qidoStudy['00201206']),
|
||||
numberOfStudyRelatedInstances: getString(qidoStudy['00201208']),
|
||||
studyDescription: getString(qidoStudy['00081030']),
|
||||
modalities: getString(
|
||||
getModalities(qidoStudy['00080060'], qidoStudy['00080061'])
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
return studies;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} dicomWebClient - Client similar to what's provided by `dicomweb-client` library
|
||||
* @param {function} dicomWebClient.searchForStudies -
|
||||
* @param {string} [studyInstanceUid]
|
||||
* @param {string} [seriesInstanceUid]
|
||||
* @param {string} [queryParamaters]
|
||||
* @returns {Promise<results>} - Promise that resolves results
|
||||
*/
|
||||
function search(
|
||||
dicomWebClient,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
queryParamaters
|
||||
) {
|
||||
const requestFn = studyInstanceUid
|
||||
? dicomWebClient.searchForInstances
|
||||
: dicomWebClient.searchForStudies;
|
||||
|
||||
// TODO: Current version does not apply query Params for `searchForInstances` call?
|
||||
// Just sets `studyInstanceUid` in options...
|
||||
return requestFn({
|
||||
studyInstanceUid,
|
||||
queryParams: queryParamaters,
|
||||
});
|
||||
}
|
||||
|
||||
export default function searchStudies(server, filter) {
|
||||
const queryParams = getQIDOQueryParams(
|
||||
filter,
|
||||
server.qidoSupportsIncludeField
|
||||
);
|
||||
const options = {
|
||||
queryParams,
|
||||
};
|
||||
|
||||
return dicomWeb.searchForStudies(options).then(resultDataToStudies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a QIDO URL given server details and a set of specified search filter
|
||||
* items
|
||||
*
|
||||
* @param filter
|
||||
* @param serverSupportsQIDOIncludeField
|
||||
* @returns {string} The URL with encoded filter query data
|
||||
*/
|
||||
function mapParams(params) {
|
||||
const commaSeparatedFields = [
|
||||
'00081030', // Study Description
|
||||
'00080060', // Modality
|
||||
// Add more fields here if you want them in the result
|
||||
].join(',');
|
||||
|
||||
const parameters = {
|
||||
PatientName: filter.patientName,
|
||||
PatientID: filter.patientId,
|
||||
AccessionNumber: filter.accessionNumber,
|
||||
StudyDescription: filter.studyDescription,
|
||||
ModalitiesInStudy: filter.modalitiesInStudy,
|
||||
limit: filter.limit,
|
||||
offset: filter.offset,
|
||||
fuzzymatching: filter.fuzzymatching,
|
||||
includefield: serverSupportsQIDOIncludeField ? commaSeparatedFields : 'all',
|
||||
};
|
||||
|
||||
// build the StudyDate range parameter
|
||||
if (filter.studyDateFrom || filter.studyDateTo) {
|
||||
const dateFrom = _dateToString(new Date(filter.studyDateFrom));
|
||||
const dateTo = _dateToString(new Date(filter.studyDateTo));
|
||||
parameters.StudyDate = `${dateFrom}-${dateTo}`;
|
||||
}
|
||||
|
||||
// Build the StudyInstanceUID parameter
|
||||
if (filter.studyInstanceUid) {
|
||||
let studyUids = filter.studyInstanceUid;
|
||||
studyUids = Array.isArray(studyUids) ? studyUids.join() : studyUids;
|
||||
studyUids = studyUids.replace(/[^0-9.]+/g, '\\');
|
||||
parameters.StudyInstanceUID = studyUids;
|
||||
}
|
||||
|
||||
// Clean query params of undefined values.
|
||||
const params = {};
|
||||
Object.keys(parameters).forEach(key => {
|
||||
if (parameters[key] !== undefined && parameters[key] !== '') {
|
||||
params[key] = parameters[key];
|
||||
}
|
||||
});
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a QIDO date string for a date range query
|
||||
* Assumes the year is positive, at most 4 digits long.
|
||||
*
|
||||
* @param date The Date object to be formatted
|
||||
* @returns {string} The formatted date string
|
||||
*/
|
||||
function _dateToString(date) {
|
||||
if (!date) return '';
|
||||
let year = date.getFullYear().toString();
|
||||
let month = (date.getMonth() + 1).toString();
|
||||
let day = date.getDate().toString();
|
||||
year = '0'.repeat(4 - year.length).concat(year);
|
||||
month = '0'.repeat(2 - month.length).concat(month);
|
||||
day = '0'.repeat(2 - day.length).concat(day);
|
||||
return ''.concat(year, month, day);
|
||||
}
|
||||
|
||||
export { mapParams, search, processResults };
|
||||
Whitespace-only changes.
@@ -1,3 +1,5 @@
|
||||
import { dicomMetadataStore } from '@ohif/core';
|
||||
|
||||
/**
|
||||
* Factory function that creates a new "Web API" data source.
|
||||
* A "Web API" data source is any source that fetches data over
|
||||
@@ -8,7 +10,7 @@
|
||||
* It's worth noting that a single implementation of this interface
|
||||
* can define different underlying sources for "read" and "write" operations.
|
||||
*/
|
||||
function create({ query, retrieve, retrieveMetadata }) {
|
||||
function create({ query, retrieve }) {
|
||||
const defaultQuery = {
|
||||
studies: {
|
||||
/**
|
||||
@@ -31,12 +33,13 @@ function create({ query, retrieve, retrieveMetadata }) {
|
||||
instances: {},
|
||||
};
|
||||
|
||||
const defaultRetrieve = {};
|
||||
const defaultRetrieve = {
|
||||
series: {},
|
||||
};
|
||||
|
||||
return {
|
||||
query,
|
||||
retrieve: {},
|
||||
// retrieveMetadata: retrieveMetadata.bind(null, dicomMetadataStore);
|
||||
retrieve,
|
||||
// then go get all series level metadata.
|
||||
// Store this in the DICOM MetadataStore.
|
||||
};
|
||||
|
||||
@@ -1,20 +1,104 @@
|
||||
import { createStudyFromSOPInstanceList,
|
||||
addInstancesToStudy } from './studies/services/wado/studyInstanceHelpers';
|
||||
import * as dcmjs from 'dcmjs';
|
||||
import {
|
||||
createStudyFromSOPInstanceList,
|
||||
addInstancesToStudy,
|
||||
} from './studies/services/wado/studyInstanceHelpers';
|
||||
|
||||
const exampleInstances = [{"00080005":{"vr":"CS","Value":["ISO_IR 100"]},"00080008":{"vr":"CS","Value":["ORIGINAL","PRIMARY","LOCALIZER"]},"00080016":{"vr":"UI","Value":["1.2.840.10008.5.1.4.1.1.2"]},"00080018":{"vr":"UI","Value":["1.3.6.1.4.1.25403.345050719074.3824.20170126082902.6"]},"00080020":{"vr":"DA","Value":["20141125"]},"00080021":{"vr":"DA","Value":["20141125"]},"00080022":{"vr":"DA","Value":["20141125"]},"00080023":{"vr":"DA","Value":["20141125"]},"00080030":{"vr":"TM","Value":["094528.000"]},"00080031":{"vr":"TM","Value":["094604.688"]},"00080032":{"vr":"TM","Value":["094623.600"]},"00080033":{"vr":"TM","Value":["094623.600"]},"00080050":{"vr":"SH","Value":["000092218"]},"00080060":{"vr":"CS","Value":["CT"]},"00080070":{"vr":"LO","Value":["TOSHIBA"]},"00080080":{"vr":"LO","Value":["Precision Imaging Metrics"]},"00080090":{"vr":"PN"},"00081010":{"vr":"SH"},"00081030":{"vr":"LO","Value":["DFCI CT CHEST W CONTRAST 6023"]},"00081032":{"vr":"SQ","Value":[{"00080100":{"vr":"SH","Value":["6023"]},"00080102":{"vr":"SH","Value":["GEIIS"]},"00080103":{"vr":"SH","Value":["0"]},"00080104":{"vr":"LO","Value":["DFCI CT CHEST W CONTRAST 6023"]}}]},"0008103E":{"vr":"LO","Value":["2.0"]},"00081040":{"vr":"LO"},"00081070":{"vr":"PN"},"00081090":{"vr":"LO","Value":["Aquilion"]},"00081110":{"vr":"SQ","Value":[{"00081150":{"vr":"UI","Value":["1.2.840.100008.3.1.2.3.1"]},"00081155":{"vr":"UI","Value":["1.3.6.1.4.1.25403.345050719074.3824.20170126082902.4"]}}]},"00100010":{"vr":"PN","Value":[{"Alphabetic":"Venus"}]},"00100020":{"vr":"LO","Value":["0000005"]},"00100021":{"vr":"LO","Value":["001R74:20050625:205502036:195212"]},"00100030":{"vr":"DA"},"00100040":{"vr":"CS","Value":["F"]},"00101000":{"vr":"LO"},"00101010":{"vr":"AS"},"00101020":{"vr":"DS"},"00101030":{"vr":"DS"},"00104000":{"vr":"LT"},"00180015":{"vr":"CS","Value":["CHEST_TO_PELVIS"]},"00180022":{"vr":"CS","Value":["SCANOSCOPE"]},"00180050":{"vr":"DS","Value":[2.0]},"00180060":{"vr":"DS","Value":[120.0]},"00180090":{"vr":"DS","Value":[1000.0]},"00181000":{"vr":"LO"},"00181020":{"vr":"LO","Value":["V4.86ER003"]},"00181030":{"vr":"LO","Value":["Chest / Abdomen/Pelvis 5mm"]},"00181100":{"vr":"DS","Value":[1000.0]},"00181120":{"vr":"DS","Value":[0.0]},"00181130":{"vr":"DS","Value":[102.0]},"00181140":{"vr":"CS","Value":["CW"]},"00181150":{"vr":"IS","Value":[6840]},"00181151":{"vr":"IS","Value":[100]},"00181152":{"vr":"IS","Value":[600]},"00181160":{"vr":"SH","Value":["LARGE"]},"00181170":{"vr":"IS","Value":[12]},"00181190":{"vr":"DS","Value":[1.6,1.4]},"00181210":{"vr":"SH","Value":["FL03"]},"00185100":{"vr":"CS","Value":["FFS"]},"0020000D":{"vr":"UI","Value":["1.3.6.1.4.1.25403.345050719074.3824.20170126082902.1"]},"0020000E":{"vr":"UI","Value":["1.3.6.1.4.1.25403.345050719074.3824.20170126082902.2"]},"00200010":{"vr":"SH"},"00200011":{"vr":"IS","Value":[1]},"00200012":{"vr":"IS","Value":[2]},"00200013":{"vr":"IS","Value":[2]},"00200020":{"vr":"CS","Value":["F","P"]},"00200032":{"vr":"DS","Value":[-1.7E-4,-512.0,1925.0]},"00200037":{"vr":"DS","Value":[0.0,0.0,-1.0,0.0,1.0,-0.0]},"00200052":{"vr":"UI","Value":["1.3.6.1.4.1.25403.345050719074.3824.20170126082902.5"]},"00201040":{"vr":"LO"},"00201041":{"vr":"DS","Value":[342.0]},"00280002":{"vr":"US","Value":[1]},"00280004":{"vr":"CS","Value":["MONOCHROME2"]},"00280010":{"vr":"US","Value":[512]},"00280011":{"vr":"US","Value":[512]},"00280030":{"vr":"DS","Value":[2.0,2.0]},"00280100":{"vr":"US","Value":[16]},"00280101":{"vr":"US","Value":[16]},"00280102":{"vr":"US","Value":[15]},"00280103":{"vr":"US","Value":[1]},"00281050":{"vr":"DS","Value":[110.0]},"00281051":{"vr":"DS","Value":[320.0]},"00281052":{"vr":"DS","Value":[0.0]},"00281053":{"vr":"DS","Value":[1.0]},"00321033":{"vr":"LO","Value":["OUTDFRAD"]},"00400002":{"vr":"DA","Value":["20141125"]},"00400003":{"vr":"TM","Value":["091000"]},"00400004":{"vr":"DA","Value":["20141125"]},"00400005":{"vr":"TM","Value":["094000.000"]},"00400244":{"vr":"DA","Value":["20141125"]},"00400245":{"vr":"TM","Value":["094528.000"]},"00400253":{"vr":"SH","Value":["3708"]},"00400260":{"vr":"SQ","Value":[{"00080100":{"vr":"SH","Value":["6035"]},"00080102":{"vr":"SH","Value":["CCG_CSTemp"]},"00080104":{"vr":"LO","Value":["6035/DFCT2 CT 3-SITES W/OC"]}}]},"00402017":{"vr":"LO","Value":["14159097"]},"7FE00010":{"vr":"OW","BulkDataURI":"http://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.1/series/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.2/instances/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.6"}},{"00080005":{"vr":"CS","Value":["ISO_IR 100"]},"00080008":{"vr":"CS","Value":["ORIGINAL","PRIMARY","LOCALIZER"]},"00080016":{"vr":"UI","Value":["1.2.840.10008.5.1.4.1.1.2"]},"00080018":{"vr":"UI","Value":["1.3.6.1.4.1.25403.345050719074.3824.20170126082902.3"]},"00080020":{"vr":"DA","Value":["20141125"]},"00080021":{"vr":"DA","Value":["20141125"]},"00080022":{"vr":"DA","Value":["20141125"]},"00080023":{"vr":"DA","VLine truncated
|
||||
import displaySetManager from './displaySetManager';
|
||||
|
||||
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
||||
const instances = exampleInstances.map(naturalizeDataset);
|
||||
class StudyMetadata {
|
||||
constructor(StudyInstanceUID) {
|
||||
this.StudyInstanceUID = StudyInstanceUID;
|
||||
this.series = [];
|
||||
}
|
||||
|
||||
addSeries(instances) {
|
||||
this.series.push(new SeriesMetadata(instances));
|
||||
}
|
||||
}
|
||||
|
||||
class SeriesMetadata {
|
||||
constructor(instances) {
|
||||
const { SeriesInstanceUID } = instances[0];
|
||||
this.SeriesInstanceUID = SeriesInstanceUID;
|
||||
this.instances = instances;
|
||||
}
|
||||
}
|
||||
|
||||
const _model = {
|
||||
studies: [],
|
||||
// studies: [{
|
||||
// seriesLists: [
|
||||
// {
|
||||
// // Series in study from dicom web server 1 (or different backend 1)
|
||||
// series: [{
|
||||
// instances: [{
|
||||
// ...instanceMetadata // Naturalized DICOM.
|
||||
// }],
|
||||
// ...seriesMetadata
|
||||
// }],
|
||||
// clientName
|
||||
// },
|
||||
// {
|
||||
// // Series in study from dicom web server 2 (or different backend 2)
|
||||
// },
|
||||
// ],
|
||||
// ...studyMetadata,
|
||||
// }]
|
||||
};
|
||||
|
||||
function _getStudy(StudyInstanceUID) {
|
||||
return _model.studies.find(StudyInstanceUID);
|
||||
}
|
||||
|
||||
function _getSeries(StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = _getStudy(StudyInstanceUID);
|
||||
|
||||
if (!study) {
|
||||
return;
|
||||
}
|
||||
|
||||
return study.series.find(
|
||||
aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) {
|
||||
const series = _getSeries(StudyInstanceUID, SeriesInstanceUID);
|
||||
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
|
||||
return series.instances.find(
|
||||
instance => instance.SOPInstanceUID === SOPInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
const dicomMetadataStore = {
|
||||
onSeriesMetadataLoaded: (callback) => {
|
||||
setTimeout(function() {
|
||||
callback(instances);
|
||||
}, 3000)
|
||||
}
|
||||
addInstances(instances) {
|
||||
const { StudyInstanceUID } = instances[0];
|
||||
|
||||
let study = _model.studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
_model.studies.push(new StudyMetadata(StudyInstanceUID));
|
||||
|
||||
study = _model.studies[_model.studies.length - 1];
|
||||
}
|
||||
|
||||
study.addSeries(instances);
|
||||
},
|
||||
getStudy: _getStudy,
|
||||
getSeries: _getSeries,
|
||||
getInstance: _getInstance,
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
// TODO => Add instances
|
||||
//_addInstance(input) // arraybuffer, or other stuff
|
||||
|
||||
export { dicomMetadataStore };
|
||||
export default dicomMetadataStore;
|
||||
@@ -0,0 +1,92 @@
|
||||
class DisplaySetManager {
|
||||
constructor() {
|
||||
this.displaySets = [];
|
||||
}
|
||||
|
||||
init(extensionManager, SOPClassHandlerIds, viewModelContext) {
|
||||
this.extensionManager = extensionManager;
|
||||
this.SOPClassHandlerIds = SOPClassHandlerIds;
|
||||
|
||||
const {
|
||||
displaySetInstanceUids,
|
||||
setDisplaySetInstanceUids,
|
||||
} = viewModelContext;
|
||||
|
||||
this.displaySetInstanceUids = displaySetInstanceUids;
|
||||
this.setDisplaySetInstanceUids = setDisplaySetInstanceUids;
|
||||
|
||||
// Reset displaySetInstanceUids
|
||||
this.setDisplaySetInstanceUids([]);
|
||||
}
|
||||
|
||||
_addDisplaySets(displaySets) {
|
||||
debugger;
|
||||
//const displayInstanceUids = [...this.displaySetInstanceUids];
|
||||
|
||||
const addedDisplaySetUids = [];
|
||||
|
||||
displaySets.forEach(displaySet => {
|
||||
this.displaySets.push(displaySet);
|
||||
|
||||
addedDisplaySetUids.push(displaySet.displaySetInstanceUid);
|
||||
});
|
||||
|
||||
return addedDisplaySetUids;
|
||||
}
|
||||
|
||||
getDisplaySetsForSeries(SeriesInstanceUID) {
|
||||
return this.displaySets.filter(
|
||||
displaySet => displaySet.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
makeDisplaySets = instances => {
|
||||
if (!instances || !instances.length) {
|
||||
throw new Error('No instances were provided.');
|
||||
}
|
||||
|
||||
const instance = instances[0];
|
||||
|
||||
debugger;
|
||||
|
||||
const existingDisplaySets =
|
||||
this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || [];
|
||||
|
||||
const SOPClassHandlerIds = this.SOPClassHandlerIds;
|
||||
|
||||
for (let i = 0; i < SOPClassHandlerIds.length; i++) {
|
||||
const SOPClassHandlerId = SOPClassHandlerIds[i];
|
||||
const handler = this.extensionManager.getModuleEntry(SOPClassHandlerId);
|
||||
|
||||
if (handler.sopClassUids.includes(instance.SOPClassUID)) {
|
||||
let addedDisplaySetUids;
|
||||
|
||||
// Check if displaySets are already created using this SeriesInstanceUID/SOPClassHandler pair.
|
||||
const cachedDisplaySets = existingDisplaySets.filter(
|
||||
displaySet => displaySet.SOPClassHandlerId === SOPClassHandlerId
|
||||
);
|
||||
|
||||
if (cachedDisplaySets.length) {
|
||||
addedDisplaySetUids = cachedDisplaySets.map(
|
||||
displaySet => displaySet.displaySetInstanceUid
|
||||
);
|
||||
} else {
|
||||
const displaySets = handler.getDisplaySetsFromSeries(instances);
|
||||
|
||||
addedDisplaySetUids = this._addDisplaySets(displaySets);
|
||||
}
|
||||
|
||||
debugger;
|
||||
|
||||
setDisplaySetInstanceUids([
|
||||
...addedDisplaySetUids,
|
||||
...this.displaySetInstanceUids,
|
||||
]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const displaySetManager = new DisplaySetManager();
|
||||
|
||||
export default displaySetManager;
|
||||
@@ -25,15 +25,15 @@ export default class ExtensionManager {
|
||||
*
|
||||
* @param {Object[]} extensions - Array of extensions
|
||||
*/
|
||||
registerExtensions = extensions => {
|
||||
registerExtensions = (extensions, dataSources) => {
|
||||
extensions.forEach(extension => {
|
||||
const hasConfiguration = Array.isArray(extension);
|
||||
|
||||
if (hasConfiguration) {
|
||||
const [ohifExtension, configuration] = extension;
|
||||
this.registerExtension(ohifExtension, configuration);
|
||||
this.registerExtension(ohifExtension, dataSources, configuration);
|
||||
} else {
|
||||
this.registerExtension(extension);
|
||||
this.registerExtension(extension, dataSources);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -44,7 +44,7 @@ export default class ExtensionManager {
|
||||
* @param {Object} extension
|
||||
* @param {Object} configuration
|
||||
*/
|
||||
registerExtension = (extension, configuration = {}) => {
|
||||
registerExtension = (extension, dataSources, configuration = {}) => {
|
||||
if (!extension) {
|
||||
log.warn(
|
||||
'Attempting to register a null/undefined extension. Exiting early.'
|
||||
@@ -89,7 +89,12 @@ export default class ExtensionManager {
|
||||
);
|
||||
|
||||
if (extensionModule) {
|
||||
this._initSpecialModuleTypes(moduleType, extensionModule);
|
||||
this._initSpecialModuleTypes(
|
||||
extensionId,
|
||||
moduleType,
|
||||
extensionModule,
|
||||
dataSources
|
||||
);
|
||||
|
||||
this.modules[moduleType].push({
|
||||
extensionId,
|
||||
@@ -112,9 +117,9 @@ export default class ExtensionManager {
|
||||
return this.modulesMap[stringEntry];
|
||||
};
|
||||
|
||||
getDataSource = dataSourceId => {
|
||||
getDataSources = dataSourceName => {
|
||||
// Note: this currently uses the data source name, which feels weird...
|
||||
return this.dataSourceMap[dataSourceId];
|
||||
return this.dataSourceMap[dataSourceName];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -155,7 +160,12 @@ export default class ExtensionManager {
|
||||
}
|
||||
};
|
||||
|
||||
_initSpecialModuleTypes = (moduleType, extensionModule) => {
|
||||
_initSpecialModuleTypes = (
|
||||
extensionId,
|
||||
moduleType,
|
||||
extensionModule,
|
||||
dataSources
|
||||
) => {
|
||||
switch (moduleType) {
|
||||
case 'commandsModule': {
|
||||
const { definitions, defaultContext } = extensionModule;
|
||||
@@ -168,7 +178,25 @@ export default class ExtensionManager {
|
||||
}
|
||||
case 'dataSourcesModule': {
|
||||
extensionModule.forEach(element => {
|
||||
this.dataSourceMap[element.name] = element;
|
||||
const namespace = `${extensionId}.${moduleType}.${element.name}`;
|
||||
|
||||
dataSources.forEach(dataSource => {
|
||||
if (dataSource.namespace === namespace) {
|
||||
const dataSourceInstance = element.createDataSource(
|
||||
dataSource.configuration
|
||||
);
|
||||
|
||||
if (this.dataSourceMap[dataSource.sourceName]) {
|
||||
this.dataSourceMap[dataSource.sourceName].push(
|
||||
dataSourceInstance
|
||||
);
|
||||
} else {
|
||||
this.dataSourceMap[dataSource.sourceName] = [
|
||||
dataSourceInstance,
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
@@ -19,6 +19,7 @@ import studies from './studies/';
|
||||
import ui from './ui';
|
||||
import user from './user.js';
|
||||
import dicomMetadataStore from './dicomMetadataStore';
|
||||
import displaySetManager from './displaySetManager';
|
||||
import utils, { hotkeys } from './utils/';
|
||||
|
||||
import {
|
||||
@@ -66,6 +67,7 @@ const OHIF = {
|
||||
MeasurementService,
|
||||
IWebApiDataSource,
|
||||
dicomMetadataStore,
|
||||
displaySetManager,
|
||||
};
|
||||
|
||||
export {
|
||||
@@ -102,6 +104,7 @@ export {
|
||||
MeasurementService,
|
||||
IWebApiDataSource,
|
||||
dicomMetadataStore,
|
||||
displaySetManager,
|
||||
};
|
||||
|
||||
export { OHIF };
|
||||
|
||||
@@ -7,8 +7,9 @@ window.config = {
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
{
|
||||
friendlyName: "Rodrigo's DICOMWeb Server",
|
||||
namespace: 'org.ohif.default.datasources.dicomweb',
|
||||
friendlyName: 'dcmjs DICOMWeb Server',
|
||||
namespace: 'org.ohif.default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'dicomweb',
|
||||
configuration: {
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
|
||||
@@ -50,10 +50,10 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
* Example: [ext1, ext2, ext3]
|
||||
* Example2: [[ext1, config], ext2, [ext3, config]]
|
||||
*/
|
||||
extensionManager.registerExtensions([
|
||||
...defaultExtensions,
|
||||
...appConfig.extensions,
|
||||
]);
|
||||
extensionManager.registerExtensions(
|
||||
[...defaultExtensions, ...appConfig.extensions],
|
||||
appConfig.dataSources
|
||||
);
|
||||
|
||||
// TODO: Init global hotkeys, or the hotkeys manager?
|
||||
// TODO: We no longer use `utils.addServer`
|
||||
@@ -69,7 +69,8 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
modes.push(window.exampleMode);
|
||||
}
|
||||
|
||||
const appRoutes = buildModeRoutes(modes, extensionManager) || [];
|
||||
const appRoutes =
|
||||
buildModeRoutes(modes, appConfig.dataSources, extensionManager) || [];
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
import React, { useContext } from 'react';
|
||||
import SOPClassHandlerManager from './SOPClassHandlerManager';
|
||||
import { displaySetManager } from '@ohif/core';
|
||||
import ViewModelContext from './ViewModelContext';
|
||||
import Compose from './Compose';
|
||||
|
||||
export default function ModeRoute({
|
||||
location,
|
||||
mode,
|
||||
dataSourceId,
|
||||
dataSourceName,
|
||||
extensionManager,
|
||||
}) {
|
||||
const { routes, sopClassHandlers, extensions } = mode;
|
||||
const dataSource = extensionManager.getDataSource(dataSourceId);
|
||||
const dataSources = extensionManager.getDataSources(dataSourceName);
|
||||
|
||||
const { displaySetInstanceUids, setDisplaySetInstanceUids } = useContext(
|
||||
ViewModelContext
|
||||
);
|
||||
// TODO: For now assume one unique datasource.
|
||||
|
||||
const dataSource = dataSources[0];
|
||||
|
||||
const viewModelContext = useContext(ViewModelContext);
|
||||
|
||||
console.log(viewModelContext);
|
||||
|
||||
const { displaySetInstanceUids } = viewModelContext;
|
||||
// Deal with toolbar.
|
||||
|
||||
// Only handling one route per mode for now
|
||||
@@ -28,28 +34,25 @@ export default function ModeRoute({
|
||||
const LayoutComponent = layoutTemplateModuleEntry.component;
|
||||
|
||||
// Add SOPClassHandlers to a new SOPClassManager.
|
||||
const manager = new SOPClassHandlerManager(
|
||||
extensionManager,
|
||||
sopClassHandlers
|
||||
);
|
||||
|
||||
/*setInterval(() => {
|
||||
setDisplaySetInstanceUids(manager.displaySets);
|
||||
}, 5000);*/
|
||||
debugger;
|
||||
|
||||
displaySetManager.init(extensionManager, sopClassHandlers, viewModelContext);
|
||||
|
||||
// setInterval(() => {
|
||||
// setDisplaySetInstanceUids(manager.displaySets);
|
||||
// }, 5000);
|
||||
|
||||
const queryParams = location.search;
|
||||
|
||||
// Call the data source to start building the view model?
|
||||
//dataSource(queryParams);
|
||||
//metadataStore.onModified();
|
||||
|
||||
const onUpdatedCallback = () => {
|
||||
// TODO: This should append, not create from scratch so we don't nuke existing display sets
|
||||
// when e.g. a new series arrives
|
||||
//manager.createDisplaySets.then(setDisplaySetInstanceUids);
|
||||
};
|
||||
dataSource.retrieve.series.metadata(
|
||||
queryParams,
|
||||
displaySetManager.makeDisplaySets
|
||||
);
|
||||
|
||||
// For each extension, look up their context modules
|
||||
// TODO: move to extension manager.
|
||||
let contextModules = [];
|
||||
extensions.forEach(extensionId => {
|
||||
const allRegisteredModuleIds = Object.keys(extensionManager.modulesMap);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { dicomMetadataStore } from '@ohif/core';
|
||||
|
||||
export default class SOPClassHandlerManager {
|
||||
constructor(extensionManager, sopClassHandlerIds) {
|
||||
this.SOPClassHandlers = sopClassHandlerIds.map(
|
||||
extensionManager.getModuleEntry
|
||||
);
|
||||
|
||||
dicomMetadataStore.onSeriesMetadataLoaded(this.onSeriesMetadataLoaded);
|
||||
|
||||
// TODO, this is unclear. How are we getting the created display sets out?
|
||||
this.displaySets = [];
|
||||
}
|
||||
|
||||
onSeriesMetadataLoaded = instances => {
|
||||
if (!instances || !instances.length) {
|
||||
throw new Error("No instances were provided.");
|
||||
}
|
||||
|
||||
const SOPClassHandlers = this.SOPClassHandlers;
|
||||
|
||||
for (let i = 0; i < SOPClassHandlers.length; i++) {
|
||||
const handler = SOPClassHandlers[i];
|
||||
|
||||
if (handler.sopClassUids.includes(instances[0].SOPClassUID)) {
|
||||
// TODO: This step is still unclear to me
|
||||
this.displaySets = handler.getDisplaySetFromSeries(instances).map(a => a.displaySetInstanceUid);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -22,28 +22,35 @@ import { ViewModelProvider } from './ViewModelContext';
|
||||
|
||||
/:modeId/:modeRoute/?queryParameters=example
|
||||
*/
|
||||
export default function buildModeRoutes(modes, extensionManager) {
|
||||
export default function buildModeRoutes(modes, dataSources, extensionManager) {
|
||||
const routes = [];
|
||||
|
||||
// TODO: Build api for this.
|
||||
// Currently builds an endpoint for all data sources, which probably
|
||||
// doesn't make sense
|
||||
const dataSources = Object.keys(extensionManager.dataSourceMap).map(a =>
|
||||
extensionManager.getDataSource(a)
|
||||
);
|
||||
// const dataSources = Object.keys(extensionManager.dataSourceMap).map(a =>
|
||||
// extensionManager.getDataSources(a)
|
||||
// );
|
||||
|
||||
const dataSourceNames = [];
|
||||
|
||||
dataSources.forEach(dataSource => {
|
||||
const { sourceName } = dataSource;
|
||||
if (!dataSourceNames.includes(sourceName)) {
|
||||
dataSourceNames.push(sourceName);
|
||||
}
|
||||
});
|
||||
|
||||
modes.forEach(mode => {
|
||||
dataSources.forEach(dataSource => {
|
||||
// todo: for each route. add route to path.
|
||||
dataSourceNames.forEach(dataSourceName => {
|
||||
// TODO: name vs id
|
||||
const dataSourceId = dataSource.name;
|
||||
const path = `/${mode.id}/${dataSourceId}`;
|
||||
const path = `/${mode.id}/${dataSourceName}`;
|
||||
|
||||
// TODO move up.
|
||||
const component = ({ location }) => (
|
||||
<ViewModelProvider>
|
||||
<ModeRoute
|
||||
location={location}
|
||||
mode={mode}
|
||||
dataSourceId={dataSourceId}
|
||||
dataSourceName={dataSourceName}
|
||||
extensionManager={extensionManager}
|
||||
/>
|
||||
</ViewModelProvider>
|
||||
|
||||
Reference in new issue
Block a user