create dicom web api
This commit is contained in:
parent
97004de446
commit
ee69a5a40b
46
extensions/default/src/DicomWebDataSource/index.js
Normal file
46
extensions/default/src/DicomWebDataSource/index.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import { mapParams, search, processResults } from './qido.js';
|
||||
import IWebApi from '@ohif/core';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} name - Data source name
|
||||
* @param {string} wadoUriRoot - Legacy? (potentially unused/replaced)
|
||||
* @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 IWebApi({
|
||||
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
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { createDicomWebApi };
|
||||
180
extensions/default/src/DicomWebDataSource/qido.js
Normal file
180
extensions/default/src/DicomWebDataSource/qido.js
Normal file
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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 DICOMWeb from '@ohif/core';
|
||||
|
||||
const { getString, getName, getModalities } = 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 };
|
||||
@ -2,36 +2,17 @@
|
||||
// TODO: Use constructor to create an instance of IWebClientApi
|
||||
// TODO: Use existing DICOMWeb configuration (previously, appConfig, to configure instance)
|
||||
|
||||
import { createDicomWebApi } from './DicomWebDataSource/index.js';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function getDataSourcesModule() {
|
||||
return [
|
||||
{
|
||||
name: 'dicomweb-client',
|
||||
name: 'dicomweb',
|
||||
type: 'webApi',
|
||||
/**
|
||||
* TODO: Create JSDoc for this Data Source implementation.
|
||||
*/
|
||||
createDataSource: dataSourceConfiguration => {
|
||||
// instantiate DicomWebClient
|
||||
// const dicomWebClient = new DicomWebClient(dataSourceConfiguration);
|
||||
|
||||
return {
|
||||
query: {
|
||||
studies: {
|
||||
mapParams: params => params,
|
||||
searchStudies: params => {
|
||||
/** dicomWebClient.searchStudies(params) **/
|
||||
},
|
||||
searchSeries: params => {
|
||||
/** dicomWebClient.searchSeries(params) **/
|
||||
},
|
||||
processResults: results => results,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
createDataSource: createDicomWebApi,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
42
platform/core/src/DataSources/IWebApiDataSource.js
Normal file
42
platform/core/src/DataSources/IWebApiDataSource.js
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Factory function that creates a new "Web API" data source.
|
||||
* A "Web API" data source is any source that fetches data over
|
||||
* HTTP. This function serves as an "adapter" to wrap those calls
|
||||
* so that all "Web API" data sources have the same interface and can
|
||||
* be used interchangeably.
|
||||
*
|
||||
* It's worth noting that a single implementation of this interface
|
||||
* can define different underlying sources for "read" and "write" operations.
|
||||
*/
|
||||
function createWebApiDataSource({ query, retrieve }) {
|
||||
const defaultQuery = {
|
||||
studies: {
|
||||
/**
|
||||
* @param {string} params.patientName
|
||||
* @param {string} params.mrn
|
||||
* @param {object} params.studyDate
|
||||
* @param {string} params.description
|
||||
* @param {string} params.modality
|
||||
* @param {string} params.accession
|
||||
* @param {string} params.sortBy
|
||||
* @param {string} params.sortDirection -
|
||||
* @param {number} params.page
|
||||
* @param {number} params.resultsPerPage
|
||||
*/
|
||||
mapParams: params => params,
|
||||
requestResults: () => {},
|
||||
processResults: results => results,
|
||||
},
|
||||
series: {},
|
||||
instances: {},
|
||||
};
|
||||
|
||||
const defaultRetrieve = {};
|
||||
|
||||
return {
|
||||
query,
|
||||
retrieve: {},
|
||||
};
|
||||
}
|
||||
|
||||
export { createWebApiDataSource };
|
||||
Loading…
Reference in New Issue
Block a user