@cornerstonejs/nifti-volume-loader 2.0.0-beta.3 → 2.0.0-beta.30
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/dist/esm/constants/index.js +0 -1
- package/dist/esm/constants/niftiLoaderScheme.js +0 -1
- package/dist/esm/cornerstoneNiftiImageLoader.d.ts +2 -7
- package/dist/esm/cornerstoneNiftiImageLoader.js +123 -7
- package/dist/esm/createNiftiImageIdsAndCacheMetadata.d.ts +17 -0
- package/dist/esm/createNiftiImageIdsAndCacheMetadata.js +250 -0
- package/dist/esm/enums/Events.js +0 -1
- package/dist/esm/enums/index.js +0 -1
- package/dist/esm/helpers/affineUtilities.js +0 -1
- package/dist/esm/helpers/convert.js +1 -3
- package/dist/esm/helpers/dataTypeCodeHelper.d.ts +1 -0
- package/dist/esm/helpers/dataTypeCodeHelper.js +22 -0
- package/dist/esm/helpers/index.d.ts +1 -2
- package/dist/esm/helpers/index.js +1 -3
- package/dist/esm/helpers/makeVolumeMetadata.d.ts +6 -2
- package/dist/esm/helpers/makeVolumeMetadata.js +40 -36
- package/dist/esm/helpers/modalityScaleNifti.d.ts +5 -1
- package/dist/esm/helpers/modalityScaleNifti.js +131 -7
- package/dist/esm/helpers/niftiConstants.js +0 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -4
- package/dist/umd/index.js +1 -2
- package/package.json +47 -16
- package/dist/esm/NiftiImageVolume.d.ts +0 -21
- package/dist/esm/NiftiImageVolume.js +0 -26
- package/dist/esm/NiftiImageVolume.js.map +0 -1
- package/dist/esm/constants/index.js.map +0 -1
- package/dist/esm/constants/niftiLoaderScheme.js.map +0 -1
- package/dist/esm/cornerstoneNiftiImageLoader.js.map +0 -1
- package/dist/esm/enums/Events.js.map +0 -1
- package/dist/esm/enums/index.js.map +0 -1
- package/dist/esm/helpers/affineUtilities.js.map +0 -1
- package/dist/esm/helpers/convert.js.map +0 -1
- package/dist/esm/helpers/fetchAndAllocateNiftiVolume.d.ts +0 -4
- package/dist/esm/helpers/fetchAndAllocateNiftiVolume.js +0 -163
- package/dist/esm/helpers/fetchAndAllocateNiftiVolume.js.map +0 -1
- package/dist/esm/helpers/index.js.map +0 -1
- package/dist/esm/helpers/makeVolumeMetadata.js.map +0 -1
- package/dist/esm/helpers/modalityScaleNifti.js.map +0 -1
- package/dist/esm/helpers/niftiConstants.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/umd/index.js.LICENSE.txt +0 -1
- package/dist/umd/index.js.map +0 -1
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
promise: Promise<NiftiImageVolume>;
|
|
4
|
-
cancel: () => void;
|
|
5
|
-
}
|
|
6
|
-
export default function cornerstoneNiftiImageVolumeLoader(volumeId: string): IVolumeLoader;
|
|
7
|
-
export {};
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
export default function cornerstoneNiftiImageLoader(imageId: string): Types.IImageLoadObject;
|
|
@@ -1,10 +1,126 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Enums, eventTarget, metaData, triggerEvent, utilities, } from '@cornerstonejs/core';
|
|
2
|
+
import * as NiftiReader from 'nifti-reader-js';
|
|
3
|
+
import { Events } from './enums';
|
|
4
|
+
import { modalityScaleNifti } from './helpers';
|
|
5
|
+
const fetchStarted = new Map();
|
|
6
|
+
let niftiScalarData = null;
|
|
7
|
+
function fetchArrayBuffer({ url, signal, onload, }) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const xhr = new XMLHttpRequest();
|
|
10
|
+
xhr.open('GET', url, true);
|
|
11
|
+
xhr.responseType = 'arraybuffer';
|
|
12
|
+
const onLoadHandler = function (e) {
|
|
13
|
+
if (onload && typeof onload === 'function') {
|
|
14
|
+
onload();
|
|
15
|
+
}
|
|
16
|
+
if (signal) {
|
|
17
|
+
signal.removeEventListener('abort', onAbortHandler);
|
|
18
|
+
}
|
|
19
|
+
resolve(xhr.response);
|
|
20
|
+
};
|
|
21
|
+
const onAbortHandler = () => {
|
|
22
|
+
xhr.abort();
|
|
23
|
+
xhr.removeEventListener('load', onLoadHandler);
|
|
24
|
+
reject(new Error('Request aborted'));
|
|
25
|
+
};
|
|
26
|
+
xhr.addEventListener('load', onLoadHandler);
|
|
27
|
+
const onProgress = (loaded, total) => {
|
|
28
|
+
const data = { url, loaded, total };
|
|
29
|
+
triggerEvent(eventTarget, Events.NIFTI_VOLUME_PROGRESS, { data });
|
|
30
|
+
};
|
|
31
|
+
xhr.onprogress = function (e) {
|
|
32
|
+
onProgress(e.loaded, e.total);
|
|
33
|
+
};
|
|
34
|
+
if (signal && signal.aborted) {
|
|
35
|
+
xhr.abort();
|
|
36
|
+
reject(new Error('Request aborted'));
|
|
37
|
+
}
|
|
38
|
+
else if (signal) {
|
|
39
|
+
signal.addEventListener('abort', onAbortHandler);
|
|
40
|
+
}
|
|
41
|
+
xhr.send();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export default function cornerstoneNiftiImageLoader(imageId) {
|
|
45
|
+
const [url, frame] = imageId.substring(6).split('?frame=');
|
|
46
|
+
const sliceIndex = parseInt(frame, 10);
|
|
47
|
+
const imagePixelModule = metaData.get(Enums.MetadataModules.IMAGE_PIXEL, imageId);
|
|
48
|
+
const imagePlaneModule = metaData.get(Enums.MetadataModules.IMAGE_PLANE, imageId);
|
|
49
|
+
const promise = new Promise((resolve, reject) => {
|
|
50
|
+
if (!fetchStarted.get(url)) {
|
|
51
|
+
fetchStarted.set(url, true);
|
|
52
|
+
fetchAndProcessNiftiData(imageId, url, sliceIndex, imagePixelModule, imagePlaneModule)
|
|
53
|
+
.then(resolve)
|
|
54
|
+
.catch(reject);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
waitForNiftiData(imageId, sliceIndex, imagePixelModule, imagePlaneModule)
|
|
58
|
+
.then(resolve)
|
|
59
|
+
.catch(reject);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
promise: promise,
|
|
64
|
+
cancelFn: undefined,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function fetchAndProcessNiftiData(imageId, url, sliceIndex, imagePixelModule, imagePlaneModule) {
|
|
68
|
+
let niftiBuffer = await fetchArrayBuffer({ url });
|
|
69
|
+
let niftiHeader = null;
|
|
70
|
+
let niftiImage = null;
|
|
71
|
+
if (NiftiReader.isCompressed(niftiBuffer)) {
|
|
72
|
+
niftiBuffer = NiftiReader.decompress(niftiBuffer);
|
|
73
|
+
}
|
|
74
|
+
if (NiftiReader.isNIFTI(niftiBuffer)) {
|
|
75
|
+
niftiHeader = NiftiReader.readHeader(niftiBuffer);
|
|
76
|
+
niftiImage = NiftiReader.readImage(niftiHeader, niftiBuffer);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const errorMessage = 'The provided buffer is not a valid NIFTI file.';
|
|
80
|
+
console.warn(errorMessage);
|
|
81
|
+
throw new Error(errorMessage);
|
|
82
|
+
}
|
|
83
|
+
const { scalarData } = modalityScaleNifti(niftiHeader, niftiImage);
|
|
84
|
+
niftiScalarData = scalarData;
|
|
85
|
+
return createImage(imageId, sliceIndex, imagePixelModule, imagePlaneModule);
|
|
86
|
+
}
|
|
87
|
+
function waitForNiftiData(imageId, sliceIndex, imagePixelModule, imagePlaneModule) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const intervalId = setInterval(() => {
|
|
90
|
+
if (niftiScalarData) {
|
|
91
|
+
clearInterval(intervalId);
|
|
92
|
+
resolve(createImage(imageId, sliceIndex, imagePixelModule, imagePlaneModule));
|
|
93
|
+
}
|
|
94
|
+
}, 10);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function createImage(imageId, sliceIndex, imagePixelModule, imagePlaneModule) {
|
|
98
|
+
const { rows, columns } = imagePlaneModule;
|
|
99
|
+
const numVoxels = rows * columns;
|
|
100
|
+
const sliceOffset = numVoxels * sliceIndex;
|
|
101
|
+
const pixelData = new niftiScalarData.constructor(numVoxels);
|
|
102
|
+
pixelData.set(niftiScalarData.subarray(sliceOffset, sliceOffset + numVoxels));
|
|
103
|
+
const voxelManager = utilities.VoxelManager.createImageVoxelManager({
|
|
104
|
+
width: columns,
|
|
105
|
+
height: rows,
|
|
106
|
+
numberOfComponents: 1,
|
|
107
|
+
scalarData: pixelData,
|
|
108
|
+
});
|
|
4
109
|
return {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
110
|
+
imageId,
|
|
111
|
+
dataType: niftiScalarData.constructor
|
|
112
|
+
.name,
|
|
113
|
+
columnPixelSpacing: imagePlaneModule.columnPixelSpacing,
|
|
114
|
+
columns: imagePlaneModule.columns,
|
|
115
|
+
height: imagePlaneModule.rows,
|
|
116
|
+
invert: imagePixelModule.photometricInterpretation === 'MONOCHROME1',
|
|
117
|
+
rowPixelSpacing: imagePlaneModule.rowPixelSpacing,
|
|
118
|
+
rows: imagePlaneModule.rows,
|
|
119
|
+
sizeInBytes: rows * columns * niftiScalarData.BYTES_PER_ELEMENT,
|
|
120
|
+
width: imagePlaneModule.columns,
|
|
121
|
+
getPixelData: () => voxelManager.getScalarData(),
|
|
122
|
+
getCanvas: undefined,
|
|
123
|
+
numberOfComponents: undefined,
|
|
124
|
+
voxelManager,
|
|
8
125
|
};
|
|
9
126
|
}
|
|
10
|
-
//# sourceMappingURL=cornerstoneNiftiImageLoader.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const urlsMap: Map<any, any>;
|
|
2
|
+
export declare function fetchArrayBuffer({ url, onProgress, controller, onLoad, onHeader, loadFullVolume, }: {
|
|
3
|
+
url: any;
|
|
4
|
+
onProgress: any;
|
|
5
|
+
controller: any;
|
|
6
|
+
onLoad: any;
|
|
7
|
+
onHeader: any;
|
|
8
|
+
loadFullVolume?: boolean;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
data: Uint8Array;
|
|
11
|
+
headerInfo: any;
|
|
12
|
+
sliceInfo: any;
|
|
13
|
+
}>;
|
|
14
|
+
declare function createNiftiImageIdsAndCacheMetadata({ url }: {
|
|
15
|
+
url: any;
|
|
16
|
+
}): Promise<any[]>;
|
|
17
|
+
export { createNiftiImageIdsAndCacheMetadata };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import * as NiftiReader from 'nifti-reader-js';
|
|
2
|
+
import { eventTarget, triggerEvent, utilities } from '@cornerstonejs/core';
|
|
3
|
+
import { rasToLps } from './helpers/convert';
|
|
4
|
+
import Events from './enums/Events';
|
|
5
|
+
import { NIFTI_LOADER_SCHEME } from './constants';
|
|
6
|
+
import makeVolumeMetadata from './helpers/makeVolumeMetadata';
|
|
7
|
+
import { getArrayConstructor } from './helpers/dataTypeCodeHelper';
|
|
8
|
+
export const urlsMap = new Map();
|
|
9
|
+
const NIFTI1_HEADER_SIZE = 348;
|
|
10
|
+
const NIFTI2_HEADER_SIZE = 540;
|
|
11
|
+
const HEADER_CHECK_SIZE = Math.max(NIFTI1_HEADER_SIZE, NIFTI2_HEADER_SIZE);
|
|
12
|
+
export async function fetchArrayBuffer({ url, onProgress, controller, onLoad, onHeader, loadFullVolume = false, }) {
|
|
13
|
+
const isCompressed = url.endsWith('.gz');
|
|
14
|
+
let receivedData = new Uint8Array(0);
|
|
15
|
+
let niftiHeader = null;
|
|
16
|
+
const sliceInfo = null;
|
|
17
|
+
let contentLength;
|
|
18
|
+
const receivedLength = 0;
|
|
19
|
+
const signal = controller.signal;
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(url, { signal });
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
contentLength = response.headers.get('Content-Length');
|
|
26
|
+
const reader = response.body.getReader();
|
|
27
|
+
const decompressionStream = isCompressed
|
|
28
|
+
? new DecompressionStream('gzip')
|
|
29
|
+
: null;
|
|
30
|
+
const decompressionWriter = decompressionStream
|
|
31
|
+
? decompressionStream.writable.getWriter()
|
|
32
|
+
: null;
|
|
33
|
+
readStream(reader, decompressionWriter, isCompressed, receivedLength, processChunk, controller).catch(console.error);
|
|
34
|
+
if (isCompressed) {
|
|
35
|
+
const decompressedStream = decompressionStream.readable.getReader();
|
|
36
|
+
while (true) {
|
|
37
|
+
const { done, value } = await decompressedStream.read();
|
|
38
|
+
if (done) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
processChunk(value);
|
|
42
|
+
if (niftiHeader && !loadFullVolume) {
|
|
43
|
+
controller.abort();
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (onLoad && typeof onLoad === 'function') {
|
|
49
|
+
onLoad();
|
|
50
|
+
}
|
|
51
|
+
return { data: receivedData, headerInfo: niftiHeader, sliceInfo };
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.name === 'AbortError') {
|
|
55
|
+
console.log('Fetch aborted');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.error('Fetch error:', error);
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
function processChunk(chunk) {
|
|
63
|
+
appendData(chunk);
|
|
64
|
+
if (onProgress && typeof onProgress === 'function') {
|
|
65
|
+
onProgress(receivedLength, contentLength);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function appendData(data) {
|
|
69
|
+
const newData = new Uint8Array(receivedData.length + data.length);
|
|
70
|
+
newData.set(receivedData);
|
|
71
|
+
newData.set(data, receivedData.length);
|
|
72
|
+
receivedData = newData;
|
|
73
|
+
if (!loadFullVolume &&
|
|
74
|
+
!niftiHeader &&
|
|
75
|
+
receivedData.length >= HEADER_CHECK_SIZE) {
|
|
76
|
+
niftiHeader = handleNiftiHeader(receivedData);
|
|
77
|
+
if (niftiHeader && niftiHeader.isValid) {
|
|
78
|
+
controller.abort();
|
|
79
|
+
}
|
|
80
|
+
onHeader?.(niftiHeader);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function readStream(reader, decompressionWriter, isCompressed, receivedLength, processChunk, controller) {
|
|
85
|
+
while (true) {
|
|
86
|
+
const { done, value } = await reader.read();
|
|
87
|
+
if (done) {
|
|
88
|
+
if (isCompressed) {
|
|
89
|
+
decompressionWriter.close();
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
receivedLength += value.length;
|
|
94
|
+
if (isCompressed) {
|
|
95
|
+
await decompressionWriter.write(value);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
processChunk(value);
|
|
99
|
+
}
|
|
100
|
+
if (controller.signal.aborted) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function handleNiftiHeader(data) {
|
|
106
|
+
if (data.length < HEADER_CHECK_SIZE) {
|
|
107
|
+
return { isValid: false, message: 'Not enough data to check header' };
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const headerBuffer = data.slice(0, HEADER_CHECK_SIZE).buffer;
|
|
111
|
+
const header = NiftiReader.readHeader(headerBuffer);
|
|
112
|
+
const version = header.sizeof_hdr === NIFTI2_HEADER_SIZE ? 2 : 1;
|
|
113
|
+
const { orientation, origin, spacing } = rasToLps(header);
|
|
114
|
+
const { dimensions, direction } = makeVolumeMetadata(header, orientation, 1);
|
|
115
|
+
const arrayConstructor = getArrayConstructor(header.datatypeCode);
|
|
116
|
+
return {
|
|
117
|
+
dimensions,
|
|
118
|
+
direction,
|
|
119
|
+
isValid: true,
|
|
120
|
+
message: `Valid Nifti-${version} header detected`,
|
|
121
|
+
origin,
|
|
122
|
+
version,
|
|
123
|
+
orientation,
|
|
124
|
+
spacing,
|
|
125
|
+
header,
|
|
126
|
+
arrayConstructor,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error('Error reading Nifti header:', error);
|
|
131
|
+
return { isValid: false, message: 'Error reading Nifti header' };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function fetchAndAllocateNiftiVolume(volumeId) {
|
|
135
|
+
const niftiURL = volumeId.substring(NIFTI_LOADER_SCHEME.length + 1);
|
|
136
|
+
const onProgress = (loaded, total) => {
|
|
137
|
+
const data = { volumeId, loaded, total };
|
|
138
|
+
triggerEvent(eventTarget, Events.NIFTI_VOLUME_PROGRESS, { data });
|
|
139
|
+
};
|
|
140
|
+
const onLoad = () => {
|
|
141
|
+
const data = { volumeId };
|
|
142
|
+
triggerEvent(eventTarget, Events.NIFTI_VOLUME_LOADED, { data });
|
|
143
|
+
};
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
urlsMap.set(niftiURL, { controller, loading: true });
|
|
146
|
+
const niftiHeader = (await new Promise((resolve) => {
|
|
147
|
+
fetchArrayBuffer({
|
|
148
|
+
url: niftiURL,
|
|
149
|
+
onProgress,
|
|
150
|
+
controller,
|
|
151
|
+
onLoad,
|
|
152
|
+
onHeader: resolve,
|
|
153
|
+
});
|
|
154
|
+
}));
|
|
155
|
+
const { dimensions, direction, isValid, message, origin, version, header, spacing, arrayConstructor, } = niftiHeader;
|
|
156
|
+
const numImages = dimensions[2];
|
|
157
|
+
if (!isValid) {
|
|
158
|
+
console.error(message);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const imageIds = [];
|
|
162
|
+
for (let i = 0; i < numImages; i++) {
|
|
163
|
+
const imageId = `nifti:${niftiURL}?frame=${i}`;
|
|
164
|
+
const imageIdIndex = i;
|
|
165
|
+
imageIds.push(imageId);
|
|
166
|
+
const imageOrientationPatient = [
|
|
167
|
+
direction[0],
|
|
168
|
+
direction[1],
|
|
169
|
+
direction[2],
|
|
170
|
+
direction[3],
|
|
171
|
+
direction[4],
|
|
172
|
+
direction[5],
|
|
173
|
+
];
|
|
174
|
+
const precision = 6;
|
|
175
|
+
const imagePositionPatient = [
|
|
176
|
+
parseFloat((origin[0] + imageIdIndex * direction[6] * spacing[0]).toFixed(precision)),
|
|
177
|
+
parseFloat((origin[1] + imageIdIndex * direction[7] * spacing[1]).toFixed(precision)),
|
|
178
|
+
parseFloat((origin[2] + imageIdIndex * direction[8] * spacing[2]).toFixed(precision)),
|
|
179
|
+
];
|
|
180
|
+
const imagePlaneMetadata = {
|
|
181
|
+
frameOfReferenceUID: '1.2.840.10008.1.4',
|
|
182
|
+
rows: dimensions[1],
|
|
183
|
+
columns: dimensions[0],
|
|
184
|
+
imageOrientationPatient,
|
|
185
|
+
rowCosines: direction.slice(0, 3),
|
|
186
|
+
columnCosines: direction.slice(3, 6),
|
|
187
|
+
imagePositionPatient,
|
|
188
|
+
sliceThickness: spacing[2],
|
|
189
|
+
sliceLocation: origin[2] + i * spacing[2],
|
|
190
|
+
pixelSpacing: [spacing[0], spacing[1]],
|
|
191
|
+
rowPixelSpacing: spacing[1],
|
|
192
|
+
columnPixelSpacing: spacing[0],
|
|
193
|
+
};
|
|
194
|
+
const imagePixelMetadata = {
|
|
195
|
+
samplesPerPixel: 1,
|
|
196
|
+
photometricInterpretation: 'MONOCHROME2',
|
|
197
|
+
rows: dimensions[1],
|
|
198
|
+
columns: dimensions[0],
|
|
199
|
+
bitsAllocated: arrayConstructor.BYTES_PER_ELEMENT * 8,
|
|
200
|
+
bitsStored: arrayConstructor.BYTES_PER_ELEMENT * 8,
|
|
201
|
+
highBit: arrayConstructor.BYTES_PER_ELEMENT * 8 - 1,
|
|
202
|
+
pixelRepresentation: 1,
|
|
203
|
+
planarConfiguration: 0,
|
|
204
|
+
pixelAspectRatio: '1\\1',
|
|
205
|
+
redPaletteColorLookupTableDescriptor: [],
|
|
206
|
+
greenPaletteColorLookupTableDescriptor: [],
|
|
207
|
+
bluePaletteColorLookupTableDescriptor: [],
|
|
208
|
+
redPaletteColorLookupTableData: [],
|
|
209
|
+
greenPaletteColorLookupTableData: [],
|
|
210
|
+
bluePaletteColorLookupTableData: [],
|
|
211
|
+
smallestPixelValue: undefined,
|
|
212
|
+
largestPixelValue: undefined,
|
|
213
|
+
};
|
|
214
|
+
const generalSeriesMetadata = {
|
|
215
|
+
seriesDate: new Date(),
|
|
216
|
+
seriesTime: new Date(),
|
|
217
|
+
};
|
|
218
|
+
utilities.genericMetadataProvider.add(imageId, {
|
|
219
|
+
type: 'imagePixelModule',
|
|
220
|
+
metadata: imagePixelMetadata,
|
|
221
|
+
});
|
|
222
|
+
utilities.genericMetadataProvider.add(imageId, {
|
|
223
|
+
type: 'imagePlaneModule',
|
|
224
|
+
metadata: imagePlaneMetadata,
|
|
225
|
+
});
|
|
226
|
+
utilities.genericMetadataProvider.add(imageId, {
|
|
227
|
+
type: 'generalSeriesModule',
|
|
228
|
+
metadata: generalSeriesMetadata,
|
|
229
|
+
});
|
|
230
|
+
utilities.genericMetadataProvider.add(imageId, {
|
|
231
|
+
type: 'niftiVersion',
|
|
232
|
+
metadata: {
|
|
233
|
+
version,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
utilities.genericMetadataProvider.addRaw(imageId, {
|
|
237
|
+
type: 'niftiHeader',
|
|
238
|
+
metadata: {
|
|
239
|
+
header,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
urlsMap.delete(niftiURL);
|
|
244
|
+
return imageIds;
|
|
245
|
+
}
|
|
246
|
+
async function createNiftiImageIdsAndCacheMetadata({ url }) {
|
|
247
|
+
const imageIds = await fetchAndAllocateNiftiVolume(url);
|
|
248
|
+
return imageIds;
|
|
249
|
+
}
|
|
250
|
+
export { createNiftiImageIdsAndCacheMetadata };
|
package/dist/esm/enums/Events.js
CHANGED
package/dist/esm/enums/index.js
CHANGED
|
@@ -3,8 +3,7 @@ const invertDataPerFrame = (dimensions, imageDataArray) => {
|
|
|
3
3
|
let TypedArrayConstructor;
|
|
4
4
|
let bytesPerVoxel;
|
|
5
5
|
if (imageDataArray instanceof Uint8Array ||
|
|
6
|
-
imageDataArray instanceof ArrayBuffer
|
|
7
|
-
imageDataArray instanceof SharedArrayBuffer) {
|
|
6
|
+
imageDataArray instanceof ArrayBuffer) {
|
|
8
7
|
TypedArrayConstructor = Uint8Array;
|
|
9
8
|
bytesPerVoxel = 1;
|
|
10
9
|
}
|
|
@@ -80,4 +79,3 @@ function lpsToRas(header) {
|
|
|
80
79
|
};
|
|
81
80
|
}
|
|
82
81
|
export { lpsToRas, rasToLps, invertDataPerFrame };
|
|
83
|
-
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getArrayConstructor(datatypeCode: number): unknown;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as NIFTICONSTANTS from './niftiConstants';
|
|
2
|
+
export function getArrayConstructor(datatypeCode) {
|
|
3
|
+
switch (datatypeCode) {
|
|
4
|
+
case NIFTICONSTANTS.NIFTI_TYPE_UINT8:
|
|
5
|
+
return Uint8Array;
|
|
6
|
+
case NIFTICONSTANTS.NIFTI_TYPE_INT16:
|
|
7
|
+
return Int16Array;
|
|
8
|
+
case NIFTICONSTANTS.NIFTI_TYPE_INT32:
|
|
9
|
+
return Int32Array;
|
|
10
|
+
case NIFTICONSTANTS.NIFTI_TYPE_FLOAT32: {
|
|
11
|
+
return Float32Array;
|
|
12
|
+
}
|
|
13
|
+
case NIFTICONSTANTS.NIFTI_TYPE_INT8:
|
|
14
|
+
return Int8Array;
|
|
15
|
+
case NIFTICONSTANTS.NIFTI_TYPE_UINT16:
|
|
16
|
+
return Uint16Array;
|
|
17
|
+
case NIFTICONSTANTS.NIFTI_TYPE_UINT32:
|
|
18
|
+
return Uint32Array;
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`NIFTI datatypeCode ${datatypeCode} is not yet supported`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import makeVolumeMetadata from './makeVolumeMetadata';
|
|
2
2
|
import modalityScaleNifti from './modalityScaleNifti';
|
|
3
|
-
|
|
4
|
-
export { modalityScaleNifti, makeVolumeMetadata, fetchAndAllocateNiftiVolume };
|
|
3
|
+
export { modalityScaleNifti, makeVolumeMetadata };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import makeVolumeMetadata from './makeVolumeMetadata';
|
|
2
2
|
import modalityScaleNifti from './modalityScaleNifti';
|
|
3
|
-
|
|
4
|
-
export { modalityScaleNifti, makeVolumeMetadata, fetchAndAllocateNiftiVolume };
|
|
5
|
-
//# sourceMappingURL=index.js.map
|
|
3
|
+
export { modalityScaleNifti, makeVolumeMetadata };
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import { Types } from '@cornerstonejs/core';
|
|
2
|
-
export default function makeVolumeMetadata(niftiHeader: any, orientation: any,
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
export default function makeVolumeMetadata(niftiHeader: any, orientation: any, pixelRepresentation: any): {
|
|
3
|
+
volumeMetadata: Types.Metadata;
|
|
4
|
+
dimensions: Types.Point3;
|
|
5
|
+
direction: Types.Mat3;
|
|
6
|
+
};
|
|
@@ -1,51 +1,55 @@
|
|
|
1
|
+
import { utilities } from '@cornerstonejs/core';
|
|
1
2
|
import { vec3 } from 'gl-matrix';
|
|
2
|
-
|
|
3
|
+
const { windowLevel } = utilities;
|
|
4
|
+
export default function makeVolumeMetadata(niftiHeader, orientation, pixelRepresentation) {
|
|
3
5
|
const { numBitsPerVoxel, littleEndian, pixDims, dims } = niftiHeader;
|
|
6
|
+
const min = Infinity;
|
|
7
|
+
const max = -Infinity;
|
|
8
|
+
const frameLength = dims[1] * dims[2];
|
|
9
|
+
const middleFrameIndex = Math.floor(dims[3] / 2);
|
|
10
|
+
const offset = frameLength * middleFrameIndex;
|
|
11
|
+
const { windowWidth, windowCenter } = { windowWidth: 400, windowCenter: 40 };
|
|
4
12
|
const rowCosines = vec3.create();
|
|
5
13
|
const columnCosines = vec3.create();
|
|
14
|
+
const scanAxisNormal = vec3.create();
|
|
6
15
|
vec3.set(rowCosines, orientation[0], orientation[1], orientation[2]);
|
|
7
16
|
vec3.set(columnCosines, orientation[3], orientation[4], orientation[5]);
|
|
8
|
-
|
|
9
|
-
let max = -Infinity;
|
|
10
|
-
const xDim = dims[1];
|
|
11
|
-
const yDim = dims[2];
|
|
12
|
-
const zDim = dims[3];
|
|
13
|
-
const frameLength = xDim * yDim;
|
|
14
|
-
const middleFrameIndex = Math.floor(zDim / 2);
|
|
15
|
-
const offset = frameLength * middleFrameIndex;
|
|
16
|
-
for (let voxelIndex = offset; voxelIndex < offset + frameLength; voxelIndex++) {
|
|
17
|
-
const voxelValue = scalarData[voxelIndex];
|
|
18
|
-
if (voxelValue > max) {
|
|
19
|
-
max = voxelValue;
|
|
20
|
-
}
|
|
21
|
-
if (voxelValue < min) {
|
|
22
|
-
min = voxelValue;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const windowCenter = (max + min) / 2;
|
|
26
|
-
const windowWidth = max - min;
|
|
17
|
+
vec3.set(scanAxisNormal, orientation[6], orientation[7], orientation[8]);
|
|
27
18
|
return {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
volumeMetadata: {
|
|
20
|
+
BitsAllocated: numBitsPerVoxel,
|
|
21
|
+
BitsStored: numBitsPerVoxel,
|
|
22
|
+
SamplesPerPixel: 1,
|
|
23
|
+
HighBit: littleEndian ? numBitsPerVoxel - 1 : 1,
|
|
24
|
+
PhotometricInterpretation: 'MONOCHROME2',
|
|
25
|
+
PixelRepresentation: pixelRepresentation,
|
|
26
|
+
ImageOrientationPatient: [
|
|
27
|
+
rowCosines[0],
|
|
28
|
+
rowCosines[1],
|
|
29
|
+
rowCosines[2],
|
|
30
|
+
columnCosines[0],
|
|
31
|
+
columnCosines[1],
|
|
32
|
+
columnCosines[2],
|
|
33
|
+
],
|
|
34
|
+
PixelSpacing: [pixDims[1], pixDims[2]],
|
|
35
|
+
Columns: dims[1],
|
|
36
|
+
Rows: dims[2],
|
|
37
|
+
voiLut: [{ windowCenter, windowWidth }],
|
|
38
|
+
FrameOfReferenceUID: '1.2.3',
|
|
39
|
+
Modality: 'MR',
|
|
40
|
+
VOILUTFunction: 'LINEAR',
|
|
41
|
+
},
|
|
42
|
+
dimensions: [dims[1], dims[2], dims[3]],
|
|
43
|
+
direction: new Float32Array([
|
|
35
44
|
rowCosines[0],
|
|
36
45
|
rowCosines[1],
|
|
37
46
|
rowCosines[2],
|
|
38
47
|
columnCosines[0],
|
|
39
48
|
columnCosines[1],
|
|
40
49
|
columnCosines[2],
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
voiLut: [{ windowCenter, windowWidth }],
|
|
46
|
-
FrameOfReferenceUID: '1.2.3',
|
|
47
|
-
Modality: 'MR',
|
|
48
|
-
VOILUTFunction: 'LINEAR',
|
|
50
|
+
scanAxisNormal[0],
|
|
51
|
+
scanAxisNormal[1],
|
|
52
|
+
scanAxisNormal[2],
|
|
53
|
+
]),
|
|
49
54
|
};
|
|
50
55
|
}
|
|
51
|
-
//# sourceMappingURL=makeVolumeMetadata.js.map
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Types } from '@cornerstonejs/core';
|
|
2
|
+
export default function modalityScaleNifti(niftiHeader: any, niftiImageBuffer: any): {
|
|
3
|
+
scalarData: Types.PixelDataTypedArray;
|
|
4
|
+
pixelRepresentation: number;
|
|
5
|
+
};
|