dicom-seg-js 0.1.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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * dicom-seg-js — DICOM Segmentation (SEG) reading, built on rt-geometry-js.
3
+ *
4
+ * 0.1.0: BINARY masks and FRACTIONAL probability/occupancy fields. LABELMAP (PS3.3
5
+ * Sup 243) is planned for 0.2.0. Writing arrives in a later PR.
6
+ *
7
+ * FRACTIONAL values are per-voxel model confidence, not accuracy — see
8
+ * `docs/FRACTIONAL-SEG.md`. This library exposes honest quantities only
9
+ * (`meanValue` / `volumeAboveThreshold` / `thresholdSensitivity` from rt-geometry-js) and
10
+ * never an "accuracy" or "% correct" number.
11
+ *
12
+ * The whole rt-geometry-js surface (GridGeometry, Mask3D, ScalarField3D, resampling,
13
+ * histogram/metrics, geometry errors) is re-exported so a caller can go straight from a
14
+ * segment to a resample or a histogram from one import.
15
+ */
16
+ import { type Diagnostic, type GridGeometry, type InterpMethod, type Mask3D, type ScalarField3D, type Vec3 } from "rt-geometry-js";
17
+ import type { FractionalType, SegmentInfo, SegmentationType, SegmentsOverlap } from "./types.js";
18
+ export * from "rt-geometry-js";
19
+ export * from "./types.js";
20
+ export * from "./errors.js";
21
+ export { writeSeg } from "./dicom/port.js";
22
+ export type { WriteSegOptions, WriteSegSegment } from "./dicom/port.js";
23
+ /**
24
+ * A parsed DICOM Segmentation and the per-segment masks / fields over it.
25
+ *
26
+ * BINARY → `mask(n)`; FRACTIONAL → `field(n)` (rescaled to 0..1) and `rawField(n)` (the
27
+ * stored integers). The two are not interchangeable — calling the wrong one throws
28
+ * {@link SegmentationTypeMismatchError} rather than guessing a threshold (roadmap §7.1).
29
+ */
30
+ export declare class Segmentation {
31
+ readonly type: SegmentationType;
32
+ /** FRACTIONAL only — PROBABILITY vs OCCUPANCY, or undefined if the SEG didn't declare it. */
33
+ readonly fractionalType: FractionalType | undefined;
34
+ /** FRACTIONAL only — the stored value that means 1.0 (usually 255). */
35
+ readonly maximumFractionalValue: number | undefined;
36
+ readonly segmentsOverlap: SegmentsOverlap;
37
+ /** The SEG's own sampling grid, built from the Per-Frame / Shared Functional Groups.
38
+ * Not required to match any source image series — resample to cross grids. */
39
+ readonly geometry: GridGeometry;
40
+ readonly frameOfReferenceUID: string | undefined;
41
+ readonly contentLabel: string | undefined;
42
+ readonly diagnostics: readonly Diagnostic[];
43
+ private readonly parsed;
44
+ private readonly maskCache;
45
+ private readonly fieldCache;
46
+ private readonly rawFieldCache;
47
+ private constructor();
48
+ static fromDicom(bytes: ArrayBuffer): Segmentation;
49
+ /** Every segment, in `SegmentSequence` order. */
50
+ segments(): readonly SegmentInfo[];
51
+ hasSegment(segmentNumber: number): boolean;
52
+ private assertSegment;
53
+ private get sliceSize();
54
+ /**
55
+ * The boolean mask for a **BINARY** segment. Throws {@link SegmentationTypeMismatchError}
56
+ * on a FRACTIONAL SEG — threshold `field(n)` yourself, there is no safe default cut.
57
+ */
58
+ mask(segmentNumber: number): Mask3D;
59
+ /**
60
+ * The confidence/occupancy field for a **FRACTIONAL** segment, rescaled to `[0, 1]` by
61
+ * `MaximumFractionalValue`. Throws {@link SegmentationTypeMismatchError} on a BINARY SEG
62
+ * — use `mask(n)`.
63
+ */
64
+ field(segmentNumber: number): ScalarField3D;
65
+ /** The raw stored integers (0..`maximumFractionalValue`) for a FRACTIONAL segment, unscaled. */
66
+ rawField(segmentNumber: number): ScalarField3D;
67
+ private fractionalField;
68
+ /**
69
+ * The footprint of a segment as a `Mask3D`: for BINARY, the mask itself; for FRACTIONAL,
70
+ * the voxels with a non-zero stored value. Handy as the `mask` argument to
71
+ * `meanValue` / `volumeAboveThreshold` / `thresholdSensitivity` — "confidence over the
72
+ * region the model marked at all".
73
+ */
74
+ support(segmentNumber: number): Mask3D;
75
+ /**
76
+ * Interpolated confidence at a physical point for a FRACTIONAL segment (0 outside the
77
+ * grid). The confidence-under-cursor tooltip from §7.3 — the same call as
78
+ * `dose.sample()` against a different field. Trilinear by default.
79
+ */
80
+ sampleConfidence(segmentNumber: number, point: Vec3, opts?: {
81
+ method?: InterpMethod;
82
+ }): number;
83
+ }
84
+ /** Parse one DICOM SEG object's bytes. Throws `NotSegmentationError` /
85
+ * `MalformedSegmentationError` / `UnsupportedSegmentationTypeError`. */
86
+ export declare function readSeg(bytes: ArrayBuffer): Segmentation;
87
+ export type { ParsedSeg } from "./dicom/port.js";
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * dicom-seg-js — DICOM Segmentation (SEG) reading, built on rt-geometry-js.
3
+ *
4
+ * 0.1.0: BINARY masks and FRACTIONAL probability/occupancy fields. LABELMAP (PS3.3
5
+ * Sup 243) is planned for 0.2.0. Writing arrives in a later PR.
6
+ *
7
+ * FRACTIONAL values are per-voxel model confidence, not accuracy — see
8
+ * `docs/FRACTIONAL-SEG.md`. This library exposes honest quantities only
9
+ * (`meanValue` / `volumeAboveThreshold` / `thresholdSensitivity` from rt-geometry-js) and
10
+ * never an "accuracy" or "% correct" number.
11
+ *
12
+ * The whole rt-geometry-js surface (GridGeometry, Mask3D, ScalarField3D, resampling,
13
+ * histogram/metrics, geometry errors) is re-exported so a caller can go straight from a
14
+ * segment to a resample or a histogram from one import.
15
+ */
16
+ import { createScalarField, maskFromDense, sampleFieldAt, } from "rt-geometry-js";
17
+ import { binaryFrame, fractionalFrame, readSegDataset } from "./dicom/port.js";
18
+ import { SegmentationTypeMismatchError } from "./errors.js";
19
+ export * from "rt-geometry-js";
20
+ export * from "./types.js";
21
+ export * from "./errors.js";
22
+ // Write a conformant SEG from a Mask3D per BINARY segment / a ScalarField3D per FRACTIONAL
23
+ // segment. The low-level frame encoder (encodeSegFrames) stays internal.
24
+ export { writeSeg } from "./dicom/port.js";
25
+ /**
26
+ * A parsed DICOM Segmentation and the per-segment masks / fields over it.
27
+ *
28
+ * BINARY → `mask(n)`; FRACTIONAL → `field(n)` (rescaled to 0..1) and `rawField(n)` (the
29
+ * stored integers). The two are not interchangeable — calling the wrong one throws
30
+ * {@link SegmentationTypeMismatchError} rather than guessing a threshold (roadmap §7.1).
31
+ */
32
+ export class Segmentation {
33
+ type;
34
+ /** FRACTIONAL only — PROBABILITY vs OCCUPANCY, or undefined if the SEG didn't declare it. */
35
+ fractionalType;
36
+ /** FRACTIONAL only — the stored value that means 1.0 (usually 255). */
37
+ maximumFractionalValue;
38
+ segmentsOverlap;
39
+ /** The SEG's own sampling grid, built from the Per-Frame / Shared Functional Groups.
40
+ * Not required to match any source image series — resample to cross grids. */
41
+ geometry;
42
+ frameOfReferenceUID;
43
+ contentLabel;
44
+ diagnostics;
45
+ parsed;
46
+ maskCache = new Map();
47
+ fieldCache = new Map();
48
+ rawFieldCache = new Map();
49
+ constructor(parsed) {
50
+ this.parsed = parsed;
51
+ this.type = parsed.segmentationType;
52
+ this.fractionalType = parsed.fractionalType;
53
+ this.maximumFractionalValue = parsed.maximumFractionalValue;
54
+ this.segmentsOverlap = parsed.segmentsOverlap;
55
+ this.geometry = parsed.geometry;
56
+ this.frameOfReferenceUID = parsed.frameOfReferenceUID;
57
+ this.contentLabel = parsed.contentLabel;
58
+ this.diagnostics = parsed.diagnostics;
59
+ }
60
+ static fromDicom(bytes) {
61
+ return new Segmentation(readSegDataset(bytes));
62
+ }
63
+ /** Every segment, in `SegmentSequence` order. */
64
+ segments() {
65
+ return this.parsed.segments;
66
+ }
67
+ hasSegment(segmentNumber) {
68
+ return this.parsed.segments.some((s) => s.number === segmentNumber);
69
+ }
70
+ assertSegment(segmentNumber) {
71
+ if (!this.hasSegment(segmentNumber)) {
72
+ throw new RangeError(`no segment with SegmentNumber ${segmentNumber} (present: ${this.parsed.segments.map((s) => s.number).join(", ")})`);
73
+ }
74
+ }
75
+ get sliceSize() {
76
+ return this.parsed.rows * this.parsed.columns;
77
+ }
78
+ /**
79
+ * The boolean mask for a **BINARY** segment. Throws {@link SegmentationTypeMismatchError}
80
+ * on a FRACTIONAL SEG — threshold `field(n)` yourself, there is no safe default cut.
81
+ */
82
+ mask(segmentNumber) {
83
+ this.assertSegment(segmentNumber);
84
+ if (this.type !== "BINARY") {
85
+ throw new SegmentationTypeMismatchError(`mask() is for BINARY segmentations; this is FRACTIONAL — use field(${segmentNumber}) and apply your own threshold`);
86
+ }
87
+ const cached = this.maskCache.get(segmentNumber);
88
+ if (cached)
89
+ return cached;
90
+ const rc = this.sliceSize;
91
+ const data = new Uint8Array(this.parsed.geometry.planes.length * rc);
92
+ for (const fr of this.parsed.frames) {
93
+ if (fr.segmentNumber !== segmentNumber)
94
+ continue;
95
+ const bits = binaryFrame(this.parsed, fr.frameIndex);
96
+ const base = fr.planeIndex * rc;
97
+ for (let i = 0; i < rc; i++)
98
+ if (bits[i])
99
+ data[base + i] = 1;
100
+ }
101
+ const mask = maskFromDense(this.parsed.geometry, data);
102
+ this.maskCache.set(segmentNumber, mask);
103
+ return mask;
104
+ }
105
+ /**
106
+ * The confidence/occupancy field for a **FRACTIONAL** segment, rescaled to `[0, 1]` by
107
+ * `MaximumFractionalValue`. Throws {@link SegmentationTypeMismatchError} on a BINARY SEG
108
+ * — use `mask(n)`.
109
+ */
110
+ field(segmentNumber) {
111
+ return this.fractionalField(segmentNumber, true, this.fieldCache);
112
+ }
113
+ /** The raw stored integers (0..`maximumFractionalValue`) for a FRACTIONAL segment, unscaled. */
114
+ rawField(segmentNumber) {
115
+ return this.fractionalField(segmentNumber, false, this.rawFieldCache);
116
+ }
117
+ fractionalField(segmentNumber, rescale, cache) {
118
+ this.assertSegment(segmentNumber);
119
+ if (this.type !== "FRACTIONAL") {
120
+ throw new SegmentationTypeMismatchError(`field() is for FRACTIONAL segmentations; this is BINARY — use mask(${segmentNumber})`);
121
+ }
122
+ const cached = cache.get(segmentNumber);
123
+ if (cached)
124
+ return cached;
125
+ const rc = this.sliceSize;
126
+ const buffer = new Float32Array(this.parsed.geometry.planes.length * rc);
127
+ const divisor = rescale ? (this.maximumFractionalValue ?? 255) : 1;
128
+ for (const fr of this.parsed.frames) {
129
+ if (fr.segmentNumber !== segmentNumber)
130
+ continue;
131
+ const bytes = fractionalFrame(this.parsed, fr.frameIndex);
132
+ const base = fr.planeIndex * rc;
133
+ for (let i = 0; i < rc; i++)
134
+ buffer[base + i] = bytes[i] / divisor;
135
+ }
136
+ const field = createScalarField(this.parsed.geometry, buffer);
137
+ cache.set(segmentNumber, field);
138
+ return field;
139
+ }
140
+ /**
141
+ * The footprint of a segment as a `Mask3D`: for BINARY, the mask itself; for FRACTIONAL,
142
+ * the voxels with a non-zero stored value. Handy as the `mask` argument to
143
+ * `meanValue` / `volumeAboveThreshold` / `thresholdSensitivity` — "confidence over the
144
+ * region the model marked at all".
145
+ */
146
+ support(segmentNumber) {
147
+ if (this.type === "BINARY")
148
+ return this.mask(segmentNumber);
149
+ const raw = this.rawField(segmentNumber);
150
+ const [columns, rows, planes] = raw.dimensions;
151
+ const data = new Uint8Array(columns * rows * planes);
152
+ for (let k = 0; k < planes; k++) {
153
+ const slice = raw.getSliceBuffer(k);
154
+ const base = k * columns * rows;
155
+ for (let i = 0; i < slice.length; i++)
156
+ if (slice[i] > 0)
157
+ data[base + i] = 1;
158
+ }
159
+ return maskFromDense(this.geometry, data);
160
+ }
161
+ /**
162
+ * Interpolated confidence at a physical point for a FRACTIONAL segment (0 outside the
163
+ * grid). The confidence-under-cursor tooltip from §7.3 — the same call as
164
+ * `dose.sample()` against a different field. Trilinear by default.
165
+ */
166
+ sampleConfidence(segmentNumber, point, opts = {}) {
167
+ return sampleFieldAt(this.field(segmentNumber), point, {
168
+ method: opts.method ?? "trilinear",
169
+ outOfBounds: 0,
170
+ });
171
+ }
172
+ }
173
+ /** Parse one DICOM SEG object's bytes. Throws `NotSegmentationError` /
174
+ * `MalformedSegmentationError` / `UnsupportedSegmentationTypeError`. */
175
+ export function readSeg(bytes) {
176
+ return Segmentation.fromDicom(bytes);
177
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Segmentation type. dicom-seg-js 0.1.0 handles `BINARY` and `FRACTIONAL`; `LABELMAP`
3
+ * (PS3.3 Sup 243) throws {@link UnsupportedSegmentationTypeError} until 0.2.0.
4
+ */
5
+ export type SegmentationType = "BINARY" | "FRACTIONAL";
6
+ /**
7
+ * `SegmentationFractionalType` (0062,0010). PROBABILITY = "the probability that the
8
+ * segmented property occupies the voxel"; OCCUPANCY = "the fraction of the voxel volume
9
+ * the property occupies". A stored 0.5 means very different things under each — never
10
+ * assume one (roadmap §7.1). Undefined when the SEG did not declare it.
11
+ */
12
+ export type FractionalType = "PROBABILITY" | "OCCUPANCY";
13
+ /** `SegmentsOverlap` (0062,0013) — whether segments may share voxels. */
14
+ export type SegmentsOverlap = "YES" | "NO" | "UNDEFINED";
15
+ /** A DICOM coded concept — `CodeValue` + `CodingSchemeDesignator` + `CodeMeaning`. */
16
+ export interface CodedConcept {
17
+ readonly value: string;
18
+ readonly scheme: string;
19
+ readonly meaning: string;
20
+ }
21
+ /** One entry of `SegmentSequence` (0062,0002). */
22
+ export interface SegmentInfo {
23
+ /** `SegmentNumber` (0062,0004) — 1-based, unique within the SEG. The key for `mask()` / `field()`. */
24
+ readonly number: number;
25
+ /** `SegmentLabel` (0062,0005). */
26
+ readonly label: string;
27
+ /** `SegmentAlgorithmType` (0062,0008) — `AUTOMATIC` | `SEMIAUTOMATIC` | `MANUAL`. */
28
+ readonly algorithmType: string;
29
+ /** `SegmentAlgorithmName` (0062,0009), when present. */
30
+ readonly algorithmName: string | undefined;
31
+ /** `SegmentedPropertyCategoryCodeSequence` (0062,0003) — e.g. Tissue, Anatomical Structure. */
32
+ readonly category: CodedConcept | undefined;
33
+ /** `SegmentedPropertyTypeCodeSequence` (0062,000F) — e.g. Liver, Tumor. */
34
+ readonly propertyType: CodedConcept | undefined;
35
+ /** `SegmentedPropertyTypeModifierCodeSequence` (0062,0011), when present — e.g. Left. */
36
+ readonly propertyTypeModifier: CodedConcept | undefined;
37
+ /** `TrackingID` (0062,0020) / `TrackingUID` (0062,0021), when present. */
38
+ readonly trackingId: string | undefined;
39
+ readonly trackingUid: string | undefined;
40
+ /** Number of frames actually stored for this segment (sparse SEGs omit all-empty frames). */
41
+ readonly frameCount: number;
42
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,105 @@
1
+ # FRACTIONAL segmentations
2
+
3
+ A FRACTIONAL SEG stores one 8-bit value per voxel per segment. `dicom-seg-js` rescales it
4
+ to `[0, 1]` with `MaximumFractionalValue` (`field(n)`) and also gives you the raw integers
5
+ (`rawField(n)`). What that number *means* is the subject of this document.
6
+
7
+ ## 1. PROBABILITY and OCCUPANCY are different quantities
8
+
9
+ `SegmentationFractionalType` (0062,0010) declares one of two meanings:
10
+
11
+ | | 0.5 means |
12
+ |---|---|
13
+ | **PROBABILITY** | "there is a 50% chance the segmented property is present at this voxel" |
14
+ | **OCCUPANCY** | "the segmented property fills 50% of this voxel's volume" |
15
+
16
+ They are not interchangeable. A partial-volume boundary voxel that is genuinely half tumour
17
+ has OCCUPANCY 0.5 and (if the model is certain) PROBABILITY ≈ 1.0. A voxel the model is
18
+ unsure about has PROBABILITY 0.5 and OCCUPANCY that is either 0 or 1 (the tumour is either
19
+ there or not — the model just doesn't know which).
20
+
21
+ `dicom-seg-js` therefore:
22
+
23
+ - exposes `seg.fractionalType` and **never defaults it** — an absent
24
+ `SegmentationFractionalType` reads back as `undefined` with a `FRACTIONAL_TYPE_ABSENT`
25
+ diagnostic, not as PROBABILITY;
26
+ - (planned) emits a diagnostic when the value distribution contradicts the declared type —
27
+ e.g. an OCCUPANCY field that is bimodal at 0 and `MaximumFractionalValue` with almost
28
+ nothing in between, which is a thresholded probability mask mislabelled.
29
+
30
+ If your pipeline needs one specific meaning, check `seg.fractionalType` and reject the
31
+ file when it is `undefined` or wrong. Do not let the library pick for you, because it
32
+ can't.
33
+
34
+ ## 2. Confidence is not accuracy
35
+
36
+ A per-voxel PROBABILITY is the model's **confidence** at that location. It is not the
37
+ accuracy of the segmentation:
38
+
39
+ - **Accuracy needs ground truth**, which does not exist at inference time. Averaging
40
+ confidence over a segment tells you how sure the model was, not how often it was right.
41
+ - **Most model outputs are uncalibrated.** A softmax value of 0.9 does not mean 90% of
42
+ voxels with that value are correct; networks are typically overconfident, and the
43
+ mapping from score to empirical frequency is model- and dataset-specific. Calibration
44
+ (temperature scaling, isotonic regression, …) is a separate step that the SEG file does
45
+ not record.
46
+
47
+ So `dicom-seg-js` exposes honest quantities only, via the re-exported `rt-geometry-js`
48
+ functions:
49
+
50
+ ```ts
51
+ import { readSeg, meanValue, volumeAboveThreshold, thresholdSensitivity } from "dicom-seg-js";
52
+
53
+ const seg = readSeg(bytes);
54
+ const field = seg.field(1);
55
+ const support = seg.support(1); // voxels the model marked at all
56
+
57
+ meanValue(field, support); // mean confidence over that region
58
+ volumeAboveThreshold(field, support, 0.7); // mm³ at confidence >= 0.7
59
+ thresholdSensitivity(field, support, [0.3, 0.5, 0.7, 0.9]);
60
+ // how much the segmented volume moves as the confidence cut moves — a flat curve
61
+ // means the threshold barely matters, a steep one means it dominates the result
62
+ ```
63
+
64
+ There is deliberately **no** `accuracy()`, `percentCorrect()`, `reliability()`, or single
65
+ summary "quality" number anywhere in the API.
66
+
67
+ ## 3. Displaying confidence
68
+
69
+ `seg.sampleConfidence(n, point)` gives an interpolated value for a cursor tooltip (§7.3) —
70
+ the same call as `dose.sample()` on a different field.
71
+
72
+ Presenting that value as "**87% confidence**" implies the model is calibrated, which it
73
+ usually is not. Unless you have calibrated the model yourself, prefer relative
74
+ presentation: a heatmap, or high/medium/low banding. This is a UI obligation on the
75
+ consumer, not something the library can enforce.
76
+
77
+ ## 4. Validation
78
+
79
+ `dicom-seg-js`'s reconstruction was checked against
80
+ [`highdicom`](https://github.com/ImagingDataCommons/highdicom) 0.28 on real SEG files from
81
+ TCIA. Full method and the harness are in
82
+ [`../VALIDATION.md`](../VALIDATION.md) / [`../scripts/validation/`](../scripts/validation/);
83
+ the headline:
84
+
85
+ | File (TCIA) | Type | Segments | Result |
86
+ |---|---|---|---|
87
+ | C4KC-KiTS `KiTS-00007` | BINARY | 2 (Kidney, Mass) | **voxel-exact** — 122/122 slice checksums identical |
88
+ | NSCLC-Radiomics `LUNG1-005` | BINARY | 6 (Esophagus, GTV, Heart, L/R Lung, Cord) | **voxel-exact** — 546/546 |
89
+ | ISPY1 `ISPY1_1004` | FRACTIONAL / OCCUPANCY | 1 (PE Tumor) | **voxel-exact** — 60/60; raw value sum 22 876 305 matched exactly |
90
+
91
+ 728 `(segment, plane)` slices, every one byte-for-byte identical to highdicom's
92
+ reconstruction (BINARY bit-unpacking included).
93
+
94
+ ### Fractional types in the wild
95
+
96
+ Of the TCIA collections that publish DICOM SEG, most are `BINARY`. `FRACTIONAL` appears
97
+ mainly in the breast-MRI collections (ISPY1/ISPY2, ACRIN-6698). In the sample checked, the
98
+ declared fractional type was **`OCCUPANCY`** — but the `ISPY1_1004` file's non-zero values
99
+ are **all exactly 255**: it is a binary mask stored as FRACTIONAL, not a graded occupancy
100
+ field. `dicom-seg-js` emits a `FRACTIONAL_VALUES_LOOK_BINARY` diagnostic for exactly this
101
+ case (≥ 98% of non-zero values pinned at `MaximumFractionalValue`). No genuinely graded
102
+ `PROBABILITY` field turned up in the sample; that is consistent with FRACTIONAL SEG being
103
+ rare and usually a thresholded export rather than a raw model head. Treat a FRACTIONAL SEG
104
+ as graded only after checking its value distribution (`thresholdSensitivity`, or the
105
+ diagnostic).
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "dicom-seg-js",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "DICOM Segmentation (SEG) reading — BINARY masks and FRACTIONAL probability/occupancy fields — built on rt-geometry-js.",
6
+ "keywords": [
7
+ "dicom",
8
+ "segmentation",
9
+ "dicom-seg",
10
+ "seg",
11
+ "medical-imaging",
12
+ "mask",
13
+ "fractional",
14
+ "dcmjs"
15
+ ],
16
+ "license": "MIT",
17
+ "author": "Adeel Khan",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/adeelbarki/dicom-imaging-toolkit-packages.git",
21
+ "directory": "packages/dicom-seg"
22
+ },
23
+ "homepage": "https://github.com/adeelbarki/dicom-imaging-toolkit-packages/tree/main/packages/dicom-seg#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/adeelbarki/dicom-imaging-toolkit-packages/issues"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "CHANGELOG.md",
38
+ "docs/FRACTIONAL-SEG.md"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "scripts": {
44
+ "pretest": "npm run check:deps",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "typecheck": "tsc --noEmit",
48
+ "check:deps": "node ../../scripts/check-dependency-rule.mjs",
49
+ "build": "tsc -p tsconfig.build.json",
50
+ "prebuild": "npm run build --workspace rt-geometry-js",
51
+ "prepublishOnly": "npm run build && npm test"
52
+ },
53
+ "peerDependencies": {
54
+ "rt-geometry-js": "^0.1.2"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^26.2.0",
58
+ "rt-geometry-js": "^0.1.2",
59
+ "typescript": "^5.6.3",
60
+ "vitest": "^2.1.4"
61
+ },
62
+ "dependencies": {
63
+ "dcmjs": "^0.52.0"
64
+ }
65
+ }