@fnndsc/salsa 3.16.0 → 3.18.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.
@@ -5,3 +5,4 @@
5
5
  export * from './dictionary.js';
6
6
  export * from './tags.js';
7
7
  export * from './series.js';
8
+ export * from './pixels.js';
@@ -21,4 +21,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
21
  __exportStar(require("./dictionary.js"), exports);
22
22
  __exportStar(require("./tags.js"), exports);
23
23
  __exportStar(require("./series.js"), exports);
24
+ __exportStar(require("./pixels.js"), exports);
24
25
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dicom/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;GAGG;AACH,kDAAgC;AAChC,4CAA0B;AAC1B,8CAA4B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dicom/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;GAGG;AACH,kDAAgC;AAChC,4CAA0B;AAC1B,8CAA4B;AAC5B,8CAA4B"}
@@ -0,0 +1,28 @@
1
+ import { type Result } from '@fnndsc/cumin';
2
+ import { type DicomFolderIO } from './series.js';
3
+ /** A slice's luminance: `gray[y * width + x]`, 0 (black) to 255 (white). */
4
+ export interface DicomGray {
5
+ width: number;
6
+ height: number;
7
+ gray: Uint8Array;
8
+ }
9
+ /**
10
+ * The outcome of reading a slice for a thumbnail: the grid, or the news that
11
+ * the pixels are compressed and this build cannot decode them. An unreadable
12
+ * or non-DICOM file is an error, not an outcome.
13
+ */
14
+ export type DicomGrayOutcome = ({
15
+ kind: 'gray';
16
+ } & DicomGray) | {
17
+ kind: 'compressed';
18
+ transferSyntax: string;
19
+ };
20
+ /**
21
+ * Reads one DICOM slice as an 8-bit grayscale grid.
22
+ *
23
+ * @param path - The slice file's VFS path.
24
+ * @param io - The reader, injected for tests.
25
+ * @returns The grid, the compressed verdict, or an error when the bytes are
26
+ * not a readable DICOM file.
27
+ */
28
+ export declare function dicomSlice_gray(path: string, io?: DicomFolderIO): Promise<Result<DicomGrayOutcome>>;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.dicomSlice_gray = dicomSlice_gray;
7
+ /**
8
+ * @file One DICOM slice as an 8-bit grayscale grid, for a text thumbnail.
9
+ *
10
+ * The kernel reads a slice's pixels, applies the modality rescale and a
11
+ * window/level (the file's own, or an auto range when it declares none), and
12
+ * normalises to 0..255 with MONOCHROME1 inverted so bright always reads high.
13
+ * A surface then renders that grid — an ASCII ramp on any pipe, ANSI
14
+ * half-blocks on a colour terminal — without knowing anything about DICOM.
15
+ *
16
+ * Only uncompressed transfer syntaxes are read here; a compressed one (the
17
+ * JPEG family, RLE) needs a codec the kernel does not carry, and says so
18
+ * rather than pretending. dcmjs decodes the endianness of the uncompressed
19
+ * ones, so the pixels arrive native-endian in the naturalised dataset.
20
+ *
21
+ * @module
22
+ */
23
+ const dcmjs_1 = __importDefault(require("dcmjs"));
24
+ const cumin_1 = require("@fnndsc/cumin");
25
+ const series_js_1 = require("./series.js");
26
+ /** Transfer syntaxes the kernel cannot decode without a codec: JPEG* and RLE. */
27
+ const COMPRESSED_SYNTAX = /^1\.2\.840\.10008\.1\.2\.[45]/;
28
+ /**
29
+ * Reads one DICOM slice as an 8-bit grayscale grid.
30
+ *
31
+ * @param path - The slice file's VFS path.
32
+ * @param io - The reader, injected for tests.
33
+ * @returns The grid, the compressed verdict, or an error when the bytes are
34
+ * not a readable DICOM file.
35
+ */
36
+ async function dicomSlice_gray(path, io = series_js_1.dicomFolderIO_default) {
37
+ const bytes = await io.readBinary(path);
38
+ if (!bytes.ok)
39
+ return (0, cumin_1.Err)();
40
+ try {
41
+ const buffer = new Uint8Array(bytes.value).slice().buffer;
42
+ const message = dcmjs_1.default.data.DicomMessage.readFile(buffer, { ignoreErrors: true });
43
+ const meta = dcmjs_1.default.data.DicomMetaDictionary.naturalizeDataset(message.meta);
44
+ const ds = dcmjs_1.default.data.DicomMetaDictionary.naturalizeDataset(message.dict);
45
+ const transferSyntax = typeof meta.TransferSyntaxUID === 'string' ? meta.TransferSyntaxUID : '';
46
+ if (COMPRESSED_SYNTAX.test(transferSyntax))
47
+ return (0, cumin_1.Ok)({ kind: 'compressed', transferSyntax });
48
+ const height = numberOf(ds.Rows) ?? 0;
49
+ const width = numberOf(ds.Columns) ?? 0;
50
+ if (height === 0 || width === 0)
51
+ return (0, cumin_1.Err)();
52
+ const count = width * height;
53
+ const pixelData = Array.isArray(ds.PixelData) ? ds.PixelData[0] : ds.PixelData;
54
+ if (!(pixelData instanceof ArrayBuffer))
55
+ return (0, cumin_1.Err)();
56
+ const bits = numberOf(ds.BitsAllocated) ?? 16;
57
+ const signed = numberOf(ds.PixelRepresentation) === 1;
58
+ const src = new Float64Array(count);
59
+ if (bits === 16) {
60
+ const view = signed ? new Int16Array(pixelData, 0, count) : new Uint16Array(pixelData, 0, count);
61
+ for (let i = 0; i < count; i++)
62
+ src[i] = view[i];
63
+ }
64
+ else if (bits === 8) {
65
+ const view = new Uint8Array(pixelData, 0, count);
66
+ for (let i = 0; i < count; i++)
67
+ src[i] = view[i];
68
+ }
69
+ else {
70
+ return (0, cumin_1.Err)();
71
+ }
72
+ // Modality rescale: stored value to a physical unit (HU for CT, etc.).
73
+ const slope = numberOf(ds.RescaleSlope) ?? 1;
74
+ const intercept = numberOf(ds.RescaleIntercept) ?? 0;
75
+ if (slope !== 1 || intercept !== 0)
76
+ for (let i = 0; i < count; i++)
77
+ src[i] = src[i] * slope + intercept;
78
+ // The file's own window/level, or an auto range when it declares none.
79
+ let center = firstNumber(ds.WindowCenter);
80
+ let width_wl = firstNumber(ds.WindowWidth);
81
+ if (center === undefined || width_wl === undefined || width_wl <= 0) {
82
+ const auto = autoWindow(src);
83
+ center = auto.center;
84
+ width_wl = auto.width;
85
+ }
86
+ const low = center - width_wl / 2;
87
+ const invert = ds.PhotometricInterpretation === 'MONOCHROME1';
88
+ const gray = new Uint8Array(count);
89
+ for (let i = 0; i < count; i++) {
90
+ let t = ((src[i] - low) / width_wl) * 255;
91
+ t = t < 0 ? 0 : t > 255 ? 255 : t;
92
+ gray[i] = invert ? 255 - t : t;
93
+ }
94
+ return (0, cumin_1.Ok)({ kind: 'gray', width, height, gray });
95
+ }
96
+ catch {
97
+ return (0, cumin_1.Err)();
98
+ }
99
+ }
100
+ /** The first number of a DICOM value that may be a number, an array, or a DS string. */
101
+ function firstNumber(value) {
102
+ const one = Array.isArray(value) ? value[0] : value;
103
+ if (typeof one === 'number')
104
+ return Number.isFinite(one) ? one : undefined;
105
+ if (typeof one === 'string') {
106
+ const parsed = Number.parseFloat(one);
107
+ return Number.isFinite(parsed) ? parsed : undefined;
108
+ }
109
+ return undefined;
110
+ }
111
+ /** A whole number from a DICOM value that may arrive as a number or a string. */
112
+ function numberOf(value) {
113
+ if (typeof value === 'number')
114
+ return value;
115
+ if (typeof value === 'string') {
116
+ const parsed = Number.parseInt(value, 10);
117
+ return Number.isFinite(parsed) ? parsed : undefined;
118
+ }
119
+ return undefined;
120
+ }
121
+ /** A window from the robust range of the data: its 2nd to 98th percentile. */
122
+ function autoWindow(src) {
123
+ const sorted = src.slice().sort();
124
+ const low = sorted[Math.floor(sorted.length * 0.02)] ?? 0;
125
+ const high = sorted[Math.floor(sorted.length * 0.98)] ?? low + 1;
126
+ const width = Math.max(1, high - low);
127
+ return { center: low + width / 2, width };
128
+ }
129
+ //# sourceMappingURL=pixels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pixels.js","sourceRoot":"","sources":["../../src/dicom/pixels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @file Standing in for what a PACS answer says about a person.
3
+ *
4
+ * A demo runs against a live hospital PACS, and a live PACS answers with
5
+ * the names, record numbers and dates of real patients. This substitutes a
6
+ * stable stand-in for each of those before an answer reaches a screen, so a
7
+ * projector, a screenshot or a recording carries none of them.
8
+ *
9
+ * What this is NOT: anonymisation in the regulatory sense. A record number
10
+ * is seven digits, an accession eight, and a hash of a space that small is
11
+ * enumerable in under a second — so the salt below is what stands between a
12
+ * token and the value behind it. The salt is made once per daemon and never
13
+ * written down, which makes tokens stable through one demo and meaningless
14
+ * after it. Two runs of the same demo do not agree, and that is the point:
15
+ * a pairing leaked from one recording says nothing about any other.
16
+ *
17
+ * The DICOM identifiers are deliberately left alone. They are not shown in
18
+ * a listing and they are what the kernel reconciles an answer against, so
19
+ * standing in for them would break the answer while hiding nothing.
20
+ *
21
+ * @module
22
+ */
23
+ /**
24
+ * The stand-in for one value: stable for this session, opaque outside it.
25
+ *
26
+ * @param value - The real value.
27
+ * @param chars - How many hex characters to keep.
28
+ * @returns The stand-in, or an empty string for an empty value.
29
+ */
30
+ export declare function token_of(value: string, chars?: number): string;
31
+ /**
32
+ * A stand-in name, in the form DICOM writes one.
33
+ *
34
+ * The same person reads the same way everywhere in one answer, which is
35
+ * what makes a demo followable; a different session gives a different
36
+ * person the same name and nobody can tell.
37
+ *
38
+ * @param value - The real name.
39
+ * @returns A name of the same shape, carrying nothing of the original.
40
+ */
41
+ export declare function nameStandIn_of(value: string): string;
42
+ /**
43
+ * A study date, coarsened to the month it fell in.
44
+ *
45
+ * @param value - A DICOM date, `YYYYMMDD`.
46
+ * @returns The first of that month, or the value unchanged when it is not a date.
47
+ */
48
+ export declare function date_toMonth(value: string): string;
49
+ /**
50
+ * A birth date, coarsened to its year.
51
+ *
52
+ * Coarser than a study date on purpose: an age, with a modality and a small
53
+ * institution, identifies a person where a month of imaging does not.
54
+ *
55
+ * @param value - A DICOM date, `YYYYMMDD`.
56
+ * @returns The first of that year, or the value unchanged when it is not a date.
57
+ */
58
+ export declare function birthDate_toYear(value: string): string;
59
+ /** The fields this stands in for, for a caller that needs to know. */
60
+ export declare const STOOD_IN_FIELDS: ReadonlyArray<string>;
61
+ /**
62
+ * Stands in for one field's value.
63
+ *
64
+ * @param field - The DICOM keyword.
65
+ * @param value - What the PACS said.
66
+ * @returns The stand-in, or the value unchanged when the field identifies nobody.
67
+ */
68
+ export declare function field_standIn(field: string, value: string): string;
69
+ /**
70
+ * Walks a decoded PACS answer, standing in for every identifying value.
71
+ *
72
+ * Done on the DECODED payload rather than on the model or the rendering,
73
+ * because both are built from it: substituting here means a listing, a
74
+ * table, a CSV and a console line all say the same thing, and none of them
75
+ * has to know this happened.
76
+ *
77
+ * @param payload - The decoded answer, as DICOM-shaped objects.
78
+ * @returns A copy carrying stand-ins.
79
+ */
80
+ export declare function answer_standIn(payload: unknown): unknown;
81
+ /**
82
+ * Stands in for the values in a query's own terms.
83
+ *
84
+ * The line an operator typed is the first thing on a screen and it carries
85
+ * the record number they typed. This rewrites the terms so the echoed line
86
+ * names the same token the rows do — which also ties the line to the answer
87
+ * below it, rather than hiding it behind stars that a PACS would read as a
88
+ * wildcard.
89
+ *
90
+ * @param criteria - The question, as field to value.
91
+ * @returns The question with identifying values stood in for.
92
+ */
93
+ export declare function criteria_standIn(criteria: Record<string, string>): Record<string, string>;
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * @file Standing in for what a PACS answer says about a person.
4
+ *
5
+ * A demo runs against a live hospital PACS, and a live PACS answers with
6
+ * the names, record numbers and dates of real patients. This substitutes a
7
+ * stable stand-in for each of those before an answer reaches a screen, so a
8
+ * projector, a screenshot or a recording carries none of them.
9
+ *
10
+ * What this is NOT: anonymisation in the regulatory sense. A record number
11
+ * is seven digits, an accession eight, and a hash of a space that small is
12
+ * enumerable in under a second — so the salt below is what stands between a
13
+ * token and the value behind it. The salt is made once per daemon and never
14
+ * written down, which makes tokens stable through one demo and meaningless
15
+ * after it. Two runs of the same demo do not agree, and that is the point:
16
+ * a pairing leaked from one recording says nothing about any other.
17
+ *
18
+ * The DICOM identifiers are deliberately left alone. They are not shown in
19
+ * a listing and they are what the kernel reconciles an answer against, so
20
+ * standing in for them would break the answer while hiding nothing.
21
+ *
22
+ * @module
23
+ */
24
+ var __importDefault = (this && this.__importDefault) || function (mod) {
25
+ return (mod && mod.__esModule) ? mod : { "default": mod };
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.STOOD_IN_FIELDS = void 0;
29
+ exports.token_of = token_of;
30
+ exports.nameStandIn_of = nameStandIn_of;
31
+ exports.date_toMonth = date_toMonth;
32
+ exports.birthDate_toYear = birthDate_toYear;
33
+ exports.field_standIn = field_standIn;
34
+ exports.answer_standIn = answer_standIn;
35
+ exports.criteria_standIn = criteria_standIn;
36
+ const crypto_1 = __importDefault(require("crypto"));
37
+ /**
38
+ * The secret behind every token, made once per process.
39
+ *
40
+ * A daemon is a session, so this is a session's salt: stable while the
41
+ * demo runs, gone when it ends.
42
+ */
43
+ const SALT = crypto_1.default.randomBytes(32);
44
+ /** How many hex characters a stand-in identifier carries. */
45
+ const TOKEN_CHARS = 8;
46
+ /** Family names for the stand-in, chosen to read as names and nothing more. */
47
+ const SURNAMES = [
48
+ 'ASHDOWN', 'BELLWEATHER', 'CARRICK', 'DUNMORE', 'EASTWICK', 'FAIRHOLM',
49
+ 'GRANTLEY', 'HARBOURNE', 'IRVINGTON', 'JARRETT', 'KESTREL', 'LOWNDES',
50
+ 'MARCHETTI', 'NORWOOD', 'OAKLEY', 'PENHALE', 'QUILLON', 'RAVENSCAR',
51
+ 'STARLING', 'THORNBURY', 'UNDERHILL', 'VANTAGE', 'WESTMARCH', 'YARROW',
52
+ ];
53
+ /** Given names for the stand-in. */
54
+ const FORENAMES = [
55
+ 'ALDEN', 'BRIONY', 'CASPAR', 'DELPHINE', 'EAMON', 'FENELLA', 'GARETH',
56
+ 'HESTER', 'ISOLDE', 'JASPER', 'KERENSA', 'LOWELL', 'MERIEL', 'NEVIN',
57
+ 'ORLA', 'PERRIN', 'QUENBY', 'ROSALIND', 'SEVERIN', 'TAMSIN', 'ULRIC',
58
+ 'VERITY', 'WYNTER', 'XANTHE',
59
+ ];
60
+ /**
61
+ * The stand-in for one value: stable for this session, opaque outside it.
62
+ *
63
+ * @param value - The real value.
64
+ * @param chars - How many hex characters to keep.
65
+ * @returns The stand-in, or an empty string for an empty value.
66
+ */
67
+ function token_of(value, chars = TOKEN_CHARS) {
68
+ if (value === '')
69
+ return '';
70
+ return crypto_1.default.createHash('sha256').update(SALT).update(value).digest('hex').slice(0, chars);
71
+ }
72
+ /**
73
+ * A stand-in name, in the form DICOM writes one.
74
+ *
75
+ * The same person reads the same way everywhere in one answer, which is
76
+ * what makes a demo followable; a different session gives a different
77
+ * person the same name and nobody can tell.
78
+ *
79
+ * @param value - The real name.
80
+ * @returns A name of the same shape, carrying nothing of the original.
81
+ */
82
+ function nameStandIn_of(value) {
83
+ if (value === '')
84
+ return '';
85
+ const digest = crypto_1.default.createHash('sha256').update(SALT).update(value).digest();
86
+ const surname = SURNAMES[digest[0] % SURNAMES.length];
87
+ const forename = FORENAMES[digest[1] % FORENAMES.length];
88
+ return `${surname}^${forename}`;
89
+ }
90
+ /**
91
+ * A study date, coarsened to the month it fell in.
92
+ *
93
+ * @param value - A DICOM date, `YYYYMMDD`.
94
+ * @returns The first of that month, or the value unchanged when it is not a date.
95
+ */
96
+ function date_toMonth(value) {
97
+ return /^\d{8}$/.test(value) ? `${value.slice(0, 6)}01` : value;
98
+ }
99
+ /**
100
+ * A birth date, coarsened to its year.
101
+ *
102
+ * Coarser than a study date on purpose: an age, with a modality and a small
103
+ * institution, identifies a person where a month of imaging does not.
104
+ *
105
+ * @param value - A DICOM date, `YYYYMMDD`.
106
+ * @returns The first of that year, or the value unchanged when it is not a date.
107
+ */
108
+ function birthDate_toYear(value) {
109
+ return /^\d{8}$/.test(value) ? `${value.slice(0, 4)}0101` : value;
110
+ }
111
+ /** How each identifying field is stood in for. */
112
+ const STAND_INS = {
113
+ PatientID: (value) => token_of(value),
114
+ PatientName: nameStandIn_of,
115
+ OtherPatientIDs: (value) => token_of(value),
116
+ OtherPatientNames: nameStandIn_of,
117
+ PatientMotherBirthName: nameStandIn_of,
118
+ AccessionNumber: (value) => token_of(value),
119
+ PatientBirthDate: birthDate_toYear,
120
+ PatientBirthTime: () => '',
121
+ PatientAge: () => '',
122
+ PatientAddress: () => '',
123
+ PatientTelephoneNumbers: () => '',
124
+ StudyDate: date_toMonth,
125
+ SeriesDate: date_toMonth,
126
+ ReferringPhysicianName: nameStandIn_of,
127
+ PerformingPhysicianName: nameStandIn_of,
128
+ OperatorsName: nameStandIn_of,
129
+ PhysiciansOfRecord: nameStandIn_of,
130
+ NameOfPhysiciansReadingStudy: nameStandIn_of,
131
+ InstitutionName: (value) => (value === '' ? '' : `SITE ${token_of(value, 4)}`),
132
+ InstitutionAddress: () => '',
133
+ };
134
+ /** The fields this stands in for, for a caller that needs to know. */
135
+ exports.STOOD_IN_FIELDS = Object.keys(STAND_INS);
136
+ /**
137
+ * Stands in for one field's value.
138
+ *
139
+ * @param field - The DICOM keyword.
140
+ * @param value - What the PACS said.
141
+ * @returns The stand-in, or the value unchanged when the field identifies nobody.
142
+ */
143
+ function field_standIn(field, value) {
144
+ const rule = STAND_INS[field];
145
+ return rule === undefined ? value : rule(value);
146
+ }
147
+ /**
148
+ * Walks a decoded PACS answer, standing in for every identifying value.
149
+ *
150
+ * Done on the DECODED payload rather than on the model or the rendering,
151
+ * because both are built from it: substituting here means a listing, a
152
+ * table, a CSV and a console line all say the same thing, and none of them
153
+ * has to know this happened.
154
+ *
155
+ * @param payload - The decoded answer, as DICOM-shaped objects.
156
+ * @returns A copy carrying stand-ins.
157
+ */
158
+ function answer_standIn(payload) {
159
+ if (Array.isArray(payload))
160
+ return payload.map((one) => answer_standIn(one));
161
+ if (payload === null || typeof payload !== 'object')
162
+ return payload;
163
+ const out = {};
164
+ for (const [field, held] of Object.entries(payload)) {
165
+ if (held !== null && typeof held === 'object' && 'value' in held) {
166
+ const inner = held;
167
+ const value = inner.value;
168
+ out[field] = typeof value === 'string'
169
+ ? { ...inner, value: field_standIn(field, value) }
170
+ : { ...inner, value: answer_standIn(value) };
171
+ continue;
172
+ }
173
+ if (typeof held === 'string') {
174
+ out[field] = field_standIn(field, held);
175
+ continue;
176
+ }
177
+ out[field] = answer_standIn(held);
178
+ }
179
+ return out;
180
+ }
181
+ /**
182
+ * Stands in for the values in a query's own terms.
183
+ *
184
+ * The line an operator typed is the first thing on a screen and it carries
185
+ * the record number they typed. This rewrites the terms so the echoed line
186
+ * names the same token the rows do — which also ties the line to the answer
187
+ * below it, rather than hiding it behind stars that a PACS would read as a
188
+ * wildcard.
189
+ *
190
+ * @param criteria - The question, as field to value.
191
+ * @returns The question with identifying values stood in for.
192
+ */
193
+ function criteria_standIn(criteria) {
194
+ return Object.fromEntries(Object.entries(criteria).map(([field, value]) => [field, field_standIn(field, value)]));
195
+ }
196
+ //# sourceMappingURL=anonymise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymise.js","sourceRoot":"","sources":["../../src/pacs/anonymise.ts"],"names":[],"mappings":"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"}
@@ -62,3 +62,4 @@ export declare function pacsRetrieve_delete(retrieveId: number): Promise<Result<
62
62
  * @returns Result containing PACSQueryStatusReport with series-level status.
63
63
  */
64
64
  export declare function pacsRetrieve_statusForQuery(queryId: number): Promise<Result<PACSQueryStatusReport>>;
65
+ export * from './anonymise.js';
@@ -3,6 +3,20 @@
3
3
  * @file PACS operations.
4
4
  * @module
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
6
20
  Object.defineProperty(exports, "__esModule", { value: true });
7
21
  exports.pacsServers_list = pacsServers_list;
8
22
  exports.pacsQueries_list = pacsQueries_list;
@@ -88,4 +102,5 @@ async function pacsRetrieve_delete(retrieveId) {
88
102
  async function pacsRetrieve_statusForQuery(queryId) {
89
103
  return (0, cumin_1.pacsRetrieve_statusForQuery)(queryId);
90
104
  }
105
+ __exportStar(require("./anonymise.js"), exports);
91
106
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pacs/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AA8BH,4CAIC;AAQD,4CAIC;AASD,gDAKC;AAQD,wDAIC;AAQD,kDAIC;AASD,gDAKC;AAQD,kDAIC;AASD,kEAIC;AAzHD,yCAoBuB;AAEvB;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CACpC,UAAiC,EAAE;IAEnC,OAAO,IAAA,wBAAsB,EAAC,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CACpC,UAAgC,EAAE;IAElC,OAAO,IAAA,wBAAsB,EAAC,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,kBAAkB,CACtC,UAAkB,EAClB,IAAyB;IAEzB,OAAO,IAAA,0BAAwB,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,sBAAsB,CAC1C,OAAe;IAEf,OAAO,IAAA,8BAA4B,EAAC,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CACvC,OAAe;IAEf,OAAO,IAAA,2BAAyB,EAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,kBAAkB,CACtC,OAAe,EACf,OAAqB;IAErB,OAAO,IAAA,0BAAwB,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CACvC,UAAkB;IAElB,OAAO,IAAA,2BAAyB,EAAC,UAAU,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,2BAA2B,CAC/C,OAAe;IAEf,OAAO,IAAA,mCAAiC,EAAC,OAAO,CAAC,CAAC;AACpD,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pacs/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AA8BH,4CAIC;AAQD,4CAIC;AASD,gDAKC;AAQD,wDAIC;AAQD,kDAIC;AASD,gDAKC;AAQD,kDAIC;AASD,kEAIC;AAzHD,yCAoBuB;AAEvB;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CACpC,UAAiC,EAAE;IAEnC,OAAO,IAAA,wBAAsB,EAAC,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CACpC,UAAgC,EAAE;IAElC,OAAO,IAAA,wBAAsB,EAAC,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,kBAAkB,CACtC,UAAkB,EAClB,IAAyB;IAEzB,OAAO,IAAA,0BAAwB,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,sBAAsB,CAC1C,OAAe;IAEf,OAAO,IAAA,8BAA4B,EAAC,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CACvC,OAAe;IAEf,OAAO,IAAA,2BAAyB,EAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,kBAAkB,CACtC,OAAe,EACf,OAAqB;IAErB,OAAO,IAAA,0BAAwB,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CACvC,UAAkB;IAElB,OAAO,IAAA,2BAAyB,EAAC,UAAU,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,2BAA2B,CAC/C,OAAe;IAEf,OAAO,IAAA,mCAAiC,EAAC,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,iDAA+B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fnndsc/salsa",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "Shared Application Logic and Service Assets for ChRIS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,7 +60,7 @@
60
60
  "adm-zip": "npm:empty-npm-package@1.0.0"
61
61
  },
62
62
  "dependencies": {
63
- "@fnndsc/cumin": "^3.21.1",
63
+ "@fnndsc/cumin": "^3.22.1",
64
64
  "axios": "^1.6.8",
65
65
  "dcmjs": "^0.52.0",
66
66
  "ws": "^8.21.0"