@cornerstonejs/metadata 5.0.16 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/displayset/BaseDisplaySet.d.ts +18 -0
- package/dist/esm/displayset/BaseDisplaySet.js +13 -0
- package/dist/esm/displayset/IDisplaySet.d.ts +13 -0
- package/dist/esm/displayset/IDisplaySet.js +0 -0
- package/dist/esm/displayset/ImageStackDisplaySet.d.ts +20 -0
- package/dist/esm/displayset/ImageStackDisplaySet.js +57 -0
- package/dist/esm/displayset/buildSeriesInfo.d.ts +2 -0
- package/dist/esm/displayset/buildSeriesInfo.js +27 -0
- package/dist/esm/displayset/createDisplaySetFromGroup.d.ts +9 -0
- package/dist/esm/displayset/createDisplaySetFromGroup.js +86 -0
- package/dist/esm/displayset/defaultDisplaySetSplitRules.d.ts +2 -0
- package/dist/esm/displayset/defaultDisplaySetSplitRules.js +93 -0
- package/dist/esm/displayset/displaySetProvider.d.ts +1 -0
- package/dist/esm/displayset/displaySetProvider.js +16 -0
- package/dist/esm/displayset/displayset.test.d.ts +1 -0
- package/dist/esm/displayset/displayset.test.js +357 -0
- package/dist/esm/displayset/groupInstancesBySplitRules.d.ts +2 -0
- package/dist/esm/displayset/groupInstancesBySplitRules.js +37 -0
- package/dist/esm/displayset/index.d.ts +22 -0
- package/dist/esm/displayset/index.js +15 -0
- package/dist/esm/displayset/isEcgInstance.d.ts +2 -0
- package/dist/esm/displayset/isEcgInstance.js +10 -0
- package/dist/esm/displayset/isImageInstance.d.ts +2 -0
- package/dist/esm/displayset/isImageInstance.js +45 -0
- package/dist/esm/displayset/isVideoInstance.d.ts +2 -0
- package/dist/esm/displayset/isVideoInstance.js +31 -0
- package/dist/esm/displayset/isWsiInstance.d.ts +2 -0
- package/dist/esm/displayset/isWsiInstance.js +7 -0
- package/dist/esm/displayset/registerDisplaySetMetadata.d.ts +5 -0
- package/dist/esm/displayset/registerDisplaySetMetadata.js +19 -0
- package/dist/esm/displayset/resolveInstances.d.ts +6 -0
- package/dist/esm/displayset/resolveInstances.js +16 -0
- package/dist/esm/displayset/splitImageIdsBySplitRules.d.ts +7 -0
- package/dist/esm/displayset/splitImageIdsBySplitRules.js +12 -0
- package/dist/esm/displayset/types.d.ts +59 -0
- package/dist/esm/displayset/types.js +0 -0
- package/dist/esm/displayset/viewportTypes.d.ts +4 -0
- package/dist/esm/displayset/viewportTypes.js +13 -0
- package/dist/esm/enums/MetadataModules.d.ts +2 -1
- package/dist/esm/enums/MetadataModules.js +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/registerDefaultProviders.js +2 -0
- package/dist/esm/types/MetadataModuleTypes.d.ts +2 -0
- package/dist/esm/utilities/index.d.ts +1 -0
- package/dist/esm/utilities/index.js +1 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IDisplaySet } from './IDisplaySet';
|
|
2
|
+
import type { NaturalizedInstance, ViewportTypeHint } from './types';
|
|
3
|
+
export type BaseDisplaySetOptions = {
|
|
4
|
+
displaySetId: string;
|
|
5
|
+
viewportTypes?: readonly ViewportTypeHint[];
|
|
6
|
+
instances?: NaturalizedInstance[];
|
|
7
|
+
imageIds?: Iterable<string>;
|
|
8
|
+
underlyingImageIds?: Iterable<string>;
|
|
9
|
+
};
|
|
10
|
+
export declare class BaseDisplaySet implements IDisplaySet {
|
|
11
|
+
displaySetId: string;
|
|
12
|
+
viewportTypes: readonly ViewportTypeHint[];
|
|
13
|
+
preferredViewportType: ViewportTypeHint;
|
|
14
|
+
readonly instances: readonly NaturalizedInstance[];
|
|
15
|
+
readonly imageIds: readonly string[];
|
|
16
|
+
readonly underlyingImageIds: readonly string[];
|
|
17
|
+
constructor(options: BaseDisplaySetOptions);
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getPreferredViewportType } from './viewportTypes';
|
|
2
|
+
export class BaseDisplaySet {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.displaySetId = options.displaySetId;
|
|
5
|
+
this.viewportTypes = options.viewportTypes?.length
|
|
6
|
+
? [...options.viewportTypes]
|
|
7
|
+
: ['stack'];
|
|
8
|
+
this.preferredViewportType = getPreferredViewportType(this.viewportTypes);
|
|
9
|
+
this.instances = [...(options.instances ?? [])];
|
|
10
|
+
this.imageIds = [...(options.imageIds ?? [])];
|
|
11
|
+
this.underlyingImageIds = [...(options.underlyingImageIds ?? [])];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NaturalizedInstance, ViewportTypeHint } from './types';
|
|
2
|
+
export interface IDisplaySet {
|
|
3
|
+
displaySetId: string;
|
|
4
|
+
viewportTypes: readonly ViewportTypeHint[];
|
|
5
|
+
preferredViewportType: ViewportTypeHint;
|
|
6
|
+
instances: readonly NaturalizedInstance[];
|
|
7
|
+
imageIds: readonly string[];
|
|
8
|
+
underlyingImageIds: readonly string[];
|
|
9
|
+
isMultiFrame?: boolean;
|
|
10
|
+
isClip?: boolean;
|
|
11
|
+
numImageFrames?: number;
|
|
12
|
+
splitNumber?: number;
|
|
13
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseDisplaySet, type BaseDisplaySetOptions } from './BaseDisplaySet';
|
|
2
|
+
import type { NaturalizedInstance, ViewportTypeHint } from './types';
|
|
3
|
+
export type ImageStackDisplaySetOptions = Omit<BaseDisplaySetOptions, 'imageIds' | 'underlyingImageIds'> & {
|
|
4
|
+
instances?: NaturalizedInstance[];
|
|
5
|
+
imageIds?: Iterable<string>;
|
|
6
|
+
underlyingImageIds?: Iterable<string>;
|
|
7
|
+
};
|
|
8
|
+
export declare class ImageStackDisplaySet extends BaseDisplaySet {
|
|
9
|
+
isMultiFrame: boolean;
|
|
10
|
+
constructor(options: ImageStackDisplaySetOptions);
|
|
11
|
+
static fromInstances(instances: NaturalizedInstance[], options?: {
|
|
12
|
+
displaySetId?: string;
|
|
13
|
+
viewportTypes?: readonly ViewportTypeHint[];
|
|
14
|
+
imageIds?: Iterable<string>;
|
|
15
|
+
}): ImageStackDisplaySet;
|
|
16
|
+
static fromImageIds(imageIds: string[], getNaturalizedInstance: (imageId: string) => NaturalizedInstance | undefined, options?: {
|
|
17
|
+
displaySetId?: string;
|
|
18
|
+
viewportTypes?: readonly ViewportTypeHint[];
|
|
19
|
+
}): ImageStackDisplaySet;
|
|
20
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { BaseDisplaySet } from './BaseDisplaySet';
|
|
2
|
+
function collectUnderlyingImageIds(instances) {
|
|
3
|
+
const ids = [];
|
|
4
|
+
for (const instance of instances) {
|
|
5
|
+
if (instance.imageId) {
|
|
6
|
+
ids.push(instance.imageId);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return ids;
|
|
10
|
+
}
|
|
11
|
+
function collectImageIds(instances, underlyingImageIds) {
|
|
12
|
+
const imageIds = [];
|
|
13
|
+
for (const instance of instances) {
|
|
14
|
+
if (instance.imageId) {
|
|
15
|
+
imageIds.push(instance.imageId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (imageIds.length === 0) {
|
|
19
|
+
return [...underlyingImageIds];
|
|
20
|
+
}
|
|
21
|
+
return imageIds;
|
|
22
|
+
}
|
|
23
|
+
export class ImageStackDisplaySet extends BaseDisplaySet {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
const instances = options.instances ?? [];
|
|
26
|
+
const underlyingImageIds = options.underlyingImageIds ?? collectUnderlyingImageIds(instances);
|
|
27
|
+
const underlyingList = [...underlyingImageIds];
|
|
28
|
+
const imageIds = options.imageIds ?? collectImageIds(instances, underlyingList);
|
|
29
|
+
super({
|
|
30
|
+
...options,
|
|
31
|
+
instances,
|
|
32
|
+
imageIds,
|
|
33
|
+
underlyingImageIds: underlyingList,
|
|
34
|
+
});
|
|
35
|
+
this.isMultiFrame = instances.some((instance) => Number(instance.NumberOfFrames) > 1);
|
|
36
|
+
}
|
|
37
|
+
static fromInstances(instances, options) {
|
|
38
|
+
const displaySetId = options?.displaySetId ??
|
|
39
|
+
instances[0]?.SeriesInstanceUID ??
|
|
40
|
+
`display-set-${instances[0]?.imageId ?? 'unknown'}`;
|
|
41
|
+
return new ImageStackDisplaySet({
|
|
42
|
+
displaySetId,
|
|
43
|
+
viewportTypes: options?.viewportTypes ?? ['stack', 'volume', 'volume3d'],
|
|
44
|
+
instances,
|
|
45
|
+
imageIds: options?.imageIds,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
static fromImageIds(imageIds, getNaturalizedInstance, options) {
|
|
49
|
+
const instances = imageIds
|
|
50
|
+
.map((imageId) => getNaturalizedInstance(imageId))
|
|
51
|
+
.filter((instance) => instance !== undefined);
|
|
52
|
+
return ImageStackDisplaySet.fromInstances(instances, {
|
|
53
|
+
...options,
|
|
54
|
+
imageIds,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function buildSeriesInfo(instances) {
|
|
2
|
+
const NumberOfSeriesRelatedInstances = instances.length;
|
|
3
|
+
let numberOfFrames = 0;
|
|
4
|
+
let numberOfNonImageObjects = 0;
|
|
5
|
+
let numberOfSOPInstanceUIDsPerSeries = 0;
|
|
6
|
+
for (const instance of instances) {
|
|
7
|
+
if (instance.NumberOfFrames) {
|
|
8
|
+
numberOfFrames += Number(instance.NumberOfFrames);
|
|
9
|
+
}
|
|
10
|
+
else if (instance.Rows) {
|
|
11
|
+
numberOfFrames += 1;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
numberOfNonImageObjects += 1;
|
|
15
|
+
}
|
|
16
|
+
if (instance.SOPInstanceUID) {
|
|
17
|
+
numberOfSOPInstanceUIDsPerSeries += 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
NumberOfSeriesRelatedInstances,
|
|
22
|
+
numberOfFrames,
|
|
23
|
+
numImageFrames: numberOfFrames,
|
|
24
|
+
numberOfNonImageObjects,
|
|
25
|
+
numberOfSOPInstanceUIDsPerSeries,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IDisplaySet } from './IDisplaySet';
|
|
2
|
+
import type { InstanceGroup } from './types';
|
|
3
|
+
export type CreateDisplaySetFromGroupOptions = {
|
|
4
|
+
displaySetId?: string;
|
|
5
|
+
imageIds?: Iterable<string>;
|
|
6
|
+
splitNumber?: number;
|
|
7
|
+
descriptionName?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function createDisplaySetFromGroup(group: InstanceGroup, options?: CreateDisplaySetFromGroupOptions): IDisplaySet;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { BaseDisplaySet } from './BaseDisplaySet';
|
|
2
|
+
import { ImageStackDisplaySet } from './ImageStackDisplaySet';
|
|
3
|
+
import { isEcgInstance } from './isEcgInstance';
|
|
4
|
+
import { isVideoInstance } from './isVideoInstance';
|
|
5
|
+
import { isWsiInstance } from './isWsiInstance';
|
|
6
|
+
import { getPreferredViewportType, getViewportTypesForGroup, } from './viewportTypes';
|
|
7
|
+
const RESERVED_ATTRIBUTE_KEYS = new Set([
|
|
8
|
+
'imageIds',
|
|
9
|
+
'underlyingImageIds',
|
|
10
|
+
'instances',
|
|
11
|
+
'displaySetId',
|
|
12
|
+
]);
|
|
13
|
+
function isAssignable(target, key) {
|
|
14
|
+
let obj = target;
|
|
15
|
+
while (obj) {
|
|
16
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
17
|
+
if (descriptor) {
|
|
18
|
+
if (descriptor.get || descriptor.set) {
|
|
19
|
+
return typeof descriptor.set === 'function';
|
|
20
|
+
}
|
|
21
|
+
return descriptor.writable !== false;
|
|
22
|
+
}
|
|
23
|
+
obj = Object.getPrototypeOf(obj);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function applyCustomAttributes(displaySet, group, viewportTypes, options) {
|
|
28
|
+
const { instances, matchedRule } = group;
|
|
29
|
+
const first = instances[0];
|
|
30
|
+
if (!matchedRule.customAttributes || !first) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const sopClassUids = [
|
|
34
|
+
...new Set(instances.map((i) => i.SOPClassUID).filter(Boolean)),
|
|
35
|
+
];
|
|
36
|
+
const isMultiFrame = Number(first.NumberOfFrames) > 1;
|
|
37
|
+
const attributes = matchedRule.customAttributes({ instance: first, isMultiFrame, sopClassUids, viewportTypes }, {
|
|
38
|
+
instances,
|
|
39
|
+
splitNumber: options.splitNumber,
|
|
40
|
+
descriptionName: options.descriptionName,
|
|
41
|
+
});
|
|
42
|
+
if (!attributes) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
46
|
+
if (RESERVED_ATTRIBUTE_KEYS.has(key)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (isAssignable(displaySet, key)) {
|
|
50
|
+
displaySet[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
displaySet.preferredViewportType = getPreferredViewportType(displaySet.viewportTypes);
|
|
54
|
+
}
|
|
55
|
+
export function createDisplaySetFromGroup(group, options = {}) {
|
|
56
|
+
const viewportTypes = getViewportTypesForGroup(group);
|
|
57
|
+
const { instances } = group;
|
|
58
|
+
const baseDisplaySetId = instances[0]?.SeriesInstanceUID ??
|
|
59
|
+
`display-set-${instances[0]?.imageId ?? 'unknown'}`;
|
|
60
|
+
const displaySetId = options.displaySetId ??
|
|
61
|
+
(options.splitNumber
|
|
62
|
+
? `${baseDisplaySetId}:${options.splitNumber}`
|
|
63
|
+
: baseDisplaySetId);
|
|
64
|
+
const first = instances[0];
|
|
65
|
+
let displaySet;
|
|
66
|
+
if (first &&
|
|
67
|
+
(isVideoInstance(first) || isEcgInstance(first) || isWsiInstance(first))) {
|
|
68
|
+
const imageIds = instances.map((i) => i.imageId).filter(Boolean);
|
|
69
|
+
displaySet = new BaseDisplaySet({
|
|
70
|
+
displaySetId,
|
|
71
|
+
viewportTypes,
|
|
72
|
+
instances,
|
|
73
|
+
imageIds: options.imageIds ?? imageIds,
|
|
74
|
+
underlyingImageIds: imageIds,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
displaySet = ImageStackDisplaySet.fromInstances(instances, {
|
|
79
|
+
displaySetId,
|
|
80
|
+
viewportTypes,
|
|
81
|
+
imageIds: options.imageIds,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
applyCustomAttributes(displaySet, group, viewportTypes, options);
|
|
85
|
+
return displaySet;
|
|
86
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isEcgInstance } from './isEcgInstance';
|
|
2
|
+
import { isImageInstance } from './isImageInstance';
|
|
3
|
+
import { isVideoInstance } from './isVideoInstance';
|
|
4
|
+
import { isWsiInstance } from './isWsiInstance';
|
|
5
|
+
const VOLUME_MODALITIES = new Set(['CT', 'MR', 'PT', 'NM']);
|
|
6
|
+
export const defaultDisplaySetSplitRules = [
|
|
7
|
+
{
|
|
8
|
+
id: 'video',
|
|
9
|
+
viewportTypes: ['video'],
|
|
10
|
+
matches: (instance) => isVideoInstance(instance),
|
|
11
|
+
groupBy: ['SOPInstanceUID'],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'ecg',
|
|
15
|
+
viewportTypes: ['ecg'],
|
|
16
|
+
matches: (instance) => isEcgInstance(instance),
|
|
17
|
+
groupBy: ['SOPInstanceUID'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'wholeslide',
|
|
21
|
+
viewportTypes: ['wholeslide'],
|
|
22
|
+
matches: (instance) => isWsiInstance(instance),
|
|
23
|
+
groupBy: ['SeriesInstanceUID'],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'singleImageModality',
|
|
27
|
+
viewportTypes: ['stack'],
|
|
28
|
+
matches: (instance) => ['CR', 'DX', 'MG'].includes(instance.Modality ?? '') &&
|
|
29
|
+
isImageInstance(instance) &&
|
|
30
|
+
!!instance.Rows,
|
|
31
|
+
groupBy: [
|
|
32
|
+
'SeriesInstanceUID',
|
|
33
|
+
(instance) => `rows=${Math.round(Number(instance.Rows) / 64)}&cols=${Math.round(Number(instance.Columns) / 64)}`,
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'multiFrame',
|
|
38
|
+
viewportTypes: ['stack'],
|
|
39
|
+
series: ({ instances }) => {
|
|
40
|
+
const first = instances[0];
|
|
41
|
+
return {
|
|
42
|
+
isMultiFrame: Number(first?.NumberOfFrames) > 1 &&
|
|
43
|
+
first?.SliceLocation !== undefined,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
matches: (instance, { series }) => !!series.isMultiFrame && isImageInstance(instance) && !!instance.Rows,
|
|
47
|
+
groupBy: ['SeriesInstanceUID', 'InstanceNumber'],
|
|
48
|
+
customAttributes: ({ isMultiFrame }, options) => {
|
|
49
|
+
const numberOfFrames = options.instances[0]?.NumberOfFrames;
|
|
50
|
+
return {
|
|
51
|
+
isClip: true,
|
|
52
|
+
numImageFrames: numberOfFrames === undefined ? undefined : Number(numberOfFrames),
|
|
53
|
+
splitNumber: options.splitNumber,
|
|
54
|
+
isMultiFrame,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'mixedDimensionalityBValue',
|
|
60
|
+
viewportTypes: ['volume', 'volume3d', 'stack'],
|
|
61
|
+
series: ({ instances }) => {
|
|
62
|
+
const [instance] = instances;
|
|
63
|
+
if (!instance || instance.Modality !== 'MR') {
|
|
64
|
+
return { mixedBValue: false };
|
|
65
|
+
}
|
|
66
|
+
const hasBValue = instances.some((i) => i.DiffusionBValue !== undefined);
|
|
67
|
+
const missingBValue = instances.some((i) => i.DiffusionBValue === undefined);
|
|
68
|
+
return { mixedBValue: hasBValue && missingBValue };
|
|
69
|
+
},
|
|
70
|
+
matches: (instance, { series }) => !!series.mixedBValue && isImageInstance(instance) && !!instance.Rows,
|
|
71
|
+
groupBy: [
|
|
72
|
+
'SeriesInstanceUID',
|
|
73
|
+
(instance) => instance.DiffusionBValue === undefined,
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'volume3d',
|
|
78
|
+
viewportTypes: ['volume', 'volume3d', 'stack'],
|
|
79
|
+
series: ({ instances }) => {
|
|
80
|
+
const modality = instances[0]?.Modality;
|
|
81
|
+
return {
|
|
82
|
+
supportsVolume3d: !!modality && VOLUME_MODALITIES.has(modality) && instances.length > 1,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
matches: (instance, { series }) => !!series.supportsVolume3d && isImageInstance(instance) && !!instance.Rows,
|
|
86
|
+
groupBy: ['SeriesInstanceUID'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'defaultImageRule',
|
|
90
|
+
viewportTypes: ['stack', 'volume', 'volume3d'],
|
|
91
|
+
matches: (instance) => isImageInstance(instance) && !!instance.Rows,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerDisplaySetProviders(): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MetadataModules } from '../enums';
|
|
2
|
+
import { addAddProvider } from '../metaData';
|
|
3
|
+
import { addWritableCacheForType } from '../utilities/metadataProvider/cacheData';
|
|
4
|
+
function displaySetAddProvider(next, query, _data, options) {
|
|
5
|
+
const displaySet = options?.displaySet;
|
|
6
|
+
if (displaySet) {
|
|
7
|
+
return displaySet;
|
|
8
|
+
}
|
|
9
|
+
return next(query, _data, options);
|
|
10
|
+
}
|
|
11
|
+
export function registerDisplaySetProviders() {
|
|
12
|
+
addWritableCacheForType(MetadataModules.DISPLAY_SET);
|
|
13
|
+
addAddProvider(MetadataModules.DISPLAY_SET, displaySetAddProvider, {
|
|
14
|
+
priority: 40_000,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { buildSeriesInfo } from './buildSeriesInfo';
|
|
3
|
+
import { createDisplaySetFromGroup } from './createDisplaySetFromGroup';
|
|
4
|
+
import { defaultDisplaySetSplitRules } from './defaultDisplaySetSplitRules';
|
|
5
|
+
import { groupInstancesBySplitRules } from './groupInstancesBySplitRules';
|
|
6
|
+
import { ImageStackDisplaySet } from './ImageStackDisplaySet';
|
|
7
|
+
import { isVideoInstance } from './isVideoInstance';
|
|
8
|
+
import { resolveInstances } from './resolveInstances';
|
|
9
|
+
import { splitImageIdsBySplitRules } from './splitImageIdsBySplitRules';
|
|
10
|
+
import { getPreferredViewportType } from './viewportTypes';
|
|
11
|
+
describe('displayset split utilities', () => {
|
|
12
|
+
const instances = [
|
|
13
|
+
{
|
|
14
|
+
imageId: 'wadors:1',
|
|
15
|
+
Modality: 'CT',
|
|
16
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.2',
|
|
17
|
+
Rows: 512,
|
|
18
|
+
Columns: 512,
|
|
19
|
+
SeriesInstanceUID: '1.2.3',
|
|
20
|
+
InstanceNumber: 1,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
imageId: 'wadors:2',
|
|
24
|
+
Modality: 'CT',
|
|
25
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.2',
|
|
26
|
+
Rows: 512,
|
|
27
|
+
Columns: 512,
|
|
28
|
+
SeriesInstanceUID: '1.2.3',
|
|
29
|
+
InstanceNumber: 2,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
const getNaturalizedInstance = (imageId) => instances.find((instance) => instance.imageId === imageId);
|
|
33
|
+
it('resolveInstances preserves order and skips missing ids', () => {
|
|
34
|
+
const resolved = resolveInstances(['wadors:2', 'wadors:missing', 'wadors:1'], getNaturalizedInstance);
|
|
35
|
+
expect(resolved.map((i) => i.imageId)).toEqual(['wadors:2', 'wadors:1']);
|
|
36
|
+
});
|
|
37
|
+
it('default rules group multi-slice CT as volume (MPR) preferred', () => {
|
|
38
|
+
const groups = splitImageIdsBySplitRules(instances.map((i) => i.imageId), {
|
|
39
|
+
getNaturalizedInstance: (id) => instances.find((i) => i.imageId === id),
|
|
40
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
41
|
+
});
|
|
42
|
+
expect(groups).toHaveLength(1);
|
|
43
|
+
expect(groups[0].matchedRule.id).toBe('volume3d');
|
|
44
|
+
const displaySet = createDisplaySetFromGroup(groups[0]);
|
|
45
|
+
expect(displaySet.viewportTypes[0]).toBe('volume');
|
|
46
|
+
expect(displaySet.viewportTypes).toContain('volume3d');
|
|
47
|
+
expect(displaySet.preferredViewportType).toBe('volume');
|
|
48
|
+
});
|
|
49
|
+
it('video rule uses video viewportTypes', () => {
|
|
50
|
+
const videoInstance = {
|
|
51
|
+
imageId: 'wadors:video',
|
|
52
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.77.1.4.1',
|
|
53
|
+
Modality: 'US',
|
|
54
|
+
};
|
|
55
|
+
const groups = splitImageIdsBySplitRules(['wadors:video'], {
|
|
56
|
+
getNaturalizedInstance: () => videoInstance,
|
|
57
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
58
|
+
});
|
|
59
|
+
expect(groups[0].matchedRule.id).toBe('video');
|
|
60
|
+
const displaySet = createDisplaySetFromGroup(groups[0]);
|
|
61
|
+
expect(displaySet.viewportTypes).toEqual(['video']);
|
|
62
|
+
expect(getPreferredViewportType(displaySet.viewportTypes)).toBe('video');
|
|
63
|
+
expect(displaySet.instances[0]?.imageId).toBe('wadors:video');
|
|
64
|
+
});
|
|
65
|
+
it('ecg rule uses ecg viewportTypes', () => {
|
|
66
|
+
const ecgInstance = {
|
|
67
|
+
imageId: 'wadors:ecg',
|
|
68
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.9.1.1',
|
|
69
|
+
Modality: 'ECG',
|
|
70
|
+
};
|
|
71
|
+
const groups = splitImageIdsBySplitRules(['wadors:ecg'], {
|
|
72
|
+
getNaturalizedInstance: () => ecgInstance,
|
|
73
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
74
|
+
});
|
|
75
|
+
expect(groups[0].matchedRule.id).toBe('ecg');
|
|
76
|
+
expect(createDisplaySetFromGroup(groups[0]).viewportTypes[0]).toBe('ecg');
|
|
77
|
+
});
|
|
78
|
+
it('splits MR mixed B-value series', () => {
|
|
79
|
+
const mrInstances = [
|
|
80
|
+
{
|
|
81
|
+
imageId: 'wadors:a',
|
|
82
|
+
Modality: 'MR',
|
|
83
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
84
|
+
Rows: 256,
|
|
85
|
+
SeriesInstanceUID: 'series-mr',
|
|
86
|
+
DiffusionBValue: 800,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
imageId: 'wadors:b',
|
|
90
|
+
Modality: 'MR',
|
|
91
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
92
|
+
Rows: 256,
|
|
93
|
+
SeriesInstanceUID: 'series-mr',
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
const groups = splitImageIdsBySplitRules(mrInstances.map((i) => i.imageId), {
|
|
97
|
+
getNaturalizedInstance: (id) => mrInstances.find((i) => i.imageId === id),
|
|
98
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
99
|
+
});
|
|
100
|
+
expect(groups).toHaveLength(2);
|
|
101
|
+
});
|
|
102
|
+
it('ImageStackDisplaySet exposes underlying and frame ids', () => {
|
|
103
|
+
const displaySet = ImageStackDisplaySet.fromInstances(instances, {
|
|
104
|
+
displaySetId: 'uid-1',
|
|
105
|
+
viewportTypes: ['stack', 'volume', 'volume3d'],
|
|
106
|
+
});
|
|
107
|
+
expect(displaySet.underlyingImageIds.length).toBe(2);
|
|
108
|
+
expect(displaySet.viewportTypes[0]).toBe('stack');
|
|
109
|
+
expect(displaySet.preferredViewportType).toBe('stack');
|
|
110
|
+
});
|
|
111
|
+
it('groups by default image rule into a single group', () => {
|
|
112
|
+
const singleInstance = [instances[0]];
|
|
113
|
+
const rules = [
|
|
114
|
+
{
|
|
115
|
+
id: 'defaultImageRule',
|
|
116
|
+
viewportTypes: ['stack'],
|
|
117
|
+
matches: (instance) => instance.SOPClassUID === '1.2.840.10008.5.1.4.1.1.2' &&
|
|
118
|
+
!!instance.Rows,
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
const groups = groupInstancesBySplitRules(singleInstance, rules);
|
|
122
|
+
expect(groups).toHaveLength(1);
|
|
123
|
+
expect(groups[0].instances).toHaveLength(1);
|
|
124
|
+
});
|
|
125
|
+
it('spreads matched-rule customAttributes flat onto the display set', () => {
|
|
126
|
+
const multiFrameInstances = [
|
|
127
|
+
{
|
|
128
|
+
imageId: 'wadors:mf',
|
|
129
|
+
Modality: 'XA',
|
|
130
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.12.1',
|
|
131
|
+
Rows: 512,
|
|
132
|
+
NumberOfFrames: 30,
|
|
133
|
+
SliceLocation: 0,
|
|
134
|
+
SeriesInstanceUID: 'series-mf',
|
|
135
|
+
InstanceNumber: 1,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
const groups = splitImageIdsBySplitRules(['wadors:mf'], {
|
|
139
|
+
getNaturalizedInstance: () => multiFrameInstances[0],
|
|
140
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
141
|
+
});
|
|
142
|
+
expect(groups[0].matchedRule.id).toBe('multiFrame');
|
|
143
|
+
const displaySet = createDisplaySetFromGroup(groups[0], { splitNumber: 2 });
|
|
144
|
+
expect(displaySet.isClip).toBe(true);
|
|
145
|
+
expect(displaySet.numImageFrames).toBe(30);
|
|
146
|
+
expect(displaySet.splitNumber).toBe(2);
|
|
147
|
+
expect(displaySet.viewportTypes).toEqual(['stack']);
|
|
148
|
+
});
|
|
149
|
+
it('coerces a string NumberOfFrames to a numeric numImageFrames', () => {
|
|
150
|
+
const multiFrameInstances = [
|
|
151
|
+
{
|
|
152
|
+
imageId: 'wadors:mf-str',
|
|
153
|
+
Modality: 'XA',
|
|
154
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.12.1',
|
|
155
|
+
Rows: 512,
|
|
156
|
+
NumberOfFrames: '30',
|
|
157
|
+
SliceLocation: 0,
|
|
158
|
+
SeriesInstanceUID: 'series-mf-str',
|
|
159
|
+
InstanceNumber: 1,
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
const groups = splitImageIdsBySplitRules(['wadors:mf-str'], {
|
|
163
|
+
getNaturalizedInstance: () => multiFrameInstances[0],
|
|
164
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
165
|
+
});
|
|
166
|
+
expect(groups[0].matchedRule.id).toBe('multiFrame');
|
|
167
|
+
const displaySet = createDisplaySetFromGroup(groups[0]);
|
|
168
|
+
expect(displaySet.numImageFrames).toBe(30);
|
|
169
|
+
expect(typeof displaySet.numImageFrames).toBe('number');
|
|
170
|
+
});
|
|
171
|
+
it('classifies an MPEG2 transfer syntax instance as video', () => {
|
|
172
|
+
const mpeg2Instance = {
|
|
173
|
+
imageId: 'wadors:mpeg2',
|
|
174
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.7',
|
|
175
|
+
TransferSyntaxUID: '1.2.840.10008.1.2.4.100',
|
|
176
|
+
Modality: 'OT',
|
|
177
|
+
};
|
|
178
|
+
expect(isVideoInstance(mpeg2Instance)).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
it('buildSeriesInfo and grouping are safe on an empty instance list', () => {
|
|
181
|
+
expect(() => buildSeriesInfo([])).not.toThrow();
|
|
182
|
+
const seriesInfo = buildSeriesInfo([]);
|
|
183
|
+
expect(seriesInfo.NumberOfSeriesRelatedInstances).toBe(0);
|
|
184
|
+
expect(groupInstancesBySplitRules([], defaultDisplaySetSplitRules)).toEqual([]);
|
|
185
|
+
});
|
|
186
|
+
it('does not let customAttributes clobber resolved data fields', () => {
|
|
187
|
+
const stackInstances = [
|
|
188
|
+
{
|
|
189
|
+
imageId: 'wadors:reserved',
|
|
190
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.2',
|
|
191
|
+
Rows: 512,
|
|
192
|
+
SeriesInstanceUID: 'series-reserved',
|
|
193
|
+
InstanceNumber: 1,
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
const group = {
|
|
197
|
+
instances: stackInstances,
|
|
198
|
+
matchedRule: {
|
|
199
|
+
id: 'reserved-clobber',
|
|
200
|
+
viewportTypes: ['stack'],
|
|
201
|
+
customAttributes: () => ({
|
|
202
|
+
imageIds: ['evil-frame'],
|
|
203
|
+
underlyingImageIds: ['evil-underlying'],
|
|
204
|
+
instances: [],
|
|
205
|
+
displaySetId: 'evil-uid',
|
|
206
|
+
customFlag: true,
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const displaySet = createDisplaySetFromGroup(group, {
|
|
211
|
+
displaySetId: 'good-uid',
|
|
212
|
+
});
|
|
213
|
+
expect(displaySet.imageIds).toEqual(['wadors:reserved']);
|
|
214
|
+
expect(displaySet.underlyingImageIds).toEqual(['wadors:reserved']);
|
|
215
|
+
expect(displaySet.instances).toHaveLength(1);
|
|
216
|
+
expect(displaySet.displaySetId).toBe('good-uid');
|
|
217
|
+
expect(displaySet.customFlag).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
it('derives unique displaySetIds for splits of one series', () => {
|
|
220
|
+
const seriesUID = 'series-split';
|
|
221
|
+
const makeGroup = (imageId) => ({
|
|
222
|
+
instances: [
|
|
223
|
+
{
|
|
224
|
+
imageId,
|
|
225
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
226
|
+
Rows: 256,
|
|
227
|
+
SeriesInstanceUID: seriesUID,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
matchedRule: { id: 'split', viewportTypes: ['stack'] },
|
|
231
|
+
});
|
|
232
|
+
const ds0 = createDisplaySetFromGroup(makeGroup('wadors:s0'), {
|
|
233
|
+
splitNumber: 0,
|
|
234
|
+
});
|
|
235
|
+
const ds1 = createDisplaySetFromGroup(makeGroup('wadors:s1'), {
|
|
236
|
+
splitNumber: 1,
|
|
237
|
+
});
|
|
238
|
+
expect(ds0.displaySetId).toBe(seriesUID);
|
|
239
|
+
expect(ds1.displaySetId).toBe(`${seriesUID}:1`);
|
|
240
|
+
expect(ds0.displaySetId).not.toBe(ds1.displaySetId);
|
|
241
|
+
});
|
|
242
|
+
it('namespaces buckets by rule so identical split keys do not merge', () => {
|
|
243
|
+
const insts = [
|
|
244
|
+
{ imageId: 'a', Modality: 'XA' },
|
|
245
|
+
{ imageId: 'b', Modality: 'NM' },
|
|
246
|
+
];
|
|
247
|
+
const rules = [
|
|
248
|
+
{
|
|
249
|
+
id: 'ruleA',
|
|
250
|
+
matches: (i) => i.Modality === 'XA',
|
|
251
|
+
groupBy: [() => 'same'],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'ruleB',
|
|
255
|
+
matches: (i) => i.Modality === 'NM',
|
|
256
|
+
groupBy: [() => 'same'],
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
const groups = groupInstancesBySplitRules(insts, rules);
|
|
260
|
+
expect(groups).toHaveLength(2);
|
|
261
|
+
expect(new Set(groups.map((g) => g.matchedRule.id))).toEqual(new Set(['ruleA', 'ruleB']));
|
|
262
|
+
});
|
|
263
|
+
it('returns groups in a deterministic order regardless of input order', () => {
|
|
264
|
+
const mr = [
|
|
265
|
+
{
|
|
266
|
+
imageId: 'a',
|
|
267
|
+
Modality: 'MR',
|
|
268
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
269
|
+
Rows: 256,
|
|
270
|
+
SeriesInstanceUID: 's',
|
|
271
|
+
DiffusionBValue: 800,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
imageId: 'b',
|
|
275
|
+
Modality: 'MR',
|
|
276
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
277
|
+
Rows: 256,
|
|
278
|
+
SeriesInstanceUID: 's',
|
|
279
|
+
},
|
|
280
|
+
];
|
|
281
|
+
const options = {
|
|
282
|
+
getNaturalizedInstance: (id) => mr.find((i) => i.imageId === id),
|
|
283
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
284
|
+
};
|
|
285
|
+
const forward = splitImageIdsBySplitRules(['a', 'b'], options);
|
|
286
|
+
const reverse = splitImageIdsBySplitRules(['b', 'a'], options);
|
|
287
|
+
expect(forward.map((g) => g.splitKey)).toEqual(reverse.map((g) => g.splitKey));
|
|
288
|
+
});
|
|
289
|
+
it('series hook splits a mixed-b-value DWI series into two display sets', () => {
|
|
290
|
+
const mixed = [
|
|
291
|
+
{
|
|
292
|
+
imageId: 'b800',
|
|
293
|
+
Modality: 'MR',
|
|
294
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
295
|
+
Rows: 256,
|
|
296
|
+
SeriesInstanceUID: 'dwi',
|
|
297
|
+
DiffusionBValue: 800,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
imageId: 'noB',
|
|
301
|
+
Modality: 'MR',
|
|
302
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
303
|
+
Rows: 256,
|
|
304
|
+
SeriesInstanceUID: 'dwi',
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
const groups = splitImageIdsBySplitRules(['b800', 'noB'], {
|
|
308
|
+
getNaturalizedInstance: (id) => mixed.find((i) => i.imageId === id),
|
|
309
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
310
|
+
});
|
|
311
|
+
expect(groups).toHaveLength(2);
|
|
312
|
+
expect(groups.every((g) => g.matchedRule.id === 'mixedDimensionalityBValue')).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
it('series hook leaves a non-mixed DWI series as one volume display set', () => {
|
|
315
|
+
const allBValue = [
|
|
316
|
+
{
|
|
317
|
+
imageId: 'b0',
|
|
318
|
+
Modality: 'MR',
|
|
319
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
320
|
+
Rows: 256,
|
|
321
|
+
SeriesInstanceUID: 'dwi-uniform',
|
|
322
|
+
DiffusionBValue: 0,
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
imageId: 'b1000',
|
|
326
|
+
Modality: 'MR',
|
|
327
|
+
SOPClassUID: '1.2.840.10008.5.1.4.1.1.4',
|
|
328
|
+
Rows: 256,
|
|
329
|
+
SeriesInstanceUID: 'dwi-uniform',
|
|
330
|
+
DiffusionBValue: 1000,
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
const groups = splitImageIdsBySplitRules(['b0', 'b1000'], {
|
|
334
|
+
getNaturalizedInstance: (id) => allBValue.find((i) => i.imageId === id),
|
|
335
|
+
splitRules: defaultDisplaySetSplitRules,
|
|
336
|
+
});
|
|
337
|
+
expect(groups).toHaveLength(1);
|
|
338
|
+
expect(groups[0].matchedRule.id).toBe('volume3d');
|
|
339
|
+
});
|
|
340
|
+
it('reports instances that match no rule via onUnmatched', () => {
|
|
341
|
+
const insts = [
|
|
342
|
+
{ imageId: 'a', Modality: 'CT' },
|
|
343
|
+
{ imageId: 'b', Modality: 'SR' },
|
|
344
|
+
];
|
|
345
|
+
const rules = [
|
|
346
|
+
{
|
|
347
|
+
id: 'ct',
|
|
348
|
+
matches: (i) => i.Modality === 'CT',
|
|
349
|
+
groupBy: ['imageId'],
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
const unmatched = [];
|
|
353
|
+
const groups = groupInstancesBySplitRules(insts, rules, (i) => unmatched.push(i.imageId));
|
|
354
|
+
expect(unmatched).toEqual(['b']);
|
|
355
|
+
expect(groups).toHaveLength(1);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function buildSplitKey(instance, context, splitRule, ruleDiscriminator) {
|
|
2
|
+
const groupBy = splitRule.groupBy ?? ['SeriesInstanceUID'];
|
|
3
|
+
const parts = groupBy.map((key) => typeof key === 'function' ? key(instance, context) : instance[key]);
|
|
4
|
+
return JSON.stringify([ruleDiscriminator, ...parts]);
|
|
5
|
+
}
|
|
6
|
+
export function groupInstancesBySplitRules(instances, splitRules, onUnmatched) {
|
|
7
|
+
if (!instances.length) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const ruleContexts = splitRules.map((rule) => ({
|
|
11
|
+
series: rule.series?.({ instances }) ?? {},
|
|
12
|
+
}));
|
|
13
|
+
const instancesMap = new Map();
|
|
14
|
+
for (const instance of instances) {
|
|
15
|
+
let matched = false;
|
|
16
|
+
for (let ruleIndex = 0; ruleIndex < splitRules.length; ruleIndex++) {
|
|
17
|
+
const splitRule = splitRules[ruleIndex];
|
|
18
|
+
const context = ruleContexts[ruleIndex];
|
|
19
|
+
if (splitRule.matches && !splitRule.matches(instance, context)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
matched = true;
|
|
23
|
+
const key = buildSplitKey(instance, context, splitRule, `${ruleIndex}:${splitRule.id ?? ''}`);
|
|
24
|
+
let group = instancesMap.get(key);
|
|
25
|
+
if (!group) {
|
|
26
|
+
group = { instances: [], matchedRule: splitRule, splitKey: key };
|
|
27
|
+
instancesMap.set(key, group);
|
|
28
|
+
}
|
|
29
|
+
group.instances.push(instance);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
if (!matched) {
|
|
33
|
+
onUnmatched?.(instance);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Array.from(instancesMap.values()).sort((a, b) => (a.splitKey ?? '').localeCompare(b.splitKey ?? ''));
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type { IDisplaySet } from './IDisplaySet';
|
|
2
|
+
export { BaseDisplaySet } from './BaseDisplaySet';
|
|
3
|
+
export type { BaseDisplaySetOptions } from './BaseDisplaySet';
|
|
4
|
+
export { ImageStackDisplaySet } from './ImageStackDisplaySet';
|
|
5
|
+
export type { ImageStackDisplaySetOptions } from './ImageStackDisplaySet';
|
|
6
|
+
export { resolveInstances } from './resolveInstances';
|
|
7
|
+
export type { ResolveInstancesOptions } from './resolveInstances';
|
|
8
|
+
export { buildSeriesInfo } from './buildSeriesInfo';
|
|
9
|
+
export { groupInstancesBySplitRules } from './groupInstancesBySplitRules';
|
|
10
|
+
export { splitImageIdsBySplitRules } from './splitImageIdsBySplitRules';
|
|
11
|
+
export type { SplitImageIdsBySplitRulesOptions } from './splitImageIdsBySplitRules';
|
|
12
|
+
export { registerDisplaySetMetadata, type RegisterDisplaySetMetadataOptions, } from './registerDisplaySetMetadata';
|
|
13
|
+
export { registerDisplaySetProviders } from './displaySetProvider';
|
|
14
|
+
export { defaultDisplaySetSplitRules } from './defaultDisplaySetSplitRules';
|
|
15
|
+
export { createDisplaySetFromGroup } from './createDisplaySetFromGroup';
|
|
16
|
+
export type { CreateDisplaySetFromGroupOptions } from './createDisplaySetFromGroup';
|
|
17
|
+
export { isImageInstance } from './isImageInstance';
|
|
18
|
+
export { isVideoInstance } from './isVideoInstance';
|
|
19
|
+
export { isEcgInstance } from './isEcgInstance';
|
|
20
|
+
export { isWsiInstance } from './isWsiInstance';
|
|
21
|
+
export { getViewportTypesForRule, getPreferredViewportType, getViewportTypesForGroup, } from './viewportTypes';
|
|
22
|
+
export type { NaturalizedInstance, SeriesInfo, SeriesFacts, SeriesContext, RuleContext, SplitRule, SplitContext, SplitRuleOptions, SplitRuleCustomAttributesContext, InstanceGroup, ViewportTypeHint, } from './types';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { BaseDisplaySet } from './BaseDisplaySet';
|
|
2
|
+
export { ImageStackDisplaySet } from './ImageStackDisplaySet';
|
|
3
|
+
export { resolveInstances } from './resolveInstances';
|
|
4
|
+
export { buildSeriesInfo } from './buildSeriesInfo';
|
|
5
|
+
export { groupInstancesBySplitRules } from './groupInstancesBySplitRules';
|
|
6
|
+
export { splitImageIdsBySplitRules } from './splitImageIdsBySplitRules';
|
|
7
|
+
export { registerDisplaySetMetadata, } from './registerDisplaySetMetadata';
|
|
8
|
+
export { registerDisplaySetProviders } from './displaySetProvider';
|
|
9
|
+
export { defaultDisplaySetSplitRules } from './defaultDisplaySetSplitRules';
|
|
10
|
+
export { createDisplaySetFromGroup } from './createDisplaySetFromGroup';
|
|
11
|
+
export { isImageInstance } from './isImageInstance';
|
|
12
|
+
export { isVideoInstance } from './isVideoInstance';
|
|
13
|
+
export { isEcgInstance } from './isEcgInstance';
|
|
14
|
+
export { isWsiInstance } from './isWsiInstance';
|
|
15
|
+
export { getViewportTypesForRule, getPreferredViewportType, getViewportTypesForGroup, } from './viewportTypes';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const ECG_SOP_CLASS_UIDS = new Set([
|
|
2
|
+
'1.2.840.10008.5.1.4.1.1.9.1.1',
|
|
3
|
+
'1.2.840.10008.5.1.4.1.1.9.1.2',
|
|
4
|
+
'1.2.840.10008.5.1.4.1.1.9.1.3',
|
|
5
|
+
'1.2.840.10008.5.1.4.1.1.9.2.1',
|
|
6
|
+
'1.2.840.10008.5.1.4.1.1.9.3.1',
|
|
7
|
+
]);
|
|
8
|
+
export function isEcgInstance(instance) {
|
|
9
|
+
return ECG_SOP_CLASS_UIDS.has(instance.SOPClassUID ?? '');
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const IMAGE_STORAGE_SOP_CLASS_UIDS = new Set([
|
|
2
|
+
'1.2.840.10008.5.1.4.1.1.1',
|
|
3
|
+
'1.2.840.10008.5.1.4.1.1.1.1',
|
|
4
|
+
'1.2.840.10008.5.1.4.1.1.1.1.1',
|
|
5
|
+
'1.2.840.10008.5.1.4.1.1.2',
|
|
6
|
+
'1.2.840.10008.5.1.4.1.1.2.1',
|
|
7
|
+
'1.2.840.10008.5.1.4.1.1.2.2',
|
|
8
|
+
'1.2.840.10008.5.1.4.1.1.4',
|
|
9
|
+
'1.2.840.10008.5.1.4.1.1.4.1',
|
|
10
|
+
'1.2.840.10008.5.1.4.1.1.4.2',
|
|
11
|
+
'1.2.840.10008.5.1.4.1.1.4.3',
|
|
12
|
+
'1.2.840.10008.5.1.4.1.1.4.4',
|
|
13
|
+
'1.2.840.10008.5.1.4.1.1.7',
|
|
14
|
+
'1.2.840.10008.5.1.4.1.1.7.1',
|
|
15
|
+
'1.2.840.10008.5.1.4.1.1.7.2',
|
|
16
|
+
'1.2.840.10008.5.1.4.1.1.7.3',
|
|
17
|
+
'1.2.840.10008.5.1.4.1.1.7.4',
|
|
18
|
+
'1.2.840.10008.5.1.4.1.1.12.1',
|
|
19
|
+
'1.2.840.10008.5.1.4.1.1.12.1.1',
|
|
20
|
+
'1.2.840.10008.5.1.4.1.1.12.2',
|
|
21
|
+
'1.2.840.10008.5.1.4.1.1.12.2.1',
|
|
22
|
+
'1.2.840.10008.5.1.4.1.1.13.1.1',
|
|
23
|
+
'1.2.840.10008.5.1.4.1.1.13.1.2',
|
|
24
|
+
'1.2.840.10008.5.1.4.1.1.13.1.3',
|
|
25
|
+
'1.2.840.10008.5.1.4.1.1.13.1.4',
|
|
26
|
+
'1.2.840.10008.5.1.4.1.1.13.1.5',
|
|
27
|
+
'1.2.840.10008.5.1.4.1.1.13.1.6',
|
|
28
|
+
'1.2.840.10008.5.1.4.1.1.128',
|
|
29
|
+
'1.2.840.10008.5.1.4.1.1.77.1.1',
|
|
30
|
+
'1.2.840.10008.5.1.4.1.1.77.1.1.1',
|
|
31
|
+
'1.2.840.10008.5.1.4.1.1.77.1.2',
|
|
32
|
+
'1.2.840.10008.5.1.4.1.1.77.1.2.1',
|
|
33
|
+
'1.2.840.10008.5.1.4.1.1.77.1.3',
|
|
34
|
+
'1.2.840.10008.5.1.4.1.1.77.1.4',
|
|
35
|
+
'1.2.840.10008.5.1.4.1.1.77.1.4.1',
|
|
36
|
+
'1.2.840.10008.5.1.4.1.1.128.1',
|
|
37
|
+
'1.2.840.10008.5.1.4.1.1.128.2',
|
|
38
|
+
'1.2.840.10008.5.1.4.1.1.128.3',
|
|
39
|
+
'1.2.840.10008.5.1.4.1.1.128.4',
|
|
40
|
+
'1.2.840.10008.5.1.4.1.1.128.5',
|
|
41
|
+
'1.2.840.10008.5.1.4.1.1.77.1.6',
|
|
42
|
+
]);
|
|
43
|
+
export function isImageInstance(instance) {
|
|
44
|
+
return IMAGE_STORAGE_SOP_CLASS_UIDS.has(instance.SOPClassUID ?? '');
|
|
45
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { videoUIDs } from '../utilities/isVideoTransferSyntax';
|
|
2
|
+
const VIDEO_SOP_CLASS_UIDS = new Set([
|
|
3
|
+
'1.2.840.10008.5.1.4.1.1.77.1.2.1',
|
|
4
|
+
'1.2.840.10008.5.1.4.1.1.77.1.4.1',
|
|
5
|
+
'1.2.840.10008.5.1.4.1.1.77.1.1.1',
|
|
6
|
+
]);
|
|
7
|
+
const SECONDARY_CAPTURE_SOP_CLASS_UIDS = new Set([
|
|
8
|
+
'1.2.840.10008.5.1.4.1.1.7',
|
|
9
|
+
'1.2.840.10008.5.1.4.1.1.7.4',
|
|
10
|
+
]);
|
|
11
|
+
function getTransferSyntaxUids(instance) {
|
|
12
|
+
const tsuid = instance.AvailableTransferSyntaxUID ||
|
|
13
|
+
instance.TransferSyntaxUID ||
|
|
14
|
+
instance['00083002'];
|
|
15
|
+
return (Array.isArray(tsuid) ? tsuid : [tsuid]).filter((value) => typeof value === 'string' && value.length > 0);
|
|
16
|
+
}
|
|
17
|
+
export function isVideoInstance(instance) {
|
|
18
|
+
const tsuids = getTransferSyntaxUids(instance);
|
|
19
|
+
if (tsuids.some((tsuid) => videoUIDs.has(tsuid))) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (instance.SOPClassUID === '1.2.840.10008.5.1.4.1.1.77.1.4.1') {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (VIDEO_SOP_CLASS_UIDS.has(instance.SOPClassUID ?? '')) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const numberOfFrames = Number(instance.NumberOfFrames) || 0;
|
|
29
|
+
return (SECONDARY_CAPTURE_SOP_CLASS_UIDS.has(instance.SOPClassUID ?? '') &&
|
|
30
|
+
numberOfFrames >= 90);
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IDisplaySet } from './IDisplaySet';
|
|
2
|
+
export type RegisterDisplaySetMetadataOptions = {
|
|
3
|
+
includeImageIds?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare function registerDisplaySetMetadata(imageIds: string[], displaySet: IDisplaySet, options?: RegisterDisplaySetMetadataOptions): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MetadataModules } from '../enums';
|
|
2
|
+
import { addTyped } from '../metaData';
|
|
3
|
+
export function registerDisplaySetMetadata(imageIds, displaySet, options = {}) {
|
|
4
|
+
const idsToRegister = new Set(imageIds);
|
|
5
|
+
if (options.includeImageIds) {
|
|
6
|
+
for (const frameId of displaySet.imageIds) {
|
|
7
|
+
idsToRegister.add(frameId);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
for (const underlyingId of displaySet.underlyingImageIds) {
|
|
11
|
+
idsToRegister.add(underlyingId);
|
|
12
|
+
}
|
|
13
|
+
for (const imageId of idsToRegister) {
|
|
14
|
+
if (!imageId) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
addTyped(MetadataModules.DISPLAY_SET, imageId, { displaySet });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { NaturalizedInstance } from './types';
|
|
2
|
+
export type ResolveInstancesOptions = {
|
|
3
|
+
skipMissing?: boolean;
|
|
4
|
+
onMissing?: (imageId: string) => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveInstances(imageIds: string[], getNaturalizedInstance: (imageId: string) => NaturalizedInstance | undefined, options?: ResolveInstancesOptions): NaturalizedInstance[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function resolveInstances(imageIds, getNaturalizedInstance, options = {}) {
|
|
2
|
+
const { skipMissing = true, onMissing } = options;
|
|
3
|
+
const instances = [];
|
|
4
|
+
for (const imageId of imageIds) {
|
|
5
|
+
const instance = getNaturalizedInstance(imageId);
|
|
6
|
+
if (!instance) {
|
|
7
|
+
if (!skipMissing) {
|
|
8
|
+
throw new Error(`No naturalized instance for imageId: ${imageId}`);
|
|
9
|
+
}
|
|
10
|
+
onMissing?.(imageId);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
instances.push(instance);
|
|
14
|
+
}
|
|
15
|
+
return instances;
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InstanceGroup, NaturalizedInstance, SplitContext, SplitRule } from './types';
|
|
2
|
+
export type SplitImageIdsBySplitRulesOptions = SplitContext & {
|
|
3
|
+
splitRules: SplitRule[];
|
|
4
|
+
onMissingImageId?: (imageId: string) => void;
|
|
5
|
+
onUnmatchedInstance?: (instance: NaturalizedInstance) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function splitImageIdsBySplitRules(imageIds: string[], options: SplitImageIdsBySplitRulesOptions): InstanceGroup[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { groupInstancesBySplitRules } from './groupInstancesBySplitRules';
|
|
2
|
+
import { resolveInstances } from './resolveInstances';
|
|
3
|
+
export function splitImageIdsBySplitRules(imageIds, options) {
|
|
4
|
+
const { getNaturalizedInstance, splitRules, onMissingImageId, onUnmatchedInstance, } = options;
|
|
5
|
+
const instances = resolveInstances(imageIds, getNaturalizedInstance, {
|
|
6
|
+
onMissing: onMissingImageId,
|
|
7
|
+
});
|
|
8
|
+
if (!instances.length) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return groupInstancesBySplitRules(instances, splitRules, onUnmatchedInstance);
|
|
12
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type NaturalizedInstance = {
|
|
2
|
+
imageId?: string;
|
|
3
|
+
Modality?: string;
|
|
4
|
+
SOPClassUID?: string;
|
|
5
|
+
Rows?: number;
|
|
6
|
+
Columns?: number;
|
|
7
|
+
NumberOfFrames?: number;
|
|
8
|
+
SliceLocation?: number;
|
|
9
|
+
SeriesInstanceUID?: string;
|
|
10
|
+
InstanceNumber?: number;
|
|
11
|
+
DiffusionBValue?: number;
|
|
12
|
+
TransferSyntaxUID?: string;
|
|
13
|
+
AvailableTransferSyntaxUID?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
export type ViewportTypeHint = 'stack' | 'volume' | 'volume3d' | 'video' | 'wholeslide' | 'ecg' | string;
|
|
17
|
+
export type SeriesInfo = {
|
|
18
|
+
NumberOfSeriesRelatedInstances: number;
|
|
19
|
+
numberOfFrames: number;
|
|
20
|
+
numImageFrames: number;
|
|
21
|
+
numberOfNonImageObjects: number;
|
|
22
|
+
numberOfSOPInstanceUIDsPerSeries: number;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
export type SeriesFacts = Record<string, unknown>;
|
|
26
|
+
export type SeriesContext = {
|
|
27
|
+
instances: NaturalizedInstance[];
|
|
28
|
+
};
|
|
29
|
+
export type RuleContext = {
|
|
30
|
+
series: SeriesFacts;
|
|
31
|
+
};
|
|
32
|
+
export type SplitRuleCustomAttributesContext = {
|
|
33
|
+
instance: NaturalizedInstance;
|
|
34
|
+
isMultiFrame?: boolean;
|
|
35
|
+
sopClassUids?: string[];
|
|
36
|
+
viewportTypes?: readonly ViewportTypeHint[];
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
};
|
|
39
|
+
export type SplitRuleOptions = {
|
|
40
|
+
instances: NaturalizedInstance[];
|
|
41
|
+
splitNumber?: number;
|
|
42
|
+
descriptionName?: string;
|
|
43
|
+
};
|
|
44
|
+
export type SplitRule = {
|
|
45
|
+
id?: string;
|
|
46
|
+
viewportTypes?: readonly ViewportTypeHint[];
|
|
47
|
+
series?: (context: SeriesContext) => SeriesFacts;
|
|
48
|
+
matches?: (instance: NaturalizedInstance, context: RuleContext) => boolean;
|
|
49
|
+
groupBy?: (string | ((instance: NaturalizedInstance, context: RuleContext) => unknown))[];
|
|
50
|
+
customAttributes?: (attributes: SplitRuleCustomAttributesContext, options: SplitRuleOptions) => Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
export type SplitContext = {
|
|
53
|
+
getNaturalizedInstance: (imageId: string) => NaturalizedInstance | undefined;
|
|
54
|
+
};
|
|
55
|
+
export type InstanceGroup = {
|
|
56
|
+
instances: NaturalizedInstance[];
|
|
57
|
+
matchedRule: SplitRule;
|
|
58
|
+
splitKey?: string;
|
|
59
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { InstanceGroup, SplitRule, ViewportTypeHint } from './types';
|
|
2
|
+
export declare function getViewportTypesForRule(rule: SplitRule): readonly ViewportTypeHint[];
|
|
3
|
+
export declare function getPreferredViewportType(viewportTypes: readonly ViewportTypeHint[]): ViewportTypeHint;
|
|
4
|
+
export declare function getViewportTypesForGroup(group: InstanceGroup): readonly ViewportTypeHint[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const DEFAULT_VIEWPORT_TYPES = ['stack'];
|
|
2
|
+
export function getViewportTypesForRule(rule) {
|
|
3
|
+
if (rule.viewportTypes?.length) {
|
|
4
|
+
return rule.viewportTypes;
|
|
5
|
+
}
|
|
6
|
+
return DEFAULT_VIEWPORT_TYPES;
|
|
7
|
+
}
|
|
8
|
+
export function getPreferredViewportType(viewportTypes) {
|
|
9
|
+
return viewportTypes[0] ?? 'stack';
|
|
10
|
+
}
|
|
11
|
+
export function getViewportTypesForGroup(group) {
|
|
12
|
+
return getViewportTypesForRule(group.matchedRule);
|
|
13
|
+
}
|
|
@@ -47,7 +47,8 @@ declare enum MetadataModules {
|
|
|
47
47
|
COMPRESSED_FRAME_DATA = "compressedFrameData",
|
|
48
48
|
BASE_IMAGE_ID = "baseImageId",
|
|
49
49
|
FRAME_IMAGE_IDS = "frameImageIds",
|
|
50
|
-
NATURALIZED = "naturalized"
|
|
50
|
+
NATURALIZED = "naturalized",
|
|
51
|
+
DISPLAY_SET = "displaySetModule"
|
|
51
52
|
}
|
|
52
53
|
export declare const ADD_MODULE_TYPE_SUFFIX = "Add";
|
|
53
54
|
export declare function getAddModuleType(type: string): string;
|
|
@@ -49,6 +49,7 @@ var MetadataModules;
|
|
|
49
49
|
MetadataModules["BASE_IMAGE_ID"] = "baseImageId";
|
|
50
50
|
MetadataModules["FRAME_IMAGE_IDS"] = "frameImageIds";
|
|
51
51
|
MetadataModules["NATURALIZED"] = "naturalized";
|
|
52
|
+
MetadataModules["DISPLAY_SET"] = "displaySetModule";
|
|
52
53
|
})(MetadataModules || (MetadataModules = {}));
|
|
53
54
|
export const ADD_MODULE_TYPE_SUFFIX = 'Add';
|
|
54
55
|
export function getAddModuleType(type) {
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export * as Enums from './enums';
|
|
|
3
3
|
export { version } from './version';
|
|
4
4
|
export * as metaData from './metaData';
|
|
5
5
|
export * as utilities from './utilities';
|
|
6
|
+
export * as displaySet from './displayset';
|
|
7
|
+
export type { IDisplaySet, BaseDisplaySetOptions, ImageStackDisplaySetOptions, ResolveInstancesOptions, SplitImageIdsBySplitRulesOptions, RegisterDisplaySetMetadataOptions, NaturalizedInstance, SeriesInfo, SeriesFacts, SeriesContext, RuleContext, SplitRule, SplitContext, SplitRuleOptions, SplitRuleCustomAttributesContext, InstanceGroup, ViewportTypeHint, } from './displayset';
|
|
8
|
+
export { BaseDisplaySet, ImageStackDisplaySet, resolveInstances, buildSeriesInfo, groupInstancesBySplitRules, splitImageIdsBySplitRules, registerDisplaySetMetadata, registerDisplaySetProviders, defaultDisplaySetSplitRules, createDisplaySetFromGroup, isImageInstance, isVideoInstance, isEcgInstance, isWsiInstance, getViewportTypesForRule, getPreferredViewportType, getViewportTypesForGroup, } from './displayset';
|
|
9
|
+
export type { CreateDisplaySetFromGroupOptions } from './displayset';
|
|
6
10
|
export * as logging from './utilities/logging';
|
|
7
11
|
export { registerDefaultProviders } from './registerDefaultProviders';
|
|
8
12
|
export type * from './types';
|
package/dist/esm/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export * as Enums from './enums';
|
|
|
3
3
|
export { version } from './version';
|
|
4
4
|
export * as metaData from './metaData';
|
|
5
5
|
export * as utilities from './utilities';
|
|
6
|
+
export * as displaySet from './displayset';
|
|
7
|
+
export { BaseDisplaySet, ImageStackDisplaySet, resolveInstances, buildSeriesInfo, groupInstancesBySplitRules, splitImageIdsBySplitRules, registerDisplaySetMetadata, registerDisplaySetProviders, defaultDisplaySetSplitRules, createDisplaySetFromGroup, isImageInstance, isVideoInstance, isEcgInstance, isWsiInstance, getViewportTypesForRule, getPreferredViewportType, getViewportTypesForGroup, } from './displayset';
|
|
6
8
|
export * as logging from './utilities/logging';
|
|
7
9
|
export { registerDefaultProviders } from './registerDefaultProviders';
|
|
8
10
|
const { calculateSUVScalingFactors } = calculateSUV;
|
|
@@ -15,6 +15,7 @@ import { registerCompressedFrameDataProvider } from './utilities/metadataProvide
|
|
|
15
15
|
import { registerScalingFromInstanceProvider } from './utilities/metadataProvider/scalingFromInstance';
|
|
16
16
|
import { registerNaturalizedHandlers } from './utilities/metadataProvider/naturalizedHandlers';
|
|
17
17
|
import { registerImageIdProviders } from './utilities/metadataProvider/imageIdsProviders';
|
|
18
|
+
import { registerDisplaySetProviders } from './displayset/displaySetProvider';
|
|
18
19
|
const TYPED_PROVIDER_BRIDGE_PRIORITY = -1000;
|
|
19
20
|
export function registerDefaultProviders() {
|
|
20
21
|
addProvider(metadataModuleProvider, TYPED_PROVIDER_BRIDGE_PRIORITY);
|
|
@@ -24,6 +25,7 @@ export function registerDefaultProviders() {
|
|
|
24
25
|
registerNaturalizedHandlers();
|
|
25
26
|
registerUriModule();
|
|
26
27
|
registerImageIdProviders();
|
|
28
|
+
registerDisplaySetProviders();
|
|
27
29
|
registerDataLookup();
|
|
28
30
|
registerInstanceFromListener();
|
|
29
31
|
registerCombineFrameProvider();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IDisplaySet } from '../displayset/IDisplaySet';
|
|
1
2
|
export interface DicomDateObject {
|
|
2
3
|
year: number;
|
|
3
4
|
month: number;
|
|
@@ -97,4 +98,5 @@ export interface MetadataModuleType {
|
|
|
97
98
|
frameModule: FrameMetadata;
|
|
98
99
|
transferSyntax: TransferSyntaxMetadata;
|
|
99
100
|
compressedFrameData: CompressedFrameDataMetadata;
|
|
101
|
+
displaySetModule: IDisplaySet;
|
|
100
102
|
}
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "5.0
|
|
1
|
+
export declare const version = "5.1.0";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '5.0
|
|
1
|
+
export const version = '5.1.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/metadata",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Cornerstone3D Metadata Foundation - Provider chain, DICOM stream parsing, tag modules, and series splitting",
|
|
5
5
|
"module": "./dist/esm/index.js",
|
|
6
6
|
"types": "./dist/esm/index.d.ts",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@cornerstonejs/calculate-suv": "1.0.3",
|
|
83
|
-
"@cornerstonejs/utils": "5.0
|
|
83
|
+
"@cornerstonejs/utils": "5.1.0",
|
|
84
84
|
"dcmjs": "0.50.1",
|
|
85
85
|
"gl-matrix": "3.4.3"
|
|
86
86
|
},
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
"type": "individual",
|
|
96
96
|
"url": "https://ohif.org/donate"
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "f86b3b590f646412726bfb8bbea8e6724f36cbf1"
|
|
99
99
|
}
|