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:
Danny Brown authored and GitHub committed 2019-08-13 15:36:46 -04:00
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
+174
View File
@@ -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;
+20
View File
@@ -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');
});
});
+82
View File
@@ -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')
);
});
});
+21
View File
@@ -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);
// });
// });
+55
View File
@@ -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'
);
});
});
});
+28
View File
@@ -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;
+46
View File
@@ -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);
});
});
+40
View File
@@ -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;
+23
View File
@@ -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);
});
});
+62
View File
@@ -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;
};
+333
View File
@@ -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);
});
});
+98
View File
@@ -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',
};
+40
View File
@@ -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
);
}
});
});
}
+14
View File
@@ -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);
}