@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.
Files changed (100) hide show
  1. package/dist/esm/displayset/BaseDisplaySet.d.ts +3 -4
  2. package/dist/esm/displayset/BaseDisplaySet.js +1 -5
  3. package/dist/esm/displayset/IDisplaySet.d.ts +9 -3
  4. package/dist/esm/displayset/ImageStackDisplaySet.d.ts +1 -3
  5. package/dist/esm/displayset/ImageStackDisplaySet.js +0 -1
  6. package/dist/esm/displayset/__fixtures__/scenarioInstances.d.ts +12 -0
  7. package/dist/esm/displayset/__fixtures__/scenarioInstances.js +210 -0
  8. package/dist/esm/displayset/compileSplitRules.d.ts +3 -0
  9. package/dist/esm/displayset/compileSplitRules.js +197 -0
  10. package/dist/esm/displayset/compiledDisplaySetRules.d.ts +3 -0
  11. package/dist/esm/displayset/compiledDisplaySetRules.js +3 -0
  12. package/dist/esm/displayset/createDisplaySetFromGroup.js +54 -19
  13. package/dist/esm/displayset/displaySetContext.d.ts +150 -0
  14. package/dist/esm/displayset/displaySetContext.js +918 -0
  15. package/dist/esm/displayset/displaySetRules.d.ts +5 -0
  16. package/dist/esm/displayset/displaySetRules.js +142 -0
  17. package/dist/esm/displayset/displaySetRules.test.d.ts +1 -0
  18. package/dist/esm/displayset/displaySetRules.test.js +1306 -0
  19. package/dist/esm/displayset/displaySetRulesTypes.d.ts +34 -0
  20. package/dist/esm/displayset/displaySetRulesTypes.js +0 -0
  21. package/dist/esm/displayset/displayset.test.js +422 -130
  22. package/dist/esm/displayset/groupInstancesBySplitRules.d.ts +8 -2
  23. package/dist/esm/displayset/groupInstancesBySplitRules.js +174 -17
  24. package/dist/esm/displayset/index.d.ts +14 -4
  25. package/dist/esm/displayset/index.js +10 -3
  26. package/dist/esm/displayset/isEcgInstance.d.ts +2 -1
  27. package/dist/esm/displayset/isEcgInstance.js +4 -9
  28. package/dist/esm/displayset/isImageInstance.d.ts +2 -1
  29. package/dist/esm/displayset/isImageInstance.js +4 -44
  30. package/dist/esm/displayset/isReconstructableInstances.d.ts +2 -0
  31. package/dist/esm/displayset/isReconstructableInstances.js +91 -0
  32. package/dist/esm/displayset/isVideoInstance.d.ts +2 -1
  33. package/dist/esm/displayset/isVideoInstance.js +4 -30
  34. package/dist/esm/displayset/isWsiInstance.d.ts +2 -1
  35. package/dist/esm/displayset/isWsiInstance.js +4 -6
  36. package/dist/esm/displayset/metadataContext.d.ts +13 -0
  37. package/dist/esm/displayset/metadataContext.js +42 -0
  38. package/dist/esm/displayset/metadataContext.test.d.ts +1 -0
  39. package/dist/esm/displayset/metadataContext.test.js +62 -0
  40. package/dist/esm/displayset/scenarioFixtures.test.d.ts +1 -0
  41. package/dist/esm/displayset/scenarioFixtures.test.js +206 -0
  42. package/dist/esm/displayset/seriesContext.test.d.ts +1 -0
  43. package/dist/esm/displayset/seriesContext.test.js +238 -0
  44. package/dist/esm/displayset/seriesFunctions.d.ts +29 -0
  45. package/dist/esm/displayset/seriesFunctions.js +327 -0
  46. package/dist/esm/displayset/sopClassUidLists.d.ts +14 -0
  47. package/dist/esm/displayset/sopClassUidLists.js +100 -0
  48. package/dist/esm/displayset/sortInstances.d.ts +10 -0
  49. package/dist/esm/displayset/sortInstances.js +33 -0
  50. package/dist/esm/displayset/sortInstances.test.d.ts +1 -0
  51. package/dist/esm/displayset/sortInstances.test.js +56 -0
  52. package/dist/esm/displayset/splitImageIdsBySplitRules.d.ts +3 -3
  53. package/dist/esm/displayset/splitImageIdsBySplitRules.js +5 -5
  54. package/dist/esm/displayset/splitRuleSchema.d.ts +11 -0
  55. package/dist/esm/displayset/splitRuleSchema.js +168 -0
  56. package/dist/esm/displayset/splitRuleSet.d.ts +12 -0
  57. package/dist/esm/displayset/splitRuleSet.js +47 -0
  58. package/dist/esm/displayset/splitRuleSet.test.d.ts +1 -0
  59. package/dist/esm/displayset/splitRuleSet.test.js +119 -0
  60. package/dist/esm/displayset/types.d.ts +37 -4
  61. package/dist/esm/displayset/types.js +15 -0
  62. package/dist/esm/index.d.ts +4 -2
  63. package/dist/esm/index.js +2 -1
  64. package/dist/esm/safeFunctions/compile.d.ts +31 -0
  65. package/dist/esm/safeFunctions/compile.js +142 -0
  66. package/dist/esm/safeFunctions/compile.test.d.ts +1 -0
  67. package/dist/esm/safeFunctions/compile.test.js +82 -0
  68. package/dist/esm/safeFunctions/expression/compiler.d.ts +23 -0
  69. package/dist/esm/safeFunctions/expression/compiler.js +481 -0
  70. package/dist/esm/safeFunctions/expression/expression.test.d.ts +1 -0
  71. package/dist/esm/safeFunctions/expression/expression.test.js +325 -0
  72. package/dist/esm/safeFunctions/expression/index.d.ts +4 -0
  73. package/dist/esm/safeFunctions/expression/index.js +3 -0
  74. package/dist/esm/safeFunctions/expression/parser.d.ts +56 -0
  75. package/dist/esm/safeFunctions/expression/parser.js +248 -0
  76. package/dist/esm/safeFunctions/expression/tokenizer.d.ts +29 -0
  77. package/dist/esm/safeFunctions/expression/tokenizer.js +198 -0
  78. package/dist/esm/safeFunctions/index.d.ts +5 -0
  79. package/dist/esm/safeFunctions/index.js +4 -0
  80. package/dist/esm/safeFunctions/matchTests.d.ts +16 -0
  81. package/dist/esm/safeFunctions/matchTests.js +203 -0
  82. package/dist/esm/safeFunctions/schema.d.ts +72 -0
  83. package/dist/esm/safeFunctions/schema.js +361 -0
  84. package/dist/esm/safeFunctions/types.d.ts +47 -0
  85. package/dist/esm/safeFunctions/types.js +0 -0
  86. package/dist/esm/safeFunctions/values.d.ts +6 -0
  87. package/dist/esm/safeFunctions/values.js +71 -0
  88. package/dist/esm/utilities/Tags.d.ts +1 -0
  89. package/dist/esm/utilities/Tags.js +14 -2
  90. package/dist/esm/utilities/index.d.ts +1 -1
  91. package/dist/esm/utilities/index.js +1 -1
  92. package/dist/esm/utilities/toNumber.d.ts +1 -1
  93. package/dist/esm/utilities/toNumber.js +1 -1
  94. package/dist/esm/version.d.ts +1 -1
  95. package/dist/esm/version.js +1 -1
  96. package/package.json +2 -2
  97. package/dist/esm/displayset/defaultDisplaySetSplitRules.d.ts +0 -2
  98. package/dist/esm/displayset/defaultDisplaySetSplitRules.js +0 -93
  99. package/dist/esm/displayset/viewportTypes.d.ts +0 -4
  100. package/dist/esm/displayset/viewportTypes.js +0 -13
@@ -0,0 +1,327 @@
1
+ import { vec3 } from 'gl-matrix';
2
+ import { asArray, toFiniteFirst, toFiniteVector } from '@cornerstonejs/utils';
3
+ import { readOwn } from '../safeFunctions/index.js';
4
+ export function instanceKey(instance) {
5
+ const key = instance.SOPInstanceUID ?? instance.imageId;
6
+ return key === undefined ? undefined : String(key);
7
+ }
8
+ function byKey(a, b) {
9
+ const aKey = instanceKey(a) ?? '';
10
+ const bKey = instanceKey(b) ?? '';
11
+ return aKey < bKey ? -1 : aKey > bKey ? 1 : 0;
12
+ }
13
+ function numberArg(args, name, fallback) {
14
+ return toFiniteFirst(readOwn(args, name)) ?? fallback;
15
+ }
16
+ function readVector(instance, attribute, length) {
17
+ return toFiniteVector(readOwn(instance, attribute), length);
18
+ }
19
+ const round = (value, step) => Math.round(value / step) * step;
20
+ function searchSorted(values, value, after = false) {
21
+ let low = 0;
22
+ let high = values.length;
23
+ while (low < high) {
24
+ const mid = (low + high) >> 1;
25
+ if (values[mid] < value || (after && values[mid] === value)) {
26
+ low = mid + 1;
27
+ }
28
+ else {
29
+ high = mid;
30
+ }
31
+ }
32
+ return low;
33
+ }
34
+ function latticeAnchor(distinct, spacing, tolerance, offLattice) {
35
+ const EPSILON = 1e-6;
36
+ const n = distinct.length;
37
+ const countOf = (from, lo, hi, values) => {
38
+ let count = 0;
39
+ for (let i = lo; i < hi; i++) {
40
+ count += offLattice(from, values[i]) ? 0 : 1;
41
+ }
42
+ return count;
43
+ };
44
+ if (tolerance >= 0.5) {
45
+ return distinct[0];
46
+ }
47
+ let counts;
48
+ if (tolerance + EPSILON >= 0.5) {
49
+ counts = (index) => countOf(distinct[index], 0, n, distinct);
50
+ }
51
+ else {
52
+ const phaseOf = (d) => {
53
+ const steps = (d - distinct[0]) / spacing;
54
+ return steps - Math.floor(steps);
55
+ };
56
+ const order = distinct
57
+ .map((d) => [phaseOf(d), d])
58
+ .sort(([a], [b]) => a - b);
59
+ const phases = [];
60
+ const values = [];
61
+ for (const turn of [-1, 0, 1]) {
62
+ for (const [phase, d] of order) {
63
+ phases.push(phase + turn);
64
+ values.push(d);
65
+ }
66
+ }
67
+ counts = (index) => {
68
+ const from = distinct[index];
69
+ const phase = phaseOf(from);
70
+ const lo = searchSorted(phases, phase - tolerance - EPSILON);
71
+ const hi = searchSorted(phases, phase + tolerance + EPSILON, true);
72
+ const innerLo = searchSorted(phases, phase - tolerance + EPSILON);
73
+ const innerHi = searchSorted(phases, phase + tolerance - EPSILON, true);
74
+ if (innerLo >= innerHi) {
75
+ return countOf(from, lo, hi, values);
76
+ }
77
+ return (innerHi -
78
+ innerLo +
79
+ countOf(from, lo, innerLo, values) +
80
+ countOf(from, innerHi, hi, values));
81
+ };
82
+ }
83
+ let anchor = distinct[0];
84
+ let bestCount = -1;
85
+ for (let index = 0; index < n; index++) {
86
+ const count = counts(index);
87
+ if (count > bestCount) {
88
+ bestCount = count;
89
+ anchor = distinct[index];
90
+ }
91
+ }
92
+ return anchor;
93
+ }
94
+ export const planeGeometry = (instances, { args }) => {
95
+ const tolerance = numberArg(args, 'tolerance', 0.1);
96
+ const result = {
97
+ regularCount: 0,
98
+ irregularCount: 0,
99
+ positions: 0,
100
+ complete: false,
101
+ duplicates: false,
102
+ distance: {},
103
+ index: {},
104
+ };
105
+ const candidates = [];
106
+ for (const instance of [...instances].sort(byKey)) {
107
+ const key = instanceKey(instance);
108
+ const position = readVector(instance, 'ImagePositionPatient', 3);
109
+ const orientation = readVector(instance, 'ImageOrientationPatient', 6);
110
+ if (key === undefined || !position || !orientation) {
111
+ result.irregularCount++;
112
+ continue;
113
+ }
114
+ candidates.push({
115
+ key,
116
+ position,
117
+ orientation,
118
+ orientationKey: orientation.map((v) => round(v, 1e-3).toFixed(3)).join(),
119
+ });
120
+ }
121
+ const orientationCounts = new Map();
122
+ for (const { orientationKey } of candidates) {
123
+ orientationCounts.set(orientationKey, (orientationCounts.get(orientationKey) ?? 0) + 1);
124
+ }
125
+ const [dominant] = [...orientationCounts.entries()].sort(([aKey, aCount], [bKey, bCount]) => bCount - aCount || (aKey < bKey ? -1 : aKey > bKey ? 1 : 0));
126
+ if (!dominant) {
127
+ return result;
128
+ }
129
+ const inPlane = candidates.filter((c) => c.orientationKey === dominant[0]);
130
+ result.irregularCount += candidates.length - inPlane.length;
131
+ const [row0, row1, row2, col0, col1, col2] = inPlane[0].orientation;
132
+ const normal = vec3.cross(vec3.create(), vec3.fromValues(row0, row1, row2), vec3.fromValues(col0, col1, col2));
133
+ vec3.normalize(normal, normal);
134
+ result.normal = Array.from(normal);
135
+ const projected = inPlane.map((candidate) => ({
136
+ ...candidate,
137
+ d: vec3.dot(normal, candidate.position),
138
+ }));
139
+ const distinct = [];
140
+ for (const d of projected.map((p) => p.d).sort((a, b) => a - b)) {
141
+ if (!distinct.length || d - distinct[distinct.length - 1] >= 0.01) {
142
+ distinct.push(round(d, 1e-3));
143
+ }
144
+ }
145
+ const gapCounts = new Map();
146
+ for (let i = 1; i < distinct.length; i++) {
147
+ const gap = round(distinct[i] - distinct[i - 1], 0.01);
148
+ gapCounts.set(gap, (gapCounts.get(gap) ?? 0) + 1);
149
+ }
150
+ const [spacingEntry] = [...gapCounts.entries()].sort(([aGap, aCount], [bGap, bCount]) => bCount - aCount || aGap - bGap);
151
+ const spacing = spacingEntry?.[0];
152
+ if (!spacing) {
153
+ result.irregularCount += inPlane.length;
154
+ return result;
155
+ }
156
+ result.spacing = spacing;
157
+ const offLattice = (from, to) => {
158
+ const steps = (to - from) / spacing;
159
+ return Math.abs(steps - Math.round(steps)) > tolerance;
160
+ };
161
+ const anchor = latticeAnchor(distinct, spacing, tolerance, offLattice);
162
+ const regular = projected.filter(({ d }) => !offLattice(anchor, d));
163
+ result.irregularCount += projected.length - regular.length;
164
+ result.regularCount = regular.length;
165
+ const lowest = regular.reduce((low, c) => (c.d < low.d ? c : low));
166
+ result.origin = [...lowest.position];
167
+ const occupied = new Map();
168
+ for (const { key, d } of projected) {
169
+ result.distance[key] = d - lowest.d;
170
+ }
171
+ for (const { key, d } of regular) {
172
+ const index = Math.round((d - lowest.d) / spacing);
173
+ result.index[key] = index;
174
+ occupied.set(index, (occupied.get(index) ?? 0) + 1);
175
+ }
176
+ result.positions = occupied.size;
177
+ result.complete = Math.max(...occupied.keys()) === occupied.size - 1;
178
+ result.duplicates = [...occupied.values()].some((count) => count > 1);
179
+ return result;
180
+ };
181
+ export function dicomDateTimeToSeconds(date, time) {
182
+ const timeText = time == null ? '' : String(time).replace(/:/g, '').trim();
183
+ const dateText = date == null ? '' : String(date).replace(/-/g, '').trim();
184
+ const timeMatch = /^(\d{2})(\d{2})?(\d{2})?(\.\d+)?/.exec(timeText);
185
+ if (!timeMatch) {
186
+ return undefined;
187
+ }
188
+ const [, hh, mm = '0', ss = '0', fraction = ''] = timeMatch;
189
+ const timeOfDay = Number(hh) * 3600 + Number(mm) * 60 + Number(ss) + Number(`0${fraction}`);
190
+ const dateMatch = /^(\d{4})(\d{2})(\d{2})$/.exec(dateText);
191
+ if (!dateMatch) {
192
+ return timeOfDay;
193
+ }
194
+ const [, year, month, day] = dateMatch;
195
+ return (Date.UTC(Number(year), Number(month) - 1, Number(day)) / 1000 + timeOfDay);
196
+ }
197
+ function acquisitionSeconds(instance) {
198
+ const dateTime = readOwn(instance, 'AcquisitionDateTime');
199
+ if (dateTime != null && String(dateTime).length > 8) {
200
+ const text = String(dateTime);
201
+ return dicomDateTimeToSeconds(text.slice(0, 8), text.slice(8));
202
+ }
203
+ for (const [date, time] of [
204
+ ['AcquisitionDate', 'AcquisitionTime'],
205
+ ['ContentDate', 'ContentTime'],
206
+ ]) {
207
+ const seconds = dicomDateTimeToSeconds(readOwn(instance, date), readOwn(instance, time));
208
+ if (seconds !== undefined) {
209
+ return seconds;
210
+ }
211
+ }
212
+ return undefined;
213
+ }
214
+ export const timeClusters = (instances, { args }) => {
215
+ const maxGap = numberArg(args, 'maxGap', 30);
216
+ const result = {
217
+ time: {},
218
+ start: {},
219
+ clusters: 0,
220
+ untimedCount: 0,
221
+ };
222
+ const timed = [];
223
+ for (const instance of instances) {
224
+ const key = instanceKey(instance);
225
+ const seconds = acquisitionSeconds(instance);
226
+ if (key === undefined || seconds === undefined) {
227
+ result.untimedCount++;
228
+ continue;
229
+ }
230
+ timed.push({ key, seconds });
231
+ result.time[key] = seconds;
232
+ }
233
+ timed.sort((a, b) => a.seconds - b.seconds || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
234
+ let start;
235
+ let previous;
236
+ for (const { key, seconds } of timed) {
237
+ if (previous === undefined || seconds - previous > maxGap) {
238
+ start = seconds;
239
+ result.clusters++;
240
+ }
241
+ result.start[key] = start;
242
+ previous = seconds;
243
+ }
244
+ result.first = timed[0]?.seconds;
245
+ return result;
246
+ };
247
+ function predecessorUids(instance) {
248
+ const uids = [];
249
+ const items = (value) => value == null ? [] : asArray(value);
250
+ for (const study of items(readOwn(instance, 'PredecessorDocumentsSequence'))) {
251
+ for (const series of items(readOwn(study, 'ReferencedSeriesSequence'))) {
252
+ for (const sop of items(readOwn(series, 'ReferencedSOPSequence'))) {
253
+ const uid = readOwn(sop, 'ReferencedSOPInstanceUID');
254
+ if (typeof uid === 'string' && uid) {
255
+ uids.push(uid);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ return uids;
261
+ }
262
+ export const revisionChains = (instances) => {
263
+ const keys = instances.map(instanceKey);
264
+ const indexOf = new Map();
265
+ keys.forEach((key, index) => key !== undefined && indexOf.set(key, index));
266
+ const predecessors = instances.map((instance, index) => predecessorUids(instance)
267
+ .map((uid) => indexOf.get(uid))
268
+ .filter((predecessor) => predecessor !== undefined &&
269
+ predecessor !== index &&
270
+ instances[predecessor].SeriesInstanceUID ===
271
+ instance.SeriesInstanceUID));
272
+ const named = new Set(predecessors.flat());
273
+ const chainOf = instances.map(() => undefined);
274
+ instances.forEach((_instance, end) => {
275
+ if (named.has(end)) {
276
+ return;
277
+ }
278
+ const stack = [end];
279
+ while (stack.length) {
280
+ const index = stack.pop();
281
+ if (chainOf[index] !== undefined) {
282
+ continue;
283
+ }
284
+ chainOf[index] = end;
285
+ stack.push(...predecessors[index]);
286
+ }
287
+ });
288
+ const result = { active: {} };
289
+ keys.forEach((key, index) => {
290
+ const activeKey = keys[chainOf[index] ?? index];
291
+ if (key !== undefined && activeKey !== undefined) {
292
+ result.active[key] = activeKey;
293
+ }
294
+ });
295
+ return result;
296
+ };
297
+ export const BUILT_IN_SERIES_FUNCTIONS = Object.freeze({
298
+ planeGeometry: Object.freeze({
299
+ compute: planeGeometry,
300
+ fields: Object.freeze({
301
+ normal: 'series',
302
+ origin: 'series',
303
+ spacing: 'series',
304
+ regularCount: 'series',
305
+ irregularCount: 'series',
306
+ positions: 'series',
307
+ complete: 'series',
308
+ duplicates: 'series',
309
+ distance: 'instance',
310
+ index: 'instance',
311
+ }),
312
+ }),
313
+ timeClusters: Object.freeze({
314
+ compute: timeClusters,
315
+ fields: Object.freeze({
316
+ time: 'instance',
317
+ start: 'instance',
318
+ first: 'series',
319
+ clusters: 'series',
320
+ untimedCount: 'series',
321
+ }),
322
+ }),
323
+ revisionChains: Object.freeze({
324
+ compute: revisionChains,
325
+ fields: Object.freeze({ active: 'instance' }),
326
+ }),
327
+ });
@@ -0,0 +1,14 @@
1
+ export declare const imageSopClassUids: readonly string[];
2
+ export declare const videoSopClassUids: readonly string[];
3
+ export declare const videoTransferSyntaxUids: readonly string[];
4
+ export declare const longVideoSopClassUids: readonly string[];
5
+ export declare const longVideoMinFrames = 90;
6
+ export declare const ecgSopClassUids: readonly string[];
7
+ export declare const wholeSlideSopClassUids: readonly string[];
8
+ export declare const segmentationSopClassUids: readonly string[];
9
+ export declare const rtStructureSetSopClassUids: readonly string[];
10
+ export declare const parametricMapSopClassUids: readonly string[];
11
+ export declare const structuredReportSopClassUids: readonly string[];
12
+ export declare const encapsulatedDocumentSopClassUids: readonly string[];
13
+ export declare const bulkAnnotationSopClassUids: readonly string[];
14
+ export declare const chartSopClassUids: readonly string[];
@@ -0,0 +1,100 @@
1
+ import { videoUIDs } from '../utilities/isVideoTransferSyntax.js';
2
+ const list = (uids) => uids;
3
+ export const imageSopClassUids = list([
4
+ '1.2.840.10008.5.1.4.1.1.1',
5
+ '1.2.840.10008.5.1.4.1.1.1.1',
6
+ '1.2.840.10008.5.1.4.1.1.1.1.1',
7
+ '1.2.840.10008.5.1.4.1.1.1.2',
8
+ '1.2.840.10008.5.1.4.1.1.1.2.1',
9
+ '1.2.840.10008.5.1.4.1.1.1.3',
10
+ '1.2.840.10008.5.1.4.1.1.1.3.1',
11
+ '1.2.840.10008.5.1.4.1.1.2',
12
+ '1.2.840.10008.5.1.4.1.1.2.1',
13
+ '1.2.840.10008.5.1.4.1.1.2.2',
14
+ '1.2.840.10008.5.1.4.1.1.3.1',
15
+ '1.2.840.10008.5.1.4.1.1.4',
16
+ '1.2.840.10008.5.1.4.1.1.4.1',
17
+ '1.2.840.10008.5.1.4.1.1.4.3',
18
+ '1.2.840.10008.5.1.4.1.1.4.4',
19
+ '1.2.840.10008.5.1.4.1.1.6.1',
20
+ '1.2.840.10008.5.1.4.1.1.6.2',
21
+ '1.2.840.10008.5.1.4.1.1.7',
22
+ '1.2.840.10008.5.1.4.1.1.7.1',
23
+ '1.2.840.10008.5.1.4.1.1.7.2',
24
+ '1.2.840.10008.5.1.4.1.1.7.3',
25
+ '1.2.840.10008.5.1.4.1.1.7.4',
26
+ '1.2.840.10008.5.1.4.1.1.12.1',
27
+ '1.2.840.10008.5.1.4.1.1.12.1.1',
28
+ '1.2.840.10008.5.1.4.1.1.12.2',
29
+ '1.2.840.10008.5.1.4.1.1.12.2.1',
30
+ '1.2.840.10008.5.1.4.1.1.13.1.1',
31
+ '1.2.840.10008.5.1.4.1.1.13.1.2',
32
+ '1.2.840.10008.5.1.4.1.1.13.1.3',
33
+ '1.2.840.10008.5.1.4.1.1.13.1.4',
34
+ '1.2.840.10008.5.1.4.1.1.13.1.5',
35
+ '1.2.840.10008.5.1.4.1.1.14.1',
36
+ '1.2.840.10008.5.1.4.1.1.14.2',
37
+ '1.2.840.10008.5.1.4.1.1.20',
38
+ '1.2.840.10008.5.1.4.1.1.77.1.1',
39
+ '1.2.840.10008.5.1.4.1.1.77.1.1.1',
40
+ '1.2.840.10008.5.1.4.1.1.77.1.2',
41
+ '1.2.840.10008.5.1.4.1.1.77.1.2.1',
42
+ '1.2.840.10008.5.1.4.1.1.77.1.3',
43
+ '1.2.840.10008.5.1.4.1.1.77.1.4',
44
+ '1.2.840.10008.5.1.4.1.1.77.1.4.1',
45
+ '1.2.840.10008.5.1.4.1.1.77.1.5.1',
46
+ '1.2.840.10008.5.1.4.1.1.77.1.5.2',
47
+ '1.2.840.10008.5.1.4.1.1.77.1.5.4',
48
+ '1.2.840.10008.5.1.4.1.1.77.1.6',
49
+ '1.2.840.10008.5.1.4.1.1.82.1',
50
+ '1.2.840.10008.5.1.4.1.1.128',
51
+ '1.2.840.10008.5.1.4.1.1.128.1',
52
+ '1.2.840.10008.5.1.4.1.1.130',
53
+ '1.2.840.10008.5.1.4.1.1.481.1',
54
+ '1.2.840.10008.5.1.4.1.1.481.2',
55
+ ]);
56
+ export const videoSopClassUids = list([
57
+ '1.2.840.10008.5.1.4.1.1.77.1.1.1',
58
+ '1.2.840.10008.5.1.4.1.1.77.1.2.1',
59
+ '1.2.840.10008.5.1.4.1.1.77.1.4.1',
60
+ ]);
61
+ export const videoTransferSyntaxUids = list([...videoUIDs]);
62
+ export const longVideoSopClassUids = list([
63
+ '1.2.840.10008.5.1.4.1.1.7',
64
+ '1.2.840.10008.5.1.4.1.1.7.4',
65
+ ]);
66
+ export const longVideoMinFrames = 90;
67
+ export const ecgSopClassUids = list([
68
+ '1.2.840.10008.5.1.4.1.1.9.1.1',
69
+ '1.2.840.10008.5.1.4.1.1.9.1.2',
70
+ '1.2.840.10008.5.1.4.1.1.9.1.3',
71
+ '1.2.840.10008.5.1.4.1.1.9.2.1',
72
+ '1.2.840.10008.5.1.4.1.1.9.3.1',
73
+ ]);
74
+ export const wholeSlideSopClassUids = list([
75
+ '1.2.840.10008.5.1.4.1.1.77.1.6',
76
+ ]);
77
+ export const segmentationSopClassUids = list([
78
+ '1.2.840.10008.5.1.4.1.1.66.4',
79
+ '1.2.840.10008.5.1.4.1.1.66.7',
80
+ ]);
81
+ export const rtStructureSetSopClassUids = list([
82
+ '1.2.840.10008.5.1.4.1.1.481.3',
83
+ ]);
84
+ export const parametricMapSopClassUids = list([
85
+ '1.2.840.10008.5.1.4.1.1.30',
86
+ ]);
87
+ export const structuredReportSopClassUids = list([
88
+ '1.2.840.10008.5.1.4.1.1.88.11',
89
+ '1.2.840.10008.5.1.4.1.1.88.22',
90
+ '1.2.840.10008.5.1.4.1.1.88.33',
91
+ '1.2.840.10008.5.1.4.1.1.88.34',
92
+ '1.2.840.10008.5.1.4.1.1.88.67',
93
+ ]);
94
+ export const encapsulatedDocumentSopClassUids = list([
95
+ '1.2.840.10008.5.1.4.1.1.104.1',
96
+ ]);
97
+ export const bulkAnnotationSopClassUids = list([
98
+ '1.2.840.10008.5.1.4.1.1.91.1',
99
+ ]);
100
+ export const chartSopClassUids = list([]);
@@ -0,0 +1,10 @@
1
+ import type { NaturalizedInstance, RuleContext } from './types.js';
2
+ export declare function compare(a: unknown, b: unknown): number;
3
+ export declare function compareSOPInstanceUID(a: NaturalizedInstance, b: NaturalizedInstance, _context?: RuleContext): number;
4
+ export declare function compareInstanceNumber(a: NaturalizedInstance, b: NaturalizedInstance, _context?: RuleContext): number;
5
+ export declare function sortByInstanceNumber(a: NaturalizedInstance, b: NaturalizedInstance, context?: RuleContext): number;
6
+ export declare const instancesSortCriteria: {
7
+ default: typeof sortByInstanceNumber;
8
+ sortByInstanceNumber: typeof sortByInstanceNumber;
9
+ };
10
+ export declare function sortInstancesCopy<T extends NaturalizedInstance>(instances: readonly T[], criteria?: (a: NaturalizedInstance, b: NaturalizedInstance, context?: RuleContext) => number, context?: RuleContext): T[];
@@ -0,0 +1,33 @@
1
+ import { toFiniteFirst } from '@cornerstonejs/utils';
2
+ export function compare(a, b) {
3
+ if (a == b) {
4
+ return 0;
5
+ }
6
+ if (!a && b) {
7
+ return -1;
8
+ }
9
+ if (!b && a) {
10
+ return 1;
11
+ }
12
+ return a < b ? -1 : 1;
13
+ }
14
+ export function compareSOPInstanceUID(a, b, _context) {
15
+ return compare(String(a.SOPInstanceUID ?? a.imageId ?? ''), String(b.SOPInstanceUID ?? b.imageId ?? ''));
16
+ }
17
+ export function compareInstanceNumber(a, b, _context) {
18
+ const aNumber = toFiniteFirst(a.InstanceNumber);
19
+ const bNumber = toFiniteFirst(b.InstanceNumber);
20
+ return aNumber !== undefined && bNumber !== undefined
21
+ ? aNumber - bNumber
22
+ : Number(aNumber === undefined) - Number(bNumber === undefined);
23
+ }
24
+ export function sortByInstanceNumber(a, b, context) {
25
+ return (compareInstanceNumber(a, b, context) || compareSOPInstanceUID(a, b, context));
26
+ }
27
+ export const instancesSortCriteria = {
28
+ default: sortByInstanceNumber,
29
+ sortByInstanceNumber,
30
+ };
31
+ export function sortInstancesCopy(instances, criteria = instancesSortCriteria.default, context) {
32
+ return [...instances].sort((a, b) => criteria(a, b, context));
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { compareInstanceNumber, sortByInstanceNumber, sortInstancesCopy, } from './sortInstances.js';
2
+ const uids = (instances) => instances.map((instance) => instance.SOPInstanceUID);
3
+ describe('sortByInstanceNumber - the default instance order', () => {
4
+ const CASES = [
5
+ [
6
+ 'orders by InstanceNumber, also a numeric string',
7
+ [
8
+ { SOPInstanceUID: 'c', InstanceNumber: 10 },
9
+ { SOPInstanceUID: 'a', InstanceNumber: 2 },
10
+ { SOPInstanceUID: 'b', InstanceNumber: '3' },
11
+ ],
12
+ ['a', 'b', 'c'],
13
+ ],
14
+ [
15
+ 'puts an absent InstanceNumber after the numbered instances, not at 0',
16
+ [
17
+ { SOPInstanceUID: 'none' },
18
+ { SOPInstanceUID: 'one', InstanceNumber: 1 },
19
+ { SOPInstanceUID: 'zero', InstanceNumber: 0 },
20
+ ],
21
+ ['zero', 'one', 'none'],
22
+ ],
23
+ [
24
+ 'orders equal numbers by SOPInstanceUID',
25
+ [
26
+ { SOPInstanceUID: '1.2.9', InstanceNumber: 1 },
27
+ { SOPInstanceUID: '1.2.3', InstanceNumber: 1 },
28
+ ],
29
+ ['1.2.3', '1.2.9'],
30
+ ],
31
+ ];
32
+ for (const [name, instances, expected] of CASES) {
33
+ it(`${name}, and returns a copy`, () => {
34
+ const input = [...instances];
35
+ expect(uids(sortInstancesCopy(input))).toEqual(expected);
36
+ expect(input).toEqual(instances);
37
+ });
38
+ }
39
+ it('passes context down a custom comparator chain', () => {
40
+ const context = { series: {} };
41
+ const seen = [];
42
+ const byNumberThenDescription = (a, b, ctx) => {
43
+ seen.push(ctx);
44
+ return (compareInstanceNumber(a, b, ctx) ||
45
+ String(b.SeriesDescription).localeCompare(String(a.SeriesDescription)) ||
46
+ sortByInstanceNumber(a, b, ctx));
47
+ };
48
+ const sorted = sortInstancesCopy([
49
+ { SOPInstanceUID: 'x', InstanceNumber: 1, SeriesDescription: 'A' },
50
+ { SOPInstanceUID: 'y', InstanceNumber: 1, SeriesDescription: 'B' },
51
+ ], byNumberThenDescription, context);
52
+ expect(uids(sorted)).toEqual(['y', 'x']);
53
+ expect(seen.length).toBeGreaterThan(0);
54
+ expect(seen.every((value) => value === context)).toBe(true);
55
+ });
56
+ });
@@ -1,6 +1,6 @@
1
- import type { InstanceGroup, NaturalizedInstance, SplitContext, SplitRule } from './types.js';
2
- export type SplitImageIdsBySplitRulesOptions = SplitContext & {
3
- splitRules: SplitRule[];
1
+ import type { GroupInstancesOptions, InstanceGroup, NaturalizedInstance, SplitContext, SplitRuleSet } from './types.js';
2
+ export type SplitImageIdsBySplitRulesOptions = SplitContext & GroupInstancesOptions & {
3
+ splitRules: SplitRuleSet;
4
4
  onMissingImageId?: (imageId: string) => void;
5
5
  onUnmatchedInstance?: (instance: NaturalizedInstance) => void;
6
6
  };
@@ -1,12 +1,12 @@
1
1
  import { groupInstancesBySplitRules } from './groupInstancesBySplitRules.js';
2
2
  import { resolveInstances } from './resolveInstances.js';
3
3
  export function splitImageIdsBySplitRules(imageIds, options) {
4
- const { getNaturalizedInstance, splitRules, onMissingImageId, onUnmatchedInstance, } = options;
4
+ const { getNaturalizedInstance, splitRules, onMissingImageId, onUnmatchedInstance, sortInstances, compareInstances, } = options;
5
5
  const instances = resolveInstances(imageIds, getNaturalizedInstance, {
6
6
  onMissing: onMissingImageId,
7
7
  });
8
- if (!instances.length) {
9
- return [];
10
- }
11
- return groupInstancesBySplitRules(instances, splitRules, onUnmatchedInstance);
8
+ return groupInstancesBySplitRules(instances, splitRules, onUnmatchedInstance, {
9
+ sortInstances,
10
+ compareInstances,
11
+ });
12
12
  }
@@ -0,0 +1,11 @@
1
+ import type { SchemaShape } from '../safeFunctions/index.js';
2
+ export declare const ATTRIBUTE_ONLY_NAMES: readonly ["splitNumber", "sopClassUids"];
3
+ export declare const splitRuleSchema: {
4
+ rule: SchemaShape;
5
+ contextEntry: SchemaShape;
6
+ condition: SchemaShape;
7
+ matchTest: SchemaShape;
8
+ name: SchemaShape;
9
+ sortKey: SchemaShape;
10
+ attributeValue: SchemaShape;
11
+ };