├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── lib ├── charLS-DynamicMemory-browser.js ├── jpeg-baseline.js ├── jpeg-ls.js └── jpx.js ├── package-lock.json ├── package.json ├── release └── current │ ├── daikon-min.js │ └── daikon.js ├── src ├── compression-utils.js ├── dictionary.js ├── image.js ├── iterator.js ├── license.js ├── main.js ├── orderedmap.js ├── parser.js ├── rle.js ├── series.js ├── siemens.js ├── tag.js └── utilities.js └── tests ├── browser.html ├── data ├── deflated.dcm ├── explicit_big.dcm ├── explicit_little.dcm ├── implicit_little.dcm ├── jpeg_2000.dcm ├── jpeg_baseline_8bit.dcm ├── jpeg_lossless_sel1.dcm ├── jpeg_ls.dcm ├── rle.dcm └── volume │ ├── brain_001.dcm │ ├── brain_002.dcm │ ├── brain_003.dcm │ ├── brain_004.dcm │ ├── brain_005.dcm │ ├── brain_006.dcm │ ├── brain_007.dcm │ ├── brain_008.dcm │ ├── brain_009.dcm │ ├── brain_010.dcm │ ├── brain_011.dcm │ ├── brain_012.dcm │ ├── brain_013.dcm │ ├── brain_014.dcm │ ├── brain_015.dcm │ ├── brain_016.dcm │ ├── brain_017.dcm │ ├── brain_018.dcm │ ├── brain_019.dcm │ └── brain_020.dcm ├── driver-deflated.js ├── driver-explicit-big.js ├── driver-explicit-little.js ├── driver-implicit.js ├── driver-jpeg-2000.js ├── driver-jpeg-baseline-8bit.js ├── driver-jpeg-lossless-sel1.js ├── driver-jpeg-ls.js ├── driver-rle.js ├── driver-series.js └── driver-tags.js /.gitattributes: -------------------------------------------------------------------------------- 1 | lib/* linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: testing 2 | on: push 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | matrix: 8 | node-version: [20.x, 18.x, 16.x] 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Use Node.js ${{ matrix.node-version }} 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: ${{ matrix.node-version }} 16 | cache: "npm" 17 | - run: npm install 18 | - run: npm run build 19 | # TODO: activate this once a linting setup exists 20 | # - name: Lint 21 | # if: matrix.os != 'windows-latest' 22 | # run: npm run lint 23 | - run: npm run test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .project 3 | .vscode/ 4 | .DS_Store 5 | /build/ 6 | .jshint* 7 | node_modules/ 8 | release.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2012-2023, RII-UTHSCSA 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 6 | * following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following 9 | * disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 12 | * disclaimer in the documentation and/or other materials provided with the distribution. 13 | * 14 | * - Neither the name of the RII-UTHSCSA nor the names of its contributors may be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Daikon 2 | ====== 3 | 4 | Daikon is a pure JavaScript DICOM reader. Here are some of its features: 5 | 6 | - Works in the browser and Node.js environments. 7 | - Parses DICOM headers and reads image data. 8 | - Supports compressed DICOM data. 9 | - Orders and concatenates multi-file image data. 10 | - Supports RGB and Palette data. 11 | - Supports Siemens "Mosaic" image data. 12 | - Parses Siemens CSA header. 13 | 14 | ### Supported Transfer Syntax 15 | 16 | Uncompressed: 17 | - 1.2.840.10008.1.2 (Implicit VR Little Endian) 18 | - 1.2.840.10008.1.2.1 (Explicit VR Little Endian) 19 | - 1.2.840.10008.1.2.2 (Explicit VR Big Endian) 20 | 21 | Compressed: 22 | - 1.2.840.10008.1.2.1.99 (Deflated Explicit VR Little Endian) 23 | - 1.2.840.10008.1.2.4.50 (JPEG Baseline (Process 1) Lossy JPEG 8-bit) 24 | - 1.2.840.10008.1.2.4.51 (JPEG Baseline (Processes 2 & 4) Lossy JPEG 12-bit) 25 | - 1.2.840.10008.1.2.4.57 (JPEG Lossless, Nonhierarchical (Processes 14)) 26 | - 1.2.840.10008.1.2.4.70 (JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])) 27 | - 1.2.840.10008.1.2.4.80 (JPEG-LS Image Compression (Lossless Only)) 28 | - 1.2.840.10008.1.2.4.81 (JPEG-LS Image Compression) 29 | - 1.2.840.10008.1.2.4.90 (JPEG 2000 Image Compression (Lossless Only)) 30 | - 1.2.840.10008.1.2.4.91 (JPEG 2000 Image Compression) 31 | - 1.2.840.10008.1.2.5 (RLE Lossless) 32 | 33 | 34 | ### Usage 35 | [API](https://github.com/rii-mango/Daikon/wiki/API) and [more examples](https://github.com/rii-mango/Daikon/tree/master/tests) 36 | 37 | #### Simple Example 38 | ```javascript 39 | daikon.Parser.verbose = true; 40 | var image = daikon.Series.parseImage(data); 41 | var rawData = image.getRawData(); // ArrayBuffer 42 | var interpretedData = image.getInterpretedData(); // Float32Array (handles byte order, datatype, scale, mask) 43 | //var interpretedData = image.getInterpretedData(true); // Array 44 | //var interpretedData = image.getInterpretedData(false, true); // Object with properties: data, min, max, minIndex, maxIndex, numCols, numRows 45 | ``` 46 | 47 | #### Series Example 48 | ```javascript 49 | var series = new daikon.Series(); 50 | var files = fs.readdirSync('./data/volume/'); 51 | 52 | // iterate over files 53 | for (var ctr in files) { 54 | var name = './data/volume/' + files[ctr]; 55 | var buf = fs.readFileSync(name); 56 | 57 | // parse DICOM file 58 | var image = daikon.Series.parseImage(new DataView(toArrayBuffer(buf))); 59 | 60 | if (image === null) { 61 | console.error(daikon.Series.parserError); 62 | } else if (image.hasPixelData()) { 63 | // if it's part of the same series, add it 64 | if ((series.images.length === 0) || 65 | (image.getSeriesId() === series.images[0].getSeriesId())) { 66 | series.addImage(image); 67 | } 68 | } 69 | } 70 | 71 | // order the image files, determines number of frames, etc. 72 | series.buildSeries(); 73 | 74 | // output some header info 75 | console.log("Number of images read is " + series.images.length); 76 | console.log("Each slice is " + series.images[0].getCols() + " x " + series.images[0].getRows()); 77 | console.log("Each voxel is " + series.images[0].getBitsAllocated() + " bits, " + 78 | (series.images[0].littleEndian ? "little" : "big") + " endian"); 79 | 80 | // concat the image data into a single ArrayBuffer 81 | series.concatenateImageData(null, function (imageData) { 82 | console.log("Total image data size is " + imageData.byteLength + " bytes"); 83 | }); 84 | ``` 85 | #### Browser 86 | See [tests/browser.html](https://github.com/rii-mango/Daikon/blob/master/tests/browser.html) for an example. For a more advanced example, see [this class](https://github.com/rii-mango/Papaya/blob/master/src/js/volume/dicom/header-dicom.js) in Papaya. 87 | 88 | ### Install 89 | Get a packaged source file from the [release folder](https://github.com/rii-mango/Daikon/tree/master/release): 90 | 91 | * [daikon.js](https://raw.githubusercontent.com/rii-mango/Daikon/master/release/current/daikon.js) 92 | * [daikon-min.js](https://raw.githubusercontent.com/rii-mango/Daikon/master/release/current/daikon-min.js) 93 | 94 | Or install via [NPM](https://www.npmjs.com/): 95 | 96 | ``` 97 | npm install daikon 98 | ``` 99 | 100 | ### Testing 101 | ``` 102 | npm test 103 | ``` 104 | 105 | ### Building 106 | ``` 107 | npm run build 108 | ``` 109 | This will output daikon.js and daikon-min.js to build/ 110 | 111 | ### Acknowledgments 112 | Daikon makes use of [JPEGLosslessDecoderJS](https://github.com/rii-mango/JPEGLosslessDecoderJS) for JPEG Lossless support as well as the following third-party libraries: 113 | - [g-squared](https://github.com/g-squared/cornerstoneWADOImageLoader) for JPEG Baseline support. 114 | - [image-JPEG2000](https://github.com/OHIF/image-JPEG2000) for JPEG 2000 support. 115 | 116 | Also thanks to these contributors: 117 | - [@DLiblik](https://github.com/DLiblik) 118 | - [@nickhingston](https://github.com/nickhingston) 119 | - [@suoc](https://github.com/suoc) 120 | - [@buddamus](https://github.com/buddamus) 121 | - [@usb-radiology](https://github.com/usb-radiology) 122 | - [@cdrake](https://github.com/cdrake) 123 | 124 | ### Disclaimer 125 | The authors of this software have not sought nor received approval for clinical/diagnostic use of this software library. 126 | -------------------------------------------------------------------------------- /lib/jpeg-baseline.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 notmasteryet 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // - The JPEG specification can be found in the ITU CCITT Recommendation T.81 18 | // (www.w3.org/Graphics/JPEG/itu-t81.pdf) 19 | // - The JFIF specification can be found in the JPEG File Interchange Format 20 | // (www.w3.org/Graphics/JPEG/jfif3.pdf) 21 | // - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters 22 | // in PostScript Level 2, Technical Note #5116 23 | // (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) 24 | 25 | var ColorSpace = {Unkown: 0, Grayscale: 1, AdobeRGB: 2, RGB: 3, CYMK: 4}; 26 | var JpegImage = (function jpegImage() { 27 | "use strict"; 28 | var dctZigZag = new Int32Array([ 29 | 0, 30 | 1, 8, 31 | 16, 9, 2, 32 | 3, 10, 17, 24, 33 | 32, 25, 18, 11, 4, 34 | 5, 12, 19, 26, 33, 40, 35 | 48, 41, 34, 27, 20, 13, 6, 36 | 7, 14, 21, 28, 35, 42, 49, 56, 37 | 57, 50, 43, 36, 29, 22, 15, 38 | 23, 30, 37, 44, 51, 58, 39 | 59, 52, 45, 38, 31, 40 | 39, 46, 53, 60, 41 | 61, 54, 47, 42 | 55, 62, 43 | 63 44 | ]); 45 | 46 | var dctCos1 = 4017; // cos(pi/16) 47 | var dctSin1 = 799; // sin(pi/16) 48 | var dctCos3 = 3406; // cos(3*pi/16) 49 | var dctSin3 = 2276; // sin(3*pi/16) 50 | var dctCos6 = 1567; // cos(6*pi/16) 51 | var dctSin6 = 3784; // sin(6*pi/16) 52 | var dctSqrt2 = 5793; // sqrt(2) 53 | var dctSqrt1d2 = 2896; // sqrt(2) / 2 54 | 55 | function constructor() { 56 | } 57 | 58 | function buildHuffmanTable(codeLengths, values) { 59 | var k = 0, code = [], i, j, length = 16; 60 | while (length > 0 && !codeLengths[length - 1]) 61 | length--; 62 | code.push({children: [], index: 0}); 63 | var p = code[0], q; 64 | for (i = 0; i < length; i++) { 65 | for (j = 0; j < codeLengths[i]; j++) { 66 | p = code.pop(); 67 | p.children[p.index] = values[k]; 68 | while (p.index > 0) { 69 | p = code.pop(); 70 | } 71 | p.index++; 72 | code.push(p); 73 | while (code.length <= i) { 74 | code.push(q = {children: [], index: 0}); 75 | p.children[p.index] = q.children; 76 | p = q; 77 | } 78 | k++; 79 | } 80 | if (i + 1 < length) { 81 | // p here points to last code 82 | code.push(q = {children: [], index: 0}); 83 | p.children[p.index] = q.children; 84 | p = q; 85 | } 86 | } 87 | return code[0].children; 88 | } 89 | 90 | function getBlockBufferOffset(component, row, col) { 91 | return 64 * ((component.blocksPerLine + 1) * row + col); 92 | } 93 | 94 | function decodeScan(data, offset, 95 | frame, components, resetInterval, 96 | spectralStart, spectralEnd, 97 | successivePrev, successive) { 98 | var precision = frame.precision; 99 | var samplesPerLine = frame.samplesPerLine; 100 | var scanLines = frame.scanLines; 101 | var mcusPerLine = frame.mcusPerLine; 102 | var progressive = frame.progressive; 103 | var maxH = frame.maxH, maxV = frame.maxV; 104 | 105 | var startOffset = offset, bitsData = 0, bitsCount = 0; 106 | 107 | function readBit() { 108 | if (bitsCount > 0) { 109 | bitsCount--; 110 | return (bitsData >> bitsCount) & 1; 111 | } 112 | bitsData = data[offset++]; 113 | if (bitsData == 0xFF) { 114 | var nextByte = data[offset++]; 115 | if (nextByte) { 116 | throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); 117 | } 118 | // unstuff 0 119 | } 120 | bitsCount = 7; 121 | return bitsData >>> 7; 122 | } 123 | 124 | function decodeHuffman(tree) { 125 | var node = tree; 126 | var bit; 127 | while ((bit = readBit()) !== null) { 128 | node = node[bit]; 129 | if (typeof node === 'number') 130 | return node; 131 | if (typeof node !== 'object') 132 | throw "invalid huffman sequence"; 133 | } 134 | return null; 135 | } 136 | 137 | function receive(length) { 138 | var n = 0; 139 | while (length > 0) { 140 | var bit = readBit(); 141 | if (bit === null) 142 | return; 143 | n = (n << 1) | bit; 144 | length--; 145 | } 146 | return n; 147 | } 148 | 149 | function receiveAndExtend(length) { 150 | var n = receive(length); 151 | if (n >= 1 << (length - 1)) 152 | return n; 153 | return n + (-1 << length) + 1; 154 | } 155 | 156 | function decodeBaseline(component, offset) { 157 | var t = decodeHuffman(component.huffmanTableDC); 158 | var diff = t === 0 ? 0 : receiveAndExtend(t); 159 | component.blockData[offset] = (component.pred += diff); 160 | var k = 1; 161 | while (k < 64) { 162 | var rs = decodeHuffman(component.huffmanTableAC); 163 | var s = rs & 15, r = rs >> 4; 164 | if (s === 0) { 165 | if (r < 15) 166 | break; 167 | k += 16; 168 | continue; 169 | } 170 | k += r; 171 | var z = dctZigZag[k]; 172 | component.blockData[offset + z] = receiveAndExtend(s); 173 | k++; 174 | } 175 | } 176 | 177 | function decodeDCFirst(component, offset) { 178 | var t = decodeHuffman(component.huffmanTableDC); 179 | var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); 180 | component.blockData[offset] = (component.pred += diff); 181 | } 182 | 183 | function decodeDCSuccessive(component, offset) { 184 | component.blockData[offset] |= readBit() << successive; 185 | } 186 | 187 | var eobrun = 0; 188 | function decodeACFirst(component, offset) { 189 | if (eobrun > 0) { 190 | eobrun--; 191 | return; 192 | } 193 | var k = spectralStart, e = spectralEnd; 194 | while (k <= e) { 195 | var rs = decodeHuffman(component.huffmanTableAC); 196 | var s = rs & 15, r = rs >> 4; 197 | if (s === 0) { 198 | if (r < 15) { 199 | eobrun = receive(r) + (1 << r) - 1; 200 | break; 201 | } 202 | k += 16; 203 | continue; 204 | } 205 | k += r; 206 | var z = dctZigZag[k]; 207 | component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive); 208 | k++; 209 | } 210 | } 211 | 212 | var successiveACState = 0, successiveACNextValue; 213 | function decodeACSuccessive(component, offset) { 214 | var k = spectralStart, e = spectralEnd, r = 0; 215 | while (k <= e) { 216 | var z = dctZigZag[k]; 217 | switch (successiveACState) { 218 | case 0: // initial state 219 | var rs = decodeHuffman(component.huffmanTableAC); 220 | var s = rs & 15; 221 | r = rs >> 4; 222 | if (s === 0) { 223 | if (r < 15) { 224 | eobrun = receive(r) + (1 << r); 225 | successiveACState = 4; 226 | } else { 227 | r = 16; 228 | successiveACState = 1; 229 | } 230 | } else { 231 | if (s !== 1) 232 | throw "invalid ACn encoding"; 233 | successiveACNextValue = receiveAndExtend(s); 234 | successiveACState = r ? 2 : 3; 235 | } 236 | continue; 237 | case 1: // skipping r zero items 238 | case 2: 239 | if (component.blockData[offset + z]) { 240 | component.blockData[offset + z] += (readBit() << successive); 241 | } else { 242 | r--; 243 | if (r === 0) 244 | successiveACState = successiveACState == 2 ? 3 : 0; 245 | } 246 | break; 247 | case 3: // set value for a zero item 248 | if (component.blockData[offset + z]) { 249 | component.blockData[offset + z] += (readBit() << successive); 250 | } else { 251 | component.blockData[offset + z] = successiveACNextValue << successive; 252 | successiveACState = 0; 253 | } 254 | break; 255 | case 4: // eob 256 | if (component.blockData[offset + z]) { 257 | component.blockData[offset + z] += (readBit() << successive); 258 | } 259 | break; 260 | } 261 | k++; 262 | } 263 | if (successiveACState === 4) { 264 | eobrun--; 265 | if (eobrun === 0) 266 | successiveACState = 0; 267 | } 268 | } 269 | 270 | function decodeMcu(component, decode, mcu, row, col) { 271 | var mcuRow = (mcu / mcusPerLine) | 0; 272 | var mcuCol = mcu % mcusPerLine; 273 | var blockRow = mcuRow * component.v + row; 274 | var blockCol = mcuCol * component.h + col; 275 | var offset = getBlockBufferOffset(component, blockRow, blockCol); 276 | decode(component, offset); 277 | } 278 | 279 | function decodeBlock(component, decode, mcu) { 280 | var blockRow = (mcu / component.blocksPerLine) | 0; 281 | var blockCol = mcu % component.blocksPerLine; 282 | var offset = getBlockBufferOffset(component, blockRow, blockCol); 283 | decode(component, offset); 284 | } 285 | 286 | var componentsLength = components.length; 287 | var component, i, j, k, n; 288 | var decodeFn; 289 | if (progressive) { 290 | if (spectralStart === 0) 291 | decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; 292 | else 293 | decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; 294 | } else { 295 | decodeFn = decodeBaseline; 296 | } 297 | 298 | var mcu = 0, marker; 299 | var mcuExpected; 300 | if (componentsLength == 1) { 301 | mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; 302 | } else { 303 | mcuExpected = mcusPerLine * frame.mcusPerColumn; 304 | } 305 | if (!resetInterval) { 306 | resetInterval = mcuExpected; 307 | } 308 | 309 | var h, v; 310 | while (mcu < mcuExpected) { 311 | // reset interval stuff 312 | for (i = 0; i < componentsLength; i++) { 313 | components[i].pred = 0; 314 | } 315 | eobrun = 0; 316 | 317 | if (componentsLength == 1) { 318 | component = components[0]; 319 | for (n = 0; n < resetInterval; n++) { 320 | decodeBlock(component, decodeFn, mcu); 321 | mcu++; 322 | } 323 | } else { 324 | for (n = 0; n < resetInterval; n++) { 325 | for (i = 0; i < componentsLength; i++) { 326 | component = components[i]; 327 | h = component.h; 328 | v = component.v; 329 | for (j = 0; j < v; j++) { 330 | for (k = 0; k < h; k++) { 331 | decodeMcu(component, decodeFn, mcu, j, k); 332 | } 333 | } 334 | } 335 | mcu++; 336 | } 337 | } 338 | 339 | // find marker 340 | bitsCount = 0; 341 | marker = (data[offset] << 8) | data[offset + 1]; 342 | if (marker <= 0xFF00) { 343 | throw "marker was not found"; 344 | } 345 | 346 | if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx 347 | offset += 2; 348 | } else { 349 | break; 350 | } 351 | } 352 | 353 | return offset - startOffset; 354 | } 355 | 356 | // A port of poppler's IDCT method which in turn is taken from: 357 | // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, 358 | // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", 359 | // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 360 | // 988-991. 361 | function quantizeAndInverse(component, blockBufferOffset, p) { 362 | var qt = component.quantizationTable; 363 | var v0, v1, v2, v3, v4, v5, v6, v7, t; 364 | var i; 365 | 366 | // dequant 367 | for (i = 0; i < 64; i++) { 368 | p[i] = component.blockData[blockBufferOffset + i] * qt[i]; 369 | } 370 | 371 | // inverse DCT on rows 372 | for (i = 0; i < 8; ++i) { 373 | var row = 8 * i; 374 | 375 | // check for all-zero AC coefficients 376 | if (p[1 + row] === 0 && p[2 + row] === 0 && p[3 + row] === 0 && 377 | p[4 + row] === 0 && p[5 + row] === 0 && p[6 + row] === 0 && 378 | p[7 + row] === 0) { 379 | t = (dctSqrt2 * p[0 + row] + 512) >> 10; 380 | p[0 + row] = t; 381 | p[1 + row] = t; 382 | p[2 + row] = t; 383 | p[3 + row] = t; 384 | p[4 + row] = t; 385 | p[5 + row] = t; 386 | p[6 + row] = t; 387 | p[7 + row] = t; 388 | continue; 389 | } 390 | 391 | // stage 4 392 | v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; 393 | v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; 394 | v2 = p[2 + row]; 395 | v3 = p[6 + row]; 396 | v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; 397 | v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; 398 | v5 = p[3 + row] << 4; 399 | v6 = p[5 + row] << 4; 400 | 401 | // stage 3 402 | t = (v0 - v1 + 1) >> 1; 403 | v0 = (v0 + v1 + 1) >> 1; 404 | v1 = t; 405 | t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; 406 | v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; 407 | v3 = t; 408 | t = (v4 - v6 + 1) >> 1; 409 | v4 = (v4 + v6 + 1) >> 1; 410 | v6 = t; 411 | t = (v7 + v5 + 1) >> 1; 412 | v5 = (v7 - v5 + 1) >> 1; 413 | v7 = t; 414 | 415 | // stage 2 416 | t = (v0 - v3 + 1) >> 1; 417 | v0 = (v0 + v3 + 1) >> 1; 418 | v3 = t; 419 | t = (v1 - v2 + 1) >> 1; 420 | v1 = (v1 + v2 + 1) >> 1; 421 | v2 = t; 422 | t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; 423 | v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; 424 | v7 = t; 425 | t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; 426 | v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; 427 | v6 = t; 428 | 429 | // stage 1 430 | p[0 + row] = v0 + v7; 431 | p[7 + row] = v0 - v7; 432 | p[1 + row] = v1 + v6; 433 | p[6 + row] = v1 - v6; 434 | p[2 + row] = v2 + v5; 435 | p[5 + row] = v2 - v5; 436 | p[3 + row] = v3 + v4; 437 | p[4 + row] = v3 - v4; 438 | } 439 | 440 | // inverse DCT on columns 441 | for (i = 0; i < 8; ++i) { 442 | var col = i; 443 | 444 | // check for all-zero AC coefficients 445 | if (p[1 * 8 + col] === 0 && p[2 * 8 + col] === 0 && p[3 * 8 + col] === 0 && 446 | p[4 * 8 + col] === 0 && p[5 * 8 + col] === 0 && p[6 * 8 + col] === 0 && 447 | p[7 * 8 + col] === 0) { 448 | t = (dctSqrt2 * p[i + 0] + 8192) >> 14; 449 | p[0 * 8 + col] = t; 450 | p[1 * 8 + col] = t; 451 | p[2 * 8 + col] = t; 452 | p[3 * 8 + col] = t; 453 | p[4 * 8 + col] = t; 454 | p[5 * 8 + col] = t; 455 | p[6 * 8 + col] = t; 456 | p[7 * 8 + col] = t; 457 | continue; 458 | } 459 | 460 | // stage 4 461 | v0 = (dctSqrt2 * p[0 * 8 + col] + 2048) >> 12; 462 | v1 = (dctSqrt2 * p[4 * 8 + col] + 2048) >> 12; 463 | v2 = p[2 * 8 + col]; 464 | v3 = p[6 * 8 + col]; 465 | v4 = (dctSqrt1d2 * (p[1 * 8 + col] - p[7 * 8 + col]) + 2048) >> 12; 466 | v7 = (dctSqrt1d2 * (p[1 * 8 + col] + p[7 * 8 + col]) + 2048) >> 12; 467 | v5 = p[3 * 8 + col]; 468 | v6 = p[5 * 8 + col]; 469 | 470 | // stage 3 471 | t = (v0 - v1 + 1) >> 1; 472 | v0 = (v0 + v1 + 1) >> 1; 473 | v1 = t; 474 | t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; 475 | v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; 476 | v3 = t; 477 | t = (v4 - v6 + 1) >> 1; 478 | v4 = (v4 + v6 + 1) >> 1; 479 | v6 = t; 480 | t = (v7 + v5 + 1) >> 1; 481 | v5 = (v7 - v5 + 1) >> 1; 482 | v7 = t; 483 | 484 | // stage 2 485 | t = (v0 - v3 + 1) >> 1; 486 | v0 = (v0 + v3 + 1) >> 1; 487 | v3 = t; 488 | t = (v1 - v2 + 1) >> 1; 489 | v1 = (v1 + v2 + 1) >> 1; 490 | v2 = t; 491 | t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; 492 | v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; 493 | v7 = t; 494 | t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; 495 | v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; 496 | v6 = t; 497 | 498 | // stage 1 499 | p[0 * 8 + col] = v0 + v7; 500 | p[7 * 8 + col] = v0 - v7; 501 | p[1 * 8 + col] = v1 + v6; 502 | p[6 * 8 + col] = v1 - v6; 503 | p[2 * 8 + col] = v2 + v5; 504 | p[5 * 8 + col] = v2 - v5; 505 | p[3 * 8 + col] = v3 + v4; 506 | p[4 * 8 + col] = v3 - v4; 507 | } 508 | 509 | // convert to 8-bit integers 510 | for (i = 0; i < 64; ++i) { 511 | var index = blockBufferOffset + i; 512 | var q = p[i]; 513 | q = (q <= -2056 / component.bitConversion) ? 0 : 514 | (q >= 2024 / component.bitConversion) ? 255 / component.bitConversion : 515 | (q + 2056 / component.bitConversion) >> 4; 516 | component.blockData[index] = q; 517 | } 518 | } 519 | 520 | function buildComponentData(frame, component) { 521 | var lines = []; 522 | var blocksPerLine = component.blocksPerLine; 523 | var blocksPerColumn = component.blocksPerColumn; 524 | var samplesPerLine = blocksPerLine << 3; 525 | var computationBuffer = new Int32Array(64); 526 | 527 | var i, j, ll = 0; 528 | for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { 529 | for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { 530 | var offset = getBlockBufferOffset(component, blockRow, blockCol); 531 | quantizeAndInverse(component, offset, computationBuffer); 532 | } 533 | } 534 | return component.blockData; 535 | } 536 | 537 | function clampToUint8(a) { 538 | return a <= 0 ? 0 : a >= 255 ? 255 : a | 0; 539 | } 540 | 541 | constructor.prototype = { 542 | load: function load(path) { 543 | var handleData = (function (data) { 544 | this.parse(data); 545 | if (this.onload) 546 | this.onload(); 547 | }).bind(this); 548 | 549 | if (path.indexOf("data:") > -1) { 550 | var offset = path.indexOf("base64,") + 7; 551 | var data = atob(path.substring(offset)); 552 | var arr = new Uint8Array(data.length); 553 | for (var i = data.length - 1; i >= 0; i--) { 554 | arr[i] = data.charCodeAt(i); 555 | } 556 | handleData(data); 557 | } else { 558 | var xhr = new XMLHttpRequest(); 559 | xhr.open("GET", path, true); 560 | xhr.responseType = "arraybuffer"; 561 | xhr.onload = (function () { 562 | // TODO catch parse error 563 | var data = new Uint8Array(xhr.response); 564 | handleData(data); 565 | }).bind(this); 566 | xhr.send(null); 567 | } 568 | }, 569 | parse: function parse(data) { 570 | 571 | function readUint16() { 572 | var value = (data[offset] << 8) | data[offset + 1]; 573 | offset += 2; 574 | return value; 575 | } 576 | 577 | function readDataBlock() { 578 | var length = readUint16(); 579 | var array = data.subarray(offset, offset + length - 2); 580 | offset += array.length; 581 | return array; 582 | } 583 | 584 | function prepareComponents(frame) { 585 | var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); 586 | var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); 587 | for (var i = 0; i < frame.components.length; i++) { 588 | component = frame.components[i]; 589 | var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH); 590 | var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV); 591 | var blocksPerLineForMcu = mcusPerLine * component.h; 592 | var blocksPerColumnForMcu = mcusPerColumn * component.v; 593 | 594 | var blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); 595 | component.blockData = new Int16Array(blocksBufferSize); 596 | component.blocksPerLine = blocksPerLine; 597 | component.blocksPerColumn = blocksPerColumn; 598 | } 599 | frame.mcusPerLine = mcusPerLine; 600 | frame.mcusPerColumn = mcusPerColumn; 601 | } 602 | 603 | var offset = 0, length = data.length; 604 | var jfif = null; 605 | var adobe = null; 606 | var pixels = null; 607 | var frame, resetInterval; 608 | var quantizationTables = []; 609 | var huffmanTablesAC = [], huffmanTablesDC = []; 610 | var fileMarker = readUint16(); 611 | if (fileMarker != 0xFFD8) { // SOI (Start of Image) 612 | throw "SOI not found"; 613 | } 614 | 615 | fileMarker = readUint16(); 616 | while (fileMarker != 0xFFD9) { // EOI (End of image) 617 | var i, j, l; 618 | switch (fileMarker) { 619 | case 0xFFE0: // APP0 (Application Specific) 620 | case 0xFFE1: // APP1 621 | case 0xFFE2: // APP2 622 | case 0xFFE3: // APP3 623 | case 0xFFE4: // APP4 624 | case 0xFFE5: // APP5 625 | case 0xFFE6: // APP6 626 | case 0xFFE7: // APP7 627 | case 0xFFE8: // APP8 628 | case 0xFFE9: // APP9 629 | case 0xFFEA: // APP10 630 | case 0xFFEB: // APP11 631 | case 0xFFEC: // APP12 632 | case 0xFFED: // APP13 633 | case 0xFFEE: // APP14 634 | case 0xFFEF: // APP15 635 | case 0xFFFE: // COM (Comment) 636 | var appData = readDataBlock(); 637 | 638 | if (fileMarker === 0xFFE0) { 639 | if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && 640 | appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' 641 | jfif = { 642 | version: {major: appData[5], minor: appData[6]}, 643 | densityUnits: appData[7], 644 | xDensity: (appData[8] << 8) | appData[9], 645 | yDensity: (appData[10] << 8) | appData[11], 646 | thumbWidth: appData[12], 647 | thumbHeight: appData[13], 648 | thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) 649 | }; 650 | } 651 | } 652 | // TODO APP1 - Exif 653 | if (fileMarker === 0xFFEE) { 654 | if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && 655 | appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' 656 | adobe = { 657 | version: appData[6], 658 | flags0: (appData[7] << 8) | appData[8], 659 | flags1: (appData[9] << 8) | appData[10], 660 | transformCode: appData[11] 661 | }; 662 | } 663 | } 664 | break; 665 | 666 | case 0xFFDB: // DQT (Define Quantization Tables) 667 | var quantizationTablesLength = readUint16(); 668 | var quantizationTablesEnd = quantizationTablesLength + offset - 2; 669 | while (offset < quantizationTablesEnd) { 670 | var quantizationTableSpec = data[offset++]; 671 | var tableData = new Int32Array(64); 672 | if ((quantizationTableSpec >> 4) === 0) { // 8 bit values 673 | for (j = 0; j < 64; j++) { 674 | var z = dctZigZag[j]; 675 | tableData[z] = data[offset++]; 676 | } 677 | } else if ((quantizationTableSpec >> 4) === 1) { //16 bit 678 | for (j = 0; j < 64; j++) { 679 | var zz = dctZigZag[j]; 680 | tableData[zz] = readUint16(); 681 | } 682 | } else 683 | throw "DQT: invalid table spec"; 684 | quantizationTables[quantizationTableSpec & 15] = tableData; 685 | } 686 | break; 687 | 688 | case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) 689 | case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) 690 | case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) 691 | if (frame) { 692 | throw "Only single frame JPEGs supported"; 693 | } 694 | readUint16(); // skip data length 695 | frame = {}; 696 | frame.extended = (fileMarker === 0xFFC1); 697 | frame.progressive = (fileMarker === 0xFFC2); 698 | frame.precision = data[offset++]; 699 | frame.scanLines = readUint16(); 700 | frame.samplesPerLine = readUint16(); 701 | frame.components = []; 702 | frame.componentIds = {}; 703 | var componentsCount = data[offset++], componentId; 704 | var maxH = 0, maxV = 0; 705 | for (i = 0; i < componentsCount; i++) { 706 | componentId = data[offset]; 707 | var h = data[offset + 1] >> 4; 708 | var v = data[offset + 1] & 15; 709 | if (maxH < h) 710 | maxH = h; 711 | if (maxV < v) 712 | maxV = v; 713 | var qId = data[offset + 2]; 714 | l = frame.components.push({ 715 | h: h, 716 | v: v, 717 | quantizationTable: quantizationTables[qId], 718 | quantizationTableId: qId, 719 | bitConversion: 255 / ((1 << frame.precision) - 1) 720 | }); 721 | frame.componentIds[componentId] = l - 1; 722 | offset += 3; 723 | } 724 | frame.maxH = maxH; 725 | frame.maxV = maxV; 726 | prepareComponents(frame); 727 | break; 728 | 729 | case 0xFFC4: // DHT (Define Huffman Tables) 730 | var huffmanLength = readUint16(); 731 | for (i = 2; i < huffmanLength; ) { 732 | var huffmanTableSpec = data[offset++]; 733 | var codeLengths = new Uint8Array(16); 734 | var codeLengthSum = 0; 735 | for (j = 0; j < 16; j++, offset++) 736 | codeLengthSum += (codeLengths[j] = data[offset]); 737 | var huffmanValues = new Uint8Array(codeLengthSum); 738 | for (j = 0; j < codeLengthSum; j++, offset++) 739 | huffmanValues[j] = data[offset]; 740 | i += 17 + codeLengthSum; 741 | 742 | ((huffmanTableSpec >> 4) === 0 ? 743 | huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = 744 | buildHuffmanTable(codeLengths, huffmanValues); 745 | } 746 | break; 747 | 748 | case 0xFFDD: // DRI (Define Restart Interval) 749 | readUint16(); // skip data length 750 | resetInterval = readUint16(); 751 | break; 752 | 753 | case 0xFFDA: // SOS (Start of Scan) 754 | var scanLength = readUint16(); 755 | var selectorsCount = data[offset++]; 756 | var components = [], component; 757 | for (i = 0; i < selectorsCount; i++) { 758 | var componentIndex = frame.componentIds[data[offset++]]; 759 | component = frame.components[componentIndex]; 760 | var tableSpec = data[offset++]; 761 | component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; 762 | component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; 763 | components.push(component); 764 | } 765 | var spectralStart = data[offset++]; 766 | var spectralEnd = data[offset++]; 767 | var successiveApproximation = data[offset++]; 768 | var processed = decodeScan(data, offset, 769 | frame, components, resetInterval, 770 | spectralStart, spectralEnd, 771 | successiveApproximation >> 4, successiveApproximation & 15); 772 | offset += processed; 773 | break; 774 | default: 775 | if (data[offset - 3] == 0xFF && 776 | data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { 777 | // could be incorrect encoding -- last 0xFF byte of the previous 778 | // block was eaten by the encoder 779 | offset -= 3; 780 | break; 781 | } 782 | throw "unknown JPEG marker " + fileMarker.toString(16); 783 | } 784 | fileMarker = readUint16(); 785 | } 786 | 787 | this.width = frame.samplesPerLine; 788 | this.height = frame.scanLines; 789 | this.jfif = jfif; 790 | this.adobe = adobe; 791 | this.components = []; 792 | switch (frame.components.length) 793 | { 794 | case 1: 795 | this.colorspace = ColorSpace.Grayscale; 796 | break; 797 | case 3: 798 | if (this.adobe) 799 | this.colorspace = ColorSpace.AdobeRGB; 800 | else 801 | this.colorspace = ColorSpace.RGB; 802 | break; 803 | case 4: 804 | this.colorspace = ColorSpace.CYMK; 805 | break; 806 | default: 807 | this.colorspace = ColorSpace.Unknown; 808 | } 809 | for (var i = 0; i < frame.components.length; i++) { 810 | var component = frame.components[i]; 811 | if (!component.quantizationTable && component.quantizationTableId !== null) 812 | component.quantizationTable = quantizationTables[component.quantizationTableId]; 813 | this.components.push({ 814 | output: buildComponentData(frame, component), 815 | scaleX: component.h / frame.maxH, 816 | scaleY: component.v / frame.maxV, 817 | blocksPerLine: component.blocksPerLine, 818 | blocksPerColumn: component.blocksPerColumn, 819 | bitConversion: component.bitConversion 820 | }); 821 | } 822 | }, 823 | getData16: function getData16(width, height) { 824 | if (this.components.length !== 1) 825 | throw 'Unsupported color mode'; 826 | var scaleX = this.width / width, scaleY = this.height / height; 827 | 828 | var component, componentScaleX, componentScaleY; 829 | var x, y, i; 830 | var offset = 0; 831 | var numComponents = this.components.length; 832 | var dataLength = width * height * numComponents; 833 | var data = new Uint16Array(dataLength); 834 | var componentLine; 835 | 836 | // lineData is reused for all components. Assume first component is 837 | // the biggest 838 | var lineData = new Uint16Array((this.components[0].blocksPerLine << 3) * 839 | this.components[0].blocksPerColumn * 8); 840 | 841 | // First construct image data ... 842 | for (i = 0; i < numComponents; i++) { 843 | component = this.components[i]; 844 | var blocksPerLine = component.blocksPerLine; 845 | var blocksPerColumn = component.blocksPerColumn; 846 | var samplesPerLine = blocksPerLine << 3; 847 | 848 | var j, k, ll = 0; 849 | var lineOffset = 0; 850 | for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { 851 | var scanLine = blockRow << 3; 852 | for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { 853 | var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol); 854 | var offset = 0, sample = blockCol << 3; 855 | for (j = 0; j < 8; j++) { 856 | var lineOffset = (scanLine + j) * samplesPerLine; 857 | for (k = 0; k < 8; k++) { 858 | lineData[lineOffset + sample + k] = 859 | component.output[bufferOffset + offset++]; 860 | } 861 | } 862 | } 863 | } 864 | 865 | componentScaleX = component.scaleX * scaleX; 866 | componentScaleY = component.scaleY * scaleY; 867 | offset = i; 868 | 869 | var cx, cy; 870 | var index; 871 | for (y = 0; y < height; y++) { 872 | for (x = 0; x < width; x++) { 873 | cy = 0 | (y * componentScaleY); 874 | cx = 0 | (x * componentScaleX); 875 | index = cy * samplesPerLine + cx; 876 | data[offset] = lineData[index]; 877 | offset += numComponents; 878 | } 879 | } 880 | } 881 | return data; 882 | }, 883 | getData: function getData(width, height) { 884 | var scaleX = this.width / width, scaleY = this.height / height; 885 | 886 | var component, componentScaleX, componentScaleY; 887 | var x, y, i; 888 | var offset = 0; 889 | var Y, Cb, Cr, K, C, M, Ye, R, G, B; 890 | var colorTransform; 891 | var numComponents = this.components.length; 892 | var dataLength = width * height * numComponents; 893 | var data = new Uint8Array(dataLength); 894 | var componentLine; 895 | 896 | // lineData is reused for all components. Assume first component is 897 | // the biggest 898 | var lineData = new Uint8Array((this.components[0].blocksPerLine << 3) * 899 | this.components[0].blocksPerColumn * 8); 900 | 901 | // First construct image data ... 902 | for (i = 0; i < numComponents; i++) { 903 | component = this.components[i]; 904 | var blocksPerLine = component.blocksPerLine; 905 | var blocksPerColumn = component.blocksPerColumn; 906 | var samplesPerLine = blocksPerLine << 3; 907 | 908 | var j, k, ll = 0; 909 | var lineOffset = 0; 910 | for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { 911 | var scanLine = blockRow << 3; 912 | for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { 913 | var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol); 914 | var offset = 0, sample = blockCol << 3; 915 | for (j = 0; j < 8; j++) { 916 | var lineOffset = (scanLine + j) * samplesPerLine; 917 | for (k = 0; k < 8; k++) { 918 | lineData[lineOffset + sample + k] = 919 | component.output[bufferOffset + offset++] * component.bitConversion; 920 | } 921 | } 922 | } 923 | } 924 | 925 | componentScaleX = component.scaleX * scaleX; 926 | componentScaleY = component.scaleY * scaleY; 927 | offset = i; 928 | 929 | var cx, cy; 930 | var index; 931 | for (y = 0; y < height; y++) { 932 | for (x = 0; x < width; x++) { 933 | cy = 0 | (y * componentScaleY); 934 | cx = 0 | (x * componentScaleX); 935 | index = cy * samplesPerLine + cx; 936 | data[offset] = lineData[index]; 937 | offset += numComponents; 938 | } 939 | } 940 | } 941 | 942 | // ... then transform colors, if necessary 943 | switch (numComponents) { 944 | case 1: 945 | case 2: 946 | break; 947 | // no color conversion for one or two compoenents 948 | 949 | case 3: 950 | // The default transform for three components is true 951 | colorTransform = true; 952 | // The adobe transform marker overrides any previous setting 953 | if (this.adobe && this.adobe.transformCode) 954 | colorTransform = true; 955 | else if (typeof this.colorTransform !== 'undefined') 956 | colorTransform = !!this.colorTransform; 957 | 958 | if (colorTransform) { 959 | for (i = 0; i < dataLength; i += numComponents) { 960 | Y = data[i ]; 961 | Cb = data[i + 1]; 962 | Cr = data[i + 2]; 963 | 964 | R = clampToUint8(Y - 179.456 + 1.402 * Cr); 965 | G = clampToUint8(Y + 135.459 - 0.344 * Cb - 0.714 * Cr); 966 | B = clampToUint8(Y - 226.816 + 1.772 * Cb); 967 | 968 | data[i ] = R; 969 | data[i + 1] = G; 970 | data[i + 2] = B; 971 | } 972 | } 973 | break; 974 | case 4: 975 | if (!this.adobe) 976 | throw 'Unsupported color mode (4 components)'; 977 | // The default transform for four components is false 978 | colorTransform = false; 979 | // The adobe transform marker overrides any previous setting 980 | if (this.adobe && this.adobe.transformCode) 981 | colorTransform = true; 982 | else if (typeof this.colorTransform !== 'undefined') 983 | colorTransform = !!this.colorTransform; 984 | 985 | if (colorTransform) { 986 | for (i = 0; i < dataLength; i += numComponents) { 987 | Y = data[i]; 988 | Cb = data[i + 1]; 989 | Cr = data[i + 2]; 990 | 991 | C = clampToUint8(434.456 - Y - 1.402 * Cr); 992 | M = clampToUint8(119.541 - Y + 0.344 * Cb + 0.714 * Cr); 993 | Y = clampToUint8(481.816 - Y - 1.772 * Cb); 994 | 995 | data[i ] = C; 996 | data[i + 1] = M; 997 | data[i + 2] = Y; 998 | // K is unchanged 999 | } 1000 | } 1001 | break; 1002 | default: 1003 | throw 'Unsupported color mode'; 1004 | } 1005 | return data; 1006 | } 1007 | }; 1008 | 1009 | return constructor; 1010 | })(); 1011 | var moduleType = typeof module; 1012 | if ((moduleType !== 'undefined') && module.exports) { 1013 | module.exports = { 1014 | JpegImage: JpegImage 1015 | }; 1016 | } -------------------------------------------------------------------------------- /lib/jpeg-ls.js: -------------------------------------------------------------------------------- 1 | // Adapted from: https://github.com/chafey/cornerstoneWADOImageLoader/blob/73ed7c4bbbd275bb0f7f9f363ef82575c17bb5f1/src/webWorker/decodeTask/decoders/decodeJPEGLS.js 2 | /*! 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2014 Chris Hafey (chafey@gmail.com) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | "use strict"; 27 | 28 | var CharLS = CharLS || ((typeof require !== 'undefined') ? require('../lib/charLS-DynamicMemory-browser.js') : null); 29 | 30 | var JpegLS = (function () { 31 | var charLS; 32 | 33 | function constructor() { 34 | } 35 | 36 | constructor.prototype = { 37 | decodeJPEGLS: function(pixelData, signed) { 38 | return decodeJPEGLS(pixelData, signed); 39 | } 40 | }; 41 | 42 | function jpegLSDecode(data, isSigned) { 43 | // prepare input parameters 44 | var dataPtr = charLS._malloc(data.length); 45 | charLS.writeArrayToMemory(data, dataPtr); 46 | 47 | // prepare output parameters 48 | var imagePtrPtr=charLS._malloc(4); 49 | var imageSizePtr=charLS._malloc(4); 50 | var widthPtr=charLS._malloc(4); 51 | var heightPtr=charLS._malloc(4); 52 | var bitsPerSamplePtr=charLS._malloc(4); 53 | var stridePtr=charLS._malloc(4); 54 | var allowedLossyErrorPtr =charLS._malloc(4); 55 | var componentsPtr=charLS._malloc(4); 56 | var interleaveModePtr=charLS._malloc(4); 57 | 58 | // Decode the image 59 | var result = charLS.ccall( 60 | 'jpegls_decode', 61 | 'number', 62 | ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'], 63 | [dataPtr, data.length, imagePtrPtr, imageSizePtr, widthPtr, heightPtr, bitsPerSamplePtr, stridePtr, componentsPtr, allowedLossyErrorPtr, interleaveModePtr] 64 | ); 65 | 66 | // Extract result values into object 67 | var image = { 68 | result : result, 69 | width : charLS.getValue(widthPtr,'i32'), 70 | height : charLS.getValue(heightPtr,'i32'), 71 | bitsPerSample : charLS.getValue(bitsPerSamplePtr,'i32'), 72 | stride : charLS.getValue(stridePtr,'i32'), 73 | components : charLS.getValue(componentsPtr, 'i32'), 74 | allowedLossyError : charLS.getValue(allowedLossyErrorPtr, 'i32'), 75 | interleaveMode: charLS.getValue(interleaveModePtr, 'i32'), 76 | pixelData: undefined 77 | }; 78 | 79 | // Copy image from emscripten heap into appropriate array buffer type 80 | var imagePtr = charLS.getValue(imagePtrPtr, '*'); 81 | if(image.bitsPerSample <= 8) { 82 | image.pixelData = new Uint8Array(image.width * image.height * image.components); 83 | image.pixelData.set(new Uint8Array(charLS.HEAP8.buffer, imagePtr, image.pixelData.length)); 84 | } else { 85 | // I have seen 16 bit signed images, but I don't know if 16 bit unsigned is valid, hoping to get 86 | // answer here: 87 | // https://github.com/team-charls/charls/issues/14 88 | if(isSigned) { 89 | image.pixelData = new Int16Array(image.width * image.height * image.components); 90 | image.pixelData.set(new Int16Array(charLS.HEAP16.buffer, imagePtr, image.pixelData.length)); 91 | } else { 92 | image.pixelData = new Uint16Array(image.width * image.height * image.components); 93 | image.pixelData.set(new Uint16Array(charLS.HEAP16.buffer, imagePtr, image.pixelData.length)); 94 | } 95 | } 96 | 97 | // free memory and return image object 98 | charLS._free(dataPtr); 99 | charLS._free(imagePtr); 100 | charLS._free(imagePtrPtr); 101 | charLS._free(imageSizePtr); 102 | charLS._free(widthPtr); 103 | charLS._free(heightPtr); 104 | charLS._free(bitsPerSamplePtr); 105 | charLS._free(stridePtr); 106 | charLS._free(componentsPtr); 107 | charLS._free(interleaveModePtr); 108 | 109 | return image; 110 | } 111 | 112 | function initializeJPEGLS() { 113 | // check to make sure codec is loaded 114 | if(typeof CharLS === 'undefined') { 115 | throw 'No JPEG-LS decoder loaded'; 116 | } 117 | 118 | // Try to initialize CharLS 119 | // CharLS https://github.com/chafey/charls 120 | if(!charLS) { 121 | charLS = CharLS(); 122 | if(!charLS || !charLS._jpegls_decode) { 123 | throw 'JPEG-LS failed to initialize'; 124 | } 125 | } 126 | } 127 | 128 | function decodeJPEGLS(pixelData, signed) { 129 | initializeJPEGLS(); 130 | 131 | var image = jpegLSDecode(pixelData, signed); 132 | // console.log(image); 133 | 134 | // throw error if not success or too much data 135 | if(image.result !== 0 && image.result !== 6) { 136 | throw 'JPEG-LS decoder failed to decode frame (error code ' + image.result + ')'; 137 | } 138 | 139 | var imageFrame = {}; 140 | imageFrame.columns = image.width; 141 | imageFrame.rows = image.height; 142 | imageFrame.pixelData = image.pixelData; 143 | 144 | return imageFrame; 145 | } 146 | 147 | return constructor; 148 | }()); 149 | var moduleType = typeof module; 150 | if ((moduleType !== 'undefined') && module.exports) { 151 | module.exports = JpegLS; 152 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daikon", 3 | "version": "1.2.46", 4 | "description": "A JavaScript DICOM reader.", 5 | "main": "src/main.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "dependencies": { 10 | "@wearemothership/dicom-character-set": "^1.0.4-opt.1", 11 | "jpeg-lossless-decoder-js": "2.0.7", 12 | "pako": "^2.1", 13 | "fflate": "*", 14 | "xss": "1.0.14" 15 | }, 16 | "devDependencies": { 17 | "esbuild": "*", 18 | "browserify": "*", 19 | "uglify-js": "*", 20 | "full-icu": "*", 21 | "icu4c-data": "*", 22 | "jsdoc-to-markdown": "*", 23 | "mocha": "*" 24 | }, 25 | "scripts": { 26 | "test": "NODE_ICU_DATA=./node_modules/icu4c-data/ && mocha --timeout 0 tests", 27 | "build": "rm -rf build; mkdir build; esbuild src/main.js --bundle --global-name=daikon --platform=node --outfile=build/daikon.js; esbuild src/main.js --bundle --global-name=daikon --platform=node --minify --outfile=build/daikon-min.js", 28 | "build-old": "rm -rf build; mkdir build; browserify --standalone daikon src/main.js -o build/daikon.js; uglifyjs build/daikon.js -o build/daikon-min.js", 29 | "release": "rm release/current/*.js; cp build/*.js release/current/.", 30 | "doc": "rm -rf build; mkdir build; ./node_modules/.bin/jsdoc2md src/*.js > build/docs.md" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/rii-mango/Daikon.git" 35 | }, 36 | "keywords": [ 37 | "JavaScript", 38 | "DICOM" 39 | ], 40 | "author": "Michael Martinez", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/rii-mango/Daikon/issues" 44 | }, 45 | "homepage": "https://github.com/rii-mango/Daikon" 46 | } 47 | -------------------------------------------------------------------------------- /src/compression-utils.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | var daikon = daikon || {}; 9 | daikon.CompressionUtils = daikon.CompressionUtils || {}; 10 | 11 | 12 | /*** Static Pseudo-constants ***/ 13 | 14 | daikon.CompressionUtils.JPEG_MAGIC_NUMBER = [0xFF, 0xD8]; 15 | daikon.CompressionUtils.JPEG2000_MAGIC_NUMBER = [0xFF, 0x4F, 0xFF, 0x51]; 16 | 17 | 18 | /*** Static methods ***/ 19 | 20 | daikon.CompressionUtils.isHeaderJPEG = function (data) { 21 | if (data) { 22 | if (data.getUint8(0) !== daikon.CompressionUtils.JPEG_MAGIC_NUMBER[0]) { 23 | return false; 24 | } 25 | 26 | if (data.getUint8(1) !== daikon.CompressionUtils.JPEG_MAGIC_NUMBER[1]) { 27 | return false; 28 | } 29 | 30 | return true; 31 | } 32 | 33 | return false; 34 | }; 35 | 36 | 37 | daikon.CompressionUtils.isHeaderJPEG2000 = function (data) { 38 | var ctr; 39 | 40 | if (data) { 41 | for (ctr = 0; ctr < daikon.CompressionUtils.JPEG2000_MAGIC_NUMBER.length; ctr+=1) { 42 | if (data.getUint8(ctr) !== daikon.CompressionUtils.JPEG2000_MAGIC_NUMBER[ctr]) { 43 | return false; 44 | } 45 | } 46 | 47 | return true; 48 | } 49 | 50 | return false; 51 | }; 52 | 53 | 54 | /*** Exports ***/ 55 | 56 | var moduleType = typeof module; 57 | if ((moduleType !== 'undefined') && module.exports) { 58 | module.exports = daikon.CompressionUtils; 59 | } -------------------------------------------------------------------------------- /src/iterator.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | var daikon = daikon || {}; 9 | 10 | 11 | /*** Constructor ***/ 12 | daikon.OrderedMapIterator = daikon.OrderedMapIterator || function (orderedMap) { 13 | this.orderedMap = orderedMap; 14 | this.index = 0; 15 | }; 16 | 17 | 18 | /*** Prototype Methods ***/ 19 | 20 | daikon.OrderedMapIterator.prototype.hasNext = function() { 21 | return (this.index < this.orderedMap.orderedKeys.length); 22 | }; 23 | 24 | 25 | 26 | daikon.OrderedMapIterator.prototype.next = function() { 27 | var item = this.orderedMap.get(this.orderedMap.orderedKeys[this.index]); 28 | this.index += 1; 29 | return item; 30 | }; 31 | 32 | 33 | /*** Exports ***/ 34 | 35 | var moduleType = typeof module; 36 | if ((moduleType !== 'undefined') && module.exports) { 37 | module.exports = daikon.OrderedMapIterator; 38 | } 39 | -------------------------------------------------------------------------------- /src/license.js: -------------------------------------------------------------------------------- 1 | /*! jpg.js 2 | * @license 3 | * Copyright 2015 Mozilla Foundation 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /*! jpx.js 19 | * Copyright 2012 Mozilla Foundation 20 | * 21 | * Licensed under the Apache License, Version 2.0 (the "License"); 22 | * you may not use this file except in compliance with the License. 23 | * You may obtain a copy of the License at 24 | * 25 | * http://www.apache.org/licenses/LICENSE-2.0 26 | * 27 | * Unless required by applicable law or agreed to in writing, software 28 | * distributed under the License is distributed on an "AS IS" BASIS, 29 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 | * See the License for the specific language governing permissions and 31 | * limitations under the License. 32 | */ 33 | 34 | /*! lossless.js 35 | * Copyright (C) 2015 Michael Martinez 36 | * Changes: Added support for selection values 2-7, fixed minor bugs & 37 | * warnings, split into multiple class files, and general clean up. 38 | * 39 | * Copyright (C) 2003-2009 JNode.org 40 | * Original source: http://webuser.fh-furtwangen.de/~dersch/ 41 | * Changed License to LGPL with the friendly permission of Helmut Dersch. 42 | * 43 | * Copyright (C) Helmut Dersch 44 | * 45 | * This library is free software; you can redistribute it and/or modify it 46 | * under the terms of the GNU Lesser General Public License as published 47 | * by the Free Software Foundation; either version 2.1 of the License, or 48 | * (at your option) any later version. 49 | * 50 | * This library is distributed in the hope that it will be useful, but 51 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 52 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 53 | * License for more details. 54 | * 55 | * You should have received a copy of the GNU Lesser General Public License 56 | * along with this library; If not, write to the Free Software Foundation, Inc., 57 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 58 | */ -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ****/ 8 | 9 | /** 10 | * daikon 11 | * @type {*|{}} 12 | */ 13 | var daikon = daikon || {}; 14 | 15 | daikon.CompressionUtils = daikon.CompressionUtils || ((typeof require !== 'undefined') ? require('./compression-utils.js') : null); 16 | daikon.Dictionary = daikon.Dictionary || ((typeof require !== 'undefined') ? require('./dictionary.js') : null); 17 | daikon.Image = daikon.Image || ((typeof require !== 'undefined') ? require('./image.js') : null); 18 | daikon.OrderedMapIterator = daikon.OrderedMapIterator || ((typeof require !== 'undefined') ? require('./iterator.js') : null); 19 | daikon.OrderedMap = daikon.OrderedMap || ((typeof require !== 'undefined') ? require('./orderedmap.js') : null); 20 | daikon.Parser = daikon.Parser || ((typeof require !== 'undefined') ? require('./parser.js') : null); 21 | daikon.RLE = daikon.RLE || ((typeof require !== 'undefined') ? require('./rle.js') : null); 22 | daikon.Series = daikon.Series || ((typeof require !== 'undefined') ? require('./series.js') : null); 23 | daikon.Tag = daikon.Tag || ((typeof require !== 'undefined') ? require('./tag.js') : null); 24 | daikon.Utils = daikon.Utils || ((typeof require !== 'undefined') ? require('./utilities.js') : null); 25 | daikon.Siemens = daikon.Siemens || ((typeof require !== 'undefined') ? require('./siemens.js') : null); 26 | 27 | var jpeg = jpeg || {}; 28 | jpeg.lossless = jpeg.lossless || {}; 29 | jpeg.lossless.Decoder = ((typeof require !== 'undefined') ? require('jpeg-lossless-decoder-js') : null); 30 | 31 | var JpegDecoder = JpegDecoder || ((typeof require !== 'undefined') ? require('../lib/jpeg-baseline.js').JpegImage : null); 32 | 33 | var JpxImage = JpxImage || ((typeof require !== 'undefined') ? require('../lib/jpx.js') : null); 34 | 35 | var CharLS = CharLS || ((typeof require !== 'undefined') ? require('../lib/charLS-DynamicMemory-browser.js') : null); 36 | var JpegLSDecoder = JpegLSDecoder || ((typeof require !== 'undefined') ? require('../lib/jpeg-ls.js') : null); 37 | 38 | //use fflate not pako 39 | var pako = pako || ((typeof require !== 'undefined') ? require('pako') : null); 40 | //var fflate = fflate || ((typeof require !== 'undefined') ? require('fflate') : null); 41 | 42 | /*** Exports ***/ 43 | 44 | var moduleType = typeof module; 45 | if ((moduleType !== 'undefined') && module.exports) { 46 | module.exports = daikon; 47 | } 48 | -------------------------------------------------------------------------------- /src/orderedmap.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require */ 4 | 5 | "use strict"; 6 | 7 | // Based on: http://stackoverflow.com/questions/3549894/javascript-data-structure-for-fast-lookup-and-ordered-looping 8 | 9 | /*** Imports ***/ 10 | var daikon = daikon || {}; 11 | daikon.OrderedMapIterator = daikon.OrderedMapIterator || ((typeof require !== 'undefined') ? require('./iterator.js') : null); 12 | 13 | 14 | /*** Constructor ***/ 15 | daikon.OrderedMap = daikon.OrderedMap || function () { 16 | this.map = {}; 17 | this.orderedKeys = []; 18 | }; 19 | 20 | 21 | 22 | daikon.OrderedMap.prototype.put = function(key, value) { 23 | if (key in this.map) { // key already exists, replace value 24 | this.map[key] = value; 25 | } else { // insert new key and value 26 | this.orderedKeys.push(key); 27 | this.orderedKeys.sort(function(a, b) { return parseFloat(a) - parseFloat(b); }); 28 | this.map[key] = value; 29 | } 30 | }; 31 | 32 | 33 | 34 | daikon.OrderedMap.prototype.remove = function(key) { 35 | var index = this.orderedKeys.indexOf(key); 36 | if(index === -1) { 37 | throw new Error('key does not exist'); 38 | } 39 | 40 | this.orderedKeys.splice(index, 1); 41 | delete this.map[key]; 42 | }; 43 | 44 | 45 | 46 | daikon.OrderedMap.prototype.get = function(key) { 47 | if (key in this.map) { 48 | return this.map[key]; 49 | } 50 | 51 | return null; 52 | }; 53 | 54 | 55 | 56 | daikon.OrderedMap.prototype.iterator = function() { 57 | return new daikon.OrderedMapIterator(this); 58 | }; 59 | 60 | 61 | 62 | daikon.OrderedMap.prototype.getOrderedValues = function() { 63 | var orderedValues = [], it = this.iterator(); 64 | 65 | while (it.hasNext()) { 66 | orderedValues.push(it.next()); 67 | } 68 | 69 | return orderedValues; 70 | }; 71 | 72 | 73 | 74 | /*** Exports ***/ 75 | 76 | var moduleType = typeof module; 77 | if ((moduleType !== 'undefined') && module.exports) { 78 | module.exports = daikon.OrderedMap; 79 | } 80 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | var daikon = daikon || {}; 9 | daikon.Tag = daikon.Tag || ((typeof require !== 'undefined') ? require('./tag.js') : null); 10 | daikon.Utils = daikon.Utils || ((typeof require !== 'undefined') ? require('./utilities.js') : null); 11 | daikon.Dictionary = daikon.Dictionary || ((typeof require !== 'undefined') ? require('./dictionary.js') : null); 12 | daikon.Image = daikon.Image || ((typeof require !== 'undefined') ? require('./image.js') : null); 13 | 14 | //use fflate not pako 15 | var pako = pako || ((typeof require !== 'undefined') ? require('pako') : null); 16 | //var fflate = fflate || ((typeof require !== 'undefined') ? require('fflate') : null); 17 | 18 | /*** Constructor ***/ 19 | 20 | /** 21 | * The Parser constructor. 22 | * @property {boolean} explicit 23 | * @property {boolean} littleEndian 24 | * @type {Function} 25 | */ 26 | daikon.Parser = daikon.Parser || function () { 27 | this.littleEndian = true; 28 | this.explicit = true; 29 | this.metaFound = false; 30 | this.metaFinished = false; 31 | this.metaFinishedOffset = -1; 32 | this.needsDeflate = false; 33 | this.inflated = null; 34 | this.encapsulation = false; 35 | this.level = 0; 36 | this.error = null; 37 | }; 38 | 39 | 40 | /*** Static Fields ***/ 41 | 42 | /** 43 | * Global property to output string representation of tags as they are parsed. 44 | * @type {boolean} 45 | */ 46 | daikon.Parser.verbose = false; 47 | 48 | 49 | /*** Static Pseudo-constants ***/ 50 | 51 | daikon.Parser.MAGIC_COOKIE_OFFSET = 128; 52 | daikon.Parser.MAGIC_COOKIE = [68, 73, 67, 77]; 53 | daikon.Parser.VRS = ["AE", "AS", "AT", "CS", "DA", "DS", "DT", "FL", "FD", "IS", "LO", "LT", "OB", "OD", "OF", "OW", "PN", "SH", "SL", "SS", "ST", "TM", "UI", "UL", "UN", "US", "UT", "UC"]; 54 | daikon.Parser.DATA_VRS = ["OB", "OW", "OF", "SQ", "UT", "UN", "UC"]; 55 | daikon.Parser.RAW_DATA_VRS = ["OB", "OD", "OF", "OW", "UN"]; 56 | daikon.Parser.TRANSFER_SYNTAX_IMPLICIT_LITTLE = "1.2.840.10008.1.2"; 57 | daikon.Parser.TRANSFER_SYNTAX_EXPLICIT_LITTLE = "1.2.840.10008.1.2.1"; 58 | daikon.Parser.TRANSFER_SYNTAX_EXPLICIT_BIG = "1.2.840.10008.1.2.2"; 59 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG = "1.2.840.10008.1.2.4"; 60 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LOSSLESS = "1.2.840.10008.1.2.4.57"; 61 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LOSSLESS_SEL1 = "1.2.840.10008.1.2.4.70"; 62 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_BASELINE_8BIT = "1.2.840.10008.1.2.4.50"; 63 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_BASELINE_12BIT = "1.2.840.10008.1.2.4.51"; 64 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LS_LOSSLESS = "1.2.840.10008.1.2.4.80"; 65 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LS = "1.2.840.10008.1.2.4.81"; 66 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_2000_LOSSLESS = "1.2.840.10008.1.2.4.90"; 67 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_2000 = "1.2.840.10008.1.2.4.91"; 68 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_RLE = "1.2.840.10008.1.2.5"; 69 | daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_DEFLATE = "1.2.840.10008.1.2.1.99"; 70 | daikon.Parser.UNDEFINED_LENGTH = 0xFFFFFFFF; 71 | 72 | 73 | /*** Static Methods ***/ 74 | 75 | /** 76 | * Returns true if the DICOM magic cookie is found. 77 | * @param {DataView} data 78 | * @returns {boolean} 79 | */ 80 | daikon.Parser.isMagicCookieFound = function (data) { 81 | var offset = daikon.Parser.MAGIC_COOKIE_OFFSET, magicCookieLength = daikon.Parser.MAGIC_COOKIE.length, ctr; 82 | 83 | for (ctr = 0; ctr < magicCookieLength; ctr += 1) { 84 | if (data.getUint8(offset + ctr) !== daikon.Parser.MAGIC_COOKIE[ctr]) { 85 | return false; 86 | } 87 | } 88 | 89 | return true; 90 | }; 91 | 92 | 93 | /*** Prototype Methods ***/ 94 | 95 | /** 96 | * Parses this data and returns an image object. 97 | * @param {DataView} data 98 | * @returns {daikon.Image|null} 99 | */ 100 | daikon.Parser.prototype.parse = function (data) { 101 | var image = null, offset, tag, copyMeta, copyDeflated; 102 | 103 | try { 104 | image = new daikon.Image(); 105 | offset = this.findFirstTagOffset(data); 106 | tag = this.getNextTag(data, offset); 107 | 108 | while (tag !== null) { 109 | if (daikon.Parser.verbose) { 110 | console.log(tag.toString()); 111 | } 112 | 113 | image.putTag(tag); 114 | 115 | if (tag.isPixelData()) { 116 | break; 117 | } 118 | 119 | if (this.needsDeflate && (tag.offsetEnd >= this.metaFinishedOffset)) { 120 | this.needsDeflate = false; 121 | copyMeta = data.buffer.slice(0, tag.offsetEnd); 122 | copyDeflated = data.buffer.slice(tag.offsetEnd); 123 | this.inflated = daikon.Utils.concatArrayBuffers(copyMeta, pako.inflateRaw(copyDeflated)); 124 | //this.inflated = daikon.Utils.concatArrayBuffers(copyMeta, fflate.decompressSync(new Uint8Array(copyDeflated))); 125 | data = new DataView(this.inflated); 126 | } 127 | 128 | tag = this.getNextTag(data, tag.offsetEnd); 129 | } 130 | } catch (err) { 131 | this.error = err; 132 | } 133 | 134 | if (image !== null) { 135 | image.littleEndian = this.littleEndian; 136 | } 137 | 138 | return image; 139 | }; 140 | 141 | 142 | 143 | daikon.Parser.prototype.parseEncapsulated = function (data) { 144 | var offset = 0, tag, tags = []; 145 | 146 | this.encapsulation = true; 147 | 148 | try { 149 | tag = this.getNextTag(data, offset); 150 | 151 | while (tag !== null) { 152 | if (tag.isSublistItem()) { 153 | tags.push(tag); 154 | } 155 | 156 | if (daikon.Parser.verbose) { 157 | console.log(tag.toString()); 158 | } 159 | 160 | tag = this.getNextTag(data, tag.offsetEnd); 161 | } 162 | } catch (err) { 163 | this.error = err; 164 | 165 | } 166 | 167 | return tags; 168 | }; 169 | 170 | 171 | 172 | daikon.Parser.prototype.testForValidTag = function (data) { 173 | var offset, tag = null; 174 | 175 | try { 176 | offset = this.findFirstTagOffset(data); 177 | tag = this.getNextTag(data, offset, false); 178 | } catch (err) { 179 | this.error = err; 180 | } 181 | 182 | return tag; 183 | }; 184 | 185 | 186 | 187 | daikon.Parser.prototype.getNextTag = function (data, offset, testForTag) { 188 | var group = 0, element, value = null, offsetStart = offset, offsetValue, length = 0, little = true, vr = null, tag; 189 | 190 | if (offset >= data.byteLength) { 191 | return null; 192 | } 193 | 194 | if (this.metaFinished) { 195 | little = this.littleEndian; 196 | group = data.getUint16(offset, little); 197 | } else { 198 | group = data.getUint16(offset, true); 199 | 200 | if (((this.metaFinishedOffset !== -1) && (offset >= this.metaFinishedOffset)) || (group !== 0x0002)) { 201 | this.metaFinished = true; 202 | little = this.littleEndian; 203 | group = data.getUint16(offset, little); 204 | } else { 205 | little = true; 206 | } 207 | } 208 | 209 | if (!this.metaFound && (group === 0x0002)) { 210 | this.metaFound = true; 211 | } 212 | 213 | offset += 2; 214 | 215 | element = data.getUint16(offset, little); 216 | offset += 2; 217 | if (this.explicit || !this.metaFinished) { 218 | vr = daikon.Utils.getStringAt(data, offset, 2); 219 | 220 | if (!this.metaFound && this.metaFinished && (daikon.Parser.VRS.indexOf(vr) === -1)) { 221 | vr = daikon.Dictionary.getVR(group, element); 222 | length = data.getUint32(offset, little); 223 | offset += 4; 224 | this.explicit = false; 225 | } else { 226 | offset += 2; 227 | 228 | if (daikon.Parser.DATA_VRS.indexOf(vr) !== -1) { 229 | offset += 2; // skip two empty bytes 230 | 231 | length = data.getUint32(offset, little); 232 | offset += 4; 233 | } else { 234 | length = data.getUint16(offset, little); 235 | offset += 2; 236 | } 237 | } 238 | } else { 239 | vr = daikon.Dictionary.getVR(group, element); 240 | length = data.getUint32(offset, little); 241 | 242 | if (length === daikon.Parser.UNDEFINED_LENGTH) { 243 | vr = 'SQ'; 244 | } 245 | 246 | offset += 4; 247 | } 248 | 249 | offsetValue = offset; 250 | 251 | var isPixelData = ((group === daikon.Tag.TAG_PIXEL_DATA[0]) && (element === daikon.Tag.TAG_PIXEL_DATA[1])); 252 | /* 253 | color lookup data will be in (0028,12XX), so don't try to treat these as a sublist even though it can look like a list. Example: 254 | (0028,1201) OW 0000\ffff\ffff\0000\ffff\ffff\0000\cccc\0000\0000\1e1e\0000\0101... # 512, 1 RedPaletteColorLookupTableData 255 | (0028,1202) OW 0000\ffff\0000\ffff\8080\3333\ffff\b3b3\0000\0000\1e1e\0000\0101... # 512, 1 GreenPaletteColorLookupTableData 256 | (0028,1203) OW 0000\0000\ffff\ffff\0000\4d4d\0000\0000\0000\0000\1e1e\0000\0101... # 512, 1 BluePaletteColorLookupTableData 257 | */ 258 | var isLookupTableData = 0x0028 === group && element>= 0x1201 && element<0x1300; 259 | 260 | if ((vr === 'SQ') || (!isLookupTableData && !isPixelData && !this.encapsulation && (daikon.Parser.DATA_VRS.indexOf(vr) !== -1) && (vr !== 'UC'))) { 261 | value = this.parseSublist(data, offset, length, vr !== 'SQ'); 262 | 263 | if (length === daikon.Parser.UNDEFINED_LENGTH) { 264 | length = value[value.length - 1].offsetEnd - offset; 265 | } 266 | } else if ((length > 0) && !testForTag) { 267 | if (length === daikon.Parser.UNDEFINED_LENGTH) { 268 | if (isPixelData) { 269 | length = (data.byteLength - offset); 270 | } 271 | } 272 | 273 | value = data.buffer.slice(offset, offset + length); 274 | } 275 | 276 | offset += length; 277 | tag = new daikon.Tag(group, element, vr, value, offsetStart, offsetValue, offset, this.littleEndian, this.charset); 278 | 279 | if (tag.value) { 280 | if (tag.isTransformSyntax()) { 281 | // 传输语法已存在 282 | this.transformSyntaxAlreadyExist = true; 283 | if (tag.value[0] === daikon.Parser.TRANSFER_SYNTAX_IMPLICIT_LITTLE) { 284 | this.explicit = false; 285 | this.littleEndian = true; 286 | } else if (tag.value[0] === daikon.Parser.TRANSFER_SYNTAX_EXPLICIT_BIG) { 287 | this.explicit = true; 288 | this.littleEndian = false; 289 | } else if (tag.value[0] === daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_DEFLATE) { 290 | this.needsDeflate = true; 291 | this.explicit = true; 292 | this.littleEndian = true; 293 | } else { 294 | this.explicit = true; 295 | this.littleEndian = true; 296 | } 297 | } else if (tag.isMetaLength()) { 298 | this.metaFinishedOffset = tag.value[0] + offset; 299 | } else if (tag.isCharset()) { 300 | var charset = tag.value; 301 | if (charset.length == 2) { 302 | charset = (charset[0] || "ISO 2022 IR 6") + "\\" + charset[1]; 303 | } 304 | else if (charset.length == 1) { 305 | 306 | charset = charset[0]; 307 | } 308 | this.charset = charset; 309 | } 310 | } 311 | 312 | return tag; 313 | }; 314 | 315 | 316 | 317 | daikon.Parser.prototype.parseSublist = function (data, offset, length, raw) { 318 | var sublistItem, 319 | offsetEnd = offset + length, 320 | tags = []; 321 | 322 | this.level++; 323 | 324 | if (length === daikon.Parser.UNDEFINED_LENGTH) { 325 | sublistItem = this.parseSublistItem(data, offset, raw); 326 | 327 | while (!sublistItem.isSequenceDelim()) { 328 | tags.push(sublistItem); 329 | offset = sublistItem.offsetEnd; 330 | sublistItem = this.parseSublistItem(data, offset, raw); 331 | } 332 | 333 | tags.push(sublistItem); 334 | } else { 335 | while (offset < offsetEnd) { 336 | sublistItem = this.parseSublistItem(data, offset, raw); 337 | tags.push(sublistItem); 338 | offset = sublistItem.offsetEnd; 339 | } 340 | } 341 | 342 | this.level--; 343 | 344 | return tags; 345 | }; 346 | 347 | 348 | 349 | daikon.Parser.prototype.parseSublistItem = function (data, offset, raw) { 350 | var group, element, length, offsetEnd, tag, offsetStart = offset, value = null, offsetValue, sublistItemTag, tags = []; 351 | 352 | group = data.getUint16(offset, this.littleEndian); 353 | offset += 2; 354 | 355 | element = data.getUint16(offset, this.littleEndian); 356 | offset += 2; 357 | 358 | length = data.getUint32(offset, this.littleEndian); 359 | offset += 4; 360 | 361 | offsetValue = offset; 362 | 363 | if (length === daikon.Parser.UNDEFINED_LENGTH) { 364 | tag = this.getNextTag(data, offset); 365 | 366 | while (tag && !tag.isSublistItemDelim()) { 367 | tags.push(tag); 368 | offset = tag.offsetEnd; 369 | tag = this.getNextTag(data, offset); 370 | } 371 | 372 | tag && tags.push(tag); 373 | tag && (offset = tag.offsetEnd); 374 | } else if (raw) { 375 | value = data.buffer.slice(offset, offset + length); 376 | offset = offset + length; 377 | } else { 378 | offsetEnd = offset + length; 379 | 380 | while (offset < offsetEnd) { 381 | tag = this.getNextTag(data, offset); 382 | tags.push(tag); 383 | offset = tag.offsetEnd; 384 | } 385 | } 386 | 387 | sublistItemTag = new daikon.Tag(group, element, null, value || tags, offsetStart, offsetValue, offset, this.littleEndian); 388 | 389 | return sublistItemTag; 390 | }; 391 | 392 | 393 | 394 | daikon.Parser.prototype.findFirstTagOffset = function (data) { 395 | var offset = 0, 396 | magicCookieLength = daikon.Parser.MAGIC_COOKIE.length, 397 | searchOffsetMax = daikon.Parser.MAGIC_COOKIE_OFFSET * 5, 398 | found = false, 399 | ctr = 0, 400 | ctrIn = 0, 401 | ch = 0; 402 | 403 | if (daikon.Parser.isMagicCookieFound(data)) { 404 | offset = daikon.Parser.MAGIC_COOKIE_OFFSET + magicCookieLength; 405 | } else { 406 | for (ctr = 0; ctr < searchOffsetMax; ctr += 1) { 407 | ch = data.getUint8(ctr); 408 | if (ch === daikon.Parser.MAGIC_COOKIE[0]) { 409 | found = true; 410 | for (ctrIn = 1; ctrIn < magicCookieLength; ctrIn += 1) { 411 | if (data.getUint8(ctr + ctrIn) !== daikon.Parser.MAGIC_COOKIE[ctrIn]) { 412 | found = false; 413 | } 414 | } 415 | 416 | if (found) { 417 | offset = ctr + magicCookieLength; 418 | break; 419 | } 420 | } 421 | } 422 | } 423 | 424 | return offset; 425 | }; 426 | 427 | 428 | 429 | daikon.Parser.prototype.hasError = function () { 430 | return (this.error !== null); 431 | }; 432 | 433 | 434 | /*** Exports ***/ 435 | 436 | var moduleType = typeof module; 437 | if ((moduleType !== 'undefined') && module.exports) { 438 | module.exports = daikon.Parser; 439 | } 440 | -------------------------------------------------------------------------------- /src/rle.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | var daikon = daikon || {}; 9 | 10 | 11 | /*** Constructor ***/ 12 | 13 | /** 14 | * The RLE constructor. 15 | * @type {Function} 16 | */ 17 | daikon.RLE = daikon.RLE || function () { 18 | this.rawData = null; 19 | this.bytesRead = 0; 20 | this.bytesPut = 0; 21 | this.segElemPut = 0; 22 | this.numSegments = 0; 23 | this.segmentOffsets = []; 24 | this.littleEndian = true; 25 | this.segmentIndex = 0; 26 | this.numElements = 0; 27 | this.size = 0; 28 | this.output = null; 29 | }; 30 | 31 | 32 | /*** Static Pseudo-constants ***/ 33 | 34 | daikon.RLE.HEADER_SIZE = 64; 35 | 36 | 37 | /*** Prototype Methods ***/ 38 | 39 | /** 40 | * Decodes the RLE data. 41 | * @param {ArrayBuffer} data 42 | * @param {boolean} littleEndian 43 | * @param {number} numElements 44 | * @returns {DataView} 45 | */ 46 | daikon.RLE.prototype.decode = function (data, littleEndian, numElements) { 47 | var ctr; 48 | 49 | this.rawData = new DataView(data); 50 | this.littleEndian = littleEndian; 51 | this.numElements = numElements; 52 | 53 | this.readHeader(); 54 | this.output = new DataView(new ArrayBuffer(this.size)); 55 | 56 | for (ctr = 0; ctr < this.numSegments; ctr+=1) { 57 | this.readNextSegment(); 58 | } 59 | 60 | return this.processData(); 61 | }; 62 | 63 | 64 | daikon.RLE.prototype.processData = function () { 65 | /*jslint bitwise: true */ 66 | 67 | var ctr, temp1, temp2, temp3, value, outputProcessed, offset; 68 | 69 | if (this.numSegments === 1) { 70 | return this.output; 71 | } else if (this.numSegments === 2) { 72 | outputProcessed = new DataView(new ArrayBuffer(this.size)); 73 | 74 | for (ctr = 0; ctr < this.numElements; ctr+=1) { 75 | temp1 = (this.output.getInt8(ctr)); 76 | temp2 = (this.output.getInt8(ctr + this.numElements)); 77 | value = (((temp1 & 0xFF) << 8) | (temp2 & 0xFF)); 78 | outputProcessed.setInt16(ctr * 2, value, this.littleEndian); 79 | } 80 | 81 | return outputProcessed; 82 | } else if (this.numSegments === 3) { // rgb 83 | outputProcessed = new DataView(new ArrayBuffer(this.size)); 84 | offset = (2 * this.numElements); 85 | 86 | for (ctr = 0; ctr < this.numElements; ctr+=1) { 87 | outputProcessed.setInt8(ctr * 3, this.output.getInt8(ctr)); 88 | outputProcessed.setInt8(ctr * 3 + 1, this.output.getInt8(ctr + this.numElements)); 89 | outputProcessed.setInt8(ctr * 3 + 2, this.output.getInt8(ctr + offset)); 90 | } 91 | 92 | return outputProcessed; 93 | } else { 94 | throw new Error("RLE data with " + this.numSegments + " segments is not supported!"); 95 | } 96 | }; 97 | 98 | 99 | 100 | daikon.RLE.prototype.readHeader = function () { 101 | var ctr; 102 | 103 | this.numSegments = this.getInt32(); 104 | this.size = this.numElements * this.numSegments; 105 | 106 | for (ctr = 0; ctr < this.numSegments; ctr+=1) { 107 | this.segmentOffsets[ctr] = this.getInt32(); 108 | } 109 | 110 | this.bytesRead = daikon.RLE.HEADER_SIZE; 111 | }; 112 | 113 | 114 | 115 | daikon.RLE.prototype.hasValidInput = function () { 116 | return ((this.bytesRead < this.rawData.buffer.byteLength) && 117 | (this.bytesPut < this.size) && (this.segElemPut < this.numElements)); 118 | }; 119 | 120 | 121 | 122 | daikon.RLE.prototype.readNextSegment = function () { 123 | var code; 124 | 125 | this.bytesRead = this.segmentOffsets[this.segmentIndex]; 126 | this.segElemPut = 0; 127 | 128 | while (this.hasValidInput()) { 129 | code = this.get(); 130 | 131 | if ((code >= 0) && (code < 128)) { 132 | this.readLiteral(code); 133 | } else if ((code <= -1) && (code > -128)) { 134 | this.readEncoded(code); 135 | } else if (code === -128) { 136 | console.warn("RLE: unsupported code!"); 137 | } 138 | } 139 | 140 | this.segmentIndex+=1; 141 | }; 142 | 143 | 144 | 145 | daikon.RLE.prototype.readLiteral = function (code) { 146 | var ctr, length = (code + 1); 147 | 148 | if (this.hasValidInput()) { 149 | for (ctr = 0; ctr < length; ctr+=1) { 150 | this.put(this.get()); 151 | } 152 | } else { 153 | console.warn("RLE: insufficient data!"); 154 | } 155 | }; 156 | 157 | 158 | 159 | daikon.RLE.prototype.readEncoded = function (code) { 160 | var ctr, 161 | runLength = (1 - code), 162 | encoded = this.get(); 163 | 164 | for (ctr = 0; ctr < runLength; ctr+=1) { 165 | this.put(encoded); 166 | } 167 | }; 168 | 169 | 170 | 171 | daikon.RLE.prototype.getInt32 = function () { 172 | var value = this.rawData.getInt32(this.bytesRead, this.littleEndian); 173 | this.bytesRead += 4; 174 | return value; 175 | }; 176 | 177 | 178 | 179 | daikon.RLE.prototype.getInt16 = function () { 180 | var value = this.rawData.getInt16(this.bytesRead, this.littleEndian); 181 | this.bytesRead += 2; 182 | return value; 183 | }; 184 | 185 | 186 | 187 | daikon.RLE.prototype.get = function () { 188 | var value = this.rawData.getInt8(this.bytesRead); 189 | this.bytesRead += 1; 190 | return value; 191 | }; 192 | 193 | 194 | 195 | daikon.RLE.prototype.put = function (val) { 196 | this.output.setInt8(this.bytesPut, val); 197 | this.bytesPut += 1; 198 | this.segElemPut += 1; 199 | }; 200 | 201 | 202 | 203 | /*** Exports ***/ 204 | 205 | var moduleType = typeof module; 206 | if ((moduleType !== 'undefined') && module.exports) { 207 | module.exports = daikon.RLE; 208 | } 209 | -------------------------------------------------------------------------------- /src/series.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | var daikon = daikon || {}; 9 | daikon.Parser = daikon.Parser || ((typeof require !== 'undefined') ? require('./parser.js') : null); 10 | daikon.Image = daikon.Image || ((typeof require !== 'undefined') ? require('./image.js') : null); 11 | daikon.OrderedMap = daikon.OrderedMap || ((typeof require !== 'undefined') ? require('./orderedmap.js') : null); 12 | daikon.OrderedMapIterator = daikon.OrderedMapIterator || ((typeof require !== 'undefined') ? require('./iterator.js') : null); 13 | daikon.Utils = daikon.Utils || ((typeof require !== 'undefined') ? require('./utilities.js') : null); 14 | 15 | 16 | /*** Constructor ***/ 17 | 18 | /** 19 | * The Series constructor. 20 | * @property {daikon.Image[]} images 21 | * @type {Function} 22 | */ 23 | daikon.Series = daikon.Series || function () { 24 | this.images = []; 25 | this.imagesOriginalOrder = null; 26 | this.isMosaic = false; 27 | this.isElscint = false; 28 | this.isCompressed = false; 29 | this.numberOfFrames = 0; 30 | this.numberOfFramesInFile = 0; 31 | this.isMultiFrame = false; 32 | this.isMultiFrameVolume = false; 33 | this.isMultiFrameTimeseries = false; 34 | this.isImplicitTimeseries = false; 35 | this.sliceSense = false; 36 | this.sliceDir = daikon.Image.SLICE_DIRECTION_UNKNOWN; 37 | this.error = null; 38 | }; 39 | 40 | 41 | /*** Static fields ***/ 42 | daikon.Series.parserError = null; 43 | 44 | /** 45 | * True to keep original order of images, ignoring metadata-based ordering. 46 | * @type {boolean} 47 | */ 48 | daikon.Series.useExplicitOrdering = false; 49 | 50 | /** 51 | * A hint to software to use this explicit distance (mm) between slices (see daikon.Series.useExplicitOrdering) 52 | * @type {number} 53 | */ 54 | daikon.Series.useExplicitSpacing = 0; 55 | 56 | 57 | /*** Static Methods ***/ 58 | 59 | /** 60 | * Parses the DICOM header and return an image object. 61 | * @param {DataView} data 62 | * @returns {daikon.Image|null} 63 | */ 64 | daikon.Series.parseImage = function (data) { 65 | var parser, image; 66 | 67 | parser = new daikon.Parser(); 68 | image = parser.parse(data); 69 | 70 | if (parser.hasError()) { 71 | daikon.Series.parserError = parser.error; 72 | return null; 73 | } 74 | 75 | if (parser.inflated) { 76 | image.inflated = parser.inflated; 77 | } 78 | 79 | return image; 80 | }; 81 | 82 | 83 | 84 | daikon.Series.getMosaicOffset = function (mosaicCols, mosaicColWidth, mosaicRowHeight, mosaicWidth, xLocVal, 85 | yLocVal, zLocVal) { 86 | var xLoc, yLoc, zLoc; 87 | 88 | xLoc = xLocVal; 89 | yLoc = yLocVal; 90 | zLoc = zLocVal; 91 | 92 | xLoc = ((zLoc % mosaicCols) * mosaicColWidth) + xLoc; 93 | yLoc = (((parseInt(zLoc / mosaicCols)) * mosaicRowHeight) + yLoc) * mosaicWidth; 94 | 95 | return (xLoc + yLoc); 96 | }; 97 | 98 | 99 | 100 | daikon.Series.orderDicoms = function (images, numFrames, sliceDir) { 101 | var hasImagePosition, hasSliceLocation, hasImageNumber, timeMap, timeIt, ctr, ctrIn, dg, ordered, 102 | imagesOrderedByTimeAndSpace; 103 | 104 | hasImagePosition = (images[0].getImagePosition() !== null); 105 | hasSliceLocation = (images[0].getSliceLocation() !== null); 106 | hasImageNumber = (images[0].getImageNumber() !== null); 107 | 108 | timeMap = daikon.Series.orderByTime(images, numFrames, sliceDir, hasImagePosition, hasSliceLocation); 109 | timeIt = timeMap.orderedKeys; 110 | 111 | imagesOrderedByTimeAndSpace = []; 112 | 113 | for (ctr = 0; ctr < timeIt.length; ctr += 1) { 114 | dg = timeMap.get(timeIt[ctr]); 115 | 116 | if (hasImagePosition) { 117 | ordered = daikon.Series.orderByImagePosition(dg, sliceDir); 118 | } else if (hasSliceLocation) { 119 | ordered = daikon.Series.orderBySliceLocation(dg); 120 | } else if (hasImageNumber) { 121 | ordered = daikon.Series.orderByImageNumber(dg); 122 | } else { 123 | ordered = dg; 124 | } 125 | 126 | for (ctrIn = 0; ctrIn < ordered.length; ctrIn += 1) { 127 | imagesOrderedByTimeAndSpace.push(ordered[ctrIn]); 128 | } 129 | } 130 | 131 | for (ctrIn = 0; ctrIn < imagesOrderedByTimeAndSpace.length; ctrIn += 1) { 132 | imagesOrderedByTimeAndSpace[ctrIn].index = ctrIn; 133 | } 134 | 135 | return imagesOrderedByTimeAndSpace; 136 | }; 137 | 138 | 139 | 140 | daikon.Series.orderByImagePosition = function (images, sliceDir) { 141 | var dicomMap, ctr; 142 | dicomMap = new daikon.OrderedMap(); 143 | 144 | for (ctr = 0; ctr < images.length; ctr += 1) { 145 | dicomMap.put(images[ctr].getImagePositionSliceDir(sliceDir), images[ctr]); 146 | } 147 | 148 | return dicomMap.getOrderedValues(); 149 | }; 150 | 151 | 152 | 153 | daikon.Series.orderBySliceLocation = function (images) { 154 | var dicomMap, ctr; 155 | dicomMap = new daikon.OrderedMap(); 156 | 157 | for (ctr = 0; ctr < images.length; ctr += 1) { 158 | dicomMap.put(images[ctr].getSliceLocation(), images[ctr]); 159 | } 160 | 161 | return dicomMap.getOrderedValues(); 162 | }; 163 | 164 | 165 | 166 | daikon.Series.orderByImageNumber = function (images) { 167 | var dicomMap, ctr; 168 | dicomMap = new daikon.OrderedMap(); 169 | 170 | for (ctr = 0; ctr < images.length; ctr += 1) { 171 | dicomMap.put(images[ctr].getImageNumber(), images[ctr]); 172 | } 173 | 174 | return dicomMap.getOrderedValues(); 175 | }; 176 | 177 | 178 | 179 | daikon.Series.hasMatchingSlice = function (dg, image, sliceDir, doImagePos, doSliceLoc) { 180 | var matchingNum = 0, ctr, current, imagePos, sliceLoc, imageNum; 181 | 182 | if (doImagePos) { 183 | matchingNum = image.getImagePositionSliceDir(sliceDir); 184 | } else if (doSliceLoc) { 185 | matchingNum = image.getSliceLocation(); 186 | } else { 187 | matchingNum = image.getImageNumber(); 188 | } 189 | 190 | for (ctr = 0; ctr < dg.length; ctr += 1) { 191 | current = dg[ctr]; 192 | 193 | if (doImagePos) { 194 | imagePos = current.getImagePositionSliceDir(sliceDir); 195 | if (imagePos === matchingNum) { 196 | return true; 197 | } 198 | } else if (doSliceLoc) { 199 | sliceLoc = current.getSliceLocation(); 200 | if (sliceLoc === matchingNum) { 201 | return true; 202 | } 203 | } else { 204 | imageNum = current.getImageNumber(); 205 | if (imageNum === matchingNum) { 206 | return true; 207 | } 208 | } 209 | } 210 | 211 | return false; 212 | }; 213 | 214 | 215 | 216 | daikon.Series.orderByTime = function (images, numFrames, sliceDir, hasImagePosition, hasSliceLocation) { 217 | var dicomMap, hasTemporalPosition, hasTemporalNumber, ctr, image, tempPos, dg, timeBySliceMap, imageNum, 218 | sliceMarker, slice, dicomsCopy, dicomsCopyIndex, sliceIt, timeIt, dgFound, it; 219 | 220 | dicomMap = new daikon.OrderedMap(); 221 | hasTemporalPosition = (numFrames > 1) && (images[0].getTemporalPosition() !== null); 222 | hasTemporalNumber = (numFrames > 1) && (images[0].getTemporalNumber() !== null) && (images[0].getTemporalNumber() === numFrames); 223 | 224 | if (hasTemporalPosition && hasTemporalNumber) { // explicit series 225 | for (ctr = 0; ctr < images.length; ctr += 1) { 226 | image = images[ctr]; 227 | 228 | tempPos = image.getTemporalPosition(); 229 | dg = dicomMap.get(tempPos); 230 | if (!dg) { 231 | dg = []; 232 | dicomMap.put(tempPos, dg); 233 | } 234 | 235 | dg.push(image); 236 | } 237 | } else { // implicit series 238 | // order data by slice then time 239 | timeBySliceMap = new daikon.OrderedMap(); 240 | for (ctr = 0; ctr < images.length; ctr += 1) { 241 | if (images[ctr] !== null) { 242 | imageNum = images[ctr].getImageNumber(); 243 | sliceMarker = ctr; 244 | if (hasImagePosition) { 245 | sliceMarker = images[ctr].getImagePositionSliceDir(sliceDir); 246 | } else if (hasSliceLocation) { 247 | sliceMarker = images[ctr].getSliceLocation(); 248 | } 249 | 250 | slice = timeBySliceMap.get(sliceMarker); 251 | if (slice === null) { 252 | slice = new daikon.OrderedMap(); 253 | timeBySliceMap.put(sliceMarker, slice); 254 | } 255 | 256 | slice.put(ctr, images[ctr]); 257 | } 258 | } 259 | 260 | // copy into DICOM array (ordered by slice by time) 261 | dicomsCopy = []; 262 | dicomsCopyIndex = 0; 263 | sliceIt = timeBySliceMap.iterator(); 264 | while (sliceIt.hasNext()) { 265 | slice = sliceIt.next(); 266 | timeIt = slice.iterator(); 267 | while (timeIt.hasNext()) { 268 | dicomsCopy[dicomsCopyIndex] = timeIt.next(); 269 | dicomsCopyIndex += 1; 270 | } 271 | } 272 | 273 | // groups dicoms by timepoint 274 | for (ctr = 0; ctr < dicomsCopy.length; ctr += 1) { 275 | if (dicomsCopy[ctr] !== null) { 276 | dgFound = null; 277 | it = dicomMap.iterator(); 278 | while (it.hasNext()) { 279 | dg = it.next(); 280 | if (!daikon.Series.hasMatchingSlice(dg, dicomsCopy[ctr], sliceDir, hasImagePosition, hasSliceLocation)) { 281 | dgFound = dg; 282 | break; 283 | } 284 | } 285 | 286 | if (dgFound === null) { 287 | dgFound = []; 288 | dicomMap.put(dicomMap.orderedKeys.length, dgFound); 289 | } 290 | 291 | dgFound.push(dicomsCopy[ctr]); 292 | } 293 | } 294 | } 295 | 296 | return dicomMap; 297 | }; 298 | 299 | 300 | /*** Prototype Methods ***/ 301 | 302 | daikon.Series.prototype.getOrder = function () { 303 | var ctr, order = []; 304 | 305 | for (ctr = 0; ctr < this.imagesOriginalOrder.length; ctr += 1) { 306 | order[ctr] = this.imagesOriginalOrder[ctr].index; 307 | } 308 | 309 | return order; 310 | }; 311 | 312 | 313 | /** 314 | * Returns the series ID. 315 | * @returns {string} 316 | */ 317 | daikon.Series.prototype.toString = function () { 318 | return this.images[0].getSeriesId(); 319 | }; 320 | 321 | 322 | /** 323 | * Returns a nice name for the series. 324 | * @returns {string|null} 325 | */ 326 | daikon.Series.prototype.getName = function () { 327 | var des = this.images[0].getSeriesDescription(); 328 | var uid = this.images[0].getSeriesInstanceUID(); 329 | 330 | if (des !== null) { 331 | return des; 332 | } 333 | 334 | if (uid !== null) { 335 | return uid; 336 | } 337 | 338 | return null; 339 | }; 340 | 341 | 342 | /** 343 | * Adds an image to the series. 344 | * @param {daikon.Image} image 345 | */ 346 | daikon.Series.prototype.addImage = function (image) { 347 | this.images.push(image); 348 | }; 349 | 350 | 351 | /** 352 | * Returns true if the specified image is part of the series (or if no images are yet part of the series). 353 | * @param {daikon.Image} image 354 | * @returns {boolean} 355 | */ 356 | daikon.Series.prototype.matchesSeries = function (image) { 357 | if (this.images.length === 0) { 358 | return true; 359 | } 360 | 361 | return (this.images[0].getSeriesId() === image.getSeriesId()); 362 | }; 363 | 364 | 365 | /** 366 | * Orders and organizes the images in this series. 367 | */ 368 | daikon.Series.prototype.buildSeries = function () { 369 | var hasFrameTime, ctr, sliceLoc, orderedImages, sliceLocationFirst, sliceLocationLast, sliceLocDiff, 370 | sliceLocations, orientation, imagePos; 371 | 372 | this.isMosaic = this.images[0].isMosaic(); 373 | this.isElscint = this.images[0].isElscint(); 374 | this.isCompressed = this.images[0].isCompressed(); 375 | 376 | // check for multi-frame 377 | this.numberOfFrames = this.images[0].getNumberOfFrames(); 378 | this.numberOfFramesInFile = this.images[0].getNumberOfImplicitFrames(); 379 | this.isMultiFrame = (this.numberOfFrames > 1) || (this.isMosaic && (this.images[0].length > 1)); 380 | this.isMultiFrameVolume = false; 381 | this.isMultiFrameTimeseries = false; 382 | this.isImplicitTimeseries = false; 383 | 384 | if (this.isMultiFrame) { 385 | hasFrameTime = (this.images[0].getFrameTime() > 0); 386 | if (this.isMosaic) { 387 | this.isMultiFrameTimeseries = true; 388 | } else { 389 | if (hasFrameTime) { 390 | this.isMultiFrameTimeseries = true; 391 | } else if (this.numberOfFramesInFile > 1) { 392 | this.isMultiFrameTimeseries = true; 393 | this.numberOfFrames = this.images.length; 394 | } else { 395 | this.isMultiFrameVolume = true; 396 | } 397 | } 398 | } 399 | 400 | if (!this.isMosaic && (this.numberOfFrames <= 1)) { // check for implicit frame count 401 | imagePos = (this.images[0].getImagePosition() || []); 402 | sliceLoc = imagePos.toString(); 403 | this.numberOfFrames = 0; 404 | 405 | for (ctr = 0; ctr < this.images.length; ctr += 1) { 406 | imagePos = (this.images[ctr].getImagePosition() || []); 407 | 408 | if (imagePos.toString() === sliceLoc) { 409 | this.numberOfFrames += 1; 410 | } 411 | } 412 | 413 | if (this.numberOfFrames > 1) { 414 | this.isImplicitTimeseries = true; 415 | } 416 | } 417 | 418 | this.sliceDir = this.images[0].getAcquiredSliceDirection(); 419 | 420 | if (daikon.Series.useExplicitOrdering) { 421 | orderedImages = this.images.slice(); 422 | } else { 423 | orderedImages = daikon.Series.orderDicoms(this.images, this.numberOfFrames, this.sliceDir); 424 | } 425 | 426 | sliceLocationFirst = orderedImages[0].getImagePositionSliceDir(this.sliceDir); 427 | sliceLocationLast = orderedImages[orderedImages.length - 1].getImagePositionSliceDir(this.sliceDir); 428 | sliceLocDiff = sliceLocationLast - sliceLocationFirst; 429 | 430 | if (daikon.Series.useExplicitOrdering) { 431 | this.sliceSense = false; 432 | } else if (this.isMosaic) { 433 | this.sliceSense = true; 434 | } else if (this.isMultiFrame) { 435 | sliceLocations = orderedImages[0].getSliceLocationVector(); 436 | if (sliceLocations !== null) { 437 | orientation = orderedImages[0].getOrientation(); 438 | 439 | if (orientation.charAt(2) === 'Z') { 440 | this.sliceSense = (sliceLocations[0] - sliceLocations[sliceLocations.length - 1]) < 0; 441 | } else { 442 | this.sliceSense = (sliceLocations[0] - sliceLocations[sliceLocations.length - 1]) > 0; 443 | } 444 | } else { 445 | this.sliceSense = sliceLocationFirst < 0 ? false : true; // maybe??? 446 | } 447 | } else { 448 | /* 449 | * "The direction of the axes is defined fully by the patient's orientation. The x-axis is increasing to the left hand side of the patient. The 450 | * y-axis is increasing to the posterior side of the patient. The z-axis is increasing toward the head of the patient." 451 | */ 452 | if ((this.sliceDir === daikon.Image.SLICE_DIRECTION_SAGITTAL) || (this.sliceDir === daikon.Image.SLICE_DIRECTION_CORONAL)) { 453 | if (sliceLocDiff > 0) { 454 | this.sliceSense = false; 455 | } else { 456 | this.sliceSense = true; 457 | } 458 | } else { 459 | if (sliceLocDiff > 0) { 460 | this.sliceSense = true; 461 | } else { 462 | this.sliceSense = false; 463 | } 464 | } 465 | } 466 | 467 | this.imagesOriginalOrder = this.images; 468 | this.images = orderedImages; 469 | }; 470 | 471 | 472 | /** 473 | * Concatenates image data (asynchronously). 474 | * @param {object} progressMeter -- the object must have a drawProgress(percent, label) function [e.g., drawProgress(.5, "Loading...")] 475 | * @param {Function} onFinishedImageRead -- callback 476 | */ 477 | daikon.Series.prototype.concatenateImageData = function (progressMeter, onFinishedImageRead) { 478 | var buffer, data, length; 479 | 480 | if (this.isMosaic) { 481 | data = this.getMosaicData(this.images[0], this.images[0].getPixelDataBytes()); 482 | } else { 483 | data = this.images[0].getPixelDataBytes(); 484 | } 485 | 486 | length = this.validatePixelDataLength(this.images[0]); 487 | this.images[0].clearPixelData(); 488 | buffer = new Uint8Array(new ArrayBuffer(length * this.images.length)); 489 | buffer.set(new Uint8Array(data, 0, length), 0); 490 | 491 | setTimeout(daikon.Utils.bind(this, function() { this.concatenateNextImageData(buffer, length, progressMeter, 1, onFinishedImageRead)}), 0); 492 | }; 493 | 494 | 495 | 496 | daikon.Series.prototype.concatenateNextImageData = function (buffer, frameSize, progressMeter, index, 497 | onFinishedImageRead) { 498 | var data, length; 499 | 500 | if (index >= this.images.length) { 501 | if (progressMeter) { 502 | progressMeter.drawProgress(1, "Reading DICOM Images"); 503 | } 504 | 505 | onFinishedImageRead(buffer.buffer); 506 | } else { 507 | if (progressMeter) { 508 | progressMeter.drawProgress(index / this.images.length, "Reading DICOM Images"); 509 | } 510 | 511 | if (this.isMosaic) { 512 | data = this.getMosaicData(this.images[index], this.images[index].getPixelDataBytes()); 513 | } else { 514 | data = this.images[index].getPixelDataBytes(); 515 | } 516 | 517 | length = this.validatePixelDataLength(this.images[index]); 518 | this.images[index].clearPixelData(); 519 | buffer.set(new Uint8Array(data, 0, length), (frameSize * index)); 520 | 521 | setTimeout(daikon.Utils.bind(this, function() {this.concatenateNextImageData(buffer, frameSize, progressMeter, 522 | index + 1, onFinishedImageRead);}), 0); 523 | } 524 | }; 525 | 526 | 527 | 528 | daikon.Series.prototype.validatePixelDataLength = function (image) { 529 | var length = image.getPixelDataBytes().byteLength, 530 | sliceLength = image.getCols() * image.getRows(); 531 | 532 | // pixel data length should be divisible by slice size, if not, try to figure out correct pixel data length 533 | if ((length % sliceLength) === 0) { 534 | return length; 535 | } 536 | 537 | return sliceLength * image.getNumberOfFrames() * image.getNumberOfSamplesPerPixel() * (image.getBitsAllocated() / 8); 538 | }; 539 | 540 | 541 | 542 | daikon.Series.prototype.getMosaicData = function (image, data) { 543 | var mosaicWidth, mosaicHeight, mosaicRows, mosaicCols, mosaicRowHeight, mosaicColWidth, 544 | numBytes, ctrS, ctrR, ctrC, numSlices, numRows, numCols, buffer, dataTyped, offset, ctr, index = 0; 545 | 546 | numBytes = parseInt(this.images[0].getBitsAllocated() / 8); 547 | numSlices = this.images[0].getMosaicCols() * this.images[0].getMosaicRows(); 548 | numRows = parseInt(this.images[0].getRows() / this.images[0].getMosaicRows()); 549 | numCols = parseInt(this.images[0].getCols() / this.images[0].getMosaicCols()); 550 | 551 | mosaicWidth = this.images[0].getCols(); 552 | mosaicHeight = this.images[0].getRows(); 553 | mosaicRows = this.images[0].getMosaicRows(); 554 | mosaicCols = this.images[0].getMosaicCols(); 555 | mosaicRowHeight = parseInt(mosaicHeight / mosaicRows); 556 | mosaicColWidth = parseInt(mosaicWidth / mosaicCols); 557 | 558 | buffer = new Uint8Array(new ArrayBuffer(numSlices * numRows * numCols * numBytes)); 559 | dataTyped = new Uint8Array(data); 560 | 561 | for (ctrS = 0; ctrS < numSlices; ctrS += 1) { 562 | for (ctrR = 0; ctrR < numRows; ctrR += 1) { 563 | for (ctrC = 0; ctrC < numCols; ctrC += 1) { 564 | offset = daikon.Series.getMosaicOffset(mosaicCols, mosaicColWidth, mosaicRowHeight, mosaicWidth, ctrC, 565 | ctrR, ctrS); 566 | for (ctr = 0; ctr < numBytes; ctr += 1) { 567 | buffer[index] = dataTyped[(offset * numBytes) + ctr]; 568 | index += 1; 569 | } 570 | } 571 | } 572 | } 573 | 574 | return buffer.buffer; 575 | }; 576 | 577 | 578 | /*** Exports ***/ 579 | 580 | var moduleType = typeof module; 581 | if ((moduleType !== 'undefined') && module.exports) { 582 | module.exports = daikon.Series; 583 | } 584 | -------------------------------------------------------------------------------- /src/siemens.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | var daikon = daikon || {}; 9 | daikon.Utils = daikon.Utils || ((typeof require !== 'undefined') ? require('./utilities.js') : null); 10 | 11 | 12 | /*** Constructor ***/ 13 | 14 | /** 15 | * The Siemens constructor. 16 | * @params {ArrayBuffer} buffer 17 | * @type {Function} 18 | */ 19 | daikon.Siemens = daikon.Siemens || function (buffer) { 20 | this.output = ""; 21 | this.data = new DataView(buffer, 0); 22 | }; 23 | 24 | 25 | /*** Static Pseudo-constants ***/ 26 | 27 | daikon.Siemens.CSA2_MAGIC_NUMBER = [83, 86, 49, 48]; 28 | daikon.Siemens.NAME_LENGTH = 64; 29 | daikon.Siemens.ELEMENT_CSA1 = 0x1010; 30 | daikon.Siemens.ELEMENT_CSA2 = 0x1020; 31 | daikon.Siemens.GROUP_CSA = 0x029; 32 | 33 | 34 | /*** Prototype Methods ***/ 35 | 36 | /** 37 | * Reads the Siemens header. (See http://nipy.org/nibabel/dicom/siemens_csa.html) 38 | * @returns {string} 39 | */ 40 | daikon.Siemens.prototype.readHeader = function () { 41 | /*jslint bitwise: true */ 42 | 43 | var ctr, match; 44 | 45 | try { 46 | if (this.data.byteLength > daikon.Siemens.CSA2_MAGIC_NUMBER.length) { 47 | match = true; 48 | 49 | for (ctr = 0; ctr < daikon.Siemens.CSA2_MAGIC_NUMBER.length; ctr += 1) { 50 | match &= (this.data.getUint8(ctr) === daikon.Siemens.CSA2_MAGIC_NUMBER[ctr]); 51 | } 52 | 53 | if (match) { 54 | this.readHeaderAtOffset(daikon.Siemens.CSA2_MAGIC_NUMBER.length + 4); 55 | } else { 56 | this.readHeaderAtOffset(0); 57 | } 58 | } 59 | } catch (error) { 60 | console.log(error); 61 | } 62 | 63 | return this.output; 64 | }; 65 | 66 | 67 | 68 | daikon.Siemens.prototype.readHeaderAtOffset = function (offset) { 69 | var numTags, ctr; 70 | 71 | this.output += '\n'; 72 | 73 | numTags = daikon.Utils.swap32(this.data.getUint32(offset)); 74 | 75 | if ((numTags < 1) || (numTags > 128)) { 76 | return this.output; 77 | } 78 | 79 | offset += 4; 80 | 81 | offset += 4; // unused 82 | 83 | for (ctr = 0; ctr < numTags; ctr += 1) { 84 | offset = this.readTag(offset); 85 | 86 | if (offset === -1) { 87 | break; 88 | } 89 | } 90 | 91 | return this.output; 92 | }; 93 | 94 | 95 | 96 | daikon.Siemens.prototype.readTag = function (offset) { 97 | var name, ctr, numItems; 98 | 99 | name = this.readString(offset, daikon.Siemens.NAME_LENGTH); 100 | 101 | offset += daikon.Siemens.NAME_LENGTH; 102 | 103 | offset += 4; // vm 104 | 105 | offset += 4; 106 | 107 | offset += 4; // syngodt 108 | 109 | numItems = daikon.Utils.swap32(this.data.getUint32(offset)); 110 | offset += 4; 111 | 112 | offset += 4; // unused 113 | 114 | this.output += (" " + name + "="); 115 | 116 | for (ctr = 0; ctr < numItems; ctr += 1) { 117 | offset = this.readItem(offset); 118 | 119 | if (offset === -1) { 120 | break; 121 | } else if ((offset % 4) !== 0) { 122 | offset += (4 - (offset % 4)); 123 | } 124 | } 125 | 126 | this.output += ('\n'); 127 | 128 | return offset; 129 | }; 130 | 131 | 132 | 133 | daikon.Siemens.prototype.readString = function (offset, length) { 134 | var char2, ctr, str = ""; 135 | 136 | for (ctr = 0; ctr < length; ctr += 1) { 137 | char2 = this.data.getUint8(offset + ctr); 138 | 139 | if (char2 === 0) { 140 | break; 141 | } 142 | 143 | str += String.fromCharCode(char2); 144 | } 145 | 146 | return str; 147 | }; 148 | 149 | 150 | 151 | daikon.Siemens.prototype.readItem = function (offset) { 152 | var itemLength; 153 | 154 | itemLength = daikon.Utils.swap32(this.data.getUint32(offset)); 155 | 156 | if ((offset + itemLength) > this.data.buffer.length) { 157 | return -1; 158 | } 159 | 160 | offset += 16; 161 | 162 | if (itemLength > 0) { 163 | this.output += (this.readString(offset, itemLength) + " "); 164 | } 165 | 166 | return offset + itemLength; 167 | }; 168 | 169 | 170 | /** 171 | * Returns true if the specified group and element indicate this tag can be read. 172 | * @param {number} group 173 | * @param {number} element 174 | * @returns {boolean} 175 | */ 176 | daikon.Siemens.prototype.canRead = function (group, element) { 177 | return (group === daikon.Siemens.GROUP_CSA) && ((element === daikon.Siemens.ELEMENT_CSA1) || (element === daikon.Siemens.ELEMENT_CSA2)); 178 | }; 179 | 180 | 181 | /*** Exports ***/ 182 | 183 | var moduleType = typeof module; 184 | if ((moduleType !== 'undefined') && module.exports) { 185 | module.exports = daikon.Siemens; 186 | } 187 | -------------------------------------------------------------------------------- /src/tag.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require */ 4 | 5 | "use strict"; 6 | 7 | var xss = require("xss"); 8 | 9 | /*** Imports ***/ 10 | var daikon = daikon || {}; 11 | daikon.Utils = daikon.Utils || ((typeof require !== 'undefined') ? require('./utilities.js') : null); 12 | daikon.Dictionary = daikon.Dictionary || ((typeof require !== 'undefined') ? require('./dictionary.js') : null); 13 | daikon.Siemens = daikon.Siemens || ((typeof require !== 'undefined') ? require('./siemens.js') : null); 14 | 15 | 16 | /*** Constructor ***/ 17 | 18 | /** 19 | * The Tag constuctor. 20 | * @property {number} group 21 | * @property {number} element 22 | * @property {string} vr 23 | * @property {number} offsetStart 24 | * @property {number} offsetValue 25 | * @property {number} offsetEnd 26 | * @property {boolean} sublist - true if this tag is a sublist 27 | * @property {number|number[]|string|string[]|object} value 28 | * @type {Function} 29 | */ 30 | daikon.Tag = daikon.Tag || function (group, element, vr, value, offsetStart, offsetValue, offsetEnd, littleEndian, charset) { 31 | this.group = group; 32 | this.element = element; 33 | this.vr = vr; 34 | this.offsetStart = offsetStart; 35 | this.offsetValue = offsetValue; 36 | this.offsetEnd = offsetEnd; 37 | this.sublist = false; 38 | this.preformatted = false; 39 | this.id = daikon.Tag.createId(group, element); 40 | 41 | if (value instanceof Array) { 42 | this.value = value; 43 | this.sublist = true; 44 | } else if (value !== null) { 45 | var dv = new DataView(value); 46 | this.value = daikon.Tag.convertValue(vr, dv, littleEndian, charset); 47 | 48 | if ((this.value === dv) && this.isPrivateData()) { 49 | this.value = daikon.Tag.convertPrivateValue(group, element, dv); 50 | this.preformatted = (this.value !== dv); 51 | } 52 | } else { 53 | this.value = null; 54 | } 55 | }; 56 | 57 | 58 | /*** Static Pseudo-constants ***/ 59 | 60 | daikon.Tag.PRIVATE_DATA_READERS = [daikon.Siemens]; 61 | 62 | daikon.Tag.VR_AE_MAX_LENGTH = 16; 63 | daikon.Tag.VR_AS_MAX_LENGTH = 4; 64 | daikon.Tag.VR_AT_MAX_LENGTH = 4; 65 | daikon.Tag.VR_CS_MAX_LENGTH = 16; 66 | daikon.Tag.VR_DA_MAX_LENGTH = 8; 67 | daikon.Tag.VR_DS_MAX_LENGTH = 16; 68 | daikon.Tag.VR_DT_MAX_LENGTH = 26; 69 | daikon.Tag.VR_FL_MAX_LENGTH = 4; 70 | daikon.Tag.VR_FD_MAX_LENGTH = 8; 71 | daikon.Tag.VR_IS_MAX_LENGTH = 12; 72 | daikon.Tag.VR_LO_MAX_LENGTH = 64; 73 | daikon.Tag.VR_LT_MAX_LENGTH = 10240; 74 | daikon.Tag.VR_OB_MAX_LENGTH = -1; 75 | daikon.Tag.VR_OD_MAX_LENGTH = -1; 76 | daikon.Tag.VR_OF_MAX_LENGTH = -1; 77 | daikon.Tag.VR_OW_MAX_LENGTH = -1; 78 | daikon.Tag.VR_PN_MAX_LENGTH = 64 * 5; 79 | daikon.Tag.VR_SH_MAX_LENGTH = 16; 80 | daikon.Tag.VR_SL_MAX_LENGTH = 4; 81 | daikon.Tag.VR_SS_MAX_LENGTH = 2; 82 | daikon.Tag.VR_ST_MAX_LENGTH = 1024; 83 | daikon.Tag.VR_TM_MAX_LENGTH = 16; 84 | daikon.Tag.VR_UI_MAX_LENGTH = 64; 85 | daikon.Tag.VR_UL_MAX_LENGTH = 4; 86 | daikon.Tag.VR_UN_MAX_LENGTH = -1; 87 | daikon.Tag.VR_US_MAX_LENGTH = 2; 88 | daikon.Tag.VR_UT_MAX_LENGTH = -1; 89 | daikon.Tag.VR_UC_MAX_LENGTH = -1; 90 | 91 | // metadata 92 | daikon.Tag.TAG_TRANSFER_SYNTAX = [0x0002, 0x0010]; 93 | daikon.Tag.TAG_META_LENGTH = [0x0002, 0x0000]; 94 | 95 | // sublists 96 | daikon.Tag.TAG_SUBLIST_ITEM = [0xFFFE, 0xE000]; 97 | daikon.Tag.TAG_SUBLIST_ITEM_DELIM = [0xFFFE, 0xE00D]; 98 | daikon.Tag.TAG_SUBLIST_SEQ_DELIM = [0xFFFE, 0xE0DD]; 99 | 100 | // image dims 101 | daikon.Tag.TAG_ROWS = [0x0028, 0x0010]; 102 | daikon.Tag.TAG_COLS = [0x0028, 0x0011]; 103 | daikon.Tag.TAG_ACQUISITION_MATRIX = [0x0018, 0x1310]; 104 | daikon.Tag.TAG_NUMBER_OF_FRAMES = [0x0028, 0x0008]; 105 | daikon.Tag.TAG_NUMBER_TEMPORAL_POSITIONS = [0x0020, 0x0105]; 106 | 107 | // voxel dims 108 | daikon.Tag.TAG_PIXEL_SPACING = [0x0028, 0x0030]; 109 | daikon.Tag.TAG_SLICE_THICKNESS = [0x0018, 0x0050]; 110 | daikon.Tag.TAG_SLICE_GAP = [0x0018, 0x0088]; 111 | daikon.Tag.TAG_TR = [0x0018, 0x0080]; 112 | daikon.Tag.TAG_FRAME_TIME = [0x0018, 0x1063]; 113 | 114 | // datatype 115 | daikon.Tag.TAG_BITS_ALLOCATED = [0x0028, 0x0100]; 116 | daikon.Tag.TAG_BITS_STORED = [0x0028, 0x0101]; 117 | daikon.Tag.TAG_PIXEL_REPRESENTATION = [0x0028, 0x0103]; 118 | daikon.Tag.TAG_HIGH_BIT = [0x0028, 0x0102]; 119 | daikon.Tag.TAG_PHOTOMETRIC_INTERPRETATION = [0x0028, 0x0004]; 120 | daikon.Tag.TAG_SAMPLES_PER_PIXEL = [0x0028, 0x0002]; 121 | daikon.Tag.TAG_PLANAR_CONFIG = [0x0028, 0x0006]; 122 | daikon.Tag.TAG_PALETTE_RED = [0x0028, 0x1201]; 123 | daikon.Tag.TAG_PALETTE_GREEN = [0x0028, 0x1202]; 124 | daikon.Tag.TAG_PALETTE_BLUE = [0x0028, 0x1203]; 125 | 126 | // data scale 127 | daikon.Tag.TAG_DATA_SCALE_SLOPE = [0x0028, 0x1053]; 128 | daikon.Tag.TAG_DATA_SCALE_INTERCEPT = [0x0028, 0x1052]; 129 | daikon.Tag.TAG_DATA_SCALE_ELSCINT = [0x0207, 0x101F]; 130 | daikon.Tag.TAG_PIXEL_BANDWIDTH = [0x0018, 0x0095]; 131 | 132 | // range 133 | daikon.Tag.TAG_IMAGE_MIN = [0x0028, 0x0106]; 134 | daikon.Tag.TAG_IMAGE_MAX = [0x0028, 0x0107]; 135 | daikon.Tag.TAG_WINDOW_CENTER = [0x0028, 0x1050]; 136 | daikon.Tag.TAG_WINDOW_WIDTH = [0x0028, 0x1051]; 137 | 138 | // descriptors 139 | daikon.Tag.TAG_SPECIFIC_CHAR_SET = [0x0008, 0x0005]; 140 | daikon.Tag.TAG_PATIENT_NAME = [0x0010, 0x0010]; 141 | daikon.Tag.TAG_PATIENT_ID = [0x0010, 0x0020]; 142 | daikon.Tag.TAG_STUDY_DATE = [0x0008, 0x0020]; 143 | daikon.Tag.TAG_STUDY_TIME = [0x0008, 0x0030]; 144 | daikon.Tag.TAG_STUDY_DES = [0x0008, 0x1030]; 145 | daikon.Tag.TAG_IMAGE_TYPE = [0x0008, 0x0008]; 146 | daikon.Tag.TAG_IMAGE_COMMENTS = [0x0020, 0x4000]; 147 | daikon.Tag.TAG_SEQUENCE_NAME = [0x0018, 0x0024]; 148 | daikon.Tag.TAG_MODALITY = [0x0008, 0x0060]; 149 | 150 | // session ID 151 | daikon.Tag.TAG_FRAME_OF_REF_UID = [0x0020, 0x0052]; 152 | 153 | // study ID 154 | daikon.Tag.TAG_STUDY_UID = [0x0020, 0x000D]; 155 | 156 | // volume ID 157 | daikon.Tag.TAG_SERIES_DESCRIPTION = [0x0008, 0x103E]; 158 | daikon.Tag.TAG_SERIES_INSTANCE_UID = [0x0020, 0x000E]; 159 | daikon.Tag.TAG_SERIES_NUMBER = [0x0020, 0x0011]; 160 | daikon.Tag.TAG_ECHO_NUMBER = [0x0018, 0x0086]; 161 | daikon.Tag.TAG_TEMPORAL_POSITION = [0x0020, 0x0100]; 162 | 163 | // slice ID 164 | daikon.Tag.TAG_IMAGE_NUM = [0x0020, 0x0013]; 165 | daikon.Tag.TAG_SLICE_LOCATION = [0x0020, 0x1041]; 166 | 167 | // orientation 168 | daikon.Tag.TAG_IMAGE_ORIENTATION = [0x0020, 0x0037]; 169 | daikon.Tag.TAG_IMAGE_POSITION = [0x0020, 0x0032]; 170 | daikon.Tag.TAG_SLICE_LOCATION_VECTOR = [0x0018, 0x2005]; 171 | 172 | // LUT shape 173 | daikon.Tag.TAG_LUT_SHAPE = [0x2050, 0x0020]; 174 | 175 | // pixel data 176 | daikon.Tag.TAG_PIXEL_DATA = [0x7FE0, 0x0010]; 177 | 178 | 179 | /*** Static methods ***/ 180 | 181 | /** 182 | * Create an ID string based on the specified group and element 183 | * @param {number} group 184 | * @param {number} element 185 | * @returns {string} 186 | */ 187 | daikon.Tag.createId = function (group, element) { 188 | var groupStr = daikon.Utils.dec2hex(group), 189 | elemStr = daikon.Utils.dec2hex(element); 190 | return groupStr + elemStr; 191 | }; 192 | 193 | 194 | 195 | daikon.Tag.getUnsignedInteger16 = function (rawData, littleEndian) { 196 | var data, mul, ctr; 197 | 198 | mul = rawData.byteLength / 2; 199 | data = []; 200 | for (ctr = 0; ctr < mul; ctr += 1) { 201 | data[ctr] = rawData.getUint16(ctr * 2, littleEndian); 202 | } 203 | 204 | return data; 205 | }; 206 | 207 | 208 | 209 | daikon.Tag.getSignedInteger16 = function (rawData, littleEndian) { 210 | var data, mul, ctr; 211 | 212 | mul = rawData.byteLength / 2; 213 | data = []; 214 | for (ctr = 0; ctr < mul; ctr += 1) { 215 | data[ctr] = rawData.getInt16(ctr * 2, littleEndian); 216 | } 217 | 218 | return data; 219 | }; 220 | 221 | 222 | 223 | daikon.Tag.getFloat32 = function (rawData, littleEndian) { 224 | var data, mul, ctr; 225 | 226 | mul = rawData.byteLength / 4; 227 | data = []; 228 | for (ctr = 0; ctr < mul; ctr += 1) { 229 | data[ctr] = rawData.getFloat32(ctr * 4, littleEndian); 230 | } 231 | 232 | return data; 233 | }; 234 | 235 | 236 | 237 | daikon.Tag.getSignedInteger32 = function (rawData, littleEndian) { 238 | var data, mul, ctr; 239 | 240 | mul = rawData.byteLength / 4; 241 | data = []; 242 | for (ctr = 0; ctr < mul; ctr += 1) { 243 | data[ctr] = rawData.getInt32(ctr * 4, littleEndian); 244 | } 245 | 246 | return data; 247 | }; 248 | 249 | 250 | 251 | daikon.Tag.getUnsignedInteger32 = function (rawData, littleEndian) { 252 | var data, mul, ctr; 253 | 254 | mul = rawData.byteLength / 4; 255 | data = []; 256 | for (ctr = 0; ctr < mul; ctr += 1) { 257 | data[ctr] = rawData.getUint32(ctr * 4, littleEndian); 258 | } 259 | 260 | return data; 261 | }; 262 | 263 | 264 | 265 | daikon.Tag.getFloat64 = function (rawData, littleEndian) { 266 | var data, mul, ctr; 267 | 268 | if (rawData.byteLength < 8) { 269 | return 0; 270 | } 271 | 272 | mul = rawData.byteLength / 8; 273 | data = []; 274 | for (ctr = 0; ctr < mul; ctr += 1) { 275 | data[ctr] = rawData.getFloat64(ctr * 8, littleEndian); 276 | } 277 | 278 | return data; 279 | }; 280 | 281 | 282 | 283 | daikon.Tag.getDoubleElscint = function (rawData) { 284 | var data = [], reordered = [], ctr; 285 | 286 | for (ctr = 0; ctr < 8; ctr += 1) { 287 | data[ctr] = rawData.getUint8(ctr); 288 | } 289 | 290 | reordered[0] = data[3]; 291 | reordered[1] = data[2]; 292 | reordered[2] = data[1]; 293 | reordered[3] = data[0]; 294 | reordered[4] = data[7]; 295 | reordered[5] = data[6]; 296 | reordered[6] = data[5]; 297 | reordered[7] = data[4]; 298 | 299 | data = [daikon.Utils.bytesToDouble(reordered)]; 300 | 301 | return data; 302 | }; 303 | 304 | 305 | 306 | daikon.Tag.getFixedLengthStringValue = function (rawData, maxLength, charset, vr) { 307 | var data, mul, ctr; 308 | 309 | mul = Math.floor(rawData.byteLength / maxLength); 310 | data = []; 311 | for (ctr = 0; ctr < mul; ctr += 1) { 312 | data[ctr] = daikon.Utils.getStringAt(rawData, ctr * maxLength, maxLength, charset, vr); 313 | } 314 | 315 | return data; 316 | }; 317 | 318 | 319 | 320 | daikon.Tag.getStringValue = function (rawData, charset, vr) { 321 | var data = daikon.Utils.getStringAt(rawData, 0, rawData.byteLength, charset, vr).split('\\'), ctr; 322 | 323 | for (ctr = 0; ctr < data.length; ctr += 1) { 324 | data[ctr] = daikon.Utils.trim(data[ctr]); 325 | } 326 | 327 | return data; 328 | }; 329 | 330 | 331 | 332 | daikon.Tag.getDateStringValue = function (rawData) { 333 | var dotFormat = (daikon.Tag.getSingleStringValue(rawData)[0].indexOf('.') !== -1), 334 | stringData = daikon.Tag.getFixedLengthStringValue(rawData, dotFormat ? 10 : daikon.Tag.VR_DA_MAX_LENGTH), 335 | parts = null, 336 | data = [], 337 | ctr; 338 | 339 | for (ctr = 0; ctr < stringData.length; ctr += 1) { 340 | if (dotFormat) { 341 | parts = stringData[ctr].split('.'); 342 | if (parts.length === 3) { 343 | data[ctr] = new Date(daikon.Utils.safeParseInt(parts[0]), 344 | daikon.Utils.safeParseInt(parts[1]) - 1, 345 | daikon.Utils.safeParseInt(parts[2])); 346 | } else { 347 | data[ctr] = new Date(); 348 | } 349 | } else if (stringData[ctr].length === 8) { 350 | data[ctr] = new Date(daikon.Utils.safeParseInt(stringData[ctr].substring(0, 4)), 351 | daikon.Utils.safeParseInt(stringData[ctr].substring(4, 6)) - 1, 352 | daikon.Utils.safeParseInt(stringData[ctr].substring(6, 8))); 353 | } else { 354 | data[ctr] = Date.parse(stringData[ctr]); 355 | } 356 | 357 | if (!daikon.Utils.isValidDate(data[ctr])) { 358 | data[ctr] = stringData[ctr]; 359 | } 360 | } 361 | 362 | return data; 363 | }; 364 | 365 | 366 | 367 | daikon.Tag.getDateTimeStringValue = function (rawData) { 368 | var stringData = daikon.Tag.getStringValue(rawData), 369 | data = [], 370 | ctr, 371 | year = null, 372 | month = null, 373 | date = null, 374 | hours = null, 375 | minutes = null, 376 | seconds = null; 377 | 378 | for (ctr = 0; ctr < stringData.length; ctr += 1) { 379 | if (stringData[ctr].length >= 4) { 380 | year = parseInt(stringData[ctr].substring(0, 4), 10); // required 381 | 382 | if (stringData[ctr].length >= 6) { 383 | month = daikon.Utils.safeParseInt(stringData[ctr].substring(4, 6)) - 1; 384 | } 385 | 386 | if (stringData[ctr].length >= 8) { 387 | date = daikon.Utils.safeParseInt(stringData[ctr].substring(6, 8)); 388 | } 389 | 390 | if (stringData[ctr].length >= 10) { 391 | hours = daikon.Utils.safeParseInt(stringData[ctr].substring(8, 10)); 392 | } 393 | 394 | if (stringData[ctr].length >= 12) { 395 | minutes = daikon.Utils.safeParseInt(stringData[ctr].substring(10, 12)); 396 | } 397 | 398 | if (stringData[ctr].length >= 14) { 399 | seconds = daikon.Utils.safeParseInt(stringData[ctr].substring(12, 14)); 400 | } 401 | 402 | data[ctr] = new Date(year, month, date, hours, minutes, seconds); 403 | } else { 404 | data[ctr] = Date.parse(stringData[ctr]); 405 | } 406 | 407 | if (!daikon.Utils.isValidDate(data[ctr])) { 408 | data[ctr] = stringData[ctr]; 409 | } 410 | } 411 | 412 | return data; 413 | }; 414 | 415 | 416 | 417 | daikon.Tag.getTimeStringValue = function (rawData, ms) { 418 | var stringData = daikon.Tag.getStringValue(rawData), 419 | data = []; 420 | 421 | if (ms) { 422 | var parts = null, 423 | ctr, 424 | hours = 0, 425 | minutes = 0, 426 | seconds = 0; 427 | 428 | for (ctr = 0; ctr < stringData.length; ctr += 1) { 429 | if (stringData[ctr].indexOf(':') !== -1) { 430 | parts = stringData[ctr].split(':'); 431 | hours = daikon.Utils.safeParseInt(parts[0]); 432 | 433 | if (parts.length > 1) { 434 | minutes = daikon.Utils.safeParseInt(parts[1]); 435 | } 436 | 437 | if (parts.length > 2) { 438 | seconds = daikon.Utils.safeParseFloat(parts[2]); 439 | } 440 | } else { 441 | if (stringData[ctr].length >= 2) { 442 | hours = daikon.Utils.safeParseInt(stringData[ctr].substring(0, 2)); 443 | } 444 | 445 | if (stringData[ctr].length >= 4) { 446 | minutes = daikon.Utils.safeParseInt(stringData[ctr].substring(2, 4)); 447 | } 448 | 449 | if (stringData[ctr].length >= 6) { 450 | seconds = daikon.Utils.safeParseFloat(stringData[ctr].substring(4)); 451 | } 452 | } 453 | 454 | data[ctr] = Math.round((hours * 60 * 60 * 1000) + (minutes * 60 * 1000) + (seconds * 1000)); 455 | } 456 | 457 | return data; 458 | } 459 | 460 | 461 | return stringData; 462 | }; 463 | 464 | 465 | 466 | daikon.Tag.getDoubleStringValue = function (rawData) { 467 | var stringData = daikon.Tag.getStringValue(rawData), 468 | data = [], 469 | ctr; 470 | 471 | for (ctr = 0; ctr < stringData.length; ctr += 1) { 472 | data[ctr] = parseFloat(stringData[ctr]); 473 | } 474 | 475 | return data; 476 | }; 477 | 478 | 479 | 480 | daikon.Tag.getIntegerStringValue = function (rawData) { 481 | var stringData = daikon.Tag.getStringValue(rawData), 482 | data = [], 483 | ctr; 484 | 485 | for (ctr = 0; ctr < stringData.length; ctr += 1) { 486 | data[ctr] = parseInt(stringData[ctr], 10); 487 | } 488 | 489 | return data; 490 | }; 491 | 492 | 493 | 494 | daikon.Tag.getSingleStringValue = function (rawData, maxLength, charset, vr) { 495 | var len = rawData.byteLength; 496 | if (maxLength) { 497 | len = Math.min(rawData.byteLength, maxLength); 498 | } 499 | return [daikon.Utils.trim(daikon.Utils.getStringAt(rawData, 0, len, charset, vr))]; 500 | }; 501 | 502 | 503 | 504 | daikon.Tag.getPersonNameStringValue = function (rawData, charset, vr) { 505 | var stringData = daikon.Tag.getStringValue(rawData, charset, vr), 506 | data = [], 507 | ctr; 508 | 509 | for (ctr = 0; ctr < stringData.length; ctr += 1) { 510 | data[ctr] = stringData[ctr].replace('^', ' '); 511 | } 512 | 513 | return data; 514 | }; 515 | 516 | 517 | 518 | daikon.Tag.convertPrivateValue = function (group, element, rawData) { 519 | var ctr, privReader; 520 | 521 | for (ctr = 0; ctr < daikon.Tag.PRIVATE_DATA_READERS.length; ctr += 1) { 522 | privReader = new daikon.Tag.PRIVATE_DATA_READERS[ctr](rawData.buffer); 523 | if (privReader.canRead(group, element)) { 524 | return privReader.readHeader(); 525 | } 526 | } 527 | 528 | return rawData; 529 | }; 530 | 531 | 532 | 533 | daikon.Tag.convertValue = function (vr, rawData, littleEndian, charset) { 534 | var data = null; 535 | // http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html 536 | if (vr === 'AE') { 537 | data = daikon.Tag.getSingleStringValue(rawData, daikon.Tag.VR_AE_MAX_LENGTH); 538 | } else if (vr === 'AS') { 539 | data = daikon.Tag.getFixedLengthStringValue(rawData, daikon.Tag.VR_AS_MAX_LENGTH); 540 | } else if (vr === 'AT') { 541 | data = daikon.Tag.getUnsignedInteger16(rawData, littleEndian); 542 | } else if (vr === 'CS') { 543 | data = daikon.Tag.getStringValue(rawData); 544 | } else if (vr === 'DA') { 545 | data = daikon.Tag.getDateStringValue(rawData); 546 | } else if (vr === 'DS') { 547 | data = daikon.Tag.getDoubleStringValue(rawData); 548 | } else if (vr === 'DT') { 549 | data = daikon.Tag.getDateTimeStringValue(rawData); 550 | } else if (vr === 'FL') { 551 | data = daikon.Tag.getFloat32(rawData, littleEndian); 552 | } else if (vr === 'FD') { 553 | data = daikon.Tag.getFloat64(rawData, littleEndian); 554 | } else if (vr === 'FE') { // special Elscint double (see dictionary) 555 | data = daikon.Tag.getDoubleElscint(rawData, littleEndian); 556 | } else if (vr === 'IS') { 557 | data = daikon.Tag.getIntegerStringValue(rawData); 558 | } else if (vr === 'LO') { 559 | data = daikon.Tag.getStringValue(rawData, charset, vr); 560 | } else if (vr === 'LT') { 561 | data = daikon.Tag.getSingleStringValue(rawData, daikon.Tag.VR_AT_MAX_LENGTH, charset, vr); 562 | } else if (vr === 'OB') { 563 | data = rawData; 564 | } else if (vr === 'OD') { 565 | data = rawData; 566 | } else if (vr === 'OF') { 567 | data = rawData; 568 | } else if (vr === 'OW') { 569 | data = rawData; 570 | } else if (vr === 'PN') { 571 | data = daikon.Tag.getPersonNameStringValue(rawData, charset, vr); 572 | } else if (vr === 'SH') { 573 | data = daikon.Tag.getStringValue(rawData, charset, vr); 574 | } else if (vr === 'SL') { 575 | data = daikon.Tag.getSignedInteger32(rawData, littleEndian); 576 | } else if (vr === 'SQ') { 577 | data = null; 578 | } else if (vr === 'SS') { 579 | data = daikon.Tag.getSignedInteger16(rawData, littleEndian); 580 | } else if (vr === 'ST') { 581 | data = daikon.Tag.getSingleStringValue(rawData, daikon.Tag.VR_ST_MAX_LENGTH, charset, vr); 582 | } else if (vr === 'TM') { 583 | data = daikon.Tag.getTimeStringValue(rawData); 584 | } else if (vr === 'UI') { 585 | data = daikon.Tag.getStringValue(rawData); 586 | } else if (vr === 'UL') { 587 | data = daikon.Tag.getUnsignedInteger32(rawData, littleEndian); 588 | } else if (vr === 'UN') { 589 | data = rawData; 590 | } else if (vr === 'US') { 591 | data = daikon.Tag.getUnsignedInteger16(rawData, littleEndian); 592 | } else if (vr === 'UT') { 593 | data = daikon.Tag.getSingleStringValue(rawData, Number.MAX_SAFE_INTEGER, charset, vr); 594 | } else if (vr === 'UC') { 595 | data = daikon.Tag.getStringValue(rawData); 596 | } 597 | 598 | return data; 599 | }; 600 | 601 | 602 | /*** Prototype Methods ***/ 603 | 604 | /** 605 | * Returns a string representation of this tag. 606 | * @param {number} [level] - the indentation level 607 | * @param {boolean} [html] 608 | * @returns {string} 609 | */ 610 | daikon.Tag.prototype.toString = function (level, html) { 611 | var valueStr = '', 612 | ctr, 613 | groupStr = daikon.Utils.dec2hex(this.group), 614 | elemStr = daikon.Utils.dec2hex(this.element), 615 | tagStr = '(' + groupStr + ',' + elemStr + ')', 616 | des = '', 617 | padding; 618 | 619 | if (level === undefined) { 620 | level = 0; 621 | } 622 | 623 | padding = ""; 624 | for (ctr = 0; ctr < level; ctr += 1) { 625 | if (html) { 626 | padding += " "; 627 | } else { 628 | padding += " "; 629 | } 630 | } 631 | 632 | if (this.sublist) { 633 | for (ctr = 0; ctr < this.value.length; ctr += 1) { 634 | valueStr += ('\n' + (this.value[ctr].toString(level + 1, html))); 635 | } 636 | } else if (this.vr === 'SQ') { 637 | valueStr = ''; 638 | } else if (this.isPixelData()) { 639 | valueStr = ''; 640 | } else if (!this.value) { 641 | valueStr = ''; 642 | } else { 643 | if (html && this.preformatted) { 644 | valueStr = "[
"+this.value +"]"; 645 | } else { 646 | valueStr = '[' + this.value + ']'; 647 | } 648 | } 649 | 650 | if (this.isSublistItem()) { 651 | tagStr = "Sequence Item"; 652 | } else if (this.isSublistItemDelim()) { 653 | tagStr = "Sequence Item Delimiter"; 654 | } else if (this.isSequenceDelim()) { 655 | tagStr = "Sequence Delimiter"; 656 | } else if (this.isPixelData()) { 657 | tagStr = "Pixel Data"; 658 | } else { 659 | des = daikon.Utils.convertCamcelCaseToTitleCase(daikon.Dictionary.getDescription(this.group, this.element)); 660 | } 661 | 662 | // filter for xss 663 | valueStr = xss(valueStr); 664 | 665 | if (html) { 666 | return padding + "" + tagStr + " " + des + ' ' + valueStr; 667 | } else { 668 | return padding + ' ' + tagStr + ' ' + des + ' ' + valueStr; 669 | } 670 | }; 671 | 672 | 673 | /** 674 | * Returns an HTML string representation of this tag. 675 | * @param {number} level - the indentation level 676 | * @returns {string} 677 | */ 678 | daikon.Tag.prototype.toHTMLString = function (level) { 679 | return this.toString(level, true); 680 | }; 681 | 682 | 683 | /** 684 | * Returns true if this is the transform syntax tag. 685 | * @returns {boolean} 686 | */ 687 | daikon.Tag.prototype.isTransformSyntax = function () { 688 | return (this.group === daikon.Tag.TAG_TRANSFER_SYNTAX[0]) && (this.element === daikon.Tag.TAG_TRANSFER_SYNTAX[1]); 689 | }; 690 | 691 | 692 | /** 693 | * Returns true if this is the char set tag. 694 | * @returns {boolean} 695 | */ 696 | daikon.Tag.prototype.isCharset = function () { 697 | return (this.group === daikon.Tag.TAG_SPECIFIC_CHAR_SET[0]) && (this.element === daikon.Tag.TAG_SPECIFIC_CHAR_SET[1]); 698 | }; 699 | 700 | 701 | /** 702 | * Returns true if this is the pixel data tag. 703 | * @returns {boolean} 704 | */ 705 | daikon.Tag.prototype.isPixelData = function () { 706 | return (this.group === daikon.Tag.TAG_PIXEL_DATA[0]) && (this.element === daikon.Tag.TAG_PIXEL_DATA[1]); 707 | }; 708 | 709 | 710 | /** 711 | * Returns true if this tag contains private data. 712 | * @returns {boolean} 713 | */ 714 | daikon.Tag.prototype.isPrivateData = function () { 715 | /*jslint bitwise: true */ 716 | return ((this.group & 1) === 1); 717 | }; 718 | 719 | 720 | /** 721 | * Returns true if this tag contains private data that can be read. 722 | * @returns {boolean} 723 | */ 724 | daikon.Tag.prototype.hasInterpretedPrivateData = function () { 725 | return this.isPrivateData() && daikon.Utils.isString(this.value); 726 | }; 727 | 728 | 729 | /** 730 | * Returns true if this tag is a sublist item. 731 | * @returns {boolean} 732 | */ 733 | daikon.Tag.prototype.isSublistItem = function () { 734 | return (this.group === daikon.Tag.TAG_SUBLIST_ITEM[0]) && (this.element === daikon.Tag.TAG_SUBLIST_ITEM[1]); 735 | }; 736 | 737 | 738 | /** 739 | * Returns true if this tag is a sublist item delimiter. 740 | * @returns {boolean} 741 | */ 742 | daikon.Tag.prototype.isSublistItemDelim = function () { 743 | return (this.group === daikon.Tag.TAG_SUBLIST_ITEM_DELIM[0]) && (this.element === daikon.Tag.TAG_SUBLIST_ITEM_DELIM[1]); 744 | }; 745 | 746 | 747 | /** 748 | * Returns true if this tag is a sequence delimiter. 749 | * @returns {boolean} 750 | */ 751 | daikon.Tag.prototype.isSequenceDelim = function () { 752 | return (this.group === daikon.Tag.TAG_SUBLIST_SEQ_DELIM[0]) && (this.element === daikon.Tag.TAG_SUBLIST_SEQ_DELIM[1]); 753 | }; 754 | 755 | 756 | /** 757 | * Returns true if this is a meta length tag. 758 | * @returns {boolean} 759 | */ 760 | daikon.Tag.prototype.isMetaLength = function () { 761 | return (this.group === daikon.Tag.TAG_META_LENGTH[0]) && (this.element === daikon.Tag.TAG_META_LENGTH[1]); 762 | }; 763 | 764 | 765 | /*** Exports ***/ 766 | 767 | var moduleType = typeof module; 768 | if ((moduleType !== 'undefined') && module.exports) { 769 | module.exports = daikon.Tag; 770 | } 771 | -------------------------------------------------------------------------------- /src/utilities.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint browser: true, node: true */ 3 | /*global require, module */ 4 | 5 | "use strict"; 6 | 7 | /*** Imports ***/ 8 | //var convertBytes = require('@wearemothership/dicom-character-set').convertBytes; 9 | var daikon = daikon || {}; 10 | daikon.Utils = daikon.Utils || {}; 11 | 12 | 13 | daikon.Utils.crcTable = null; 14 | 15 | 16 | /*** Static Pseudo-constants ***/ 17 | 18 | daikon.Utils.MAX_VALUE = 9007199254740991; 19 | daikon.Utils.MIN_VALUE = -9007199254740991; 20 | 21 | 22 | 23 | /*** Static methods ***/ 24 | 25 | daikon.Utils.dec2hex = function (i) { 26 | return (i + 0x10000).toString(16).substr(-4).toUpperCase(); 27 | }; 28 | 29 | 30 | 31 | // http://stackoverflow.com/questions/966225/how-can-i-create-a-two-dimensional-array-in-javascript 32 | daikon.Utils.createArray = function (length) { 33 | var arr = new Array(length || 0), 34 | i = length; 35 | 36 | if (arguments.length > 1) { 37 | var args = Array.prototype.slice.call(arguments, 1); 38 | while(i--) arr[length-1 - i] = daikon.Utils.createArray.apply(this, args); 39 | } 40 | 41 | return arr; 42 | }; 43 | 44 | 45 | daikon.Utils.getStringAt = function (dataview, start, length, charset, vr) { 46 | var str = "", ctr, ch; 47 | 48 | for (ctr = 0; ctr < length; ctr += 1) { 49 | ch = dataview.getUint8(start + ctr); 50 | 51 | if (ch !== 0) { 52 | str += String.fromCharCode(ch); 53 | } 54 | } 55 | 56 | /* - @from wearemothership dicom-character-set 57 | var strBuff = new Uint8Array(dataview.buffer, dataview.byteOffset + start, length); 58 | var str = convertBytes(charset || "ISO 2022 IR 6", strBuff, {vr: vr} ); 59 | while (str && str.charCodeAt(str.length - 1) === 0) { 60 | str = str.slice(0,-1); 61 | } 62 | */ 63 | return str; 64 | }; 65 | 66 | 67 | 68 | daikon.Utils.trim = function (str) { 69 | return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 70 | }; 71 | 72 | 73 | 74 | daikon.Utils.stripLeadingZeros = function (str) { 75 | return str.replace(/^[0]+/g, ""); 76 | }; 77 | 78 | 79 | 80 | daikon.Utils.safeParseInt = function (str) { 81 | str = daikon.Utils.stripLeadingZeros(str); 82 | if (str.length > 0) { 83 | return parseInt(str, 10); 84 | } 85 | 86 | return 0; 87 | }; 88 | 89 | 90 | 91 | daikon.Utils.convertCamcelCaseToTitleCase = function (str) { 92 | var result = str.replace(/([A-Z][a-z])/g, " $1"); 93 | return daikon.Utils.trim(result.charAt(0).toUpperCase() + result.slice(1)); 94 | }; 95 | 96 | 97 | 98 | daikon.Utils.safeParseFloat = function (str) { 99 | str = daikon.Utils.stripLeadingZeros(str); 100 | if (str.length > 0) { 101 | return parseFloat(str); 102 | } 103 | 104 | return 0; 105 | }; 106 | 107 | 108 | // http://stackoverflow.com/questions/8361086/convert-byte-array-to-numbers-in-javascript 109 | daikon.Utils.bytesToDouble = function (data) { 110 | var sign = (data[0] & 1<<7)>>7; 111 | 112 | var exponent = (((data[0] & 127) << 4) | (data[1]&(15<<4))>>4); 113 | 114 | if(exponent == 0) return 0; 115 | if(exponent == 0x7ff) return (sign) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; 116 | 117 | var mul = Math.pow(2,exponent - 1023 - 52); 118 | var mantissa = data[7]+ 119 | data[6]*Math.pow(2,8)+ 120 | data[5]*Math.pow(2,8*2)+ 121 | data[4]*Math.pow(2,8*3)+ 122 | data[3]*Math.pow(2,8*4)+ 123 | data[2]*Math.pow(2,8*5)+ 124 | (data[1]&15)*Math.pow(2,8*6)+ 125 | Math.pow(2,52); 126 | 127 | return Math.pow(-1,sign)*mantissa*mul; 128 | }; 129 | 130 | 131 | 132 | daikon.Utils.concatArrayBuffers = function (buffer1, buffer2) { 133 | var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 134 | tmp.set(new Uint8Array(buffer1), 0); 135 | tmp.set(new Uint8Array(buffer2), buffer1.byteLength); 136 | return tmp.buffer; 137 | }; 138 | 139 | 140 | 141 | daikon.Utils.concatArrayBuffers2 = function (buffers) { 142 | var length = 0, offset = 0, ctr; 143 | 144 | for (ctr = 0; ctr < buffers.length; ctr += 1) { 145 | length += buffers[ctr].byteLength; 146 | } 147 | 148 | var tmp = new Uint8Array(length); 149 | 150 | for (ctr = 0; ctr < buffers.length; ctr += 1) { 151 | tmp.set(new Uint8Array(buffers[ctr]), offset); 152 | offset += buffers[ctr].byteLength; 153 | 154 | } 155 | 156 | return tmp.buffer; 157 | }; 158 | 159 | 160 | 161 | daikon.Utils.fillBuffer = function (array, buffer, offset, numBytes) { 162 | var ctr; 163 | 164 | if (numBytes === 1) { 165 | for (ctr = 0; ctr < array.length; ctr+=1) { 166 | buffer.setUint8(offset + ctr, array[ctr]); 167 | } 168 | } else if (numBytes === 2) { 169 | for (ctr = 0; ctr < array.length; ctr+=1) { 170 | buffer.setUint16(offset + (ctr * 2), array[ctr], true); 171 | } 172 | } 173 | }; 174 | 175 | 176 | 177 | daikon.Utils.fillBufferRGB = function (array, buffer, offset) { 178 | var r, g, b, ctr, numElements = (parseInt(array.length / 3)); 179 | 180 | for (ctr = 0; ctr < numElements; ctr+=1) { 181 | r = array[ctr * 3]; 182 | g = array[ctr * 3 + 1]; 183 | b = array[ctr * 3 + 2]; 184 | 185 | buffer.setUint8(offset + ctr, parseInt((r + b + g) / 3), true); 186 | } 187 | }; 188 | 189 | 190 | 191 | daikon.Utils.bind = function (scope, fn, args, appendArgs) { 192 | if (arguments.length === 2) { 193 | return function () { 194 | return fn.apply(scope, arguments); 195 | }; 196 | } 197 | 198 | var method = fn, 199 | slice = Array.prototype.slice; 200 | 201 | return function () { 202 | var callArgs = args || arguments; 203 | 204 | if (appendArgs === true) { 205 | callArgs = slice.call(arguments, 0); 206 | callArgs = callArgs.concat(args); 207 | } else if (typeof appendArgs === 'number') { 208 | callArgs = slice.call(arguments, 0); // copy arguments first 209 | Ext.Array.insert(callArgs, appendArgs, args); 210 | } 211 | 212 | return method.apply(scope || window, callArgs); 213 | }; 214 | }; 215 | 216 | 217 | 218 | daikon.Utils.toArrayBuffer = function (buffer) { 219 | var ab, view, i; 220 | 221 | ab = new ArrayBuffer(buffer.length); 222 | view = new Uint8Array(ab); 223 | for (i = 0; i < buffer.length; i += 1) { 224 | view[i] = buffer[i]; 225 | } 226 | return ab; 227 | }; 228 | 229 | 230 | 231 | // http://stackoverflow.com/questions/203739/why-does-instanceof-return-false-for-some-literals 232 | daikon.Utils.isString = function (s) { 233 | return typeof(s) === 'string' || s instanceof String; 234 | }; 235 | 236 | 237 | 238 | // http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript 239 | daikon.Utils.isValidDate = function(d) { 240 | if (Object.prototype.toString.call(d) === "[object Date]") { 241 | if (isNaN(d.getTime())) { 242 | return false; 243 | } else { 244 | return true; 245 | } 246 | } else { 247 | return false; 248 | } 249 | }; 250 | 251 | 252 | 253 | daikon.Utils.swap32 = function (val) { 254 | /*jslint bitwise: true */ 255 | return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) | ((val >> 8) & 0xFF00) | ((val >> 24) & 0xFF); 256 | }; 257 | 258 | 259 | 260 | daikon.Utils.swap16 = function (val) { 261 | /*jslint bitwise: true */ 262 | return ((((val & 0xFF) << 8) | ((val >> 8) & 0xFF)) << 16) >> 16; // since JS uses 32-bit when bit shifting 263 | }; 264 | 265 | 266 | // http://stackoverflow.com/questions/18638900/javascript-crc32 267 | daikon.Utils.makeCRCTable = function(){ 268 | var c; 269 | var crcTable = []; 270 | for(var n =0; n < 256; n++){ 271 | c = n; 272 | for(var k =0; k < 8; k++){ 273 | c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); 274 | } 275 | crcTable[n] = c; 276 | } 277 | return crcTable; 278 | }; 279 | 280 | daikon.Utils.crc32 = function(dataView) { 281 | var crcTable = daikon.Utils.crcTable || (daikon.Utils.crcTable = daikon.Utils.makeCRCTable()); 282 | var crc = 0 ^ (-1); 283 | 284 | for (var i = 0; i < dataView.byteLength; i++ ) { 285 | crc = (crc >>> 8) ^ crcTable[(crc ^ dataView.getUint8(i)) & 0xFF]; 286 | } 287 | 288 | return (crc ^ (-1)) >>> 0; 289 | }; 290 | 291 | 292 | 293 | daikon.Utils.createBitMask = function (numBytes, bitsStored, unsigned) { 294 | var mask = 0xFFFFFFFF; 295 | mask >>>= (((4 - numBytes) * 8) + ((numBytes * 8) - bitsStored)); 296 | 297 | if (unsigned) { 298 | if (numBytes == 1) { 299 | mask &= 0x000000FF; 300 | } else if (numBytes == 2) { 301 | mask &= 0x0000FFFF; 302 | } else if (numBytes == 4) { 303 | mask &= 0xFFFFFFFF; 304 | } else if (numBytes == 8) { 305 | mask = 0xFFFFFFFF; 306 | } 307 | } else { 308 | mask = 0xFFFFFFFF; 309 | } 310 | 311 | return mask; 312 | }; 313 | 314 | 315 | 316 | /*** Exports ***/ 317 | 318 | var moduleType = typeof module; 319 | if ((moduleType !== 'undefined') && module.exports) { 320 | module.exports = daikon.Utils; 321 | } 322 | -------------------------------------------------------------------------------- /tests/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 67 | 68 |
Select a file:
77 |