@cornerstonejs/metadata 5.13.1 → 5.14.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 +3 -4
- package/dist/esm/displayset/BaseDisplaySet.js +1 -5
- package/dist/esm/displayset/IDisplaySet.d.ts +9 -3
- package/dist/esm/displayset/ImageStackDisplaySet.d.ts +1 -3
- package/dist/esm/displayset/ImageStackDisplaySet.js +0 -1
- package/dist/esm/displayset/__fixtures__/scenarioInstances.d.ts +12 -0
- package/dist/esm/displayset/__fixtures__/scenarioInstances.js +210 -0
- package/dist/esm/displayset/compileSplitRules.d.ts +3 -0
- package/dist/esm/displayset/compileSplitRules.js +197 -0
- package/dist/esm/displayset/compiledDisplaySetRules.d.ts +3 -0
- package/dist/esm/displayset/compiledDisplaySetRules.js +3 -0
- package/dist/esm/displayset/createDisplaySetFromGroup.js +54 -19
- package/dist/esm/displayset/displaySetContext.d.ts +150 -0
- package/dist/esm/displayset/displaySetContext.js +918 -0
- package/dist/esm/displayset/displaySetRules.d.ts +5 -0
- package/dist/esm/displayset/displaySetRules.js +142 -0
- package/dist/esm/displayset/displaySetRules.test.d.ts +1 -0
- package/dist/esm/displayset/displaySetRules.test.js +1306 -0
- package/dist/esm/displayset/displaySetRulesTypes.d.ts +34 -0
- package/dist/esm/displayset/displaySetRulesTypes.js +0 -0
- package/dist/esm/displayset/displayset.test.js +422 -130
- package/dist/esm/displayset/groupInstancesBySplitRules.d.ts +8 -2
- package/dist/esm/displayset/groupInstancesBySplitRules.js +174 -17
- package/dist/esm/displayset/index.d.ts +14 -4
- package/dist/esm/displayset/index.js +10 -3
- package/dist/esm/displayset/isEcgInstance.d.ts +2 -1
- package/dist/esm/displayset/isEcgInstance.js +4 -9
- package/dist/esm/displayset/isImageInstance.d.ts +2 -1
- package/dist/esm/displayset/isImageInstance.js +4 -44
- package/dist/esm/displayset/isReconstructableInstances.d.ts +2 -0
- package/dist/esm/displayset/isReconstructableInstances.js +91 -0
- package/dist/esm/displayset/isVideoInstance.d.ts +2 -1
- package/dist/esm/displayset/isVideoInstance.js +4 -30
- package/dist/esm/displayset/isWsiInstance.d.ts +2 -1
- package/dist/esm/displayset/isWsiInstance.js +4 -6
- package/dist/esm/displayset/metadataContext.d.ts +13 -0
- package/dist/esm/displayset/metadataContext.js +42 -0
- package/dist/esm/displayset/metadataContext.test.d.ts +1 -0
- package/dist/esm/displayset/metadataContext.test.js +62 -0
- package/dist/esm/displayset/scenarioFixtures.test.d.ts +1 -0
- package/dist/esm/displayset/scenarioFixtures.test.js +206 -0
- package/dist/esm/displayset/seriesContext.test.d.ts +1 -0
- package/dist/esm/displayset/seriesContext.test.js +238 -0
- package/dist/esm/displayset/seriesFunctions.d.ts +29 -0
- package/dist/esm/displayset/seriesFunctions.js +327 -0
- package/dist/esm/displayset/sopClassUidLists.d.ts +14 -0
- package/dist/esm/displayset/sopClassUidLists.js +100 -0
- package/dist/esm/displayset/sortInstances.d.ts +10 -0
- package/dist/esm/displayset/sortInstances.js +33 -0
- package/dist/esm/displayset/sortInstances.test.d.ts +1 -0
- package/dist/esm/displayset/sortInstances.test.js +56 -0
- package/dist/esm/displayset/splitImageIdsBySplitRules.d.ts +3 -3
- package/dist/esm/displayset/splitImageIdsBySplitRules.js +5 -5
- package/dist/esm/displayset/splitRuleSchema.d.ts +11 -0
- package/dist/esm/displayset/splitRuleSchema.js +168 -0
- package/dist/esm/displayset/splitRuleSet.d.ts +12 -0
- package/dist/esm/displayset/splitRuleSet.js +47 -0
- package/dist/esm/displayset/splitRuleSet.test.d.ts +1 -0
- package/dist/esm/displayset/splitRuleSet.test.js +119 -0
- package/dist/esm/displayset/types.d.ts +37 -4
- package/dist/esm/displayset/types.js +15 -0
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/safeFunctions/compile.d.ts +31 -0
- package/dist/esm/safeFunctions/compile.js +142 -0
- package/dist/esm/safeFunctions/compile.test.d.ts +1 -0
- package/dist/esm/safeFunctions/compile.test.js +82 -0
- package/dist/esm/safeFunctions/expression/compiler.d.ts +23 -0
- package/dist/esm/safeFunctions/expression/compiler.js +481 -0
- package/dist/esm/safeFunctions/expression/expression.test.d.ts +1 -0
- package/dist/esm/safeFunctions/expression/expression.test.js +325 -0
- package/dist/esm/safeFunctions/expression/index.d.ts +4 -0
- package/dist/esm/safeFunctions/expression/index.js +3 -0
- package/dist/esm/safeFunctions/expression/parser.d.ts +56 -0
- package/dist/esm/safeFunctions/expression/parser.js +248 -0
- package/dist/esm/safeFunctions/expression/tokenizer.d.ts +29 -0
- package/dist/esm/safeFunctions/expression/tokenizer.js +198 -0
- package/dist/esm/safeFunctions/index.d.ts +5 -0
- package/dist/esm/safeFunctions/index.js +4 -0
- package/dist/esm/safeFunctions/matchTests.d.ts +16 -0
- package/dist/esm/safeFunctions/matchTests.js +203 -0
- package/dist/esm/safeFunctions/schema.d.ts +72 -0
- package/dist/esm/safeFunctions/schema.js +361 -0
- package/dist/esm/safeFunctions/types.d.ts +47 -0
- package/dist/esm/safeFunctions/types.js +0 -0
- package/dist/esm/safeFunctions/values.d.ts +6 -0
- package/dist/esm/safeFunctions/values.js +71 -0
- package/dist/esm/utilities/Tags.d.ts +1 -0
- package/dist/esm/utilities/Tags.js +14 -2
- package/dist/esm/utilities/index.d.ts +1 -1
- package/dist/esm/utilities/index.js +1 -1
- package/dist/esm/utilities/toNumber.d.ts +1 -1
- package/dist/esm/utilities/toNumber.js +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -2
- package/dist/esm/displayset/defaultDisplaySetSplitRules.d.ts +0 -2
- package/dist/esm/displayset/defaultDisplaySetSplitRules.js +0 -93
- package/dist/esm/displayset/viewportTypes.d.ts +0 -4
- package/dist/esm/displayset/viewportTypes.js +0 -13
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import type { InstanceGroup, NaturalizedInstance, SplitRule } from './types.js';
|
|
2
|
-
export declare function
|
|
1
|
+
import type { GroupInstancesOptions, InstanceGroup, NaturalizedInstance, OrderInstancesOptions, SeriesFacts, SplitRule, SplitRuleSet } from './types.js';
|
|
2
|
+
export declare function computeSeriesFacts(instances: NaturalizedInstance[], rules: readonly SplitRule[]): SeriesFacts;
|
|
3
|
+
type Comparator = NonNullable<SplitRule['compareInstances']>;
|
|
4
|
+
export declare function chainComparators(comparators: Comparator[]): Comparator;
|
|
5
|
+
export declare function orderInstancesForRule(instances: NaturalizedInstance[], splitRule: SplitRule, options?: OrderInstancesOptions): NaturalizedInstance[];
|
|
6
|
+
export declare function normalizeSplitKeyPart(value: unknown): unknown;
|
|
7
|
+
export declare function groupInstancesBySplitRules(instances: NaturalizedInstance[], ruleSet: SplitRuleSet, onUnmatched?: (instance: NaturalizedInstance) => void, options?: GroupInstancesOptions): InstanceGroup[];
|
|
8
|
+
export {};
|
|
@@ -1,37 +1,194 @@
|
|
|
1
|
-
|
|
1
|
+
import { resolveSplitRuleSet } from './splitRuleSet.js';
|
|
2
|
+
import { displaySetContextFor, withInstances } from './displaySetContext.js';
|
|
3
|
+
import { sortInstancesCopy } from './sortInstances.js';
|
|
4
|
+
import { hasNoValue, isUnsafeKey } from '../safeFunctions/index.js';
|
|
5
|
+
export function computeSeriesFacts(instances, rules) {
|
|
6
|
+
const facts = {};
|
|
7
|
+
for (const rule of rules) {
|
|
8
|
+
if (!rule.series) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const computed = rule.series({ instances, series: facts }) ?? {};
|
|
12
|
+
for (const [name, value] of Object.entries(computed)) {
|
|
13
|
+
if (!isUnsafeKey(name) &&
|
|
14
|
+
!Object.prototype.hasOwnProperty.call(facts, name)) {
|
|
15
|
+
facts[name] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return facts;
|
|
20
|
+
}
|
|
21
|
+
export function chainComparators(comparators) {
|
|
22
|
+
if (comparators.length === 1) {
|
|
23
|
+
return comparators[0];
|
|
24
|
+
}
|
|
25
|
+
return (a, b, context) => {
|
|
26
|
+
for (const comparator of comparators) {
|
|
27
|
+
const order = comparator(a, b, context);
|
|
28
|
+
if (Number.isFinite(order) && order !== 0) {
|
|
29
|
+
return order;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildInstanceOrderer(splitRule, context, options) {
|
|
36
|
+
const comparators = [
|
|
37
|
+
splitRule.compareInstances,
|
|
38
|
+
options.compareInstances,
|
|
39
|
+
].filter((comparator) => typeof comparator === 'function');
|
|
40
|
+
const baseSort = options.sortInstances;
|
|
41
|
+
const orderContext = {
|
|
42
|
+
matchedRule: splitRule,
|
|
43
|
+
series: context.series,
|
|
44
|
+
};
|
|
45
|
+
return (instances) => {
|
|
46
|
+
let ordered = sortInstancesCopy(instances);
|
|
47
|
+
if (baseSort) {
|
|
48
|
+
ordered = [...baseSort(ordered, orderContext)];
|
|
49
|
+
}
|
|
50
|
+
if (comparators.length) {
|
|
51
|
+
const compare = chainComparators(comparators);
|
|
52
|
+
const sortContext = {
|
|
53
|
+
...context,
|
|
54
|
+
displaySet: displaySetContextFor(context.series, ordered),
|
|
55
|
+
};
|
|
56
|
+
ordered.sort((a, b) => compare(a, b, sortContext));
|
|
57
|
+
}
|
|
58
|
+
return ordered;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function orderInstancesForRule(instances, splitRule, options = {}) {
|
|
62
|
+
const context = {
|
|
63
|
+
series: options.series
|
|
64
|
+
? withInstances(options.series, instances)
|
|
65
|
+
: computeSeriesFacts(sortInstancesCopy(instances), [splitRule]),
|
|
66
|
+
};
|
|
67
|
+
return buildInstanceOrderer(splitRule, context, options)(instances);
|
|
68
|
+
}
|
|
69
|
+
function isDigitAt(value, index) {
|
|
70
|
+
const code = value.charCodeAt(index);
|
|
71
|
+
return code >= 0x30 && code <= 0x39;
|
|
72
|
+
}
|
|
73
|
+
function digitRunEnd(value, start) {
|
|
74
|
+
let end = start;
|
|
75
|
+
while (end < value.length && isDigitAt(value, end)) {
|
|
76
|
+
end += 1;
|
|
77
|
+
}
|
|
78
|
+
return end;
|
|
79
|
+
}
|
|
80
|
+
function compareDigitRuns(a, b) {
|
|
81
|
+
const aValue = a.replace(/^0+/, '');
|
|
82
|
+
const bValue = b.replace(/^0+/, '');
|
|
83
|
+
if (aValue.length !== bValue.length) {
|
|
84
|
+
return aValue.length - bValue.length;
|
|
85
|
+
}
|
|
86
|
+
if (aValue !== bValue) {
|
|
87
|
+
return aValue < bValue ? -1 : 1;
|
|
88
|
+
}
|
|
89
|
+
return a.length - b.length;
|
|
90
|
+
}
|
|
91
|
+
function compareSplitKeys(a, b) {
|
|
92
|
+
let aIndex = 0;
|
|
93
|
+
let bIndex = 0;
|
|
94
|
+
while (aIndex < a.length && bIndex < b.length) {
|
|
95
|
+
if (isDigitAt(a, aIndex) && isDigitAt(b, bIndex)) {
|
|
96
|
+
const aEnd = digitRunEnd(a, aIndex);
|
|
97
|
+
const bEnd = digitRunEnd(b, bIndex);
|
|
98
|
+
const byDigits = compareDigitRuns(a.slice(aIndex, aEnd), b.slice(bIndex, bEnd));
|
|
99
|
+
if (byDigits !== 0) {
|
|
100
|
+
return byDigits;
|
|
101
|
+
}
|
|
102
|
+
aIndex = aEnd;
|
|
103
|
+
bIndex = bEnd;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const aCode = a.charCodeAt(aIndex);
|
|
107
|
+
const bCode = b.charCodeAt(bIndex);
|
|
108
|
+
if (aCode !== bCode) {
|
|
109
|
+
return aCode - bCode;
|
|
110
|
+
}
|
|
111
|
+
aIndex += 1;
|
|
112
|
+
bIndex += 1;
|
|
113
|
+
}
|
|
114
|
+
return a.length - aIndex - (b.length - bIndex);
|
|
115
|
+
}
|
|
116
|
+
export function normalizeSplitKeyPart(value) {
|
|
117
|
+
if (hasNoValue(value)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
const parts = value.map(normalizeSplitKeyPart);
|
|
122
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
123
|
+
}
|
|
124
|
+
if (typeof value === 'string') {
|
|
125
|
+
const number = Number(value);
|
|
126
|
+
return Number.isFinite(number) && String(number) === value ? number : value;
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
function buildGroupByParts(instance, context, splitRule) {
|
|
2
131
|
const groupBy = splitRule.groupBy ?? ['SeriesInstanceUID'];
|
|
3
|
-
|
|
4
|
-
|
|
132
|
+
return groupBy.map((key) => normalizeSplitKeyPart(typeof key === 'function' ? key(instance, context) : instance[key]));
|
|
133
|
+
}
|
|
134
|
+
function buildSplitKey(ruleDiscriminator, baseKeyParts) {
|
|
135
|
+
return JSON.stringify([ruleDiscriminator, ...baseKeyParts]);
|
|
5
136
|
}
|
|
6
|
-
export function groupInstancesBySplitRules(instances,
|
|
137
|
+
export function groupInstancesBySplitRules(instances, ruleSet, onUnmatched, options = {}) {
|
|
138
|
+
const splitRules = resolveSplitRuleSet(ruleSet);
|
|
7
139
|
if (!instances.length) {
|
|
8
140
|
return [];
|
|
9
141
|
}
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
142
|
+
const sorted = sortInstancesCopy(instances);
|
|
143
|
+
const context = {
|
|
144
|
+
series: computeSeriesFacts(sorted, splitRules),
|
|
145
|
+
};
|
|
146
|
+
const claimedByRule = splitRules.map(() => []);
|
|
147
|
+
for (const instance of sorted) {
|
|
15
148
|
let matched = false;
|
|
16
149
|
for (let ruleIndex = 0; ruleIndex < splitRules.length; ruleIndex++) {
|
|
17
150
|
const splitRule = splitRules[ruleIndex];
|
|
18
|
-
const context = ruleContexts[ruleIndex];
|
|
19
151
|
if (splitRule.matches && !splitRule.matches(instance, context)) {
|
|
20
152
|
continue;
|
|
21
153
|
}
|
|
154
|
+
claimedByRule[ruleIndex].push(instance);
|
|
22
155
|
matched = true;
|
|
23
|
-
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (!matched) {
|
|
159
|
+
onUnmatched?.(instance);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const ruleOrderers = splitRules.map((rule) => buildInstanceOrderer(rule, context, options));
|
|
163
|
+
const instancesMap = new Map();
|
|
164
|
+
const groupRuleIndex = new Map();
|
|
165
|
+
for (let ruleIndex = 0; ruleIndex < splitRules.length; ruleIndex++) {
|
|
166
|
+
const splitRule = splitRules[ruleIndex];
|
|
167
|
+
for (const instance of claimedByRule[ruleIndex]) {
|
|
168
|
+
const key = buildSplitKey(splitRule.id, buildGroupByParts(instance, context, splitRule));
|
|
24
169
|
let group = instancesMap.get(key);
|
|
25
170
|
if (!group) {
|
|
26
|
-
group = {
|
|
171
|
+
group = {
|
|
172
|
+
instances: [],
|
|
173
|
+
matchedRule: splitRule,
|
|
174
|
+
series: context.series,
|
|
175
|
+
splitKey: key,
|
|
176
|
+
};
|
|
27
177
|
instancesMap.set(key, group);
|
|
178
|
+
groupRuleIndex.set(key, ruleIndex);
|
|
28
179
|
}
|
|
29
180
|
group.instances.push(instance);
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
if (!matched) {
|
|
33
|
-
onUnmatched?.(instance);
|
|
34
181
|
}
|
|
35
182
|
}
|
|
36
|
-
|
|
183
|
+
const groups = Array.from(instancesMap.values());
|
|
184
|
+
for (const group of groups) {
|
|
185
|
+
const ruleIndex = groupRuleIndex.get(group.splitKey ?? '') ?? 0;
|
|
186
|
+
group.instances = ruleOrderers[ruleIndex](group.instances);
|
|
187
|
+
}
|
|
188
|
+
return groups.sort((a, b) => {
|
|
189
|
+
const aKey = a.splitKey ?? '';
|
|
190
|
+
const bKey = b.splitKey ?? '';
|
|
191
|
+
const ruleOrder = (groupRuleIndex.get(aKey) ?? 0) - (groupRuleIndex.get(bKey) ?? 0);
|
|
192
|
+
return ruleOrder !== 0 ? ruleOrder : compareSplitKeys(aKey, bKey);
|
|
193
|
+
});
|
|
37
194
|
}
|
|
@@ -6,17 +6,27 @@ export type { ImageStackDisplaySetOptions } from './ImageStackDisplaySet.js';
|
|
|
6
6
|
export { resolveInstances } from './resolveInstances.js';
|
|
7
7
|
export type { ResolveInstancesOptions } from './resolveInstances.js';
|
|
8
8
|
export { buildSeriesInfo } from './buildSeriesInfo.js';
|
|
9
|
-
export { groupInstancesBySplitRules } from './groupInstancesBySplitRules.js';
|
|
9
|
+
export { groupInstancesBySplitRules, orderInstancesForRule, } from './groupInstancesBySplitRules.js';
|
|
10
|
+
export type { PlaneGeometry, TimeClusters } from './seriesFunctions.js';
|
|
11
|
+
export { resolveSplitRuleSet } from './splitRuleSet.js';
|
|
12
|
+
export { compare, compareInstanceNumber, compareSOPInstanceUID, sortByInstanceNumber, instancesSortCriteria, sortInstancesCopy, } from './sortInstances.js';
|
|
10
13
|
export { splitImageIdsBySplitRules } from './splitImageIdsBySplitRules.js';
|
|
11
14
|
export type { SplitImageIdsBySplitRulesOptions } from './splitImageIdsBySplitRules.js';
|
|
12
15
|
export { registerDisplaySetMetadata, type RegisterDisplaySetMetadataOptions, } from './registerDisplaySetMetadata.js';
|
|
13
16
|
export { registerDisplaySetProviders } from './displaySetProvider.js';
|
|
14
|
-
export { defaultDisplaySetSplitRules } from './
|
|
17
|
+
export { compiledDisplaySetRules, defaultDisplaySetSplitRules, } from './compiledDisplaySetRules.js';
|
|
18
|
+
export { displaySetRules, createDisplaySetSplitRules } from './displaySetRules.js';
|
|
19
|
+
export { defaultMetadataContext } from './displaySetContext.js';
|
|
20
|
+
export { setMetadataContext, getMetadataContext, getContextValue, } from './metadataContext.js';
|
|
21
|
+
export { imageSopClassUids, videoSopClassUids, videoTransferSyntaxUids, longVideoSopClassUids, longVideoMinFrames, ecgSopClassUids, wholeSlideSopClassUids, segmentationSopClassUids, rtStructureSetSopClassUids, parametricMapSopClassUids, structuredReportSopClassUids, encapsulatedDocumentSopClassUids, bulkAnnotationSopClassUids, chartSopClassUids, } from './sopClassUidLists.js';
|
|
22
|
+
export type { ContextEntry, ContextGroupEntry, ContextValues, DisplaySetContext, InstanceContextFunction, } from './displaySetContext.js';
|
|
23
|
+
export type { InstanceHook, RawAttributeValue, RawCondition, RawMatchTest, RawName, RawSortKey, RawSplitRule, DisplaySetRules, CreateDisplaySetSplitRulesOptions, } from './displaySetRulesTypes.js';
|
|
15
24
|
export { createDisplaySetFromGroup } from './createDisplaySetFromGroup.js';
|
|
16
25
|
export type { CreateDisplaySetFromGroupOptions } from './createDisplaySetFromGroup.js';
|
|
17
26
|
export { isImageInstance } from './isImageInstance.js';
|
|
18
27
|
export { isVideoInstance } from './isVideoInstance.js';
|
|
19
28
|
export { isEcgInstance } from './isEcgInstance.js';
|
|
20
29
|
export { isWsiInstance } from './isWsiInstance.js';
|
|
21
|
-
export {
|
|
22
|
-
export
|
|
30
|
+
export { isReconstructableInstances } from './isReconstructableInstances.js';
|
|
31
|
+
export { DEFAULT_SPLIT_RULE_PRIORITY_LIMIT, DISPLAY_SET_HANDLER_NAMES, } from './types.js';
|
|
32
|
+
export type { NaturalizedInstance, SeriesInfo, SeriesFacts, SeriesContext, SeriesFunction, SeriesFunctionDefinition, SeriesFieldScope, RuleContext, SplitRule, SplitContext, SplitRuleOptions, SplitRuleCustomAttributesContext, InstanceGroup, InstanceOrderContext, SortInstances, GroupInstancesOptions, SplitRuleSet, SplitRuleSetEntry, OrderInstancesOptions, } from './types.js';
|
|
@@ -2,14 +2,21 @@ export { BaseDisplaySet } from './BaseDisplaySet.js';
|
|
|
2
2
|
export { ImageStackDisplaySet } from './ImageStackDisplaySet.js';
|
|
3
3
|
export { resolveInstances } from './resolveInstances.js';
|
|
4
4
|
export { buildSeriesInfo } from './buildSeriesInfo.js';
|
|
5
|
-
export { groupInstancesBySplitRules } from './groupInstancesBySplitRules.js';
|
|
5
|
+
export { groupInstancesBySplitRules, orderInstancesForRule, } from './groupInstancesBySplitRules.js';
|
|
6
|
+
export { resolveSplitRuleSet } from './splitRuleSet.js';
|
|
7
|
+
export { compare, compareInstanceNumber, compareSOPInstanceUID, sortByInstanceNumber, instancesSortCriteria, sortInstancesCopy, } from './sortInstances.js';
|
|
6
8
|
export { splitImageIdsBySplitRules } from './splitImageIdsBySplitRules.js';
|
|
7
9
|
export { registerDisplaySetMetadata, } from './registerDisplaySetMetadata.js';
|
|
8
10
|
export { registerDisplaySetProviders } from './displaySetProvider.js';
|
|
9
|
-
export { defaultDisplaySetSplitRules } from './
|
|
11
|
+
export { compiledDisplaySetRules, defaultDisplaySetSplitRules, } from './compiledDisplaySetRules.js';
|
|
12
|
+
export { displaySetRules, createDisplaySetSplitRules } from './displaySetRules.js';
|
|
13
|
+
export { defaultMetadataContext } from './displaySetContext.js';
|
|
14
|
+
export { setMetadataContext, getMetadataContext, getContextValue, } from './metadataContext.js';
|
|
15
|
+
export { imageSopClassUids, videoSopClassUids, videoTransferSyntaxUids, longVideoSopClassUids, longVideoMinFrames, ecgSopClassUids, wholeSlideSopClassUids, segmentationSopClassUids, rtStructureSetSopClassUids, parametricMapSopClassUids, structuredReportSopClassUids, encapsulatedDocumentSopClassUids, bulkAnnotationSopClassUids, chartSopClassUids, } from './sopClassUidLists.js';
|
|
10
16
|
export { createDisplaySetFromGroup } from './createDisplaySetFromGroup.js';
|
|
11
17
|
export { isImageInstance } from './isImageInstance.js';
|
|
12
18
|
export { isVideoInstance } from './isVideoInstance.js';
|
|
13
19
|
export { isEcgInstance } from './isEcgInstance.js';
|
|
14
20
|
export { isWsiInstance } from './isWsiInstance.js';
|
|
15
|
-
export {
|
|
21
|
+
export { isReconstructableInstances } from './isReconstructableInstances.js';
|
|
22
|
+
export { DEFAULT_SPLIT_RULE_PRIORITY_LIMIT, DISPLAY_SET_HANDLER_NAMES, } from './types.js';
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { DisplaySetContext } from './displaySetContext.js';
|
|
1
2
|
import type { NaturalizedInstance } from './types.js';
|
|
2
|
-
export declare function isEcgInstance(instance: NaturalizedInstance): boolean;
|
|
3
|
+
export declare function isEcgInstance(instance: NaturalizedInstance, context?: DisplaySetContext): boolean;
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'
|
|
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 ?? '');
|
|
1
|
+
import { isTrue } from '../safeFunctions/values.js';
|
|
2
|
+
import { getContextValue } from './metadataContext.js';
|
|
3
|
+
export function isEcgInstance(instance, context) {
|
|
4
|
+
return isTrue(getContextValue('isEcg', instance, context));
|
|
10
5
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { DisplaySetContext } from './displaySetContext.js';
|
|
1
2
|
import type { NaturalizedInstance } from './types.js';
|
|
2
|
-
export declare function isImageInstance(instance: NaturalizedInstance): boolean;
|
|
3
|
+
export declare function isImageInstance(instance: NaturalizedInstance, context?: DisplaySetContext): boolean;
|
|
@@ -1,45 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'
|
|
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 ?? '');
|
|
1
|
+
import { isTrue } from '../safeFunctions/values.js';
|
|
2
|
+
import { getContextValue } from './metadataContext.js';
|
|
3
|
+
export function isImageInstance(instance, context) {
|
|
4
|
+
return isTrue(getContextValue('isImage', instance, context));
|
|
45
5
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { asArrayFirst, toFiniteFirst, toFiniteVector, } from '@cornerstonejs/utils';
|
|
2
|
+
import { readOwn } from '../safeFunctions/index.js';
|
|
3
|
+
import { planeGeometry } from './seriesFunctions.js';
|
|
4
|
+
const SPACING_TOLERANCE = 0.2;
|
|
5
|
+
const item = (value) => asArrayFirst(value);
|
|
6
|
+
function vectorAt(source, path, field, length) {
|
|
7
|
+
let current = source;
|
|
8
|
+
for (const sequence of path) {
|
|
9
|
+
current = item(readOwn(current, sequence));
|
|
10
|
+
}
|
|
11
|
+
return toFiniteVector(readOwn(current, field), length);
|
|
12
|
+
}
|
|
13
|
+
function geometryOf(positions, orientation) {
|
|
14
|
+
return planeGeometry(positions.map((ImagePositionPatient, index) => ({
|
|
15
|
+
SOPInstanceUID: String(index),
|
|
16
|
+
ImagePositionPatient,
|
|
17
|
+
ImageOrientationPatient: orientation,
|
|
18
|
+
})), { args: { tolerance: SPACING_TOLERANCE } });
|
|
19
|
+
}
|
|
20
|
+
function isSingleFrameVolume(instances) {
|
|
21
|
+
if (instances.length < 2) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const [first] = instances;
|
|
25
|
+
const same = (name) => (instance) => toFiniteFirst(readOwn(instance, name)) ===
|
|
26
|
+
toFiniteFirst(readOwn(first, name));
|
|
27
|
+
if (!instances.every(same('Rows')) ||
|
|
28
|
+
!instances.every(same('Columns')) ||
|
|
29
|
+
!instances.every(same('SamplesPerPixel'))) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const geometry = planeGeometry([...instances], {
|
|
33
|
+
args: { tolerance: SPACING_TOLERANCE },
|
|
34
|
+
});
|
|
35
|
+
return (geometry.irregularCount === 0 &&
|
|
36
|
+
geometry.positions >= 2 &&
|
|
37
|
+
!geometry.duplicates);
|
|
38
|
+
}
|
|
39
|
+
function framePositions(instance) {
|
|
40
|
+
const perFrame = asArrayFromSequence(readOwn(instance, 'PerFrameFunctionalGroupsSequence')).map((group) => vectorAt(group, ['PlanePositionSequence'], 'ImagePositionPatient', 3));
|
|
41
|
+
if (perFrame.filter(Boolean).length >= 2) {
|
|
42
|
+
return perFrame;
|
|
43
|
+
}
|
|
44
|
+
const shared = vectorAt(instance, ['SharedFunctionalGroupsSequence', 'PlanePositionSequence'], 'ImagePositionPatient', 3);
|
|
45
|
+
const count = toFiniteFirst(instance.NumberOfFrames, perFrame.length);
|
|
46
|
+
return shared ? new Array(count).fill(shared) : [];
|
|
47
|
+
}
|
|
48
|
+
function asArrayFromSequence(value) {
|
|
49
|
+
return Array.isArray(value) ? value : value == null ? [] : [value];
|
|
50
|
+
}
|
|
51
|
+
function isMultiFrameVolume(instance) {
|
|
52
|
+
const perFrame0 = item(readOwn(instance, 'PerFrameFunctionalGroupsSequence'));
|
|
53
|
+
const shared = item(readOwn(instance, 'SharedFunctionalGroupsSequence'));
|
|
54
|
+
const detector = item(readOwn(instance, 'DetectorInformationSequence'));
|
|
55
|
+
const hasPixelMeasures = readOwn(perFrame0, 'PixelMeasuresSequence') != null ||
|
|
56
|
+
readOwn(shared, 'PixelMeasuresSequence') != null ||
|
|
57
|
+
(instance.PixelSpacing != null &&
|
|
58
|
+
(instance.SliceThickness != null ||
|
|
59
|
+
instance.SpacingBetweenFrames != null));
|
|
60
|
+
const orientation = toFiniteVector(instance.ImageOrientationPatient, 6) ??
|
|
61
|
+
vectorAt(shared, ['PlaneOrientationSequence'], 'ImageOrientationPatient', 6) ??
|
|
62
|
+
vectorAt(perFrame0, ['PlaneOrientationSequence'], 'ImageOrientationPatient', 6) ??
|
|
63
|
+
toFiniteVector(readOwn(detector, 'ImageOrientationPatient'), 6);
|
|
64
|
+
const hasPosition = readOwn(perFrame0, 'PlanePositionSequence') != null ||
|
|
65
|
+
readOwn(perFrame0, 'CTPositionSequence') != null ||
|
|
66
|
+
instance.ImagePositionPatient != null ||
|
|
67
|
+
readOwn(detector, 'ImagePositionPatient') != null;
|
|
68
|
+
if (!hasPixelMeasures || !orientation || !hasPosition) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (String(instance.Modality ?? '').includes('NM')) {
|
|
72
|
+
const subType = asArrayFromSequence(instance.ImageType)[2];
|
|
73
|
+
if (subType !== 'RECON TOMO' && subType !== 'RECON GATED TOMO') {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const positions = framePositions(instance);
|
|
78
|
+
if (positions.filter(Boolean).length < 2) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return geometryOf(positions, orientation).positions >= 2;
|
|
82
|
+
}
|
|
83
|
+
export function isReconstructableInstances(instances) {
|
|
84
|
+
const defined = instances.filter(Boolean);
|
|
85
|
+
if (!defined.length) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return toFiniteFirst(defined[0].NumberOfFrames, 0) > 1
|
|
89
|
+
? isMultiFrameVolume(defined[0])
|
|
90
|
+
: isSingleFrameVolume(defined);
|
|
91
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { DisplaySetContext } from './displaySetContext.js';
|
|
1
2
|
import type { NaturalizedInstance } from './types.js';
|
|
2
|
-
export declare function isVideoInstance(instance: NaturalizedInstance): boolean;
|
|
3
|
+
export declare function isVideoInstance(instance: NaturalizedInstance, context?: DisplaySetContext): boolean;
|
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'
|
|
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);
|
|
1
|
+
import { isTrue } from '../safeFunctions/values.js';
|
|
2
|
+
import { getContextValue } from './metadataContext.js';
|
|
3
|
+
export function isVideoInstance(instance, context) {
|
|
4
|
+
return isTrue(getContextValue('isVideo', instance, context));
|
|
31
5
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { DisplaySetContext } from './displaySetContext.js';
|
|
1
2
|
import type { NaturalizedInstance } from './types.js';
|
|
2
|
-
export declare function isWsiInstance(instance: NaturalizedInstance): boolean;
|
|
3
|
+
export declare function isWsiInstance(instance: NaturalizedInstance, context?: DisplaySetContext): boolean;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
return instance.Modality === 'SM';
|
|
1
|
+
import { isTrue } from '../safeFunctions/values.js';
|
|
2
|
+
import { getContextValue } from './metadataContext.js';
|
|
3
|
+
export function isWsiInstance(instance, context) {
|
|
4
|
+
return isTrue(getContextValue('isWsi', instance, context));
|
|
7
5
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContextProgram } from './displaySetContext.js';
|
|
2
|
+
import type { DisplaySetContext } from './displaySetContext.js';
|
|
3
|
+
import type { NaturalizedInstance, SeriesFunction, SeriesFunctionDefinition } from './types.js';
|
|
4
|
+
export declare function setMetadataContext(context?: DisplaySetContext): void;
|
|
5
|
+
export declare function getMetadataContext(): DisplaySetContext;
|
|
6
|
+
export type CompileMetadataContextOptions = {
|
|
7
|
+
seriesFunctions?: Readonly<Record<string, SeriesFunction | SeriesFunctionDefinition>>;
|
|
8
|
+
deniedNames?: readonly string[];
|
|
9
|
+
definition?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function compileMetadataContext(context: DisplaySetContext | undefined, options?: CompileMetadataContextOptions): ContextProgram;
|
|
12
|
+
export declare function getMetadataContextProgram(context: DisplaySetContext | undefined, options?: CompileMetadataContextOptions): ContextProgram;
|
|
13
|
+
export declare function getContextValue(name: string, instance: NaturalizedInstance, context?: DisplaySetContext): unknown;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createSite, safeFunctionCompilers } from '../safeFunctions/index.js';
|
|
2
|
+
import { ContextProgram, defaultMetadataContext } from './displaySetContext.js';
|
|
3
|
+
import { BUILT_IN_SERIES_FUNCTIONS } from './seriesFunctions.js';
|
|
4
|
+
import { splitRuleSchema } from './splitRuleSchema.js';
|
|
5
|
+
let activeContext = defaultMetadataContext;
|
|
6
|
+
export function setMetadataContext(context) {
|
|
7
|
+
activeContext = context ?? defaultMetadataContext;
|
|
8
|
+
}
|
|
9
|
+
export function getMetadataContext() {
|
|
10
|
+
return activeContext;
|
|
11
|
+
}
|
|
12
|
+
const programs = new WeakMap();
|
|
13
|
+
export function compileMetadataContext(context, options = {}) {
|
|
14
|
+
const program = new ContextProgram({ ...defaultMetadataContext, ...context }, {
|
|
15
|
+
seriesFunctions: {
|
|
16
|
+
...BUILT_IN_SERIES_FUNCTIONS,
|
|
17
|
+
...options.seriesFunctions,
|
|
18
|
+
},
|
|
19
|
+
deniedNames: options.deniedNames,
|
|
20
|
+
site: createSite({
|
|
21
|
+
definition: options.definition ?? 'metadata context',
|
|
22
|
+
path: 'context',
|
|
23
|
+
}, splitRuleSchema, safeFunctionCompilers),
|
|
24
|
+
});
|
|
25
|
+
if (!options.seriesFunctions && !options.deniedNames) {
|
|
26
|
+
programs.set(context ?? defaultMetadataContext, program);
|
|
27
|
+
}
|
|
28
|
+
return program;
|
|
29
|
+
}
|
|
30
|
+
export function getMetadataContextProgram(context, options = {}) {
|
|
31
|
+
const shareable = !options.seriesFunctions && !options.deniedNames;
|
|
32
|
+
const cached = shareable
|
|
33
|
+
? programs.get(context ?? defaultMetadataContext)
|
|
34
|
+
: undefined;
|
|
35
|
+
return cached ?? compileMetadataContext(context, options);
|
|
36
|
+
}
|
|
37
|
+
function programOf(context) {
|
|
38
|
+
return getMetadataContextProgram(context);
|
|
39
|
+
}
|
|
40
|
+
export function getContextValue(name, instance, context) {
|
|
41
|
+
return programOf(context ?? activeContext).instanceValue(name, instance);
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { defaultMetadataContext } from './displaySetContext.js';
|
|
2
|
+
import { isImageInstance } from './isImageInstance.js';
|
|
3
|
+
import { isVideoInstance } from './isVideoInstance.js';
|
|
4
|
+
import { getContextValue, getMetadataContext, setMetadataContext, } from './metadataContext.js';
|
|
5
|
+
import { imageSopClassUids } from './sopClassUidLists.js';
|
|
6
|
+
const NEW_UID = '1.2.3.4';
|
|
7
|
+
const CT = '1.2.840.10008.5.1.4.1.1.2';
|
|
8
|
+
const extended = {
|
|
9
|
+
...defaultMetadataContext,
|
|
10
|
+
imageSopClassUids: [...imageSopClassUids, NEW_UID],
|
|
11
|
+
};
|
|
12
|
+
describe('the active metadata context', () => {
|
|
13
|
+
afterEach(() => setMetadataContext(undefined));
|
|
14
|
+
const CASES = [
|
|
15
|
+
['the default context', () => undefined, undefined, true, false],
|
|
16
|
+
[
|
|
17
|
+
'an active context with one more UID',
|
|
18
|
+
() => setMetadataContext(extended),
|
|
19
|
+
undefined,
|
|
20
|
+
true,
|
|
21
|
+
true,
|
|
22
|
+
],
|
|
23
|
+
['an explicit context argument', () => undefined, extended, true, true],
|
|
24
|
+
[
|
|
25
|
+
'the default again after setMetadataContext(undefined)',
|
|
26
|
+
() => {
|
|
27
|
+
setMetadataContext(extended);
|
|
28
|
+
setMetadataContext(undefined);
|
|
29
|
+
},
|
|
30
|
+
undefined,
|
|
31
|
+
true,
|
|
32
|
+
false,
|
|
33
|
+
],
|
|
34
|
+
];
|
|
35
|
+
for (const [name, setup, context, ct, added] of CASES) {
|
|
36
|
+
it(`gives isImageInstance the lists of ${name}`, () => {
|
|
37
|
+
setup();
|
|
38
|
+
expect([
|
|
39
|
+
isImageInstance({ SOPClassUID: CT }, context),
|
|
40
|
+
isImageInstance({ SOPClassUID: NEW_UID }, context),
|
|
41
|
+
]).toEqual([ct, added]);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
it('returns the default context, and the context that a host sets', () => {
|
|
45
|
+
expect(getMetadataContext()).toBe(defaultMetadataContext);
|
|
46
|
+
setMetadataContext(extended);
|
|
47
|
+
expect(getMetadataContext()).toBe(extended);
|
|
48
|
+
});
|
|
49
|
+
it('applies a customized isVideo expression to isVideoInstance', () => {
|
|
50
|
+
const subject = { SOPClassUID: CT, Modality: 'XC' };
|
|
51
|
+
expect(isVideoInstance(subject)).toBe(false);
|
|
52
|
+
setMetadataContext({
|
|
53
|
+
...defaultMetadataContext,
|
|
54
|
+
isVideo: "Modality == 'XC'",
|
|
55
|
+
});
|
|
56
|
+
expect(isVideoInstance(subject)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it('reads any instance-scope name, and rejects a series-scope name', () => {
|
|
59
|
+
expect(getContextValue('isMultiFrame', { NumberOfFrames: 3 })).toBe(true);
|
|
60
|
+
expect(() => getContextValue('seriesInstanceCount', {})).toThrow(/series scope/);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|