@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
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
export const INSTANCE_EXPRESSION_SCOPE = Object.freeze({
|
|
2
|
+
params: Object.freeze(['instance', 'context']),
|
|
3
|
+
implicitScope: 'instance',
|
|
4
|
+
});
|
|
5
|
+
export function hasOwn(object, key) {
|
|
6
|
+
return (object != null &&
|
|
7
|
+
(typeof object === 'object' || typeof object === 'function') &&
|
|
8
|
+
Object.prototype.hasOwnProperty.call(object, key));
|
|
9
|
+
}
|
|
10
|
+
export function readOwn(object, key) {
|
|
11
|
+
return hasOwn(object, key)
|
|
12
|
+
? object[key]
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
export function isUnsafeKey(key) {
|
|
16
|
+
return key === '__proto__';
|
|
17
|
+
}
|
|
18
|
+
function stringify(fragment) {
|
|
19
|
+
if (typeof fragment === 'function') {
|
|
20
|
+
return '<function>';
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(fragment) ?? String(fragment);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return String(fragment);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class RuleCompileError extends Error {
|
|
30
|
+
constructor(definition, path, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.definition = definition;
|
|
33
|
+
this.path = path;
|
|
34
|
+
this.name = 'RuleCompileError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function invalidAt(site, message, ...fragment) {
|
|
38
|
+
const where = site.path ? `${site.path}: ` : '';
|
|
39
|
+
const what = fragment.length ? `: ${stringify(fragment[0])}` : '';
|
|
40
|
+
throw new RuleCompileError(site.definition, site.path, `Invalid ${site.definition}: ${where}${message}${what}`);
|
|
41
|
+
}
|
|
42
|
+
export function didYouMean(name, candidates) {
|
|
43
|
+
const lower = name.toLowerCase();
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
if (candidate && candidate !== name && candidate.toLowerCase() === lower) {
|
|
46
|
+
return `; did you mean '${candidate}'?`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
export function at(site, key, spec) {
|
|
52
|
+
const path = typeof key === 'number'
|
|
53
|
+
? `${site.path}[${key}]`
|
|
54
|
+
: site.path
|
|
55
|
+
? `${site.path}.${key}`
|
|
56
|
+
: key;
|
|
57
|
+
return {
|
|
58
|
+
...site,
|
|
59
|
+
path,
|
|
60
|
+
extraKeys: undefined,
|
|
61
|
+
expression: spec?.expression ?? site.expression,
|
|
62
|
+
call: spec?.call ?? site.call,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const LEAF_KINDS = new Set([
|
|
66
|
+
'string',
|
|
67
|
+
'number',
|
|
68
|
+
'boolean',
|
|
69
|
+
'true',
|
|
70
|
+
'scalar',
|
|
71
|
+
'literal',
|
|
72
|
+
'priority',
|
|
73
|
+
'expression',
|
|
74
|
+
]);
|
|
75
|
+
export function isLeafKind(kind) {
|
|
76
|
+
return LEAF_KINDS.has(kind);
|
|
77
|
+
}
|
|
78
|
+
const LEAF_CHECKS = {
|
|
79
|
+
string: ['a string', (v) => typeof v === 'string'],
|
|
80
|
+
expression: ['an expression string', (v) => typeof v === 'string'],
|
|
81
|
+
number: [
|
|
82
|
+
'a finite number',
|
|
83
|
+
(v) => typeof v === 'number' && Number.isFinite(v),
|
|
84
|
+
],
|
|
85
|
+
boolean: ['a boolean', (v) => typeof v === 'boolean'],
|
|
86
|
+
true: ['true', (v) => v === true],
|
|
87
|
+
scalar: [
|
|
88
|
+
'a string, a finite number or a boolean',
|
|
89
|
+
(v) => typeof v === 'string' ||
|
|
90
|
+
typeof v === 'boolean' ||
|
|
91
|
+
(typeof v === 'number' && Number.isFinite(v)),
|
|
92
|
+
],
|
|
93
|
+
literal: ['any value', () => true],
|
|
94
|
+
priority: [
|
|
95
|
+
'a finite number, or null',
|
|
96
|
+
(v) => v === null || (typeof v === 'number' && Number.isFinite(v)),
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
function checkLeaf(spec, value, site, label = 'value') {
|
|
100
|
+
const [expected, test] = LEAF_CHECKS[spec.kind];
|
|
101
|
+
if (!test(value)) {
|
|
102
|
+
invalidAt(site, `expected ${expected}`, value);
|
|
103
|
+
}
|
|
104
|
+
if (spec.oneOf && !spec.oneOf.includes(value)) {
|
|
105
|
+
invalidAt(site, `unknown ${label} ${stringify(value)}; allowed: ${spec.oneOf.join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function describeForm(form, scope) {
|
|
109
|
+
const names = [];
|
|
110
|
+
const operators = [];
|
|
111
|
+
for (const [key, spec] of Object.entries(form.keys)) {
|
|
112
|
+
if (spec.role === 'operator') {
|
|
113
|
+
operators.push(key);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (key === '*') {
|
|
117
|
+
names.push('<any key>');
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
names.push(spec.optional || spec.role === 'modifier' ? `${key}?` : key);
|
|
121
|
+
}
|
|
122
|
+
if (operators.length) {
|
|
123
|
+
names.splice(1, 0, `<one of ${operators.join(' | ')}>`);
|
|
124
|
+
}
|
|
125
|
+
const text = `{ ${names.join(', ')} }`;
|
|
126
|
+
const expressionScope = form.expression ?? ('expression' in form.keys ? scope : undefined);
|
|
127
|
+
return expressionScope
|
|
128
|
+
? `${text} called with (${expressionScope.params.join(', ')})`
|
|
129
|
+
: text;
|
|
130
|
+
}
|
|
131
|
+
export function describeShape(shape, site) {
|
|
132
|
+
const scope = site?.expression;
|
|
133
|
+
const parts = [];
|
|
134
|
+
if (shape.string === 'expression') {
|
|
135
|
+
parts.push(scope
|
|
136
|
+
? `an expression string called with (${scope.params.join(', ')})`
|
|
137
|
+
: 'an expression string');
|
|
138
|
+
}
|
|
139
|
+
else if (shape.string === 'attribute') {
|
|
140
|
+
parts.push('a name (a DICOM keyword or a context name)');
|
|
141
|
+
}
|
|
142
|
+
for (const form of Object.values(shape.forms)) {
|
|
143
|
+
parts.push(describeForm(form, scope));
|
|
144
|
+
}
|
|
145
|
+
if (shape.function) {
|
|
146
|
+
parts.push(site?.call ? `a function ${site.call}` : 'a function');
|
|
147
|
+
}
|
|
148
|
+
if (parts.length === 1) {
|
|
149
|
+
return parts[0];
|
|
150
|
+
}
|
|
151
|
+
return `${parts.slice(0, -1).join(', ')}, or ${parts[parts.length - 1]}`;
|
|
152
|
+
}
|
|
153
|
+
export function unrecognized(shape, fragment, site) {
|
|
154
|
+
return invalidAt(site, `unrecognized ${shape.name}; expected ${describeShape(shape, site)}`, fragment);
|
|
155
|
+
}
|
|
156
|
+
export function selectForm(shape, fragment, site) {
|
|
157
|
+
if (fragment && typeof fragment === 'object' && !Array.isArray(fragment)) {
|
|
158
|
+
let fallback;
|
|
159
|
+
for (const entry of Object.entries(shape.forms)) {
|
|
160
|
+
const { selectBy } = entry[1];
|
|
161
|
+
if (selectBy === undefined) {
|
|
162
|
+
fallback ??= entry;
|
|
163
|
+
}
|
|
164
|
+
else if (Object.prototype.hasOwnProperty.call(fragment, selectBy)) {
|
|
165
|
+
return entry;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (fallback) {
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return unrecognized(shape, fragment, site);
|
|
173
|
+
}
|
|
174
|
+
export function checkForm(form, fragment, site, keyNoun = 'key') {
|
|
175
|
+
const allowed = {
|
|
176
|
+
...form.keys,
|
|
177
|
+
...site.extraKeys,
|
|
178
|
+
};
|
|
179
|
+
const wildcard = allowed['*'];
|
|
180
|
+
for (const key of Object.keys(fragment)) {
|
|
181
|
+
if (!Object.prototype.hasOwnProperty.call(allowed, key) && !wildcard) {
|
|
182
|
+
invalidAt(site, `unknown ${keyNoun} '${key}'${didYouMean(key, Object.keys(allowed))}; allowed: ${Object.keys(allowed).join(', ')}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const operators = [];
|
|
186
|
+
let hasOperatorKeys = false;
|
|
187
|
+
for (const [key, spec] of Object.entries(allowed)) {
|
|
188
|
+
if (key === '*') {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (spec.role === 'operator') {
|
|
192
|
+
hasOperatorKeys = true;
|
|
193
|
+
}
|
|
194
|
+
const present = Object.prototype.hasOwnProperty.call(fragment, key) &&
|
|
195
|
+
fragment[key] !== undefined;
|
|
196
|
+
if (!present) {
|
|
197
|
+
if (!spec.optional && !spec.role) {
|
|
198
|
+
invalidAt(site, `missing ${keyNoun} '${key}'`, fragment);
|
|
199
|
+
}
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (spec.role === 'operator') {
|
|
203
|
+
operators.push(key);
|
|
204
|
+
}
|
|
205
|
+
if (isLeafKind(spec.kind)) {
|
|
206
|
+
const keySite = at(site, key, spec);
|
|
207
|
+
if (spec.list) {
|
|
208
|
+
const list = fragment[key];
|
|
209
|
+
if (!Array.isArray(list)) {
|
|
210
|
+
invalidAt(keySite, 'expected an array', list);
|
|
211
|
+
}
|
|
212
|
+
list.forEach((entry, index) => checkLeaf(spec, entry, at(keySite, index), key));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
checkLeaf(spec, fragment[key], keySite, key);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (hasOperatorKeys) {
|
|
220
|
+
const all = Object.entries(allowed)
|
|
221
|
+
.filter(([, spec]) => spec.role === 'operator')
|
|
222
|
+
.map(([key]) => key);
|
|
223
|
+
const subject = form.selectBy
|
|
224
|
+
? `${form.selectBy} ${stringify(fragment[form.selectBy])}`
|
|
225
|
+
: 'this form';
|
|
226
|
+
if (!operators.length) {
|
|
227
|
+
invalidAt(site, `no operator for ${subject}; expected exactly one of: ${all.join(', ')}`, fragment);
|
|
228
|
+
}
|
|
229
|
+
if (operators.length > 1) {
|
|
230
|
+
invalidAt(site, `more than one operator for ${subject} (${operators.join(', ')}); expected exactly one of: ${all.join(', ')}`, fragment);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const [key, spec] of Object.entries(allowed)) {
|
|
234
|
+
if (spec.role === 'modifier' &&
|
|
235
|
+
fragment[key] !== undefined &&
|
|
236
|
+
spec.with &&
|
|
237
|
+
!spec.with.some((operator) => operators.includes(operator))) {
|
|
238
|
+
invalidAt(site, `'${key}' applies only with ${spec.with.join(', ')}`, fragment);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export function compileKey(spec, value, site) {
|
|
243
|
+
if (spec.function && typeof value === 'function') {
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
if (spec.list) {
|
|
247
|
+
if (!Array.isArray(value)) {
|
|
248
|
+
if (isLeafKind(spec.kind)) {
|
|
249
|
+
return invalidAt(site, 'expected an array', value);
|
|
250
|
+
}
|
|
251
|
+
const shape = site.schema[spec.kind];
|
|
252
|
+
const entries = shape ? describeShape(shape, site) : spec.kind;
|
|
253
|
+
return invalidAt(site, spec.function
|
|
254
|
+
? `expected an array of (${entries}), or a function${site.call ? ` ${site.call}` : ''}`
|
|
255
|
+
: `expected an array of (${entries})`, value);
|
|
256
|
+
}
|
|
257
|
+
const entrySpec = { ...spec, list: false, function: false };
|
|
258
|
+
return value.map((entry, index) => compileKey(entrySpec, entry, at(site, index)));
|
|
259
|
+
}
|
|
260
|
+
if (isLeafKind(spec.kind)) {
|
|
261
|
+
checkLeaf(spec, value, site);
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
if (spec.kind === 'record') {
|
|
265
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
266
|
+
return invalidAt(site, 'expected an object', value);
|
|
267
|
+
}
|
|
268
|
+
const keys = spec.keys ?? {};
|
|
269
|
+
checkForm({ keys }, value, site);
|
|
270
|
+
const result = {};
|
|
271
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
272
|
+
if (isUnsafeKey(key)) {
|
|
273
|
+
return invalidAt(at(site, key), `the key "${key}" is not allowed`, value);
|
|
274
|
+
}
|
|
275
|
+
const entrySpec = Object.prototype.hasOwnProperty.call(keys, key)
|
|
276
|
+
? keys[key]
|
|
277
|
+
: keys['*'];
|
|
278
|
+
result[key] = compileKey(entrySpec, entry, at(site, key, entrySpec));
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
const shape = site.schema[spec.kind];
|
|
283
|
+
const compiler = site.compilers[spec.kind];
|
|
284
|
+
if (!shape || !compiler) {
|
|
285
|
+
return invalidAt(site, `the schema has no compiler for '${spec.kind}'`);
|
|
286
|
+
}
|
|
287
|
+
if (shape.function && typeof value === 'function') {
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
return compiler(value, { ...site, extraKeys: spec.extraKeys });
|
|
291
|
+
}
|
|
292
|
+
export function matchForm(shape, fragment, site) {
|
|
293
|
+
const [name, form] = selectForm(shape, fragment, site);
|
|
294
|
+
checkForm(form, fragment, site, shape.keyNoun);
|
|
295
|
+
return [name, form];
|
|
296
|
+
}
|
|
297
|
+
export const MATCH_OPERATOR_NAMES = Object.freeze([
|
|
298
|
+
'equals',
|
|
299
|
+
'doesNotEqual',
|
|
300
|
+
'notNull',
|
|
301
|
+
'includes',
|
|
302
|
+
'doesNotInclude',
|
|
303
|
+
'greaterThan',
|
|
304
|
+
'greaterThanOrEqual',
|
|
305
|
+
'lessThan',
|
|
306
|
+
'lessThanOrEqual',
|
|
307
|
+
'range',
|
|
308
|
+
'contains',
|
|
309
|
+
'containsI',
|
|
310
|
+
'doesNotContain',
|
|
311
|
+
'doesNotContainI',
|
|
312
|
+
'containsAll',
|
|
313
|
+
'startsWith',
|
|
314
|
+
'endsWith',
|
|
315
|
+
]);
|
|
316
|
+
export const matchTestShape = {
|
|
317
|
+
name: 'test',
|
|
318
|
+
function: true,
|
|
319
|
+
keyNoun: 'operator',
|
|
320
|
+
forms: {
|
|
321
|
+
operator: {
|
|
322
|
+
keys: Object.fromEntries(MATCH_OPERATOR_NAMES.map((name) => [
|
|
323
|
+
name,
|
|
324
|
+
{ kind: 'literal', role: 'operator' },
|
|
325
|
+
])),
|
|
326
|
+
description: 'One operator and its operand.',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
export const conditionShape = {
|
|
331
|
+
name: 'condition',
|
|
332
|
+
function: true,
|
|
333
|
+
forms: {
|
|
334
|
+
map: {
|
|
335
|
+
keys: { '*': { kind: 'matchTest' } },
|
|
336
|
+
description: 'A map of name to test. Every entry must hold. `{}` is true.',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
export const valueShape = {
|
|
341
|
+
name: 'value',
|
|
342
|
+
string: 'attribute',
|
|
343
|
+
function: true,
|
|
344
|
+
forms: {
|
|
345
|
+
template: {
|
|
346
|
+
selectBy: 'template',
|
|
347
|
+
keys: { template: { kind: 'string' } },
|
|
348
|
+
description: 'A string with `{name}` placeholders.',
|
|
349
|
+
},
|
|
350
|
+
expression: {
|
|
351
|
+
selectBy: 'expression',
|
|
352
|
+
keys: { expression: { kind: 'expression' } },
|
|
353
|
+
description: 'An expression; the result is the value, not coerced.',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
export const safeFunctionSchema = {
|
|
358
|
+
condition: conditionShape,
|
|
359
|
+
matchTest: matchTestShape,
|
|
360
|
+
value: valueShape,
|
|
361
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type SafeFunctionSubject = Record<string, unknown>;
|
|
2
|
+
export type RawScalar = string | number | boolean;
|
|
3
|
+
export type RawMatchTest = RawScalar | RawScalar[] | null | {
|
|
4
|
+
equals: RawScalar | RawScalar[] | null;
|
|
5
|
+
} | {
|
|
6
|
+
doesNotEqual: RawScalar | RawScalar[] | null;
|
|
7
|
+
} | {
|
|
8
|
+
notNull: boolean;
|
|
9
|
+
} | {
|
|
10
|
+
includes: RawScalar | RawScalar[];
|
|
11
|
+
} | {
|
|
12
|
+
doesNotInclude: RawScalar | RawScalar[];
|
|
13
|
+
} | {
|
|
14
|
+
greaterThan: number;
|
|
15
|
+
} | {
|
|
16
|
+
greaterThanOrEqual: number;
|
|
17
|
+
} | {
|
|
18
|
+
lessThan: number;
|
|
19
|
+
} | {
|
|
20
|
+
lessThanOrEqual: number;
|
|
21
|
+
} | {
|
|
22
|
+
range: [number, number];
|
|
23
|
+
} | {
|
|
24
|
+
contains: string | string[];
|
|
25
|
+
} | {
|
|
26
|
+
containsI: string | string[];
|
|
27
|
+
} | {
|
|
28
|
+
doesNotContain: string | string[];
|
|
29
|
+
} | {
|
|
30
|
+
doesNotContainI: string | string[];
|
|
31
|
+
} | {
|
|
32
|
+
containsAll: RawScalar | RawScalar[];
|
|
33
|
+
} | {
|
|
34
|
+
startsWith: string | string[];
|
|
35
|
+
} | {
|
|
36
|
+
endsWith: string | string[];
|
|
37
|
+
} | ((value: unknown, subject?: unknown, context?: unknown) => boolean);
|
|
38
|
+
export type RawCondition = Record<string, RawMatchTest> | InlinePredicate;
|
|
39
|
+
export type InlinePredicate = (subject: any, context?: any) => boolean;
|
|
40
|
+
export type InlineValue = (subject: any, context?: any) => unknown;
|
|
41
|
+
export type RawValue = string | {
|
|
42
|
+
template: string;
|
|
43
|
+
} | {
|
|
44
|
+
expression: string;
|
|
45
|
+
} | InlineValue;
|
|
46
|
+
export type CompiledPredicate<Subject = SafeFunctionSubject> = (subject: Subject, context?: unknown) => boolean;
|
|
47
|
+
export type CompiledValue<Subject = SafeFunctionSubject> = (subject: Subject, context?: unknown) => unknown;
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isAbsent(value: unknown): boolean;
|
|
2
|
+
export declare function isEmpty(value: unknown): boolean;
|
|
3
|
+
export declare function hasNoValue(value: unknown): boolean;
|
|
4
|
+
export declare function looseEquals(left: unknown, right: unknown): boolean;
|
|
5
|
+
export declare function compareFinite(test: (a: number, b: number) => boolean): (left: unknown, right: unknown) => boolean;
|
|
6
|
+
export declare function isTrue(value: unknown): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { toFiniteFirst } from '@cornerstonejs/utils';
|
|
2
|
+
export function isAbsent(value) {
|
|
3
|
+
return value === undefined;
|
|
4
|
+
}
|
|
5
|
+
export function isEmpty(value) {
|
|
6
|
+
return (value === null ||
|
|
7
|
+
value === '' ||
|
|
8
|
+
(Array.isArray(value) && value.every((item) => hasNoValue(item))));
|
|
9
|
+
}
|
|
10
|
+
export function hasNoValue(value) {
|
|
11
|
+
return isAbsent(value) || isEmpty(value);
|
|
12
|
+
}
|
|
13
|
+
export function looseEquals(left, right) {
|
|
14
|
+
if (hasNoValue(left) || hasNoValue(right)) {
|
|
15
|
+
return emptyKind(left) === emptyKind(right);
|
|
16
|
+
}
|
|
17
|
+
const leftArray = Array.isArray(left);
|
|
18
|
+
const rightArray = Array.isArray(right);
|
|
19
|
+
if (leftArray && rightArray) {
|
|
20
|
+
return (left.length === right.length &&
|
|
21
|
+
left.every((element, index) => scalarEquals(element, right[index])));
|
|
22
|
+
}
|
|
23
|
+
if (leftArray) {
|
|
24
|
+
return left.length === 1 && scalarEquals(left[0], right);
|
|
25
|
+
}
|
|
26
|
+
if (rightArray) {
|
|
27
|
+
return right.length === 1 && scalarEquals(left, right[0]);
|
|
28
|
+
}
|
|
29
|
+
return scalarEquals(left, right);
|
|
30
|
+
}
|
|
31
|
+
function scalarEquals(a, b) {
|
|
32
|
+
if (hasNoValue(a) || hasNoValue(b)) {
|
|
33
|
+
return emptyKind(a) === emptyKind(b);
|
|
34
|
+
}
|
|
35
|
+
if (a === b) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (typeof a === 'object' || typeof b === 'object') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (typeof a === 'number' || typeof b === 'number') {
|
|
42
|
+
const x = toFiniteFirst(a);
|
|
43
|
+
const y = toFiniteFirst(b);
|
|
44
|
+
if (x !== undefined && y !== undefined) {
|
|
45
|
+
return x === y;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return String(a) === String(b);
|
|
49
|
+
}
|
|
50
|
+
function emptyKind(value) {
|
|
51
|
+
if (isAbsent(value)) {
|
|
52
|
+
return 'absent';
|
|
53
|
+
}
|
|
54
|
+
if (value === null) {
|
|
55
|
+
return 'null';
|
|
56
|
+
}
|
|
57
|
+
if (value === '') {
|
|
58
|
+
return 'text';
|
|
59
|
+
}
|
|
60
|
+
return isEmpty(value) ? 'array' : undefined;
|
|
61
|
+
}
|
|
62
|
+
export function compareFinite(test) {
|
|
63
|
+
return (left, right) => {
|
|
64
|
+
const a = toFiniteFirst(left);
|
|
65
|
+
const b = toFiniteFirst(right);
|
|
66
|
+
return a !== undefined && b !== undefined && test(a, b);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function isTrue(value) {
|
|
70
|
+
return !hasNoValue(value) && Boolean(value);
|
|
71
|
+
}
|
|
@@ -22,4 +22,5 @@ export declare const mapModuleTags: Map<string, TagEntry[]>;
|
|
|
22
22
|
export declare const mapTagInfo: Map<string, TagEntry>;
|
|
23
23
|
export declare function addTag(name: string, value: TagEntry): void;
|
|
24
24
|
export declare function resolveHexFromKeyword(keyword: string): string | undefined;
|
|
25
|
+
export declare function keywordIgnoringCase(name: string): string | undefined;
|
|
25
26
|
export declare const Tags: Record<string, TagEntry>;
|
|
@@ -82,12 +82,24 @@ export function addTag(name, value) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
export function resolveHexFromKeyword(keyword) {
|
|
85
|
-
const entry = nameMap
|
|
86
|
-
|
|
85
|
+
const entry = Object.prototype.hasOwnProperty.call(nameMap, keyword)
|
|
86
|
+
? nameMap[keyword]
|
|
87
|
+
: undefined;
|
|
88
|
+
if (!entry?.tag) {
|
|
87
89
|
return undefined;
|
|
88
90
|
}
|
|
89
91
|
return entry.tag.substring(1, 5) + entry.tag.substring(6, 10);
|
|
90
92
|
}
|
|
93
|
+
let keywordsByLowerCase;
|
|
94
|
+
export function keywordIgnoringCase(name) {
|
|
95
|
+
if (!keywordsByLowerCase) {
|
|
96
|
+
keywordsByLowerCase = new Map();
|
|
97
|
+
for (const keyword of Object.keys(nameMap)) {
|
|
98
|
+
keywordsByLowerCase.set(keyword.toLowerCase(), keyword);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return keywordsByLowerCase.get(name.toLowerCase());
|
|
102
|
+
}
|
|
91
103
|
export const Tags = {};
|
|
92
104
|
const tagGroups = new Map();
|
|
93
105
|
for (const [moduleName, keywords] of moduleDefinitions) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { getSingleBufferFromArray } from './bulkDataFromArray.js';
|
|
2
2
|
export { definedAttributesOf } from './definedAttributesOf.js';
|
|
3
|
-
export { toNumber, toFiniteNumber } from './toNumber.js';
|
|
3
|
+
export { toNumber, toFiniteNumber, toFiniteFirst, toFiniteVector, } from './toNumber.js';
|
|
4
4
|
export { default as toNumberDefault } from './toNumber.js';
|
|
5
5
|
export { default as isVideoTransferSyntax, videoUIDs, } from './isVideoTransferSyntax.js';
|
|
6
6
|
export { default as imageIdToURI } from './imageIdToURI.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { getSingleBufferFromArray } from './bulkDataFromArray.js';
|
|
2
2
|
export { definedAttributesOf } from './definedAttributesOf.js';
|
|
3
|
-
export { toNumber, toFiniteNumber } from './toNumber.js';
|
|
3
|
+
export { toNumber, toFiniteNumber, toFiniteFirst, toFiniteVector, } from './toNumber.js';
|
|
4
4
|
export { default as toNumberDefault } from './toNumber.js';
|
|
5
5
|
export { default as isVideoTransferSyntax, videoUIDs, } from './isVideoTransferSyntax.js';
|
|
6
6
|
export { default as imageIdToURI } from './imageIdToURI.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { toNumber, toFiniteNumber } from '@cornerstonejs/utils';
|
|
1
|
+
export { toNumber, toFiniteNumber, toFiniteFirst, toFiniteVector, } from '@cornerstonejs/utils';
|
|
2
2
|
export { toNumber as default } from '@cornerstonejs/utils';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { toNumber, toFiniteNumber } from '@cornerstonejs/utils';
|
|
1
|
+
export { toNumber, toFiniteNumber, toFiniteFirst, toFiniteVector, } from '@cornerstonejs/utils';
|
|
2
2
|
export { toNumber as default } from '@cornerstonejs/utils';
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "5.
|
|
1
|
+
export declare const version = "5.14.0";
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '5.
|
|
1
|
+
export const version = '5.14.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cornerstonejs/metadata",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.14.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",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@cornerstonejs/calculate-suv": "1.0.3",
|
|
84
|
-
"@cornerstonejs/utils": "5.
|
|
84
|
+
"@cornerstonejs/utils": "5.14.0",
|
|
85
85
|
"dcmjs": "0.52.0",
|
|
86
86
|
"gl-matrix": "3.4.3"
|
|
87
87
|
},
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { isEcgInstance } from './isEcgInstance.js';
|
|
2
|
-
import { isImageInstance } from './isImageInstance.js';
|
|
3
|
-
import { isVideoInstance } from './isVideoInstance.js';
|
|
4
|
-
import { isWsiInstance } from './isWsiInstance.js';
|
|
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
|
-
];
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { InstanceGroup, SplitRule, ViewportTypeHint } from './types.js';
|
|
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[];
|