Chore/kill yalc with fire (#629)
* init * more shifting * Move @babel dependencies up to workspace root * Begin updating dependent libraries to use sync'd webpack builds w/ watches * Commit changes before a long weekend * Split packages into platform and extensions * Consolidate how/where we specify file/module type entrypoints * Make it possible to pass in extensions as App props * Resolve viewer's modules * Target for UMD bundle * Add Webpack Stylus loader * Provide WebPack build options for microscopy, vtk, and ui * Better entrypoint for extensions * Push changes up to switch PCs * Fix ESM symlink build for Viewers * Shift docs to root * Specify additional lerna config props * Begin providing basic readme info * Rename example extension folder * Shift links to bottom of doc * First attempt at a netlify deploy preview * Attempt to fix deploy preview build * push workspace enabled to initial command; remove second yarn install; use npx to call gitbook cli commands * Try to use global gitbook-cli * Export node_modules path * More agressive with modifying PATH * Follow deploy log output advice * try again to set bin path * Try swapping bash with a new shell * Try bash -l instead of exec bash * Annd let's try again * and again * We may have figured it out johnny, boy * We need to pull cornerstone-wado-image-loader files from hoisted node_modules * Attempt to fix ticks/escapes * Change copy syntax; try running tests on viewers from root for circleci * Lower version to match circleci image * Try alternative jest-canvas-mock location and version file syntax * Run version command instead of calling node directly * Try alternative jest transform * Set path and enable workspaces * Add codecov flags * Use cpx so our copying finishes? * Get all platform unit tests to run * Run and report individually and in parallel * Try once relying on codecov yaml to split w/ flags * Add projects to split by flags * Generate separate reports * Trigger codecov after everything has finished running; these may not support flags * Use recommended flags from issue comments for codecov * Fix typo * Remove clear flag * Shift back to calling codecov from root * Generate example for codecov issue * Try fixing paths * Simplify * Try using codecov orb * Bump circleci version * Upload core and viewer * Use full string paths * Remove individual codecov calls * Fix dir * Combine files before upload * Escape anchors * tryfix syntax * Escape string literal * Combine lines to reduce path * long paths * Use home alias * Fix filename * Try to see the contents of our cat'd file * Also upload `core` * Try running with aliased folder * Fix path; split PR and Merge unit tests into separate jobs * Set default threshold * Fix typo * Changing to scoped package names * Adding note * Catch more updates * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.0 - @ohif/extension-dicom-html@0.0.4-alpha.0 - @ohif/extension-dicom-microscopy@0.0.9-alpha.0 - @ohif/extension-dicom-pdf@0.0.8-alpha.0 - @ohif/extension-vtk@0.1.4-alpha.0 - @ohif/core@0.11.1-alpha.0 - @ohif/i18n@0.2.3-alpha.0 - @ohif/ui@0.2.18-alpha.0 - @ohif/viewer@0.0.22-alpha.0 * Clean up primary readme * Support for dev and dev:* commands * Shift build command; satisfy default PWA build * Remove old scripts * Clean up UI to set webpack scripts * Tidy up project links * Clean duplicate code in UI project * Update UI project's docs * Clean up ui and i18n config * Reduce duplicate code in `@ohif/core` * Fix @ohif/ui versions * bump @ohif/core versions * Redux testkit dep * Remove unused/duplicate config files for projects/packages * ohif-core --> @ohif/core * Safer publish command * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.1 - @ohif/extension-dicom-html@0.0.4-alpha.1 - @ohif/extension-dicom-microscopy@0.0.9-alpha.1 - @ohif/extension-dicom-pdf@0.0.8-alpha.1 - @ohif/extension-vtk@0.1.4-alpha.1 - @ohif/core@0.11.1-alpha.1 - @ohif/i18n@0.2.3-alpha.1 - @ohif/ui@0.2.18-alpha.1 - @ohif/viewer@0.0.22-alpha.1 * test version bump * Add global prettier config * chore(release): publish %s [skip ci] - @ohif/viewer@0.0.22-alpha.2 * test lerna publish * Update package.jsons * Fix build scripts * catch changes * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.2 - @ohif/extension-dicom-html@0.0.4-alpha.2 - @ohif/extension-dicom-microscopy@0.0.9-alpha.2 - @ohif/extension-dicom-pdf@0.0.8-alpha.2 - @ohif/extension-vtk@0.1.4-alpha.2 - @ohif/core@0.11.1-alpha.2 - @ohif/i18n@0.2.3-alpha.2 - @ohif/ui@0.2.18-alpha.2 - @ohif/viewer@0.0.22-alpha.3 * fix eslint issues caused by eslint 6 and our react-app config * Publish with canary tag * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.3 - @ohif/extension-dicom-html@0.0.4-alpha.3 - @ohif/extension-dicom-microscopy@0.0.9-alpha.3 - @ohif/extension-dicom-pdf@0.0.8-alpha.3 - @ohif/extension-vtk@0.1.4-alpha.3 - @ohif/core@0.11.1-alpha.3 - @ohif/i18n@0.2.3-alpha.3 - @ohif/ui@0.2.18-alpha.3 - @ohif/viewer@0.0.22-alpha.4 * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.4 - @ohif/extension-dicom-html@0.0.4-alpha.4 - @ohif/extension-dicom-microscopy@0.0.9-alpha.4 - @ohif/extension-dicom-pdf@0.0.8-alpha.4 - @ohif/extension-vtk@0.1.4-alpha.4 - @ohif/core@0.11.1-alpha.4 - @ohif/i18n@0.2.3-alpha.4 - @ohif/ui@0.2.18-alpha.4 - @ohif/viewer@0.0.22-alpha.5 * Simplify build * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.5 - @ohif/extension-dicom-html@0.0.4-alpha.5 - @ohif/extension-dicom-microscopy@0.0.9-alpha.5 - @ohif/extension-dicom-pdf@0.0.8-alpha.5 - @ohif/extension-vtk@0.1.4-alpha.5 - @ohif/core@0.11.1-alpha.5 - @ohif/i18n@0.2.3-alpha.5 - @ohif/ui@0.2.18-alpha.5 - @ohif/viewer@0.0.22-alpha.6 * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.6 - @ohif/extension-dicom-html@0.0.4-alpha.6 - @ohif/extension-dicom-microscopy@0.0.9-alpha.6 - @ohif/extension-dicom-pdf@0.0.8-alpha.6 - @ohif/extension-vtk@0.1.4-alpha.6 - @ohif/core@0.11.1-alpha.6 - @ohif/i18n@0.2.3-alpha.6 - @ohif/ui@0.2.18-alpha.6 - @ohif/viewer@0.0.22-alpha.7 * Split version and publish * chore(release): publish %s [skip ci] - @ohif/extension-cornerstone@0.0.39-alpha.7 - @ohif/extension-dicom-html@0.0.4-alpha.7 - @ohif/extension-dicom-microscopy@0.0.9-alpha.7 - @ohif/extension-dicom-pdf@0.0.8-alpha.7 - @ohif/extension-vtk@0.1.4-alpha.7 - @ohif/core@0.11.1-alpha.7 - @ohif/i18n@0.2.3-alpha.7 - @ohif/ui@0.2.18-alpha.7 - @ohif/viewer@0.0.22-alpha.8 * Try simplify netlify build * react-viewerbase --> @ohif/ui * add test for stylus loader * Try new way to specify publish path * Use an output folder that won't be ignored * Try move command * fiddle with paths * Dig for some information * Make sure we're in root for our commands * Remove discovery * Setup script-tag example * Fix typo * Move config folder * Update cornerstone extension to use shared/root webpack configs * capture changes * Update extension webpack configs * Don't check in docz output * Fix babel runtime resolution issue * Simplify ui build steps * Push in progress * Shift directories * Get viewer dev path to work * Fix viewer commonjs build * Fix build pwa config for viewers * Make sure our package specific command can be called from root * Update notes * Fix imports and misc. merge notes * Bump wado-image-loader version * Switch up commands to remove webworkers copy * Fix CI config * Fix more broken package references * Add react-dropzone * Persist so e2e test can use * Fix lockfile * Use cypress orb to simplify CI * Fix config * Try again * Install yarn latest for orb * Install yarn a different way * Wait on correct port * Run tests in viewer project * Fix cypress config * Make Cypress happier - use default location * Simplify e2e testing * Capture UI changes since fork * Catch core up * Grab latest deps after merge * Fix broken import * Make sure cornerstone-wado-image-loader resolves to 3+ * Note regarding orbs * Fix refresh on 404 routes * Fix odd error for cornerstone-wado-image-loader * Different target for UMD * Simplify dev script * Try to get fancy with netlify output * Let's do it again * Try to fix measurementTools issue * See if we can get our lerna magic to work on merge to monorepo branch
This commit is contained in:
1 parent
cac911f08e
commit
08ff214ea6
919 files changed
+45652
-52097
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,174 @@
|
||||
import OHIFError from '../classes/OHIFError.js';
|
||||
import getImageId from './getImageId';
|
||||
|
||||
let stackMap = {};
|
||||
let configuration = {};
|
||||
let stackManagerMetaDataProvider;
|
||||
const stackUpdatedCallbacks = [];
|
||||
|
||||
/**
|
||||
* Loop through the current series and add metadata to the
|
||||
* Cornerstone meta data provider. This will be used to fill information
|
||||
* into the viewport overlays, and to calculate reference lines and orientation markers
|
||||
* @param {Object} stackMap stackMap object
|
||||
* @param {Object} study Study object
|
||||
* @param {Object} displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
function createAndAddStack(
|
||||
stackMap,
|
||||
study,
|
||||
displaySet,
|
||||
stackUpdatedCallbacks,
|
||||
metadataProvider
|
||||
) {
|
||||
const images = displaySet.images;
|
||||
if (!images) {
|
||||
return;
|
||||
}
|
||||
|
||||
const numImages = images.length;
|
||||
const imageIds = [];
|
||||
let imageId;
|
||||
|
||||
displaySet.images.forEach((instance, imageIndex) => {
|
||||
const image = instance.getData();
|
||||
const metaData = {
|
||||
instance: image, // in this context, instance will be the data of the InstanceMetadata object...
|
||||
series: displaySet, // TODO: Check this
|
||||
study,
|
||||
numImages,
|
||||
imageIndex: imageIndex + 1,
|
||||
};
|
||||
|
||||
const numberOfFrames = image.numberOfFrames;
|
||||
if (numberOfFrames > 1) {
|
||||
for (let i = 0; i < numberOfFrames; i++) {
|
||||
metaData.frameNumber = i;
|
||||
imageId = getImageId(image, i);
|
||||
imageIds.push(imageId);
|
||||
metadataProvider.addMetadata(imageId, metaData);
|
||||
}
|
||||
} else {
|
||||
metaData.frameNumber = 1;
|
||||
imageId = getImageId(image);
|
||||
imageIds.push(imageId);
|
||||
metadataProvider.addMetadata(imageId, metaData);
|
||||
}
|
||||
});
|
||||
|
||||
const stack = {
|
||||
studyInstanceUid: study.studyInstanceUid,
|
||||
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
||||
imageIds,
|
||||
frameRate: displaySet.frameRate,
|
||||
isClip: displaySet.isClip,
|
||||
};
|
||||
|
||||
stackMap[displaySet.displaySetInstanceUid] = stack;
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
configuration = {
|
||||
createAndAddStack,
|
||||
};
|
||||
|
||||
/**
|
||||
* This object contains all the functions needed for interacting with the stack manager.
|
||||
* Generally, findStack is the only function used. If you want to know when new stacks
|
||||
* come in, you can register a callback with addStackUpdatedCallback.
|
||||
*/
|
||||
const StackManager = {
|
||||
setMetadataProvider(provider) {
|
||||
stackManagerMetaDataProvider = provider;
|
||||
},
|
||||
/**
|
||||
* Removes all current stacks
|
||||
*/
|
||||
clearStacks() {
|
||||
stackMap = {};
|
||||
},
|
||||
/**
|
||||
* Create a stack from an image set, as well as add in the metadata on a per image bases.
|
||||
* @param study The study who's metadata will be added
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
makeAndAddStack(study, displaySet) {
|
||||
if (!stackManagerMetaDataProvider) {
|
||||
throw new Error(
|
||||
'Please call StackManager.setMetadataProvider(provider) first.'
|
||||
);
|
||||
}
|
||||
|
||||
return configuration.createAndAddStack(
|
||||
stackMap,
|
||||
study,
|
||||
displaySet,
|
||||
stackUpdatedCallbacks,
|
||||
stackManagerMetaDataProvider
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Find a stack from the currently created stacks.
|
||||
* @param displaySetInstanceUid The UID of the stack to find.
|
||||
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
||||
*/
|
||||
findStack(displaySetInstanceUid) {
|
||||
return stackMap[displaySetInstanceUid];
|
||||
},
|
||||
/**
|
||||
* Find a stack or reate one if it has not been created yet
|
||||
* @param study The study who's metadata will be added
|
||||
* @param displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
findOrCreateStack(study, displaySet) {
|
||||
let stack = this.findStack(displaySet.displaySetInstanceUid);
|
||||
|
||||
if (!stack || !stack.imageIds) {
|
||||
stack = this.makeAndAddStack(study, displaySet);
|
||||
}
|
||||
|
||||
return stack;
|
||||
},
|
||||
/**
|
||||
* Gets the underlying map of displaySetInstanceUid to stack object.
|
||||
* WARNING: Do not change this object. It directly affects the manager.
|
||||
* @returns {{}} map of displaySetInstanceUid -> stack.
|
||||
*/
|
||||
getAllStacks() {
|
||||
return stackMap;
|
||||
},
|
||||
/**
|
||||
* Adds in a callback to be called on a stack being added / updated.
|
||||
* @param callback must accept at minimum one argument,
|
||||
* which is the stack that was added / updated.
|
||||
*/
|
||||
addStackUpdatedCallback(callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new OHIFError('callback must be provided as a function');
|
||||
}
|
||||
stackUpdatedCallbacks.push(callback);
|
||||
},
|
||||
/**
|
||||
* Return configuration
|
||||
*/
|
||||
getConfiguration() {
|
||||
return configuration;
|
||||
},
|
||||
/**
|
||||
* Set configuration, in order to provide compatibility
|
||||
* with other systems by overriding this functions
|
||||
* @param {Object} config object with functions to be overrided
|
||||
*
|
||||
* For now, only makeAndAddStack can be overrided
|
||||
*/
|
||||
setConfiguration(config) {
|
||||
configuration = config;
|
||||
},
|
||||
};
|
||||
|
||||
export { StackManager };
|
||||
export default StackManager;
|
||||
@@ -0,0 +1,20 @@
|
||||
const absoluteUrl = path => {
|
||||
let absolutePath = '/';
|
||||
|
||||
if (!path) return absolutePath;
|
||||
|
||||
// TODO: Find another way to get root url
|
||||
const absoluteUrl = window.location.origin;
|
||||
const absoluteUrlParts = absoluteUrl.split('/');
|
||||
|
||||
if (absoluteUrlParts.length > 4) {
|
||||
const rootUrlPrefixIndex = absoluteUrl.indexOf(absoluteUrlParts[3]);
|
||||
absolutePath += absoluteUrl.substring(rootUrlPrefixIndex) + path;
|
||||
} else {
|
||||
absolutePath += path;
|
||||
}
|
||||
|
||||
return absolutePath.replace(/\/\/+/g, '/');
|
||||
};
|
||||
|
||||
export default absoluteUrl;
|
||||
@@ -0,0 +1,51 @@
|
||||
import absoluteUrl from './absoluteUrl';
|
||||
|
||||
describe('absoluteUrl', () => {
|
||||
test('should return /path_1/path_2/path_3/path_to_destination when the window.location.origin is http://dummy.com/path_1/path_2 and the path is /path_3/path_to_destination', () => {
|
||||
let global = {
|
||||
window: Object.create(window),
|
||||
};
|
||||
const url = 'http://dummy.com/path_1/path_2';
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
origin: url,
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
const absoluteUrlOutput = absoluteUrl('/path_3/path_to_destination');
|
||||
expect(absoluteUrlOutput).toEqual(
|
||||
'/path_1/path_2/path_3/path_to_destination'
|
||||
);
|
||||
});
|
||||
|
||||
test('should return / when the path is not defined', () => {
|
||||
const absoluteUrlOutput = absoluteUrl(undefined);
|
||||
expect(absoluteUrlOutput).toBe('/');
|
||||
});
|
||||
|
||||
test('should return the original path when there path in the window.origin after the domain and port', () => {
|
||||
global.window = Object.create(window);
|
||||
const url = 'http://dummy.com';
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
origin: url,
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
const absoluteUrlOutput = absoluteUrl('path_1/path_2/path_3');
|
||||
expect(absoluteUrlOutput).toEqual('/path_1/path_2/path_3');
|
||||
});
|
||||
|
||||
test('should be able to return the absolute path even when the path contains duplicates', () => {
|
||||
global.window = Object.create(window);
|
||||
const url = 'http://dummy.com';
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
origin: url,
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
const absoluteUrlOutput = absoluteUrl('path_1/path_1/path_1');
|
||||
expect(absoluteUrlOutput).toEqual('/path_1/path_1/path_1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import addServers from './addServers';
|
||||
|
||||
describe('addServers', () => {
|
||||
const servers = {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
requestOptions: {
|
||||
requestFromBrowser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
oidc: [
|
||||
{
|
||||
authority: 'http://127.0.0.1/auth/realms/ohif',
|
||||
client_id: 'ohif-viewer',
|
||||
redirect_uri: 'http://127.0.0.1/callback',
|
||||
response_type: 'code',
|
||||
scope: 'openid',
|
||||
post_logout_redirect_uri: '/logout-redirect.html',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const store = {
|
||||
dispatch: jest.fn(),
|
||||
};
|
||||
|
||||
test('should be able to add a server and dispatch to the store successfuly', () => {
|
||||
addServers(servers, store);
|
||||
expect(store.dispatch).toBeCalledWith({
|
||||
server: {
|
||||
authority: 'http://127.0.0.1/auth/realms/ohif',
|
||||
client_id: 'ohif-viewer',
|
||||
post_logout_redirect_uri: '/logout-redirect.html',
|
||||
redirect_uri: 'http://127.0.0.1/callback',
|
||||
response_type: 'code',
|
||||
scope: 'openid',
|
||||
type: 'oidc',
|
||||
},
|
||||
type: 'ADD_SERVER',
|
||||
});
|
||||
expect(store.dispatch).toBeCalledWith({
|
||||
server: {
|
||||
imageRendering: 'wadors',
|
||||
name: 'DCM4CHEE',
|
||||
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
qidoSupportsIncludeField: true,
|
||||
requestOptions: { requestFromBrowser: true },
|
||||
thumbnailRendering: 'wadors',
|
||||
type: 'dicomWeb',
|
||||
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
|
||||
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
|
||||
},
|
||||
type: 'ADD_SERVER',
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw an error if servers list is not defined', () => {
|
||||
expect(() => addServers(undefined, store)).toThrowError(
|
||||
new Error('The servers and store must be defined')
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw an error if store is not defined', () => {
|
||||
expect(() => addServers(servers, undefined)).toThrowError(
|
||||
new Error('The servers and store must be defined')
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw an error when both server and store are not defined', () => {
|
||||
expect(() => addServers(undefined, undefined)).toThrowError(
|
||||
new Error('The servers and store must be defined')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
// TODO: figure out where else to put this function
|
||||
const addServers = (servers, store) => {
|
||||
if (!servers || !store) {
|
||||
throw new Error('The servers and store must be defined');
|
||||
}
|
||||
|
||||
Object.keys(servers).forEach(serverType => {
|
||||
const endpoints = servers[serverType];
|
||||
endpoints.forEach(endpoint => {
|
||||
const server = Object.assign({}, endpoint);
|
||||
server.type = serverType;
|
||||
|
||||
store.dispatch({
|
||||
type: 'ADD_SERVER',
|
||||
server,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default addServers;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Leaving here as a starting point
|
||||
// import createStacks from './createStacks.js';
|
||||
|
||||
// describe('createStacks.js', () => {
|
||||
// const seriesMetadatas = [
|
||||
// {
|
||||
// getInstanceCount: jest.fn().mockReturnValue(1),
|
||||
// getData: jest.fn().mockReturnValue({
|
||||
// seriesDate: '2019-06-04',
|
||||
// }),
|
||||
// },
|
||||
// {
|
||||
// getInstanceCount: jest.fn().mockReturnValue(1),
|
||||
// getData: jest.fn().mockReturnValue({
|
||||
// seriesDate: '2018-06-04',
|
||||
// }),
|
||||
// },
|
||||
// ];
|
||||
// const studyMetadata = {
|
||||
// getSeriesCount: jest.fn().mockReturnValue(2),
|
||||
// forEachSeries: jest.fn().mockImplementation(callback => {
|
||||
// callback(seriesMetadatas[0], 0);
|
||||
// callback(seriesMetadatas[1], 1);
|
||||
// }),
|
||||
// getStudyInstanceUID: jest.fn(),
|
||||
// };
|
||||
|
||||
// it('sorts displaySets by seriesNumber, then by seriesDate', () => {
|
||||
// const displaySets = createStacks(studyMetadata);
|
||||
|
||||
// expect(displaySets.length).toBe(2);
|
||||
// });
|
||||
// });
|
||||
@@ -0,0 +1,55 @@
|
||||
import getWADORSImageId from './getWADORSImageId';
|
||||
|
||||
// https://stackoverflow.com/a/6021027/3895126
|
||||
function updateQueryStringParameter(uri, key, value) {
|
||||
const regex = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
|
||||
const separator = uri.indexOf('?') !== -1 ? '&' : '?';
|
||||
if (uri.match(regex)) {
|
||||
return uri.replace(regex, '$1' + key + '=' + value + '$2');
|
||||
} else {
|
||||
return uri + separator + key + '=' + value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone from an image instance
|
||||
*
|
||||
* @param instance
|
||||
* @param frame
|
||||
* @param thumbnail
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
export default function getImageId(instance, frame, thumbnail = false) {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof instance.getImageId === 'function') {
|
||||
return instance.getImageId();
|
||||
}
|
||||
|
||||
if (instance.url) {
|
||||
if (frame !== undefined) {
|
||||
instance.url = updateQueryStringParameter(instance.url, 'frame', frame);
|
||||
}
|
||||
|
||||
return instance.url;
|
||||
}
|
||||
|
||||
const renderingAttr = thumbnail ? 'thumbnailRendering' : 'imageRendering';
|
||||
|
||||
if (
|
||||
!instance[renderingAttr] ||
|
||||
instance[renderingAttr] === 'wadouri' ||
|
||||
!instance.wadorsuri
|
||||
) {
|
||||
let imageId = 'dicomweb:' + instance.wadouri;
|
||||
if (frame !== undefined) {
|
||||
imageId += '&frame=' + frame;
|
||||
}
|
||||
|
||||
return imageId;
|
||||
} else {
|
||||
return getWADORSImageId(instance, frame, thumbnail); // WADO-RS Retrieve Frame
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
function getWADORSImageUrl(instance, frame) {
|
||||
let wadorsuri = instance.wadorsuri;
|
||||
|
||||
if (!wadorsuri) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to sum 1 because WADO-RS frame number is 1-based
|
||||
frame = frame ? parseInt(frame) + 1 : 1;
|
||||
|
||||
// Replaces /frame/1 by /frame/{frame}
|
||||
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, `frames/${frame}`);
|
||||
|
||||
return wadorsuri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
|
||||
*
|
||||
* @param {object} instanceMetada metadata object (InstanceMetadata)
|
||||
* @param {(string\|number)} [frame] the frame number
|
||||
* @returns {string} The imageId to be used by Cornerstone
|
||||
*/
|
||||
export default function getWADORSImageId(instance, frame) {
|
||||
const uri = getWADORSImageUrl(instance, frame);
|
||||
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
return `wadors:${uri}`;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import getWADORSImageId from './getWADORSImageId';
|
||||
|
||||
describe('getWADORSImageId', () => {
|
||||
it('should always return undefined if the instance has no `wadorsuri` property', () => {
|
||||
const frame = '42';
|
||||
const instance = {};
|
||||
|
||||
expect(getWADORSImageId(instance)).toBeUndefined();
|
||||
expect(getWADORSImageId(instance, frame)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should always prepend the `wadorsuri` with `wadors:`', () => {
|
||||
const frame = '42';
|
||||
const instance = {
|
||||
wadorsuri: 'wadorsuri',
|
||||
};
|
||||
|
||||
expect(getWADORSImageId(instance)).toEqual('wadors:wadorsuri');
|
||||
expect(getWADORSImageId(instance, frame)).toEqual('wadors:wadorsuri');
|
||||
});
|
||||
|
||||
describe('with no frame provided', () => {
|
||||
it('should replace `frames/:number` with `frames/1`', () => {
|
||||
const instance = {
|
||||
wadorsuri: 'frames/42',
|
||||
};
|
||||
|
||||
expect(getWADORSImageId(instance)).toEqual('wadors:frames/1');
|
||||
});
|
||||
|
||||
it('should work on a real wadorsuri', () => {
|
||||
const instance = {
|
||||
wadorsuri:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.1/series/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.2/instances/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.8/frames/22',
|
||||
};
|
||||
|
||||
expect(getWADORSImageId(instance)).toEqual(
|
||||
'wadors:https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.1/series/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.2/instances/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.8/frames/1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a frame provided', () => {
|
||||
it('should replace `frames/:number` with the argument frame plus one', () => {
|
||||
const frame = '42';
|
||||
const instance = {
|
||||
wadorsuri: 'frames/1',
|
||||
};
|
||||
|
||||
expect(getWADORSImageId(instance, frame)).toEqual('wadors:frames/43');
|
||||
});
|
||||
|
||||
it('should work on a real wadorsuri', () => {
|
||||
const frame = '42';
|
||||
const instance = {
|
||||
wadorsuri:
|
||||
'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.1/series/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.2/instances/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.8/frames/22',
|
||||
};
|
||||
|
||||
expect(getWADORSImageId(instance, frame)).toEqual(
|
||||
'wadors:https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.1/series/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.2/instances/1.3.6.1.4.1.25403.52237031786.3872.20100510032220.8/frames/43'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Create a random GUID
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
const guid = () => {
|
||||
const getFourRandomValues = () => {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
};
|
||||
return (
|
||||
getFourRandomValues() +
|
||||
getFourRandomValues() +
|
||||
'-' +
|
||||
getFourRandomValues() +
|
||||
'-' +
|
||||
getFourRandomValues() +
|
||||
'-' +
|
||||
getFourRandomValues() +
|
||||
'-' +
|
||||
getFourRandomValues() +
|
||||
getFourRandomValues() +
|
||||
getFourRandomValues()
|
||||
);
|
||||
};
|
||||
|
||||
export default guid;
|
||||
@@ -0,0 +1,46 @@
|
||||
import guid from './guid';
|
||||
|
||||
describe('guid', () => {
|
||||
Math.random = jest.fn(() => 0.4677647565236618);
|
||||
const guidValue = guid();
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should return 77bf77bf-77bf-77bf-77bf-77bf77bf77bf when the random value is fixed on 0.4677647565236618', () => {
|
||||
expect(guidValue).toBe('77bf77bf-77bf-77bf-77bf-77bf77bf77bf');
|
||||
});
|
||||
|
||||
test('should always return a guid of size 36', () => {
|
||||
expect(guidValue.length).toBe(36);
|
||||
});
|
||||
|
||||
test('should always return a guid with five sequences', () => {
|
||||
expect(guidValue.split('-').length).toBe(5);
|
||||
});
|
||||
|
||||
test('should always return a guid with four dashes', () => {
|
||||
expect(guidValue.split('-').length - 1).toBe(4);
|
||||
});
|
||||
|
||||
test('should return the first sequence with length of eigth', () => {
|
||||
expect(guidValue.split('-')[0].length).toBe(8);
|
||||
});
|
||||
|
||||
test('should return the second sequence with length of four', () => {
|
||||
expect(guidValue.split('-')[1].length).toBe(4);
|
||||
});
|
||||
|
||||
test('should return the third sequence with length of four', () => {
|
||||
expect(guidValue.split('-')[2].length).toBe(4);
|
||||
});
|
||||
|
||||
test('should return the fourth sequence with length of four', () => {
|
||||
expect(guidValue.split('-')[3].length).toBe(4);
|
||||
});
|
||||
|
||||
test('should return the last sequence with length of twelve', () => {
|
||||
expect(guidValue.split('-')[4].length).toBe(12);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { DICOMTagDescriptions } from './DICOMTagDescriptions';
|
||||
import ObjectPath from './objectPath';
|
||||
import StackManager from './StackManager.js';
|
||||
import absoluteUrl from './absoluteUrl';
|
||||
import addServers from './addServers';
|
||||
import guid from './guid';
|
||||
import sortBy from './sortBy.js';
|
||||
import studyMetadataManager from './studyMetadataManager';
|
||||
import updateMetaDataManager from './updateMetaDataManager.js';
|
||||
import writeScript from './writeScript.js';
|
||||
|
||||
const utils = {
|
||||
guid,
|
||||
ObjectPath,
|
||||
absoluteUrl,
|
||||
addServers,
|
||||
sortBy,
|
||||
writeScript,
|
||||
StackManager,
|
||||
studyMetadataManager,
|
||||
// Updates WADO-RS metaDataManager
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
};
|
||||
|
||||
export {
|
||||
guid,
|
||||
ObjectPath,
|
||||
absoluteUrl,
|
||||
addServers,
|
||||
sortBy,
|
||||
writeScript,
|
||||
StackManager,
|
||||
studyMetadataManager,
|
||||
// Updates WADO-RS metaDataManager
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
};
|
||||
|
||||
export default utils;
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as utils from './index.js';
|
||||
|
||||
describe('Top level exports', () => {
|
||||
test('should export the modules ', () => {
|
||||
const expectedExports = [
|
||||
'guid',
|
||||
'ObjectPath',
|
||||
'absoluteUrl',
|
||||
'addServers',
|
||||
'sortBy',
|
||||
'writeScript',
|
||||
'StackManager',
|
||||
'studyMetadataManager',
|
||||
// Updates WADO-RS metaDataManager
|
||||
'updateMetaDataManager',
|
||||
'DICOMTagDescriptions',
|
||||
].sort();
|
||||
|
||||
const exports = Object.keys(utils.default).sort();
|
||||
|
||||
expect(exports).toEqual(expectedExports);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { sopClassDictionary } from './sopClassDictionary';
|
||||
|
||||
const imagesTypes = [
|
||||
sopClassDictionary.ComputedRadiographyImageStorage,
|
||||
sopClassDictionary.DigitalXRayImageStorageForPresentation,
|
||||
sopClassDictionary.DigitalXRayImageStorageForProcessing,
|
||||
sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation,
|
||||
sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing,
|
||||
sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation,
|
||||
sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing,
|
||||
sopClassDictionary.CTImageStorage,
|
||||
sopClassDictionary.EnhancedCTImageStorage,
|
||||
sopClassDictionary.LegacyConvertedEnhancedCTImageStorage,
|
||||
sopClassDictionary.UltrasoundMultiframeImageStorage,
|
||||
sopClassDictionary.MRImageStorage,
|
||||
sopClassDictionary.EnhancedMRImageStorage,
|
||||
sopClassDictionary.EnhancedMRColorImageStorage,
|
||||
sopClassDictionary.LegacyConvertedEnhancedMRImageStorage,
|
||||
sopClassDictionary.UltrasoundImageStorage,
|
||||
sopClassDictionary.SecondaryCaptureImageStorage,
|
||||
sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage,
|
||||
sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage,
|
||||
sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage,
|
||||
sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage,
|
||||
sopClassDictionary.XRayAngiographicImageStorage,
|
||||
sopClassDictionary.EnhancedXAImageStorage,
|
||||
sopClassDictionary.XRayRadiofluoroscopicImageStorage,
|
||||
sopClassDictionary.EnhancedXRFImageStorage,
|
||||
sopClassDictionary.XRay3DAngiographicImageStorage,
|
||||
sopClassDictionary.XRay3DCraniofacialImageStorage,
|
||||
sopClassDictionary.BreastTomosynthesisImageStorage,
|
||||
sopClassDictionary.BreastProjectionXRayImageStorageForPresentation,
|
||||
sopClassDictionary.BreastProjectionXRayImageStorageForProcessing,
|
||||
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation,
|
||||
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing,
|
||||
sopClassDictionary.NuclearMedicineImageStorage,
|
||||
sopClassDictionary.VLEndoscopicImageStorage,
|
||||
sopClassDictionary.VideoEndoscopicImageStorage,
|
||||
sopClassDictionary.VLMicroscopicImageStorage,
|
||||
sopClassDictionary.VideoMicroscopicImageStorage,
|
||||
sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage,
|
||||
sopClassDictionary.VLPhotographicImageStorage,
|
||||
sopClassDictionary.VideoPhotographicImageStorage,
|
||||
sopClassDictionary.OphthalmicPhotography8BitImageStorage,
|
||||
sopClassDictionary.OphthalmicPhotography16BitImageStorage,
|
||||
sopClassDictionary.OphthalmicTomographyImageStorage,
|
||||
sopClassDictionary.VLWholeSlideMicroscopyImageStorage,
|
||||
sopClassDictionary.PositronEmissionTomographyImageStorage,
|
||||
sopClassDictionary.EnhancedPETImageStorage,
|
||||
sopClassDictionary.LegacyConvertedEnhancedPETImageStorage,
|
||||
sopClassDictionary.RTImageStorage,
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks whether dicom files with specified SOP Class UID have image data
|
||||
* @param {string} sopClassUid - SOP Class UID to be checked
|
||||
* @returns {boolean} - true if it has image data
|
||||
*/
|
||||
export const isImage = sopClassUid => {
|
||||
if (!sopClassUid) return false;
|
||||
return imagesTypes.indexOf(sopClassUid) !== -1;
|
||||
};
|
||||
@@ -0,0 +1,333 @@
|
||||
import { sopClassDictionary } from './sopClassDictionary';
|
||||
import { isImage } from './isImage';
|
||||
|
||||
describe('isImage', () => {
|
||||
test('should return true when the image is of type sopClassDictionary.ComputedRadiographyImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.ComputedRadiographyImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.DigitalXRayImageStorageForPresentation', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.DigitalXRayImageStorageForPresentation
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.DigitalXRayImageStorageForProcessing', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.DigitalXRayImageStorageForProcessing
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.CTImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.CTImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.EnhancedCTImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.EnhancedCTImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedCTImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.LegacyConvertedEnhancedCTImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.UltrasoundMultiframeImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.UltrasoundMultiframeImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.MRImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.MRImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.EnhancedMRImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.EnhancedMRImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.EnhancedMRColorImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.EnhancedMRColorImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedMRImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.LegacyConvertedEnhancedMRImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.UltrasoundImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.UltrasoundImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.SecondaryCaptureImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.SecondaryCaptureImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.XRayAngiographicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.XRayAngiographicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.EnhancedXAImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.EnhancedXAImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.XRayRadiofluoroscopicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.XRayRadiofluoroscopicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.EnhancedXRFImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.EnhancedXRFImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.XRay3DAngiographicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.XRay3DAngiographicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.XRay3DCraniofacialImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.XRay3DCraniofacialImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.BreastTomosynthesisImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.BreastTomosynthesisImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.BreastProjectionXRayImageStorageForPresentation', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.BreastProjectionXRayImageStorageForPresentation
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.BreastProjectionXRayImageStorageForProcessing', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.BreastProjectionXRayImageStorageForProcessing
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.NuclearMedicineImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.NuclearMedicineImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VLEndoscopicImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.VLEndoscopicImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VideoEndoscopicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.VideoEndoscopicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VLMicroscopicImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.VLMicroscopicImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VideoMicroscopicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.VideoMicroscopicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VLPhotographicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.VLPhotographicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VideoPhotographicImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.VideoPhotographicImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.OphthalmicPhotography8BitImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.OphthalmicPhotography8BitImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.OphthalmicPhotography16BitImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.OphthalmicPhotography16BitImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.OphthalmicTomographyImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.OphthalmicTomographyImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.VLWholeSlideMicroscopyImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.VLWholeSlideMicroscopyImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.PositronEmissionTomographyImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.PositronEmissionTomographyImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.EnhancedPETImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.EnhancedPETImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.LegacyConvertedEnhancedPETImageStorage', () => {
|
||||
const isImageStatus = isImage(
|
||||
sopClassDictionary.LegacyConvertedEnhancedPETImageStorage
|
||||
);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true when the image is of type sopClassDictionary.RTImageStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.RTImageStorage);
|
||||
expect(isImageStatus).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false when the image is of type sopClassDictionary.SpatialFiducialsStorage', () => {
|
||||
const isImageStatus = isImage(sopClassDictionary.SpatialFiducialsStorage);
|
||||
expect(isImageStatus).toBe(false);
|
||||
});
|
||||
|
||||
test('should return false when the image is undefined', () => {
|
||||
const isImageStatus = isImage(undefined);
|
||||
expect(isImageStatus).toBe(false);
|
||||
});
|
||||
|
||||
test('should return false when the image is null', () => {
|
||||
const isImageStatus = isImage(null);
|
||||
expect(isImageStatus).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
export class ObjectPath {
|
||||
/**
|
||||
* Set an object property based on "path" (namespace) supplied creating
|
||||
* ... intermediary objects if they do not exist.
|
||||
* @param object {Object} An object where the properties specified on path should be set.
|
||||
* @param path {String} A string representing the property to be set, e.g. "user.study.series.timepoint".
|
||||
* @param value {Any} The value of the property that will be set.
|
||||
* @return {Boolean} Returns "true" on success, "false" if any intermediate component of the supplied path
|
||||
* ... is not a valid Object, in which case the property cannot be set. No excpetions are thrown.
|
||||
*/
|
||||
static set(object, path, value) {
|
||||
let components = ObjectPath.getPathComponents(path),
|
||||
length = components !== null ? components.length : 0,
|
||||
result = false;
|
||||
|
||||
if (length > 0 && ObjectPath.isValidObject(object)) {
|
||||
let i = 0,
|
||||
last = length - 1,
|
||||
currentObject = object;
|
||||
|
||||
while (i < last) {
|
||||
let field = components[i];
|
||||
|
||||
if (field in currentObject) {
|
||||
if (!ObjectPath.isValidObject(currentObject[field])) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
currentObject[field] = {};
|
||||
}
|
||||
|
||||
currentObject = currentObject[field];
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === last) {
|
||||
currentObject[components[last]] = value;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object property based on "path" (namespace) supplied traversing the object
|
||||
* ... tree as necessary.
|
||||
* @param object {Object} An object where the properties specified might exist.
|
||||
* @param path {String} A string representing the property to be searched for, e.g. "user.study.series.timepoint".
|
||||
* @return {Any} The value of the property if found. By default, returns the special type "undefined".
|
||||
*/
|
||||
static get(object, path) {
|
||||
let found, // undefined by default
|
||||
components = ObjectPath.getPathComponents(path),
|
||||
length = components !== null ? components.length : 0;
|
||||
|
||||
if (length > 0 && ObjectPath.isValidObject(object)) {
|
||||
let i = 0,
|
||||
last = length - 1,
|
||||
currentObject = object;
|
||||
|
||||
while (i < last) {
|
||||
let field = components[i];
|
||||
|
||||
const isValid = ObjectPath.isValidObject(currentObject[field]);
|
||||
if (field in currentObject && isValid) {
|
||||
currentObject = currentObject[field];
|
||||
i++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i === last && components[last] in currentObject) {
|
||||
found = currentObject[components[last]];
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the supplied argument is a real JavaScript Object instance.
|
||||
* @param object {Any} The subject to be tested.
|
||||
* @return {Boolean} Returns "true" if the object is a real Object instance and "false" otherwise.
|
||||
*/
|
||||
static isValidObject(object) {
|
||||
return (
|
||||
typeof object === 'object' && object !== null && object instanceof Object
|
||||
);
|
||||
}
|
||||
|
||||
static getPathComponents(path) {
|
||||
return typeof path === 'string' ? path.split('.') : null;
|
||||
}
|
||||
}
|
||||
|
||||
export default ObjectPath;
|
||||
@@ -0,0 +1,99 @@
|
||||
import objectPath from './objectPath';
|
||||
|
||||
describe('objectPath', () => {
|
||||
test('should return false when the supplied argument is not a real JavaScript Object instance such as undefined', () => {
|
||||
expect(objectPath.isValidObject(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
test('should return false when the supplied argument is not a real JavaScript Object instance such as null', () => {
|
||||
expect(objectPath.isValidObject(null)).toBe(false);
|
||||
});
|
||||
|
||||
test('should return true when the supplied argument is a real JavaScript Object instance', () => {
|
||||
expect(objectPath.isValidObject({})).toBe(true);
|
||||
});
|
||||
|
||||
test('should return [path1, path2, path3] when the path is path1.path2.path3', () => {
|
||||
const path = 'path1.path2.path3';
|
||||
const expectedPathComponents = objectPath.getPathComponents(path);
|
||||
expect(expectedPathComponents).toEqual(['path1', 'path2', 'path3']);
|
||||
});
|
||||
|
||||
test('should return null when the path is not a string', () => {
|
||||
const path = 20;
|
||||
const expectedPathComponents = objectPath.getPathComponents(path);
|
||||
expect(expectedPathComponents).toEqual(null);
|
||||
});
|
||||
|
||||
test('should return [path1path2path3] when the path is path1path2path3', () => {
|
||||
const path = 'path1path2path3';
|
||||
const expectedPathComponents = objectPath.getPathComponents(path);
|
||||
expect(expectedPathComponents).toEqual(['path1path2path3']);
|
||||
});
|
||||
|
||||
test('should return the property obj.myProperty when the object contains myProperty', () => {
|
||||
const searchObject = {
|
||||
obj: {
|
||||
myProperty: 'MOCK_VALUE',
|
||||
},
|
||||
};
|
||||
const path = 'obj.myProperty';
|
||||
const expectedPathComponents = objectPath.get(searchObject, path);
|
||||
expect(expectedPathComponents).toEqual(searchObject.obj.myProperty);
|
||||
});
|
||||
|
||||
test('should return undefined when the object does not contain a property', () => {
|
||||
const searchObject = {
|
||||
obj: {
|
||||
myProperty: 'MOCK_VALUE',
|
||||
},
|
||||
};
|
||||
const path = 'obj.unknownProperty';
|
||||
const expectedPathComponents = objectPath.get(searchObject, path);
|
||||
expect(expectedPathComponents).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('should return undefined when the object is not a valid object', () => {
|
||||
const searchObject = undefined;
|
||||
const path = 'obj.unknownProperty';
|
||||
const expectedPathComponents = objectPath.get(searchObject, path);
|
||||
expect(expectedPathComponents).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('should return undefined when the inner object is not a valid object', () => {
|
||||
const searchObject = {
|
||||
obj: {
|
||||
myProperty: null,
|
||||
},
|
||||
};
|
||||
const path = 'obj.unknownProperty';
|
||||
const expectedPathComponents = objectPath.get(searchObject, path);
|
||||
expect(expectedPathComponents).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('should set the property obj.myProperty when the object does not contain myProperty', () => {
|
||||
const searchObject = {
|
||||
obj: {
|
||||
anyProperty: 'MOCK_VALUE',
|
||||
},
|
||||
};
|
||||
const newValue = 'NEW_VALUE';
|
||||
const path = 'obj.myProperty';
|
||||
const output = objectPath.set(searchObject, path, newValue);
|
||||
expect(output).toBe(true);
|
||||
expect(searchObject.obj.myProperty).toEqual(newValue);
|
||||
});
|
||||
|
||||
test('should return false when the object which is being set is not in a valid path', () => {
|
||||
const searchObject = {
|
||||
obj: {
|
||||
myProperty: 'MOCK_VALUE',
|
||||
},
|
||||
};
|
||||
const path = undefined;
|
||||
const newValue = 'NEW_VALUE';
|
||||
|
||||
const output = objectPath.set(searchObject, path, newValue);
|
||||
expect(output).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
// TODO: Deprecate since we have the same thing in dcmjs?
|
||||
export const sopClassDictionary = {
|
||||
ComputedRadiographyImageStorage: '1.2.840.10008.5.1.4.1.1.1',
|
||||
DigitalXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.1',
|
||||
DigitalXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.1.1',
|
||||
DigitalMammographyXRayImageStorageForPresentation:
|
||||
'1.2.840.10008.5.1.4.1.1.1.2',
|
||||
DigitalMammographyXRayImageStorageForProcessing:
|
||||
'1.2.840.10008.5.1.4.1.1.1.2.1',
|
||||
DigitalIntraOralXRayImageStorageForPresentation:
|
||||
'1.2.840.10008.5.1.4.1.1.1.3',
|
||||
DigitalIntraOralXRayImageStorageForProcessing:
|
||||
'1.2.840.10008.5.1.4.1.1.1.3.1',
|
||||
CTImageStorage: '1.2.840.10008.5.1.4.1.1.2',
|
||||
EnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.1',
|
||||
LegacyConvertedEnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.2',
|
||||
UltrasoundMultiframeImageStorage: '1.2.840.10008.5.1.4.1.1.3.1',
|
||||
MRImageStorage: '1.2.840.10008.5.1.4.1.1.4',
|
||||
EnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.1',
|
||||
MRSpectroscopyStorage: '1.2.840.10008.5.1.4.1.1.4.2',
|
||||
EnhancedMRColorImageStorage: '1.2.840.10008.5.1.4.1.1.4.3',
|
||||
LegacyConvertedEnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.4',
|
||||
UltrasoundImageStorage: '1.2.840.10008.5.1.4.1.1.6.1',
|
||||
EnhancedUSVolumeStorage: '1.2.840.10008.5.1.4.1.1.6.2',
|
||||
SecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7',
|
||||
MultiframeSingleBitSecondaryCaptureImageStorage:
|
||||
'1.2.840.10008.5.1.4.1.1.7.1',
|
||||
MultiframeGrayscaleByteSecondaryCaptureImageStorage:
|
||||
'1.2.840.10008.5.1.4.1.1.7.2',
|
||||
MultiframeGrayscaleWordSecondaryCaptureImageStorage:
|
||||
'1.2.840.10008.5.1.4.1.1.7.3',
|
||||
MultiframeTrueColorSecondaryCaptureImageStorage:
|
||||
'1.2.840.10008.5.1.4.1.1.7.4',
|
||||
Sop12LeadECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.1',
|
||||
GeneralECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.2',
|
||||
AmbulatoryECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.3',
|
||||
HemodynamicWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.2.1',
|
||||
CardiacElectrophysiologyWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.3.1',
|
||||
BasicVoiceAudioWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.4.1',
|
||||
GeneralAudioWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.4.2',
|
||||
ArterialPulseWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.5.1',
|
||||
RespiratoryWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.6.1',
|
||||
GrayscaleSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.1',
|
||||
ColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.2',
|
||||
PseudoColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.3',
|
||||
BlendingSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.4',
|
||||
XAXRFGrayscaleSoftcopyPresentationStateStorage:
|
||||
'1.2.840.10008.5.1.4.1.1.11.5',
|
||||
XRayAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.12.1',
|
||||
EnhancedXAImageStorage: '1.2.840.10008.5.1.4.1.1.12.1.1',
|
||||
XRayRadiofluoroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.12.2',
|
||||
EnhancedXRFImageStorage: '1.2.840.10008.5.1.4.1.1.12.2.1',
|
||||
XRay3DAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.1',
|
||||
XRay3DCraniofacialImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.2',
|
||||
BreastTomosynthesisImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.3',
|
||||
BreastProjectionXRayImageStorageForPresentation:
|
||||
'1.2.840.10008.5.1.4.1.1.13.1.4',
|
||||
BreastProjectionXRayImageStorageForProcessing:
|
||||
'1.2.840.10008.5.1.4.1.1.13.1.5',
|
||||
IntravascularOpticalCoherenceTomographyImageStorageForPresentation:
|
||||
'1.2.840.10008.5.1.4.1.1.14.1',
|
||||
IntravascularOpticalCoherenceTomographyImageStorageForProcessing:
|
||||
'1.2.840.10008.5.1.4.1.1.14.2',
|
||||
NuclearMedicineImageStorage: '1.2.840.10008.5.1.4.1.1.20',
|
||||
RawDataStorage: '1.2.840.10008.5.1.4.1.1.66',
|
||||
SpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.1',
|
||||
SpatialFiducialsStorage: '1.2.840.10008.5.1.4.1.1.66.2',
|
||||
DeformableSpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.3',
|
||||
SegmentationStorage: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
SurfaceSegmentationStorage: '1.2.840.10008.5.1.4.1.1.66.5',
|
||||
RealWorldValueMappingStorage: '1.2.840.10008.5.1.4.1.1.67',
|
||||
SurfaceScanMeshStorage: '1.2.840.10008.5.1.4.1.1.68.1',
|
||||
SurfaceScanPointCloudStorage: '1.2.840.10008.5.1.4.1.1.68.2',
|
||||
VLEndoscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.1',
|
||||
VideoEndoscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.1.1',
|
||||
VLMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.2',
|
||||
VideoMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.2.1',
|
||||
VLSlideCoordinatesMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.3',
|
||||
VLPhotographicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.4',
|
||||
VideoPhotographicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.4.1',
|
||||
OphthalmicPhotography8BitImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.1',
|
||||
OphthalmicPhotography16BitImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.2',
|
||||
StereometricRelationshipStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.3',
|
||||
OphthalmicTomographyImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.4',
|
||||
VLWholeSlideMicroscopyImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.6',
|
||||
LensometryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.1',
|
||||
AutorefractionMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.2',
|
||||
KeratometryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.3',
|
||||
SubjectiveRefractionMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.4',
|
||||
VisualAcuityMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.5',
|
||||
SpectaclePrescriptionReportStorage: '1.2.840.10008.5.1.4.1.1.78.6',
|
||||
OphthalmicAxialMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.7',
|
||||
IntraocularLensCalculationsStorage: '1.2.840.10008.5.1.4.1.1.78.8',
|
||||
MacularGridThicknessandVolumeReport: '1.2.840.10008.5.1.4.1.1.79.1',
|
||||
OphthalmicVisualFieldStaticPerimetryMeasurementsStorage:
|
||||
'1.2.840.10008.5.1.4.1.1.80.1',
|
||||
OphthalmicThicknessMapStorage: '1.2.840.10008.5.1.4.1.1.81.1',
|
||||
CornealTopographyMapStorage: '1.2.840.10008.5.1.4.1.1.82.1',
|
||||
BasicTextSR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||
EnhancedSR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||
ComprehensiveSR: '1.2.840.10008.5.1.4.1.1.88.33',
|
||||
Comprehensive3DSR: '1.2.840.10008.5.1.4.1.1.88.34',
|
||||
ProcedureLog: '1.2.840.10008.5.1.4.1.1.88.40',
|
||||
MammographyCADSR: '1.2.840.10008.5.1.4.1.1.88.50',
|
||||
KeyObjectSelection: '1.2.840.10008.5.1.4.1.1.88.59',
|
||||
ChestCADSR: '1.2.840.10008.5.1.4.1.1.88.65',
|
||||
XRayRadiationDoseSR: '1.2.840.10008.5.1.4.1.1.88.67',
|
||||
RadiopharmaceuticalRadiationDoseSR: '1.2.840.10008.5.1.4.1.1.88.68',
|
||||
ColonCADSR: '1.2.840.10008.5.1.4.1.1.88.69',
|
||||
ImplantationPlanSRDocumentStorage: '1.2.840.10008.5.1.4.1.1.88.70',
|
||||
EncapsulatedPDFStorage: '1.2.840.10008.5.1.4.1.1.104.1',
|
||||
EncapsulatedCDAStorage: '1.2.840.10008.5.1.4.1.1.104.2',
|
||||
PositronEmissionTomographyImageStorage: '1.2.840.10008.5.1.4.1.1.128',
|
||||
EnhancedPETImageStorage: '1.2.840.10008.5.1.4.1.1.130',
|
||||
LegacyConvertedEnhancedPETImageStorage: '1.2.840.10008.5.1.4.1.1.128.1',
|
||||
BasicStructuredDisplayStorage: '1.2.840.10008.5.1.4.1.1.131',
|
||||
RTImageStorage: '1.2.840.10008.5.1.4.1.1.481.1',
|
||||
RTDoseStorage: '1.2.840.10008.5.1.4.1.1.481.2',
|
||||
RTStructureSetStorage: '1.2.840.10008.5.1.4.1.1.481.3',
|
||||
RTBeamsTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.4',
|
||||
RTPlanStorage: '1.2.840.10008.5.1.4.1.1.481.5',
|
||||
RTBrachyTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.6',
|
||||
RTTreatmentSummaryRecordStorage: '1.2.840.10008.5.1.4.1.1.481.7',
|
||||
RTIonPlanStorage: '1.2.840.10008.5.1.4.1.1.481.8',
|
||||
RTIonBeamsTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.9',
|
||||
RTBeamsDeliveryInstructionStorage: '1.2.840.10008.5.1.4.34.7',
|
||||
GenericImplantTemplateStorage: '1.2.840.10008.5.1.4.43.1',
|
||||
ImplantAssemblyTemplateStorage: '1.2.840.10008.5.1.4.44.1',
|
||||
ImplantTemplateGroupStorage: '1.2.840.10008.5.1.4.45.1',
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// Return the array sorting function for its object's properties
|
||||
export default function sortBy() {
|
||||
var fields = [].slice.call(arguments),
|
||||
n_fields = fields.length;
|
||||
|
||||
return function(A, B) {
|
||||
var a, b, field, key, reverse, result, i;
|
||||
|
||||
for (i = 0; i < n_fields; i++) {
|
||||
result = 0;
|
||||
field = fields[i];
|
||||
|
||||
key = typeof field === 'string' ? field : field.name;
|
||||
|
||||
a = A[key];
|
||||
b = B[key];
|
||||
|
||||
if (typeof field.primer !== 'undefined') {
|
||||
a = field.primer(a);
|
||||
b = field.primer(b);
|
||||
}
|
||||
|
||||
reverse = field.reverse ? -1 : 1;
|
||||
|
||||
if (a < b) {
|
||||
result = reverse * -1;
|
||||
}
|
||||
|
||||
if (a > b) {
|
||||
result = reverse * 1;
|
||||
}
|
||||
|
||||
if (result !== 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TypeSafeCollection } from '../classes/TypeSafeCollection';
|
||||
|
||||
const studyMetadataList = new TypeSafeCollection();
|
||||
|
||||
function add(studyMetadata) {
|
||||
studyMetadataList.insert(studyMetadata);
|
||||
}
|
||||
|
||||
function get(studyInstanceUID) {
|
||||
return studyMetadataList.findBy({ studyInstanceUID });
|
||||
}
|
||||
|
||||
function all(options) {
|
||||
return studyMetadataList.all(options);
|
||||
}
|
||||
|
||||
function remove(studyInstanceUID) {
|
||||
studyMetadataList.remove({ studyInstanceUID });
|
||||
}
|
||||
|
||||
function purge() {
|
||||
studyMetadataList.removeAll();
|
||||
}
|
||||
|
||||
export default {
|
||||
add,
|
||||
get,
|
||||
all,
|
||||
remove,
|
||||
purge,
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
import { WadoRsMetaDataBuilder } from '../classes/metadata/WadoRsMetaDataBuilder';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import getWADORSImageId from './getWADORSImageId';
|
||||
|
||||
function getRadiopharmaceuticalInfoMetaData(instance) {
|
||||
const radiopharmaceuticalInfo = instance.radiopharmaceuticalInfo;
|
||||
|
||||
if (instance.modality !== 'PT' || !radiopharmaceuticalInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new WadoRsMetaDataBuilder()
|
||||
.addTag('00181072', radiopharmaceuticalInfo.radiopharmaceuticalStartTime)
|
||||
.addTag('00181074', radiopharmaceuticalInfo.radionuclideTotalDose)
|
||||
.addTag('00181075', radiopharmaceuticalInfo.radionuclideHalfLife)
|
||||
.toJSON();
|
||||
}
|
||||
|
||||
const getWadoRsInstanceMetaData = (study, series, instance) => {
|
||||
return new WadoRsMetaDataBuilder()
|
||||
.addTag('00080016', instance.sopClassUid)
|
||||
.addTag('00080018', instance.sopInstanceUid)
|
||||
.addTag('00080021', series.seriesDate)
|
||||
.addTag('00080031', series.seriesTime)
|
||||
.addTag('0008103e', series.seriesDescription)
|
||||
.addTag('00080060', series.modality)
|
||||
.addTag('00101010', study.patientAge)
|
||||
.addTag('00101020', study.patientSize)
|
||||
.addTag('00101030', study.patientWeight)
|
||||
.addTag('0020000d', study.studyInstanceUid)
|
||||
.addTag('00081030', study.studyDescription)
|
||||
.addTag('00100010', study.patientName)
|
||||
.addTag('00100020', study.patientId)
|
||||
.addTag('00080020', study.studyDate)
|
||||
.addTag('00080030', study.studyTime)
|
||||
.addTag('00080050', study.accessionNumber)
|
||||
.addTag('00200013', instance.instanceNumber)
|
||||
.addTag('00180050', instance.sliceThickness)
|
||||
.addTag('0020000e', series.seriesInstanceUid)
|
||||
.addTag('00200011', series.seriesNumber)
|
||||
.addTag('00200032', instance.imagePositionPatient, true)
|
||||
.addTag('00200037', instance.imageOrientationPatient, true)
|
||||
.addTag('00200052', instance.frameOfReferenceUID)
|
||||
.addTag('00201041', instance.sliceLocation)
|
||||
.addTag('00280002', instance.samplesPerPixel)
|
||||
.addTag('00280004', instance.photometricInterpretation)
|
||||
.addTag('00280006', instance.planarConfiguration)
|
||||
.addTag('00280010', instance.rows)
|
||||
.addTag('00280011', instance.columns)
|
||||
.addTag('00280030', instance.pixelSpacing, true)
|
||||
.addTag('00280034', instance.pixelAspectRatio, true)
|
||||
.addTag('00280100', instance.bitsAllocated)
|
||||
.addTag('00280101', instance.bitsStored)
|
||||
.addTag('00280102', instance.highBit)
|
||||
.addTag('00280103', instance.pixelRepresentation)
|
||||
.addTag('00280106', instance.smallestPixelValue)
|
||||
.addTag('00280107', instance.largestPixelValue)
|
||||
.addTag('00281050', instance.windowCenter, true)
|
||||
.addTag('00281051', instance.windowWidth, true)
|
||||
.addTag('00281052', instance.rescaleIntercept)
|
||||
.addTag('00281053', instance.rescaleSlope)
|
||||
.addTag('00281054', instance.rescaleType)
|
||||
.addTag('00281101', instance.redPaletteColorLookupTableDescriptor)
|
||||
.addTag('00281102', instance.greenPaletteColorLookupTableDescriptor)
|
||||
.addTag('00281103', instance.bluePaletteColorLookupTableDescriptor)
|
||||
.addTag('00281201', instance.redPaletteColorLookupTableData)
|
||||
.addTag('00281202', instance.greenPaletteColorLookupTableData)
|
||||
.addTag('00281203', instance.bluePaletteColorLookupTableData)
|
||||
.addTag('00540016', getRadiopharmaceuticalInfoMetaData(instance))
|
||||
.toJSON();
|
||||
};
|
||||
|
||||
export default function updateMetaDataManager(study) {
|
||||
study.seriesList.forEach(series => {
|
||||
series.instances.forEach(instance => {
|
||||
// Cache just images that are going to be loaded via WADO-RS
|
||||
if (
|
||||
instance.imageRendering !== 'wadors' &&
|
||||
instance.thumbnailRendering !== 'wadors'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metaData = getWadoRsInstanceMetaData(study, series, instance);
|
||||
const numberOfFrames = instance.numberOfFrames || 1;
|
||||
|
||||
// We can share the same metaData with all frames because it doesn't have
|
||||
// any frame specific data, such as frameNumber, pixelData, offset, etc.
|
||||
// WADO-RS frame number is 1-based
|
||||
for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) {
|
||||
const imageId = getWADORSImageId(instance, frameNumber);
|
||||
|
||||
// TODO Drop dependency on this
|
||||
cornerstoneWADOImageLoader.wadors.metaDataManager.add(
|
||||
imageId,
|
||||
metaData
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* jshint -W060 */
|
||||
import absoluteUrl from './absoluteUrl';
|
||||
|
||||
export default function writeScript(fileName, callback) {
|
||||
const script = document.createElement('script');
|
||||
script.src = absoluteUrl(fileName);
|
||||
script.onload = () => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(script);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
Reference in new issue
Block a user