dcmjs 0.49.3 → 0.50.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 (55) hide show
  1. package/README.md +50 -0
  2. package/build/dcmjs.es.js +1071 -112
  3. package/build/dcmjs.es.js.map +1 -1
  4. package/build/dcmjs.js +1071 -112
  5. package/build/dcmjs.js.map +1 -1
  6. package/build/dcmjs.min.js +2 -2
  7. package/build/dcmjs.min.js.map +1 -1
  8. package/generate/dictionary.mjs +56029 -0
  9. package/package.json +18 -2
  10. package/.babelrc +0 -9
  11. package/.github/workflows/lint-and-format.yml +0 -27
  12. package/.github/workflows/publish-package.yml +0 -45
  13. package/.github/workflows/tests.yml +0 -24
  14. package/.prettierrc +0 -5
  15. package/.vscode/extensions.json +0 -7
  16. package/.vscode/settings.json +0 -8
  17. package/changelog.md +0 -31
  18. package/docs/ArrayBufferExpanderListener.md +0 -303
  19. package/docs/AsyncDicomReader-skill.md +0 -730
  20. package/eslint.config.mjs +0 -30
  21. package/generate-dictionary.js +0 -145
  22. package/jest.setup.js +0 -39
  23. package/netlify.toml +0 -22
  24. package/rollup.config.mjs +0 -57
  25. package/test/ArrayBufferExpanderListener.test.js +0 -365
  26. package/test/DICOMWEB.test.js +0 -1
  27. package/test/DicomMetaDictionary.test.js +0 -73
  28. package/test/SequenceOfItems.test.js +0 -86
  29. package/test/adapters.test.js +0 -43
  30. package/test/anonymizer.test.js +0 -176
  31. package/test/arrayItem.json +0 -351
  32. package/test/async-data.test.js +0 -575
  33. package/test/data-encoding.test.js +0 -59
  34. package/test/data-options.test.js +0 -199
  35. package/test/data.test.js +0 -1776
  36. package/test/derivations.test.js +0 -1
  37. package/test/helper/DicomDataReadBufferStreamBuilder.js +0 -89
  38. package/test/information-filter.test.js +0 -165
  39. package/test/integration/DicomMessage.readFile.test.js +0 -50
  40. package/test/lossless-read-write.test.js +0 -1407
  41. package/test/mocks/minimal_fields_dataset.json +0 -17
  42. package/test/mocks/null_number_vrs_dataset.json +0 -102
  43. package/test/normalizers.test.js +0 -38
  44. package/test/odd-frame-bit-data.js +0 -138
  45. package/test/rawTags.js +0 -170
  46. package/test/readBufferStream.test.js +0 -158
  47. package/test/sample-dicom.json +0 -904
  48. package/test/sample-op.lei +0 -0
  49. package/test/sample-sr.json +0 -997
  50. package/test/sr-tid.test.js +0 -251
  51. package/test/testUtils.js +0 -85
  52. package/test/utilities/deepEqual.test.js +0 -87
  53. package/test/utilities.test.js +0 -205
  54. package/test/video-test-dict.js +0 -40
  55. package/test/writeBufferStream.test.js +0 -149
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "dcmjs",
3
- "version": "0.49.3",
3
+ "version": "0.50.0",
4
4
  "description": "Javascript implementation of DICOM manipulation",
5
5
  "main": "build/dcmjs.js",
6
6
  "module": "build/dcmjs.es.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./build/dcmjs.es.js",
10
+ "require": "./build/dcmjs.js"
11
+ },
12
+ "./dictionary": "./generate/dictionary.mjs"
13
+ },
14
+ "files": [
15
+ "build",
16
+ "generate/dictionary.mjs",
17
+ "README.md",
18
+ "License.txt"
19
+ ],
7
20
  "directories": {
8
21
  "example": "examples"
9
22
  },
@@ -15,7 +28,10 @@
15
28
  "dev": "rollup -c -w",
16
29
  "format": "prettier --write 'src/**/*.js' 'test/**/*.js'",
17
30
  "format:check": "prettier --check 'src/**/*.js' 'test/**/*.js'",
18
- "lint": "eslint --fix 'src/**/*.js' 'test/**/*.js'"
31
+ "lint": "eslint --fix 'src/**/*.js' 'test/**/*.js'",
32
+ "generate-dictionary": "node generate/generate-dictionary.js",
33
+ "pack-dictionary": "node generate/pack_dicom.mjs",
34
+ "bench:dictionary": "bun run generate/bench-dictionary-load.mjs"
19
35
  },
20
36
  "repository": {
21
37
  "type": "git",
package/.babelrc DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "presets": [
3
- ["@babel/preset-env", {
4
- "targets": {
5
- "browsers": ["ie 11"]
6
- }
7
- }]
8
- ]
9
- }
@@ -1,27 +0,0 @@
1
- name: Run lint and format check
2
-
3
- on:
4
- - push
5
- - pull_request
6
-
7
- jobs:
8
- lint-and-format:
9
- runs-on: ubuntu-latest
10
-
11
- steps:
12
- - name: Checkout repository
13
- uses: actions/checkout@v4
14
-
15
- - name: Setup Node.js
16
- uses: actions/setup-node@v4
17
- with:
18
- node-version: 18
19
-
20
- - name: Install packages
21
- run: yarn install --frozen-lockfile
22
-
23
- - name: Run ESLint
24
- run: yarn lint
25
-
26
- - name: Check Prettier formatting
27
- run: yarn format:check
@@ -1,45 +0,0 @@
1
- name: Publish package to NPM
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
-
8
- permissions:
9
- contents: write
10
- issues: write
11
- pull-requests: write
12
- id-token: write
13
-
14
- jobs:
15
- publish-package:
16
- runs-on: ubuntu-latest
17
- environment: publish
18
-
19
- steps:
20
- - name: Checkout repository
21
- uses: actions/checkout@v4
22
- with:
23
- persist-credentials: false
24
-
25
- - name: Setup Node.js
26
- uses: actions/setup-node@v4
27
- with:
28
- node-version: 24
29
- registry-url: 'https://registry.npmjs.org'
30
-
31
- - name: Install packages
32
- uses: bahmutov/npm-install@v1
33
-
34
- - name: Run tests
35
- run: npm run test
36
-
37
- - name: Run build
38
- run: npm run build
39
-
40
- - name: Semantic release
41
- uses: cycjimmy/semantic-release-action@v6
42
- env:
43
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
45
- NPM_CONFIG_PROVENANCE: true
@@ -1,24 +0,0 @@
1
- name: Run tests
2
-
3
- on:
4
- - push
5
- - pull_request
6
-
7
- jobs:
8
- test:
9
- runs-on: ubuntu-latest
10
-
11
- steps:
12
- - name: Checkout repository
13
- uses: actions/checkout@v4
14
-
15
- - name: Setup Node.js
16
- uses: actions/setup-node@v4
17
- with:
18
- node-version: 18
19
-
20
- - name: Install packages
21
- run: yarn install --frozen-lockfile
22
-
23
- - name: Run tests
24
- run: yarn test
package/.prettierrc DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "tabWidth": 4,
3
- "trailingComma": "none",
4
- "arrowParens": "avoid"
5
- }
@@ -1,7 +0,0 @@
1
- {
2
- "recommendations": [
3
- "dbaeumer.vscode-eslint",
4
- "firsttris.vscode-jest-runner",
5
- "smikitky.vscode-dicom"
6
- ]
7
- }
@@ -1,8 +0,0 @@
1
- {
2
-
3
- "editor.defaultFormatter": "esbenp.prettier-vscode",
4
- "[javascript]": {
5
- "editor.tabSize": 4,
6
- "editor.formatOnSave": true
7
- },
8
- }
package/changelog.md DELETED
@@ -1,31 +0,0 @@
1
- # Changelog
2
- All notable changes to this project will be documented in this file.
3
-
4
- The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
- and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
-
7
- ## 2026-01-19
8
- Added multiple measurements for a single annotation in an SR object
9
-
10
- ## [0.2.1] - 2018-10-17
11
- ### Added
12
- - Added Adapters and Utilities to support translation between common imaging toolkits (Cornerstone, VTK.js) and DICOM Structured Reports. Utilities are tied to the DICOM Standard and help build compliant files. Adapters are specific to the toolkits in question and help make it easier for developers to use the Utilities.
13
-
14
- Note: These are generally still a work in progress. We are currently only confident in the Cornerstone Length adapter, and the Utilities (TID1500, TID1501, TID300, Length) which back it.
15
-
16
- ## [0.2.0] - 2018-10-02
17
- ### Added
18
- - Example using [VTK.js with DICOM Segmentation](https://dcmjs-org.github.io/dcmjs/examples/vtkDisplay/index.html)
19
-
20
- ### Changed
21
- - BitArray class provides static methods
22
- to pack and unpack bit and bytes to support
23
- dicom SEG encoding.
24
-
25
- ## [0.1.5] - 2018-08-23
26
- ### Fixed
27
- - Fixed dcmjs compatibility with IE11
28
-
29
- ## [0.1.4] - 2018-08-23
30
- ### Added
31
- - Added Webpack and babel to replace Rollup
@@ -1,303 +0,0 @@
1
- # ArrayBufferExpanderFilter
2
-
3
- ## Overview
4
-
5
- The `ArrayBufferExpanderFilter` is a filter for `DicomMetadataListener` that converts `ArrayBuffer[]` child values into expanded `listener.startObject([])` and `value(fragment)` calls. This is particularly useful when working with compressed or fragmented pixel data that may be delivered as an array of ArrayBuffers.
6
-
7
- ## Purpose
8
-
9
- When the `AsyncDicomReader` reads compressed pixel data, it can deliver frame data in two formats:
10
-
11
- 1. **Compact format**: A single `ArrayBuffer[]` value passed to `listener.value()`
12
- 2. **Expanded format**: A sequence of calls:
13
- - `listener.startObject([])`
14
- - Multiple `listener.value(fragment)` calls (one per ArrayBuffer)
15
- - `listener.pop()`
16
-
17
- The `ArrayBufferExpanderFilter` automatically converts format #1 into format #2, ensuring consistent handling regardless of how the reader delivers the data.
18
-
19
- **Important**:
20
- - Each frame is ALWAYS delivered as an array (ArrayBuffer[]), even for single frames
21
- - Video transfer syntaxes are handled as though they were a single frame
22
- - Binary data can be delivered fragmented - a single frame may be split across multiple ArrayBuffer fragments
23
- - Multiple fragments are combined into one array per frame
24
- - When the `AsyncDicomReader` delivers `ArrayBuffer[]` directly to `value()`, it does NOT call `startObject([])`/`pop()` around them. The expander filter makes those calls to properly represent the array structure, then captures the result and assigns it to the tag's `Value` field.
25
-
26
- ## Usage
27
-
28
- ### Basic Usage
29
-
30
- ```javascript
31
- import { AsyncDicomReader } from 'dcmjs';
32
- import { DicomMetadataListener, ArrayBufferExpanderFilter } from 'dcmjs/utilities';
33
-
34
- async function readDicomWithExpansion(arrayBuffer) {
35
- const reader = new AsyncDicomReader();
36
- reader.stream.setData(arrayBuffer);
37
-
38
- // Create listener with the expander filter
39
- const listener = new DicomMetadataListener(ArrayBufferExpanderFilter);
40
-
41
- // Read the file using the listener
42
- await reader.readFile({ listener });
43
-
44
- // Access the result
45
- return listener.dict;
46
- }
47
- ```
48
-
49
- ### Combining with Custom Filters
50
-
51
- You can combine the expander with custom filters to process fragments individually:
52
-
53
- ```javascript
54
- // Create a custom filter to track fragments
55
- const fragmentTrackingFilter = {
56
- fragmentCount: 0,
57
- fragmentSizes: [],
58
-
59
- value(next, val) {
60
- if (val instanceof ArrayBuffer || ArrayBuffer.isView(val)) {
61
- this.fragmentCount++;
62
- this.fragmentSizes.push(val.byteLength);
63
- console.log(`Processing fragment ${this.fragmentCount}, size: ${val.byteLength}`);
64
- }
65
- return next(val);
66
- }
67
- };
68
-
69
- async function readWithFragmentTracking(arrayBuffer) {
70
- const reader = new AsyncDicomReader();
71
- reader.stream.setData(arrayBuffer);
72
-
73
- // Create listener with both filters
74
- // ArrayBufferExpanderFilter runs first to expand, then fragmentTrackingFilter sees individual fragments
75
- const listener = new DicomMetadataListener(
76
- ArrayBufferExpanderFilter,
77
- fragmentTrackingFilter
78
- );
79
-
80
- await reader.readFile({ listener });
81
-
82
- console.log(`Total fragments: ${listener.filters[1].fragmentCount}`);
83
- console.log(`Fragment sizes:`, listener.filters[1].fragmentSizes);
84
-
85
- return listener.dict;
86
- }
87
- ```
88
-
89
- ### Processing Each Frame Fragment
90
-
91
- This is useful for streaming or progressive decoding:
92
-
93
- ```javascript
94
- // Create a streaming filter to process fragments as they arrive
95
- function createStreamingFilter(onFrameFragment) {
96
- let inPixelData = false;
97
- let currentFrameFragments = [];
98
-
99
- return {
100
- addTag(next, tag, tagInfo) {
101
- // Track when we're processing pixel data
102
- inPixelData = tag === '7FE00010'; // Pixel Data tag
103
- return next(tag, tagInfo);
104
- },
105
-
106
- startObject(next, dest) {
107
- if (inPixelData && Array.isArray(dest)) {
108
- // Starting a new frame with multiple fragments
109
- currentFrameFragments = [];
110
- }
111
- return next(dest);
112
- },
113
-
114
- value(next, val) {
115
- if (inPixelData && (val instanceof ArrayBuffer || ArrayBuffer.isView(val))) {
116
- // Process each fragment as it arrives
117
- onFrameFragment(val);
118
- currentFrameFragments.push(val);
119
- }
120
- return next(val);
121
- },
122
-
123
- pop(next) {
124
- if (inPixelData && currentFrameFragments.length > 0) {
125
- // Finished processing all fragments for this frame
126
- console.log(`Completed frame with ${currentFrameFragments.length} fragments`);
127
- currentFrameFragments = [];
128
- }
129
- return next();
130
- }
131
- };
132
- }
133
-
134
- async function streamFrameFragments(arrayBuffer) {
135
- const reader = new AsyncDicomReader();
136
- reader.stream.setData(arrayBuffer);
137
-
138
- // Create streaming filter
139
- const streamingFilter = createStreamingFilter((fragment) => {
140
- console.log(`Received fragment of ${fragment.byteLength} bytes`);
141
- // Process fragment immediately (e.g., send to decoder)
142
- });
143
-
144
- // Combine expander with streaming filter
145
- const listener = new DicomMetadataListener(
146
- ArrayBufferExpanderFilter,
147
- streamingFilter
148
- );
149
-
150
- await reader.readFile({ listener });
151
-
152
- return listener.dict;
153
- }
154
- ```
155
-
156
- ## How It Works
157
-
158
- The `ArrayBufferExpanderFilter` is a filter object that integrates with `DicomMetadataListener`'s filter chain. It provides a `value()` filter method that:
159
-
160
- 1. When `value(next, v)` is called, it checks if the value is an array of ArrayBuffers
161
- 2. If yes:
162
- - Saves the current tag context (via `this.current`)
163
- - Calls `this.startObject([])` to create a new array context
164
- - Calls `this.value(fragment)` for each ArrayBuffer in the array
165
- - Calls `this.pop()` to get the resulting array structure
166
- - Assigns that array to the tag's `Value` field
167
- 3. If no, passes the value through to the next filter by calling `next(v)`
168
-
169
- When used as a filter in `DicomMetadataListener`, `this` refers to the listener instance, giving the filter access to all listener methods and properties.
170
-
171
- This ensures that any subsequent filters or processing logic always receives individual fragments through the proper `startObject([])/value/pop` sequence, regardless of how the `AsyncDicomReader` delivers them.
172
-
173
- The resulting data structure will have a `Value` array containing the individual fragments. Note that each frame is always delivered as an array, and binary data can be fragmented across multiple ArrayBuffer chunks.
174
-
175
- ## Detection Logic
176
-
177
- The listener considers a value to be an `ArrayBuffer[]` if:
178
- - It is an `Array`
179
- - All elements are either `ArrayBuffer` instances or typed array views (like `Uint8Array`)
180
-
181
- This is checked using the `_isArrayBufferArray()` helper method:
182
-
183
- ```javascript
184
- _isArrayBufferArray(arr) {
185
- return arr.every(
186
- item => item instanceof ArrayBuffer || ArrayBuffer.isView(item)
187
- );
188
- }
189
- ```
190
-
191
- ## Use Cases
192
-
193
- ### 1. Fragmented Compressed Frames
194
- Some compressed formats split single frames into multiple fragments:
195
-
196
- ```javascript
197
- // Without expander: might receive ArrayBuffer[]
198
- // With expander: always receives individual ArrayBuffer calls
199
- ```
200
-
201
- ### 2. Multiframe Images with Offset Table
202
- When reading multiframe compressed images with an offset table, child elements may be delivered as arrays.
203
-
204
- ### 3. Video Sequences
205
- Video frames stored as arrays of fragments can be processed consistently.
206
-
207
- ### 4. Bulkdata Streaming
208
- When implementing custom bulkdata storage, individual fragments can be written to separate files or streams.
209
-
210
- ## API Reference
211
-
212
- ### Filter Object
213
-
214
- `ArrayBufferExpanderFilter` is a plain JavaScript object (not a class) that can be passed to the `DicomMetadataListener` constructor.
215
-
216
- ```javascript
217
- const listener = new DicomMetadataListener(ArrayBufferExpanderFilter);
218
- ```
219
-
220
- ### Filter Method
221
-
222
- The filter provides one method:
223
-
224
- #### `value(next, v)`
225
-
226
- Filter method that intercepts value calls.
227
-
228
- **Parameters:**
229
- - `next` (Function): The next function in the filter chain to call for pass-through
230
- - `v` (any): The value being set
231
-
232
- **Behavior:**
233
- - If `v` is an `ArrayBuffer[]`, expands it into `startObject([])/value/pop` sequence
234
- - Otherwise, calls `next(v)` to pass through to the next filter
235
-
236
- **Context:**
237
- When called, `this` refers to the `DicomMetadataListener` instance, providing access to:
238
- - `this.current` - Current parsing state
239
- - `this.startObject([])` - Start array method (arrays are created via `startObject([])`)
240
- - `this.value()` - Value method
241
- - `this.pop()` - Pop method
242
- - `this.fmi` - File Meta Information
243
- - `this.dict` - Dataset dictionary
244
- - All other listener methods and properties
245
-
246
- ## Combining with Other Patterns
247
-
248
- ### With Multiple Filters
249
-
250
- The `ArrayBufferExpanderFilter` can be easily combined with other filters in the `DicomMetadataListener`:
251
-
252
- ```javascript
253
- const loggingFilter = {
254
- value(next, v) {
255
- console.log('Processing value:', v);
256
- return next(v);
257
- }
258
- };
259
-
260
- // Filters are applied in order: expander runs first, then logging
261
- const listener = new DicomMetadataListener(
262
- ArrayBufferExpanderFilter,
263
- loggingFilter
264
- );
265
- ```
266
-
267
- ### Filter Ordering
268
-
269
- The order of filters matters. Filters are executed in the order they are passed to the constructor:
270
-
271
- ```javascript
272
- // ArrayBufferExpanderFilter runs first to expand arrays,
273
- // then customFilter sees the individual fragments
274
- const listener = new DicomMetadataListener(
275
- ArrayBufferExpanderFilter,
276
- customFilter
277
- );
278
-
279
- // If you want customFilter to see the original ArrayBuffer[],
280
- // put it before the expander
281
- const listener2 = new DicomMetadataListener(
282
- customFilter,
283
- ArrayBufferExpanderFilter
284
- );
285
- ```
286
-
287
- ## Performance Considerations
288
-
289
- - **Minimal Overhead**: The expander only adds overhead when checking array types
290
- - **Memory Efficient**: No additional memory allocation; just changes the calling sequence
291
- - **Streaming Friendly**: Allows downstream listeners to process fragments immediately
292
-
293
- ## Limitations
294
-
295
- 1. **Only Expands ArrayBuffer Arrays**: Other array types (e.g., `[string, string]`) are not expanded
296
- 2. **One Level Deep**: Does not recursively expand nested structures
297
- 3. **Requires Complete Array**: Cannot partially expand; receives entire array at once
298
-
299
- ## See Also
300
-
301
- - [AsyncDicomReader Skill Guide](./AsyncDicomReader-skill.md)
302
- - [DicomMetadataListener](../src/utilities/DicomMetadataListener.js)
303
- - DICOM Standard PS3.5 (Encapsulated Format)