fix: get direct url pixel data should be optional for video (#4152)
This commit is contained in:
parent
35077f1f02
commit
649ffab4d9
26
extensions/default/src/utils/createRenderedRetrieve.js
Normal file
26
extensions/default/src/utils/createRenderedRetrieve.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Generates the rendered URL that can be used for direct retrieve of the pixel data binary stream.
|
||||||
|
*
|
||||||
|
* @param {object} config - The configuration object.
|
||||||
|
* @param {string} config.wadoRoot - The root URL for the WADO service.
|
||||||
|
* @param {object} params - The parameters object.
|
||||||
|
* @param {string} params.tag - The tag name of the URL to retrieve.
|
||||||
|
* @param {string} params.defaultPath - The path for the pixel data URL.
|
||||||
|
* @param {object} params.instance - The instance object that the tag is in.
|
||||||
|
* @param {string} params.defaultType - The mime type of the response.
|
||||||
|
* @param {string} params.singlepart - The type of the part to retrieve.
|
||||||
|
* @param {string} params.fetchPart - Unknown parameter.
|
||||||
|
* @param {string} params.url - Unknown parameter.
|
||||||
|
* @returns {string|Promise<string>} - An absolute URL to the binary stream.
|
||||||
|
*/
|
||||||
|
const createRenderedRetrieve = (config, params) => {
|
||||||
|
const { wadoRoot } = config;
|
||||||
|
const { instance, tag = 'PixelData' } = params;
|
||||||
|
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
||||||
|
|
||||||
|
if (tag === 'PixelData' || tag === 'EncapsulatedDocument') {
|
||||||
|
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createRenderedRetrieve;
|
||||||
46
extensions/default/src/utils/createRenderedRetrieve.test.js
Normal file
46
extensions/default/src/utils/createRenderedRetrieve.test.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import createRenderedRetrieve from './createRenderedRetrieve';
|
||||||
|
|
||||||
|
describe('createRenderedRetrieve', () => {
|
||||||
|
const config = {
|
||||||
|
wadoRoot: 'https://example.com/wado',
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
instance: {
|
||||||
|
StudyInstanceUID: 'study-uid',
|
||||||
|
SeriesInstanceUID: 'series-uid',
|
||||||
|
SOPInstanceUID: 'sop-uid',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return the rendered URL for PixelData tag', () => {
|
||||||
|
const result = createRenderedRetrieve(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'https://example.com/wado/studies/study-uid/series/series-uid/instances/sop-uid/rendered'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the rendered URL for EncapsulatedDocument tag', () => {
|
||||||
|
const result = createRenderedRetrieve(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'EncapsulatedDocument',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'https://example.com/wado/studies/study-uid/series/series-uid/instances/sop-uid/rendered'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for unknown tag', () => {
|
||||||
|
const result = createRenderedRetrieve(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'UnknownTag',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
40
extensions/default/src/utils/getBulkdataValue.js
Normal file
40
extensions/default/src/utils/getBulkdataValue.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Generates a URL that can be used for direct retrieve of the bulkdata.
|
||||||
|
*
|
||||||
|
* @param {object} config - The configuration object.
|
||||||
|
* @param {object} params - The parameters object.
|
||||||
|
* @param {string} params.tag - The tag name of the URL to retrieve.
|
||||||
|
* @param {string} params.defaultPath - The path for the pixel data URL.
|
||||||
|
* @param {object} params.instance - The instance object that the tag is in.
|
||||||
|
* @param {string} params.defaultType - The mime type of the response.
|
||||||
|
* @param {string} params.singlepart - The type of the part to retrieve.
|
||||||
|
* @param {string} params.fetchPart - Unknown.
|
||||||
|
* @returns {string|Promise<string>} - An absolute URL to the resource, if the absolute URL can be retrieved as singlepart,
|
||||||
|
* or is already retrieved, or a promise to a URL for such use if a BulkDataURI.
|
||||||
|
*/
|
||||||
|
const getBulkdataValue = (config, params) => {
|
||||||
|
const {
|
||||||
|
instance,
|
||||||
|
tag = 'PixelData',
|
||||||
|
defaultPath = '/pixeldata',
|
||||||
|
defaultType = 'video/mp4',
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
const value = instance[tag];
|
||||||
|
|
||||||
|
const { SeriesInstanceUID, SOPInstanceUID } = instance;
|
||||||
|
|
||||||
|
const BulkDataURI =
|
||||||
|
(value && value.BulkDataURI) ||
|
||||||
|
`series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`;
|
||||||
|
const hasQuery = BulkDataURI.indexOf('?') !== -1;
|
||||||
|
const hasAccept = BulkDataURI.indexOf('accept=') !== -1;
|
||||||
|
const acceptUri =
|
||||||
|
BulkDataURI + (hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`);
|
||||||
|
|
||||||
|
// The DICOMweb standard states that the default is multipart related, and then
|
||||||
|
// separately states that the accept parameter is the URL parameter equivalent of the accept header.
|
||||||
|
return acceptUri;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getBulkdataValue;
|
||||||
105
extensions/default/src/utils/getBulkdataValue.test.js
Normal file
105
extensions/default/src/utils/getBulkdataValue.test.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import getBulkdataValue from './getBulkdataValue';
|
||||||
|
|
||||||
|
jest.mock('@ohif/core');
|
||||||
|
|
||||||
|
global.URL.createObjectURL = jest.fn(() => 'blob:');
|
||||||
|
|
||||||
|
describe('getBulkdataValue', () => {
|
||||||
|
const config = {
|
||||||
|
singlepart: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
instance: {
|
||||||
|
StudyInstanceUID: 'study-uid',
|
||||||
|
SeriesInstanceUID: 'series-uid',
|
||||||
|
SOPInstanceUID: 'sop-uid',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return the BulkDataURI with defaultType if singlepart is true without accept', () => {
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: 'https://example.com/bulkdata',
|
||||||
|
retrieveBulkData: jest.fn().mockResolvedValueOnce(new Uint8Array([0, 1, 2])),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getBulkdataValue(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toContain(value.BulkDataURI);
|
||||||
|
expect(result).toContain('accept=video/mp4');
|
||||||
|
const acceptCount = result.match(/accept=video\/mp4/g)?.length || 0;
|
||||||
|
expect(acceptCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the BulkDataURI with accept', () => {
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: 'https://example.com/bulkdata?accept=video/mp4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getBulkdataValue(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toContain(value.BulkDataURI);
|
||||||
|
expect(result).toContain('accept=video/mp4');
|
||||||
|
const acceptCount = result.match(/accept=video\/mp4/g)?.length || 0;
|
||||||
|
expect(acceptCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the BulkDataURI with accept and query params', () => {
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: 'https://example.com/bulkdata?test=123',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getBulkdataValue(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toContain(value.BulkDataURI);
|
||||||
|
expect(result).toContain('accept=video/mp4');
|
||||||
|
expect(result).toContain('test=123');
|
||||||
|
const acceptCount = result.match(/accept=video\/mp4/g)?.length || 0;
|
||||||
|
expect(acceptCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return default path with accept', () => {
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultPath = '/testing';
|
||||||
|
const defaultURI = `series/${params.instance.SeriesInstanceUID}/instances/${params.instance.SOPInstanceUID}${defaultPath}`;
|
||||||
|
|
||||||
|
const result = getBulkdataValue(config, {
|
||||||
|
...params,
|
||||||
|
defaultPath,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toContain(defaultURI);
|
||||||
|
expect(result).toContain('accept=video/mp4');
|
||||||
|
const acceptCount = result.match(/accept=video\/mp4/g)?.length || 0;
|
||||||
|
expect(acceptCount).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,5 +1,8 @@
|
|||||||
import { utils } from '@ohif/core';
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
|
import getBulkdataValue from './getBulkdataValue';
|
||||||
|
import createRenderedRetrieve from './createRenderedRetrieve';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a URL that can be used for direct retrieve of the bulkdata
|
* Generates a URL that can be used for direct retrieve of the bulkdata
|
||||||
*
|
*
|
||||||
@ -15,63 +18,48 @@ import { utils } from '@ohif/core';
|
|||||||
* or is already retrieved, or a promise to a URL for such use if a BulkDataURI
|
* or is already retrieved, or a promise to a URL for such use if a BulkDataURI
|
||||||
*/
|
*/
|
||||||
const getDirectURL = (config, params) => {
|
const getDirectURL = (config, params) => {
|
||||||
const { wadoRoot, singlepart } = config;
|
const { singlepart } = config;
|
||||||
const {
|
const {
|
||||||
instance,
|
instance,
|
||||||
tag = 'PixelData',
|
tag = 'PixelData',
|
||||||
defaultPath = '/pixeldata',
|
|
||||||
defaultType = 'video/mp4',
|
defaultType = 'video/mp4',
|
||||||
singlepart: fetchPart = 'video',
|
singlepart: fetchPart = 'video',
|
||||||
url = null,
|
url = null,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = instance[tag];
|
const value = instance[tag];
|
||||||
if (!value) {
|
if (value) {
|
||||||
return undefined;
|
if (value.DirectRetrieveURL) {
|
||||||
}
|
return value.DirectRetrieveURL;
|
||||||
|
}
|
||||||
if (value.DirectRetrieveURL) {
|
|
||||||
return value.DirectRetrieveURL;
|
if (value.InlineBinary) {
|
||||||
}
|
const blob = utils.b64toBlob(value.InlineBinary, defaultType);
|
||||||
if (value.InlineBinary) {
|
value.DirectRetrieveURL = URL.createObjectURL(blob);
|
||||||
const blob = utils.b64toBlob(value.InlineBinary, defaultType);
|
return value.DirectRetrieveURL;
|
||||||
value.DirectRetrieveURL = URL.createObjectURL(blob);
|
}
|
||||||
return value.DirectRetrieveURL;
|
|
||||||
}
|
if (!singlepart || (singlepart !== true && singlepart.indexOf(fetchPart) === -1)) {
|
||||||
if (!singlepart || (singlepart !== true && singlepart.indexOf(fetchPart) === -1)) {
|
if (value.retrieveBulkData) {
|
||||||
if (value.retrieveBulkData) {
|
// Try the specified retrieve type.
|
||||||
// Try the specified retrieve type.
|
const options = {
|
||||||
const options = {
|
mediaType: defaultType,
|
||||||
mediaType: defaultType,
|
};
|
||||||
};
|
return value.retrieveBulkData(options).then(arr => {
|
||||||
return value.retrieveBulkData(options).then(arr => {
|
value.DirectRetrieveURL = URL.createObjectURL(new Blob([arr], { type: defaultType }));
|
||||||
value.DirectRetrieveURL = URL.createObjectURL(new Blob([arr], { type: defaultType }));
|
return value.DirectRetrieveURL;
|
||||||
return value.DirectRetrieveURL;
|
});
|
||||||
});
|
}
|
||||||
|
console.warn('Unable to retrieve', tag, 'from', instance);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
console.warn('Unable to retrieve', tag, 'from', instance);
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
return createRenderedRetrieve(config, params) || getBulkdataValue(config, params);
|
||||||
const BulkDataURI =
|
|
||||||
(value && value.BulkDataURI) ||
|
|
||||||
`series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`;
|
|
||||||
const hasQuery = BulkDataURI.indexOf('?') !== -1;
|
|
||||||
const hasAccept = BulkDataURI.indexOf('accept=') !== -1;
|
|
||||||
const acceptUri =
|
|
||||||
BulkDataURI + (hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`);
|
|
||||||
|
|
||||||
if (tag === 'PixelData' || tag === 'EncapsulatedDocument') {
|
|
||||||
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The DICOMweb standard states that the default is multipart related, and then
|
|
||||||
// separately states that the accept parameter is the URL parameter equivalent of the accept header.
|
|
||||||
return acceptUri;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getDirectURL;
|
export default getDirectURL;
|
||||||
|
|||||||
169
extensions/default/src/utils/getDirectURL.test.js
Normal file
169
extensions/default/src/utils/getDirectURL.test.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import getDirectURL from './getDirectURL';
|
||||||
|
import getBulkdataValue from './getBulkdataValue';
|
||||||
|
import createRenderedRetrieve from './createRenderedRetrieve';
|
||||||
|
|
||||||
|
jest.mock('@ohif/core');
|
||||||
|
jest.mock('./getBulkdataValue');
|
||||||
|
jest.mock('./createRenderedRetrieve');
|
||||||
|
|
||||||
|
global.URL.createObjectURL = jest.fn(() => 'blob:');
|
||||||
|
|
||||||
|
describe('getDirectURL', () => {
|
||||||
|
const config = {
|
||||||
|
singlepart: true,
|
||||||
|
defaultType: 'video/mp4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
tag: 'PixelData',
|
||||||
|
defaultPath: '/path/to/pixeldata',
|
||||||
|
instance: {
|
||||||
|
StudyInstanceUID: 'study-uid',
|
||||||
|
SeriesInstanceUID: 'series-uid',
|
||||||
|
SOPInstanceUID: 'sop-uid',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return the provided URL if it exists', () => {
|
||||||
|
const url = 'https://example.com/direct-retrieve';
|
||||||
|
|
||||||
|
const result = getDirectURL(config, {
|
||||||
|
...params,
|
||||||
|
url: 'https://example.com/direct-retrieve',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the DirectRetrieveURL if it exists', () => {
|
||||||
|
const value = {
|
||||||
|
DirectRetrieveURL: 'https://example.com/direct-retrieve',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getDirectURL(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(value.DirectRetrieveURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the URL for InlineBinary', () => {
|
||||||
|
const value = {
|
||||||
|
InlineBinary: 'base64-encoded-data',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getDirectURL(config, {
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toContain('blob:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the BulkDataURI with defaultType if singlepart is false and there is no retrieveBulkData', () => {
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: 'https://example.com/bulkdata',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getDirectURL(
|
||||||
|
{
|
||||||
|
...config,
|
||||||
|
singlepart: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the BulkDataURI with defaultType if singlepart is false with retrieveBulkData', async () => {
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: 'https://example.com/bulkdata',
|
||||||
|
retrieveBulkData: jest.fn().mockResolvedValueOnce(new Uint8Array([0, 1, 2])),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await getDirectURL(
|
||||||
|
{
|
||||||
|
...config,
|
||||||
|
singlepart: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toContain('blob:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the BulkDataURI with defaultType if singlepart does not include fetchPart', async () => {
|
||||||
|
const arr = new Uint8Array([0, 1, 2]);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
BulkDataURI: 'https://example.com/bulkdata',
|
||||||
|
retrieveBulkData: jest.fn().mockResolvedValueOnce(arr),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await getDirectURL(
|
||||||
|
{
|
||||||
|
...config,
|
||||||
|
singlepart: ['audio'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...params,
|
||||||
|
tag: 'PixelData',
|
||||||
|
instance: {
|
||||||
|
...params.instance,
|
||||||
|
PixelData: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toContain('blob:');
|
||||||
|
expect(URL.createObjectURL).toHaveBeenCalledWith(new Blob([arr], { type: 'accept=video/mp4' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the URL from getBulkdataValue if it exists', () => {
|
||||||
|
const bulkDataURL = 'https://example.com/bulkdata';
|
||||||
|
|
||||||
|
getBulkdataValue.mockReturnValueOnce(bulkDataURL);
|
||||||
|
|
||||||
|
const result = getDirectURL(config, params);
|
||||||
|
|
||||||
|
expect(getBulkdataValue).toHaveBeenCalledWith(config, params);
|
||||||
|
expect(result).toBe(bulkDataURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the URL from createRenderedRetrieve if getBulkdataValue returns falsy', () => {
|
||||||
|
const renderedRetrieveURL = 'https://example.com/rendered-retrieve';
|
||||||
|
|
||||||
|
getBulkdataValue.mockReturnValueOnce(null);
|
||||||
|
createRenderedRetrieve.mockReturnValueOnce(renderedRetrieveURL);
|
||||||
|
|
||||||
|
const result = getDirectURL(config, params);
|
||||||
|
|
||||||
|
expect(getBulkdataValue).toHaveBeenCalledWith(config, params);
|
||||||
|
expect(createRenderedRetrieve).toHaveBeenCalledWith(config, params);
|
||||||
|
expect(result).toBe(renderedRetrieveURL);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user