Skip to content

Commit d821e27

Browse files
authored
fix: Retrieve PDF bulkdata, requesting both octet and pdf (#86)
* fix: Retrieve PDF bulkdata, requesting both octet and pdf * PR review comments * PR comments
1 parent 6d6ae85 commit d821e27

File tree

2 files changed

+106
-67
lines changed

2 files changed

+106
-67
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module.exports = {
22
root: true,
33
extends: ['airbnb-base', 'prettier'],
44
rules: {
5-
'import/extensions': 1, // Better for native ES Module usage
5+
'import/extensions': "always", // Better for native ES Module usage
66
'no-console': 0, // We can remove this later
77
'no-underscore-dangle': 0,
88
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],

src/api.js

Lines changed: 105 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ const MEDIATYPES = {
3737
PNG: 'image/png',
3838
};
3939

40+
/**
41+
* debugLog is a function that can be called with console.log arguments, and will
42+
* be conditionally displayed, only when debug logging is enabled.
43+
*/
44+
let debugLog = () => {};
45+
4046
/**
4147
* @typedef { import("../types/types").InstanceMetadata } InstanceMetadata
4248
*/
@@ -66,6 +72,7 @@ class DICOMwebClient {
6672
* @param {Object=} options.headers - HTTP headers
6773
* @param {Array.<RequestHook>=} options.requestHooks - Request hooks.
6874
* @param {Object=} options.verbose - print to console request warnings and errors, default true
75+
* @param {Object=} options.debug - print to the console debug level information/status updates.
6976
* @param {boolean|String} options.singlepart - retrieve singlepart for the named types.
7077
* The available types are: bulkdata, video, image. true means all.
7178
*/
@@ -86,28 +93,28 @@ class DICOMwebClient {
8693
}
8794

8895
if ('qidoURLPrefix' in options) {
89-
console.log(`use URL prefix for QIDO-RS: ${options.qidoURLPrefix}`);
96+
debugLog(`use URL prefix for QIDO-RS: ${options.qidoURLPrefix}`);
9097
this.qidoURL = `${this.baseURL}/${options.qidoURLPrefix}`;
9198
} else {
9299
this.qidoURL = this.baseURL;
93100
}
94101

95102
if ('wadoURLPrefix' in options) {
96-
console.log(`use URL prefix for WADO-RS: ${options.wadoURLPrefix}`);
103+
debugLog(`use URL prefix for WADO-RS: ${options.wadoURLPrefix}`);
97104
this.wadoURL = `${this.baseURL}/${options.wadoURLPrefix}`;
98105
} else {
99106
this.wadoURL = this.baseURL;
100107
}
101108

102109
if ('stowURLPrefix' in options) {
103-
console.log(`use URL prefix for STOW-RS: ${options.stowURLPrefix}`);
110+
debugLog(`use URL prefix for STOW-RS: ${options.stowURLPrefix}`);
104111
this.stowURL = `${this.baseURL}/${options.stowURLPrefix}`;
105112
} else {
106113
this.stowURL = this.baseURL;
107114
}
108115

109116
if (options.singlepart) {
110-
console.log('use singlepart', options.singlepart);
117+
debugLog('use singlepart', options.singlepart);
111118
this.singlepart = options.singlepart === true ? 'bulkdata,video,image' : options.singlepart;
112119
} else {
113120
this.singlepart = '';
@@ -125,8 +132,33 @@ class DICOMwebClient {
125132

126133
// Verbose - print to console request warnings and errors, default true
127134
this.verbose = options.verbose !== false;
135+
136+
this.setDebug(options.debug);
137+
138+
139+
}
140+
141+
/**
142+
* Allows setting the debug log information.
143+
* Note this is different from verbose in that verbose is whether to include warning/error information, defaulting to true
144+
*
145+
* @param {boolean} debugLevel
146+
* @param {function} debugLogFunction to call with the debug output arguments.
147+
*/
148+
setDebug(debugLevel = false, debugLogFunction = null) {
149+
this.debugLevel = !!debugLevel;
150+
debugLog = debugLogFunction || debugLevel ? console.log : () => {};
128151
}
129152

153+
/**
154+
* Gets debug flag
155+
*
156+
* @returns true if debug logging is enabled
157+
*/
158+
getDebug() {
159+
return this.debugLevel;
160+
}
161+
130162
/**
131163
* Sets verbose flag.
132164
*
@@ -194,12 +226,12 @@ class DICOMwebClient {
194226

195227
// Event triggered when upload starts
196228
request.onloadstart = function onloadstart() {
197-
// console.log('upload started: ', url)
229+
debugLog('upload started: ', url)
198230
};
199231

200232
// Event triggered when upload ends
201233
request.onloadend = function onloadend() {
202-
// console.log('upload finished')
234+
debugLog('upload finished')
203235
};
204236

205237
// Handle response message
@@ -699,7 +731,8 @@ class DICOMwebClient {
699731

700732
/**
701733
* Performs an HTTP GET request that accepts a multipart message
702-
* with a application/octet-stream media type.
734+
* with a application/octet-stream, OR any of the equivalencies for that (eg
735+
* application/pdf etc)
703736
*
704737
* @param {String} url - Unique resource locator
705738
* @param {Object[]} mediaTypes - Acceptable media types and optionally the UIDs of the
@@ -721,7 +754,7 @@ class DICOMwebClient {
721754
const headers = {};
722755
const defaultMediaType = 'application/octet-stream';
723756
const supportedMediaTypes = {
724-
'1.2.840.10008.1.2.1': [defaultMediaType],
757+
'1.2.840.10008.1.2.1': [...Object.values(MEDIATYPES)],
725758
};
726759

727760
let acceptableMediaTypes = mediaTypes;
@@ -826,13 +859,16 @@ class DICOMwebClient {
826859
}
827860

828861
/**
829-
* Builds an accept header field value for HTTP GET multipart request
830-
messages.
831-
*
832-
* @param {Object[]} mediaTypes - Acceptable media types
833-
* @param {Object[]} supportedMediaTypes - Supported media types
834-
* @private
835-
*/
862+
* Builds an accept header field value for HTTP GET multipart request
863+
* messages. Will throw an exception if no media types are found which are acceptable,
864+
* but will only log a verbose level message when types are specified which are
865+
* not acceptable. This allows requesting several types with having to know
866+
* whether they are all acceptable or not.
867+
*
868+
* @param {Object[]} mediaTypes - Acceptable media types
869+
* @param {Object[]} supportedMediaTypes - Supported media types
870+
* @private
871+
*/
836872
static _buildMultipartAcceptHeaderFieldValue(
837873
mediaTypes,
838874
supportedMediaTypes,
@@ -863,9 +899,10 @@ class DICOMwebClient {
863899
.includes(mediaType)
864900
) {
865901
if (!mediaType.endsWith('/*') || !mediaType.endsWith('/')) {
866-
throw new Error(
902+
debugLog(
867903
`Media type ${mediaType} is not supported for requested resource`,
868904
);
905+
return;
869906
}
870907
}
871908

@@ -907,14 +944,21 @@ class DICOMwebClient {
907944
Array.isArray(supportedMediaTypes) &&
908945
!supportedMediaTypes.includes(mediaType)
909946
) {
910-
throw new Error(
911-
`Media type ${mediaType} is not supported for requested resource`,
912-
);
947+
if( this.verbose ) {
948+
console.warn(
949+
`Media type ${mediaType} is not supported for requested resource`,
950+
);
951+
}
952+
return;
913953
}
914954

915955
fieldValueParts.push(fieldValue);
916956
});
917957

958+
if( !fieldValueParts.length ) {
959+
throw new Error(`No acceptable media types found among ${JSON.stringify(mediaTypes)}`);
960+
}
961+
918962
return fieldValueParts.join(', ');
919963
}
920964

@@ -961,15 +1005,15 @@ class DICOMwebClient {
9611005
}
9621006

9631007
/**
964-
* Gets common type of acceptable media types and asserts that only
1008+
* Gets common base type of acceptable media types and asserts that only
9651009
one type is specified. For example, ``("image/jpeg", "image/jp2")``
9661010
will pass, but ``("image/jpeg", "video/mpeg2")`` will raise an
9671011
exception.
9681012
*
9691013
* @param {Object[]} mediaTypes - Acceptable media types and optionally the UIDs of the
9701014
corresponding transfer syntaxes
9711015
* @private
972-
* @returns {String[]} Common media type
1016+
* @returns {String[]} Common media type, eg `image/` for the above example.
9731017
*/
9741018
static _getCommonMediaType(mediaTypes) {
9751019
if (!mediaTypes || !mediaTypes.length) {
@@ -994,7 +1038,7 @@ class DICOMwebClient {
9941038
* @return {Object[]} Study representations (http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_6.7.html#table_6.7.1-2)
9951039
*/
9961040
searchForStudies(options = {}) {
997-
console.log('search for studies');
1041+
debugLog('search for studies');
9981042
let withCredentials = false;
9991043
let url = `${this.qidoURL}/studies`;
10001044
if ('queryParams' in options) {
@@ -1022,7 +1066,7 @@ class DICOMwebClient {
10221066
'Study Instance UID is required for retrieval of study metadata',
10231067
);
10241068
}
1025-
console.log(`retrieve metadata of study ${options.studyInstanceUID}`);
1069+
debugLog(`retrieve metadata of study ${options.studyInstanceUID}`);
10261070
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/metadata`;
10271071
let withCredentials = false;
10281072
if ('withCredentials' in options) {
@@ -1044,7 +1088,7 @@ class DICOMwebClient {
10441088
searchForSeries(options = {}) {
10451089
let url = this.qidoURL;
10461090
if ('studyInstanceUID' in options) {
1047-
console.log(`search series of study ${options.studyInstanceUID}`);
1091+
debugLog(`search series of study ${options.studyInstanceUID}`);
10481092
url += `/studies/${options.studyInstanceUID}`;
10491093
}
10501094
url += '/series';
@@ -1081,7 +1125,7 @@ class DICOMwebClient {
10811125
);
10821126
}
10831127

1084-
console.log(`retrieve metadata of series ${options.seriesInstanceUID}`);
1128+
debugLog(`retrieve metadata of series ${options.seriesInstanceUID}`);
10851129
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${options.seriesInstanceUID}/metadata`;
10861130
let withCredentials = false;
10871131
if ('withCredentials' in options) {
@@ -1107,17 +1151,17 @@ class DICOMwebClient {
11071151
if ('studyInstanceUID' in options) {
11081152
url += `/studies/${options.studyInstanceUID}`;
11091153
if ('seriesInstanceUID' in options) {
1110-
console.log(
1154+
debugLog(
11111155
`search for instances of series ${options.seriesInstanceUID}`,
11121156
);
11131157
url += `/series/${options.seriesInstanceUID}`;
11141158
} else {
1115-
console.log(
1159+
debugLog(
11161160
`search for instances of study ${options.studyInstanceUID}`,
11171161
);
11181162
}
11191163
} else {
1120-
console.log('search for instances');
1164+
debugLog('search for instances');
11211165
}
11221166
url += '/instances';
11231167
if ('queryParams' in options) {
@@ -1191,7 +1235,7 @@ class DICOMwebClient {
11911235
'SOP Instance UID is required for retrieval of instance metadata',
11921236
);
11931237
}
1194-
console.log(`retrieve metadata of instance ${options.sopInstanceUID}`);
1238+
debugLog(`retrieve metadata of instance ${options.sopInstanceUID}`);
11951239
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${options.seriesInstanceUID}/instances/${options.sopInstanceUID}/metadata`;
11961240
let withCredentials = false;
11971241
if ('withCredentials' in options) {
@@ -1232,7 +1276,7 @@ class DICOMwebClient {
12321276
'frame numbers are required for retrieval of instance frames',
12331277
);
12341278
}
1235-
console.log(
1279+
debugLog(
12361280
`retrieve frames ${options.frameNumbers.toString()} of instance ${
12371281
options.sopInstanceUID
12381282
}`,
@@ -1287,6 +1331,8 @@ class DICOMwebClient {
12871331
'1.2.840.10008.1.2.4.91': ['image/jp2'],
12881332
'1.2.840.10008.1.2.4.92': ['image/jpx'],
12891333
'1.2.840.10008.1.2.4.93': ['image/jpx'],
1334+
'1.2.840.10008.1.2.4.201': ['image/jhc'],
1335+
'1.2.840.10008.1.2.4.202': ['image/jhc'],
12901336
};
12911337

12921338
const headers = {
@@ -1554,7 +1600,7 @@ class DICOMwebClient {
15541600
);
15551601
}
15561602

1557-
console.debug(
1603+
debugLog(
15581604
`retrieve rendered frames ${options.frameNumbers.toString()} of instance ${
15591605
options.sopInstanceUID
15601606
}`,
@@ -1881,43 +1927,34 @@ class DICOMwebClient {
18811927
return this._httpGet(url, options.headers, 'arraybuffer', null, withCredentials);
18821928
}
18831929

1884-
if (!mediaTypes) {
1885-
return this._httpGetMultipartApplicationOctetStream(
1886-
url,
1887-
mediaTypes,
1888-
byteRange,
1889-
false,
1890-
false,
1891-
withCredentials,
1892-
);
1893-
}
1894-
1895-
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes);
1896-
1897-
if (commonMediaType === MEDIATYPES.OCTET_STREAM) {
1898-
return this._httpGetMultipartApplicationOctetStream(
1899-
url,
1900-
mediaTypes,
1901-
byteRange,
1902-
false,
1903-
progressCallback,
1904-
withCredentials,
1905-
);
1906-
}
1907-
if (commonMediaType.startsWith('image')) {
1908-
return this._httpGetMultipartImage(
1909-
url,
1910-
mediaTypes,
1911-
byteRange,
1912-
false,
1913-
false,
1914-
progressCallback,
1915-
withCredentials,
1916-
);
1930+
if (mediaTypes) {
1931+
try {
1932+
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes);
1933+
1934+
if (commonMediaType==='image/') {
1935+
return this._httpGetMultipartImage(
1936+
url,
1937+
mediaTypes,
1938+
byteRange,
1939+
false,
1940+
false,
1941+
progressCallback,
1942+
withCredentials,
1943+
);
1944+
}
1945+
} catch(e) {
1946+
// No-op - this happens sometimes if trying to fetch the specific desired type but want to fallback to octet-stream
1947+
}
19171948
}
19181949

1919-
throw new Error(
1920-
`Media type ${commonMediaType} is not supported for retrieval of bulk data.`,
1950+
// Just use the media types provided
1951+
return this._httpGetMultipartApplicationOctetStream(
1952+
url,
1953+
mediaTypes,
1954+
byteRange,
1955+
false,
1956+
progressCallback,
1957+
withCredentials,
19211958
);
19221959
}
19231960

@@ -1954,6 +1991,8 @@ class DICOMwebClient {
19541991
options.request,
19551992
);
19561993
}
1994+
1995+
19571996
}
19581997

19591998

0 commit comments

Comments
 (0)