feat: Display series specified by URL parameters in viewer (#2979)
* WIP * WIP * WIP * fixed Bill comments on PR - https://github.com/OHIF/Viewers/pull/2979 * added series number to series filter and removed extra console.log * changed the filter of seriesFilterKeys to studyFilterKeys * changed the seriesInstaceUID to SeriesInstanceUID to make coerence with StudyInstanceUID * reverted change on extension retrieveMetadataLoaderAsync and make the right change on Mode.tsx * fixed PR comments and add small information about the filters Co-authored-by: Thomas Forster <thomasforster@MacBookPro-Thomas.local>
This commit is contained in:
parent
5482de08bc
commit
2507a4b546
@ -70,6 +70,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
|||||||
|
|
||||||
const wadoConfig = {
|
const wadoConfig = {
|
||||||
url: wadoRoot,
|
url: wadoRoot,
|
||||||
|
staticWado,
|
||||||
singlepart,
|
singlepart,
|
||||||
headers: UserAuthenticationService.getAuthorizationHeader(),
|
headers: UserAuthenticationService.getAuthorizationHeader(),
|
||||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||||
@ -80,7 +81,10 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
|||||||
const qidoDicomWebClient = staticWado
|
const qidoDicomWebClient = staticWado
|
||||||
? new StaticWadoClient(qidoConfig)
|
? new StaticWadoClient(qidoConfig)
|
||||||
: new api.DICOMwebClient(qidoConfig);
|
: new api.DICOMwebClient(qidoConfig);
|
||||||
const wadoDicomWebClient = new api.DICOMwebClient(wadoConfig);
|
|
||||||
|
const wadoDicomWebClient = staticWado
|
||||||
|
? new StaticWadoClient(wadoConfig)
|
||||||
|
: new api.DICOMwebClient(wadoConfig);
|
||||||
|
|
||||||
const implementation = {
|
const implementation = {
|
||||||
initialize: ({ params, query }) => {
|
initialize: ({ params, query }) => {
|
||||||
|
|||||||
@ -171,7 +171,7 @@ function mapParams(params, options = {}) {
|
|||||||
// Named
|
// Named
|
||||||
PatientName: withWildcard(params.patientName),
|
PatientName: withWildcard(params.patientName),
|
||||||
//PatientID: withWildcard(params.patientId),
|
//PatientID: withWildcard(params.patientId),
|
||||||
"00100020": withWildcard(params.patientId), // Temporarily to make the tests pass with dicomweb-server.. Apparently it's broken?
|
'00100020': withWildcard(params.patientId), // Temporarily to make the tests pass with dicomweb-server.. Apparently it's broken?
|
||||||
AccessionNumber: withWildcard(params.accessionNumber),
|
AccessionNumber: withWildcard(params.accessionNumber),
|
||||||
StudyDescription: withWildcard(params.studyDescription),
|
StudyDescription: withWildcard(params.studyDescription),
|
||||||
ModalitiesInStudy: params.modalitiesInStudy,
|
ModalitiesInStudy: params.modalitiesInStudy,
|
||||||
|
|||||||
@ -8,14 +8,19 @@ import { api } from 'dicomweb-client';
|
|||||||
* by manually implementing a query option.
|
* by manually implementing a query option.
|
||||||
*/
|
*/
|
||||||
export default class StaticWadoClient extends api.DICOMwebClient {
|
export default class StaticWadoClient extends api.DICOMwebClient {
|
||||||
static filterKeys = {
|
static studyFilterKeys = {
|
||||||
"StudyInstanceUID": "0020000D",
|
StudyInstanceUID: '0020000D',
|
||||||
"PatientName": "00100010",
|
PatientName: '00100010',
|
||||||
"00100020": "mrn",
|
'00100020': 'mrn',
|
||||||
"StudyDescription": "00081030",
|
StudyDescription: '00081030',
|
||||||
"StudyDate": "00080020",
|
StudyDate: '00080020',
|
||||||
"ModalitiesInStudy": "00080061",
|
ModalitiesInStudy: '00080061',
|
||||||
AccessionNumber: "00080050",
|
AccessionNumber: '00080050',
|
||||||
|
};
|
||||||
|
|
||||||
|
static seriesFilterKeys = {
|
||||||
|
SeriesInstanceUID: '0020000E',
|
||||||
|
SeriesNumber: '00200011',
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(qidoConfig) {
|
constructor(qidoConfig) {
|
||||||
@ -36,7 +41,7 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
|||||||
const { queryParams } = options;
|
const { queryParams } = options;
|
||||||
if (!queryParams) return searchResult;
|
if (!queryParams) return searchResult;
|
||||||
const filtered = searchResult.filter(study => {
|
const filtered = searchResult.filter(study => {
|
||||||
for (const key of Object.keys(StaticWadoClient.filterKeys)) {
|
for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) {
|
||||||
if (!this.filterItem(key, queryParams, study)) return false;
|
if (!this.filterItem(key, queryParams, study)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -44,6 +49,22 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchForSeries(options) {
|
||||||
|
if (!this.staticWado) return super.searchForSeries(options);
|
||||||
|
|
||||||
|
let searchResult = await super.searchForSeries(options);
|
||||||
|
const { queryParams } = options;
|
||||||
|
if (!queryParams) return searchResult;
|
||||||
|
const filtered = searchResult.filter(study => {
|
||||||
|
for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) {
|
||||||
|
if (!this.filterItem(key, queryParams, study)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares values, matching any instance of desired to any instance of
|
* Compares values, matching any instance of desired to any instance of
|
||||||
* actual by recursively go through the paired set of values. That is,
|
* actual by recursively go through the paired set of values. That is,
|
||||||
@ -66,7 +87,7 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
|||||||
if (actual?.Alphabetic) {
|
if (actual?.Alphabetic) {
|
||||||
actual = actual.Alphabetic;
|
actual = actual.Alphabetic;
|
||||||
}
|
}
|
||||||
if (typeof (actual) == 'string') {
|
if (typeof actual == 'string') {
|
||||||
if (actual.length === 0) return true;
|
if (actual.length === 0) return true;
|
||||||
if (desired.length === 0 || desired === '*') return true;
|
if (desired.length === 0 || desired === '*') return true;
|
||||||
if (desired[0] === '*' && desired[desired.length - 1] === '*') {
|
if (desired[0] === '*' && desired[desired.length - 1] === '*') {
|
||||||
@ -75,7 +96,10 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
|||||||
} else if (desired[desired.length - 1] === '*') {
|
} else if (desired[desired.length - 1] === '*') {
|
||||||
return actual.indexOf(desired.substring(0, desired.length - 1)) != -1;
|
return actual.indexOf(desired.substring(0, desired.length - 1)) != -1;
|
||||||
} else if (desired[0] === '*') {
|
} else if (desired[0] === '*') {
|
||||||
return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1;
|
return (
|
||||||
|
actual.indexOf(desired.substring(1)) ===
|
||||||
|
actual.length - desired.length + 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return desired === actual;
|
return desired === actual;
|
||||||
@ -88,8 +112,7 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
|||||||
if (dash === -1) return this.compareValues(range, value);
|
if (dash === -1) return this.compareValues(range, value);
|
||||||
const start = range.substring(0, dash);
|
const start = range.substring(0, dash);
|
||||||
const end = range.substring(dash + 1);
|
const end = range.substring(dash + 1);
|
||||||
return (!start || value >= start) &&
|
return (!start || value >= start) && (!end || value <= end);
|
||||||
(!end || value <= end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,13 +124,15 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
filterItem(key, queryParams, study) {
|
filterItem(key, queryParams, study) {
|
||||||
const altKey = StaticWadoClient.filterKeys[key] || key;
|
const altKey = StaticWadoClient.studyFilterKeys[key] || key;
|
||||||
if (!queryParams) return true;
|
if (!queryParams) return true;
|
||||||
const testValue = queryParams[key] || queryParams[altKey];
|
const testValue = queryParams[key] || queryParams[altKey];
|
||||||
if (!testValue) return true;
|
if (!testValue) return true;
|
||||||
const valueElem = study[key] || study[altKey];
|
const valueElem = study[key] || study[altKey];
|
||||||
if (!valueElem) return false;
|
if (!valueElem) return false;
|
||||||
if (valueElem.vr == 'DA') return this.compareDateRange(testValue, valueElem.Value[0]);
|
if (valueElem.vr == 'DA') {
|
||||||
|
return this.compareDateRange(testValue, valueElem.Value[0]);
|
||||||
|
}
|
||||||
const value = valueElem.Value;
|
const value = valueElem.Value;
|
||||||
return this.compareValues(testValue, value) && true;
|
return this.compareValues(testValue, value) && true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,10 +15,11 @@ import Compose from './Compose';
|
|||||||
* @param props.servicesManager to read services from
|
* @param props.servicesManager to read services from
|
||||||
* @param props.studyInstanceUIDs for a list of studies to read
|
* @param props.studyInstanceUIDs for a list of studies to read
|
||||||
* @param props.dataSource to read the data from
|
* @param props.dataSource to read the data from
|
||||||
|
* @param props.filters filters from query params to read the data from
|
||||||
* @returns array of subscriptions to cancel
|
* @returns array of subscriptions to cancel
|
||||||
*/
|
*/
|
||||||
function defaultRouteInit(
|
function defaultRouteInit(
|
||||||
{ servicesManager, studyInstanceUIDs, dataSource },
|
{ servicesManager, studyInstanceUIDs, dataSource, filters },
|
||||||
hangingProtocol
|
hangingProtocol
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
@ -44,7 +45,10 @@ function defaultRouteInit(
|
|||||||
unsubscriptions.push(instanceAddedUnsubscribe);
|
unsubscriptions.push(instanceAddedUnsubscribe);
|
||||||
|
|
||||||
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
||||||
dataSource.retrieve.series.metadata({ StudyInstanceUID })
|
dataSource.retrieve.series.metadata({
|
||||||
|
StudyInstanceUID,
|
||||||
|
filters,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// The hanging protocol matching service is fairly expensive to run multiple
|
// The hanging protocol matching service is fairly expensive to run multiple
|
||||||
@ -230,6 +234,7 @@ export default function ModeRoute({
|
|||||||
if (!layoutTemplateData.current) {
|
if (!layoutTemplateData.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: For some reason this is running before the Providers
|
// TODO: For some reason this is running before the Providers
|
||||||
// are calling setServiceImplementation
|
// are calling setServiceImplementation
|
||||||
// TODO -> iterate through services.
|
// TODO -> iterate through services.
|
||||||
@ -250,8 +255,32 @@ export default function ModeRoute({
|
|||||||
hangingProtocolService.setActiveProtocols(hangingProtocol);
|
hangingProtocolService.setActiveProtocols(hangingProtocol);
|
||||||
mode?.onModeEnter({ servicesManager, extensionManager, commandsManager });
|
mode?.onModeEnter({ servicesManager, extensionManager, commandsManager });
|
||||||
|
|
||||||
|
|
||||||
const setupRouteInit = async () => {
|
const setupRouteInit = async () => {
|
||||||
|
/**
|
||||||
|
* The next line should get all the query parameters provided by the URL
|
||||||
|
* - except the StudyInstaceUIDs - and create an object called filters
|
||||||
|
* used to filtering the study as the user wants otherwise it will return
|
||||||
|
* a empty object.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* const filters = {
|
||||||
|
* seriesInstaceUID: 1.2.276.0.7230010.3.1.3.1791068887.5412.1620253993.114611
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const filters =
|
||||||
|
Array.from(query.keys()).reduce(
|
||||||
|
(acc: Record<string, string>, val: string) => {
|
||||||
|
if (val !== 'StudyInstanceUIDs') {
|
||||||
|
if (['seriesInstanceUID', 'SeriesInstanceUID'].includes(val)) {
|
||||||
|
return { ...acc, seriesInstanceUID: query.get(val) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...acc, [val]: query.get(val) };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
) ?? {};
|
||||||
|
|
||||||
if (route.init) {
|
if (route.init) {
|
||||||
return await route.init(
|
return await route.init(
|
||||||
{
|
{
|
||||||
@ -260,6 +289,7 @@ export default function ModeRoute({
|
|||||||
hotkeysManager,
|
hotkeysManager,
|
||||||
studyInstanceUIDs,
|
studyInstanceUIDs,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
filters,
|
||||||
},
|
},
|
||||||
hangingProtocol
|
hangingProtocol
|
||||||
);
|
);
|
||||||
@ -270,6 +300,7 @@ export default function ModeRoute({
|
|||||||
servicesManager,
|
servicesManager,
|
||||||
studyInstanceUIDs,
|
studyInstanceUIDs,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
filters,
|
||||||
},
|
},
|
||||||
hangingProtocol
|
hangingProtocol
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user