@cosyte/dicom 0.0.1 → 0.0.4
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/CHANGELOG.md +414 -25
- package/README.md +57 -48
- package/TRADEMARKS.md +32 -0
- package/dist/index.cjs +651 -200
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -133
- package/dist/index.d.ts +158 -133
- package/dist/index.mjs +651 -200
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @cosyte/dicom
|
|
2
2
|
|
|
3
|
-
> Read a real-world, vendor-quirky DICOM Part 10 file and pull the metadata you need in one line
|
|
3
|
+
> Read a real-world, vendor-quirky DICOM Part 10 file and pull the metadata you need in one line, without having read the DICOM standard.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@cosyte/dicom)
|
|
6
6
|
[](https://github.com/cosyte/dicom/actions/workflows/ci.yml)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
[](https://nodejs.org)
|
|
9
9
|
|
|
10
|
-
A developer-focused DICOM Part 10 parser and utility library for Node.js and TypeScript. **Metadata-first**: it reads the headers
|
|
10
|
+
A developer-focused DICOM Part 10 parser and utility library for Node.js and TypeScript. **Metadata-first**: it reads the headers (patient, study, series, image, codes, UIDs) leniently and fast, exposes pixel data as raw bytes, and **never decodes pixels**. Sibling to [`@cosyte/hl7`](https://github.com/cosyte/hl7); same engineering bar.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ A developer-focused DICOM Part 10 parser and utility library for Node.js and Typ
|
|
|
16
16
|
Useful output after install + parse. No DICOM spec knowledge required.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
# pnpm (recommended)
|
|
19
|
+
# pnpm (recommended). Also works with: npm install @cosyte/dicom | yarn add @cosyte/dicom
|
|
20
20
|
pnpm add @cosyte/dicom
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -26,25 +26,25 @@ import { parseDicom } from "@cosyte/dicom";
|
|
|
26
26
|
|
|
27
27
|
const ds = parseDicom(await readFile("study.dcm"));
|
|
28
28
|
|
|
29
|
-
ds.get("PatientName")?.value; // PN value
|
|
30
|
-
ds.study.instanceUid; // "1.2.840.…"
|
|
31
|
-
ds.image.rescaleSlope; // number | undefined
|
|
29
|
+
ds.get("PatientName")?.value; // PN value: structured, never flattened
|
|
30
|
+
ds.study.instanceUid; // "1.2.840.…": the global study anchor
|
|
31
|
+
ds.image.rescaleSlope; // number | undefined: undefined means "absent", never 1
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
That's the pitch: no config, no schema upload, no spec lookup. The parser accepts vendor-quirky input by default
|
|
34
|
+
That's the pitch: no config, no schema upload, no spec lookup. The parser accepts vendor-quirky input by default (it tolerates a missing preamble, a wrong File Meta group length, odd-length values, and the dozen-or-so deviations real scanners emit) and records each as a stable warning code rather than failing. You reach for typed value decode, safety-critical views, profiles, or the serializer when you want them.
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
38
|
## Features
|
|
39
39
|
|
|
40
|
-
- **One-line metadata extraction
|
|
41
|
-
- **Two access patterns
|
|
42
|
-
- **Lazy typed value decode
|
|
43
|
-
- **Real-world tolerance, Postel's Law
|
|
44
|
-
- **Source/vendor profile system
|
|
45
|
-
- **Metadata-level de-identification
|
|
46
|
-
- **Spec-clean serializer
|
|
47
|
-
- **Strict TypeScript, dual ESM + CJS, Node ≥ 22
|
|
40
|
+
- **One-line metadata extraction**: `ds.patient`, `ds.study`, `ds.series`, `ds.image`: typed, fail-safe views over the safety-critical attributes. No `(group,element)` tags to memorise.
|
|
41
|
+
- **Two access patterns**: named views, or structural `ds.get("PatientName")` / `ds.get("(0010,0010)")` by keyword or tag, plus `ds.elements()` to walk everything.
|
|
42
|
+
- **Lazy typed value decode**: `element.value` decodes raw bytes into a discriminated `DicomValue` across all 34 VRs (numbers, `bigint`s, person names, dates/times, sequences, raw `binary`), honoring `(0008,0005)` Specific Character Set through nested items.
|
|
43
|
+
- **Real-world tolerance, Postel's Law**: a lenient reader emits 24 stable warning codes for what it tolerated; only 4 truly-structural conditions are fatal. The serializer always writes spec-clean Part 10.
|
|
44
|
+
- **Source/vendor profile system**: `defineProfile()` + 5 built-ins (`ge`, `siemens`, `philips`, `strict`, `lenient`) that only ever _tighten or annotate_ a parse, resolving vendor private tags by the file's live Private Creator string, never a wrong decode.
|
|
45
|
+
- **Metadata-level de-identification**: `deidentify()` applies the PS3.15 Annex E Basic Profile + the nine metadata Options, returning a fresh dataset and a value-free audit report.
|
|
46
|
+
- **Spec-clean serializer**: `serializeDicom(ds)` round-trips a dataset back to Part 10 bytes in its source transfer syntax (no transcode), with correct File Meta group length, even-length padding, byte-exact sequence passthrough, and lossless File Meta: non-modeled `(0002,xxxx)` elements are preserved and re-emitted in tag order.
|
|
47
|
+
- **Strict TypeScript, dual ESM + CJS, Node ≥ 22**: `noUncheckedIndexedAccess`, no `any`, JSDoc + `@example` on every public export feeding your editor's IntelliSense. Zero runtime dependencies today.
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
@@ -52,18 +52,18 @@ That's the pitch: no config, no schema upload, no spec lookup. The parser accept
|
|
|
52
52
|
|
|
53
53
|
A DICOM Part 10 file is a 128-byte preamble + the `DICM` magic, then a **File Meta** group (always Explicit VR Little Endian) naming the **transfer syntax**, then the **dataset**: a flat, tag-ordered stream of **data elements**.
|
|
54
54
|
|
|
55
|
-
Each element is identified by a `(group,element)` **tag** (e.g. `(0010,0010)` = Patient's Name) and carries a two-letter **VR** (Value Representation
|
|
55
|
+
Each element is identified by a `(group,element)` **tag** (e.g. `(0010,0010)` = Patient's Name) and carries a two-letter **VR** (Value Representation: `PN`, `DA`, `US`, `SQ`, …) that says how to decode its bytes. Some elements are **sequences** (`SQ`): ordered lists of **items**, each a nested dataset. The transfer syntax decides endianness, whether VRs are written explicitly, and whether the stream is deflated.
|
|
56
56
|
|
|
57
57
|
```
|
|
58
58
|
DICOM file
|
|
59
59
|
├── preamble (128 bytes) + "DICM"
|
|
60
|
-
├── File Meta group (0002,xxxx)
|
|
60
|
+
├── File Meta group (0002,xxxx) : transfer syntax UID, SOP Class/Instance UID
|
|
61
61
|
└── dataset
|
|
62
62
|
├── (0008,0060) Modality "CT"
|
|
63
63
|
├── (0010,0010) PatientName PN "Doe^Jane"
|
|
64
64
|
├── (0020,000D) StudyInstanceUID UI "1.2.840.…"
|
|
65
65
|
├── (0028,0100) BitsAllocated US 16
|
|
66
|
-
└── (7FE0,0010) PixelData OW «raw bytes
|
|
66
|
+
└── (7FE0,0010) PixelData OW «raw bytes, not decoded»
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
`@cosyte/dicom` reads all of that leniently and hands you typed accessors over it. The one thing it deliberately does **not** do is decode the pixels.
|
|
@@ -74,11 +74,11 @@ DICOM file
|
|
|
74
74
|
|
|
75
75
|
### Safety-critical views
|
|
76
76
|
|
|
77
|
-
The four views
|
|
77
|
+
The four views (`patient`, `study`, `series`, `image`) pull the right field out of the right tag for the jobs that matter most, and they are **fail-safe**: a missing value is typed-absent (`undefined`), never a substituted default.
|
|
78
78
|
|
|
79
79
|
```ts
|
|
80
80
|
const p = ds.patient;
|
|
81
|
-
p.id; // "MRN-42"
|
|
81
|
+
p.id; // "MRN-42": NOT globally unique on its own…
|
|
82
82
|
p.issuerOfId; // …pair with the issuer for cross-system matching
|
|
83
83
|
p.name?.alphabetic.familyName; // structured PN, never flattened
|
|
84
84
|
|
|
@@ -95,7 +95,7 @@ s.accessionNumber; // ties the study to the HIS order (0008,0050)
|
|
|
95
95
|
ds.get("Modality"); // by keyword
|
|
96
96
|
ds.get("(0008,0060)"); // same element, by tag
|
|
97
97
|
ds.has("PixelData"); // boolean
|
|
98
|
-
ds.elements(); // readonly Element[]
|
|
98
|
+
ds.elements(); // readonly Element[]: walk everything
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
### Typed values
|
|
@@ -120,7 +120,7 @@ Recipes for the jobs a metadata parser is actually asked to do. Every attribute
|
|
|
120
120
|
|
|
121
121
|
### Index a folder of studies
|
|
122
122
|
|
|
123
|
-
Pull a few fields out of each file to build a searchable index
|
|
123
|
+
Pull a few fields out of each file to build a searchable index: the bread-and-butter PACS/archive job.
|
|
124
124
|
|
|
125
125
|
```ts
|
|
126
126
|
import { readFile } from "node:fs/promises";
|
|
@@ -145,18 +145,18 @@ Nothing here throws on a quirky file; absent fields come back `undefined`. Check
|
|
|
145
145
|
|
|
146
146
|
### Build routing keys
|
|
147
147
|
|
|
148
|
-
Routing and reconciliation hang off a small set of identifiers. Surface them correctly
|
|
148
|
+
Routing and reconciliation hang off a small set of identifiers. Surface them correctly: a Patient ID without its issuer is ambiguous across systems (PS3.3 C.7.1.1).
|
|
149
149
|
|
|
150
150
|
```ts
|
|
151
151
|
// Hierarchy keys for filing into Study → Series → Instance:
|
|
152
|
-
const studyKey = ds.study.instanceUid; // (0020,000D)
|
|
152
|
+
const studyKey = ds.study.instanceUid; // (0020,000D): global anchor
|
|
153
153
|
const seriesKey = ds.series.instanceUid; // (0020,000E)
|
|
154
154
|
const instanceKey = ds.image.sopInstanceUid; // (0008,0018)
|
|
155
155
|
|
|
156
|
-
// Cross-system patient key
|
|
156
|
+
// Cross-system patient key: id ALONE is not unique; pair it with the issuer:
|
|
157
157
|
const p = ds.patient;
|
|
158
158
|
const patientKey = `${p.issuerOfId ?? "?"}|${p.id ?? "?"}`;
|
|
159
|
-
p.otherIds; // (0010,1002) Other Patient IDs Sequence
|
|
159
|
+
p.otherIds; // (0010,1002) Other Patient IDs Sequence: additional {id, issuer} pairs
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
### Read pixel-interpretation metadata safely
|
|
@@ -170,12 +170,12 @@ img.rescaleIntercept; // (0028,1052) apply as: stored*slope + intercept
|
|
|
170
170
|
img.signed; // true/false only if (0028,0103) Pixel Representation was present; else undefined
|
|
171
171
|
img.bitsStored; // (0028,0101)
|
|
172
172
|
img.photometricInterpretation; // (0028,0004) never defaulted to MONOCHROME2
|
|
173
|
-
img.pixelSpacing; // (0028,0030) patient-plane mm
|
|
173
|
+
img.pixelSpacing; // (0028,0030) patient-plane mm, distinct from imagerPixelSpacing
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
> **Vendor note.** Philips writes private rescale tags `(2005,1409/140A/140B)` that shadow the standard `(0028,1052/1053)`; using the standard tags alone can yield non-quantitative values. This parser **preserves** the private tags so you can prefer them
|
|
176
|
+
> **Vendor note.** Philips writes private rescale tags `(2005,1409/140A/140B)` that shadow the standard `(0028,1052/1053)`; using the standard tags alone can yield non-quantitative values. This parser **preserves** the private tags so you can prefer them. Reach them with `ds.get("(2005,1409)")` (optionally under `profiles.philips`).
|
|
177
177
|
|
|
178
|
-
For Enhanced multi-frame objects, `image.frame(i)` resolves each frame's functional-group macros Per-Frame-else-Shared (PS3.3 C.7.6.16). It throws a `DicomValueError`
|
|
178
|
+
For Enhanced multi-frame objects, `image.frame(i)` resolves each frame's functional-group macros Per-Frame-else-Shared (PS3.3 C.7.6.16). It throws a `DicomValueError` (carrying only structural facts, never PHI) for an out-of-range frame or a required geometry macro missing from both groups.
|
|
179
179
|
|
|
180
180
|
```ts
|
|
181
181
|
if (img.isEnhancedMultiFrame) {
|
|
@@ -187,15 +187,15 @@ if (img.isEnhancedMultiFrame) {
|
|
|
187
187
|
|
|
188
188
|
### De-identify before sharing
|
|
189
189
|
|
|
190
|
-
`deidentify()` applies the PS3.15 Annex E Basic Application Level Confidentiality Profile
|
|
190
|
+
`deidentify()` applies the PS3.15 Annex E Basic Application Level Confidentiality Profile (replacing, emptying, or removing every attribute the standard lists as identifying) and returns a fresh dataset plus a value-free report.
|
|
191
191
|
|
|
192
192
|
```ts
|
|
193
193
|
import { parseDicom, deidentify, serializeDicom } from "@cosyte/dicom";
|
|
194
194
|
|
|
195
195
|
const { dataset, report } = deidentify(parseDicom(buf));
|
|
196
|
-
const safe = serializeDicom(dataset); // safe to share
|
|
196
|
+
const safe = serializeDicom(dataset); // safe to share: input dataset never mutated
|
|
197
197
|
|
|
198
|
-
report.attributes.length; // count of attributes acted on (each carries tag/keyword/action
|
|
198
|
+
report.attributes.length; // count of attributes acted on (each carries tag/keyword/action, no values)
|
|
199
199
|
report.warnings; // e.g. DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED
|
|
200
200
|
```
|
|
201
201
|
|
|
@@ -210,6 +210,10 @@ deidentify(parseDicom(buf), {
|
|
|
210
210
|
|
|
211
211
|
This is **metadata-level** de-identification. Pixel cleaning is out of scope: when a file carries burned-in annotation this layer cannot remove, you get a `DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED` warning rather than a false sense of safety (pixel cleaning is deferred to `@cosyte/dicom-pixel`).
|
|
212
212
|
|
|
213
|
+
The action table comes from NEMA's PS3.15 2026c DocBook, the normative publication of the standard, rather than from a third-party mirror of it, so the current edition's patient attributes are removed rather than quietly kept. That includes the three rows the standard states as a repeating-group mask rather than a single tag: `(50xx,xxxx)` Curve Data, `(60xx,3000)` Overlay Data and `(60xx,4000)` Overlay Comments are matched in every overlay or curve group the standard defines, removed, and named in the report with the mask that matched them. Overlay comments in particular are a common carrier for text typed onto a study, so a clean report on a file that still held them was worse than no report.
|
|
214
|
+
|
|
215
|
+
The groups a mask covers are the sixteen even ones PS3.5 bounds it to (`6000`-`601E`, `5000`-`501E`), not any four hex digits. Reading `xx` as a wildcard would strip attributes the standard never marked, which is data loss on a call you asked to be conservative. That bound is read out of PS3.5 itself, pinned by SHA-256 the same way the action table is, rather than copied into the source by hand: the current edition states the overlay range, and the curve range comes from the 2004 edition its own note delegates to, with the two required to agree where they overlap.
|
|
216
|
+
|
|
213
217
|
### Bridge to FHIR / HL7 v2
|
|
214
218
|
|
|
215
219
|
A common consulting ask is joining imaging to the rest of the record. The authoritative crosswalk is the FHIR [`ImagingStudy` "Mappings for DICOM"](https://build.fhir.org/imagingstudy-mappings.html) tab. The join keys a metadata parser must surface correctly:
|
|
@@ -230,7 +234,7 @@ const imagingStudy = {
|
|
|
230
234
|
const obr18 = ds.study.accessionNumber;
|
|
231
235
|
```
|
|
232
236
|
|
|
233
|
-
> Series and SOP Instance UIDs are **not** represented in HL7 v2
|
|
237
|
+
> Series and SOP Instance UIDs are **not** represented in HL7 v2. Image-level identity lives only in DICOM.
|
|
234
238
|
|
|
235
239
|
### Round-trip: read, edit, re-serialize
|
|
236
240
|
|
|
@@ -238,10 +242,10 @@ const obr18 = ds.study.accessionNumber;
|
|
|
238
242
|
import { parseDicom, serializeDicom } from "@cosyte/dicom";
|
|
239
243
|
|
|
240
244
|
const ds = parseDicom(buf);
|
|
241
|
-
const out = serializeDicom(ds); // spec-clean Part 10, same transfer syntax
|
|
245
|
+
const out = serializeDicom(ds); // spec-clean Part 10, same transfer syntax, no transcode
|
|
242
246
|
```
|
|
243
247
|
|
|
244
|
-
The serializer is the conservative half of Postel's Law: it rebuilds the File Meta group with a correct `(0002,0000)` length, pads values to even length, and re-emits sequences and encapsulated pixel data byte-for-byte. The File Meta group round-trips losslessly
|
|
248
|
+
The serializer is the conservative half of Postel's Law: it rebuilds the File Meta group with a correct `(0002,0000)` length, pads values to even length, and re-emits sequences and encapsulated pixel data byte-for-byte. The File Meta group round-trips losslessly: non-modeled `(0002,xxxx)` elements (Sending/Receiving AE Title, Private Information, etc.) are preserved on parse and re-emitted in ascending tag order.
|
|
245
249
|
|
|
246
250
|
---
|
|
247
251
|
|
|
@@ -256,13 +260,13 @@ import { parseDicom, profiles } from "@cosyte/dicom";
|
|
|
256
260
|
const ds = parseDicom(buf, { profile: profiles.siemens });
|
|
257
261
|
```
|
|
258
262
|
|
|
259
|
-
A profile bundles three things that only ever **tighten or annotate** a parse
|
|
263
|
+
A profile bundles three things that only ever **tighten or annotate** a parse, never loosen it past the lenient default:
|
|
260
264
|
|
|
261
|
-
- **Private-dictionary overlay
|
|
262
|
-
- **Escalations
|
|
263
|
-
- **Suppressions
|
|
265
|
+
- **Private-dictionary overlay**: resolves the Implicit VR of vendor private data elements by the file's _live_ Private Creator string (e.g. `"SIEMENS CSA HEADER"`), keyed canonically as `"GGGGxxLL"` (PS3.5 §7.8.1), never a hard-coded block number. (This is why Agfa IMPAX re-assigning blocks still resolves.) An unknown creator degrades to `UN` plus a `DICOM_PRIVATE_CREATOR_UNKNOWN` warning.
|
|
266
|
+
- **Escalations**: Tier-2 warning codes promoted to a thrown `DicomParseError` (a stricter posture for known-unsafe deviations).
|
|
267
|
+
- **Suppressions**: benign, high-volume warning codes silenced for a known-quirky source.
|
|
264
268
|
|
|
265
|
-
Five built-ins ship under the `profiles` namespace: `ge`, `siemens`, `philips` (vendor overlays, grounded in the public GDCM / dcm4che / dcm2niix dictionaries) and `strict` / `lenient` (posture presets). Build your own with `defineProfile()
|
|
269
|
+
Five built-ins ship under the `profiles` namespace: `ge`, `siemens`, `philips` (vendor overlays, grounded in the public GDCM / dcm4che / dcm2niix dictionaries) and `strict` / `lenient` (posture presets). Build your own with `defineProfile()`. It validates input, composes via `extends`, and returns a frozen profile:
|
|
266
270
|
|
|
267
271
|
```ts
|
|
268
272
|
import { defineProfile, profiles } from "@cosyte/dicom";
|
|
@@ -284,7 +288,7 @@ At an RSNA-era interoperability test, ~80% of real-world patient CDs failed stri
|
|
|
284
288
|
|
|
285
289
|
| Tier | Behavior | When | Example codes |
|
|
286
290
|
| ---- | -------------- | ------------------------------ | ------------------------ |
|
|
287
|
-
| 0 | Silent | Spec-compliant input |
|
|
291
|
+
| 0 | Silent | Spec-compliant input | none |
|
|
288
292
|
| 1 | Auto-handled | Trivial deviation, no warning | trailing-space tidy |
|
|
289
293
|
| 2 | Warning | Recoverable deviation | `DICOM_MISSING_PREAMBLE` |
|
|
290
294
|
| 3 | Fatal (always) | Unrecoverable structural error | `NOT_DICOM_PART_10` |
|
|
@@ -304,7 +308,7 @@ for (const w of ds.warnings) {
|
|
|
304
308
|
|
|
305
309
|
The 24 Tier-2 codes (`DICOM_MISSING_PREAMBLE`, `DICOM_FILE_META_GROUP_LENGTH_MISMATCH`, `DICOM_UN_PARSED_AS_SQ`, `DICOM_ODD_LENGTH_VALUE_PADDED`, `DICOM_PRIVATE_CREATOR_UNKNOWN`, `DICOM_VR_MISMATCH`, `DICOM_DA_LEGACY_FORMAT`, … ) live in [`src/parser/warnings.ts`](./src/parser/warnings.ts). Narrow on `w.code === WARNING_CODES.…` for typo-free comparisons, or pass `{ onWarning }` to `parseDicom` to stream them.
|
|
306
310
|
|
|
307
|
-
The 4 Tier-3 fatal codes
|
|
311
|
+
The 4 Tier-3 fatal codes (`NOT_DICOM_PART_10`, `INVALID_FILE_META`, `UNSUPPORTED_TRANSFER_SYNTAX`, `EMPTY_INPUT`) always throw a `DicomParseError`; they represent input the parser cannot meaningfully recover.
|
|
308
312
|
|
|
309
313
|
---
|
|
310
314
|
|
|
@@ -330,7 +334,7 @@ try {
|
|
|
330
334
|
|
|
331
335
|
### `DicomValueError`
|
|
332
336
|
|
|
333
|
-
Thrown only by `image.frame(i)
|
|
337
|
+
Thrown only by `image.frame(i)`: `FRAME_INDEX_OUT_OF_RANGE` for an index outside `[0, numberOfFrames)`, or `MISSING_REQUIRED_FUNCTIONAL_GROUP` when an enhanced object lacks a required geometry macro in both the Per-Frame and Shared groups. Value decode (`element.value`) never throws. It warns and returns `null`/typed-absent instead.
|
|
334
338
|
|
|
335
339
|
### `DicomSerializeError`
|
|
336
340
|
|
|
@@ -338,7 +342,7 @@ Thrown by `serializeDicom` for `MISSING_TRANSFER_SYNTAX` (the dataset names no t
|
|
|
338
342
|
|
|
339
343
|
### `ProfileDefinitionError` · `DeidentifyError`
|
|
340
344
|
|
|
341
|
-
`defineProfile()` throws `ProfileDefinitionError` for a structurally invalid profile; `deidentify()` throws `DeidentifyError` (`INVALID_OPTIONS`) for an unknown Retain option or malformed UID root. Both messages carry only structural facts
|
|
345
|
+
`defineProfile()` throws `ProfileDefinitionError` for a structurally invalid profile; `deidentify()` throws `DeidentifyError` (`INVALID_OPTIONS`) for an unknown Retain option or malformed UID root. Both messages carry only structural facts (option names, the UID root), never a decoded value.
|
|
342
346
|
|
|
343
347
|
---
|
|
344
348
|
|
|
@@ -360,18 +364,23 @@ Supported transfer syntaxes (structure for all; **pixels never decoded**): Impli
|
|
|
360
364
|
|
|
361
365
|
v1 is metadata-feature-complete. Future companion packages (separate repos, demand-sequenced):
|
|
362
366
|
|
|
363
|
-
- **`@cosyte/dicom-pixel
|
|
364
|
-
- **`@cosyte/dicom-net
|
|
365
|
-
- **`@cosyte/dicomweb
|
|
367
|
+
- **`@cosyte/dicom-pixel`**: pixel decode/decompression, frame extraction, burned-in-annotation cleaning.
|
|
368
|
+
- **`@cosyte/dicom-net`**: DIMSE network services.
|
|
369
|
+
- **`@cosyte/dicomweb`**: QIDO / WADO / STOW REST clients.
|
|
366
370
|
|
|
367
371
|
---
|
|
368
372
|
|
|
369
373
|
## Contributing
|
|
370
374
|
|
|
371
|
-
Vendor-quirk fixtures (synthetic or properly de-identified), profile improvements, and dictionary corrections are all welcome
|
|
375
|
+
Vendor-quirk fixtures (synthetic or properly de-identified), profile improvements, and dictionary corrections are all welcome. The more real-world edge cases the test suite covers, the more robust the parser gets. See [CONTRIBUTING.md](./CONTRIBUTING.md) if present, or open an issue.
|
|
372
376
|
|
|
373
377
|
---
|
|
374
378
|
|
|
379
|
+
## Trademarks
|
|
380
|
+
|
|
381
|
+
GE, Siemens, and Philips are trademarks of their respective owners. cosyte is not affiliated with, endorsed by, or
|
|
382
|
+
sponsored by any of them. The names identify the vendors whose private dictionaries the built-in profiles resolve. See [TRADEMARKS.md](./TRADEMARKS.md).
|
|
383
|
+
|
|
375
384
|
## License
|
|
376
385
|
|
|
377
386
|
[MIT](./LICENSE) © Cosyte
|
package/TRADEMARKS.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Trademarks
|
|
2
|
+
|
|
3
|
+
`@cosyte/dicom` is an independent open-source project. cosyte is **not affiliated with, endorsed by,
|
|
4
|
+
or sponsored by** any company named in this repository or its documentation.
|
|
5
|
+
|
|
6
|
+
## Why these names appear
|
|
7
|
+
|
|
8
|
+
DICOM private data elements carry no on-wire VR under Implicit VR Little Endian, so resolving them
|
|
9
|
+
requires knowing whose private blocks they belong to. Naming the vendor is the only way to
|
|
10
|
+
describe (or correctly decode) its private dictionary.
|
|
11
|
+
|
|
12
|
+
Every reference is **descriptive**: it identifies whose private dictionary a profile resolves, and nothing more. Naming a system is the only way to say
|
|
13
|
+
whether a library works with it.
|
|
14
|
+
|
|
15
|
+
## Where the profiles come from
|
|
16
|
+
|
|
17
|
+
The built-in source profiles are authored through this package's own public `defineProfile()` API,
|
|
18
|
+
from public open-source dictionaries (the GDCM private dictionary and dcm2niix tag notes) and
|
|
19
|
+
from vendor-published schema identifiers. Each profile documents its sources inline. They embed
|
|
20
|
+
no privileged, confidential, or reverse-engineered material, and no patient data.
|
|
21
|
+
|
|
22
|
+
## Names referenced
|
|
23
|
+
|
|
24
|
+
| Name | Where it appears |
|
|
25
|
+
| ------- | ----------------------------------------------------------------------------- |
|
|
26
|
+
| GE | `profiles.ge`: a built-in profile name, resolving the `GEMS_*` private blocks |
|
|
27
|
+
| Siemens | `profiles.siemens`: a built-in profile name |
|
|
28
|
+
| Philips | `profiles.philips`: a built-in profile name |
|
|
29
|
+
|
|
30
|
+
All product names, logos, and brands are the property of their respective owners. Use of a name here
|
|
31
|
+
does not imply any affiliation with, or endorsement by, its owner. If you own one of these marks and
|
|
32
|
+
would like a reference changed, please open an issue.
|