| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228 |
6×
9×
9×
8×
8×
1×
10×
1×
9×
9×
9×
9×
36×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
9×
| import { external } from '../externalModules.js';
import getImageFrame from './getImageFrame.js';
import decodeImageFrame from './decodeImageFrame.js';
import isColorImageFn from './isColorImage.js';
import convertColorSpace from './convertColorSpace.js';
import getMinMax from '../shared/getMinMax.js';
import isJPEGBaseline8BitColor from './isJPEGBaseline8BitColor.js';
let lastImageIdDrawn = '';
function isModalityLUTForDisplay(sopClassUid) {
// special case for XA and XRF
// https://groups.google.com/forum/#!searchin/comp.protocols.dicom/Modality$20LUT$20XA/comp.protocols.dicom/UBxhOZ2anJ0/D0R_QP8V2wIJ
return (
sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.1' && // XA
sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.2.1'
); // XRF
}
function convertToIntPixelData(floatPixelData) {
const floatMinMax = getMinMax(floatPixelData);
const floatRange = Math.abs(floatMinMax.max - floatMinMax.min);
const intRange = 65535;
const slope = floatRange / intRange;
const intercept = floatMinMax.min;
const numPixels = floatPixelData.length;
const intPixelData = new Uint16Array(numPixels);
let min = 65535;
let max = 0;
for (let i = 0; i < numPixels; i++) {
const rescaledPixel = Math.floor((floatPixelData[i] - intercept) / slope);
intPixelData[i] = rescaledPixel;
min = Math.min(min, rescaledPixel);
max = Math.max(max, rescaledPixel);
}
return {
min,
max,
intPixelData,
slope,
intercept
};
}
/**
* Helper function to set pixel data to the right typed array. This is needed because web workers
* can transfer array buffers but not typed arrays
* @param imageFrame
*/
function setPixelDataType(imageFrame) {
Iif (imageFrame.bitsAllocated === 32) {
imageFrame.pixelData = new Float32Array(imageFrame.pixelData);
} else if (imageFrame.bitsAllocated === 16) {
Iif (imageFrame.pixelRepresentation === 0) {
imageFrame.pixelData = new Uint16Array(imageFrame.pixelData);
} else {
imageFrame.pixelData = new Int16Array(imageFrame.pixelData);
}
} else {
imageFrame.pixelData = new Uint8Array(imageFrame.pixelData);
}
}
function createImage (imageId, pixelData, transferSyntax, options) {
if (!pixelData || !pixelData.length) {
return Promise.reject(new Error('The file does not contain image data.'));
}
const cornerstone = external.cornerstone;
const canvas = document.createElement('canvas');
const imageFrame = getImageFrame(imageId);
const decodePromise = decodeImageFrame(
imageFrame,
transferSyntax,
pixelData,
canvas,
options
);
const getMetaData = key => cornerstone.metaData.get(key, imageId) || {};
return new Promise((resolve, reject) => {
decodePromise.then(imageFrame => {
const imagePlaneModule = getMetaData('imagePlaneModule');
const voiLutModule = getMetaData('voiLutModule');
const modalityLutModule = getMetaData('modalityLutModule');
const sopCommonModule = getMetaData('sopCommonModule');
const isColorImage = isColorImageFn(imageFrame.photometricInterpretation);
// JPEGBaseline (8 bits) is already returning the pixel data in the right format (rgba)
// because it's using a canvas to load and decode images.
Eif (!isJPEGBaseline8BitColor(imageFrame, transferSyntax)) {
setPixelDataType(imageFrame);
// convert color space
Iif (isColorImage) {
// setup the canvas context
canvas.height = imageFrame.rows;
canvas.width = imageFrame.columns;
const context = canvas.getContext('2d');
const imageData = context.createImageData(
imageFrame.columns,
imageFrame.rows
);
convertColorSpace(imageFrame, imageData);
imageFrame.imageData = imageData;
imageFrame.pixelData = imageData.data;
// calculate smallest and largest PixelValue of the converted pixelData
const minMax = getMinMax(imageFrame.pixelData);
imageFrame.smallestPixelValue = minMax.min;
imageFrame.largestPixelValue = minMax.max;
}
}
const image = {
imageId,
color: isColorImage,
columnPixelSpacing: imagePlaneModule.pixelSpacing
? imagePlaneModule.pixelSpacing[1]
: undefined,
columns: imageFrame.columns,
height: imageFrame.rows,
intercept: modalityLutModule.rescaleIntercept
? modalityLutModule.rescaleIntercept
: 0,
invert: imageFrame.photometricInterpretation === 'MONOCHROME1',
minPixelValue: imageFrame.smallestPixelValue,
maxPixelValue: imageFrame.largestPixelValue,
render: undefined, // set below
rowPixelSpacing: imagePlaneModule.pixelSpacing
? imagePlaneModule.pixelSpacing[0]
: undefined,
rows: imageFrame.rows,
sizeInBytes: imageFrame.pixelData.length,
slope: modalityLutModule.rescaleSlope
? modalityLutModule.rescaleSlope
: 1,
width: imageFrame.columns,
windowCenter: voiLutModule.windowCenter
? voiLutModule.windowCenter[0]
: undefined,
windowWidth: voiLutModule.windowWidth
? voiLutModule.windowWidth[0]
: undefined,
decodeTimeInMS: imageFrame.decodeTimeInMS,
floatPixelData: undefined
};
// add function to return pixel data
Iif (imageFrame.pixelData instanceof Float32Array) {
const floatPixelData = imageFrame.pixelData;
const results = convertToIntPixelData(floatPixelData);
image.minPixelValue = results.min;
image.maxPixelValue = results.max;
image.slope = results.slope;
image.intercept = results.intercept;
image.floatPixelData = floatPixelData;
image.getPixelData = () => results.intPixelData;
} else {
image.getPixelData = () => imageFrame.pixelData;
}
// Setup the renderer
Iif (image.color) {
image.render = cornerstone.renderColorImage;
image.getCanvas = function() {
if (lastImageIdDrawn === imageId) {
return canvas;
}
canvas.height = image.rows;
canvas.width = image.columns;
const context = canvas.getContext('2d');
context.putImageData(imageFrame.imageData, 0, 0);
lastImageIdDrawn = imageId;
return canvas;
};
} else {
image.render = cornerstone.renderGrayscaleImage;
}
// Modality LUT
Iif (
modalityLutModule.modalityLUTSequence &&
modalityLutModule.modalityLUTSequence.length > 0 &&
isModalityLUTForDisplay(sopCommonModule.sopClassUID)
) {
image.modalityLUT = modalityLutModule.modalityLUTSequence[0];
}
// VOI LUT
Iif (
voiLutModule.voiLUTSequence &&
voiLutModule.voiLUTSequence.length > 0
) {
image.voiLUT = voiLutModule.voiLUTSequence[0];
}
Iif (image.color) {
image.windowWidth = 255;
image.windowCenter = 127;
}
// set the ww/wc to cover the dynamic range of the image if no values are supplied
Iif (image.windowCenter === undefined || image.windowWidth === undefined) {
const maxVoi = image.maxPixelValue * image.slope + image.intercept;
const minVoi = image.minPixelValue * image.slope + image.intercept;
image.windowWidth = maxVoi - minVoi;
image.windowCenter = (maxVoi + minVoi) / 2;
}
resolve(image);
}, reject);
});
}
export default createImage;
|